Merge "Add certificate validation"

This commit is contained in:
Jenkins 2017-10-05 19:31:37 +00:00 committed by Gerrit Code Review
commit ad25a4016c
18 changed files with 1281 additions and 58 deletions

View File

@ -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]
)

View File

@ -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)

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1 @@
This is not a certificate.

View File

@ -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-----

View File

@ -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-----

Binary file not shown.

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIJAKmiuSJghxIGMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTc1NzU1WhcNMTcwNjMwMTc1
NzU1WjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA4KHdygQZ2/posNLLE/9lR0U57/iQKyFAfzO70RA5
9mYZWSQTk8yI4wsDQi75xBjyuhYExsl+9lX6dgV0uEElhkycRemTN9pHmdGLi6of
OIpVd5drZGcK19ndPeG5IzFvCpWXfsKuZ2kJf8p9i5XDNhigtYNq5rfLZBOIE3FY
HPKBbx9cBaPOL8kjyX8LPwG7tpmNRLAF4XgQZu/AbfWx0jg8UqqJhOKwPQz+YOPY
1eJ55BDyDYYiRj70qhQ1jIbfmYbWjg1VOv7LKzzwQWI8gTnKND26+L0D1tAy4joO
cV/XM9lWheBCvzWTULqKpy95hyUMTz9mdG3xb5yFEccYwwIDAQABoyAwHjAPBgNV
HRMECDAGAQH/AgECMAsGA1UdDwQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAPwKA
rC4S7P//VbffPNYOdmlu1dOjSSoNgtXC2lwpCy2iuvakasIAaDtseEjHgvcqJ/ty
mHeOQu23qAP584ss+GoR7JUjlaTRoXRt/5PQ66HyrJXl/2jWLtT+7yU2+UtOxWSa
fE3xUnzZlW4ES0hi+pWCpK+WaEya8q1+ak+i5oF8kQ3nRcT1f0IcOgyYnhvu8GGI
Zd7BA8boUhR+L+X52zk6loaOEIwsmsfero9i2pn+JGZKyQfFKI8+bsnYuc7elIbY
PER8fGWHid/DoIgQety153LLKtfR/20rYBrlnNtatg3ePTRdFZ1p7lnLPfA+AiV5
e1Y6hSfZYXpZRV+Qtw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIJAKnOrxg9gSXNMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxOTQyWhcNMTcwNjMwMTgx
OTQyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAnhmtSiir3aB5igvVEQlHqIw2K98q336cyjipFq9b
Pt1YslTwLfUAagr7224i0tny45PIZ3o1YlBxhEwd/i1tMnCz2+DQyat+p+vVbbiI
ceN1ZzRFE4zJV0QjG+H+TOWqzjtdtq04jkdrKMOsp3Lv4NHIuEuLocQLPLuT79wP
VUO+BCHlU/0bUQHhAU/Jx9B81GQ1/4lYS400AYtANSEccMR1djUTjFha4wiwSDH2
QZQBgmiqmDhf22uoioFgay9+yhOJ3SJx/lIiMavM2LMgNbns1DcbAD8oKGS69Mmo
TsQlgOVMQTIDbwsm3WaxIcY8BipUACSe3E+RdqDrP/MTDwIDAQABoxowGDAJBgNV
HRMEAjAAMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEACP8ZAbyAtffS
sWZ6WbLcm+BT7FFdUm62gGqC2knaIZQud3AV5/wtj2CAw1lXgGbaCqfWNQrTDY9z
PpGXyQ5tvNUtBZG23K7nMvid+U1WDh1fhlRC0kxCK2MsPwv9T5BM3tj/YF0MGWuQ
3GlFL8wU8UoAP3alUhxQl5qXqfXc6qfMW2ec4Jb3j6nbezL7ttn15LiBCsvoJ6/V
Go9bbox81UrbtxnVin5+cYczUdB+Q9+fe23B/6MG99hzWU9arkkU7ZOlnI/bW9Zb
fx4+atZfpi18nyd2ljgiTapB6Ex6uxVPrzwuKxpGMt9wU1++ZYc3YDke4EOT+OIX
nKc7UFfjyg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIJAKOf0EhCGUdQMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxNDMwWhcNMTcwNjMwMTgx
NDMwWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA575xHdxVuT5CYPom/PbFdDLt0PgG3CZFkiRNxPAG
rDN8cG5ouTw0R9RMBFA+nYcTx/4GnPJmSBEBVqMoPSzsB6Rx9k21KymNlaEs2O1W
jSMsYd9gW4NlHdyoomYw0nXQjkstmtdJxDNWg0zSZrHMPnkOVh+JNV58i4FXOx5O
bxWo4sSyAvNjAEH9GwDwy+Jz0X4RdFGQrGjm+/v+ohvy8JqU5ZKpz2oP2oQURjDj
+AH3ghmgNAVAk0syjtSqydEJd9aeMLTmTaUtP+gPnXdj/ZBj+TQH01RNlSECH2l4
WrymS3g4+X5xsA7DeLIbiXB4K1xjbJFCSfDYrV52H/fE6QIDAQABoyAwHjAPBgNV
HRMECDAGAQH/AgECMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEARNRE
XC8y/RoPLUVAVKJ/RwcH4cwaHoSSu6HnygIpg9Qs7Xc7u1aKCL0dRF1NqfmaqHZ6
ZjllxDi5t0CFIXPfDQIYchfSzOafhJGEH3gilBwfmN43N4/eCSvdRKfhRbRFOD9j
0JHRAHkn2JRcwSTTjJEcJUJJETAIIbX1ovobJZuJOY0faI1O/Z2KILYrwdmcfnZ5
3i0kUps5BWrBrcs70gBsDBugeM24ANa7hJzFk+9TztfLWF1AUfjpZ4Bj/rb21+Gp
08FRjvn80Y5bGlNh0Q7Qbu8NS8VbAHHF3t3PUVRymJhIycpvBBBS7dcQqHf+v1gs
Z/UpzuobJvnhq+7mcQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDiDCCAnCgAwIBAgIJAOYNT7MuoypuMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgyMTMyWhcNMTcwNjMwMTgy
MTMyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA3qypxOJ2X0k47SIEkRGvA/ECqzVAX3nsC0yPnbF3
14SGe7xFBzi0VMAXOcpVj0BL+G5TL95O5loVN/UnU5/xtjSa712HOOlJfvnqmv63
BLq9cMS2strwufeOK3YUtQExtJdxMjcEYuCMt+NlQ3Hl+xNfBc0LXWNBdlusP4fs
6sLEgyD4ywSLC9oHzyzgDxi0pr52itu+KnZv2iET/Wotg/8Aiw5Q5fiTc8DoysdZ
MF1ix56oGo1SFFGFf+n2iwYbImtNGt6//jKEDP8P4iLdLAxHxmfsXXnl+Zs/6VoA
RrnS3Xt7F5xj4CWuoZy4CLo8YXhXdznRQZ2r5Qha65cFNwIDAQABow8wDTALBgNV
HQ8EBAMCAwgwDQYJKoZIhvcNAQELBQADggEBALOBnrWz3xMg4Yh92zsXfrSm2uAL
P8jgXQtQdsYWSEWcfYNYOSlmLnICweDnAn0V5Kzp0E5wZSHP8Ut4IYEKwe+IF7HA
pv3mpg1CYtwVbVsN0dhlLHDVuF27i0r8LuOv9yh0wxY3hPYrd2WvQ/qTP8NO0EoD
fM8w2fjNeTu2jB+lYWhGWOHfzEvltosxMZIPWBJxrh3PbYdbuJJZlm/NPqj5Urxx
nRB89AEBHKEKHlmNIMMOM3mQ+ShssgGrbRV6U6iJ5qv4H5RdaZtmXMGhjt8dFJrD
A2YZYW15QLQDEyud4flSztaw6UMHJi+4FChBAnuJuOcNRZA83v3szuM9t64=
-----END CERTIFICATE-----

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAI20mCsVbBjpMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTYwNjMwMTM0NTU5WhcNMTcwNjMwMTM0NTU5WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA5NjoTq0D6LM6z7V16E1+Eul25L6X1BSLcppsHhVMB/NM1e7hjoUtAODv
71L/bAAgV4ky4aphOfwYQLfeAP8nkq4CU30LaAyAQwrT4RyW1NG7AA00xylauebt
sc2GUUy06gQ0Z6OjjEfA5HA4W+HYfeyuNzQpWXHWz/6K8xcKb9w10qAjDhNilHbj
3RVtL6u6rsZgQi0DlMxpHsp6gLezIRMN72B/AOKrzaobw4nY4hOVkqbRlOHB/tsk
4BJLUuW5WM30TVpfsKe07jCBgqUwb9XD9lZa1alkFRsSTZoWijeQBM6kiLD/VFNC
YlIKBrN7HZtfOlqhftMknCsoyrWsjQIDAQABo1AwTjAdBgNVHQ4EFgQUnDu25deh
bIOsCaj3LAcE9r2gx0QwHwYDVR0jBBgwFoAUnDu25dehbIOsCaj3LAcE9r2gx0Qw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAowntOCmRXKgz7M6hnbQc
sEyIt5tN3QILHSeprVO7/ONVIiPJMCfB+S8gJKJ0d5R0xXDYN/6+HyYlgfaL33Gt
HY75y8MRnfqpgbEWhXWsBkxgeuqWiM/OFMTqLtgkVbxsVzoUl6V+tHsZaaM9yuyb
iUBM9McAPGIgodpMGG86BV6qg07VjqWjl5pBUU4B2zvvzZjwrC8jqUYksVESHB9U
WBzwfPLXoj0PUfAog34ZtT33UXX8M3oXTw+yb/hx0rContYMc78Lnlk6mV9gGG+X
+3gSwAHn0SMZNNkKc3gdb1CLfluvHw2Od2jat0yfHHawh1JBtnfHrAU0px3Kzw5U
0A==
-----END CERTIFICATE-----

