feat(corim): add x5chain trust verification with PKIX and CRL support#276
Conversation
ac7a6c7 to
ca7fa70
Compare
cb84ffc to
980f78f
Compare
thomas-fossati
left a comment
There was a problem hiding this comment.
A most impressive contribution, I must say.
🚢 it!
|
Hi @setrofim @yogeshbdeshpande @DhanusML , this PR is ready for review,all checks are passing! |
|
Hi @thomas-fossati quick question on CRL policy for VerifyWithX5Chain: |
This is a policy decision that the stack should not make. Instead, we should set and document a reasonable default (fail-close) and allow users to override the behaviour. |
Thanks @thomas-fossati for the CRL-policy guidance — I've updated the implementation accordingly. Default: strict mode ( Strict vs permissive
The only behavioural difference is the missing-issuer-CRL case: strict fails closed; permissive skips. Revoked serials and invalid CRLs still fail in both modes. APIanchors, err := corim.LoadTrustAnchors(readFile, trustAnchorPaths, crlPaths)
// Default: anchors.CrlPolicy == corim.CrlPolicyStrict (zero value)
// anchors.CrlPolicy = corim.CrlPolicyPermissive // explicit override
err = signed.VerifyWithX5Chain(anchors)Happy to adjust naming or defaults if you prefer something else. |
305ebed to
566f74d
Compare
|
Thanks, @magickli1! The update looks good to me. You have a linter check that has failed in corim/x509chain_test.go. You can probably fix it by passing a pointer to |
566f74d to
4c71ad9
Compare
Sorry about that — I should have checked earlier. I've fixed it . |
|
@thomas-fossati Could you help me to ping the other reviewers? I'll follow up with the cocli PR once this merges. |
|
@magickli1 Thank you for the PR. I will review it by the end of the day today and provide you the review feedback! |
CoRIM-rs already stored COSE x5chain headers but had no PKIX trust path. Add end-to-end verification behind `feature = "openssl"`, aligned with veraison/corim#276 (TrustAnchors, CRL strict/permissive, Go parity). API: TrustAnchors, CrlPolicy, load_trust_anchors, verify_x5chain_ders, SignedCorim::verify_with_x5chain. Order: leaf signing policy → PKIX (explicit anchors or OS store; no merge) → optional CRL when material is supplied → COSE verify via validated leaf key (no signature-validity). Includes openssl X.509 helpers, CorimError variants, test PKI fixtures, and inline tests. Default features unchanged (`default = []`). Signed-off-by: magickli <lq1029901708@gmail.com>
yogeshbdeshpande
left a comment
There was a problem hiding this comment.
started reviewing it now
yogeshbdeshpande
left a comment
There was a problem hiding this comment.
I have left comments based on my review!
Please let me know if you want to discuss this during the Veraison Sync up today at 4PM UK time.
Add VerifyWithX5Chain and LoadTrustAnchors (PKIX + optional CRL, then COSE verify). Empty CRL set skips revocation; CrlPolicyStrict requires nextUpdate. Stricter x5chain decode (RFC 9052): reject empty arrays and multi-cert elements. Leaf keyUsage optional; digitalSignature required when present. README documents the new API. Signed-off-by: magickli <lq1029901708@gmail.com>
4c71ad9 to
0eb3e6b
Compare
Thanks for the review — I'll address your comments on the PR. I can't make today's sync (4 PM UK)。 |
Summary
Adds library support to verify CoRIMs signed with a COSE Sign1
x5chainheader: core PKIX path validation per RFC 5280 §6 (signature chain, validity period, name & basic constraints, path-length), optional post-PKIX CRL revocation checks, then COSE signature verification with the PKIX-validated leaf public key.This is not a full §6 implementation — certificate-policy processing is out of scope (see below).
Closes the gap for embedders that today must extract the leaf from
x5chainand callSignedCorim.Verify(pk)manually. CLI exposure is tracked in cocli#59.Motivation
Today callers must extract the leaf certificate from
x5chainand callSignedCorim.Verify(pk)manually. This PR provides an end-to-end trust path for embedders that load trust material from files or construct aTrustAnchorsvalue in memory.New API
TrustAnchors*x509.CertPool), optional CRLs,CrlPolicy, optionalCurrentTime(zero →time.Now())CrlPolicy/CrlPolicyStrict/CrlPolicyPermissiveCRLsis non-empty (default: strict)LoadTrustAnchors(readFile, trustAnchorPaths, crlPaths)TrustAnchorsSignedCorim.VerifyWithX5Chain(anchors)Naming aligns with COSE
x5chainterminology and pairs withVerify(pk)(external key, no PKIX).LoadTrustAnchorsuses aLoad*name for file I/O and matches cocli--trust-anchorsterminology.Call
SignedCorim.FromCOSEfirst. Key-based verification without PKIX remainsSignedCorim.Verify.Trust-anchor semantics
Aligned with maintainer feedback on cocli#59:
TrustAnchors.PooltrustAnchorPathsemptyniltrustAnchorPathsnon-emptyCallers may also construct
TrustAnchorsin memory (e.g. an empty non-nil pool for explicit-trust-only tests).LoadTrustAnchorsdetails: PEM trust-anchor bundles viax509.CertPool.AppendCertsFromPEM; PEM CRL files may contain multiple blocks; duplicate DER anchors acrosstrustAnchorPathsare added once (PEM bundles rely onCertPooldedup). Load/parse errors usetrust anchorwording (e.g.loading trust anchor from %s).CRL policy
Revocation is opt-in at the API level (
CRLsempty → skip all revocation checks). When CRL material is supplied, behaviour is governed byCrlPolicy(zero value =CrlPolicyStrict):TrustAnchors.CRLsCrlPolicyCrlPolicyStrict(default)CRL_CHECK_ALL: for each(certificate, issuer)on the verified chain (excluding the trust anchor at the chain end), require at least one valid CRL inCRLssigned by that issuer; missing issuer CRL → fail (unable to get certificate CRL)CrlPolicyPermissiveShared rules (both policies, when
CRLsis non-empty):Strict vs permissive (quick reference)
CRLsemptyVerification order
FromCOSE); elseno Sign1 message foundx5chainheader; elsex5chain: header not set in CoRIMvalidateLeafSigningCert): non-CA;digitalSignaturewhen KeyUsage is present (certs without KeyUsage pass)ExtKeyUsageAnyagainstanchors.Pool(system store whenPool == nil)anchors.CRLsis non-empty (checkChainRevocation, perCrlPolicyabove)kid)When PKIX returns multiple valid chains,
selectVerifiedChainpicks the path with the most DER overlap with the presentedx5chain(ties → first best-scoring chain).PKIX errors
PKIX failures are wrapped as
x5chain verification failed: %w, preserving standard library types (e.g.x509.UnknownAuthorityError). Callers should useerrors.Asrather than matching custom anchor-hint strings.COSE signature failures after successful PKIX are wrapped as
x5chain: COSE signature verification failed.Usage example
signed.Verify(publicKey)LoadTrustAnchors(..., []string{...}, crlPaths)+VerifyWithX5ChainLoadTrustAnchors(..., nil, crlPaths)→Pool == nilCRLs, default /CrlPolicyStrictanchors.CrlPolicy = corim.CrlPolicyPermissivecocli corim verify --file signed.cbor --trust-anchors anchor.der --crl issuer.crl --crl-policy strict|permissive— cocli#59Other changes
FromCOSE/extractX5ChainfixesFromCOSEclearsSigningCert/IntermediateCertsbefore decode; on any failure (including payload validation after headers parse), clearsmessage,SigningCert, andIntermediateCertsextractX5Chainassigns parsed certs directly (no longer viaAddSigningCert/AddIntermediateCerts), so parse errors do not mutate object stateextractX5Chaincalls still preserve existing fields on parse failureDocumentation & fixtures
Package examples (pkg.go.dev):
ExampleSignedCorim_VerifyWithX5Chain,ExampleLoadTrustAnchors(x5chainExampleMetahelper avoids example name clashes)Godoc on
TrustAnchors,CrlPolicy,LoadTrustAnchors, andVerifyWithX5ChainNo root
README.mdchange (usage in examples + godoc)scripts/gen-certs.sh: PKIX extensions for test CA chain;regeneratecommandRe-issued
testdata/*.der(keys unchanged; verify needs.deronly;.keyfor test signing):FORCE=1 make test-certs # or: scripts/gen-certs.sh regenerateOut of scope
crypto/x509does not implement policy validation and neither does this PR.kidvs x5chain leaf cross-check (COSE treatskidas a hint; useVerifyfor external-key verify)VerifyWithX5Chainonly)--trust-anchors,--crl,--crl-policy strict|permissive)Revocation caveat: With
CRLsempty, revocation is not checked. WithCRLsnon-empty andCrlPolicyPermissive, issuers without a matching CRL are skipped — both deviate from RFC 5280 §6.1.3(3)/§6.3 expectations for full revocation status determination.CrlPolicyStrictis the safer default when CRLs are supplied.Test plan
make presubmitpasses (coverage ≥84.4%, lint clean)x509chain_test.go:VerifyWithX5Chain,LoadTrustAnchors(DER/PEM anchors and CRLs); helpersvalidateLeafSigningCert,checkChainRevocation,selectVerifiedChainerrors.AsforUnknownAuthorityError), wrong anchor viaLoadTrustAnchors, expiry, revocation (leaf/intermediate), tampered payload, signing-key mismatch, CRL not-yet-validCrlPolicyStrict: missing issuer CRL failsCrlPolicyPermissive: missing issuer CRL passes; revoked / all matching CRLs expired still failLoadTrustAnchorswith emptytrustAnchorPaths—Poolnil (system store at verify time);wrongTrustAnchorFails,emptyPathsUsesSystemStoresignedcorim_test.go:FromCOSEidempotent / shrink / absent header / stale intermediates / payload failure clears x5chain andmessage/extractX5Chainpreserves state on errorVerifyWithX5Chainreturnsno Sign1 message foundwhenFromCOSEwas not called