//! Provider-neutral cryptographic operations. //! //! XML parsing and protocol orchestration depend on this contract rather than //! concrete cryptographic crates. Secret-bearing keys remain opaque behind //! operation-specific handles; this provider owns primitive dispatch and //! randomness. #[cfg(feature = "rustcrypto")] use crate::rustcrypto_sha3 as sha3; #[cfg(feature = "aws-lc-fips")] mod aws_lc; #[cfg(feature = "xmlenc")] mod chacha; #[cfg(feature = "xmlenc")] mod key_establishment; #[cfg(feature = "pkcs11")] pub mod pkcs11; #[cfg(feature = "xmlenc")] pub use key_establishment::{ EcdhCurve, RustCryptoDhKey, RustCryptoEcdhKey, RustCryptoX448Key, RustCryptoX25519Key, }; #[cfg(feature = "xmldsig")] mod key_association; #[cfg(feature = "xmldsig")] mod key_encapsulation; #[cfg(feature = "xmldsig")] pub use key_association::{KeyAssociation, KeyVerificationEvent, SignatureVerificationInput}; #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] mod rsa_pkcs1v15; #[cfg(feature = "rustcrypto")] pub(crate) mod rsa_pss; #[cfg(feature = "xmldsig")] pub use key_encapsulation::{ EncapsulatedKey, KeyDecapsulationKey, KeyEncapsulationAlgorithm, KeyEncapsulationKey, }; #[cfg(feature = "experimental-pq")] pub use key_encapsulation::{ MlKemPrivateKeyEncoding, RustCryptoMlKemPrivateKey, RustCryptoMlKemPublicKey, }; /// Secret candidate whose recovery validity is retained until content work ends. /// Debug output never exposes key bytes or padding validity. #[cfg(feature = "xmlenc")] pub struct RecoveredContentKey { bytes: zeroize::Zeroizing>, valid: subtle::Choice, implicit_rejection: bool, opaque: Option>, } #[cfg(feature = "xmlenc")] impl core::fmt::Debug for RecoveredContentKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("RecoveredContentKey") .finish_non_exhaustive() } } #[cfg(feature = "xmlenc")] impl RecoveredContentKey { /// An explicitly supplied key, or one recovered by an integrity-checking /// mechanism such as OAEP or key wrap. A KEM real-or-rejection secret also /// has no separate recovery-validity result; authenticated content decides /// acceptance, or policy explicitly delegates authentication to the caller. /// This constructor itself proves neither ciphertext integrity nor identity. /// Do not use this for RSA-v1.5 recovery, whose validity must be /// retained separately with `recovery` until content work completes. #[must_use] pub fn confirmed(bytes: Vec) -> Self { Self { bytes: zeroize::Zeroizing::new(bytes), valid: subtle::Choice::from(1), implicit_rejection: false, opaque: None, } } #[cfg(feature = "legacy-algorithms")] /// Preserve a trusted provider's constant-time padding-validation result. /// `bytes` must already contain the fixed-width real-or-fallback candidate; /// the caller must not branch on `valid` during recovery. The enclosing /// content operation consumes this state only after primitive decryption. #[must_use] pub fn recovery(bytes: zeroize::Zeroizing>, valid: subtle::Choice) -> Self { Self { bytes, valid, implicit_rejection: true, opaque: None, } } /// Retain a non-exportable content key until the content operation ends. pub fn opaque(key: Arc) -> Self { Self { bytes: zeroize::Zeroizing::new(Vec::new()), valid: subtle::Choice::from(1), implicit_rejection: false, opaque: Some(key), } } /// Public content-key width without exposing the key value. #[must_use] pub fn key_len(&self) -> usize { self.opaque .as_ref() .map_or(self.bytes.len(), |key| key.key_len()) } pub(crate) fn same_candidate(&self, other: &Self) -> bool { use subtle::ConstantTimeEq; match (&self.opaque, &other.opaque) { (Some(a), Some(b)) => Arc::ptr_eq(a, b), // Width is public algorithm metadata; secret bytes and recovery // validity must not select an early-exit comparison path. (None, None) => bool::from( self.bytes.as_slice().ct_eq(other.bytes.as_slice()) & self.valid.unwrap_u8().ct_eq(&other.valid.unwrap_u8()), ), _ => false, } } pub(crate) fn decrypt( &self, provider: &dyn CryptoProvider, algorithm: DataEncryptionAlgorithm, ciphertext: &[u8], parameters: Option>, ) -> Result, ProviderError> { match &self.opaque { Some(_) if parameters.is_some() => Err(ProviderError::Unsupported { operation: ProviderOperation::Decrypt, algorithm: Some(algorithm.uri().into()), }), Some(key) => provider.decrypt_content_key(algorithm, key.as_ref(), ciphertext), None => provider.decrypt_data_with_parameters( algorithm, &self.bytes, ciphertext, parameters, ), } } #[cfg(test)] pub(crate) fn bytes(&self) -> &[u8] { &self.bytes } pub(crate) fn valid(&self) -> bool { bool::from(self.valid) } /// A nested transport feeds a wrapping operation, not a content cipher. /// Preserve recovery validity across every level until content work ends. pub(crate) fn unwrap_nested( &self, provider: &dyn CryptoProvider, algorithm: KeyWrapAlgorithm, ciphertext: &[u8], output_len: usize, ) -> Result { use subtle::ConditionallySelectable; if self.opaque.is_some() { return Err(ProviderError::KeyNotExportable); } if self.implicit_rejection { // XMLEnc 1.1 §6.1.2: process content even after RSA-v1.5 rejection. // The same protection must survive intermediate wrapping layers. // Branch on the public recovery mechanism, never padding validity. // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-bleichenbacher-attack provider.require_capability(ProviderCapability::Random)?; let mut fallback = zeroize::Zeroizing::new(vec![0; output_len]); provider.fill_random(&mut fallback)?; let mut candidate = match provider.unwrap_key(algorithm, &self.bytes, ciphertext) { Ok(bytes) => zeroize::Zeroizing::new(bytes), Err(ProviderError::AuthenticationFailed) => { return Ok(Self { bytes: fallback, valid: subtle::Choice::from(0), implicit_rejection: true, opaque: None, }); } Err(error) => return Err(error), }; if candidate.len() != output_len { return Err(ProviderError::InvalidOutputSize { operation: ProviderOperation::KeyUnwrap, expected: output_len, actual: candidate.len(), }); } for (byte, random) in candidate.iter_mut().zip(fallback.iter()) { *byte = u8::conditional_select(random, byte, self.valid); } return Ok(Self { bytes: candidate, valid: self.valid, implicit_rejection: true, opaque: None, }); } let bytes = provider.unwrap_key(algorithm, &self.bytes, ciphertext)?; Ok(Self { bytes: zeroize::Zeroizing::new(bytes), valid: self.valid, implicit_rejection: false, opaque: None, }) } /// Consume a completed recovery outside a content operation. Content /// operations must retain the candidate until after primitive decryption. /// The returned allocation is caller-owned; the caller is responsible for /// zeroizing it when no longer needed (for example with `Zeroizing`). /// /// # Errors /// Returns `KeyNotExportable` for an opaque handle, or `AuthenticationFailed` /// when the retained recovery validity rejects the candidate. pub fn into_key(mut self) -> Result, ProviderError> { if self.opaque.is_some() { return Err(ProviderError::KeyNotExportable); } if !self.valid() { return Err(ProviderError::AuthenticationFailed); } Ok(core::mem::take(&mut *self.bytes)) } } #[cfg(all(feature = "aws-lc-fips", feature = "xmlenc"))] pub use aws_lc::AwsLcRsaPrivateKey; #[cfg(feature = "aws-lc-fips")] pub use aws_lc::{AwsLcFipsProvider, AwsLcSigningKey}; #[cfg(feature = "xmlenc")] use alloc::borrow::Cow; use alloc::{borrow::ToOwned, boxed::Box, string::String, sync::Arc, vec::Vec}; // Randomness serves signing and encryption only; verification never draws it. #[cfg(feature = "xmldsig-sign")] use getrandom::{ SysRng, rand_core::{TryCryptoRng, TryRng}, }; #[cfg(feature = "xmldsig")] use crate::xmldsig::DigestAlgorithm; #[cfg(feature = "xmlenc")] use crate::xmlenc::{DataEncryptionAlgorithm, KeyWrapAlgorithm, RsaOaepParameters}; /// A cryptographic operation advertised by a provider. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ProviderOperation { /// Message digest computation. Digest, /// Public-key signature generation. Sign, /// Public-key signature verification. Verify, /// X.509 certificate or CRL signature verification. VerifyCertificate, /// Authenticated or padded symmetric encryption. Encrypt, /// Authenticated or padded symmetric decryption. Decrypt, /// Symmetric key wrapping. KeyWrap, /// Symmetric key unwrapping. KeyUnwrap, /// Public-key key transport. KeyTransport, /// Private-key recovery of transported key bytes. KeyRecovery, /// Key agreement. KeyAgreement, /// Establish a shared secret with a recipient public key. Encapsulate, /// Recover the shared secret using a recipient private key. Decapsulate, /// Key derivation. Kdf, /// Cryptographically secure random bytes. Random, } /// One exact provider capability, including operation-specific parameters. /// /// Capability discovery describes mechanisms, not policy permission. Callers /// must still apply the immutable operation policy before provider dispatch. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum ProviderCapability<'a> { /// Exact KEM parameter set, independent of algorithm permission. #[cfg(feature = "xmldsig")] Encapsulate(KeyEncapsulationAlgorithm), /// Exact KEM parameter set for private-key decapsulation. #[cfg(feature = "xmldsig")] Decapsulate(KeyEncapsulationAlgorithm), /// Message digest computation for an `XMLDSig` digest method. #[cfg(feature = "xmldsig")] Digest(DigestAlgorithm), /// Provider dispatch for an `XMLDSig` signing method. /// /// The opaque signing key remains responsible for accepting the method and /// implementing its primitive. #[cfg(feature = "xmldsig")] Sign(crate::xmldsig::SignatureAlgorithm), /// Provider dispatch for an `XMLDSig` verification method. /// /// The opaque verification key remains responsible for accepting the /// method and implementing its primitive. #[cfg(feature = "xmldsig")] Verify(crate::xmldsig::SignatureAlgorithm), /// X.509 signature verification with complete algorithm parameters. #[cfg(feature = "xmldsig")] VerifyCertificate(X509SignatureAlgorithm), /// `XMLEnc` content encryption. #[cfg(feature = "xmlenc")] Encrypt(DataEncryptionAlgorithm), /// `XMLEnc` content decryption. #[cfg(feature = "xmlenc")] Decrypt(DataEncryptionAlgorithm), /// RFC 3394 key wrapping. #[cfg(feature = "xmlenc")] KeyWrap(KeyWrapAlgorithm), /// RFC 3394 key unwrapping. #[cfg(feature = "xmlenc")] KeyUnwrap(KeyWrapAlgorithm), /// RSA-OAEP key transport with complete digest, MGF, and label parameters. #[cfg(feature = "xmlenc")] KeyTransport(&'a RsaOaepParameters), /// RSA-OAEP key recovery with complete digest, MGF, and label parameters. #[cfg(feature = "xmlenc")] KeyRecovery(&'a RsaOaepParameters), /// Historical RSAES-PKCS1-v1_5 transport, without OAEP parameters. #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] Pkcs1v15Transport, /// Historical RSAES-PKCS1-v1_5 fixed-length content-key recovery. #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] Pkcs1v15Recovery, /// Provider-defined key agreement identified by its standard URI. KeyAgreement(&'a KeyAgreementParameters<'a>), /// Provider-defined key derivation identified by its standard URI. Kdf(&'a KdfParameters<'a>), /// Cryptographically secure random byte generation. Random, } impl ProviderCapability<'_> { /// Operation category used in diagnostics. #[must_use] pub const fn operation(&self) -> ProviderOperation { match self { #[cfg(feature = "xmldsig")] Self::Encapsulate(_) => ProviderOperation::Encapsulate, #[cfg(feature = "xmldsig")] Self::Decapsulate(_) => ProviderOperation::Decapsulate, #[cfg(feature = "xmldsig")] Self::Digest(_) => ProviderOperation::Digest, #[cfg(feature = "xmldsig")] Self::Sign(_) => ProviderOperation::Sign, #[cfg(feature = "xmldsig")] Self::Verify(_) => ProviderOperation::Verify, #[cfg(feature = "xmldsig")] Self::VerifyCertificate(_) => ProviderOperation::VerifyCertificate, #[cfg(feature = "xmlenc")] Self::Encrypt(_) => ProviderOperation::Encrypt, #[cfg(feature = "xmlenc")] Self::Decrypt(_) => ProviderOperation::Decrypt, #[cfg(feature = "xmlenc")] Self::KeyWrap(_) => ProviderOperation::KeyWrap, #[cfg(feature = "xmlenc")] Self::KeyUnwrap(_) => ProviderOperation::KeyUnwrap, #[cfg(feature = "xmlenc")] Self::KeyTransport(_) => ProviderOperation::KeyTransport, #[cfg(feature = "xmlenc")] Self::KeyRecovery(_) => ProviderOperation::KeyRecovery, #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] Self::Pkcs1v15Transport => ProviderOperation::KeyTransport, #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] Self::Pkcs1v15Recovery => ProviderOperation::KeyRecovery, Self::KeyAgreement(_) => ProviderOperation::KeyAgreement, Self::Kdf(_) => ProviderOperation::Kdf, Self::Random => ProviderOperation::Random, } } /// Standard algorithm identifier used in unsupported-operation errors. #[must_use] pub fn algorithm(&self) -> Option<&str> { match self { #[cfg(feature = "xmldsig")] Self::Encapsulate(algorithm) | Self::Decapsulate(algorithm) => Some(algorithm.uri()), #[cfg(feature = "xmldsig")] Self::Digest(algorithm) => Some(algorithm.uri()), #[cfg(feature = "xmldsig")] Self::Sign(algorithm) | Self::Verify(algorithm) => Some(algorithm.uri()), #[cfg(feature = "xmldsig")] Self::VerifyCertificate(algorithm) => algorithm.oid(), #[cfg(feature = "xmlenc")] Self::Encrypt(algorithm) | Self::Decrypt(algorithm) => Some(algorithm.uri()), #[cfg(feature = "xmlenc")] Self::KeyWrap(algorithm) | Self::KeyUnwrap(algorithm) => Some(algorithm.uri()), #[cfg(feature = "xmlenc")] Self::KeyTransport(parameters) | Self::KeyRecovery(parameters) => { Some(parameters.algorithm.uri()) } Self::KeyAgreement(parameters) => Some(parameters.algorithm), #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] Self::Pkcs1v15Transport | Self::Pkcs1v15Recovery => { Some(crate::xmlenc::KeyTransportAlgorithm::RsaPkcs1v15.uri()) } Self::Kdf(parameters) => Some(parameters.algorithm), Self::Random => None, } } } /// Provider-neutral parameters for an asymmetric key-agreement operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct KeyAgreementParameters<'a> { /// Standard key-agreement algorithm URI. pub algorithm: &'a str, /// Encoded peer public key in the algorithm's standard wire format. pub peer_public_key: &'a [u8], } /// Provider-neutral context octets or significant bits for key derivation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KdfContext<'a> { /// `XMLEnc` 1.1 section 5.6.2.2 legacy DH framing. `KeySize` is derived from /// `output_len`; callers cannot supply a contradictory decimal key width. LegacyDh { /// URI of the consuming `EncryptionMethod`, not `AgreementMethod`. encryption_algorithm: &'a str, /// Decoded KA-Nonce octets, empty when the XML element is absent. nonce: &'a [u8], }, /// Complete octets, as used by HKDF info and byte-aligned `ConcatKDF`. Octets(&'a [u8]), /// An MSB-first bit string. Its last octet must have zero unused low bits; /// the byte slice must have exactly `bit_len.div_ceil(8)` octets. Bits { /// Packed bits, without the `XMLEnc` attribute's padding-count prefix. bytes: &'a [u8], /// Number of significant bits, not rounded to the next octet. bit_len: usize, }, } /// Provider-neutral parameters for a key-derivation operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct KdfParameters<'a> { /// Standard KDF algorithm URI. pub algorithm: &'a str, /// Optional digest or PRF URI selected by the KDF parameters. pub digest: Option<&'a str>, /// Caller-provided salt, when the KDF defines one. pub salt: &'a [u8], /// Algorithm-specific context such as `ConcatKDF` `OtherInfo` bits or HKDF info octets. pub info: KdfContext<'a>, /// Policy-validated iteration count for iterative KDFs; zero when not applicable. pub iterations: u64, /// Policy-validated requested output length in bytes. pub output_len: usize, } /// Provider-neutral X.509 certificate and CRL signature parameters. #[cfg(feature = "xmldsig")] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum X509SignatureAlgorithm { /// DSA with the selected message digest. Dsa(DigestAlgorithm), /// RSASSA-PKCS1-v1_5 with the selected message digest. RsaPkcs1v15(DigestAlgorithm), /// RSASSA-PSS with explicit RFC 4055 parameters. RsaPss { /// Message digest applied to the signed certificate data. digest: DigestAlgorithm, /// Digest used by MGF1. mgf_digest: DigestAlgorithm, /// Salt length in octets. salt_len: usize, }, /// ECDSA with the selected message digest; SPKI selects the curve. Ecdsa(DigestAlgorithm), /// Pure Ed25519 as specified by RFC 8410. Ed25519, /// Pure Ed448 as specified by RFC 8410. Ed448, /// Pure ML-DSA or SLH-DSA with the PKIX-mandated empty context. PostQuantum(crate::xmldsig::PqAlgorithm), /// DSTU 4145 with the selected digest; SPKI selects the curve. #[cfg(feature = "dstu4145")] Dstu4145 { /// Message digest applied to the signed certificate data. digest: DigestAlgorithm, /// Whether the signature octets follow the big-endian profile. big_endian: bool, }, } #[cfg(feature = "xmldsig")] impl X509SignatureAlgorithm { /// Return the assigned `AlgorithmIdentifier` OID, or `None` for unsupported combinations. #[must_use] pub const fn oid(self) -> Option<&'static str> { Some(match self { #[cfg(feature = "legacy-algorithms")] Self::Dsa(DigestAlgorithm::Md5 | DigestAlgorithm::Ripemd160) | Self::RsaPkcs1v15(DigestAlgorithm::Md5 | DigestAlgorithm::Ripemd160) | Self::Ecdsa(DigestAlgorithm::Md5 | DigestAlgorithm::Ripemd160) => return None, #[cfg(feature = "dstu4145")] Self::Dsa(DigestAlgorithm::Gost34311 | DigestAlgorithm::Dstu7564_256) | Self::RsaPkcs1v15(DigestAlgorithm::Gost34311 | DigestAlgorithm::Dstu7564_256) | Self::Ecdsa(DigestAlgorithm::Gost34311 | DigestAlgorithm::Dstu7564_256) => { return None; } Self::Dsa(DigestAlgorithm::Sha1) => "1.2.840.10040.4.3", Self::Dsa(DigestAlgorithm::Sha224) => "2.16.840.1.101.3.4.3.1", Self::Dsa(DigestAlgorithm::Sha256) => "2.16.840.1.101.3.4.3.2", Self::Dsa(DigestAlgorithm::Sha384) => "2.16.840.1.101.3.4.3.3", Self::Dsa(DigestAlgorithm::Sha512) => "2.16.840.1.101.3.4.3.4", Self::Dsa(DigestAlgorithm::Sha3_224) => "2.16.840.1.101.3.4.3.5", Self::Dsa(DigestAlgorithm::Sha3_256) => "2.16.840.1.101.3.4.3.6", Self::Dsa(DigestAlgorithm::Sha3_384) => "2.16.840.1.101.3.4.3.7", Self::Dsa(DigestAlgorithm::Sha3_512) => "2.16.840.1.101.3.4.3.8", Self::RsaPkcs1v15(DigestAlgorithm::Sha1) => "1.2.840.113549.1.1.5", Self::RsaPkcs1v15(DigestAlgorithm::Sha224) => "1.2.840.113549.1.1.14", Self::RsaPkcs1v15(DigestAlgorithm::Sha256) => "1.2.840.113549.1.1.11", Self::RsaPkcs1v15(DigestAlgorithm::Sha384) => "1.2.840.113549.1.1.12", Self::RsaPkcs1v15(DigestAlgorithm::Sha512) => "1.2.840.113549.1.1.13", Self::RsaPkcs1v15(DigestAlgorithm::Sha3_224) => "2.16.840.1.101.3.4.3.13", Self::RsaPkcs1v15(DigestAlgorithm::Sha3_256) => "2.16.840.1.101.3.4.3.14", Self::RsaPkcs1v15(DigestAlgorithm::Sha3_384) => "2.16.840.1.101.3.4.3.15", Self::RsaPkcs1v15(DigestAlgorithm::Sha3_512) => "2.16.840.1.101.3.4.3.16", Self::RsaPss { .. } => "1.2.840.113549.1.1.10", Self::Ecdsa(DigestAlgorithm::Sha1) => "1.2.840.10045.4.1", Self::Ecdsa(DigestAlgorithm::Sha224) => "1.2.840.10045.4.3.1", Self::Ecdsa(DigestAlgorithm::Sha256) => "1.2.840.10045.4.3.2", Self::Ecdsa(DigestAlgorithm::Sha384) => "1.2.840.10045.4.3.3", Self::Ecdsa(DigestAlgorithm::Sha512) => "1.2.840.10045.4.3.4", Self::Ecdsa(DigestAlgorithm::Sha3_224) => "2.16.840.1.101.3.4.3.9", Self::Ecdsa(DigestAlgorithm::Sha3_256) => "2.16.840.1.101.3.4.3.10", Self::Ecdsa(DigestAlgorithm::Sha3_384) => "2.16.840.1.101.3.4.3.11", Self::Ecdsa(DigestAlgorithm::Sha3_512) => "2.16.840.1.101.3.4.3.12", Self::Ed25519 => "1.3.101.112", Self::Ed448 => "1.3.101.113", Self::PostQuantum(algorithm) => algorithm.oid(), // The identifier also carries the field basis, which only SPKI fixes. #[cfg(feature = "dstu4145")] Self::Dstu4145 { .. } => return None, }) } } /// Structured invalid-input reasons returned by cryptographic providers. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] #[non_exhaustive] pub enum ProviderInputError { /// Invalid ML-KEM key encoding, parameter set or consistency check. #[error("invalid ML-KEM key material")] MlKemKey, /// Invalid public ciphertext framing, never an implicit-rejection signal. #[error("invalid ML-KEM ciphertext length")] MlKemCiphertext, /// An EC scalar or peer point is not valid for the selected named curve. #[error("invalid ECDH key material")] EcdhKey, /// An operation-owned ephemeral sender key has already been consumed. #[error("ECDH-ES sender key already consumed; supply a fresh key pair")] ConsumedEphemeralKey, /// Finite-field domain, scalar, or peer fails DH validation. #[error("invalid DH key material")] DhKey, /// An HKDF request violates the primitive's parameter or output contract. #[error("invalid HKDF parameters")] HkdfParameters, /// A PBKDF2 request has invalid iterations, length, or unrelated context. #[error("invalid PBKDF2 parameters")] Pbkdf2Parameters, /// A `ConcatKDF` request violates its bit-string or counter contract. #[error("invalid ConcatKDF parameters")] ConcatKdfParameters, /// A legacy DH KDF request has invalid framing or exceeds its byte counter. #[error("invalid legacy DH KDF parameters")] LegacyDhKdfParameters, /// A primitive rejected a key or IV after its public preconditions were checked. #[error("failed to initialize {0}")] PrimitiveInitialization(&'static str), /// AES-CBC input does not contain an IV followed by complete blocks. #[error("invalid AES-CBC framing")] AesCbcFraming, /// AES-CBC block decryption failed. #[error("invalid AES-CBC ciphertext")] AesCbcCiphertext, /// AES-GCM input does not contain a nonce and authentication tag. #[error("invalid AES-GCM framing")] AesGcmFraming, /// AES key-wrap input or output framing is invalid. #[error("invalid AES key-wrap framing")] AesKeyWrapFraming, /// Legacy compatibility variant retained for downstream construction and matching. /// /// Explicit MGF parameters are valid for both RSA-OAEP URIs, so current providers never /// return this reason. #[deprecated(note = "explicit MGF parameters are supported for both RSA-OAEP URIs")] #[error("legacy RSA-OAEP requires MGF1-SHA1")] LegacyRsaOaepMgf, } /// Failure returned by a cryptographic provider. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] #[non_exhaustive] pub enum ProviderError { /// A secret-bearing external handle cannot be exported as bytes. #[error("key is not exportable")] KeyNotExportable, /// Redacted external-provider failure: never includes credentials or selectors. #[error("external cryptographic provider failed: {0}")] External(ExternalProviderError), /// The selected provider does not implement the operation/parameters. #[error("provider does not support {operation:?} with algorithm {algorithm:?}")] Unsupported { /// Requested operation. operation: ProviderOperation, /// Requested algorithm URI or name. algorithm: Option, }, /// A key has the wrong size for the selected algorithm. #[error("invalid key size: expected {expected} bytes, got {actual}")] InvalidKeySize { /// Required key length. expected: usize, /// Supplied key length. actual: usize, }, /// A provider reported success but returned bytes that violate the selected /// operation's fixed-size output contract. #[error( "invalid provider output size for {operation:?}: expected {expected} bytes, got {actual}" )] InvalidOutputSize { /// Operation whose output contract was violated. operation: ProviderOperation, /// Exact output length required by the algorithm. expected: usize, /// Actual provider output length. actual: usize, }, /// A provider reported success but returned bytes outside the selected /// operation's variable-size output contract. #[error( "invalid provider output size for {operation:?}: expected {minimum}..={maximum} bytes, got {actual}" )] InvalidOutputSizeRange { /// Operation whose output contract was violated. operation: ProviderOperation, /// Smallest output length permitted by the algorithm. minimum: usize, /// Largest output length permitted by the algorithm. maximum: usize, /// Actual provider output length. actual: usize, }, /// Input framing, padding, or primitive initialization is invalid. #[error("invalid cryptographic input: {0}")] InvalidInput(ProviderInputError), /// Authenticated decryption or key-wrap integrity validation failed. #[error("cryptographic authentication failed")] AuthenticationFailed, /// Operating-system randomness was unavailable. #[error("operating-system random number generation failed: {0}")] Random(String), /// The operation's account or the allocator refused storage. This is a /// spent ceiling, never a finding about the document. #[error("provider resource refusal: {0}")] Resource(structured_asn1::memory::MemoryError), } /// Operational failure classes for an explicitly selected external provider. #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[non_exhaustive] pub enum ExternalProviderError { /// The module, token or device is unavailable. #[error("module or token unavailable")] Unavailable, /// Authentication failed or the operation requires authentication. #[error("authentication failed")] Credentials, /// The selected object does not exist, is ambiguous, or has expired. #[error("key object unavailable or ambiguous")] Object, /// Token key usage does not authorize this primitive. #[error("key usage denied")] Usage, /// The key and selected provider do not share an execution domain. #[error("key provider binding mismatch")] Binding, /// A provider-side operation or synchronization failed. #[error("token operation failed")] Operation, } /// Caller-owned identity for external-provider handles, independent of engine name. #[derive(Clone, Default)] pub struct ProviderBinding(Arc<()>); impl ProviderBinding { /// Test identity without copying secret or public key material. #[must_use] pub fn matches(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } /// Non-exportable symmetric key used for content decryption. #[cfg(feature = "xmlenc")] pub trait ContentDecryptionKey: Send + Sync { /// Exact external domain owning this handle. fn provider_binding(&self) -> &ProviderBinding; /// Public key width in bytes. fn key_len(&self) -> usize; /// Execute within the selected provider's execution domain. /// /// # Errors /// Returns binding, key-usage, framing or authentication failures from the /// selected execution domain; an unavailable mechanism returns `Unsupported`. fn decrypt_with_provider( &self, provider: &dyn CryptoProvider, algorithm: DataEncryptionAlgorithm, ciphertext: &[u8], ) -> Result, ProviderError>; } /// An opaque KEK that unwraps directly into a non-exportable content key. #[cfg(feature = "xmlenc")] pub trait KeyUnwrappingKey: Send + Sync { /// Exact external domain owning this handle. fn provider_binding(&self) -> &ProviderBinding; /// Unwrap within the selected execution domain without reading `CKA_VALUE`. /// /// # Errors /// Returns binding/usage failures, invalid wrapping parameters, integrity /// rejection or an unavailable mechanism, without exporting the content key. fn unwrap_with_provider( &self, provider: &dyn CryptoProvider, algorithm: KeyWrapAlgorithm, content_algorithm: DataEncryptionAlgorithm, wrapped: &[u8], ) -> Result; } /// Opaque public-key handle used for asymmetric key transport. /// /// Implementations own their key material and operation. The orchestration /// layer can inspect only public RSA components needed for policy validation /// and output framing; it cannot recover a backend-specific key object. #[cfg(feature = "xmlenc")] pub trait KeyTransportKey: Send + Sync { /// External execution domain, or `None` for software-owned public keys. fn provider_binding(&self) -> Option<&ProviderBinding> { None } /// Execute historical transport; unsupported opaque handles never fall back. /// /// # Errors /// Returns `Unsupported` by default. Implementations also report invalid key /// or message widths, randomness failures and external-provider failures. #[cfg(feature = "legacy-algorithms")] fn transport_pkcs1v15( &self, _provider: &dyn CryptoProvider, _plaintext: &[u8], ) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyTransport, algorithm: Some( crate::xmlenc::KeyTransportAlgorithm::RsaPkcs1v15 .uri() .into(), ), }) } /// RSA modulus bytes without redundant leading zero octets. /// /// These components must identify the exact key used by /// [`Self::transport_with_provider`]; returning metadata for another key /// would violate the policy boundary. fn rsa_modulus(&self) -> Cow<'_, [u8]>; /// RSA public exponent bytes without redundant leading zero octets. fn rsa_exponent(&self) -> Cow<'_, [u8]>; /// Execute OAEP key transport using the selected provider's randomness. /// /// # Errors /// Returns unsupported parameters, invalid key/message widths, randomness /// failures or an execution-domain failure. fn transport_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError>; } /// Opaque private-key handle used to recover transported key bytes. /// /// Private key material never crosses this boundary. The ciphertext size is /// public metadata required to reject malformed RSA inputs before dispatch. #[cfg(feature = "xmlenc")] pub trait KeyRecoveryKey: Send + Sync { /// Recover without exporting a CEK when supported by the execution domain. /// /// # Errors /// Propagates OAEP recovery failures, including invalid framing, /// authentication rejection and unsupported parameters or key bindings. fn recover_content_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, _content_algorithm: DataEncryptionAlgorithm, ciphertext: &[u8], ) -> Result { self.recover_with_provider(provider, parameters, ciphertext) .map(RecoveredContentKey::confirmed) } /// Recover a content key of the caller's already validated algorithm width. /// /// # Errors /// Returns `Unsupported` by default. Implementations report public framing, /// key and execution failures, retaining secret padding validity in the result. #[cfg(feature = "legacy-algorithms")] fn recover_pkcs1v15( &self, _provider: &dyn CryptoProvider, _ciphertext: &[u8], _key_len: usize, ) -> Result { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyRecovery, algorithm: Some( crate::xmlenc::KeyTransportAlgorithm::RsaPkcs1v15 .uri() .into(), ), }) } /// Native engine binding; an incompatible provider must reject this handle. fn provider_name(&self) -> Option<&'static str> { None } /// Borrow the matching public SPKI when available for certificate binding. fn public_spki(&self) -> Option<&[u8]> { None } /// Exact mathematical bit length of the recovery key's public modulus, /// excluding leading zero padding. Not the rounded ciphertext width. fn rsa_modulus_bits(&self) -> usize; /// Public exponent of that same key, or `None` if wider than 64 bits. /// Opaque providers expose public metadata without copying private material. fn rsa_public_exponent(&self) -> Option; /// Exact RSA ciphertext width in bytes for the key used by /// [`Self::recover_with_provider`]. fn ciphertext_len(&self) -> usize; /// Execute OAEP recovery using the selected provider's randomness. /// /// # Errors /// Returns invalid ciphertext/key framing, OAEP authentication rejection, /// unsupported parameters, randomness failure or an execution-domain failure. fn recover_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError>; } /// Opaque private-key handle used for provider-defined key agreement. pub trait KeyAgreementKey: Send + Sync { /// Public modulus/subgroup bit widths for finite-field DH. Operations use /// these to recheck key strength under their own policy; no secret is exported. fn dh_domain_bits(&self) -> Option<(usize, usize)> { None } /// External execution domain, when the private key resides outside this process. fn provider_binding(&self) -> Option<&ProviderBinding> { None } /// Derive the raw shared secret for the supplied peer and parameters. /// /// # Errors /// Returns unsupported agreement parameters, invalid peer/key material, /// an already consumed ephemeral key or an execution-domain failure. fn agree(&self, parameters: &KeyAgreementParameters<'_>) -> Result, ProviderError>; } /// Incremental message digest owned by one provider operation. /// /// Input is absorbed in order across any number of [`update`](Self::update) /// calls; [`finalize`](Self::finalize) consumes the state. A provider may /// serialize its other operations while a state is live, so callers finish a /// digest before starting another operation on the same provider. /// /// This is the backend interface of the selected provider (runtime backend, /// FIPS selection, token sessions), not a cross-product contract: the shared /// streaming digest callbacks of the `signature-primitives` packages are /// implemented by adapters on top of it. #[cfg(feature = "xmldsig")] pub trait DigestState { /// Absorb the next input bytes. /// /// # Errors /// Returns the selected provider's digest-update failure. fn update(&mut self, data: &[u8]) -> Result<(), ProviderError>; /// Write the digest of all absorbed bytes to the start of `output` and /// return its length. The owner finalizes once, then drops the state. /// /// # Errors /// Returns the selected provider's digest-finalization failure. fn finalize( &mut self, output: &mut [u8; crate::xmldsig::MAX_DIGEST_OUTPUT], ) -> Result; } /// A digest state with the charge for the block that holds it. #[cfg(feature = "xmldsig")] struct Charged { state: T, // Inside the block it pays for, so it releases just before the block does: // the one moment the account is lower than the allocator, and nothing runs in it. _charge: structured_asn1::memory::MemoryReservation, } // One element, because a fallibly allocated `Box<[T; 1]>` is the only `Box` that // stable Rust offers without `unsafe`. #[cfg(feature = "xmldsig")] impl DigestState for [Charged; 1] { fn update(&mut self, data: &[u8]) -> Result<(), ProviderError> { self[0].state.update(data) } fn finalize( &mut self, output: &mut [u8; crate::xmldsig::MAX_DIGEST_OUTPUT], ) -> Result { self[0].state.finalize(output) } } /// Move `state` to the heap under `memory`: its complete block is admitted /// first, the allocation can fail, and the charge lasts as long as the state. /// /// # Errors /// Returns [`ProviderError::Resource`] when the account or the allocator refuses. #[cfg(feature = "xmldsig")] pub fn charged_digest_state<'s, T: DigestState + 's>( state: T, memory: &structured_asn1::memory::Memory, ) -> Result, ProviderError> { use structured_asn1::memory::MemoryError; let charge = memory .reserve(core::mem::size_of::>()) .map_err(ProviderError::Resource)?; let mut block = Vec::new(); block .try_reserve_exact(1) .map_err(|_| ProviderError::Resource(MemoryError::OutOfMemory))?; block.push(Charged { state, _charge: charge, }); let block: Box<[Charged; 1]> = match block.into_boxed_slice().try_into() { Ok(block) => block, Err(_) => unreachable!("the slice holds the one pushed element"), }; Ok(block) } /// Provider operations used by the XML Security pipelines. pub trait CryptoProvider: Send + Sync { /// External execution domain. Different instances of one engine are not interchangeable. fn binding(&self) -> Option<&ProviderBinding> { None } /// Recover a candidate without requiring its symmetric value to leave the provider. /// /// # Errors /// Propagates `recover_key` failures by default; opaque implementations also /// report execution-domain binding, usage and unavailable-mechanism errors. #[cfg(feature = "xmlenc")] fn recover_content_key( &self, key: &dyn KeyRecoveryKey, parameters: &RsaOaepParameters, _content_algorithm: DataEncryptionAlgorithm, ciphertext: &[u8], ) -> Result { self.recover_key(key, parameters, ciphertext) .map(RecoveredContentKey::confirmed) } /// Unwrap into an opaque content key. Providers must opt in explicitly. /// /// # Errors /// Returns `Unsupported` by default. Implementations report key binding, /// wrapping integrity, parameter and external-operation failures. #[cfg(feature = "xmlenc")] fn unwrap_content_key( &self, _key: &dyn KeyUnwrappingKey, algorithm: KeyWrapAlgorithm, _content_algorithm: DataEncryptionAlgorithm, _wrapped: &[u8], ) -> Result { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyUnwrap, algorithm: Some(algorithm.uri().into()), }) } /// Decrypt with an opaque key; capability remains distinct from policy permission. /// /// # Errors /// Returns `Unsupported` by default. Implementations report binding, key /// usage, ciphertext framing, authentication and external-operation failures. #[cfg(feature = "xmlenc")] fn decrypt_content_key( &self, algorithm: DataEncryptionAlgorithm, _key: &dyn ContentDecryptionKey, _ciphertext: &[u8], ) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::Decrypt, algorithm: Some(algorithm.uri().into()), }) } /// Stable provider name for diagnostics and capability reporting. fn name(&self) -> &'static str; /// Import original PKCS#8 input into this engine without exporting another /// provider's private handle. Policy and input budgets are enforced by the caller. /// Like the provider's recovery and transport handles, the imported key /// may be shared across threads. /// /// # Errors /// Returns an unsupported method, malformed or mismatched key encoding, /// or the selected provider's import failure. #[cfg(feature = "xmldsig")] fn import_signing_key( &self, algorithm: crate::xmldsig::SignatureAlgorithm, _pkcs8: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { Err(ProviderError::Unsupported { operation: ProviderOperation::Sign, algorithm: Some(algorithm.uri().into()), } .into()) } /// Import original PKCS#8 input into a provider-owned RSA recovery handle. /// /// # Errors /// Returns `Unsupported` by default, or an invalid key encoding or import /// failure from an implementing provider. #[cfg(feature = "xmlenc")] fn import_recovery_key(&self, _pkcs8: &[u8]) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyRecovery, algorithm: None, }) } /// Return whether this build can dispatch the requested capability. /// /// [`ProviderCapability::Sign`] and [`ProviderCapability::Verify`] describe /// provider dispatch only: the supplied opaque key performs the executable /// algorithm-support check when the operation runs. fn supports(&self, capability: ProviderCapability<'_>) -> bool; /// Import a recipient SPKI into the selected engine. The caller reserves /// import work and grants permission; unavailable engines never fall back. /// /// # Errors /// Returns an unavailable parameter set, invalid or mismatched SPKI key /// material, or a provider import failure. #[cfg(feature = "xmldsig")] fn import_encapsulation_key( &self, algorithm: KeyEncapsulationAlgorithm, _spki: &[u8], ) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::Encapsulate, algorithm: Some(algorithm.uri().into()), }) } /// Import a recipient PKCS#8 into this engine without exporting another /// provider's private handle. The encoded parameter set must match the request. /// /// # Errors /// Returns an unavailable parameter set, invalid or inconsistent private /// key encoding, or a provider import failure. #[cfg(feature = "xmldsig")] fn import_decapsulation_key( &self, algorithm: KeyEncapsulationAlgorithm, _pkcs8: &[u8], ) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::Decapsulate, algorithm: Some(algorithm.uri().into()), }) } /// Fill caller-owned output with cryptographically secure random bytes. /// /// # Errors /// Returns an unavailable randomness capability or the selected entropy /// source's failure; callers must not consume the output after an error. fn fill_random(&self, output: &mut [u8]) -> Result<(), ProviderError>; /// Encapsulate through this engine; unsupported engines never delegate to `RustCrypto`. /// /// # Errors /// Returns `Unsupported` by default. Implementations report key-domain, /// key-material, randomness and encapsulation-operation failures. #[cfg(feature = "xmldsig")] fn encapsulate_key( &self, key: &dyn KeyEncapsulationKey, ) -> Result { Err(ProviderError::Unsupported { operation: ProviderOperation::Encapsulate, algorithm: Some(key.algorithm().uri().into()), }) } /// Decapsulate without revealing implicit-rejection validity. /// /// # Errors /// Returns unsupported parameters, invalid public ciphertext framing, key /// binding or provider-operation failures, not implicit-rejection validity. #[cfg(feature = "xmldsig")] fn decapsulate_key( &self, key: &dyn KeyDecapsulationKey, ciphertext: &[u8], ) -> Result, ProviderError> { let _ = ciphertext; Err(ProviderError::Unsupported { operation: ProviderOperation::Decapsulate, algorithm: Some(key.algorithm().uri().into()), }) } /// Start an incremental message digest; streamed input never needs to be /// collected in memory first. Storage for the state is admitted in `memory` /// before it exists, as [`charged_digest_state`] does. /// /// # Errors /// Returns an unsupported digest, a resource refusal, or the selected /// provider's state-creation failure. #[cfg(feature = "xmldsig")] fn begin_digest( &self, algorithm: DigestAlgorithm, memory: &structured_asn1::memory::Memory, ) -> Result, ProviderError>; /// Compute the message digest of one contiguous input over the same /// execution as streamed input. /// /// # Errors /// Propagates state creation, update and finalization failures unchanged. #[cfg(feature = "xmldsig")] fn digest( &self, algorithm: DigestAlgorithm, data: &[u8], memory: &structured_asn1::memory::Memory, ) -> Result { let mut state = self.begin_digest(algorithm, memory)?; state.update(data)?; crate::xmldsig::finish_digest(algorithm, &mut *state) } /// Sign bytes with an opaque key handle. /// /// Providers that delegate primitive signing to the supplied key must call /// [`crate::xmldsig::SigningKey::sign_with_provider`] so randomized /// primitives consume this provider's randomness. /// /// # Errors /// Returns an unsupported algorithm/key binding or the key's signing, /// digest or randomness failure. #[cfg(feature = "xmldsig")] fn sign( &self, key: &dyn crate::xmldsig::SigningKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], ) -> Result, crate::xmldsig::SigningKeyError>; /// Verify bytes with an opaque key handle. /// /// The `XMLDSig` facade validates algorithm- and key-specific signature /// framing before this provider boundary. /// /// # Errors /// Returns unsupported algorithm/key combinations or provider/key execution /// failures. A completed cryptographic mismatch is `Ok(false)`. #[cfg(feature = "xmldsig")] fn verify( &self, key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], signature: &[u8], ) -> Result; /// Sign with explicit domain separation; unsupported contexts must not be discarded. /// /// # Errors /// Returns an unsupported context or propagates the selected signing failure. #[cfg(feature = "xmldsig")] fn sign_with_context( &self, key: &dyn crate::xmldsig::SigningKey, algorithm: crate::xmldsig::SignatureAlgorithm, context: &crate::xmldsig::SignatureContext, data: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { if !context.as_bytes().is_empty() { return Err(crate::xmldsig::SigningKeyError::UnsupportedAlgorithm { uri: algorithm.uri().to_owned(), }); } self.sign(key, algorithm, data) } /// Verify with explicit domain separation; unsupported contexts fail closed. /// /// # Errors /// Returns an unsupported context or propagates verification execution /// failures; a completed cryptographic mismatch remains `Ok(false)`. #[cfg(feature = "xmldsig")] fn verify_with_context( &self, key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, context: &crate::xmldsig::SignatureContext, data: &[u8], signature: &[u8], ) -> Result { if !context.as_bytes().is_empty() { return Err( crate::xmldsig::SignatureVerificationError::UnsupportedAlgorithm { uri: algorithm.uri().to_owned(), } .into(), ); } self.verify(key, algorithm, data, signature) } /// Verify once while reporting the actual successful leaf. Providers that /// cannot identify it retain ordinary verification and report no receipt. /// /// # Errors /// Propagates verification and observer failures; inability to produce a /// receipt alone does not turn ordinary verification into an error. #[cfg(feature = "xmldsig")] fn verify_with_key_receipt( &self, key: &dyn crate::xmldsig::VerifyingKey, input: SignatureVerificationInput<'_>, _observer: &mut dyn FnMut( KeyVerificationEvent<'_>, ) -> Result<(), crate::xmldsig::DsigError>, ) -> Result { self.verify_with_context( key, input.algorithm, input.context, input.data, input.signature, ) } /// Compare the successful public receipt with original effective PKIX key /// state. Unknown semantics are Unproven, never a second provider fallback. /// /// # Errors /// Implementations propagate work-admission and comparison execution failures. /// Unsupported comparison semantics return `KeyAssociation::Unproven` instead. #[cfg(feature = "xmldsig")] fn associate_verification_key( &self, _actual: &[u8], _expected: structured_pkix::path::WorkingKey<'_>, _charge: &dyn Fn(u64) -> Result<(), structured_asn1::memory::MemoryError>, ) -> Result { Ok(KeyAssociation::Unproven) } /// Verify an X.509 certificate or CRL signature under its issuer SPKI. /// /// # Errors /// Returns unsupported algorithms/parameters, invalid issuer key encoding /// or provider execution failures; a completed mismatch is `Ok(false)`. #[cfg(feature = "xmldsig")] fn verify_x509_signature( &self, algorithm: X509SignatureAlgorithm, signed_data: &[u8], signature: &[u8], issuer_spki_der: &[u8], ) -> Result { let _ = (signed_data, signature, issuer_spki_der); Err(ProviderError::Unsupported { operation: ProviderOperation::VerifyCertificate, algorithm: algorithm.oid().map(str::to_owned), }) } /// Encrypt `XMLEnc` content bytes, including standard framing. /// /// # Errors /// Returns an unsupported cipher, invalid key size or plaintext framing, /// randomness failure or provider encryption failure. #[cfg(feature = "xmlenc")] fn encrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError>; /// Decrypt `XMLEnc` content bytes, including framing validation. /// /// # Errors /// Returns an unsupported cipher, invalid key/ciphertext framing or content /// authentication, padding or provider execution failure. #[cfg(feature = "xmlenc")] fn decrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], ) -> Result, ProviderError>; /// Encrypt while preserving XML-carried cipher parameters. Providers /// must implement the profile explicitly, never discard it or fall back. /// /// # Errors /// Returns unsupported explicit parameters or propagates content-encryption failures. #[cfg(feature = "xmlenc")] fn encrypt_data_with_parameters( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], parameters: Option>, ) -> Result, ProviderError> { if parameters.is_some() { return Err(ProviderError::Unsupported { operation: ProviderOperation::Encrypt, algorithm: Some(algorithm.uri().into()), }); } self.encrypt_data(algorithm, key, plaintext) } /// Decrypt with the same explicit parameter contract as encryption. /// /// # Errors /// Returns unsupported explicit parameters or propagates content-decryption failures. #[cfg(feature = "xmlenc")] fn decrypt_data_with_parameters( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], parameters: Option>, ) -> Result, ProviderError> { if parameters.is_some() { return Err(ProviderError::Unsupported { operation: ProviderOperation::Decrypt, algorithm: Some(algorithm.uri().into()), }); } self.decrypt_data(algorithm, key, ciphertext) } /// Wrap a content key with RFC 3394 AES Key Wrap. /// /// Successful output contains the complete RFC 3394 value and is exactly /// eight bytes longer than `key`. The `XMLEnc` facade validates that framing /// before serializing provider output. /// /// # Errors /// Returns an unsupported wrapping method, invalid KEK/key framing, /// randomness failure where required, or a wrapping-operation failure. #[cfg(feature = "xmlenc")] fn wrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], key: &[u8], ) -> Result, ProviderError>; /// Unwrap a content key with RFC 3394 AES Key Wrap. /// /// # Errors /// Returns an unsupported method, invalid KEK/wrapped framing or a failed /// integrity check or provider operation. #[cfg(feature = "xmlenc")] fn unwrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], wrapped: &[u8], ) -> Result, ProviderError>; /// Wrap key bytes using an opaque RSA public-key operation. /// /// # Errors /// Returns unsupported OAEP parameters, invalid key/message widths, key /// binding, randomness or transport-operation failures. #[cfg(feature = "xmlenc")] fn transport_key( &self, key: &dyn KeyTransportKey, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError>; /// Recover key bytes using an opaque RSA private-key operation. /// /// # Errors /// Returns unsupported OAEP parameters, invalid key/ciphertext framing, /// binding, authentication, randomness or recovery-operation failures. #[cfg(feature = "xmlenc")] fn recover_key( &self, key: &dyn KeyRecoveryKey, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError>; /// Transport using RSAES-PKCS1-v1_5; not an OAEP configuration. /// /// # Errors /// Returns `Unsupported` by default. Implementations report invalid public /// input widths, key binding, randomness or transport-operation failures. #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] fn transport_pkcs1v15( &self, _key: &dyn KeyTransportKey, _plaintext: &[u8], ) -> Result, ProviderError> { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyTransport, algorithm: Some( crate::xmlenc::KeyTransportAlgorithm::RsaPkcs1v15 .uri() .into(), ), }) } /// Recover a fixed-width content key without exposing padding validity. /// /// # Errors /// Returns `Unsupported` by default. Implementations report public framing, /// binding, randomness or provider failures, retaining padding validity in the result. #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] fn recover_pkcs1v15( &self, _key: &dyn KeyRecoveryKey, _ciphertext: &[u8], _key_len: usize, ) -> Result { Err(ProviderError::Unsupported { operation: ProviderOperation::KeyRecovery, algorithm: Some( crate::xmlenc::KeyTransportAlgorithm::RsaPkcs1v15 .uri() .into(), ), }) } /// Perform key agreement with an opaque provider-owned private key. /// /// # Errors /// Returns `Unsupported` for an unavailable capability, `Binding` for a /// mismatched external domain, or the key's agreement failure. fn agree_key( &self, key: &dyn KeyAgreementKey, parameters: &KeyAgreementParameters<'_>, ) -> Result, ProviderError> { self.require_capability(ProviderCapability::KeyAgreement(parameters))?; if let Some(binding) = key.provider_binding() && !self .binding() .is_some_and(|selected| selected.matches(binding)) { return Err(ProviderError::External(ExternalProviderError::Binding)); } key.agree(parameters) } /// Derive key bytes from caller-owned secret material. /// /// Implementations that advertise [`ProviderCapability::Kdf`] must perform /// the advertised derivation here. This method is required so capability /// discovery cannot silently inherit a contradictory unsupported default. /// /// # Errors /// Returns unsupported parameters, invalid framing/iterations/output size /// or the selected provider's derivation failure. fn derive_key( &self, parameters: &KdfParameters<'_>, secret: &[u8], ) -> Result, ProviderError>; /// Reject an unavailable exact capability without falling back. /// /// # Errors /// Returns `ProviderError::Unsupported` with the requested operation and /// algorithm identifier when `supports` is false. fn require_capability(&self, capability: ProviderCapability<'_>) -> Result<(), ProviderError> { if self.supports(capability) { Ok(()) } else { Err(ProviderError::Unsupported { operation: capability.operation(), algorithm: capability.algorithm().map(str::to_owned), }) } } } /// Pure-Rust provider backed by `RustCrypto` crates. #[derive(Debug, Clone, Copy, Default)] pub struct RustCryptoProvider; /// Defines the `RustCrypto` incremental hasher for every compiled algorithm. #[cfg(feature = "rustcrypto")] macro_rules! rust_crypto_digest { ($($(#[$attr:meta])* $variant:ident => $hasher:ty),+ $(,)?) => { enum RustCryptoDigest { $($(#[$attr])* $variant($hasher),)+ } impl RustCryptoDigest { fn new(algorithm: DigestAlgorithm) -> Self { use sha2::Digest as _; match algorithm { $($(#[$attr])* DigestAlgorithm::$variant => Self::$variant(<$hasher>::new()),)+ #[cfg(feature = "dstu4145")] DigestAlgorithm::Gost34311 | DigestAlgorithm::Dstu7564_256 => { unreachable!("begin_digest sends these to the DSTU 4145 module") } } } } impl DigestState for RustCryptoDigest { fn update(&mut self, data: &[u8]) -> Result<(), ProviderError> { use sha2::Digest as _; match self { $($(#[$attr])* Self::$variant(hasher) => hasher.update(data),)+ } Ok(()) } fn finalize( &mut self, output: &mut [u8; crate::xmldsig::MAX_DIGEST_OUTPUT], ) -> Result { use sha2::Digest as _; Ok(match self { $($(#[$attr])* Self::$variant(hasher) => { let digest = hasher.finalize_reset(); output[..digest.len()].copy_from_slice(&digest); digest.len() })+ }) } } }; } #[cfg(feature = "rustcrypto")] rust_crypto_digest! { #[cfg(feature = "legacy-algorithms")] Md5 => md5::Md5, #[cfg(feature = "legacy-algorithms")] Ripemd160 => ripemd::Ripemd160, Sha1 => sha1::Sha1, Sha224 => sha2::Sha224, Sha256 => sha2::Sha256, Sha384 => sha2::Sha384, Sha512 => sha2::Sha512, Sha3_224 => sha3::Sha3_224, Sha3_256 => sha3::Sha3_256, Sha3_384 => sha3::Sha3_384, Sha3_512 => sha3::Sha3_512, } /// Opaque RSA public-key handle for the built-in `RustCrypto` provider. #[cfg(feature = "xmlenc")] #[derive(Clone)] pub struct RustCryptoRsaPublicKey { key: rsa::RsaPublicKey, modulus: Vec, exponent: Vec, } #[cfg(feature = "xmlenc")] impl RustCryptoRsaPublicKey { /// Wrap an already parsed `RustCrypto` RSA public key. #[must_use] pub fn new(key: rsa::RsaPublicKey) -> Self { use rsa::traits::PublicKeyParts as _; let modulus = key.n().to_be_bytes_trimmed_vartime().into_vec(); let exponent = key.e().to_be_bytes_trimmed_vartime().into_vec(); Self { key, modulus, exponent, } } } #[cfg(feature = "xmlenc")] impl From for RustCryptoRsaPublicKey { fn from(key: rsa::RsaPublicKey) -> Self { Self::new(key) } } #[cfg(feature = "xmlenc")] impl KeyTransportKey for RustCryptoRsaPublicKey { #[cfg(feature = "legacy-algorithms")] fn transport_pkcs1v15( &self, provider: &dyn CryptoProvider, plaintext: &[u8], ) -> Result, ProviderError> { rustcrypto::transport_pkcs1v15(provider, &self.key, plaintext) } fn rsa_modulus(&self) -> Cow<'_, [u8]> { Cow::Borrowed(&self.modulus) } fn rsa_exponent(&self) -> Cow<'_, [u8]> { Cow::Borrowed(&self.exponent) } fn transport_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError> { rustcrypto::transport_key(provider, &self.key, parameters, plaintext) } } #[cfg(feature = "xmlenc")] impl KeyTransportKey for rsa::RsaPublicKey { #[cfg(feature = "legacy-algorithms")] fn transport_pkcs1v15( &self, provider: &dyn CryptoProvider, plaintext: &[u8], ) -> Result, ProviderError> { rustcrypto::transport_pkcs1v15(provider, self, plaintext) } fn rsa_modulus(&self) -> Cow<'_, [u8]> { use rsa::traits::PublicKeyParts as _; Cow::Owned(self.n().to_be_bytes_trimmed_vartime().into_vec()) } fn rsa_exponent(&self) -> Cow<'_, [u8]> { use rsa::traits::PublicKeyParts as _; Cow::Owned(self.e().to_be_bytes_trimmed_vartime().into_vec()) } fn transport_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError> { rustcrypto::transport_key(provider, self, parameters, plaintext) } } /// Opaque RSA private-key handle for the built-in `RustCrypto` provider. #[cfg(feature = "xmlenc")] #[derive(Clone)] pub struct RustCryptoRsaPrivateKey { key: rsa::RsaPrivateKey, ciphertext_len: usize, } #[cfg(feature = "xmlenc")] impl RustCryptoRsaPrivateKey { /// Wrap an already parsed `RustCrypto` RSA private key. #[must_use] pub fn new(key: rsa::RsaPrivateKey) -> Self { use rsa::traits::PublicKeyParts as _; let ciphertext_len = key.size(); Self { key, ciphertext_len, } } } #[cfg(feature = "xmlenc")] impl From for RustCryptoRsaPrivateKey { fn from(key: rsa::RsaPrivateKey) -> Self { Self::new(key) } } #[cfg(feature = "xmlenc")] impl KeyRecoveryKey for RustCryptoRsaPrivateKey { #[cfg(feature = "legacy-algorithms")] fn recover_pkcs1v15( &self, provider: &dyn CryptoProvider, ciphertext: &[u8], key_len: usize, ) -> Result { rsa_pkcs1v15::recover(provider, &self.key, ciphertext, key_len) } fn rsa_modulus_bits(&self) -> usize { KeyRecoveryKey::rsa_modulus_bits(&self.key) } fn rsa_public_exponent(&self) -> Option { KeyRecoveryKey::rsa_public_exponent(&self.key) } fn ciphertext_len(&self) -> usize { self.ciphertext_len } fn recover_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError> { rustcrypto::recover_key(provider, &self.key, parameters, ciphertext) } } #[cfg(feature = "xmlenc")] impl KeyRecoveryKey for rsa::RsaPrivateKey { #[cfg(feature = "legacy-algorithms")] fn recover_pkcs1v15( &self, provider: &dyn CryptoProvider, ciphertext: &[u8], key_len: usize, ) -> Result { rsa_pkcs1v15::recover(provider, self, ciphertext, key_len) } fn rsa_modulus_bits(&self) -> usize { use rsa::traits::PublicKeyParts as _; self.n().bit_width_vartime() as usize } fn rsa_public_exponent(&self) -> Option { use rsa::traits::PublicKeyParts as _; if self.e().bit_width_vartime() > 64 { return None; } let mut exponent = 0_u64; let word_bits = crypto_bigint::Word::BITS as usize; for (index, word) in self.e().as_words().iter().take(64 / word_bits).enumerate() { #[cfg(target_pointer_width = "64")] let word = *word; #[cfg(target_pointer_width = "32")] let word = u64::from(*word); exponent |= word << (index * word_bits); } Some(exponent) } fn ciphertext_len(&self) -> usize { use rsa::traits::PublicKeyParts as _; self.size() } fn recover_with_provider( &self, provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError> { rustcrypto::recover_key(provider, self, parameters, ciphertext) } } /// Process-wide immutable default provider. It contains no mutable state or keys. pub static RUST_CRYPTO_PROVIDER: RustCryptoProvider = RustCryptoProvider; /// Borrow the pure-Rust default provider. #[must_use] pub fn default_provider() -> &'static dyn CryptoProvider { &RUST_CRYPTO_PROVIDER } /// Adapter used when a `RustCrypto` primitive requires a fallible RNG object. #[cfg(feature = "xmldsig-sign")] pub(crate) struct ProviderRng<'a>(pub(crate) &'a dyn CryptoProvider); #[cfg(feature = "xmldsig-sign")] impl TryRng for ProviderRng<'_> { type Error = ProviderError; fn try_next_u32(&mut self) -> Result { let mut bytes = [0_u8; 4]; self.try_fill_bytes(&mut bytes)?; Ok(u32::from_le_bytes(bytes)) } fn try_next_u64(&mut self) -> Result { let mut bytes = [0_u8; 8]; self.try_fill_bytes(&mut bytes)?; Ok(u64::from_le_bytes(bytes)) } fn try_fill_bytes(&mut self, output: &mut [u8]) -> Result<(), Self::Error> { self.0.fill_random(output) } } #[cfg(feature = "xmldsig-sign")] impl TryCryptoRng for ProviderRng<'_> {} #[cfg(feature = "xmldsig")] impl RustCryptoProvider { fn verify_context_leaf( key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, context: &crate::xmldsig::SignatureContext, data: &[u8], signature: &[u8], ) -> Result { if key.provider_binding().is_some() { return Err(ProviderError::External(ExternalProviderError::Binding).into()); } key.verify_with_context(algorithm, context, data, signature) } } impl CryptoProvider for RustCryptoProvider { #[cfg(feature = "xmldsig")] fn verify_with_key_receipt( &self, key: &dyn crate::xmldsig::VerifyingKey, input: SignatureVerificationInput<'_>, observer: &mut dyn FnMut(KeyVerificationEvent<'_>) -> Result<(), crate::xmldsig::DsigError>, ) -> Result { self.require_capability(ProviderCapability::Verify(input.algorithm))?; key_association::observed_leaf(key, observer, &|key| { Self::verify_context_leaf( key, input.algorithm, input.context, input.data, input.signature, ) }) } // Without `rustcrypto` this provider verifies nothing, so the trait // default (no association proven) applies. #[cfg(feature = "rustcrypto")] fn associate_verification_key( &self, actual: &[u8], expected: structured_pkix::path::WorkingKey<'_>, charge: &dyn Fn(u64) -> Result<(), structured_asn1::memory::MemoryError>, ) -> Result { key_association::compare_rustcrypto(actual, expected, charge) } #[cfg(feature = "xmldsig")] fn encapsulate_key( &self, key: &dyn KeyEncapsulationKey, ) -> Result { self.require_capability(ProviderCapability::Encapsulate(key.algorithm()))?; if key.provider_binding().is_some() { return Err(ProviderError::External(ExternalProviderError::Binding)); } key.encapsulate_with_provider(self) } #[cfg(feature = "xmldsig")] fn decapsulate_key( &self, key: &dyn KeyDecapsulationKey, ciphertext: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::Decapsulate(key.algorithm()))?; if key.provider_binding().is_some() { return Err(ProviderError::External(ExternalProviderError::Binding)); } if ciphertext.len() != key.algorithm().ciphertext_len() { return Err(ProviderError::InvalidInput( ProviderInputError::MlKemCiphertext, )); } key.decapsulate(ciphertext) } #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] fn transport_pkcs1v15( &self, key: &dyn KeyTransportKey, plaintext: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::Pkcs1v15Transport)?; key.transport_pkcs1v15(self, plaintext) } #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] fn recover_pkcs1v15( &self, key: &dyn KeyRecoveryKey, ciphertext: &[u8], key_len: usize, ) -> Result { self.require_capability(ProviderCapability::Pkcs1v15Recovery)?; key.recover_pkcs1v15(self, ciphertext, key_len) } fn name(&self) -> &'static str { "rustcrypto" } #[cfg(feature = "experimental-pq")] fn import_encapsulation_key( &self, algorithm: KeyEncapsulationAlgorithm, spki: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::Encapsulate(algorithm))?; let key = RustCryptoMlKemPublicKey::from_spki_der_with_algorithm(spki, Some(algorithm))?; Ok(Arc::new(key)) } #[cfg(feature = "experimental-pq")] fn import_decapsulation_key( &self, algorithm: KeyEncapsulationAlgorithm, pkcs8: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::Decapsulate(algorithm))?; let key = RustCryptoMlKemPrivateKey::from_pkcs8_der_with_algorithm(pkcs8, Some(algorithm))?; Ok(Arc::new(key)) } #[cfg(feature = "xmlenc")] fn import_recovery_key(&self, pkcs8: &[u8]) -> Result, ProviderError> { use crate::rsa_encoding::RsaPrivateKeyEncoding as _; let key = rsa::RsaPrivateKey::from_pkcs8_der(pkcs8).map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization( "RSA recovery key", )) })?; Ok(Arc::new(RustCryptoRsaPrivateKey::new(key))) } // Without `xmldsig-sign` the trait default reports signing unsupported. #[cfg(feature = "xmldsig-sign")] fn import_signing_key( &self, algorithm: crate::xmldsig::SignatureAlgorithm, der: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { #[cfg(feature = "experimental-pq")] use crate::xmldsig::PostQuantumSigningKey; use crate::xmldsig::{ DsaSigningKey, EcdsaP256SigningKey, EcdsaP384SigningKey, EcdsaP521SigningKey, EdDsaSigningKey, RsaSigningKey, SignatureAlgorithm as A, SigningKeyError, }; Ok(match algorithm { #[cfg(feature = "experimental-pq")] A::PostQuantum(parameter) => { Box::new(PostQuantumSigningKey::from_pkcs8_der(parameter, der)?) } A::Ed25519 | A::Ed25519Ctx | A::Ed25519Ph | A::Ed448 | A::Ed448Ph => { Box::new(EdDsaSigningKey::from_pkcs8_der(algorithm, der)?) } method if method.is_rsa() => Box::new(RsaSigningKey::from_pkcs8_der(der)?), A::DsaSha1 | A::DsaSha256 => Box::new(DsaSigningKey::from_pkcs8_der(der)?), method if method.ecdsa_digest().is_some() => { if let Ok(key) = EcdsaP256SigningKey::from_pkcs8_der(der) { Box::new(key) } else if let Ok(key) = EcdsaP384SigningKey::from_pkcs8_der(der) { Box::new(key) } else { Box::new(EcdsaP521SigningKey::from_pkcs8_der(der)?) } } _ => { return Err(SigningKeyError::UnsupportedAlgorithm { uri: algorithm.uri().into(), }); } }) } fn supports(&self, capability: ProviderCapability<'_>) -> bool { match capability { // FIPS 203 section 7.2: encapsulation draws its message from the // provider CSPRNG, which only `xmldsig-sign` compiles; decapsulation // is deterministic. #[cfg(feature = "xmldsig")] ProviderCapability::Encapsulate(_) => { cfg!(all(feature = "experimental-pq", feature = "xmldsig-sign")) } #[cfg(feature = "xmldsig")] ProviderCapability::Decapsulate(_) => cfg!(feature = "experimental-pq"), // Without `rustcrypto` no digest, signature or certificate // primitive is compiled, and a supplied provider performs them. #[cfg(feature = "xmldsig")] ProviderCapability::Digest(_) => cfg!(feature = "rustcrypto"), #[cfg(feature = "xmldsig")] // Opaque keys own these primitives and reject unsupported methods // during dispatch; the provider advertises its dispatch surface. ProviderCapability::Sign(algorithm) | ProviderCapability::Verify(algorithm) => { if !cfg!(feature = "rustcrypto") { return false; } if let Some(parameters) = algorithm.rsa_pss_parameters() && parameters.salt_len > i32::MAX as usize { return false; } !matches!( algorithm, crate::xmldsig::SignatureAlgorithm::PostQuantum(_) ) || cfg!(feature = "experimental-pq") } #[cfg(feature = "rustcrypto")] ProviderCapability::VerifyCertificate(algorithm) => { is_supported_x509_signature(algorithm) } #[cfg(all(feature = "xmldsig", not(feature = "rustcrypto")))] ProviderCapability::VerifyCertificate(_) => false, #[cfg(feature = "xmlenc")] ProviderCapability::Encrypt(_) | ProviderCapability::Decrypt(_) => true, #[cfg(feature = "xmlenc")] ProviderCapability::KeyWrap(algorithm) | ProviderCapability::KeyUnwrap(algorithm) => { algorithm.wrapped_len(16).is_some() } #[cfg(feature = "xmlenc")] ProviderCapability::KeyTransport(parameters) | ProviderCapability::KeyRecovery(parameters) => { !parameters.algorithm.requires_explicit_permission() && parameters.mgf_digest.mgf_uri().is_some() } #[cfg(all(feature = "xmlenc", feature = "legacy-algorithms"))] ProviderCapability::Pkcs1v15Transport | ProviderCapability::Pkcs1v15Recovery => true, ProviderCapability::Random => cfg!(feature = "xmldsig-sign"), ProviderCapability::KeyAgreement(parameters) => { #[cfg(feature = "xmlenc")] { matches!( parameters.algorithm, key_establishment::X25519_URI | key_establishment::X448_URI | key_establishment::ECDH_URI | key_establishment::DH_ES_URI | key_establishment::DH_URI ) } #[cfg(not(feature = "xmlenc"))] { let _ = parameters; false } } ProviderCapability::Kdf(parameters) => { #[cfg(feature = "xmlenc")] { key_establishment::supports_kdf(parameters) } #[cfg(not(feature = "xmlenc"))] { let _ = parameters; false } } } } fn fill_random(&self, output: &mut [u8]) -> Result<(), ProviderError> { #[cfg(feature = "xmldsig-sign")] { SysRng .try_fill_bytes(output) .map_err(|error| ProviderError::Random(error.to_string())) } // A verification-only build compiles no entropy source. #[cfg(not(feature = "xmldsig-sign"))] { let _ = output; Err(ProviderError::Unsupported { operation: ProviderOperation::Random, algorithm: None, }) } } fn derive_key( &self, parameters: &KdfParameters<'_>, secret: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::Kdf(parameters))?; #[cfg(feature = "xmlenc")] { key_establishment::derive_key(parameters, secret) } #[cfg(not(feature = "xmlenc"))] { let _ = secret; Err(ProviderError::Unsupported { operation: ProviderOperation::Kdf, algorithm: Some(parameters.algorithm.to_owned()), }) } } #[cfg(feature = "xmldsig")] fn begin_digest( &self, algorithm: DigestAlgorithm, memory: &structured_asn1::memory::Memory, ) -> Result, ProviderError> { #[cfg(feature = "dstu4145")] if crate::xmldsig::dstu4145::is_ukrainian_digest(algorithm) { return crate::xmldsig::dstu4145::begin_digest(algorithm, memory); } #[cfg(feature = "rustcrypto")] { charged_digest_state(RustCryptoDigest::new(algorithm), memory) } #[cfg(not(feature = "rustcrypto"))] { let _ = memory; Err(ProviderError::Unsupported { operation: ProviderOperation::Digest, algorithm: Some(algorithm.uri().into()), }) } } #[cfg(feature = "xmldsig")] fn sign( &self, key: &dyn crate::xmldsig::SigningKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { self.require_capability(ProviderCapability::Sign(algorithm))?; key.sign_with_provider(self, algorithm, data) } #[cfg(feature = "xmldsig")] fn sign_with_context( &self, key: &dyn crate::xmldsig::SigningKey, algorithm: crate::xmldsig::SignatureAlgorithm, context: &crate::xmldsig::SignatureContext, data: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { self.require_capability(ProviderCapability::Sign(algorithm))?; key.sign_with_provider_context(self, algorithm, context, data) } #[cfg(feature = "xmldsig")] fn verify_with_context( &self, key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, context: &crate::xmldsig::SignatureContext, data: &[u8], signature: &[u8], ) -> Result { self.require_capability(ProviderCapability::Verify(algorithm))?; key_association::observed_leaf(key, &mut |_| Ok(()), &|key| { Self::verify_context_leaf(key, algorithm, context, data, signature) }) } #[cfg(feature = "xmldsig")] fn verify( &self, key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], signature: &[u8], ) -> Result { self.require_capability(ProviderCapability::Verify(algorithm))?; key_association::observed_leaf(key, &mut |_| Ok(()), &|key| { if key.provider_binding().is_some() { return Err(ProviderError::External(ExternalProviderError::Binding).into()); } key.verify(algorithm, data, signature) }) } #[cfg(feature = "xmldsig")] fn verify_x509_signature( &self, algorithm: X509SignatureAlgorithm, signed_data: &[u8], signature: &[u8], issuer_spki_der: &[u8], ) -> Result { // A refused key or framing is a non-matching signature. #[cfg(feature = "dstu4145")] if let X509SignatureAlgorithm::Dstu4145 { digest, big_endian } = algorithm && crate::xmldsig::dstu4145::is_ukrainian_digest(digest) { return Ok(crate::xmldsig::dstu4145::verify_identified( digest, big_endian, issuer_spki_der, signed_data, signature, &crate::xmldsig::pkix::CertificateSession::standalone(), ) .unwrap_or(false)); } #[cfg(feature = "rustcrypto")] { rustcrypto_x509::verify_signature(algorithm, signed_data, signature, issuer_spki_der) } #[cfg(not(feature = "rustcrypto"))] { let _ = (signed_data, signature, issuer_spki_der); Err(ProviderError::Unsupported { operation: ProviderOperation::VerifyCertificate, algorithm: algorithm.oid().map(str::to_owned), }) } } #[cfg(feature = "xmlenc")] fn encrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError> { rustcrypto::encrypt_data(self, algorithm, key, plaintext) } #[cfg(feature = "xmlenc")] fn decrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], ) -> Result, ProviderError> { rustcrypto::decrypt_data(algorithm, key, ciphertext) } #[cfg(feature = "xmlenc")] fn wrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], key: &[u8], ) -> Result, ProviderError> { rustcrypto::wrap_key(self, algorithm, kek, key) } #[cfg(feature = "xmlenc")] fn encrypt_data_with_parameters( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], parameters: Option>, ) -> Result, ProviderError> { match parameters { Some(parameters) => chacha::encrypt(algorithm, key, plaintext, parameters), None => self.encrypt_data(algorithm, key, plaintext), } } #[cfg(feature = "xmlenc")] fn decrypt_data_with_parameters( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], parameters: Option>, ) -> Result, ProviderError> { match parameters { Some(parameters) => chacha::decrypt(algorithm, key, ciphertext, parameters), None => self.decrypt_data(algorithm, key, ciphertext), } } #[cfg(feature = "xmlenc")] fn unwrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], wrapped: &[u8], ) -> Result, ProviderError> { rustcrypto::unwrap_key(algorithm, kek, wrapped) } #[cfg(feature = "xmlenc")] fn transport_key( &self, key: &dyn KeyTransportKey, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::KeyTransport(parameters))?; key.transport_with_provider(self, parameters, plaintext) } #[cfg(feature = "xmlenc")] fn recover_key( &self, key: &dyn KeyRecoveryKey, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError> { self.require_capability(ProviderCapability::KeyRecovery(parameters))?; key.recover_with_provider(self, parameters, ciphertext) } } #[cfg(feature = "rustcrypto")] fn is_supported_x509_signature(algorithm: X509SignatureAlgorithm) -> bool { match algorithm { X509SignatureAlgorithm::Dsa(DigestAlgorithm::Sha1) | X509SignatureAlgorithm::Ed25519 | X509SignatureAlgorithm::Ed448 => true, X509SignatureAlgorithm::Ecdsa(_) | X509SignatureAlgorithm::RsaPkcs1v15(_) => { rustcrypto_x509::xml_signature_algorithm(algorithm).is_some() } X509SignatureAlgorithm::PostQuantum(_) => cfg!(feature = "experimental-pq"), #[cfg(feature = "dstu4145")] X509SignatureAlgorithm::Dstu4145 { digest, .. } => { crate::xmldsig::dstu4145::is_ukrainian_digest(digest) } X509SignatureAlgorithm::RsaPss { digest, mgf_digest, .. } => { matches!( digest, DigestAlgorithm::Sha256 | DigestAlgorithm::Sha384 | DigestAlgorithm::Sha512 ) && digest == mgf_digest } X509SignatureAlgorithm::Dsa(_) => false, } } #[cfg(feature = "rustcrypto")] pub(crate) mod rustcrypto_x509 { use alloc::borrow::ToOwned; use der::Decode as _; use rsa::{ RsaPublicKey, pss::{Signature as RsaPssSignature, VerifyingKey as RsaPssVerifyingKey}, traits::PublicKeyParts as _, }; use sha1::Digest as _; use sha2::{Sha256, Sha384, Sha512}; use signature::{Verifier as _, hazmat::PrehashVerifier as _}; use super::{ProviderError, X509SignatureAlgorithm}; use crate::xmldsig::pkix::CertificateSession; use crate::xmldsig::signature::verify_ecdsa_signature_spki_in; use crate::xmldsig::{DigestAlgorithm, SignatureAlgorithm}; pub(super) fn verify_signature( algorithm: X509SignatureAlgorithm, signed_data: &[u8], signature: &[u8], issuer_spki_der: &[u8], ) -> Result { // The provider trait hands no account here; the issuer SPKI was // already admitted under the chain's own session by its caller. let session = CertificateSession::standalone(); match algorithm { X509SignatureAlgorithm::Dsa(DigestAlgorithm::Sha1) => { // Certificate signatures are ASN.1 DER integers sized by the // issuer's q parameter. XMLDSig's fixed 20-byte r||s framing // applies only to SignatureValue, never to X.509 signatures. let Ok(key) = crate::xmldsig::signature::decode_dsa_verifying_key(issuer_spki_der, &session) else { return Ok(false); }; let Ok(signature) = dsa::Signature::from_der(signature) else { return Ok(false); }; let digest = sha1::Sha1::digest(signed_data); Ok(key.verify_prehash(&digest, &signature).is_ok()) } X509SignatureAlgorithm::RsaPkcs1v15(_) => { let Some(xml_algorithm) = xml_signature_algorithm(algorithm) else { return unsupported(algorithm); }; // A refused key or framing is a non-matching signature. Ok( crate::xmldsig::signature::verify_rsa_signature_spki_primitive( xml_algorithm, issuer_spki_der, signed_data, signature, &session, ) .unwrap_or(false), ) } X509SignatureAlgorithm::Ecdsa(_) => { let Some(xml_algorithm) = xml_signature_algorithm(algorithm) else { return unsupported(algorithm); }; // RFC 5280 ECDSA certificate signatures are always ASN.1 DER; // XMLDSig's SignatureValue framing policy is irrelevant here. Ok(verify_ecdsa_signature_spki_in( xml_algorithm, issuer_spki_der, signed_data, signature, crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der, &session, ) .unwrap_or(false)) } X509SignatureAlgorithm::RsaPss { digest, mgf_digest, salt_len, } => { // RFC 4055 key restrictions are part of signature validity. Check // them before provider capability so an incompatible key is a // deterministic non-match even when the requested MGF is unsupported. let Some(key) = compatible_rsa_pss_public_key_from_spki(issuer_spki_der, algorithm, &session) else { return Ok(false); }; if digest != mgf_digest { return unsupported(algorithm); } verify_rsa_pss(digest, salt_len, signed_data, signature, key) } X509SignatureAlgorithm::Ed25519 => { let Ok(key) = crate::xmldsig::modern::ed25519_verifying_key(issuer_spki_der, &session) else { return Ok(false); }; let Ok(signature) = ed25519_dalek::Signature::try_from(signature) else { return Ok(false); }; Ok(key.verify_strict(signed_data, &signature).is_ok()) } X509SignatureAlgorithm::Ed448 => { // RFC 8410 sections 3 and 6 select pure Ed448 without context // or external prehash: https://www.rfc-editor.org/rfc/rfc8410.html#section-6 let Ok(key) = crate::xmldsig::modern::ed448_verifying_key(issuer_spki_der, &session) else { return Ok(false); }; let Ok(signature) = ed448_goldilocks::Signature::try_from(signature) else { return Ok(false); }; Ok(key.verify_raw(&signature, signed_data).is_ok()) } #[cfg(feature = "experimental-pq")] X509SignatureAlgorithm::PostQuantum(algorithm) => { // RFC 9881 §3 and RFC 9909 §§1, 4 require pure signatures // over the DER object with an empty context, not an XML context. // https://www.rfc-editor.org/rfc/rfc9881.html#section-3 // https://www.rfc-editor.org/rfc/rfc9909.html#section-4 Ok(crate::xmldsig::post_quantum::verify( algorithm, issuer_spki_der, &crate::xmldsig::SignatureContext::default(), signed_data, signature, &session, ) .unwrap_or(false)) } _ => unsupported(algorithm), } } fn verify_rsa_pss( digest: DigestAlgorithm, salt_len: usize, signed_data: &[u8], signature: &[u8], key: RsaPublicKey, ) -> Result { if !rsa_pss_salt_fits_key(&key, digest, salt_len) { return Ok(false); } let Ok(signature) = RsaPssSignature::try_from(signature) else { return Ok(false); }; let verified = match digest { DigestAlgorithm::Sha256 => { RsaPssVerifyingKey::::new_with_salt_len(key, salt_len) .verify(signed_data, &signature) } DigestAlgorithm::Sha384 => { RsaPssVerifyingKey::::new_with_salt_len(key, salt_len) .verify(signed_data, &signature) } DigestAlgorithm::Sha512 => { RsaPssVerifyingKey::::new_with_salt_len(key, salt_len) .verify(signed_data, &signature) } _ => { return unsupported(X509SignatureAlgorithm::RsaPss { digest, mgf_digest: digest, salt_len, }); } }; Ok(verified.is_ok()) } pub(super) fn rsa_pss_salt_fits_key( key: &RsaPublicKey, digest: DigestAlgorithm, salt_len: usize, ) -> bool { let Some(em_bits) = key.n().bit_width().checked_sub(1) else { return false; }; let Ok(em_len) = usize::try_from(em_bits.div_ceil(8)) else { return false; }; digest .output_len() .checked_add(salt_len) .and_then(|length| length.checked_add(2)) .is_some_and(|required| required <= em_len) } pub(crate) fn compatible_rsa_pss_public_key_from_spki( spki_der: &[u8], signature_algorithm: X509SignatureAlgorithm, session: &CertificateSession, ) -> Option { let X509SignatureAlgorithm::RsaPss { digest, mgf_digest, salt_len, } = signature_algorithm else { return None; }; super::rsa_pss::public_key( spki_der, crate::xmldsig::RsaPssParameters { digest, mgf_digest, salt_len, }, session, ) .ok() } /// The `XMLDSig` algorithm whose verifier also checks this RSA PKCS#1 v1.5 /// or ECDSA certificate signature, when the digest has one. pub(super) const fn xml_signature_algorithm( algorithm: X509SignatureAlgorithm, ) -> Option { match algorithm { X509SignatureAlgorithm::RsaPkcs1v15(digest) => match digest { DigestAlgorithm::Sha1 => Some(SignatureAlgorithm::RsaSha1), DigestAlgorithm::Sha224 => Some(SignatureAlgorithm::RsaSha224), DigestAlgorithm::Sha256 => Some(SignatureAlgorithm::RsaSha256), DigestAlgorithm::Sha384 => Some(SignatureAlgorithm::RsaSha384), DigestAlgorithm::Sha512 => Some(SignatureAlgorithm::RsaSha512), _ => None, }, X509SignatureAlgorithm::Ecdsa(digest) => match digest { #[cfg(feature = "legacy-algorithms")] DigestAlgorithm::Md5 | DigestAlgorithm::Ripemd160 => None, #[cfg(feature = "dstu4145")] DigestAlgorithm::Gost34311 | DigestAlgorithm::Dstu7564_256 => None, DigestAlgorithm::Sha1 => Some(SignatureAlgorithm::EcdsaSha1), DigestAlgorithm::Sha224 => Some(SignatureAlgorithm::EcdsaSha224), DigestAlgorithm::Sha256 => Some(SignatureAlgorithm::EcdsaSha256), DigestAlgorithm::Sha384 => Some(SignatureAlgorithm::EcdsaSha384), DigestAlgorithm::Sha512 => Some(SignatureAlgorithm::EcdsaSha512), DigestAlgorithm::Sha3_224 => Some(SignatureAlgorithm::EcdsaSha3_224), DigestAlgorithm::Sha3_256 => Some(SignatureAlgorithm::EcdsaSha3_256), DigestAlgorithm::Sha3_384 => Some(SignatureAlgorithm::EcdsaSha3_384), DigestAlgorithm::Sha3_512 => Some(SignatureAlgorithm::EcdsaSha3_512), }, _ => None, } } fn unsupported(algorithm: X509SignatureAlgorithm) -> Result { Err(ProviderError::Unsupported { operation: super::ProviderOperation::VerifyCertificate, algorithm: algorithm.oid().map(str::to_owned), }) } } #[cfg(feature = "xmlenc")] mod rustcrypto { use crate::rustcrypto_sha3 as sha3; #[cfg(feature = "legacy-algorithms")] use aes::Aes192; use aes::{ Aes128, Aes256, cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::NoPadding}, }; use aes_gcm::{ Aes128Gcm, Aes256Gcm, Nonce, aead::{AeadInOut, KeyInit}, }; use aes_kw::{KwAes128, KwAes256}; use camellia::{Camellia128, Camellia192, Camellia256}; use cbc::{Decryptor, Encryptor}; #[cfg(feature = "legacy-algorithms")] use des::TdesEde3; use rsa::{Oaep, traits::PaddingScheme}; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; use super::{CryptoProvider, ProviderError, ProviderInputError}; use crate::xmlenc::{ DataEncryptionAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, RsaOaepParameters, }; pub(super) fn encrypt_data( provider: &dyn CryptoProvider, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError> { check_key(algorithm.key_len(), key)?; match algorithm { #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::TripleDesCbc => { encrypt_cbc::(provider, key, plaintext) } #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::Aes192Cbc => encrypt_cbc::(provider, key, plaintext), #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::Aes192Gcm => encrypt_gcm::< aes_gcm::AesGcm, >(provider, key, plaintext), DataEncryptionAlgorithm::Aes128Cbc => encrypt_cbc::(provider, key, plaintext), DataEncryptionAlgorithm::Aes256Cbc => encrypt_cbc::(provider, key, plaintext), // RFC 9231 §2.6.2 uses the AES CBC framing and padding with // Camellia substituted as the block cipher. // https://www.rfc-editor.org/rfc/rfc9231.html#section-2.6.2 DataEncryptionAlgorithm::Camellia128Cbc => { encrypt_cbc::(provider, key, plaintext) } DataEncryptionAlgorithm::Camellia192Cbc => { encrypt_cbc::(provider, key, plaintext) } DataEncryptionAlgorithm::Camellia256Cbc => { encrypt_cbc::(provider, key, plaintext) } DataEncryptionAlgorithm::Aes128Gcm => { encrypt_gcm::(provider, key, plaintext) } DataEncryptionAlgorithm::Aes256Gcm => { encrypt_gcm::(provider, key, plaintext) } DataEncryptionAlgorithm::ChaCha20 | DataEncryptionAlgorithm::ChaCha20Poly1305 => { Err(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("missing ChaCha parameters"), )) } } } pub(super) fn decrypt_data( algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], ) -> Result, ProviderError> { check_key(algorithm.key_len(), key)?; match algorithm { #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::TripleDesCbc => decrypt_cbc::(key, ciphertext), #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::Aes192Cbc => decrypt_cbc::(key, ciphertext), #[cfg(feature = "legacy-algorithms")] DataEncryptionAlgorithm::Aes192Gcm => { decrypt_gcm::>(key, ciphertext) } DataEncryptionAlgorithm::Aes128Cbc => decrypt_cbc::(key, ciphertext), DataEncryptionAlgorithm::Aes256Cbc => decrypt_cbc::(key, ciphertext), DataEncryptionAlgorithm::Camellia128Cbc => decrypt_cbc::(key, ciphertext), DataEncryptionAlgorithm::Camellia192Cbc => decrypt_cbc::(key, ciphertext), DataEncryptionAlgorithm::Camellia256Cbc => decrypt_cbc::(key, ciphertext), DataEncryptionAlgorithm::Aes128Gcm => decrypt_gcm::(key, ciphertext), DataEncryptionAlgorithm::Aes256Gcm => decrypt_gcm::(key, ciphertext), DataEncryptionAlgorithm::ChaCha20 | DataEncryptionAlgorithm::ChaCha20Poly1305 => { Err(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("missing ChaCha parameters"), )) } } } fn check_key(expected: usize, key: &[u8]) -> Result<(), ProviderError> { if key.len() == expected { Ok(()) } else { Err(ProviderError::InvalidKeySize { expected, actual: key.len(), }) } } fn encrypt_cbc( provider: &dyn CryptoProvider, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError> where C: aes::cipher::BlockCipherEncrypt + aes::cipher::KeyInit, { let block = C::block_size(); let pad_len = block - (plaintext.len() % block); let length = plaintext .len() .checked_add(pad_len) .and_then(|len| len.checked_add(block)) .ok_or(ProviderError::InvalidInput( ProviderInputError::AesCbcFraming, ))?; let mut output = zeroize::Zeroizing::new(vec![0_u8; length]); let (iv, padded) = output.split_at_mut(block); provider.fill_random(iv)?; padded[..plaintext.len()].copy_from_slice(plaintext); if pad_len > 1 { let last = padded.len() - 1; provider.fill_random(&mut padded[plaintext.len()..last])?; } *padded.last_mut().expect("padding is non-empty") = u8::try_from(pad_len).expect("CBC block padding fits in one octet"); Encryptor::::new_from_slices(key, iv) .map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization("AES-CBC")) })? .encrypt_padded::(padded, plaintext.len() + pad_len) .map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization( "AES-CBC padding", )) })?; Ok(core::mem::take(&mut *output)) } fn decrypt_cbc(key: &[u8], ciphertext: &[u8]) -> Result, ProviderError> where C: aes::cipher::BlockCipherDecrypt + aes::cipher::KeyInit, { let block = C::block_size(); if ciphertext.len() < 2 * block || !(ciphertext.len() - block).is_multiple_of(block) { return Err(ProviderError::InvalidInput( ProviderInputError::AesCbcFraming, )); } let (iv, body) = ciphertext.split_at(block); let mut plaintext = zeroize::Zeroizing::new(body.to_vec()); Decryptor::::new_from_slices(key, iv) .map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization("AES-CBC")) })? .decrypt_padded::(&mut plaintext) .map_err(|_| ProviderError::InvalidInput(ProviderInputError::AesCbcCiphertext))?; let pad_len = *plaintext.last().ok_or(ProviderError::InvalidInput( ProviderInputError::AesCbcCiphertext, ))?; let padding_bytes = usize::from(pad_len); if !(1..=block).contains(&padding_bytes) || padding_bytes > plaintext.len() { return Err(ProviderError::InvalidInput( ProviderInputError::AesCbcCiphertext, )); } let length = plaintext.len() - padding_bytes; plaintext.truncate(length); Ok(core::mem::take(&mut *plaintext)) } fn encrypt_gcm( provider: &dyn CryptoProvider, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError> where C: AeadInOut + KeyInit, { let mut nonce = [0_u8; 12]; provider.fill_random(&mut nonce)?; let cipher = C::new_from_slice(key).map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization("AES-GCM")) })?; let length = plaintext .len() .checked_add(28) .ok_or(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("AES-GCM length"), ))?; // Encrypt directly in the final nonce-prefixed allocation rather than // copying an intermediate ciphertext into another document-sized Vec. let mut output = zeroize::Zeroizing::new(Vec::with_capacity(length)); output.extend_from_slice(&nonce); output.extend_from_slice(plaintext); let nonce = Nonce::try_from(nonce.as_slice()).map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization( "AES-GCM nonce", )) })?; let tag = cipher .encrypt_inout_detached(&nonce, &[], output[12..].as_mut().into()) .map_err(|_| ProviderError::AuthenticationFailed)?; output.extend_from_slice(&tag); Ok(core::mem::take(&mut *output)) } fn decrypt_gcm(key: &[u8], ciphertext: &[u8]) -> Result, ProviderError> where C: AeadInOut + KeyInit, { if ciphertext.len() < 28 { return Err(ProviderError::InvalidInput( ProviderInputError::AesGcmFraming, )); } let (nonce, body) = ciphertext.split_at(12); let cipher = C::new_from_slice(key).map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization("AES-GCM")) })?; let mut plaintext = zeroize::Zeroizing::new(body.to_vec()); let nonce = Nonce::try_from(nonce).map_err(|_| { ProviderError::InvalidInput(ProviderInputError::PrimitiveInitialization( "AES-GCM nonce", )) })?; cipher .decrypt_in_place(&nonce, &[], &mut *plaintext) .map_err(|_| ProviderError::AuthenticationFailed)?; Ok(core::mem::take(&mut *plaintext)) } pub(super) fn wrap_key( provider: &dyn CryptoProvider, algorithm: KeyWrapAlgorithm, kek: &[u8], key: &[u8], ) -> Result, ProviderError> { check_key(algorithm.key_len(), kek)?; if let KeyWrapAlgorithm::Cbc(cipher) = algorithm { if cipher.cbc_block_len().is_none() { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } // XMLEnc 1.1 §3.4 defines EncryptedKey as EncryptedType; CBC // encrypts key bytes using the same padding/IV contract as data. // https://www.w3.org/TR/xmlenc-core1/#sec-EncryptedKey return encrypt_data(provider, cipher, kek, key); } #[cfg(feature = "legacy-algorithms")] if algorithm == KeyWrapAlgorithm::TripleDes { return wrap_des3(provider, kek, key); } #[cfg(not(feature = "legacy-algorithms"))] let _ = provider; // RFC 3394 §2.2.1 requires at least two 64-bit plaintext blocks. // Reject before allocating: the primitive also accepts the one-block // degenerate case, which is not this XML Encryption algorithm. // https://www.rfc-editor.org/rfc/rfc3394.html#section-2.2.1 if key.len() < 16 || !key.len().is_multiple_of(8) { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } let length = algorithm .wrapped_len(key.len()) .ok_or(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, ))?; let mut output = vec![0_u8; length]; // RFC 9231 §2.6.3 specifies RFC 3394 with Camellia replacing AES, // including the same A6 integrity register. Reuse its generic engine. // https://www.rfc-editor.org/rfc/rfc9231.html#section-2.6.3 macro_rules! camellia_wrap { ($cipher:ty) => { aes_kw::AesKw::<$cipher>::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: algorithm.key_len(), actual: kek.len(), })? .wrap_key(key, &mut output) }; } match algorithm { KeyWrapAlgorithm::Cbc(_) => unreachable!("CBC wrapping already dispatched"), KeyWrapAlgorithm::CamelliaKw128 => camellia_wrap!(Camellia128), KeyWrapAlgorithm::CamelliaKw192 => camellia_wrap!(Camellia192), KeyWrapAlgorithm::CamelliaKw256 => camellia_wrap!(Camellia256), #[cfg(feature = "legacy-algorithms")] KeyWrapAlgorithm::AesKw192 => aes_kw::KwAes192::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 24, actual: kek.len(), })? .wrap_key(key, &mut output), #[cfg(feature = "legacy-algorithms")] KeyWrapAlgorithm::TripleDes => unreachable!("CMS wrapping dispatched before AES"), KeyWrapAlgorithm::AesKw128 => KwAes128::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 16, actual: kek.len(), })? .wrap_key(key, &mut output), KeyWrapAlgorithm::AesKw256 => KwAes256::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 32, actual: kek.len(), })? .wrap_key(key, &mut output), } .map_err(|_| ProviderError::InvalidInput(ProviderInputError::AesKeyWrapFraming))?; Ok(output) } pub(super) fn unwrap_key( algorithm: KeyWrapAlgorithm, kek: &[u8], wrapped: &[u8], ) -> Result, ProviderError> { check_key(algorithm.key_len(), kek)?; if let KeyWrapAlgorithm::Cbc(cipher) = algorithm { if cipher.cbc_block_len().is_none() { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } return decrypt_data(cipher, kek, wrapped); } #[cfg(feature = "legacy-algorithms")] if algorithm == KeyWrapAlgorithm::TripleDes { return unwrap_des3(kek, wrapped); } if wrapped.len() < 24 || !wrapped.len().is_multiple_of(8) { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } let mut output = zeroize::Zeroizing::new(vec![0_u8; wrapped.len() - 8]); macro_rules! camellia_unwrap { ($cipher:ty) => { aes_kw::AesKw::<$cipher>::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: algorithm.key_len(), actual: kek.len(), })? .unwrap_key(wrapped, &mut output) }; } match algorithm { KeyWrapAlgorithm::Cbc(_) => unreachable!("CBC unwrapping already dispatched"), KeyWrapAlgorithm::CamelliaKw128 => camellia_unwrap!(Camellia128), KeyWrapAlgorithm::CamelliaKw192 => camellia_unwrap!(Camellia192), KeyWrapAlgorithm::CamelliaKw256 => camellia_unwrap!(Camellia256), #[cfg(feature = "legacy-algorithms")] KeyWrapAlgorithm::AesKw192 => aes_kw::KwAes192::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 24, actual: kek.len(), })? .unwrap_key(wrapped, &mut output), #[cfg(feature = "legacy-algorithms")] KeyWrapAlgorithm::TripleDes => unreachable!("CMS unwrapping dispatched before AES"), KeyWrapAlgorithm::AesKw128 => KwAes128::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 16, actual: kek.len(), })? .unwrap_key(wrapped, &mut output), KeyWrapAlgorithm::AesKw256 => KwAes256::new_from_slice(kek) .map_err(|_| ProviderError::InvalidKeySize { expected: 32, actual: kek.len(), })? .unwrap_key(wrapped, &mut output), } .map_err(|_| ProviderError::AuthenticationFailed)?; Ok(core::mem::take(&mut *output)) } #[cfg(feature = "legacy-algorithms")] const CMS_IV: [u8; 8] = [0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05]; #[cfg(feature = "legacy-algorithms")] fn wrap_des3( provider: &dyn CryptoProvider, kek: &[u8], key: &[u8], ) -> Result, ProviderError> { use sha1::Digest as _; // XMLEnc 1.1 §5.7.1 permits other key types in addition to RFC 3217's // DES CEK. Preserve opaque key octets (especially AES); generated DES // content keys are normalized to odd parity at their generation boundary. // https://www.w3.org/TR/xmlenc-core1/#sec-CMS-3DES if key.len() < 16 || !key.len().is_multiple_of(8) { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } let length = key .len() .checked_add(16) .ok_or(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, ))?; let mut output = zeroize::Zeroizing::new(vec![0; length]); provider.fill_random(&mut output[..8])?; output[8..8 + key.len()].copy_from_slice(key); output[8 + key.len()..].copy_from_slice(&Sha1::digest(key)[..8]); let (iv, body) = output.split_at_mut(8); let body_len = body.len(); Encryptor::::new_from_slices(kek, iv) .map_err(|_| ProviderError::AuthenticationFailed)? .encrypt_padded::(body, body_len) .map_err(|_| ProviderError::AuthenticationFailed)?; output.reverse(); Encryptor::::new_from_slices(kek, &CMS_IV) .map_err(|_| ProviderError::AuthenticationFailed)? .encrypt_padded::(&mut output, length) .map_err(|_| ProviderError::AuthenticationFailed)?; Ok(core::mem::take(&mut *output)) } #[cfg(feature = "legacy-algorithms")] fn unwrap_des3(kek: &[u8], wrapped: &[u8]) -> Result, ProviderError> { use sha1::Digest as _; use subtle::ConstantTimeEq as _; // RFC 3217 §3.2: undo the outer CBC, octet reversal and inner CBC, // then authenticate the checksum before exposing any recovered bytes. // https://www.rfc-editor.org/rfc/rfc3217#section-3.2 if wrapped.len() < 32 || !wrapped.len().is_multiple_of(8) { return Err(ProviderError::InvalidInput( ProviderInputError::AesKeyWrapFraming, )); } let mut output = zeroize::Zeroizing::new(wrapped.to_vec()); Decryptor::::new_from_slices(kek, &CMS_IV) .map_err(|_| ProviderError::AuthenticationFailed)? .decrypt_padded::(&mut output) .map_err(|_| ProviderError::AuthenticationFailed)?; output.reverse(); let (iv, body) = output.split_at_mut(8); Decryptor::::new_from_slices(kek, iv) .map_err(|_| ProviderError::AuthenticationFailed)? .decrypt_padded::(body) .map_err(|_| ProviderError::AuthenticationFailed)?; let key_len = body.len() - 8; if !bool::from(Sha1::digest(&body[..key_len])[..8].ct_eq(&body[key_len..])) { return Err(ProviderError::AuthenticationFailed); } output.copy_within(8..8 + key_len, 0); zeroize::Zeroize::zeroize(&mut output[key_len..]); output.truncate(key_len); Ok(core::mem::take(&mut *output)) } #[cfg(feature = "legacy-algorithms")] pub(super) fn transport_pkcs1v15( provider: &dyn CryptoProvider, key: &rsa::RsaPublicKey, plaintext: &[u8], ) -> Result, ProviderError> { rsa::Pkcs1v15Encrypt .encrypt(&mut super::ProviderRng(provider), key, plaintext) .map_err(|error| map_rsa_error(&error)) } pub(super) fn transport_key( provider: &dyn CryptoProvider, key: &rsa::RsaPublicKey, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError> { if parameters.algorithm.requires_explicit_permission() { return Err(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("RSA-OAEP algorithm"), )); } run_oaep(provider, parameters, OaepOperation::Encrypt(key), plaintext) } pub(super) fn recover_key( provider: &dyn CryptoProvider, key: &rsa::RsaPrivateKey, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError> { if parameters.algorithm.requires_explicit_permission() { return Err(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("RSA-OAEP algorithm"), )); } run_oaep( provider, parameters, OaepOperation::Decrypt(key), ciphertext, ) } #[derive(Clone, Copy)] enum OaepOperation<'a> { Encrypt(&'a rsa::RsaPublicKey), Decrypt(&'a rsa::RsaPrivateKey), } fn run_oaep( provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, operation: OaepOperation<'_>, input: &[u8], ) -> Result, ProviderError> { // RFC 8017 §7.1 selects Hash and MGF1's hash independently. One // dispatch keeps encryption and blinded recovery on the same contract. // https://www.rfc-editor.org/rfc/rfc8017.html#section-7.1 match parameters.digest { #[cfg(feature = "legacy-algorithms")] OaepDigestAlgorithm::Md5 => { run_oaep_mgf::(provider, parameters, operation, input) } #[cfg(feature = "legacy-algorithms")] OaepDigestAlgorithm::Ripemd160 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha3_224 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha3_256 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha3_384 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha3_512 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha1 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha224 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha256 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha384 => { run_oaep_mgf::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha512 => { run_oaep_mgf::(provider, parameters, operation, input) } } } fn run_oaep_mgf( provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, operation: OaepOperation<'_>, input: &[u8], ) -> Result, ProviderError> { match parameters.mgf_digest { OaepDigestAlgorithm::Sha1 => { run_oaep_primitive::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha224 => { run_oaep_primitive::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha256 => { run_oaep_primitive::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha384 => { run_oaep_primitive::(provider, parameters, operation, input) } OaepDigestAlgorithm::Sha512 => { run_oaep_primitive::(provider, parameters, operation, input) } _ => Err(ProviderError::InvalidInput( ProviderInputError::PrimitiveInitialization("unregistered XML OAEP mask digest"), )), } } fn run_oaep_primitive( provider: &dyn CryptoProvider, parameters: &RsaOaepParameters, operation: OaepOperation<'_>, input: &[u8], ) -> Result, ProviderError> where D: sha2::digest::Digest + sha2::digest::FixedOutputReset, M: sha2::digest::Digest + sha2::digest::FixedOutputReset, { let mut rng = super::ProviderRng(provider); let padding = Oaep::::new_with_mgf_hash_and_label(parameters.label.clone()); match operation { OaepOperation::Encrypt(key) => padding.encrypt(&mut rng, key, input), OaepOperation::Decrypt(key) => padding.decrypt(Some(&mut rng), key, input), } .map_err(|error| map_rsa_error(&error)) } pub(super) fn map_rsa_error(error: &rsa::Error) -> ProviderError { match error { rsa::Error::Rng => ProviderError::Random("RSA randomness failed".into()), _ => ProviderError::AuthenticationFailed, } } } #[cfg(test)] mod tests { #[cfg(feature = "xmldsig")] use std::sync::atomic::AtomicUsize; use std::sync::atomic::{AtomicBool, Ordering}; use super::*; #[cfg(feature = "xmlenc")] #[test] fn recovered_candidate_equality_includes_every_byte_and_validity() { // Deduplication must retain distinct candidates, including equal real/ // rejection bytes with different validity. Timing is ensured by ct_eq, // not by a flaky wall-clock assertion. let reference = RecoveredContentKey::confirmed(vec![0x52; 32]); assert!(reference.same_candidate(&RecoveredContentKey::confirmed(vec![0x52; 32]))); for index in 0..32 { let mut bytes = vec![0x52; 32]; bytes[index] ^= 1; assert!(!reference.same_candidate(&RecoveredContentKey::confirmed(bytes))); } assert!(!reference.same_candidate(&RecoveredContentKey::confirmed(vec![0x52; 16]))); for left in [0, 1] { for right in [0, 1] { let mut a = RecoveredContentKey::confirmed(vec![0x52; 32]); let mut b = RecoveredContentKey::confirmed(vec![0x52; 32]); a.valid = subtle::Choice::from(left); b.valid = subtle::Choice::from(right); assert_eq!(a.same_candidate(&b), left == right); } } } #[cfg(all(feature = "legacy-algorithms", feature = "xmlenc"))] #[test] fn legacy_ciphers_cover_empty_and_block_boundary_plaintexts() { // Each cipher's own framing, including DES's 8-byte blocks, must agree // with the public size preflight for empty and non-aligned messages. for algorithm in [ DataEncryptionAlgorithm::TripleDesCbc, DataEncryptionAlgorithm::Aes192Cbc, DataEncryptionAlgorithm::Aes192Gcm, ] { let key = [0x31; 24]; for length in [0, 1, 7, 8, 15, 16, 17, 31, 32, 33] { let plaintext = vec![0x41; length]; let ciphertext = RUST_CRYPTO_PROVIDER .encrypt_data(algorithm, &key, &plaintext) .expect("valid legacy encryption must succeed"); assert_eq!( Some(ciphertext.len()), algorithm.ciphertext_len_for_plaintext(length) ); assert_eq!( RUST_CRYPTO_PROVIDER .decrypt_data(algorithm, &key, &ciphertext) .expect("matching legacy key must recover plaintext"), plaintext ); assert!( RUST_CRYPTO_PROVIDER .encrypt_data(algorithm, &key[..23], &plaintext) .is_err() ); assert!( RUST_CRYPTO_PROVIDER .decrypt_data(algorithm, &key[..23], &ciphertext) .is_err() ); assert!( RUST_CRYPTO_PROVIDER .decrypt_data(algorithm, &key, &ciphertext[..ciphertext.len() - 1]) .is_err() ); if algorithm == DataEncryptionAlgorithm::Aes192Gcm { let mut corrupted = ciphertext; corrupted[12] ^= 1; assert!(matches!( RUST_CRYPTO_PROVIDER.decrypt_data(algorithm, &key, &corrupted), Err(ProviderError::AuthenticationFailed) )); } } } } #[cfg(all(feature = "legacy-algorithms", feature = "xmlenc"))] #[test] fn cms_triple_des_unwrap_matches_rfc3217_vector() { // RFC 3217 §3.4 independently validates both CBC passes and reversal; // a round-trip alone could hide matching errors in wrap and unwrap. // https://www.rfc-editor.org/rfc/rfc3217.html#section-3.4 let kek = hex_literal::hex!("255e0d1c07b646dfb3134cc843ba8aa71f025b7c0838251f"); let key = hex_literal::hex!("2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98"); let wrapped = hex_literal::hex!( "690107618ef092b3b48ca1796b234ae9fa33ebb4159604037db5d6a84eb3aac2768c632775a467d4" ); assert_eq!( RUST_CRYPTO_PROVIDER .unwrap_key(KeyWrapAlgorithm::TripleDes, &kek, &wrapped) .expect("RFC 3217 vector must authenticate"), key ); for offset in 0..wrapped.len() { let mut corrupted = wrapped; corrupted[offset] ^= 1; assert!(matches!( RUST_CRYPTO_PROVIDER.unwrap_key(KeyWrapAlgorithm::TripleDes, &kek, &corrupted), Err(ProviderError::AuthenticationFailed) )); } } #[cfg(all(feature = "legacy-algorithms", feature = "xmlenc"))] #[test] fn legacy_wrap_preserves_opaque_key_bytes_and_checks_integrity() { // XMLEnc permits wrapping non-DES keys; parity normalization must not // corrupt opaque AES key octets. Each algorithm has its own overhead. for algorithm in [KeyWrapAlgorithm::AesKw192, KeyWrapAlgorithm::TripleDes] { let kek = [0x37; 24]; for length in [16, 24, 32] { let key: Vec<_> = (0_u8..length).collect(); let mut wrapped = RUST_CRYPTO_PROVIDER .wrap_key(algorithm, &kek, &key) .expect("valid content key must wrap"); assert_eq!(Some(wrapped.len()), algorithm.wrapped_len(key.len())); assert_eq!( RUST_CRYPTO_PROVIDER .unwrap_key(algorithm, &kek, &wrapped) .expect("matching KEK must recover exact key octets"), key ); assert!( RUST_CRYPTO_PROVIDER .unwrap_key(algorithm, &[0x38; 24], &wrapped) .is_err() ); wrapped[0] ^= 1; assert!( RUST_CRYPTO_PROVIDER .unwrap_key(algorithm, &kek, &wrapped) .is_err() ); } for length in [0, 1, 8, 15, 17] { assert!( RUST_CRYPTO_PROVIDER .wrap_key(algorithm, &kek, &vec![0; length]) .is_err() ); } } } #[cfg(all(feature = "legacy-algorithms", feature = "xmldsig"))] #[test] fn legacy_digests_match_known_answers() { // Published empty/abc answers prevent URI wiring from silently choosing // a different hash with the same output width. for (algorithm, expected) in [ ( DigestAlgorithm::Md5, &hex_literal::hex!("900150983cd24fb0d6963f7d28e17f72")[..], ), ( DigestAlgorithm::Ripemd160, &hex_literal::hex!("8eb208f7e05d987a9b044a8e98c6b087f15a0bfc")[..], ), ] { assert_eq!( &*RUST_CRYPTO_PROVIDER .digest(algorithm, b"abc", &crate::xmldsig::digest::digest_memory()) .expect("compiled digest must be supported"), expected ); assert_eq!(DigestAlgorithm::from_uri(algorithm.uri()), Some(algorithm)); assert!(algorithm.requires_explicit_permission()); assert!(!algorithm.signing_allowed()); } } /// A provider defect: one byte more than the algorithm's digest width. #[cfg(feature = "xmldsig")] struct ExtraByteDigest(Box); #[cfg(feature = "xmldsig")] impl DigestState for ExtraByteDigest { fn update(&mut self, data: &[u8]) -> Result<(), ProviderError> { self.0.update(data) } fn finalize( &mut self, output: &mut [u8; crate::xmldsig::MAX_DIGEST_OUTPUT], ) -> Result { Ok(self.0.finalize(output)? + 1) } } #[cfg(feature = "xmldsig")] struct CountingRandomProvider { random_calls: AtomicUsize, reject_digest: Option, extra_digest_byte: bool, accept_signatures: bool, } #[cfg(feature = "xmldsig")] impl CryptoProvider for CountingRandomProvider { fn name(&self) -> &'static str { "counting-random" } fn supports(&self, capability: ProviderCapability<'_>) -> bool { RUST_CRYPTO_PROVIDER.supports(capability) } fn fill_random(&self, output: &mut [u8]) -> Result<(), ProviderError> { self.random_calls.fetch_add(1, Ordering::Relaxed); RUST_CRYPTO_PROVIDER.fill_random(output) } fn derive_key( &self, parameters: &KdfParameters<'_>, secret: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.derive_key(parameters, secret) } fn begin_digest( &self, algorithm: DigestAlgorithm, memory: &structured_asn1::memory::Memory, ) -> Result, ProviderError> { if self.reject_digest == Some(algorithm) { return Err(ProviderError::Unsupported { operation: ProviderOperation::Digest, algorithm: Some(algorithm.uri().to_owned()), }); } let state = RUST_CRYPTO_PROVIDER.begin_digest(algorithm, memory)?; Ok(if self.extra_digest_byte { Box::new(ExtraByteDigest(state)) } else { state }) } fn sign( &self, key: &dyn crate::xmldsig::SigningKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], ) -> Result, crate::xmldsig::SigningKeyError> { key.sign_with_provider(self, algorithm, data) } fn verify( &self, key: &dyn crate::xmldsig::VerifyingKey, algorithm: crate::xmldsig::SignatureAlgorithm, data: &[u8], signature: &[u8], ) -> Result { if self.accept_signatures { return Ok(true); } RUST_CRYPTO_PROVIDER.verify(key, algorithm, data, signature) } #[cfg(feature = "xmlenc")] fn encrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], plaintext: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.encrypt_data(algorithm, key, plaintext) } #[cfg(feature = "xmlenc")] fn decrypt_data( &self, algorithm: DataEncryptionAlgorithm, key: &[u8], ciphertext: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.decrypt_data(algorithm, key, ciphertext) } #[cfg(feature = "xmlenc")] fn wrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], key: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.wrap_key(algorithm, kek, key) } #[cfg(feature = "xmlenc")] fn unwrap_key( &self, algorithm: KeyWrapAlgorithm, kek: &[u8], wrapped: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.unwrap_key(algorithm, kek, wrapped) } #[cfg(feature = "xmlenc")] fn transport_key( &self, key: &dyn KeyTransportKey, parameters: &RsaOaepParameters, plaintext: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.transport_key(key, parameters, plaintext) } #[cfg(feature = "xmlenc")] fn recover_key( &self, key: &dyn KeyRecoveryKey, parameters: &RsaOaepParameters, ciphertext: &[u8], ) -> Result, ProviderError> { RUST_CRYPTO_PROVIDER.recover_key(key, parameters, ciphertext) } } #[cfg(feature = "xmldsig")] #[test] fn capability_query_is_explicit_about_unimplemented_operations() { assert!(RUST_CRYPTO_PROVIDER.supports(ProviderCapability::Digest(DigestAlgorithm::Sha256))); let agreement = KeyAgreementParameters { algorithm: "urn:unsupported:agreement", peer_public_key: &[], }; assert!(!RUST_CRYPTO_PROVIDER.supports(ProviderCapability::KeyAgreement(&agreement))); assert!(RUST_CRYPTO_PROVIDER.supports(ProviderCapability::Sign( crate::xmldsig::SignatureAlgorithm::RsaSha1 ))); assert!(RUST_CRYPTO_PROVIDER.supports(ProviderCapability::Verify( crate::xmldsig::SignatureAlgorithm::RsaSha1 ))); for digest in [DigestAlgorithm::Sha1, DigestAlgorithm::Sha512] { assert!( RUST_CRYPTO_PROVIDER.supports(ProviderCapability::VerifyCertificate( X509SignatureAlgorithm::Ecdsa(digest) )) ); } // Certificate capabilities must agree with the actual execution // mapping, not merely with the presence of an ECDSA primitive. #[cfg(feature = "legacy-algorithms")] for digest in [DigestAlgorithm::Md5, DigestAlgorithm::Ripemd160] { assert!( !RUST_CRYPTO_PROVIDER.supports(ProviderCapability::VerifyCertificate( X509SignatureAlgorithm::Ecdsa(digest) )) ); } } #[cfg(feature = "xmldsig-sign")] #[test] fn x509_digest_key_info_uses_the_selected_provider() { use crate::xmldsig::{ DigestAlgorithm, KeyInfoWriteError, KeyInfoWriter, RsaSigningKey, X509DigestKeyInfoWriter, }; // KeyInfo generation is part of the signing operation's provider // boundary; a writer must not silently fall back to RustCrypto. let key = RsaSigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/rsa/rsa-2048-key.pem" )) .expect("RSA fixture must parse"); let writer = X509DigestKeyInfoWriter::from_pem( include_str!("../tests/fixtures/keys/rsa/rsa-2048-cert.pem"), DigestAlgorithm::Sha224, ) .expect("certificate fixture must parse"); let provider = CountingRandomProvider { random_calls: AtomicUsize::new(0), reject_digest: Some(DigestAlgorithm::Sha224), extra_digest_byte: false, accept_signatures: false, }; let error = writer .write_key_info_with_provider(&key, &provider) .expect_err("the selected provider must control X509Digest"); assert!(matches!( error, KeyInfoWriteError::Provider(ProviderError::Unsupported { operation: ProviderOperation::Digest, .. }) )); } #[cfg(feature = "xmldsig-sign")] #[test] fn x509_digest_writer_rejects_trailing_certificate_der() { use crate::xmldsig::{DigestAlgorithm, KeyInfoWriteError, X509DigestKeyInfoWriter}; // The writer retains both digest bytes and the validated signing-key // identity, so construction must accept exactly one DER certificate. let (_, certificate) = x509_parser::pem::parse_x509_pem(include_bytes!( "../tests/fixtures/keys/rsa/rsa-2048-cert.pem" )) .expect("certificate fixture must parse"); let mut certificate_der = certificate.contents; certificate_der.push(0); assert!(matches!( X509DigestKeyInfoWriter::from_der(&certificate_der, DigestAlgorithm::Sha256), Err(KeyInfoWriteError::InvalidCertificateDer) )); } #[cfg(all(feature = "xmldsig", feature = "xmlenc"))] #[test] fn capability_queries_include_oaep_and_pss_parameters() { use crate::xmlenc::{KeyTransportAlgorithm, OaepDigestAlgorithm}; let explicit_legacy = RsaOaepParameters { algorithm: KeyTransportAlgorithm::RsaOaepMgf1p, digest: OaepDigestAlgorithm::Sha256, mgf_digest: OaepDigestAlgorithm::Sha256, label: Vec::new(), }; assert!(RUST_CRYPTO_PROVIDER.supports(ProviderCapability::KeyTransport(&explicit_legacy))); let modern = RsaOaepParameters::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha512) .label(b"label".to_vec()); assert!(RUST_CRYPTO_PROVIDER.supports(ProviderCapability::KeyTransport(&modern))); let supported_pss = X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha256, salt_len: 32, }; assert!( RUST_CRYPTO_PROVIDER.supports(ProviderCapability::VerifyCertificate(supported_pss)) ); let unsupported_pss = X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha384, salt_len: 32, }; assert!( !RUST_CRYPTO_PROVIDER.supports(ProviderCapability::VerifyCertificate(unsupported_pss)) ); } struct RecordingAgreementKey(AtomicBool); impl KeyAgreementKey for RecordingAgreementKey { fn agree( &self, _parameters: &KeyAgreementParameters<'_>, ) -> Result, ProviderError> { self.0.store(true, Ordering::Relaxed); Ok(vec![0x42]) } } #[test] fn unsupported_agreement_and_kdf_fail_without_dispatch_or_fallback() { let agreement = KeyAgreementParameters { algorithm: "urn:example:agreement", peer_public_key: b"peer", }; let key = RecordingAgreementKey(AtomicBool::new(false)); let error = RUST_CRYPTO_PROVIDER .agree_key(&key, &agreement) .expect_err("unsupported agreement must fail closed"); assert!(matches!( error, ProviderError::Unsupported { operation: ProviderOperation::KeyAgreement, algorithm: Some(ref algorithm), } if algorithm == agreement.algorithm )); assert!(!key.0.load(Ordering::Relaxed)); let kdf = KdfParameters { algorithm: "urn:example:kdf", digest: Some("urn:example:digest"), salt: b"salt", info: KdfContext::Octets(b"info"), iterations: 1, output_len: 32, }; assert!(matches!( RUST_CRYPTO_PROVIDER.derive_key(&kdf, b"secret"), Err(ProviderError::Unsupported { operation: ProviderOperation::Kdf, algorithm: Some(ref algorithm), }) if algorithm == kdf.algorithm )); } #[cfg(feature = "xmldsig-sign")] #[test] fn rsa_signing_uses_the_selected_providers_randomness() { use crate::xmldsig::{RsaSigningKey, SignatureAlgorithm}; // RSA PKCS#1 v1.5 uses randomness for blinding even though its wire // signature is deterministic; the selected provider owns that source. let key = RsaSigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/rsa/rsa-2048-key.pem" )) .expect("RSA fixture must parse"); let provider = CountingRandomProvider { random_calls: AtomicUsize::new(0), reject_digest: None, extra_digest_byte: false, accept_signatures: false, }; let signature = provider .sign(&key, SignatureAlgorithm::RsaSha256, b"signed info") .expect("RSA signing must succeed"); assert_ne!(signature, [] as [u8; 0]); assert!(provider.random_calls.load(Ordering::Relaxed) > 0); } #[cfg(feature = "xmldsig-sign")] #[test] fn ecdsa_signing_uses_the_selected_providers_digest() { use crate::xmldsig::{ EcdsaP256SigningKey, EcdsaP384SigningKey, SignatureAlgorithm, SigningKeyError, }; // SignatureMethod chooses the hash independently of the EC key curve. // Both built-in ECDSA keys must therefore ask the selected provider for // that digest instead of hashing behind the provider boundary. let cases: [( Box, SignatureAlgorithm, DigestAlgorithm, ); 2] = [ ( Box::new( EcdsaP256SigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/ec/ec-prime256v1-key.pem" )) .expect("P-256 fixture must parse"), ), SignatureAlgorithm::EcdsaSha384, DigestAlgorithm::Sha384, ), ( Box::new( EcdsaP384SigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/ec/ec-prime384v1-key.pem" )) .expect("P-384 fixture must parse"), ), SignatureAlgorithm::EcdsaSha256, DigestAlgorithm::Sha256, ), ]; for (key, signature_algorithm, digest_algorithm) in cases { let provider = CountingRandomProvider { random_calls: AtomicUsize::new(0), reject_digest: Some(digest_algorithm), extra_digest_byte: false, accept_signatures: false, }; let error = provider .sign(key.as_ref(), signature_algorithm, b"signed info") .expect_err("provider digest rejection must stop ECDSA signing"); assert!(matches!( error, SigningKeyError::Provider(ProviderError::Unsupported { operation: ProviderOperation::Digest, algorithm: Some(ref uri), }) if uri == digest_algorithm.uri() )); } } #[cfg(feature = "xmldsig-sign")] #[test] fn ecdsa_signing_rejects_provider_digests_with_the_wrong_length() { use crate::xmldsig::{ EcdsaP256SigningKey, EcdsaP384SigningKey, SignatureAlgorithm, SigningKeyError, }; // Prehash signers may truncate oversized input, so the provider // boundary must reject it before either curve receives the digest. let cases: [( Box, SignatureAlgorithm, usize, ); 2] = [ ( Box::new( EcdsaP256SigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/ec/ec-prime256v1-key.pem" )) .expect("P-256 fixture must parse"), ), SignatureAlgorithm::EcdsaSha256, 32, ), ( Box::new( EcdsaP384SigningKey::from_pkcs8_pem(include_str!( "../tests/fixtures/keys/ec/ec-prime384v1-key.pem" )) .expect("P-384 fixture must parse"), ), SignatureAlgorithm::EcdsaSha384, 48, ), ]; for (key, algorithm, expected) in cases { let provider = CountingRandomProvider { random_calls: AtomicUsize::new(0), reject_digest: None, extra_digest_byte: true, accept_signatures: false, }; let error = provider .sign(key.as_ref(), algorithm, b"signed info") .expect_err("an oversized provider digest must not reach ECDSA prehash signing"); assert!(matches!( error, SigningKeyError::Provider(ProviderError::InvalidOutputSize { operation: ProviderOperation::Digest, expected: actual_expected, actual, }) if actual_expected == expected && actual == expected + 1 )); } } #[cfg(feature = "xmldsig")] #[test] fn verification_facade_rejects_malformed_dsa_before_provider_dispatch() { use crate::xmldsig::{ DefaultKeyResolver, DsigStatus, FailureReason, SignatureAlgorithm, VerifyContext, }; let original = include_str!( "../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-dsa.xml" ); let value_start = original .find("") .expect("Merlin fixture must contain SignatureValue") + "".len(); let value_end = original[value_start..] .find("") .map(|offset| value_start + offset) .expect("Merlin fixture must close SignatureValue"); let mut malformed = original.to_owned(); malformed.replace_range(value_start..value_end, "AQ=="); let provider = CountingRandomProvider { random_calls: AtomicUsize::new(0), reject_digest: None, extra_digest_byte: false, accept_signatures: true, }; let mut policy = crate::policy::VerificationPolicy::default(); policy .key_trust .allowed_legacy_signature_algorithms .insert(SignatureAlgorithm::DsaSha1); policy.key_trust.dsa_keys.minimum_modulus_bits = 1024; policy.key_trust.mode = crate::policy::VerificationTrustMode::CryptographicOnly; let result = VerifyContext::new() .policy(policy) .provider(&provider) .key_resolver(&DefaultKeyResolver::default()) .verify(&malformed) .expect("malformed framing must be a verification miss"); assert_eq!( result.status, DsigStatus::Invalid(FailureReason::SignatureMismatch) ); } #[cfg(feature = "xmldsig")] #[test] fn rustcrypto_provider_verifies_parameterized_rsa_pss_certificates() { use crate::rsa_encoding::RsaPublicKeyEncoding as _; use der::{Decode as _, Encode as _}; use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; use rsa::{RsaPrivateKey, pss::SigningKey as RsaPssSigningKey}; use sha2::Sha256; use signature::{RandomizedSigner, SignatureEncoding}; use x509_cert::spki::{AlgorithmIdentifierOwned, ObjectIdentifier}; const SHA256_PSS: X509SignatureAlgorithm = X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha256, salt_len: 32, }; // X.509 RSASSA-PSS carries salt and MGF parameters that cannot be // represented by the XMLDSig SignatureAlgorithm enum. let mut rng = ChaCha20Rng::from_seed([0x5a; 32]); let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("deterministic RSA key generation"); let public_key = private_key .to_public_key() .to_public_key_der() .expect("RSA public key must encode as SPKI"); let signing_key = RsaPssSigningKey::::new_with_salt_len(private_key, 32); let signed_data = b"certificate tbs bytes"; let signature = signing_key .try_sign_with_rng(&mut rng, signed_data) .expect("RSA-PSS signing must succeed") .to_vec(); assert!( RUST_CRYPTO_PROVIDER .verify_x509_signature(SHA256_PSS, signed_data, &signature, public_key.as_bytes()) .expect("standard RSA-PSS parameters must be supported") ); // The same key under the id-RSASSA-PSS identifier with `parameters`. let pss_key = |parameters| { let mut spki = x509_cert::SubjectPublicKeyInfo::from_der(public_key.as_bytes()) .expect("RSA SPKI must decode"); spki.algorithm = AlgorithmIdentifierOwned { oid: ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10"), parameters, }; spki.to_der().expect("PSS SPKI must encode") }; let parameterless_pss_spki = pss_key(None); assert!( RUST_CRYPTO_PROVIDER .verify_x509_signature(SHA256_PSS, signed_data, &signature, ¶meterless_pss_spki) .expect("parameterless PSS keys impose no signature restrictions") ); let pss_parameters = der::asn1::Any::from_der(&[ 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20, ]) .expect("standard SHA-256 PSS parameters must decode"); let pss_spki = pss_key(Some(pss_parameters)); assert!( RUST_CRYPTO_PROVIDER .verify_x509_signature(SHA256_PSS, signed_data, &signature, &pss_spki) .expect("RFC 4055 PSS SubjectPublicKeyInfo must be supported") ); for incompatible in [ X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha384, mgf_digest: DigestAlgorithm::Sha256, salt_len: 32, }, X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha384, salt_len: 32, }, X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha256, salt_len: 16, }, ] { assert!( !RUST_CRYPTO_PROVIDER .verify_x509_signature(incompatible, signed_data, &signature, &pss_spki,) .expect("incompatible PSS key restrictions are invalid, not unsupported") ); } } #[cfg(feature = "xmldsig")] #[test] fn rustcrypto_provider_verifies_dsa_certificate_signature_at_q_width() { use base64::Engine as _; // OpenSSL-generated L=2048/N=224 DSA material. X.509 carries DER r/s // integers at q width, not XMLDSig's legacy fixed 20-byte components. let spki = base64::engine::general_purpose::STANDARD .decode("MIIDQzCCAjYGByqGSM44BAEwggIpAoIBAQDEkm7mUEj1dizQRRrcU6ehyhpQ1NAkcKi9XyNcBJDZlyTdVH09XZ04UZNuXAWRL1hEDvDAvFimuwmW7k099j0PRM+WypsfOOgZPJhIVNZu9poTPGINKpbMTXFmR+qhrYM4z+NSKxuUBWZwX5HibBIG5INbx8IDHWAxZqxgHQsebDej1+yZyCTTpmDS9nKGkBRVaxsJgZt958UPNlIz1ECf4n4P4mPLAl7W5xV8VSWMqlXdkOAPbLC/mChjFoCj0jmCQpbcOvd7a6cWhcyhw/yikoVoKEPNWr9xLtdJV37f1/4q/xTvoPKWhMmgMQ/DigUnYgPzmexyS82m5HLZ/vOJAh0A/ckrg9g9PsZesUsH/4bEijeNwWGXB5e+/LCt0QKCAQEAuBGFzyjZEmvbDKbb+8tz+zqw4lK7RGwOjVM3v9xPS6LuG5L1OwCNQcUcVIsU9VxBnEx9oMnl8eVX1nq3kfdiZB2F9ESxwX5FzBt+KLjMOzBa8rPlzVcyCZ3sT3orAQ2D/q7ffDhTCUt+v8UNiAhVbaNnR/vI7AkVoP9crRjpOSV/7b5MGa0BcjIyEzTtqM58wppfSQt8jkj7WT3+Bww/Y9rOtshDE2QosaX/7xoDnzyeZ3amLjTe3/MjBcsKlbK2z4QuaI6xoQBVd/QjP8FjXpZBhXWFIAsOL/sz6uR2Er0ovdX8DBA0EJpuzlTX94Lvf+Eh+5/83ESAm97fk4pnhQOCAQUAAoIBAEwSwKuLFPeR7UJGXkWM9egyYewhqHpIXPBEWOVPqwTw3xLc3EkufpYY9wkhJS08KD+J92jMjm//0bYeVf7fXisc6PHtGY4wx5XBm1g9HKw9lwRjbk7nH495dlZdl0BXHa14TJ8myE2zOM1jsaFyz6jAFTaRnKYIj6WlKOj59d2iAXtLZRme9r+7U4G6zDUkphyIEcIGH4vhb6gm3URr1zAV5kJjTlsPAiqgeH/PgxU52tmvLphJgv/xPxsuX5W0/s7iKbphIb2YWh/gtTWXvRQHiQQ2fCncI3TAMnZ75dBY0gPOVLQJhUyffeRbk9UULux/jc8QBPgKBS7GM5DnNSw=") .expect("DSA SPKI fixture must decode"); let signature = base64::engine::general_purpose::STANDARD .decode("MD0CHQChtB1c+f5BmTJCtT7Gi4cyQiR2igj0znRQYCJ3Ahw4NGg4pL5jgA8Ri07ESV9Yr90WfUmRrbRcnjsY") .expect("DSA signature fixture must decode"); let message = b"certificate tbs bytes for dsa q-width regression"; assert!( rustcrypto_x509::verify_signature( X509SignatureAlgorithm::Dsa(DigestAlgorithm::Sha1), message, &signature, &spki, ) .expect("supported DSA-SHA1 certificate signature") ); let mut tampered = signature; *tampered.last_mut().expect("DER signature is non-empty") ^= 1; assert!( !rustcrypto_x509::verify_signature( X509SignatureAlgorithm::Dsa(DigestAlgorithm::Sha1), message, &tampered, &spki, ) .expect("tampered DSA-SHA1 certificate signature is a verification miss") ); } #[cfg(feature = "xmldsig")] #[test] fn primitive_provider_does_not_embed_rsa_strength_policy() { use crate::rsa_encoding::RsaPublicKeyEncoding as _; use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; use rsa::{RsaPrivateKey, pss::SigningKey as RsaPssSigningKey}; use sha2::Sha256; use signature::{RandomizedSigner, SignatureEncoding}; let mut rng = ChaCha20Rng::from_seed([0x3c; 32]); let private_key = RsaPrivateKey::new(&mut rng, 1024).expect("deterministic weak RSA key generation"); let public_key = private_key .to_public_key() .to_public_key_der() .expect("weak RSA public key must encode as SPKI"); let signed_data = b"certificate tbs bytes"; let signature = RsaPssSigningKey::::new_with_salt_len(private_key, 32) .try_sign_with_rng(&mut rng, signed_data) .expect("weak RSA-PSS key can still produce a cryptographic signature") .to_vec(); assert!( RUST_CRYPTO_PROVIDER .verify_x509_signature( X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha256, salt_len: 32, }, signed_data, &signature, public_key.as_bytes(), ) .expect( "provider must evaluate structurally valid RSA-PSS independently of policy" ) ); } #[cfg(feature = "xmldsig")] #[test] fn oversized_rsa_pss_salt_is_a_verification_miss() { use crate::rsa_encoding::RsaPublicKeyEncoding as _; use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; use rsa::{RsaPrivateKey, traits::PublicKeyParts as _}; // ASN.1 saltLength is attacker-controlled. It must not reach the // dependency's unchecked hLen + saltLen + 2 arithmetic. let mut rng = ChaCha20Rng::from_seed([0x55; 32]); let public_key = RsaPrivateKey::new(&mut rng, 1024) .expect("deterministic RSA key generation") .to_public_key(); let spki = public_key .to_public_key_der() .expect("RSA public key must encode as SPKI"); assert!(rustcrypto_x509::rsa_pss_salt_fits_key( &public_key, DigestAlgorithm::Sha256, 0, )); assert!(rustcrypto_x509::rsa_pss_salt_fits_key( &public_key, DigestAlgorithm::Sha256, 94, )); assert!(!rustcrypto_x509::rsa_pss_salt_fits_key( &public_key, DigestAlgorithm::Sha256, 95, )); assert!( !RUST_CRYPTO_PROVIDER .verify_x509_signature( X509SignatureAlgorithm::RsaPss { digest: DigestAlgorithm::Sha256, mgf_digest: DigestAlgorithm::Sha256, salt_len: usize::MAX, }, b"certificate tbs bytes", &vec![0_u8; public_key.size()], spki.as_bytes(), ) .expect("oversized PSS salt must fail without panicking") ); } }