Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial RFC 5280 test cases #7

Merged
merged 28 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
20a0b17
assets: fix issuers
woodruffw Jun 15, 2023
da7ea44
limbo: begin work on 5280 testcases
woodruffw Jun 20, 2023
43d1e41
Merge remote-tracking branch 'origin/main' into rfc5280
woodruffw Jun 20, 2023
4e1fa79
reformat, linting
woodruffw Jun 20, 2023
79ec0e5
cleanup
woodruffw Jun 22, 2023
01b3544
limbo: add a stub webpki mod
woodruffw Jun 22, 2023
70479da
testcases/rfc5280: critical aki, unknown ext
tnytown Jun 30, 2023
28a073c
Merge remote-tracking branch 'origin/main' into rfc5280
tnytown Jun 30, 2023
439855b
limbo: replace `v3_root_ca`, more rfc5280 tests
tnytown Jul 6, 2023
43b43c5
Merge remote-tracking branch 'origin/main' into rfc5280
tnytown Jul 6, 2023
d0c24a7
limbo.json: regenerate
tnytown Jul 6, 2023
d30d511
limbo: AKI, SAN test cases
tnytown Jul 6, 2023
79a198a
Merge remote-tracking branch 'origin/main' into rfc5280
tnytown Jul 6, 2023
512cbe9
limbo: conform to CA/B on partial wildcards
tnytown Jul 6, 2023
d6ba2ef
Update limbo/testcases/webpki.py
tnytown Jul 7, 2023
cb0be5f
gocryptox509: implement skipping
tnytown Jul 7, 2023
8ce9927
limbo: rework `Builder.intermediate_ca`
tnytown Jul 10, 2023
5683e35
webpki: `cryptography.io` chain happy path test
tnytown Jul 10, 2023
e8e873e
Merge remote-tracking branch 'origin/main' into rfc5280
tnytown Jul 10, 2023
1be4eb0
gocryptox509: squash bug with ExpectedResult
tnytown Jul 10, 2023
8b745af
`make reformat`
tnytown Jul 10, 2023
c1cd565
webpki: negative `cryptography.io` case
tnytown Jul 10, 2023
6d2a416
assets: cryptography.io.cer -> cryptography.io.pem
tnytown Jul 10, 2023
3b7c6f5
webpki: chain of pain case
tnytown Jul 11, 2023
c8722d7
webpki: `chain_untrusted_root`
tnytown Jul 11, 2023
7f1731d
`make reformat`
tnytown Jul 11, 2023
2ff083d
limbo: use `importlib.resources` for `_assets`
tnytown Jul 11, 2023
809f5d0
`make reformat`
tnytown Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion harness/gocryptox509/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ func evaluateTestcase(testcase Testcase) (testcaseResult, error) {
ts, err = time.Parse(time.RFC3339, *testcase.ValidationTime)

if err != nil {
fmt.Printf("%s\n", err)
return testcaseSkipped, errors.Wrap(err, "unable to parse testcase time as RFC3339")
}
}

expectSuccess := testcase.ExpectedResult == testcasePassed
expectSuccess := testcaseResult(testcase.ExpectedResult.(string)) == testcasePassed

