From c4af052fe9112b39fb22b40f2a1577ea3b27172d Mon Sep 17 00:00:00 2001 From: sebastian Date: Thu, 16 Jul 2026 22:23:45 -0600 Subject: [PATCH 1/2] fix(ci): restore failing checks --- .github/workflows/integration.yml | 1 - Cargo.lock | 194 +++++++++++------- Cargo.toml | 4 +- crates/rustauth-core/src/utils/url.rs | 5 +- crates/rustauth-saml/src/bridge.rs | 98 +++++---- crates/rustauth-saml/src/saml/assertions.rs | 22 +- crates/rustauth-saml/src/saml/encryption.rs | 7 +- crates/rustauth-saml/src/saml/logout.rs | 13 +- crates/rustauth-saml/src/saml/security.rs | 2 +- crates/rustauth-saml/src/saml/signature.rs | 44 ++-- crates/rustauth-saml/src/saml/xml.rs | 24 ++- crates/rustauth-saml/tests/security.rs | 48 +++-- .../tests/sso/fixtures/idp_fixtures.rs | 24 +-- .../tests/sso/fixtures/saml_crypto.rs | 28 +-- 14 files changed, 295 insertions(+), 219 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 4f9229bb..6297f1a3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,7 +8,6 @@ on: - release/0.3.0 merge_group: workflow_dispatch: - workflow_dispatch: concurrency: group: integration-${{ github.workflow }}-${{ github.ref }} diff --git a/Cargo.lock b/Cargo.lock index 2199e253..73125aaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.21.7" @@ -501,9 +507,9 @@ dependencies = [ [[package]] name = "bergshamra" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d7fa82e5a4437e4766511843d661355af8005b30576db52b96b595865353dc" +checksum = "e8de2a24da6668062582e91a645072fd37548d40220c5100881e03dd7033a9ad" dependencies = [ "base64 0.22.1", "bergshamra-c14n", @@ -521,9 +527,9 @@ dependencies = [ [[package]] name = "bergshamra-c14n" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5802757aa34889959a88721cf22044fb13ab3b3fae556bfc94d3679f74d2b6" +checksum = "58ca6d3e7f9feac696955542543b6fa2a0fc73c1acb44e8fd7a7e7904944e431" dependencies = [ "bergshamra-core", "bergshamra-xml", @@ -532,18 +538,18 @@ dependencies = [ [[package]] name = "bergshamra-core" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de25efa9a8386aad3d17cfd029277ae8ccb917475848eb66d6bf72c6a70ad75" +checksum = "55b84b45351fa895727d60062a25e5423ccfbab715ed7fcc8bd584ffd32fbd04" dependencies = [ "thiserror 2.0.18", ] [[package]] name = "bergshamra-crypto" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7486bf4a51a6277de8b8f3979c43151ffed8c6636381b84b4825e19ca803884d" +checksum = "d99a30ae3309ea195496b27bb133360d8f19b69aa315d96c42357c7603a76836" dependencies = [ "aes", "aes-gcm", @@ -555,6 +561,7 @@ dependencies = [ "dsa", "ecdsa", "ed25519-dalek", + "getrandom 0.4.2", "hkdf", "hmac 0.12.1", "kryptering", @@ -568,7 +575,7 @@ dependencies = [ "pbkdf2", "pkcs1", "pkcs8 0.10.2", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand 0.8.6", "ripemd", "rsa", @@ -582,9 +589,9 @@ dependencies = [ [[package]] name = "bergshamra-dsig" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4f0a9ec25c1a804dbb717ecf47da4579d41e35a0db127d83c29396fc79290" +checksum = "77b4da9dedf7ff55d566243e9fcd4ded304c455c15d5c4dfca624dc5afd88ed8" dependencies = [ "base64 0.22.1", "bergshamra-c14n", @@ -607,9 +614,9 @@ dependencies = [ [[package]] name = "bergshamra-enc" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41620927810588fc9155932f1020fd501d8b658a2a4831e56333748a49ee647e" +checksum = "949020f300b5084ef2b6ad1373a4af39a3bcffb536d188bc84043d07cc914fb1" dependencies = [ "base64 0.22.1", "bergshamra-c14n", @@ -628,9 +635,9 @@ dependencies = [ [[package]] name = "bergshamra-keys" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4b1a745bef9d6ea50a8ed8145d364a85892a8b2e5a499b9435089b14df167b" +checksum = "588151f20d7289208959d20f8bb3e8134a5ed6eb29ad0d9f548dc436b4cda241" dependencies = [ "base64 0.22.1", "bergshamra-core", @@ -653,7 +660,7 @@ dependencies = [ "pkcs1", "pkcs5", "pkcs8 0.10.2", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rsa", "sha1", "sha2 0.10.9", @@ -668,9 +675,9 @@ dependencies = [ [[package]] name = "bergshamra-pkcs12" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d46759b4868e86830544904e1d15ae061bc387384c7a806703096fb51e380c5" +checksum = "05af483a06eab517aad705ff48dbc0537ee34cb07b0518220d471ee236925092" dependencies = [ "aes", "bergshamra-core", @@ -686,9 +693,9 @@ dependencies = [ [[package]] name = "bergshamra-transforms" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e467a1622d7fe18fc784f19d2ba0355dc82d2d425c90b2269c10361dc9b8e88a" +checksum = "966b5850e3ed05afb3ddc2c950b96572608efd3b94ba90f3b657ad901dbd9950" dependencies = [ "base64 0.22.1", "bergshamra-c14n", @@ -701,9 +708,9 @@ dependencies = [ [[package]] name = "bergshamra-xml" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644aeb454e2791fd16ce90db48a9175a2a464991dc76022e1ca7503815c79772" +checksum = "c164fd176781764f16570f02061f1eb61527a03f6a81a6e7eac8d6e3e5e4128b" dependencies = [ "bergshamra-core", "uppsala", @@ -1089,6 +1096,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1154,9 +1167,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] @@ -1219,6 +1232,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +dependencies = [ + "cpubits", + "ctutils", + "num-traits", + "rand_core 0.10.1", + "serdect", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -1236,28 +1263,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.1", ] [[package]] name = "cryptoki" -version = "0.7.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d645cc2c5faf466571c0c752d39d8fbc2746773b2f043ac8f9cd73bec55db9" +checksum = "ff765b99fc49f3116c9a908484486a2b92fd73c48da45c3a69716471c6cc56c6" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.0", "cryptoki-sys", "libloading", "log", - "paste", "secrecy", ] [[package]] name = "cryptoki-sys" -version = "0.1.8" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "750380200f47d4ff677be725b6e0d78b590e1d0343573dcd4b62147f25dc6efa" +checksum = "f1fd850498411e4057f1cba79e6e2bc7cbe960544c1046ab46d4685c403a1121" dependencies = [ "libloading", ] @@ -1754,8 +1782,8 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff", "generic-array", @@ -2433,6 +2461,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ + "ctutils", "typenum", ] @@ -2841,31 +2870,31 @@ dependencies = [ [[package]] name = "kryptering" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2448476edac64a0fed5f3dc93a8ce0298711e6e3e856908c3b5ef2fe26464c" +checksum = "a91a2f2be6c09c89d098b24ccfe26cec2e993d8588ea0d48b0e388ac99fc6b49" dependencies = [ "aes", "aes-gcm", "aes-kw", "cbc", + "crypto-bigint 0.7.5", "cryptoki", "des", "digest 0.10.7", "dsa", "ecdsa", "ed25519-dalek", + "getrandom 0.4.2", "hkdf", "hmac 0.12.1", "md-5 0.10.6", "ml-dsa", - "num-bigint-dig", - "num-traits", "p256", "p384", "p521", "pbkdf2", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand 0.8.6", "ripemd", "rsa", @@ -2876,6 +2905,7 @@ dependencies = [ "slh-dsa", "thiserror 2.0.18", "x25519-dalek", + "zeroize", ] [[package]] @@ -2907,12 +2937,12 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "winapi", + "windows-link", ] [[package]] @@ -3072,25 +3102,27 @@ dependencies = [ [[package]] name = "ml-dsa" -version = "0.1.0-rc.7" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6e554a2affc86740759dbe568a92abd58b47fea4e28ebe1b7bb4da99e490d4" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" dependencies = [ "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", "hybrid-array", "module-lattice", - "pkcs8 0.11.0-rc.11", - "rand_core 0.10.1", - "sha3 0.11.0", + "pkcs8 0.11.0", + "shake", "signature 3.0.0", ] [[package]] name = "module-lattice" -version = "0.1.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfecc750073acc09af2f8899b2342d520d570392ba1c3aed53eeb0d84ca4103" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" dependencies = [ + "ctutils", "hybrid-array", "num-traits", ] @@ -3415,8 +3447,8 @@ dependencies = [ [[package]] name = "opensaml" -version = "0.1.0" -source = "git+https://github.com/sebasxsala/opensaml-rs?rev=d65e77da80e835d91dc6ff830ab6e8bf1f2292bf#d65e77da80e835d91dc6ff830ab6e8bf1f2292bf" +version = "0.1.4" +source = "git+https://github.com/sebasxsala/opensaml-rs?rev=c857a2b85a46e2a3df2dea43d2e5e1fdb1e4f2ff#c857a2b85a46e2a3df2dea43d2e5e1fdb1e4f2ff" dependencies = [ "base64 0.22.1", "bergshamra", @@ -3510,7 +3542,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ - "base16ct", + "base16ct 0.2.0", "ecdsa", "elliptic-curve", "primeorder", @@ -3558,12 +3590,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pastey" version = "0.1.1" @@ -3672,9 +3698,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.11" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der 0.8.0", "spki 0.8.0", @@ -3882,9 +3908,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -4911,7 +4937,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", + "base16ct 0.2.0", "der 0.7.10", "generic-array", "pkcs8 0.10.2", @@ -4921,9 +4947,9 @@ dependencies = [ [[package]] name = "secrecy" -version = "0.8.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ "zeroize", ] @@ -5098,6 +5124,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct 1.0.0", + "serde", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -5168,6 +5204,17 @@ dependencies = [ "keccak 0.2.0", ] +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" @@ -5261,15 +5308,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slh-dsa" -version = "0.2.0-rc.4" +version = "0.2.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f6f9b5317f06189671584c283b3f26339b89c97f21b5c50ae24aec397304a7" +checksum = "371c02fe34044d8866ddf7cb0e8204a87ef31a39f0408bed41c4253ea9dd61ed" dependencies = [ "const-oid 0.10.2", "digest 0.11.3", "hmac 0.13.0", "hybrid-array", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand_core 0.10.1", "sha2 0.11.0", "sha3 0.11.0", @@ -5345,6 +5392,12 @@ dependencies = [ "der 0.8.0", ] +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlx" version = "0.8.6" @@ -5985,9 +6038,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tsp-ltv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072e18abb3fbc096b1c607cbb3eefffda07430f226c1a4cf8a9411185bc004d9" +checksum = "af6699c957eb2d3142aef0e5e1221dd4f307c314fefa5bcc48e5fc95c9b4309c" dependencies = [ "base64 0.22.1", "chrono", @@ -5997,6 +6050,7 @@ dependencies = [ "digest 0.10.7", "ecdsa", "ed25519-dalek", + "getrandom 0.2.17", "hex", "log", "md-5 0.10.6", @@ -6089,9 +6143,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "uppsala" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1600dc6ec465bbba4056042fc73041b82081849ab3117f5d2e233eb96e3e1725" +checksum = "9d87ccb81b1e9ddc119871ecbd40a5e93f173f0777838db1e8c0dec223ae3c49" [[package]] name = "url" diff --git a/Cargo.toml b/Cargo.toml index 5535cff8..cec866d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ rustauth-axum = { path = "crates/rustauth-axum", version = "0.3.1" } rustauth-cli = { path = "crates/rustauth-cli", version = "0.3.1" } openidconnect = { version = "4.0.1", default-features = false, features = ["reqwest", "rustls-tls"] } publicsuffix2 = { version = "0.5.2", default-features = false, features = ["std", "idna"] } -quick-xml = "0.37.5" +quick-xml = "0.41" rand = "0.8" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } scrypt = "0.11" @@ -116,7 +116,7 @@ uuid = { version = "1", features = ["serde", "v4", "v5"] } webauthn-rs = { version = "0.5", features = ["conditional-ui", "danger-allow-state-serialisation", "danger-credential-internals"] } webauthn-rs-core = "=0.5.5" x509-parser = "0.18" -opensaml = { git = "https://github.com/sebasxsala/opensaml-rs", rev = "d65e77da80e835d91dc6ff830ab6e8bf1f2292bf", version = "0.1.0", default-features = false } +opensaml = { git = "https://github.com/sebasxsala/opensaml-rs", rev = "c857a2b85a46e2a3df2dea43d2e5e1fdb1e4f2ff", version = "0.1.4", default-features = false } [profile.test] debug = 1 diff --git a/crates/rustauth-core/src/utils/url.rs b/crates/rustauth-core/src/utils/url.rs index 74a8512b..e04a90b0 100644 --- a/crates/rustauth-core/src/utils/url.rs +++ b/crates/rustauth-core/src/utils/url.rs @@ -96,10 +96,9 @@ fn split_host_and_port(host: &str) -> Option<(&str, Option<&str>)> { let rest = &host[end + 1..]; let port = if rest.is_empty() { None - } else if let Some(port) = rest.strip_prefix(':') { - Some(port) } else { - return None; + let port = rest.strip_prefix(':')?; + Some(port) }; return Some((name, port)); } diff --git a/crates/rustauth-saml/src/bridge.rs b/crates/rustauth-saml/src/bridge.rs index 40b8a369..1a517f0f 100644 --- a/crates/rustauth-saml/src/bridge.rs +++ b/crates/rustauth-saml/src/bridge.rs @@ -14,7 +14,7 @@ use opensaml::idp::IdentityProvider; #[cfg(feature = "saml-signed")] use opensaml::logout::{ create_logout_request_with_id, create_logout_response_with_id, parse_logout_request, - parse_logout_response, + parse_logout_response_without_request_id, }; use opensaml::metadata::{Endpoint, IdpMetadataConfig, SpMetadataConfig}; use opensaml::sp::ServiceProvider; @@ -162,6 +162,7 @@ pub fn create_identity_provider(config: &SamlConfig) -> Result Result { let sp = create_service_provider(config, base_url, provider_id, opts)?; let idp = create_identity_provider(config)?; - parse_logout_response(&sp.setting, &idp.metadata, binding, request) + parse_logout_response_without_request_id(&sp.setting, &idp.metadata, binding, request) } #[cfg(feature = "saml-signed")] @@ -350,30 +350,29 @@ pub fn assertion_id_from_saml_content(xml: &str) -> Option { fn sp_entity_setting(config: &SamlConfig, opts: &SpBuildOptions) -> EntitySetting { let skew_ms = opts.clock_skew.as_millis().min(i64::MAX as u128) as i64; - let mut setting = EntitySetting { - entity_id: config - .sp_metadata - .entity_id - .clone() - .or_else(|| Some(config.issuer.clone())), - request_signature_algorithm: resolve_signature_algorithm(config), - authn_requests_signed: config.authn_requests_signed, - want_assertions_signed: config.want_assertions_signed, - want_message_signed: config.want_assertions_signed, - want_logout_request_signed: opts.want_logout_request_signed, - want_logout_response_signed: opts.want_logout_response_signed, - is_assertion_encrypted: config.sp_metadata.is_assertion_encrypted.unwrap_or(false), - private_key: secret_to_string(config.private_key.as_ref()) - .or_else(|| secret_to_string(config.sp_metadata.private_key.as_ref())), - private_key_pass: secret_to_string(config.sp_metadata.private_key_pass.as_ref()), - signing_cert: sp_signing_certs(config).into_iter().next(), - enc_private_key: secret_to_string(config.decryption_pvk.as_ref()) - .or_else(|| secret_to_string(config.sp_metadata.enc_private_key.as_ref())), - enc_private_key_pass: secret_to_string(config.sp_metadata.enc_private_key_pass.as_ref()), - clock_drifts: (-skew_ms, skew_ms), - relay_state: opts.relay_state.clone().unwrap_or_default(), - ..EntitySetting::default() - }; + let mut setting = EntitySetting::default(); + setting.entity_id = config + .sp_metadata + .entity_id + .clone() + .or_else(|| Some(config.issuer.clone())); + setting.request_signature_algorithm = resolve_signature_algorithm(config); + setting.authn_requests_signed = config.authn_requests_signed; + setting.want_assertions_signed = config.want_assertions_signed; + setting.want_message_signed = config.want_assertions_signed; + setting.want_logout_request_signed = opts.want_logout_request_signed; + setting.want_logout_response_signed = opts.want_logout_response_signed; + setting.is_assertion_encrypted = config.sp_metadata.is_assertion_encrypted.unwrap_or(false); + setting.private_key = secret_to_string(config.private_key.as_ref()) + .or_else(|| secret_to_string(config.sp_metadata.private_key.as_ref())); + setting.private_key_pass = secret_to_string(config.sp_metadata.private_key_pass.as_ref()); + setting.signing_cert = sp_signing_certs(config).into_iter().next(); + setting.enc_private_key = secret_to_string(config.decryption_pvk.as_ref()) + .or_else(|| secret_to_string(config.sp_metadata.enc_private_key.as_ref())); + setting.enc_private_key_pass = + secret_to_string(config.sp_metadata.enc_private_key_pass.as_ref()); + setting.clock_drifts = (-skew_ms, skew_ms); + setting.relay_state = opts.relay_state.clone().unwrap_or_default(); if let Some(format) = &config.identifier_format { setting.name_id_format = vec![format.clone()]; } @@ -391,15 +390,14 @@ fn sp_entity_setting(config: &SamlConfig, opts: &SpBuildOptions) -> EntitySettin } fn idp_entity_setting(config: &SamlConfig) -> EntitySetting { - EntitySetting { - entity_id: config - .idp_metadata - .as_ref() - .and_then(|idp| idp.entity_id.clone()) - .or_else(|| Some(config.issuer.clone())), - want_authn_requests_signed: config.authn_requests_signed, - ..EntitySetting::default() - } + let mut setting = EntitySetting::default(); + setting.entity_id = config + .idp_metadata + .as_ref() + .and_then(|idp| idp.entity_id.clone()) + .or_else(|| Some(config.issuer.clone())); + setting.want_authn_requests_signed = config.authn_requests_signed; + setting } fn sp_signing_certs(config: &SamlConfig) -> Vec { diff --git a/crates/rustauth-saml/src/saml/assertions.rs b/crates/rustauth-saml/src/saml/assertions.rs index 74167657..2fd4e71b 100644 --- a/crates/rustauth-saml/src/saml/assertions.rs +++ b/crates/rustauth-saml/src/saml/assertions.rs @@ -15,7 +15,7 @@ use crate::options::SamlConfig; use super::encryption::{decrypt_encrypted_assertion_response, SamlAssertionDecryptionError}; use super::security::{collect_saml_runtime_algorithms, SamlConditions, SamlRuntimeAlgorithms}; use super::signature::SamlSignatureInfo; -use super::xml::{local_name, validate_saml_xml}; +use super::xml::{decode_xml_reference, decode_xml_text, local_name, validate_saml_xml}; pub const ENCRYPTED_ASSERTION_UNSUPPORTED: &str = "Encrypted SAML assertions are not supported"; @@ -388,7 +388,14 @@ fn extract_assertion_attributes_from_xml(xml: &str) -> BTreeMap } Ok(Event::Text(text)) => { if current_attribute.is_some() { - if let Ok(value) = text.unescape() { + if let Ok(value) = decode_xml_text(&text) { + current_text.push_str(&value); + } + } + } + Ok(Event::GeneralRef(reference)) => { + if current_attribute.is_some() { + if let Ok(value) = decode_xml_reference(&reference) { current_text.push_str(&value); } } @@ -539,8 +546,13 @@ fn parse_saml_response_xml_detailed( } Ok(Event::Text(text)) => { state.current_text.push_str( - &text - .unescape() + &decode_xml_text(&text) + .map_err(|error| SamlResponseParseError::InvalidXml(error.to_string()))?, + ); + } + Ok(Event::GeneralRef(reference)) => { + state.current_text.push_str( + &decode_xml_reference(&reference) .map_err(|error| SamlResponseParseError::InvalidXml(error.to_string()))?, ); } @@ -824,7 +836,7 @@ fn attr( let attribute = attribute.map_err(|error| RustAuthError::Api(error.to_string()))?; if local_name(attribute.key.as_ref())? == name { return attribute - .decode_and_unescape_value(reader.decoder()) + .decoded_and_normalized_value(quick_xml::XmlVersion::Implicit1_0, reader.decoder()) .map(|value| Some(value.into_owned())) .map_err(|error| RustAuthError::Api(error.to_string())); } diff --git a/crates/rustauth-saml/src/saml/encryption.rs b/crates/rustauth-saml/src/saml/encryption.rs index feb0e3f9..96ccc3f7 100644 --- a/crates/rustauth-saml/src/saml/encryption.rs +++ b/crates/rustauth-saml/src/saml/encryption.rs @@ -43,8 +43,11 @@ pub fn decrypt_encrypted_assertion_response( } let key = opensaml::crypto::keys::load_private_key(private_key_pem, None) .map_err(|_| SamlAssertionDecryptionError::InvalidPrivateKey)?; - let (response, _) = - opensaml::crypto::decrypt_assertion(xml, &key).map_err(map_decryption_error)?; + let mut options = opensaml::crypto::AssertionDecryptionOptions::default(); + // Calling this explicit decryption API with a PEM key opts in to software decryption. + options.allow_insecure_software_rsa_key_transport_decryption = true; + let (response, _) = opensaml::crypto::decrypt_assertion(xml, &key, options) + .map_err(map_decryption_error)?; Ok(response) } #[cfg(not(feature = "saml-signed"))] diff --git a/crates/rustauth-saml/src/saml/logout.rs b/crates/rustauth-saml/src/saml/logout.rs index 3402fc03..aa622885 100644 --- a/crates/rustauth-saml/src/saml/logout.rs +++ b/crates/rustauth-saml/src/saml/logout.rs @@ -18,7 +18,7 @@ use crate::bridge::{ use crate::options::SamlConfig; use crate::saml_impl::metadata::first_single_logout_service_location; use crate::saml_impl::signature::SamlSignatureInfo; -use crate::saml_impl::xml::{local_name, validate_saml_xml}; +use crate::saml_impl::xml::{decode_xml_reference, decode_xml_text, local_name, validate_saml_xml}; use opensaml::constants::Binding; #[cfg(feature = "saml-signed")] use opensaml::flow::HttpRequest; @@ -762,11 +762,10 @@ fn parse_logout_request_xml(xml: &str) -> Result { - current_text.push_str( - &text - .unescape() - .map_err(|error| RustAuthError::Api(error.to_string()))?, - ); + current_text.push_str(&decode_xml_text(&text)?); + } + Ok(Event::GeneralRef(reference)) => { + current_text.push_str(&decode_xml_reference(&reference)?); } Ok(Event::End(element)) => { match local_name(element.name().as_ref())?.as_str() { @@ -903,7 +902,7 @@ fn attribute_value( let attr = attr.map_err(|error| RustAuthError::Api(error.to_string()))?; if local_name(attr.key.as_ref())? == name { return attr - .decode_and_unescape_value(reader.decoder()) + .decoded_and_normalized_value(quick_xml::XmlVersion::Implicit1_0, reader.decoder()) .map(|value| Some(value.into_owned())) .map_err(|error| RustAuthError::Api(error.to_string())); } diff --git a/crates/rustauth-saml/src/saml/security.rs b/crates/rustauth-saml/src/saml/security.rs index 1c3bd3dc..f5eaecc8 100644 --- a/crates/rustauth-saml/src/saml/security.rs +++ b/crates/rustauth-saml/src/saml/security.rs @@ -350,7 +350,7 @@ fn attr( let attribute = attribute.map_err(|error| RustAuthError::Api(error.to_string()))?; if local_name(attribute.key.as_ref())? == name { return attribute - .decode_and_unescape_value(reader.decoder()) + .decoded_and_normalized_value(quick_xml::XmlVersion::Implicit1_0, reader.decoder()) .map(|value| Some(value.into_owned())) .map_err(|error| RustAuthError::Api(error.to_string())); } diff --git a/crates/rustauth-saml/src/saml/signature.rs b/crates/rustauth-saml/src/saml/signature.rs index 2046951f..fbb9bf93 100644 --- a/crates/rustauth-saml/src/saml/signature.rs +++ b/crates/rustauth-saml/src/saml/signature.rs @@ -127,22 +127,12 @@ fn verify_post_message( let param = parser_type.query_param(); let request = HttpRequest::post(vec![(param.to_owned(), compact)]); let certs = [cert.to_owned()]; - flow( - &FlowOptions { - binding: Some(Binding::Post), - parser_type: Some(parser_type), - check_signature: true, - from_issuer: None, - signing_certs: &certs, - decrypt_key: None, - decrypt_key_pass: None, - clock_drifts: (0, 0), - expected_audience: None, - expected_in_response_to: None, - }, - &request, - ) - .map_err(map_verify_error)?; + let mut options = FlowOptions::default(); + options.binding = Some(Binding::Post); + options.parser_type = Some(parser_type); + options.check_signature = true; + options.signing_certs = &certs; + flow(&options, &request).map_err(map_verify_error)?; let element = if signature.assertion { SamlSignedElement::Assertion } else { @@ -200,22 +190,12 @@ fn verify_redirect_signature( let mut request = HttpRequest::redirect(query); request.octet_string = Some(octet); let certs = [cert.to_owned()]; - flow( - &FlowOptions { - binding: Some(Binding::Redirect), - parser_type: Some(parser_type), - check_signature: true, - from_issuer: None, - signing_certs: &certs, - decrypt_key: None, - decrypt_key_pass: None, - clock_drifts: (0, 0), - expected_audience: None, - expected_in_response_to: None, - }, - &request, - ) - .map_err(map_verify_error)?; + let mut options = FlowOptions::default(); + options.binding = Some(Binding::Redirect); + options.parser_type = Some(parser_type); + options.check_signature = true; + options.signing_certs = &certs; + flow(&options, &request).map_err(map_verify_error)?; Ok(()) } #[cfg(not(feature = "saml-signed"))] diff --git a/crates/rustauth-saml/src/saml/xml.rs b/crates/rustauth-saml/src/saml/xml.rs index 99fba8d1..c0b27fa8 100644 --- a/crates/rustauth-saml/src/saml/xml.rs +++ b/crates/rustauth-saml/src/saml/xml.rs @@ -1,4 +1,4 @@ -use quick_xml::events::Event; +use quick_xml::events::{BytesRef, BytesText, Event}; use quick_xml::Reader; use rustauth_core::error::RustAuthError; @@ -50,3 +50,25 @@ pub fn local_name(name: &[u8]) -> Result { .map_or(value, |(_, local)| local) .to_owned()) } + +pub fn decode_xml_text(text: &BytesText<'_>) -> Result { + text.decode() + .map(|value| value.into_owned()) + .map_err(|error| RustAuthError::Api(error.to_string())) +} + +pub fn decode_xml_reference(reference: &BytesRef<'_>) -> Result { + if let Some(value) = reference + .resolve_char_ref() + .map_err(|error| RustAuthError::Api(error.to_string()))? + { + return Ok(value.to_string()); + } + + let name = reference + .decode() + .map_err(|error| RustAuthError::Api(error.to_string()))?; + quick_xml::escape::resolve_predefined_entity(&name) + .map(str::to_owned) + .ok_or_else(|| RustAuthError::Api(format!("unrecognized XML entity `{name}`"))) +} diff --git a/crates/rustauth-saml/tests/security.rs b/crates/rustauth-saml/tests/security.rs index 47357b4e..ff58abcc 100644 --- a/crates/rustauth-saml/tests/security.rs +++ b/crates/rustauth-saml/tests/security.rs @@ -41,6 +41,10 @@ fn sp_signing_cert_pem() -> &'static str { #[cfg(feature = "saml-signed")] fn signed_login_response(in_response_to: &str) -> Result> { + let mut idp_setting = EntitySetting::default(); + idp_setting.private_key = Some(sp_private_key_pem().to_owned()); + idp_setting.signing_cert = Some(sp_signing_cert_pem().to_owned()); + idp_setting.request_signature_algorithm = RSA_SHA256.to_owned(); let idp = IdentityProvider::from_config( &IdpMetadataConfig { entity_id: "https://idp.example.com".to_owned(), @@ -52,13 +56,13 @@ fn signed_login_response(in_response_to: &str) -> Result Result Result<(), Box Result<(), Box> { + let xml = r#" + + + + + https://sp.example.com/entity?tenant=acme&region=us-east + + + + + "#; + + let parsed = parse_saml_response(&encode_saml_xml(xml))?; + + assert_eq!( + parsed.assertion.audiences, + vec!["https://sp.example.com/entity?tenant=acme®ion=us-east".to_owned()] + ); + Ok(()) +} + #[test] fn saml_config_defaults_want_assertions_signed_to_true() -> Result<(), Box> { let config: SamlConfig = serde_json::from_value(serde_json::json!({ diff --git a/crates/rustauth-sso/tests/sso/fixtures/idp_fixtures.rs b/crates/rustauth-sso/tests/sso/fixtures/idp_fixtures.rs index 6fff1fd7..e50296c9 100644 --- a/crates/rustauth-sso/tests/sso/fixtures/idp_fixtures.rs +++ b/crates/rustauth-sso/tests/sso/fixtures/idp_fixtures.rs @@ -142,13 +142,12 @@ pub fn register_idp_fixture_body_with_options( } fn signing_setting(private_key: &str, cert: &str, entity_id: &str) -> EntitySetting { - EntitySetting { - entity_id: Some(entity_id.to_owned()), - private_key: Some(private_key.to_owned()), - signing_cert: Some(cert.to_owned()), - request_signature_algorithm: RSA_SHA256.to_owned(), - ..Default::default() - } + let mut setting = EntitySetting::default(); + setting.entity_id = Some(entity_id.to_owned()); + setting.private_key = Some(private_key.to_owned()); + setting.signing_cert = Some(cert.to_owned()); + setting.request_signature_algorithm = RSA_SHA256.to_owned(); + setting } pub fn idp_for_fixture( @@ -184,6 +183,8 @@ pub fn idp_for_fixture( } pub fn sp_for_fixture(kind: IdpFixtureKind) -> Result> { + let mut setting = signing_setting(sp_private_key_pem(), sp_signing_cert_pem(), SP_ENTITY_ID); + setting.want_assertions_signed = true; Ok(ServiceProvider::from_config( &SpMetadataConfig { entity_id: SP_ENTITY_ID.to_owned(), @@ -193,14 +194,7 @@ pub fn sp_for_fixture(kind: IdpFixtureKind) -> Result &'static str { } fn signing_setting(private_key: &str, cert: &str) -> EntitySetting { - EntitySetting { - private_key: Some(private_key.to_owned()), - signing_cert: Some(cert.to_owned()), - request_signature_algorithm: RSA_SHA256.to_owned(), - entity_id: None, - ..Default::default() - } + let mut setting = EntitySetting::default(); + setting.private_key = Some(private_key.to_owned()); + setting.signing_cert = Some(cert.to_owned()); + setting.request_signature_algorithm = RSA_SHA256.to_owned(); + setting } pub fn test_idp() -> Result> { @@ -84,6 +82,11 @@ pub fn test_sp( authn_signed: bool, want_signed: bool, ) -> Result> { + let mut setting = signing_setting(sp_private_key_pem(), sp_signing_cert_pem()); + setting.entity_id = Some(SP_ENTITY_ID.to_owned()); + setting.authn_requests_signed = authn_signed; + setting.want_assertions_signed = want_signed; + setting.enc_private_key = Some(sp_private_key_pem().to_owned()); Ok(ServiceProvider::from_config( &SpMetadataConfig { entity_id: SP_ENTITY_ID.to_owned(), @@ -97,16 +100,7 @@ pub fn test_sp( ], ..Default::default() }, - EntitySetting { - entity_id: Some(SP_ENTITY_ID.to_owned()), - private_key: Some(sp_private_key_pem().to_owned()), - signing_cert: Some(sp_signing_cert_pem().to_owned()), - request_signature_algorithm: RSA_SHA256.to_owned(), - authn_requests_signed: authn_signed, - want_assertions_signed: want_signed, - enc_private_key: Some(sp_private_key_pem().to_owned()), - ..Default::default() - }, + setting, )?) } From 04c2be7aae7cef05d56d6a3e3530d1121aebe4ea Mon Sep 17 00:00:00 2001 From: sebastian Date: Mon, 20 Jul 2026 12:01:03 -0600 Subject: [PATCH 2/2] ci: serialize rustauth-cli integration tests --- .github/workflows/integration.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 6297f1a3..23752615 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -64,7 +64,10 @@ jobs: nextest_args: --all-features --run-ignored only - package: rustauth-cli services: postgres mysql - nextest_args: --all-features --run-ignored only + nextest_args: >- + --all-features + --run-ignored only + --test-threads 1 - package: rustauth services: postgres mysql nextest_args: --all-features --run-ignored only