View File

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEWzCCA0OgAwIBAgIBATANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJVUzEN
MAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVzdDENMAsGA1UEChMEVGVzdDENMAsG
A1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEhMB8GCSqGSIb3DQEJARYSdGVzdEBl
bWFpbC5hZGRyZXNzMB4XDTE2MDYzMDE5NDA1N1oXDTE3MDYzMDE5NDA1N1owbzEL
MAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNV
BAsMBFRlc3QxDzANBgNVBAMMBkNsaWVudDEiMCAGCSqGSIb3DQEJARYTdGVzdEBj
bGllbnQuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALOK
dKWvu8NLDee4KvHf5WmmJsNeqrfZt+5EBXlp9wEJ3i//6vRpZe9Gr/k3xfbQPVng
PS8LUBancZ/zPos6ZibUuJi+ZjgVXUm61S18536wq4S1LH4Hkb4RgJW+IKqlqi0z
RVC3xeNcUhGprcH9JtjinOusQ1HLWy4mSr5aaCfCVshj3YEN5uCrfDOXPkS5B1kd
kpnEZJt2tAUPLlIKD4Ytjq9A84bL6wHTrUg5NmZ8j+yfXfD0qE1rN69AZFQ2V72R
uKUbxvvx2T8ObJh1VpiSlMLLEeoEY1OUCZWB+Xz6GX9uL9B9zeD0+f9WcswI3UCw
9WEFgDHYWxgFciu7IYsCAwEAAaOB9TCB8jAJBgNVHRMEAjAAMCwGCWCGSAGG+EIB
DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUDs4X
d7VArujtnCJ3Ppht7hSfdL0wgZcGA1UdIwSBjzCBjKF/pH0wezELMAkGA1UEBhMC
VVMxDTALBgNVBAgTBFRlc3QxDTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3Qx
DTALBgNVBAsTBFRlc3QxDTALBgNVBAMTBFRlc3QxITAfBgkqhkiG9w0BCQEWEnRl
c3RAZW1haWwuYWRkcmVzc4IJAKmiuSJghxIGMA0GCSqGSIb3DQEBCwUAA4IBAQBm
f2VVj4Eqb+5pAgimkejDrYRzDgDQ4Eyr45vdUtu7JoGovGmkxg5z3izW/UKKj8GC
04aXIJiIu8d7mn5ZxuaIS0/mtVN167tVVI0wBlkQRK5dJNjn47fTixymEy4lwdUl
0iSb1JP6beVmSMIywD5lFxGPiW/MEJSvDCdlOT2Ojiv/Sbn9Q09PsXei0fAmNGZn
FEUSnlqgWkeGIIv3+//kY8pHlZ1RyYSShQ+3Vb8Qifx0lbiFQWDP82EgETu7JKWn
fKCoogSDybcLqB/WeGOQ0myXgEth5Lhkdo0n08J/FYL/bA1thADVnV66ZpERgb3h
38P4rEcobzZdVPcS4zwP
-----END CERTIFICATE-----

