def verify(self, datas, datau): signed_data = cms.ContentInfo.load(datas)['content'] # signed_data.debug() signature = signed_data['signer_infos'][0].native['signature'] algo = signed_data['digest_algorithms'][0]['algorithm'].native attrs = signed_data['signer_infos'][0]['signed_attrs'] mdData = getattr(hashlib, algo)(datau).digest() if attrs is not None and not isinstance(attrs, core.Void): mdSigned = None for attr in attrs: if attr['type'].native == 'message_digest': mdSigned = attr['values'].native[0] signedData = attrs.dump() signedData = b'\x31' + signedData[1:] else: mdSigned = mdData signedData = datau hashok = mdData == mdSigned serial = signed_data['signer_infos'][0]['sid'].native['serial_number'] public_key = None for cert in signed_data['certificates']: if serial == cert.native['tbs_certificate']['serial_number']: cert = cert.dump() cert = pem.armor(u'CERTIFICATE', cert) public_key = crypto.load_certificate( crypto.FILETYPE_PEM, cert).get_pubkey().to_cryptography_key() break try: public_key.verify(signature, signedData, padding.PKCS1v15(), getattr(hashes, algo.upper())()) signatureok = True except: signatureok = False # TODO verify certificates certok = True for cert in signed_data['certificates']: scert = pem.armor(u'CERTIFICATE', cert.dump()).decode() if not self.verify_cert(scert): print('*' * 10, 'failed certificate verification') print('cert.issuer:', cert.native['tbs_certificate']['issuer']) print('cert.subject:', cert.native['tbs_certificate']['subject']) certok = False return { 'hashok?': hashok, 'signatureok?': signatureok, 'certok?': certok }
def _format_crl(self, name, content, fmt): # type: (str, bytes, str) -> Tuple[str, bytes] """ Forces a CRL to DER or PEM. name: The original name of the downloaded CRL. content: The CRL contents. fmt: 'der' or 'pem'. Returns the updated name and content: (name, content), """ base, ext = os.path.splitext(name) is_pem = pem.detect(content) if fmt == 'pem': ext = '.pem' if not is_pem: content = pem.armor('X509 CRL', content) elif fmt == 'der': ext = '.crl' if is_pem: _, _, content = pem.unarmor(content) return (base + ext, content)
def test_self_sign_certificate(self): # Warning: proof of concept code only! pub, priv = self.session.generate_keypair(KeyType.RSA, 1024) tbs = TbsCertificate({ 'version': 'v1', 'serial_number': 1, 'issuer': Name.build({ 'common_name': 'Test Certificate', }), 'subject': Name.build({ 'common_name': 'Test Certificate', }), 'signature': { 'algorithm': 'sha1_rsa', 'parameters': None, }, 'validity': { 'not_before': Time({ 'utc_time': datetime.datetime(2017, 1, 1, 0, 0), }), 'not_after': Time({ 'utc_time': datetime.datetime(2038, 12, 31, 23, 59), }), }, 'subject_public_key_info': { 'algorithm': { 'algorithm': 'rsa', 'parameters': None, }, 'public_key': RSAPublicKey.load(encode_rsa_public_key(pub)), } }) # Sign the TBS Certificate value = priv.sign(tbs.dump(), mechanism=Mechanism.SHA1_RSA_PKCS) cert = Certificate({ 'tbs_certificate': tbs, 'signature_algorithm': { 'algorithm': 'sha1_rsa', 'parameters': None, }, 'signature_value': value, }) # Pipe our certificate to OpenSSL to verify it with subprocess.Popen((OPENSSL, 'verify'), stdin=subprocess.PIPE, stdout=subprocess.DEVNULL) as proc: proc.stdin.write(pem.armor('CERTIFICATE', cert.dump())) proc.stdin.close() self.assertEqual(proc.wait(), 0)
def dump_dh_parameters(dh_parameters, encoding='pem'): """ Serializes an asn1crypto.algos.DHParameters object into a byte string :param dh_parameters: An asn1crypto.algos.DHParameters object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded DH parameters """ if encoding not in set(['pem', 'der']): raise ValueError( pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding))) if not isinstance(dh_parameters, algos.DHParameters): raise TypeError( pretty_message( ''' dh_parameters must be an instance of asn1crypto.algos.DHParameters, not %s ''', type_name(dh_parameters))) output = dh_parameters.dump() if encoding == 'pem': output = pem.armor('DH PARAMETERS', output) return output
def summon(ctx, architecture, cert_label, output, no_pem, as_pfx, ignore_tty, pfx_pass): cfg: CertomancerConfig = next(ctx.obj['config']) pki_arch = cfg.get_pki_arch(architecture) if as_pfx and not pyca_cryptography_present(): as_pfx = False logger.warning( "pyca/cryptography not installed, no PFX files will be created") output_is_binary = as_pfx or no_pem if not ignore_tty and output_is_binary and \ output is None and sys.stdout.isatty(): raise click.ClickException( "Refusing to write binary output to a TTY. Pass --ignore-tty if " "you really want to ignore this check.") if as_pfx: if pfx_pass is not None: pfx_pass = pfx_pass.encode('utf8') data = pki_arch.package_pkcs12(cert_label, password=pfx_pass) else: data = pki_arch.get_cert(CertLabel(cert_label)).dump() if not no_pem: data = pem.armor('certificate', data) if output is None: # we want to write bytes, not strings sys.stdout.buffer.write(data) else: with open(output, 'wb') as outf: outf.write(data)
def form_valid(self, form): entity = self.object issuer = entity.get_issuer() cert = Certificate.objects.filter(entity=entity).order_by('-valid_at').first() days = form.cleaned_data['days'] emails = cert.subject_alt_emails domains = cert.subject_alt_domains ips = cert.subject_alt_ips if issuer.is_private_key_encrypted(): self.req = SigningRequest.objects.create( entity=entity, key_pem=pem.armor('PUBLIC KEY', cert.public_key_info().dump()), days=days, emails=emails, domains=domains, ips=ips ) self.cert = None else: self.cert = issuer.issue_cert( entity, cert.public_key_info(), expires_at=timedelta(days=days), emails=emails, domains=domains, ips=ips ) self.req = None return HttpResponseRedirect(self.get_success_url())
def dump_dh_parameters(dh_parameters, encoding='pem'): """ Serializes an asn1crypto.algos.DHParameters object into a byte string :param dh_parameters: An asn1crypto.algos.DHParameters object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded DH parameters """ if encoding not in set(['pem', 'der']): raise ValueError(pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding) )) if not isinstance(dh_parameters, algos.DHParameters): raise TypeError(pretty_message( ''' dh_parameters must be an instance of asn1crypto.algos.DHParameters, not %s ''', type_name(dh_parameters) )) output = dh_parameters.dump() if encoding == 'pem': output = pem.armor('DH PARAMETERS', output) return output
def pem_armor_csr(certification_request): """ Encodes a CSR into PEM format :param certification_request: An asn1crypto.csr.CertificationRequest object of the CSR to armor. Typically this is obtained from CSRBuilder.build(). :return: A byte string of the PEM-encoded CSR """ if not isinstance(certification_request, csr.CertificationRequest): raise TypeError(_pretty_message( ''' certification_request must be an instance of asn1crypto.csr.CertificationRequest, not %s ''', _type_name(certification_request) )) return pem.armor( 'CERTIFICATE REQUEST', certification_request.dump() )
def dump(self): return ( self.fgprint, self.certobj.issuer.human_friendly, self.certobj.subject.human_friendly, pem.armor('CERTIFICATE', self.certder), )
def armor(self, expected_bytes_filename, relative_path, type_name, headers): with open(os.path.join(fixtures_dir, relative_path), 'rb') as f: byte_string = f.read() encoded_bytes = pem.armor(type_name, byte_string, headers=headers) with open(os.path.join(fixtures_dir, expected_bytes_filename), 'rb') as f: expected_bytes = f.read() self.assertEqual(expected_bytes, encoded_bytes)
def dump_to_pem(self): """ Dump the certificate request to a PEM-encoded string :return: the certificate request PEM string :rtype: str """ return pem.armor(u"CERTIFICATE REQUEST", self._req.dump())
def to_pem(self): """Encode the TSSPrivKey as PEM encoded ASN.1. Returns: Returns the PEM encoding as bytes. """ der = self.to_der() return pem.armor("TSS2 PRIVATE KEY", der)
def serve_any_cert(self, _request: Request, *, arch: str, label: str, use_pem): mime = 'application/x-pem-file' if use_pem else 'application/pkix-cert' pki_arch = self.architectures[ArchLabel(arch)] cert = pki_arch.get_cert(CertLabel(label)) data = cert.dump() if use_pem: data = pem.armor('certificate', data) return Response(data, mimetype=mime)
def armor(self, expected_bytes_filename, relative_path, type_name, headers): with open(os.path.join(fixtures_dir, relative_path), 'rb') as f: byte_string = f.read() encoded_bytes = pem.armor(type_name, byte_string, headers=headers) with open(os.path.join(fixtures_dir, expected_bytes_filename), 'rb') as f: expected_bytes = f.read() # In case a user on Windows has CRLF translation on in Git. # Ran into this with the GitHub Actions Windows environments. expected_bytes = expected_bytes.replace(b'\r\n', b'\n') self.assertEqual(expected_bytes, encoded_bytes)
def get_path(temp_dir=None, cache_length=24): """ Get the filesystem path to a file that contains OpenSSL-compatible CA certs. On OS X and Windows, there are extracted from the system certificate store and cached in a file on the filesystem. This path should not be writable by other users, otherwise they could inject CA certs into the trust list. :param temp_dir: The temporary directory to cache the CA certs in on OS X and Windows. Needs to have secure permissions so other users can not modify the contents. :param cache_length: The number of hours to cache the CA certs on OS X and Windows :raises: oscrypto.errors.CACertsError - when an error occurs exporting/locating certs :return: The full filesystem path to a CA certs file """ ca_path = system_path() # Windows and OS X if ca_path is None: if temp_dir is None: temp_dir = tempfile.gettempdir() if not os.path.isdir(temp_dir): raise CACertsError(pretty_message( ''' The temp dir specified, "%s", is not a directory ''', temp_dir )) ca_path = os.path.join(temp_dir, 'oscrypto-ca-bundle.crt') if _cached_path_needs_update(ca_path, cache_length): with path_lock: if _cached_path_needs_update(ca_path, cache_length): with open(ca_path, 'wb') as f: for cert in extract_from_system(): f.write(armor('CERTIFICATE', cert)) if not ca_path: raise CACertsError('No CA certs found') return ca_path
def get_path(temp_dir=None, cache_length=24): """ Get the filesystem path to a file that contains OpenSSL-compatible CA certs. On OS X and Windows, there are extracted from the system certificate store and cached in a file on the filesystem. This path should not be writable by other users, otherwise they could inject CA certs into the trust list. :param temp_dir: The temporary directory to cache the CA certs in on OS X and Windows. Needs to have secure permissions so other users can not modify the contents. :param cache_length: The number of hours to cache the CA certs on OS X and Windows :raises: oscrypto.errors.CACertsError - when an error occurs exporting/locating certs :return: The full filesystem path to a CA certs file """ ca_path = system_path() # Windows and OS X if ca_path is None: if temp_dir is None: temp_dir = tempfile.gettempdir() if not os.path.isdir(temp_dir): raise CACertsError( pretty_message( ''' The temp dir specified, "%s", is not a directory ''', temp_dir)) ca_path = os.path.join(temp_dir, 'oscrypto-ca-bundle.crt') if _cached_path_needs_update(ca_path, cache_length): with path_lock: if _cached_path_needs_update(ca_path, cache_length): with open(ca_path, 'wb') as f: for cert in extract_from_system(): f.write(armor('CERTIFICATE', cert)) if not ca_path: raise CACertsError('No CA certs found') return ca_path
def register_ca_certificate(): if utility_verison() < '20.10': print('ERROR: update Azure Sphere SDK to the latest version') return response = iotclient.get_registration_code() regCode = response['registrationCode'] if os.path.exists(CA_FILE_NAME): os.remove(CA_FILE_NAME) if os.path.exists(VER_FILE_NAME): os.remove(VER_FILE_NAME) if not utility_download_ca_certificate(CA_FILE_NAME): print('ERROR: failed to download ca certificate') return if not utility_download_validation_certificate(regCode, VER_FILE_NAME): print('ERROR: failed to download validation certificate') return with open(CA_FILE_NAME, 'rb') as f: ca_pem = pem.armor('CERTIFICATE', f.read()) with open(VER_FILE_NAME, 'rb') as f: ver_pem = pem.armor('CERTIFICATE', f.read()) try: response = iotclient.register_ca_certificate( caCertificate=ca_pem.decode('utf-8'), verificationCertificate=ver_pem.decode('utf-8'), setAsActive=True, allowAutoRegistration=True) except iotclient.exceptions.ResourceAlreadyExistsException: print("INFO: Already have this CA registered")
def serve_crl(self, request: Request, *, label: ServiceLabel, arch: str, crl_no, use_pem): pki_arch = self.architectures[ArchLabel(arch)] mime = 'application/x-pem-file' if use_pem else 'application/pkix-crl' if crl_no is not None: crl = pki_arch.service_registry.get_crl(label, number=crl_no) else: crl = pki_arch.service_registry.get_crl( label, self.at_time(request) ) data = crl.dump() if use_pem: data = pem.armor('X509 CRL', data) return Response(data, mimetype=mime)
def serve_cert(self, _request: Request, *, label: str, arch: str, cert_label: Optional[str], use_pem): mime = 'application/x-pem-file' if use_pem else 'application/pkix-cert' pki_arch = self.architectures[ArchLabel(arch)] cert_label = CertLabel(cert_label) if cert_label is not None else None cert = pki_arch.service_registry.get_cert_from_repo( ServiceLabel(label), cert_label ) if cert is None: raise NotFound() data = cert.dump() if use_pem: data = pem.armor('certificate', data) return Response(data, mimetype=mime)
def main(self): cakeyID = bytes((0x1,)) ca_cert_pem = asn1pem.armor('CERTIFICATE', self.cert_load(cakeyID)) trusted_cert_pems = (ca_cert_pem,) for fname in ( 'pdf-signed-cms-hsm.pdf', ): print('*' * 20, fname) try: data = open(fname, 'rb').read() except: continue (hashok, signatureok, certok) = pdf.verify(data, trusted_cert_pems) print('signature ok?', signatureok) print('hash ok?', hashok) print('cert ok?', certok)
def _read(self, to_pem=False): """ This function returns an existing certificate from the OPTIGA(TM) Trust device :param to_pem: A boolean flag to indicate, whether you want return certificate PEM encoded :raises: - ValueError - when any of the parameters contain an invalid value - TypeError - when any of the parameters are of the wrong type - OSError - when an error is returned by the core initialisation library :returns: A byte string with a PEM certificate or DER encoded byte string """ oid = self._optiga.object_id if self.id not in self._optiga.object_id_values: raise ValueError( 'Certificate Slot is not correct. ' 'Supported values are {0} class you used {1}'.format(self._optiga.object_id_values, self.id) ) _supported_objects = (oid.IFX_CERT.value, oid.USER_CERT_1.value, oid.USER_CERT_2.value, oid.USER_CERT_3.value, oid.TRUST_ANCHOR_1.value, oid.TRUST_ANCHOR_2.value, oid.DATA_SLOT_1500B_0, oid.DATA_SLOT_1500B_1) if self.id not in _supported_objects: raise ValueError( 'Object ID is not one of supported {0}'.format(_supported_objects) ) der_cert = self.read() # print(list(der_cert)) if len(der_cert) == 0: raise ValueError( 'Certificate Slot {0} is empty'.format(self.id) ) # OPTIGA Trust Code to tag an X509 certificate if der_cert[0] == 0xC0: der_cert = der_cert[9:] if to_pem: return asn1_pem.armor('CERTIFICATE', der_cert) else: return bytes(der_cert)
def main(): pdfname = 'pdf.pdf' if len(sys.argv) > 1: pdfname = sys.argv[1] config = open(pdfname + ".json", "rt").read() config = json.loads(config) tosign = base64.decodebytes(config['tosign'].encode('ascii')) clshsm = Signer(dllpath) keyid, cert = clshsm.certificate() signed_bytes = clshsm.sign(keyid, tosign, "sha256") config['signed_bytes'] = b"".join( base64.encodebytes(signed_bytes).split()).decode('ascii') config['certificate'] = asn1pem.armor("CERTIFICATE", cert).decode('ascii') json.dump(config, open(pdfname + ".json", "wt"), indent=4)
def pem_armor_csr(certification_request): """ Encodes a CSR into PEM format :param certification_request: An asn1crypto.csr.CertificationRequest object of the CSR to armor. Typically this is obtained from Builder.build(). :return: A byte string of the PEM-encoded CSR """ if not isinstance(certification_request, csr.CertificationRequest): raise TypeError( _pretty_message( ''' certification_request must be an instance of asn1crypto.csr.CertificationRequest, not %s ''', _type_name(certification_request))) return pem.armor('CERTIFICATE REQUEST', certification_request.dump())
def pem_armor_crl(certificate_list): """ Encodes a CRL into PEM format :param certificate_list: An asn1crypto.crl.CertificateList object of the CRL to armor. Typically this is obtained from CertificateListBuilder.build(). :return: A byte string of the PEM-encoded CRL """ if not isinstance(certificate_list, crl.CertificateList): raise TypeError( _pretty_message( ''' certificate_list must be an instance of asn1crypto.crl.CertificateList, not %s ''', _type_name(certificate_list))) return pem.armor('X509 CRL', certificate_list.dump())
def encode(byte_string, is_der = None, alg = 'Curve25519' ): try: raw = OctetString(byte_string) except Exception as e: raise FailedToParseByteString privateKey = PrivateKey(raw.dump()) privateKeyAlgorithmIdentifier = PrivateKeyAlgorithmIdentifier( {'algorithm': alg }); oneAsymmetricKey = OneAsymmetricKey({ 'version': Integer(0), 'privateKeyAlgorithm': privateKeyAlgorithmIdentifier, 'privateKey': privateKey}) der = oneAsymmetricKey.dump() if is_der: return der return pem.armor('PRIVATE KEY',der).decode('ASCII')
def pem_armor_crl(certificate_list): """ Encodes a CRL into PEM format :param certificate_list: An asn1crypto.crl.CertificateList object of the CRL to armor. Typically this is obtained from CertificateListBuilder.build(). :return: A byte string of the PEM-encoded CRL """ if not isinstance(certificate_list, crl.CertificateList): raise TypeError(_pretty_message( ''' certificate_list must be an instance of asn1crypto.crl.CertificateList, not %s ''', _type_name(certificate_list) )) return pem.armor('X509 CRL', certificate_list.dump())
def dump_public_key(public_key, encoding='pem'): """ Serializes a public key object into a byte string :param public_key: An oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded public key """ if encoding not in set(['pem', 'der']): raise ValueError(pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding) )) is_oscrypto = isinstance(public_key, PublicKey) if not isinstance(public_key, keys.PublicKeyInfo) and not is_oscrypto: raise TypeError(pretty_message( ''' public_key must be an instance of oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo, not %s ''', type_name(public_key) )) if is_oscrypto: public_key = public_key.asn1 output = public_key.dump() if encoding == 'pem': output = pem.armor('PUBLIC KEY', output) return output
def dump_certificate(certificate, encoding='pem'): """ Serializes a certificate object into a byte string :param certificate: An oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded certificate """ if encoding not in set(['pem', 'der']): raise ValueError(pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding) )) is_oscrypto = isinstance(certificate, Certificate) if not isinstance(certificate, x509.Certificate) and not is_oscrypto: raise TypeError(pretty_message( ''' certificate must be an instance of oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate, not %s ''', type_name(certificate) )) if is_oscrypto: certificate = certificate.asn1 output = certificate.dump() if encoding == 'pem': output = pem.armor('CERTIFICATE', output) return output
def dump_certificate(certificate, encoding='pem'): """ Serializes a certificate object into a byte string :param certificate: An oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded certificate """ if encoding not in set(['pem', 'der']): raise ValueError( pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding))) is_oscrypto = isinstance(certificate, Certificate) if not isinstance(certificate, x509.Certificate) and not is_oscrypto: raise TypeError( pretty_message( ''' certificate must be an instance of oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate, not %s ''', type_name(certificate))) if is_oscrypto: certificate = certificate.asn1 output = certificate.dump() if encoding == 'pem': output = pem.armor('CERTIFICATE', output) return output
def dump_public_key(public_key, encoding='pem'): """ Serializes a public key object into a byte string :param public_key: An oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo object :param encoding: A unicode string of "pem" or "der" :return: A byte string of the encoded public key """ if encoding not in set(['pem', 'der']): raise ValueError( pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding))) is_oscrypto = isinstance(public_key, PublicKey) if not isinstance(public_key, keys.PublicKeyInfo) and not is_oscrypto: raise TypeError( pretty_message( ''' public_key must be an instance of oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo, not %s ''', type_name(public_key))) if is_oscrypto: public_key = public_key.asn1 output = public_key.dump() if encoding == 'pem': output = pem.armor('PUBLIC KEY', output) return output
def necronomicon(ctx, architecture, crl_repo, output, no_pem, at_time, ignore_tty): cfg: CertomancerConfig = next(ctx.obj['config']) pki_arch = cfg.get_pki_arch(architecture) if at_time is None: at_time = datetime.now(tz=tzlocal.get_localzone()) else: at_time = parse_dt(at_time) crl = pki_arch.service_registry.get_crl(repo_label=crl_repo, at_time=at_time) if output is None and no_pem and not ignore_tty and sys.stdout.isatty(): raise click.ClickException( "Refusing to write binary output to a TTY. Pass --ignore-tty if " "you really want to ignore this check.") data = crl.dump() if not no_pem: data = pem.armor('X509 CRL', data) if output is None: sys.stdout.buffer.write(data) else: with open(output, 'wb') as f: f.write(data)
def to_pem(self): """Get the PEM-encoding of the certificate.""" return pem.armor(self.PEM_MARKERS[0], self.to_der())
def verify_bankid_response(bank_id_response, ensure_certificates_still_valid=True, BANK_ID_ROOT_CERT=None): if not isinstance(bank_id_response, dict): raise TypeError("Response not a dictionary") if 'completionData' not in bank_id_response: raise AttributeError('Completion data missing in dictionary') try: cdc = CompletionDataContainer(bank_id_response['completionData']) # First step is to hash the data and verify the digest matches _LOG.info("1. Message Digest Verification\n") bid_signed_data_raw_bytes = cdc.signature_container.bid_signed_data_raw.encode( ) # TODO - Parse out of the XML which hashing algorithm should be sued bid_signed_data_hash = hashlib.sha256( bid_signed_data_raw_bytes).digest().hex() key_info_hash = hashlib.sha256( cdc.signature_container.key_info_raw.encode()).digest().hex() signed_data_hash_from_signature = base64.b64decode( cdc.signature_container.signed_data_digest.text).hex() key_info_hash_from_signature = base64.b64decode( cdc.signature_container.key_data_digest.text).hex() if bid_signed_data_hash != signed_data_hash_from_signature: raise AssertionError('Signed Data hash does not match!') if key_info_hash != key_info_hash_from_signature: raise AssertionError('Key Info hash does not match!') _LOG.info("\n2. Signature verification\n") # Helper function for the certificates user_certificate_string = make_cert( cdc.signature_container.certificates[0].text) # Making a certificate object out of it user_certificate = crypto.load_certificate( crypto.FILETYPE_PEM, BytesIO(user_certificate_string.encode()).read()) signature_bytes = base64.b64decode( cdc.signature_container.signature_value.text) signed_info = cdc.signature_container.signed_info.encode() try: _LOG.debug("Certificate:", user_certificate.get_subject()) _LOG.debug("Signature Bytes:", signature_bytes) _LOG.debug("Signature Data Raw:", signed_info) OpenSSL.crypto.verify(user_certificate, signature_bytes, signed_info, 'sha256') except OpenSSL.crypto.Error as e: raise AssertionError('The BankID signature is not valid!') _LOG.info("\n3. OCSP Response Verification\n") ocsp = base64.b64decode( bank_id_response['completionData']['ocspResponse']) ocsp_response = asn1crypto.ocsp.OCSPResponse.load(ocsp) basic_ocsp_response = ocsp_response['response_bytes'][ 'response'].parsed # Some help by listing all the different parts of the OCSP response _LOG.debug("TBS Response Data", basic_ocsp_response['tbs_response_data']) _LOG.debug("SignatureAlgorithm", basic_ocsp_response['signature_algorithm'].signature_algo) _LOG.debug("SignatureAlgorithm Hash Function", basic_ocsp_response['signature_algorithm'].hash_algo) _LOG.debug("Signature", basic_ocsp_response['signature'].__bytes__()) _LOG.debug("Cert", basic_ocsp_response['certs']) # Response content _LOG.debug("version", basic_ocsp_response['tbs_response_data']['version']) _LOG.debug("responderID", basic_ocsp_response['tbs_response_data'] ['responder_id']) # has native _LOG.info("producedAt", basic_ocsp_response['tbs_response_data']['produced_at']) cest = pytz.timezone('Europe/Stockholm') ocsp_produced_at = basic_ocsp_response['tbs_response_data'][ 'produced_at'].native if not isinstance(ocsp_produced_at, datetime.datetime): raise AssertionError('OCSP produced at is not a datetime!') ocsp_produced_at = ocsp_produced_at.astimezone(cest).strftime( '%Y-%m-%d %H:%M:%S') _LOG.debug("responses", basic_ocsp_response['tbs_response_data']['responses']) _LOG.debug( "response Extentions", basic_ocsp_response['tbs_response_data']['response_extensions']) _LOG.debug("Extentions") extention = basic_ocsp_response['tbs_response_data'][ 'response_extensions'][0] _LOG.debug('extn_id', extention['extn_id']) _LOG.debug('critical', extention['critical']) # Cannot _LOG.debug the value without an exception being raised - need to parse that ourself later # print ('extn_value', extention['extn_value']) single_response = basic_ocsp_response['tbs_response_data'][ 'responses'][0] _LOG.debug("CertID", single_response['cert_id']) _LOG.debug("certStatus", single_response['cert_status']) _LOG.debug("thisUpdate", single_response['this_update']) _LOG.debug("nextUpdate", single_response['next_update']) _LOG.debug("singleExtensions", single_response['single_extensions']) _LOG.info("3.1. OCSP Response - Verify success ") if ocsp_response['response_status'].native != 'successful': raise AssertionError('OCSP response status was not successful') _LOG.info("3.2. OCSP Response - Verify signature ") # Transform the asn1 certificate to an openssl certificate der_bytes = basic_ocsp_response['certs'][0].dump() pem_bytes = pem.armor('CERTIFICATE', der_bytes) ocsp_certificate = crypto.load_certificate(crypto.FILETYPE_PEM, pem_bytes) # Get the signature bytes signature = basic_ocsp_response['signature'].__bytes__() # Dump the TBS response data as DER bytes signature_data = basic_ocsp_response['tbs_response_data'].dump() # Define the hashing algorithm to be used digest_method = basic_ocsp_response['signature_algorithm'].hash_algo _LOG.debug("Certificate", ocsp_certificate.get_subject()) _LOG.debug("Signature", signature) _LOG.debug("Signature data", signature_data) _LOG.debug("Digest Method", digest_method) try: OpenSSL.crypto.verify(ocsp_certificate, signature, signature_data, digest_method) except OpenSSL.crypto.Error as e: raise AssertionError('The OCSP signature is not valid!') _LOG.info("3.2. OCSP Response - Compare nonce") nonce_computed = hashlib.sha1( bank_id_response['completionData']['signature'].encode( 'utf-8')).digest().hex() # A helper because the asn1 library seems to have a problem with the nonce parsing in some form or the other nonce_parser = NonceParse(extention.contents) # Verify that the computed nonce is part of the nonce value given in the oscp # Note that it only partially matches as we use sha-1 to compute the hash _LOG.debug("Nonce value computed ", nonce_computed) _LOG.debug("Nonce value presented", nonce_parser.value.hex()) if not nonce_parser.value.hex().startswith(nonce_computed): raise AssertionError('Computed nonce not matching the OCSP nonce') _LOG.info( "\n4. Verify all the certificates by relying on the BankID root certificate as a trusted one \n" ) user_cert = crypto.load_certificate( crypto.FILETYPE_PEM, make_cert(cdc.signature_container.certificates[0].text).encode()) bank_user_cert = crypto.load_certificate( crypto.FILETYPE_PEM, make_cert(cdc.signature_container.certificates[1].text).encode()) bank_bank_id_cert = crypto.load_certificate( crypto.FILETYPE_PEM, make_cert(cdc.signature_container.certificates[2].text).encode()) bank_id_root_cert = crypto.load_certificate(crypto.FILETYPE_PEM, BANK_ID_ROOT_CERT.encode()) # 3. verify the certificate chain of the tbs certificate # Make sure we respect or do not respect certificate expiration times if not ensure_certificates_still_valid: tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) bank_user_cert.set_notAfter( tomorrow.strftime('%Y%m%d%H%M%SZ').encode()) bank_bank_id_cert.set_notAfter( tomorrow.strftime('%Y%m%d%H%M%SZ').encode()) bank_id_root_cert.set_notAfter( tomorrow.strftime('%Y%m%d%H%M%SZ').encode()) ocsp_certificate.set_notAfter( tomorrow.strftime('%Y%m%d%H%M%SZ').encode()) user_cert.set_notAfter(tomorrow.strftime('%Y%m%d%H%M%SZ').encode()) store = crypto.X509Store() store.add_cert(bank_user_cert) store.add_cert(bank_bank_id_cert) store.add_cert(bank_id_root_cert) try: # Verify the user certificate up to the root certificate store_ctx = crypto.X509StoreContext(store, user_cert) store_ctx.verify_certificate() _LOG.debug('User Certificate issued by the respective bank... OK') except X509StoreContextError: raise AssertionError( 'BankID user certificate chain could not be verified.') try: # Verify the ocsp certificate up to the root certificate store_ctx = crypto.X509StoreContext(store, ocsp_certificate) store_ctx.verify_certificate() _LOG.debug('OCSP Certificate issued by the respective bank... OK') except X509StoreContextError: raise AssertionError( 'OCSP certificate chain could not be verified.') except Exception as e: raise e return ocsp_produced_at
def private_key_pem(self, key): return pem.armor('RSA PRIVATE KEY', asymmetric.dump_private_key(key, None, 'der'))
def verify(self, datas, datau): signed_data = cms.ContentInfo.load(datas)['content'] # signed_data.debug() signature = signed_data['signer_infos'][0].native['signature'] algo = signed_data['digest_algorithms'][0]['algorithm'].native attrs = signed_data['signer_infos'][0]['signed_attrs'] mdData = getattr(hashlib, algo)(datau).digest() if attrs is not None and not isinstance(attrs, core.Void): mdSigned = None for attr in attrs: if attr['type'].native == 'message_digest': mdSigned = attr['values'].native[0] signedData = attrs.dump() signedData = b'\x31' + signedData[1:] else: mdSigned = mdData signedData = datau hashok = mdData == mdSigned serial = signed_data['signer_infos'][0]['sid'].native['serial_number'] public_key = None for cert in signed_data['certificates']: if serial == cert.native['tbs_certificate']['serial_number']: cert = cert.dump() cert = pem.armor(u'CERTIFICATE', cert) public_key = crypto.load_certificate(crypto.FILETYPE_PEM, cert).get_pubkey().to_cryptography_key() break sigalgo = signed_data['signer_infos'][0]['signature_algorithm'] # sigalgo.debug() sigalgoname = sigalgo.signature_algo if sigalgoname == 'rsassa_pss': parameters = sigalgo['parameters'] #parameters.debug() #print(parameters.native) salgo = parameters['hash_algorithm'].native['algorithm'].upper() mgf = getattr(padding, parameters['mask_gen_algorithm'].native['algorithm'].upper())(getattr(hashes, salgo)()) salt_length = parameters['salt_length'].native try: public_key.verify( signature, signedData, padding.PSS(mgf, salt_length), getattr(hashes, salgo)() ) signatureok = True except: signatureok = False elif sigalgoname == 'rsassa_pkcs1v15': try: public_key.verify( signature, signedData, padding.PKCS1v15(), getattr(hashes, algo.upper())() ) signatureok = True except: signatureok = False else: raise ValueError('Unknown signature algorithm') # TODO verify certificates certok = True for cert in signed_data['certificates']: scert = pem.armor(u'CERTIFICATE', cert.dump()).decode() if not self.verify_cert(scert): print('*' * 10, 'failed certificate verification') print('cert.issuer:', cert.native['tbs_certificate']['issuer']) print('cert.subject:', cert.native['tbs_certificate']['subject']) certok = False return (hashok, signatureok, certok)
def dump_openssl_private_key(private_key, passphrase): """ Serializes a private key object into a byte string of the PEM formats used by OpenSSL. The format chosen will depend on the type of private key - RSA, DSA or EC. Do not use this method unless you really must interact with a system that does not support PKCS#8 private keys. The encryption provided by PKCS#8 is far superior to the OpenSSL formats. This is due to the fact that the OpenSSL formats don't stretch the passphrase, making it very easy to brute-force. :param private_key: An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo object :param passphrase: A unicode string of the passphrase to encrypt the private key with. A passphrase of None will result in no encryption. A blank string will result in a ValueError to help ensure that the lack of passphrase is intentional. :raises: ValueError - when a blank string is provided for the passphrase :return: A byte string of the encoded and encrypted public key """ if passphrase is not None: if not isinstance(passphrase, str_cls): raise TypeError(pretty_message( ''' passphrase must be a unicode string, not %s ''', type_name(passphrase) )) if passphrase == '': raise ValueError(pretty_message( ''' passphrase may not be a blank string - pass None to disable encryption ''' )) is_oscrypto = isinstance(private_key, PrivateKey) if not isinstance(private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError(pretty_message( ''' private_key must be an instance of oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo, not %s ''', type_name(private_key) )) if is_oscrypto: private_key = private_key.asn1 output = private_key.unwrap().dump() headers = None if passphrase is not None: iv = rand_bytes(16) headers = OrderedDict() headers['Proc-Type'] = '4,ENCRYPTED' headers['DEK-Info'] = 'AES-128-CBC,%s' % binascii.hexlify(iv).decode('ascii') key_length = 16 passphrase_bytes = passphrase.encode('utf-8') key = hashlib.md5(passphrase_bytes + iv[0:8]).digest() while key_length > len(key): key += hashlib.md5(key + passphrase_bytes + iv[0:8]).digest() key = key[0:key_length] iv, output = aes_cbc_pkcs7_encrypt(key, output, iv) if private_key.algorithm == 'ec': object_type = 'EC PRIVATE KEY' elif private_key.algorithm == 'rsa': object_type = 'RSA PRIVATE KEY' elif private_key.algorithm == 'dsa': object_type = 'DSA PRIVATE KEY' return pem.armor(object_type, output, headers=headers)
def get_path(temp_dir=None, cache_length=24, cert_callback=None): """ Get the filesystem path to a file that contains OpenSSL-compatible CA certs. On OS X and Windows, there are extracted from the system certificate store and cached in a file on the filesystem. This path should not be writable by other users, otherwise they could inject CA certs into the trust list. :param temp_dir: The temporary directory to cache the CA certs in on OS X and Windows. Needs to have secure permissions so other users can not modify the contents. :param cache_length: The number of hours to cache the CA certs on OS X and Windows :param cert_callback: A callback that is called once for each certificate in the trust store. It should accept two parameters: an asn1crypto.x509.Certificate object, and a reason. The reason will be None if the certificate is being exported, otherwise it will be a unicode string of the reason it won't. This is only called on Windows and OS X when passed to this function. :raises: oscrypto.errors.CACertsError - when an error occurs exporting/locating certs :return: The full filesystem path to a CA certs file """ ca_path, temp = _ca_path(temp_dir) # Windows and OS X if temp and _cached_path_needs_update(ca_path, cache_length): empty_set = set() any_purpose = '2.5.29.37.0' apple_ssl = '1.2.840.113635.100.1.3' win_server_auth = '1.3.6.1.5.5.7.3.1' with path_lock: if _cached_path_needs_update(ca_path, cache_length): with open(ca_path, 'wb') as f: for cert, trust_oids, reject_oids in extract_from_system(cert_callback, True): if sys.platform == 'darwin': if trust_oids != empty_set and any_purpose not in trust_oids \ and apple_ssl not in trust_oids: if cert_callback: cert_callback(Certificate.load(cert), 'implicitly distrusted for TLS') continue if reject_oids != empty_set and (apple_ssl in reject_oids or any_purpose in reject_oids): if cert_callback: cert_callback(Certificate.load(cert), 'explicitly distrusted for TLS') continue elif sys.platform == 'win32': if trust_oids != empty_set and any_purpose not in trust_oids \ and win_server_auth not in trust_oids: if cert_callback: cert_callback(Certificate.load(cert), 'implicitly distrusted for TLS') continue if reject_oids != empty_set and (win_server_auth in reject_oids or any_purpose in reject_oids): if cert_callback: cert_callback(Certificate.load(cert), 'explicitly distrusted for TLS') continue if cert_callback: cert_callback(Certificate.load(cert), None) f.write(armor('CERTIFICATE', cert)) if not ca_path: raise CACertsError('No CA certs found') return ca_path
def get_path(temp_dir=None, cache_length=24, map_vendor_oids=True, cert_callback=None): """ Get the filesystem path to a file that contains OpenSSL-compatible CA certs. On OS X and Windows, there are extracted from the system certificate store and cached in a file on the filesystem. This path should not be writable by other users, otherwise they could inject CA certs into the trust list. :param temp_dir: The temporary directory to cache the CA certs in on OS X and Windows. Needs to have secure permissions so other users can not modify the contents. :param cache_length: The number of hours to cache the CA certs on OS X and Windows :param map_vendor_oids: A bool indicating if the following mapping of OIDs should happen for trust information from the OS trust list: - 1.2.840.113635.100.1.3 (apple_ssl) -> 1.3.6.1.5.5.7.3.1 (server_auth) - 1.2.840.113635.100.1.3 (apple_ssl) -> 1.3.6.1.5.5.7.3.2 (client_auth) - 1.2.840.113635.100.1.8 (apple_smime) -> 1.3.6.1.5.5.7.3.4 (email_protection) - 1.2.840.113635.100.1.9 (apple_eap) -> 1.3.6.1.5.5.7.3.13 (eap_over_ppp) - 1.2.840.113635.100.1.9 (apple_eap) -> 1.3.6.1.5.5.7.3.14 (eap_over_lan) - 1.2.840.113635.100.1.11 (apple_ipsec) -> 1.3.6.1.5.5.7.3.5 (ipsec_end_system) - 1.2.840.113635.100.1.11 (apple_ipsec) -> 1.3.6.1.5.5.7.3.6 (ipsec_tunnel) - 1.2.840.113635.100.1.11 (apple_ipsec) -> 1.3.6.1.5.5.7.3.7 (ipsec_user) - 1.2.840.113635.100.1.11 (apple_ipsec) -> 1.3.6.1.5.5.7.3.17 (ipsec_ike) - 1.2.840.113635.100.1.16 (apple_code_signing) -> 1.3.6.1.5.5.7.3.3 (code_signing) - 1.2.840.113635.100.1.20 (apple_time_stamping) -> 1.3.6.1.5.5.7.3.8 (time_stamping) - 1.3.6.1.4.1.311.10.3.2 (microsoft_time_stamp_signing) -> 1.3.6.1.5.5.7.3.8 (time_stamping) :param cert_callback: A callback that is called once for each certificate in the trust store. It should accept two parameters: an asn1crypto.x509.Certificate object, and a reason. The reason will be None if the certificate is being exported, otherwise it will be a unicode string of the reason it won't. This is only called on Windows and OS X when passed to this function. :raises: oscrypto.errors.CACertsError - when an error occurs exporting/locating certs :return: The full filesystem path to a CA certs file """ ca_path, temp = _ca_path(temp_dir) # Windows and OS X if temp and _cached_path_needs_update(ca_path, cache_length): empty_set = set() with path_lock: if _cached_path_needs_update(ca_path, cache_length): with open(ca_path, 'wb') as f: for cert, trust_oids, reject_oids in extract_from_system(cert_callback): if trust_oids == empty_set and reject_oids == empty_set: f.write(armor('CERTIFICATE', cert)) else: if map_vendor_oids: trust_oids = _map_oids(trust_oids) reject_oids = _map_oids(reject_oids) f.write(armor( 'TRUSTED CERTIFICATE', TrustedCertificate([ Certificate.load(cert), CertificateAux({ 'trust': trust_oids, 'reject': reject_oids, }) ]).dump() )) if not ca_path: raise CACertsError('No CA certs found') return ca_path
def dump_private_key(private_key, passphrase, encoding='pem', target_ms=200): """ Serializes a private key object into a byte string of the PKCS#8 format :param private_key: An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo object :param passphrase: A unicode string of the passphrase to encrypt the private key with. A passphrase of None will result in no encryption. A blank string will result in a ValueError to help ensure that the lack of passphrase is intentional. :param encoding: A unicode string of "pem" or "der" :param target_ms: Use PBKDF2 with the number of iterations that takes about this many milliseconds on the current machine. :raises: ValueError - when a blank string is provided for the passphrase :return: A byte string of the encoded and encrypted public key """ if encoding not in set(['pem', 'der']): raise ValueError( pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding))) if passphrase is not None: if not isinstance(passphrase, str_cls): raise TypeError( pretty_message( ''' passphrase must be a unicode string, not %s ''', type_name(passphrase))) if passphrase == '': raise ValueError( pretty_message(''' passphrase may not be a blank string - pass None to disable encryption ''')) is_oscrypto = isinstance(private_key, PrivateKey) if not isinstance(private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError( pretty_message( ''' private_key must be an instance of oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo, not %s ''', type_name(private_key))) if is_oscrypto: private_key = private_key.asn1 output = private_key.dump() if passphrase is not None: cipher = 'aes256_cbc' key_length = 32 kdf_hmac = 'sha256' kdf_salt = rand_bytes(key_length) iterations = pbkdf2_iteration_calculator(kdf_hmac, key_length, target_ms=target_ms, quiet=True) # Need a bare minimum of 10,000 iterations for PBKDF2 as of 2015 if iterations < 10000: iterations = 10000 passphrase_bytes = passphrase.encode('utf-8') key = pbkdf2(kdf_hmac, passphrase_bytes, kdf_salt, iterations, key_length) iv, ciphertext = aes_cbc_pkcs7_encrypt(key, output, None) output = keys.EncryptedPrivateKeyInfo({ 'encryption_algorithm': { 'algorithm': 'pbes2', 'parameters': { 'key_derivation_func': { 'algorithm': 'pbkdf2', 'parameters': { 'salt': algos.Pbkdf2Salt(name='specified', value=kdf_salt), 'iteration_count': iterations, 'prf': { 'algorithm': kdf_hmac, 'parameters': core.Null() } } }, 'encryption_scheme': { 'algorithm': cipher, 'parameters': iv } } }, 'encrypted_data': ciphertext }).dump() if encoding == 'pem': if passphrase is None: object_type = 'PRIVATE KEY' else: object_type = 'ENCRYPTED PRIVATE KEY' output = pem.armor(object_type, output) return output
def public_key_pem(self, key): return pem.armor('PUBLIC KEY', asymmetric.dump_private_key(key, None, 'der'))
def dump_openssl_private_key(private_key, passphrase): """ Serializes a private key object into a byte string of the PEM formats used by OpenSSL. The format chosen will depend on the type of private key - RSA, DSA or EC. Do not use this method unless you really must interact with a system that does not support PKCS#8 private keys. The encryption provided by PKCS#8 is far superior to the OpenSSL formats. This is due to the fact that the OpenSSL formats don't stretch the passphrase, making it very easy to brute-force. :param private_key: An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo object :param passphrase: A unicode string of the passphrase to encrypt the private key with. A passphrase of None will result in no encryption. A blank string will result in a ValueError to help ensure that the lack of passphrase is intentional. :raises: ValueError - when a blank string is provided for the passphrase :return: A byte string of the encoded and encrypted public key """ if passphrase is not None: if not isinstance(passphrase, str_cls): raise TypeError( pretty_message( ''' passphrase must be a unicode string, not %s ''', type_name(passphrase))) if passphrase == '': raise ValueError( pretty_message(''' passphrase may not be a blank string - pass None to disable encryption ''')) is_oscrypto = isinstance(private_key, PrivateKey) if not isinstance(private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError( pretty_message( ''' private_key must be an instance of oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo, not %s ''', type_name(private_key))) if is_oscrypto: private_key = private_key.asn1 output = _unwrap_private_key_info(private_key).dump() headers = None if passphrase is not None: iv = rand_bytes(16) headers = OrderedDict() headers['Proc-Type'] = '4,ENCRYPTED' headers['DEK-Info'] = 'AES-128-CBC,%s' % binascii.hexlify(iv).decode( 'ascii') key_length = 16 passphrase_bytes = passphrase.encode('utf-8') key = hashlib.md5(passphrase_bytes + iv[0:8]).digest() while key_length > len(key): key += hashlib.md5(key + passphrase_bytes + iv[0:8]).digest() key = key[0:key_length] iv, output = aes_cbc_pkcs7_encrypt(key, output, iv) if private_key.algorithm == 'ec': object_type = 'EC PRIVATE KEY' elif private_key.algorithm == 'rsa': object_type = 'RSA PRIVATE KEY' elif private_key.algorithm == 'dsa': object_type = 'DSA PRIVATE KEY' return pem.armor(object_type, output, headers=headers)
def dump_private_key(private_key, passphrase, encoding='pem', target_ms=200): """ Serializes a private key object into a byte string of the PKCS#8 format :param private_key: An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo object :param passphrase: A unicode string of the passphrase to encrypt the private key with. A passphrase of None will result in no encryption. A blank string will result in a ValueError to help ensure that the lack of passphrase is intentional. :param encoding: A unicode string of "pem" or "der" :param target_ms: Use PBKDF2 with the number of iterations that takes about this many milliseconds on the current machine. :raises: ValueError - when a blank string is provided for the passphrase :return: A byte string of the encoded and encrypted public key """ if encoding not in set(['pem', 'der']): raise ValueError(pretty_message( ''' encoding must be one of "pem", "der", not %s ''', repr(encoding) )) if passphrase is not None: if not isinstance(passphrase, str_cls): raise TypeError(pretty_message( ''' passphrase must be a unicode string, not %s ''', type_name(passphrase) )) if passphrase == '': raise ValueError(pretty_message( ''' passphrase may not be a blank string - pass None to disable encryption ''' )) is_oscrypto = isinstance(private_key, PrivateKey) if not isinstance(private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError(pretty_message( ''' private_key must be an instance of oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo, not %s ''', type_name(private_key) )) if is_oscrypto: private_key = private_key.asn1 output = private_key.dump() if passphrase is not None: cipher = 'aes256_cbc' key_length = 32 kdf_hmac = 'sha256' kdf_salt = rand_bytes(key_length) iterations = pbkdf2_iteration_calculator(kdf_hmac, key_length, target_ms=target_ms, quiet=True) # Need a bare minimum of 10,000 iterations for PBKDF2 as of 2015 if iterations < 10000: iterations = 10000 passphrase_bytes = passphrase.encode('utf-8') key = pbkdf2(kdf_hmac, passphrase_bytes, kdf_salt, iterations, key_length) iv, ciphertext = aes_cbc_pkcs7_encrypt(key, output, None) output = keys.EncryptedPrivateKeyInfo({ 'encryption_algorithm': { 'algorithm': 'pbes2', 'parameters': { 'key_derivation_func': { 'algorithm': 'pbkdf2', 'parameters': { 'salt': algos.Pbkdf2Salt( name='specified', value=kdf_salt ), 'iteration_count': iterations, 'prf': { 'algorithm': kdf_hmac, 'parameters': core.Null() } } }, 'encryption_scheme': { 'algorithm': cipher, 'parameters': iv } } }, 'encrypted_data': ciphertext }).dump() if encoding == 'pem': if passphrase is None: object_type = 'PRIVATE KEY' else: object_type = 'ENCRYPTED PRIVATE KEY' output = pem.armor(object_type, output) return output