From fe75d16464d7816658f6c1c275f61d8f5e8a082d Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 29 Jul 2025 15:03:18 -0400 Subject: [PATCH 01/15] RUST-2245: Fix typo in e2e test logging --- .evergreen/run-gssapi-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh index 6957a705d..763272de7 100644 --- a/.evergreen/run-gssapi-tests.sh +++ b/.evergreen/run-gssapi-tests.sh @@ -58,7 +58,7 @@ TEST_OPTIONS=() cargo_test test::auth::gssapi_skip_local::with_service_realm_and_host_options # Unauthenticate -echo "Unuthenticating $PRINCIPAL_CROSS" +echo "Unauthenticating $PRINCIPAL_CROSS" kdestroy # Run remaining tests From 9dda3b3e4e9a3cf2a7b0415a0386f86468e75bbe Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 30 Jul 2025 11:33:54 -0400 Subject: [PATCH 02/15] RUST-2245: Initial second attempt at GSSAPI on Windows --- Cargo.lock | 819 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- src/client/auth/gssapi.rs | 204 +++++++++- 3 files changed, 1006 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad6aa0aa8..e4f6ca0d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,36 @@ dependencies = [ "nodrop", ] +[[package]] +name = "async-dnssd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-util", + "libc", + "log", + "pin-utils", + "pkg-config", + "tokio", + "winapi", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -137,6 +167,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -152,6 +205,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -170,6 +229,29 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.9.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.101", + "which", +] + [[package]] name = "bindgen" version = "0.71.1" @@ -179,13 +261,13 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.101", ] @@ -284,6 +366,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -365,6 +453,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -475,6 +572,27 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf1e6e5492f8f0830c37f301f6349e0dac8b2466e4fe89eef90e9eef906cd046" +dependencies = [ + "crypto-common", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -485,6 +603,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "ctrlc" version = "3.4.7" @@ -495,6 +623,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "darling" version = "0.20.11" @@ -543,6 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -613,6 +769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -628,18 +785,84 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -683,6 +906,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flate2" version = "1.1.2" @@ -723,6 +962,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "function_name" version = "0.2.3" @@ -846,6 +1091,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -887,6 +1133,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.11" @@ -981,6 +1238,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1377,6 +1643,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1412,6 +1687,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lambda_runtime" version = "0.6.1" @@ -1449,6 +1733,15 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" @@ -1474,7 +1767,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7518e6902e94f92e7c7271232684b60988b4bd813529b4ef9d97aead96956ae8" dependencies = [ - "bindgen", + "bindgen 0.71.1", "pkg-config", ] @@ -1485,15 +1778,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1601,6 +1906,15 @@ dependencies = [ "digest", ] +[[package]] +name = "md4" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.7.5" @@ -1718,6 +2032,7 @@ dependencies = [ "sha2", "snap", "socket2 0.5.10", + "sspi", "stringprep", "strsim", "take_mut", @@ -1800,12 +2115,61 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "serde", + "smallvec 1.15.0", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1813,6 +2177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1834,6 +2199,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1876,19 +2250,57 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", ] [[package]] -name = "overload" -version = "0.1.1" +name = "p521" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] [[package]] name = "parking_lot" @@ -1930,6 +2342,7 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", + "sha1", ] [[package]] @@ -1942,12 +2355,116 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "picky" +version = "7.0.0-rc.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ed0f61c4ac11274678ad0da95386c6216d6ff898ef98e54aac23d4a853c324c" +dependencies = [ + "base64 0.22.1", + "digest", + "ed25519-dalek", + "hex", + "md-5", + "num-bigint-dig", + "p256", + "p384", + "p521", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.8.5", + "rand_core 0.6.4", + "rsa", + "serde", + "sha1", + "sha2", + "sha3", + "thiserror 1.0.69", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "picky-asn1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" +dependencies = [ + "oid", + "serde", + "serde_bytes", + "time", + "zeroize", +] + +[[package]] +name = "picky-asn1-der" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dccb53c26f70c082e008818f524bd45d057069517b047bd0c0ee062d6d7d7f2" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444990fc9e53de47353c95e6521a5f669676da986a10482ff3708bdaa1a2a4b" +dependencies = [ + "base64 0.22.1", + "num-bigint-dig", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", + "widestring", + "zeroize", +] + +[[package]] +name = "picky-krb" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45ffe5f2122cdda5e9059ab837a65ba1b77729db43fc1500f2fce6b27070eab" +dependencies = [ + "aes", + "byteorder", + "cbc", + "crypto", + "des", + "hmac", + "num-bigint-dig", + "oid", + "pbkdf2 0.12.2", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.8.5", + "serde", + "sha1", + "thiserror 1.0.69", + "uuid", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1980,6 +2497,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs5" version = "0.7.1" @@ -2059,6 +2587,15 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2088,7 +2625,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "socket2 0.5.10", "thiserror 2.0.12", @@ -2108,7 +2645,7 @@ dependencies = [ "lru-slab", "rand 0.9.1", "ring", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", @@ -2340,6 +2877,16 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2354,12 +2901,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha1", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -2385,6 +2959,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.8" @@ -2394,7 +2981,7 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -2404,6 +2991,7 @@ version = "0.23.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -2429,6 +3017,7 @@ version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2505,6 +3094,20 @@ dependencies = [ "sha2", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2663,6 +3266,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2687,6 +3300,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -2740,6 +3363,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -2750,6 +3379,50 @@ dependencies = [ "der", ] +[[package]] +name = "sspi" +version = "0.15.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214ee905dcdd4b7ab11404b616e58dc6944d80fe8592fbdc13abc87d7e2bff0a" +dependencies = [ + "async-dnssd", + "async-recursion", + "bitflags 2.9.0", + "byteorder", + "cfg-if", + "crypto-mac", + "futures", + "hmac", + "lazy_static", + "md-5", + "md4", + "num-bigint-dig", + "num-derive", + "num-traits", + "oid", + "picky", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "rand 0.8.5", + "rsa", + "rustls", + "serde", + "serde_derive", + "sha1", + "sha2", + "time", + "tokio", + "tracing", + "url", + "uuid", + "windows", + "windows-registry", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2863,7 +3536,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -3440,6 +4113,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "widestring" version = "1.2.0" @@ -3608,6 +4293,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3632,13 +4326,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -3660,6 +4371,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3672,6 +4389,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3684,12 +4407,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3702,6 +4437,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3714,6 +4455,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3726,6 +4473,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3738,6 +4491,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winreg" version = "0.50.0" @@ -3772,6 +4531,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yansi" version = "1.0.1" @@ -3848,6 +4619,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 263c7fc5e..3378e046d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ gcp-oidc = ["dep:reqwest"] gcp-kms = ["dep:reqwest"] # Enable support for GSSAPI (Kerberos) authentication. -gssapi-auth = ["dep:cross-krb5", "dns-resolver"] +gssapi-auth = ["dep:cross-krb5", "dep:sspi", "dns-resolver"] zstd-compression = ["dep:zstd"] zlib-compression = ["dep:flate2"] @@ -110,6 +110,7 @@ sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } socket2 = "0.5.5" +sspi = { version = "0.15.1", optional = true } stringprep = "0.1.2" strsim = "0.11.1" take_mut = "0.2.2" diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 835ceaec3..09254b57a 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -1,12 +1,16 @@ use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; +use sspi::{ + AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialsBuffers, + DataRepresentation, InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, + SecurityStatus, Sspi, SspiImpl, Username, +}; use crate::{ bson::Bson, client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, - GSSAPI_STR, + Credential, GSSAPI_STR, }, options::ServerApi, }, @@ -376,3 +380,199 @@ async fn canonicalize_hostname( Ok(hostname.to_string()) } + +async fn windows_sspi( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + resolver_config: Option<&ResolverConfig>, +) -> Result<()> { + let properties = GssapiProperties::from_credential(credential)?; + + let conn_host = conn.address.host().to_string(); + let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); + let hostname = canonicalize_hostname( + hostname, + &properties.canonicalize_host_name, + resolver_config, + ) + .await?; + + // Configuration + let kerberos_config = KerberosConfig::new("", "".to_string()); + let mut kerberos = Kerberos::new_client_from_config(kerberos_config).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to create SSPI Kerberos client: {e}"), + ) + })?; + + // Acquire Creds + let mut acq_creds_handle_result = get_cred_handle(&mut kerberos, credential)?; + + let mut conversation_id = None; + let mut input_token: Vec = vec![]; + let source = credential.source.as_deref().unwrap_or("$external"); + + loop { + let (output_token, status) = step( + &mut kerberos, + &mut acq_creds_handle_result.credentials_handle, + input_token.as_slice(), + credential.username.clone(), + properties.clone(), + &hostname, + )?; + if status == SecurityStatus::ContinueNeeded || status == SecurityStatus::Ok { + let command = if conversation_id.is_none() { + SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + output_token, + server_api.cloned(), + ) + .into_command()? + } else { + SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + output_token, + server_api.cloned(), + ) + .into_command() + }; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + conversation_id = Some(sasl_response.conversation_id); + input_token = sasl_response.payload; + + if sasl_response.done { + return Ok(()) + } + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("problem authenticating; status = {status:?}"), + )); + } + } + + Ok(()) +} + +pub(crate) fn get_cred_handle( + kerberos: &mut Kerberos, + credential: &Credential, +) -> Result>> { + let mut acq_creds_handle_result = kerberos + .acquire_credentials_handle() + .with_credential_use(sspi::CredentialUse::Outbound); + + if let Some(pwd) = credential.password.as_ref() { + if let Some(username) = credential.username.as_ref() { + let identity = sspi::AuthIdentity { + username: Username::parse(username).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to parse user principal: {e}"), + ) + })?, + password: pwd.into(), + }; + + acq_creds_handle_result = acq_creds_handle_result.with_auth_data(&identity.into()); + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + "Username required but not specified", + )); + } + } + + acq_creds_handle_result.execute(kerberos).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to acquire credentials handle: {e:?}"), + ) + }) +} + +fn step_helper( + kerberos: &mut Kerberos, + cred_handle: &mut ::CredentialsHandle, + input_buffer: &mut [SecurityBuffer], + output_buffer: &mut [SecurityBuffer], + user_principal: Option, + properties: GssapiProperties, + hostname: &str, +) -> Result { + // todo: consider not only refactoring this duplicated code into a helper function, + // but possibly updating GssapiProperties::from-credential to create the service principal for me + let service_name: &str = properties.service_name.as_ref(); + let mut service_principal = format!("{service_name}/{hostname}"); + if let Some(service_realm) = properties.service_realm.as_ref() { + service_principal = format!("{service_principal}@{service_realm}"); + } else if let Some(user_principal) = user_principal.as_ref() { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{service_principal}{realm}"); + } + } + let mut builder = kerberos + .initialize_security_context() + .with_credentials_handle(cred_handle) + .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(&service_principal) + .with_input(input_buffer) + .with_output(output_buffer); + + kerberos + .initialize_security_context_impl(&mut builder) + .map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to initialize security context: {e}"), + ) + })? + .resolve_to_result() + .map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("Failed to resolve to result: {e}")) + }) +} + +pub fn step( + kerberos: &mut Kerberos, + cred_handle: &mut ::CredentialsHandle, + input_token: &[u8], + user_principal: Option, + properties: GssapiProperties, + hostname: &str, +) -> Result<(Vec, SecurityStatus)> { + let mut secure_input_buffer = + vec![SecurityBuffer::new(input_token.to_vec(), BufferType::Token)]; + let mut secure_output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; + match step_helper( + kerberos, + cred_handle, + &mut secure_input_buffer, + &mut secure_output_buffer, + user_principal, + properties, + hostname, + ) { + Ok(result) => { + let output_buffer = secure_output_buffer[0].to_owned(); + Ok((output_buffer.buffer, result.status)) + } + Err(e) => Err(Error::authentication_error( + GSSAPI_STR, + &format!("error stepping: {e:?}"), + )), + } +} From 4186779f00a00d7e6206b50c0a800f45b7e9b391 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 30 Jul 2025 13:52:23 -0400 Subject: [PATCH 03/15] RUST-2245: Refactor service_principal name creation --- src/client/auth/gssapi.rs | 89 ++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs index 09254b57a..8255d70c7 100644 --- a/src/client/auth/gssapi.rs +++ b/src/client/auth/gssapi.rs @@ -58,8 +58,9 @@ pub(crate) async fn authenticate_stream( .await?; let user_principal = credential.username.clone(); + let service_principal = properties.service_principal_name(&hostname, user_principal.as_ref()); let (mut authenticator, initial_token) = - GssapiAuthenticator::init(user_principal, properties.clone(), &hostname).await?; + GssapiAuthenticator::init(user_principal, service_principal).await?; let source = credential.source.as_deref().unwrap_or("$external"); @@ -194,6 +195,24 @@ impl GssapiProperties { Ok(properties) } + + fn service_principal_name(self, hostname: &String, user_principal: Option<&String>) -> String { + // Set the service principal name in addition to the user provided properties + let service_name: &str = self.service_name.as_ref(); + let mut service_principal = format!("{service_name}/{hostname}"); + if let Some(service_realm) = self.service_realm.as_ref() { + service_principal = format!("{service_principal}@{service_realm}"); + } else if let Some(user_principal) = user_principal { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{service_principal}{realm}"); + } + } + + service_principal + } } struct GssapiAuthenticator { @@ -208,22 +227,8 @@ impl GssapiAuthenticator { // getting an initial token to send to the server. async fn init( user_principal: Option, - properties: GssapiProperties, - hostname: &str, + service_principal: String, ) -> Result<(Self, Vec)> { - let service_name: &str = properties.service_name.as_ref(); - let mut service_principal = format!("{service_name}/{hostname}"); - if let Some(service_realm) = properties.service_realm.as_ref() { - service_principal = format!("{service_principal}@{service_realm}"); - } else if let Some(user_principal) = user_principal.as_ref() { - if let Some(idx) = user_principal.find('@') { - // If no SERVICE_REALM was specified, use realm specified in the - // username. Note that `realm` starts with '@'. - let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{service_principal}{realm}"); - } - } - let (pending_ctx, initial_token) = ClientCtx::new( InitiateFlags::empty(), user_principal.as_deref(), @@ -398,6 +403,9 @@ async fn windows_sspi( ) .await?; + let service_principal = + properties.service_principal_name(&hostname, credential.username.as_ref()); + // Configuration let kerberos_config = KerberosConfig::new("", "".to_string()); let mut kerberos = Kerberos::new_client_from_config(kerberos_config).map_err(|e| { @@ -419,9 +427,7 @@ async fn windows_sspi( &mut kerberos, &mut acq_creds_handle_result.credentials_handle, input_token.as_slice(), - credential.username.clone(), - properties.clone(), - &hostname, + service_principal.clone(), )?; if status == SecurityStatus::ContinueNeeded || status == SecurityStatus::Ok { let command = if conversation_id.is_none() { @@ -450,7 +456,7 @@ async fn windows_sspi( input_token = sasl_response.payload; if sasl_response.done { - return Ok(()) + return Ok(()); } } else { return Err(Error::authentication_error( @@ -459,8 +465,6 @@ async fn windows_sspi( )); } } - - Ok(()) } pub(crate) fn get_cred_handle( @@ -471,7 +475,8 @@ pub(crate) fn get_cred_handle( .acquire_credentials_handle() .with_credential_use(sspi::CredentialUse::Outbound); - if let Some(pwd) = credential.password.as_ref() { + let mut auth_data: Option = None; + if let Some(pwd) = credential.password.clone() { if let Some(username) = credential.username.as_ref() { let identity = sspi::AuthIdentity { username: Username::parse(username).map_err(|e| { @@ -483,7 +488,7 @@ pub(crate) fn get_cred_handle( password: pwd.into(), }; - acq_creds_handle_result = acq_creds_handle_result.with_auth_data(&identity.into()); + auth_data = Some(identity.into()); } else { return Err(Error::authentication_error( GSSAPI_STR, @@ -492,6 +497,10 @@ pub(crate) fn get_cred_handle( } } + if let Some(auth_data) = auth_data.as_ref() { + acq_creds_handle_result = acq_creds_handle_result.with_auth_data(auth_data); + } + acq_creds_handle_result.execute(kerberos).map_err(|e| { Error::authentication_error( GSSAPI_STR, @@ -505,24 +514,8 @@ fn step_helper( cred_handle: &mut ::CredentialsHandle, input_buffer: &mut [SecurityBuffer], output_buffer: &mut [SecurityBuffer], - user_principal: Option, - properties: GssapiProperties, - hostname: &str, + service_principal: String, ) -> Result { - // todo: consider not only refactoring this duplicated code into a helper function, - // but possibly updating GssapiProperties::from-credential to create the service principal for me - let service_name: &str = properties.service_name.as_ref(); - let mut service_principal = format!("{service_name}/{hostname}"); - if let Some(service_realm) = properties.service_realm.as_ref() { - service_principal = format!("{service_principal}@{service_realm}"); - } else if let Some(user_principal) = user_principal.as_ref() { - if let Some(idx) = user_principal.find('@') { - // If no SERVICE_REALM was specified, use realm specified in the - // username. Note that `realm` starts with '@'. - let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{service_principal}{realm}"); - } - } let mut builder = kerberos .initialize_security_context() .with_credentials_handle(cred_handle) @@ -532,7 +525,7 @@ fn step_helper( .with_input(input_buffer) .with_output(output_buffer); - kerberos + let result = kerberos .initialize_security_context_impl(&mut builder) .map_err(|e| { Error::authentication_error( @@ -543,16 +536,16 @@ fn step_helper( .resolve_to_result() .map_err(|e| { Error::authentication_error(GSSAPI_STR, &format!("Failed to resolve to result: {e}")) - }) + }); + + result } pub fn step( kerberos: &mut Kerberos, cred_handle: &mut ::CredentialsHandle, input_token: &[u8], - user_principal: Option, - properties: GssapiProperties, - hostname: &str, + service_principal: String, ) -> Result<(Vec, SecurityStatus)> { let mut secure_input_buffer = vec![SecurityBuffer::new(input_token.to_vec(), BufferType::Token)]; @@ -562,9 +555,7 @@ pub fn step( cred_handle, &mut secure_input_buffer, &mut secure_output_buffer, - user_principal, - properties, - hostname, + service_principal, ) { Ok(result) => { let output_buffer = secure_output_buffer[0].to_owned(); From 3cf15d07d5f83f166740746b145d60933dcdf07c Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 30 Jul 2025 14:23:15 -0400 Subject: [PATCH 04/15] RUST-2245: Separate windows and non-windows implementations --- src/client/auth/gssapi.rs | 569 ------------------------------ src/client/auth/gssapi/mod.rs | 208 +++++++++++ src/client/auth/gssapi/nix.rs | 206 +++++++++++ src/client/auth/gssapi/windows.rs | 186 ++++++++++ 4 files changed, 600 insertions(+), 569 deletions(-) delete mode 100644 src/client/auth/gssapi.rs create mode 100644 src/client/auth/gssapi/mod.rs create mode 100644 src/client/auth/gssapi/nix.rs create mode 100644 src/client/auth/gssapi/windows.rs diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs deleted file mode 100644 index 8255d70c7..000000000 --- a/src/client/auth/gssapi.rs +++ /dev/null @@ -1,569 +0,0 @@ -use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; -use sspi::{ - AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialsBuffers, - DataRepresentation, InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, - SecurityStatus, Sspi, SspiImpl, Username, -}; - -use crate::{ - bson::Bson, - client::{ - auth::{ - sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, GSSAPI_STR, - }, - options::ServerApi, - }, - cmap::Connection, - error::{Error, Result}, - options::ResolverConfig, -}; - -const SERVICE_NAME: &str = "SERVICE_NAME"; -const CANONICALIZE_HOST_NAME: &str = "CANONICALIZE_HOST_NAME"; -const SERVICE_REALM: &str = "SERVICE_REALM"; -const SERVICE_HOST: &str = "SERVICE_HOST"; - -#[derive(Debug, Clone)] -pub(crate) struct GssapiProperties { - pub service_name: String, - pub canonicalize_host_name: CanonicalizeHostName, - pub service_realm: Option, - pub service_host: Option, -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub(crate) enum CanonicalizeHostName { - #[default] - None, - Forward, - ForwardAndReverse, -} - -pub(crate) async fn authenticate_stream( - conn: &mut Connection, - credential: &Credential, - server_api: Option<&ServerApi>, - resolver_config: Option<&ResolverConfig>, -) -> Result<()> { - let properties = GssapiProperties::from_credential(credential)?; - - let conn_host = conn.address.host().to_string(); - let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); - let hostname = canonicalize_hostname( - hostname, - &properties.canonicalize_host_name, - resolver_config, - ) - .await?; - - let user_principal = credential.username.clone(); - let service_principal = properties.service_principal_name(&hostname, user_principal.as_ref()); - let (mut authenticator, initial_token) = - GssapiAuthenticator::init(user_principal, service_principal).await?; - - let source = credential.source.as_deref().unwrap_or("$external"); - - let command = SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - initial_token, - server_api.cloned(), - ) - .into_command()?; - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - let mut conversation_id = Some(sasl_response.conversation_id); - let mut payload = sasl_response.payload; - - // Limit number of auth challenge steps (typically, only one step is needed, however - // different configurations may require more). - for _ in 0..10 { - let challenge = payload.as_slice(); - let output_token = authenticator.step(challenge).await?; - - // The step may return None, which is a valid final step. We still need to - // send a saslContinue command, so we send an empty payload if there is no - // token. - let token = output_token.unwrap_or(vec![]); - let command = SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - conversation_id = Some(sasl_response.conversation_id); - payload = sasl_response.payload; - - // Although unlikely, there are cases where authentication can be done - // at this point. - if sasl_response.done { - return Ok(()); - } - - // The authenticator is considered "complete" when the Kerberos auth - // process is done. However, this is not the end of the full auth flow. - // We no longer need to issue challenges to the authenticator, so we - // break the loop and continue with the rest of the flow. - if authenticator.is_complete() { - break; - } - } - - let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; - let command = SaslContinue::new( - source.to_string(), - conversation_id.unwrap(), - output_token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - if sasl_response.done { - Ok(()) - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "GSSAPI authentication failed after 10 attempts", - )) - } -} - -impl GssapiProperties { - pub fn from_credential(credential: &Credential) -> Result { - let mut properties = GssapiProperties { - service_name: "mongodb".to_string(), - canonicalize_host_name: CanonicalizeHostName::None, - service_realm: None, - service_host: None, - }; - - if let Some(mechanism_properties) = &credential.mechanism_properties { - if let Some(Bson::String(name)) = mechanism_properties.get(SERVICE_NAME) { - properties.service_name = name.clone(); - } - - if let Some(canonicalize) = mechanism_properties.get(CANONICALIZE_HOST_NAME) { - properties.canonicalize_host_name = match canonicalize { - Bson::String(s) => match s.as_str() { - "none" => CanonicalizeHostName::None, - "forward" => CanonicalizeHostName::Forward, - "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, - _ => { - return Err(Error::authentication_error( - GSSAPI_STR, - format!( - "Invalid CANONICALIZE_HOST_NAME value: {s}. Valid values are \ - 'none', 'forward', 'forwardAndReverse'", - ) - .as_str(), - )) - } - }, - Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, - Bson::Boolean(false) => CanonicalizeHostName::None, - _ => { - return Err(Error::authentication_error( - GSSAPI_STR, - "CANONICALIZE_HOST_NAME must be a string or boolean", - )) - } - }; - } - - if let Some(Bson::String(realm)) = mechanism_properties.get(SERVICE_REALM) { - properties.service_realm = Some(realm.clone()); - } - - if let Some(Bson::String(host)) = mechanism_properties.get(SERVICE_HOST) { - properties.service_host = Some(host.clone()); - } - } - - Ok(properties) - } - - fn service_principal_name(self, hostname: &String, user_principal: Option<&String>) -> String { - // Set the service principal name in addition to the user provided properties - let service_name: &str = self.service_name.as_ref(); - let mut service_principal = format!("{service_name}/{hostname}"); - if let Some(service_realm) = self.service_realm.as_ref() { - service_principal = format!("{service_principal}@{service_realm}"); - } else if let Some(user_principal) = user_principal { - if let Some(idx) = user_principal.find('@') { - // If no SERVICE_REALM was specified, use realm specified in the - // username. Note that `realm` starts with '@'. - let (_, realm) = user_principal.split_at(idx); - service_principal = format!("{service_principal}{realm}"); - } - } - - service_principal - } -} - -struct GssapiAuthenticator { - pending_ctx: Option, - established_ctx: Option, - user_principal: Option, - is_complete: bool, -} - -impl GssapiAuthenticator { - // Initialize the GssapiAuthenticator by creating a PendingClientCtx and - // getting an initial token to send to the server. - async fn init( - user_principal: Option, - service_principal: String, - ) -> Result<(Self, Vec)> { - let (pending_ctx, initial_token) = ClientCtx::new( - InitiateFlags::empty(), - user_principal.as_deref(), - &service_principal, - None, // No channel bindings - ) - .map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to initialize GSSAPI context: {e}"), - ) - })?; - - Ok(( - Self { - pending_ctx: Some(pending_ctx), - established_ctx: None, - user_principal, - is_complete: false, - }, - initial_token.to_vec(), - )) - } - - // Issue the server provided token to the client context. If the ClientCtx - // is established, an optional final token that must be sent to the server - // may be returned; otherwise another token to pass to the server is - // returned and the client context remains in the pending state. - async fn step(&mut self, challenge: &[u8]) -> Result>> { - if challenge.is_empty() { - Err(Error::authentication_error( - GSSAPI_STR, - "Expected challenge data for GSSAPI continuation", - )) - } else if let Some(pending_ctx) = self.pending_ctx.take() { - match pending_ctx.step(challenge).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {e}")) - })? { - Step::Finished((ctx, token)) => { - self.is_complete = true; - self.established_ctx = Some(ctx); - Ok(token.map(|t| t.to_vec())) - } - Step::Continue((ctx, token)) => { - self.pending_ctx = Some(ctx); - Ok(Some(token.to_vec())) - } - } - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "Authentication context not initialized", - )) - } - } - - // Perform the final step of Kerberos authentication by gss_unwrap-ing the - // final server challenge, then wrapping the protocol bytes + user principal. - // The resulting token must be sent to the server. - fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { - if let Some(mut established_ctx) = self.established_ctx.take() { - let _ = established_ctx.unwrap(payload).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) - })?; - - if let Some(user_principal) = self.user_principal.take() { - let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; - let bytes = [bytes, user_principal.as_bytes()].concat(); - let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) - })?; - Ok(output_token.to_vec()) - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "User principal not specified", - )) - } - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "Authentication context not established", - )) - } - } - - fn is_complete(&self) -> bool { - self.is_complete - } -} - -async fn canonicalize_hostname( - hostname: &str, - mode: &CanonicalizeHostName, - resolver_config: Option<&ResolverConfig>, -) -> Result { - if mode == &CanonicalizeHostName::None { - return Ok(hostname.to_string()); - } - - let resolver = - crate::runtime::AsyncResolver::new(resolver_config.map(|c| c.inner.clone())).await?; - - let hostname = match mode { - CanonicalizeHostName::Forward => { - let lookup_records = resolver.cname_lookup(hostname).await?; - - if !lookup_records.records().is_empty() { - // As long as there is a record, we can return the original hostname. - // Although the spec says to return the canonical name, this is not - // done by any drivers in practice since the majority of them use - // libraries that do not follow CNAME chains. Also, we do not want to - // use the canonical name since it will likely differ from the input - // name, and the use of the input name is required for the service - // principal to be accepted by the GSSAPI auth flow. - hostname.to_lowercase().to_string() - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - &format!("No addresses found for hostname: {hostname}"), - )); - } - } - CanonicalizeHostName::ForwardAndReverse => { - // forward lookup - let ips = resolver.ip_lookup(hostname).await?; - - if let Some(first_address) = ips.iter().next() { - // reverse lookup - match resolver.reverse_lookup(first_address).await { - Ok(reverse_lookup) => { - if let Some(name) = reverse_lookup.iter().next() { - name.to_lowercase().to_string() - } else { - hostname.to_lowercase() - } - } - Err(_) => hostname.to_lowercase(), - } - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - &format!("No addresses found for hostname: {hostname}"), - )); - } - } - CanonicalizeHostName::None => unreachable!(), - }; - - // Sometimes reverse lookup results in a trailing "." since that is the correct - // way to present a FQDN. However, GSSAPI rejects the trailing "." so we remove - // it here manually. - let hostname = hostname.trim_end_matches("."); - - Ok(hostname.to_string()) -} - -async fn windows_sspi( - conn: &mut Connection, - credential: &Credential, - server_api: Option<&ServerApi>, - resolver_config: Option<&ResolverConfig>, -) -> Result<()> { - let properties = GssapiProperties::from_credential(credential)?; - - let conn_host = conn.address.host().to_string(); - let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); - let hostname = canonicalize_hostname( - hostname, - &properties.canonicalize_host_name, - resolver_config, - ) - .await?; - - let service_principal = - properties.service_principal_name(&hostname, credential.username.as_ref()); - - // Configuration - let kerberos_config = KerberosConfig::new("", "".to_string()); - let mut kerberos = Kerberos::new_client_from_config(kerberos_config).map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to create SSPI Kerberos client: {e}"), - ) - })?; - - // Acquire Creds - let mut acq_creds_handle_result = get_cred_handle(&mut kerberos, credential)?; - - let mut conversation_id = None; - let mut input_token: Vec = vec![]; - let source = credential.source.as_deref().unwrap_or("$external"); - - loop { - let (output_token, status) = step( - &mut kerberos, - &mut acq_creds_handle_result.credentials_handle, - input_token.as_slice(), - service_principal.clone(), - )?; - if status == SecurityStatus::ContinueNeeded || status == SecurityStatus::Ok { - let command = if conversation_id.is_none() { - SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - output_token, - server_api.cloned(), - ) - .into_command()? - } else { - SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - output_token, - server_api.cloned(), - ) - .into_command() - }; - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - conversation_id = Some(sasl_response.conversation_id); - input_token = sasl_response.payload; - - if sasl_response.done { - return Ok(()); - } - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - &format!("problem authenticating; status = {status:?}"), - )); - } - } -} - -pub(crate) fn get_cred_handle( - kerberos: &mut Kerberos, - credential: &Credential, -) -> Result>> { - let mut acq_creds_handle_result = kerberos - .acquire_credentials_handle() - .with_credential_use(sspi::CredentialUse::Outbound); - - let mut auth_data: Option = None; - if let Some(pwd) = credential.password.clone() { - if let Some(username) = credential.username.as_ref() { - let identity = sspi::AuthIdentity { - username: Username::parse(username).map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to parse user principal: {e}"), - ) - })?, - password: pwd.into(), - }; - - auth_data = Some(identity.into()); - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - "Username required but not specified", - )); - } - } - - if let Some(auth_data) = auth_data.as_ref() { - acq_creds_handle_result = acq_creds_handle_result.with_auth_data(auth_data); - } - - acq_creds_handle_result.execute(kerberos).map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to acquire credentials handle: {e:?}"), - ) - }) -} - -fn step_helper( - kerberos: &mut Kerberos, - cred_handle: &mut ::CredentialsHandle, - input_buffer: &mut [SecurityBuffer], - output_buffer: &mut [SecurityBuffer], - service_principal: String, -) -> Result { - let mut builder = kerberos - .initialize_security_context() - .with_credentials_handle(cred_handle) - .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) - .with_target_data_representation(DataRepresentation::Native) - .with_target_name(&service_principal) - .with_input(input_buffer) - .with_output(output_buffer); - - let result = kerberos - .initialize_security_context_impl(&mut builder) - .map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to initialize security context: {e}"), - ) - })? - .resolve_to_result() - .map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("Failed to resolve to result: {e}")) - }); - - result -} - -pub fn step( - kerberos: &mut Kerberos, - cred_handle: &mut ::CredentialsHandle, - input_token: &[u8], - service_principal: String, -) -> Result<(Vec, SecurityStatus)> { - let mut secure_input_buffer = - vec![SecurityBuffer::new(input_token.to_vec(), BufferType::Token)]; - let mut secure_output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; - match step_helper( - kerberos, - cred_handle, - &mut secure_input_buffer, - &mut secure_output_buffer, - service_principal, - ) { - Ok(result) => { - let output_buffer = secure_output_buffer[0].to_owned(); - Ok((output_buffer.buffer, result.status)) - } - Err(e) => Err(Error::authentication_error( - GSSAPI_STR, - &format!("error stepping: {e:?}"), - )), - } -} diff --git a/src/client/auth/gssapi/mod.rs b/src/client/auth/gssapi/mod.rs new file mode 100644 index 000000000..72ae33bc9 --- /dev/null +++ b/src/client/auth/gssapi/mod.rs @@ -0,0 +1,208 @@ +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(not(target_os = "windows"))] +mod nix; + +use crate::{ + bson::Bson, + client::{ + auth::{Credential, GSSAPI_STR}, + options::ServerApi, + }, + cmap::Connection, + error::{Error, Result}, + options::ResolverConfig, +}; + +const SERVICE_NAME: &str = "SERVICE_NAME"; +const CANONICALIZE_HOST_NAME: &str = "CANONICALIZE_HOST_NAME"; +const SERVICE_REALM: &str = "SERVICE_REALM"; +const SERVICE_HOST: &str = "SERVICE_HOST"; + +#[derive(Debug, Clone)] +pub(crate) struct GssapiProperties { + pub service_name: String, + pub canonicalize_host_name: CanonicalizeHostName, + pub service_realm: Option, + pub service_host: Option, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub(crate) enum CanonicalizeHostName { + #[default] + None, + Forward, + ForwardAndReverse, +} + +pub(crate) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + resolver_config: Option<&ResolverConfig>, +) -> Result<()> { + let properties = GssapiProperties::from_credential(credential)?; + + let conn_host = conn.address.host().to_string(); + let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); + let hostname = canonicalize_hostname( + hostname, + &properties.canonicalize_host_name, + resolver_config, + ) + .await?; + + let user_principal = credential.username.clone(); + let service_principal = properties.service_principal_name(&hostname, user_principal.as_ref()); + let source = credential.source.as_deref().unwrap_or("$external"); + + #[cfg(target_os = "windows")] + let result = + windows::authenticate_stream(conn, credential, server_api, service_principal, source).await; + + #[cfg(not(target_os = "windows"))] + let result = + nix::authenticate_stream(conn, server_api, user_principal, service_principal, source).await; + + result +} + +impl GssapiProperties { + pub fn from_credential(credential: &Credential) -> Result { + let mut properties = GssapiProperties { + service_name: "mongodb".to_string(), + canonicalize_host_name: CanonicalizeHostName::None, + service_realm: None, + service_host: None, + }; + + if let Some(mechanism_properties) = &credential.mechanism_properties { + if let Some(Bson::String(name)) = mechanism_properties.get(SERVICE_NAME) { + properties.service_name = name.clone(); + } + + if let Some(canonicalize) = mechanism_properties.get(CANONICALIZE_HOST_NAME) { + properties.canonicalize_host_name = match canonicalize { + Bson::String(s) => match s.as_str() { + "none" => CanonicalizeHostName::None, + "forward" => CanonicalizeHostName::Forward, + "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + format!( + "Invalid CANONICALIZE_HOST_NAME value: {s}. Valid values are \ + 'none', 'forward', 'forwardAndReverse'", + ) + .as_str(), + )) + } + }, + Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, + Bson::Boolean(false) => CanonicalizeHostName::None, + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + "CANONICALIZE_HOST_NAME must be a string or boolean", + )) + } + }; + } + + if let Some(Bson::String(realm)) = mechanism_properties.get(SERVICE_REALM) { + properties.service_realm = Some(realm.clone()); + } + + if let Some(Bson::String(host)) = mechanism_properties.get(SERVICE_HOST) { + properties.service_host = Some(host.clone()); + } + } + + Ok(properties) + } + + fn service_principal_name(self, hostname: &String, user_principal: Option<&String>) -> String { + // Set the service principal name in addition to the user provided properties + let service_name: &str = self.service_name.as_ref(); + let mut service_principal = format!("{service_name}/{hostname}"); + if let Some(service_realm) = self.service_realm.as_ref() { + service_principal = format!("{service_principal}@{service_realm}"); + } else if let Some(user_principal) = user_principal { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{service_principal}{realm}"); + } + } + + service_principal + } +} + +pub(crate) async fn canonicalize_hostname( + hostname: &str, + mode: &CanonicalizeHostName, + resolver_config: Option<&ResolverConfig>, +) -> Result { + if mode == &CanonicalizeHostName::None { + return Ok(hostname.to_string()); + } + + let resolver = + crate::runtime::AsyncResolver::new(resolver_config.map(|c| c.inner.clone())).await?; + + let hostname = match mode { + CanonicalizeHostName::Forward => { + let lookup_records = resolver.cname_lookup(hostname).await?; + + if !lookup_records.records().is_empty() { + // As long as there is a record, we can return the original hostname. + // Although the spec says to return the canonical name, this is not + // done by any drivers in practice since the majority of them use + // libraries that do not follow CNAME chains. Also, we do not want to + // use the canonical name since it will likely differ from the input + // name, and the use of the input name is required for the service + // principal to be accepted by the GSSAPI auth flow. + hostname.to_lowercase().to_string() + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )); + } + } + CanonicalizeHostName::ForwardAndReverse => { + // forward lookup + let ips = resolver.ip_lookup(hostname).await?; + + if let Some(first_address) = ips.iter().next() { + // reverse lookup + match resolver.reverse_lookup(first_address).await { + Ok(reverse_lookup) => { + if let Some(name) = reverse_lookup.iter().next() { + name.to_lowercase().to_string() + } else { + hostname.to_lowercase() + } + } + Err(_) => hostname.to_lowercase(), + } + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )); + } + } + CanonicalizeHostName::None => unreachable!(), + }; + + // Sometimes reverse lookup results in a trailing "." since that is the correct + // way to present a FQDN. However, GSSAPI rejects the trailing "." so we remove + // it here manually. + let hostname = hostname.trim_end_matches("."); + + Ok(hostname.to_string()) +} diff --git a/src/client/auth/gssapi/nix.rs b/src/client/auth/gssapi/nix.rs new file mode 100644 index 000000000..5f8f1b83d --- /dev/null +++ b/src/client/auth/gssapi/nix.rs @@ -0,0 +1,206 @@ +use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; + +use crate::{ + client::{ + auth::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + GSSAPI_STR, + }, + options::ServerApi, + }, + cmap::Connection, + error::{Error, Result}, +}; + +pub(super) async fn authenticate_stream( + conn: &mut Connection, + server_api: Option<&ServerApi>, + user_principal: Option, + service_principal: String, + source: &str, +) -> Result<()> { + let (mut authenticator, initial_token) = + GssapiAuthenticator::init(user_principal, service_principal).await?; + + let command = SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + initial_token, + server_api.cloned(), + ) + .into_command()?; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + let mut conversation_id = Some(sasl_response.conversation_id); + let mut payload = sasl_response.payload; + + // Limit number of auth challenge steps (typically, only one step is needed, however + // different configurations may require more). + for _ in 0..10 { + let challenge = payload.as_slice(); + let output_token = authenticator.step(challenge).await?; + + // The step may return None, which is a valid final step. We still need to + // send a saslContinue command, so we send an empty payload if there is no + // token. + let token = output_token.unwrap_or(vec![]); + let command = SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; + + // Although unlikely, there are cases where authentication can be done + // at this point. + if sasl_response.done { + return Ok(()); + } + + // The authenticator is considered "complete" when the Kerberos auth + // process is done. However, this is not the end of the full auth flow. + // We no longer need to issue challenges to the authenticator, so we + // break the loop and continue with the rest of the flow. + if authenticator.is_complete() { + break; + } + } + + let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + if sasl_response.done { + Ok(()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "GSSAPI authentication failed after 10 attempts", + )) + } +} + +struct GssapiAuthenticator { + pending_ctx: Option, + established_ctx: Option, + user_principal: Option, + is_complete: bool, +} + +impl GssapiAuthenticator { + // Initialize the GssapiAuthenticator by creating a PendingClientCtx and + // getting an initial token to send to the server. + async fn init( + user_principal: Option, + service_principal: String, + ) -> Result<(Self, Vec)> { + let (pending_ctx, initial_token) = ClientCtx::new( + InitiateFlags::empty(), + user_principal.as_deref(), + &service_principal, + None, // No channel bindings + ) + .map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to initialize GSSAPI context: {e}"), + ) + })?; + + Ok(( + Self { + pending_ctx: Some(pending_ctx), + established_ctx: None, + user_principal, + is_complete: false, + }, + initial_token.to_vec(), + )) + } + + // Issue the server provided token to the client context. If the ClientCtx + // is established, an optional final token that must be sent to the server + // may be returned; otherwise another token to pass to the server is + // returned and the client context remains in the pending state. + async fn step(&mut self, challenge: &[u8]) -> Result>> { + if challenge.is_empty() { + Err(Error::authentication_error( + GSSAPI_STR, + "Expected challenge data for GSSAPI continuation", + )) + } else if let Some(pending_ctx) = self.pending_ctx.take() { + match pending_ctx.step(challenge).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {e}")) + })? { + Step::Finished((ctx, token)) => { + self.is_complete = true; + self.established_ctx = Some(ctx); + Ok(token.map(|t| t.to_vec())) + } + Step::Continue((ctx, token)) => { + self.pending_ctx = Some(ctx); + Ok(Some(token.to_vec())) + } + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "Authentication context not initialized", + )) + } + } + + // Perform the final step of Kerberos authentication by gss_unwrap-ing the + // final server challenge, then wrapping the protocol bytes + user principal. + // The resulting token must be sent to the server. + fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { + if let Some(mut established_ctx) = self.established_ctx.take() { + let _ = established_ctx.unwrap(payload).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) + })?; + + if let Some(user_principal) = self.user_principal.take() { + let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; + let bytes = [bytes, user_principal.as_bytes()].concat(); + let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) + })?; + Ok(output_token.to_vec()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "User principal not specified", + )) + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "Authentication context not established", + )) + } + } + + fn is_complete(&self) -> bool { + self.is_complete + } +} diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs new file mode 100644 index 000000000..12ceebe64 --- /dev/null +++ b/src/client/auth/gssapi/windows.rs @@ -0,0 +1,186 @@ +use sspi::{ + AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialsBuffers, + DataRepresentation, InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, + SecurityStatus, Sspi, SspiImpl, Username, +}; + +use crate::{ + client::{ + auth::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + Credential, GSSAPI_STR, + }, + options::ServerApi, + }, + cmap::Connection, + error::{Error, Result}, + options::ResolverConfig, +}; + +pub(super) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + service_principal: String, + source: &str, +) -> Result<()> { + // Configuration + let kerberos_config = KerberosConfig::new("", "".to_string()); + let mut kerberos = Kerberos::new_client_from_config(kerberos_config).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to create SSPI Kerberos client: {e}"), + ) + })?; + + // Acquire Creds + let mut acq_creds_handle_result = get_cred_handle(&mut kerberos, credential)?; + + let mut conversation_id = None; + let mut input_token: Vec = vec![]; + + loop { + let (output_token, status) = step( + &mut kerberos, + &mut acq_creds_handle_result.credentials_handle, + input_token.as_slice(), + service_principal.clone(), + )?; + if status == SecurityStatus::ContinueNeeded || status == SecurityStatus::Ok { + let command = if conversation_id.is_none() { + SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + output_token, + server_api.cloned(), + ) + .into_command()? + } else { + SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + output_token, + server_api.cloned(), + ) + .into_command() + }; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + conversation_id = Some(sasl_response.conversation_id); + input_token = sasl_response.payload; + + if sasl_response.done { + return Ok(()); + } + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("problem authenticating; status = {status:?}"), + )); + } + } +} + +fn get_cred_handle( + kerberos: &mut Kerberos, + credential: &Credential, +) -> Result>> { + let mut acq_creds_handle_result = kerberos + .acquire_credentials_handle() + .with_credential_use(sspi::CredentialUse::Outbound); + + let mut auth_data: Option = None; + if let Some(pwd) = credential.password.clone() { + if let Some(username) = credential.username.as_ref() { + let identity = sspi::AuthIdentity { + username: Username::parse(username).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to parse user principal: {e}"), + ) + })?, + password: pwd.into(), + }; + + auth_data = Some(identity.into()); + } else { + return Err(Error::authentication_error( + GSSAPI_STR, + "Username required but not specified", + )); + } + } + + if let Some(auth_data) = auth_data.as_ref() { + acq_creds_handle_result = acq_creds_handle_result.with_auth_data(auth_data); + } + + acq_creds_handle_result.execute(kerberos).map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to acquire credentials handle: {e:?}"), + ) + }) +} + +fn step_helper( + kerberos: &mut Kerberos, + cred_handle: &mut ::CredentialsHandle, + input_buffer: &mut [SecurityBuffer], + output_buffer: &mut [SecurityBuffer], + service_principal: String, +) -> Result { + let mut builder = kerberos + .initialize_security_context() + .with_credentials_handle(cred_handle) + .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(&service_principal) + .with_input(input_buffer) + .with_output(output_buffer); + + let result = kerberos + .initialize_security_context_impl(&mut builder) + .map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to initialize security context: {e}"), + ) + })? + .resolve_to_result() + .map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("Failed to resolve to result: {e}")) + }); + + result +} + +fn step( + kerberos: &mut Kerberos, + cred_handle: &mut ::CredentialsHandle, + input_token: &[u8], + service_principal: String, +) -> Result<(Vec, SecurityStatus)> { + let mut secure_input_buffer = + vec![SecurityBuffer::new(input_token.to_vec(), BufferType::Token)]; + let mut secure_output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; + match step_helper( + kerberos, + cred_handle, + &mut secure_input_buffer, + &mut secure_output_buffer, + service_principal, + ) { + Ok(result) => { + let output_buffer = secure_output_buffer[0].to_owned(); + Ok((output_buffer.buffer, result.status)) + } + Err(e) => Err(Error::authentication_error( + GSSAPI_STR, + &format!("error stepping: {e:?}"), + )), + } +} From 9175eb2bab96f0ee8fe4cc2cd4f282bcf785170c Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 15:50:05 -0400 Subject: [PATCH 05/15] RUST-2245: Update windows implementation to use windows api instead of sspi --- Cargo.lock | 1188 ++++++++++++----------------- Cargo.toml | 29 +- src/client/auth/gssapi/windows.rs | 580 ++++++++++---- 3 files changed, 947 insertions(+), 850 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4f6ca0d4..fe3762d13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -92,36 +92,6 @@ dependencies = [ "nodrop", ] -[[package]] -name = "async-dnssd" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-util", - "libc", - "log", - "pin-utils", - "pkg-config", - "tokio", - "winapi", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -141,7 +111,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -152,7 +122,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -167,6 +137,43 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483020b893cdef3d89637e428d588650c71cfae7ea2e6ecbaee4de4ff99fb2dd" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.3.1", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1541072f81945fa1251f8795ef6c92c4282d74d59f88498ae7d4bf00f0ebdad9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.13.3" @@ -190,6 +197,244 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "aws-runtime" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.81.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92ea8a7602321c83615c82b408820ad54280fb026e92de0eeea937342fafa24" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2", + "http 1.3.1", + "hyper 1.6.0", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tower 0.5.2", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d52251ed4b9776a3e8487b2a01ac915f73b2da3af8fc1e77e0fce697a550d4" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -205,12 +450,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.13.1" @@ -223,6 +462,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.0" @@ -235,7 +484,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.12.1", @@ -248,7 +497,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.104", "which", ] @@ -258,7 +507,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -269,7 +518,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -280,9 +529,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -323,12 +572,12 @@ dependencies = [ "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.2", + "getrandom 0.3.3", "hex", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", - "rand 0.9.1", + "rand 0.9.2", "serde", "serde_bytes", "serde_json", @@ -339,18 +588,18 @@ dependencies = [ [[package]] name = "bson" version = "3.0.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#266aa3039e603cad96a5dde377aaf8251be76c79" +source = "git+https://github.com/mongodb/bson-rust?branch=main#e13a5886597d23f8039a95225e3bb2c661e4249f" dependencies = [ "ahash", "base64 0.22.1", "bitvec", "getrandom 0.2.16", - "getrandom 0.3.2", + "getrandom 0.3.3", "hex", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", - "rand 0.9.1", + "rand 0.9.2", "serde", "serde_bytes", "serde_json", @@ -366,18 +615,22 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "cbc" version = "0.1.2" @@ -389,9 +642,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -504,6 +757,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -535,7 +798,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d4ddf7139e64dc916b11d434421031bcc5ba02e521a49a011652a0f68775188" dependencies = [ "anyhow", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "libgssapi", "windows", @@ -572,27 +835,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1e6e5492f8f0830c37f301f6349e0dac8b2466e4fe89eef90e9eef906cd046" -dependencies = [ - "crypto-common", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -603,16 +845,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "ctrlc" version = "3.4.7" @@ -623,33 +855,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "darling" version = "0.20.11" @@ -671,7 +876,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -682,7 +887,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -698,7 +903,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", - "pem-rfc7468", "zeroize", ] @@ -720,7 +924,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -731,7 +935,7 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -744,7 +948,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -769,7 +973,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -782,7 +985,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -797,72 +1000,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "encoding_rs" version = "0.8.35" @@ -881,7 +1024,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -897,7 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -906,22 +1049,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "flate2" version = "1.1.2" @@ -1050,7 +1177,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1091,7 +1218,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1109,9 +1235,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -1133,17 +1259,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "h2" version = "0.4.11" @@ -1156,7 +1271,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -1171,9 +1286,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -1232,21 +1347,12 @@ dependencies = [ "parking_lot", "rand 0.8.5", "resolv-conf", - "smallvec 1.15.0", + "smallvec 1.15.1", "thiserror 1.0.69", "tokio", "tracing", ] -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1371,7 +1477,7 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "smallvec 1.15.0", + "smallvec 1.15.1", "tokio", "want", ] @@ -1386,11 +1492,12 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.0", + "webpki-roots 1.0.2", ] [[package]] @@ -1496,7 +1603,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.0", + "smallvec 1.15.1", "zerovec", ] @@ -1558,7 +1665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", - "smallvec 1.15.0", + "smallvec 1.15.1", "utf8_iter", ] @@ -1585,12 +1692,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -1610,7 +1717,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -1673,7 +1780,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -1687,15 +1794,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - [[package]] name = "lambda_runtime" version = "0.6.1" @@ -1733,9 +1831,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lazycell" @@ -1755,7 +1850,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "834339e86b2561169d45d3b01741967fee3e5716c7d0b6e33cd4e3b34c9558cd" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "lazy_static", "libgssapi-sys", @@ -1778,15 +1873,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.3", ] -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1851,7 +1940,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1865,7 +1954,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1876,7 +1965,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1887,7 +1976,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1906,15 +1995,6 @@ dependencies = [ "digest", ] -[[package]] -name = "md4" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" -dependencies = [ - "digest", -] - [[package]] name = "memchr" version = "2.7.5" @@ -1956,7 +2036,7 @@ dependencies = [ [[package]] name = "mongocrypt" version = "0.3.1" -source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#66c4ee29a2184c26ff5d7b290a23b5fdcf9c7d26" +source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#694951462f80e3157b5e5a217543e5c30088604b" dependencies = [ "bson 2.15.0", "bson 3.0.0", @@ -1968,7 +2048,7 @@ dependencies = [ [[package]] name = "mongocrypt-sys" version = "0.1.4+1.12.0" -source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#66c4ee29a2184c26ff5d7b290a23b5fdcf9c7d26" +source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#694951462f80e3157b5e5a217543e5c30088604b" [[package]] name = "mongodb" @@ -1977,6 +2057,9 @@ dependencies = [ "anyhow", "approx", "async-trait", + "aws-config", + "aws-credential-types", + "aws-types", "backtrace", "base64 0.13.1", "bitflags 1.3.2", @@ -2032,7 +2115,6 @@ dependencies = [ "sha2", "snap", "socket2 0.5.10", - "sspi", "stringprep", "strsim", "take_mut", @@ -2047,6 +2129,7 @@ dependencies = [ "typed-builder", "uuid", "webpki-roots 0.26.11", + "windows-sys 0.60.2", "zstd", ] @@ -2057,7 +2140,7 @@ dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2072,7 +2155,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2083,7 +2166,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -2106,31 +2189,13 @@ dependencies = [ ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "serde", - "smallvec 1.15.0", - "zeroize", +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", ] [[package]] @@ -2139,17 +2204,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -2159,17 +2213,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2177,7 +2220,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2199,15 +2241,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "oid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" -dependencies = [ - "serde", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -2220,7 +2253,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -2237,7 +2270,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2259,48 +2292,16 @@ dependencies = [ ] [[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" +name = "outref" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] -name = "p521" -version = "0.13.3" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "rand_core 0.6.4", - "sha2", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" @@ -2321,7 +2322,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.15.0", + "smallvec 1.15.1", "windows-targets 0.52.6", ] @@ -2342,7 +2343,6 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "sha1", ] [[package]] @@ -2355,116 +2355,12 @@ dependencies = [ "serde", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "picky" -version = "7.0.0-rc.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ed0f61c4ac11274678ad0da95386c6216d6ff898ef98e54aac23d4a853c324c" -dependencies = [ - "base64 0.22.1", - "digest", - "ed25519-dalek", - "hex", - "md-5", - "num-bigint-dig", - "p256", - "p384", - "p521", - "picky-asn1", - "picky-asn1-der", - "picky-asn1-x509", - "rand 0.8.5", - "rand_core 0.6.4", - "rsa", - "serde", - "sha1", - "sha2", - "sha3", - "thiserror 1.0.69", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "picky-asn1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" -dependencies = [ - "oid", - "serde", - "serde_bytes", - "time", - "zeroize", -] - -[[package]] -name = "picky-asn1-der" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dccb53c26f70c082e008818f524bd45d057069517b047bd0c0ee062d6d7d7f2" -dependencies = [ - "picky-asn1", - "serde", - "serde_bytes", -] - -[[package]] -name = "picky-asn1-x509" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444990fc9e53de47353c95e6521a5f669676da986a10482ff3708bdaa1a2a4b" -dependencies = [ - "base64 0.22.1", - "num-bigint-dig", - "oid", - "picky-asn1", - "picky-asn1-der", - "serde", - "widestring", - "zeroize", -] - -[[package]] -name = "picky-krb" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ffe5f2122cdda5e9059ab837a65ba1b77729db43fc1500f2fce6b27070eab" -dependencies = [ - "aes", - "byteorder", - "cbc", - "crypto", - "des", - "hmac", - "num-bigint-dig", - "oid", - "pbkdf2 0.12.2", - "picky-asn1", - "picky-asn1-der", - "picky-asn1-x509", - "rand 0.8.5", - "serde", - "sha1", - "thiserror 1.0.69", - "uuid", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -2482,7 +2378,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2497,17 +2393,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - [[package]] name = "pkcs5" version = "0.7.1" @@ -2579,21 +2464,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn 2.0.101", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", + "syn 2.0.104", ] [[package]] @@ -2641,9 +2517,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash 2.1.1", "rustls", @@ -2703,9 +2579,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -2746,7 +2622,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -2771,11 +2647,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2795,7 +2671,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2821,6 +2697,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2868,7 +2750,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.0", + "webpki-roots 1.0.2", ] [[package]] @@ -2877,16 +2759,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "ring" version = "0.17.14" @@ -2901,27 +2773,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "sha1", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustc-demangle" version = "0.1.26" @@ -2965,7 +2816,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2978,18 +2829,18 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.30" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "aws-lc-rs", "log", @@ -3001,6 +2852,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -3095,27 +2958,26 @@ dependencies = [ ] [[package]] -name = "sec1" -version = "0.7.3" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3174,16 +3036,16 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -3222,7 +3084,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.10.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -3241,7 +3103,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3266,16 +3128,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -3293,23 +3145,13 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simdutf8" version = "0.1.5" @@ -3333,9 +3175,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snap" @@ -3363,12 +3205,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" version = "0.7.3" @@ -3379,50 +3215,6 @@ dependencies = [ "der", ] -[[package]] -name = "sspi" -version = "0.15.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214ee905dcdd4b7ab11404b616e58dc6944d80fe8592fbdc13abc87d7e2bff0a" -dependencies = [ - "async-dnssd", - "async-recursion", - "bitflags 2.9.0", - "byteorder", - "cfg-if", - "crypto-mac", - "futures", - "hmac", - "lazy_static", - "md-5", - "md4", - "num-bigint-dig", - "num-derive", - "num-traits", - "oid", - "picky", - "picky-asn1", - "picky-asn1-der", - "picky-asn1-x509", - "picky-krb", - "rand 0.8.5", - "rsa", - "rustls", - "serde", - "serde_derive", - "sha1", - "sha2", - "time", - "tokio", - "tracing", - "url", - "uuid", - "windows", - "windows-registry", - "windows-sys 0.60.2", - "zeroize", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3465,9 +3257,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3491,7 +3283,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3500,8 +3292,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", - "core-foundation", + "bitflags 2.9.1", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3534,7 +3326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.8", "windows-sys 0.59.0", @@ -3566,7 +3358,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3577,7 +3369,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3656,9 +3448,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -3682,7 +3474,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3729,9 +3521,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -3786,7 +3578,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-util", "http 1.3.1", @@ -3830,7 +3622,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3862,7 +3654,7 @@ checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", - "smallvec 1.15.0", + "smallvec 1.15.1", "thread_local", "tracing-core", "tracing-log", @@ -3891,7 +3683,7 @@ checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3944,6 +3736,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3956,7 +3754,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -3980,6 +3778,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "want" version = "0.3.1" @@ -4026,7 +3830,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -4061,7 +3865,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4101,14 +3905,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.0", + "webpki-roots 1.0.2", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -4207,7 +4011,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4218,7 +4022,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4513,7 +4317,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -4532,16 +4336,10 @@ dependencies = [ ] [[package]] -name = "x25519-dalek" -version = "2.0.1" +name = "xmlparser" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core 0.6.4", - "serde", - "zeroize", -] +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yansi" @@ -4569,7 +4367,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -4590,7 +4388,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4610,7 +4408,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -4619,20 +4417,6 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] [[package]] name = "zerotrie" @@ -4647,9 +4431,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" dependencies = [ "yoke", "zerofrom", @@ -4664,7 +4448,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3378e046d..78fe3c9e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ dns-resolver = ["dep:hickory-resolver", "dep:hickory-proto"] cert-key-password = ["dep:pem", "dep:pkcs8"] # Enable support for MONGODB-AWS authentication. -aws-auth = ["dep:reqwest"] +aws-auth = ["dep:reqwest", "dep:aws-config", "dep:aws-types", "dep:aws-credential-types"] # Enable support for on-demand Azure KMS credentials. azure-kms = ["dep:reqwest"] @@ -56,7 +56,7 @@ gcp-oidc = ["dep:reqwest"] gcp-kms = ["dep:reqwest"] # Enable support for GSSAPI (Kerberos) authentication. -gssapi-auth = ["dep:cross-krb5", "dep:sspi", "dns-resolver"] +gssapi-auth = ["dep:cross-krb5", "dep:windows-sys", "dns-resolver"] zstd-compression = ["dep:zstd"] zlib-compression = ["dep:flate2"] @@ -80,7 +80,6 @@ chrono = { version = "0.4.7", default-features = false, features = [ "clock", "std", ] } -cross-krb5 = { version = "0.4.2", optional = true, default-features = false } derive_more = "0.99.17" derive-where = "1.2.7" flate2 = { version = "1.0", optional = true } @@ -110,7 +109,6 @@ sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } socket2 = "0.5.5" -sspi = { version = "0.15.1", optional = true } stringprep = "0.1.2" strsim = "0.11.1" take_mut = "0.2.2" @@ -123,6 +121,22 @@ zstd = { version = "0.11.2", optional = true } macro_magic = "0.5.1" rustversion = "1.0.20" +[dependencies.aws-config] +version = "1" +optional = true +default-features = false +features = ["default-https-client", "rt-tokio"] + +[dependencies.aws-types] +version = "1.3.7" +optional = true +default-features = false + +[dependencies.aws-credential-types] +version = "1.2.4" +optional = true +default-features = false + [dependencies.bson2] git = "https://github.com/mongodb/bson-rust" branch = "2.15.x" @@ -220,6 +234,13 @@ features = ["serde", "serde_json-1"] rustdoc-args = ["--cfg", "docsrs"] all-features = true +# Target-specific dependencies for GSSAPI authentication +[target.'cfg(not(windows))'.dependencies] +cross-krb5 = { version = "0.4.2", optional = true, default-features = false } + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.60", optional = true, features = ["Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Foundation", "Win32_System", "Win32_System_Rpc"] } + [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(mongodb_internal_tracking_arc)', diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 12ceebe64..8036132f9 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -1,8 +1,14 @@ -use sspi::{ - AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialsBuffers, - DataRepresentation, InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, - SecurityStatus, Sspi, SspiImpl, Username, +use std::ptr; +use windows_sys::Win32::Foundation::{SEC_E_OK, SEC_I_CONTINUE_NEEDED}; +use windows_sys::Win32::Security::Authentication::Identity::{ + AcquireCredentialsHandleW, DecryptMessage, DeleteSecurityContext, EncryptMessage, + FreeCredentialsHandle, InitializeSecurityContextW, QueryContextAttributesW, SecBuffer, + SecBufferDesc, SecPkgContext_Sizes, ISC_REQ_ALLOCATE_MEMORY, ISC_REQ_MUTUAL_AUTH, + SECBUFFER_DATA, SECBUFFER_PADDING, SECBUFFER_STREAM, SECBUFFER_TOKEN, SECBUFFER_VERSION, + SECPKG_ATTR_SIZES, SECPKG_CRED_OUTBOUND, SECQOP_WRAP_NO_ENCRYPT, SECURITY_NETWORK_DREP, }; +use windows_sys::Win32::Security::Credentials::SecHandle; +use windows_sys::Win32::System::Rpc::{SEC_WINNT_AUTH_IDENTITY_UNICODE, SEC_WINNT_AUTH_IDENTITY_W}; use crate::{ client::{ @@ -14,7 +20,6 @@ use crate::{ }, cmap::Connection, error::{Error, Result}, - options::ResolverConfig, }; pub(super) async fn authenticate_stream( @@ -24,163 +29,450 @@ pub(super) async fn authenticate_stream( service_principal: String, source: &str, ) -> Result<()> { - // Configuration - let kerberos_config = KerberosConfig::new("", "".to_string()); - let mut kerberos = Kerberos::new_client_from_config(kerberos_config).map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to create SSPI Kerberos client: {e}"), + let user_principal = credential.username.clone(); + let password = credential.password.clone(); + let (mut authenticator, initial_token) = + SspiAuthenticator::init(user_principal, password, service_principal).await?; + + let command = SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + initial_token, + server_api.cloned(), + ) + .into_command()?; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + let mut conversation_id = Some(sasl_response.conversation_id); + let mut payload = sasl_response.payload; + + for _ in 0..10 { + let challenge = payload.as_slice(); + let output_token = authenticator.step(challenge).await?; + + let token = output_token.unwrap_or(vec![]); + let command = SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), ) - })?; - - // Acquire Creds - let mut acq_creds_handle_result = get_cred_handle(&mut kerberos, credential)?; - - let mut conversation_id = None; - let mut input_token: Vec = vec![]; - - loop { - let (output_token, status) = step( - &mut kerberos, - &mut acq_creds_handle_result.credentials_handle, - input_token.as_slice(), - service_principal.clone(), - )?; - if status == SecurityStatus::ContinueNeeded || status == SecurityStatus::Ok { - let command = if conversation_id.is_none() { - SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - output_token, - server_api.cloned(), - ) - .into_command()? - } else { - SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - output_token, - server_api.cloned(), - ) - .into_command() - }; + .into_command(); - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - conversation_id = Some(sasl_response.conversation_id); - input_token = sasl_response.payload; + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; - if sasl_response.done { - return Ok(()); - } - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - &format!("problem authenticating; status = {status:?}"), - )); + if sasl_response.done { + return Ok(()); } + + if authenticator.is_complete() { + break; + } + } + + let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + if sasl_response.done { + Ok(()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "GSSAPI authentication failed after 10 attempts", + )) } } -fn get_cred_handle( - kerberos: &mut Kerberos, - credential: &Credential, -) -> Result>> { - let mut acq_creds_handle_result = kerberos - .acquire_credentials_handle() - .with_credential_use(sspi::CredentialUse::Outbound); - - let mut auth_data: Option = None; - if let Some(pwd) = credential.password.clone() { - if let Some(username) = credential.username.as_ref() { - let identity = sspi::AuthIdentity { - username: Username::parse(username).map_err(|e| { - Error::authentication_error( +struct SspiAuthenticator { + cred_handle: Option, + ctx_handle: Option, + have_cred: bool, + have_context: bool, + auth_complete: bool, + name_token: Vec, + user_plus_realm: String, +} + +impl SspiAuthenticator { + async fn init( + user_principal: Option, + password: Option, + service_principal: String, + ) -> Result<(Self, Vec)> { + let user_plus_realm = user_principal.clone().unwrap_or_default(); + + let name_token_string = service_principal.clone(); + let name_token: Vec = name_token_string + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + + let mut authenticator = Self { + cred_handle: None, + ctx_handle: None, + have_cred: false, + have_context: false, + auth_complete: false, + name_token, + user_plus_realm, + }; + + let initial_token = authenticator + .acquire_credentials_and_init(user_principal, password) + .await?; + Ok((authenticator, initial_token)) + } + + async fn acquire_credentials_and_init( + &mut self, + user_principal: Option, + password: Option, + ) -> Result> { + unsafe { + let mut cred_handle = SecHandle::default(); + let mut expiry: i64 = 0; + + let mut auth_identity = SEC_WINNT_AUTH_IDENTITY_W::default(); + auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + let username_wide: Vec; + let domain_wide: Vec; + let password_wide: Vec; + + if let Some(user_principal) = &user_principal { + if let Some(at_pos) = user_principal.find('@') { + let username = &user_principal[..at_pos]; + let domain = &user_principal[at_pos + 1..]; + + username_wide = username.encode_utf16().chain(std::iter::once(0)).collect(); + domain_wide = domain.encode_utf16().chain(std::iter::once(0)).collect(); + + auth_identity.User = username_wide.as_ptr() as *mut u16; + auth_identity.UserLength = username.len() as u32; + auth_identity.Domain = domain_wide.as_ptr() as *mut u16; + auth_identity.DomainLength = domain.len() as u32; + + if let Some(password) = &password { + password_wide = password.encode_utf16().chain(std::iter::once(0)).collect(); + auth_identity.Password = password_wide.as_ptr() as *mut u16; + auth_identity.PasswordLength = password.len() as u32; + } + } + } + + let package_name: Vec = "kerberos\0".encode_utf16().collect(); + let result = AcquireCredentialsHandleW( + std::ptr::null(), + package_name.as_ptr(), + SECPKG_CRED_OUTBOUND, + ptr::null_mut(), + if password.is_some() { + &auth_identity as *const _ as *const _ + } else { + ptr::null() + }, + None, + ptr::null_mut(), + &mut cred_handle, + &mut expiry, + ); + + if result != SEC_E_OK { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("Failed to acquire credentials handle: {:?}", result), + )); + } + + self.cred_handle = Some(cred_handle); + self.have_cred = true; + + let initial_token = self.initialize_security_context(&[])?; + Ok(initial_token) + } + } + + async fn step(&mut self, challenge: &[u8]) -> Result>> { + if self.auth_complete { + return Ok(None); + } + + let token = self.initialize_security_context(challenge)?; + Ok(Some(token)) + } + + fn initialize_security_context(&mut self, input_token: &[u8]) -> Result> { + unsafe { + let mut ctx_handle = if self.have_context { + self.ctx_handle.unwrap() + } else { + SecHandle::default() + }; + + let mut input_buffer = SecBuffer { + cbBuffer: input_token.len() as u32, + BufferType: SECBUFFER_TOKEN, + pvBuffer: if input_token.is_empty() { + ptr::null_mut() + } else { + input_token.as_ptr() as *mut _ + }, + }; + + let input_buffer_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 1, + pBuffers: &mut input_buffer, + }; + + let mut output_buffer = SecBuffer { + cbBuffer: 0, + BufferType: SECBUFFER_TOKEN, + pvBuffer: ptr::null_mut(), + }; + + let mut output_buffer_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 1, + pBuffers: &mut output_buffer, + }; + + let mut context_attr = 0u32; + + let result = InitializeSecurityContextW( + &self.cred_handle.unwrap(), + if self.have_context { + &ctx_handle + } else { + ptr::null() + }, + self.name_token.as_ptr(), + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MUTUAL_AUTH, + 0, + SECURITY_NETWORK_DREP, + if self.have_context { + &input_buffer_desc + } else { + ptr::null() + }, + 0, + &mut ctx_handle, + &mut output_buffer_desc, + &mut context_attr, + ptr::null_mut(), + ); + + self.ctx_handle = Some(ctx_handle); + self.have_context = true; + + match result { + SEC_E_OK => { + self.auth_complete = true; + } + SEC_I_CONTINUE_NEEDED => {} + _ => { + return Err(Error::authentication_error( GSSAPI_STR, - &format!("Failed to parse user principal: {e}"), - ) - })?, - password: pwd.into(), + &format!("InitializeSecurityContext failed: {:?}", result), + )); + } + } + + let token = if output_buffer.pvBuffer.is_null() || output_buffer.cbBuffer == 0 { + Vec::new() + } else { + let token_slice = std::slice::from_raw_parts( + output_buffer.pvBuffer as *const u8, + output_buffer.cbBuffer as usize, + ); + token_slice.to_vec() }; - auth_data = Some(identity.into()); - } else { - return Err(Error::authentication_error( - GSSAPI_STR, - "Username required but not specified", - )); + Ok(token) } } - if let Some(auth_data) = auth_data.as_ref() { - acq_creds_handle_result = acq_creds_handle_result.with_auth_data(auth_data); - } + fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { + unsafe { + let mut message = payload.to_vec(); - acq_creds_handle_result.execute(kerberos).map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to acquire credentials handle: {e:?}"), - ) - }) -} + let mut wrap_bufs = [ + SecBuffer { + cbBuffer: message.len() as u32, + BufferType: SECBUFFER_STREAM, + pvBuffer: message.as_mut_ptr() as *mut _, + }, + SecBuffer { + cbBuffer: 0, + BufferType: SECBUFFER_DATA, + pvBuffer: ptr::null_mut(), + }, + ]; -fn step_helper( - kerberos: &mut Kerberos, - cred_handle: &mut ::CredentialsHandle, - input_buffer: &mut [SecurityBuffer], - output_buffer: &mut [SecurityBuffer], - service_principal: String, -) -> Result { - let mut builder = kerberos - .initialize_security_context() - .with_credentials_handle(cred_handle) - .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) - .with_target_data_representation(DataRepresentation::Native) - .with_target_name(&service_principal) - .with_input(input_buffer) - .with_output(output_buffer); - - let result = kerberos - .initialize_security_context_impl(&mut builder) - .map_err(|e| { - Error::authentication_error( - GSSAPI_STR, - &format!("Failed to initialize security context: {e}"), - ) - })? - .resolve_to_result() - .map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("Failed to resolve to result: {e}")) - }); - - result + let mut wrap_buf_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 2, + pBuffers: wrap_bufs.as_mut_ptr(), + }; + + let result = DecryptMessage( + &self.ctx_handle.unwrap(), + &mut wrap_buf_desc, + 0, + ptr::null_mut(), + ); + if result != SEC_E_OK { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("DecryptMessage failed: {:?}", result), + )); + } + + if wrap_bufs[1].cbBuffer < 4 { + return Err(Error::authentication_error( + GSSAPI_STR, + "Server message is too short", + )); + } + + let data_ptr = wrap_bufs[1].pvBuffer as *const u8; + let first_byte = *data_ptr; + if (first_byte & 1) == 0 { + return Err(Error::authentication_error( + GSSAPI_STR, + "Server does not support the required security layer", + )); + } + + let mut sizes = SecPkgContext_Sizes::default(); + let result = QueryContextAttributesW( + &self.ctx_handle.unwrap(), + SECPKG_ATTR_SIZES, + &mut sizes as *mut _ as *mut _, + ); + if result != SEC_E_OK { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("QueryContextAttributes failed: {:?}", result), + )); + } + + let user_plus_realm = &self.user_plus_realm; + let plaintext_message_size = 4 + user_plus_realm.len(); + let total_size = sizes.cbSecurityTrailer as usize + + plaintext_message_size + + sizes.cbBlockSize as usize; + let mut message_buf = vec![0u8; total_size]; + + let plaintext_start = sizes.cbSecurityTrailer as usize; + message_buf[plaintext_start] = 1; + message_buf[plaintext_start + 1] = 0; + message_buf[plaintext_start + 2] = 0; + message_buf[plaintext_start + 3] = 0; + message_buf[plaintext_start + 4..plaintext_start + 4 + user_plus_realm.len()] + .copy_from_slice(user_plus_realm.as_bytes()); + + let mut encrypt_bufs = [ + SecBuffer { + cbBuffer: sizes.cbSecurityTrailer, + BufferType: SECBUFFER_TOKEN, + pvBuffer: message_buf.as_mut_ptr() as *mut _, + }, + SecBuffer { + cbBuffer: plaintext_message_size as u32, + BufferType: SECBUFFER_DATA, + pvBuffer: message_buf + .as_mut_ptr() + .add(sizes.cbSecurityTrailer as usize) + as *mut _, + }, + SecBuffer { + cbBuffer: sizes.cbBlockSize, + BufferType: SECBUFFER_PADDING, + pvBuffer: message_buf + .as_mut_ptr() + .add(plaintext_start + plaintext_message_size) + as *mut _, + }, + ]; + + let mut encrypt_buf_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 3, + pBuffers: encrypt_bufs.as_mut_ptr(), + }; + + let result = EncryptMessage( + &self.ctx_handle.unwrap(), + SECQOP_WRAP_NO_ENCRYPT, + &mut encrypt_buf_desc, + 0, + ); + if result != SEC_E_OK { + return Err(Error::authentication_error( + GSSAPI_STR, + &format!("EncryptMessage failed: {:?}", result), + )); + } + + let total_len = + encrypt_bufs[0].cbBuffer + encrypt_bufs[1].cbBuffer + encrypt_bufs[2].cbBuffer; + let mut result_buf = Vec::with_capacity(total_len as usize); + + let buf0_slice = std::slice::from_raw_parts( + encrypt_bufs[0].pvBuffer as *const u8, + encrypt_bufs[0].cbBuffer as usize, + ); + result_buf.extend_from_slice(buf0_slice); + + let buf1_slice = std::slice::from_raw_parts( + encrypt_bufs[1].pvBuffer as *const u8, + encrypt_bufs[1].cbBuffer as usize, + ); + result_buf.extend_from_slice(buf1_slice); + + let buf2_slice = std::slice::from_raw_parts( + encrypt_bufs[2].pvBuffer as *const u8, + encrypt_bufs[2].cbBuffer as usize, + ); + result_buf.extend_from_slice(buf2_slice); + + Ok(result_buf) + } + } + + fn is_complete(&self) -> bool { + self.auth_complete + } } -fn step( - kerberos: &mut Kerberos, - cred_handle: &mut ::CredentialsHandle, - input_token: &[u8], - service_principal: String, -) -> Result<(Vec, SecurityStatus)> { - let mut secure_input_buffer = - vec![SecurityBuffer::new(input_token.to_vec(), BufferType::Token)]; - let mut secure_output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; - match step_helper( - kerberos, - cred_handle, - &mut secure_input_buffer, - &mut secure_output_buffer, - service_principal, - ) { - Ok(result) => { - let output_buffer = secure_output_buffer[0].to_owned(); - Ok((output_buffer.buffer, result.status)) +impl Drop for SspiAuthenticator { + fn drop(&mut self) { + unsafe { + if let Some(ctx) = &self.ctx_handle { + let _ = DeleteSecurityContext(ctx); + } + if let Some(cred) = &self.cred_handle { + let _ = FreeCredentialsHandle(cred); + } } - Err(e) => Err(Error::authentication_error( - GSSAPI_STR, - &format!("error stepping: {e:?}"), - )), } } From 08ca68ab4ae4314460b349b3019dee5a1e6ba418 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 15:57:06 -0400 Subject: [PATCH 06/15] RUST-2245: Fix rustfmt --- src/client/auth/gssapi/windows.rs | 41 +++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 8036132f9..751d584ba 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -1,20 +1,41 @@ use std::ptr; -use windows_sys::Win32::Foundation::{SEC_E_OK, SEC_I_CONTINUE_NEEDED}; -use windows_sys::Win32::Security::Authentication::Identity::{ - AcquireCredentialsHandleW, DecryptMessage, DeleteSecurityContext, EncryptMessage, - FreeCredentialsHandle, InitializeSecurityContextW, QueryContextAttributesW, SecBuffer, - SecBufferDesc, SecPkgContext_Sizes, ISC_REQ_ALLOCATE_MEMORY, ISC_REQ_MUTUAL_AUTH, - SECBUFFER_DATA, SECBUFFER_PADDING, SECBUFFER_STREAM, SECBUFFER_TOKEN, SECBUFFER_VERSION, - SECPKG_ATTR_SIZES, SECPKG_CRED_OUTBOUND, SECQOP_WRAP_NO_ENCRYPT, SECURITY_NETWORK_DREP, +use windows_sys::Win32::{ + Foundation::{SEC_E_OK, SEC_I_CONTINUE_NEEDED}, + Security::{ + Authentication::Identity::{ + AcquireCredentialsHandleW, + DecryptMessage, + DeleteSecurityContext, + EncryptMessage, + FreeCredentialsHandle, + InitializeSecurityContextW, + QueryContextAttributesW, + SecBuffer, + SecBufferDesc, + SecPkgContext_Sizes, + ISC_REQ_ALLOCATE_MEMORY, + ISC_REQ_MUTUAL_AUTH, + SECBUFFER_DATA, + SECBUFFER_PADDING, + SECBUFFER_STREAM, + SECBUFFER_TOKEN, + SECBUFFER_VERSION, + SECPKG_ATTR_SIZES, + SECPKG_CRED_OUTBOUND, + SECQOP_WRAP_NO_ENCRYPT, + SECURITY_NETWORK_DREP, + }, + Credentials::SecHandle, + }, + System::Rpc::{SEC_WINNT_AUTH_IDENTITY_UNICODE, SEC_WINNT_AUTH_IDENTITY_W}, }; -use windows_sys::Win32::Security::Credentials::SecHandle; -use windows_sys::Win32::System::Rpc::{SEC_WINNT_AUTH_IDENTITY_UNICODE, SEC_WINNT_AUTH_IDENTITY_W}; use crate::{ client::{ auth::{ sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, GSSAPI_STR, + Credential, + GSSAPI_STR, }, options::ServerApi, }, From 0d6858bea19ea432132504714de2a73fa90fdbad Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 17:27:24 -0400 Subject: [PATCH 07/15] RUST-2245: Refactor to share more common code --- src/client/auth/gssapi/mod.rs | 94 ++++++++++++++++++++++++-- src/client/auth/gssapi/nix.rs | 107 ++---------------------------- src/client/auth/gssapi/windows.rs | 105 +++-------------------------- 3 files changed, 102 insertions(+), 204 deletions(-) diff --git a/src/client/auth/gssapi/mod.rs b/src/client/auth/gssapi/mod.rs index 72ae33bc9..539626486 100644 --- a/src/client/auth/gssapi/mod.rs +++ b/src/client/auth/gssapi/mod.rs @@ -7,7 +7,11 @@ mod nix; use crate::{ bson::Bson, client::{ - auth::{Credential, GSSAPI_STR}, + auth::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + Credential, + GSSAPI_STR, + }, options::ServerApi, }, cmap::Connection, @@ -58,14 +62,92 @@ pub(crate) async fn authenticate_stream( let source = credential.source.as_deref().unwrap_or("$external"); #[cfg(target_os = "windows")] - let result = - windows::authenticate_stream(conn, credential, server_api, service_principal, source).await; + let (mut authenticator, initial_token) = windows::SspiAuthenticator::init( + user_principal, + credential.password.clone(), + service_principal, + )?; #[cfg(not(target_os = "windows"))] - let result = - nix::authenticate_stream(conn, server_api, user_principal, service_principal, source).await; + let (mut authenticator, initial_token) = + nix::GssapiAuthenticator::init(user_principal, service_principal)?; - result + let command = SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + initial_token, + server_api.cloned(), + ) + .into_command()?; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + let mut conversation_id = Some(sasl_response.conversation_id); + let mut payload = sasl_response.payload; + + // Limit number of auth challenge steps (typically, only one step is needed, however + // different configurations may require more). + for _ in 0..10 { + let challenge = payload.as_slice(); + let output_token = authenticator.step(challenge)?; + + // The step may return None, which is a valid final step. We still need to + // send a saslContinue command, so we send an empty payload if there is no + // token. + let token = output_token.unwrap_or(vec![]); + let command = SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; + + // Although unlikely, there are cases where authentication can be done + // at this point. + if sasl_response.done { + return Ok(()); + } + + // The authenticator is considered "complete" when the Kerberos auth + // process is done. However, this is not the end of the full auth flow. + // We no longer need to issue challenges to the authenticator, so we + // break the loop and continue with the rest of the flow. + if authenticator.is_complete() { + break; + } + } + + let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + if sasl_response.done { + Ok(()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "GSSAPI authentication failed after 10 attempts", + )) + } } impl GssapiProperties { diff --git a/src/client/auth/gssapi/nix.rs b/src/client/auth/gssapi/nix.rs index 5f8f1b83d..4cb1f9d59 100644 --- a/src/client/auth/gssapi/nix.rs +++ b/src/client/auth/gssapi/nix.rs @@ -1,106 +1,11 @@ use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; use crate::{ - client::{ - auth::{ - sasl::{SaslContinue, SaslResponse, SaslStart}, - GSSAPI_STR, - }, - options::ServerApi, - }, - cmap::Connection, + client::auth::GSSAPI_STR, error::{Error, Result}, }; -pub(super) async fn authenticate_stream( - conn: &mut Connection, - server_api: Option<&ServerApi>, - user_principal: Option, - service_principal: String, - source: &str, -) -> Result<()> { - let (mut authenticator, initial_token) = - GssapiAuthenticator::init(user_principal, service_principal).await?; - - let command = SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - initial_token, - server_api.cloned(), - ) - .into_command()?; - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - let mut conversation_id = Some(sasl_response.conversation_id); - let mut payload = sasl_response.payload; - - // Limit number of auth challenge steps (typically, only one step is needed, however - // different configurations may require more). - for _ in 0..10 { - let challenge = payload.as_slice(); - let output_token = authenticator.step(challenge).await?; - - // The step may return None, which is a valid final step. We still need to - // send a saslContinue command, so we send an empty payload if there is no - // token. - let token = output_token.unwrap_or(vec![]); - let command = SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - conversation_id = Some(sasl_response.conversation_id); - payload = sasl_response.payload; - - // Although unlikely, there are cases where authentication can be done - // at this point. - if sasl_response.done { - return Ok(()); - } - - // The authenticator is considered "complete" when the Kerberos auth - // process is done. However, this is not the end of the full auth flow. - // We no longer need to issue challenges to the authenticator, so we - // break the loop and continue with the rest of the flow. - if authenticator.is_complete() { - break; - } - } - - let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; - let command = SaslContinue::new( - source.to_string(), - conversation_id.unwrap(), - output_token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - if sasl_response.done { - Ok(()) - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "GSSAPI authentication failed after 10 attempts", - )) - } -} - -struct GssapiAuthenticator { +pub(super) struct GssapiAuthenticator { pending_ctx: Option, established_ctx: Option, user_principal: Option, @@ -110,7 +15,7 @@ struct GssapiAuthenticator { impl GssapiAuthenticator { // Initialize the GssapiAuthenticator by creating a PendingClientCtx and // getting an initial token to send to the server. - async fn init( + pub(super) fn init( user_principal: Option, service_principal: String, ) -> Result<(Self, Vec)> { @@ -142,7 +47,7 @@ impl GssapiAuthenticator { // is established, an optional final token that must be sent to the server // may be returned; otherwise another token to pass to the server is // returned and the client context remains in the pending state. - async fn step(&mut self, challenge: &[u8]) -> Result>> { + pub(super) fn step(&mut self, challenge: &[u8]) -> Result>> { if challenge.is_empty() { Err(Error::authentication_error( GSSAPI_STR, @@ -173,7 +78,7 @@ impl GssapiAuthenticator { // Perform the final step of Kerberos authentication by gss_unwrap-ing the // final server challenge, then wrapping the protocol bytes + user principal. // The resulting token must be sent to the server. - fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { + pub(super) fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { if let Some(mut established_ctx) = self.established_ctx.take() { let _ = established_ctx.unwrap(payload).map_err(|e| { Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) @@ -200,7 +105,7 @@ impl GssapiAuthenticator { } } - fn is_complete(&self) -> bool { + pub(super) fn is_complete(&self) -> bool { self.is_complete } } diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 751d584ba..c992b7682 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -31,98 +31,11 @@ use windows_sys::Win32::{ }; use crate::{ - client::{ - auth::{ - sasl::{SaslContinue, SaslResponse, SaslStart}, - Credential, - GSSAPI_STR, - }, - options::ServerApi, - }, - cmap::Connection, + client::auth::GSSAPI_STR, error::{Error, Result}, }; -pub(super) async fn authenticate_stream( - conn: &mut Connection, - credential: &Credential, - server_api: Option<&ServerApi>, - service_principal: String, - source: &str, -) -> Result<()> { - let user_principal = credential.username.clone(); - let password = credential.password.clone(); - let (mut authenticator, initial_token) = - SspiAuthenticator::init(user_principal, password, service_principal).await?; - - let command = SaslStart::new( - source.to_string(), - crate::client::auth::AuthMechanism::Gssapi, - initial_token, - server_api.cloned(), - ) - .into_command()?; - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - let mut conversation_id = Some(sasl_response.conversation_id); - let mut payload = sasl_response.payload; - - for _ in 0..10 { - let challenge = payload.as_slice(); - let output_token = authenticator.step(challenge).await?; - - let token = output_token.unwrap_or(vec![]); - let command = SaslContinue::new( - source.to_string(), - conversation_id.clone().unwrap(), - token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - conversation_id = Some(sasl_response.conversation_id); - payload = sasl_response.payload; - - if sasl_response.done { - return Ok(()); - } - - if authenticator.is_complete() { - break; - } - } - - let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; - let command = SaslContinue::new( - source.to_string(), - conversation_id.unwrap(), - output_token, - server_api.cloned(), - ) - .into_command(); - - let response_doc = conn.send_message(command).await?; - let sasl_response = - SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; - - if sasl_response.done { - Ok(()) - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "GSSAPI authentication failed after 10 attempts", - )) - } -} - -struct SspiAuthenticator { +pub(super) struct SspiAuthenticator { cred_handle: Option, ctx_handle: Option, have_cred: bool, @@ -133,7 +46,7 @@ struct SspiAuthenticator { } impl SspiAuthenticator { - async fn init( + pub(super) fn init( user_principal: Option, password: Option, service_principal: String, @@ -156,13 +69,11 @@ impl SspiAuthenticator { user_plus_realm, }; - let initial_token = authenticator - .acquire_credentials_and_init(user_principal, password) - .await?; + let initial_token = authenticator.acquire_credentials_and_init(user_principal, password)?; Ok((authenticator, initial_token)) } - async fn acquire_credentials_and_init( + fn acquire_credentials_and_init( &mut self, user_principal: Option, password: Option, @@ -231,7 +142,7 @@ impl SspiAuthenticator { } } - async fn step(&mut self, challenge: &[u8]) -> Result>> { + pub(super) fn step(&mut self, challenge: &[u8]) -> Result>> { if self.auth_complete { return Ok(None); } @@ -331,7 +242,7 @@ impl SspiAuthenticator { } } - fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { + pub(super) fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { unsafe { let mut message = payload.to_vec(); @@ -480,7 +391,7 @@ impl SspiAuthenticator { } } - fn is_complete(&self) -> bool { + pub(super) fn is_complete(&self) -> bool { self.auth_complete } } From 56661ab05bd8ee992d2414c1fdd1f226fe998077 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 18:10:08 -0400 Subject: [PATCH 08/15] RUST-2245: Standardize requirement of user_principal and use better names --- src/client/auth/gssapi/nix.rs | 26 ++++++------ src/client/auth/gssapi/windows.rs | 69 +++++++++++-------------------- 2 files changed, 35 insertions(+), 60 deletions(-) diff --git a/src/client/auth/gssapi/nix.rs b/src/client/auth/gssapi/nix.rs index 4cb1f9d59..12f19d6c7 100644 --- a/src/client/auth/gssapi/nix.rs +++ b/src/client/auth/gssapi/nix.rs @@ -8,7 +8,7 @@ use crate::{ pub(super) struct GssapiAuthenticator { pending_ctx: Option, established_ctx: Option, - user_principal: Option, + user_principal: String, is_complete: bool, } @@ -32,6 +32,11 @@ impl GssapiAuthenticator { ) })?; + let user_principal = user_principal.ok_or_else(Error::authentication_error( + GSSAPI_STR, + "User principal not specified", + ))?; + Ok(( Self { pending_ctx: Some(pending_ctx), @@ -84,19 +89,12 @@ impl GssapiAuthenticator { Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) })?; - if let Some(user_principal) = self.user_principal.take() { - let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; - let bytes = [bytes, user_principal.as_bytes()].concat(); - let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { - Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) - })?; - Ok(output_token.to_vec()) - } else { - Err(Error::authentication_error( - GSSAPI_STR, - "User principal not specified", - )) - } + let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; + let bytes = [bytes, self.user_principal.as_bytes()].concat(); + let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) + })?; + Ok(output_token.to_vec()) } else { Err(Error::authentication_error( GSSAPI_STR, diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index c992b7682..27cc7111c 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -3,26 +3,11 @@ use windows_sys::Win32::{ Foundation::{SEC_E_OK, SEC_I_CONTINUE_NEEDED}, Security::{ Authentication::Identity::{ - AcquireCredentialsHandleW, - DecryptMessage, - DeleteSecurityContext, - EncryptMessage, - FreeCredentialsHandle, - InitializeSecurityContextW, - QueryContextAttributesW, - SecBuffer, - SecBufferDesc, - SecPkgContext_Sizes, - ISC_REQ_ALLOCATE_MEMORY, - ISC_REQ_MUTUAL_AUTH, - SECBUFFER_DATA, - SECBUFFER_PADDING, - SECBUFFER_STREAM, - SECBUFFER_TOKEN, - SECBUFFER_VERSION, - SECPKG_ATTR_SIZES, - SECPKG_CRED_OUTBOUND, - SECQOP_WRAP_NO_ENCRYPT, + AcquireCredentialsHandleW, DecryptMessage, DeleteSecurityContext, EncryptMessage, + FreeCredentialsHandle, InitializeSecurityContextW, QueryContextAttributesW, SecBuffer, + SecBufferDesc, SecPkgContext_Sizes, ISC_REQ_ALLOCATE_MEMORY, ISC_REQ_MUTUAL_AUTH, + SECBUFFER_DATA, SECBUFFER_PADDING, SECBUFFER_STREAM, SECBUFFER_TOKEN, + SECBUFFER_VERSION, SECPKG_ATTR_SIZES, SECPKG_CRED_OUTBOUND, SECQOP_WRAP_NO_ENCRYPT, SECURITY_NETWORK_DREP, }, Credentials::SecHandle, @@ -38,11 +23,9 @@ use crate::{ pub(super) struct SspiAuthenticator { cred_handle: Option, ctx_handle: Option, - have_cred: bool, - have_context: bool, auth_complete: bool, - name_token: Vec, - user_plus_realm: String, + service_principal: Vec, + user_principal: String, } impl SspiAuthenticator { @@ -51,10 +34,11 @@ impl SspiAuthenticator { password: Option, service_principal: String, ) -> Result<(Self, Vec)> { - let user_plus_realm = user_principal.clone().unwrap_or_default(); + let user_principal = user_principal.ok_or_else(|| { + Error::authentication_error(GSSAPI_STR, "User principal not specified") + })?; - let name_token_string = service_principal.clone(); - let name_token: Vec = name_token_string + let service_principal: Vec = service_principal .encode_utf16() .chain(std::iter::once(0)) .collect(); @@ -62,14 +46,13 @@ impl SspiAuthenticator { let mut authenticator = Self { cred_handle: None, ctx_handle: None, - have_cred: false, - have_context: false, auth_complete: false, - name_token, - user_plus_realm, + service_principal, + user_principal: user_principal.clone(), }; - let initial_token = authenticator.acquire_credentials_and_init(user_principal, password)?; + let initial_token = + authenticator.acquire_credentials_and_init(Some(user_principal), password)?; Ok((authenticator, initial_token)) } @@ -135,7 +118,6 @@ impl SspiAuthenticator { } self.cred_handle = Some(cred_handle); - self.have_cred = true; let initial_token = self.initialize_security_context(&[])?; Ok(initial_token) @@ -153,11 +135,7 @@ impl SspiAuthenticator { fn initialize_security_context(&mut self, input_token: &[u8]) -> Result> { unsafe { - let mut ctx_handle = if self.have_context { - self.ctx_handle.unwrap() - } else { - SecHandle::default() - }; + let mut ctx_handle = self.ctx_handle.unwrap_or_default(); let mut input_buffer = SecBuffer { cbBuffer: input_token.len() as u32, @@ -191,16 +169,16 @@ impl SspiAuthenticator { let result = InitializeSecurityContextW( &self.cred_handle.unwrap(), - if self.have_context { + if self.ctx_handle.is_some() { &ctx_handle } else { ptr::null() }, - self.name_token.as_ptr(), + self.service_principal.as_ptr(), ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MUTUAL_AUTH, 0, SECURITY_NETWORK_DREP, - if self.have_context { + if self.ctx_handle.is_some() { &input_buffer_desc } else { ptr::null() @@ -213,7 +191,6 @@ impl SspiAuthenticator { ); self.ctx_handle = Some(ctx_handle); - self.have_context = true; match result { SEC_E_OK => { @@ -307,8 +284,8 @@ impl SspiAuthenticator { )); } - let user_plus_realm = &self.user_plus_realm; - let plaintext_message_size = 4 + user_plus_realm.len(); + let user_principal = &self.user_principal; + let plaintext_message_size = 4 + user_principal.len(); let total_size = sizes.cbSecurityTrailer as usize + plaintext_message_size + sizes.cbBlockSize as usize; @@ -319,8 +296,8 @@ impl SspiAuthenticator { message_buf[plaintext_start + 1] = 0; message_buf[plaintext_start + 2] = 0; message_buf[plaintext_start + 3] = 0; - message_buf[plaintext_start + 4..plaintext_start + 4 + user_plus_realm.len()] - .copy_from_slice(user_plus_realm.as_bytes()); + message_buf[plaintext_start + 4..plaintext_start + 4 + user_principal.len()] + .copy_from_slice(user_principal.as_bytes()); let mut encrypt_bufs = [ SecBuffer { From a32a408c7c35ddad721897094c3dc8d6dbc44bf1 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 18:35:58 -0400 Subject: [PATCH 09/15] RUST-2245: More clean up, refactoring, and renaming --- src/client/auth/gssapi/windows.rs | 50 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 27cc7111c..8dae867ba 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -29,6 +29,8 @@ pub(super) struct SspiAuthenticator { } impl SspiAuthenticator { + // Initialize the SspiAuthenticator by acquiring a credentials handle and + // making the first call to InitializeSecurityContext. pub(super) fn init( user_principal: Option, password: Option, @@ -51,16 +53,11 @@ impl SspiAuthenticator { user_principal: user_principal.clone(), }; - let initial_token = - authenticator.acquire_credentials_and_init(Some(user_principal), password)?; + let initial_token = authenticator.acquire_credentials_and_init(password)?; Ok((authenticator, initial_token)) } - fn acquire_credentials_and_init( - &mut self, - user_principal: Option, - password: Option, - ) -> Result> { + fn acquire_credentials_and_init(&mut self, password: Option) -> Result> { unsafe { let mut cred_handle = SecHandle::default(); let mut expiry: i64 = 0; @@ -68,31 +65,32 @@ impl SspiAuthenticator { let mut auth_identity = SEC_WINNT_AUTH_IDENTITY_W::default(); auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + // Note that SSPI uses the term "domain" instead of + // "realm" in this context. let username_wide: Vec; let domain_wide: Vec; let password_wide: Vec; - if let Some(user_principal) = &user_principal { - if let Some(at_pos) = user_principal.find('@') { - let username = &user_principal[..at_pos]; - let domain = &user_principal[at_pos + 1..]; + if let Some(at_pos) = self.user_principal.find('@') { + let username = &self.user_principal[..at_pos]; + let domain = &self.user_principal[at_pos + 1..]; - username_wide = username.encode_utf16().chain(std::iter::once(0)).collect(); - domain_wide = domain.encode_utf16().chain(std::iter::once(0)).collect(); + username_wide = username.encode_utf16().chain(std::iter::once(0)).collect(); + domain_wide = domain.encode_utf16().chain(std::iter::once(0)).collect(); - auth_identity.User = username_wide.as_ptr() as *mut u16; - auth_identity.UserLength = username.len() as u32; - auth_identity.Domain = domain_wide.as_ptr() as *mut u16; - auth_identity.DomainLength = domain.len() as u32; + auth_identity.User = username_wide.as_ptr() as *mut u16; + auth_identity.UserLength = username.len() as u32; + auth_identity.Domain = domain_wide.as_ptr() as *mut u16; + auth_identity.DomainLength = domain.len() as u32; - if let Some(password) = &password { - password_wide = password.encode_utf16().chain(std::iter::once(0)).collect(); - auth_identity.Password = password_wide.as_ptr() as *mut u16; - auth_identity.PasswordLength = password.len() as u32; - } + if let Some(password) = &password { + password_wide = password.encode_utf16().chain(std::iter::once(0)).collect(); + auth_identity.Password = password_wide.as_ptr() as *mut u16; + auth_identity.PasswordLength = password.len() as u32; } } + // Security package name let package_name: Vec = "kerberos\0".encode_utf16().collect(); let result = AcquireCredentialsHandleW( std::ptr::null(), @@ -124,6 +122,8 @@ impl SspiAuthenticator { } } + // Issue the server provided token to the context handle. If auth is complete, + // no token is returned; otherwise, return the next token to send to the server. pub(super) fn step(&mut self, challenge: &[u8]) -> Result>> { if self.auth_complete { return Ok(None); @@ -219,6 +219,12 @@ impl SspiAuthenticator { } } + // Perform the final step of Kerberos authentication by decrypting the + // final server challenge, then encrypting the protocol bytes + user + // principal. The resulting token must be sent to the server. + // For consistency with nix/GSSAPI, we use the terminology "unwrap" and + // "wrap" here, even though in the context of SSPI it is "decrypt" and + // "encrypt" (as seen in the implementation of this method). pub(super) fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { unsafe { let mut message = payload.to_vec(); From 6bb92279eb426ce779a3c8cc2f5364ef03d0815c Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Tue, 5 Aug 2025 18:37:33 -0400 Subject: [PATCH 10/15] RUST-2245: Static analysis fixes --- src/client/auth/gssapi/nix.rs | 7 +++---- src/client/auth/gssapi/windows.rs | 25 ++++++++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/client/auth/gssapi/nix.rs b/src/client/auth/gssapi/nix.rs index 12f19d6c7..ec8861ed9 100644 --- a/src/client/auth/gssapi/nix.rs +++ b/src/client/auth/gssapi/nix.rs @@ -32,10 +32,9 @@ impl GssapiAuthenticator { ) })?; - let user_principal = user_principal.ok_or_else(Error::authentication_error( - GSSAPI_STR, - "User principal not specified", - ))?; + let user_principal = user_principal.ok_or_else(|| { + Error::authentication_error(GSSAPI_STR, "User principal not specified") + })?; Ok(( Self { diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 8dae867ba..379d4886b 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -3,11 +3,26 @@ use windows_sys::Win32::{ Foundation::{SEC_E_OK, SEC_I_CONTINUE_NEEDED}, Security::{ Authentication::Identity::{ - AcquireCredentialsHandleW, DecryptMessage, DeleteSecurityContext, EncryptMessage, - FreeCredentialsHandle, InitializeSecurityContextW, QueryContextAttributesW, SecBuffer, - SecBufferDesc, SecPkgContext_Sizes, ISC_REQ_ALLOCATE_MEMORY, ISC_REQ_MUTUAL_AUTH, - SECBUFFER_DATA, SECBUFFER_PADDING, SECBUFFER_STREAM, SECBUFFER_TOKEN, - SECBUFFER_VERSION, SECPKG_ATTR_SIZES, SECPKG_CRED_OUTBOUND, SECQOP_WRAP_NO_ENCRYPT, + AcquireCredentialsHandleW, + DecryptMessage, + DeleteSecurityContext, + EncryptMessage, + FreeCredentialsHandle, + InitializeSecurityContextW, + QueryContextAttributesW, + SecBuffer, + SecBufferDesc, + SecPkgContext_Sizes, + ISC_REQ_ALLOCATE_MEMORY, + ISC_REQ_MUTUAL_AUTH, + SECBUFFER_DATA, + SECBUFFER_PADDING, + SECBUFFER_STREAM, + SECBUFFER_TOKEN, + SECBUFFER_VERSION, + SECPKG_ATTR_SIZES, + SECPKG_CRED_OUTBOUND, + SECQOP_WRAP_NO_ENCRYPT, SECURITY_NETWORK_DREP, }, Credentials::SecHandle, From 6654548676284167e5c9c68ef67fefe5afc3614e Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 6 Aug 2025 14:07:07 -0400 Subject: [PATCH 11/15] RUST-2245: Update e2e tests to handle windows --- .evergreen/config.yml | 8 ++++ .evergreen/run-gssapi-tests.sh | 74 ++++++++++++++++++---------------- src/test/auth/gssapi.rs | 28 ++++++++++++- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index c7a03538c..b7a4f1734 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -274,6 +274,14 @@ buildvariants: tasks: - test-gssapi-auth + - name: gssapi-auth-windows + display_name: "GSSAPI Authentication - Windows" + patchable: true + run_on: + - windows-64-vsMulti-small + tasks: + - test-gssapi-auth + - name: x509-auth display_name: "x509 Authentication" patchable: false diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh index 763272de7..b53c6719f 100644 --- a/.evergreen/run-gssapi-tests.sh +++ b/.evergreen/run-gssapi-tests.sh @@ -16,50 +16,56 @@ FEATURE_FLAGS+=("gssapi-auth") set +o errexit -# Create a krb5 config file with relevant -touch krb5.conf -echo "[realms] - $SASL_REALM = { - kdc = $SASL_HOST - admin_server = $SASL_HOST - } - - $SASL_REALM_CROSS = { - kdc = $SASL_HOST - admin_server = $SASL_HOST - } - -[domain_realm] - .$SASL_DOMAIN = $SASL_REALM - $SASL_DOMAIN = $SASL_REALM -" > krb5.conf - -export KRB5_CONFIG=krb5.conf - -# Authenticate the user principal in the KDC before running the e2e test -echo "Authenticating $PRINCIPAL" -echo "$SASL_PASS" | kinit -p $PRINCIPAL -klist +if [ "Windows_NT" != "$OS" ]; then + # Create a krb5 config file with relevant + touch krb5.conf + echo "[realms] + $SASL_REALM = { + kdc = $SASL_HOST + admin_server = $SASL_HOST + } + + $SASL_REALM_CROSS = { + kdc = $SASL_HOST + admin_server = $SASL_HOST + } + + [domain_realm] + .$SASL_DOMAIN = $SASL_REALM + $SASL_DOMAIN = $SASL_REALM + " > krb5.conf + + export KRB5_CONFIG=krb5.conf + + # Authenticate the user principal in the KDC before running the e2e test + echo "Authenticating $PRINCIPAL" + echo "$SASL_PASS" | kinit -p $PRINCIPAL + klist +fi # Run end-to-end auth tests for "$PRINCIPAL" user TEST_OPTIONS+=("--skip with_service_realm_and_host_options") cargo_test test::auth::gssapi_skip_local -# Unauthenticate -echo "Unauthenticating $PRINCIPAL" -kdestroy +if [ "Windows_NT" != "$OS" ]; then + # Unauthenticate + echo "Unauthenticating $PRINCIPAL" + kdestroy -# Authenticate the alternative user principal in the KDC and run other e2e test -echo "Authenticating $PRINCIPAL_CROSS" -echo "$SASL_PASS_CROSS" | kinit -p $PRINCIPAL_CROSS -klist + # Authenticate the alternative user principal in the KDC and run other e2e test + echo "Authenticating $PRINCIPAL_CROSS" + echo "$SASL_PASS_CROSS" | kinit -p $PRINCIPAL_CROSS + klist +fi TEST_OPTIONS=() cargo_test test::auth::gssapi_skip_local::with_service_realm_and_host_options -# Unauthenticate -echo "Unauthenticating $PRINCIPAL_CROSS" -kdestroy +if [ "Windows_NT" != "$OS" ]; then + # Unauthenticate + echo "Unauthenticating $PRINCIPAL_CROSS" + kdestroy +fi # Run remaining tests cargo_test spec::auth diff --git a/src/test/auth/gssapi.rs b/src/test/auth/gssapi.rs index 45154d8b2..a00df0efc 100644 --- a/src/test/auth/gssapi.rs +++ b/src/test/auth/gssapi.rs @@ -9,6 +9,7 @@ use crate::{ /// - auth_mechanism_properties is an optional set of authMechanismProperties to append to the uri async fn run_gssapi_auth_test( user_principal_var: &str, + #[cfg(target_os = "windows")] password_var: &str, gssapi_db_var: &str, auth_mechanism_properties: Option<&str>, ) { @@ -28,9 +29,19 @@ async fn run_gssapi_auth_test( }; // Create client + #[cfg(not(target_os = "windows"))] let uri = format!( "mongodb://{user_principal}@{host}/?authSource=%24external&authMechanism=GSSAPI{props}" ); + #[cfg(target_os = "windows")] + let uri = { + let password = + std::env::var(password_var).unwrap_or_else(|_| panic!("{password_var} not set")); + format!( + "mongodb://{user_principal}:{password}@{host}/?authSource=%24external&\ + authMechanism=GSSAPI{props}" + ) + }; let client = Client::with_uri_str(uri) .await .expect("failed to create MongoDB Client"); @@ -57,13 +68,22 @@ async fn run_gssapi_auth_test( #[tokio::test] async fn no_options() { - run_gssapi_auth_test("PRINCIPAL", "GSSAPI_DB", None).await + run_gssapi_auth_test( + "PRINCIPAL", + #[cfg(target_os = "windows")] + "SASL_PASS", + "GSSAPI_DB", + None, + ) + .await } #[tokio::test] async fn explicit_canonicalize_host_name_false() { run_gssapi_auth_test( "PRINCIPAL", + #[cfg(target_os = "windows")] + "SASL_PASS", "GSSAPI_DB", Some("CANONICALIZE_HOST_NAME:false"), ) @@ -74,6 +94,8 @@ async fn explicit_canonicalize_host_name_false() { async fn canonicalize_host_name_forward() { run_gssapi_auth_test( "PRINCIPAL", + #[cfg(target_os = "windows")] + "SASL_PASS", "GSSAPI_DB", Some("CANONICALIZE_HOST_NAME:forward"), ) @@ -84,6 +106,8 @@ async fn canonicalize_host_name_forward() { async fn canonicalize_host_name_forward_and_reverse() { run_gssapi_auth_test( "PRINCIPAL", + #[cfg(target_os = "windows")] + "SASL_PASS", "GSSAPI_DB", Some("CANONICALIZE_HOST_NAME:forwardAndReverse"), ) @@ -100,6 +124,8 @@ async fn with_service_realm_and_host_options() { run_gssapi_auth_test( "PRINCIPAL_CROSS", + #[cfg(target_os = "windows")] + "SASL_PASS_CROSS", "GSSAPI_DB_CROSS", Some(format!("SERVICE_REALM:{service_realm},SERVICE_HOST:{service_host}").as_str()), ) From 9a3ffa859d9e0ebd74e2a5d221c40732220fdf6c Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 6 Aug 2025 15:22:32 -0400 Subject: [PATCH 12/15] RUST-2245: Fix evergreen tasks --- .evergreen/config.yml | 4 ++-- .evergreen/run-gssapi-tests.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index b7a4f1734..85dd6b1b4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -278,7 +278,7 @@ buildvariants: display_name: "GSSAPI Authentication - Windows" patchable: true run_on: - - windows-64-vsMulti-small + - windows-64-vs2017-small tasks: - test-gssapi-auth @@ -1413,7 +1413,7 @@ functions: type: test params: binary: bash - working_dir: ${PROJECT_DIRECTORY} + working_dir: src args: - .evergreen/run-gssapi-tests.sh include_expansions_in_env: diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh index b53c6719f..53a7138c8 100644 --- a/.evergreen/run-gssapi-tests.sh +++ b/.evergreen/run-gssapi-tests.sh @@ -16,7 +16,7 @@ FEATURE_FLAGS+=("gssapi-auth") set +o errexit -if [ "Windows_NT" != "$OS" ]; then +if [[ "Windows_NT" != "$OSTYPE" ]]; then # Create a krb5 config file with relevant touch krb5.conf echo "[realms] @@ -47,7 +47,7 @@ fi TEST_OPTIONS+=("--skip with_service_realm_and_host_options") cargo_test test::auth::gssapi_skip_local -if [ "Windows_NT" != "$OS" ]; then +if [[ "Windows_NT" != "$OSTYPE" ]]; then # Unauthenticate echo "Unauthenticating $PRINCIPAL" kdestroy @@ -61,7 +61,7 @@ fi TEST_OPTIONS=() cargo_test test::auth::gssapi_skip_local::with_service_realm_and_host_options -if [ "Windows_NT" != "$OS" ]; then +if [[ "Windows_NT" != "$OSTYPE" ]]; then # Unauthenticate echo "Unauthenticating $PRINCIPAL_CROSS" kdestroy From 726e9f89356018ba991974d6cac4868fcfb229b0 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Wed, 6 Aug 2025 15:46:06 -0400 Subject: [PATCH 13/15] RUST-2245: Add more comments --- .evergreen/run-gssapi-tests.sh | 4 ++++ src/client/auth/gssapi/mod.rs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh index 53a7138c8..1ba01e7d7 100644 --- a/.evergreen/run-gssapi-tests.sh +++ b/.evergreen/run-gssapi-tests.sh @@ -16,6 +16,10 @@ FEATURE_FLAGS+=("gssapi-auth") set +o errexit +# On Windows, `kinit`/`kdestroy` and other krb5 config settings are +# not available, nor are they required steps. Windows uses SSPI which +# is similar to but distinct from (KRB5) GSSAPI. Therefore, we only +# run the following steps if we are not on Windows. if [[ "Windows_NT" != "$OSTYPE" ]]; then # Create a krb5 config file with relevant touch krb5.conf diff --git a/src/client/auth/gssapi/mod.rs b/src/client/auth/gssapi/mod.rs index 539626486..7dd8fcb51 100644 --- a/src/client/auth/gssapi/mod.rs +++ b/src/client/auth/gssapi/mod.rs @@ -25,7 +25,7 @@ const SERVICE_REALM: &str = "SERVICE_REALM"; const SERVICE_HOST: &str = "SERVICE_HOST"; #[derive(Debug, Clone)] -pub(crate) struct GssapiProperties { +struct GssapiProperties { pub service_name: String, pub canonicalize_host_name: CanonicalizeHostName, pub service_realm: Option, @@ -33,7 +33,7 @@ pub(crate) struct GssapiProperties { } #[derive(Debug, Default, Clone, PartialEq)] -pub(crate) enum CanonicalizeHostName { +enum CanonicalizeHostName { #[default] None, Forward, @@ -223,7 +223,7 @@ impl GssapiProperties { } } -pub(crate) async fn canonicalize_hostname( +async fn canonicalize_hostname( hostname: &str, mode: &CanonicalizeHostName, resolver_config: Option<&ResolverConfig>, From 51c98bc453aa2761f86aee1fe475e74f70e27362 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Fri, 8 Aug 2025 15:52:21 -0400 Subject: [PATCH 14/15] RUST-2245: Reduce unsafe to smaller scopes, add nosemgrep comments --- src/client/auth/gssapi/windows.rs | 227 ++++++++++++++++-------------- 1 file changed, 121 insertions(+), 106 deletions(-) diff --git a/src/client/auth/gssapi/windows.rs b/src/client/auth/gssapi/windows.rs index 379d4886b..040b981d4 100644 --- a/src/client/auth/gssapi/windows.rs +++ b/src/client/auth/gssapi/windows.rs @@ -73,40 +73,42 @@ impl SspiAuthenticator { } fn acquire_credentials_and_init(&mut self, password: Option) -> Result> { - unsafe { - let mut cred_handle = SecHandle::default(); - let mut expiry: i64 = 0; - - let mut auth_identity = SEC_WINNT_AUTH_IDENTITY_W::default(); - auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; - - // Note that SSPI uses the term "domain" instead of - // "realm" in this context. - let username_wide: Vec; - let domain_wide: Vec; - let password_wide: Vec; - - if let Some(at_pos) = self.user_principal.find('@') { - let username = &self.user_principal[..at_pos]; - let domain = &self.user_principal[at_pos + 1..]; - - username_wide = username.encode_utf16().chain(std::iter::once(0)).collect(); - domain_wide = domain.encode_utf16().chain(std::iter::once(0)).collect(); - - auth_identity.User = username_wide.as_ptr() as *mut u16; - auth_identity.UserLength = username.len() as u32; - auth_identity.Domain = domain_wide.as_ptr() as *mut u16; - auth_identity.DomainLength = domain.len() as u32; - - if let Some(password) = &password { - password_wide = password.encode_utf16().chain(std::iter::once(0)).collect(); - auth_identity.Password = password_wide.as_ptr() as *mut u16; - auth_identity.PasswordLength = password.len() as u32; - } + let mut cred_handle = SecHandle::default(); + let mut expiry: i64 = 0; + + let mut auth_identity = SEC_WINNT_AUTH_IDENTITY_W::default(); + auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + // Note that SSPI uses the term "domain" instead of + // "realm" in this context. + let username_wide: Vec; + let domain_wide: Vec; + let password_wide: Vec; + + if let Some(at_pos) = self.user_principal.find('@') { + let username = &self.user_principal[..at_pos]; + let domain = &self.user_principal[at_pos + 1..]; + + username_wide = username.encode_utf16().chain(std::iter::once(0)).collect(); + domain_wide = domain.encode_utf16().chain(std::iter::once(0)).collect(); + + auth_identity.User = username_wide.as_ptr() as *mut u16; + auth_identity.UserLength = username.len() as u32; + auth_identity.Domain = domain_wide.as_ptr() as *mut u16; + auth_identity.DomainLength = domain.len() as u32; + + if let Some(password) = &password { + password_wide = password.encode_utf16().chain(std::iter::once(0)).collect(); + auth_identity.Password = password_wide.as_ptr() as *mut u16; + auth_identity.PasswordLength = password.len() as u32; } + } + + // Security package name + let package_name: Vec = "kerberos\0".encode_utf16().collect(); - // Security package name - let package_name: Vec = "kerberos\0".encode_utf16().collect(); + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let result = AcquireCredentialsHandleW( std::ptr::null(), package_name.as_ptr(), @@ -129,12 +131,12 @@ impl SspiAuthenticator { &format!("Failed to acquire credentials handle: {:?}", result), )); } + } - self.cred_handle = Some(cred_handle); + self.cred_handle = Some(cred_handle); - let initial_token = self.initialize_security_context(&[])?; - Ok(initial_token) - } + let initial_token = self.initialize_security_context(&[])?; + Ok(initial_token) } // Issue the server provided token to the context handle. If auth is complete, @@ -149,39 +151,40 @@ impl SspiAuthenticator { } fn initialize_security_context(&mut self, input_token: &[u8]) -> Result> { - unsafe { - let mut ctx_handle = self.ctx_handle.unwrap_or_default(); + let mut ctx_handle = self.ctx_handle.unwrap_or_default(); - let mut input_buffer = SecBuffer { - cbBuffer: input_token.len() as u32, - BufferType: SECBUFFER_TOKEN, - pvBuffer: if input_token.is_empty() { - ptr::null_mut() - } else { - input_token.as_ptr() as *mut _ - }, - }; + let mut input_buffer = SecBuffer { + cbBuffer: input_token.len() as u32, + BufferType: SECBUFFER_TOKEN, + pvBuffer: if input_token.is_empty() { + ptr::null_mut() + } else { + input_token.as_ptr() as *mut _ + }, + }; - let input_buffer_desc = SecBufferDesc { - ulVersion: SECBUFFER_VERSION, - cBuffers: 1, - pBuffers: &mut input_buffer, - }; + let input_buffer_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 1, + pBuffers: &mut input_buffer, + }; - let mut output_buffer = SecBuffer { - cbBuffer: 0, - BufferType: SECBUFFER_TOKEN, - pvBuffer: ptr::null_mut(), - }; + let mut output_buffer = SecBuffer { + cbBuffer: 0, + BufferType: SECBUFFER_TOKEN, + pvBuffer: ptr::null_mut(), + }; - let mut output_buffer_desc = SecBufferDesc { - ulVersion: SECBUFFER_VERSION, - cBuffers: 1, - pBuffers: &mut output_buffer, - }; + let mut output_buffer_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 1, + pBuffers: &mut output_buffer, + }; - let mut context_attr = 0u32; + let mut context_attr = 0u32; + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let result = InitializeSecurityContextW( &self.cred_handle.unwrap(), if self.ctx_handle.is_some() { @@ -241,28 +244,29 @@ impl SspiAuthenticator { // "wrap" here, even though in the context of SSPI it is "decrypt" and // "encrypt" (as seen in the implementation of this method). pub(super) fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { - unsafe { - let mut message = payload.to_vec(); - - let mut wrap_bufs = [ - SecBuffer { - cbBuffer: message.len() as u32, - BufferType: SECBUFFER_STREAM, - pvBuffer: message.as_mut_ptr() as *mut _, - }, - SecBuffer { - cbBuffer: 0, - BufferType: SECBUFFER_DATA, - pvBuffer: ptr::null_mut(), - }, - ]; + let mut message = payload.to_vec(); + + let mut wrap_bufs = [ + SecBuffer { + cbBuffer: message.len() as u32, + BufferType: SECBUFFER_STREAM, + pvBuffer: message.as_mut_ptr() as *mut _, + }, + SecBuffer { + cbBuffer: 0, + BufferType: SECBUFFER_DATA, + pvBuffer: ptr::null_mut(), + }, + ]; - let mut wrap_buf_desc = SecBufferDesc { - ulVersion: SECBUFFER_VERSION, - cBuffers: 2, - pBuffers: wrap_bufs.as_mut_ptr(), - }; + let mut wrap_buf_desc = SecBufferDesc { + ulVersion: SECBUFFER_VERSION, + cBuffers: 2, + pBuffers: wrap_bufs.as_mut_ptr(), + }; + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let result = DecryptMessage( &self.ctx_handle.unwrap(), &mut wrap_buf_desc, @@ -275,15 +279,19 @@ impl SspiAuthenticator { &format!("DecryptMessage failed: {:?}", result), )); } + } - if wrap_bufs[1].cbBuffer < 4 { - return Err(Error::authentication_error( - GSSAPI_STR, - "Server message is too short", - )); - } + if wrap_bufs[1].cbBuffer < 4 { + return Err(Error::authentication_error( + GSSAPI_STR, + "Server message is too short", + )); + } + + let data_ptr = wrap_bufs[1].pvBuffer as *const u8; - let data_ptr = wrap_bufs[1].pvBuffer as *const u8; + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let first_byte = *data_ptr; if (first_byte & 1) == 0 { return Err(Error::authentication_error( @@ -291,8 +299,12 @@ impl SspiAuthenticator { "Server does not support the required security layer", )); } + } + + let mut sizes = SecPkgContext_Sizes::default(); - let mut sizes = SecPkgContext_Sizes::default(); + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let result = QueryContextAttributesW( &self.ctx_handle.unwrap(), SECPKG_ATTR_SIZES, @@ -304,22 +316,24 @@ impl SspiAuthenticator { &format!("QueryContextAttributes failed: {:?}", result), )); } + } - let user_principal = &self.user_principal; - let plaintext_message_size = 4 + user_principal.len(); - let total_size = sizes.cbSecurityTrailer as usize - + plaintext_message_size - + sizes.cbBlockSize as usize; - let mut message_buf = vec![0u8; total_size]; - - let plaintext_start = sizes.cbSecurityTrailer as usize; - message_buf[plaintext_start] = 1; - message_buf[plaintext_start + 1] = 0; - message_buf[plaintext_start + 2] = 0; - message_buf[plaintext_start + 3] = 0; - message_buf[plaintext_start + 4..plaintext_start + 4 + user_principal.len()] - .copy_from_slice(user_principal.as_bytes()); - + let user_principal = &self.user_principal; + let plaintext_message_size = 4 + user_principal.len(); + let total_size = + sizes.cbSecurityTrailer as usize + plaintext_message_size + sizes.cbBlockSize as usize; + let mut message_buf = vec![0u8; total_size]; + + let plaintext_start = sizes.cbSecurityTrailer as usize; + message_buf[plaintext_start] = 1; + message_buf[plaintext_start + 1] = 0; + message_buf[plaintext_start + 2] = 0; + message_buf[plaintext_start + 3] = 0; + message_buf[plaintext_start + 4..plaintext_start + 4 + user_principal.len()] + .copy_from_slice(user_principal.as_bytes()); + + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage + unsafe { let mut encrypt_bufs = [ SecBuffer { cbBuffer: sizes.cbSecurityTrailer, @@ -396,6 +410,7 @@ impl SspiAuthenticator { impl Drop for SspiAuthenticator { fn drop(&mut self) { + // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage unsafe { if let Some(ctx) = &self.ctx_handle { let _ = DeleteSecurityContext(ctx); From 71b3f6870ea3d5daeb795c6b782adafb116c82b0 Mon Sep 17 00:00:00 2001 From: Matthew Chiaravalloti Date: Fri, 8 Aug 2025 16:11:43 -0400 Subject: [PATCH 15/15] RUST-2245: Refactor GssapiAuthenticator --- src/client/auth/gssapi/nix.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/client/auth/gssapi/nix.rs b/src/client/auth/gssapi/nix.rs index ec8861ed9..d9999d169 100644 --- a/src/client/auth/gssapi/nix.rs +++ b/src/client/auth/gssapi/nix.rs @@ -5,11 +5,17 @@ use crate::{ error::{Error, Result}, }; +#[derive(Default)] +enum CtxState { + #[default] + Empty, + Pending(PendingClientCtx), + Established(ClientCtx), +} + pub(super) struct GssapiAuthenticator { - pending_ctx: Option, - established_ctx: Option, + ctx: CtxState, user_principal: String, - is_complete: bool, } impl GssapiAuthenticator { @@ -38,10 +44,8 @@ impl GssapiAuthenticator { Ok(( Self { - pending_ctx: Some(pending_ctx), - established_ctx: None, + ctx: CtxState::Pending(pending_ctx), user_principal, - is_complete: false, }, initial_token.to_vec(), )) @@ -57,17 +61,16 @@ impl GssapiAuthenticator { GSSAPI_STR, "Expected challenge data for GSSAPI continuation", )) - } else if let Some(pending_ctx) = self.pending_ctx.take() { + } else if let CtxState::Pending(pending_ctx) = std::mem::take(&mut self.ctx) { match pending_ctx.step(challenge).map_err(|e| { Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {e}")) })? { Step::Finished((ctx, token)) => { - self.is_complete = true; - self.established_ctx = Some(ctx); + self.ctx = CtxState::Established(ctx); Ok(token.map(|t| t.to_vec())) } Step::Continue((ctx, token)) => { - self.pending_ctx = Some(ctx); + self.ctx = CtxState::Pending(ctx); Ok(Some(token.to_vec())) } } @@ -83,7 +86,7 @@ impl GssapiAuthenticator { // final server challenge, then wrapping the protocol bytes + user principal. // The resulting token must be sent to the server. pub(super) fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { - if let Some(mut established_ctx) = self.established_ctx.take() { + if let CtxState::Established(ref mut established_ctx) = self.ctx { let _ = established_ctx.unwrap(payload).map_err(|e| { Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) })?; @@ -103,6 +106,6 @@ impl GssapiAuthenticator { } pub(super) fn is_complete(&self) -> bool { - self.is_complete + matches!(self.ctx, CtxState::Established(_)) } }