Browse Source

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
Peter Hamilton 2 years ago
parent
commit
ad879a1fbc

+ 350
- 0
cursive/certificate_utils.py View File

@@ -0,0 +1,350 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+"""Support certificate validation."""
14
+
15
+from cryptography.hazmat.primitives.asymmetric import ec
16
+from cryptography.hazmat.primitives.asymmetric import padding
17
+from cryptography.hazmat.primitives.asymmetric import rsa
18
+from cryptography import x509, exceptions as cryptography_exceptions
19
+from oslo_log import log as logging
20
+from oslo_utils import timeutils
21
+
22
+from cursive import exception
23
+from cursive import signature_utils
24
+
25
+LOG = logging.getLogger(__name__)
26
+
27
+
28
+def is_within_valid_dates(certificate):
29
+    """Determine if the certificate is outside its valid date range.
30
+
31
+    :param certificate: the cryptography certificate object
32
+    :return: False if the certificate valid time range does not include
33
+             now, True otherwise.
34
+    """
35
+    # Get now in UTC, since certificate returns times in UTC
36
+    now = timeutils.utcnow()
37
+
38
+    # Confirm the certificate valid time range includes now
39
+    if now < certificate.not_valid_before:
40
+        return False
41
+    elif now > certificate.not_valid_after:
42
+        return False
43
+    return True
44
+
45
+
46
+def is_issuer(issuing_certificate, issued_certificate):
47
+    """Determine if the issuing cert is the parent of the issued cert.
48
+
49
+    Determine if the issuing certificate is the parent of the issued
50
+    certificate by:
51
+    * conducting subject and issuer name matching, and
52
+    * verifying the signature of the issued certificate with the issuing
53
+      certificate's public key
54
+
55
+    :param issuing_certificate: the cryptography certificate object that
56
+           is the potential parent of the issued certificate
57
+    :param issued_certificate: the cryptography certificate object that
58
+           is the potential child of the issuing certificate
59
+    :return: True if the issuing certificate is the parent of the issued
60
+             certificate, False otherwise.
61
+    """
62
+    if (issuing_certificate is None) or (issued_certificate is None):
63
+        return False
64
+    elif issuing_certificate.subject != issued_certificate.issuer:
65
+        return False
66
+    else:
67
+        try:
68
+            verify_certificate_signature(
69
+                issuing_certificate,
70
+                issued_certificate
71
+            )
72
+        except cryptography_exceptions.InvalidSignature:
73
+            # If verification fails, an exception is expected.
74
+            return False
75
+        return True
76
+
77
+
78
+def can_sign_certificates(certificate, certificate_uuid=''):
79
+    """Determine if the certificate can sign other certificates.
80
+
81
+    :param certificate: the cryptography certificate object
82
+    :param certificate_uuid: the uuid of the certificate
83
+    :return: False if the certificate cannot sign other certificates,
84
+             True otherwise.
85
+    """
86
+    try:
87
+        basic_constraints = certificate.extensions.get_extension_for_oid(
88
+            x509.oid.ExtensionOID.BASIC_CONSTRAINTS
89
+        ).value
90
+    except x509.extensions.ExtensionNotFound:
91
+        LOG.debug(
92
+            "Certificate '%s' does not have a basic constraints extension.",
93
+            certificate_uuid)
94
+        return False
95
+
96
+    try:
97
+        key_usage = certificate.extensions.get_extension_for_oid(
98
+            x509.oid.ExtensionOID.KEY_USAGE
99
+        ).value
100
+    except x509.extensions.ExtensionNotFound:
101
+        LOG.debug(
102
+            "Certificate '%s' does not have a key usage extension.",
103
+            certificate_uuid)
104
+        return False
105
+
106
+    if basic_constraints.ca and key_usage.key_cert_sign:
107
+        return True
108
+
109
+    if not basic_constraints.ca:
110
+        LOG.debug(
111
+            "Certificate '%s' is not marked as a CA in its basic constraints "
112
+            "extension.",
113
+            certificate_uuid)
114
+    if not key_usage.key_cert_sign:
115
+        LOG.debug(
116
+            "Certificate '%s' is not marked for verifying certificate "
117
+            "signatures in its key usage extension.",
118
+            certificate_uuid)
119
+
120
+    return False
121
+
122
+
123
+def verify_certificate_signature(signing_certificate, certificate):
124
+    """Verify that the certificate was signed correctly.
125
+
126
+    :param signing_certificate: the cryptography certificate object used to
127
+           sign the certificate
128
+    :param certificate: the cryptography certificate object that was signed
129
+           by the signing certificate
130
+    :raises: cryptography.exceptions.InvalidSignature if certificate signature
131
+             verification fails.
132
+    """
133
+    signature_hash_algorithm = certificate.signature_hash_algorithm
134
+    signature_bytes = certificate.signature
135
+    signer_public_key = signing_certificate.public_key()
136
+
137
+    if isinstance(signer_public_key, rsa.RSAPublicKey):
138
+        verifier = signer_public_key.verifier(
139
+            signature_bytes, padding.PKCS1v15(), signature_hash_algorithm
140
+        )
141
+    elif isinstance(signer_public_key, ec.EllipticCurvePublicKey):
142
+        verifier = signer_public_key.verifier(
143
+            signature_bytes, ec.ECDSA(signature_hash_algorithm)
144
+        )
145
+    else:
146
+        verifier = signer_public_key.verifier(
147
+            signature_bytes, signature_hash_algorithm
148
+        )
149
+
150
+    verifier.update(certificate.tbs_certificate_bytes)
151
+    verifier.verify()
152
+
153
+
154
+def verify_certificate(context, certificate_uuid,
155
+                       trusted_certificate_uuids,
156
+                       enforce_valid_dates=True,
157
+                       enforce_signing_extensions=True,
158
+                       enforce_path_length=True):
159
+    """Validate a certificate against a set of trusted certificates.
160
+
161
+    From the key manager, load the set of trusted certificates and the
162
+    certificate to validate. Store the trusted certificates in a certificate
163
+    verification context. Use the context to verify that the certificate is
164
+    cryptographically linked to at least one of the trusted certificates.
165
+
166
+    :param context: the user context for authentication
167
+    :param certificate_uuid: the uuid of a certificate to validate, stored in
168
+           the key manager
169
+    :param trusted_certificate_uuids: a list containing the uuids of trusted
170
+           certificates stored in the key manager
171
+    :param enforce_valid_dates: a boolean indicating whether date checking
172
+           should be enforced during certificate verification, defaults to
173
+           True
174
+    :param enforce_signing_extensions: a boolean indicating whether extension
175
+           checking should be enforced during certificate verification,
176
+           defaults to True
177
+    :param enforce_path_length: a boolean indicating whether path length
178
+           constraints should be enforced during certificate verification,
179
+           defaults to True
180
+    :raises: SignatureVerificationError if the certificate verification fails
181
+             for any reason.
182
+    """
183
+    trusted_certificates = list()
184
+    for uuid in trusted_certificate_uuids:
185
+        try:
186
+            trusted_certificates.append(
187
+                (uuid, signature_utils.get_certificate(context, uuid))
188
+            )
189
+        except exception.SignatureVerificationError:
190
+            LOG.warning("Skipping trusted certificate: %(id)s" % {'id': uuid})
191
+
192
+    certificate = signature_utils.get_certificate(context, certificate_uuid)
193
+    certificate_context = CertificateVerificationContext(
194
+        trusted_certificates,
195
+        enforce_valid_dates=enforce_valid_dates,
196
+        enforce_signing_extensions=enforce_signing_extensions,
197
+        enforce_path_length=enforce_path_length
198
+    )
199
+    certificate_context.update(certificate)
200
+    certificate_context.verify()
201
+
202
+
203
+class CertificateVerificationContext(object):
204
+    """A collection of signing certificates.
205
+
206
+    A collection of signing certificates that may be used to verify the
207
+    signatures of other certificates.
208
+    """
209
+
210
+    def __init__(self, certificate_tuples, enforce_valid_dates=True,
211
+                 enforce_signing_extensions=True,
212
+                 enforce_path_length=True):
213
+        self._signing_certificates = []
214
+        for certificate_tuple in certificate_tuples:
215
+            certificate_uuid, certificate = certificate_tuple
216
+            if not isinstance(certificate, x509.Certificate):
217
+                LOG.error(
218
+                    "A signing certificate must be an x509.Certificate object."
219
+                )
220
+                continue
221
+
222
+            if enforce_valid_dates:
223
+                if not is_within_valid_dates(certificate):
224
+                    LOG.warning(
225
+                        "Certificate '%s' is outside its valid date range and "
226
+                        "cannot be used as a signing certificate.",
227
+                        certificate_uuid)
228
+                    continue
229
+
230
+            if enforce_signing_extensions:
231
+                if not can_sign_certificates(certificate, certificate_uuid):
232
+                    LOG.warning(
233
+                        "Certificate '%s' is not configured to act as a "
234
+                        "signing certificate. It will not be used as a "
235
+                        "signing certificate.",
236
+                        certificate_uuid)
237
+                    continue
238
+            self._signing_certificates.append(certificate_tuple)
239
+
240
+        self._signed_certificate = None
241
+        self._enforce_valid_dates = enforce_valid_dates
242
+        self._enforce_path_length = enforce_path_length
243
+
244
+    def update(self, certificate):
245
+        """Process the certificate to be verified.
246
+
247
+        Raises an exception if the certificate is invalid. Stores it
248
+        otherwise.
249
+
250
+        :param certificate: the cryptography certificate to be verified
251
+        :raises: SignatureVerificationError if the certificate is not of the
252
+                 right type or if it is outside its valid date range.
253
+        """
254
+        if not isinstance(certificate, x509.Certificate):
255
+            raise exception.SignatureVerificationError(
256
+                "The certificate must be an x509.Certificate object."
257
+            )
258
+
259
+        if self._enforce_valid_dates:
260
+            if not is_within_valid_dates(certificate):
261
+                raise exception.SignatureVerificationError(
262
+                    "The certificate is outside its valid date range."
263
+                )
264
+
265
+        self._signed_certificate = certificate
266
+
267
+    def verify(self):
268
+        """Locate the certificate's signing certificate and verify it.
269
+
270
+        Locate the certificate's signing certificate in the context
271
+        certificate cache, using both subject/issuer name matching and
272
+        signature verification. If the certificate is self-signed, verify that
273
+        it is also located in the context's certificate cache. Construct the
274
+        certificate chain from certificates in the context certificate cache.
275
+        Verify that the signing certificate can have a sufficient number of
276
+        child certificates to support the chain.
277
+
278
+        :raises: SignatureVerificationError if certificate validation fails
279
+                 for any reason, including mismatched signatures or a failure
280
+                 to find the required signing certificate.
281
+        """
282
+        signed_certificate = self._signed_certificate
283
+        certificate_chain = [('base', signed_certificate)]
284
+
285
+        # Build the certificate chain.
286
+        while True:
287
+            signing_certificate_tuple = None
288
+
289
+            # Search for the signing certificate
290
+            for certificate_tuple in self._signing_certificates:
291
+                _, candidate = certificate_tuple
292
+                if is_issuer(candidate, signed_certificate):
293
+                    signing_certificate_tuple = certificate_tuple
294
+                    break
295
+
296
+            # If a valid signing certificate is found, prepare to find the
297
+            # next link in the certificate chain. Otherwise, raise an error.
298
+            if signing_certificate_tuple:
299
+                # If the certificate is self-signed, the root of the
300
+                # certificate chain has been found. Otherwise, repeat the
301
+                # verification process using the newly found signing
302
+                # certificate.
303
+                if signed_certificate == signing_certificate_tuple[1]:
304
+                    break
305
+                else:
306
+                    certificate_chain.insert(0, signing_certificate_tuple)
307
+                    signed_certificate = signing_certificate_tuple[1]
308
+            else:
309
+                uuid = certificate_chain[0][0]
310
+                raise exception.SignatureVerificationError(
311
+                    "Certificate chain building failed. Could not locate the "
312
+                    "signing certificate for %s in the set of trusted "
313
+                    "certificates." %
314
+                    "the base certificate" if uuid == 'base'
315
+                    else "certificate '%s'" % uuid
316
+                )
317
+
318
+        if self._enforce_path_length:
319
+            # Verify that each certificate's path length constraint allows
320
+            # for it to support the rest of the certificate chain.
321
+            for i in range(len(certificate_chain)):
322
+                certificate = certificate_chain[i][1]
323
+
324
+                # No need to check the last certificate in the chain.
325
+                if certificate == certificate_chain[-1][1]:
326
+                    break
327
+
328
+                try:
329
+                    constraints = certificate.extensions.get_extension_for_oid(
330
+                        x509.oid.ExtensionOID.BASIC_CONSTRAINTS
331
+                    ).value
332
+                except x509.extensions.ExtensionNotFound:
333
+                    raise exception.SignatureVerificationError(
334
+                        "Certificate validation failed. The signing "
335
+                        "certificate '%s' does not have a basic constraints "
336
+                        "extension." % certificate_chain[i][0]
337
+                    )
338
+
339
+                # Path length only applies to non-self-issued intermediate
340
+                # certificates. Do not include the current or end certificates
341
+                # when computing path length.
342
+                chain_length = len(certificate_chain[i:])
343
+                chain_length = (chain_length - 2) if chain_length > 2 else 0
344
+                if constraints.path_length < chain_length:
345
+                    raise exception.SignatureVerificationError(
346
+                        "Certificate validation failed. The signing "
347
+                        "certificate '%s' is not configured to support "
348
+                        "certificate chains of sufficient "
349
+                        "length." % certificate_chain[i][0]
350
+                    )

