Skip to content

feat(xmldsig): resolve X509 selectors#72

Open
polaz wants to merge 13 commits into
mainfrom
feat/#71-keyinfo-donor-autoresolve
Open

feat(xmldsig): resolve X509 selectors#72
polaz wants to merge 13 commits into
mainfrom
feat/#71-keyinfo-donor-autoresolve

Conversation

@polaz

@polaz polaz commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Resolve selector-only X509Data against configured certificates by subject, issuer/serial, SKI, and digest
  • Keep certificate selection fail-closed for malformed, unsupported, ambiguous, and algorithm-incompatible inputs; chain validation requires a distinct configured anchor
  • Verify supported Aleksey donor vectors through DefaultKeyResolver, including embedded X.509, configured selectors, and an anchored certificate chain
  • Add a reproducible donor fixture importer and document the expanded resolver capability

Testing

  • cargo check --workspace
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --workspace --all-features
  • cargo nextest run --workspace (504 passed)
  • cargo test --doc --workspace (3 passed)
  • git diff --check

Closes #71

polaz added 2 commits July 1, 2026 01:36
- Match configured certificates by subject, issuer/serial, SKI, and digest
- Verify supported donor vectors through DefaultKeyResolver
- Add reproducible donor fixture import and validated chain coverage

Closes #71
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added broader XML Digital Signature verification support, including more certificate selector types and chain-based validation.
    • Expanded verification to recognize embedded certificates and selected trust anchors in more cases.
  • Bug Fixes
    • Improved certificate matching so signatures resolve more reliably when multiple certificates are present.
    • Added stricter handling for ambiguous or unsupported certificate selectors, preventing incorrect validation results.
  • Documentation
    • Updated the README status notes to reflect the latest supported verification-key resolution options.

Walkthrough

Adds selector-based X.509 certificate resolution in the parser and key resolver, updates donor verification to use embedded, selected, and chain-based certificate paths, and adds fixture tooling, a new chain fixture, count updates, and README wording.

Changes

Selector-based X.509 resolution and donor verification

Layer / File(s) Summary
Parser selector matching
src/xmldsig/parse.rs
Extends X509Data matching to include digest selectors, updates candidate selection and ambiguity handling, exposes certificate parsing helpers, and adjusts parser tests for digest-based selection.
Key resolver configured lookup
src/xmldsig/keys.rs
Reworks resolve_x509 to resolve from embedded chains or configured trusted certificates, adds selector-driven policy helpers, adds the unsupported digest error variant, and extends key-resolver tests.
Donor verification expectations
tests/donor_full_verification_suite.rs
Replaces the donor suite expectation model with embedded, named, selected, and chain-based verification modes, adds PEM-to-DER helpers, rewires verification branches, and updates skip accounting and pass counts.
Fixture tooling and counts
scripts/import-donor-fixtures.sh, tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-rsa-x509chain.xml, tests/fixtures_smoke.rs, README.md
Adds a donor fixture import script, introduces a new enveloping RSA X.509 chain fixture, updates the fixture-count smoke test, and rewrites the README status note for verification-key resolution.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main X509 selector-resolution change.
Description check ✅ Passed The description is clearly related to the selector-resolution and donor fixture updates in this PR.
Linked Issues check ✅ Passed The changes implement selector-only X509Data resolution, fail-closed handling, donor fixture support, and the required verification coverage from #71.
Out of Scope Changes check ✅ Passed The README, smoke test count, fixture importer, and test adjustments all support the resolver and donor coverage work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#71-keyinfo-donor-autoresolve

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR expands XML signature X.509 key resolution. The main changes are:

  • Configured certificate lookup by subject, issuer serial, SKI, and digest selectors.
  • Chain validation for selector-resolved certificates.
  • Cross-certificate selector matching for embedded and configured chains.
  • Donor fixture coverage for embedded, configured, and chain-based X.509 verification.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