// TODO: Support testcases that constrain signature algorthms.
if len(testcase.SignatureAlgorithms) != 0 {
Expand Down
421 changes: 399 additions & 22 deletions limbo.json

Large diffs are not rendered by default.

Empty file added limbo/_assets/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions limbo/_assets/cryptography.io.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
-----BEGIN CERTIFICATE-----
MIIFJDCCBAygAwIBAgISBCjrgR1TEHICklNpQDzj1PqPMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMzA1MjkxODQ0MDBaFw0yMzA4MjcxODQzNTlaMBoxGDAWBgNVBAMT
D2NyeXB0b2dyYXBoeS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AObo0GReSiFFL4eMlFHutcV+LpLDorPpzzFxxJsXhrm19GyWYHdr4ml7GIAEjqI7
QZp0aYw1lmtHwgNnaRySU+aWj6LMWI/rIP5rXZYIZLyXSfLbHP0xlfYEvcrcprOm
Au0YuQgy3TBO0qz6FKx5PtfbDc7p/LYD5tnG5NkbQ4o+7Ko361w787WSb8OV5NFd
nPqSeIjwxqSy62G6oOHL4wRFDTCOdNjHeYJnPC0L3P9qkGeC6zjqt2h8Q+GE9zNQ
enqaEOeBIZo46mti6Tvzzc7dqILw1ATqIXJdjwABzuT8Ob34/LsPorLQoRP1+YHF
++D2JyyvYKM/aFpQI+HHfGUCAwEAAaOCAkowggJGMA4GA1UdDwEB/wQEAwIFoDAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNV
HQ4EFgQUOtGXHs6fLoMQEwjlwSu88r4qLf0wHwYDVR0jBBgwFoAUFC6zF7dYVsuu
UAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8v
cjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9y
Zy8wGgYDVR0RBBMwEYIPY3J5cHRvZ3JhcGh5LmlvMEwGA1UdIARFMEMwCAYGZ4EM
AQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0
c2VuY3J5cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHcAtz77JN+cTbp1
8jnFulj0bF38Qs96nzXEnh0JgSXttJkAAAGIaQn30wAABAMASDBGAiEAglrQJj8G
a7/1upmZ2Is6AqPT9pQpSty0sH4PgnqyQxICIQDEpKnk6Rt6KzvEpIIIEtXgrYx+
crerlx4SQVQbnwfz0gB1AHoyjFTYty22IOo44FIe6YQWcDIThU070ivBOlejUutS
AAABiGkJ9+sAAAQDAEYwRAIgaLwFE4CfhV09wq5IR5zmo/90y5OQJ2MnW5gpRZZh
s4YCICEAGxUN/f95xFmxOCfqXv3SEozwkrMHA33abVjCQiaGMA0GCSqGSIb3DQEB
CwUAA4IBAQBSTN5U/3yp6cGMBXlS5WcrB/XOY6TtxPmeSvLM3vqNbpRGu1JOFFtn
31eweHOTj66GWowSy9+uAhp1V9Uf0hoJMa/b+CkCelyJN4QZCcMfhKrPAD4prbHa
GYFaLo5SQqkK1hYHo9LH+qhaOBx9hF5aLrGbEFWXQE9/W7KSeCzz6LBLw9xVrB2v
NTLlXXt5tUiczOIzge5KGaSQr5wgc1viddcRsYuZjtgWlqJ5E5QcZxD8xLTfBe5W
9vl/k1CB4CZ1IG8Sa9+n91Kxm3HTLL6TcrEOutChwMfZfrLH/piWoRQxezCpn82N
RaeeHd1Bv3oH3SeVJUHLxgzUv/dh6GSi
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
163 changes: 41 additions & 122 deletions limbo/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,43 @@
import logging
from dataclasses import dataclass
from functools import cache, cached_property
from importlib import resources
from typing import Generic, TypeVar

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
from cryptography.x509 import NameOID
from cryptography.x509 import ExtensionType, NameOID, SubjectAlternativeName

_EPOCH = datetime.datetime.fromtimestamp(0)
_ONE_THOUSAND_YEARS_OF_TORMENT = _EPOCH + datetime.timedelta(days=365 * 1000)
_ASSETS_PATH = resources.files("limbo._assets")
_ExtensionType = TypeVar("_ExtensionType", bound=ExtensionType)


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class CertificatePair:
class Certificate:
"""
An X.509 certificate and its associated private key.
An X.509 certificate.
"""

cert: x509.Certificate

@cached_property
def cert_pem(self) -> str:
return self.cert.public_bytes(encoding=serialization.Encoding.PEM).decode()


@dataclass(frozen=True)
class CertificatePair(Certificate):
"""
An X.509 certificate and its associated private key.
"""

key: PrivateKeyTypes

@cached_property
Expand All @@ -39,135 +55,31 @@ def key_pem(self) -> str:
encryption_algorithm=serialization.NoEncryption(),
).decode()

@cached_property
def cert_pem(self) -> str:
return self.cert.public_bytes(encoding=serialization.Encoding.PEM).decode()


