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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
61 changes: 57 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,39 @@ name = "xmlsec1"
path = "tools/xmlsec1/src/main.rs"
required-features = ["xmldsig", "xmlenc", "c14n"]

[[test]]
name = "xmlsec1_interop"
path = "tests/xmlsec1_interop.rs"
required-features = ["xmldsig", "c14n"]

[[test]]
name = "signing_digest"
required-features = ["xmldsig", "c14n"]

[[test]]
name = "signature_pipeline_integration"
required-features = ["xmldsig"]

[[test]]
name = "donor_negative_vectors"
required-features = ["xmldsig"]

[[test]]
name = "xpath_transform_integration"
required-features = ["xmldsig", "c14n"]

[[test]]
name = "base64_transform_integration"
required-features = ["xmldsig", "c14n"]

[[test]]
name = "merlin_interop"
required-features = ["xmldsig"]

[[test]]
name = "uri_integration"
required-features = ["xmldsig", "c14n"]

[[test]]
name = "cli_import_snapshot"
path = "tools/xmlsec1/tests/import_snapshot.rs"
Expand All @@ -77,6 +110,14 @@ required-features = ["xmldsig", "xmlenc", "c14n"]
name = "encrypt"
required-features = ["xmlenc"]

[[example]]
name = "sign"
required-features = ["xmldsig", "c14n"]

[[example]]
name = "verify"
required-features = ["xmldsig"]