src/xmldsig/keys.rs Adds configured X.509 selector resolution and applies chain policy to selected certificates.
src/xmldsig/parse.rs Updates embedded X.509 selector matching so selectors can identify different certificates in one chain.
tests/donor_full_verification_suite.rs Extends donor verification coverage for embedded certificates, configured selectors, named keys, and validated chains.

Reviews (4): Last reviewed commit: "test(xmldsig): pin donor chain time" | Re-trigger Greptile

Comment thread src/xmldsig/keys.rs Outdated
Comment thread src/xmldsig/parse.rs Outdated
Comment thread src/xmldsig/keys.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/import-donor-fixtures.sh (1)

1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Script name implies bulk import but only copies one hardcoded file.

import-donor-fixtures.sh (plural) currently hardcodes a single file copy. Fine for now, but if this is meant to be the reusable importer referenced by the PR objective ("adds a reproducible donor fixture importer"), consider parameterizing it (e.g., accept a relative path argument, or loop over a list) so future donor fixtures don't require a near-duplicate script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/import-donor-fixtures.sh` around lines 1 - 11, The
import-donor-fixtures.sh script currently behaves like a one-off copy instead of
a reusable importer, since it hardcodes a single fixture path in the install
step. Update the script around repo_root, donor_root, fixture_root, and the
install logic so it can take a relative path argument or iterate over a list of
donor fixture files, allowing additional fixtures to be imported without
duplicating the script.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/xmldsig/keys.rs`:
- Around line 231-263: The configured selector-chain handling in the key
resolution logic is too strict because `x509_certificate_matches_selectors` only
accepts one certificate matching all selector categories, so valid leaf/issuer
combinations can be missed. Update the certificate-selection path in the
trusted-certs loop to collect configured certificates matching any selector
category, ensure each selector category is represented across that set, and then
choose the unique leaf certificate from those matches. Keep the ambiguity and
invalid-certificate handling via `KeyResolutionError` consistent with the
existing `parse_x509_certificate` and `matches/leaves` flow.

In `@tests/donor_full_verification_suite.rs`:
- Around line 257-260: The donor full verification test is relying on the
current system time when `DefaultKeyResolver::new` is created with
`verify_chains: true`, which makes fixture validation time-dependent. Update the
`KeyResolverConfig` in this test to set `verification_time: Some(...)` to a
fixed instant that falls within the donor certificate chain validity window so
the verification remains deterministic.
- Around line 115-119: The donor_full_verification_suite coverage is missing the
TOFU/default-resolver verification for the same xml fixture, so add a new
VectorCase alongside the existing ValidChain case that exercises embedded chain
validation for enveloping-rsa-x509chain.xml without a trust anchor. Use the
existing test data and naming patterns in donor_full_verification_suite.rs, and
make sure the new case targets the verification path that uses the default
resolver/TOFU behavior rather than the anchored Expectation::ValidChain branch.

---

Outside diff comments:
In `@scripts/import-donor-fixtures.sh`:
- Around line 1-11: The import-donor-fixtures.sh script currently behaves like a
one-off copy instead of a reusable importer, since it hardcodes a single fixture
path in the install step. Update the script around repo_root, donor_root,
fixture_root, and the install logic so it can take a relative path argument or
iterate over a list of donor fixture files, allowing additional fixtures to be
imported without duplicating the script.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eaad40ac-3249-44a7-91f9-d49609594c28

📥 Commits

Reviewing files that changed from the base of the PR and between a14c381 and 283a773.

📒 Files selected for processing (7)
  • README.md
  • scripts/import-donor-fixtures.sh
  • src/xmldsig/keys.rs
  • src/xmldsig/parse.rs
  • tests/donor_full_verification_suite.rs
  • tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-rsa-x509chain.xml
  • tests/fixtures_smoke.rs

Comment thread src/xmldsig/keys.rs
Comment thread tests/donor_full_verification_suite.rs
Comment thread tests/donor_full_verification_suite.rs
@polaz

polaz commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai Re: reusable donor importer — fixed. The script now accepts relative fixture paths, defaults to the current chain fixture, creates destination directories, and was validated in both default and explicit-argument modes.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd import-donor-fixtures.sh
echo "---"
cat scripts/import-donor-fixtures.sh

Length of output: 607


@polaz Thanks for the update — however, when I pulled the current PR branch, scripts/import-donor-fixtures.sh still shows the original hardcoded version:

#!/usr/bin/env bash
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
donor_root="$repo_root/donors/xmlsec/tests"
fixture_root="$repo_root/tests/fixtures/xmldsig"

install -m 0644 \
  "$donor_root/aleksey-xmldsig-01/enveloping-rsa-x509chain.xml" \
  "$fixture_root/aleksey-xmldsig-01/enveloping-rsa-x509chain.xml"

There's no argument parsing, default-to-current-fixture logic, or destination directory creation (mkdir -p) in this version. Could you confirm the commit with the fix has actually been pushed to this branch? I want to make sure I'm reviewing the latest state before confirming this is resolved.

(੭ ` ᵥ ´ )੭ ⁼³₌₃ puzzled bunny scratches head

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/xmldsig/parse.rs (1)