@cache
def v3_root_ca(*, aki: bool = True, ski: bool = True) -> CertificatePair:
@dataclass(frozen=True)
class _Extension(Generic[_ExtensionType]):
"""
A (configurable) X.509v3 root CA.
An X.509 extension and its criticality.
"""
key = rsa.generate_private_key(public_exponent=65537, key_size=4096)

builder = x509.CertificateBuilder()
builder = builder.subject_name(
x509.Name(
[
x509.NameAttribute(NameOID.COMMON_NAME, "x509-limbo-root"),
]
)
)
builder = builder.issuer_name(
x509.Name(
[
x509.NameAttribute(NameOID.COMMON_NAME, "x509-limbo-root"),
]
)
)
builder = builder.not_valid_before(_EPOCH)
builder = builder.not_valid_after(_ONE_THOUSAND_YEARS_OF_TORMENT)
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(key.public_key())
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False,
)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
)
builder = builder.add_extension(
x509.KeyUsage(
digital_signature=False,
key_cert_sign=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
),
critical=False,
)
certificate = builder.sign(
private_key=key,
algorithm=hashes.SHA256(),
)
ext: _ExtensionType
critical: bool

return CertificatePair(certificate, key)


@cache
def intermediate_ca_pathlen_n(parent: CertificatePair, pathlen: int) -> CertificatePair:
def ext(extension: _ExtensionType, *, critical: bool) -> _Extension[_ExtensionType]:
"""
An intermediate CA chained up to a root CA.

The intermediate CA has a `pathlen:N` constraint, where `N` varies.

These intermediates can be used to assert various behaviors, including:

* That `pathlen:N` constraints are properly honored;
* That certificates are correctly uniqued by both their key **and** their
subject (as each intermediate generated here shares the same key)
Constructs a new _Extension to pass into certificate builder helpers.
"""
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

builder = x509.CertificateBuilder()
builder = builder.subject_name(
x509.Name(
[
x509.NameAttribute(
NameOID.COMMON_NAME, f"x509-limbo-intermediate-pathlen-{pathlen}"
),
]
)
)
builder = builder.issuer_name(parent.cert.subject)
builder = builder.not_valid_before(_EPOCH)
builder = builder.not_valid_after(_ONE_THOUSAND_YEARS_OF_TORMENT)
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(key.public_key())
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False,
)
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
parent.key.public_key() # type: ignore[arg-type]
),
critical=False,
)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=pathlen),
critical=True,
)
builder = builder.add_extension(
x509.KeyUsage(
digital_signature=False,
key_cert_sign=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
),
critical=False,
)
certificate = builder.sign(
private_key=parent.key, # type: ignore[arg-type]
algorithm=hashes.SHA256(),
)

return CertificatePair(certificate, key)
return _Extension(extension, critical)


@cache
def ee_cert(parent: CertificatePair) -> CertificatePair:
def ee_cert(
parent: CertificatePair,
subject_alternative_name: _Extension[SubjectAlternativeName] | None = None,
*,
extra_extension: _Extension | None = None,
) -> CertificatePair:
"""
Produces an end-entity (EE) certificate, signed by the given `parent`'s
key.
Expand Down Expand Up @@ -200,7 +112,7 @@ def ee_cert(parent: CertificatePair) -> CertificatePair:
)
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
critical=False,
)
builder = builder.add_extension(
x509.KeyUsage(
Expand All @@ -216,6 +128,13 @@ def ee_cert(parent: CertificatePair) -> CertificatePair:
),
critical=False,
)
if subject_alternative_name is not None:
builder = builder.add_extension(
subject_alternative_name.ext, subject_alternative_name.critical
)
if extra_extension is not None:
builder = builder.add_extension(extra_extension.ext, extra_extension.critical)

certificate = builder.sign(
private_key=parent.key, # type: ignore[arg-type]
algorithm=hashes.SHA256(),
Expand Down
2 changes: 2 additions & 0 deletions limbo/testcases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from limbo.testcases._core import registry

from .pathlen import * # noqa: F403
from .rfc5280 import * # noqa: F403
from .webpki import * # noqa: F403

__all__ = [
"registry",
Expand Down
Loading