View File

@ -0,0 +1,403 @@
# 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.
import datetime
import mock
import os
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from cursive import certificate_utils
from cursive import exception
from cursive.tests import base
class TestCertificateUtils(base.TestCase):
"""Test methods for the certificate verification context and utilities"""
def setUp(self):
super(TestCertificateUtils, self).setUp()
self.cert_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'data'
)
def tearDown(self):
super(TestCertificateUtils, self).tearDown()
def load_certificate(self, cert_name):
# Load the raw certificate file data.
path = os.path.join(self.cert_path, cert_name)
with open(path, 'rb') as cert_file:
data = cert_file.read()
# Convert the raw certificate data into a certificate object, first
# as a PEM-encoded certificate and, if that fails, then as a
# DER-encoded certificate. If both fail, the certificate cannot be
# loaded.
try:
return x509.load_pem_x509_certificate(data, default_backend())
except Exception:
try:
return x509.load_der_x509_certificate(data, default_backend())
except Exception:
raise exception.SignatureVerificationError(
"Failed to load certificate: %s" % path
)
def load_certificates(self, cert_names):
certs = list()
for cert_name in cert_names:
cert = self.load_certificate(cert_name)
certs.append(cert)
return certs
@mock.patch('oslo_utils.timeutils.utcnow')
def test_is_within_valid_dates(self, mock_utcnow):
# Verify a certificate is valid at a time within its valid date range
cert = self.load_certificate('self_signed_cert.pem')
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
result = certificate_utils.is_within_valid_dates(cert)
self.assertEqual(True, result)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_is_before_valid_dates(self, mock_utcnow):
# Verify a certificate is invalid at a time before its valid date range
cert = self.load_certificate('self_signed_cert.pem')
mock_utcnow.return_value = datetime.datetime(2000, 1, 1)
result = certificate_utils.is_within_valid_dates(cert)
self.assertEqual(False, result)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_is_after_valid_dates(self, mock_utcnow):
# Verify a certificate is invalid at a time after its valid date range
cert = self.load_certificate('self_signed_cert.pem')
mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
result = certificate_utils.is_within_valid_dates(cert)
self.assertEqual(False, result)
def test_is_issuer(self):
# Test issuer and subject name matching for a self-signed certificate.
cert = self.load_certificate('self_signed_cert.pem')
result = certificate_utils.is_issuer(cert, cert)
self.assertEqual(True, result)
def test_is_not_issuer(self):
# Test issuer and subject name mismatching.
cert = self.load_certificate('self_signed_cert.pem')
alt = self.load_certificate('orphaned_cert.pem')
result = certificate_utils.is_issuer(cert, alt)
self.assertEqual(False, result)
def test_is_issuer_with_invalid_certs(self):
# Test issuer check with invalid certificates
cert = self.load_certificate('self_signed_cert.pem')
result = certificate_utils.is_issuer(cert, None)
self.assertEqual(False, result)
result = certificate_utils.is_issuer(None, cert)
self.assertEqual(False, result)
def test_can_sign_certificates(self):
# Test that a well-formatted certificate can sign
cert = self.load_certificate('self_signed_cert.pem')
result = certificate_utils.can_sign_certificates(cert, 'test-ID')
self.assertEqual(True, result)
def test_cannot_sign_certificates_without_basic_constraints(self):
# Verify a certificate without basic constraints cannot sign
cert = self.load_certificate(
'self_signed_cert_missing_ca_constraint.pem'
)
result = certificate_utils.can_sign_certificates(cert, 'test-ID')
self.assertEqual(False, result)
def test_cannot_sign_certificates_with_invalid_basic_constraints(self):
# Verify a certificate with invalid basic constraints cannot sign
cert = self.load_certificate(
'self_signed_cert_invalid_ca_constraint.pem'
)
result = certificate_utils.can_sign_certificates(cert, 'test-ID')
self.assertEqual(False, result)
def test_cannot_sign_certificates_without_key_usage(self):
# Verify a certificate without key usage cannot sign
cert = self.load_certificate('self_signed_cert_missing_key_usage.pem')
result = certificate_utils.can_sign_certificates(cert, 'test-ID')
self.assertEqual(False, result)
def test_cannot_sign_certificates_with_invalid_key_usage(self):
# Verify a certificate with invalid key usage cannot sign
cert = self.load_certificate('self_signed_cert_invalid_key_usage.pem')
result = certificate_utils.can_sign_certificates(cert, 'test-ID')
self.assertEqual(False, result)
def test_verify_signing_certificate(self):
signing_certificate = self.load_certificate('self_signed_cert.pem')
signed_certificate = self.load_certificate('signed_cert.pem')
certificate_utils.verify_certificate_signature(
signing_certificate,
signed_certificate
)
@mock.patch('cursive.signature_utils.get_certificate')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_verify_valid_certificate(self, mock_utcnow, mock_get_cert):
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der',
'signed_cert.pem']
)
mock_get_cert.side_effect = certs
cert_uuid = '3'
trusted_cert_uuids = ['1', '2']
certificate_utils.verify_certificate(
None, cert_uuid, trusted_cert_uuids
)
@mock.patch('cursive.signature_utils.get_certificate')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_verify_invalid_certificate(self, mock_utcnow, mock_get_cert):
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der',
'orphaned_cert.pem']
)
mock_get_cert.side_effect = certs
cert_uuid = '3'
trusted_cert_uuids = ['1', '2']
self.assertRaisesRegex(
exception.SignatureVerificationError,
"Certificate chain building failed. Could not locate the "
"signing certificate for the base certificate in the set of "
"trusted certificates.",
certificate_utils.verify_certificate,
None,
cert_uuid,
trusted_cert_uuids
)
@mock.patch('cursive.signature_utils.get_certificate')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_verify_valid_certificate_with_no_root(self, mock_utcnow,
mock_get_cert):
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
# Test verifying a valid certificate against an empty list of trusted
# certificates.
certs = self.load_certificates(['signed_cert.pem'])
mock_get_cert.side_effect = certs
cert_uuid = '3'
trusted_cert_uuids = []
self.assertRaisesRegex(
exception.SignatureVerificationError,
"Certificate chain building failed. Could not locate the "
"signing certificate for the base certificate in the set of "
"trusted certificates.",
certificate_utils.verify_certificate,
None,
cert_uuid,
trusted_cert_uuids
)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_init(self, mock_utcnow):
# Test constructing a context object with a valid set of certificates
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
context = certificate_utils.CertificateVerificationContext(
cert_tuples
)
self.assertEqual(2, len(context._signing_certificates))
for t in cert_tuples:
path, cert = t
self.assertIn(cert, [x[1] for x in context._signing_certificates])
@mock.patch('cursive.certificate_utils.LOG')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_init_with_invalid_certificate(self, mock_utcnow,
mock_log):
# Test constructing a context object with an invalid certificate
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
alt_cert_tuples = [('path', None)]
context = certificate_utils.CertificateVerificationContext(
alt_cert_tuples
)
self.assertEqual(0, len(context._signing_certificates))
self.assertEqual(1, mock_log.error.call_count)
@mock.patch('cursive.certificate_utils.LOG')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_init_with_non_signing_certificate(self, mock_utcnow,
mock_log):
# Test constructing a context object with an non-signing certificate
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
non_signing_cert = self.load_certificate(
'self_signed_cert_missing_key_usage.pem'
)
alt_cert_tuples = [('path', non_signing_cert)]
context = certificate_utils.CertificateVerificationContext(
alt_cert_tuples
)
self.assertEqual(0, len(context._signing_certificates))
self.assertEqual(1, mock_log.warning.call_count)
@mock.patch('cursive.certificate_utils.LOG')
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_init_with_out_of_date_certificate(self, mock_utcnow,
mock_log):
# Test constructing a context object with out-of-date certificates
mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
context = certificate_utils.CertificateVerificationContext(cert_tuples)
self.assertEqual(0, len(context._signing_certificates))
self.assertEqual(2, mock_log.warning.call_count)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_update_with_valid_certificate(self, mock_utcnow):
# Test updating the context with a valid certificate
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
context = certificate_utils.CertificateVerificationContext(cert_tuples)
cert = self.load_certificate('orphaned_cert.pem')
context.update(cert)
self.assertEqual(cert, context._signed_certificate)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_update_with_date_invalid_certificate(self, mock_utcnow):
# Test updating the context with an out-of-date certificate
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
context = certificate_utils.CertificateVerificationContext(cert_tuples)
cert = self.load_certificate('orphaned_cert.pem')
mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
self.assertRaisesRegex(
exception.SignatureVerificationError,
"The certificate is outside its valid date range.",
context.update,
cert
)
def test_context_update_with_invalid_certificate(self):
# Test updating the context with an invalid certificate
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
context = certificate_utils.CertificateVerificationContext(
cert_tuples
)
self.assertRaisesRegex(
exception.SignatureVerificationError,
"The certificate must be an x509.Certificate object.",
context.update,
None
)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_verify(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
# Test verification with a two-link certificate chain.
context = certificate_utils.CertificateVerificationContext(
cert_tuples
)
cert = self.load_certificate('signed_cert.pem')
context.update(cert)
context.verify()
# Test verification with a single-link certificate chain.
context = certificate_utils.CertificateVerificationContext(
cert_tuples
)
context.update(certs[0])
context.verify()
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_verify_disable_checks(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
certs = self.load_certificates(
['self_signed_cert.pem', 'self_signed_cert.der']
)
cert_tuples = [('1', certs[0]), ('2', certs[1])]
# Test verification with a two-link certificate chain.
context = certificate_utils.CertificateVerificationContext(
cert_tuples,
enforce_valid_dates=False,
enforce_signing_extensions=False,
enforce_path_length=False
)
cert = self.load_certificate('signed_cert.pem')
context.update(cert)
context.verify()
# Test verification with a single-link certificate chain.
context = certificate_utils.CertificateVerificationContext(
cert_tuples,
enforce_valid_dates=False,
enforce_signing_extensions=False,
enforce_path_length=False
)
context.update(certs[0])
context.verify()
@mock.patch('oslo_utils.timeutils.utcnow')
def test_context_verify_invalid_chain_length(self, mock_utcnow):
mock_utcnow.return_value = datetime.datetime(2017, 11, 1)
certs = self.load_certificates(
['grandparent_cert.pem', 'parent_cert.pem', 'child_cert.pem']
)
cert_tuples = [
('1', certs[0]),
('2', certs[1]),
('3', certs[2])
]
cert = self.load_certificate('grandchild_cert.pem')
context = certificate_utils.CertificateVerificationContext(
cert_tuples
)
context.update(cert)
self.assertRaisesRegex(
exception.SignatureVerificationError,
"Certificate validation failed. The signing certificate '1' is "
"not configured to support certificate chains of sufficient "
"length.",
context.verify
)
context = certificate_utils.CertificateVerificationContext(
cert_tuples,
enforce_path_length=False
)
context.update(cert)
context.verify()

View File

@ -12,6 +12,7 @@
import base64
import datetime
import mock
from castellan.common.exception import KeyManagerError
import cryptography.exceptions as crypto_exceptions
@ -20,7 +21,6 @@ from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa
import mock
from oslo_utils import timeutils
from cursive import exception
@ -110,6 +110,12 @@ class BadPublicKey(object):
class TestSignatureUtils(base.TestCase):
"""Test methods of signature_utils"""
def setUp(self):
super(TestSignatureUtils, self).setUp()
def tearDown(self):
super(TestSignatureUtils, self).tearDown()
def test_should_create_verifier(self):
image_props = {CERT_UUID: 'CERT_UUID',
HASH_METHOD: 'HASH_METHOD',
@ -283,7 +289,8 @@ class TestSignatureUtils(base.TestCase):
'RSB-PSS')
@mock.patch('cursive.signature_utils.get_certificate')
def test_get_public_key_rsa(self, mock_get_cert):
@mock.patch('cursive.certificate_utils.verify_certificate')
def test_get_public_key_rsa(self, mock_verify_cert, mock_get_cert):
fake_cert = FakeCryptoCertificate()
mock_get_cert.return_value = fake_cert
sig_key_type = signature_utils.SignatureKeyType.lookup(
@ -294,7 +301,8 @@ class TestSignatureUtils(base.TestCase):
self.assertEqual(fake_cert.public_key(), result_pub_key)
@mock.patch('cursive.signature_utils.get_certificate')
def test_get_public_key_ecc(self, mock_get_cert):
@mock.patch('cursive.certificate_utils.verify_certificate')
def test_get_public_key_ecc(self, mock_verify_cert, mock_get_cert):
fake_cert = FakeCryptoCertificate(TEST_ECC_PRIVATE_KEY.public_key())
mock_get_cert.return_value = fake_cert
sig_key_type = signature_utils.SignatureKeyType.lookup('ECC_SECP521R1')
@ -303,7 +311,8 @@ class TestSignatureUtils(base.TestCase):
self.assertEqual(fake_cert.public_key(), result_pub_key)
@mock.patch('cursive.signature_utils.get_certificate')
def test_get_public_key_dsa(self, mock_get_cert):
@mock.patch('cursive.certificate_utils.verify_certificate')
def test_get_public_key_dsa(self, mock_verify_cert, mock_get_cert):
fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key())
mock_get_cert.return_value = fake_cert
sig_key_type = signature_utils.SignatureKeyType.lookup(
@ -314,7 +323,9 @@ class TestSignatureUtils(base.TestCase):
self.assertEqual(fake_cert.public_key(), result_pub_key)
@mock.patch('cursive.signature_utils.get_certificate')
def test_get_public_key_invalid_key(self, mock_get_certificate):
@mock.patch('cursive.certificate_utils.verify_certificate')
def test_get_public_key_invalid_key(self, mock_verify_certificate,
mock_get_certificate):
bad_pub_key = 'A' * 256
mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key)
sig_key_type = signature_utils.SignatureKeyType.lookup(
@ -335,34 +346,6 @@ class TestSignatureUtils(base.TestCase):
self.assertEqual(x509_cert,
signature_utils.get_certificate(None, cert_uuid))
@mock.patch('cryptography.x509.load_der_x509_certificate')
@mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
def test_get_expired_certificate(self, mock_key_manager_API,
mock_load_cert):
cert_uuid = 'valid_format_cert'
x509_cert = FakeCryptoCertificate(
not_valid_after=timeutils.utcnow() -
datetime.timedelta(hours=1))
mock_load_cert.return_value = x509_cert
self.assertRaisesRegex(exception.SignatureVerificationError,
'Certificate is not valid after: .*',
signature_utils.get_certificate, None,
cert_uuid)
@mock.patch('cryptography.x509.load_der_x509_certificate')
@mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
def test_get_not_yet_valid_certificate(self, mock_key_manager_API,
mock_load_cert):
cert_uuid = 'valid_format_cert'
x509_cert = FakeCryptoCertificate(
not_valid_before=timeutils.utcnow() +
datetime.timedelta(hours=1))
mock_load_cert.return_value = x509_cert
self.assertRaisesRegex(exception.SignatureVerificationError,
'Certificate is not valid before: .*',
signature_utils.get_certificate, None,
cert_uuid)
@mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
def test_get_certificate_key_manager_fail(self, mock_key_manager_API):
bad_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0695'

View File

@ -0,0 +1,37 @@
---
prelude: >
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.