Skip to content

Commit

Permalink
limbo: add rfc5280::expired-intermediate (#98)
Browse files Browse the repository at this point in the history
* limbo: add rfc5280::expired-intermediate

Signed-off-by: William Woodruff <[email protected]>

* limbo: expired-root

Signed-off-by: William Woodruff <[email protected]>

* limbo: expired-leaf

Signed-off-by: William Woodruff <[email protected]>

* limbo: rfc5280: more namespacing

Signed-off-by: William Woodruff <[email protected]>

* lintage

Signed-off-by: William Woodruff <[email protected]>

* render-summary: prevent emojification

Signed-off-by: William Woodruff <[email protected]>

---------

Signed-off-by: William Woodruff <[email protected]>
  • Loading branch information
woodruffw authored Nov 24, 2023
1 parent 8cb6ab6 commit 9d878bf
Show file tree
Hide file tree
Showing 8 changed files with 807 additions and 562 deletions.
2 changes: 1 addition & 1 deletion harness/render-summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
else:
_OUT = sys.stdout

_RESULT_ROW = "| {testcase_id} | {status} | {expected} | {actual} | {context} |"
_RESULT_ROW = "| `{testcase_id}` | {status} | {expected} | {actual} | {context} |"


def _render(s: str) -> None:
Expand Down
714 changes: 393 additions & 321 deletions limbo.json

Large diffs are not rendered by default.

243 changes: 4 additions & 239 deletions limbo/testcases/rfc5280/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
from datetime import datetime

from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import ec

from limbo.assets import ASSETS_PATH, Certificate, ext
from limbo.models import Feature, KnownEKUs, PeerName
from limbo.testcases._core import Builder, testcase

from .aki import * # noqa: F403
from .nc import * # noqa: F403
from .san import * # noqa: F403
from .ski import * # noqa: F403
from .validity import * # noqa: F403


@testcase
Expand Down Expand Up @@ -144,195 +147,6 @@ def unknown_critical_extension_intermediate(builder: Builder) -> None:
)


