#![cfg(feature = "xmlenc")]
use xml_sec::provider::{
CryptoProvider, KdfContext, KdfParameters, KeyAgreementParameters, RUST_CRYPTO_PROVIDER,
RustCryptoX25519Key,
};
#[test]
fn ecdh_all_nist_curves_preserve_fixed_width_and_validate_peers() {
use xml_sec::provider::{EcdhCurve, RustCryptoEcdhKey};
for (curve, width) in [
(EcdhCurve::P256, 32),
(EcdhCurve::P384, 48),
(EcdhCurve::P521, 66),
] {
let mut scalar = vec![0; width];
scalar[width - 1] = 1;
let key = RustCryptoEcdhKey::from_scalar(curve, &scalar).unwrap();
let public = key.public_key();
let mut parameters = KeyAgreementParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#ECDH-ES",
peer_public_key: &public,
};
assert_eq!(
RUST_CRYPTO_PROVIDER.agree_key(&key, ¶meters).unwrap(),
public[1..1 + width]
);
parameters.peer_public_key = &[0];
assert!(RUST_CRYPTO_PROVIDER.agree_key(&key, ¶meters).is_err());
assert!(RustCryptoEcdhKey::from_scalar(curve, &vec![0; width]).is_err());
}
}
fn hex(value: &str) -> Vec<u8> {
let (pairs, remainder) = value.as_bytes().as_chunks::<2>();
assert!(remainder.is_empty(), "odd test hex width");
pairs
.iter()
.map(|pair| {
let digit = |byte: u8| match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 10,
_ => panic!("invalid test hex"),
};
digit(pair[0]) * 16 + digit(pair[1])
})
.collect()
}
#[test]
fn legacy_dh_kdf_matches_xmlenc_normative_example() {
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2001/04/xmlenc#dh",
digest: Some("http://www.w3.org/2000/09/xmldsig#sha1"),
salt: &[],
info: KdfContext::LegacyDh {
encryption_algorithm: "Example:Block/Alg",
nonce: b"foo",
},
iterations: 0,
output_len: 10,
};
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &hex("deadbeef"))
.unwrap(),
hex("59d9ba5e06072c119409")
);
let mut invalid = parameters;
invalid.output_len = 255 * 20 + 1;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(&invalid, b"secret")
.is_err()
);
invalid = parameters;
invalid.info = KdfContext::Octets(b"Example:Block/Algfoo80");
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(&invalid, b"secret")
.is_err()
);
}
#[test]
fn legacy_dh_counter_reaches_ff_without_wrapping_or_decimal_encoding() {
use sha1::Digest as _;
let output_len = 255 * 20;
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2001/04/xmlenc#dh",
digest: Some("http://www.w3.org/2000/09/xmldsig#sha1"),
salt: &[],
info: KdfContext::LegacyDh {
encryption_algorithm: "urn:consuming-cipher",
nonce: b"nonce",
},
iterations: 0,
output_len,
};
let actual = RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap();
for (index, block) in actual.as_chunks::<20>().0.iter().enumerate() {
let mut hash = sha1::Sha1::new();
hash.update(b"secret");
hash.update(format!("{:02X}", index + 1).as_bytes());
hash.update(b"urn:consuming-cipher");
hash.update(b"nonce");
hash.update((output_len * 8).to_string().as_bytes());
assert_eq!(block.as_slice(), hash.finalize().as_slice());
}
}
#[test]
fn hkdf_matches_rfc5869_case_one() {
let salt = hex("000102030405060708090a0b0c");
let info = hex("f0f1f2f3f4f5f6f7f8f9");
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#hkdf",
digest: Some("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"),
salt: &salt,
info: KdfContext::Octets(&info),
iterations: 0,
output_len: 42,
};
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &[0x0b; 22])
.unwrap(),
hex("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
);
}
#[test]
fn pbkdf2_matches_rfc6070_and_rejects_invalid_parameters() {
let mut parameters = KdfParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#pbkdf2",
digest: Some("http://www.w3.org/2000/09/xmldsig#hmac-sha1"),
salt: b"salt",
info: KdfContext::Octets(&[]),
iterations: 1,
output_len: 20,
};
for (iterations, expected) in [
(1, "0c60c80f961f0e71f3a9b524af6012062fe037a6"),
(2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
(4096, "4b007901b765489abead49d926f721d065a429c1"),
] {
parameters.iterations = iterations;
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"password")
.unwrap(),
hex(expected)
);
}
parameters.salt = b"sa\0lt";
parameters.output_len = 16;
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"pass\0word")
.unwrap(),
hex("56fa6aa75548099dcc37d7f03425e0c3")
);
parameters.iterations = 0;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"password")
.is_err()
);
parameters.iterations = 1;
parameters.output_len = 0;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"password")
.is_err()
);
parameters.output_len = 16;
parameters.info = KdfContext::Octets(b"not a PBKDF2 parameter");
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"password")
.is_err()
);
parameters.info = KdfContext::Octets(&[]);
parameters.digest = None;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"password")
.is_err()
);
}
#[test]
fn pbkdf2_all_prfs_match_rustcrypto_across_output_blocks() {
use xml_sec::xmldsig::SignatureAlgorithm;
macro_rules! check {
($digest:ty, $prf:expr, $width:expr) => {{
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#pbkdf2",
digest: Some($prf.uri()),
salt: b"salt",
info: KdfContext::Octets(&[]),
iterations: 3,
output_len: 2 * $width + 1,
};
let password = [0xa5; 200];
let mut expected = vec![0; parameters.output_len];
pbkdf2::pbkdf2_hmac::<$digest>(&password, parameters.salt, 3, &mut expected);
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &password)
.unwrap(),
expected
);
}};
}
check!(sha1::Sha1, SignatureAlgorithm::HmacSha1, 20);
check!(sha2::Sha224, SignatureAlgorithm::HmacSha224, 28);
check!(sha2::Sha256, SignatureAlgorithm::HmacSha256, 32);
check!(sha2::Sha384, SignatureAlgorithm::HmacSha384, 48);
check!(sha2::Sha512, SignatureAlgorithm::HmacSha512, 64);
}
#[test]
fn concat_kdf_hashes_counter_secret_and_context_without_hmac_or_salt() {
use sha2::Digest;
for length in [0, 1, 19, 20, 55, 56, 63, 64, 65, 111, 112, 127, 128, 129] {
let info = vec![0x5a; length];
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#ConcatKDF",
digest: Some("http://www.w3.org/2001/04/xmlenc#sha256"),
salt: &[],
info: KdfContext::Octets(&info),
iterations: 0,
output_len: 37,
};
let mut expected = Vec::new();
for counter in [1_u32, 2] {
let mut digest = sha2::Sha256::new();
digest.update(counter.to_be_bytes());
digest.update(b"secret");
digest.update(&info);
expected.extend_from_slice(&digest.finalize());
}
expected.truncate(37);
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap(),
expected
);
}
}
#[test]
fn concat_kdf_rejects_malformed_bit_strings_and_unrelated_parameters() {
let mut parameters = KdfParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#ConcatKDF",
digest: Some("http://www.w3.org/2001/04/xmlenc#sha256"),
salt: &[],
info: KdfContext::Bits {
bytes: &[0x80],
bit_len: 1,
},
iterations: 0,
output_len: 16,
};
let partial = RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap();
parameters.info = KdfContext::Octets(&[0x80]);
assert_ne!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap(),
partial
);
for context in [
KdfContext::Bits {
bytes: &[0x81],
bit_len: 1,
},
KdfContext::Bits {
bytes: &[0x80],
bit_len: 9,
},
KdfContext::Bits {
bytes: &[0x80],
bit_len: 0,
},
KdfContext::Bits {
bytes: &[],
bit_len: usize::MAX,
},
] {
parameters.info = context;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
}
parameters.info = KdfContext::Bits {
bytes: &[0x80],
bit_len: 1,
};
parameters.algorithm = "http://www.w3.org/2021/04/xmldsig-more#hkdf";
parameters.digest = Some("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256");
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
parameters.algorithm = "http://www.w3.org/2009/xmlenc11#pbkdf2";
parameters.iterations = 1;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
}
#[test]
fn agreement_primitives_compose_with_xmlenc_key_wrap_and_authenticated_content() {
use xml_sec::provider::{EcdhCurve, KeyAgreementKey, RustCryptoEcdhKey};
use xml_sec::xmlenc::{
DataEncryptionAlgorithm, DecryptedContent, EncryptedDataBuilder, EncryptionRecipient,
KekDecryptor, KeyWrapAlgorithm, decrypt,
};
let check = |sender: &dyn KeyAgreementKey,
sender_public: &[u8],
recipient: &dyn KeyAgreementKey,
recipient_public: &[u8],
agreement: &str| {
let sender_secret = zeroize::Zeroizing::new(
RUST_CRYPTO_PROVIDER
.agree_key(
sender,
&KeyAgreementParameters {
algorithm: agreement,
peer_public_key: recipient_public,
},
)
.unwrap(),
);
let recipient_secret = zeroize::Zeroizing::new(
RUST_CRYPTO_PROVIDER
.agree_key(
recipient,
&KeyAgreementParameters {
algorithm: agreement,
peer_public_key: sender_public,
},
)
.unwrap(),
);
assert_eq!(*sender_secret, *recipient_secret);
for (content, wrap) in [
(
DataEncryptionAlgorithm::Aes128Gcm,
KeyWrapAlgorithm::AesKw128,
),
(
DataEncryptionAlgorithm::Aes256Gcm,
KeyWrapAlgorithm::AesKw256,
),
] {
for (kdf, digest, context) in [
(
"http://www.w3.org/2021/04/xmldsig-more#hkdf",
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
KdfContext::Octets(b"recipient context"),
),
(
"http://www.w3.org/2009/xmlenc11#ConcatKDF",
"http://www.w3.org/2001/04/xmlenc#sha256",
KdfContext::Bits {
bytes: &[0xa8],
bit_len: 5,
},
),
] {
let parameters = KdfParameters {
algorithm: kdf,
digest: Some(digest),
salt: &[],
info: context,
iterations: 0,
output_len: wrap.key_len(),
};
let sender_kek = RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &sender_secret)
.unwrap();
let recipient_kek = RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &recipient_secret)
.unwrap();
let encrypted = EncryptedDataBuilder::new(content)
.add_recipient(EncryptionRecipient::aes_key_wrap(sender_kek, wrap))
.encrypt_binary(b"agreement-derived recipient key")
.unwrap();
assert_eq!(
decrypt(
&encrypted.encrypted_data_xml,
&KekDecryptor::new(recipient_kek.clone())
)
.unwrap(),
DecryptedContent::Bytes(b"agreement-derived recipient key".to_vec())
);
let mut wrong = zeroize::Zeroizing::new(recipient_kek);
wrong[0] ^= 1;
assert!(
decrypt(
&encrypted.encrypted_data_xml,
&KekDecryptor::new(wrong.to_vec())
)
.is_err()
);
}
}
};
for (curve, width) in [
(EcdhCurve::P256, 32),
(EcdhCurve::P384, 48),
(EcdhCurve::P521, 66),
] {
let mut scalar = zeroize::Zeroizing::new(vec![0; width]);
scalar[width - 1] = 3;
let sender = RustCryptoEcdhKey::from_scalar(curve, &scalar).unwrap();
scalar[width - 1] = 5;
let recipient = RustCryptoEcdhKey::from_scalar(curve, &scalar).unwrap();
check(
&sender,
&sender.public_key(),
&recipient,
&recipient.public_key(),
"http://www.w3.org/2009/xmlenc11#ECDH-ES",
);
}
let sender = RustCryptoX25519Key::from_bytes([3; 32]);
let recipient = RustCryptoX25519Key::from_bytes([5; 32]);
check(
&sender,
&sender.public_key(),
&recipient,
&recipient.public_key(),
"http://www.w3.org/2021/04/xmldsig-more#x25519",
);
}
#[test]
fn hkdf_empty_salt_and_info_match_rfc5869_case_three() {
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#hkdf",
digest: Some("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"),
salt: &[],
info: KdfContext::Octets(&[]),
iterations: 0,
output_len: 42,
};
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &[0x0b; 22])
.unwrap(),
hex("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8")
);
}
#[test]
fn hkdf_sha1_matches_rfc5869_case_four() {
let salt = hex("000102030405060708090a0b0c");
let info = hex("f0f1f2f3f4f5f6f7f8f9");
let parameters = KdfParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#hkdf",
digest: Some("http://www.w3.org/2000/09/xmldsig#hmac-sha1"),
salt: &salt,
info: KdfContext::Octets(&info),
iterations: 0,
output_len: 42,
};
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, &[0x0b; 11])
.unwrap(),
hex("085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896")
);
}
#[test]
fn hkdf_all_prfs_enforce_exact_expand_boundary() {
use xml_sec::xmldsig::SignatureAlgorithm;
for (prf, width) in [
(SignatureAlgorithm::HmacSha1, 20),
(SignatureAlgorithm::HmacSha224, 28),
(SignatureAlgorithm::HmacSha256, 32),
(SignatureAlgorithm::HmacSha384, 48),
(SignatureAlgorithm::HmacSha512, 64),
] {
let mut parameters = KdfParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#hkdf",
digest: Some(prf.uri()),
salt: &[],
info: KdfContext::Octets(&[]),
iterations: 0,
output_len: 255 * width,
};
assert_eq!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap()
.len(),
parameters.output_len
);
parameters.output_len += 1;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
parameters.output_len = 0;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.unwrap()
.is_empty()
);
parameters.digest = Some(SignatureAlgorithm::RsaSha256.uri());
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
}
}
#[test]
fn x25519_masks_peer_high_bit_per_rfc7748() {
let alice = RustCryptoX25519Key::from_bytes([7; 32]);
let bob = RustCryptoX25519Key::from_bytes([9; 32]);
let mut public = bob.public_key();
let parameters = KeyAgreementParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#x25519",
peer_public_key: &public,
};
let expected = RUST_CRYPTO_PROVIDER.agree_key(&alice, ¶meters).unwrap();
public[31] |= 0x80;
let parameters = KeyAgreementParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#x25519",
peer_public_key: &public,
};
assert_eq!(
RUST_CRYPTO_PROVIDER.agree_key(&alice, ¶meters).unwrap(),
expected
);
}
#[test]
fn hkdf_rejects_excessive_output_and_iteration_parameter() {
let mut parameters = KdfParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#hkdf",
digest: Some("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"),
salt: &[],
info: KdfContext::Octets(&[]),
iterations: 0,
output_len: 255 * 32 + 1,
};
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
parameters.output_len = 32;
parameters.iterations = 1;
assert!(
RUST_CRYPTO_PROVIDER
.derive_key(¶meters, b"secret")
.is_err()
);
}
#[test]
fn x25519_matches_rfc7748_alice_bob() {
let alice = RustCryptoX25519Key::from_bytes(
hex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
.try_into()
.unwrap(),
);
let bob = RustCryptoX25519Key::from_bytes(
hex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
.try_into()
.unwrap(),
);
assert_eq!(
alice.public_key().as_slice(),
hex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
);
let expected = hex("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
for (key, peer) in [(&alice, bob.public_key()), (&bob, alice.public_key())] {
let parameters = KeyAgreementParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#x25519",
peer_public_key: &peer,
};
assert_eq!(
RUST_CRYPTO_PROVIDER.agree_key(key, ¶meters).unwrap(),
expected
);
}
}
#[test]
fn x25519_rejects_noncontributory_and_wrong_width_peers() {
let key = RustCryptoX25519Key::from_bytes([7; 32]);
for peer in [&[0; 32][..], &[0; 31][..], &[0; 33][..]] {
let parameters = KeyAgreementParameters {
algorithm: "http://www.w3.org/2021/04/xmldsig-more#x25519",
peer_public_key: peer,
};
assert!(RUST_CRYPTO_PROVIDER.agree_key(&key, ¶meters).is_err());
}
}