diff --git a/Cargo.lock b/Cargo.lock index 121700cdd..3afd61005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,12 +10,12 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.5.2" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "ac8202ab55fcbf46ca829833f347a82a2a4ce0596f0304ac322c2d100030cd56" dependencies = [ - "crypto-common 0.1.7", - "generic-array", + "crypto-common 0.2.0-rc.4", + "inout 0.2.0-rc.6", ] [[package]] @@ -42,13 +42,13 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.3" +version = "0.11.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "0686ba04dc80c816104c96cd7782b748f6ad58c5dd4ee619ff3258cf68e83d54" dependencies = [ "aead", - "aes 0.8.4", - "cipher 0.4.4", + "aes 0.9.0-rc.1", + "cipher 0.5.0-rc.1", "ctr", "ghash", "subtle", @@ -56,11 +56,12 @@ dependencies = [ [[package]] name = "aes-kw" -version = "0.2.1" +version = "0.3.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +checksum = "02eaa2d54d0fad0116e4b1efb65803ea0bf059ce970a67cd49718d87e807cb51" dependencies = [ - "aes 0.8.4", + "aes 0.9.0-rc.1", + "const-oid 0.10.1", ] [[package]] @@ -159,9 +160,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -169,14 +170,14 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 1.0.69", + "thiserror 2.0.17", ] [[package]] name = "asn1-rs-derive" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", @@ -284,6 +285,15 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -712,6 +722,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" @@ -780,9 +796,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -850,7 +866,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.7", + "crypto-common 0.1.6", "inout 0.1.4", ] @@ -860,6 +876,7 @@ version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e12a13eb01ded5d32ee9658d94f553a19e804204f2dc811df69ab4d9e0cb8c7" dependencies = [ + "block-buffer 0.11.0-rc.5", "crypto-common 0.2.0-rc.4", "inout 0.2.0-rc.6", ] @@ -955,6 +972,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam" version = "0.8.4" @@ -1011,47 +1034,27 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crypto" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1e6e5492f8f0830c37f301f6349e0dac8b2466e4fe89eef90e9eef906cd046" -dependencies = [ - "crypto-common 0.1.7", -] - -[[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-bigint" version = "0.7.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4113edbc9f68c0a64d5b911f803eb245d04bb812680fd56776411f69c670f3e0" dependencies = [ + "hybrid-array", "num-traits", "rand_core 0.9.3", "serdect", "subtle", + "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -1062,6 +1065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" dependencies = [ "hybrid-array", + "rand_core 0.9.3", ] [[package]] @@ -1074,13 +1078,47 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-primes" +version = "0.7.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f2523fbb68811c8710829417ad488086720a6349e337c38d12fa81e09e50bf" +dependencies = [ + "crypto-bigint", + "libm", + "rand_core 0.9.3", +] + +[[package]] +name = "cryptoki" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781357a7779a8e92ea985121bbf379a9adf0777f44ab6392efc6abd5aa9b67db" +dependencies = [ + "bitflags 1.3.2", + "cryptoki-sys", + "libloading", + "log", + "paste", + "secrecy", +] + +[[package]] +name = "cryptoki-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "753e27d860277930ae9f394c119c8c70303236aab0ffab1d51f3d207dbb2bc4b" +dependencies = [ + "libloading", +] + [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "27e41d01c6f73b9330177f5cf782ae5b581b5f2c7840e298e0275ceee5001434" dependencies = [ - "cipher 0.4.4", + "cipher 0.5.0-rc.1", ] [[package]] @@ -1096,14 +1134,14 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.3" +version = "5.0.0-pre.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest 0.11.0-rc.3", "fiat-crypto", "rustc_version", "subtle", @@ -1144,7 +1182,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.111", ] [[package]] @@ -1156,15 +1194,26 @@ dependencies = [ "const-oid 0.9.6", "der_derive", "flagset", - "pem-rfc7468", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d8dd2f26c86b27a2a8ea2767ec7f9df7a89516e4794e54ac01ee618dda3aa4" +dependencies = [ + "const-oid 0.10.1", + "pem-rfc7468 1.0.0-rc.3", "zeroize", ] [[package]] name = "der-parser" -version = "9.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ "asn1-rs", "displaydoc", @@ -1205,15 +1254,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "des" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "des" version = "0.9.0-rc.1" @@ -1241,6 +1281,7 @@ dependencies = [ "expect-test", "futures", "hex", + "http-client-proxy", "ironrdp", "notify-debouncer-mini", "parking_lot", @@ -1249,12 +1290,13 @@ dependencies = [ "rustls-pemfile 2.2.0", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tap", "thiserror 2.0.17", "tokio 1.48.0", "tokio-rustls", "tracing", + "url", "uuid", "win-api-wrappers", "windows 0.61.3", @@ -1304,14 +1346,15 @@ dependencies = [ "hex", "hostname 0.4.2", "http-body-util", + "http-client-proxy", "hyper 1.8.1", "hyper-util", - "ironrdp-acceptor 0.6.0", - "ironrdp-connector 0.6.0", + "ironrdp-acceptor", + "ironrdp-connector", "ironrdp-core", - "ironrdp-pdu 0.5.0", + "ironrdp-pdu", "ironrdp-rdcleanpath", - "ironrdp-tokio 0.6.0", + "ironrdp-tokio", "jmux-proxy", "job-queue", "job-queue-libsql", @@ -1324,7 +1367,7 @@ dependencies = [ "parking_lot", "pcap-file", "picky", - "picky-krb 0.12.0", + "picky-krb", "pin-project-lite 0.2.16", "portpicker", "proptest", @@ -1336,7 +1379,7 @@ dependencies = [ "serde-querystring", "serde_json", "serde_urlencoded", - "sha2", + "sha2 0.10.9", "smol_str", "sysevent", "sysevent-codes", @@ -1431,7 +1474,7 @@ dependencies = [ "serde", "serde_json", "sha1 0.10.6", - "sha2", + "sha2 0.10.9", "tokio 1.48.0", "tokio-postgres", "tower 0.5.2", @@ -1542,8 +1585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.7", + "crypto-common 0.1.6", "subtle", ] @@ -1716,23 +1758,24 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.17.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "b4ab355ec063f7a110eb627471058093aba00eb7f4e70afbd15e696b79d1077b" dependencies = [ - "der", - "digest 0.10.7", + "der 0.8.0-rc.9", + "digest 0.11.0-rc.3", "elliptic-curve", "rfc6979", "signature", - "spki", + "spki 0.8.0-rc.4", + "zeroize", ] [[package]] name = "ed25519" -version = "2.2.3" +version = "3.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "9ef49c0b20c0ad088893ad2a790a29c06a012b3f05bcfc66661fd22a94b32129" dependencies = [ "pkcs8", "signature", @@ -1740,15 +1783,14 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "3.0.0-pre.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", + "rand_core 0.9.3", + "sha2 0.11.0-rc.2", "subtle", "zeroize", ] @@ -1761,20 +1803,21 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.14.0-rc.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "2e3be87c458d756141f3b6ee188828132743bf90c7d14843e2835d6443e5fb03" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", - "digest 0.10.7", + "base16ct 0.3.0", + "crypto-bigint", + "digest 0.11.0-rc.3", "ff", - "generic-array", "group", "hkdf", - "pem-rfc7468", + "hybrid-array", + "once_cell", + "pem-rfc7468 1.0.0-rc.3", "pkcs8", - "rand_core 0.6.4", + "rand_core 0.9.3", "sec1", "subtle", "zeroize", @@ -1866,19 +1909,19 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" -version = "0.13.1" +version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "d42dd26f5790eda47c1a2158ea4120e32c35ddc9a7743c98a292accc01b54ef3" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", "subtle", ] [[package]] name = "fiat-crypto" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" @@ -1899,6 +1942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -2095,13 +2139,12 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -2133,11 +2176,10 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "4f88107cb02ed63adcc4282942e60c4d09d80208d33b360ce7c729ce6dae1739" dependencies = [ - "opaque-debug", "polyval", ] @@ -2149,12 +2191,12 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" -version = "0.13.0" +version = "0.14.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "1ff6a0b2dd4b981b1ae9e3e6830ab146771f3660d31d57bafd9018805a91b0f1" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core 0.9.3", "subtle", ] @@ -2196,6 +2238,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2251,6 +2302,19 @@ dependencies = [ "http 1.4.0", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -2271,11 +2335,11 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "d8ef30358b03ca095a5b910547f4f8d4b9f163e4057669c5233ef595b1ecf008" dependencies = [ - "hmac 0.12.1", + "hmac 0.13.0-rc.2", ] [[package]] @@ -2382,6 +2446,20 @@ dependencies = [ "pin-project-lite 0.2.16", ] +[[package]] +name = "http-client-proxy" +version = "0.0.0" +dependencies = [ + "anyhow", + "ipnet", + "parking_lot", + "proxy_cfg", + "reqwest", + "rstest", + "tracing", + "url", +] + [[package]] name = "http-range-header" version = "0.3.1" @@ -2418,7 +2496,9 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" dependencies = [ + "subtle", "typenum", + "zeroize", ] [[package]] @@ -2529,11 +2609,9 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.16", "socket2 0.6.1", - "system-configuration", "tokio 1.48.0", "tower-service", "tracing", - "windows-registry 0.6.1", ] [[package]] @@ -2782,117 +2860,76 @@ dependencies = [ [[package]] name = "ironrdp" -version = "0.5.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.13.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ - "ironrdp-acceptor 0.1.0", + "ironrdp-acceptor", "ironrdp-server", ] [[package]] name = "ironrdp-acceptor" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" -dependencies = [ - "ironrdp-async 0.1.0", - "ironrdp-connector 0.1.0", - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", - "tracing", -] - -[[package]] -name = "ironrdp-acceptor" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7bbe1fd9a54d5e9669e4006f4840ea89339cebff2a7fb345dc925b17547925b" +version = "0.7.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ - "ironrdp-async 0.6.0", - "ironrdp-connector 0.6.0", + "ironrdp-async", + "ironrdp-connector", "ironrdp-core", - "ironrdp-pdu 0.5.0", - "ironrdp-svc 0.4.1", + "ironrdp-pdu", + "ironrdp-svc", "tracing", ] [[package]] name = "ironrdp-ainput" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.4.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bitflags 2.10.0", + "ironrdp-core", "ironrdp-dvc", - "ironrdp-pdu 0.1.0", "num-derive", "num-traits", ] [[package]] name = "ironrdp-async" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" -dependencies = [ - "bytes 1.11.0", - "ironrdp-connector 0.1.0", - "ironrdp-pdu 0.1.0", - "tracing", -] - -[[package]] -name = "ironrdp-async" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724ce488772b7850f6307b4d82559d87dadb7afdf816a35f6cf6e5a989a716f0" +version = "0.7.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bytes 1.11.0", - "ironrdp-connector 0.6.0", + "ironrdp-connector", "ironrdp-core", - "ironrdp-pdu 0.5.0", + "ironrdp-pdu", "tracing", ] [[package]] name = "ironrdp-cliprdr" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.4.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bitflags 2.10.0", - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "ironrdp-connector" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" -dependencies = [ - "ironrdp-error 0.1.0", - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", - "rand_core 0.6.4", - "sspi 0.11.1", + "ironrdp-core", + "ironrdp-pdu", + "ironrdp-svc", "tracing", - "url", - "winapi", ] [[package]] name = "ironrdp-connector" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb98a29bdd7ef95b490050ddabe4ddd816e527ea0df9f25cc7a9a30d2584dc1" +version = "0.7.1" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "ironrdp-core", - "ironrdp-error 0.1.3", - "ironrdp-pdu 0.5.0", - "ironrdp-svc 0.4.1", + "ironrdp-error", + "ironrdp-pdu", + "ironrdp-svc", "picky", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.14.4", - "rand_core 0.6.4", - "sspi 0.16.1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.9.2", + "sspi", "tracing", "url", ] @@ -2900,189 +2937,165 @@ dependencies = [ [[package]] name = "ironrdp-core" version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2db60a59716a84d09040d29c9e75e81545842510fccb0934c09b28e78b46680" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ - "ironrdp-error 0.1.3", + "ironrdp-error", ] [[package]] name = "ironrdp-displaycontrol" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.4.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ + "ironrdp-core", "ironrdp-dvc", - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", + "ironrdp-pdu", + "ironrdp-svc", "tracing", ] [[package]] name = "ironrdp-dvc" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.4.1" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", + "ironrdp-core", + "ironrdp-pdu", + "ironrdp-svc", "slab", "tracing", ] -[[package]] -name = "ironrdp-error" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" - [[package]] name = "ironrdp-error" version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9d7794e854eef2f13fdf79c8502bcc567a75a15fd0522885f37739386a4cef" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" [[package]] name = "ironrdp-graphics" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.6.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bit_field", "bitflags 2.10.0", "bitvec", "byteorder", - "ironrdp-error 0.1.0", - "ironrdp-pdu 0.1.0", - "lazy_static", - "num-derive", - "num-traits", - "thiserror 1.0.69", -] - -[[package]] -name = "ironrdp-pdu" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" -dependencies = [ - "bit_field", - "bitflags 2.10.0", - "byteorder", - "der-parser", - "ironrdp-error 0.1.0", - "md-5", - "num-bigint", + "ironrdp-core", + "ironrdp-pdu", "num-derive", - "num-integer", "num-traits", - "pkcs1", - "sha1 0.10.6", - "tap", - "thiserror 1.0.69", - "x509-cert", + "yuv", ] [[package]] name = "ironrdp-pdu" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc69c5d6ad3399965e0d3762886857f5861d4d854efe8d2bfc3462eb2b2b555a" +version = "0.6.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bit_field", "bitflags 2.10.0", "byteorder", "der-parser", "ironrdp-core", - "ironrdp-error 0.1.3", - "md-5", + "ironrdp-error", + "md-5 0.10.6", "num-bigint", "num-derive", "num-integer", "num-traits", - "pkcs1", + "pkcs1 0.7.5", "sha1 0.10.6", "tap", - "thiserror 1.0.69", + "thiserror 2.0.17", "x509-cert", ] [[package]] name = "ironrdp-rdcleanpath" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3f5401de43e86384ac0f7f356af8c0bdc321671853f76095da5d480d6998e0" +version = "0.2.1" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ - "der", + "der 0.7.10", ] [[package]] name = "ironrdp-rdpsnd" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.6.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bitflags 2.10.0", - "ironrdp-pdu 0.1.0", - "ironrdp-svc 0.1.0", + "ironrdp-core", + "ironrdp-pdu", + "ironrdp-svc", "tracing", ] [[package]] name = "ironrdp-server" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.9.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "anyhow", "async-trait", - "ironrdp-acceptor 0.1.0", + "bytes 1.11.0", + "ironrdp-acceptor", "ironrdp-ainput", + "ironrdp-async", "ironrdp-cliprdr", + "ironrdp-core", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-graphics", - "ironrdp-pdu 0.1.0", + "ironrdp-pdu", "ironrdp-rdpsnd", - "ironrdp-svc 0.1.0", - "ironrdp-tokio 0.1.0", + "ironrdp-svc", + "ironrdp-tokio", + "qoicoubeh", + "rayon", + "rustls-pemfile 2.2.0", "tokio 1.48.0", "tokio-rustls", "tracing", + "x509-cert", + "zstd-safe", ] [[package]] name = "ironrdp-svc" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" -dependencies = [ - "bitflags 2.10.0", - "ironrdp-pdu 0.1.0", -] - -[[package]] -name = "ironrdp-svc" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98959b1f0f4e9ae705880c73d604ad8f8ebf99feb2e33507092773c4b091c76c" +version = "0.5.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bitflags 2.10.0", "ironrdp-core", - "ironrdp-pdu 0.5.0", + "ironrdp-pdu", ] [[package]] name = "ironrdp-tokio" -version = "0.1.0" -source = "git+https://github.com/Devolutions/IronRDP?rev=2e1a9ac88e38e7d92d893007bc25d0a05c365861#2e1a9ac88e38e7d92d893007bc25d0a05c365861" +version = "0.7.0" +source = "git+https://github.com/Devolutions/IronRDP?rev=bd2aed76867f4038c32df9a0d24532ee40d2f14c#bd2aed76867f4038c32df9a0d24532ee40d2f14c" dependencies = [ "bytes 1.11.0", - "ironrdp-async 0.1.0", + "ironrdp-async", "tokio 1.48.0", ] [[package]] -name = "ironrdp-tokio" -version = "0.6.0" +name = "iso7816" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5815ae4dd7866a6730efb653281406a77fd1f5426d77dd959fc04e3512410f" +checksum = "cd3c7e91da489667bb054f9cd2f1c60cc2ac4478a899f403d11dbc62189215b0" dependencies = [ - "bytes 1.11.0", - "ironrdp-async 0.6.0", - "tokio 1.48.0", + "heapless", +] + +[[package]] +name = "iso7816-tlv" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7660d28d24a831d690228a275d544654a30f3b167a8e491cf31af5fe5058b546" +dependencies = [ + "untrusted 0.9.0", ] [[package]] @@ -3237,10 +3250,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02a8789fa3ae967c245095183b29a7414b930d110d8b7fd4dffc5d366120ce0" dependencies = [ "argon2", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.15.2", - "picky-krb 0.12.0", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", "thiserror 2.0.17", "time", "tracing", @@ -3249,9 +3262,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.2.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" dependencies = [ "cpufeatures", ] @@ -3281,9 +3294,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin 0.9.8", -] [[package]] name = "lazycell" @@ -3525,6 +3535,17 @@ dependencies = [ "zerocopy 0.7.35", ] +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -3648,6 +3669,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md-5" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ec86664728010f574d67ef01aec964e6f1299241a3402857c1a8a390a62478" +dependencies = [ + "cfg-if", + "digest 0.11.0-rc.3", +] + [[package]] name = "md4" version = "0.10.2" @@ -4056,15 +4087,15 @@ checksum = "50f0690370ba64c23218c7eaf146b8d84b21b265bbad0dafb19b38c92327ef35" dependencies = [ "bitflags 2.10.0", "ironrdp-core", - "ironrdp-error 0.1.3", + "ironrdp-error", "uuid", ] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -4088,23 +4119,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "serde", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -4131,17 +4145,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" @@ -4149,7 +4152,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -4220,12 +4222,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.75" @@ -4272,40 +4268,44 @@ dependencies = [ [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0-pre.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "81b374901df34ee468167a58e2a49e468cb059868479cafebeb804f6b855423d" dependencies = [ "ecdsa", "elliptic-curve", + "primefield", "primeorder", - "sha2", + "sha2 0.11.0-rc.2", ] [[package]] name = "p384" -version = "0.13.1" +version = "0.14.0-pre.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +checksum = "701032b3730df6b882496d6cee8221de0ce4bc11ddc64e6d89784aa5b8a6de30" dependencies = [ "ecdsa", "elliptic-curve", + "fiat-crypto", + "primefield", "primeorder", - "sha2", + "sha2 0.11.0-rc.2", ] [[package]] name = "p521" -version = "0.13.3" +version = "0.14.0-pre.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +checksum = "40ba29c2906eb5c89a8c411c4f11243ee4e5517ee7d71d9a13fedc877a6057b1" dependencies = [ - "base16ct 0.2.0", + "base16ct 0.3.0", "ecdsa", "elliptic-curve", + "primefield", "primeorder", - "rand_core 0.6.4", - "sha2", + "rand_core 0.9.3", + "sha2 0.11.0-rc.2", ] [[package]] @@ -4348,17 +4348,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", - "sha1 0.10.6", -] - [[package]] name = "pbkdf2" version = "0.13.0-rc.1" @@ -4404,6 +4393,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e58fab693c712c0d4e88f8eb3087b6521d060bcaf76aeb20cb192d809115ba" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4470,57 +4468,74 @@ dependencies = [ [[package]] name = "picky" -version = "7.0.0-rc.15" +version = "7.0.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83be360ca0cc8659abfbda932098e606fe52fa129508b92f0ce2998c00679170" +checksum = "4cdc52be663aebd70d7006ae305c87eb32a2b836d6c2f26f7e384f845d80b621" dependencies = [ - "aes 0.8.4", + "aead", + "aes 0.9.0-rc.1", "aes-gcm", "aes-kw", "base64 0.22.1", - "cbc 0.1.2", - "des 0.8.1", - "digest 0.10.7", + "block-buffer 0.11.0-rc.5", + "block-padding 0.4.0-rc.4", + "cbc 0.2.0-rc.1", + "cipher 0.5.0-rc.1", + "crypto-bigint", + "crypto-common 0.2.0-rc.4", + "crypto-primes", + "ctr", + "curve25519-dalek", + "der 0.8.0-rc.9", + "des", + "digest 0.11.0-rc.3", + "ecdsa", + "ed25519", "ed25519-dalek", + "elliptic-curve", + "ff", + "ghash", + "group", "hex", - "hmac 0.12.1", + "hkdf", + "hmac 0.13.0-rc.2", "http 1.4.0", - "md-5", - "num-bigint-dig", + "inout 0.2.0-rc.6", + "keccak", + "md-5 0.11.0-rc.2", "p256", "p384", "p521", - "pbkdf2 0.12.2", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.14.4", - "rand 0.8.5", - "rand_core 0.6.4", + "pbkdf2", + "pem-rfc7468 1.0.0-rc.3", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "pkcs1 0.8.0-rc.4", + "pkcs8", + "polyval", + "primefield", + "primeorder", + "rand 0.9.2", + "rand_core 0.9.3", "rc2", + "rfc6979", "rsa", + "sec1", "serde", "serde_json", - "sha1 0.10.6", - "sha2", + "sha1 0.11.0-rc.2", + "sha2 0.11.0-rc.2", "sha3", - "thiserror 1.0.69", + "signature", + "spki 0.8.0-rc.4", + "thiserror 2.0.17", "time", + "universal-hash", "x25519-dalek", "zeroize", ] -[[package]] -name = "picky-asn1" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" -dependencies = [ - "oid", - "serde", - "serde_bytes", - "time", -] - [[package]] name = "picky-asn1" version = "0.10.1" @@ -4536,117 +4551,29 @@ dependencies = [ [[package]] name = "picky-asn1-der" -version = "0.4.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" +checksum = "b491eb61603cba1ad5c6be0269883538f8d74136c35e3641a840fb0fbcd41efc" dependencies = [ - "picky-asn1 0.8.0", + "picky-asn1", "serde", "serde_bytes", ] [[package]] -name = "picky-asn1-der" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dccb53c26f70c082e008818f524bd45d057069517b047bd0c0ee062d6d7d7f2" -dependencies = [ - "picky-asn1 0.10.1", - "serde", - "serde_bytes", -] - -[[package]] -name = "picky-asn1-x509" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" -dependencies = [ - "base64 0.21.7", - "oid", - "picky-asn1 0.8.0", - "picky-asn1-der 0.4.1", - "serde", - "widestring 1.2.1", -] - -[[package]] -name = "picky-asn1-x509" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f974c1b3348705c23887c4f3b90947b9f23566db8b032e48af59c91f888f6f" -dependencies = [ - "base64 0.22.1", - "num-bigint-dig", - "oid", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "serde", - "widestring 1.2.1", - "zeroize", -] - -[[package]] -name = "picky-asn1-x509" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97cd14d567a17755910fa8718277baf39d08682a980b1b1a4b4da7d0bc61a04" -dependencies = [ - "base64 0.22.1", - "oid", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "serde", -] - -[[package]] -name = "picky-krb" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71cf61ebe6e657a81bcac31f9d61d52c23a1fd517b0dad77b915a7d3d15d2e8" -dependencies = [ - "aes 0.8.4", - "byteorder", - "cbc 0.1.2", - "crypto", - "des 0.8.1", - "hmac 0.12.1", - "num-bigint-dig", - "oid", - "pbkdf2 0.12.2", - "picky-asn1 0.8.0", - "picky-asn1-der 0.4.1", - "picky-asn1-x509 0.12.0", - "rand 0.8.5", - "serde", - "sha1 0.10.6", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "picky-krb" -version = "0.11.0" +name = "picky-asn1-x509" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ffe5f2122cdda5e9059ab837a65ba1b77729db43fc1500f2fce6b27070eab" +checksum = "c97cd14d567a17755910fa8718277baf39d08682a980b1b1a4b4da7d0bc61a04" dependencies = [ - "aes 0.8.4", - "byteorder", - "cbc 0.1.2", - "crypto", - "des 0.8.1", - "hmac 0.12.1", - "num-bigint-dig", + "base64 0.22.1", + "crypto-bigint", "oid", - "pbkdf2 0.12.2", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.14.4", - "rand 0.8.5", + "picky-asn1", + "picky-asn1-der", "serde", - "sha1 0.10.6", - "thiserror 1.0.69", - "uuid", + "widestring 1.2.1", + "zeroize", ] [[package]] @@ -4661,17 +4588,17 @@ dependencies = [ "byteorder", "cbc 0.2.0-rc.1", "cipher 0.5.0-rc.1", - "crypto-bigint 0.7.0-rc.8", + "crypto-bigint", "crypto-common 0.2.0-rc.4", - "des 0.9.0-rc.1", + "des", "digest 0.11.0-rc.3", "hmac 0.13.0-rc.2", "inout 0.2.0-rc.6", "oid", - "pbkdf2 0.13.0-rc.1", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.15.2", + "pbkdf2", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", "rand 0.9.2", "serde", "sha1 0.11.0-rc.2", @@ -4743,19 +4670,28 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der 0.8.0-rc.9", + "spki 0.8.0-rc.4", ] [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.11.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "93eac55f10aceed84769df670ea4a32d2ffad7399400d41ee1c13b1cd8e1b478" dependencies = [ - "der", - "spki", + "der 0.8.0-rc.9", + "spki 0.8.0-rc.4", ] [[package]] @@ -4796,21 +4732,20 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.2" +version = "0.7.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "1ffd40cc99d0fbb02b4b3771346b811df94194bc103983efa0203c8893755085" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portpicker" @@ -4832,10 +4767,10 @@ dependencies = [ "bytes 1.11.0", "fallible-iterator 0.2.0", "hmac 0.12.1", - "md-5", + "md-5 0.10.6", "memchr", "rand 0.9.2", - "sha2", + "sha2 0.10.9", "stringprep", ] @@ -4912,11 +4847,24 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "primefield" +version = "0.14.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcd4a163053332fd93f39b81c133e96a98567660981654579c90a99062fbf5" +dependencies = [ + "crypto-bigint", + "ff", + "rand_core 0.9.3", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" -version = "0.13.6" +version = "0.14.0-pre.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "1c36e8766fcd270fa9c665b9dc364f570695f5a59240949441b077a397f15b74" dependencies = [ "elliptic-curve", ] @@ -5081,6 +5029,15 @@ dependencies = [ "winreg 0.55.0", ] +[[package]] +name = "qoicoubeh" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b82aa3fef8a980075775b8c46f874823b5b4a15de327d2dbb3b6fd818480ba" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -5240,13 +5197,33 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rc2" -version = "0.8.1" +version = "0.9.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +checksum = "b03621ac292cc723def9e0fd0eb9573b1df8d6a9ee7ad637fe94dfc153705f3c" dependencies = [ - "cipher 0.4.4", + "cipher 0.5.0-rc.1", ] [[package]] @@ -5385,11 +5362,11 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "d369f9c4f79388704648e7bcb92749c0d6cf4397039293a9b747694fa4fb4bae" dependencies = [ - "hmac 0.12.1", + "hmac 0.13.0-rc.2", "subtle", ] @@ -5424,21 +5401,20 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.10.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "bf8955ab399f6426998fde6b76ae27233cce950705e758a6c17afd2f6d0e5d52" dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", + "const-oid 0.10.1", + "crypto-bigint", + "crypto-primes", + "digest 0.11.0-rc.3", + "pkcs1 0.8.0-rc.4", "pkcs8", - "rand_core 0.6.4", - "sha1 0.10.6", + "rand_core 0.9.3", + "sha1 0.11.0-rc.2", "signature", - "spki", + "spki 0.8.0-rc.4", "subtle", "zeroize", ] @@ -5582,7 +5558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c742cb7d8e43daae2dd9ca4b1da442b4500a461ce1c84249e6ac99b4bc12562e" dependencies = [ "rustls 0.23.35", - "sha2", + "sha2 0.10.9", "windows-sys 0.59.0", ] @@ -5743,18 +5719,26 @@ checksum = "d25679a62f678e7485f21abdea76f91a15322a0fe51efea791fd9d124f1c473c" [[package]] name = "sec1" -version = "0.7.3" +version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "1dff52f6118bc9f0ac974a54a639d499ac26a6cad7a6e39bc0990c19625e793b" dependencies = [ - "base16ct 0.2.0", - "der", - "generic-array", - "pkcs8", + "base16ct 0.3.0", + "der 0.8.0-rc.9", + "hybrid-array", "subtle", "zeroize", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5985,13 +5969,24 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.3", +] + [[package]] name = "sha3" -version = "0.10.8" +version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" dependencies = [ - "digest 0.10.7", + "digest 0.11.0-rc.3", "keccak", ] @@ -6021,12 +6016,12 @@ dependencies = [ [[package]] name = "signature" -version = "2.2.0" +version = "3.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "fc280a6ff65c79fbd6622f64d7127f32b85563bca8c53cd2e9141d6744a9056d" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", + "digest 0.11.0-rc.3", + "rand_core 0.9.3", ] [[package]] @@ -6120,93 +6115,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] [[package]] -name = "sspi" -version = "0.11.1" +name = "spki" +version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d31fab47d9290be28a8d027c8428756826f1d4fe1e5ba0f51d24f52c568e21" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ - "async-dnssd", - "async-recursion", - "bitflags 2.10.0", - "byteorder", - "cfg-if", - "crypto-mac", - "futures", - "hmac 0.12.1", - "lazy_static", - "md-5", - "md4", - "num-bigint-dig", - "num-derive", - "num-traits", - "oid", - "picky", - "picky-asn1 0.8.0", - "picky-asn1-der 0.4.1", - "picky-asn1-x509 0.12.0", - "picky-krb 0.8.0", - "rand 0.8.5", - "serde", - "serde_derive", - "sha1 0.10.6", - "sha2", - "time", - "tokio 1.48.0", - "tracing", - "url", - "uuid", - "winapi", - "windows 0.51.1", - "windows-sys 0.48.0", - "winreg 0.51.0", - "zeroize", + "base64ct", + "der 0.8.0-rc.9", ] [[package]] name = "sspi" -version = "0.16.1" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523f6a99e26c1e6476a424d54bbda5354a01ee7f18b9d93dc48a8fd45ae8189b" +checksum = "43f73fe6be958ae27fa8e982d9acc42d16f34eb74714d95bb53015528667cae4" dependencies = [ "async-dnssd", "async-recursion", "bitflags 2.10.0", + "block-buffer 0.11.0-rc.5", "byteorder", "cfg-if", + "crypto-bigint", + "crypto-common 0.2.0-rc.4", "crypto-mac", + "crypto-primes", + "cryptoki", + "curve25519-dalek", + "der 0.8.0-rc.9", + "digest 0.11.0-rc.3", + "ed25519-dalek", + "ff", "futures", - "hmac 0.12.1", - "lazy_static", - "md-5", + "getrandom 0.3.4", + "group", + "hmac 0.13.0-rc.2", + "md-5 0.11.0-rc.2", "md4", - "num-bigint-dig", "num-derive", "num-traits", "oid", + "p256", + "p384", + "p521", + "pem-rfc7468 1.0.0-rc.3", "picky", - "picky-asn1 0.10.1", - "picky-asn1-der 0.5.2", - "picky-asn1-x509 0.14.4", - "picky-krb 0.11.0", - "rand 0.8.5", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "pkcs1 0.8.0-rc.4", + "pkcs8", + "primefield", + "primeorder", + "rand 0.9.2", "rsa", "rustls 0.23.35", "serde", - "serde_derive", - "sha1 0.10.6", - "sha2", + "sha1 0.11.0-rc.2", + "sha2 0.11.0-rc.2", + "signature", + "spki 0.8.0-rc.4", "time", "tokio 1.48.0", "tracing", "url", "uuid", - "windows 0.61.3", - "windows-registry 0.5.3", - "windows-sys 0.60.2", + "windows 0.62.2", + "windows-registry 0.6.1", + "winscard", "zeroize", ] @@ -6334,17 +6315,6 @@ dependencies = [ "windows 0.61.3", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - [[package]] name = "system-configuration-sys" version = "0.5.0" @@ -6496,6 +6466,7 @@ checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", + "js-sys", "libc", "num-conv", "num_threads", @@ -7348,11 +7319,11 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "a55be643b40a21558f44806b53ee9319595bc7ca6896372e4e08e5d7d83c9cd6" dependencies = [ - "crypto-common 0.1.7", + "crypto-common 0.2.0-rc.4", "subtle", ] @@ -7770,25 +7741,27 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.51.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -7801,12 +7774,12 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.51.1" +name = "windows-collections" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-targets 0.48.5", + "windows-core 0.62.2", ] [[package]] @@ -7843,7 +7816,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -7890,6 +7874,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-registry" version = "0.5.3" @@ -8074,6 +8068,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -8275,22 +8278,35 @@ dependencies = [ [[package]] name = "winreg" -version = "0.51.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] -name = "winreg" -version = "0.55.0" +name = "winscard" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +checksum = "73b6ec4e6176df62589d1ac9950f6295be87ca06ee61a7c9a579a2bcc80efe34" dependencies = [ - "cfg-if", - "windows-sys 0.59.0", + "bitflags 2.10.0", + "crypto-bigint", + "flate2", + "iso7816", + "iso7816-tlv", + "num-derive", + "num-traits", + "picky", + "picky-asn1-x509", + "rand_core 0.9.3", + "rsa", + "sha1 0.11.0-rc.2", + "time", + "tracing", + "uuid", ] [[package]] @@ -8316,12 +8332,12 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.1" +version = "3.0.0-pre.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "3a45998121837fd8c92655d2334aa8f3e5ef0645cdfda5b321b13760c548fd55" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core 0.9.3", "serde", "zeroize", ] @@ -8333,8 +8349,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ "const-oid 0.9.6", - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", "tls_codec", ] @@ -8370,6 +8386,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "yuv" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f1bad143caadcfcaec93039dc9c40a30fc86f23d9e7cc03764a39fe51d9d43" +dependencies = [ + "num-traits", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -8484,3 +8509,22 @@ dependencies = [ "quote 1.0.42", "syn 2.0.111", ] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index b376bfbf0..d504049f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,16 @@ lto = true [patch.crates-io] tracing-appender = { git = "https://github.com/CBenoit/tracing.git", rev = "42097daf92e683cf18da7639ddccb056721a796c" } +# Patch IronRDP crates to use git master which has sspi 0.18.5 (fixes build error with sspi 0.16.1) +ironrdp-connector = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-acceptor = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-tokio = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c", features = ["reqwest"] } +ironrdp-async = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-pdu = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-svc = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-core = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-rdcleanpath = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } +ironrdp-error = { git = "https://github.com/Devolutions/IronRDP", rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" } [workspace.lints.rust] # Declare the custom cfgs. diff --git a/README.md b/README.md index fe7faf232..62eb05d2e 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,41 @@ Stable options are: * **StaticRootPath** (_FilePath_): Path to the static files for the standalone web application. This is an advanced option which should typically not be changed. +- **Proxy** (_Object_): HTTP/SOCKS proxy configuration for outbound requests. + Supports three modes: Off (never use proxy), System (auto-detect), Manual (explicit configuration). + + * **Mode** (_String_): Proxy mode (default is `System`). + - `Off`: Never use a proxy, ignore environment variables + - `System`: Auto-detect proxy from environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) + or system settings (per-user and machine-wide settings with WinHTTP fallback on Windows, + `/etc/sysconfig/proxy` on RHEL/SUSE systems, SCDynamicStoreCopyProxies() on macOS) + - `Manual`: Use explicitly configured proxy URLs + + * **Http** (_URL_): HTTP proxy URL for `http://` requests (e.g., `http://proxy.corp:8080`). + Only used when Mode is `Manual`. + + * **Https** (_URL_): HTTPS proxy URL for `https://` requests (e.g., `http://proxy.corp:8080`). + Only used when Mode is `Manual`. + + * **All** (_URL_): Fallback proxy URL for all protocols (e.g., `socks5://proxy.corp:1080`). + Only used when Mode is `Manual`. + The URL scheme determines the proxy type: + - `http://proxy.corp:8080` - HTTP CONNECT proxy + - `socks5://proxy.corp:1080` - SOCKS5 proxy + - `socks4://proxy.corp:1080` - SOCKS4 proxy + + * **Exclude** (_Array of Strings_): Bypass list with NO_PROXY semantics (only used when Mode is `Manual`). + Supports: + - Wildcard: `*` (bypass proxy for all targets) + - Exact hostname: `localhost`, `example.com` + - Domain suffix: `.corp.local` (matches `foo.corp.local`) + - IP address: `127.0.0.1` + - CIDR range: `10.0.0.0/8`, `192.168.0.0/16` + + Authentication can be included in proxy URLs: `http://username:password@proxy.corp:8080` + + See the [Cookbook](./docs/COOKBOOK.md) for configuration examples. + - **VerbosityProfile** (_String_): Logging verbosity profile (pre-defined tracing directives). Possible values: diff --git a/config_schema.json b/config_schema.json index ee02b87a8..a669f66eb 100644 --- a/config_schema.json +++ b/config_schema.json @@ -96,6 +96,10 @@ "$ref": "#/definitions/AiGatewayConf", "description": "JSON object describing the AI gateway configuration (experimental, requires enable_unstable)." }, + "Proxy": { + "$ref": "#/definitions/ProxyConf", + "description": "HTTP/SOCKS proxy configuration for outbound requests." + }, "LogFile": { "type": "string", "description": "Path to the log file." @@ -630,6 +634,38 @@ }, "additionalProperties": false }, + "ProxyConf": { + "type": "object", + "description": "HTTP/SOCKS proxy configuration for outbound requests. Supports three modes: Off (never use proxy), System (auto-detect from environment/system settings), Manual (use explicitly configured URLs).", + "properties": { + "Mode": { + "type": "string", + "enum": ["Off", "System", "Manual"], + "default": "System", + "description": "Proxy mode: Off (never use proxy), System (auto-detect from environment variables or WinHTTP), Manual (use configured URLs)" + }, + "Http": { + "type": "string", + "description": "HTTP proxy URL for http:// requests (e.g., http://proxy.corp:8080). Only used when Mode is Manual." + }, + "Https": { + "type": "string", + "description": "HTTPS proxy URL for https:// requests (e.g., http://proxy.corp:8080). Only used when Mode is Manual." + }, + "All": { + "type": "string", + "description": "Fallback proxy URL for all protocols (e.g., socks5://proxy.corp:1080). Only used when Mode is Manual." + }, + "Exclude": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Bypass list with NO_PROXY semantics. Supports: wildcard '*', exact hostname 'example.com', domain suffix '.corp.local', IP address '127.0.0.1', CIDR range '10.0.0.0/8'. Only used when Mode is Manual." + } + }, + "additionalProperties": false + }, "DebugConf": { "type": "object", "properties": { diff --git a/crates/http-client-proxy/Cargo.toml b/crates/http-client-proxy/Cargo.toml new file mode 100644 index 000000000..e45a6d65b --- /dev/null +++ b/crates/http-client-proxy/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "http-client-proxy" +version = "0.0.0" +edition = "2024" +authors = ["Devolutions Inc. "] +publish = false + +[lints] +workspace = true + +[dependencies] +proxy_cfg = "0.4" +reqwest = { version = "0.12", default-features = false } +anyhow = "1.0" +url = "2.5" +tracing = "0.1" +ipnet = "2.10" +parking_lot = "0.12" + +[dev-dependencies] +rstest = "0.25" diff --git a/crates/http-client-proxy/src/lib.rs b/crates/http-client-proxy/src/lib.rs new file mode 100644 index 000000000..a981e4c5d --- /dev/null +++ b/crates/http-client-proxy/src/lib.rs @@ -0,0 +1,328 @@ +use std::collections::{HashMap, VecDeque}; +use std::net::IpAddr; +use std::str::FromStr; + +use anyhow::Context as _; +use ipnet::IpNet; +use parking_lot::RwLock; +use tracing::warn; +use url::Url; + +/// Manual proxy configuration with protocol-specific URLs and exclude list. +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)] +pub struct ManualProxyConfig { + /// HTTP proxy URL (e.g., `http://proxy.corp:8080`). + pub http: Option, + /// HTTPS proxy URL (e.g., `http://proxy.corp:8080`). + pub https: Option, + /// Fallback proxy URL for all protocols (e.g., `socks5://proxy.corp:1080`). + pub all: Option, + /// Bypass list (same semantics as NO_PROXY). + /// + /// Supports hostnames, domain suffixes (.corp.local), IPs, CIDR ranges, and "*" for all. + pub exclude: Vec, +} + +impl ManualProxyConfig { + /// Check if a target URL should bypass the proxy based on the exclude list. + /// + /// This function implements NO_PROXY semantics: + /// - "*" matches everything (bypass proxy for all targets) + /// - Exact hostname match: "localhost", "example.com" + /// - Domain suffix match: ".corp.local" matches "foo.corp.local" + /// - IP address match: "127.0.0.1" + /// - CIDR range match: "10.0.0.0/8", "192.168.0.0/16" + pub fn should_bypass(&self, target: &Url) -> bool { + let host = match target.host_str() { + Some(h) => h, + None => return false, + }; + + for pattern in &self.exclude { + // Wildcard matches everything. + if pattern == "*" { + return true; + } + + // Domain suffix match (starts with dot). + if let Some(suffix) = pattern.strip_prefix('.') { + if host.ends_with(suffix) || host == suffix { + return true; + } + continue; + } + + // Exact hostname match. + if host == pattern { + return true; + } + + // Try IP address or CIDR range matching. + if let Some(target_ip) = target.host().and_then(|h| match h { + url::Host::Ipv4(ip) => Some(IpAddr::V4(ip)), + url::Host::Ipv6(ip) => Some(IpAddr::V6(ip)), + url::Host::Domain(_) => None, + }) { + // Check if pattern is an IP address. + if let Ok(pattern_ip) = IpAddr::from_str(pattern) { + if target_ip == pattern_ip { + return true; + } + continue; + } + + // Check if pattern is a CIDR range. + if let Ok(cidr) = IpNet::from_str(pattern) + && cidr.contains(&target_ip) + { + return true; + } + } + } + + false + } + + /// Select the appropriate proxy URL for a target URL. + /// + /// Selection order: + /// - For http:// -> use http proxy, fallback to all + /// - For https:// -> use https proxy, fallback to all + /// - For other schemes -> use all + pub fn select_proxy(&self, target: &Url) -> Option<&Url> { + match target.scheme() { + "http" => self.http.as_ref().or(self.all.as_ref()), + "https" => self.https.as_ref().or(self.all.as_ref()), + _ => self.all.as_ref(), + } + } +} + +/// Proxy configuration that can be specified in configuration files. +/// +/// This enum determines how HTTP client proxy settings are resolved: +/// - `Off`: Never use a proxy, ignore environment variables +/// - `System`: Use environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) or WinHTTP +/// - `Manual`: Use explicitly configured proxy URLs with optional exclude list +#[expect( + clippy::large_enum_variant, + reason = "Manual is the most common variant in practice, boxing would add unnecessary indirection" +)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ProxyConfig { + /// Never use a proxy, ignore environment variables. + Off, + /// Use environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) or WinHTTP configuration. + System, + /// Use manually configured proxy URLs from the configuration file. + Manual(ManualProxyConfig), +} + +impl Default for ProxyConfig { + fn default() -> Self { + Self::System + } +} + +/// Generates a cache key for HTTP clients based on URL and proxy configuration. +/// +/// Keys are based on scheme, host, port, and proxy configuration. +/// Path, query, and fragment are intentionally ignored as they don't affect +/// proxy selection or client configuration. +fn make_cache_key(url: &Url, config: &ProxyConfig) -> u64 { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + url.scheme().hash(&mut hasher); + url.host_str().unwrap_or("").hash(&mut hasher); + url.port().hash(&mut hasher); + config.hash(&mut hasher); + hasher.finish() +} + +/// FIFO cache for HTTP clients with a maximum capacity of 50 entries. +/// +/// This cache stores reqwest::Client instances to avoid repeated client creation +/// for the same (scheme, host, port, proxy_config) combinations. +/// +/// When the cache reaches capacity, the oldest entry is evicted (FIFO). +/// +/// Note: reqwest::Client already uses Arc internally, so we don't need to wrap it. +struct ClientCache { + /// Map from cache key to client + map: HashMap, + /// Queue tracking insertion order for FIFO eviction + queue: VecDeque, + /// Maximum number of clients to cache + capacity: usize, +} + +impl ClientCache { + const DEFAULT_CAPACITY: usize = 50; + + fn new() -> Self { + Self { + map: HashMap::new(), + queue: VecDeque::new(), + capacity: Self::DEFAULT_CAPACITY, + } + } + + /// Get a client from the cache, or None if not present. + fn get(&self, key: u64) -> Option { + self.map.get(&key).cloned() + } + + /// Insert a client into the cache, evicting the oldest entry if at capacity. + fn insert(&mut self, key: u64, client: reqwest::Client) { + // If already in cache, update it without adding duplicate to queue. + if let std::collections::hash_map::Entry::Occupied(mut e) = self.map.entry(key) { + e.insert(client); + return; + } + + // If at capacity, evict oldest entry. + if self.map.len() >= self.capacity + && let Some(oldest_key) = self.queue.pop_front() + { + self.map.remove(&oldest_key); + } + + // Insert new entry. + self.map.insert(key, client); + self.queue.push_back(key); + } +} + +/// Detects proxy configuration for a specific target URL using system settings. +/// +/// This function uses `proxy_cfg::get_proxy_config()` and then calls +/// `get_proxy_for_url()` method to detect proxy configuration from system +/// settings (WinHTTP on Windows, environment variables on Unix). +/// +/// Returns `Ok(None)` if no proxy is configured or if proxy detection fails. +fn detect_system_proxy_for_url(url: &Url) -> anyhow::Result> { + // Try to detect proxy using system configuration. + let proxy_config = match proxy_cfg::get_proxy_config() + .map_err(|e| anyhow::anyhow!("failed to detect proxy configuration: {e}"))? + { + Some(cfg) => cfg, + None => return Ok(None), + }; + + // Get the proxy URL for the specific target URL. + // This handles NO_PROXY exclusions automatically. + let proxy_url = match proxy_config.get_proxy_for_url(url) { + Some(url) => url, + None => return Ok(None), + }; + + let proxy_url = Url::parse(&proxy_url).context("invalid proxy URL from system settings")?; + + Ok(Some(proxy_url)) +} + +/// Builds a reqwest client with proxy configuration for a specific target URL. +/// +/// This function uses the provided configuration to determine the appropriate +/// proxy for the target URL based on the configured mode. +/// +/// # Arguments +/// +/// * `builder` - A reqwest::ClientBuilder to start with (may have timeout, TLS config, etc.) +/// * `url` - The URL that the client will connect to (used for proxy selection) +/// * `config` - Proxy configuration (mode, manual URLs, exclude list) +/// +/// # Returns +/// +/// A configured reqwest::Client ready to use, or an error if client creation fails. +pub fn build_client_with_proxy( + mut builder: reqwest::ClientBuilder, + url: &Url, + config: &ProxyConfig, +) -> reqwest::Result { + let proxy_url = match config { + ProxyConfig::Off => { + // No proxy mode - never use a proxy. + None + } + ProxyConfig::System => { + // System mode - use environment variables or WinHTTP. + detect_system_proxy_for_url(url).ok().flatten() + } + ProxyConfig::Manual(manual) => { + // Manual mode - check exclude list, then use configured URLs. + if manual.should_bypass(url) { + None + } else { + manual.select_proxy(url).cloned() + } + } + }; + + if let Some(proxy_url) = proxy_url { + // Create reqwest::Proxy from the proxy URL. + let proxy = reqwest::Proxy::all(proxy_url.clone()).inspect_err(|error| { + warn!(%proxy_url, %error, "Failed to configure proxy"); + })?; + + builder = builder.proxy(proxy); + } + + builder.build() +} + +/// Gets or creates a cached HTTP client with proxy configuration. +/// +/// This function maintains a global cache of up to 50 HTTP clients to avoid +/// repeated client creation overhead. Clients are cached based on the target +/// URL's scheme, host, port, and proxy configuration. +/// +/// The cache uses FIFO eviction: when the cache reaches 50 entries, the oldest +/// entry is removed to make room for new ones. +/// +/// # Arguments +/// +/// * `builder` - A reqwest::ClientBuilder with additional configuration (timeout, TLS, etc.) +/// * `url` - The target URL (only scheme, host, and port are used for caching) +/// * `config` - Proxy configuration +/// +/// # Returns +/// +/// A reqwest::Client that can be safely shared and cloned (uses Arc internally). +pub fn get_or_create_cached_client( + builder: reqwest::ClientBuilder, + url: &Url, + config: &ProxyConfig, +) -> reqwest::Result { + /// Global cache for HTTP clients. + static CLIENT_CACHE: std::sync::LazyLock> = + std::sync::LazyLock::new(|| RwLock::new(ClientCache::new())); + + let cache_key = make_cache_key(url, config); + + // Fast path: check if client is already cached (read lock). + { + let cache = CLIENT_CACHE.read(); + if let Some(client) = cache.get(cache_key) { + return Ok(client); + } + } + + // Slow path: create new client and cache it (write lock). + let mut cache = CLIENT_CACHE.write(); + + // Check again in case another thread created the client while we were waiting. + if let Some(client) = cache.get(cache_key) { + return Ok(client); + } + + // Build new client with proxy configuration. + let client = build_client_with_proxy(builder, url, config)?; + + // Insert into cache. + cache.insert(cache_key, client.clone()); + + Ok(client) +} diff --git a/crates/http-client-proxy/tests/manual_proxy.rs b/crates/http-client-proxy/tests/manual_proxy.rs new file mode 100644 index 000000000..43f460086 --- /dev/null +++ b/crates/http-client-proxy/tests/manual_proxy.rs @@ -0,0 +1,138 @@ +#![allow(clippy::unwrap_used, reason = "test code can panic on errors")] + +use http_client_proxy::ManualProxyConfig; +use url::Url; + +#[test] +fn test_should_bypass_wildcard() { + let config = ManualProxyConfig { + exclude: vec!["*".to_owned()], + ..Default::default() + }; + + let url = Url::parse("http://example.com").unwrap(); + assert!(config.should_bypass(&url)); +} + +#[test] +fn test_should_bypass_exact_hostname() { + let config = ManualProxyConfig { + exclude: vec!["localhost".to_owned(), "example.com".to_owned()], + ..Default::default() + }; + + assert!(config.should_bypass(&Url::parse("http://localhost").unwrap())); + assert!(config.should_bypass(&Url::parse("https://example.com").unwrap())); + assert!(!config.should_bypass(&Url::parse("http://other.com").unwrap())); +} + +#[test] +fn test_should_bypass_domain_suffix() { + let config = ManualProxyConfig { + exclude: vec![".corp.local".to_owned()], + ..Default::default() + }; + + assert!(config.should_bypass(&Url::parse("http://foo.corp.local").unwrap())); + assert!(config.should_bypass(&Url::parse("https://bar.corp.local").unwrap())); + assert!(config.should_bypass(&Url::parse("http://corp.local").unwrap())); + assert!(!config.should_bypass(&Url::parse("http://example.com").unwrap())); +} + +#[test] +fn test_should_bypass_ip_address() { + let config = ManualProxyConfig { + exclude: vec!["127.0.0.1".to_owned(), "::1".to_owned()], + ..Default::default() + }; + + assert!(config.should_bypass(&Url::parse("http://127.0.0.1").unwrap())); + assert!(config.should_bypass(&Url::parse("http://[::1]").unwrap())); + assert!(!config.should_bypass(&Url::parse("http://192.168.1.1").unwrap())); +} + +#[test] +fn test_should_bypass_cidr() { + let config = ManualProxyConfig { + exclude: vec!["10.0.0.0/8".to_owned(), "192.168.0.0/16".to_owned()], + ..Default::default() + }; + + assert!(config.should_bypass(&Url::parse("http://10.0.0.1").unwrap())); + assert!(config.should_bypass(&Url::parse("http://10.255.255.255").unwrap())); + assert!(config.should_bypass(&Url::parse("http://192.168.1.100").unwrap())); + assert!(!config.should_bypass(&Url::parse("http://172.16.0.1").unwrap())); +} + +#[test] +#[expect( + clippy::similar_names, + reason = "test semantically requires http and https proxy URLs" +)] +fn test_select_proxy_http() { + let http_proxy_url = Url::parse("http://http-proxy:8080").unwrap(); + let https_proxy_url = Url::parse("http://https-proxy:8443").unwrap(); + + let config = ManualProxyConfig { + http: Some(http_proxy_url.clone()), + https: Some(https_proxy_url), + ..Default::default() + }; + + let target = Url::parse("http://example.com").unwrap(); + assert_eq!(config.select_proxy(&target), Some(&http_proxy_url)); +} + +#[test] +#[expect( + clippy::similar_names, + reason = "test semantically requires http and https proxy URLs" +)] +fn test_select_proxy_https() { + let http_proxy_url = Url::parse("http://http-proxy:8080").unwrap(); + let https_proxy_url = Url::parse("http://https-proxy:8443").unwrap(); + + let config = ManualProxyConfig { + http: Some(http_proxy_url), + https: Some(https_proxy_url.clone()), + ..Default::default() + }; + + let target = Url::parse("https://example.com").unwrap(); + assert_eq!(config.select_proxy(&target), Some(&https_proxy_url)); +} + +#[test] +fn test_select_proxy_fallback_to_all() { + let all_proxy = Url::parse("socks5://socks-proxy:1080").unwrap(); + + let config = ManualProxyConfig { + all: Some(all_proxy.clone()), + ..Default::default() + }; + + // HTTP falls back to all when http is not configured. + assert_eq!( + config.select_proxy(&Url::parse("http://example.com").unwrap()), + Some(&all_proxy) + ); + + // HTTPS falls back to all when https is not configured. + assert_eq!( + config.select_proxy(&Url::parse("https://example.com").unwrap()), + Some(&all_proxy) + ); + + // Other schemes use all. + assert_eq!( + config.select_proxy(&Url::parse("ftp://example.com").unwrap()), + Some(&all_proxy) + ); +} + +#[test] +fn test_select_proxy_none() { + let config = ManualProxyConfig::default(); + + assert_eq!(config.select_proxy(&Url::parse("http://example.com").unwrap()), None); +} diff --git a/crates/http-client-proxy/tests/proxy_config.rs b/crates/http-client-proxy/tests/proxy_config.rs new file mode 100644 index 000000000..6e106ad2f --- /dev/null +++ b/crates/http-client-proxy/tests/proxy_config.rs @@ -0,0 +1,92 @@ +#![allow(clippy::unwrap_used, reason = "test code can panic on errors")] + +use http_client_proxy::{ManualProxyConfig, ProxyConfig, build_client_with_proxy}; +use rstest::rstest; +use url::Url; + +#[rstest] +#[case("http://example.com", true)] +#[case("https://example.com", true)] +#[case("ftp://example.com", true)] +fn test_build_client_with_manual_http_proxy(#[case] target_url_str: &str, #[case] should_succeed: bool) { + let builder = reqwest::Client::builder(); + let target_url = Url::parse(target_url_str).unwrap(); + let config = ProxyConfig::Manual(ManualProxyConfig { + http: Some(Url::parse("http://manual-proxy:8080").unwrap()), + ..Default::default() + }); + + let result = build_client_with_proxy(builder, &target_url, &config); + assert_eq!(result.is_ok(), should_succeed); +} + +#[rstest] +#[case("http://example.com", true)] +#[case("https://example.com", true)] +fn test_build_client_with_manual_socks5_proxy(#[case] target_url_str: &str, #[case] should_succeed: bool) { + let builder = reqwest::Client::builder(); + let target_url = Url::parse(target_url_str).unwrap(); + let config = ProxyConfig::Manual(ManualProxyConfig { + all: Some(Url::parse("socks5://socks-proxy:1080").unwrap()), + ..Default::default() + }); + + let result = build_client_with_proxy(builder, &target_url, &config); + assert_eq!(result.is_ok(), should_succeed); +} + +#[test] +fn test_build_client_with_system_mode() { + let builder = reqwest::Client::builder(); + let target_url = Url::parse("http://example.com").unwrap(); + let config = ProxyConfig::System; + + // System mode should succeed (may or may not use a proxy depending on environment). + let result = build_client_with_proxy(builder, &target_url, &config); + assert!(result.is_ok()); +} + +#[test] +fn test_build_client_with_off_mode() { + let builder = reqwest::Client::builder(); + let target_url = Url::parse("http://example.com").unwrap(); + let config = ProxyConfig::Off; + + let result = build_client_with_proxy(builder, &target_url, &config); + assert!(result.is_ok()); +} + +#[test] +fn test_build_client_with_exclude_list() { + let builder = reqwest::Client::builder(); + let target_url = Url::parse("http://localhost").unwrap(); + let config = ProxyConfig::Manual(ManualProxyConfig { + http: Some(Url::parse("http://proxy:8080").unwrap()), + exclude: vec!["localhost".to_owned()], + ..Default::default() + }); + + // Should succeed without using proxy (localhost is in exclude list). + let result = build_client_with_proxy(builder, &target_url, &config); + assert!(result.is_ok()); +} + +#[test] +#[expect(clippy::similar_names, reason = "test semantically requires http and https targets")] +fn test_build_client_with_protocol_specific_proxies() { + let http_target = Url::parse("http://example.com").unwrap(); + let https_target = Url::parse("https://example.com").unwrap(); + + let config = ProxyConfig::Manual(ManualProxyConfig { + http: Some(Url::parse("http://http-proxy:8080").unwrap()), + https: Some(Url::parse("http://https-proxy:8443").unwrap()), + ..Default::default() + }); + + // Both should succeed with their respective proxies. + let http_result = build_client_with_proxy(reqwest::Client::builder(), &http_target, &config); + assert!(http_result.is_ok()); + + let https_result = build_client_with_proxy(reqwest::Client::builder(), &https_target, &config); + assert!(https_result.is_ok()); +} diff --git a/devolutions-agent/Cargo.toml b/devolutions-agent/Cargo.toml index 2707e38f8..9c5415f04 100644 --- a/devolutions-agent/Cargo.toml +++ b/devolutions-agent/Cargo.toml @@ -21,18 +21,20 @@ devolutions-agent-shared = { path = "../crates/devolutions-agent-shared" } devolutions-gateway-task = { path = "../crates/devolutions-gateway-task" } devolutions-log = { path = "../crates/devolutions-log" } futures = "0.3" +http-client-proxy = { path = "../crates/http-client-proxy" } parking_lot = "0.12" -rand = "0.8" # FIXME(@CBenoit): maybe we don’t need this crate -rustls-pemfile = "2.2" # FIXME(@CBenoit): maybe we don’t need this crate +rand = "0.8" # FIXME(@CBenoit): maybe we don't need this crate +rustls-pemfile = "2.2" # FIXME(@CBenoit): maybe we don't need this crate serde_json = "1" serde = { version = "1", features = ["derive"] } tap = "1.0" tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] } tracing = "0.1" +url = "2.5" [dependencies.ironrdp] git = "https://github.com/Devolutions/IronRDP" -rev = "2e1a9ac88e38e7d92d893007bc25d0a05c365861" +rev = "bd2aed76867f4038c32df9a0d24532ee40d2f14c" default-features = false features = [ "server", # FIXME(@CBenoit): this is enabling AWS LC unconditionally. @@ -54,14 +56,14 @@ features = [ ] [target.'cfg(windows)'.dependencies] +aws-lc-rs = "1.14" +devolutions-pedm = { path = "../crates/devolutions-pedm" } hex = "0.4" notify-debouncer-mini = "0.6" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "system-proxy", "socks"] } -aws-lc-rs = "1.14" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "socks"] } sha2 = "0.10" thiserror = "2" uuid = { version = "1.17", features = ["v4"] } -devolutions-pedm = { path = "../crates/devolutions-pedm" } win-api-wrappers = { path = "../crates/win-api-wrappers" } [target.'cfg(windows)'.dependencies.windows] diff --git a/devolutions-agent/src/config.rs b/devolutions-agent/src/config.rs index 9b899361f..1632fcc60 100644 --- a/devolutions-agent/src/config.rs +++ b/devolutions-agent/src/config.rs @@ -8,6 +8,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use devolutions_agent_shared::get_data_dir; use serde::{Deserialize, Serialize}; use tap::prelude::*; +use url::Url; const DEFAULT_RDP_PORT: u16 = 3389; @@ -19,6 +20,7 @@ pub struct Conf { pub remote_desktop: RemoteDesktopConf, pub pedm: dto::PedmConf, pub session: dto::SessionConf, + pub proxy: dto::ProxyConf, pub debug: dto::DebugConf, } @@ -46,6 +48,7 @@ impl Conf { remote_desktop, pedm: conf_file.pedm.clone().unwrap_or_default(), session: conf_file.session.clone().unwrap_or_default(), + proxy: conf_file.proxy.clone().unwrap_or_default(), debug: conf_file.debug.clone().unwrap_or_default(), }) } @@ -301,6 +304,10 @@ pub mod dto { #[serde(default, skip_serializing_if = "Option::is_none")] pub session: Option, + /// HTTP/SOCKS proxy configuration for outbound requests + #[serde(skip_serializing_if = "Option::is_none")] + pub proxy: Option, + /// (Unstable) Unsafe debug options for developers #[serde(rename = "__debug__", skip_serializing_if = "Option::is_none")] pub debug: Option, @@ -320,6 +327,7 @@ pub mod dto { updater: Some(UpdaterConf { enabled: true }), remote_desktop: None, pedm: None, + proxy: None, debug: None, session: Some(SessionConf { enabled: false }), rest: serde_json::Map::new(), @@ -421,6 +429,73 @@ pub mod dto { Self::default().eq(self) } } + + /// Proxy mode for HTTP client configuration. + #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, Default, Serialize, Deserialize)] + pub enum ProxyMode { + /// Never use a proxy, ignore environment variables. + Off, + /// Use environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) or WinHTTP configuration. + #[default] + System, + /// Use manually configured proxy URLs from the configuration file. + Manual, + } + + /// HTTP/SOCKS proxy configuration for outbound requests. + /// + /// This configuration supports three modes: + /// - Off: Never use a proxy + /// - System: Auto-detect proxy from environment variables or system settings + /// - Manual: Use explicitly configured proxy URLs + /// + /// Manual configuration supports protocol-specific proxy URLs: + /// - Http: HTTP proxy (e.g., `http://proxy.corp:8080`) + /// - Https: HTTPS proxy (e.g., `http://proxy.corp:8080`) + /// - All: Fallback proxy for all protocols (e.g., `socks5://proxy.corp:1080`) + /// + /// The Exclude list supports NO_PROXY semantics: + /// - "*" matches everything (bypass proxy for all targets) + /// - Exact hostname: "localhost", "example.com" + /// - Domain suffix: ".corp.local" matches "foo.corp.local" + /// - IP address: "127.0.0.1" + /// - CIDR range: "10.0.0.0/8", "192.168.0.0/16" + #[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)] + #[serde(rename_all = "PascalCase")] + pub struct ProxyConf { + /// Proxy mode (Off, System, Manual). + #[serde(default)] + pub mode: ProxyMode, + /// HTTP proxy URL (e.g., `http://proxy.corp:8080`). + #[serde(skip_serializing_if = "Option::is_none")] + pub http: Option, + /// HTTPS proxy URL (e.g., `http://proxy.corp:8080`). + #[serde(skip_serializing_if = "Option::is_none")] + pub https: Option, + /// Fallback proxy URL for all protocols (e.g., `socks5://proxy.corp:1080`). + #[serde(skip_serializing_if = "Option::is_none")] + pub all: Option, + /// Bypass list (same semantics as NO_PROXY). + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + pub exclude: Vec, + } + + impl ProxyConf { + /// Convert this DTO to the http-client-proxy library's ProxyConfig enum. + pub fn to_proxy_config(&self) -> http_client_proxy::ProxyConfig { + match self.mode { + ProxyMode::Off => http_client_proxy::ProxyConfig::Off, + ProxyMode::System => http_client_proxy::ProxyConfig::System, + ProxyMode::Manual => http_client_proxy::ProxyConfig::Manual(http_client_proxy::ManualProxyConfig { + http: self.http.clone(), + https: self.https.clone(), + all: self.all.clone(), + exclude: self.exclude.clone(), + }), + } + } + } } pub fn handle_cli(command: &str) -> Result<(), anyhow::Error> { diff --git a/devolutions-agent/src/updater/io.rs b/devolutions-agent/src/updater/io.rs index 5ab87a532..43c7a973f 100644 --- a/devolutions-agent/src/updater/io.rs +++ b/devolutions-agent/src/updater/io.rs @@ -5,6 +5,7 @@ use futures::TryFutureExt; use tokio::fs::File; use tokio::io::AsyncWriteExt; +use crate::config::dto::ProxyConf; use crate::updater::UpdaterError; /// Parse file:// URL according to RFC 8089 @@ -27,28 +28,43 @@ pub(crate) fn parse_file_url(url: &str) -> Option<&Utf8Path> { } } -/// Download binary file to memory -pub(crate) async fn download_binary(url: &str) -> Result, UpdaterError> { +/// Download binary file to memory. +pub(crate) async fn download_binary(url: &str, proxy_conf: &ProxyConf) -> Result, UpdaterError> { if let Some(path) = parse_file_url(url) { info!(%url, "Reading file from local filesystem..."); tokio::fs::read(path).await.map_err(UpdaterError::Io) } else { info!(%url, "Downloading file from network..."); - let body = reqwest::get(url) + let target_url = url::Url::parse(url) + .map_err(|e| UpdaterError::Io(std::io::Error::other(format!("invalid url ({url}): {e}"))))?; + + let proxy_config = proxy_conf.to_proxy_config(); + + let client = + http_client_proxy::get_or_create_cached_client(reqwest::Client::builder(), &target_url, &proxy_config) + .map_err(|source| UpdaterError::FileDownload { + source, + url: url.to_owned(), + })?; + + let body = client + .get(url) + .send() .and_then(|response| response.bytes()) .map_err(|source| UpdaterError::FileDownload { source, url: url.to_owned(), }) .await?; + Ok(body.to_vec()) } } /// Download UTF-8 file to memory -pub(crate) async fn download_utf8(url: &str) -> Result { - let bytes = download_binary(url).await?; +pub(crate) async fn download_utf8(url: &str, proxy_conf: &ProxyConf) -> Result { + let bytes = download_binary(url, proxy_conf).await?; String::from_utf8(bytes).map_err(|_| UpdaterError::Utf8) } diff --git a/devolutions-agent/src/updater/mod.rs b/devolutions-agent/src/updater/mod.rs index 40f7e9c99..994a1c08c 100644 --- a/devolutions-agent/src/updater/mod.rs +++ b/devolutions-agent/src/updater/mod.rs @@ -46,12 +46,14 @@ async fn load_productinfo_source(conf: &ConfHandle) -> Result(state: DgwState) -> Router { .with_state(state) } -/// Returns a cached HTTP client with the specified timeout. +/// Creates or retrieves a cached HTTP client with the specified timeout and proxy configuration. /// -/// The client is created once on first use and cloned for subsequent calls (cloning a -/// reqwest::Client is cheap as it uses Arc internally). -fn create_client(timeout: Duration) -> reqwest::Client { - static AI_CLIENT: OnceLock = OnceLock::new(); - - AI_CLIENT - .get_or_init(|| { - reqwest::Client::builder() - .timeout(timeout) - .build() - .expect("parameters known to be valid only") - }) - .clone() +/// Clients are cached per (scheme, host, port, proxy_config) combination to avoid repeated +/// client creation. The cache uses FIFO eviction with a capacity of 50 entries. +fn get_or_create_client( + timeout: Duration, + target_url: &str, + proxy_conf: &crate::config::dto::ProxyConf, +) -> Result { + // Parse the target URL. + let target_url = url::Url::parse(target_url).map_err(HttpError::internal().with_msg("invalid target URL").err())?; + + // Convert proxy configuration to http-client-proxy format. + let proxy_config = proxy_conf.to_proxy_config(); + + // Get or create a cached client with the configured timeout and proxy support. + http_client_proxy::get_or_create_cached_client( + reqwest::Client::builder().timeout(timeout), + &target_url, + &proxy_config, + ) + .map_err(HttpError::internal().with_msg("failed to create HTTP client").err()) } /// Validates the Authorization header against the gateway API key. @@ -122,8 +128,8 @@ async fn proxy_to_provider( debug!(%url, provider = %provider_config.name, "Proxying request to AI provider"); - // Create a client with the configured timeout. - let client = create_client(ai_conf.request_timeout); + // Get or create a cached client with the configured timeout and proxy support. + let client = get_or_create_client(ai_conf.request_timeout, &url, &conf.proxy)?; // Build the request. let mut request_builder = client.request(method, &url); diff --git a/devolutions-gateway/src/config.rs b/devolutions-gateway/src/config.rs index b6632db5c..2df6ed073 100644 --- a/devolutions-gateway/src/config.rs +++ b/devolutions-gateway/src/config.rs @@ -106,6 +106,7 @@ pub struct Conf { pub verbosity_profile: dto::VerbosityProfile, pub web_app: WebAppConf, pub ai_gateway: AiGatewayConf, + pub proxy: dto::ProxyConf, pub debug: dto::DebugConf, } @@ -799,6 +800,7 @@ impl Conf { .as_ref() .map(AiGatewayConf::from_dto) .unwrap_or_default(), + proxy: conf_file.proxy.clone().unwrap_or_default(), debug: conf_file.debug.clone().unwrap_or_default(), }) } @@ -1526,6 +1528,10 @@ pub mod dto { #[serde(skip_serializing_if = "Option::is_none")] pub traffic_audit_database: Option, + /// HTTP/SOCKS proxy configuration for outbound requests + #[serde(skip_serializing_if = "Option::is_none")] + pub proxy: Option, + /// (Unstable) Unsafe debug options for developers #[serde(rename = "__debug__", skip_serializing_if = "Option::is_none")] pub debug: Option, @@ -1578,6 +1584,7 @@ pub mod dto { ai_gateway: None, job_queue_database: None, traffic_audit_database: None, + proxy: None, debug: None, rest: serde_json::Map::new(), } @@ -2100,4 +2107,80 @@ pub mod dto { #[serde(skip_serializing_if = "Option::is_none")] pub api_version: Option, } + + /// Proxy mode determines how proxy configuration is resolved. + #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, Default, Serialize, Deserialize)] + pub enum ProxyMode { + /// Never use a proxy, ignore environment variables. + Off, + /// Use environment variables, Linux sysconfig, Windows Registry, or macOS System Configuration. + #[default] + System, + /// Use manually configured proxy URLs from the configuration file. + Manual, + } + + /// HTTP/SOCKS proxy configuration for outbound requests. + /// + /// Mode determines how proxies are configured: + /// - Off: Never use a proxy + /// - System: Use environment variables, system configuration, or OS-specific settings + /// - Manual: Use explicitly configured URLs + /// + /// In Manual mode, the URL scheme determines the proxy type: + /// - `http://proxy.corp:8080` - HTTP CONNECT proxy + /// - `socks5://proxy.corp:1080` - SOCKS5 proxy + /// - `socks4://proxy.corp:1080` - SOCKS4 proxy + /// + /// Proxy credentials can be embedded in the URL: `http://user:pass@proxy.corp:8080` + #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] + #[serde(rename_all = "PascalCase")] + pub struct ProxyConf { + /// How to determine proxy configuration (default: System) + #[serde(default)] + pub mode: ProxyMode, + /// HTTP proxy URL (used only in Manual mode) + #[serde(skip_serializing_if = "Option::is_none")] + pub http: Option, + /// HTTPS proxy URL (used only in Manual mode) + #[serde(skip_serializing_if = "Option::is_none")] + pub https: Option, + /// Fallback proxy URL for all protocols (used only in Manual mode) + /// Typically a SOCKS proxy. + #[serde(skip_serializing_if = "Option::is_none")] + pub all: Option, + /// Bypass list for manual mode (same semantics as NO_PROXY). + /// Supports hostnames, domain suffixes (.corp.local), IPs, CIDR ranges, and "*" for all. + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + pub exclude: Vec, + } + + impl Default for ProxyConf { + fn default() -> Self { + Self { + mode: ProxyMode::System, + http: None, + https: None, + all: None, + exclude: Vec::new(), + } + } + } + + impl ProxyConf { + /// Convert this DTO to the http-client-proxy ProxyConfig. + pub fn to_proxy_config(&self) -> http_client_proxy::ProxyConfig { + match self.mode { + ProxyMode::Off => http_client_proxy::ProxyConfig::Off, + ProxyMode::System => http_client_proxy::ProxyConfig::System, + ProxyMode::Manual => http_client_proxy::ProxyConfig::Manual(http_client_proxy::ManualProxyConfig { + http: self.http.clone(), + https: self.https.clone(), + all: self.all.clone(), + exclude: self.exclude.clone(), + }), + } + } + } } diff --git a/devolutions-gateway/src/rdp_pcb.rs b/devolutions-gateway/src/rdp_pcb.rs index 3ff270deb..483cf970c 100644 --- a/devolutions-gateway/src/rdp_pcb.rs +++ b/devolutions-gateway/src/rdp_pcb.rs @@ -65,7 +65,7 @@ fn decode_pcb(buf: &[u8]) -> Result, io::Erro Ok(Some((pcb, read_len))) } - Err(e) if matches!(e.kind, ironrdp_core::DecodeErrorKind::NotEnoughBytes { .. }) => Ok(None), + Err(e) if matches!(e.kind(), ironrdp_core::DecodeErrorKind::NotEnoughBytes { .. }) => Ok(None), Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), } } diff --git a/devolutions-gateway/src/rdp_proxy.rs b/devolutions-gateway/src/rdp_proxy.rs index 2e8062109..aa47d38c6 100644 --- a/devolutions-gateway/src/rdp_proxy.rs +++ b/devolutions-gateway/src/rdp_proxy.rs @@ -269,12 +269,10 @@ where ironrdp_core::decode(&received_connect_initial.0.data).context("decode Connect Initial PDU")?; trace!(message = ?received_connect_initial, "Received Connect Initial PDU from client"); - received_connect_initial - .conference_create_request - .gcc_blocks - .core - .optional_data - .server_selected_protocol = Some(server_security_protocol); + let mut gcc_blocks = received_connect_initial.conference_create_request.into_gcc_blocks(); + gcc_blocks.core.optional_data.server_selected_protocol = Some(server_security_protocol); + // Update the conference request with modified gcc_blocks + received_connect_initial.conference_create_request = ironrdp_pdu::gcc::ConferenceCreateRequest::new(gcc_blocks)?; trace!(message = ?received_connect_initial, "Send Connection Request PDU to server"); let x224_msg_buf = ironrdp_core::encode_vec(&received_connect_initial)?; let pdu = x224::X224Data { diff --git a/devolutions-gateway/src/subscriber.rs b/devolutions-gateway/src/subscriber.rs index 27abbecb1..03ec6e193 100644 --- a/devolutions-gateway/src/subscriber.rs +++ b/devolutions-gateway/src/subscriber.rs @@ -69,8 +69,12 @@ impl Message { } } -#[instrument(skip(subscriber))] -pub async fn send_message(subscriber: &Subscriber, message: &Message) -> anyhow::Result<()> { +#[instrument(skip(subscriber, proxy_conf))] +pub async fn send_message( + subscriber: &Subscriber, + message: &Message, + proxy_conf: &crate::config::dto::ProxyConf, +) -> anyhow::Result<()> { use std::time::Duration; use backoff::backoff::Backoff as _; @@ -85,7 +89,11 @@ pub async fn send_message(subscriber: &Subscriber, message: &Message) -> anyhow: .with_multiplier(RETRY_MULTIPLIER) .build(); - let client = reqwest::Client::new(); + // Build client with proxy support for the subscriber URL. + let proxy_config = proxy_conf.to_proxy_config(); + let client = + http_client_proxy::get_or_create_cached_client(reqwest::Client::builder(), &subscriber.url, &proxy_config) + .context("failed to build HTTP client with proxy configuration")?; let op = || async { let response = client @@ -243,8 +251,9 @@ async fn subscriber_task( if let Some(subscriber) = conf.subscriber.clone() { debug!(?msg, %subscriber.url, "Send message"); + let proxy_conf = conf.proxy.clone(); ChildTask::spawn(async move { - if let Err(error) = send_message(&subscriber, &msg).await { + if let Err(error) = send_message(&subscriber, &msg, &proxy_conf).await { warn!(error = format!("{error:#}"), "Couldn't send message to the subscriber"); } }) @@ -265,7 +274,7 @@ async fn subscriber_task( let msg = Message::session_list(Vec::new()); debug!(?msg, %subscriber.url, "Send message"); - if let Err(error) = send_message(&subscriber, &msg).await { + if let Err(error) = send_message(&subscriber, &msg, &conf.proxy).await { warn!(error = format!("{error:#}"), "Couldn't send message to the subscriber"); } } diff --git a/devolutions-gateway/tests/config.rs b/devolutions-gateway/tests/config.rs index add876f81..67bbcca78 100644 --- a/devolutions-gateway/tests/config.rs +++ b/devolutions-gateway/tests/config.rs @@ -6,6 +6,7 @@ use std::str::FromStr as _; use devolutions_gateway::config::dto::*; use rstest::*; use tap::prelude::*; +use url::Url; use uuid::Uuid; struct Sample { @@ -93,6 +94,7 @@ fn hub_sample() -> Sample { verbosity_profile: Some(VerbosityProfile::Tls), web_app: None, ai_gateway: None, + proxy: None, debug: None, rest: Default::default(), }, @@ -138,6 +140,7 @@ fn legacy_sample() -> Sample { verbosity_profile: None, web_app: None, ai_gateway: None, + proxy: None, debug: None, rest: Default::default(), }, @@ -182,6 +185,7 @@ fn system_store_sample() -> Sample { verbosity_profile: None, web_app: None, ai_gateway: None, + proxy: None, debug: None, rest: Default::default(), }, @@ -258,6 +262,7 @@ fn standalone_custom_auth_sample() -> Sample { static_root_path: None, }), ai_gateway: None, + proxy: None, debug: None, rest: Default::default(), }, @@ -334,6 +339,87 @@ fn standalone_no_auth_sample() -> Sample { static_root_path: Some("/path/to/webapp/static/root".into()), }), ai_gateway: None, + proxy: None, + debug: None, + rest: Default::default(), + }, + } +} + +fn proxy_sample() -> Sample { + Sample { + json_repr: r#"{ + "Id": "123e4567-e89b-12d3-a456-426614174000", + "Hostname": "hostname.example.io", + "TlsPrivateKeyFile": "/path/to/tls-private.key", + "TlsCertificateFile": "/path/to/tls-certificate.pem", + "ProvisionerPublicKeyFile": "provisioner.pem", + "Listeners": [ + { + "InternalUrl": "tcp://*:8080", + "ExternalUrl": "tcp://*:8080" + }, + { + "InternalUrl": "http://*:7171", + "ExternalUrl": "https://*:7171" + } + ], + "Proxy": { + "Mode": "Manual", + "Http": "http://proxy.corp:8080", + "Exclude": ["localhost", ".internal.net", "192.168.1.0/24"] + } + }"#, + file_conf: ConfFile { + id: Some(Uuid::from_str("123e4567-e89b-12d3-a456-426614174000").unwrap()), + hostname: Some("hostname.example.io".to_owned()), + provisioner_public_key_file: Some("provisioner.pem".into()), + provisioner_public_key_data: None, + provisioner_private_key_file: None, + provisioner_private_key_data: None, + sub_provisioner_public_key: None, + delegation_private_key_file: None, + delegation_private_key_data: None, + tls_certificate_source: None, + tls_certificate_file: Some("/path/to/tls-certificate.pem".into()), + tls_private_key_file: Some("/path/to/tls-private.key".into()), + tls_private_key_password: None, + tls_certificate_subject_name: None, + tls_certificate_store_location: None, + tls_certificate_store_name: None, + tls_verify_strict: None, + listeners: vec![ + ListenerConf { + internal_url: "tcp://*:8080".to_owned(), + external_url: "tcp://*:8080".to_owned(), + }, + ListenerConf { + internal_url: "http://*:7171".to_owned(), + external_url: "https://*:7171".to_owned(), + }, + ], + subscriber: None, + log_file: None, + jrl_file: None, + plugins: None, + recording_path: None, + job_queue_database: None, + traffic_audit_database: None, + ngrok: None, + verbosity_profile: None, + web_app: None, + ai_gateway: None, + proxy: Some(ProxyConf { + mode: ProxyMode::Manual, + http: Some(Url::parse("http://proxy.corp:8080").unwrap()), + https: None, + all: None, + exclude: vec![ + "localhost".to_owned(), + ".internal.net".to_owned(), + "192.168.1.0/24".to_owned(), + ], + }), debug: None, rest: Default::default(), }, @@ -346,6 +432,7 @@ fn standalone_no_auth_sample() -> Sample { #[case(system_store_sample())] #[case(standalone_custom_auth_sample())] #[case(standalone_no_auth_sample())] +#[case(proxy_sample())] fn sample_parsing(#[case] sample: Sample) { let from_json = serde_json::from_str::(sample.json_repr) .unwrap() diff --git a/docs/COOKBOOK.md b/docs/COOKBOOK.md index 7b99394c3..324cbc467 100644 --- a/docs/COOKBOOK.md +++ b/docs/COOKBOOK.md @@ -7,6 +7,7 @@ Developer-oriented cookbook for testing purposes. - [Standalone web application custom authentication](#standalone-web-application-custom-authentication) - [Preflight API](#preflight-api) - [Network monitoring API](#network-monitoring-api) +- [HTTP/SOCKS proxy configuration](#httpsocks-proxy-configuration) - [Proxy-based credentials injection for RDP](#proxy-based-credentials-injection-for-rdp) - [Traffic Audit API](#traffic-audit-api) @@ -301,6 +302,189 @@ The response will look similar to this: Each log entry is only returned once. After you make this request, the existing log is deleted from memory. +## HTTP/SOCKS proxy configuration + +Devolutions Gateway and Devolutions Agent support HTTP/HTTPS/SOCKS proxy configuration for outbound requests. +Proxies are used for requests such as: +- Subscriber API notifications (Gateway) +- AI Gateway provider requests (Gateway) +- Update downloads and version checks (Agent) + +The proxy configuration supports three modes: +- **Off**: Never use a proxy, ignore environment variables +- **System**: Auto-detect proxy from environment variables or system settings (default) +- **Manual**: Use explicitly configured proxy URLs + +### System proxy detection (default) + +By default (`Mode: System`), proxy configuration is automatically detected from system settings: + +- **Environment variables**: HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY +- **Linux sysconfig**: `/etc/sysconfig/proxy` on RHEL/SUSE systems +- **Windows Registry**: Per-user and machine-wide settings with WinHTTP fallback +- **macOS System Configuration**: SCDynamicStoreCopyProxies() + +**Gateway configuration example:** +```json +{ + "Proxy": { + "Mode": "System" + } +} +``` + +**Agent configuration example:** +```json +{ + "Proxy": { + "Mode": "System" + } +} +``` + +Since `System` is the default mode, you can omit the `Proxy` section entirely to use system proxy detection. + +### Manual proxy configuration + +You can override auto-detection by setting `Mode: Manual` and specifying explicit proxy URLs. +The configuration supports protocol-specific proxies (`Http`, `Https`) and a fallback proxy (`All`). + +**Protocol-specific proxies:** +```json +{ + "Proxy": { + "Mode": "Manual", + "Http": "http://proxy.corp:8080", + "Https": "http://proxy.corp:8443" + } +} +``` + +**SOCKS5 proxy as fallback for all protocols:** +```json +{ + "Proxy": { + "Mode": "Manual", + "All": "socks5://proxy.corp:1080" + } +} +``` + +**Corporate proxy with authentication:** +```json +{ + "Proxy": { + "Mode": "Manual", + "Http": "http://username:password@proxy.corp:8080", + "Https": "http://username:password@proxy.corp:8080" + } +} +``` + +**Combined configuration with exclude list:** +```json +{ + "Proxy": { + "Mode": "Manual", + "Http": "http://proxy.corp:8080", + "Https": "http://proxy.corp:8080", + "Exclude": ["localhost", ".internal.net", "192.168.1.0/24"] + } +} +``` + +### Disabling proxy + +To completely disable proxy usage (ignoring system settings): + +```json +{ + "Proxy": { + "Mode": "Off" + } +} +``` + +### Exclusions + +**When using `Mode: Manual`**, specify exclusions in the `Exclude` array: + +```json +{ + "Proxy": { + "Mode": "Manual", + "Http": "http://proxy.corp:8080", + "Exclude": [ + "localhost", + ".internal.corp", + "devolutions.net", + "127.0.0.1", + "192.168.0.0/16" + ] + } +} +``` + +The `Exclude` list supports NO_PROXY semantics: +- Wildcard: `*` (bypass proxy for all targets) +- Exact hostname: `localhost`, `example.com` +- Domain suffix: `.corp.local` (matches `foo.corp.local`) +- IP address: `127.0.0.1` +- CIDR range: `10.0.0.0/8`, `192.168.0.0/16` + +### Proxy selection priority + +For `Mode: Manual`, the proxy is selected based on the target URL scheme: + +1. For `http://` requests → use `Http` proxy, fallback to `All` +2. For `https://` requests → use `Https` proxy, fallback to `All` +3. For other schemes → use `All` proxy + +If the target matches an entry in the `Exclude` list, no proxy is used. + +### Testing proxy configuration + +**Test Gateway subscriber notifications with a proxy:** + +1. Configure the proxy in `gateway.json`: +```json +{ + "Proxy": { + "Mode": "Manual", + "Https": "http://proxy.corp:8080" + }, + "Subscriber": { + "Url": "https://example.com/notifications", + "Token": "your-token-here" + } +} +``` + +2. Monitor Gateway logs for proxy-related messages: +```bash +tail -f /var/log/devolutions-gateway/gateway.log | grep -i proxy +``` + +**Test Agent updates with a proxy:** + +1. Configure the proxy in `agent.json`: +```json +{ + "Proxy": { + "Mode": "Manual", + "Https": "http://proxy.corp:8080" + }, + "Updater": { + "Enabled": true + } +} +``` + +2. Trigger an update check and monitor logs: +```bash +# View agent logs +tail -f /var/log/devolutions-agent/agent.log | grep -i "download\|proxy" +``` ## Proxy-based credentials injection for RDP