use std::{fmt, sync::Arc};
use rsa::RsaPublicKey;
pub const XMLENC_NS: &str = "http://www.w3.org/2001/04/xmlenc#";
pub const XMLENC11_NS: &str = "http://www.w3.org/2009/xmlenc11#";
pub const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
pub const MAX_CIPHER_VALUE_BASE64_LEN: usize =
crate::hard_limits::ENCRYPTION_CIPHER_VALUE_BASE64_BYTE_CEILING;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncryptedDataType {
Element,
Content,
Other(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DataEncryptionAlgorithm {
#[cfg(feature = "legacy-algorithms")]
TripleDesCbc,
#[cfg(feature = "legacy-algorithms")]
Aes192Cbc,
#[cfg(feature = "legacy-algorithms")]
Aes192Gcm,
Aes128Cbc,
Aes256Cbc,
Aes128Gcm,
Aes256Gcm,
Camellia128Cbc,
Camellia192Cbc,
Camellia256Cbc,
ChaCha20,
ChaCha20Poly1305,
}
impl DataEncryptionAlgorithm {
pub const fn is_authenticated(self) -> bool {
match self {
Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305 => true,
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Gcm => true,
Self::Aes128Cbc
| Self::Aes256Cbc
| Self::Camellia128Cbc
| Self::Camellia192Cbc
| Self::Camellia256Cbc
| Self::ChaCha20 => false,
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc | Self::Aes192Cbc => false,
}
}
pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
if matches!(self, Self::ChaCha20 | Self::ChaCha20Poly1305) {
return crate::key_manager::SymmetricKeyKind::ChaCha20;
}
if matches!(
self,
Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc
) {
return crate::key_manager::SymmetricKeyKind::Camellia;
}
#[cfg(feature = "legacy-algorithms")]
if matches!(self, Self::TripleDesCbc) {
return crate::key_manager::SymmetricKeyKind::Des;
}
crate::key_manager::SymmetricKeyKind::Aes
}
pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
match uri {
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#tripledes-cbc" => Ok(Self::TripleDesCbc),
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#aes192-cbc" => Ok(Self::Aes192Cbc),
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2009/xmlenc11#aes192-gcm" => Ok(Self::Aes192Gcm),
"http://www.w3.org/2001/04/xmlenc#aes128-cbc" => Ok(Self::Aes128Cbc),
"http://www.w3.org/2001/04/xmlenc#aes256-cbc" => Ok(Self::Aes256Cbc),
"http://www.w3.org/2009/xmlenc11#aes128-gcm" => Ok(Self::Aes128Gcm),
"http://www.w3.org/2009/xmlenc11#aes256-gcm" => Ok(Self::Aes256Gcm),
"http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc" => Ok(Self::Camellia128Cbc),
"http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc" => Ok(Self::Camellia192Cbc),
"http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc" => Ok(Self::Camellia256Cbc),
"http://www.w3.org/2021/04/xmldsig-more#chacha20" => Ok(Self::ChaCha20),
"http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305" => Ok(Self::ChaCha20Poly1305),
_ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
}
}
pub const fn key_len(self) -> usize {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => 24,
Self::Aes128Cbc | Self::Aes128Gcm => 16,
Self::Aes256Cbc | Self::Aes256Gcm => 32,
Self::Camellia128Cbc => 16,
Self::Camellia192Cbc => 24,
Self::Camellia256Cbc => 32,
Self::ChaCha20 | Self::ChaCha20Poly1305 => 32,
}
}
pub const fn uri(self) -> &'static str {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc => "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Cbc => "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Gcm => "http://www.w3.org/2009/xmlenc11#aes192-gcm",
Self::Aes128Cbc => "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
Self::Aes256Cbc => "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
Self::Aes128Gcm => "http://www.w3.org/2009/xmlenc11#aes128-gcm",
Self::Aes256Gcm => "http://www.w3.org/2009/xmlenc11#aes256-gcm",
Self::Camellia128Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc",
Self::Camellia192Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc",
Self::Camellia256Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc",
Self::ChaCha20 => "http://www.w3.org/2021/04/xmldsig-more#chacha20",
Self::ChaCha20Poly1305 => "http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305",
}
}
pub(crate) const fn minimum_ciphertext_len(self) -> usize {
if matches!(self, Self::ChaCha20) {
return 0;
}
if matches!(self, Self::ChaCha20Poly1305) {
return 16;
}
match self.cbc_block_len() {
Some(block) => block * 2,
None => 28,
}
}
pub const fn requires_explicit_permission(self) -> bool {
match self {
Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => true,
Self::ChaCha20 | Self::ChaCha20Poly1305 => true,
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => true,
_ => false,
}
}
pub(crate) const fn cbc_block_len(self) -> Option<usize> {
match self {
Self::Aes128Cbc | Self::Aes256Cbc => Some(16),
Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => Some(16),
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Cbc => Some(16),
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc => Some(8),
_ => None,
}
}
pub(crate) fn ciphertext_len_for_plaintext(self, plaintext_len: usize) -> Option<usize> {
if matches!(self, Self::ChaCha20) {
return Some(plaintext_len);
}
if matches!(self, Self::ChaCha20Poly1305) {
return plaintext_len.checked_add(16);
}
match self.cbc_block_len() {
Some(block) => (plaintext_len / block)
.checked_add(1)?
.checked_mul(block)?
.checked_add(block),
None => plaintext_len.checked_add(28),
}
}
}
pub(crate) fn validate_ciphertext_framing(
algorithm: DataEncryptionAlgorithm,
ciphertext_len: usize,
) -> Result<(), XmlEncError> {
let minimum = algorithm.minimum_ciphertext_len();
if ciphertext_len < minimum {
let algorithm_name = match algorithm {
DataEncryptionAlgorithm::ChaCha20 => "ChaCha20",
DataEncryptionAlgorithm::ChaCha20Poly1305 => "ChaCha20-Poly1305",
DataEncryptionAlgorithm::Camellia128Cbc
| DataEncryptionAlgorithm::Camellia192Cbc
| DataEncryptionAlgorithm::Camellia256Cbc => "Camellia-CBC",
_ => match algorithm.cbc_block_len() {
Some(8) => "Triple DES CBC",
Some(_) => "AES-CBC",
None => "AES-GCM",
},
};
return Err(XmlEncError::DataTooShort {
algorithm: algorithm_name,
minimum,
actual: ciphertext_len,
});
}
if let Some(block) = algorithm.cbc_block_len()
&& !(ciphertext_len - block).is_multiple_of(block)
{
return Err(XmlEncError::InvalidCbcCiphertextLength {
algorithm,
block,
actual: ciphertext_len - block,
});
}
Ok(())
}
impl KeyTransportAlgorithm {
pub const fn requires_explicit_permission(self) -> bool {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::RsaPkcs1v15 => true,
_ => false,
}
}
pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
match uri {
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" => Ok(Self::RsaOaepMgf1p),
"http://www.w3.org/2009/xmlenc11#rsa-oaep" => Ok(Self::RsaOaep11),
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#rsa-1_5" => Ok(Self::RsaPkcs1v15),
_ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
}
}
pub const fn uri(self) -> &'static str {
match self {
Self::RsaOaepMgf1p => "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
Self::RsaOaep11 => "http://www.w3.org/2009/xmlenc11#rsa-oaep",
#[cfg(feature = "legacy-algorithms")]
Self::RsaPkcs1v15 => "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
}
}
}
impl KeyWrapAlgorithm {
pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
if let Self::Cbc(algorithm) = self {
return algorithm.key_kind();
}
if matches!(
self,
Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256
) {
return crate::key_manager::SymmetricKeyKind::Camellia;
}
#[cfg(feature = "legacy-algorithms")]
if matches!(self, Self::TripleDes) {
return crate::key_manager::SymmetricKeyKind::Des;
}
crate::key_manager::SymmetricKeyKind::Aes
}
pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
match uri {
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#kw-aes192" => Ok(Self::AesKw192),
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#kw-tripledes" => Ok(Self::TripleDes),
"http://www.w3.org/2001/04/xmlenc#kw-aes128" => Ok(Self::AesKw128),
"http://www.w3.org/2001/04/xmlenc#kw-aes256" => Ok(Self::AesKw256),
"http://www.w3.org/2001/04/xmldsig-more#kw-camellia128" => Ok(Self::CamelliaKw128),
"http://www.w3.org/2001/04/xmldsig-more#kw-camellia192" => Ok(Self::CamelliaKw192),
"http://www.w3.org/2001/04/xmldsig-more#kw-camellia256" => Ok(Self::CamelliaKw256),
_ => DataEncryptionAlgorithm::from_uri(uri)
.ok()
.filter(|algorithm| algorithm.cbc_block_len().is_some())
.map(Self::Cbc)
.ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
}
}
pub const fn key_len(self) -> usize {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::AesKw192 | Self::TripleDes => 24,
Self::AesKw128 => 16,
Self::AesKw256 => 32,
Self::CamelliaKw128 => 16,
Self::CamelliaKw192 => 24,
Self::CamelliaKw256 => 32,
Self::Cbc(algorithm) => algorithm.key_len(),
}
}
pub const fn uri(self) -> &'static str {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::AesKw192 => "http://www.w3.org/2001/04/xmlenc#kw-aes192",
#[cfg(feature = "legacy-algorithms")]
Self::TripleDes => "http://www.w3.org/2001/04/xmlenc#kw-tripledes",
Self::AesKw128 => "http://www.w3.org/2001/04/xmlenc#kw-aes128",
Self::AesKw256 => "http://www.w3.org/2001/04/xmlenc#kw-aes256",
Self::CamelliaKw128 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128",
Self::CamelliaKw192 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192",
Self::CamelliaKw256 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256",
Self::Cbc(algorithm) => algorithm.uri(),
}
}
pub const fn requires_explicit_permission(self) -> bool {
match self {
Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256 => true,
Self::Cbc(_) => true,
#[cfg(feature = "legacy-algorithms")]
Self::AesKw192 | Self::TripleDes => true,
_ => false,
}
}
pub(crate) fn wrapped_len(self, plaintext_len: usize) -> Option<usize> {
let overhead = match self {
Self::Cbc(algorithm) => {
return if algorithm.cbc_block_len().is_some() {
algorithm.ciphertext_len_for_plaintext(plaintext_len)
} else {
None
};
}
#[cfg(feature = "legacy-algorithms")]
Self::TripleDes => 16,
_ => 8,
};
plaintext_len.checked_add(overhead)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyTransportAlgorithm {
#[cfg(feature = "legacy-algorithms")]
RsaPkcs1v15,
RsaOaepMgf1p,
RsaOaep11,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyWrapAlgorithm {
Cbc(DataEncryptionAlgorithm),
#[cfg(feature = "legacy-algorithms")]
AesKw192,
#[cfg(feature = "legacy-algorithms")]
TripleDes,
AesKw128,
AesKw256,
CamelliaKw128,
CamelliaKw192,
CamelliaKw256,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OaepDigestAlgorithm {
#[cfg(feature = "legacy-algorithms")]
Md5,
#[cfg(feature = "legacy-algorithms")]
Ripemd160,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
}
impl OaepDigestAlgorithm {
pub fn from_uri(uri: &str) -> Option<Self> {
match uri {
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmldsig-more#md5" => Some(Self::Md5),
#[cfg(feature = "legacy-algorithms")]
"http://www.w3.org/2001/04/xmlenc#ripemd160" => Some(Self::Ripemd160),
"http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
"http://www.w3.org/2001/04/xmldsig-more#sha224" => Some(Self::Sha224),
"http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
"http://www.w3.org/2001/04/xmlenc#sha384"
| "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
"http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
"http://www.w3.org/2007/05/xmldsig-more#sha3-224" => Some(Self::Sha3_224),
"http://www.w3.org/2007/05/xmldsig-more#sha3-256" => Some(Self::Sha3_256),
"http://www.w3.org/2007/05/xmldsig-more#sha3-384" => Some(Self::Sha3_384),
"http://www.w3.org/2007/05/xmldsig-more#sha3-512" => Some(Self::Sha3_512),
_ => None,
}
}
pub fn from_mgf_uri(uri: &str) -> Option<Self> {
match uri {
"http://www.w3.org/2009/xmlenc11#mgf1sha1" => Some(Self::Sha1),
"http://www.w3.org/2009/xmlenc11#mgf1sha224" => Some(Self::Sha224),
"http://www.w3.org/2009/xmlenc11#mgf1sha256" => Some(Self::Sha256),
"http://www.w3.org/2009/xmlenc11#mgf1sha384" => Some(Self::Sha384),
"http://www.w3.org/2009/xmlenc11#mgf1sha512" => Some(Self::Sha512),
_ => None,
}
}
pub const fn uri(self) -> &'static str {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::Md5 => "http://www.w3.org/2001/04/xmldsig-more#md5",
#[cfg(feature = "legacy-algorithms")]
Self::Ripemd160 => "http://www.w3.org/2001/04/xmlenc#ripemd160",
Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
Self::Sha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224",
Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
Self::Sha384 => "http://www.w3.org/2001/04/xmlenc#sha384",
Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
Self::Sha3_224 => "http://www.w3.org/2007/05/xmldsig-more#sha3-224",
Self::Sha3_256 => "http://www.w3.org/2007/05/xmldsig-more#sha3-256",
Self::Sha3_384 => "http://www.w3.org/2007/05/xmldsig-more#sha3-384",
Self::Sha3_512 => "http://www.w3.org/2007/05/xmldsig-more#sha3-512",
}
}
pub const fn mgf_uri(self) -> Option<&'static str> {
match self {
Self::Sha1 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
Self::Sha224 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha224"),
Self::Sha256 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha256"),
Self::Sha384 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha384"),
Self::Sha512 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha512"),
_ => None,
}
}
pub const fn requires_explicit_permission(self) -> bool {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::Md5 | Self::Ripemd160 => true,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::{OaepDigestAlgorithm, XmlEncError};
#[test]
fn operation_plan_failures_have_a_distinct_error_class() {
let error = XmlEncError::from(crate::operation::OperationPlanError::StaleResourceIdentity);
assert!(matches!(error, XmlEncError::OperationPlan(_)));
}
#[test]
fn oaep_sha384_accepts_both_interoperable_digest_uris() {
for uri in [
"http://www.w3.org/2001/04/xmlenc#sha384",
"http://www.w3.org/2001/04/xmldsig-more#sha384",
] {
assert_eq!(
OaepDigestAlgorithm::from_uri(uri),
Some(OaepDigestAlgorithm::Sha384)
);
}
}
#[test]
fn oaep_mgf_uris_round_trip() {
for algorithm in [
OaepDigestAlgorithm::Sha1,
OaepDigestAlgorithm::Sha224,
OaepDigestAlgorithm::Sha256,
OaepDigestAlgorithm::Sha384,
OaepDigestAlgorithm::Sha512,
] {
assert_eq!(
OaepDigestAlgorithm::from_mgf_uri(
algorithm.mgf_uri().expect("standard MGF1 digest URI")
),
Some(algorithm)
);
}
assert_eq!(
OaepDigestAlgorithm::from_mgf_uri("urn:unsupported-mgf"),
None
);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RsaOaepParameters {
pub algorithm: KeyTransportAlgorithm,
pub digest: OaepDigestAlgorithm,
pub mgf_digest: OaepDigestAlgorithm,
pub label: Vec<u8>,
}
impl RsaOaepParameters {
pub fn legacy() -> Self {
Self {
algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
digest: OaepDigestAlgorithm::Sha1,
mgf_digest: OaepDigestAlgorithm::Sha1,
label: Vec::new(),
}
}
pub fn xmlenc11(digest: OaepDigestAlgorithm, mgf_digest: OaepDigestAlgorithm) -> Self {
Self {
algorithm: KeyTransportAlgorithm::RsaOaep11,
digest,
mgf_digest,
label: Vec::new(),
}
}
pub fn label(mut self, label: impl Into<Vec<u8>>) -> Self {
self.label = label.into();
self
}
}
impl Default for RsaOaepParameters {
fn default() -> Self {
Self::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha256)
}
}
#[derive(Clone)]
pub enum EncryptionRecipient {
#[cfg(feature = "legacy-algorithms")]
RsaPkcs1v15 {
public_key: Arc<dyn crate::provider::KeyTransportKey>,
recipient: Option<String>,
key_name: Option<String>,
},
RsaOaep {
public_key: Arc<dyn crate::provider::KeyTransportKey>,
parameters: RsaOaepParameters,
recipient: Option<String>,
key_name: Option<String>,
},
AesKeyWrap {
kek: Vec<u8>,
algorithm: KeyWrapAlgorithm,
recipient: Option<String>,
key_name: Option<String>,
},
}
impl zeroize::Zeroize for EncryptionRecipient {
fn zeroize(&mut self) {
if let Self::AesKeyWrap { kek, .. } = self {
kek.zeroize();
}
}
}
impl zeroize::ZeroizeOnDrop for EncryptionRecipient {}
impl Drop for EncryptionRecipient {
fn drop(&mut self) {
zeroize::Zeroize::zeroize(self);
}
}
impl fmt::Debug for EncryptionRecipient {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "legacy-algorithms")]
Self::RsaPkcs1v15 {
recipient,
key_name,
..
} => formatter
.debug_struct("EncryptionRecipient::RsaPkcs1v15")
.field("public_key", &"[PUBLIC KEY]")
.field("recipient", recipient)
.field("key_name", key_name)
.finish(),
Self::RsaOaep {
parameters,
recipient,
key_name,
..
} => formatter
.debug_struct("EncryptionRecipient::RsaOaep")
.field("public_key", &"[PUBLIC KEY]")
.field("parameters", parameters)
.field("recipient", recipient)
.field("key_name", key_name)
.finish(),
Self::AesKeyWrap {
algorithm,
recipient,
key_name,
..
} => formatter
.debug_struct("EncryptionRecipient::AesKeyWrap")
.field("kek", &"[REDACTED]")
.field("algorithm", algorithm)
.field("recipient", recipient)
.field("key_name", key_name)
.finish(),
}
}
}
impl EncryptionRecipient {
#[cfg(feature = "legacy-algorithms")]
pub fn rsa_pkcs1v15(public_key: RsaPublicKey) -> Self {
Self::provider_pkcs1v15(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
public_key,
)))
}
#[cfg(feature = "legacy-algorithms")]
pub fn provider_pkcs1v15(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
Self::RsaPkcs1v15 {
public_key,
recipient: None,
key_name: None,
}
}
pub fn rsa_oaep(public_key: RsaPublicKey) -> Self {
Self::provider_key_transport(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
public_key,
)))
}
pub fn provider_key_transport(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
Self::RsaOaep {
public_key,
parameters: RsaOaepParameters::default(),
recipient: None,
key_name: None,
}
}
pub fn aes_key_wrap(kek: impl Into<Vec<u8>>, algorithm: KeyWrapAlgorithm) -> Self {
Self::AesKeyWrap {
kek: kek.into(),
algorithm,
recipient: None,
key_name: None,
}
}
pub fn oaep_parameters(mut self, parameters: RsaOaepParameters) -> Self {
if let Self::RsaOaep {
parameters: current,
..
} = &mut self
{
*current = parameters;
}
self
}
pub fn recipient(mut self, value: impl Into<String>) -> Self {
match &mut self {
#[cfg(feature = "legacy-algorithms")]
Self::RsaPkcs1v15 { recipient, .. } => *recipient = Some(value.into()),
Self::RsaOaep { recipient, .. } | Self::AesKeyWrap { recipient, .. } => {
*recipient = Some(value.into());
}
}
self
}
pub fn key_name(mut self, value: impl Into<String>) -> Self {
match &mut self {
#[cfg(feature = "legacy-algorithms")]
Self::RsaPkcs1v15 { key_name, .. } => *key_name = Some(value.into()),
Self::RsaOaep { key_name, .. } | Self::AesKeyWrap { key_name, .. } => {
*key_name = Some(value.into());
}
}
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplacementMode {
ReplaceElement,
ReplaceContent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionResult {
pub encrypted_data_xml: String,
pub replacement: ReplacementMode,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DocumentEncryptionOptions<'a> {
pub element_id: Option<&'a str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionMethod {
pub algorithm: String,
pub key_size_bits: Option<usize>,
pub oaep_digest: Option<String>,
pub mgf_algorithm: Option<String>,
pub oaep_params: Option<Vec<u8>>,
pub chacha: Option<ChaChaParameters>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ChaChaParameters {
pub nonce: Option<[u8; 12]>,
pub counter: Option<[u8; 4]>,
pub aad: Option<String>,
}
impl ChaChaParameters {
pub fn borrowed(&self) -> ChaChaParametersRef<'_> {
ChaChaParametersRef {
nonce: self.nonce.as_ref(),
counter: self.counter,
aad: self.aad.as_deref(),
}
}
pub fn validate(&self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
self.borrowed().validate(algorithm)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ChaChaParametersRef<'a> {
pub nonce: Option<&'a [u8; 12]>,
pub counter: Option<[u8; 4]>,
pub aad: Option<&'a str>,
}
impl ChaChaParametersRef<'_> {
pub fn validate(self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
let valid = match algorithm {
DataEncryptionAlgorithm::ChaCha20 => self.counter.is_some() && self.aad.is_none(),
DataEncryptionAlgorithm::ChaCha20Poly1305 => self.counter.is_none(),
_ => false,
};
if !valid {
return Err(XmlEncError::InvalidStructure(
"invalid ChaCha EncryptionMethod parameters".into(),
));
}
Ok(())
}
}
impl EncryptionMethod {
pub(crate) fn validate_structure(&self) -> Result<(), XmlEncError> {
if let Some(parameters) = &self.chacha {
parameters.validate(DataEncryptionAlgorithm::from_uri(&self.algorithm)?)?;
} else if self.algorithm == DataEncryptionAlgorithm::ChaCha20.uri() {
return Err(XmlEncError::MissingRequired("ChaCha20 Counter"));
}
if self.key_size_bits == Some(0) {
return Err(XmlEncError::InvalidStructure(
"KeySize must be a positive integer".into(),
));
}
let is_legacy_oaep = self.algorithm == KeyTransportAlgorithm::RsaOaepMgf1p.uri();
let is_oaep11 = self.algorithm == KeyTransportAlgorithm::RsaOaep11.uri();
if (self.oaep_params.is_some()
|| self.oaep_digest.is_some()
|| self.mgf_algorithm.is_some())
&& !is_legacy_oaep
&& !is_oaep11
{
return Err(XmlEncError::InvalidStructure(
"OAEP parameters are only valid for RSA-OAEP EncryptionMethod".into(),
));
}
if let (Some(actual), Some(expected)) =
(self.key_size_bits, fixed_aes_key_size(&self.algorithm))
&& actual != expected
{
return Err(XmlEncError::InvalidStructure(format!(
"EncryptionMethod {} requires KeySize {expected}, got {actual}",
self.algorithm
)));
}
Ok(())
}
}
fn fixed_aes_key_size(algorithm: &str) -> Option<usize> {
let key_len = DataEncryptionAlgorithm::from_uri(algorithm)
.map(DataEncryptionAlgorithm::key_len)
.or_else(|_| KeyWrapAlgorithm::from_uri(algorithm).map(KeyWrapAlgorithm::key_len))
.ok()?;
Some(key_len * 8)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CipherData {
Value {
value: String,
},
Reference {
uri: String,
transforms: Vec<crate::xmldsig::transforms::Transform>,
},
Bytes(Vec<u8>),
}
impl CipherData {
pub(super) fn octets(&self) -> Result<std::borrow::Cow<'_, [u8]>, XmlEncError> {
use base64::Engine as _;
match self {
Self::Value { value } => base64::engine::general_purpose::STANDARD
.decode(value)
.map(std::borrow::Cow::Owned)
.map_err(|error| XmlEncError::Base64(error.to_string())),
Self::Bytes(bytes) => Ok(std::borrow::Cow::Borrowed(bytes)),
Self::Reference { .. } => Err(XmlEncError::InvalidStructure(
"CipherReference requires its original document context".into(),
)),
}
}
pub fn inline_value(&self) -> Option<&str> {
match self {
Self::Value { value } => Some(value),
Self::Reference { .. } | Self::Bytes(_) => None,
}
}
pub fn inline_value_mut(&mut self) -> Option<&mut String> {
match self {
Self::Value { value } => Some(value),
Self::Reference { .. } | Self::Bytes(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedKey {
pub sources: EncryptionKeySources,
pub id: Option<String>,
pub recipient: Option<String>,
pub key_name: Option<String>,
pub encryption_method: EncryptionMethod,
pub cipher_data: CipherData,
pub reference_list: Option<ReferenceList>,
pub carried_key_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EncryptionKeySources {
pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
pub encrypted_keys: Vec<EncryptedKey>,
pub derived_keys: Vec<super::DerivedKey>,
pub agreement_methods: Vec<super::AgreementMethod>,
}
impl EncryptionKeySources {
pub(super) fn is_empty(&self) -> bool {
self.encrypted_keys.is_empty()
&& self.encapsulation_methods.is_empty()
&& self.derived_keys.is_empty()
&& self.agreement_methods.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceList {
pub data_references: Vec<String>,
pub key_references: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedData {
pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
pub id: Option<String>,
pub encrypted_type: Option<EncryptedDataType>,
pub key_name: Option<String>,
pub encryption_method: EncryptionMethod,
pub encrypted_keys: Vec<EncryptedKey>,
pub derived_keys: Vec<super::DerivedKey>,
pub agreement_methods: Vec<super::AgreementMethod>,
pub cipher_data: CipherData,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecryptedContent {
Xml(String),
Bytes(Vec<u8>),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum XmlEncError {
#[error(transparent)]
KeyEstablishment(#[from] crate::key_establishment::KeyEstablishmentError),
#[error("ciphertext reference error: {0}")]
Transform(#[from] crate::xmldsig::TransformError),
#[error("XML Encryption policy violation: {0}")]
Policy(#[from] crate::policy::PolicyViolation),
#[error("cryptographic provider error: {0}")]
Provider(#[from] crate::provider::ProviderError),
#[error("XML parsing error: {0}")]
XmlParse(#[from] crate::xml::dom::ParseError),
#[error("XML document error: {0}")]
Document(#[from] crate::document::XmlDocumentError),
#[error("missing required {0}")]
MissingRequired(&'static str),
#[error("invalid encrypted structure: {0}")]
InvalidStructure(String),
#[error("invalid XML Encryption operation plan: {0}")]
OperationPlan(String),
#[error("selected node ID is missing or ambiguous: {id}")]
SelectedNodeUnavailable {
id: String,
},
#[error("unsupported encryption algorithm: {0}")]
UnsupportedAlgorithm(String),
#[error("invalid base64 data: {0}")]
Base64(String),
#[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
DataTooShort {
algorithm: &'static str,
minimum: usize,
actual: usize,
},
#[error(
"{algorithm} ciphertext length must be a non-zero multiple of {block} bytes, got {actual}"
)]
InvalidCbcCiphertextLength {
algorithm: DataEncryptionAlgorithm,
block: usize,
actual: usize,
},
#[error("invalid XMLEnc padding")]
InvalidPadding,
#[error("AEAD authentication failed")]
AeadAuthenticationFailed,
#[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
InvalidKeySize {
algorithm: DataEncryptionAlgorithm,
expected: usize,
actual: usize,
},
#[error(
"{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
)]
AmbiguousKeyCandidates {
algorithm: DataEncryptionAlgorithm,
actual: usize,
},
#[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
InvalidKekSize {
algorithm: KeyWrapAlgorithm,
expected: usize,
actual: usize,
},
#[error("wrapped-key value must be {expected} bytes, got {actual}")]
InvalidWrappedKeyLength {
expected: usize,
actual: usize,
},
#[error("invalid encryption configuration: {0}")]
InvalidEncryptionConfig(String),
#[error("no suitable decryption key was resolved")]
KeyNotFound,
#[error("no matching EncryptedData element was found")]
EncryptedDataNotFound,
#[error("more than one EncryptedData element matched; select one by Id")]
AmbiguousEncryptedData,
#[error("no matching element was found for encryption")]
EncryptionTargetNotFound,
#[error("more than one element matched the encryption target")]
AmbiguousEncryptionTarget,
#[error("EncryptedData must declare Element or Content Type for document replacement")]
ReplacementRequiresXml,
#[error("RSA-OAEP key unwrap failed: {0}")]
Rsa(String),
#[error("RSA-OAEP key wrap failed: {0}")]
RsaEncrypt(String),
#[error("AES key unwrap failed integrity validation")]
KeyWrapIntegrity,
#[error("operating-system random number generation failed: {0}")]
Rng(String),
#[error("XML encryption serialization failed: {0}")]
XmlSerialize(String),
#[error("decrypted XML is not valid UTF-8: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
}
impl From<crate::operation::OperationPlanError> for XmlEncError {
fn from(error: crate::operation::OperationPlanError) -> Self {
Self::OperationPlan(error.to_string())
}
}
impl fmt::Display for DataEncryptionAlgorithm {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
#[cfg(feature = "legacy-algorithms")]
Self::TripleDesCbc => "Triple DES CBC",
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Cbc => "AES-192-CBC",
#[cfg(feature = "legacy-algorithms")]
Self::Aes192Gcm => "AES-192-GCM",
Self::Aes128Cbc => "AES-128-CBC",
Self::Aes256Cbc => "AES-256-CBC",
Self::Aes128Gcm => "AES-128-GCM",
Self::Aes256Gcm => "AES-256-GCM",
Self::Camellia128Cbc => "Camellia-128-CBC",
Self::Camellia192Cbc => "Camellia-192-CBC",
Self::Camellia256Cbc => "Camellia-256-CBC",
Self::ChaCha20 => "ChaCha20",
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
})
}
}
#[cfg(test)]
mod framing_tests {
use super::*;
#[test]
fn cbc_framing_error_names_the_selected_cipher() {
let error = validate_ciphertext_framing(DataEncryptionAlgorithm::Aes128Cbc, 33)
.expect_err("AES ciphertext body is not block-aligned");
assert_eq!(
error.to_string(),
"AES-128-CBC ciphertext length must be a non-zero multiple of 16 bytes, got 17"
);
#[cfg(feature = "legacy-algorithms")]
{
let error = validate_ciphertext_framing(DataEncryptionAlgorithm::TripleDesCbc, 17)
.expect_err("3DES ciphertext body is not block-aligned");
assert_eq!(
error.to_string(),
"Triple DES CBC ciphertext length must be a non-zero multiple of 8 bytes, got 9"
);
}
}
}