def __init__(self, module): self.changed = False self.domain_status = None self.verification_method = None self.file_location = None self.file_contents = None self.dns_location = None self.dns_contents = None self.dns_resource_type = None self.emails = None self.ov_eligible = None self.ov_days_remaining = None self.ev_eligble = None self.ev_days_remaining = None # Note that verification_method is the 'current' verification # method of the domain, we'll use module.params when requesting a new # one, in case the verification method has changed. self.verification_method = None self.ecs_client = None # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( entrust_api_user=module.params['entrust_api_user'], entrust_api_key=module.params['entrust_api_key'], entrust_api_cert=module.params['entrust_api_client_cert_path'], entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], entrust_api_specification_path=module.params['entrust_api_specification_path'] ) except SessionConfigurationException as e: module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) try: self.ecs_client.GetAppVersion() except RestOperationException as e: module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message)))
def __init__(self, module, backend): super(EntrustCertificateBackend, self).__init__(module, backend) self.trackingId = None self.notAfter = get_relative_time_option(module.params['entrust_not_after'], 'entrust_not_after', backend=self.backend) if self.csr_content is None and self.csr_path is None: raise CertificateError( 'csr_path or csr_content is required for entrust provider' ) if self.csr_content is None and not os.path.exists(self.csr_path): raise CertificateError( 'The certificate signing request file {0} does not exist'.format(self.csr_path) ) self._ensure_csr_loaded() # ECS API defaults to using the validated organization tied to the account. # We want to always force behavior of trying to use the organization provided in the CSR. # To that end we need to parse out the organization from the CSR. self.csr_org = None if self.backend == 'pyopenssl': csr_subject = self.csr.get_subject() csr_subject_components = csr_subject.get_components() for k, v in csr_subject_components: if k.upper() == 'O': # Entrust does not support multiple validated organizations in a single certificate if self.csr_org is not None: self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations " "found in Subject DN: '{0}'. ".format(csr_subject))) else: self.csr_org = v elif self.backend == 'cryptography': csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME) if len(csr_subject_orgs) == 1: self.csr_org = csr_subject_orgs[0].value elif len(csr_subject_orgs) > 1: self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations found in " "Subject DN: '{0}'. ".format(self.csr.subject))) # If no organization in the CSR, explicitly tell ECS that it should be blank in issued cert, not defaulted to # organization tied to the account. if self.csr_org is None: self.csr_org = '' try: self.ecs_client = ECSClient( entrust_api_user=self.module.params['entrust_api_user'], entrust_api_key=self.module.params['entrust_api_key'], entrust_api_cert=self.module.params['entrust_api_client_cert_path'], entrust_api_cert_key=self.module.params['entrust_api_client_cert_key_path'], entrust_api_specification_path=self.module.params['entrust_api_specification_path'] ) except SessionConfigurationException as e: module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e.message)))
def __init__(self, module): self.path = module.params['path'] self.full_chain_path = module.params['full_chain_path'] self.force = module.params['force'] self.backup = module.params['backup'] self.request_type = module.params['request_type'] self.csr = module.params['csr'] # All return values self.changed = False self.filename = None self.tracking_id = None self.cert_status = None self.serial_number = None self.cert_days = None self.cert_details = None self.backup_file = None self.backup_full_chain_file = None self.cert = None self.ecs_client = None if self.path and os.path.exists(self.path): try: self.cert = crypto_utils.load_certificate( self.path, backend='cryptography') except Exception as dummy: self.cert = None # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( entrust_api_user=module.params['entrust_api_user'], entrust_api_key=module.params['entrust_api_key'], entrust_api_cert=module.params['entrust_api_client_cert_path'], entrust_api_cert_key=module. params['entrust_api_client_cert_key_path'], entrust_api_specification_path=module. params['entrust_api_specification_path']) except SessionConfigurationException as e: module.fail_json( msg='Failed to initialize Entrust Provider: {0}'.format( to_native(e))) try: self.ecs_client.GetAppVersion() except RestOperationException as e: module.fail_json( msg= 'Please verify credential information. Received exception when testing ECS connection: {0}' .format(to_native(e.message)))
class EcsDomain(object): ''' Entrust Certificate Services domain class. ''' def __init__(self, module): self.changed = False self.domain_status = None self.verification_method = None self.file_location = None self.file_contents = None self.dns_location = None self.dns_contents = None self.dns_resource_type = None self.emails = None self.ov_eligible = None self.ov_days_remaining = None self.ev_eligble = None self.ev_days_remaining = None # Note that verification_method is the 'current' verification # method of the domain, we'll use module.params when requesting a new # one, in case the verification method has changed. self.verification_method = None self.ecs_client = None # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( entrust_api_user=module.params['entrust_api_user'], entrust_api_key=module.params['entrust_api_key'], entrust_api_cert=module.params['entrust_api_client_cert_path'], entrust_api_cert_key=module. params['entrust_api_client_cert_key_path'], entrust_api_specification_path=module. params['entrust_api_specification_path']) except SessionConfigurationException as e: module.fail_json( msg='Failed to initialize Entrust Provider: {0}'.format( to_native(e))) try: self.ecs_client.GetAppVersion() except RestOperationException as e: module.fail_json( msg= 'Please verify credential information. Received exception when testing ECS connection: {0}' .format(to_native(e.message))) def set_domain_details(self, domain_details): if domain_details.get('verificationMethod'): self.verification_method = domain_details[ 'verificationMethod'].lower() self.domain_status = domain_details['verificationStatus'] self.ov_eligible = domain_details.get('ovEligible') self.ov_days_remaining = calculate_days_remaining( domain_details.get('ovExpiry')) self.ev_eligible = domain_details.get('evEligible') self.ev_days_remaining = calculate_days_remaining( domain_details.get('evExpiry')) self.client_id = domain_details['clientId'] if self.verification_method == 'dns' and domain_details.get( 'dnsMethod'): self.dns_location = domain_details['dnsMethod']['recordDomain'] self.dns_resource_type = domain_details['dnsMethod']['recordType'] self.dns_contents = domain_details['dnsMethod']['recordValue'] elif self.verification_method == 'web_server' and domain_details.get( 'webServerMethod'): self.file_location = domain_details['webServerMethod'][ 'fileLocation'] self.file_contents = domain_details['webServerMethod'][ 'fileContents'] elif self.verification_method == 'email' and domain_details.get( 'emailMethod'): self.emails = domain_details['emailMethod'] def check(self, module): try: domain_details = self.ecs_client.GetDomain( clientId=module.params['client_id'], domain=module.params['domain_name']) self.set_domain_details(domain_details) if self.domain_status != 'APPROVED' and self.domain_status != 'INITIAL_VERIFICATION' and self.domain_status != 'RE_VERIFICATION': return False # If domain verification is in process, we want to return the random values and treat it as a valid. if self.domain_status == 'INITIAL_VERIFICATION' or self.domain_status == 'RE_VERIFICATION': # Unless the verification method has changed, in which case we need to do a reverify request. if self.verification_method != module.params[ 'verification_method']: return False if self.domain_status == 'EXPIRING': return False return True except RestOperationException as dummy: return False def request_domain(self, module): if not self.check(module): body = {} body['verificationMethod'] = module.params[ 'verification_method'].upper() if module.params['verification_method'] == 'email': emailMethod = {} if module.params['verification_email']: emailMethod['emailSource'] = 'SPECIFIED' emailMethod['email'] = module.params['verification_email'] else: emailMethod['emailSource'] = 'INCLUDE_WHOIS' body['emailMethod'] = emailMethod # Only populate domain name in body if it is not an existing domain if not self.domain_status: body['domainName'] = module.params['domain_name'] try: if not self.domain_status: self.ecs_client.AddDomain( clientId=module.params['client_id'], Body=body) else: self.ecs_client.ReverifyDomain( clientId=module.params['client_id'], domain=module.params['domain_name'], Body=body) time.sleep(5) result = self.ecs_client.GetDomain( clientId=module.params['client_id'], domain=module.params['domain_name']) # It takes a bit of time before the random values are available if module.params[ 'verification_method'] == 'dns' or module.params[ 'verification_method'] == 'web_server': for i in range(4): # Check both that random values are now available, and that they're different than were populated by previous 'check' if module.params['verification_method'] == 'dns': if result.get('dnsMethod') and result['dnsMethod'][ 'recordValue'] != self.dns_contents: break elif module.params[ 'verification_method'] == 'web_server': if result.get('webServerMethod') and result[ 'webServerMethod'][ 'fileContents'] != self.file_contents: break time.sleep(10) result = self.ecs_client.GetDomain( clientId=module.params['client_id'], domain=module.params['domain_name']) self.changed = True self.set_domain_details(result) except RestOperationException as e: module.fail_json( msg= 'Failed to request domain validation from Entrust (ECS) {0}' .format(e.message)) def dump(self): result = { 'changed': self.changed, 'client_id': self.client_id, 'domain_status': self.domain_status, } if self.verification_method: result['verification_method'] = self.verification_method if self.ov_eligible is not None: result['ov_eligible'] = self.ov_eligible if self.ov_days_remaining: result['ov_days_remaining'] = self.ov_days_remaining if self.ev_eligible is not None: result['ev_eligible'] = self.ev_eligible if self.ev_days_remaining: result['ev_days_remaining'] = self.ev_days_remaining if self.emails: result['emails'] = self.emails if self.verification_method == 'dns': result['dns_location'] = self.dns_location result['dns_contents'] = self.dns_contents result['dns_resource_type'] = self.dns_resource_type elif self.verification_method == 'web_server': result['file_location'] = self.file_location result['file_contents'] = self.file_contents elif self.verification_method == 'email': result['emails'] = self.emails return result
class EcsCertificate(object): ''' Entrust Certificate Services certificate class. ''' def __init__(self, module): self.path = module.params['path'] self.full_chain_path = module.params['full_chain_path'] self.force = module.params['force'] self.backup = module.params['backup'] self.request_type = module.params['request_type'] self.csr = module.params['csr'] # All return values self.changed = False self.filename = None self.tracking_id = None self.cert_status = None self.serial_number = None self.cert_days = None self.cert_details = None self.backup_file = None self.backup_full_chain_file = None self.cert = None self.ecs_client = None if self.path and os.path.exists(self.path): try: self.cert = load_certificate(self.path, backend='cryptography') except Exception as dummy: self.cert = None # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( entrust_api_user=module.params['entrust_api_user'], entrust_api_key=module.params['entrust_api_key'], entrust_api_cert=module.params['entrust_api_client_cert_path'], entrust_api_cert_key=module. params['entrust_api_client_cert_key_path'], entrust_api_specification_path=module. params['entrust_api_specification_path']) except SessionConfigurationException as e: module.fail_json( msg='Failed to initialize Entrust Provider: {0}'.format( to_native(e))) try: self.ecs_client.GetAppVersion() except RestOperationException as e: module.fail_json( msg= 'Please verify credential information. Received exception when testing ECS connection: {0}' .format(to_native(e.message))) # Conversion of the fields that go into the 'tracking' parameter of the request object def convert_tracking_params(self, module): body = {} tracking = {} if module.params['requester_name']: tracking['requesterName'] = module.params['requester_name'] if module.params['requester_email']: tracking['requesterEmail'] = module.params['requester_email'] if module.params['requester_phone']: tracking['requesterPhone'] = module.params['requester_phone'] if module.params['tracking_info']: tracking['trackingInfo'] = module.params['tracking_info'] if module.params['custom_fields']: # Omit custom fields from submitted dict if not present, instead of submitting them with value of 'null' # The ECS API does technically accept null without error, but it complicates debugging user escalations and is unnecessary bandwidth. custom_fields = {} for k, v in module.params['custom_fields'].items(): if v is not None: custom_fields[k] = v tracking['customFields'] = custom_fields if module.params['additional_emails']: tracking['additionalEmails'] = module.params['additional_emails'] body['tracking'] = tracking return body def convert_cert_subject_params(self, module): body = {} if module.params['subject_alt_name']: body['subjectAltName'] = module.params['subject_alt_name'] if module.params['org']: body['org'] = module.params['org'] if module.params['ou']: body['ou'] = module.params['ou'] return body def convert_general_params(self, module): body = {} if module.params['eku']: body['eku'] = module.params['eku'] if self.request_type == 'new': body['certType'] = module.params['cert_type'] body['clientId'] = module.params['client_id'] body.update( convert_module_param_to_json_bool(module, 'ctLog', 'ct_log')) body.update( convert_module_param_to_json_bool( module, 'endUserKeyStorageAgreement', 'end_user_key_storage_agreement')) return body def convert_expiry_params(self, module): body = {} if module.params['cert_lifetime']: body['certLifetime'] = module.params['cert_lifetime'] elif module.params['cert_expiry']: body['certExpiryDate'] = module.params['cert_expiry'] # If neither cerTLifetime or certExpiryDate was specified and the request type is new, default to 365 days elif self.request_type != 'reissue': gmt_now = datetime.datetime.fromtimestamp( time.mktime(time.gmtime())) expiry = gmt_now + datetime.timedelta(days=365) body['certExpiryDate'] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") return body def set_tracking_id_by_serial_number(self, module): try: # Use serial_number to identify if certificate is an Entrust Certificate # with an associated tracking ID serial_number = "{0:X}".format(self.cert.serial_number) cert_results = self.ecs_client.GetCertificates( serialNumber=serial_number).get('certificates', {}) if len(cert_results) == 1: self.tracking_id = cert_results[0].get('trackingId') except RestOperationException as dummy: # If we fail to find a cert by serial number, that's fine, we just don't set self.tracking_id return def set_cert_details(self, module): try: self.cert_details = self.ecs_client.GetCertificate( trackingId=self.tracking_id) self.cert_status = self.cert_details.get('status') self.serial_number = self.cert_details.get('serialNumber') self.cert_days = calculate_cert_days( self.cert_details.get('expiresAfter')) except RestOperationException as e: module.fail_json( 'Failed to get details of certificate with tracking_id="{0}", Error: ' .format(self.tracking_id), to_native(e.message)) def check(self, module): if self.cert: # We will only treat a certificate as valid if it is found as a managed entrust cert. # We will only set updated tracking ID based on certificate in "path" if it is managed by entrust. self.set_tracking_id_by_serial_number(module) if module.params[ 'tracking_id'] and self.tracking_id and module.params[ 'tracking_id'] != self.tracking_id: module.warn( 'tracking_id parameter of "{0}" provided, but will be ignored. Valid certificate was present in path "{1}" with ' 'tracking_id of "{2}".'.format( module.params['tracking_id'], self.path, self.tracking_id)) # If we did not end up setting tracking_id based on existing cert, get from module params if not self.tracking_id: self.tracking_id = module.params['tracking_id'] if not self.tracking_id: return False self.set_cert_details(module) if self.cert_status == 'EXPIRED' or self.cert_status == 'SUSPENDED' or self.cert_status == 'REVOKED': return False if self.cert_days < module.params['remaining_days']: return False return True def request_cert(self, module): if not self.check(module) or self.force: body = {} # Read the CSR contents if self.csr and os.path.exists(self.csr): with open(self.csr, 'r') as csr_file: body['csr'] = csr_file.read() # Check if the path is already a cert # tracking_id may be set as a parameter or by get_cert_details if an entrust cert is in 'path'. If tracking ID is null # We will be performing a reissue operation. if self.request_type != 'new' and not self.tracking_id: module.warn( 'No existing Entrust certificate found in path={0} and no tracking_id was provided, setting request_type to "new" for this task' 'run. Future playbook runs that point to the pathination file in {1} will use request_type={2}' .format(self.path, self.path, self.request_type)) self.request_type = 'new' elif self.request_type == 'new' and self.tracking_id: module.warn( 'Existing certificate being acted upon, but request_type is "new", so will be a new certificate issuance rather than a' 'reissue or renew') # Use cases where request type is new and no existing certificate, or where request type is reissue/renew and a valid # existing certificate is found, do not need warnings. body.update(self.convert_tracking_params(module)) body.update(self.convert_cert_subject_params(module)) body.update(self.convert_general_params(module)) body.update(self.convert_expiry_params(module)) if not module.check_mode: try: if self.request_type == 'validate_only': body['validateOnly'] = 'true' result = self.ecs_client.NewCertRequest(Body=body) if self.request_type == 'new': result = self.ecs_client.NewCertRequest(Body=body) elif self.request_type == 'renew': result = self.ecs_client.RenewCertRequest( trackingId=self.tracking_id, Body=body) elif self.request_type == 'reissue': result = self.ecs_client.ReissueCertRequest( trackingId=self.tracking_id, Body=body) self.tracking_id = result.get('trackingId') self.set_cert_details(module) except RestOperationException as e: module.fail_json( msg= 'Failed to request new certificate from Entrust (ECS) {0}' .format(e.message)) if self.request_type != 'validate_only': if self.backup: self.backup_file = module.backup_local(self.path) write_file( module, to_bytes(self.cert_details.get('endEntityCert'))) if self.full_chain_path and self.cert_details.get( 'chainCerts'): if self.backup: self.backup_full_chain_file = module.backup_local( self.full_chain_path) chain_string = '\n'.join( self.cert_details.get('chainCerts')) + '\n' write_file(module, to_bytes(chain_string), path=self.full_chain_path) self.changed = True # If there is no certificate present in path but a tracking ID was specified, save it to disk elif not os.path.exists(self.path) and self.tracking_id: if not module.check_mode: write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) if self.full_chain_path and self.cert_details.get( 'chainCerts'): chain_string = '\n'.join( self.cert_details.get('chainCerts')) + '\n' write_file(module, to_bytes(chain_string), path=self.full_chain_path) self.changed = True def dump(self): result = { 'changed': self.changed, 'filename': self.path, 'tracking_id': self.tracking_id, 'cert_status': self.cert_status, 'serial_number': self.serial_number, 'cert_days': self.cert_days, 'cert_details': self.cert_details, } if self.backup_file: result['backup_file'] = self.backup_file result['backup_full_chain_file'] = self.backup_full_chain_file return result
class EntrustCertificateBackend(CertificateBackend): def __init__(self, module, backend): super(EntrustCertificateBackend, self).__init__(module, backend) self.trackingId = None self.notAfter = get_relative_time_option( module.params['entrust_not_after'], 'entrust_not_after', backend=self.backend) if self.csr_content is None and self.csr_path is None: raise CertificateError( 'csr_path or csr_content is required for entrust provider') if self.csr_content is None and not os.path.exists(self.csr_path): raise CertificateError( 'The certificate signing request file {0} does not exist'. format(self.csr_path)) self._ensure_csr_loaded() # ECS API defaults to using the validated organization tied to the account. # We want to always force behavior of trying to use the organization provided in the CSR. # To that end we need to parse out the organization from the CSR. self.csr_org = None if self.backend == 'pyopenssl': csr_subject = self.csr.get_subject() csr_subject_components = csr_subject.get_components() for k, v in csr_subject_components: if k.upper() == 'O': # Entrust does not support multiple validated organizations in a single certificate if self.csr_org is not None: self.module.fail_json(msg=( "Entrust provider does not currently support multiple validated organizations. Multiple organizations " "found in Subject DN: '{0}'. ".format(csr_subject) )) else: self.csr_org = v elif self.backend == 'cryptography': csr_subject_orgs = self.csr.subject.get_attributes_for_oid( NameOID.ORGANIZATION_NAME) if len(csr_subject_orgs) == 1: self.csr_org = csr_subject_orgs[0].value elif len(csr_subject_orgs) > 1: self.module.fail_json(msg=( "Entrust provider does not currently support multiple validated organizations. Multiple organizations found in " "Subject DN: '{0}'. ".format(self.csr.subject))) # If no organization in the CSR, explicitly tell ECS that it should be blank in issued cert, not defaulted to # organization tied to the account. if self.csr_org is None: self.csr_org = '' try: self.ecs_client = ECSClient( entrust_api_user=self.module.params['entrust_api_user'], entrust_api_key=self.module.params['entrust_api_key'], entrust_api_cert=self.module. params['entrust_api_client_cert_path'], entrust_api_cert_key=self.module. params['entrust_api_client_cert_key_path'], entrust_api_specification_path=self.module. params['entrust_api_specification_path']) except SessionConfigurationException as e: module.fail_json( msg='Failed to initialize Entrust Provider: {0}'.format( to_native(e.message))) def generate_certificate(self): """(Re-)Generate certificate.""" body = {} # Read the CSR that was generated for us if self.csr_content is not None: # csr_content contains bytes body['csr'] = to_native(self.csr_content) else: with open(self.csr_path, 'r') as csr_file: body['csr'] = csr_file.read() body['certType'] = self.module.params['entrust_cert_type'] # Handle expiration (30 days if not specified) expiry = self.notAfter if not expiry: gmt_now = datetime.datetime.fromtimestamp( time.mktime(time.gmtime())) expiry = gmt_now + datetime.timedelta(days=365) expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") body['certExpiryDate'] = expiry_iso3339 body['org'] = self.csr_org body['tracking'] = { 'requesterName': self.module.params['entrust_requester_name'], 'requesterEmail': self.module.params['entrust_requester_email'], 'requesterPhone': self.module.params['entrust_requester_phone'], } try: result = self.ecs_client.NewCertRequest(Body=body) self.trackingId = result.get('trackingId') except RestOperationException as e: self.module.fail_json( msg= 'Failed to request new certificate from Entrust Certificate Services (ECS): {0}' .format(to_native(e.message))) self.cert_bytes = to_bytes(result.get('endEntityCert')) self.cert = load_certificate(path=None, content=self.cert_bytes, backend=self.backend) def get_certificate_data(self): """Return bytes for self.cert.""" return self.cert_bytes def needs_regeneration(self): parent_check = super(EntrustCertificateBackend, self).needs_regeneration() try: cert_details = self._get_cert_details() except RestOperationException as e: self.module.fail_json( msg= 'Failed to get status of existing certificate from Entrust Certificate Services (ECS): {0}.' .format(to_native(e.message))) # Always issue a new certificate if the certificate is expired, suspended or revoked status = cert_details.get('status', False) if status == 'EXPIRED' or status == 'SUSPENDED' or status == 'REVOKED': return True # If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed if self.module.params['entrust_cert_type'] and cert_details.get( 'certType') and self.module.params[ 'entrust_cert_type'] != cert_details.get('certType'): return True return parent_check def _get_cert_details(self): cert_details = {} try: self._ensure_existing_certificate_loaded() except Exception as dummy: return if self.existing_certificate: serial_number = None expiry = None if self.backend == 'pyopenssl': serial_number = "{0:X}".format( self.existing_certificate.get_serial_number()) time_string = to_native( self.existing_certificate.get_notAfter()) expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") elif self.backend == 'cryptography': serial_number = "{0:X}".format( cryptography_serial_number_of_cert( self.existing_certificate)) expiry = self.existing_certificate.not_valid_after # get some information about the expiry of this certificate expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") cert_details['expiresAfter'] = expiry_iso3339 # If a trackingId is not already defined (from the result of a generate) # use the serial number to identify the tracking Id if self.trackingId is None and serial_number is not None: cert_results = self.ecs_client.GetCertificates( serialNumber=serial_number).get('certificates', {}) # Finding 0 or more than 1 result is a very unlikely use case, it simply means we cannot perform additional checks # on the 'state' as returned by Entrust Certificate Services (ECS). The general certificate validity is # still checked as it is in the rest of the module. if len(cert_results) == 1: self.trackingId = cert_results[0].get('trackingId') if self.trackingId is not None: cert_details.update( self.ecs_client.GetCertificate(trackingId=self.trackingId)) return cert_details