996-1039: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require every selector value to be represented, not just every category.

Line 999, Line 1006, Line 1018, and Line 1026 currently make each category pass if any selector in that category matches a chain member. That lets mixed data like one matching X509Digest plus one non-matching X509Digest still resolve, which violates the fail-closed selector behavior required here.

Proposed direction
-    let subject_match = info.subject_names.is_empty()
-        || info.parsed_certificates.iter().any(|certificate| {
-            info.subject_names
-                .iter()
-                .any(|subject| subject.trim() == certificate.subject_dn)
-        });
+    let subject_match = info.subject_names.iter().all(|subject| {
+        info.parsed_certificates
+            .iter()
+            .any(|certificate| subject.trim() == certificate.subject_dn)
+    });

-    let ski_match = info.skis.is_empty()
-        || info.parsed_certificates.iter().any(|certificate| {
-            certificate
-                .subject_key_identifier
-                .as_ref()
-                .is_some_and(|certificate_ski| info.skis.iter().any(|ski| ski == certificate_ski))
-        });
+    let ski_match = info.skis.iter().all(|ski| {
+        info.parsed_certificates.iter().any(|certificate| {
+            certificate
+                .subject_key_identifier
+                .as_ref()
+                .is_some_and(|certificate_ski| ski == certificate_ski)
+        })
+    });

Apply the same all(selector) -> any(certificate) pattern to issuer/serial and digest selectors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/xmldsig/parse.rs` around lines 996 - 1039, The selector matching logic in
x509_selector_categories_match_chain is too permissive because each category
currently succeeds when any single selector in that category matches; update the
subject, issuer/serial, SKI, and digest checks so every provided selector value
must be represented by at least one parsed certificate, using an all(selector)
over the selector lists rather than category-level any() short-circuiting. Keep
the existing ParseError handling and locate the changes within
x509_selector_categories_match_chain in parse.rs, especially the subject_names,
issuer_serials, skis, and digests branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/xmldsig/parse.rs`:
- Around line 996-1039: The selector matching logic in
x509_selector_categories_match_chain is too permissive because each category
currently succeeds when any single selector in that category matches; update the
subject, issuer/serial, SKI, and digest checks so every provided selector value
must be represented by at least one parsed certificate, using an all(selector)
over the selector lists rather than category-level any() short-circuiting. Keep
the existing ParseError handling and locate the changes within
x509_selector_categories_match_chain in parse.rs, especially the subject_names,
issuer_serials, skis, and digests branches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a186001e-957a-4009-adca-3fa3e448a0fb

📥 Commits

Reviewing files that changed from the base of the PR and between 283a773 and 990db95.

📒 Files selected for processing (4)
  • scripts/import-donor-fixtures.sh
  • src/xmldsig/keys.rs
  • src/xmldsig/parse.rs
  • tests/donor_full_verification_suite.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(xmldsig): auto-resolve donor KeyInfo vectors

1 participant