From c10e7b74d6de0c81018603e3d67d6bd06c244a75 Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Tue, 9 Apr 2024 18:01:34 +0200 Subject: [PATCH 1/5] Add encrypted response to `test_vectors` --- test_vectors/response_encrypted.xml | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test_vectors/response_encrypted.xml diff --git a/test_vectors/response_encrypted.xml b/test_vectors/response_encrypted.xml new file mode 100644 index 0000000..cb72e20 --- /dev/null +++ b/test_vectors/response_encrypted.xml @@ -0,0 +1,101 @@ + + + http://idp.example.com/metadata.php + + + + + + + + + + + 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 + 1HmDiOSkEn+2NmOHuEFeA+D8jsCAL5VJ3emK + + + + + + + + + + + + + + + + + + + MIICfjCCAeegAwIBAgIBADANBgkqhkiG9w0BAQ0FADBcMQswCQYDVQQGEwJ1czEQMA4GA1UECAwH + QmF2YXJpYTETMBEGA1UECgwKSCAmIEQgR21iSDEmMCQGA1UEAwwdaHR0cHM6Ly9kZXYuc2Vydmlj + ZS1vemcubG9jYWwwHhcNMjAwOTE2MDUzOTE4WhcNMjEwOTE2MDUzOTE4WjBcMQswCQYDVQQGEwJ1 + czEQMA4GA1UECAwHQmF2YXJpYTETMBEGA1UECgwKSCAmIEQgR21iSDEmMCQGA1UEAwwdaHR0cHM6 + Ly9kZXYuc2VydmljZS1vemcubG9jYWwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4EtZ3z + 75gAGTEsPuGRyGw63wb5BTFV4JOuZuuCVYhwpJjvr4oo0TXR1Rhe+0jfEWqBfB5R+HpLgqyJYqSE + JQjQfxu2YXKQTlDKWgYxV5qUQS8TzjmlZ9QtNvHwN1vJOhy+NbmdWyTWP4NULF3nBlsCpXSDIbnl + BPp83eOYUMMhAgMBAAGjUDBOMB0GA1UdDgQWBBSkiHUXlzmJ2glQ8/1+4gBwEi5LqTAfBgNVHSME + GDAWgBSkiHUXlzmJ2glQ8/1+4gBwEi5LqTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GB + ALratqPXXMFhShW4pnJg+VTkhSToBYIqJOGnJhxBbwHqTW6yFQyB0QCS1+zBuQmVmGelT6ND9VW9 + 3oVm5RnFBo8YiykWKCSfD6XcgCRFYJ8r6S0it9HMKzkPGS9I9xoGFbxjJYq3SXx6DPoVkokvoerd + TgqMNp5HSvLC5Q6+wkgD + + + + + + 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 + + + + + From 8939eaa235225a760be1465dc2d26feda0379c81 Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Tue, 9 Apr 2024 18:19:24 +0200 Subject: [PATCH 2/5] Implement `EncryptedAssertion` --- src/key_info.rs | 46 ++++ src/schema/encrypted_assertion.rs | 335 ++++++++++++++++++++++++++++++ src/schema/mod.rs | 2 + src/schema/response.rs | 31 ++- 4 files changed, 408 insertions(+), 6 deletions(-) create mode 100644 src/schema/encrypted_assertion.rs diff --git a/src/key_info.rs b/src/key_info.rs index 07190eb..5fbb641 100644 --- a/src/key_info.rs +++ b/src/key_info.rs @@ -3,7 +3,10 @@ use quick_xml::Writer; use serde::Deserialize; use std::io::Cursor; +use crate::schema::EncryptedKey; + const NAME: &str = "ds:KeyInfo"; +const SCHEMA: &str = "xmlns:ds"; #[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct KeyInfo { @@ -83,3 +86,46 @@ impl TryFrom<&X509Data> for Event<'_> { )?))) } } + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EncryptedKeyInfo { + #[serde(rename = "@Id")] + pub id: Option, + #[serde(rename = "@xmlns:ds")] + pub ds: String, + #[serde(rename = "EncryptedKey")] + pub encrypted_key: Option, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: EncryptedKeyInfo) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&EncryptedKeyInfo> for Event<'_> { + type Error = Box; + + fn try_from(value: &EncryptedKeyInfo) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::new(NAME); + if let Some(id) = &value.id { + root.push_attribute(("Id", id.as_ref())); + } + root.push_attribute((SCHEMA, value.ds.as_ref())); + + writer.write_event(Event::Start(root))?; + if let Some(encrypted_key) = &value.encrypted_key { + let event: Event<'_> = encrypted_key.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} diff --git a/src/schema/encrypted_assertion.rs b/src/schema/encrypted_assertion.rs new file mode 100644 index 0000000..efe1c10 --- /dev/null +++ b/src/schema/encrypted_assertion.rs @@ -0,0 +1,335 @@ +use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use quick_xml::Writer; +use serde::Deserialize; +use std::io::Cursor; + +use crate::key_info::{EncryptedKeyInfo, KeyInfo}; +use crate::signature::DigestMethod; + +const NAME: &str = "saml2:EncryptedAssertion"; +const SCHEMA: (&str, &str) = ("xmlns:saml2", "urn:oasis:names:tc:SAML:2.0:assertion"); + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EncryptedAssertion { + #[serde(rename = "EncryptedData")] + pub data: Option, +} + +impl EncryptedAssertion { + pub fn encrypted_key_info(&self) -> Option<(&CipherValue, &String)> { + self.data + .as_ref() + .and_then(|ed| ed.key_info.as_ref()) + .and_then(|k| k.encrypted_key.as_ref()) + .and_then(|e| e.cipher_data.as_ref().zip(e.encryption_method.as_ref())) + .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + } + + pub fn encrypted_value_info(&self) -> Option<(&CipherValue, &String)> { + self.data + .as_ref() + .and_then(|ed| ed.cipher_data.as_ref().zip(ed.encryption_method.as_ref())) + .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + } +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: EncryptedAssertion) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&EncryptedAssertion> for Event<'_> { + type Error = Box; + + fn try_from(value: &EncryptedAssertion) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::from_content(NAME, NAME.len()); + root.push_attribute(SCHEMA); + + writer.write_event(Event::Start(root))?; + + if let Some(encrypted_data) = &value.data { + let event: Event<'_> = encrypted_data.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +const ED_NAME: &str = "xenc:EncryptedData"; +const ED_SCHEMA: (&str, &str) = ("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EncryptedData { + #[serde(rename = "@Id")] + pub id: Option, + #[serde(rename = "@Type")] + pub ty: Option, + #[serde(rename = "EncryptionMethod")] + pub encryption_method: Option, + #[serde(rename = "KeyInfo")] + pub key_info: Option, + #[serde(rename = "CipherData")] + pub cipher_data: Option, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: EncryptedData) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&EncryptedData> for Event<'_> { + type Error = Box; + + fn try_from(value: &EncryptedData) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::from_content(ED_NAME, ED_NAME.len()); + root.push_attribute(ED_SCHEMA); + if let Some(id) = &value.id { + root.push_attribute(("Id", id.as_ref())); + } + if let Some(ty) = &value.ty { + root.push_attribute(("Type", ty.as_ref())); + } + + writer.write_event(Event::Start(root))?; + + if let Some(encryption_method) = &value.encryption_method { + let event: Event<'_> = encryption_method.try_into()?; + writer.write_event(event)?; + } + if let Some(key_info) = &value.key_info { + let event: Event<'_> = key_info.try_into()?; + writer.write_event(event)?; + } + if let Some(cipher_data) = &value.cipher_data { + let event: Event<'_> = cipher_data.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(ED_NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +const EM_NAME: &str = "xenc:EncryptionMethod"; +const EM_SCHEMA: (&str, &str) = ("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EncryptionMethod { + #[serde(rename = "@Algorithm")] + pub algorithm: Option, + #[serde(rename = "DigestMethod")] + pub digest_method: Option, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: EncryptionMethod) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&EncryptionMethod> for Event<'_> { + type Error = Box; + + fn try_from(value: &EncryptionMethod) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::from_content(EM_NAME, EM_NAME.len()); + root.push_attribute(EM_SCHEMA); + if let Some(algorithm) = &value.algorithm { + root.push_attribute(("Algorithm", algorithm.as_ref())); + } + + writer.write_event(Event::Start(root))?; + + if let Some(digest_method) = &value.digest_method { + let event: Event<'_> = digest_method.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(EM_NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +const CD_NAME: &str = "xenc:CipherData"; +const CD_SCHEMA: (&str, &str) = ("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct CipherData { + #[serde(rename = "CipherValue")] + pub cipher_value: Option, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: CipherData) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&CipherData> for Event<'_> { + type Error = Box; + + fn try_from(value: &CipherData) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::from_content(CD_NAME, CD_NAME.len()); + root.push_attribute(CD_SCHEMA); + + writer.write_event(Event::Start(root))?; + + if let Some(cipher_value) = &value.cipher_value { + let event: Event<'_> = cipher_value.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(CD_NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +const CV_NAME: &str = "xenc:CipherValue"; + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct CipherValue { + #[serde(rename = "$value")] + pub value: String, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: CipherValue) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&CipherValue> for Event<'_> { + type Error = Box; + + fn try_from(value: &CipherValue) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let root = BytesStart::from_content(CV_NAME, CV_NAME.len()); + + writer.write_event(Event::Start(root))?; + + writer.write_event(Event::Text(BytesText::new(&value.value)))?; + + writer.write_event(Event::End(BytesEnd::new(CV_NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +const EK_NAME: &str = "xenc:EncryptedKey"; +const EK_SCHEMA: (&str, &str) = ("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); + +#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EncryptedKey { + #[serde(rename = "@Id")] + pub id: Option, + #[serde(rename = "@Recipient")] + pub recipient: Option, + #[serde(rename = "EncryptionMethod")] + pub encryption_method: Option, + #[serde(rename = "KeyInfo")] + pub key_info: Option, + #[serde(rename = "CipherData")] + pub cipher_data: Option, +} + +impl TryFrom for Event<'_> { + type Error = Box; + + fn try_from(value: EncryptedKey) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&EncryptedKey> for Event<'_> { + type Error = Box; + + fn try_from(value: &EncryptedKey) -> Result { + let mut write_buf = Vec::new(); + let mut writer = Writer::new(Cursor::new(&mut write_buf)); + let mut root = BytesStart::from_content(EK_NAME, EK_NAME.len()); + root.push_attribute(EK_SCHEMA); + if let Some(id) = &value.id { + root.push_attribute(("Id", id.as_ref())); + } + if let Some(recipient) = &value.recipient { + root.push_attribute(("Recipient", recipient.as_ref())); + } + + writer.write_event(Event::Start(root))?; + + if let Some(encryption_method) = &value.encryption_method { + let event: Event<'_> = encryption_method.try_into()?; + writer.write_event(event)?; + } + + if let Some(key_info) = &value.key_info { + let event: Event<'_> = key_info.try_into()?; + writer.write_event(event)?; + } + + if let Some(cipher_data) = &value.cipher_data { + let event: Event<'_> = cipher_data.try_into()?; + writer.write_event(event)?; + } + + writer.write_event(Event::End(BytesEnd::new(EK_NAME)))?; + Ok(Event::Text(BytesText::from_escaped(String::from_utf8( + write_buf, + )?))) + } +} + +#[cfg(test)] +mod test { + use crate::schema::Response; + + #[test] + fn test_encrypted_assertion_key_info() { + let response_xml = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/response_encrypted.xml", + )); + let response: Response = response_xml + .parse() + .expect("failed to parse response_encrypted.xml"); + + let key_info_exists = response + .encrypted_assertion + .expect("EncryptedAssertion missing") + .encrypted_key_info() + .is_some(); + + assert!(key_info_exists, "KeyInfo missing on EncryptedAssertion"); + } +} diff --git a/src/schema/mod.rs b/src/schema/mod.rs index 5a30181..302de33 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -1,5 +1,6 @@ pub mod authn_request; mod conditions; +mod encrypted_assertion; mod issuer; mod name_id_policy; mod response; @@ -7,6 +8,7 @@ mod subject; pub use authn_request::AuthnRequest; pub use conditions::*; +pub use encrypted_assertion::{CipherValue, EncryptedAssertion, EncryptedKey}; pub use issuer::Issuer; pub use name_id_policy::NameIdPolicy; pub use response::Response; diff --git a/src/schema/response.rs b/src/schema/response.rs index 70bf4d2..1800e9c 100644 --- a/src/schema/response.rs +++ b/src/schema/response.rs @@ -1,4 +1,4 @@ -use crate::schema::{Assertion, Issuer, Status}; +use crate::schema::{Assertion, EncryptedAssertion, Issuer, Status}; use crate::signature::Signature; use chrono::prelude::*; use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}; @@ -32,7 +32,7 @@ pub struct Response { #[serde(rename = "Status")] pub status: Option, #[serde(rename = "EncryptedAssertion")] - pub encrypted_assertion: Option, + pub encrypted_assertion: Option, #[serde(rename = "Assertion")] pub assertion: Option, } @@ -111,10 +111,10 @@ impl TryFrom<&Response> for Event<'_> { writer.write_event(event)?; } - // TODO: encrypted assertion - // if let Some(assertion) = &self.encrypted_assertion { - // writer.write(assertion.to_xml()?.as_bytes())?; - // } + if let Some(encrypted_assertion) = &value.encrypted_assertion { + let event: Event<'_> = encrypted_assertion.try_into()?; + writer.write_event(event)?; + } writer.write_event(Event::End(BytesEnd::new(NAME)))?; Ok(Event::Text(BytesText::from_escaped(String::from_utf8( @@ -183,4 +183,23 @@ mod test { assert_eq!(expected_response, actual_response); } + + #[test] + fn test_deserialize_serialize_response_encrypted_assertion() { + let response_xml = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_vectors/response_encrypted.xml", + )); + let expected_response: Response = response_xml + .parse() + .expect("failed to parse response_encrypted.xml"); + let serialized_response = expected_response + .to_xml() + .expect("failed to convert response to xml"); + let actual_response: Response = serialized_response + .parse() + .expect("failed to re-parse response"); + + assert_eq!(expected_response, actual_response); + } } From a9494a985b6357cd7c9be2c3695ef8665b678029 Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Wed, 10 Apr 2024 11:54:55 +0200 Subject: [PATCH 3/5] Add decryption of `EncryptedAssertion` --- src/crypto.rs | 64 +++++++++++++++- src/idp/tests.rs | 8 +- src/schema/encrypted_assertion.rs | 121 ++++++++++++++++++++++++++++-- src/service_provider/mod.rs | 67 ++++++++++++++++- 4 files changed, 246 insertions(+), 14 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 5815d2c..99be38e 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,7 +1,5 @@ use base64::{engine::general_purpose, Engine as _}; use std::collections::HashMap; -use std::convert::TryInto; -use std::ffi::CString; use std::str::FromStr; use thiserror::Error; @@ -9,6 +7,10 @@ use thiserror::Error; use crate::xmlsec::{self, XmlSecKey, XmlSecKeyFormat, XmlSecSignatureContext}; #[cfg(feature = "xmlsec")] use libxml::parser::Parser as XmlParser; +#[cfg(feature = "xmlsec")] +use std::ffi::CString; +#[cfg(feature = "xmlsec")] +use openssl::symm::{Cipher, Crypter, Mode}; #[cfg(feature = "xmlsec")] const XMLNS_XML_DSIG: &str = "http://www.w3.org/2000/09/xmldsig#"; @@ -671,6 +673,64 @@ impl UrlVerifier { } } +#[cfg(feature = "xmlsec")] +pub(crate) fn decrypt( + t: Cipher, + key: &[u8], + iv: Option<&[u8]>, + data: &[u8], +) -> Result, Error> { + let mut decrypter = + Crypter::new(t, Mode::Decrypt, key, iv).map_err(|error| Error::OpenSSLError { error })?; + decrypter.pad(false); + let mut out = vec![0; data.len() + t.block_size()]; + + let count = decrypter + .update(data, &mut out) + .map_err(|error| Error::OpenSSLError { error })?; + + let rest = decrypter + .finalize(&mut out[count..]) + .map_err(|error| Error::OpenSSLError { error })?; + + out.truncate(count + rest); + Ok(out) +} + +#[cfg(feature = "xmlsec")] +pub(crate) fn decrypt_aead( + t: Cipher, + key: &[u8], + iv: Option<&[u8]>, + aad: &[u8], + data: &[u8], + tag: &[u8], +) -> Result, Error> { + let mut decrypter = + Crypter::new(t, Mode::Decrypt, key, iv).map_err(|error| Error::OpenSSLError { error })?; + decrypter.pad(false); + let mut out = vec![0; data.len() + t.block_size()]; + + decrypter + .aad_update(aad) + .map_err(|error| Error::OpenSSLError { error })?; + + let count = decrypter + .update(data, &mut out) + .map_err(|error| Error::OpenSSLError { error })?; + + decrypter + .set_tag(tag) + .map_err(|error| Error::OpenSSLError { error })?; + + let rest = decrypter + .finalize(&mut out[count..]) + .map_err(|error| Error::OpenSSLError { error })?; + + out.truncate(count + rest); + Ok(out) +} + #[cfg(test)] mod test { use super::UrlVerifier; diff --git a/src/idp/tests.rs b/src/idp/tests.rs index 792a418..a310992 100644 --- a/src/idp/tests.rs +++ b/src/idp/tests.rs @@ -220,8 +220,8 @@ fn test_do_not_accept_unsigned_response() { let err = resp.err().unwrap(); assert_eq!( - err, - crate::service_provider::Error::FailedToParseSamlResponse + err.to_string(), + crate::service_provider::Error::FailedToParseSamlResponse.to_string() ); } @@ -248,8 +248,8 @@ fn test_do_not_accept_signed_with_wrong_key() { let err = resp.err().unwrap(); assert_eq!( - err, - crate::service_provider::Error::FailedToValidateSignature + err.to_string(), + crate::service_provider::Error::FailedToValidateSignature.to_string() ); } diff --git a/src/schema/encrypted_assertion.rs b/src/schema/encrypted_assertion.rs index efe1c10..b22bfd3 100644 --- a/src/schema/encrypted_assertion.rs +++ b/src/schema/encrypted_assertion.rs @@ -3,6 +3,15 @@ use quick_xml::Writer; use serde::Deserialize; use std::io::Cursor; +#[cfg(feature = "xmlsec")] +use openssl::{pkey::Private, rsa}; +#[cfg(feature = "xmlsec")] +use crate::crypto::{decrypt, decrypt_aead}; +#[cfg(feature = "xmlsec")] +use crate::schema::Assertion; +#[cfg(feature = "xmlsec")] +use crate::service_provider::Error; + use crate::key_info::{EncryptedKeyInfo, KeyInfo}; use crate::signature::DigestMethod; @@ -19,17 +28,26 @@ impl EncryptedAssertion { pub fn encrypted_key_info(&self) -> Option<(&CipherValue, &String)> { self.data .as_ref() - .and_then(|ed| ed.key_info.as_ref()) - .and_then(|k| k.encrypted_key.as_ref()) - .and_then(|e| e.cipher_data.as_ref().zip(e.encryption_method.as_ref())) - .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + .and_then(|ed| ed.key_info()) } pub fn encrypted_value_info(&self) -> Option<(&CipherValue, &String)> { self.data .as_ref() - .and_then(|ed| ed.cipher_data.as_ref().zip(ed.encryption_method.as_ref())) - .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + .and_then(|ed| ed.value_info()) + } + + #[cfg(feature = "xmlsec")] + pub fn decrypt(&self, decryption_key: &rsa::Rsa) -> Result { + let (ekey, method) = self + .encrypted_key_info() + .ok_or(Error::MissingEncryptedKeyInfo)?; + let decrypted_key = decrypt_assertion_key_info(ekey, method, decryption_key)?; + + let (evalue, method) = self + .encrypted_value_info() + .ok_or(Error::MissingEncryptedValueInfo)?; + decrypt_assertion_value_info(evalue, method, &decrypted_key) } } @@ -64,6 +82,80 @@ impl TryFrom<&EncryptedAssertion> for Event<'_> { } } +#[cfg(feature = "xmlsec")] +fn decrypt_assertion_key_info(cipher_value: &CipherValue, method: &str, decryption_key: &rsa::Rsa) -> Result, Error> { + use openssl::rsa::Padding; + + let padding = match method { + "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" => Padding::PKCS1_OAEP, + _ => { + return Err(Error::EncryptedAssertionKeyMethodUnsupported { method: method.to_string()}); + } + }; + + let encrypted_key = openssl::base64::decode_block(&cipher_value.value.lines().collect::())?; + let pkey_size = decryption_key.size() as usize; + let mut decrypted_key = vec![0u8; pkey_size]; + let i = decryption_key + .private_decrypt(&encrypted_key, &mut decrypted_key, padding)?; + Ok(decrypted_key[0..i].to_vec()) +} + +#[cfg(feature = "xmlsec")] +fn decrypt_assertion_value_info(cipher_value: &CipherValue, method: &str, decryption_key: &[u8]) -> Result { + use openssl::symm::Cipher; + + let encoded_value = openssl::base64::decode_block(&cipher_value.value.lines().collect::())?; + + let plaintext = match method { + "http://www.w3.org/2001/04/xmlenc#aes128-cbc" => { + let cipher = Cipher::aes_128_cbc(); + let iv_len = cipher.iv_len().unwrap(); + decrypt( + cipher, + decryption_key, + Some(&encoded_value[0..iv_len]), + &encoded_value[iv_len..] + )? + }, + "http://www.w3.org/2009/xmlenc11#aes128-gcm" => { + let cipher = Cipher::aes_128_gcm(); + let iv_len = cipher.iv_len().unwrap(); + let tag_len = 16 as usize; + let data_end = encoded_value.len() - tag_len; + decrypt_aead( + cipher, + decryption_key, + Some(&encoded_value[0..iv_len]), + &[], + &encoded_value[iv_len..data_end], + &encoded_value[data_end..] + )? + }, + _ => { + return Err(Error::EncryptedAssertionValueMethodUnsupported { method: method.to_string()}); + } + }; + + let assertion_string = match String::from_utf8(plaintext) { + Ok(s) => s, + Err(e) => { + let i = e.utf8_error().valid_up_to(); + let mut plaintext = e.into_bytes(); + plaintext.truncate(i); + let s = + String::from_utf8(plaintext).map_err(|_| Error::EncryptedAssertionInvalid)?; + let fi = s.find("<").unwrap(); + let li = s.rfind(">").unwrap(); + s[fi..li + 1].to_owned() + } + }; + + quick_xml::de::from_str(&assertion_string).map_err(|_e| { + Error::FailedToDecryptAssertion + }) +} + const ED_NAME: &str = "xenc:EncryptedData"; const ED_SCHEMA: (&str, &str) = ("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); @@ -81,6 +173,23 @@ pub struct EncryptedData { pub cipher_data: Option, } +impl EncryptedData { + pub fn key_info(&self) -> Option<(&CipherValue, &String)> { + self.key_info + .as_ref() + .and_then(|k| k.encrypted_key.as_ref()) + .and_then(|e| e.cipher_data.as_ref().zip(e.encryption_method.as_ref())) + .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + } + + pub fn value_info(&self) -> Option<(&CipherValue, &String)> { + self.cipher_data + .as_ref() + .zip(self.encryption_method.as_ref()) + .and_then(|(cd, em)| cd.cipher_value.as_ref().zip(em.algorithm.as_ref())) + } +} + impl TryFrom for Event<'_> { type Error = Box; diff --git a/src/service_provider/mod.rs b/src/service_provider/mod.rs index a237052..a2f8249 100644 --- a/src/service_provider/mod.rs +++ b/src/service_provider/mod.rs @@ -23,13 +23,15 @@ mod tests; #[cfg(feature = "xmlsec")] use crate::crypto::reduce_xml_to_signed; +#[cfg(feature = "xmlsec")] +use crate::schema::EncryptedAssertion; #[cfg(not(feature = "xmlsec"))] fn reduce_xml_to_signed(xml_str: &str, _keys: &Vec) -> Result { Ok(String::from(xml_str)) } -#[derive(Debug, Error, PartialEq)] +#[derive(Debug, Error)] pub enum Error { #[error( "SAML response destination does not match SP ACS URL. {:?} != {:?}", @@ -88,6 +90,33 @@ pub enum Error { FailedToParseCert { cert: String }, #[error("Unexpected Error Occurred!")] UnexpectedError, + #[cfg(feature = "xmlsec")] + #[error("Missing private key on service provider")] + MissingPrivateKeySP, + #[cfg(feature = "xmlsec")] + #[error("Missing encrypted key info")] + MissingEncryptedKeyInfo, + #[cfg(feature = "xmlsec")] + #[error("Missing encrypted value info")] + MissingEncryptedValueInfo, + #[cfg(feature = "xmlsec")] + #[error("Unsupported key encryption method for encrypted assertion: {method}")] + EncryptedAssertionKeyMethodUnsupported { method: String }, + #[cfg(feature = "xmlsec")] + #[error("Unsupported value encryption method for encrypted assertion: {method}")] + EncryptedAssertionValueMethodUnsupported { method: String }, + #[cfg(feature = "xmlsec")] + #[error("Encrypted assertion invalid")] + EncryptedAssertionInvalid, + #[cfg(feature = "xmlsec")] + #[error("OpenSSL error stack: {}", error)] + OpenSSLError { + #[from] + error: openssl::error::ErrorStack, + }, + #[cfg(feature = "xmlsec")] + #[error("Failed to decrypt assertion")] + FailedToDecryptAssertion, #[error("Failed to parse SAMLResponse")] FailedToParseSamlResponse, @@ -99,6 +128,21 @@ pub enum Error { MissingSloUrl, } +impl From for Error { + fn from(value: crypto::Error) -> Self { + match value { + crypto::Error::InvalidSignature + | crypto::Error::Base64Error { .. } + | crypto::Error::XmlMissingRootElement + | crypto::Error::XmlParseError { .. } + | crypto::Error::XmlSecError { .. } + | crypto::Error::XmlAttributeRemovalError { .. } + | crypto::Error::XmlNamespaceDefinitionError { .. } => Error::FailedToParseSamlResponse, + crypto::Error::OpenSSLError { error } => Error::OpenSSLError { error }, + } + } +} + #[derive(Builder, Clone)] #[builder(default, setter(into))] pub struct ServiceProvider { @@ -379,7 +423,16 @@ impl ServiceProvider { } } - if let Some(_encrypted_assertion) = &response.encrypted_assertion { + if let Some(encrypted_assertion) = &response.encrypted_assertion { + #[cfg(feature = "xmlsec")] + return self + .decrypt_assertion(encrypted_assertion) + .and_then(|assertion| { + self.validate_assertion(&assertion, possible_request_ids) + .and(Ok(assertion)) + }); + + #[cfg(not(feature = "xmlsec"))] Err(Error::EncryptedAssertionsNotYetSupported) } else if let Some(assertion) = &response.assertion { self.validate_assertion(assertion, possible_request_ids)?; @@ -389,6 +442,16 @@ impl ServiceProvider { } } + #[cfg(feature = "xmlsec")] + fn decrypt_assertion( + &self, + encrypted_assertion: &EncryptedAssertion, + ) -> Result { + let key = self.key.as_ref().ok_or(Error::MissingPrivateKeySP)?; + + encrypted_assertion.decrypt(key) + } + fn validate_assertion( &self, assertion: &Assertion, From ef8f7b8f3cee59c915928c6bcab4f63ccf102060 Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Wed, 10 Apr 2024 12:24:07 +0200 Subject: [PATCH 4/5] Remove unnecessary `map_err` --- src/crypto.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 99be38e..349e71e 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -680,18 +680,12 @@ pub(crate) fn decrypt( iv: Option<&[u8]>, data: &[u8], ) -> Result, Error> { - let mut decrypter = - Crypter::new(t, Mode::Decrypt, key, iv).map_err(|error| Error::OpenSSLError { error })?; + let mut decrypter = Crypter::new(t, Mode::Decrypt, key, iv)?; decrypter.pad(false); let mut out = vec![0; data.len() + t.block_size()]; - let count = decrypter - .update(data, &mut out) - .map_err(|error| Error::OpenSSLError { error })?; - - let rest = decrypter - .finalize(&mut out[count..]) - .map_err(|error| Error::OpenSSLError { error })?; + let count = decrypter.update(data, &mut out)?; + let rest = decrypter.finalize(&mut out[count..])?; out.truncate(count + rest); Ok(out) @@ -706,26 +700,14 @@ pub(crate) fn decrypt_aead( data: &[u8], tag: &[u8], ) -> Result, Error> { - let mut decrypter = - Crypter::new(t, Mode::Decrypt, key, iv).map_err(|error| Error::OpenSSLError { error })?; + let mut decrypter = Crypter::new(t, Mode::Decrypt, key, iv)?; decrypter.pad(false); let mut out = vec![0; data.len() + t.block_size()]; - decrypter - .aad_update(aad) - .map_err(|error| Error::OpenSSLError { error })?; - - let count = decrypter - .update(data, &mut out) - .map_err(|error| Error::OpenSSLError { error })?; - - decrypter - .set_tag(tag) - .map_err(|error| Error::OpenSSLError { error })?; - - let rest = decrypter - .finalize(&mut out[count..]) - .map_err(|error| Error::OpenSSLError { error })?; + decrypter.aad_update(aad)?; + let count = decrypter.update(data, &mut out)?; + decrypter.set_tag(tag)?; + let rest = decrypter.finalize(&mut out[count..])?; out.truncate(count + rest); Ok(out) From 827501564329723feee19b85f675dabfca1f1ddd Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Wed, 10 Apr 2024 12:29:48 +0200 Subject: [PATCH 5/5] Update README.md and CHANGELOG --- CHANGELOG | 3 +++ README.md | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a25edfb..9d2e64a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Changelog +### Unreleased +- add `EncryptedAssertion` and ability to decrypt it + ### 0.0.15 - Updates dependencies diff --git a/README.md b/README.md index 97f28cd..2558d7b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,12 @@ Current Features: - IDP-initiated SSO - SP-initiated SSO Redirect-POST binding - Helpers for validating SAML assertions - - Encrypted assertions aren't supported yet + - Encrypted assertions only support: + - **key info:** + - `http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p` + - **value info:** + - `http://www.w3.org/2001/04/xmlenc#aes128-cbc` + - `http://www.w3.org/2009/xmlenc11#aes128-gcm` - Verify SAMLRequest (AuthnRequest) message signatures - Create signed SAMLResponse (Response) messages