+ 1
- 25
cursive/signature_utils.py View File

@@ -26,7 +26,6 @@ from cryptography import x509
26 26
 from oslo_log import log as logging
27 27
 from oslo_serialization import base64
28 28
 from oslo_utils import encodeutils
29
-from oslo_utils import timeutils
30 29
 
31 30
 from cursive import exception
32 31
 from cursive.i18n import _, _LE
@@ -70,6 +69,7 @@ MASK_GEN_ALGORITHMS = {
70 69
     'MGF1': padding.MGF1,
71 70
 }
72 71
 
72
+
73 73
 # Required image property names
74 74
 (SIGNATURE, HASH_METHOD, KEY_TYPE, CERT_UUID) = (
75 75
     'img_signature',
@@ -336,28 +336,4 @@ def get_certificate(context, signature_certificate_uuid):
336 336
         certificate = x509.load_der_x509_certificate(cert_data,
337 337
                                                      default_backend())
338 338
 
339
-    # verify the certificate
340
-    verify_certificate(certificate)
341
-
342 339
     return certificate
343
-
344
-
345
-def verify_certificate(certificate):
346
-    """Verify that the certificate has not expired.
347
-
348
-    :param certificate: the cryptography certificate object
349
-    :raises: SignatureVerificationError if the certificate valid time range
350
-             does not include now
351
-    """
352
-    # Get now in UTC, since certificate returns times in UTC
353
-    now = timeutils.utcnow()
354
-
355
-    # Confirm the certificate valid time range includes now
356
-    if now < certificate.not_valid_before:
357
-        raise exception.SignatureVerificationError(
358
-            reason=_('Certificate is not valid before: %s UTC')
359
-            % certificate.not_valid_before)
360
-    elif now > certificate.not_valid_after:
361
-        raise exception.SignatureVerificationError(
362
-            reason=_('Certificate is not valid after: %s UTC')
363
-            % certificate.not_valid_after)

+ 87
- 0
cursive/tests/unit/data/child_cert.pem View File

@@ -0,0 +1,87 @@
1
+Certificate:
2
+    Data:
3
+        Version: 3 (0x2)
4
+        Serial Number: 10 (0xa)
5
+    Signature Algorithm: sha256WithRSAEncryption
6
+        Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Parent
7
+        Validity
8
+            Not Before: Oct  3 18:02:45 2017 GMT
9
+            Not After : Oct  1 18:02:45 2027 GMT
10
+        Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Child
11
+        Subject Public Key Info:
12
+            Public Key Algorithm: rsaEncryption
13
+                Public-Key: (2048 bit)
14
+                Modulus:
15
+                    00:b2:45:31:e4:99:12:1f:0f:c3:5b:78:98:39:a5:
16
+                    d0:da:c6:9f:38:23:09:df:fd:35:b6:95:b6:37:5d:
17
+                    b6:49:f2:a5:f1:62:75:62:41:09:9d:36:e5:53:c8:
18
+                    82:1a:5c:9d:2a:fd:03:9c:a9:00:6d:28:b3:29:bb:
19
+                    cf:f3:eb:0f:5c:c9:81:8d:69:e1:04:f7:9a:1c:09:
20
+                    33:ab:54:c1:ac:0c:d7:d1:11:79:6c:6f:c0:2b:54:
21
+                    9e:c2:86:85:05:a3:e4:70:06:84:42:eb:8b:c0:0e:
22
+                    3a:73:16:cd:13:79:a5:43:e6:89:8b:c3:7f:6b:04:
23
+                    cd:7f:34:6b:4a:47:65:c3:4a:6a:d3:ea:8e:57:34:
24
+                    5d:39:18:fc:d0:8e:e4:f6:ff:74:86:a0:98:06:67:
25
+                    40:0c:8f:a6:5e:46:9d:ed:b9:25:99:7c:4c:62:b8:
26
+                    19:ae:12:1e:33:0b:d3:43:b9:3c:bc:5a:f3:6b:c6:
27
+                    a9:1c:c1:ce:99:1f:64:b7:a3:8d:ed:c8:3e:95:75:
28
+                    19:e5:ce:51:f1:11:f1:c0:58:76:87:ee:42:12:a4:
29
+                    ff:8e:c6:e8:42:3d:b4:df:c7:be:a6:c7:ea:6c:88:
30
+                    04:4b:d3:f3:9b:7f:d4:db:87:21:55:36:2e:3c:1c:
31
+                    c9:21:4a:2f:7f:51:f0:08:d7:21:ea:75:c4:e2:78:
32
+                    91:9d
33
+                Exponent: 65537 (0x10001)
34
+        X509v3 extensions:
35
+            X509v3 Subject Key Identifier: 
36
+                C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B
37
+            X509v3 Authority Key Identifier: 
38
+                keyid:7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49
39
+
40
+            X509v3 Basic Constraints: 
41
+                CA:TRUE, pathlen:0
42
+            X509v3 Key Usage: 
43
+                Certificate Sign, CRL Sign
44
+            X509v3 Subject Alternative Name: 
45
+                DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com
46
+            Netscape Comment: 
47
+                OpenSSL Generated Certificate
48
+    Signature Algorithm: sha256WithRSAEncryption
49
+         10:46:2e:1e:37:b8:10:4a:8c:e3:76:7c:05:57:76:34:05:0b:
50
+         e2:ed:b3:1b:28:20:2b:56:9b:2d:59:70:e5:4e:5e:ce:a8:11:
51
+         d5:c1:9b:e7:c8:0e:61:2b:63:ae:d2:1b:ec:cf:75:31:d0:4f:
52
+         35:86:c2:51:22:64:c3:07:a7:c4:6b:13:57:cc:e5:d9:86:8d:
53
+         b4:73:45:c5:ca:48:b7:b6:02:1e:c7:de:71:c6:5f:2a:64:7d:
54
+         b5:5b:16:9a:27:7d:5f:3c:8a:5e:95:38:7f:c0:7e:d4:39:3f:
55
+         36:60:7d:7d:8e:9f:72:06:d4:69:7a:e5:45:3f:e2:c9:eb:7f:
56
+         5f:74:1a:6b:6c:b8:a1:08:05:d9:25:ee:d4:97:db:5a:72:1f:
57
+         4a:06:a9:86:76:41:58:34:0b:5a:39:be:65:ec:26:b1:13:41:
58
+         6b:86:58:fa:2e:cd:ab:06:d2:59:0e:bb:e4:44:2c:de:21:d1:
59
+         8c:9c:93:a5:d5:ae:fc:af:37:b0:91:1f:46:61:28:b9:a5:c8:
60
+         b4:3c:28:33:b1:d9:ca:49:53:fe:14:80:82:de:06:c1:ab:21:
61
+         e7:44:76:04:d8:85:b4:60:72:30:7a:28:b7:6f:4d:9e:52:70:
62
+         21:df:4e:71:aa:01:d6:ba:fa:4b:4a:61:75:9c:57:67:a6:b2:
63
+         e7:ab:24:6c
64
+-----BEGIN CERTIFICATE-----
65
+MIID9jCCAt6gAwIBAgIBCjANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEN
66
+MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEUMBIG
67
+A1UEAwwLVGVzdCBQYXJlbnQwHhcNMTcxMDAzMTgwMjQ1WhcNMjcxMDAxMTgwMjQ1
68
+WjBPMQswCQYDVQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDEN
69
+MAsGA1UECgwEVGVzdDETMBEGA1UEAwwKVGVzdCBDaGlsZDCCASIwDQYJKoZIhvcN
70
+AQEBBQADggEPADCCAQoCggEBALJFMeSZEh8Pw1t4mDml0NrGnzgjCd/9NbaVtjdd
71
+tknypfFidWJBCZ025VPIghpcnSr9A5ypAG0osym7z/PrD1zJgY1p4QT3mhwJM6tU
72
+wawM19EReWxvwCtUnsKGhQWj5HAGhELri8AOOnMWzRN5pUPmiYvDf2sEzX80a0pH
73
+ZcNKatPqjlc0XTkY/NCO5Pb/dIagmAZnQAyPpl5Gne25JZl8TGK4Ga4SHjML00O5
74
+PLxa82vGqRzBzpkfZLejje3IPpV1GeXOUfER8cBYdofuQhKk/47G6EI9tN/HvqbH
75
+6myIBEvT85t/1NuHIVU2LjwcySFKL39R8AjXIep1xOJ4kZ0CAwEAAaOB2zCB2DAd
76
+BgNVHQ4EFgQUwgPq/H5wfzQhwb4zDorgfsaiIRswHwYDVR0jBBgwFoAUer59CVpf
77
+XN7Mgho7/qjtyroWWEkwDwYDVR0TBAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwSgYD
78
+VR0RBEMwQYILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIQbWFpbC5leGFt
79
+cGxlLmNvbYIPZnRwLmV4YW1wbGUuY29tMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM
80
+IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAEEYuHje4
81
+EEqM43Z8BVd2NAUL4u2zGyggK1abLVlw5U5ezqgR1cGb58gOYStjrtIb7M91MdBP
82
+NYbCUSJkwwenxGsTV8zl2YaNtHNFxcpIt7YCHsfeccZfKmR9tVsWmid9XzyKXpU4
83
+f8B+1Dk/NmB9fY6fcgbUaXrlRT/iyet/X3Qaa2y4oQgF2SXu1JfbWnIfSgaphnZB
84
+WDQLWjm+ZewmsRNBa4ZY+i7NqwbSWQ675EQs3iHRjJyTpdWu/K83sJEfRmEouaXI
85
+tDwoM7HZyklT/hSAgt4Gwash50R2BNiFtGByMHoot29NnlJwId9OcaoB1rr6S0ph
86
+dZxXZ6ay56skbA==
87
+-----END CERTIFICATE-----

+ 87
- 0
cursive/tests/unit/data/grandchild_cert.pem View File

@@ -0,0 +1,87 @@
1
+Certificate:
2
+    Data:
3
+        Version: 3 (0x2)
4
+        Serial Number: 11 (0xb)
5
+    Signature Algorithm: sha256WithRSAEncryption
6
+        Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Child
7
+        Validity
8
+            Not Before: Oct  3 18:09:07 2017 GMT
9
+            Not After : Oct  1 18:09:07 2027 GMT
10
+        Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Grandchild
11
+        Subject Public Key Info:
12
+            Public Key Algorithm: rsaEncryption
13
+                Public-Key: (2048 bit)
14
+                Modulus:
15
+                    00:bd:3c:4b:2a:e8:03:5d:07:ae:94:f4:19:ed:00:
16
+                    21:20:dd:1c:54:f1:dc:44:d8:bf:66:b4:bf:ce:21:
17
+                    7b:bf:b4:15:7b:b3:4f:0e:d5:ef:fa:f1:31:ab:2a:
18
+                    22:78:72:20:7d:ce:58:c3:45:0d:2f:5c:23:7c:87:
19
+                    07:bf:ee:8c:8c:9f:ae:31:70:19:61:dc:92:b5:8f:
20
+                    fb:36:16:1c:08:d4:2c:c0:0c:86:e0:ee:a8:31:20:
21
+                    21:16:41:b2:78:bc:88:a8:ef:4c:3a:34:4f:a0:08:
22
+                    25:e7:35:e8:bc:66:d3:c3:b5:2a:05:34:91:b0:d0:
23
+                    ae:02:f2:a1:58:22:af:43:42:d8:40:82:0c:e3:26:
24
+                    72:22:06:d2:b1:13:87:04:83:70:f6:b0:99:39:bf:
25
+                    79:26:f6:e2:ff:24:c3:72:48:9f:68:0a:c1:c9:aa:
26
+                    b1:a8:b4:f7:cf:44:38:4a:77:bf:56:20:fa:7e:08:
27
+                    75:26:04:fb:5e:d5:4f:ff:b8:45:1f:80:12:fb:7e:
28
+                    61:7e:52:f0:dc:71:ee:72:91:27:fa:60:93:96:e5:
29
+                    78:1d:d9:fd:5a:b8:00:b9:97:46:12:b5:2a:93:0e:
30
+                    c3:1b:30:6e:b2:67:5d:c5:ca:40:3f:36:0c:7c:4f:
31
+                    d4:48:e0:1f:32:a9:28:0c:37:35:7c:5d:42:f5:cb:
32
+                    54:b9
33
+                Exponent: 65537 (0x10001)
34
+        X509v3 extensions:
35
+            X509v3 Subject Key Identifier: 
36
+                4F:6C:A8:1F:80:F0:A6:EE:41:85:B9:A2:3F:EC:3A:B2:93:B4:0E:86
37
+            X509v3 Authority Key Identifier: 
38
+                keyid:C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B
39
+
40
+            X509v3 Basic Constraints: 
41
+                CA:FALSE
42
+            X509v3 Key Usage: 
43
+                Digital Signature, Key Encipherment
44
+            X509v3 Subject Alternative Name: 
45
+                DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com
46
+            Netscape Comment: 
47
+                OpenSSL Generated Certificate
48
+    Signature Algorithm: sha256WithRSAEncryption
49
+         ae:a7:62:9e:f6:b7:e3:02:84:0f:fe:c6:7c:c1:0b:74:8e:95:
50
+         c3:2e:e9:5f:c0:8b:fc:79:45:53:5c:34:9d:b0:de:e6:cf:ed:
51
+         52:4c:3f:6a:3f:e9:8d:a3:58:d4:ae:4d:31:30:57:d5:31:f9:
52
+         a2:ed:82:e2:ae:1a:65:a5:ab:de:64:35:c9:0b:d1:86:b0:83:
53
+         57:8a:e4:ca:21:d5:9a:79:5b:44:42:ff:52:9a:51:b6:f4:6e:
54
+         f1:da:dd:3b:ca:12:cb:4c:e5:9f:a5:12:4f:13:99:85:79:c8:
55
+         00:3b:2c:25:7f:02:07:a3:4e:59:0b:4d:8e:f8:43:08:a9:91:
56
+         30:0a:17:1c:ff:91:c0:16:d5:c0:1e:ec:a5:24:c8:cc:f0:2c:
57
+         0e:30:b9:bb:34:11:83:e7:4d:02:e4:2d:2a:90:98:eb:d8:ae:
58
+         7b:2f:19:31:db:63:fc:0c:0b:47:f5:8f:7b:cf:99:0b:30:91:
59
+         a6:44:19:51:7f:15:4f:ab:8c:08:e2:bd:91:42:e4:e7:88:8e:
60
+         c0:ea:fd:09:ac:96:c6:14:ef:0e:7d:75:6a:05:b0:b5:4d:43:
61
+         60:62:31:85:61:cb:c3:0f:81:24:d6:de:10:42:54:ff:c0:63:
62
+         95:40:3d:89:52:f9:00:2a:a5:74:1c:b1:42:be:a1:2f:de:90:
63
+         cb:d5:a7:3d
64
+-----BEGIN CERTIFICATE-----
65
+MIID9DCCAtygAwIBAgIBCzANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJVUzEN
66
+MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDETMBEG
67
+A1UEAwwKVGVzdCBDaGlsZDAeFw0xNzEwMDMxODA5MDdaFw0yNzEwMDExODA5MDda
68
+MFQxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MQ0w
69
+CwYDVQQKDARUZXN0MRgwFgYDVQQDDA9UZXN0IEdyYW5kY2hpbGQwggEiMA0GCSqG
70
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9PEsq6ANdB66U9BntACEg3RxU8dxE2L9m
71
+tL/OIXu/tBV7s08O1e/68TGrKiJ4ciB9zljDRQ0vXCN8hwe/7oyMn64xcBlh3JK1
72
+j/s2FhwI1CzADIbg7qgxICEWQbJ4vIio70w6NE+gCCXnNei8ZtPDtSoFNJGw0K4C
73
+8qFYIq9DQthAggzjJnIiBtKxE4cEg3D2sJk5v3km9uL/JMNySJ9oCsHJqrGotPfP
74
+RDhKd79WIPp+CHUmBPte1U//uEUfgBL7fmF+UvDcce5ykSf6YJOW5Xgd2f1auAC5
75
+l0YStSqTDsMbMG6yZ13FykA/Ngx8T9RI4B8yqSgMNzV8XUL1y1S5AgMBAAGjgdUw
76
+gdIwHQYDVR0OBBYEFE9sqB+A8KbuQYW5oj/sOrKTtA6GMB8GA1UdIwQYMBaAFMID
77
+6vx+cH80IcG+Mw6K4H7GoiEbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMEoGA1Ud
78
+EQRDMEGCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22CEG1haWwuZXhhbXBs
79
+ZS5jb22CD2Z0cC5leGFtcGxlLmNvbTAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH
80
+ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAK6nYp72t+MC
81
+hA/+xnzBC3SOlcMu6V/Ai/x5RVNcNJ2w3ubP7VJMP2o/6Y2jWNSuTTEwV9Ux+aLt
82
+guKuGmWlq95kNckL0Yawg1eK5Moh1Zp5W0RC/1KaUbb0bvHa3TvKEstM5Z+lEk8T
83
+mYV5yAA7LCV/AgejTlkLTY74QwipkTAKFxz/kcAW1cAe7KUkyMzwLA4wubs0EYPn
84
+TQLkLSqQmOvYrnsvGTHbY/wMC0f1j3vPmQswkaZEGVF/FU+rjAjivZFC5OeIjsDq
85
+/QmslsYU7w59dWoFsLVNQ2BiMYVhy8MPgSTW3hBCVP/AY5VAPYlS+QAqpXQcsUK+
86
+oS/ekMvVpz0=
87
+-----END CERTIFICATE-----

+ 34
- 0
cursive/tests/unit/data/grandparent_cert.pem View File

@@ -0,0 +1,34 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIF7jCCA9agAwIBAgIJANHiL5B0pUVmMA0GCSqGSIb3DQEBCwUAMIGDMQswCQYD
3
+VQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwE
4
+VGVzdDENMAsGA1UECwwEVGVzdDEZMBcGA1UEAwwQVGVzdCBHcmFuZHBhcmVudDEd
5
+MBsGCSqGSIb3DQEJARYOZ3BAZXhhbXBsZS5jb20wHhcNMTcxMDAzMTc0NzMyWhcN
6
+MTcxMTAyMTc0NzMyWjCBgzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTAL
7
+BgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxGTAXBgNV
8
+BAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG9w0BCQEWDmdwQGV4YW1wbGUu
9
+Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn4m1O+fffNTSnGE5
10
+MPac07jjMNrKHEjARS4aM222C8wCPiXXrs1diTQlvtxrLFOzc0gtCH6xVl59Zcis
11
+H7kBf8oV9wNIHcfy2BmGkP7Wv68p3UsIF1IzGSHPyKJWG+l/xNexuXFaVG9y+siu
12
+5Z3bx9DMBPFfXalxwRGoS0fyBOG/tXlqicf/aojF1U3UolML58URQqQ7IvGjEq22
13
+iqAfduEwLlLb99iJ8uiFgO6Rl/hwxvy9gmrGWJGHpHQKJ2Dx37Zc8MMcAJ5yos7c
14
+GAs3e31TvRJgyEBcPKtl+xmh36wNC+V1KKRYKAfENqB6v7b1GDZrVtH7uHvSPCDQ
15
+ccKklF2thomO4cm6UpbCF7/5i50OLwtr1TcUI/YT+nR/YsCuc/PKdyXITpP0CNR9
16
+Wcw6pb5LsWgLisFh4my4PzTbwTcPGFUJHq0CUUsRP1YijMNRtiZsoMJwspMZyg9d
17
+9Ufxf7HjbugazUfvfIKMfX/s/pIZLiDLx5lV9RbHmjvlCt4OfH3FhqBveguqDLq7
18
+LbBC6tYm1E21izUqXy4Zh+oogvZeZGUBQL/JUJ6XOkUnffv1nf2HDsCmlW64NLce
19
+9gc25BtXAAf5/tMQL2J3t5SZ+Ladk+nklQXz6eClFcLEbRcd15bZ3QFXCajeLWcP
20
+Y5TZFgDQFFr7/FjDhr2bByMOJEkCAwEAAaNjMGEwHQYDVR0OBBYEFKfrobRNKmLV
21
+OJDtRPp3f/m66pkEMB8GA1UdIwQYMBaAFKfrobRNKmLVOJDtRPp3f/m66pkEMBIG
22
+A1UdEwEB/wQIMAYBAf8CAQEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IC
23
+AQByRvLd0xO238AwgDep4hBshcFH9tcSg5hDPFFeJWRnC4c63vNGXMVV7iEAoPSH
24
+LPbuELCIRqiZFYW5A8Olv2MGGZ3kjiFrWYfbLFU0/z1y82/E2NtM65cvOQKYxMk1
25
+HBmaGF8s43LdDiGUZ0yFMTwe+da+zWcfDPgSYml36ReCsn2dGFmfkPFhqSf810kI
26
+yl2EKQnjEf51AGDfrA6fmEafsQy8eFf0uH6cR8nrsa+0aXIkTHZ9erXrXujD30iL
27
+9M4T3uW/0Qk/kqSN3wUgHYWDBRyTKxCDPMiEixXDq21Jm1VzSKJAFE+xEuFHtqXl
28
+nfZQCzihdx3ckZnH3qfrJt0V0cu6qSNr6sbyrb7FVO8aCNyumdCDM9VdJ64UFAme
29
+Xd/1/195PcoFOVEokoH15EH0mPr+/DDWA39c+FaRHH0A3LmuzX/P5rTRLO3wldpL
30
+XiZkLrfG43UNq3PIdh3YZEabpFcQYTmab7N8nZnmoMRM6YoEnHjdqPcDv3xs4gJS
31
+U24bVnFqzgSW3V1GfZGnlQyGXFrlrU+wWJ55eJ59ucQn8PDYlrSz7+x9RiqoFZcQ
32
+c9L8j7dFMBx6zI4dI5Ddx5q9KNtxPJb4Hk9HEd4C5OQ0qqdBR/hSD9mDjBpqEyc8
33
+aXIzmrTpGm7A9lbyXCEaOzN+2Jvdq5KtWh/halEgqgToqQ==
34
+-----END CERTIFICATE-----

+ 1
- 0
cursive/tests/unit/data/not_a_cert.txt View File

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

+ 24
- 0
cursive/tests/unit/data/orphaned_cert.pem View File

@@ -0,0 +1,24 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIID8zCCAtugAwIBAgIJAMDwfYBIkXEGMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD
3
+VQQGEwJVUzERMA8GA1UECAwIVGVzdG9uaWExDTALBgNVBAcMBFRlc3QxJDAiBgNV
4
+BAoMG0NvbXByZWhlbnNpdmUgVGVzdGluZywgSW5jLjEaMBgGA1UECwwRVGVzdGlu
5
+ZyBPdmVyc2lnaHQxHDAaBgNVBAMME1Rlc3RpbmcgQ2VydGlmaWNhdGUwHhcNMTYw
6
+NjMwMTcyOTUwWhcNMTcwNjMwMTcyOTUwWjCBjzELMAkGA1UEBhMCVVMxETAPBgNV
7
+BAgMCFRlc3RvbmlhMQ0wCwYDVQQHDARUZXN0MSQwIgYDVQQKDBtDb21wcmVoZW5z
8
+aXZlIFRlc3RpbmcsIEluYy4xGjAYBgNVBAsMEVRlc3RpbmcgT3ZlcnNpZ2h0MRww
9
+GgYDVQQDDBNUZXN0aW5nIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC
10
+AQ8AMIIBCgKCAQEAm+W78acV27U11m7E3iUUUGb+JXMW0okP8epD9OsLtVHxR+oq
11
+iOt19rgNIH/wJzaT+CnJ1jUerjzjFu2RwGhEr8Ph2KrWWQ7vxkhJzuXmKmGBZJm3
12
+FJcADrxcmZ8V3Yqxf3zO36Rg27jqDgxSy3uzxgO7ZXrkrJjrgrg+x8wVQ/pkhd8Y
13
+gQ/YQ2r1DF1GcpS/tSkCSc3lbIpCCHhORaRmHZXURML5q7vibLmc55Ad90WxtS1d
14
+WI8RAsWnQMvP1OmZcRcPKrUlRc/w+nIrxNF9HdeOweQv2tcnNlxBOcr6MwIL+Gle
15
+N4TmmthyVYCXxNWhW1VFA3atfEfmyEpiKIcQGwIDAQABo1AwTjAdBgNVHQ4EFgQU
16
+IkPrrGyB6+XUlWbd287uFbfCvkkwHwYDVR0jBBgwFoAUIkPrrGyB6+XUlWbd287u
17
+FbfCvkkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMWi3C19pjWs9
18
+9hg1rTC0D/5C9K/0nmQ1pstVMXOKn9Z3ndUqRvLzxhHZHhQ/ATQwHKeSM2vpCmKa
19
+eV7PGivF6W+CAXJImvgNrsP2fMBnTsg2Q3hBHSIkTgwJxAHlYZ3NXSxWoDSuozvU
20
++qjRY3hMpYLSXpfGFKh73GHBNWXyjlo7pn+I4gAEoHOqDKTelOONz6PiKKi4Un2g
21
+j5FqmLZEq9VvzqSEC5VuFLZs4BpGmsKBM16+q+8JWMa025wNcdq4DxuNAkb3Zsty
22
+QZkgVYJgIeuEKOCubCQfDOya5W7ik3mtZZm9dFD5dZ3+CDB53a/AlKdi9YUAJOUW
23
+xBJzlRBlLg==
24
+-----END CERTIFICATE-----

+ 107
- 0
cursive/tests/unit/data/parent_cert.pem View File

@@ -0,0 +1,107 @@
1
+Certificate:
2
+    Data:
3
+        Version: 3 (0x2)
4
+        Serial Number: 9 (0x9)
5
+    Signature Algorithm: sha256WithRSAEncryption
6
+        Issuer: C=US, ST=Test, L=Test, O=Test, OU=Test, CN=Test Grandparent/emailAddress=gp@example.com
7
+        Validity
8
+            Not Before: Oct  3 17:58:30 2017 GMT
9
+            Not After : Oct  1 17:58:30 2027 GMT
10
+        Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Parent
11
+        Subject Public Key Info:
12
+            Public Key Algorithm: rsaEncryption
13
+                Public-Key: (2048 bit)
14
+                Modulus:
15
+                    00:9f:9d:95:c4:a3:2f:37:52:e4:7c:cf:0b:0e:7f:
16
+                    14:69:63:1e:7a:cc:a8:19:a7:88:59:c8:17:f2:21:
17
+                    13:1b:45:21:fa:cc:93:40:71:cf:77:52:5a:1e:2e:
18
+                    5a:91:16:a9:67:3a:a3:6a:ea:cb:a2:bf:24:9b:8c:
19
+                    08:96:33:19:46:f9:7a:04:f9:c2:ee:87:f3:c3:23:
20
+                    73:37:59:0e:c0:71:f4:cd:0b:ad:23:63:51:0a:4f:
21
+                    dc:d2:9b:ab:ab:8a:99:07:d4:c8:c8:70:fd:18:73:
22
+                    25:0a:48:82:32:0d:64:46:b1:63:84:24:03:0b:3c:
23
+                    b8:17:92:78:6c:2b:4d:21:1b:46:3e:c1:cf:98:0b:
24
+                    a8:43:91:c0:39:48:f5:4e:71:77:c5:43:0e:68:8f:
25
+                    01:c6:fb:59:77:d5:b3:f3:fe:95:27:ea:6e:ae:fc:
26
+                    8e:59:ad:06:97:0c:f7:a6:e7:61:df:23:91:26:d0:
27
+                    bc:80:c6:2b:02:9b:fa:0f:e6:32:69:5a:90:29:c9:
28
+                    9c:34:eb:50:ed:1d:e3:eb:0f:67:88:e3:ec:2b:1a:
29
+                    ab:41:c3:fa:d6:e8:aa:e3:7b:6a:16:3d:d8:da:6b:
30
+                    af:92:81:32:98:2f:f7:c0:bd:c4:25:bb:02:60:43:
31
+                    d5:e6:0c:29:7f:31:5d:09:4b:6a:a9:31:9b:92:24:
32
+                    09:8f
33
+                Exponent: 65537 (0x10001)
34
+        X509v3 extensions:
35
+            X509v3 Subject Key Identifier: 
36
+                7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49
37
+            X509v3 Authority Key Identifier: 
38
+                keyid:A7:EB:A1:B4:4D:2A:62:D5:38:90:ED:44:FA:77:7F:F9:BA:EA:99:04
39
+
40
+            X509v3 Basic Constraints: 
41
+                CA:TRUE, pathlen:0
42
+            X509v3 Key Usage: 
43
+                Certificate Sign, CRL Sign
44
+            X509v3 Subject Alternative Name: 
45
+                DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com
46
+            Netscape Comment: 
47
+                OpenSSL Generated Certificate
48
+    Signature Algorithm: sha256WithRSAEncryption
49
+         81:55:0d:1d:73:54:1d:72:73:72:dc:cf:ed:c1:47:c8:38:2a:
50
+         78:33:5e:55:6f:02:cc:c0:6a:6f:7e:c9:fa:4c:3d:a0:5b:25:
51
+         37:5e:87:69:7f:d8:66:73:4f:58:7d:c7:3e:6d:be:2a:85:43:
52
+         6a:cb:ff:68:59:1d:72:d2:68:ad:e9:5b:2f:8d:f6:95:31:ba:
53
+         1d:de:16:45:d9:12:51:85:12:bb:fb:89:fc:3a:7c:f5:e4:75:
54
+         64:b4:7d:ff:9f:f6:15:fa:1e:cb:18:4a:9d:e8:d8:5e:5a:d7:
55
+         dd:78:c7:df:3d:21:2d:99:ef:b4:2c:78:2f:fb:fa:a0:7e:f3:
56
+         cb:3b:05:5a:65:7d:9b:0f:9b:a3:9b:a9:ad:25:f8:32:cb:08:
57
+         fd:c2:68:d3:92:15:09:59:5f:8b:c4:84:01:5f:75:7b:f0:55:
58
+         5f:20:39:f1:26:65:3d:d8:a2:19:de:fb:79:a0:27:2a:24:ae:
59
+         95:02:84:61:72:7a:47:37:4e:9f:af:20:5b:21:ec:c4:bf:ee:
60
+         80:5b:35:4e:ee:20:46:e6:cb:a6:e2:2f:c6:3e:5a:fa:f9:97:
61
+         c3:97:09:1d:ce:08:a3:e9:09:cb:c3:59:3f:98:f3:b6:bf:00:
62
+         8b:a7:40:de:0a:1c:09:88:f7:74:fa:b1:1c:05:44:ff:ba:73:
63
+         84:3b:93:8d:a8:51:d0:d8:59:e6:cd:a8:79:d3:db:0a:1d:99:
64
+         3f:7c:a0:f9:d5:9e:dd:13:58:ee:ef:0d:3d:e2:4a:8b:85:18:
65
+         0c:86:f8:97:4d:18:54:c0:52:b8:10:38:1a:b8:8a:06:71:a5:
66
+         e7:78:11:00:5b:9f:19:92:34:28:0f:19:3f:b0:57:ea:11:69:
67
+         29:ca:ed:05:36:08:f6:8d:ec:5d:34:79:92:8e:4c:e0:1c:a4:
68
+         ad:1a:31:90:b7:16:60:da:e3:8f:ee:ea:66:df:13:e8:46:8d:
69
+         a3:e2:3b:0a:f5:87:14:3d:4b:14:ea:da:89:c7:ae:e0:60:e3:
70
+         a0:4c:04:2f:a1:0f:a9:84:5a:5a:f7:3d:4f:7b:d4:7c:e1:cd:
71
+         ef:8b:28:45:19:ea:a9:4c:9e:59:f8:41:43:10:77:89:09:3e:
72
+         30:d0:e9:58:96:45:07:50:0e:4d:cc:6a:53:9e:64:c4:8a:e0:
73
+         51:96:3a:c6:8a:e2:94:af:9c:26:9a:fe:e3:7a:cd:cc:55:60:
74
+         f0:dc:bf:f3:0d:e8:69:e4:cf:49:e1:f4:d2:87:91:31:cf:42:
75
+         f7:2c:a7:f7:7b:88:90:e4:17:96:f6:34:d2:bf:a1:66:3c:03:
76
+         db:aa:07:fa:a6:c3:4b:d3:29:d1:d1:40:6f:a7:88:a5:7f:bd:
77
+         5f:f5:00:94:db:53:5d:24
78
+-----BEGIN CERTIFICATE-----
79
+MIIFKzCCAxOgAwIBAgIBCTANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
80
+DTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTAL
81
+BgNVBAsMBFRlc3QxGTAXBgNVBAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG
82
+9w0BCQEWDmdwQGV4YW1wbGUuY29tMB4XDTE3MTAwMzE3NTgzMFoXDTI3MTAwMTE3
83
+NTgzMFowUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRl
84
+c3QxDTALBgNVBAoMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUGFyZW50MIIBIjANBgkq
85
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn52VxKMvN1LkfM8LDn8UaWMeesyoGaeI
86
+WcgX8iETG0Uh+syTQHHPd1JaHi5akRapZzqjaurLor8km4wIljMZRvl6BPnC7ofz
87
+wyNzN1kOwHH0zQutI2NRCk/c0purq4qZB9TIyHD9GHMlCkiCMg1kRrFjhCQDCzy4
88
+F5J4bCtNIRtGPsHPmAuoQ5HAOUj1TnF3xUMOaI8BxvtZd9Wz8/6VJ+purvyOWa0G
89
+lwz3pudh3yORJtC8gMYrApv6D+YyaVqQKcmcNOtQ7R3j6w9niOPsKxqrQcP61uiq
90
+43tqFj3Y2muvkoEymC/3wL3EJbsCYEPV5gwpfzFdCUtqqTGbkiQJjwIDAQABo4Hb
91
+MIHYMB0GA1UdDgQWBBR6vn0JWl9c3syCGjv+qO3KuhZYSTAfBgNVHSMEGDAWgBSn
92
+66G0TSpi1TiQ7UT6d3/5uuqZBDAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIB
93
+BjBKBgNVHREEQzBBggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tghBtYWls
94
+LmV4YW1wbGUuY29tgg9mdHAuZXhhbXBsZS5jb20wLAYJYIZIAYb4QgENBB8WHU9w
95
+ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQCB
96
+VQ0dc1QdcnNy3M/twUfIOCp4M15VbwLMwGpvfsn6TD2gWyU3Xodpf9hmc09Yfcc+
97
+bb4qhUNqy/9oWR1y0mit6VsvjfaVMbod3hZF2RJRhRK7+4n8Onz15HVktH3/n/YV
98
++h7LGEqd6NheWtfdeMffPSEtme+0LHgv+/qgfvPLOwVaZX2bD5ujm6mtJfgyywj9
99
+wmjTkhUJWV+LxIQBX3V78FVfIDnxJmU92KIZ3vt5oCcqJK6VAoRhcnpHN06fryBb
100
+IezEv+6AWzVO7iBG5sum4i/GPlr6+ZfDlwkdzgij6QnLw1k/mPO2vwCLp0DeChwJ
101
+iPd0+rEcBUT/unOEO5ONqFHQ2Fnmzah509sKHZk/fKD51Z7dE1ju7w094kqLhRgM
102
+hviXTRhUwFK4EDgauIoGcaXneBEAW58ZkjQoDxk/sFfqEWkpyu0FNgj2jexdNHmS
103
+jkzgHKStGjGQtxZg2uOP7upm3xPoRo2j4jsK9YcUPUsU6tqJx67gYOOgTAQvoQ+p
104
+hFpa9z1Pe9R84c3viyhFGeqpTJ5Z+EFDEHeJCT4w0OlYlkUHUA5NzGpTnmTEiuBR
105
+ljrGiuKUr5wmmv7jes3MVWDw3L/zDehp5M9J4fTSh5Exz0L3LKf3e4iQ5BeW9jTS
106
+v6FmPAPbqgf6psNL0ynR0UBvp4ilf71f9QCU21NdJA==
107
+-----END CERTIFICATE-----

BIN
cursive/tests/unit/data/self_signed_cert.der View File


+ 22
- 0
cursive/tests/unit/data/self_signed_cert.pem View File

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

+ 22
- 0
cursive/tests/unit/data/self_signed_cert_invalid_ca_constraint.pem View File

@@ -0,0 +1,22 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIDkzCCAnugAwIBAgIJAKnOrxg9gSXNMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
3
+BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
4
+ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
5
+FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxOTQyWhcNMTcwNjMwMTgx
6
+OTQyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
7
+dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
8
+MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
9
+AQEFAAOCAQ8AMIIBCgKCAQEAnhmtSiir3aB5igvVEQlHqIw2K98q336cyjipFq9b
10
+Pt1YslTwLfUAagr7224i0tny45PIZ3o1YlBxhEwd/i1tMnCz2+DQyat+p+vVbbiI
11
+ceN1ZzRFE4zJV0QjG+H+TOWqzjtdtq04jkdrKMOsp3Lv4NHIuEuLocQLPLuT79wP
12
+VUO+BCHlU/0bUQHhAU/Jx9B81GQ1/4lYS400AYtANSEccMR1djUTjFha4wiwSDH2
13
+QZQBgmiqmDhf22uoioFgay9+yhOJ3SJx/lIiMavM2LMgNbns1DcbAD8oKGS69Mmo
14
+TsQlgOVMQTIDbwsm3WaxIcY8BipUACSe3E+RdqDrP/MTDwIDAQABoxowGDAJBgNV
15
+HRMEAjAAMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEACP8ZAbyAtffS
16
+sWZ6WbLcm+BT7FFdUm62gGqC2knaIZQud3AV5/wtj2CAw1lXgGbaCqfWNQrTDY9z
17
+PpGXyQ5tvNUtBZG23K7nMvid+U1WDh1fhlRC0kxCK2MsPwv9T5BM3tj/YF0MGWuQ
18
+3GlFL8wU8UoAP3alUhxQl5qXqfXc6qfMW2ec4Jb3j6nbezL7ttn15LiBCsvoJ6/V
19
+Go9bbox81UrbtxnVin5+cYczUdB+Q9+fe23B/6MG99hzWU9arkkU7ZOlnI/bW9Zb
20
+fx4+atZfpi18nyd2ljgiTapB6Ex6uxVPrzwuKxpGMt9wU1++ZYc3YDke4EOT+OIX
21
+nKc7UFfjyg==
22
+-----END CERTIFICATE-----

+ 22
- 0
cursive/tests/unit/data/self_signed_cert_invalid_key_usage.pem View File

@@ -0,0 +1,22 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIDmTCCAoGgAwIBAgIJAKOf0EhCGUdQMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
3
+BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
4
+ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
5
+FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxNDMwWhcNMTcwNjMwMTgx
6
+NDMwWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
7
+dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
8
+MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
9
+AQEFAAOCAQ8AMIIBCgKCAQEA575xHdxVuT5CYPom/PbFdDLt0PgG3CZFkiRNxPAG
10
+rDN8cG5ouTw0R9RMBFA+nYcTx/4GnPJmSBEBVqMoPSzsB6Rx9k21KymNlaEs2O1W
11
+jSMsYd9gW4NlHdyoomYw0nXQjkstmtdJxDNWg0zSZrHMPnkOVh+JNV58i4FXOx5O
12
+bxWo4sSyAvNjAEH9GwDwy+Jz0X4RdFGQrGjm+/v+ohvy8JqU5ZKpz2oP2oQURjDj
13
++AH3ghmgNAVAk0syjtSqydEJd9aeMLTmTaUtP+gPnXdj/ZBj+TQH01RNlSECH2l4
14
+WrymS3g4+X5xsA7DeLIbiXB4K1xjbJFCSfDYrV52H/fE6QIDAQABoyAwHjAPBgNV
15
+HRMECDAGAQH/AgECMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEARNRE
16
+XC8y/RoPLUVAVKJ/RwcH4cwaHoSSu6HnygIpg9Qs7Xc7u1aKCL0dRF1NqfmaqHZ6
17
+ZjllxDi5t0CFIXPfDQIYchfSzOafhJGEH3gilBwfmN43N4/eCSvdRKfhRbRFOD9j
18
+0JHRAHkn2JRcwSTTjJEcJUJJETAIIbX1ovobJZuJOY0faI1O/Z2KILYrwdmcfnZ5
19
+3i0kUps5BWrBrcs70gBsDBugeM24ANa7hJzFk+9TztfLWF1AUfjpZ4Bj/rb21+Gp
20
+08FRjvn80Y5bGlNh0Q7Qbu8NS8VbAHHF3t3PUVRymJhIycpvBBBS7dcQqHf+v1gs
21
+Z/UpzuobJvnhq+7mcQ==
22
+-----END CERTIFICATE-----

+ 21
- 0
cursive/tests/unit/data/self_signed_cert_missing_ca_constraint.pem View File

@@ -0,0 +1,21 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIDiDCCAnCgAwIBAgIJAOYNT7MuoypuMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV
3
+BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
4
+ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB
5
+FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgyMTMyWhcNMTcwNjMwMTgy
6
+MTMyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz
7
+dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh
8
+MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B
9
+AQEFAAOCAQ8AMIIBCgKCAQEA3qypxOJ2X0k47SIEkRGvA/ECqzVAX3nsC0yPnbF3
10
+14SGe7xFBzi0VMAXOcpVj0BL+G5TL95O5loVN/UnU5/xtjSa712HOOlJfvnqmv63
11
+BLq9cMS2strwufeOK3YUtQExtJdxMjcEYuCMt+NlQ3Hl+xNfBc0LXWNBdlusP4fs
12
+6sLEgyD4ywSLC9oHzyzgDxi0pr52itu+KnZv2iET/Wotg/8Aiw5Q5fiTc8DoysdZ
13
+MF1ix56oGo1SFFGFf+n2iwYbImtNGt6//jKEDP8P4iLdLAxHxmfsXXnl+Zs/6VoA
14
+RrnS3Xt7F5xj4CWuoZy4CLo8YXhXdznRQZ2r5Qha65cFNwIDAQABow8wDTALBgNV
15
+HQ8EBAMCAwgwDQYJKoZIhvcNAQELBQADggEBALOBnrWz3xMg4Yh92zsXfrSm2uAL
16
+P8jgXQtQdsYWSEWcfYNYOSlmLnICweDnAn0V5Kzp0E5wZSHP8Ut4IYEKwe+IF7HA
17
+pv3mpg1CYtwVbVsN0dhlLHDVuF27i0r8LuOv9yh0wxY3hPYrd2WvQ/qTP8NO0EoD
18
+fM8w2fjNeTu2jB+lYWhGWOHfzEvltosxMZIPWBJxrh3PbYdbuJJZlm/NPqj5Urxx
19
+nRB89AEBHKEKHlmNIMMOM3mQ+ShssgGrbRV6U6iJ5qv4H5RdaZtmXMGhjt8dFJrD
20
+A2YZYW15QLQDEyud4flSztaw6UMHJi+4FChBAnuJuOcNRZA83v3szuM9t64=
21
+-----END CERTIFICATE-----

+ 21
- 0
cursive/tests/unit/data/self_signed_cert_missing_key_usage.pem View File

@@ -0,0 +1,21 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIDXTCCAkWgAwIBAgIJAI20mCsVbBjpMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
3
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
4
+aWRnaXRzIFB0eSBMdGQwHhcNMTYwNjMwMTM0NTU5WhcNMTcwNjMwMTM0NTU5WjBF
5
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
6
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
7
+CgKCAQEA5NjoTq0D6LM6z7V16E1+Eul25L6X1BSLcppsHhVMB/NM1e7hjoUtAODv
8
+71L/bAAgV4ky4aphOfwYQLfeAP8nkq4CU30LaAyAQwrT4RyW1NG7AA00xylauebt
9
+sc2GUUy06gQ0Z6OjjEfA5HA4W+HYfeyuNzQpWXHWz/6K8xcKb9w10qAjDhNilHbj
10
+3RVtL6u6rsZgQi0DlMxpHsp6gLezIRMN72B/AOKrzaobw4nY4hOVkqbRlOHB/tsk
11
+4BJLUuW5WM30TVpfsKe07jCBgqUwb9XD9lZa1alkFRsSTZoWijeQBM6kiLD/VFNC
12
+YlIKBrN7HZtfOlqhftMknCsoyrWsjQIDAQABo1AwTjAdBgNVHQ4EFgQUnDu25deh
13
+bIOsCaj3LAcE9r2gx0QwHwYDVR0jBBgwFoAUnDu25dehbIOsCaj3LAcE9r2gx0Qw
14
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAowntOCmRXKgz7M6hnbQc
15
+sEyIt5tN3QILHSeprVO7/ONVIiPJMCfB+S8gJKJ0d5R0xXDYN/6+HyYlgfaL33Gt
16
+HY75y8MRnfqpgbEWhXWsBkxgeuqWiM/OFMTqLtgkVbxsVzoUl6V+tHsZaaM9yuyb
17
+iUBM9McAPGIgodpMGG86BV6qg07VjqWjl5pBUU4B2zvvzZjwrC8jqUYksVESHB9U
18
+WBzwfPLXoj0PUfAog34ZtT33UXX8M3oXTw+yb/hx0rContYMc78Lnlk6mV9gGG+X
19
++3gSwAHn0SMZNNkKc3gdb1CLfluvHw2Od2jat0yfHHawh1JBtnfHrAU0px3Kzw5U
20
+0A==
21
+-----END CERTIFICATE-----

+ 26
- 0
cursive/tests/unit/data/signed_cert.pem View File

@@ -0,0 +1,26 @@
1
+-----BEGIN CERTIFICATE-----
2
+MIIEWzCCA0OgAwIBAgIBATANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJVUzEN
3
+MAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVzdDENMAsGA1UEChMEVGVzdDENMAsG
4
+A1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEhMB8GCSqGSIb3DQEJARYSdGVzdEBl
5
+bWFpbC5hZGRyZXNzMB4XDTE2MDYzMDE5NDA1N1oXDTE3MDYzMDE5NDA1N1owbzEL
6
+MAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNV
7
+BAsMBFRlc3QxDzANBgNVBAMMBkNsaWVudDEiMCAGCSqGSIb3DQEJARYTdGVzdEBj
8
+bGllbnQuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALOK
9
+dKWvu8NLDee4KvHf5WmmJsNeqrfZt+5EBXlp9wEJ3i//6vRpZe9Gr/k3xfbQPVng
10
+PS8LUBancZ/zPos6ZibUuJi+ZjgVXUm61S18536wq4S1LH4Hkb4RgJW+IKqlqi0z
11
+RVC3xeNcUhGprcH9JtjinOusQ1HLWy4mSr5aaCfCVshj3YEN5uCrfDOXPkS5B1kd
12
+kpnEZJt2tAUPLlIKD4Ytjq9A84bL6wHTrUg5NmZ8j+yfXfD0qE1rN69AZFQ2V72R
13
+uKUbxvvx2T8ObJh1VpiSlMLLEeoEY1OUCZWB+Xz6GX9uL9B9zeD0+f9WcswI3UCw
14
+9WEFgDHYWxgFciu7IYsCAwEAAaOB9TCB8jAJBgNVHRMEAjAAMCwGCWCGSAGG+EIB
15
+DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUDs4X
16
+d7VArujtnCJ3Ppht7hSfdL0wgZcGA1UdIwSBjzCBjKF/pH0wezELMAkGA1UEBhMC
17
+VVMxDTALBgNVBAgTBFRlc3QxDTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3Qx
18
+DTALBgNVBAsTBFRlc3QxDTALBgNVBAMTBFRlc3QxITAfBgkqhkiG9w0BCQEWEnRl
19
+c3RAZW1haWwuYWRkcmVzc4IJAKmiuSJghxIGMA0GCSqGSIb3DQEBCwUAA4IBAQBm
20
+f2VVj4Eqb+5pAgimkejDrYRzDgDQ4Eyr45vdUtu7JoGovGmkxg5z3izW/UKKj8GC
21
+04aXIJiIu8d7mn5ZxuaIS0/mtVN167tVVI0wBlkQRK5dJNjn47fTixymEy4lwdUl
22
+0iSb1JP6beVmSMIywD5lFxGPiW/MEJSvDCdlOT2Ojiv/Sbn9Q09PsXei0fAmNGZn
23
+FEUSnlqgWkeGIIv3+//kY8pHlZ1RyYSShQ+3Vb8Qifx0lbiFQWDP82EgETu7JKWn
24
+fKCoogSDybcLqB/WeGOQ0myXgEth5Lhkdo0n08J/FYL/bA1thADVnV66ZpERgb3h
25
+38P4rEcobzZdVPcS4zwP
26
+-----END CERTIFICATE-----

+ 403
- 0
cursive/tests/unit/test_certificate_utils.py View File

@@ -0,0 +1,403 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import datetime
14
+import mock
15
+import os
16
+
17
+from cryptography.hazmat.backends import default_backend
18
+from cryptography import x509
19
+
20
+from cursive import certificate_utils
21
+from cursive import exception
22
+from cursive.tests import base
23
+
24
+
25
+class TestCertificateUtils(base.TestCase):
26
+    """Test methods for the certificate verification context and utilities"""
27
+
28
+    def setUp(self):
29
+        super(TestCertificateUtils, self).setUp()
30
+
31
+        self.cert_path = os.path.join(
32
+            os.path.dirname(os.path.realpath(__file__)),
33
+            'data'
34
+        )
35
+
36
+    def tearDown(self):
37
+        super(TestCertificateUtils, self).tearDown()
38
+
39
+    def load_certificate(self, cert_name):
40
+        # Load the raw certificate file data.
41
+        path = os.path.join(self.cert_path, cert_name)
42
+        with open(path, 'rb') as cert_file:
43
+            data = cert_file.read()
44
+
45
+        # Convert the raw certificate data into a certificate object, first
46
+        # as a PEM-encoded certificate and, if that fails, then as a
47
+        # DER-encoded certificate. If both fail, the certificate cannot be
48
+        # loaded.
49
+        try:
50
+            return x509.load_pem_x509_certificate(data, default_backend())
51
+        except Exception:
52
+            try:
53
+                return x509.load_der_x509_certificate(data, default_backend())
54
+            except Exception:
55
+                raise exception.SignatureVerificationError(
56
+                    "Failed to load certificate: %s" % path
57
+                )
58
+
59
+    def load_certificates(self, cert_names):
60
+        certs = list()
61
+        for cert_name in cert_names:
62
+            cert = self.load_certificate(cert_name)
63
+            certs.append(cert)
64
+        return certs
65
+
66
+    @mock.patch('oslo_utils.timeutils.utcnow')
67
+    def test_is_within_valid_dates(self, mock_utcnow):
68
+        # Verify a certificate is valid at a time within its valid date range
69
+        cert = self.load_certificate('self_signed_cert.pem')
70
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
71
+        result = certificate_utils.is_within_valid_dates(cert)
72
+        self.assertEqual(True, result)
73
+
74
+    @mock.patch('oslo_utils.timeutils.utcnow')
75
+    def test_is_before_valid_dates(self, mock_utcnow):
76
+        # Verify a certificate is invalid at a time before its valid date range
77
+        cert = self.load_certificate('self_signed_cert.pem')
78
+        mock_utcnow.return_value = datetime.datetime(2000, 1, 1)
79
+        result = certificate_utils.is_within_valid_dates(cert)
80
+        self.assertEqual(False, result)
81
+
82
+    @mock.patch('oslo_utils.timeutils.utcnow')
83
+    def test_is_after_valid_dates(self, mock_utcnow):
84
+        # Verify a certificate is invalid at a time after its valid date range
85
+        cert = self.load_certificate('self_signed_cert.pem')
86
+        mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
87
+        result = certificate_utils.is_within_valid_dates(cert)
88
+        self.assertEqual(False, result)
89
+
90
+    def test_is_issuer(self):
91
+        # Test issuer and subject name matching for a self-signed certificate.
92
+        cert = self.load_certificate('self_signed_cert.pem')
93
+        result = certificate_utils.is_issuer(cert, cert)
94
+        self.assertEqual(True, result)
95
+
96
+    def test_is_not_issuer(self):
97
+        # Test issuer and subject name mismatching.
98
+        cert = self.load_certificate('self_signed_cert.pem')
99
+        alt = self.load_certificate('orphaned_cert.pem')
100
+        result = certificate_utils.is_issuer(cert, alt)
101
+        self.assertEqual(False, result)
102
+
103
+    def test_is_issuer_with_invalid_certs(self):
104
+        # Test issuer check with invalid certificates
105
+        cert = self.load_certificate('self_signed_cert.pem')
106
+        result = certificate_utils.is_issuer(cert, None)
107
+        self.assertEqual(False, result)
108
+        result = certificate_utils.is_issuer(None, cert)
109
+        self.assertEqual(False, result)
110
+
111
+    def test_can_sign_certificates(self):
112
+        # Test that a well-formatted certificate can sign
113
+        cert = self.load_certificate('self_signed_cert.pem')
114
+        result = certificate_utils.can_sign_certificates(cert, 'test-ID')
115
+        self.assertEqual(True, result)
116
+
117
+    def test_cannot_sign_certificates_without_basic_constraints(self):
118
+        # Verify a certificate without basic constraints cannot sign
119
+        cert = self.load_certificate(
120
+            'self_signed_cert_missing_ca_constraint.pem'
121
+        )
122
+        result = certificate_utils.can_sign_certificates(cert, 'test-ID')
123
+        self.assertEqual(False, result)
124
+
125
+    def test_cannot_sign_certificates_with_invalid_basic_constraints(self):
126
+        # Verify a certificate with invalid basic constraints cannot sign
127
+        cert = self.load_certificate(
128
+            'self_signed_cert_invalid_ca_constraint.pem'
129
+        )
130
+        result = certificate_utils.can_sign_certificates(cert, 'test-ID')
131
+        self.assertEqual(False, result)
132
+
133
+    def test_cannot_sign_certificates_without_key_usage(self):
134
+        # Verify a certificate without key usage cannot sign
135
+        cert = self.load_certificate('self_signed_cert_missing_key_usage.pem')
136
+        result = certificate_utils.can_sign_certificates(cert, 'test-ID')
137
+        self.assertEqual(False, result)
138
+
139
+    def test_cannot_sign_certificates_with_invalid_key_usage(self):
140
+        # Verify a certificate with invalid key usage cannot sign
141
+        cert = self.load_certificate('self_signed_cert_invalid_key_usage.pem')
142
+        result = certificate_utils.can_sign_certificates(cert, 'test-ID')
143
+        self.assertEqual(False, result)
144
+
145
+    def test_verify_signing_certificate(self):
146
+        signing_certificate = self.load_certificate('self_signed_cert.pem')
147
+        signed_certificate = self.load_certificate('signed_cert.pem')
148
+
149
+        certificate_utils.verify_certificate_signature(
150
+            signing_certificate,
151
+            signed_certificate
152
+        )
153
+
154
+    @mock.patch('cursive.signature_utils.get_certificate')
155
+    @mock.patch('oslo_utils.timeutils.utcnow')
156
+    def test_verify_valid_certificate(self, mock_utcnow, mock_get_cert):
157
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
158
+        certs = self.load_certificates(
159
+            ['self_signed_cert.pem', 'self_signed_cert.der',
160
+             'signed_cert.pem']
161
+        )
162
+        mock_get_cert.side_effect = certs
163
+        cert_uuid = '3'
164
+        trusted_cert_uuids = ['1', '2']
165
+        certificate_utils.verify_certificate(
166
+            None, cert_uuid, trusted_cert_uuids
167
+        )
168
+
169
+    @mock.patch('cursive.signature_utils.get_certificate')
170
+    @mock.patch('oslo_utils.timeutils.utcnow')
171
+    def test_verify_invalid_certificate(self, mock_utcnow, mock_get_cert):
172
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
173
+        certs = self.load_certificates(
174
+            ['self_signed_cert.pem', 'self_signed_cert.der',
175
+             'orphaned_cert.pem']
176
+        )
177
+        mock_get_cert.side_effect = certs
178
+        cert_uuid = '3'
179
+        trusted_cert_uuids = ['1', '2']
180
+        self.assertRaisesRegex(
181
+            exception.SignatureVerificationError,
182
+            "Certificate chain building failed. Could not locate the "
183
+            "signing certificate for the base certificate in the set of "
184
+            "trusted certificates.",
185
+            certificate_utils.verify_certificate,
186
+            None,
187
+            cert_uuid,
188
+            trusted_cert_uuids
189
+        )
190
+
191
+    @mock.patch('cursive.signature_utils.get_certificate')
192
+    @mock.patch('oslo_utils.timeutils.utcnow')
193
+    def test_verify_valid_certificate_with_no_root(self, mock_utcnow,
194
+                                                   mock_get_cert):
195
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
196
+
197
+        # Test verifying a valid certificate against an empty list of trusted
198
+        # certificates.
199
+        certs = self.load_certificates(['signed_cert.pem'])
200
+        mock_get_cert.side_effect = certs
201
+        cert_uuid = '3'
202
+        trusted_cert_uuids = []
203
+        self.assertRaisesRegex(
204
+            exception.SignatureVerificationError,
205
+            "Certificate chain building failed. Could not locate the "
206
+            "signing certificate for the base certificate in the set of "
207
+            "trusted certificates.",
208
+            certificate_utils.verify_certificate,
209
+            None,
210
+            cert_uuid,
211
+            trusted_cert_uuids
212
+        )
213
+
214
+    @mock.patch('oslo_utils.timeutils.utcnow')
215
+    def test_context_init(self, mock_utcnow):
216
+        # Test constructing a context object with a valid set of certificates
217
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
218
+        certs = self.load_certificates(
219
+            ['self_signed_cert.pem', 'self_signed_cert.der']
220
+        )
221
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
222
+        context = certificate_utils.CertificateVerificationContext(
223
+            cert_tuples
224
+        )
225
+        self.assertEqual(2, len(context._signing_certificates))
226
+        for t in cert_tuples:
227
+            path, cert = t
228
+            self.assertIn(cert, [x[1] for x in context._signing_certificates])
229
+
230
+    @mock.patch('cursive.certificate_utils.LOG')
231
+    @mock.patch('oslo_utils.timeutils.utcnow')
232
+    def test_context_init_with_invalid_certificate(self, mock_utcnow,
233
+                                                   mock_log):
234
+        # Test constructing a context object with an invalid certificate
235
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
236
+        alt_cert_tuples = [('path', None)]
237
+        context = certificate_utils.CertificateVerificationContext(
238
+            alt_cert_tuples
239
+        )
240
+        self.assertEqual(0, len(context._signing_certificates))
241
+        self.assertEqual(1, mock_log.error.call_count)
242
+
243
+    @mock.patch('cursive.certificate_utils.LOG')
244
+    @mock.patch('oslo_utils.timeutils.utcnow')
245
+    def test_context_init_with_non_signing_certificate(self, mock_utcnow,
246
+                                                       mock_log):
247
+        # Test constructing a context object with an non-signing certificate
248
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
249
+        non_signing_cert = self.load_certificate(
250
+            'self_signed_cert_missing_key_usage.pem'
251
+        )
252
+        alt_cert_tuples = [('path', non_signing_cert)]
253
+        context = certificate_utils.CertificateVerificationContext(
254
+            alt_cert_tuples
255
+        )
256
+        self.assertEqual(0, len(context._signing_certificates))
257
+        self.assertEqual(1, mock_log.warning.call_count)
258
+
259
+    @mock.patch('cursive.certificate_utils.LOG')
260
+    @mock.patch('oslo_utils.timeutils.utcnow')
261
+    def test_context_init_with_out_of_date_certificate(self, mock_utcnow,
262
+                                                       mock_log):
263
+        # Test constructing a context object with out-of-date certificates
264
+        mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
265
+        certs = self.load_certificates(
266
+            ['self_signed_cert.pem', 'self_signed_cert.der']
267
+        )
268
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
269
+        context = certificate_utils.CertificateVerificationContext(cert_tuples)
270
+        self.assertEqual(0, len(context._signing_certificates))
271
+        self.assertEqual(2, mock_log.warning.call_count)
272
+
273
+    @mock.patch('oslo_utils.timeutils.utcnow')
274
+    def test_context_update_with_valid_certificate(self, mock_utcnow):
275
+        # Test updating the context with a valid certificate
276
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
277
+        certs = self.load_certificates(
278
+            ['self_signed_cert.pem', 'self_signed_cert.der']
279
+        )
280
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
281
+        context = certificate_utils.CertificateVerificationContext(cert_tuples)
282
+        cert = self.load_certificate('orphaned_cert.pem')
283
+        context.update(cert)
284
+        self.assertEqual(cert, context._signed_certificate)
285
+
286
+    @mock.patch('oslo_utils.timeutils.utcnow')
287
+    def test_context_update_with_date_invalid_certificate(self, mock_utcnow):
288
+        # Test updating the context with an out-of-date certificate
289
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
290
+        certs = self.load_certificates(
291
+            ['self_signed_cert.pem', 'self_signed_cert.der']
292
+        )
293
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
294
+        context = certificate_utils.CertificateVerificationContext(cert_tuples)
295
+        cert = self.load_certificate('orphaned_cert.pem')
296
+        mock_utcnow.return_value = datetime.datetime(2100, 1, 1)
297
+        self.assertRaisesRegex(
298
+            exception.SignatureVerificationError,
299
+            "The certificate is outside its valid date range.",
300
+            context.update,
301
+            cert
302
+        )
303
+
304
+    def test_context_update_with_invalid_certificate(self):
305
+        # Test updating the context with an invalid certificate
306
+        certs = self.load_certificates(
307
+            ['self_signed_cert.pem', 'self_signed_cert.der']
308
+        )
309
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
310
+        context = certificate_utils.CertificateVerificationContext(
311
+            cert_tuples
312
+        )
313
+
314
+        self.assertRaisesRegex(
315
+            exception.SignatureVerificationError,
316
+            "The certificate must be an x509.Certificate object.",
317
+            context.update,
318
+            None
319
+        )
320
+
321
+    @mock.patch('oslo_utils.timeutils.utcnow')
322
+    def test_context_verify(self, mock_utcnow):
323
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
324
+        certs = self.load_certificates(
325
+            ['self_signed_cert.pem', 'self_signed_cert.der']
326
+        )
327
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
328
+
329
+        # Test verification with a two-link certificate chain.
330
+        context = certificate_utils.CertificateVerificationContext(
331
+            cert_tuples
332
+        )
333
+        cert = self.load_certificate('signed_cert.pem')
334
+        context.update(cert)
335
+        context.verify()
336
+
337
+        # Test verification with a single-link certificate chain.
338
+        context = certificate_utils.CertificateVerificationContext(
339
+            cert_tuples
340
+        )
341
+        context.update(certs[0])
342
+        context.verify()
343
+
344
+    @mock.patch('oslo_utils.timeutils.utcnow')
345
+    def test_context_verify_disable_checks(self, mock_utcnow):
346
+        mock_utcnow.return_value = datetime.datetime(2017, 1, 1)
347
+        certs = self.load_certificates(
348
+            ['self_signed_cert.pem', 'self_signed_cert.der']
349
+        )
350
+        cert_tuples = [('1', certs[0]), ('2', certs[1])]
351
+
352
+        # Test verification with a two-link certificate chain.
353
+        context = certificate_utils.CertificateVerificationContext(
354
+            cert_tuples,
355
+            enforce_valid_dates=False,
356
+            enforce_signing_extensions=False,
357
+            enforce_path_length=False
358
+        )
359
+        cert = self.load_certificate('signed_cert.pem')
360
+        context.update(cert)
361
+        context.verify()
362
+
363
+        # Test verification with a single-link certificate chain.
364
+        context = certificate_utils.CertificateVerificationContext(
365
+            cert_tuples,
366
+            enforce_valid_dates=False,
367
+            enforce_signing_extensions=False,
368
+            enforce_path_length=False
369
+        )
370
+        context.update(certs[0])
371
+        context.verify()
372
+
373
+    @mock.patch('oslo_utils.timeutils.utcnow')
374
+    def test_context_verify_invalid_chain_length(self, mock_utcnow):
375
+        mock_utcnow.return_value = datetime.datetime(2017, 11, 1)
376
+        certs = self.load_certificates(
377
+            ['grandparent_cert.pem', 'parent_cert.pem', 'child_cert.pem']
378
+        )
379
+        cert_tuples = [
380
+            ('1', certs[0]),
381
+            ('2', certs[1]),
382
+            ('3', certs[2])
383
+        ]
384
+        cert = self.load_certificate('grandchild_cert.pem')
385
+
386
+        context = certificate_utils.CertificateVerificationContext(
387
+            cert_tuples
388
+        )
389
+        context.update(cert)
390
+        self.assertRaisesRegex(
391
+            exception.SignatureVerificationError,
392
+            "Certificate validation failed. The signing certificate '1' is "
393
+            "not configured to support certificate chains of sufficient "
394
+            "length.",
395
+            context.verify
396
+        )
397
+
398
+        context = certificate_utils.CertificateVerificationContext(
399
+            cert_tuples,
400
+            enforce_path_length=False
401
+        )
402
+        context.update(cert)
403
+        context.verify()

+ 16
- 33
cursive/tests/unit/test_signature_utils.py View File

@@ -12,6 +12,7 @@
12 12
 
13 13
 import base64
14 14
 import datetime
15
+import mock
15 16
 
16 17
 from castellan.common.exception import KeyManagerError
17 18
 import cryptography.exceptions as crypto_exceptions
@@ -20,7 +21,6 @@ from cryptography.hazmat.primitives.asymmetric import dsa
20 21
 from cryptography.hazmat.primitives.asymmetric import ec
21 22
 from cryptography.hazmat.primitives.asymmetric import padding
22 23
 from cryptography.hazmat.primitives.asymmetric import rsa
23
-import mock
24 24
 from oslo_utils import timeutils
25 25
 
26 26
 from cursive import exception
@@ -110,6 +110,12 @@ class BadPublicKey(object):
110 110
 class TestSignatureUtils(base.TestCase):
111 111
     """Test methods of signature_utils"""
112 112
 
113
+    def setUp(self):
114
+        super(TestSignatureUtils, self).setUp()
115
+
116
+    def tearDown(self):
117
+        super(TestSignatureUtils, self).tearDown()
118
+
113 119
     def test_should_create_verifier(self):
114 120
         image_props = {CERT_UUID: 'CERT_UUID',
115 121
                        HASH_METHOD: 'HASH_METHOD',
@@ -283,7 +289,8 @@ class TestSignatureUtils(base.TestCase):
283 289
                                'RSB-PSS')
284 290
 
285 291
     @mock.patch('cursive.signature_utils.get_certificate')
286
-    def test_get_public_key_rsa(self, mock_get_cert):
292
+    @mock.patch('cursive.certificate_utils.verify_certificate')
293
+    def test_get_public_key_rsa(self, mock_verify_cert, mock_get_cert):
287 294
         fake_cert = FakeCryptoCertificate()
288 295
         mock_get_cert.return_value = fake_cert
289 296
         sig_key_type = signature_utils.SignatureKeyType.lookup(
@@ -294,7 +301,8 @@ class TestSignatureUtils(base.TestCase):
294 301
         self.assertEqual(fake_cert.public_key(), result_pub_key)
295 302
 
296 303
     @mock.patch('cursive.signature_utils.get_certificate')
297
-    def test_get_public_key_ecc(self, mock_get_cert):
304
+    @mock.patch('cursive.certificate_utils.verify_certificate')
305
+    def test_get_public_key_ecc(self, mock_verify_cert, mock_get_cert):
298 306
         fake_cert = FakeCryptoCertificate(TEST_ECC_PRIVATE_KEY.public_key())
299 307
         mock_get_cert.return_value = fake_cert
300 308
         sig_key_type = signature_utils.SignatureKeyType.lookup('ECC_SECP521R1')
@@ -303,7 +311,8 @@ class TestSignatureUtils(base.TestCase):
303 311
         self.assertEqual(fake_cert.public_key(), result_pub_key)
304 312
 
305 313
     @mock.patch('cursive.signature_utils.get_certificate')
306
-    def test_get_public_key_dsa(self, mock_get_cert):
314
+    @mock.patch('cursive.certificate_utils.verify_certificate')
315
+    def test_get_public_key_dsa(self, mock_verify_cert, mock_get_cert):
307 316
         fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key())
308 317
         mock_get_cert.return_value = fake_cert
309 318
         sig_key_type = signature_utils.SignatureKeyType.lookup(
@@ -314,7 +323,9 @@ class TestSignatureUtils(base.TestCase):
314 323
         self.assertEqual(fake_cert.public_key(), result_pub_key)
315 324
 
316 325
     @mock.patch('cursive.signature_utils.get_certificate')
317
-    def test_get_public_key_invalid_key(self, mock_get_certificate):
326
+    @mock.patch('cursive.certificate_utils.verify_certificate')
327
+    def test_get_public_key_invalid_key(self, mock_verify_certificate,
328
+                                        mock_get_certificate):
318 329
         bad_pub_key = 'A' * 256
319 330
         mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key)
320 331
         sig_key_type = signature_utils.SignatureKeyType.lookup(
@@ -335,34 +346,6 @@ class TestSignatureUtils(base.TestCase):
335 346
         self.assertEqual(x509_cert,
336 347
                          signature_utils.get_certificate(None, cert_uuid))
337 348
 
338
-    @mock.patch('cryptography.x509.load_der_x509_certificate')
339
-    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
340
-    def test_get_expired_certificate(self, mock_key_manager_API,
341
-                                     mock_load_cert):
342
-        cert_uuid = 'valid_format_cert'
343
-        x509_cert = FakeCryptoCertificate(
344
-            not_valid_after=timeutils.utcnow() -
345
-            datetime.timedelta(hours=1))
346
-        mock_load_cert.return_value = x509_cert
347
-        self.assertRaisesRegex(exception.SignatureVerificationError,
348
-                               'Certificate is not valid after: .*',
349
-                               signature_utils.get_certificate, None,
350
-                               cert_uuid)
351
-
352
-    @mock.patch('cryptography.x509.load_der_x509_certificate')
353
-    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
354
-    def test_get_not_yet_valid_certificate(self, mock_key_manager_API,
355
-                                           mock_load_cert):
356
-        cert_uuid = 'valid_format_cert'
357
-        x509_cert = FakeCryptoCertificate(
358
-            not_valid_before=timeutils.utcnow() +
359
-            datetime.timedelta(hours=1))
360
-        mock_load_cert.return_value = x509_cert
361
-        self.assertRaisesRegex(exception.SignatureVerificationError,
362
-                               'Certificate is not valid before: .*',
363
-                               signature_utils.get_certificate, None,
364
-                               cert_uuid)
365
-
366 349
     @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
367 350
     def test_get_certificate_key_manager_fail(self, mock_key_manager_API):
368 351
         bad_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0695'

+ 37
- 0
releasenotes/notes/add-certificate-validation-68a1ffbd5369a8d1.yaml View File

@@ -0,0 +1,37 @@
1
+---
2
+prelude: >
3
+    The cursive library supports the verification of digital signatures.
4
+    However, there is no way currently to validate the certificate used to
5
+    generate a given signature. Adding certificate validation improves the
6
+    security of signature verification when each is used together.
7
+features:
8
+  - Adds a variety of certificate utility functions that inspect certificate
9
+    attributes and extensions for different settings.
10
+  - Adds the CertificateVerificationContext class which uses a set of
11
+    trusted certificates to conduct certificate validation, verifying that a
12
+    given certificate is part of a certificate chain rooted with a trusted
13
+    certificate.
14
+  - Adds a verify_certificate method that loads all certificates needed for
15
+    certificate validation from the key manager and uses them to create a
16
+    CertificateVerificationContext object. The context is then used to
17
+    determine if a certificate is valid.
18
+upgrade:
19
+  - The addition of certificate validation as a separate operation from the
20
+    signature verification process preserves backwards compatibility.
21
+    Signatures previously verifiable with cursive will still be verifiable.
22
+    However, their signing certificates may not be valid. Each signing
23
+    certificate should be checked for validity before it is used to conduct
24
+    signature verification.
25
+security:
26
+  - The usage of certificate validation with the signature verification
27
+    process improves the security of signature verification. A signature
28
+    should not be considered valid unless its corresponding certificate is
29
+    also valid.
30
+other:
31
+  - The CertificateVerificationContext is built using a set of trusted
32
+    certificates. However, to conduct certificate verification the context
33
+    builds the full certificate chain, starting with the certificate to
34
+    validate and ending with the self-signed root certificate. If this
35
+    self-signed root certificate is not present in the context, or if one
36
+    of the intermediate certificates is not present in the context, the
37
+    certificate chain cannot be built and certificate validation will fail.

Loading…
Cancel
Save