def _load_cert_object(self, *path_components): with open(os.path.join(fixtures_dir, *path_components), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) cert = x509.Certificate.load(cert_bytes) return cert
def _sign_string(self, string_to_sign): try: # We expect the private key to be the an PKCS8 pem formatted string. pem_bytes = self.credentials.private_key.encode('utf-8') if pem.detect(pem_bytes): _, _, der_bytes = pem.unarmor(pem_bytes) # In PKCS8 the key is wrapped in a container that describes it info = keys.PrivateKeyInfo.load(der_bytes, strict=True) # The unwrapped key is equivalent to pkcs1 contents key = rsa.PrivateKey.load_pkcs1(info.unwrap().dump(), 'DER') else: raise Exception('Not a PEM file') except: message = \ "Failed to import private key from: '%s'. The private key is " \ "corrupted or it is not in PKCS8 PEM format. The private key " \ "was extracted either from 'env' (environment variables), " \ "'shared-credentials-file' (a profile in the shared " \ "credential file, by default under ~/.altus/credentials), or " \ "'auth-config-file' (a file containing the credentials whose " \ "location was supplied on the command line.)" % \ self.credentials.method LOG.debug(message, exc_info=True) raise Exception(message) # We sign the hash. signature = rsa.sign(string_to_sign.encode('utf-8'), key, 'SHA-256') return urlsafe_b64encode(signature).strip().decode('utf-8')
def test_invalid_csr(self): csr_pem = b'-----BEGIN CERTIFICATE REQUEST-----\nMIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx\nITAfBgNVBAoMGEludgVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAOX7xMqjolAJ39LsPdhPRTxlHxowKyqd6wnHKdT7\nRVjmc9oY2+awntbkxS6qd8xsTipiL69G/eALMZxoWVIJQ6RGjN1ZA/y1IYXDybYc\nIe3vlobMNcuj1a1Oa9JkS9eg4Hd1xRRsREqsIv0rcMXWLOBBAGGfJrTVCTp0YsHG\nOlKWm9pqSLfSZta2R8ULIMyBEYgAi410LNxGdWXbaxbhlZAMS+POrofVQBKwjPzD\nnZhXrE3NKGPy3YW2Shgx/hAwXf4qXWdcA8zpfsgX3xUwhrLCjTFipaKkCJP1s+kc\nsRJ/Ou9RJhvgQqqOFQB6tCL1bPK3OzsGWaTlbEAHRcc7AYUCAwEAAaAAMA0GCSqG\nSIb3DQEBCwUAA4IBAQBtG7zuAHbcHNPd7ZYqqxNFWn0mFw6TuEfF+77aSQnUgk1a\nt2LaxJg+cTVVtC0dE03ita0eWfTF23WItqiDxk2kSaI4HQWgNV6M0EYVYhykrBhe\ngUTS3g3b2ibDVdgrUDQesaF2EEMRXjtYlyA2LimKPrfsPmybFBOosJPrH+YMHpK+\ndTB5X66qDdwuFo0CB3sfadIrSGdDYHurARheM83WS6qCe+UDydduBbzDC2QfBcje\n8XK1ZU7+pKD8XDefVlsWxYyjcKbQlfv7m1FBlHTH8fFNlrvof+slPChLafLvFW1G\n8LaF8Z5pcfz+3opsCMjgNLxZNO28yOAREjTouqiY\n-----END CERTIFICATE REQUEST-----\n' req = csr.CertificationRequest.load(pem.unarmor(csr_pem)[2]) verified = verify_csr(req) self.assertFalse(verified)
def test_verify_ecdsa_csr(self): csr_pem = b'-----BEGIN CERTIFICATE REQUEST-----\nMIIBiDCB6gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh\nMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGbMBAGByqGSM49AgEG\nBSuBBAAjA4GGAAQBi2M5M4iMZhmBeXx+tGTdqP3MOcDwARhz9we41DHD4GLL7sKO\nR2erxXJbO0L1kpe2EKPWfP9xF1cmMjqoi6K15lgAV5uGTetsr7ITFrNzC+k2mEWX\ntpS/9l3goWq6icrSziZQBNNSqGAeCGbMxrtiblxGpKHE7jLN+ErdX2Jh064in3Gg\nADAKBggqhkjOPQQDAgOBjAAwgYgCQgGtS2jmm4lM38Xxt1aXu8/rTbw+W2gsSQMx\nMLBoyaVJ5sbKpQNgvyNN17MvLnyR0J7NYJSmR+3n7jrWl3S9Q4s85AJCANqjvXn+\nHjCGebrPBF7eaw1dInjDLmO4NqK8+ro028JEEj/JO8aYkZZjUFDFJM3G57Ja/p41\n5ozzR5r8dQ64K/aX\n-----END CERTIFICATE REQUEST-----\n' req = csr.CertificationRequest.load(pem.unarmor(csr_pem)[2]) verified = verify_csr(req) self.assertTrue(verified)
def _grab_crl(user_agent, url, timeout): """ Fetches a CRL and parses it :param user_agent: A unicode string of the user agent to use when fetching the URL :param url: A unicode string of the URL to fetch the CRL from :param timeout: The number of seconds after which an HTTP request should timeout :return: An asn1crypto.crl.CertificateList object """ if sys.version_info < (3,): url = util.iri_to_uri(url) request = Request(url) request.add_header(b'Accept', b'application/pkix-crl') request.add_header(b'User-Agent', user_agent.encode('iso-8859-1')) response = urlopen(request, None, timeout) data = response.read() if pem.detect(data): _, _, data = pem.unarmor(data) return crl.CertificateList.load(data)
def _validate_unarmor(self, certs, var_name): """ Takes a list of byte strings or asn1crypto.x509.Certificates objects, validates and loads them while unarmoring any PEM-encoded contents :param certs: A list of byte strings or asn1crypto.x509.Certificate objects :param var_name: A unicode variable name to use in any TypeError exceptions :return: A list of asn1crypto.x509.Certificate objects """ output = [] for cert in certs: if isinstance(cert, x509.Certificate): output.append(cert) else: if not isinstance(cert, byte_cls): raise TypeError(pretty_message( ''' %s must contain only byte strings or asn1crypto.x509.Certificate objects, not %s ''', var_name, type_name(cert) )) if pem.detect(cert): _, _, cert = pem.unarmor(cert) output.append(x509.Certificate.load(cert)) return output
def prepend(self, cert): """ Prepends a cert to the path. This should be the issuer of the previously prepended cert. :param cert: An asn1crypto.x509.Certificate object or a byte string :return: The current ValidationPath object, for chaining """ if not isinstance(cert, x509.Certificate): if not isinstance(cert, byte_cls): raise TypeError(pretty_message( ''' cert must be a byte string or an asn1crypto.x509.Certificate object, not %s ''', type_name(cert) )) if pem.detect(cert): _, _, cert = pem.unarmor(cert) cert = x509.Certificate.load(cert) if cert.issuer_serial in self._cert_hashes: raise DuplicateCertificateError() self._cert_hashes.add(cert.issuer_serial) self._certs.insert(0, cert) return self
def extract_from_system(cert_callback=None): """ Extracts trusted CA certs from the system CA cert bundle :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. :return: A list of 3-element tuples: - 0: a byte string of a DER-encoded certificate - 1: a set of unicode strings that are OIDs of purposes to trust the certificate for - 2: a set of unicode strings that are OIDs of purposes to reject the certificate for """ all_purposes = '2.5.29.37.0' ca_path = system_path() output = [] with open(ca_path, 'rb') as f: for armor_type, _, cert_bytes in unarmor(f.read(), multiple=True): # Without more info, a certificate is trusted for all purposes if armor_type == 'CERTIFICATE': if cert_callback: cert_callback(Certificate.load(cert_bytes), None) output.append((cert_bytes, set(), set())) # The OpenSSL TRUSTED CERTIFICATE construct adds OIDs for trusted # and rejected purposes, so we extract that info. elif armor_type == 'TRUSTED CERTIFICATE': cert, aux = TrustedCertificate.load(cert_bytes) reject_all = False trust_oids = set() reject_oids = set() for purpose in aux['trust']: if purpose.dotted == all_purposes: trust_oids = set([purpose.dotted]) break trust_oids.add(purpose.dotted) for purpose in aux['reject']: if purpose.dotted == all_purposes: reject_all = True break reject_oids.add(purpose.dotted) if reject_all: if cert_callback: cert_callback(cert, 'explicitly distrusted') continue if cert_callback: cert_callback(cert, None) output.append((cert.dump(), trust_oids, reject_oids)) return output
def test_get_list_mutate(self): certs = trust_list.get_list() certs2 = trust_list.get_list() with open(digicert_ca_path, 'rb') as f: _, _, digicert_ca_bytes = pem.unarmor(f.read()) digicert_ca_cert = x509.Certificate.load(digicert_ca_bytes) certs.append(digicert_ca_cert) self.assertNotEqual(certs2, certs)
def unarmor(self, relative_path, expected_bytes_filename, expected_type_name, expected_headers): with open(os.path.join(fixtures_dir, relative_path), 'rb') as f: byte_string = f.read() type_name, headers, decoded_bytes = pem.unarmor(byte_string) self.assertEqual(expected_type_name, type_name) self.assertEqual(expected_headers, headers) with open(os.path.join(fixtures_dir, expected_bytes_filename), 'rb') as f: expected_bytes = f.read() self.assertEqual(expected_bytes, decoded_bytes)
def from_pem(cls, pem_string): """Read a single PEM-encoded certificate from a string. Args: pem_string: the certificate string. Returns: a Certificate object. """ _, _, der_bytes = pem.unarmor(pem_string) return cls.from_der(der_bytes)
def test_verify_dsa_csr(self): """ I don't know why this fails, but I don't think we actually care about DSA. """ csr_pem = b'-----BEGIN CERTIFICATE REQUEST-----\nMIICSzCCAggCAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx\nITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAbgwggEsBgcqhkjO\nOAQBMIIBHwKBgQD4dXOBuPkpOYn3HBjnzSgfZ6+rTNJxfHHebNRH6JkeojvVvssq\ngMeUjJwldVygHM3ewJbHcCTsUYSFXZwtR1F3CHY7NnC8ybYuot4djmDPNcSGNPRY\nmuNfcTaK7VLRGY396kRrL1ExqtZ0eb/1uOFcHz9s3udhqTLRyOWy9GiYiQIVAPZQ\njMRMUgQbAnsfgqFrSXDXhgXrAoGBAI5FAQN8NN++QJUlnEVSIDtdihIwNJ2tAKgS\nFViPne5IDzcKRqIYxwr1Ci7OxinsGMFZqXjT5Y7IlhYosONMzoPXxYXV0gJjS0bj\nxMZrCMHpyuXPN0AyScJpw/hWbVuHcfOVM8/2Nwzpx8VaLYhXTPtWuIsYrP4+CMWI\njr0nzRwaA4GFAAKBgQDl+UDgDprQdutjIhmjEdsyB36rANFoOQDjKVL/Vj6uDbqi\nWfn54qNV7MnI3pAk/nZPh30OmcpSnl0m5E522b63q7WV2tzLlKO7vPOipJyX9GH8\nP/71H/g6UGRbLSVkApWqOw3z+ijxN9bvM+zB8ihXTwlQ96OO+3XmZc1QWjP63aAA\nMAsGCWCGSAFlAwQDAgMwADAtAhUAmYbJM2jyLqRxWO2hgJNC2zqbnsACFDKcB0gM\nW/IY3ZafXVi27kP4dEmr\n-----END CERTIFICATE REQUEST-----\n' req = csr.CertificationRequest.load(pem.unarmor(csr_pem)[2]) verified = verify_csr(req) self.assertTrue(verified)
def certs_from_pem(pem_string): """Read multiple PEM-encoded certificates from a string. Args: pem_string: the certificate string. Yields: Certificate objects. """ for _, _, der_bytes in pem.unarmor(pem_string, multiple=True): yield Certificate.from_der(der_bytes)
def test_fetch_crl(self): with open(os.path.join(fixtures_dir, 'GeoTrust_EV_SSL_CA_-_G4.crt'), 'rb') as f: file_bytes = f.read() if pem.detect(file_bytes): _, _, file_bytes = pem.unarmor(file_bytes) intermediate = x509.Certificate.load(file_bytes) crls = crl_client.fetch(intermediate, timeout=3) context = ValidationContext(crls=crls) registry = context.certificate_registry path = registry.build_paths(intermediate)[0] verify_crl(intermediate, path, context)
def test_fetch_ocsp(self): with open(os.path.join(fixtures_dir, 'GeoTrust_EV_SSL_CA_-_G4.crt'), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) intermediate = x509.Certificate.load(cert_bytes) registry = CertificateRegistry() path = registry.build_paths(intermediate)[0] issuer = path.find_issuer(intermediate) ocsp_response = ocsp_client.fetch(intermediate, issuer, timeout=3) context = ValidationContext(ocsps=[ocsp_response]) verify_ocsp_response(intermediate, path, context)
def _unarmor_pem(data, password=None): """ Removes PEM-encoding from a public key, private key or certificate. If the private key is encrypted, the password will be used to decrypt it. :param data: A byte string of the PEM-encoded data :param password: A byte string of the encryption password, or None :return: A 3-element tuple in the format: (key_type, algorithm, der_bytes). The key_type will be a unicode string of "public key", "private key" or "certificate". The algorithm will be a unicode string of "rsa", "dsa" or "ec". """ object_type, headers, der_bytes = pem.unarmor(data) type_regex = '^((DSA|EC|RSA) PRIVATE KEY|ENCRYPTED PRIVATE KEY|PRIVATE KEY|PUBLIC KEY|RSA PUBLIC KEY|CERTIFICATE)' armor_type = re.match(type_regex, object_type) if not armor_type: raise ValueError(pretty_message( ''' data does not seem to contain a PEM-encoded certificate, private key or public key ''' )) pem_header = armor_type.group(1) data = data.strip() # RSA private keys are encrypted after being DER-encoded, but before base64 # encoding, so they need to be hanlded specially if pem_header in set(['RSA PRIVATE KEY', 'DSA PRIVATE KEY', 'EC PRIVATE KEY']): algo = armor_type.group(2).lower() return ('private key', algo, _unarmor_pem_openssl_private(headers, der_bytes, password)) key_type = pem_header.lower() algo = None if key_type == 'encrypted private key': key_type = 'private key' elif key_type == 'rsa public key': key_type = 'public key' algo = 'rsa' return (key_type, algo, der_bytes)
def read_cert_bundle(self, ca_bundle_file, storage=None): """ Reads a certificate file including certificates in PEM format """ if storage is None: storage = SnowflakeOCSP.ROOT_CERTIFICATES_DICT logger.debug('reading certificate bundle: %s', ca_bundle_file) with open(ca_bundle_file, 'rb') as all_certs: # don't lock storage from asn1crypto import pem pem_certs = pem.unarmor(all_certs.read(), multiple=True) for type_name, _, der_bytes in pem_certs: if type_name == 'CERTIFICATE': crt = Certificate.load(der_bytes) storage[crt.subject.sha256] = crt
def _list_certificates(directory): for filename in os.listdir(directory): if filename.endswith(".pem"): path = os.path.join(directory, filename) with open(path, "rb") as fh: buf = fh.read() header, _, der_bytes = pem.unarmor(buf) cert = x509.Certificate.load(der_bytes) server = False for extension in cert["tbs_certificate"]["extensions"]: if extension["extn_id"].native == "extended_key_usage": if "server_auth" in extension["extn_value"].native: server = True yield cert.subject.native[ "common_name"], path, buf, cert, server
def __init__( self, expected_measurement=None, accept_debug=False, accept_configuration_needed=False, accept_group_out_of_date=False, ): self.expected_measurement = expected_measurement self.accept_debug = accept_debug self.accept_configuration_needed = accept_configuration_needed self.accept_group_out_of_date = accept_group_out_of_date trust_roots = [] for _, _, der_bytes in pem.unarmor(intel_sgx_root_ca, multiple=True): trust_roots.append(der_bytes) self.context = ValidationContext(trust_roots=trust_roots)
def test_fetch_crl(self): with open( os.path.join(fixtures_dir, 'digicert-sha2-secure-server-ca.crt'), 'rb') as f: file_bytes = f.read() if pem.detect(file_bytes): _, _, file_bytes = pem.unarmor(file_bytes) intermediate = x509.Certificate.load(file_bytes) crls = crl_client.fetch(intermediate, timeout=3) context = ValidationContext(crls=crls) registry = context.certificate_registry path = registry.build_paths(intermediate)[0] verify_crl(intermediate, path, context)
def __init__(self, end_entity_cert, intermediate_certs=None, validation_context=None): """ :param end_entity_cert: An asn1crypto.x509.Certificate object or a byte string of the DER or PEM-encoded X.509 end-entity certificate to validate :param intermediate_certs: None or a list of asn1crypto.x509.Certificate objects or a byte string of a DER or PEM-encoded X.509 certificate. Used in constructing certificate paths for validation. :param validation_context: A certvalidator.context.ValidationContext() object that controls validation options """ if not isinstance(end_entity_cert, Certificate): if not isinstance(end_entity_cert, byte_cls): raise TypeError(pretty_message( ''' end_entity_cert must be a byte string or an instance of asn1crypto.x509.Certificate, not %s ''', type_name(end_entity_cert) )) if pem.detect(end_entity_cert): _, _, end_entity_cert = pem.unarmor(end_entity_cert) end_entity_cert = Certificate.load(end_entity_cert) if validation_context is None: validation_context = ValidationContext() if not isinstance(validation_context, ValidationContext): raise TypeError(pretty_message( ''' validation_context must be an instance of certvalidator.context.ValidationContext, not %s ''', type_name(validation_context) )) if intermediate_certs is not None: certificate_registry = validation_context.certificate_registry for intermediate_cert in intermediate_certs: certificate_registry.add_other_cert(intermediate_cert) self._context = validation_context self._certificate = end_entity_cert
def build_paths(self, end_entity_cert): """ Builds a list of ValidationPath objects from a certificate in the operating system trust store to the end-entity certificate :param end_entity_cert: A byte string of a DER or PEM-encoded X.509 certificate, or an instance of asn1crypto.x509.Certificate :return: A list of certvalidator.path.ValidationPath objects that represent the possible paths from the end-entity certificate to one of the CA certs. """ if not isinstance(end_entity_cert, byte_cls) and not isinstance(end_entity_cert, x509.Certificate): raise TypeError(pretty_message( ''' end_entity_cert must be a byte string or an instance of asn1crypto.x509.Certificate, not %s ''', type_name(end_entity_cert) )) if isinstance(end_entity_cert, byte_cls): if pem.detect(end_entity_cert): _, _, end_entity_cert = pem.unarmor(end_entity_cert) end_entity_cert = x509.Certificate.load(end_entity_cert) path = ValidationPath(end_entity_cert) paths = [] failed_paths = [] self._walk_issuers(path, paths, failed_paths) if len(paths) == 0: cert_name = end_entity_cert.subject.human_friendly missing_issuer_name = failed_paths[0].first.issuer.human_friendly raise PathBuildingError(pretty_message( ''' Unable to build a validation path for the certificate "%s" - no issuer matching "%s" was found ''', cert_name, missing_issuer_name )) return paths
def sign(common_name, overwrite=False): """ Sign certificate signing request by it's common name """ req_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem") with open(req_path) as fh: csr_buf = fh.read() header, _, der_bytes = pem.unarmor(csr_buf) csr = CertificationRequest.load(der_bytes) # Sign with function below cert, buf = _sign(csr, csr_buf, overwrite) os.unlink(req_path) return cert, buf
def pem_to_der(cert: bytes, return_multiple: bool = True): """Convert a given certificate or list to PEM format.""" # initialize the certificate array cert_list = [] # If certificate is in DER then un-armour it if pem.detect(cert): for _, _, der_bytes in pem.unarmor(cert, multiple=True): cert_list.append(der_bytes) else: cert_list.append(cert) # return multiple if return_multiple is set else first element if return_multiple: return cert_list return cert_list.pop()
def _check_certificate(self, certificate): _, _, certificate_bytes = pem.unarmor( certificate.encode(), multiple=False) certificate = x509.Certificate.load(certificate_bytes) serialnumber = certificate.serial_number cert_client = pki.cert.CertClient(self.get_connection()) res = cert_client.review_cert(serialnumber) logger.debug("Dogtag: Respuesta Validano certificado: "+repr(res)) ca_cert_info = self.issuer_dn_to_dic(res.issuer_dn) user_cert_info = self.extract_dic_from_X509Name( certificate.issuer.native, ca_cert_info) dev = res.status == 'VALID' and ca_cert_info == user_cert_info logger.info("Dogtag: validate cert %r == %r" % (serialnumber, dev)) return dev
def test_fetch_ocsp(self): with open( os.path.join(fixtures_dir, 'digicert-sha2-secure-server-ca.crt'), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) intermediate = x509.Certificate.load(cert_bytes) registry = CertificateRegistry() path = registry.build_paths(intermediate)[0] issuer = path.find_issuer(intermediate) ocsp_response = ocsp_client.fetch(intermediate, issuer, timeout=3) context = ValidationContext(ocsps=[ocsp_response]) verify_ocsp_response(intermediate, path, context)
def revoke_certificate(self, certificate): # Fixme: Esta función abre y reconstruye el crl # El problema es que la concurrencia puede afectar la reconstrucción # del crl. # Esto podría ayudar https://github.com/ambitioninc/django-db-mutex # Fixme: Este método no es eficiente pues requiere reconstruir otra # lista y pasar los valores de la anterior cuando debería ser # solamente agregar el nuevo valor, pero no logré descifrar como # hacerlo _, _, certificate_bytes = pem.unarmor( certificate.encode(), multiple=False) certificate = x509.Certificate.load(certificate_bytes) ca_private_key = asymmetric.load_private_key( self.ca_key, password=settings.CA_KEY_PASSWD) ca_certificate = asymmetric.load_certificate(self.ca_crt) with open(settings.CA_CRL, 'rb') as f: cert_list = crl.CertificateList.load(f.read()) builder = CertificateListBuilder( cert_list.issuing_distribution_point_value.native[ 'distribution_point'][0], ca_certificate, 1000 ) for revoked_cert in cert_list[ 'tbs_cert_list']['revoked_certificates']: revoked_cert_serial = revoked_cert['user_certificate'].native revoked_time = revoked_cert['revocation_date'].native reason = revoked_cert['crl_entry_extensions'][0][ 'extn_value'].native builder.add_certificate(revoked_cert_serial, revoked_time, reason) builder.add_certificate(certificate.serial_number, timezone.now(), "cessation_of_operation") crl_list = builder.build(ca_private_key) with open(settings.CA_CRL, 'wb') as f: f.write(crl_list.dump()) logger.info("SimpleCA: revoke certificate, don't make anything")
def store_request(buf, overwrite=False, address="", user=""): """ Store CSR for later processing """ if not buf: raise ValueError("No signing request supplied") if pem.detect(buf): header, _, der_bytes = pem.unarmor(buf) csr = CertificationRequest.load(der_bytes) else: csr = CertificationRequest.load(buf) buf = pem_armor_csr(csr) common_name = csr["certification_request_info"]["subject"].native["common_name"] if not re.match(const.RE_COMMON_NAME, common_name): raise ValueError("Invalid common name %s" % repr(common_name)) request_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem") # If there is cert, check if it's the same if os.path.exists(request_path) and not overwrite: if open(request_path, "rb").read() == buf: raise errors.RequestExists("Request already exists") else: raise errors.DuplicateCommonNameError("Another request with same common name already exists") else: with open(request_path + ".part", "wb") as fh: fh.write(buf) os.rename(request_path + ".part", request_path) attach_csr = buf, "application/x-pem-file", common_name + ".csr" mailer.send("request-stored.md", attachments=(attach_csr,), common_name=common_name) setxattr(request_path, "user.request.address", address) setxattr(request_path, "user.request.user", user) try: hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(address) except (socket.herror, OSError): # Failed to resolve hostname or resolved to multiple pass else: setxattr(request_path, "user.request.hostname", hostname) return request_path, csr, common_name
def unwrap_pkcs8(blob): if not pem.detect(blob): return _, _, der_bytes = pem.unarmor(blob) data = EncryptedPrivateKeyInfo.load(der_bytes).native if "encryption_algorithm" not in data: return if "encrypted_data" not in data: return if "algorithm" not in data["encryption_algorithm"]: return if data["encryption_algorithm"]["algorithm"] != "pbes2": sys.stderr.write( "[%s] encryption_algorithm <%s> is not supported currently!\n" % (sys.argv[0], data["encryption_algorithm"]["algorithm"])) return # encryption data encrypted_data = data["encrypted_data"] # KDF params = data["encryption_algorithm"]["parameters"] kdf = params["key_derivation_func"] if kdf["algorithm"] != "pbkdf2": sys.stderr.write( "[%s] kdf algorithm <%s> is not supported currently!\n" % (sys.argv[0], kdf["algorithm"])) return kdf_params = kdf["parameters"] salt = kdf_params["salt"] iterations = kdf_params["iteration_count"] # Cipher cipher_params = params["encryption_scheme"] cipher = cipher_params["algorithm"] iv = cipher_params["parameters"] if cipher != "tripledes_3key": sys.stderr.write("[%s] cipher <%s> is not supported currently!\n" % (sys.argv[0], cipher)) return sys.stdout.write("$PEM$1$1$%s$%s$%s$%d$%s\n" % (salt.encode("hex"), iterations, iv.encode("hex"), len(encrypted_data), encrypted_data.encode("hex")))
def build_paths(self, end_entity_cert): """ Builds a list of ValidationPath objects from a certificate in the operating system trust store to the end-entity certificate :param end_entity_cert: A byte string of a DER or PEM-encoded X.509 certificate, or an instance of asn1crypto.x509.Certificate :return: A list of pyhanko_certvalidator.path.ValidationPath objects that represent the possible paths from the end-entity certificate to one of the CA certs. """ if not isinstance(end_entity_cert, byte_cls) and not isinstance( end_entity_cert, x509.Certificate): raise TypeError( pretty_message( ''' end_entity_cert must be a byte string or an instance of asn1crypto.x509.Certificate, not %s ''', type_name(end_entity_cert))) if isinstance(end_entity_cert, byte_cls): if pem.detect(end_entity_cert): _, _, end_entity_cert = pem.unarmor(end_entity_cert) end_entity_cert = x509.Certificate.load(end_entity_cert) path = ValidationPath(end_entity_cert) paths = [] failed_paths = [] self._walk_issuers(path, paths, failed_paths) if len(paths) == 0: cert_name = end_entity_cert.subject.human_friendly missing_issuer_name = failed_paths[0].first.issuer.human_friendly raise PathBuildingError( pretty_message( ''' Unable to build a validation path for the certificate "%s" - no issuer matching "%s" was found ''', cert_name, missing_issuer_name)) return paths
def get_revoked(serial): if isinstance(serial, str): serial = int(serial, 16) path = os.path.join(config.REVOKED_DIR, "%040x.pem" % serial) with open(path, "rb") as fh: buf = fh.read() header, _, der_bytes = pem.unarmor(buf) cert = x509.Certificate.load(der_bytes) try: reason = getxattr(path, "user.revocation.reason").decode("ascii") except IOError: # TODO: make sure it's not required reason = "key_compromise" return path, buf, cert, \ cert["tbs_certificate"]["validity"]["not_before"].native.replace(tzinfo=None), \ cert["tbs_certificate"]["validity"]["not_after"].native.replace(tzinfo=None), \ datetime.utcfromtimestamp(os.stat(path).st_ctime), \ reason
def test_unarmor_multiple(self): data = self.unarmor_armor_files() input_data = b'' der_data = [] for pem_file, der_file in ((data[0][0], data[0][1]), (data[1][0], data[1][1])): with open(os.path.join(fixtures_dir, pem_file), 'rb') as f: input_data += f.read() + b'\n' with open(os.path.join(fixtures_dir, der_file), 'rb') as f: der_data.append(f.read()) i = 0 for name, headers, der_bytes in pem.unarmor(input_data, True): self.assertEqual('CERTIFICATE', name) self.assertEqual({}, headers) self.assertEqual(der_data[i], der_bytes) i += 1 self.assertEqual(2, i)
def extract_from_system(): """ Extracts trusted CA certs from the system CA cert bundle :return: A list of byte strings - each a DER-encoded certificate """ ca_path = system_path() output = [] with open(ca_path, 'rb') as f: for entry_info in unarmor(f.read(), multiple=True): if entry_info[0] == 'CERTIFICATE': output.append(entry_info[2]) return output
def on_put(self, req, resp): # Consume token now = time() timestamp = req.get_param_as_int("t", required=True) username = req.get_param("u", required=True) user = User.objects.get(username) csum = hashlib.sha256() csum.update(config.TOKEN_SECRET) csum.update(username.encode("ascii")) csum.update(str(timestamp).encode("ascii")) margin = 300 # Tolerate 5 minute clock skew as Kerberos does if csum.hexdigest() != req.get_param("c", required=True): raise falcon.HTTPForbidden( "Forbidden", "Invalid token supplied, did you copy-paste link correctly?") if now < timestamp - margin: raise falcon.HTTPForbidden( "Forbidden", "Token not valid yet, are you sure server clock is correct?") if now > timestamp + margin + config.TOKEN_LIFETIME: raise falcon.HTTPForbidden("Forbidden", "Token expired") # At this point consider token to be legitimate body = req.stream.read(req.content_length) header, _, der_bytes = pem.unarmor(body) csr = CertificationRequest.load(der_bytes) common_name = csr["certification_request_info"]["subject"].native[ "common_name"] assert common_name == username or common_name.startswith( username + "@"), "Invalid common name %s" % common_name try: _, resp.body = self.authority._sign( csr, body, profile="default", overwrite=config.TOKEN_OVERWRITE_PERMITTED) resp.set_header("Content-Type", "application/x-pem-file") logger.info("Autosigned %s as proven by token ownership", common_name) except FileExistsError: logger.info("Won't autosign duplicate %s", common_name) raise falcon.HTTPConflict( "Certificate with such common name (CN) already exists", "Will not overwrite existing certificate signing request, explicitly delete existing one and try again" )
def add_other_cert(self, cert): """ Allows adding an "other" cert that is obtained from doing revocation check via OCSP or CRL, or some other method :param cert: An asn1crypto.x509.Certificate object or a byte string of a DER or PEM-encoded certificate :return: A boolean indicating if the certificate was added - will return False if the certificate was already present """ if not isinstance(cert, x509.Certificate): if not isinstance(cert, byte_cls): raise TypeError(pretty_message( ''' cert must be a byte string or an instance of asn1crypto.x509.Certificate, not %s ''', type_name(cert) )) if pem.detect(cert): _, _, cert = pem.unarmor(cert) cert = x509.Certificate.load(cert) hashable = cert.subject.hashable if hashable not in self._subject_map: self._subject_map[hashable] = [] # Don't add the cert if we already have it else: serial_number = cert.serial_number for existing_cert in self._subject_map[hashable]: if existing_cert.serial_number == serial_number: return False self._subject_map[hashable].append(cert) if cert.key_identifier: self._key_identifier_map[cert.key_identifier] = cert else: self._key_identifier_map[cert.public_key.sha1] = cert return True
def parse_cert(byte_data): if not isinstance(byte_data, bytes): raise TypeError("byte_data must be a byte string") if byte_data[0] == 'M': byte_data = base64.b64decode(byte_data) if pem.detect(byte_data): file_type, headers, byte_data = pem.unarmor(byte_data) if file_type != 'CERTIFICATE': raise TypeError( "CERTIFICATE expected, but got {}".format(file_type)) x509cert = x509.Certificate.load(byte_data) return x509cert
def add_other_cert(self, cert): """ Allows adding an "other" cert that is obtained from doing revocation check via OCSP or CRL, or some other method :param cert: An asn1crypto.x509.Certificate object or a byte string of a DER or PEM-encoded certificate :return: A boolean indicating if the certificate was added - will return False if the certificate was already present """ if not isinstance(cert, x509.Certificate): if not isinstance(cert, byte_cls): raise TypeError( pretty_message( ''' cert must be a byte string or an instance of asn1crypto.x509.Certificate, not %s ''', type_name(cert))) if pem.detect(cert): _, _, cert = pem.unarmor(cert) cert = x509.Certificate.load(cert) hashable = cert.subject.hashable if hashable not in self._subject_map: self._subject_map[hashable] = [] # Don't add the cert if we already have it else: serial_number = cert.serial_number for existing_cert in self._subject_map[hashable]: if existing_cert.serial_number == serial_number: return False self._subject_map[hashable].append(cert) if cert.key_identifier: self._key_identifier_map[cert.key_identifier] = cert else: self._key_identifier_map[cert.public_key.sha1] = cert return True
def decode(byte_string): try: if pem.detect(byte_string): type_name, headers, decoded_bytes = pem.unarmor(byte_string) byte_string = decoded_bytes key = OneAsymmetricKey.load(byte_string) if key['privateKeyAlgorithm']['algorithm'].native != 'Curve25519' and key['privateKeyAlgorithm']['algorithm'].native != 'Ed25519': raise NotACurve25519Key key = PrivateKey.load(key['privateKey'].native) if len(key.native) != 32: raise NotA256BitCurve25519Key except Exception as e: raise FailedToParseCurve25519Key return key.native
def validate_vmc(self): try: end_entity_cert = None intermediates = [] with open(self.vmc_file, 'rb') as f: readfile = f.read() # Create parsed data for expiry and embedded svg check self.parsed_vmc = self.parse_vmc_cert(readfile) for type_name, headers, der_bytes in pem.unarmor( readfile, multiple=True): if end_entity_cert is None: end_entity_cert = der_bytes else: intermediates.append(der_bytes) validator = CertificateValidator(end_entity_cert, intermediates) validated = validator.validate_usage( set(['digital_signature']) # ,extended_key_usage=set(["server_auth", "client_auth"]) ) if validated: print("Certificate Validated") except errors.PathValidationError as PathValidationError: self.vmc_response["errors"].append("Warning: " + str(PathValidationError)) print(PathValidationError) except errors.RevokedError as RevokedError: self.vmc_response["errors"].append( "Warning: Certificate Revoked.\n" + str(RevokedError)) print(RevokedError) except errors.InvalidCertificateError as InvalidCertificateError: self.vmc_response["errors"].append( "Warning: Certificate Is Invalid.\n" + str(InvalidCertificateError)) print(InvalidCertificateError) except errors.PathBuildingError as PathBuildingError: # self.vmc_response["errors"].append("Warning: Cannot Build Path.\n"+str(PathBuildingError)) print(PathBuildingError) except Exception as e: self.vmc_response["errors"].append( "Warning: Validation Exception.\n" + str(e)) print(e)
def unwrap_pkcs8(blob): if not pem.detect(blob): return _, _, der_bytes = pem.unarmor(blob) data = EncryptedPrivateKeyInfo.load(der_bytes).native if "encryption_algorithm" not in data: return if "encrypted_data" not in data: return if "algorithm" not in data["encryption_algorithm"]: return if data["encryption_algorithm"]["algorithm"] != "pbes2": sys.stderr.write("[%s] encryption_algorithm <%s> is not supported currently!\n" % (sys.argv[0], data["encryption_algorithm"]["algorithm"])) return # encryption data encrypted_data = data["encrypted_data"] # KDF params = data["encryption_algorithm"]["parameters"] kdf = params["key_derivation_func"] if kdf["algorithm"] != "pbkdf2": sys.stderr.write("[%s] kdf algorithm <%s> is not supported currently!\n" % (sys.argv[0], kdf["algorithm"])) return kdf_params = kdf["parameters"] salt = kdf_params["salt"] iterations = kdf_params["iteration_count"] # Cipher cipher_params = params["encryption_scheme"] cipher = cipher_params["algorithm"] iv = cipher_params["parameters"] if cipher != "tripledes_3key": sys.stderr.write("[%s] cipher <%s> is not supported currently!\n" % (sys.argv[0], cipher)) return sys.stdout.write("$PEM$1$1$%s$%s$%s$%d$%s\n" % (salt.encode("hex"), iterations, iv.encode("hex"), len(encrypted_data), encrypted_data.encode("hex")))
def _read_cert_bundle(ca_bundle_file): """ Reads a certificate file including certificates in PEM format """ from asn1crypto.x509 import Certificate from asn1crypto import pem certs = [] logger = getLogger(__name__) logger.debug('reading certificate bundle: %s', ca_bundle_file) all_certs = open(ca_bundle_file, 'rb').read() pem_certs = pem.unarmor(all_certs, multiple=True) for type_name, _, der_bytes in pem_certs: if type_name == 'CERTIFICATE': crt = Certificate.load(der_bytes) logger.debug("Found part of the chain..") certs.append(crt) return certs
def test_build_paths_custom_ca_certs(self): with open(os.path.join(fixtures_dir, 'codex.crt'), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) cert = x509.Certificate.load(cert_bytes) with open(os.path.join(fixtures_dir, 'GeoTrust_EV_SSL_CA_-_G4.crt'), 'rb') as f: other_certs = [f.read()] repo = CertificateRegistry(trust_roots=other_certs) paths = repo.build_paths(cert) self.assertEqual(1, len(paths)) path = paths[0] self.assertEqual(2, len(path)) self.assertEqual([ b'\xaa+\x03\x14\xafd.\x13\x0e\xd6\x92%\xe3\xff*\xba\xd7=b0', b"\xfcq\x7f\x98='\xcc\xb3D\xfbK\x85\xf0\x81\x8f\xab\xcb\xf0\x9b\x14" ], [item.subject.sha1 for item in path])
def cert_from_crt(crt_id, req_session, src_address): """ Get cert, pem and response code from crt id """ if DEBUG_MODE: log(f'Getting cert for crt id {crt_id} with source IP ' + src_address) new_source = source.SourceAddressAdapter(src_address) url = CRT_URL + str(crt_id) req_session.mount('https://', new_source) response = req_session.get(url) txt = response.text # pem error = response.status_code try: der_bytes = pem.unarmor(txt.encode('utf-8'))[2] cert = x509.Certificate.load(der_bytes) except: if DEBUG_MODE: log(f'Could not get certificate for crt id {crt_id}: error {error}', mode='error') return None, None, None return cert, txt, response.status_code
def on_put(self, req, resp): try: username, mail, created, expires, profile = self.manager.consume(req.get_param("token", required=True)) except RelationalMixin.DoesNotExist: raise falcon.HTTPForbidden("Forbidden", "No such token or token expired") body = req.stream.read(req.content_length) header, _, der_bytes = pem.unarmor(body) csr = CertificationRequest.load(der_bytes) common_name = csr["certification_request_info"]["subject"].native["common_name"] if not common_name.startswith(username + "@"): raise falcon.HTTPBadRequest("Bad requst", "Invalid common name %s" % common_name) try: _, resp.body = self.authority._sign(csr, body, profile=config.PROFILES.get(profile), overwrite=config.TOKEN_OVERWRITE_PERMITTED) resp.set_header("Content-Type", "application/x-pem-file") logger.info("Autosigned %s as proven by token ownership", common_name) except FileExistsError: logger.info("Won't autosign duplicate %s", common_name) raise falcon.HTTPConflict( "Certificate with such common name (CN) already exists", "Will not overwrite existing certificate signing request, explicitly delete existing one and try again")
def request_certificate(self, key_pem): # type: (str) -> bool """ Requests a new certificate for this server. key_pem: the PEM-encoded private key (byte string). Returns True if the request was accepted, raises ServerApiError otherwise. """ der = pem.unarmor(key_pem)[2] privkey = keys.PrivateKeyInfo.load(der) builder = CSRBuilder({'common_name': six.text_type(self.server_id)}, privkey.public_key_info) csr = builder.build(privkey) data = {'csr': b64encode(csr.dump())} http.post('server/csr/', data=data, auth=self._api_auth) return True
def sign(common_name, skip_notify=False, skip_push=False, overwrite=False, profile="default", signer=None): """ Sign certificate signing request by it's common name """ req_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem") with open(req_path, "rb") as fh: csr_buf = fh.read() header, _, der_bytes = pem.unarmor(csr_buf) csr = CertificationRequest.load(der_bytes) # Sign with function below cert, buf = _sign(csr, csr_buf, skip_notify, skip_push, overwrite, profile, signer) os.unlink(req_path) return cert, buf
def test_build_paths_custom_ca_certs(self): with open(os.path.join(fixtures_dir, 'mozilla.org.crt'), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) cert = x509.Certificate.load(cert_bytes) with open( os.path.join(fixtures_dir, 'digicert-sha2-secure-server-ca.crt'), 'rb') as f: other_certs = [f.read()] repo = CertificateRegistry(trust_roots=other_certs) paths = repo.build_paths(cert) self.assertEqual(1, len(paths)) path = paths[0] self.assertEqual(2, len(path)) self.assertEqual([ b"\x10_\xa6z\x80\x08\x9d\xb5'\x9f5\xce\x83\x0bC\x88\x9e\xa3\xc7\r", b'I\xac\x03\xf8\xf3Km\xca)V)\xf2I\x9a\x98\xbe\x98\xdc.\x81' ], [item.subject.sha1 for item in path])
def wrapped(resource, req, resp, *args, **kwargs): buf = req.get_header("X-SSL-CERT") if not buf: logger.info( "No TLS certificate presented to access administrative API call" ) raise falcon.HTTPForbidden( "Forbidden", "Machine not authorized to perform the operation") header, _, der_bytes = pem.unarmor( buf.replace("\t", "").encode("ascii")) cert = x509.Certificate.load(der_bytes) # TODO: validate serial for extension in cert["tbs_certificate"]["extensions"]: if extension["extn_id"].native == "extended_key_usage": if "server_auth" in extension["extn_value"].native: req.context["machine"] = cert.subject.native["common_name"] return func(resource, req, resp, *args, **kwargs) logger.info( "TLS authenticated machine '%s' not authorized to access administrative API", cert.subject.native["common_name"]) raise falcon.HTTPForbidden( "Forbidden", "Machine not authorized to perform the operation")
def test_build_paths_custom_ca_certs(self): with open(os.path.join(fixtures_dir, 'codex.crt'), 'rb') as f: cert_bytes = f.read() if pem.detect(cert_bytes): _, _, cert_bytes = pem.unarmor(cert_bytes) cert = x509.Certificate.load(cert_bytes) with open(os.path.join(fixtures_dir, 'GeoTrust_EV_SSL_CA_-_G4.crt'), 'rb') as f: other_certs = [f.read()] repo = CertificateRegistry(trust_roots=other_certs) paths = repo.build_paths(cert) self.assertEqual(1, len(paths)) path = paths[0] self.assertEqual(2, len(path)) self.assertEqual( [ b'\xaa+\x03\x14\xafd.\x13\x0e\xd6\x92%\xe3\xff*\xba\xd7=b0', b"\xfcq\x7f\x98='\xcc\xb3D\xfbK\x85\xf0\x81\x8f\xab\xcb\xf0\x9b\x14" ], [item.subject.sha1 for item in path] )
def _grab_crl(user_agent, url, timeout): """ Fetches a CRL and parses it :param user_agent: A unicode string of the user agent to use when fetching the URL :param url: A unicode string of the URL to fetch the CRL from :param timeout: The number of seconds after which an HTTP request should timeout :return: An asn1crypto.crl.CertificateList object """ headers = {'Accept': 'application/pkix-crl', 'User-Agent': user_agent} response = requests.get(url=url, timeout=timeout, headers=headers) if response.status_code != 200: raise RequestException(f"status code {response.status_code}") data = response.content if pem.detect(data): _, _, data = pem.unarmor(data) return crl.CertificateList.load(data)
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: # In the apksig repo:src/test/resources/com/android/apksig # for f in *.pem; do openssl x509 -in $f -noout -sha256 -fingerprint; done certfp = { 'dsa-1024.x509.pem': 'fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7', 'dsa-2048.x509.pem': '97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e', 'dsa-3072.x509.pem': '966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474', 'ec-p256.x509.pem': '6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599', 'ec-p384.x509.pem': '5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe', 'ec-p521.x509.pem': '69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da', 'rsa-1024_2.x509.pem': 'eba3685e799f59804684abebf0363e14ccb1c213e2b954a22669714ed97f61e9', 'rsa-1024.x509.pem': 'bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8', 'rsa-16384.x509.pem': 'f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601', 'rsa-2048_2.x509.pem': '681b0e56a796350c08647352a4db800cc44b2adc8f4c72fa350bd05d4d50264d', 'rsa-2048_3.x509.pem': 'bb77a72efc60e66501ab75953af735874f82cfe52a70d035186a01b3482180f3', 'rsa-2048.x509.pem': 'fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8', 'rsa-3072.x509.pem': '483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89', 'rsa-4096.x509.pem': '6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685', 'rsa-8192.x509.pem': '060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234', } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", "debuggable-boolean.apk", "debuggable-resource.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk" or \ apath == "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk": # Can not load as APK with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Test if the correct method returns True, while others return # False m_tests = {'1': a.is_signed_v1, '2': a.is_signed_v2, '3': a.is_signed_v3} # These APKs will raise an error excluded = [ "v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk", "v2-only-apk-sig-block-size-mismatch.apk", "v2-only-empty.apk", "v2-only-wrong-apk-sig-block-magic.apk", "v2-stripped.apk", "v2-stripped-with-ignorable-signing-schemes.apk", "v2v3-signed-v3-block-stripped.apk", "v3-only-empty.apk", "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk", "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk", "v3-stripped.apk", ] if apath[0] == "v" and apath not in excluded: methods = apath.split("-", 1)[0].split("v")[1:] for m, f in m_tests.items(): if m in methods: self.assertTrue(f()) else: self.assertFalse(f()) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v3() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual(hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual(hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h) if a.is_signed_v3(): print(apath) if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v3() elif apath == "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk" or \ apath == "v3-only-cert-and-public-key-mismatch.apk": cert = x509.Certificate.load(a.get_certificates_der_v3()[0]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) else: for c in a.get_certificates_der_v3(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: certfp = { "dsa-1024.x509.pem": "fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7", "dsa-2048.x509.pem": "97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e", "dsa-3072.x509.pem": "966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474", "ec-p256.x509.pem": "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599", "ec-p384.x509.pem": "5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe", "ec-p521.x509.pem": "69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da", "rsa-1024.x509.pem": "bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8", "rsa-16384.x509.pem": "f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601", "rsa-2048.x509.pem": "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8", "rsa-3072.x509.pem": "483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89", "rsa-4096.x509.pem": "6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685", "rsa-8192.x509.pem": "060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234", } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk": # Can not load as APK if sys.version_info.major == 2: # Different name in python2... with self.assertRaises(zipfile.BadZipfile): APK(os.path.join(root, apath)) else: with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual(hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual(hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)
def __init__(self, crl_pem: str): type_name, headers, der_bytes = asn1pem.unarmor(crl_pem.encode()) if type_name != 'X509 CRL': raise ValueError('This does not seem like a Certificate Revocation List.') self._crl = asn1crl.CertificateList.load(der_bytes)