Skip to content

Commit 64c9b6f

Browse files
committed
Support for EC KeyTypes in Policy Specification
Added support for Elliptic Curve Key types when requesting certificates in VaaS
1 parent e80788a commit 64c9b6f

4 files changed

Lines changed: 147 additions & 52 deletions

File tree

tests/test_pm.py

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from test_env import (TPP_TOKEN_URL, CLOUD_APIKEY, CLOUD_URL, TPP_PM_ROOT, CLOUD_ENTRUST_CA_NAME,
2121
CLOUD_DIGICERT_CA_NAME, TPP_CA_NAME, TPP_USER, TPP_PASSWORD)
22-
from test_utils import timestamp
22+
from test_utils import get_tpp_policy_name, get_vaas_zone
2323
from vcert import TPPTokenConnection, CloudConnection, Authentication, SCOPE_PM, logger, VenafiError, KeyType
2424
from vcert.parser import json_parser, yaml_parser
2525
from vcert.parser.utils import parse_policy_spec
@@ -50,7 +50,7 @@ def test_json_parsing(self):
5050
self._assert_policy_spec(ps)
5151

5252
def test_json_serialization(self):
53-
ps = PolicySpecification(policy=_get_policy_obj(), defaults=_get_defaults_obj())
53+
ps = PolicySpecification(policy=get_policy_obj(), defaults=get_defaults_obj())
5454
json_parser.serialize(ps, 'test_json_serialization.json')
5555

5656
def test_yaml_11_parsing(self):
@@ -61,7 +61,7 @@ def test_yaml_12_parsing(self):
6161
self._assert_policy_spec(ps)
6262

6363
def test_yaml_serialization(self):
64-
ps = PolicySpecification(policy=_get_policy_obj(), defaults=_get_defaults_obj())
64+
ps = PolicySpecification(policy=get_policy_obj(), defaults=get_defaults_obj())
6565
yaml_parser.serialize(ps, 'test_yaml_serialization.yaml')
6666

6767
def _assert_policy_spec(self, ps):
@@ -105,23 +105,23 @@ def test_create_policy_yaml(self):
105105
pass
106106

107107
def test_create_policy_full(self):
108-
policy = _get_policy_obj(ca_type=CA_TYPE_TPP)
108+
policy = get_policy_obj(ca_type=CA_TYPE_TPP)
109109
policy.key_pair.rsa_key_sizes = [2048]
110-
self._create_policy_tpp(policy=policy, defaults=_get_defaults_obj())
110+
self._create_policy_tpp(policy=policy, defaults=get_defaults_obj())
111111

112112
def test_create_policy_empty(self):
113113
self._create_policy_tpp()
114114

115115
def test_create_policy_no_policy(self):
116-
self._create_policy_tpp(defaults=_get_defaults_obj())
116+
self._create_policy_tpp(defaults=get_defaults_obj())
117117

118118
def test_create_policy_no_defaults(self):
119-
policy = _get_policy_obj(ca_type=CA_TYPE_TPP)
119+
policy = get_policy_obj(ca_type=CA_TYPE_TPP)
120120
policy.key_pair.rsa_key_sizes = [2048]
121121
self._create_policy_tpp(policy=policy)
122122

123123
def _create_policy_tpp(self, policy_spec=None, policy=None, defaults=None):
124-
zone = f"{TPP_PM_ROOT}\\{_get_tpp_policy_name()}"
124+
zone = f"{TPP_PM_ROOT}\\{get_tpp_policy_name()}"
125125
create_policy(self.tpp_conn, zone, policy_spec, policy, defaults)
126126

127127

@@ -143,25 +143,25 @@ def test_create_policy_yaml(self):
143143
pass
144144

145145
def test_create_policy_full(self):
146-
self._create_policy_cloud(policy=_get_policy_obj(), defaults=_get_defaults_obj())
146+
self._create_policy_cloud(policy=get_policy_obj(), defaults=get_defaults_obj())
147147

148148
def test_create_policy_empty(self):
149149
self._create_policy_cloud()
150150

151151
def test_create_policy_no_policy(self):
152-
self._create_policy_cloud(defaults=_get_defaults_obj())
152+
self._create_policy_cloud(defaults=get_defaults_obj())
153153

154154
def test_create_policy_no_defaults(self):
155-
self._create_policy_cloud(policy=_get_policy_obj())
155+
self._create_policy_cloud(policy=get_policy_obj())
156156

157157
def test_create_policy_entrust(self):
158-
self._create_policy_cloud(policy=_get_policy_obj(ca_type=CA_TYPE_ENTRUST), defaults=_get_defaults_obj())
158+
self._create_policy_cloud(policy=get_policy_obj(ca_type=CA_TYPE_ENTRUST), defaults=get_defaults_obj())
159159

