Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,10 @@ pkcs12 = { version = "=0.2.0-pre.0", default-features = false, features = ["kdf"
pbkdf2 = { version = "0.13", default-features = false, features = ["hmac"], optional = true }

# X.509 certificates
structured-asn1 = { version = "0.18.0", default-features = false, features = ["alloc"], optional = true }
structured-pkix = { version = "0.18.0", default-features = false, features = ["alloc"], optional = true }
structured-asn1 = { version = "0.18.2", default-features = false, features = ["alloc"], optional = true }
structured-pkix = { version = "0.18.2", default-features = false, features = ["alloc"], optional = true }
# PKCS#12 container framing; this crate keeps the password, KDF and cipher work.
structured-cms = { version = "0.18.0", default-features = false, features = ["alloc"], optional = true }
structured-cms = { version = "0.18.2", default-features = false, features = ["alloc"], optional = true }
tinyvec = { version = "1.13", optional = true }
# `pem` is RFC 7468 decoding of caller key and certificate text.
der = { version = "0.8", default-features = false, features = ["alloc", "pem"], optional = true }
Expand Down
11 changes: 6 additions & 5 deletions benches/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@ fn large_reference_sign(bencher: divan::Bencher, units: usize) {
#[divan::bench(args = specs().filter(|spec| spec.backend != xml_sec::XmlBackend::Differential))]
fn projection(bencher: divan::Bencher, spec: Spec) {
let fixture = Fixture::new(spec);
let projection = xml_sec::benchmark_support::Projection::new(&fixture.unsigned, spec.backend)
.expect("prepared backend");
bencher
.counter(divan::counter::BytesCount::new(fixture.unsigned.len()))
.bench_local(|| {
std::hint::black_box(projection.project().expect("project"));
});
// Projection consumes its backend DOM, so each sample prepares a new one.
.with_inputs(|| {
xml_sec::benchmark_support::Projection::new(&fixture.unsigned, spec.backend)
.expect("prepared backend")
})
.bench_local_values(|projection| projection.project().expect("project"));
}

/// Signed-document shape for node-set measurements: `elements` siblings with
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/competitors/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/key-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ table from the host, network or a built-in default. Missing facts return
`CrlTimeUncertain`, never non-revocation. Cached complete CRLs permit the exact
`nextUpdate` boundary, but even one nanosecond later fails freshness. Revocation
dates are checked against the same lossless operation time. Unsupported critical
demands, entry conflicts and unpaired delta CRLs cannot authorize a key.
demands, entry conflicts and delta CRLs without their complete CRL cannot
authorize a key; a bound complete-plus-delta pair is evaluated as one source.
Applicable partial lists must collectively cover all revocation reasons;
otherwise `IncompleteCrlCoverage` is returned. This checks supplied evidence,
not an implicit online revocation fetch or a requirement to supply a CRL for
Expand Down
12 changes: 9 additions & 3 deletions docs/xmldsig.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,11 +624,17 @@ CRL checking is meaningful only inside authenticated X.509 path validation. A po
CRLs without enabling certificate-chain validation is rejected during context construction rather
than silently accepting a control the resolver cannot enforce.
CRL structure is validated before authority-key applicability is selected: duplicate CRL or
CRL-entry extension OIDs fail closed, and `deltaCRLIndicator` is rejected regardless of criticality.
CRL-entry extension OIDs fail closed.
Applicable CRLs must include the RFC 5280 `nextUpdate` field and the verification time must remain
inside the bounded `thisUpdate` through `nextUpdate` validity window.
The `removeFromCRL` reason is rejected in complete CRLs because it is meaningful only in a delta
CRL. URI subject alternative names
A delta CRL is evaluated together with the complete CRL it binds to under RFC 5280 section 5.2.4:
same issuer, key and scope, a critical `deltaCRLIndicator`, and a complete CRL number from
`BaseCRLNumber` up to but excluding the delta's own. The newest bound delta decides, and its
validity window governs the pair, so a current delta keeps an expired complete CRL usable while an
expired delta is refused even over a current one. An applicable delta bound to no supplied complete
CRL fails closed.
The `removeFromCRL` reason lifts an entry only from a delta CRL and is rejected in complete CRLs.
URI subject alternative names
likewise require one RFC 3986 authority, including syntactically valid userinfo, before their host
can participate in `NameConstraints` matching.
Critical `NameConstraints` also retain their DER structure during validation: the extension must
Expand Down
19 changes: 8 additions & 11 deletions examples/benchmark_latency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ fn percentile(sorted: &[u128], percent: usize) -> u128 {
fn measure(
fixture: &Fixture,
operation: &str,
projection: Option<&xml_sec::benchmark_support::Projection<'_>>,
backend: XmlBackend,
policy: &xml_sec::policy::VerificationPolicy,
) -> (u128, usize) {
// Per-sample preparation is excluded, just as Divan's with_inputs excludes it.
// Projection consumes its backend DOM, so each sample prepares a new one.
let projection = (operation == "projection").then(|| {
xml_sec::benchmark_support::Projection::new(&fixture.unsigned, backend)
.expect("prepared projection")
});
let mut plan = match operation {
"plan_compile" | "plan_execute" => Some(xml_sec::benchmark_support::Plan::new(
fixture.spec.units.min(64),
Expand Down Expand Up @@ -332,14 +337,6 @@ fn main() -> Result<(), Box<dyn Error>> {
} = parse_request(&args)?;
let (units, backend) = (spec.units, spec.backend);
let fixture = Fixture::new(spec);
let projection = if operation == "projection" {
Some(xml_sec::benchmark_support::Projection::new(
&fixture.unsigned,
backend,
)?)
} else {
None
};
let policy = xml_sec::policy::VerificationPolicy {
signature_algorithms: Some(
[xml_sec::xmldsig::SignatureAlgorithm::RsaSha256]
Expand All @@ -349,12 +346,12 @@ fn main() -> Result<(), Box<dyn Error>> {
..Default::default()
};
for _ in 0..8 {
std::hint::black_box(measure(&fixture, operation, projection.as_ref(), &policy));
std::hint::black_box(measure(&fixture, operation, backend, &policy));
}
let mut timings = Vec::with_capacity(samples);
let mut output_bytes = 0;
for _ in 0..samples {
let (elapsed, bytes) = measure(&fixture, operation, projection.as_ref(), &policy);
let (elapsed, bytes) = measure(&fixture, operation, backend, &policy);
output_bytes = bytes;
timings.push(elapsed);
}
Expand Down
88 changes: 88 additions & 0 deletions scripts/generate-delta-crl-fixtures.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Regenerate the delta CRL fixtures of tests/delta_crl.rs.
#
# OpenSSL acts as an independent certificate authority: it issues one complete
# CRL and two delta CRLs against it, one silent and one revoking the leaf. Its
# own verifier checks both pairs here, so the fixtures carry its answer. It only
# reads a delta for a certificate that has freshestCRL (RFC 5280 4.2.1.15).
#
# Usage: scripts/generate-delta-crl-fixtures.sh
# DELTA_CRL_FIXTURE_OUTPUT optionally selects an absolute output directory.
# Requires OpenSSL 3.4 or later for fixed certificate validity dates.
set -euo pipefail

repository="$(cd "$(dirname "$0")/.." && pwd)"
output="${DELTA_CRL_FIXTURE_OUTPUT:-$repository/tests/fixtures/x509/delta-crl}"
mkdir -p "$output"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
cd "$work"

NOT_BEFORE=20260101000000Z
NOT_AFTER=20450101000000Z
# 2040-01-01T00:00:00Z, after any revocation made while generating.
VERIFICATION_TIME=2208988800

cat >ca.cnf <<'EOF'
[ca]
default_ca = authority

[authority]
database = index.txt
crlnumber = crlnumber
default_md = sha256
crl_extensions = complete

[complete]
authorityKeyIdentifier = keyid

[delta]
authorityKeyIdentifier = keyid
2.5.29.27 = critical,ASN1:INTEGER:5
EOF
: >index.txt
echo 05 >crlnumber

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ca.key 2>/dev/null
openssl req -x509 -new -key ca.key -out ca.pem \
-not_before "$NOT_BEFORE" -not_after "$NOT_AFTER" \
-subj "/CN=xml-sec Test Delta CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" 2>/dev/null
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out leaf.key 2>/dev/null
openssl req -x509 -new -key leaf.key -CA ca.pem -CAkey ca.key -out leaf.pem \
-not_before "$NOT_BEFORE" -not_after "$NOT_AFTER" \
-subj "/CN=xml-sec Test Delta Leaf" \
-addext "basicConstraints=critical,CA:FALSE" \
-addext "keyUsage=critical,digitalSignature" \
-addext "freshestCRL=URI:https://example.test/delta.crl" 2>/dev/null

# list <file> <extension section>: the next CRL of this authority.
list() {
openssl ca -gencrl -config ca.cnf -keyfile ca.key -cert ca.pem \
-crlexts "$2" -crl_lastupdate "$NOT_BEFORE" -crl_nextupdate "$NOT_AFTER" \
-out "$1"
}

list complete.pem complete
list silent.pem delta
openssl ca -revoke leaf.pem -config ca.cnf -keyfile ca.key -cert ca.pem 2>/dev/null
list revoking.pem delta

# accepted <delta>: the verifier's answer for the leaf under that pair.
accepted() {
openssl verify -CAfile ca.pem -CRLfile complete.pem -CRLfile "$1" \
-crl_check -use_deltas -attime "$VERIFICATION_TIME" leaf.pem >/dev/null 2>&1
}

accepted silent.pem
if accepted revoking.pem; then
echo "OpenSSL accepted a leaf its delta CRL revokes" >&2
exit 1
fi

openssl x509 -in ca.pem -outform DER -out "$output/ca.der"
openssl x509 -in leaf.pem -outform DER -out "$output/leaf.der"
for name in complete silent revoking; do
openssl crl -in "$name.pem" -outform DER -out "$output/$name.crl"
done
Loading
Loading