From ad879a1fbccfa31fdedd69e3193e9bf12a15f943 Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Thu, 18 Aug 2016 08:50:38 -0400 Subject: [PATCH] Add certificate validation This change adds support for certificate validation, including certificate inspection utilities. Validating a certificate requires the certificate UUID of the certificate to validate, a set of UUIDs corresponding to the set of trusted certificates needed to validate the certificate, and a user context for authentication to the key manager. A new certificate verification context is included that is used to store the set of trusted certificates once they are loaded from the key manager. This context is used to validate the signing certificate, verifying that the certificate belongs to a valid certificate chain rooted in the set of trusted certificates. All new certificate utility code is added in a new module named certificate_utils. For more information on this work, see the spec: https://review.openstack.org/#/c/488541/ SecurityImpact DocImpact Change-Id: I8d7f43fb4c0573ac3681147eac213b369bbbcb3b Implements: blueprint nova-validate-certificates --- cursive/certificate_utils.py | 350 +++++++++++++++ cursive/signature_utils.py | 26 +- cursive/tests/unit/data/child_cert.pem | 87 ++++ cursive/tests/unit/data/grandchild_cert.pem | 87 ++++ cursive/tests/unit/data/grandparent_cert.pem | 34 ++ cursive/tests/unit/data/not_a_cert.txt | 1 + cursive/tests/unit/data/orphaned_cert.pem | 24 ++ cursive/tests/unit/data/parent_cert.pem | 107 +++++ cursive/tests/unit/data/self_signed_cert.der | Bin 0 -> 925 bytes cursive/tests/unit/data/self_signed_cert.pem | 22 + ...self_signed_cert_invalid_ca_constraint.pem | 22 + .../self_signed_cert_invalid_key_usage.pem | 22 + ...self_signed_cert_missing_ca_constraint.pem | 21 + .../self_signed_cert_missing_key_usage.pem | 21 + cursive/tests/unit/data/signed_cert.pem | 26 ++ cursive/tests/unit/test_certificate_utils.py | 403 ++++++++++++++++++ cursive/tests/unit/test_signature_utils.py | 49 +-- ...rtificate-validation-68a1ffbd5369a8d1.yaml | 37 ++ 18 files changed, 1281 insertions(+), 58 deletions(-) create mode 100644 cursive/certificate_utils.py create mode 100644 cursive/tests/unit/data/child_cert.pem create mode 100644 cursive/tests/unit/data/grandchild_cert.pem create mode 100644 cursive/tests/unit/data/grandparent_cert.pem create mode 100644 cursive/tests/unit/data/not_a_cert.txt create mode 100644 cursive/tests/unit/data/orphaned_cert.pem create mode 100644 cursive/tests/unit/data/parent_cert.pem create mode 100644 cursive/tests/unit/data/self_signed_cert.der create mode 100644 cursive/tests/unit/data/self_signed_cert.pem create mode 100644 cursive/tests/unit/data/self_signed_cert_invalid_ca_constraint.pem create mode 100644 cursive/tests/unit/data/self_signed_cert_invalid_key_usage.pem create mode 100644 cursive/tests/unit/data/self_signed_cert_missing_ca_constraint.pem create mode 100644 cursive/tests/unit/data/self_signed_cert_missing_key_usage.pem create mode 100644 cursive/tests/unit/data/signed_cert.pem create mode 100644 cursive/tests/unit/test_certificate_utils.py create mode 100644 releasenotes/notes/add-certificate-validation-68a1ffbd5369a8d1.yaml diff --git a/cursive/certificate_utils.py b/cursive/certificate_utils.py new file mode 100644 index 0000000..046f695 --- /dev/null +++ b/cursive/certificate_utils.py @@ -0,0 +1,350 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Support certificate validation.""" + +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography import x509, exceptions as cryptography_exceptions +from oslo_log import log as logging +from oslo_utils import timeutils + +from cursive import exception +from cursive import signature_utils + +LOG = logging.getLogger(__name__) + + +def is_within_valid_dates(certificate): + """Determine if the certificate is outside its valid date range. + + :param certificate: the cryptography certificate object + :return: False if the certificate valid time range does not include + now, True otherwise. + """ + # Get now in UTC, since certificate returns times in UTC + now = timeutils.utcnow() + + # Confirm the certificate valid time range includes now + if now < certificate.not_valid_before: + return False + elif now > certificate.not_valid_after: + return False + return True + + +def is_issuer(issuing_certificate, issued_certificate): + """Determine if the issuing cert is the parent of the issued cert. + + Determine if the issuing certificate is the parent of the issued + certificate by: + * conducting subject and issuer name matching, and + * verifying the signature of the issued certificate with the issuing + certificate's public key + + :param issuing_certificate: the cryptography certificate object that + is the potential parent of the issued certificate + :param issued_certificate: the cryptography certificate object that + is the potential child of the issuing certificate + :return: True if the issuing certificate is the parent of the issued + certificate, False otherwise. + """ + if (issuing_certificate is None) or (issued_certificate is None): + return False + elif issuing_certificate.subject != issued_certificate.issuer: + return False + else: + try: + verify_certificate_signature( + issuing_certificate, + issued_certificate + ) + except cryptography_exceptions.InvalidSignature: + # If verification fails, an exception is expected. + return False + return True + + +def can_sign_certificates(certificate, certificate_uuid=''): + """Determine if the certificate can sign other certificates. + + :param certificate: the cryptography certificate object + :param certificate_uuid: the uuid of the certificate + :return: False if the certificate cannot sign other certificates, + True otherwise. + """ + try: + basic_constraints = certificate.extensions.get_extension_for_oid( + x509.oid.ExtensionOID.BASIC_CONSTRAINTS + ).value + except x509.extensions.ExtensionNotFound: + LOG.debug( + "Certificate '%s' does not have a basic constraints extension.", + certificate_uuid) + return False + + try: + key_usage = certificate.extensions.get_extension_for_oid( + x509.oid.ExtensionOID.KEY_USAGE + ).value + except x509.extensions.ExtensionNotFound: + LOG.debug( + "Certificate '%s' does not have a key usage extension.", + certificate_uuid) + return False + + if basic_constraints.ca and key_usage.key_cert_sign: + return True + + if not basic_constraints.ca: + LOG.debug( + "Certificate '%s' is not marked as a CA in its basic constraints " + "extension.", + certificate_uuid) + if not key_usage.key_cert_sign: + LOG.debug( + "Certificate '%s' is not marked for verifying certificate " + "signatures in its key usage extension.", + certificate_uuid) + + return False + + +def verify_certificate_signature(signing_certificate, certificate): + """Verify that the certificate was signed correctly. + + :param signing_certificate: the cryptography certificate object used to + sign the certificate + :param certificate: the cryptography certificate object that was signed + by the signing certificate + :raises: cryptography.exceptions.InvalidSignature if certificate signature + verification fails. + """ + signature_hash_algorithm = certificate.signature_hash_algorithm + signature_bytes = certificate.signature + signer_public_key = signing_certificate.public_key() + + if isinstance(signer_public_key, rsa.RSAPublicKey): + verifier = signer_public_key.verifier( + signature_bytes, padding.PKCS1v15(), signature_hash_algorithm + ) + elif isinstance(signer_public_key, ec.EllipticCurvePublicKey): + verifier = signer_public_key.verifier( + signature_bytes, ec.ECDSA(signature_hash_algorithm) + ) + else: + verifier = signer_public_key.verifier( + signature_bytes, signature_hash_algorithm + ) + + verifier.update(certificate.tbs_certificate_bytes) + verifier.verify() + + +def verify_certificate(context, certificate_uuid, + trusted_certificate_uuids, + enforce_valid_dates=True, + enforce_signing_extensions=True, + enforce_path_length=True): + """Validate a certificate against a set of trusted certificates. + + From the key manager, load the set of trusted certificates and the + certificate to validate. Store the trusted certificates in a certificate + verification context. Use the context to verify that the certificate is + cryptographically linked to at least one of the trusted certificates. + + :param context: the user context for authentication + :param certificate_uuid: the uuid of a certificate to validate, stored in + the key manager + :param trusted_certificate_uuids: a list containing the uuids of trusted + certificates stored in the key manager + :param enforce_valid_dates: a boolean indicating whether date checking + should be enforced during certificate verification, defaults to + True + :param enforce_signing_extensions: a boolean indicating whether extension + checking should be enforced during certificate verification, + defaults to True + :param enforce_path_length: a boolean indicating whether path length + constraints should be enforced during certificate verification, + defaults to True + :raises: SignatureVerificationError if the certificate verification fails + for any reason. + """ + trusted_certificates = list() + for uuid in trusted_certificate_uuids: + try: + trusted_certificates.append( + (uuid, signature_utils.get_certificate(context, uuid)) + ) + except exception.SignatureVerificationError: + LOG.warning("Skipping trusted certificate: %(id)s" % {'id': uuid}) + + certificate = signature_utils.get_certificate(context, certificate_uuid) + certificate_context = CertificateVerificationContext( + trusted_certificates, + enforce_valid_dates=enforce_valid_dates, + enforce_signing_extensions=enforce_signing_extensions, + enforce_path_length=enforce_path_length + ) + certificate_context.update(certificate) + certificate_context.verify() + + +class CertificateVerificationContext(object): + """A collection of signing certificates. + + A collection of signing certificates that may be used to verify the + signatures of other certificates. + """ + + def __init__(self, certificate_tuples, enforce_valid_dates=True, + enforce_signing_extensions=True, + enforce_path_length=True): + self._signing_certificates = [] + for certificate_tuple in certificate_tuples: + certificate_uuid, certificate = certificate_tuple + if not isinstance(certificate, x509.Certificate): + LOG.error( + "A signing certificate must be an x509.Certificate object." + ) + continue + + if enforce_valid_dates: + if not is_within_valid_dates(certificate): + LOG.warning( + "Certificate '%s' is outside its valid date range and " + "cannot be used as a signing certificate.", + certificate_uuid) + continue + + if enforce_signing_extensions: + if not can_sign_certificates(certificate, certificate_uuid): + LOG.warning( + "Certificate '%s' is not configured to act as a " + "signing certificate. It will not be used as a " + "signing certificate.", + certificate_uuid) + continue + self._signing_certificates.append(certificate_tuple) + + self._signed_certificate = None + self._enforce_valid_dates = enforce_valid_dates + self._enforce_path_length = enforce_path_length + + def update(self, certificate): + """Process the certificate to be verified. + + Raises an exception if the certificate is invalid. Stores it + otherwise. + + :param certificate: the cryptography certificate to be verified + :raises: SignatureVerificationError if the certificate is not of the + right type or if it is outside its valid date range. + """ + if not isinstance(certificate, x509.Certificate): + raise exception.SignatureVerificationError( + "The certificate must be an x509.Certificate object." + ) + + if self._enforce_valid_dates: + if not is_within_valid_dates(certificate): + raise exception.SignatureVerificationError( + "The certificate is outside its valid date range." + ) + + self._signed_certificate = certificate + + def verify(self): + """Locate the certificate's signing certificate and verify it. + + Locate the certificate's signing certificate in the context + certificate cache, using both subject/issuer name matching and + signature verification. If the certificate is self-signed, verify that + it is also located in the context's certificate cache. Construct the + certificate chain from certificates in the context certificate cache. + Verify that the signing certificate can have a sufficient number of + child certificates to support the chain. + + :raises: SignatureVerificationError if certificate validation fails + for any reason, including mismatched signatures or a failure + to find the required signing certificate. + """ + signed_certificate = self._signed_certificate + certificate_chain = [('base', signed_certificate)] + + # Build the certificate chain. + while True: + signing_certificate_tuple = None + + # Search for the signing certificate + for certificate_tuple in self._signing_certificates: + _, candidate = certificate_tuple + if is_issuer(candidate, signed_certificate): + signing_certificate_tuple = certificate_tuple + break + + # If a valid signing certificate is found, prepare to find the + # next link in the certificate chain. Otherwise, raise an error. + if signing_certificate_tuple: + # If the certificate is self-signed, the root of the + # certificate chain has been found. Otherwise, repeat the + # verification process using the newly found signing + # certificate. + if signed_certificate == signing_certificate_tuple[1]: + break + else: + certificate_chain.insert(0, signing_certificate_tuple) + signed_certificate = signing_certificate_tuple[1] + else: + uuid = certificate_chain[0][0] + raise exception.SignatureVerificationError( + "Certificate chain building failed. Could not locate the " + "signing certificate for %s in the set of trusted " + "certificates." % + "the base certificate" if uuid == 'base' + else "certificate '%s'" % uuid + ) + + if self._enforce_path_length: + # Verify that each certificate's path length constraint allows + # for it to support the rest of the certificate chain. + for i in range(len(certificate_chain)): + certificate = certificate_chain[i][1] + + # No need to check the last certificate in the chain. + if certificate == certificate_chain[-1][1]: + break + + try: + constraints = certificate.extensions.get_extension_for_oid( + x509.oid.ExtensionOID.BASIC_CONSTRAINTS + ).value + except x509.extensions.ExtensionNotFound: + raise exception.SignatureVerificationError( + "Certificate validation failed. The signing " + "certificate '%s' does not have a basic constraints " + "extension." % certificate_chain[i][0] + ) + + # Path length only applies to non-self-issued intermediate + # certificates. Do not include the current or end certificates + # when computing path length. + chain_length = len(certificate_chain[i:]) + chain_length = (chain_length - 2) if chain_length > 2 else 0 + if constraints.path_length < chain_length: + raise exception.SignatureVerificationError( + "Certificate validation failed. The signing " + "certificate '%s' is not configured to support " + "certificate chains of sufficient " + "length." % certificate_chain[i][0] + ) diff --git a/cursive/signature_utils.py b/cursive/signature_utils.py index d676193..26cc674 100644 --- a/cursive/signature_utils.py +++ b/cursive/signature_utils.py @@ -26,7 +26,6 @@ from cryptography import x509 from oslo_log import log as logging from oslo_serialization import base64 from oslo_utils import encodeutils -from oslo_utils import timeutils from cursive import exception from cursive.i18n import _, _LE @@ -70,6 +69,7 @@ MASK_GEN_ALGORITHMS = { 'MGF1': padding.MGF1, } + # Required image property names (SIGNATURE, HASH_METHOD, KEY_TYPE, CERT_UUID) = ( 'img_signature', @@ -336,28 +336,4 @@ def get_certificate(context, signature_certificate_uuid): certificate = x509.load_der_x509_certificate(cert_data, default_backend()) - # verify the certificate - verify_certificate(certificate) - return certificate - - -def verify_certificate(certificate): - """Verify that the certificate has not expired. - - :param certificate: the cryptography certificate object - :raises: SignatureVerificationError if the certificate valid time range - does not include now - """ - # Get now in UTC, since certificate returns times in UTC - now = timeutils.utcnow() - - # Confirm the certificate valid time range includes now - if now < certificate.not_valid_before: - raise exception.SignatureVerificationError( - reason=_('Certificate is not valid before: %s UTC') - % certificate.not_valid_before) - elif now > certificate.not_valid_after: - raise exception.SignatureVerificationError( - reason=_('Certificate is not valid after: %s UTC') - % certificate.not_valid_after) diff --git a/cursive/tests/unit/data/child_cert.pem b/cursive/tests/unit/data/child_cert.pem new file mode 100644 index 0000000..bfe807b --- /dev/null +++ b/cursive/tests/unit/data/child_cert.pem @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 10 (0xa) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Parent + Validity + Not Before: Oct 3 18:02:45 2017 GMT + Not After : Oct 1 18:02:45 2027 GMT + Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Child + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b2:45:31:e4:99:12:1f:0f:c3:5b:78:98:39:a5: + d0:da:c6:9f:38:23:09:df:fd:35:b6:95:b6:37:5d: + b6:49:f2:a5:f1:62:75:62:41:09:9d:36:e5:53:c8: + 82:1a:5c:9d:2a:fd:03:9c:a9:00:6d:28:b3:29:bb: + cf:f3:eb:0f:5c:c9:81:8d:69:e1:04:f7:9a:1c:09: + 33:ab:54:c1:ac:0c:d7:d1:11:79:6c:6f:c0:2b:54: + 9e:c2:86:85:05:a3:e4:70:06:84:42:eb:8b:c0:0e: + 3a:73:16:cd:13:79:a5:43:e6:89:8b:c3:7f:6b:04: + cd:7f:34:6b:4a:47:65:c3:4a:6a:d3:ea:8e:57:34: + 5d:39:18:fc:d0:8e:e4:f6:ff:74:86:a0:98:06:67: + 40:0c:8f:a6:5e:46:9d:ed:b9:25:99:7c:4c:62:b8: + 19:ae:12:1e:33:0b:d3:43:b9:3c:bc:5a:f3:6b:c6: + a9:1c:c1:ce:99:1f:64:b7:a3:8d:ed:c8:3e:95:75: + 19:e5:ce:51:f1:11:f1:c0:58:76:87:ee:42:12:a4: + ff:8e:c6:e8:42:3d:b4:df:c7:be:a6:c7:ea:6c:88: + 04:4b:d3:f3:9b:7f:d4:db:87:21:55:36:2e:3c:1c: + c9:21:4a:2f:7f:51:f0:08:d7:21:ea:75:c4:e2:78: + 91:9d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B + X509v3 Authority Key Identifier: + keyid:7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49 + + X509v3 Basic Constraints: + CA:TRUE, pathlen:0 + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Subject Alternative Name: + DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com + Netscape Comment: + OpenSSL Generated Certificate + Signature Algorithm: sha256WithRSAEncryption + 10:46:2e:1e:37:b8:10:4a:8c:e3:76:7c:05:57:76:34:05:0b: + e2:ed:b3:1b:28:20:2b:56:9b:2d:59:70:e5:4e:5e:ce:a8:11: + d5:c1:9b:e7:c8:0e:61:2b:63:ae:d2:1b:ec:cf:75:31:d0:4f: + 35:86:c2:51:22:64:c3:07:a7:c4:6b:13:57:cc:e5:d9:86:8d: + b4:73:45:c5:ca:48:b7:b6:02:1e:c7:de:71:c6:5f:2a:64:7d: + b5:5b:16:9a:27:7d:5f:3c:8a:5e:95:38:7f:c0:7e:d4:39:3f: + 36:60:7d:7d:8e:9f:72:06:d4:69:7a:e5:45:3f:e2:c9:eb:7f: + 5f:74:1a:6b:6c:b8:a1:08:05:d9:25:ee:d4:97:db:5a:72:1f: + 4a:06:a9:86:76:41:58:34:0b:5a:39:be:65:ec:26:b1:13:41: + 6b:86:58:fa:2e:cd:ab:06:d2:59:0e:bb:e4:44:2c:de:21:d1: + 8c:9c:93:a5:d5:ae:fc:af:37:b0:91:1f:46:61:28:b9:a5:c8: + b4:3c:28:33:b1:d9:ca:49:53:fe:14:80:82:de:06:c1:ab:21: + e7:44:76:04:d8:85:b4:60:72:30:7a:28:b7:6f:4d:9e:52:70: + 21:df:4e:71:aa:01:d6:ba:fa:4b:4a:61:75:9c:57:67:a6:b2: + e7:ab:24:6c +-----BEGIN CERTIFICATE----- +MIID9jCCAt6gAwIBAgIBCjANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEN +MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEUMBIG +A1UEAwwLVGVzdCBQYXJlbnQwHhcNMTcxMDAzMTgwMjQ1WhcNMjcxMDAxMTgwMjQ1 +WjBPMQswCQYDVQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDEN +MAsGA1UECgwEVGVzdDETMBEGA1UEAwwKVGVzdCBDaGlsZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALJFMeSZEh8Pw1t4mDml0NrGnzgjCd/9NbaVtjdd +tknypfFidWJBCZ025VPIghpcnSr9A5ypAG0osym7z/PrD1zJgY1p4QT3mhwJM6tU +wawM19EReWxvwCtUnsKGhQWj5HAGhELri8AOOnMWzRN5pUPmiYvDf2sEzX80a0pH +ZcNKatPqjlc0XTkY/NCO5Pb/dIagmAZnQAyPpl5Gne25JZl8TGK4Ga4SHjML00O5 +PLxa82vGqRzBzpkfZLejje3IPpV1GeXOUfER8cBYdofuQhKk/47G6EI9tN/HvqbH +6myIBEvT85t/1NuHIVU2LjwcySFKL39R8AjXIep1xOJ4kZ0CAwEAAaOB2zCB2DAd +BgNVHQ4EFgQUwgPq/H5wfzQhwb4zDorgfsaiIRswHwYDVR0jBBgwFoAUer59CVpf +XN7Mgho7/qjtyroWWEkwDwYDVR0TBAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwSgYD +VR0RBEMwQYILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIQbWFpbC5leGFt +cGxlLmNvbYIPZnRwLmV4YW1wbGUuY29tMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM +IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAEEYuHje4 +EEqM43Z8BVd2NAUL4u2zGyggK1abLVlw5U5ezqgR1cGb58gOYStjrtIb7M91MdBP +NYbCUSJkwwenxGsTV8zl2YaNtHNFxcpIt7YCHsfeccZfKmR9tVsWmid9XzyKXpU4 +f8B+1Dk/NmB9fY6fcgbUaXrlRT/iyet/X3Qaa2y4oQgF2SXu1JfbWnIfSgaphnZB +WDQLWjm+ZewmsRNBa4ZY+i7NqwbSWQ675EQs3iHRjJyTpdWu/K83sJEfRmEouaXI +tDwoM7HZyklT/hSAgt4Gwash50R2BNiFtGByMHoot29NnlJwId9OcaoB1rr6S0ph +dZxXZ6ay56skbA== +-----END CERTIFICATE----- diff --git a/cursive/tests/unit/data/grandchild_cert.pem b/cursive/tests/unit/data/grandchild_cert.pem new file mode 100644 index 0000000..f120c3f --- /dev/null +++ b/cursive/tests/unit/data/grandchild_cert.pem @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 11 (0xb) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Child + Validity + Not Before: Oct 3 18:09:07 2017 GMT + Not After : Oct 1 18:09:07 2027 GMT + Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Grandchild + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:bd:3c:4b:2a:e8:03:5d:07:ae:94:f4:19:ed:00: + 21:20:dd:1c:54:f1:dc:44:d8:bf:66:b4:bf:ce:21: + 7b:bf:b4:15:7b:b3:4f:0e:d5:ef:fa:f1:31:ab:2a: + 22:78:72:20:7d:ce:58:c3:45:0d:2f:5c:23:7c:87: + 07:bf:ee:8c:8c:9f:ae:31:70:19:61:dc:92:b5:8f: + fb:36:16:1c:08:d4:2c:c0:0c:86:e0:ee:a8:31:20: + 21:16:41:b2:78:bc:88:a8:ef:4c:3a:34:4f:a0:08: + 25:e7:35:e8:bc:66:d3:c3:b5:2a:05:34:91:b0:d0: + ae:02:f2:a1:58:22:af:43:42:d8:40:82:0c:e3:26: + 72:22:06:d2:b1:13:87:04:83:70:f6:b0:99:39:bf: + 79:26:f6:e2:ff:24:c3:72:48:9f:68:0a:c1:c9:aa: + b1:a8:b4:f7:cf:44:38:4a:77:bf:56:20:fa:7e:08: + 75:26:04:fb:5e:d5:4f:ff:b8:45:1f:80:12:fb:7e: + 61:7e:52:f0:dc:71:ee:72:91:27:fa:60:93:96:e5: + 78:1d:d9:fd:5a:b8:00:b9:97:46:12:b5:2a:93:0e: + c3:1b:30:6e:b2:67:5d:c5:ca:40:3f:36:0c:7c:4f: + d4:48:e0:1f:32:a9:28:0c:37:35:7c:5d:42:f5:cb: + 54:b9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 4F:6C:A8:1F:80:F0:A6:EE:41:85:B9:A2:3F:EC:3A:B2:93:B4:0E:86 + X509v3 Authority Key Identifier: + keyid:C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com + Netscape Comment: + OpenSSL Generated Certificate + Signature Algorithm: sha256WithRSAEncryption + ae:a7:62:9e:f6:b7:e3:02:84:0f:fe:c6:7c:c1:0b:74:8e:95: + c3:2e:e9:5f:c0:8b:fc:79:45:53:5c:34:9d:b0:de:e6:cf:ed: + 52:4c:3f:6a:3f:e9:8d:a3:58:d4:ae:4d:31:30:57:d5:31:f9: + a2:ed:82:e2:ae:1a:65:a5:ab:de:64:35:c9:0b:d1:86:b0:83: + 57:8a:e4:ca:21:d5:9a:79:5b:44:42:ff:52:9a:51:b6:f4:6e: + f1:da:dd:3b:ca:12:cb:4c:e5:9f:a5:12:4f:13:99:85:79:c8: + 00:3b:2c:25:7f:02:07:a3:4e:59:0b:4d:8e:f8:43:08:a9:91: + 30:0a:17:1c:ff:91:c0:16:d5:c0:1e:ec:a5:24:c8:cc:f0:2c: + 0e:30:b9:bb:34:11:83:e7:4d:02:e4:2d:2a:90:98:eb:d8:ae: + 7b:2f:19:31:db:63:fc:0c:0b:47:f5:8f:7b:cf:99:0b:30:91: + a6:44:19:51:7f:15:4f:ab:8c:08:e2:bd:91:42:e4:e7:88:8e: + c0:ea:fd:09:ac:96:c6:14:ef:0e:7d:75:6a:05:b0:b5:4d:43: + 60:62:31:85:61:cb:c3:0f:81:24:d6:de:10:42:54:ff:c0:63: + 95:40:3d:89:52:f9:00:2a:a5:74:1c:b1:42:be:a1:2f:de:90: + cb:d5:a7:3d +-----BEGIN CERTIFICATE----- +MIID9DCCAtygAwIBAgIBCzANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJVUzEN +MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDETMBEG +A1UEAwwKVGVzdCBDaGlsZDAeFw0xNzEwMDMxODA5MDdaFw0yNzEwMDExODA5MDda +MFQxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MQ0w +CwYDVQQKDARUZXN0MRgwFgYDVQQDDA9UZXN0IEdyYW5kY2hpbGQwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9PEsq6ANdB66U9BntACEg3RxU8dxE2L9m +tL/OIXu/tBV7s08O1e/68TGrKiJ4ciB9zljDRQ0vXCN8hwe/7oyMn64xcBlh3JK1 +j/s2FhwI1CzADIbg7qgxICEWQbJ4vIio70w6NE+gCCXnNei8ZtPDtSoFNJGw0K4C +8qFYIq9DQthAggzjJnIiBtKxE4cEg3D2sJk5v3km9uL/JMNySJ9oCsHJqrGotPfP +RDhKd79WIPp+CHUmBPte1U//uEUfgBL7fmF+UvDcce5ykSf6YJOW5Xgd2f1auAC5 +l0YStSqTDsMbMG6yZ13FykA/Ngx8T9RI4B8yqSgMNzV8XUL1y1S5AgMBAAGjgdUw +gdIwHQYDVR0OBBYEFE9sqB+A8KbuQYW5oj/sOrKTtA6GMB8GA1UdIwQYMBaAFMID +6vx+cH80IcG+Mw6K4H7GoiEbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMEoGA1Ud +EQRDMEGCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22CEG1haWwuZXhhbXBs +ZS5jb22CD2Z0cC5leGFtcGxlLmNvbTAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH +ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAK6nYp72t+MC +hA/+xnzBC3SOlcMu6V/Ai/x5RVNcNJ2w3ubP7VJMP2o/6Y2jWNSuTTEwV9Ux+aLt +guKuGmWlq95kNckL0Yawg1eK5Moh1Zp5W0RC/1KaUbb0bvHa3TvKEstM5Z+lEk8T +mYV5yAA7LCV/AgejTlkLTY74QwipkTAKFxz/kcAW1cAe7KUkyMzwLA4wubs0EYPn +TQLkLSqQmOvYrnsvGTHbY/wMC0f1j3vPmQswkaZEGVF/FU+rjAjivZFC5OeIjsDq +/QmslsYU7w59dWoFsLVNQ2BiMYVhy8MPgSTW3hBCVP/AY5VAPYlS+QAqpXQcsUK+ +oS/ekMvVpz0= +-----END CERTIFICATE----- diff --git a/cursive/tests/unit/data/grandparent_cert.pem b/cursive/tests/unit/data/grandparent_cert.pem new file mode 100644 index 0000000..8c4c180 --- /dev/null +++ b/cursive/tests/unit/data/grandparent_cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF7jCCA9agAwIBAgIJANHiL5B0pUVmMA0GCSqGSIb3DQEBCwUAMIGDMQswCQYD +VQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwE +VGVzdDENMAsGA1UECwwEVGVzdDEZMBcGA1UEAwwQVGVzdCBHcmFuZHBhcmVudDEd +MBsGCSqGSIb3DQEJARYOZ3BAZXhhbXBsZS5jb20wHhcNMTcxMDAzMTc0NzMyWhcN +MTcxMTAyMTc0NzMyWjCBgzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTAL +BgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxGTAXBgNV +BAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG9w0BCQEWDmdwQGV4YW1wbGUu +Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn4m1O+fffNTSnGE5 +MPac07jjMNrKHEjARS4aM222C8wCPiXXrs1diTQlvtxrLFOzc0gtCH6xVl59Zcis +H7kBf8oV9wNIHcfy2BmGkP7Wv68p3UsIF1IzGSHPyKJWG+l/xNexuXFaVG9y+siu +5Z3bx9DMBPFfXalxwRGoS0fyBOG/tXlqicf/aojF1U3UolML58URQqQ7IvGjEq22 +iqAfduEwLlLb99iJ8uiFgO6Rl/hwxvy9gmrGWJGHpHQKJ2Dx37Zc8MMcAJ5yos7c +GAs3e31TvRJgyEBcPKtl+xmh36wNC+V1KKRYKAfENqB6v7b1GDZrVtH7uHvSPCDQ +ccKklF2thomO4cm6UpbCF7/5i50OLwtr1TcUI/YT+nR/YsCuc/PKdyXITpP0CNR9 +Wcw6pb5LsWgLisFh4my4PzTbwTcPGFUJHq0CUUsRP1YijMNRtiZsoMJwspMZyg9d +9Ufxf7HjbugazUfvfIKMfX/s/pIZLiDLx5lV9RbHmjvlCt4OfH3FhqBveguqDLq7 +LbBC6tYm1E21izUqXy4Zh+oogvZeZGUBQL/JUJ6XOkUnffv1nf2HDsCmlW64NLce +9gc25BtXAAf5/tMQL2J3t5SZ+Ladk+nklQXz6eClFcLEbRcd15bZ3QFXCajeLWcP +Y5TZFgDQFFr7/FjDhr2bByMOJEkCAwEAAaNjMGEwHQYDVR0OBBYEFKfrobRNKmLV +OJDtRPp3f/m66pkEMB8GA1UdIwQYMBaAFKfrobRNKmLVOJDtRPp3f/m66pkEMBIG +A1UdEwEB/wQIMAYBAf8CAQEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IC +AQByRvLd0xO238AwgDep4hBshcFH9tcSg5hDPFFeJWRnC4c63vNGXMVV7iEAoPSH +LPbuELCIRqiZFYW5A8Olv2MGGZ3kjiFrWYfbLFU0/z1y82/E2NtM65cvOQKYxMk1 +HBmaGF8s43LdDiGUZ0yFMTwe+da+zWcfDPgSYml36ReCsn2dGFmfkPFhqSf810kI +yl2EKQnjEf51AGDfrA6fmEafsQy8eFf0uH6cR8nrsa+0aXIkTHZ9erXrXujD30iL +9M4T3uW/0Qk/kqSN3wUgHYWDBRyTKxCDPMiEixXDq21Jm1VzSKJAFE+xEuFHtqXl +nfZQCzihdx3ckZnH3qfrJt0V0cu6qSNr6sbyrb7FVO8aCNyumdCDM9VdJ64UFAme +Xd/1/195PcoFOVEokoH15EH0mPr+/DDWA39c+FaRHH0A3LmuzX/P5rTRLO3wldpL +XiZkLrfG43UNq3PIdh3YZEabpFcQYTmab7N8nZnmoMRM6YoEnHjdqPcDv3xs4gJS +U24bVnFqzgSW3V1GfZGnlQyGXFrlrU+wWJ55eJ59ucQn8PDYlrSz7+x9RiqoFZcQ +c9L8j7dFMBx6zI4dI5Ddx5q9KNtxPJb4Hk9HEd4C5OQ0qqdBR/hSD9mDjBpqEyc8 +aXIzmrTpGm7A9lbyXCEaOzN+2Jvdq5KtWh/halEgqgToqQ== +-----END CERTIFICATE----- diff --git a/cursive/tests/unit/data/not_a_cert.txt b/cursive/tests/unit/data/not_a_cert.txt new file mode 100644 index 0000000..240c7f0 --- /dev/null +++ b/cursive/tests/unit/data/not_a_cert.txt @@ -0,0 +1 @@ +This is not a certificate. diff --git a/cursive/tests/unit/data/orphaned_cert.pem b/cursive/tests/unit/data/orphaned_cert.pem new file mode 100644 index 0000000..d944bbc --- /dev/null +++ b/cursive/tests/unit/data/orphaned_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID8zCCAtugAwIBAgIJAMDwfYBIkXEGMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD +VQQGEwJVUzERMA8GA1UECAwIVGVzdG9uaWExDTALBgNVBAcMBFRlc3QxJDAiBgNV +BAoMG0NvbXByZWhlbnNpdmUgVGVzdGluZywgSW5jLjEaMBgGA1UECwwRVGVzdGlu +ZyBPdmVyc2lnaHQxHDAaBgNVBAMME1Rlc3RpbmcgQ2VydGlmaWNhdGUwHhcNMTYw +NjMwMTcyOTUwWhcNMTcwNjMwMTcyOTUwWjCBjzELMAkGA1UEBhMCVVMxETAPBgNV +BAgMCFRlc3RvbmlhMQ0wCwYDVQQHDARUZXN0MSQwIgYDVQQKDBtDb21wcmVoZW5z +aXZlIFRlc3RpbmcsIEluYy4xGjAYBgNVBAsMEVRlc3RpbmcgT3ZlcnNpZ2h0MRww +GgYDVQQDDBNUZXN0aW5nIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAm+W78acV27U11m7E3iUUUGb+JXMW0okP8epD9OsLtVHxR+oq +iOt19rgNIH/wJzaT+CnJ1jUerjzjFu2RwGhEr8Ph2KrWWQ7vxkhJzuXmKmGBZJm3 +FJcADrxcmZ8V3Yqxf3zO36Rg27jqDgxSy3uzxgO7ZXrkrJjrgrg+x8wVQ/pkhd8Y +gQ/YQ2r1DF1GcpS/tSkCSc3lbIpCCHhORaRmHZXURML5q7vibLmc55Ad90WxtS1d +WI8RAsWnQMvP1OmZcRcPKrUlRc/w+nIrxNF9HdeOweQv2tcnNlxBOcr6MwIL+Gle +N4TmmthyVYCXxNWhW1VFA3atfEfmyEpiKIcQGwIDAQABo1AwTjAdBgNVHQ4EFgQU +IkPrrGyB6+XUlWbd287uFbfCvkkwHwYDVR0jBBgwFoAUIkPrrGyB6+XUlWbd287u +FbfCvkkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMWi3C19pjWs9 +9hg1rTC0D/5C9K/0nmQ1pstVMXOKn9Z3ndUqRvLzxhHZHhQ/ATQwHKeSM2vpCmKa +eV7PGivF6W+CAXJImvgNrsP2fMBnTsg2Q3hBHSIkTgwJxAHlYZ3NXSxWoDSuozvU ++qjRY3hMpYLSXpfGFKh73GHBNWXyjlo7pn+I4gAEoHOqDKTelOONz6PiKKi4Un2g +j5FqmLZEq9VvzqSEC5VuFLZs4BpGmsKBM16+q+8JWMa025wNcdq4DxuNAkb3Zsty +QZkgVYJgIeuEKOCubCQfDOya5W7ik3mtZZm9dFD5dZ3+CDB53a/AlKdi9YUAJOUW +xBJzlRBlLg== +-----END CERTIFICATE----- diff --git a/cursive/tests/unit/data/parent_cert.pem b/cursive/tests/unit/data/parent_cert.pem new file mode 100644 index 0000000..7e325dc --- /dev/null +++ b/cursive/tests/unit/data/parent_cert.pem @@ -0,0 +1,107 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 9 (0x9) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Test, L=Test, O=Test, OU=Test, CN=Test Grandparent/emailAddress=gp@example.com + Validity + Not Before: Oct 3 17:58:30 2017 GMT + Not After : Oct 1 17:58:30 2027 GMT + Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Parent + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:9f:9d:95:c4:a3:2f:37:52:e4:7c:cf:0b:0e:7f: + 14:69:63:1e:7a:cc:a8:19:a7:88:59:c8:17:f2:21: + 13:1b:45:21:fa:cc:93:40:71:cf:77:52:5a:1e:2e: + 5a:91:16:a9:67:3a:a3:6a:ea:cb:a2:bf:24:9b:8c: + 08:96:33:19:46:f9:7a:04:f9:c2:ee:87:f3:c3:23: + 73:37:59:0e:c0:71:f4:cd:0b:ad:23:63:51:0a:4f: + dc:d2:9b:ab:ab:8a:99:07:d4:c8:c8:70:fd:18:73: + 25:0a:48:82:32:0d:64:46:b1:63:84:24:03:0b:3c: + b8:17:92:78:6c:2b:4d:21:1b:46:3e:c1:cf:98:0b: + a8:43:91:c0:39:48:f5:4e:71:77:c5:43:0e:68:8f: + 01:c6:fb:59:77:d5:b3:f3:fe:95:27:ea:6e:ae:fc: + 8e:59:ad:06:97:0c:f7:a6:e7:61:df:23:91:26:d0: + bc:80:c6:2b:02:9b:fa:0f:e6:32:69:5a:90:29:c9: + 9c:34:eb:50:ed:1d:e3:eb:0f:67:88:e3:ec:2b:1a: + ab:41:c3:fa:d6:e8:aa:e3:7b:6a:16:3d:d8:da:6b: + af:92:81:32:98:2f:f7:c0:bd:c4:25:bb:02:60:43: + d5:e6:0c:29:7f:31:5d:09:4b:6a:a9:31:9b:92:24: + 09:8f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49 + X509v3 Authority Key Identifier: + keyid:A7:EB:A1:B4:4D:2A:62:D5:38:90:ED:44:FA:77:7F:F9:BA:EA:99:04 + + X509v3 Basic Constraints: + CA:TRUE, pathlen:0 + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Subject Alternative Name: + DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com + Netscape Comment: + OpenSSL Generated Certificate + Signature Algorithm: sha256WithRSAEncryption + 81:55:0d:1d:73:54:1d:72:73:72:dc:cf:ed:c1:47:c8:38:2a: + 78:33:5e:55:6f:02:cc:c0:6a:6f:7e:c9:fa:4c:3d:a0:5b:25: + 37:5e:87:69:7f:d8:66:73:4f:58:7d:c7:3e:6d:be:2a:85:43: + 6a:cb:ff:68:59:1d:72:d2:68:ad:e9:5b:2f:8d:f6:95:31:ba: + 1d:de:16:45:d9:12:51:85:12:bb:fb:89:fc:3a:7c:f5:e4:75: + 64:b4:7d:ff:9f:f6:15:fa:1e:cb:18:4a:9d:e8:d8:5e:5a:d7: + dd:78:c7:df:3d:21:2d:99:ef:b4:2c:78:2f:fb:fa:a0:7e:f3: + cb:3b:05:5a:65:7d:9b:0f:9b:a3:9b:a9:ad:25:f8:32:cb:08: + fd:c2:68:d3:92:15:09:59:5f:8b:c4:84:01:5f:75:7b:f0:55: + 5f:20:39:f1:26:65:3d:d8:a2:19:de:fb:79:a0:27:2a:24:ae: + 95:02:84:61:72:7a:47:37:4e:9f:af:20:5b:21:ec:c4:bf:ee: + 80:5b:35:4e:ee:20:46:e6:cb:a6:e2:2f:c6:3e:5a:fa:f9:97: + c3:97:09:1d:ce:08:a3:e9:09:cb:c3:59:3f:98:f3:b6:bf:00: + 8b:a7:40:de:0a:1c:09:88:f7:74:fa:b1:1c:05:44:ff:ba:73: + 84:3b:93:8d:a8:51:d0:d8:59:e6:cd:a8:79:d3:db:0a:1d:99: + 3f:7c:a0:f9:d5:9e:dd:13:58:ee:ef:0d:3d:e2:4a:8b:85:18: + 0c:86:f8:97:4d:18:54:c0:52:b8:10:38:1a:b8:8a:06:71:a5: + e7:78:11:00:5b:9f:19:92:34:28:0f:19:3f:b0:57:ea:11:69: + 29:ca:ed:05:36:08:f6:8d:ec:5d:34:79:92:8e:4c:e0:1c:a4: + ad:1a:31:90:b7:16:60:da:e3:8f:ee:ea:66:df:13:e8:46:8d: + a3:e2:3b:0a:f5:87:14:3d:4b:14:ea:da:89:c7:ae:e0:60:e3: + a0:4c:04:2f:a1:0f:a9:84:5a:5a:f7:3d:4f:7b:d4:7c:e1:cd: + ef:8b:28:45:19:ea:a9:4c:9e:59:f8:41:43:10:77:89:09:3e: + 30:d0:e9:58:96:45:07:50:0e:4d:cc:6a:53:9e:64:c4:8a:e0: + 51:96:3a:c6:8a:e2:94:af:9c:26:9a:fe:e3:7a:cd:cc:55:60: + f0:dc:bf:f3:0d:e8:69:e4:cf:49:e1:f4:d2:87:91:31:cf:42: + f7:2c:a7:f7:7b:88:90:e4:17:96:f6:34:d2:bf:a1:66:3c:03: + db:aa:07:fa:a6:c3:4b:d3:29:d1:d1:40:6f:a7:88:a5:7f:bd: + 5f:f5:00:94:db:53:5d:24 +-----BEGIN CERTIFICATE----- +MIIFKzCCAxOgAwIBAgIBCTANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +DTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTAL +BgNVBAsMBFRlc3QxGTAXBgNVBAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG +9w0BCQEWDmdwQGV4YW1wbGUuY29tMB4XDTE3MTAwMzE3NTgzMFoXDTI3MTAwMTE3 +NTgzMFowUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRl +c3QxDTALBgNVBAoMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUGFyZW50MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn52VxKMvN1LkfM8LDn8UaWMeesyoGaeI +WcgX8iETG0Uh+syTQHHPd1JaHi5akRapZzqjaurLor8km4wIljMZRvl6BPnC7ofz +wyNzN1kOwHH0zQutI2NRCk/c0purq4qZB9TIyHD9GHMlCkiCMg1kRrFjhCQDCzy4 +F5J4bCtNIRtGPsHPmAuoQ5HAOUj1TnF3xUMOaI8BxvtZd9Wz8/6VJ+purvyOWa0G +lwz3pudh3yORJtC8gMYrApv6D+YyaVqQKcmcNOtQ7R3j6w9niOPsKxqrQcP61uiq +43tqFj3Y2muvkoEymC/3wL3EJbsCYEPV5gwpfzFdCUtqqTGbkiQJjwIDAQABo4Hb +MIHYMB0GA1UdDgQWBBR6vn0JWl9c3syCGjv+qO3KuhZYSTAfBgNVHSMEGDAWgBSn +66G0TSpi1TiQ7UT6d3/5uuqZBDAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIB +BjBKBgNVHREEQzBBggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tghBtYWls +LmV4YW1wbGUuY29tgg9mdHAuZXhhbXBsZS5jb20wLAYJYIZIAYb4QgENBB8WHU9w +ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQCB +VQ0dc1QdcnNy3M/twUfIOCp4M15VbwLMwGpvfsn6TD2gWyU3Xodpf9hmc09Yfcc+ +bb4qhUNqy/9oWR1y0mit6VsvjfaVMbod3hZF2RJRhRK7+4n8Onz15HVktH3/n/YV ++h7LGEqd6NheWtfdeMffPSEtme+0LHgv+/qgfvPLOwVaZX2bD5ujm6mtJfgyywj9 +wmjTkhUJWV+LxIQBX3V78FVfIDnxJmU92KIZ3vt5oCcqJK6VAoRhcnpHN06fryBb +IezEv+6AWzVO7iBG5sum4i/GPlr6+ZfDlwkdzgij6QnLw1k/mPO2vwCLp0DeChwJ +iPd0+rEcBUT/unOEO5ONqFHQ2Fnmzah509sKHZk/fKD51Z7dE1ju7w094kqLhRgM +hviXTRhUwFK4EDgauIoGcaXneBEAW58ZkjQoDxk/sFfqEWkpyu0FNgj2jexdNHmS +jkzgHKStGjGQtxZg2uOP7upm3xPoRo2j4jsK9YcUPUsU6tqJx67gYOOgTAQvoQ+p +hFpa9z1Pe9R84c3viyhFGeqpTJ5Z+EFDEHeJCT4w0OlYlkUHUA5NzGpTnmTEiuBR +ljrGiuKUr5wmmv7jes3MVWDw3L/zDehp5M9J4fTSh5Exz0L3LKf3e4iQ5BeW9jTS +v6FmPAPbqgf6psNL0ynR0UBvp4ilf71f9QCU21NdJA== +-----END CERTIFICATE----- diff --git a/cursive/tests/unit/data/self_signed_cert.der b/cursive/tests/unit/data/self_signed_cert.der new file mode 100644 index 0000000000000000000000000000000000000000..33091c8360baeb5e75ea37516041977801ffba13 GIT binary patch literal 925 zcmXqLVxDQx#MHQenTe5!iIZXFqMb?!?Luq@ylk9WZ60mkc^MhGSs4tf4Y>_C*_cCF z*o2uvgAI8NxIr8aVV028;u08_9hJ+4%H>AoGDEqF2J&#LI2px+N`MABq~<1O=IAA+ zq!gtV7aPcl^BS5Nm>C-wnwy%NnnnS+=1AOubzBpp5^`WMvNA9?G4eAQG%<29H8Ch5a!{>KDuMTdIh-4_Kczokh=st8X$)A5*_*-7u`5s6Q` z#8{4&7?3S|2#=H=P`?t82M_WxFbmzkOOs z?#KK&t%AoT4l^+`GB7SyFpx9g2PPR=VHOSpHb%z(z~lx_L$drVEX+(yEXZL9OmM(3 zWMr^sYFMKu^ydHn(CznauJM&+=3TwK*i(zQ>FS|dF`C@Ei*|iml68n7!#bzJ<9O3| zE%o0;Gs^p%-fmyP{PT0S&W|j?H&d0TF1fg{B=`U13$GV`S~KIjqk^xnwwMeSiC)weobv!vVK~I&#X^t>$3b8J!`C)$ot&&$k%!&87rO{^Q7N> zNbE?xcaZtmiy)CY{ii=~TUt$P>l5hFnRWc$y8fhFGykcid7WfGs?l$kck;&0bN8mS z-LP?~sYz|`ynlE>hd|Yxtgk-@QQLU$*_y + The cursive library supports the verification of digital signatures. + However, there is no way currently to validate the certificate used to + generate a given signature. Adding certificate validation improves the + security of signature verification when each is used together. +features: + - Adds a variety of certificate utility functions that inspect certificate + attributes and extensions for different settings. + - Adds the CertificateVerificationContext class which uses a set of + trusted certificates to conduct certificate validation, verifying that a + given certificate is part of a certificate chain rooted with a trusted + certificate. + - Adds a verify_certificate method that loads all certificates needed for + certificate validation from the key manager and uses them to create a + CertificateVerificationContext object. The context is then used to + determine if a certificate is valid. +upgrade: + - The addition of certificate validation as a separate operation from the + signature verification process preserves backwards compatibility. + Signatures previously verifiable with cursive will still be verifiable. + However, their signing certificates may not be valid. Each signing + certificate should be checked for validity before it is used to conduct + signature verification. +security: + - The usage of certificate validation with the signature verification + process improves the security of signature verification. A signature + should not be considered valid unless its corresponding certificate is + also valid. +other: + - The CertificateVerificationContext is built using a set of trusted + certificates. However, to conduct certificate verification the context + builds the full certificate chain, starting with the certificate to + validate and ending with the self-signed root certificate. If this + self-signed root certificate is not present in the context, or if one + of the intermediate certificates is not present in the context, the + certificate chain cannot be built and certificate validation will fail.