160160
def test_create_policy_digicert(self):
161-
self._create_policy_cloud(policy=_get_policy_obj(ca_type=CA_TYPE_DIGICERT), defaults=_get_defaults_obj())
161+
self._create_policy_cloud(policy=get_policy_obj(ca_type=CA_TYPE_DIGICERT), defaults=get_defaults_obj())
162162

163163
def test_validate_domains(self):
164-
policy = self._create_policy_cloud(policy=_get_policy_obj())
164+
policy = self._create_policy_cloud(policy=get_policy_obj())
165165
self.assertListEqual(policy.policy.domains, POLICY_DOMAINS)
166166

167167
def test_csr_attributes_service(self):
@@ -183,15 +183,15 @@ def test_csr_attributes_not_specified(self):
183183
self.assertTrue(cit.key_generated_by_venafi_allowed, "keyGeneratedByVenafiAllowed is not True")
184184

185185
def test_ec_key_pair(self):
186-
policy = _get_policy_obj()
186+
policy = get_policy_obj()
187187
kp = KeyPair(
188188
key_types=['EC'],
189189
rsa_key_sizes=[2048, 4096],
190190
elliptic_curves=['P521', 'P384'],
191191
reuse_allowed=False)
192192
policy.key_pair = kp
193193

194-
defaults = _get_defaults_obj()
194+
defaults = get_defaults_obj()
195195
defaults.key_pair = DefaultKeyPair(
196196
key_type='EC',
197197
rsa_key_size=2048,
@@ -205,7 +205,7 @@ def test_ec_key_pair(self):
205205
self.assertIn('P384', ['P521', 'P384'], "[P384] is not in the allowed Elliptic Curves list")
206206

207207
def _create_policy_cloud(self, policy_spec=None, policy=None, defaults=None):
208-
zone = self._get_random_zone()
208+
zone = get_vaas_zone()
209209
response = create_policy(self.cloud_conn, zone, policy_spec, policy, defaults)
210210
return response
211211

@@ -215,18 +215,14 @@ def _create_csr_attributes_policy(self, service_generated_csr=None):
215215
:param bool service_generated_csr:
216216
:rtype: common.Policy
217217
"""
218-
policy = _get_policy_obj()
218+
policy = get_policy_obj()
219219
policy.key_pair.service_generated = service_generated_csr
220-
zone = self._get_random_zone()
220+
zone = get_vaas_zone()
221221
create_policy(connector=self.cloud_conn, zone=zone, policy_spec=None, policy=policy)
222222
cit = self.cloud_conn._get_template_by_id(zone)
223223

224224
return cit
225225

226-
@staticmethod
227-
def _get_random_zone():
228-
return _get_zone()
229-
230226

231227
class TestLocalMethods(unittest.TestCase):
232228
def test_exceptions_tpp(self):
@@ -588,7 +584,7 @@ def create_policy(connector, zone, policy_spec=None, policy=None, defaults=None)
588584
POLICY_DOMAINS = ['vfidev.com', 'vfidev.net', 'venafi.example']
589585

590586

591-
def _get_policy_obj(ca_type=None):
587+
def get_policy_obj(ca_type=None):
592588
policy = Policy(
593589
subject=Subject(
594590
orgs=['OSS Venafi, Inc.'],
@@ -625,7 +621,7 @@ def _get_policy_obj(ca_type=None):
625621
return policy
626622

627623

628-
def _get_defaults_obj():
624+
def get_defaults_obj():
629625
defaults = Defaults(
630626
d_subject=DefaultSubject(
631627
org='OSS Venafi, Inc.',
@@ -639,24 +635,3 @@ def _get_defaults_obj():
639635
elliptic_curve='P521'),
640636
auto_installed=False)
641637
return defaults
642-
643-
644-
def _get_app_name():
645-
name = 'vcert-python-app-{}'
646-
return name
647-
648-
649-
def _get_cit_name():
650-
cit_name = 'vcert-python-cit-{}'
651-
return cit_name
652-
653-
654-
def _get_zone():
655-
time = timestamp()
656-
zone = f"{_get_app_name().format(time)}\\{_get_cit_name().format(time)}"
657-
return zone
658-
659-
660-
def _get_tpp_policy_name():
661-
time = timestamp()
662-
return f"{_get_app_name().format(time)}"

tests/test_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ def timestamp():
3939
return datetime.today().strftime('%Y.%m.%d-%Hh%Mm%Ss')
4040

4141

42+
def _get_app_name():
43+
name = 'vcert-python-app-{}'
44+
return name
45+
46+
47+
def _get_cit_name():
48+
cit_name = 'vcert-python-cit-{}'
49+
return cit_name
50+
51+
52+
def get_vaas_zone():
53+
t = timestamp()
54+
zone = f"{_get_app_name().format(t)}\\{_get_cit_name().format(t)}"
55+
return zone
56+
57+
58+
def get_tpp_policy_name():
59+
t = timestamp()
60+
return f"{_get_app_name().format(t)}"
61+
62+
4263
def simple_enroll(conn, zone):
4364
req = CertificateRequest(common_name=f"{random_word(12)}.venafi.example.com")
4465
conn.request_cert(req, zone)

tests/test_vaas.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@
2121

2222
from cryptography import x509
2323
from cryptography.hazmat.backends import default_backend
24-
from cryptography.hazmat.primitives import hashes
24+
from cryptography.hazmat.primitives import hashes, serialization
25+
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
2526
from cryptography.x509.oid import NameOID
2627

28+
from policy import KeyPair, DefaultKeyPair, PolicySpecification
2729
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL, RANDOM_DOMAIN
28-
from test_utils import random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse, simple_enroll
30+
from test_pm import get_policy_obj, get_defaults_obj
31+
from test_utils import random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse, simple_enroll, \
32+
get_vaas_zone
2933
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger, CSR_ORIGIN_SERVICE
3034

3135
log = logger.get_child("test-vaas")
@@ -165,3 +169,49 @@ def test_cloud_enroll_service_generated_csr(self):
165169

166170
output = cert_object.as_pkcs12('FooBarPass123')
167171
log.info(f"PKCS12 created successfully for certificate with CN: {cn}")
172+
173+
def test_enroll_ec_key_certificate(self):
174+
policy = get_policy_obj()
175+
kp = KeyPair(
176+
key_types=['EC'],
177+
elliptic_curves=['P521', 'P384'],
178+
reuse_allowed=False)
179+
policy.key_pair = kp
180+
181+
defaults = get_defaults_obj()
182+
defaults.key_pair = DefaultKeyPair(
183+
key_type='EC',
184+
elliptic_curve='P521')
185+
186+
policy_spec = PolicySpecification()
187+
policy_spec.policy = policy
188+
policy_spec.defaults = defaults
189+
190+
zone = get_vaas_zone()
191+
192+
self.cloud_conn.set_policy(zone, policy_spec)
193+
password = 'FooBarPass123'
194+
195+
request = CertificateRequest(
196+
common_name=f"{random_word(10)}.venafi.example",
197+
key_type=KeyType(
198+
key_type="ec",
199+
option="P384"
200+
),
201+
csr_origin=CSR_ORIGIN_SERVICE,
202+
key_password=password
203+
)
204+
205+
self.cloud_conn.request_cert(request, zone)
206+
cert = self.cloud_conn.retrieve_cert(request)
207+
208+
p_key = None
209+
try:
210+
p_key = serialization.load_pem_private_key(data=cert.key.encode(), password=password.encode(),
211+
backend=default_backend())
212+
except Exception as e:
213+
log.error(msg=f"Error parsing Private Key: {e.message}")
214+
215+
if p_key:
216+
self.assertIsInstance(p_key, EllipticCurvePrivateKey, "returned private key is not of type Elliptic Curve")
217+
self.assertEqual(p_key.curve.key_size, 384, f"Private Key expected curve: 384. Got: {p_key.curve.key_size}")

vcert/connection_cloud.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .policy.pm_cloud import (build_policy_spec, validate_policy_spec, AccountDetails, build_cit_request, build_user,
3535
UserDetails, build_company, build_apikey, build_app_update_request, get_ca_info,
3636
CertificateAuthorityDetails, CertificateAuthorityInfo, build_account_details,
37-
build_app_create_request)
37+
build_app_create_request, supported_rsa_key_sizes, supported_elliptic_curves)
3838
from .vaas_utils import AppDetails, RecommendedSettings, EdgeEncryptionKey, zip_to_pem, value_matches_regex
3939

4040
TOKEN_HEADER_NAME = "tppl-api-key" # nosec
@@ -50,6 +50,10 @@
5050
CSR_ATTR_COUNTRY = 'country'
5151
CSR_ATTR_SANS_BY_TYPE = 'subjectAlternativeNamesByType'
5252
CSR_ATTR_SANS_DNS = 'dnsNames'
53+
CSR_ATTR_KEY_TYPE_PARAMS = 'keyTypeParameters'
54+
CSR_ATTR_KEY_TYPE = 'keyType'
55+
CSR_ATTR_KEY_LENGTH = 'keyLength'
56+
CSR_ATTR_KEY_CURVE = 'keyCurve'
5357

5458
log = get_child("connection-vaas")
5559

@@ -739,7 +743,7 @@ def _get_service_generated_csr_attr(self, request, zone):
739743
if request.organization:
740744
if ps.policy and ps.policy.subject:
741745
policy_orgs = ps.policy.subject.orgs
742-
valid = value_matches_regex(value=request.organization,pattern_list=policy_orgs)
746+
valid = value_matches_regex(value=request.organization, pattern_list=policy_orgs)
743747
if not valid:
744748
org_str = "Organization"
745749
log.error(MSG_VALUE_NOT_MATCH_POLICY.format(org_str, f"{org_str}s", request.organization,
@@ -765,7 +769,7 @@ def _get_service_generated_csr_attr(self, request, zone):
765769
log.error(MSG_VALUE_NOT_MATCH_POLICY.format(ou_str, f"{ou_str}s", request.organizational_unit,
766770
policy_ous))
767771
raise ClientBadData
768-
csr_attr_map[CSR_ATTR_ORG_UNIT] = request.organizational_unit
772+
csr_attr_map[CSR_ATTR_ORG_UNIT] = org_units
769773
elif ps.defaults and ps.defaults.subject and ps.defaults.subject.org_units:
770774
csr_attr_map[CSR_ATTR_ORG_UNIT] = ps.defaults.subject.org_units
771775

@@ -784,7 +788,7 @@ def _get_service_generated_csr_attr(self, request, zone):
784788

785789
if request.province:
786790
if ps.policy and ps.policy.subject:
787-
policy_provinces = ps.policy.subject.localities
791+
policy_provinces = ps.policy.subject.states
788792
valid = value_matches_regex(value=request.province, pattern_list=policy_provinces)
789793
if not valid:
790794
province_str = "Province"
@@ -815,6 +819,51 @@ def _get_service_generated_csr_attr(self, request, zone):
815819
}
816820
csr_attr_map[CSR_ATTR_SANS_BY_TYPE] = sans
817821

822+
if request.key_type:
823+
if ps.policy and ps.policy.key_pair:
824+
policy_kts = ps.policy.key_pair.key_types
825+
valid = value_matches_regex(value=request.key_type.key_type.upper(), pattern_list=policy_kts)
826+
if not valid:
827+
kt_str = "Key Type"
828+
log.error(MSG_VALUE_NOT_MATCH_POLICY.format(kt_str, f"{kt_str}s", request.key_type.key_type,
829+
policy_kts))
830+
raise ClientBadData
831+
req_kt_option = request.key_type.option
832+
if request.key_type.key_type.lower() == KeyType.RSA:
833+
policy_rsa_sizes = ps.policy.key_pair.rsa_key_sizes
834+
valid = value_matches_regex(value=req_kt_option, pattern_list=policy_rsa_sizes)
835+
if not valid:
836+
rsa_str = "RSA Key Size"
837+
log.error(MSG_VALUE_NOT_MATCH_POLICY.format(rsa_str, f"{rsa_str}s", req_kt_option,
838+
policy_rsa_sizes))
839+
raise ClientBadData
840+
elif request.key_type.key_type.lower() == KeyType.ECDSA:
841+
policy_ecs = ps.policy.key_pair.elliptic_curves
842+
valid = value_matches_regex(value=req_kt_option, pattern_list=policy_ecs)
843+
if not valid:
844+
ec_str = "Elliptic Curve"
845+
log.error(MSG_VALUE_NOT_MATCH_POLICY.format(ec_str, f"{ec_str}s", req_kt_option, policy_ecs))
846+
raise ClientBadData
847+
848+
kt_param = {
849+
CSR_ATTR_KEY_TYPE: request.key_type.key_type.upper()
850+
}
851+
kt_option = request.key_type.option.upper()
852+
if kt_option == KeyType.RSA:
853+
kt_param[CSR_ATTR_KEY_LENGTH] = kt_option
854+
elif request.key_type.key_type == KeyType.ECDSA:
855+
kt_param[CSR_ATTR_KEY_CURVE] = kt_option
856+
857+
csr_attr_map[CSR_ATTR_KEY_TYPE_PARAMS] = kt_param
858+
elif ps.defaults and ps.defaults.key_pair:
859+
kt_param = {
860+
CSR_ATTR_KEY_TYPE: ps.defaults.key_pair.key_type.upper()
861+
}
862+
if ps.defaults.key_pair.key_type.lower() == KeyType.RSA:
863+
kt_param[CSR_ATTR_KEY_LENGTH] = ps.defaults.key_pair.rsa_key_size
864+
elif ps.defaults.key_pair.key_type.lower() == KeyType.ECDSA:
865+
kt_param[CSR_ATTR_KEY_CURVE] = ps.defaults.key_pair.elliptic_curve
866+
818867
return csr_attr_map
819868

820869
def _get_policy(self, zone, subject_cn_to_str):

0 commit comments

Comments
 (0)