Skip to content

Commit c5b198d

Browse files
Merge pull request #142 from Pmaraveyias/retire-cert
Retire cert
2 parents 51abc35 + 6a5a459 commit c5b198d

13 files changed

Lines changed: 252 additions & 29 deletions

requirements-build.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pip==23.3.2
12
pytest==7.4.3
23
pytest-cov==4.1.0
34
safety==2.3.5

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
requests==2.31.0
22
python-dateutil==2.8.2
3-
cryptography==40.0.2
3+
cryptography==42.0.2
44
six==1.16.0
55
ruamel.yaml==0.18.5
66
pynacl==1.5.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
url="https://github.com/Venafi/vcert-python",
1616
packages=['vcert', 'vcert.parser', 'vcert.policy'],
1717
install_requires=['requests==2.31.0', 'python-dateutil==2.8.2', 'certvalidator<=0.11.1', 'six==1.16.0',
18-
'cryptography==40.0.2', 'ruamel.yaml==0.17.31', 'pynacl==1.5.0'],
18+
'cryptography==42.0.2', 'ruamel.yaml==0.17.31', 'pynacl==1.5.0'],
1919
description='Python client library for Venafi Trust Protection Platform and Venafi Cloud.',
2020
long_description=long_description,
2121
long_description_content_type="text/markdown",

tests/test_env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
CLOUD_URL = environ.get('CLOUD_URL')
3131
CLOUD_APIKEY = environ.get('CLOUD_APIKEY')
3232
CLOUD_ZONE = environ.get('CLOUD_ZONE')
33+
VAAS_ZONE_ONLY_EC = environ.get('VAAS_ZONE_ONLY_EC')
3334
CLOUD_TEAM = environ.get('CLOUD_TEAM')
3435

3536
TPP_PM_ROOT = environ.get('TPP_PM_ROOT')

tests/test_local_methods.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,10 @@ def test_parse_tpp_policy1(self):
200200
conn = TPPConnection(url="http://example.com/", user="", password="")
201201
raw_data = json.loads(POLICY_TPP1)
202202
p = conn._parse_zone_config_to_policy(raw_data)
203-
self.assertEqual(len(p.key_types), 7)
203+
self.assertEqual(len(p.key_types), 8)
204204
raw_data['Policy']['KeyPair']['KeySize']['Locked'] = True
205205
p = conn._parse_zone_config_to_policy(raw_data)
206-
self.assertEqual(len(p.key_types), 4)
206+
self.assertEqual(len(p.key_types), 5)
207207
raw_data['Policy']['KeyPair']['KeyAlgorithm']['Locked'] = True
208208
p = conn._parse_zone_config_to_policy(raw_data)
209209
self.assertEqual(len(p.key_types), 1)

tests/test_tpp_token.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from assets import TEST_KEY_ECDSA, TEST_KEY_RSA_4096, TEST_KEY_RSA_2048_ENCRYPTED
2727
from test_env import TPP_ZONE, TPP_ZONE_ECDSA, TPP_USER, TPP_PASSWORD, TPP_TOKEN_URL
2828
from test_utils import (random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse,
29-
enroll_with_zone_update, simple_enroll)
29+
enroll_with_zone_update, simple_enroll, retire_by_id, retire_by_thumbprint)
3030
from vcert import (CustomField, KeyType, RevocationRequest, CertificateRequest, IssuerHint, logger, TPPTokenConnection)
3131
from vcert.errors import ClientBadData, ServerUnexptedBehavior
3232

@@ -175,6 +175,7 @@ def test_token_revoke_normal(self):
175175
with self.assertRaises(Exception):
176176
self.tpp_conn.renew_cert(req)
177177

178+
178179
def test_token_revoke_without_disable(self):
179180
req, cert = simple_enroll(self.tpp_conn, self.tpp_zone)
180181
rev_req = RevocationRequest(req_id=req.id, disable=False)
@@ -267,3 +268,20 @@ def test_revoke_access_token(self):
267268
cn = f"{random_word(10)}.venafi.example.com"
268269
with self.assertRaises(Exception):
269270
enroll(self.tpp_conn, self.tpp_zone, cn)
271+
272+
def test_tpp_token_retire_cert_id(self):
273+
try:
274+
req, cert = simple_enroll(self.tpp_conn, self.tpp_zone)
275+
ret_data = retire_by_id(self.tpp_conn, req.id)
276+
assert ret_data['Success'] is True
277+
except Exception as err:
278+
self.fail(f"Error in tpp retire by id test: {err}")
279+
280+
def test_tpp_token_retire_cert_thumbprint(self):
281+
try:
282+
req, cert = simple_enroll(self.tpp_conn, self.tpp_zone)
283+
cert = x509.load_pem_x509_certificate(cert.cert.encode(), default_backend())
284+
ret_data = retire_by_thumbprint(self.tpp_conn, cert)
285+
assert ret_data['Success'] is True
286+
except Exception as err:
287+
self.fail(f"Error in tpp retire by thumbprint test: {err}")

