use std::fmt;
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 = 16 * 1024 * 1024;
pub const MAX_ENCRYPTION_PLAINTEXT_LEN: usize = (MAX_CIPHER_VALUE_BASE64_LEN / 4 * 3) - 32;
pub const MAX_ENCRYPTION_DOCUMENT_LEN: usize = MAX_CIPHER_VALUE_BASE64_LEN;
pub const MAX_ENCRYPTION_RECIPIENTS: usize = 64;
pub const MAX_ENCRYPTION_METADATA_LEN: usize = 4 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncryptedDataType {
Element,
Content,
Other(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataEncryptionAlgorithm {
Aes128Cbc,
Aes256Cbc,
Aes128Gcm,
Aes256Gcm,
}
impl DataEncryptionAlgorithm {
pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
match uri {
"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),
_ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
}
}
pub const fn key_len(self) -> usize {
match self {
Self::Aes128Cbc | Self::Aes128Gcm => 16,
Self::Aes256Cbc | Self::Aes256Gcm => 32,
}
}
pub const fn uri(self) -> &'static str {
match self {
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",
}
}
}
impl KeyTransportAlgorithm {
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),
_ => 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",
}
}
}
impl KeyWrapAlgorithm {
pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
match uri {
"http://www.w3.org/2001/04/xmlenc#kw-aes128" => Ok(Self::AesKw128),
"http://www.w3.org/2001/04/xmlenc#kw-aes256" => Ok(Self::AesKw256),
_ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
}
}
pub const fn key_len(self) -> usize {
match self {
Self::AesKw128 => 16,
Self::AesKw256 => 32,
}
}
pub const fn uri(self) -> &'static str {
match self {
Self::AesKw128 => "http://www.w3.org/2001/04/xmlenc#kw-aes128",
Self::AesKw256 => "http://www.w3.org/2001/04/xmlenc#kw-aes256",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyTransportAlgorithm {
RsaOaepMgf1p,
RsaOaep11,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyWrapAlgorithm {
AesKw128,
AesKw256,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OaepDigestAlgorithm {
Sha1,
Sha256,
Sha384,
Sha512,
}
impl OaepDigestAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
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",
}
}
pub const fn mgf_uri(self) -> &'static str {
match self {
Self::Sha1 => "http://www.w3.org/2009/xmlenc11#mgf1sha1",
Self::Sha256 => "http://www.w3.org/2009/xmlenc11#mgf1sha256",
Self::Sha384 => "http://www.w3.org/2009/xmlenc11#mgf1sha384",
Self::Sha512 => "http://www.w3.org/2009/xmlenc11#mgf1sha512",
}
}
}
#[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 {
RsaOaep {
public_key: RsaPublicKey,
parameters: RsaOaepParameters,
recipient: Option<String>,
key_name: Option<String>,
},
AesKeyWrap {
kek: Vec<u8>,
algorithm: KeyWrapAlgorithm,
recipient: Option<String>,
key_name: Option<String>,
},
}
impl fmt::Debug for EncryptionRecipient {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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 {
pub fn rsa_oaep(public_key: RsaPublicKey) -> 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 {
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 {
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>,
pub allow_dtd: bool,
}
#[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>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CipherData {
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedKey {
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)]
pub struct ReferenceList {
pub data_references: Vec<String>,
pub key_references: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedData {
pub id: Option<String>,
pub encrypted_type: Option<EncryptedDataType>,
pub key_name: Option<String>,
pub encryption_method: EncryptionMethod,
pub encrypted_keys: Vec<EncryptedKey>,
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("XML parsing error: {0}")]
XmlParse(#[from] roxmltree::Error),
#[error("missing required {0}")]
MissingRequired(&'static str),
#[error("invalid encrypted structure: {0}")]
InvalidStructure(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("AES-CBC ciphertext length must be a non-zero multiple of 16 bytes, got {0}")]
InvalidCbcCiphertextLength(usize),
#[error("invalid XMLEnc padding length {pad_len} for {block_size}-byte block")]
InvalidPadding {
pad_len: u8,
block_size: usize,
},
#[error("AES-GCM authentication failed")]
AeadAuthenticationFailed,
#[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
InvalidKeySize {
algorithm: DataEncryptionAlgorithm,
expected: usize,
actual: usize,
},
#[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
InvalidKekSize {
algorithm: KeyWrapAlgorithm,
expected: usize,
actual: usize,
},
#[error("encryption plaintext exceeds {maximum}-byte limit: got {actual} bytes")]
PlaintextTooLarge {
maximum: usize,
actual: usize,
},
#[error("encryption document exceeds {maximum}-byte limit: got {actual} bytes")]
DocumentTooLarge {
maximum: usize,
actual: usize,
},
#[error("encryption recipient count exceeds {maximum}: got {actual}")]
TooManyRecipients {
maximum: usize,
actual: usize,
},
#[error("{field} exceeds {maximum}-byte encryption metadata limit: got {actual} bytes")]
EncryptionMetadataTooLarge {
field: &'static str,
maximum: 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 fmt::Display for DataEncryptionAlgorithm {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Aes128Cbc => "AES-128-CBC",
Self::Aes256Cbc => "AES-256-CBC",
Self::Aes128Gcm => "AES-128-GCM",
Self::Aes256Gcm => "AES-256-GCM",
})
}
}