@testcase
def critical_aki(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The root cert has an AKI extension marked as critical, which is disallowed
under the [RFC 5280 profile]:
> Conforming CAs MUST mark this extension as non-critical.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
"""
key = ec.generate_private_key(ec.SECP256R1())
root = builder.root_ca(
key=key,
aki=ext(
x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()), critical=True
),
)
leaf = builder.leaf_cert(root)

builder = builder.server_validation()
builder = builder.trusted_certs(root).peer_certificate(leaf).fails()


@testcase
def self_signed_root_missing_aki(builder: Builder) -> None:
"""
Produces the following **valid** chain:
```
root -> EE
```
The root cert is missing the AKI extension, which is ordinarily forbidden
under the [RFC 5280 profile] **unless** the certificate is self-signed,
which this root is:
> The keyIdentifier field of the authorityKeyIdentifier extension MUST
> be included in all certificates generated by conforming CAs to
> facilitate certification path construction. There is one exception;
> where a CA distributes its public key in the form of a "self-signed"
> certificate, the authority key identifier MAY be omitted.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
"""
root = builder.root_ca(aki=None)
leaf = builder.leaf_cert(root)

builder = builder.server_validation()
builder = builder.trusted_certs(root).peer_certificate(leaf).succeeds()


@testcase
def cross_signed_root_missing_aki(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The root is cross signed by another root but missing the AKI extension,
which is ambiguous but potentially disallowed under the [RFC 5280 profile].
> The keyIdentifier field of the authorityKeyIdentifier extension MUST
> be included in all certificates generated by conforming CAs to
> facilitate certification path construction.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
"""
xsigner_root = builder.root_ca()
root = builder.intermediate_ca(xsigner_root, pathlen=0, aki=None)
leaf = builder.leaf_cert(root)

builder = builder.server_validation().features([Feature.pedantic_rfc5280])
builder.trusted_certs(root).peer_certificate(leaf).fails()


@testcase
def intermediate_missing_aki(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> intermediate -> EE
```
The intermediate is signed by the root but missing the AKI extension, which
is forbidden under the [RFC 5280 profile].
> The keyIdentifier field of the authorityKeyIdentifier extension MUST
> be included in all certificates generated by conforming CAs to
> facilitate certification path construction.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
"""
root = builder.root_ca()
intermediate = builder.intermediate_ca(root, pathlen=0, aki=None)
leaf = builder.leaf_cert(intermediate)

builder = builder.server_validation()
builder.trusted_certs(root).untrusted_intermediates(intermediate).peer_certificate(leaf).fails()


@testcase
def leaf_missing_aki(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The EE cert is signed by the root but missing the AKI extension, which is
forbidden under the [RFC 5280 profile].
> The keyIdentifier field of the authorityKeyIdentifier extension MUST
> be included in all certificates generated by conforming CAs to
> facilitate certification path construction.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
"""
root = builder.root_ca()
leaf = builder.leaf_cert(root, aki=None)

builder = builder.server_validation()
builder.trusted_certs(root).peer_certificate(leaf).fails()


@testcase
def critical_ski(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The root cert has an SKI extension marked as critical, which is disallowed
under the [RFC 5280 profile].
> Conforming CAs MUST mark this extension as non-critical.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
"""
key = ec.generate_private_key(ec.SECP256R1())
root = builder.root_ca(
ski=ext(x509.SubjectKeyIdentifier.from_public_key(key.public_key()), critical=True),
)
leaf = builder.leaf_cert(root)

builder = builder.server_validation()
builder = builder.trusted_certs(root).peer_certificate(leaf).fails()


@testcase
def missing_ski(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The root cert is missing the SKI extension, which is disallowed under the
[RFC 5280 profile].
> To facilitate certification path construction, this extension MUST
> appear in all conforming CA certificates, that is, all certificates
> including the basic constraints extension (Section 4.2.1.9) where the
> value of cA is TRUE.
Note: for roots, the SKI should be the same value as the AKI, therefore,
this extension isn't strictly necessary, although required by the RFC.
[RFC 5280 profile]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
"""
root = builder.root_ca(ski=None)
leaf = builder.leaf_cert(root)

builder = builder.server_validation()
builder = builder.trusted_certs(root).peer_certificate(leaf).fails()


@testcase
def chain_untrusted_root(builder: Builder) -> None:
"""
Expand Down Expand Up @@ -657,32 +471,6 @@ def ee_critical_aia_invalid(builder: Builder) -> None:
).fails()


@testcase
def san_noncritical_with_empty_subject(builder: Builder) -> None:
"""
Produces an **invalid** chain due to an invalid EE cert.
The EE cert contains a non-critical Subject Alternative Name extension,
which is disallowed when the cert's Subject is empty under
RFC 5280:
> If the subject field contains an empty sequence, then the issuing CA MUST
> include a subjectAltName extension that is marked as critical.
"""

root = builder.root_ca()
leaf = builder.leaf_cert(
root,
subject=x509.Name([]),
san=ext(x509.SubjectAlternativeName([x509.DNSName("example.com")]), critical=False),
)

builder = builder.server_validation()
builder.trusted_certs(root).peer_certificate(leaf).expected_peer_name(
PeerName(kind="DNS", value="example.com")
).fails()


@testcase
def serial_number_too_long(builder: Builder) -> None:
"""
Expand Down Expand Up @@ -887,26 +675,3 @@ def mismatching_signature_algorithm(builder: Builder) -> None:
.untrusted_intermediates(*chain)
.expected_peer_name(PeerName(kind="DNS", value="cryptography.io"))
).fails()


@testcase
def malformed_subject_alternative_name(builder: Builder) -> None:
"""
Produces the following **invalid** chain:
```
root -> EE
```
The EE cert has a SubjectAlternativeName with a value in ASCII bytes, rather
than in the expected DER encoding.
"""
root = builder.root_ca()
malformed_san = ext(
x509.UnrecognizedExtension(x509.OID_SUBJECT_ALTERNATIVE_NAME, b"example.com"),
critical=False,
)
leaf = builder.leaf_cert(root, san=None, extra_extension=malformed_san)

builder = builder.server_validation()
builder = builder.trusted_certs(root).peer_certificate(leaf).fails()
Loading

0 comments on commit 9d878bf

Please sign in to comment.