[[bench]]
name = "security"
harness = false
Expand All @@ -102,7 +143,12 @@ aws-lc-rs = { version = "1.18.1", default-features = false, features = ["fips"],
rsa = { package = "sad-rsa", version = "0.10.2", default-features = false, features = ["sha1", "sha2", "hazmat"], optional = true }
sha1 = { version = "0.11", features = ["oid", "zeroize"], optional = true }
sha2 = { version = "0.11", features = ["oid", "zeroize"], optional = true }
sha3 = { version = "0.12", default-features = false, features = ["oid"], optional = true }
# Dependencies of the embedded RustCrypto SHA-3 bit-tail adaptation.
keccak = { version = "0.2", default-features = false, optional = true }
sponge-cursor = { version = "0.1", default-features = false, features = ["zeroize"], optional = true }
camellia = { version = "0.2.1", default-features = false, features = ["zeroize"], optional = true }
chacha20 = { version = "0.10.2", default-features = false, features = ["zeroize"], optional = true }
chacha20poly1305 = { version = "0.11.0", default-features = false, features = ["alloc"], optional = true }
ed448-goldilocks = { version = "=0.14.0-pre.15", default-features = false, features = ["alloc", "signing", "pkcs8"], optional = true }
# Dependencies of the embedded, patched RustCrypto ML-DSA implementation.
common = { package = "crypto-common", version = "0.2", default-features = false, features = ["rand_core"], optional = true }
Expand All @@ -125,6 +171,7 @@ curve25519-dalek = { version = "5", default-features = false, features = ["alloc
hmac = { version = "0.13", features = ["zeroize"], optional = true }
hkdf = { version = "0.13", default-features = false, optional = true }
x25519-dalek = { version = "3", default-features = false, features = ["static_secrets", "zeroize"], optional = true }
x448 = { version = "=0.14.0-pre.12", default-features = false, features = ["static_secrets"], optional = true }
signature = { version = "3", optional = true }
subtle = { version = "2", optional = true }
getrandom = { version = "0.4", features = ["sys_rng"], optional = true }
Expand All @@ -134,8 +181,8 @@ aes-gcm = { version = "0.11.1", optional = true }
aes-kw = { version = "0.3.1", optional = true }
cbc = { version = "0.2.1", optional = true }
des = { version = "0.9", optional = true }
md-5 = { version = "0.11", features = ["oid"], optional = true }
ripemd = { version = "0.2.0", default-features = false, features = ["oid"], optional = true }
md-5 = { version = "0.11", features = ["oid", "zeroize"], optional = true }
ripemd = { version = "0.2.0", default-features = false, features = ["oid", "zeroize"], optional = true }
pem = { version = "4", optional = true }
pkcs12 = { version = "=0.2.0-pre.0", default-features = false, features = ["kdf"], optional = true }
pbkdf2 = { version = "0.13", default-features = false, features = ["hmac"], optional = true }
Expand All @@ -156,6 +203,7 @@ base64 = "0.23"
thiserror = "2"

[dev-dependencies]
sha3 = { version = "0.12", default-features = false, features = ["oid", "zeroize"] }
divan = "=0.1.21"
ml-dsa = { version = "0.1.1", default-features = false, features = ["alloc", "pkcs8", "rand_core", "zeroize"] }
hex-literal = "1"
Expand Down Expand Up @@ -210,7 +258,8 @@ xmldsig = [ # XML Digital Signatures (sign + verify)
"dep:rsa",
"dep:sha1",
"dep:sha2",
"dep:sha3",
"dep:keccak",
"dep:sponge-cursor",
"dep:signature",
"dep:snafu",
"dep:subtle",
Expand All @@ -228,12 +277,16 @@ xmlenc = [ # XML Encryption (encrypt + decrypt)
"dep:aes-gcm",
"dep:aes-kw",
"dep:cbc",
"dep:camellia",
"dep:chacha20",
"dep:chacha20poly1305",
"dep:getrandom",
"dep:rsa",
"dep:sha1",
"dep:sha2",
"dep:hkdf",
"dep:x25519-dalek",
"dep:x448",
"dep:crypto-primes",
"p256/ecdh",
"p384/ecdh",
Expand Down
10 changes: 10 additions & 0 deletions LICENSE-THIRD-PARTY
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ The complete original license notices are preserved and distributed as
src/rustcrypto_ml_dsa/LICENSE-APACHE and src/rustcrypto_ml_dsa/LICENSE-MIT.
Local modifications are documented in docs/ml-dsa-patch.md.

RustCrypto sha3 0.12.0 (src/rustcrypto_sha3/)
--------------------------------------------

Source: https://crates.io/crates/sha3/0.12.0
License: Apache-2.0 OR MIT

The complete original license notices are preserved and distributed as
src/rustcrypto_sha3/LICENSE-APACHE and src/rustcrypto_sha3/LICENSE-MIT.
Local modifications are documented in docs/sha3-patch.md.

sxd-document (src/sxd_document/)
--------------------------------

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ xml-sec = { version = "0.1", default-features = false, features = ["xmldsig", "c
|------|-----------------|
| Canonicalization | Canonical XML 1.0/1.1, Exclusive C14N, comments, and document subsets |
| XML signatures | XMLDSig signing and verification, RSA-PSS/PKCS#1, DSA/ECDSA/HMAC, XPath and [OPC Relationship transforms](docs/relationship-transforms.md), `Manifest`, `KeyInfo`, and caller-provided references |
| XML encryption | AES-CBC/GCM, RSA-OAEP, AES Key Wrap, ECDH/X25519/DH, bounded KDFs, referenced ciphertext, nested recipients, and Element/Content replacement |
| XML encryption | AES-CBC/GCM, RSA-OAEP, AES Key Wrap, opt-in Camellia-CBC/Key Wrap and experimental ChaCha20/Poly1305 profiles, ECDH/X25519/X448/DH, bounded KDFs, referenced ciphertext, nested recipients, and Element/Content replacement |
| Optional compatibility algorithms | `legacy-algorithms`: MD5/RIPEMD-160 signatures and digests, AES-192, TripleDES-CBC/CMS wrap, and RSA-1.5 transport; library policy denies these by default |
| X.509 | Certificate key extraction, chain validation, CRLs, required key authorization by default, and typed trust evidence |
| Key management | Caller-owned named inventory, usage-restricted keys, xmlsec `keys.xml`, encrypted PKCS#8, and bounded RustCrypto-backed PKCS#12 import |
Expand Down Expand Up @@ -124,7 +124,7 @@ keys must explicitly select a lower operation-policy minimum; importing a key do

Decryption supports caller-owned `CipherReference` resources, transformed key retrieval,
nested/detached `EncryptedKey`, and trusted application bindings for ConcatKDF, HKDF,
PBKDF2 and ECDH/X25519/DH. No document can trigger implicit filesystem or network access.
PBKDF2 and ECDH/X25519/X448/DH. No document can trigger implicit filesystem or network access.
See [XML Encryption](docs/xmlenc.md) and [crypto providers](docs/crypto-providers.md)
for key establishment, cumulative budgets, interoperability and provider capabilities.

Expand Down
126 changes: 119 additions & 7 deletions docs/crypto-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,25 @@ agreement and derivation. Raw provider primitives do not grant XML permission.
All imported donor key/signature/encryption vectors are exercised offline;
setting `OPENSSL_BIN` additionally enables reciprocal OpenSSL testing.

With `xmlenc`, RustCrypto exposes X25519 and P-256/P-384/P-521 ECDH through
`CryptoProvider::agree_key`, using opaque `RustCryptoX25519Key` and
`RustCryptoEcdhKey` handles. ECDH validates peer points and preserves fixed-width
shared secrets; X25519 rejects noncontributory peers. These handles do not expose
With `xmlenc`, RustCrypto exposes X25519, X448 and P-256/P-384/P-521 ECDH through
`CryptoProvider::agree_key`, using opaque `RustCryptoX25519Key`,
`RustCryptoX448Key` and `RustCryptoEcdhKey` handles. ECDH validates peer points and
preserves fixed-width shared secrets; X25519 and X448 reject noncontributory
peers, including noncanonical low-order aliases. `RustCryptoX448Key::from_pkcs8_der`
imports RFC 8410 keys with absent algorithm parameters and binds any included
public key to the scalar. These handles do not expose
private-key bytes. Returned shared-secret bytes belong to the caller, which must
retain them in zeroizing storage until derivation finishes.

The pinned X448 corpus includes three decryptable HKDF vectors and two
ConcatKDF vectors whose AES-128-GCM declaration conflicts with a wrapped
32-byte content key. Tests verify the latter's X448 derivation and AES-KW
integrity, then require the public operation to reject the incompatible key
width. This is an intentional exact-key contract rather than libxmlsec1's
implicit truncation of surplus key material, not missing ConcatKDF support.
The nine P-256 ConcatKDF vectors (SHA-1, SHA-2 and SHA-3) have the same
surplus-key conflict; their independent wrapping-key recovery is also checked.

`RustCryptoDhKey` adds finite-field DH with explicitly supplied p/q/g/private
components. Import borrows the shared `KeyEstablishmentBudget` and selected
provider's randomness. Prime checks use bounded independent Miller-Rabin bases;
Expand All @@ -149,13 +161,38 @@ output bound is checked before output allocation. Primitive capability is not
XML operation support or policy permission: callers must enforce their operation
policy and cumulative resource budgets before dispatch.

RustCrypto also provides Camellia-CBC and Camellia key wrap at 128/192/256
bits, and ChaCha20/ChaCha20-Poly1305 with their transported nonce, counter
and associated-data parameters. These algorithms require explicit typed
operation-policy permission; they are not additions to the secure defaults.
Raw ChaCha20 is unauthenticated and cannot select between distinct candidate
keys. Direct KEM composition requires explicit external-authentication
permission. ChaCha20-Poly1305 provides authenticated decryption. The reader
accepts both the unqualified parameters in RFC 9231 §§2.6.7-2.6.8 and the
namespaced draft form; the writer emits the latter for compatibility with
libxmlsec1 1.3.13, which does not accept the RFC example form.
CLI encryption preserves a template's nonce in either accepted namespace form
and adds a generated nonce only when the template does not contain one.
AWS-LC FIPS and PKCS#11 do not advertise these mechanisms and never fall back
to RustCrypto. The selected Camellia 0.2.1 implementation uses secret-indexed
S-box lookups; it is not a constant-time or FIPS-approved implementation.
Use it only where the application's threat model permits that implementation
and unauthenticated CBC, rather than assuming interoperability proves security.

The same provider also implements PBKDF2 with explicit HMAC-SHA-1/224/256/384/512
PRFs and ConcatKDF with SHA-1/224/256/384/512. PBKDF2 accepts positive iteration
PRFs and ConcatKDF with SHA-1/224/256/384/512 and SHA3-224/256/384/512.
With `legacy-algorithms`, ConcatKDF additionally supports MD5 and RIPEMD-160;
capability does not grant operation-policy permission. PBKDF2 accepts positive iteration
counts without truncating the API's 64-bit value. ConcatKDF preserves arbitrary
MSB-first context bit strings through `KdfContext::Bits`; malformed padding is
rejected rather than silently hashing storage octets. SHA framing uses RustCrypto
compression, with no per-block concatenation allocation. These primitive APIs
do not themselves resolve `AgreementMethod`, `DerivedKey`, or external resources.
SHA-3 partial octets use the [local RustCrypto adaptation](sha3-patch.md),
validated against every bit-oriented NIST ShortMsg vector at all four widths.
Its padding permutations are charged before provider execution, including
rate-boundary crossings. Byte-oriented behavior is checked against unmodified
RustCrypto; no second production hash implementation is used.

The legacy DH KDF uses `KdfContext::LegacyDh` to keep the consuming algorithm URI
and decoded nonce distinct. Decimal KeySize is derived from the requested output
Expand All @@ -174,7 +211,7 @@ duplicating field buffers. Parsing parameters is not permission to execute them.
`KeyDerivationMethod::derive_key` uses a caller's shared `KeyEstablishmentBudget`
to check the compiled `KeyEstablishmentPolicy` before provider dispatch. The
same policy type is composed into encryption and decryption policy snapshots.
Its default permits SHA-2 KDFs and ECDH/X25519 agreement, not legacy DH or SHA-1;
Its default permits SHA-2 KDFs and ECDH/X25519 agreement, not X448, legacy DH or SHA-1;
those require explicit grants independent of primitive capability. Budgets count
conservative SHA compression blocks (including HMAC framing), not just PBKDF2
iterations, and reserve cumulative shared-secret and derived-key output bytes
Expand Down Expand Up @@ -242,13 +279,88 @@ Applications requiring fresh output with the same agreement keys must vary
those explicit KDF inputs as well, not merely the transported nonce.

Reciprocal libxmlsec1 1.3.13 tests cover all five SHA-1/SHA-2 families for PBKDF2,
HKDFParams and byte-aligned ConcatKDF fields. Non-octet-aligned ConcatKDF follows
HKDFParams and byte-aligned ConcatKDF fields. The four SHA-3 ConcatKDF families
also run reciprocally with byte-aligned fields. Non-octet-aligned ConcatKDF follows
[XMLEnc 1.1 section 5.4.1](https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ConcatKDF)
and has separate exact-bit tests: libxmlsec1's `xmlSecTransformConcatKdfParamsReadsBitsAttr`
rejects such fields, so that donor limitation is not imposed on xml-sec.
Reciprocal ECDH tests additionally execute XML agreement with P-256, P-384 and
P-521 keys and ConcatKDF against libxmlsec1, in both directions.

Pinned donor ciphertext also exercises complete P-384/P-521 ConcatKDF and
P-256 HKDF decryption. All four Phaos legacy DH documents are decrypted and
compared with the original payment document. The Phaos DER private-key file
is truncated in the donor itself; tests use the complete published p/q/g/x
from its unchanged `key.txt` instead. These tests grant legacy DH, SHA-1 and
legacy content algorithms explicitly, without broadening product defaults.

`bash scripts/import-donor-fixtures.sh --xmlenc-corpora` imports the complete
Merlin, Phaos and Aleksey directories and their shared agreement credentials.
`tests/fixtures/xmlenc/corpora.sha256` pins every imported file's bytes; tests
reject changed, missing and additional files. The all-feature
`complete_corpora_execute_every_vector_exactly_once` test records results only
after executing and asserting each case, then compares its exact path set with
the imported XML inventory. Auxiliary key/plaintext XML is excluded explicitly.
Malformed inputs and the verified departures below remain separate from
successful decryptions; unsupported algorithms are never counted as parity.

Merlin direct keys, RSA transport, symmetric wrapping, same-document
CipherReference and retrieved/carried EncryptedKey vectors compare complete
plaintext or replacement documents. The pinned donor's `dh0.p8`, `dh1.p8`
and `ids.p12` are truncated. Complete credentials are preserved separately in
`merlin-original-keys`, imported by `bash scripts/import-merlin-original-keys.sh`
from the [author's W3C archive](https://lists.w3.org/Archives/Public/xml-encryption/2002Mar/0008.html).
Archive SHA-256: `219d984ed14a83543d8eebc13d69178988890c2d35bc9d9115562e4d6f8055bb`.
These original credentials enable positive Merlin DH content and wrapping
checks without repairing or replacing the donor snapshot.

All ten original Merlin EncryptedData templates are executed through the native
CLI and independently through libxmlsec1, covering binary, Element and Content
data, direct keys, AES/TripleDES wrapping and RSA transport (RSA-1.5 and
RSA-OAEP, including SHA-256). The RSA template executions use independently
selected 2048-bit test credentials without weakening the CLI's key minimum.
Each ciphertext is
decrypted by the other implementation and compared with the complete expected
plaintext or replacement document.

The two Merlin `decryption-transform` documents exercise public XMLEnc payload
decryption and owned-document replacement, preserving the original signature
and the unselected ciphertext in the Except example. This is not signature
transform support: those files use the historical `2001/04/decrypt#` URI,
whereas the [Decryption Transform Recommendation, sections 2–3](https://www.w3.org/TR/xmlenc-decrypt/)
specifies `2002/07/decrypt#XML`. The pinned libxmlsec1 test script excludes both
signature-transform examples; their XMLDSig transformation is not an oracle
parity pass.

The original DH-to-TripleDES-wrapped-HMAC signature also verifies end-to-end.
For the direct DH-HMAC document, explicitly binding the HMAC-SHA256 URI and
256-bit key length to the legacy KDF yields a nonmatching archive signature.
Independent OpenSSL DH/HMAC and libxml2 canonicalization checks confirmed this
result. Tests preserve the original rejection and verify a positive in-memory
counterpart changing only SignatureValue to the independently computed tag.
This does not claim successful verification of the unmodified direct document:
[XMLEnc 1.1 section 5.6.2.2](https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-DHKeyAgreementLegacyKDF)
defines EncryptionMethod binding, not an implicit mapping to SignatureMethod.

Direct PBKDF2 and HKDF donor vectors cover all five SHA-1/SHA-2 PRFs, including
wrong-secret authentication failures. The PRF-only HKDF descriptor also
exercises omitted optional parameters. A pinned RFC 5114 DH-ES credential pair
decrypts the ConcatKDF/AES-128-KW/AES-128-GCM donor document end-to-end.
The ECDH-PBKDF2 recipes request a
32-byte session key while selecting AES-128-GCM. The public API intentionally
refuses that surplus key material rather than silently truncating it; separate
primitive comparisons verify the original KDF, unwrap and full plaintext.
This exact-key requirement is a product invariant, not a claim that XMLEnc
mandates our particular error reporting.

Aleksey RSA transport checks include every pinned OAEP/MGF combination, both
independent keys in the two-EncryptedKey document, and the certificate-selector
metadata vectors with explicitly caller-selected private keys, matching the
donor's decrypt invocation. This does not claim certificate trust validation
or certificate-based private-key lookup. ISO-8859-1 Element and Content vectors
run through the public byte-input and owned-document replacement APIs rather
than being loaded as UTF-8 strings.

The key-establishment acceptance tests in `xmlenc_encrypt_xmlsec1` require the
independent oracle, including local runs: an unavailable oracle is a setup
failure, not a successful skipped check. Install it with
Expand Down
Loading
Loading