Skip to content

Commit

Permalink
tests: Add x509-limbo test (#1)
Browse files Browse the repository at this point in the history
* tests: Add `x509-limbo` test

tests: Use subtests in `test_limbo`

Use the correct peer name types

tests: Flip the Limbo validation kind to `SERVER`

* tests: Update `limbo.json`

* tests: Fix Limbo tests that exercise unsupported features

* test: Use new server verifier API

* test: Don't allow empty peer name since the API requires it

* rust: Add name constraints OID to critical extensions list

* rust: Fix check for leaf certificates when applying name constraints

* test: Remove assert for `extended_key_usage` Limbo data since we're
populating it now

* test: Update `limbo.json`

* test: Skip EKU Limbo tests

* test: Add comments to explain why we're skipping certain Limbo tests

* rust: Leave comment explaining `is_leaf` parameter
  • Loading branch information
tetsuo-cpp authored Oct 24, 2023
1 parent 71bd69d commit 625fa41
Show file tree
Hide file tree
Showing 4 changed files with 1,493 additions and 6 deletions.
14 changes: 9 additions & 5 deletions src/rust/cryptography-x509-validation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ where
&self,
working_cert: &'a Certificate<'work>,
current_depth: u8,
is_leaf: bool,
) -> Result<IntermediateChain<'work>, ValidationError> {
if current_depth > self.policy.max_chain_depth {
return Err(PolicyError::Other("chain construction exceeds max depth").into());
Expand Down Expand Up @@ -263,12 +264,15 @@ where
self.policy
.valid_issuer(issuing_cert_candidate, working_cert, current_depth)
{
let result = self.build_chain_inner(issuing_cert_candidate, next_depth);
let result = self.build_chain_inner(issuing_cert_candidate, next_depth, false);
if let Ok(result) = result {
let (remaining, mut constraints) = result;
// Name constraints are not applied to self-issued certificates unless they're the leaf certificate in the chain.
let skip_name_constraints =
cert_is_self_issued(working_cert) && current_depth != 1;
// Name constraints are not applied to self-issued certificates unless they're
// the leaf certificate in the chain.
//
// NOTE: We can't simply check the `current_depth` since self-issued
// certificates don't increase the working depth.
let skip_name_constraints = cert_is_self_issued(working_cert) && !is_leaf;
if skip_name_constraints
|| self
.apply_name_constraints(&constraints, working_cert)
Expand Down Expand Up @@ -301,7 +305,7 @@ where
self.policy.permits_leaf(leaf)?;

// NOTE: We start the chain depth at 1, indicating the EE.
let result = self.build_chain_inner(leaf, 1);
let result = self.build_chain_inner(leaf, 1, true);
match result {
Ok(result) => {
let (chain, _) = result;
Expand Down
2 changes: 1 addition & 1 deletion src/rust/cryptography-x509-validation/src/policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub static WEBPKI_PERMITTED_ALGORITHMS: Lazy<HashSet<&AlgorithmIdentifier<'_>>>
});

const RFC5280_CRITICAL_CA_EXTENSIONS: &[asn1::ObjectIdentifier] =
&[BASIC_CONSTRAINTS_OID, KEY_USAGE_OID];
&[BASIC_CONSTRAINTS_OID, KEY_USAGE_OID, NAME_CONSTRAINTS_OID];
const RFC5280_CRITICAL_EE_EXTENSIONS: &[asn1::ObjectIdentifier] = &[
BASIC_CONSTRAINTS_OID,
SUBJECT_ALTERNATIVE_NAME_OID,
Expand Down
95 changes: 95 additions & 0 deletions tests/x509/test_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
# for complete details.

import datetime
import json
import os
from ipaddress import IPv4Address

import pytest

import cryptography_vectors
from cryptography import x509
from cryptography.x509 import load_pem_x509_certificate
from cryptography.x509.general_name import DNSName, IPAddress
Expand All @@ -18,6 +20,87 @@
from tests.x509.test_x509 import _load_cert


def _get_limbo_peer(expected_peer, testcase_id):
if expected_peer is None:
assert False, f"{testcase_id}: no expected peer name"
kind = expected_peer["kind"]
value = expected_peer["value"]
if kind == "DNS":
return x509.DNSName(value)
elif kind == "IP":
return x509.IPAddress(IPv4Address(value))
else:
assert False, f"{testcase_id}: unexpected peer kind: {kind}"


LIMBO_UNSUPPORTED_FEATURES = {
# NOTE: Path validation is required to reject wildcards on public suffixes,
# however this isn't practical and most implementations make no attempt to
# comply with this.
"pedantic-public-suffix-wildcard",
# TODO: We don't support Distinguished Name Constraints yet.
"name-constraint-dn",
# TODO: We don't support Extended Key Usage yet.
"eku",
}


def _limbo_testcase(testcase):
features = testcase["features"]
if features is not None and LIMBO_UNSUPPORTED_FEATURES.intersection(
features
):
return
testcase_id = testcase["id"]
assert (
testcase["validation_kind"] == "SERVER"
), f"{testcase_id}: non-SERVER testcases not supported yet"
assert (
testcase["signature_algorithms"] is None
), f"{testcase_id}: signature_algorithms not supported yet"
assert (
testcase["extended_key_usage"] is None
), f"{testcase_id}: extended_key_usage not supported yet"
assert (
testcase["expected_peer_names"] is None
), f"{testcase_id}: expected_peer_names not supported yet"

trusted_certs = [
load_pem_x509_certificate(cert.encode())
for cert in testcase["trusted_certs"]
]
untrusted_intermediates = [
load_pem_x509_certificate(cert.encode())
for cert in testcase["untrusted_intermediates"]
]
peer_certificate = load_pem_x509_certificate(
testcase["peer_certificate"].encode()
)
peer_name = _get_limbo_peer(testcase["expected_peer_name"], testcase_id)
validation_time = testcase["validation_time"]
validation_time = (
datetime.datetime.fromisoformat(validation_time)
if validation_time is not None
else None
)
should_pass = testcase["expected_result"] == "SUCCESS"

verifier = PolicyBuilder(time=validation_time).build_server_verifier(
peer_name
)
store = Store(trusted_certs)

try:
verifier.verify(peer_certificate, untrusted_intermediates, store)
assert (
should_pass
), f"{testcase_id}: verification succeeded when we expected failure"
except ValueError as e:
assert (
not should_pass
), f"{testcase_id}: verification failed when we expected success: {e}"


def test_verify_basic():
ee = load_pem_x509_certificate(
b"""
Expand Down Expand Up @@ -200,3 +283,15 @@ def test_store_initializes(self):
x509.load_pem_x509_certificate,
)
assert Store([cert]) is not None


def test_limbo(subtests):
limbo_file = cryptography_vectors.open_vector_file(
os.path.join("x509", "limbo.json"), "r"
)
with limbo_file:
limbo = json.load(limbo_file)
testcases = limbo["testcases"]
for testcase in testcases:
with subtests.test():
_limbo_testcase(testcase)
Loading

0 comments on commit 625fa41

Please sign in to comment.