use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SignatureAlgorithm {
RsaSha256,
RsaSha384,
RsaSha512,
EcdsaSha256,
EcdsaSha384,
EcdsaSha512,
#[cfg(feature = "weak-algos")]
RsaSha1,
#[cfg(feature = "weak-algos")]
DsaSha1,
}
impl SignatureAlgorithm {
pub const DEFAULTS: &'static [Self] = &[
Self::RsaSha256,
Self::RsaSha384,
Self::RsaSha512,
Self::EcdsaSha256,
Self::EcdsaSha384,
Self::EcdsaSha512,
];
pub const fn uri(self) -> &'static str {
match self {
Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
Self::EcdsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
#[cfg(feature = "weak-algos")]
Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
#[cfg(feature = "weak-algos")]
Self::DsaSha1 => "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
}
}
pub fn from_uri(uri: &str) -> Result<Self, Error> {
match uri {
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Ok(Self::RsaSha256),
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Ok(Self::RsaSha384),
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Ok(Self::RsaSha512),
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Ok(Self::EcdsaSha256),
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Ok(Self::EcdsaSha384),
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" => Ok(Self::EcdsaSha512),
#[cfg(feature = "weak-algos")]
"http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Ok(Self::RsaSha1),
#[cfg(feature = "weak-algos")]
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha1" => Ok(Self::RsaSha1),
#[cfg(feature = "weak-algos")]
"http://www.w3.org/2000/09/xmldsig#dsa-sha1" => Ok(Self::DsaSha1),
_ => Err(Error::DisallowedAlgorithm {
alg: uri.to_owned(),
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DigestAlgorithm {
Sha256,
Sha384,
Sha512,
#[cfg(feature = "weak-algos")]
Sha1,
}
impl DigestAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
#[cfg(feature = "weak-algos")]
Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
}
}
pub fn from_uri(uri: &str) -> Result<Self, Error> {
match uri {
"http://www.w3.org/2001/04/xmlenc#sha256" => Ok(Self::Sha256),
"http://www.w3.org/2001/04/xmldsig-more#sha384" => Ok(Self::Sha384),
"http://www.w3.org/2001/04/xmlenc#sha384" => Ok(Self::Sha384),
"http://www.w3.org/2001/04/xmlenc#sha512" => Ok(Self::Sha512),
#[cfg(feature = "weak-algos")]
"http://www.w3.org/2000/09/xmldsig#sha1" => Ok(Self::Sha1),
#[cfg(feature = "weak-algos")]
"http://www.w3.org/2001/04/xmlenc#sha1" => Ok(Self::Sha1),
_ => Err(Error::DisallowedAlgorithm {
alg: uri.to_owned(),
}),
}
}
pub fn digest(self, bytes: &[u8]) -> Vec<u8> {
use sha2::Digest as _;
match self {
Self::Sha256 => sha2::Sha256::digest(bytes).to_vec(),
Self::Sha384 => sha2::Sha384::digest(bytes).to_vec(),
Self::Sha512 => sha2::Sha512::digest(bytes).to_vec(),
#[cfg(feature = "weak-algos")]
Self::Sha1 => {
use sha1::Digest as _;
sha1::Sha1::digest(bytes).to_vec()
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum C14nAlgorithm {
ExclusiveCanonical,
ExclusiveCanonicalWithComments,
InclusiveCanonical,
InclusiveCanonicalWithComments,
}
impl C14nAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::ExclusiveCanonical => "http://www.w3.org/2001/10/xml-exc-c14n#",
Self::ExclusiveCanonicalWithComments => {
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
}
Self::InclusiveCanonical => "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
Self::InclusiveCanonicalWithComments => {
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
}
}
}
pub fn from_uri(uri: &str) -> Result<Self, Error> {
match uri {
"http://www.w3.org/2001/10/xml-exc-c14n#" => Ok(Self::ExclusiveCanonical),
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments" => {
Ok(Self::ExclusiveCanonicalWithComments)
}
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315" => Ok(Self::InclusiveCanonical),
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" => {
Ok(Self::InclusiveCanonicalWithComments)
}
_ => Err(Error::DisallowedAlgorithm {
alg: uri.to_owned(),
}),
}
}
pub fn includes_comments(self) -> bool {
matches!(
self,
Self::ExclusiveCanonicalWithComments | Self::InclusiveCanonicalWithComments
)
}
pub fn is_exclusive(self) -> bool {
matches!(
self,
Self::ExclusiveCanonical | Self::ExclusiveCanonicalWithComments
)
}
}
#[derive(Debug, Clone)]
pub struct PeerCryptoPolicy {
pub allowed_signature_algorithms: Vec<SignatureAlgorithm>,
#[cfg(feature = "xmlenc")]
pub allowed_data_encryption_algorithms: Vec<crate::xmlenc::algorithms::DataEncryptionAlgorithm>,
#[cfg(feature = "xmlenc")]
pub allowed_key_transport_algorithms: Vec<crate::xmlenc::algorithms::KeyTransportAlgorithm>,
}
impl PeerCryptoPolicy {
pub fn strong_defaults() -> Self {
Self {
allowed_signature_algorithms: SignatureAlgorithm::DEFAULTS.to_vec(),
#[cfg(feature = "xmlenc")]
allowed_data_encryption_algorithms: vec![
crate::xmlenc::algorithms::DataEncryptionAlgorithm::Aes128Gcm,
crate::xmlenc::algorithms::DataEncryptionAlgorithm::Aes256Gcm,
],
#[cfg(feature = "xmlenc")]
allowed_key_transport_algorithms: vec![
crate::xmlenc::algorithms::KeyTransportAlgorithm::RsaOaep,
],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signature_uri_roundtrip() {
for alg in [
SignatureAlgorithm::RsaSha256,
SignatureAlgorithm::RsaSha384,
SignatureAlgorithm::RsaSha512,
SignatureAlgorithm::EcdsaSha256,
SignatureAlgorithm::EcdsaSha384,
SignatureAlgorithm::EcdsaSha512,
] {
assert_eq!(SignatureAlgorithm::from_uri(alg.uri()).unwrap(), alg);
}
#[cfg(feature = "weak-algos")]
for alg in [SignatureAlgorithm::RsaSha1, SignatureAlgorithm::DsaSha1] {
assert_eq!(SignatureAlgorithm::from_uri(alg.uri()).unwrap(), alg);
}
}
#[test]
fn signature_from_unknown_uri_is_disallowed() {
let err = SignatureAlgorithm::from_uri("http://example.com/unknown").unwrap_err();
match err {
Error::DisallowedAlgorithm { alg } => assert_eq!(alg, "http://example.com/unknown"),
other => panic!("expected DisallowedAlgorithm, got {other:?}"),
}
}
#[cfg(not(feature = "weak-algos"))]
#[test]
fn signature_rsa_sha1_uri_rejected_without_weak_algos() {
let err =
SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1").unwrap_err();
match err {
Error::DisallowedAlgorithm { alg } => {
assert_eq!(alg, "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
}
other => panic!("expected DisallowedAlgorithm, got {other:?}"),
}
}
#[test]
fn digest_uri_roundtrip() {
for alg in [
DigestAlgorithm::Sha256,
DigestAlgorithm::Sha384,
DigestAlgorithm::Sha512,
] {
assert_eq!(DigestAlgorithm::from_uri(alg.uri()).unwrap(), alg);
}
#[cfg(feature = "weak-algos")]
{
let alg = DigestAlgorithm::Sha1;
assert_eq!(DigestAlgorithm::from_uri(alg.uri()).unwrap(), alg);
}
}
#[test]
fn digest_from_unknown_uri_is_disallowed() {
let err = DigestAlgorithm::from_uri("http://example.com/unknown").unwrap_err();
match err {
Error::DisallowedAlgorithm { alg } => assert_eq!(alg, "http://example.com/unknown"),
other => panic!("expected DisallowedAlgorithm, got {other:?}"),
}
}
#[test]
fn digest_known_answer_vectors_empty_input() {
let sha256_empty = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
let sha384_empty = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b";
let sha512_empty = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
assert_eq!(
hex::encode(DigestAlgorithm::Sha256.digest(b"")),
sha256_empty
);
assert_eq!(
hex::encode(DigestAlgorithm::Sha384.digest(b"")),
sha384_empty
);
assert_eq!(
hex::encode(DigestAlgorithm::Sha512.digest(b"")),
sha512_empty
);
#[cfg(feature = "weak-algos")]
{
let sha1_empty = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
assert_eq!(hex::encode(DigestAlgorithm::Sha1.digest(b"")), sha1_empty);
}
}
#[test]
fn digest_known_answer_vector_abc() {
assert_eq!(
hex::encode(DigestAlgorithm::Sha256.digest(b"abc")),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
);
}
#[test]
fn c14n_uri_roundtrip() {
for alg in [
C14nAlgorithm::ExclusiveCanonical,
C14nAlgorithm::ExclusiveCanonicalWithComments,
C14nAlgorithm::InclusiveCanonical,
C14nAlgorithm::InclusiveCanonicalWithComments,
] {
assert_eq!(C14nAlgorithm::from_uri(alg.uri()).unwrap(), alg);
}
}
#[test]
fn c14n_from_unknown_uri_is_disallowed() {
let err = C14nAlgorithm::from_uri("http://example.com/unknown").unwrap_err();
match err {
Error::DisallowedAlgorithm { alg } => assert_eq!(alg, "http://example.com/unknown"),
other => panic!("expected DisallowedAlgorithm, got {other:?}"),
}
}
#[test]
fn c14n_includes_comments_and_is_exclusive() {
assert!(!C14nAlgorithm::ExclusiveCanonical.includes_comments());
assert!(C14nAlgorithm::ExclusiveCanonicalWithComments.includes_comments());
assert!(!C14nAlgorithm::InclusiveCanonical.includes_comments());
assert!(C14nAlgorithm::InclusiveCanonicalWithComments.includes_comments());
assert!(C14nAlgorithm::ExclusiveCanonical.is_exclusive());
assert!(C14nAlgorithm::ExclusiveCanonicalWithComments.is_exclusive());
assert!(!C14nAlgorithm::InclusiveCanonical.is_exclusive());
assert!(!C14nAlgorithm::InclusiveCanonicalWithComments.is_exclusive());
}
#[test]
fn strong_defaults_excludes_weak_signature_algorithms() {
let policy = PeerCryptoPolicy::strong_defaults();
let sigs = &policy.allowed_signature_algorithms;
assert_eq!(sigs.as_slice(), SignatureAlgorithm::DEFAULTS);
#[cfg(feature = "weak-algos")]
{
assert!(!sigs.contains(&SignatureAlgorithm::RsaSha1));
assert!(!sigs.contains(&SignatureAlgorithm::DsaSha1));
}
}
#[cfg(feature = "xmlenc")]
#[test]
fn strong_defaults_xmlenc_choices() {
use crate::xmlenc::algorithms::{DataEncryptionAlgorithm, KeyTransportAlgorithm};
let policy = PeerCryptoPolicy::strong_defaults();
assert_eq!(
policy.allowed_data_encryption_algorithms,
vec![
DataEncryptionAlgorithm::Aes128Gcm,
DataEncryptionAlgorithm::Aes256Gcm,
]
);
assert!(
!policy
.allowed_data_encryption_algorithms
.contains(&DataEncryptionAlgorithm::Aes128Cbc)
);
assert!(
!policy
.allowed_data_encryption_algorithms
.contains(&DataEncryptionAlgorithm::Aes256Cbc)
);
assert_eq!(
policy.allowed_key_transport_algorithms,
vec![KeyTransportAlgorithm::RsaOaep]
);
assert!(
!policy
.allowed_key_transport_algorithms
.contains(&KeyTransportAlgorithm::RsaOaepMgf1Sha1)
);
#[cfg(feature = "weak-algos")]
assert!(
!policy
.allowed_key_transport_algorithms
.contains(&KeyTransportAlgorithm::RsaPkcs1V15)
);
}
}