tests/test_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from test_env import RANDOM_DOMAIN
3030
from vcert import CertificateRequest, FakeConnection, TPPConnection, TPPTokenConnection, CSR_ORIGIN_SERVICE
31+
from vcert.common import RetireRequest
3132

3233

3334
def random_word(length):
@@ -209,3 +210,18 @@ def renew_by_thumbprint(conn, prev_cert):
209210
print(prev_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME))
210211
assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == prev_cert.subject.get_attributes_for_oid(
211212
NameOID.COMMON_NAME)
213+
214+
215+
def retire_by_id(conn, prev_cert_id):
216+
print("trying to retire by id")
217+
ret_request = RetireRequest(req_id=prev_cert_id)
218+
retire_data = conn.retire_cert(ret_request)
219+
return retire_data
220+
221+
222+
def retire_by_thumbprint(conn, prev_cert):
223+
print("Trying to retire by thumbprint")
224+
thumbprint = binascii.hexlify(prev_cert.fingerprint(hashes.SHA1())).decode()
225+
ret_request = RetireRequest(thumbprint=thumbprint)
226+
retire_data = conn.retire_cert(ret_request)
227+
return retire_data

tests/test_vaas.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,21 @@
2525
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
2626
from cryptography.x509.oid import NameOID
2727

28-
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL, RANDOM_DOMAIN
28+
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL, RANDOM_DOMAIN, VAAS_ZONE_ONLY_EC
2929
from test_pm import get_policy_obj, get_defaults_obj
3030
from test_utils import random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse, simple_enroll, \
3131
get_vaas_zone
3232
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger, CSR_ORIGIN_SERVICE
3333
from vcert.policy import KeyPair, DefaultKeyPair, PolicySpecification
34+
from vcert.common import RetireRequest
3435

3536
log = logger.get_child("test-vaas")
3637

3738

3839
class TestVaaSMethods(unittest.TestCase):
3940
def __init__(self, *args, **kwargs):
4041
self.cloud_zone = CLOUD_ZONE
42+
self.vaas_zone_ec = VAAS_ZONE_ONLY_EC
4143
self.cloud_conn = CloudConnection(token=CLOUD_APIKEY, url=CLOUD_URL)
4244
super(TestVaaSMethods, self).__init__(*args, **kwargs)
4345

@@ -170,29 +172,14 @@ def test_cloud_enroll_service_generated_csr(self):
170172
log.info(f"PKCS12 created successfully for certificate with CN: {cn}")
171173

172174
def test_enroll_ec_key_certificate(self):
173-
policy = get_policy_obj()
174-
kp = KeyPair(
175-
key_types=['EC'],
176-
elliptic_curves=['P521', 'P384'],
177-
reuse_allowed=False)
178-
policy.key_pair = kp
175+
zone = self.vaas_zone_ec
179176

180-
defaults = get_defaults_obj()
181-
defaults.key_pair = DefaultKeyPair(
182-
key_type='EC',
183-
elliptic_curve='P521')
184-
185-
policy_spec = PolicySpecification()
186-
policy_spec.policy = policy
187-
policy_spec.defaults = defaults
188-
189-
zone = get_vaas_zone()
190-
191-
self.cloud_conn.set_policy(zone, policy_spec)
192177
password = 'FooBarPass123'
178+
random_name = f"{random_word(10)}.vfidev.com"
193179

194180
request = CertificateRequest(
195-
common_name=f"{random_word(10)}.venafi.example",
181+
common_name=random_name,
182+
san_dns=[random_name],
196183
key_type=KeyType(
197184
key_type="ec",
198185
option="P384"
@@ -214,3 +201,15 @@ def test_enroll_ec_key_certificate(self):
214201
if p_key:
215202
self.assertIsInstance(p_key, EllipticCurvePrivateKey, "returned private key is not of type Elliptic Curve")
216203
self.assertEqual(p_key.curve.key_size, 384, f"Private Key expected curve: 384. Got: {p_key.curve.key_size}")
204+
205+
def test_cloud_retire_by_thumbprint(self):
206+
try:
207+
req, cert = simple_enroll(self.cloud_conn, self.cloud_zone)
208+
cert = x509.load_pem_x509_certificate(cert.cert.encode(), default_backend())
209+
fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())).decode()
210+
time.sleep(1)
211+
ret_request = RetireRequest(thumbprint=fingerprint)
212+
ret_data = self.cloud_conn.retire_cert(ret_request)
213+
assert ret_data is True
214+
except Exception as e:
215+
log.error(msg=f"Error retiring certificate by thumbprint: {e.message}")

vcert/common.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def get_ip_address():
109109

110110
class KeyType:
111111
ALLOWED_SIZES = [2048, 3072, 4096, 8192]
112-
ALLOWED_CURVES = ["p256", "p384", "p521"]
112+
ALLOWED_CURVES = ["p256", "p384", "p521", "ed25519"]
113113
RSA = 'rsa'
114114
ECDSA = 'ec'
115115

@@ -125,7 +125,7 @@ def __init__(self, key_type, option):
125125
raise BadData
126126
elif self.key_type == KeyType.ECDSA:
127127
option = {"secp521r1": "p521", "secp384r1": "p384", "secp256r1": "p256", "p256": "p256", "p384": "p384",
128-
"p521": "p521"}[option.lower().strip()]
128+
"p521": "p521", "ed25519": "ed25519"}[option.lower().strip()]
129129
if option not in KeyType.ALLOWED_CURVES:
130130
log.error(f"unknown curve: {option}, should be one of {KeyType.ALLOWED_CURVES}")
131131
raise BadData
@@ -593,6 +593,20 @@ def __init__(self, req_id=None, thumbprint=None, reason=RevocationReasons.NoRea
593593
self.disable = disable
594594

595595

596+
class RetireRequest:
597+
def __init__(self, req_id=None, thumbprint=None, guid=None, description=None):
598+
"""
599+
:param req_id:
600+
:param thumbprint:
601+
:param guid:
602+
:param description:
603+
"""
604+
self.id = req_id
605+
self.thumbprint = thumbprint
606+
self.guid = guid
607+
self.description = description
608+
609+
596610
class Authentication:
597611
def __init__(self, user=None, password=None, access_token=None, refresh_token=None, api_key=None, state=None,
598612
token_expires=None, client_id=CLIENT_ID, scope=SCOPE_CM):

vcert/connection_cloud.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def __init__(self):
8585
CERTIFICATE_STATUS = CERTIFICATE_REQUESTS + "/{}"
8686
CERTIFICATE_RETRIEVE = API_BASE_PATH + "certificates/{}/contents"
8787
CERTIFICATE_SEARCH = API_BASE_PATH + "certificatesearch"
88+
CERTIFICATE_RETIRE = API_BASE_PATH + "certificates/retirement"
8889
APPLICATIONS = API_BASE_PATH + "applications"
8990
APP_BY_ID = APPLICATIONS + "/{}"
9091
CERTIFICATE_TEMPLATE_BY_ID = APP_BY_ID + "/certificateissuingtemplates/{}"
@@ -477,6 +478,43 @@ def revoke_cert(self, request):
477478
# not supported in Venafi Cloud
478479
raise NotImplementedError
479480

481+
def retire_cert(self, request):
482+
cert_id = None
483+
if not request.id and not request.thumbprint:
484+
log.error("id or thumbprint must be specified for retiring certificate")
485+
raise ClientBadData
486+
487+
if request.id:
488+
cert_id = request.id
489+
490+
elif request.thumbprint:
491+
response = self.search_by_thumbprint(request.thumbprint)
492+
cert_ids = response.certificateIds
493+
if len(cert_ids) > 1:
494+
log.error(f"multiple certificates matching thumbprint found")
495+
raise VenafiError
496+
cert_id = cert_ids[0]
497+
498+
retire_data = {
499+
'certificateIds': [
500+
cert_id
501+
]
502+
}
503+
504+
status, data = self._post(URLS.CERTIFICATE_RETIRE, retire_data)
505+
if status == HTTPStatus.OK:
506+
if len(data) == 0:
507+
log.error(f"certificate retirement was not successful for {cert_id}")
508+
raise VenafiError
509+
else:
510+
return True
511+
elif status == HTTPStatus.BAD_REQUEST or status == HTTPStatus.PRECONDITION_FAILED:
512+
log.error("bad request for certificate retirement")
513+
raise ClientBadData
514+
else:
515+
log.error("unexpected status returned")
516+
raise ServerUnexptedBehavior
517+
480518
def renew_cert(self, request, reuse_key=False):
481519
cert_request_id = None
482520
if not request.id and not request.thumbprint:

0 commit comments

Comments
 (0)