diff --git a/Cargo.lock b/Cargo.lock index caaee2cee..28442a894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,9 +355,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" dependencies = [ "aws-lc-sys", "zeroize", @@ -365,9 +365,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" dependencies = [ "cc", "cmake", @@ -389,9 +389,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "benches" @@ -499,9 +499,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" @@ -543,9 +543,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" +checksum = "00f4369ba008f82b968b1acbe31715ec37bd45236fa0726605a36cc3060ea256" [[package]] name = "calloop" @@ -590,9 +590,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -628,7 +628,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -711,9 +711,9 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -767,9 +767,9 @@ checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" [[package]] name = "convert_case" -version = "0.10.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" dependencies = [ "unicode-segmentation", ] @@ -808,7 +808,20 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] @@ -824,6 +837,17 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + [[package]] name = "coreaudio-rs" version = "0.13.0" @@ -1198,23 +1222,22 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", "syn", ] @@ -1351,23 +1374,22 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "drm" -version = "0.14.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" dependencies = [ "bitflags 2.10.0", "bytemuck", "drm-ffi", "drm-fourcc", - "libc", "rustix 0.38.44", ] [[package]] name = "drm-ffi" -version = "0.9.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" dependencies = [ "drm-sys", "rustix 0.38.44", @@ -1381,9 +1403,9 @@ checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "drm-sys" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" dependencies = [ "libc", "linux-raw-sys 0.6.5", @@ -1783,7 +1805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.2", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2129,9 +2151,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64", "bytes", @@ -2150,7 +2172,7 @@ dependencies = [ "tokio", "tower-service", "tracing", - "windows-registry", + "windows-registry 0.6.1", ] [[package]] @@ -2225,9 +2247,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -2239,9 +2261,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -2430,6 +2452,7 @@ dependencies = [ "ironrdp-connector", "ironrdp-core", "ironrdp-pdu", + "ironrdp-vmconnect", "tracing", ] @@ -2481,6 +2504,7 @@ dependencies = [ "ironrdp-rdpsnd-native", "ironrdp-tls", "ironrdp-tokio", + "ironrdp-vmconnect", "proc-exit", "raw-window-handle", "semver", @@ -2892,6 +2916,18 @@ dependencies = [ "url", ] +[[package]] +name = "ironrdp-vmconnect" +version = "0.1.0" +dependencies = [ + "arbitrary", + "ironrdp-connector", + "ironrdp-core", + "ironrdp-pdu", + "sspi", + "tracing", +] + [[package]] name = "ironrdp-web" version = "0.0.0" @@ -2913,6 +2949,7 @@ dependencies = [ "ironrdp-propertyset", "ironrdp-rdcleanpath", "ironrdp-rdpfile", + "ironrdp-vmconnect", "js-sys", "png", "resize", @@ -3003,9 +3040,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -3028,9 +3065,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" @@ -3039,7 +3076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -3061,13 +3098,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.5.18", ] [[package]] @@ -3122,9 +3159,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" @@ -3212,9 +3249,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", @@ -3242,9 +3279,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.11" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6" dependencies = [ "num-traits", "pxfm", @@ -3433,7 +3470,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", + "objc2-quartz-core", ] [[package]] @@ -3520,19 +3557,6 @@ dependencies = [ "objc2 0.6.3", ] -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.10.0", - "dispatch2", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-io-surface", -] - [[package]] name = "objc2-core-image" version = "0.2.2" @@ -3582,20 +3606,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.10.0", "objc2 0.6.3", - "objc2-core-foundation", ] [[package]] @@ -3635,18 +3646,6 @@ dependencies = [ "objc2-metal", ] -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-symbols" version = "0.2.2" @@ -3672,7 +3671,7 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core 0.2.2", + "objc2-quartz-core", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -3876,7 +3875,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4031,9 +4030,9 @@ dependencies = [ [[package]] name = "picky-krb" -version = "0.12.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed61c8d7448649c031ecae02afb10c679524c7a9af5fb0fbee466b3cc0d6df1" +checksum = "14b13eb1a97b2293277b475f07d0c36c33579e2e71f852557015addcd95f8892" dependencies = [ "aes", "block-buffer 0.11.0-rc.5", @@ -4315,9 +4314,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.27" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] @@ -4543,15 +4542,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "redox_syscall" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" -dependencies = [ - "bitflags 2.10.0", -] - [[package]] name = "regex" version = "1.12.2" @@ -4589,9 +4579,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -4817,9 +4807,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -5023,9 +5013,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -5152,9 +5142,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" @@ -5224,33 +5214,33 @@ dependencies = [ [[package]] name = "softbuffer" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "as-raw-xcb-connection", "bytemuck", + "cfg_aliases", + "core-graphics 0.24.0", "drm", "fastrand", + "foreign-types 0.5.0", "js-sys", + "log", "memmap2", - "ndk", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core", "raw-window-handle", "redox_syscall 0.5.18", - "rustix 1.1.2", + "rustix 0.38.44", "tiny-xlib", - "tracing", "wasm-bindgen", "wayland-backend", "wayland-client", "wayland-sys", "web-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", "x11rb", ] @@ -5285,9 +5275,9 @@ dependencies = [ [[package]] name = "sspi" -version = "0.18.5" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f73fe6be958ae27fa8e982d9acc42d16f34eb74714d95bb53015528667cae4" +checksum = "70f8d436177e1f17d2efb8ec1b661068ad04fd282544dfd8369522168300a82e" dependencies = [ "async-dnssd", "async-recursion", @@ -5336,6 +5326,7 @@ dependencies = [ "rustls", "rustls-native-certs", "serde", + "serde_derive", "sha1 0.11.0-rc.2", "sha2", "signature", @@ -5345,8 +5336,9 @@ dependencies = [ "tracing", "url", "uuid", - "windows 0.62.2", - "windows-registry", + "windows 0.61.3", + "windows-registry 0.5.3", + "windows-sys 0.60.2", "winscard", "zeroize", ] @@ -5730,9 +5722,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.9+spec-1.0.0" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", @@ -5745,18 +5737,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.4+spec-1.0.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -5766,18 +5758,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.5+spec-1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.5+spec-1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9cd6190959dce0994aa8970cd32ab116d1851ead27e866039acaf2524ce44fa" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tower" @@ -5796,9 +5788,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ "bitflags 2.10.0", "bytes", @@ -5826,9 +5818,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5838,9 +5830,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -5849,9 +5841,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -5870,9 +5862,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", @@ -6135,9 +6127,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -6148,9 +6140,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -6161,9 +6153,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6171,9 +6163,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", @@ -6184,9 +6176,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -6302,9 +6294,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -6387,16 +6379,38 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "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.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", + "windows-collections 0.3.2", "windows-core 0.62.2", - "windows-future", - "windows-numerics", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -6418,6 +6432,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -6426,9 +6453,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] @@ -6438,8 +6476,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link", - "windows-threading", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -6464,12 +6502,28 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -6477,7 +6531,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -6486,9 +6551,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", ] [[package]] @@ -6500,13 +6565,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -6515,7 +6598,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6569,7 +6652,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6624,7 +6707,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -6635,13 +6718,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +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", + "windows-link 0.2.1", ] [[package]] @@ -6840,7 +6932,7 @@ dependencies = [ "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics", + "core-graphics 0.23.2", "cursor-icon", "dpi", "js-sys", @@ -6878,9 +6970,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -6907,9 +6999,9 @@ dependencies = [ [[package]] name = "winscard" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b6ec4e6176df62589d1ac9950f6295be87ca06ee61a7c9a579a2bcc80efe34" +checksum = "b6a17695372a560c4f47f2e24e7754cdcc138fd6e62b8b54801d69f19a121a1c" dependencies = [ "bitflags 2.10.0", "crypto-bigint", @@ -7086,27 +7178,27 @@ dependencies = [ [[package]] name = "yuv" -version = "0.8.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f1bad143caadcfcaec93039dc9c40a30fc86f23d9e7cc03764a39fe51d9d43" +checksum = "30078f3e5790a2127f89c57c4ccb46d060205bf2c4c267f77cd08fb5c02c6d79" dependencies = [ "num-traits", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", diff --git a/crates/ironrdp-async/Cargo.toml b/crates/ironrdp-async/Cargo.toml index 1716abb4b..5896dc4af 100644 --- a/crates/ironrdp-async/Cargo.toml +++ b/crates/ironrdp-async/Cargo.toml @@ -17,6 +17,7 @@ test = false [dependencies] ironrdp-connector = { path = "../ironrdp-connector", version = "0.8" } # public +ironrdp-vmconnect = { path = "../ironrdp-vmconnect", version = "0.1" } # public ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["alloc"] } # public ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.6" } # public tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index 04f3d5a75..1bbb964a9 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -1,9 +1,9 @@ -use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig}; +use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig}; use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName, - State as _, + custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError, + ConnectorResult, SecurityConnector, ServerName, }; use ironrdp_core::WriteBuf; use tracing::{debug, info, instrument, trace}; @@ -15,7 +15,10 @@ use crate::{single_sequence_step, NetworkClient}; pub struct ShouldUpgrade; #[instrument(skip_all)] -pub async fn connect_begin(framed: &mut Framed, connector: &mut ClientConnector) -> ConnectorResult +pub async fn connect_begin( + framed: &mut Framed, + connector: &mut dyn ConnectorCore, +) -> ConnectorResult where S: Sync + FramedRead + FramedWrite, { @@ -33,7 +36,7 @@ where /// # Panics /// /// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade]. -pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade { +pub fn skip_connect_begin(connector: &mut dyn SecurityConnector) -> ShouldUpgrade { assert!(connector.should_perform_security_upgrade()); ShouldUpgrade } @@ -42,22 +45,27 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade { pub struct Upgraded; #[instrument(skip_all)] -pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded { +pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut dyn SecurityConnector) -> Upgraded { trace!("Marked as upgraded"); connector.mark_security_upgrade_as_done(); Upgraded } +#[non_exhaustive] +pub struct CredSSPFinished { + pub(crate) write_buf: WriteBuf, +} + #[instrument(skip_all)] -pub async fn connect_finalize( +pub async fn perform_credssp( _: Upgraded, - mut connector: ClientConnector, + connector: &mut dyn ConnectorCore, framed: &mut Framed, - network_client: &mut N, server_name: ServerName, server_public_key: Vec, + network_client: Option<&mut N>, kerberos_config: Option, -) -> ConnectorResult +) -> ConnectorResult where S: FramedRead + FramedWrite, N: NetworkClient, @@ -66,7 +74,7 @@ where if connector.should_perform_credssp() { perform_credssp_step( - &mut connector, + connector, framed, network_client, &mut buf, @@ -77,6 +85,19 @@ where .await?; } + Ok(CredSSPFinished { write_buf: buf }) +} + +#[instrument(skip_all)] +pub async fn connect_finalize( + CredSSPFinished { write_buf: mut buf }: CredSSPFinished, + framed: &mut Framed, + mut connector: ClientConnector, +) -> ConnectorResult +where + S: FramedRead + FramedWrite, +{ + buf.clear(); let result = loop { single_sequence_step(framed, &mut connector, &mut buf).await?; @@ -90,9 +111,9 @@ where Ok(result) } -async fn resolve_generator( +async fn resolve_generator( generator: &mut CredsspProcessGenerator<'_>, - network_client: &mut impl NetworkClient, + network_client: &mut N, ) -> ConnectorResult { let mut state = generator.start(); @@ -112,9 +133,9 @@ async fn resolve_generator( #[instrument(level = "trace", skip_all)] async fn perform_credssp_step( - connector: &mut ClientConnector, + connector: &mut dyn ConnectorCore, framed: &mut Framed, - network_client: &mut N, + mut network_client: Option<&mut N>, buf: &mut WriteBuf, server_name: ServerName, server_public_key: Vec, @@ -126,14 +147,13 @@ where { assert!(connector.should_perform_credssp()); - let selected_protocol = match connector.state { - ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol, - _ => return Err(general_err!("invalid connector state for CredSSP sequence")), - }; + let selected_protocol = connector + .selected_protocol() + .ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?; - let (mut sequence, mut ts_request) = CredsspSequence::init( - connector.config.credentials.clone(), - connector.config.domain.as_deref(), + let (mut sequence, mut ts_request) = connector.init_credssp( + connector.config().credentials.clone(), + connector.config().domain.as_deref(), selected_protocol, server_name, server_public_key, @@ -143,8 +163,15 @@ where loop { let client_state = { let mut generator = sequence.process_ts_request(ts_request); - trace!("resolving network"); - resolve_generator(&mut generator, network_client).await? + + if let Some(network_client_ref) = network_client.as_deref_mut() { + trace!("resolving network"); + resolve_generator(&mut generator, network_client_ref).await? + } else { + generator + .resolve_to_result() + .map_err(|e| custom_err!("resolve without network client", e))? + } }; // drop generator buf.clear(); @@ -164,7 +191,7 @@ where }; debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); diff --git a/crates/ironrdp-async/src/lib.rs b/crates/ironrdp-async/src/lib.rs index 847200c06..ec3500449 100644 --- a/crates/ironrdp-async/src/lib.rs +++ b/crates/ironrdp-async/src/lib.rs @@ -8,13 +8,14 @@ pub use bytes; mod connector; mod framed; mod session; +mod vmconnector; use ironrdp_connector::sspi::generator::NetworkRequest; use ironrdp_connector::ConnectorResult; pub use self::connector::*; pub use self::framed::*; -// pub use self::session::*; +pub use self::vmconnector::*; pub trait NetworkClient { fn send(&mut self, network_request: &NetworkRequest) -> impl Future>>; diff --git a/crates/ironrdp-async/src/vmconnector.rs b/crates/ironrdp-async/src/vmconnector.rs new file mode 100644 index 000000000..7b0996c53 --- /dev/null +++ b/crates/ironrdp-async/src/vmconnector.rs @@ -0,0 +1,57 @@ +use ironrdp_connector::{ClientConnector, ConnectorResult}; +use ironrdp_pdu::pcb::PcbVersion; +use ironrdp_vmconnect::VmClientConnector; +use tracing::info; + +use crate::{single_sequence_step, CredSSPFinished, Framed, FramedRead, FramedWrite}; + +#[non_exhaustive] +pub struct PcbSent; + +pub async fn send_pcb(framed: &mut Framed, payload: String) -> ConnectorResult +where + S: Sync + FramedRead + FramedWrite, +{ + let pcb_pdu = ironrdp_pdu::pcb::PreconnectionBlob { + id: 0, + version: PcbVersion::V2, + v2_payload: Some(payload), + }; + + let buf = ironrdp_core::encode_vec(&pcb_pdu) + .map_err(|e| ironrdp_connector::custom_err!("encode PreconnectionBlob PDU", e))?; + + framed + .write_all(&buf) + .await + .map_err(|e| ironrdp_connector::custom_err!("write PCB PDU", e))?; + + Ok(PcbSent) +} + +pub fn mark_pcb_sent_by_rdclean_path() -> PcbSent { + PcbSent +} + +pub fn vm_connector_take_over(_: PcbSent, connector: ClientConnector) -> ConnectorResult { + VmClientConnector::take_over(connector) +} + +pub async fn run_until_handover( + credssp_finished: &mut CredSSPFinished, + framed: &mut Framed, + mut connector: VmClientConnector, +) -> ConnectorResult { + let result = loop { + single_sequence_step(framed, &mut connector, &mut credssp_finished.write_buf).await?; + + if connector.should_hand_over() { + break connector.hand_over()?; + } + }; + + info!("Handover to client connector"); + credssp_finished.write_buf.clear(); + + Ok(result) +} diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index b50805aec..ff30df27c 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -1,12 +1,12 @@ use std::io::{Read, Write}; -use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig}; +use ironrdp_connector::credssp::{CredsspProcessGenerator, KerberosConfig}; use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::sspi::network_client::NetworkClient; use ironrdp_connector::{ - general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, - Sequence as _, ServerName, State as _, + general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorCore, ConnectorError, + ConnectorResult, CredsspSequenceFactory as _, SecurityConnector, Sequence, ServerName, }; use ironrdp_core::WriteBuf; use tracing::{debug, info, instrument, trace}; @@ -17,7 +17,7 @@ use crate::framed::Framed; pub struct ShouldUpgrade; #[instrument(skip_all)] -pub fn connect_begin(framed: &mut Framed, connector: &mut ClientConnector) -> ConnectorResult +pub fn connect_begin(framed: &mut Framed, connector: &mut dyn ConnectorCore) -> ConnectorResult where S: Sync + Read + Write, { @@ -35,7 +35,7 @@ where /// # Panics /// /// Panics if connector state is not [ClientConnectorState::EnhancedSecurityUpgrade]. -pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade { +pub fn skip_connect_begin(connector: &mut dyn SecurityConnector) -> ShouldUpgrade { assert!(connector.should_perform_security_upgrade()); ShouldUpgrade } @@ -44,7 +44,7 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade { pub struct Upgraded; #[instrument(skip_all)] -pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded { +pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut dyn SecurityConnector) -> Upgraded { trace!("Marked as upgraded"); connector.mark_security_upgrade_as_done(); Upgraded @@ -131,14 +131,13 @@ where { assert!(connector.should_perform_credssp()); - let selected_protocol = match connector.state { - ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol, - _ => return Err(general_err!("invalid connector state for CredSSP sequence")), - }; + let selected_protocol = connector + .selected_protocol() + .ok_or_else(|| general_err!("CredSSP protocol not selected, cannot perform CredSSP step"))?; - let (mut sequence, mut ts_request) = CredsspSequence::init( - connector.config.credentials.clone(), - connector.config.domain.as_deref(), + let (mut sequence, mut ts_request) = connector.init_credssp( + connector.config().credentials.clone(), + connector.config().domain.as_deref(), selected_protocol, server_name, server_public_key, @@ -167,7 +166,7 @@ where }; debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); @@ -192,7 +191,7 @@ where pub fn single_sequence_step( framed: &mut Framed, - connector: &mut ClientConnector, + connector: &mut dyn Sequence, buf: &mut WriteBuf, ) -> ConnectorResult<()> where @@ -202,7 +201,7 @@ where let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { debug!( - connector.state = connector.state.name(), + connector.state = connector.state().name(), hint = ?next_pdu_hint, "Wait for PDU" ); diff --git a/crates/ironrdp-client/Cargo.toml b/crates/ironrdp-client/Cargo.toml index 5a6844443..1402a3681 100644 --- a/crates/ironrdp-client/Cargo.toml +++ b/crates/ironrdp-client/Cargo.toml @@ -55,6 +55,7 @@ ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy" ironrdp-propertyset.path = "../ironrdp-propertyset" ironrdp-rdpfile.path = "../ironrdp-rdpfile" ironrdp-cfg.path = "../ironrdp-cfg" +ironrdp-vmconnect.path = "../ironrdp-vmconnect" # Windowing and rendering winit = { version = "0.30", features = ["rwh_06"] } diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index 3477c1802..99a4638eb 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -32,6 +32,8 @@ pub struct Config { /// server, which will be used for proxying DVC messages to/from user-defined DVC logic /// implemented as named pipe clients (either in the same process or in a different process). pub dvc_pipe_proxies: Vec, + + pub pcb: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -295,6 +297,36 @@ struct Args { /// `` will automatically be prefixed with `\\.\pipe\` on Windows. #[clap(long)] dvc_proxy: Vec, + + /// The ID for the HyperV VM server to connect to + #[clap(long, conflicts_with("pcb"))] + vmconnect: Option, + + /// Preconnection Blob payload to use + #[clap(long)] + pcb: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PreconnectionBlobPayload { + General(String), + VmConnect(String), +} + +impl PreconnectionBlobPayload { + pub fn general(&self) -> Option<&str> { + match self { + PreconnectionBlobPayload::General(pcb) => Some(pcb), + PreconnectionBlobPayload::VmConnect(_) => None, + } + } + + pub fn vmconnect(&self) -> Option<&str> { + match self { + PreconnectionBlobPayload::VmConnect(vm_id) => Some(vm_id), + PreconnectionBlobPayload::General(_) => None, + } + } } impl Config { @@ -470,6 +502,15 @@ impl Config { .zip(args.rdcleanpath_token) .map(|(url, auth_token)| RDCleanPathConfig { url, auth_token }); + let pcb = match (args.vmconnect, args.pcb) { + (Some(_), Some(_)) => { + unreachable!("Cannot use both `--vmconnect` and `--pcb` at the same time"); + } + (Some(vm_id), None) => Some(PreconnectionBlobPayload::VmConnect(vm_id.to_string())), + (None, Some(pcb)) => Some(PreconnectionBlobPayload::General(pcb)), + (None, None) => None, + }; + Ok(Self { log_file: args.log_file, gw, @@ -478,6 +519,7 @@ impl Config { clipboard_type, rdcleanpath, dvc_pipe_proxies: args.dvc_proxy, + pcb, }) } } diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a6693fba2..59265d9bb 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory}; use ironrdp::connector::connection_activation::ConnectionActivationState; -use ironrdp::connector::{ConnectionResult, ConnectorResult}; +use ironrdp::connector::{ClientConnector, ConnectionResult, ConnectorCore, ConnectorResult}; use ironrdp::displaycontrol::client::DisplayControlClient; use ironrdp::displaycontrol::pdu::MonitorLayoutEntry; use ironrdp::graphics::image_processing::PixelFormat; @@ -18,16 +18,22 @@ use ironrdp_core::WriteBuf; use ironrdp_dvc_pipe_proxy::DvcNamedPipeProxy; use ironrdp_rdpsnd_native::cpal; use ironrdp_tokio::reqwest::ReqwestNetworkClient; -use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite}; +use ironrdp_tokio::{ + mark_pcb_sent_by_rdclean_path, perform_credssp, run_until_handover, send_pcb, single_sequence_step_read, + split_tokio_framed, vm_connector_take_over, CredSSPFinished, Framed, FramedRead, FramedWrite, TokioStream, + Upgraded, +}; +use ironrdp_vmconnect::VmClientConnector; use rdpdr::NoopRdpdrBackend; use smallvec::SmallVec; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::TcpStream; use tokio::sync::mpsc; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, error, trace, warn}; use winit::event_loop::EventLoopProxy; +use x509_cert::der::asn1::OctetString; -use crate::config::{Config, RDCleanPathConfig}; +use crate::config::{Config, PreconnectionBlobPayload, RDCleanPathConfig}; #[derive(Debug)] pub enum RdpOutputEvent { @@ -209,51 +215,74 @@ async fn connect( drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name)); } - let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr) + let mut connector = ClientConnector::new(config.connector.clone(), client_addr) .with_static_channel(drdynvc) .with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new()))) .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); if let Some(builder) = cliprdr_factory { let backend = builder.build_cliprdr_backend(); - let cliprdr = cliprdr::Cliprdr::new(backend); - connector.attach_static_channel(cliprdr); } - let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?; - - debug!("TLS upgrade"); - - // Ensure there is no leftover - let (initial_stream, leftover_bytes) = framed.into_inner(); - - let (upgraded_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, config.destination.name()) - .await - .map_err(|e| connector::custom_err!("TLS upgrade", e))?; + let mut connector: Box = + if let Some(PreconnectionBlobPayload::VmConnect(vmconnect)) = &config.pcb { + let pcb_sent = send_pcb(&mut framed, vmconnect.to_owned()).await?; + let connector = vm_connector_take_over(pcb_sent, connector)?; + Box::new(connector) + } else { + Box::new(connector) + }; - let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector); + let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, connector.as_mut()).await?; + let (mut upgraded_framed, server_public_key) = upgrade(framed, config.destination.name()).await?; + let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut()); - let erased_stream: Box = Box::new(upgraded_stream); - let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); + let server_name = (&config.destination).into(); - let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert) - .ok_or_else(|| connector::general_err!("unable to extract tls server public key"))?; - let connection_result = ironrdp_tokio::connect_finalize( + let mut credssp_finished = perform_credssp( upgraded, - connector, + connector.as_mut(), &mut upgraded_framed, - &mut ReqwestNetworkClient::new(), - (&config.destination).into(), - server_public_key.to_owned(), + server_name, + server_public_key, + Some(&mut ReqwestNetworkClient::new()), None, ) .await?; + let connector = downcast_back_to_client_connector(connector, &mut credssp_finished, &mut upgraded_framed).await?; + let connection_result = ironrdp_tokio::connect_finalize(credssp_finished, &mut upgraded_framed, connector).await?; debug!(?connection_result); - Ok((connection_result, upgraded_framed)) + return Ok((connection_result, upgraded_framed)); + + async fn upgrade( + framed: Framed>, + server_name: &str, + ) -> ConnectorResult<( + Framed>>, + Vec, + )> + where + S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static, + { + let (initial_stream, leftover_bytes) = framed.into_inner(); + + let (upgraded_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, server_name) + .await + .map_err(|e| connector::custom_err!("TLS upgrade", e))?; + + let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert) + .ok_or_else(|| connector::general_err!("unable to extract tls server public key"))? + .to_owned(); + + let erased_stream: Box = Box::new(upgraded_stream); + let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); + + Ok((upgraded_framed, server_public_key)) + } } async fn connect_ws( @@ -302,7 +331,7 @@ async fn connect_ws( drdynvc = drdynvc.with_dynamic_channel(dvc_pipe_proxy_factory.create(channel_name, pipe_name)); } - let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr) + let mut connector = ClientConnector::new(config.connector.clone(), client_addr) .with_static_channel(drdynvc) .with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new()))) .with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0)); @@ -317,42 +346,48 @@ async fn connect_ws( let destination = format!("{}:{}", config.destination.name(), config.destination.port()); - let (upgraded, server_public_key) = connect_rdcleanpath( + let (upgraded, server_public_key, mut connector) = connect_rdcleanpath( &mut framed, - &mut connector, + connector, destination, rdcleanpath.auth_token.clone(), - None, + &config.pcb, ) .await?; - let connection_result = ironrdp_tokio::connect_finalize( + let (ws, leftover_bytes) = framed.into_inner(); + let erased_stream: Box = Box::new(ws); + let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); + + let server_name = (&config.destination).into(); + + let mut credssp_done = perform_credssp( upgraded, - connector, - &mut framed, - &mut ReqwestNetworkClient::new(), - (&config.destination).into(), + connector.as_mut(), + &mut upgraded_framed, + server_name, server_public_key, + Some(&mut ReqwestNetworkClient::new()), None, ) .await?; - let (ws, leftover_bytes) = framed.into_inner(); - let erased_stream: Box = Box::new(ws); - let upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); + let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut upgraded_framed).await?; + + let connection_result = ironrdp_tokio::connect_finalize(credssp_done, &mut upgraded_framed, connector).await?; Ok((connection_result, upgraded_framed)) } async fn connect_rdcleanpath( - framed: &mut ironrdp_tokio::Framed, - connector: &mut connector::ClientConnector, + framed: &mut Framed, + mut connector: ClientConnector, destination: String, proxy_auth_token: String, - pcb: Option, -) -> ConnectorResult<(ironrdp_tokio::Upgraded, Vec)> + pcb: &Option, +) -> ConnectorResult<(Upgraded, Vec, Box)> where - S: ironrdp_tokio::FramedRead + FramedWrite, + S: FramedRead + FramedWrite, { use ironrdp::connector::Sequence as _; use x509_cert::der::Decode as _; @@ -377,100 +412,121 @@ where let mut buf = WriteBuf::new(); - info!("Begin connection procedure"); + debug!(?pcb, "Begin connection procedure"); - { - // RDCleanPath request + // let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state() else { + // return Err(connector::general_err!("invalid connector state (send request)")); + // }; - let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else { - return Err(connector::general_err!("invalid connector state (send request)")); + debug_assert!(connector.next_pdu_hint().is_none()); + let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb { + let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( + None, + destination, + proxy_auth_token, + Some(vm_id.to_owned()), + ) + .map_err(|e| connector::custom_err!("new RDCleanPath request", e))?; + + debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect"); + + let pcb_sent = mark_pcb_sent_by_rdclean_path(); + let connector = vm_connector_take_over(pcb_sent, connector)?; + let connector: Box = Box::new(connector); + (rdcleanpath_req, connector) + } else { + let written = connector.step_no_input(&mut buf)?; + let x224_pdu_len = written.size().expect("written size"); + debug_assert_eq!(x224_pdu_len, buf.filled_len()); + let x224_pdu = buf.filled().to_vec(); + let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general()); + // RDCleanPath request + + let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( + Some(x224_pdu), + destination, + proxy_auth_token, + general_pcb.map(str::to_string), + ) + .map_err(|e| connector::custom_err!("new RDCleanPath request", e))?; + let connector: Box = Box::new(connector); + (rdcleanpath_req, connector) }; - debug_assert!(connector.next_pdu_hint().is_none()); - - let written = connector.step_no_input(&mut buf)?; - let x224_pdu_len = written.size().expect("written size"); - debug_assert_eq!(x224_pdu_len, buf.filled_len()); - let x224_pdu = buf.filled().to_vec(); - - let rdcleanpath_req = - ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb) - .map_err(|e| connector::custom_err!("new RDCleanPath request", e))?; - debug!(message = ?rdcleanpath_req, "Send RDCleanPath request"); - let rdcleanpath_req = rdcleanpath_req - .to_der() - .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; + let rdcleanpath_request = rdcleanpath_request + .to_der() + .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; - framed - .write_all(&rdcleanpath_req) - .await - .map_err(|e| connector::custom_err!("couldn't write RDCleanPath request", e))?; - } - - { - // RDCleanPath response + framed + .write_all(&rdcleanpath_request) + .await + .map_err(|e| connector::custom_err!("couldn't write RDCleanPath request", e))?; - let rdcleanpath_res = framed - .read_by_hint(&RDCLEANPATH_HINT) - .await - .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; + let rdcleanpath_result = framed + .read_by_hint(&RDCLEANPATH_HINT) + .await + .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) - .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; + let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result) + .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); + debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU"); - let (x224_connection_response, server_cert_chain) = match rdcleanpath_res - .into_enum() - .map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))? - { - ironrdp_rdcleanpath::RDCleanPath::Request { .. } => { - return Err(connector::general_err!( - "received an unexpected RDCleanPath type (request)", - )); - } - ironrdp_rdcleanpath::RDCleanPath::Response { - x224_connection_response, - server_cert_chain, - server_addr: _, - } => (x224_connection_response, server_cert_chain), - ironrdp_rdcleanpath::RDCleanPath::GeneralErr(error) => { - return Err(connector::custom_err!("received an RDCleanPath error", error)); - } - ironrdp_rdcleanpath::RDCleanPath::NegotiationErr { - x224_connection_response, - } => { - // Try to decode as X.224 Connection Confirm to extract negotiation failure details. - if let Ok(x224_confirm) = ironrdp_core::decode::< - ironrdp::pdu::x224::X224, - >(&x224_connection_response) - { - if let ironrdp::pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 { - // Convert to negotiation failure instead of generic RDCleanPath error. - let negotiation_failure = connector::NegotiationFailure::from(code); - return Err(connector::ConnectorError::new( - "RDP negotiation failed", - connector::ConnectorErrorKind::Negotiation(negotiation_failure), - )); - } + let (x224_connection_response, server_cert_chain) = match rdcleanpath_result + .into_enum() + .map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))? + { + ironrdp_rdcleanpath::RDCleanPath::Request { .. } => { + return Err(connector::general_err!( + "received an unexpected RDCleanPath type (request)", + )); + } + ironrdp_rdcleanpath::RDCleanPath::Response { + x224_connection_response, + server_cert_chain, + server_addr: _, + } => (x224_connection_response, server_cert_chain), + ironrdp_rdcleanpath::RDCleanPath::GeneralErr(error) => { + return Err(connector::custom_err!("received an RDCleanPath error", error)); + } + ironrdp_rdcleanpath::RDCleanPath::NegotiationErr { + x224_connection_response, + } => { + if let Ok(x224_confirm) = ironrdp_core::decode::< + ironrdp::pdu::x224::X224, + >(&x224_connection_response) + { + if let ironrdp::pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 { + let negotiation_failure = connector::NegotiationFailure::from(code); + return Err(connector::ConnectorError::new( + "RDP negotiation failed", + connector::ConnectorErrorKind::Negotiation(negotiation_failure), + )); } - - // Fallback to generic error if we can't decode the negotiation failure. - return Err(connector::general_err!("received an RDCleanPath negotiation error")); } - }; - let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else { - return Err(connector::general_err!("invalid connector state (wait confirm)")); - }; + return Err(connector::general_err!("received an RDCleanPath negotiation error")); + } + }; + buf.clear(); + if let Some(x224_connection_response) = x224_connection_response { debug_assert!(connector.next_pdu_hint().is_some()); - - buf.clear(); + // Write the X.224 connection response PDU let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?; - debug_assert!(written.is_nothing()); + } + + let server_public_key = extract_server_public_key(server_cert_chain)?; + + let should_upgrade = ironrdp_tokio::skip_connect_begin(connector.as_mut()); + + let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector.as_mut()); + + return Ok((upgraded, server_public_key, connector)); + fn extract_server_public_key(server_cert_chain: Vec) -> ConnectorResult> { let server_cert = server_cert_chain .into_iter() .next() @@ -487,13 +543,7 @@ where .ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))? .to_owned(); - let should_upgrade = ironrdp_tokio::skip_connect_begin(connector); - - // At this point, proxy established the TLS session. - - let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, connector); - - Ok((upgraded, server_public_key)) + Ok(server_public_key) } } @@ -689,3 +739,25 @@ async fn active_session( Ok(RdpControlFlow::TerminatedGracefully(disconnect_reason)) } + +pub async fn downcast_back_to_client_connector( + connector: Box, // `ConnectorCore: Any` + credssp_finished: &mut CredSSPFinished, + framed: &mut Framed, +) -> ConnectorResult { + let connector: Box = connector; + + let client = match connector.downcast::() { + Ok(vm_connector) => run_until_handover(credssp_finished, framed, *vm_connector).await?, + Err(err) => match err.downcast::() { + Ok(c) => *c, + Err(_) => { + return Err(connector::general_err!( + "connector is neither ClientConnector nor VmClientConnector" + )) + } + }, + }; + + Ok(client) +} diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 3018b1f7b..a1f6d10ac 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -7,14 +7,15 @@ use ironrdp_core::{decode, encode_vec, Encode, WriteBuf}; use ironrdp_pdu::x224::X224; use ironrdp_pdu::{gcc, mcs, nego, rdp, PduHint}; use ironrdp_svc::{StaticChannelSet, StaticVirtualChannel, SvcClientProcessor}; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, trace, warn}; use crate::channel_connection::{ChannelConnectionSequence, ChannelConnectionState}; use crate::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache}; use crate::{ - encode_x224_packet, general_err, reason_err, Config, ConnectorError, ConnectorErrorExt as _, ConnectorErrorKind, - ConnectorResult, DesktopSize, NegotiationFailure, Sequence, State, Written, + credssp, encode_x224_packet, general_err, reason_err, Config, ConnectorError, ConnectorErrorExt as _, + ConnectorErrorKind, ConnectorResult, CredsspSequenceFactory, DesktopSize, NegotiationFailure, SecurityConnector, + Sequence, State, Written, }; #[derive(Debug)] @@ -153,7 +154,6 @@ impl ClientConnector { { self.static_channels.insert(channel); } - pub fn get_static_channel_processor(&mut self) -> Option<&T> where T: SvcClientProcessor + 'static, @@ -171,33 +171,63 @@ impl ClientConnector { .get_by_type_mut::() .and_then(|channel| channel.channel_processor_downcast_mut()) } +} - pub fn should_perform_security_upgrade(&self) -> bool { +impl SecurityConnector for ClientConnector { + fn should_perform_security_upgrade(&self) -> bool { matches!(self.state, ClientConnectorState::EnhancedSecurityUpgrade { .. }) } - /// # Panics - /// - /// Panics if state is not [ClientConnectorState::EnhancedSecurityUpgrade]. - pub fn mark_security_upgrade_as_done(&mut self) { + fn mark_security_upgrade_as_done(&mut self) { assert!(self.should_perform_security_upgrade()); self.step(&[], &mut WriteBuf::new()).expect("transition to next state"); debug_assert!(!self.should_perform_security_upgrade()); } - pub fn should_perform_credssp(&self) -> bool { + fn should_perform_credssp(&self) -> bool { matches!(self.state, ClientConnectorState::Credssp { .. }) } - /// # Panics - /// - /// Panics if state is not [ClientConnectorState::Credssp]. - pub fn mark_credssp_as_done(&mut self) { + fn selected_protocol(&self) -> Option { + match &self.state { + ClientConnectorState::Credssp { selected_protocol } => Some(*selected_protocol), + _ => None, + } + } + + fn mark_credssp_as_done(&mut self) { assert!(self.should_perform_credssp()); let res = self.step(&[], &mut WriteBuf::new()).expect("transition to next state"); debug_assert!(!self.should_perform_credssp()); assert_eq!(res, Written::Nothing); } + + fn config(&self) -> &Config { + &self.config + } +} + +impl CredsspSequenceFactory for ClientConnector { + fn init_credssp( + &self, + credentials: crate::Credentials, + domain: Option<&str>, + protocol: nego::SecurityProtocol, + server_name: crate::ServerName, + server_public_key: Vec, + kerberos_config: Option, + ) -> ConnectorResult<(Box, sspi::credssp::TsRequest)> { + let (sequence, ts_request) = credssp::CredsspSequence::init( + credentials, + domain, + protocol, + server_name, + server_public_key, + kerberos_config, + )?; + + Ok((Box::new(sequence), ts_request)) + } } impl Sequence for ClientConnector { @@ -358,6 +388,8 @@ impl Sequence for ClientConnector { let written = encode_x224_packet(&connect_initial, output)?; + trace!(written, "Written"); + ( Written::from_size(written)?, ClientConnectorState::BasicSettingsExchangeWaitResponse { connect_initial }, diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 866ac5080..9b0c11bfb 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -5,8 +5,7 @@ use picky_asn1_x509::{oids, Certificate, ExtensionView, GeneralName}; use sspi::credssp::{self, ClientState, CredSspClient}; use sspi::generator::{Generator, NetworkRequest}; use sspi::negotiate::ProtocolConfig; -use sspi::Secret; -use sspi::Username; +use sspi::{Secret, Username}; use tracing::debug; use crate::{ @@ -77,21 +76,104 @@ pub struct CredsspSequence { } #[derive(Debug, PartialEq)] -pub(crate) enum CredsspState { +pub enum CredsspState { Ongoing, EarlyUserAuthResult, Finished, } -impl CredsspSequence { - pub fn next_pdu_hint(&self) -> Option<&dyn PduHint> { - match self.state { +pub trait CredsspSequenceTrait { + fn credssp_state(&self) -> &CredsspState; + + fn set_credssp_state(&mut self, state: CredsspState); + + fn next_pdu_hint(&self) -> Option<&dyn PduHint> { + match self.credssp_state() { CredsspState::Ongoing => Some(&CREDSSP_TS_REQUEST_HINT), CredsspState::EarlyUserAuthResult => Some(&CREDSSP_EARLY_USER_AUTH_RESULT_HINT), CredsspState::Finished => None, } } + fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult> { + match self.credssp_state() { + CredsspState::Ongoing => { + let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?; + debug!(?message, "Received"); + Ok(Some(message)) + } + CredsspState::EarlyUserAuthResult => { + let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input) + .map_err(|e| custom_err!("EarlyUserAuthResult", e))?; + + debug!(message = ?early_user_auth_result, "Received"); + + match early_user_auth_result { + credssp::EarlyUserAuthResult::Success => { + self.set_credssp_state(CredsspState::Finished); + Ok(None) + } + credssp::EarlyUserAuthResult::AccessDenied => { + Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied)) + } + } + } + _ => Err(general_err!( + "attempted to feed server request to CredSSP sequence in an unexpected state" + )), + } + } + + fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_>; + + fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult; +} + +impl CredsspSequenceTrait for CredsspSequence { + fn credssp_state(&self) -> &CredsspState { + &self.state + } + + fn set_credssp_state(&mut self, state: CredsspState) { + self.state = state; + } + + fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> { + self.client.process(request) + } + + fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult { + let (size, next_state) = match self.state { + CredsspState::Ongoing => { + let (ts_request_from_client, next_state) = match result { + ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing), + ClientState::FinalMessage(ts_request) => ( + ts_request, + if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) { + CredsspState::EarlyUserAuthResult + } else { + CredsspState::Finished + }, + ), + }; + + debug!(message = ?ts_request_from_client, "Send"); + + let written = write_credssp_request(ts_request_from_client, output)?; + + Ok((Written::from_size(written)?, next_state)) + } + CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)), + CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")), + }?; + + self.state = next_state; + + Ok(size) + } +} + +impl CredsspSequence { /// `server_name` must be the actual target server hostname (as opposed to the proxy) pub fn init( credentials: Credentials, @@ -175,71 +257,6 @@ impl CredsspSequence { Ok((sequence, initial_request)) } - - /// Returns Some(ts_request) when a TS request is received from server, - /// and None when an early user auth result PDU is received instead. - pub fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult> { - match self.state { - CredsspState::Ongoing => { - let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?; - debug!(?message, "Received"); - Ok(Some(message)) - } - CredsspState::EarlyUserAuthResult => { - let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input) - .map_err(|e| custom_err!("EarlyUserAuthResult", e))?; - - debug!(message = ?early_user_auth_result, "Received"); - - match early_user_auth_result { - credssp::EarlyUserAuthResult::Success => { - self.state = CredsspState::Finished; - Ok(None) - } - credssp::EarlyUserAuthResult::AccessDenied => { - Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied)) - } - } - } - _ => Err(general_err!( - "attempted to feed server request to CredSSP sequence in an unexpected state" - )), - } - } - - pub fn process_ts_request(&mut self, request: credssp::TsRequest) -> CredsspProcessGenerator<'_> { - self.client.process(request) - } - - pub fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult { - let (size, next_state) = match self.state { - CredsspState::Ongoing => { - let (ts_request_from_client, next_state) = match result { - ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing), - ClientState::FinalMessage(ts_request) => ( - ts_request, - if self.selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) { - CredsspState::EarlyUserAuthResult - } else { - CredsspState::Finished - }, - ), - }; - - debug!(message = ?ts_request_from_client, "Send"); - - let written = write_credssp_request(ts_request_from_client, output)?; - - Ok((Written::from_size(written)?, next_state)) - } - CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)), - CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")), - }?; - - self.state = next_state; - - Ok(size) - } } fn extract_user_name(cert: &Certificate) -> Option { diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 1e1a74020..ba6e37904 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -18,18 +18,20 @@ use core::fmt; use std::sync::Arc; use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf}; -use ironrdp_pdu::nego::NegoRequestData; +use ironrdp_pdu::nego::{NegoRequestData, SecurityProtocol}; use ironrdp_pdu::rdp::capability_sets::{self, BitmapCodecs}; use ironrdp_pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo}; use ironrdp_pdu::x224::X224; use ironrdp_pdu::{gcc, x224, PduHint}; pub use sspi; +use sspi::credssp::TsRequest; pub use self::channel_connection::{ChannelConnectionSequence, ChannelConnectionState}; pub use self::connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult}; pub use self::connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState}; pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState}; pub use self::server_name::ServerName; +use crate::credssp::{CredsspSequenceTrait, KerberosConfig}; pub use crate::license_exchange::LicenseCache; /// Provides user-friendly error messages for RDP negotiation failures @@ -433,3 +435,39 @@ where Ok(written) } + +pub trait SecurityConnector { + fn should_perform_security_upgrade(&self) -> bool; + + fn mark_security_upgrade_as_done(&mut self); + + fn should_perform_credssp(&self) -> bool; + + fn selected_protocol(&self) -> Option; + + fn mark_credssp_as_done(&mut self); + + fn config(&self) -> &Config; +} + +pub trait CredsspSequenceFactory { + fn init_credssp( + &self, + credentials: Credentials, + domain: Option<&str>, + protocol: SecurityProtocol, + server_name: ServerName, + server_public_key: Vec, + kerberos_config: Option, + ) -> ConnectorResult<(Box, TsRequest)>; +} + +pub trait ConnectorCore: Sequence + SecurityConnector + CredsspSequenceFactory + Any { + fn into_any(self: Box) -> Box; +} + +impl ConnectorCore for T { + fn into_any(self: Box) -> Box { + self + } +} diff --git a/crates/ironrdp-rdcleanpath/src/lib.rs b/crates/ironrdp-rdcleanpath/src/lib.rs index b884ba853..738e973b2 100644 --- a/crates/ironrdp-rdcleanpath/src/lib.rs +++ b/crates/ironrdp-rdcleanpath/src/lib.rs @@ -309,7 +309,7 @@ impl RDCleanPathPdu { } pub fn new_request( - x224_pdu: Vec, + x224_pdu: Option>, destination: String, proxy_auth: String, pcb: Option, @@ -319,19 +319,19 @@ impl RDCleanPathPdu { destination: Some(destination), proxy_auth: Some(proxy_auth), preconnection_blob: pcb, - x224_connection_pdu: Some(OctetString::new(x224_pdu)?), + x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?, ..Self::default() }) } pub fn new_response( server_addr: String, - x224_pdu: Vec, + x224_pdu: Option>, x509_chain: impl IntoIterator>, ) -> der::Result { Ok(Self { version: VERSION_1, - x224_connection_pdu: Some(OctetString::new(x224_pdu)?), + x224_connection_pdu: x224_pdu.map(OctetString::new).transpose()?, server_cert_chain: Some( x509_chain .into_iter() @@ -410,10 +410,10 @@ pub enum RDCleanPath { proxy_auth: String, server_auth: Option, preconnection_blob: Option, - x224_connection_request: OctetString, + x224_connection_request: Option, }, Response { - x224_connection_response: OctetString, + x224_connection_response: Option, server_cert_chain: Vec, server_addr: String, }, @@ -450,15 +450,11 @@ impl TryFrom for RDCleanPath { proxy_auth: pdu.proxy_auth.ok_or(MissingRDCleanPathField("proxy_auth"))?, server_auth: pdu.server_auth, preconnection_blob: pdu.preconnection_blob, - x224_connection_request: pdu - .x224_connection_pdu - .ok_or(MissingRDCleanPathField("x224_connection_pdu"))?, + x224_connection_request: pdu.x224_connection_pdu, } } else if let Some(server_addr) = pdu.server_addr { Self::Response { - x224_connection_response: pdu - .x224_connection_pdu - .ok_or(MissingRDCleanPathField("x224_connection_pdu"))?, + x224_connection_response: pdu.x224_connection_pdu, server_cert_chain: pdu .server_cert_chain .ok_or(MissingRDCleanPathField("server_cert_chain"))?, @@ -493,7 +489,7 @@ impl From for RDCleanPathPdu { proxy_auth: Some(proxy_auth), server_auth, preconnection_blob, - x224_connection_pdu: Some(x224_connection_request), + x224_connection_pdu: x224_connection_request, ..Default::default() }, RDCleanPath::Response { @@ -502,7 +498,7 @@ impl From for RDCleanPathPdu { server_addr, } => Self { version: VERSION_1, - x224_connection_pdu: Some(x224_connection_response), + x224_connection_pdu: x224_connection_response, server_cert_chain: Some(server_cert_chain), server_addr: Some(server_addr), ..Default::default() diff --git a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs index 9c4cf28d2..ae99aff33 100644 --- a/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs +++ b/crates/ironrdp-testsuite-core/tests/rdcleanpath.rs @@ -6,7 +6,7 @@ use rstest::rstest; fn request() -> RDCleanPathPdu { RDCleanPathPdu::new_request( - vec![0xDE, 0xAD, 0xBE, 0xFF], + Some(vec![0xDE, 0xAD, 0xBE, 0xFF]), "destination".to_owned(), "proxy auth".to_owned(), Some("PCB".to_owned()), @@ -23,7 +23,7 @@ const REQUEST_DER: &[u8] = &[ fn response_success() -> RDCleanPathPdu { RDCleanPathPdu::new_response( "192.168.7.95".to_owned(), - vec![0xDE, 0xAD, 0xBE, 0xFF], + Some(vec![0xDE, 0xAD, 0xBE, 0xFF]), [ vec![0xDE, 0xAD, 0xBE, 0xFF], vec![0xDE, 0xAD, 0xBE, 0xFF], diff --git a/crates/ironrdp-testsuite-extra/tests/mod.rs b/crates/ironrdp-testsuite-extra/tests/mod.rs index 8cc24fab1..017df627c 100644 --- a/crates/ironrdp-testsuite-extra/tests/mod.rs +++ b/crates/ironrdp-testsuite-extra/tests/mod.rs @@ -212,17 +212,21 @@ where let mut upgraded_framed = ironrdp_tokio::TokioFramed::new(upgraded_stream); let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert).expect("extract server public key"); - let connection_result = ironrdp_async::connect_finalize( + let credssp_finished = ironrdp_async::perform_credssp( upgraded, - connector, + &mut connector, &mut upgraded_framed, - &mut ironrdp_tokio::reqwest::ReqwestNetworkClient::new(), "localhost".into(), server_public_key.to_owned(), + Some(&mut ironrdp_tokio::reqwest::ReqwestNetworkClient::new()), None, ) .await - .expect("finalize connection"); + .expect("perform CredSSP"); + let connection_result = + ironrdp_async::connect_finalize(credssp_finished, &mut upgraded_framed, connector) + .await + .expect("finalize connection"); let active_stage = ActiveStage::new(connection_result); let (active_stage, mut upgraded_framed) = clientfn(active_stage, upgraded_framed, display_tx).await; diff --git a/crates/ironrdp-vmconnect/Cargo.toml b/crates/ironrdp-vmconnect/Cargo.toml new file mode 100644 index 000000000..e21d3e775 --- /dev/null +++ b/crates/ironrdp-vmconnect/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ironrdp-vmconnect" +version = "0.1.0" +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +authors.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +ironrdp-core = { path = "../ironrdp-core", version = "0.1" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.6", features = [ + "std", +] } # public + +arbitrary = { version = "1", features = ["derive"], optional = true } # public +tracing = { version = "0.1", features = ["log"] } +ironrdp-connector = { path = "../ironrdp-connector", version = "0.8" } # public + +sspi = "0.18" # public + +[lints] +workspace = true diff --git a/crates/ironrdp-vmconnect/src/config.rs b/crates/ironrdp-vmconnect/src/config.rs new file mode 100644 index 000000000..3be773fba --- /dev/null +++ b/crates/ironrdp-vmconnect/src/config.rs @@ -0,0 +1,60 @@ +use ironrdp_connector::general_err; +use ironrdp_pdu::nego::NegoRequestData; +use sspi::Username; + +#[derive(Debug, Clone)] +pub struct Credentials { + pub(crate) username: String, + pub(crate) password: String, +} + +impl Credentials { + pub(crate) fn to_sspi_auth_identity(&self, domain: Option<&str>) -> Result { + Ok(sspi::AuthIdentity { + username: Username::new(&self.username, domain)?, + password: self.password.clone().into(), + }) + } +} + +impl TryFrom<&ironrdp_connector::Credentials> for Credentials { + type Error = ironrdp_connector::ConnectorError; + + fn try_from(value: &ironrdp_connector::Credentials) -> Result { + let ironrdp_connector::Credentials::UsernamePassword { username, password } = value else { + return Err(general_err!("Invalid credentials type for VM connection",)); + }; + + Ok(Credentials { + username: username.to_owned(), + password: password.to_owned(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct VmConnectorConfig { + pub request_data: Option, + pub credentials: Credentials, +} + +impl TryFrom<&ironrdp_connector::Config> for VmConnectorConfig { + type Error = ironrdp_connector::ConnectorError; + + fn try_from(value: &ironrdp_connector::Config) -> Result { + let request_data = value.request_data.clone(); + let ironrdp_connector::Credentials::UsernamePassword { username, password } = &value.credentials else { + return Err(general_err!("Invalid credentials type for VM connection",)); + }; + + let credentials = Credentials { + username: username.to_owned(), + password: password.to_owned(), + }; + + Ok(VmConnectorConfig { + request_data, + credentials, + }) + } +} diff --git a/crates/ironrdp-vmconnect/src/connector.rs b/crates/ironrdp-vmconnect/src/connector.rs new file mode 100644 index 000000000..cc7c1def2 --- /dev/null +++ b/crates/ironrdp-vmconnect/src/connector.rs @@ -0,0 +1,248 @@ +use core::mem; + +use ironrdp_connector::{ + general_err, reason_err, ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorExt as _, + ConnectorResult, CredsspSequenceFactory, Sequence, State, Written, +}; +use ironrdp_core::{decode, WriteBuf}; +use ironrdp_pdu::nego::SecurityProtocol; +use ironrdp_pdu::x224::X224; +use ironrdp_pdu::{nego, PduHint}; +use tracing::{debug, error, info}; + +use crate::config::VmConnectorConfig; + +pub const HYPERV_SECURITY_PROTOCOL: SecurityProtocol = SecurityProtocol::HYBRID_EX + .union(SecurityProtocol::SSL) + .union(SecurityProtocol::HYBRID); + +#[derive(Default, Debug)] +#[non_exhaustive] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum VmConnectorState { + #[default] + Consumed, + EnhancedSecurityUpgrade, + Credssp, + ConnectionInitiationSendRequest, + ConnectionInitiationWaitConfirm, + Handover { + selected_protocol: SecurityProtocol, + }, +} + +impl State for VmConnectorState { + fn name(&self) -> &'static str { + match self { + Self::Consumed => "Consumed", + Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest", + Self::ConnectionInitiationWaitConfirm => "ConnectionInitiationWaitResponse", + Self::EnhancedSecurityUpgrade => "EnhancedSecurityUpgrade", + Self::Credssp => "Credssp", + Self::Handover { .. } => "Handover", + } + } + + fn is_terminal(&self) -> bool { + matches!(self, Self::Handover { .. }) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct VmClientConnector { + config: VmConnectorConfig, + state: VmConnectorState, + client_connector: ClientConnector, // hold it hostage, can't do anything with it until VMConnector handover +} + +impl Sequence for VmClientConnector { + fn next_pdu_hint(&self) -> Option<&dyn PduHint> { + match &self.state { + VmConnectorState::Consumed => None, + VmConnectorState::ConnectionInitiationSendRequest => None, + VmConnectorState::ConnectionInitiationWaitConfirm => Some(&ironrdp_pdu::X224_HINT), + VmConnectorState::EnhancedSecurityUpgrade => None, + VmConnectorState::Credssp => None, + VmConnectorState::Handover { .. } => None, + } + } + + fn state(&self) -> &dyn State { + &self.state + } + + fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult { + let (written, next_state) = match mem::take(&mut self.state) { + // Invalid state + VmConnectorState::Consumed => { + return Err(general_err!("connector sequence state is consumed (this is a bug)",)) + } + + //== Connection Initiation ==// + // Exchange supported security protocols and a few other connection flags. + VmConnectorState::EnhancedSecurityUpgrade => (Written::Nothing, VmConnectorState::Credssp), + + VmConnectorState::Credssp => (Written::Nothing, VmConnectorState::ConnectionInitiationSendRequest), + VmConnectorState::ConnectionInitiationSendRequest => { + debug!("Connection Initiation"); + + let connection_request = nego::ConnectionRequest { + nego_data: self + .config + .request_data + .clone() + .or_else(|| Some(nego::NegoRequestData::cookie(self.config.credentials.username.clone()))), + flags: nego::RequestFlags::empty(), + protocol: HYPERV_SECURITY_PROTOCOL, + }; + + debug!(message = ?connection_request, "Send"); + + let written = + ironrdp_core::encode_buf(&X224(connection_request), output).map_err(ConnectorError::encode)?; + + ( + Written::from_size(written)?, + VmConnectorState::ConnectionInitiationWaitConfirm, + ) + } + VmConnectorState::ConnectionInitiationWaitConfirm => { + let connection_confirm = decode::>(input) + .map_err(ConnectorError::decode) + .map(|p| p.0)?; + + debug!(message = ?connection_confirm, "Received"); + + let (flags, selected_protocol) = match connection_confirm { + nego::ConnectionConfirm::Response { flags, protocol } => (flags, protocol), + nego::ConnectionConfirm::Failure { code } => { + error!(?code, "Received connection failure code"); + return Err(reason_err!("Initiation", "{code}")); + } + }; + + info!(?selected_protocol, ?flags, "Server confirmed connection"); + + (Written::Nothing, VmConnectorState::Handover { selected_protocol }) + } + + VmConnectorState::Handover { .. } => { + return Err(general_err!( + "connector sequence state is already in handover (this is a bug)", + )); + } + }; + + self.state = next_state; + + Ok(written) + } +} + +impl VmClientConnector { + /// Takes over an existing `ClientConnector` and transitions it into a VM-specific connector. + /// + /// # Panics + /// + /// Panics if the provided `connector` is not in the + /// [`ClientConnectorState::ConnectionInitiationSendRequest`] state. + pub fn take_over(connector: ClientConnector) -> ConnectorResult { + assert!( + matches!(connector.state, ClientConnectorState::ConnectionInitiationSendRequest), + "Invalid connector state for VM connection, expected ConnectionInitiationSendRequest, got: {}", + connector.state.name() + ); + + debug!("Taking over VM connector"); + + let vm_connector_config = VmConnectorConfig::try_from(&connector.config)?; + let vm_connector = VmClientConnector { + config: vm_connector_config, + state: VmConnectorState::EnhancedSecurityUpgrade, + client_connector: connector, + }; + + Ok(vm_connector) + } + + pub fn should_hand_over(&self) -> bool { + matches!(self.state, VmConnectorState::Handover { .. }) + } + + /// Hands the underlying `ClientConnector` back once the VM-specific handshake is done. + pub fn hand_over(self) -> ConnectorResult { + let VmConnectorState::Handover { selected_protocol } = self.state else { + return Err(general_err!("Invalid state for handover, expected Handover")); + }; + let VmClientConnector { + mut client_connector, .. + } = self; + + client_connector.state = ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }; + + Ok(client_connector) + } +} + +impl ironrdp_connector::SecurityConnector for VmClientConnector { + fn should_perform_security_upgrade(&self) -> bool { + matches!(self.state, VmConnectorState::EnhancedSecurityUpgrade) + } + + fn mark_security_upgrade_as_done(&mut self) { + assert!(self.should_perform_security_upgrade()); + self.step(&[], &mut WriteBuf::new()).expect("transition to next state"); + debug_assert!(!self.should_perform_security_upgrade()); + } + + fn should_perform_credssp(&self) -> bool { + matches!(self.state, VmConnectorState::Credssp) + } + + fn selected_protocol(&self) -> Option { + if self.should_perform_credssp() { + Some(HYPERV_SECURITY_PROTOCOL) + } else { + None + } + } + + fn mark_credssp_as_done(&mut self) { + assert!(self.should_perform_credssp()); + self.step(&[], &mut WriteBuf::new()).expect("transition to next state"); + debug_assert!(!self.should_perform_credssp()); + } + + fn config(&self) -> &ironrdp_connector::Config { + self.client_connector.config() + } +} + +impl CredsspSequenceFactory for VmClientConnector { + fn init_credssp( + &self, + credentials: ironrdp_connector::Credentials, + domain: Option<&str>, + _protocol: SecurityProtocol, + server_name: ironrdp_connector::ServerName, + server_public_key: Vec, + _kerberos_config: Option, + ) -> ConnectorResult<( + Box, + sspi::credssp::TsRequest, + )> { + let credentials = crate::config::Credentials::try_from(&credentials)?; + + let (credssp, ts_request) = + crate::credssp::VmCredsspSequence::init(credentials, domain, server_name, server_public_key)?; + + let credssp: Box = Box::new(credssp); + + Ok((credssp, ts_request)) + } +} diff --git a/crates/ironrdp-vmconnect/src/credssp.rs b/crates/ironrdp-vmconnect/src/credssp.rs new file mode 100644 index 000000000..abda030dc --- /dev/null +++ b/crates/ironrdp-vmconnect/src/credssp.rs @@ -0,0 +1,115 @@ +use ironrdp_connector::credssp::{CredsspSequenceTrait, CredsspState}; +use ironrdp_connector::{ + custom_err, general_err, ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written, +}; +use ironrdp_core::WriteBuf; +use sspi::credssp::{self, ClientState, CredSspClient}; +use tracing::debug; + +use crate::config::Credentials; + +// pub type CredsspProcessGenerator<'a> = Generator<'a, NetworkRequest, sspi::Result>, sspi::Result>; + +#[derive(Debug)] +pub struct VmCredsspSequence { + client: CredSspClient, + state: CredsspState, +} + +impl CredsspSequenceTrait for VmCredsspSequence { + fn credssp_state(&self) -> &CredsspState { + &self.state + } + + fn set_credssp_state(&mut self, state: CredsspState) { + self.state = state; + } + + fn process_ts_request( + &mut self, + request: credssp::TsRequest, + ) -> ironrdp_connector::credssp::CredsspProcessGenerator<'_> { + self.client.process(request) + } + + fn handle_process_result(&mut self, result: ClientState, output: &mut WriteBuf) -> ConnectorResult { + let (size, next_state) = match self.state { + CredsspState::Ongoing => { + let (ts_request_from_client, next_state) = match result { + ClientState::ReplyNeeded(ts_request) => (ts_request, CredsspState::Ongoing), + ClientState::FinalMessage(ts_request) => (ts_request, CredsspState::Finished), + }; + + debug!(message = ?ts_request_from_client, "Send"); + + let written = write_credssp_request(ts_request_from_client, output)?; + + Ok((Written::from_size(written)?, next_state)) + } + CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)), + CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")), + }?; + + self.state = next_state; + + Ok(size) + } +} + +/// The main difference between this and the `credssp::CredsspSequence` is that this sequence uses NTLM only +/// No Kerberos or Negotiate, as Hyper-V does not support it +impl VmCredsspSequence { + /// `server_name` must be the actual target server hostname (as opposed to the proxy) + pub fn init( + credentials: Credentials, + domain: Option<&str>, + server_name: ServerName, + server_public_key: Vec, + ) -> ConnectorResult<(Self, credssp::TsRequest)> { + let credentials: sspi::Credentials = credentials + .to_sspi_auth_identity(domain) + .map_err(|e| custom_err!("Invalid username", e))? + .into(); + + let server_name = server_name.into_inner(); + + let service_principal_name = format!("TERMSRV/{}", &server_name); + + let credssp_config = Box::::default(); + debug!(?credssp_config); + + let client = CredSspClient::new( + server_public_key, + credentials, + credssp::CredSspMode::WithCredentials, + credssp::ClientMode::Ntlm(sspi::ntlm::NtlmConfig { + client_computer_name: Some(server_name), + }), + service_principal_name, + ) + .map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?; + + let sequence = Self { + client, + state: CredsspState::Ongoing, + }; + + let initial_request = credssp::TsRequest::default(); + + Ok((sequence, initial_request)) + } +} + +fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf) -> ConnectorResult { + let length = usize::from(ts_request.buffer_len()); + + let unfilled_buffer = output.unfilled_to(length); + + ts_request + .encode_ts_request(unfilled_buffer) + .map_err(|e| custom_err!("TsRequest", e))?; + + output.advance(length); + + Ok(length) +} diff --git a/crates/ironrdp-vmconnect/src/lib.rs b/crates/ironrdp-vmconnect/src/lib.rs new file mode 100644 index 000000000..cb7cb9da8 --- /dev/null +++ b/crates/ironrdp-vmconnect/src/lib.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod connector; +pub mod credssp; +pub use connector::*; diff --git a/crates/ironrdp-web/Cargo.toml b/crates/ironrdp-web/Cargo.toml index e99055535..d2253b10e 100644 --- a/crates/ironrdp-web/Cargo.toml +++ b/crates/ironrdp-web/Cargo.toml @@ -43,6 +43,7 @@ ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath" ironrdp-propertyset.path = "../ironrdp-propertyset" ironrdp-rdpfile.path = "../ironrdp-rdpfile" iron-remote-desktop.path = "../iron-remote-desktop" +ironrdp-vmconnect.path = "../ironrdp-vmconnect" # WASM wasm-bindgen = "0.2" diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 6d0e018ac..afa7f1177 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -17,7 +17,7 @@ use ironrdp::cliprdr::backend::ClipboardMessage; use ironrdp::cliprdr::CliprdrClient; use ironrdp::connector::connection_activation::ConnectionActivationState; use ironrdp::connector::credssp::KerberosConfig; -use ironrdp::connector::{self, ClientConnector, Credentials}; +use ironrdp::connector::{self, ClientConnector, ConnectorCore, Credentials}; use ironrdp::displaycontrol::client::DisplayControlClient; use ironrdp::dvc::DrdynvcClient; use ironrdp::graphics::image_processing::PixelFormat; @@ -27,13 +27,14 @@ use ironrdp::pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo}; use ironrdp::session::image::DecodedImage; use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason}; use ironrdp_core::WriteBuf; -use ironrdp_futures::{single_sequence_step_read, FramedWrite}; +use ironrdp_futures::{single_sequence_step_read, FramedRead, FramedWrite}; use rgb::AsPixels as _; use tap::prelude::*; use tracing::{debug, error, info, trace, warn}; use wasm_bindgen::JsValue; use wasm_bindgen_futures::spawn_local; use web_sys::HtmlCanvasElement; +use x509_cert::der::asn1::OctetString; use crate::canvas::Canvas; use crate::clipboard; @@ -56,7 +57,7 @@ struct SessionBuilderInner { password: Option, proxy_address: Option, auth_token: Option, - pcb: Option, + pcb: Option, kdc_proxy_url: Option, client_name: String, desktop_size: DesktopSize, @@ -72,6 +73,21 @@ struct SessionBuilderInner { outbound_message_size_limit: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PreconnectionBlobPayload { + General(String), + VmConnect(String), +} + +impl PreconnectionBlobPayload { + pub(crate) fn general(&self) -> Option<&str> { + match self { + PreconnectionBlobPayload::General(pcb) => Some(pcb), + PreconnectionBlobPayload::VmConnect(_) => None, + } + } +} + impl Default for SessionBuilderInner { fn default() -> Self { Self { @@ -192,6 +208,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder { /// Optional fn remote_clipboard_changed_callback(&self, callback: js_sys::Function) -> Self { + info!("Setting remote clipboard changed callback"); self.0.borrow_mut().remote_clipboard_changed_callback = Some(callback); self.clone() } @@ -210,7 +227,8 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder { fn extension(&self, ext: Extension) -> Self { iron_remote_desktop::extension_match! { match ext; - |pcb: String| { self.0.borrow_mut().pcb = Some(pcb) }; + |pcb: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::General(pcb)) }; + |vmconnect: String| { self.0.borrow_mut().pcb = Some(PreconnectionBlobPayload::VmConnect(vmconnect)) }; |kdc_proxy_url: String| { self.0.borrow_mut().kdc_proxy_url = Some(kdc_proxy_url) }; |display_control: bool| { self.0.borrow_mut().use_display_control = display_control }; |enable_credssp: bool| { self.0.borrow_mut().enable_credssp = enable_credssp }; @@ -273,6 +291,10 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder { .set_cursor_style_callback_context .clone() .context("set_cursor_style_callback_context missing")?; + info!( + "remote_clipboard_changed_callback = {:?}", + inner.remote_clipboard_changed_callback.is_some() + ); remote_clipboard_changed_callback = inner.remote_clipboard_changed_callback.clone(); force_clipboard_update_callback = inner.force_clipboard_update_callback.clone(); outbound_message_size_limit = inner.outbound_message_size_limit; @@ -939,7 +961,7 @@ struct ConnectParams { config: connector::Config, proxy_auth_token: String, destination: String, - pcb: Option, + pcb: Option, kdc_proxy_url: Option, clipboard_backend: Option, use_display_control: bool, @@ -974,16 +996,16 @@ async fn connect( ); } - let (upgraded, server_public_key) = - connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?; + let (upgraded, server_public_key, mut connector) = + connect_rdcleanpath(&mut framed, connector, destination.clone(), proxy_auth_token, &pcb).await?; - let connection_result = ironrdp_futures::connect_finalize( + let mut credssp_done = ironrdp_futures::perform_credssp( upgraded, - connector, + connector.as_mut(), &mut framed, - &mut WasmNetworkClient, (&destination).into(), server_public_key, + Some(&mut WasmNetworkClient), url::Url::parse(kdc_proxy_url.unwrap_or_default().as_str()) // if kdc_proxy_url does not exit, give url parser a empty string, it will fail anyway and map to a None .ok() .map(|url| KerberosConfig { @@ -995,6 +1017,9 @@ async fn connect( ) .await?; + let connector = downcast_back_to_client_connector(connector, &mut credssp_done, &mut framed).await?; + let connection_result = ironrdp_futures::connect_finalize(credssp_done, &mut framed, connector).await?; + let ws = framed.into_inner_no_leftover(); Ok((connection_result, ws)) @@ -1002,13 +1027,13 @@ async fn connect( async fn connect_rdcleanpath( framed: &mut ironrdp_futures::Framed, - connector: &mut ClientConnector, + mut connector: ClientConnector, destination: String, proxy_auth_token: String, - pcb: Option, -) -> Result<(ironrdp_futures::Upgraded, Vec), IronError> + pcb: &Option, +) -> connector::ConnectorResult<(ironrdp_futures::Upgraded, Vec, Box)> where - S: ironrdp_futures::FramedRead + FramedWrite, + S: FramedRead + FramedWrite, { use ironrdp::connector::Sequence as _; use x509_cert::der::Decode as _; @@ -1033,123 +1058,141 @@ where let mut buf = WriteBuf::new(); - info!("Begin connection procedure"); + debug!(?pcb, "Begin connection procedure"); - { - // RDCleanPath request + // let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state() else { + // return Err(connector::general_err!("invalid connector state (send request)")); + // }; - let connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else { - return Err(anyhow::Error::msg("invalid connector state (send request)").into()); - }; - - debug_assert!(connector.next_pdu_hint().is_none()); + debug_assert!(connector.next_pdu_hint().is_none()); + let (rdcleanpath_request, mut connector): (ironrdp_rdcleanpath::RDCleanPathPdu, Box) = + if let Some(PreconnectionBlobPayload::VmConnect(vm_id)) = pcb { + let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( + None, + destination, + proxy_auth_token, + Some(vm_id.to_owned()), + ) + .map_err(|e| connector::custom_err!("new RDCleanPath request", e))?; - let written = connector.step_no_input(&mut buf)?; - let x224_pdu_len = written.size().expect("written size"); - debug_assert_eq!(x224_pdu_len, buf.filled_len()); - let x224_pdu = buf.filled().to_vec(); + debug!(message = ?rdcleanpath_req, "Send RDCleanPath request for VMConnect"); - let rdcleanpath_req = - ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, pcb) - .context("new RDCleanPath request")?; - debug!(message = ?rdcleanpath_req, "Send RDCleanPath request"); - let rdcleanpath_req = rdcleanpath_req.to_der().context("RDCleanPath request encode")?; + let pcb_sent = ironrdp_futures::mark_pcb_sent_by_rdclean_path(); + let connector = ironrdp_futures::vm_connector_take_over(pcb_sent, connector)?; + let connector: Box = Box::new(connector); + (rdcleanpath_req, connector) + } else { + let written = connector.step_no_input(&mut buf)?; + let x224_pdu_len = written.size().expect("written size"); + debug_assert_eq!(x224_pdu_len, buf.filled_len()); + let x224_pdu = buf.filled().to_vec(); + let general_pcb = pcb.as_ref().and_then(|pcb| pcb.general()); + // RDCleanPath request + + let rdcleanpath_req = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( + Some(x224_pdu), + destination, + proxy_auth_token, + general_pcb.map(str::to_string), + ) + .map_err(|e| connector::custom_err!("new RDCleanPath request", e))?; + let connector: Box = Box::new(connector); + (rdcleanpath_req, connector) + }; - framed - .write_all(&rdcleanpath_req) - .await - .context("couldn't write RDCleanPath request")?; - } + let rdcleanpath_request = rdcleanpath_request + .to_der() + .map_err(|e| connector::custom_err!("RDCleanPath request encode", e))?; - { - // RDCleanPath response + framed + .write_all(&rdcleanpath_request) + .await + .map_err(|e| connector::custom_err!("couldn't write RDCleanPath request", e))?; - let rdcleanpath_res = framed - .read_by_hint(&RDCLEANPATH_HINT) - .await - .context("read RDCleanPath request")?; + let rdcleanpath_result = framed + .read_by_hint(&RDCLEANPATH_HINT) + .await + .map_err(|e| connector::custom_err!("read RDCleanPath request", e))?; - let rdcleanpath_res = - ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res).context("RDCleanPath response decode")?; + let rdcleanpath_result = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_result) + .map_err(|e| connector::custom_err!("RDCleanPath response decode", e))?; - debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); + debug!(message = ?rdcleanpath_result, "Received RDCleanPath PDU"); - let (x224_connection_response, server_cert_chain) = - match rdcleanpath_res.into_enum().context("invalid RDCleanPath PDU")? { - ironrdp_rdcleanpath::RDCleanPath::Request { .. } => { - return Err(anyhow::Error::msg("received an unexpected RDCleanPath type (request)").into()); - } - ironrdp_rdcleanpath::RDCleanPath::Response { - x224_connection_response, - server_cert_chain, - server_addr: _, - } => (x224_connection_response, server_cert_chain), - ironrdp_rdcleanpath::RDCleanPath::GeneralErr(error) => { - return Err( - IronError::from(anyhow::Error::new(error).context("received an RDCleanPath error")) - .with_kind(IronErrorKind::RDCleanPath), - ); + let (x224_connection_response, server_cert_chain) = match rdcleanpath_result + .into_enum() + .map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))? + { + ironrdp_rdcleanpath::RDCleanPath::Request { .. } => { + return Err(connector::general_err!( + "received an unexpected RDCleanPath type (request)", + )); + } + ironrdp_rdcleanpath::RDCleanPath::Response { + x224_connection_response, + server_cert_chain, + server_addr: _, + } => (x224_connection_response, server_cert_chain), + ironrdp_rdcleanpath::RDCleanPath::GeneralErr(error) => { + return Err(connector::custom_err!("received an RDCleanPath error", error)); + } + ironrdp_rdcleanpath::RDCleanPath::NegotiationErr { + x224_connection_response, + } => { + if let Ok(x224_confirm) = ironrdp_core::decode::< + ironrdp::pdu::x224::X224, + >(&x224_connection_response) + { + if let ironrdp::pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 { + let negotiation_failure = connector::NegotiationFailure::from(code); + return Err(connector::ConnectorError::new( + "RDP negotiation failed", + connector::ConnectorErrorKind::Negotiation(negotiation_failure), + )); } - ironrdp_rdcleanpath::RDCleanPath::NegotiationErr { - x224_connection_response, - } => { - // Try to decode as X.224 Connection Confirm to extract negotiation failure details. - if let Ok(x224_confirm) = ironrdp_core::decode::< - ironrdp::pdu::x224::X224, - >(&x224_connection_response) - { - if let ironrdp::pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 { - // Convert to negotiation failure instead of generic RDCleanPath error. - let negotiation_failure = connector::NegotiationFailure::from(code); - return Err(IronError::from( - anyhow::Error::new(negotiation_failure).context("RDP negotiation failed"), - ) - .with_kind(IronErrorKind::NegotiationFailure)); - } - } + } - // Fallback to generic error if we can't decode the negotiation failure. - return Err( - IronError::from(anyhow::Error::msg("received an RDCleanPath negotiation error")) - .with_kind(IronErrorKind::RDCleanPath), - ); - } - }; + return Err(connector::general_err!("received an RDCleanPath negotiation error")); + } + }; - let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else { - return Err(anyhow::Error::msg("invalid connector state (wait confirm)").into()); - }; + // let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state() else { + // return Err(connector::general_err!("invalid connector state (wait confirm)")); + // }; + buf.clear(); + if let Some(x224_connection_response) = x224_connection_response { debug_assert!(connector.next_pdu_hint().is_some()); - - buf.clear(); let written = connector.step(x224_connection_response.as_bytes(), &mut buf)?; - debug_assert!(written.is_nothing()); + } + + let server_public_key = extract_server_public_key(server_cert_chain)?; + + let should_upgrade = ironrdp_futures::skip_connect_begin(connector.as_mut()); + let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector.as_mut()); + + return Ok((upgraded, server_public_key, connector)); + + fn extract_server_public_key(server_cert_chain: Vec) -> connector::ConnectorResult> { let server_cert = server_cert_chain .into_iter() .next() - .context("server cert chain missing from rdcleanpath response")?; + .ok_or_else(|| connector::general_err!("server cert chain missing from rdcleanpath response"))?; let cert = x509_cert::Certificate::from_der(server_cert.as_bytes()) - .context("failed to decode x509 certificate sent by proxy")?; + .map_err(|e| connector::custom_err!("server cert chain missing from rdcleanpath response", e))?; let server_public_key = cert .tbs_certificate .subject_public_key_info .subject_public_key .as_bytes() - .context("subject public key BIT STRING is not aligned")? + .ok_or_else(|| connector::general_err!("subject public key BIT STRING is not aligned"))? .to_owned(); - let should_upgrade = ironrdp_futures::skip_connect_begin(connector); - - // At this point, proxy established the TLS session. - - let upgraded = ironrdp_futures::mark_as_upgraded(should_upgrade, connector); - - Ok((upgraded, server_public_key)) + Ok(server_public_key) } } @@ -1157,3 +1200,25 @@ where fn f64_to_u16_saturating_cast(value: f64) -> u16 { value as u16 } + +async fn downcast_back_to_client_connector( + connector: Box, // `ConnectorCore: Any` + credssp_finished: &mut ironrdp_futures::CredSSPFinished, + framed: &mut ironrdp_futures::Framed, +) -> connector::ConnectorResult { + let connector: Box = connector; + + let client = match connector.downcast::() { + Ok(vm_connector) => ironrdp_futures::run_until_handover(credssp_finished, framed, *vm_connector).await?, + Err(err) => match err.downcast::() { + Ok(c) => *c, + Err(_) => { + return Err(connector::general_err!( + "connector is neither ClientConnector nor VmClientConnector" + )) + } + }, + }; + + Ok(client) +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 332177af9..9b2639101 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -8,7 +8,7 @@ pub mod ffi { use core::fmt::Write as _; use diplomat_runtime::DiplomatWriteable; - use ironrdp::connector::Sequence as _; + use ironrdp::connector::{SecurityConnector as _, Sequence as _}; use ironrdp::displaycontrol::client::DisplayControlClient; use ironrdp::dvc::DvcProcessor; use ironrdp_dvc_pipe_proxy::DvcNamedPipeProxy; diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index b912d2685..a9ffe8b8d 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -4,6 +4,7 @@ pub mod network; #[diplomat::bridge] pub mod ffi { + use ironrdp::connector::credssp::CredsspSequenceTrait as _; use ironrdp::connector::ClientConnectorState; use super::network::ffi::{ClientState, CredsspProcessGenerator}; diff --git a/ffi/src/rdcleanpath.rs b/ffi/src/rdcleanpath.rs index a842c747b..7f426424d 100644 --- a/ffi/src/rdcleanpath.rs +++ b/ffi/src/rdcleanpath.rs @@ -29,7 +29,7 @@ pub mod ffi { let pcb_opt = if pcb.is_empty() { None } else { Some(pcb.to_owned()) }; let pdu = ironrdp_rdcleanpath::RDCleanPathPdu::new_request( - x224_pdu.to_vec(), + Some(x224_pdu.to_vec()), destination.to_owned(), proxy_auth.to_owned(), pcb_opt, diff --git a/web-client/iron-remote-desktop-rdp/src/main.ts b/web-client/iron-remote-desktop-rdp/src/main.ts index b5b0200e2..e289cdc76 100644 --- a/web-client/iron-remote-desktop-rdp/src/main.ts +++ b/web-client/iron-remote-desktop-rdp/src/main.ts @@ -43,3 +43,7 @@ export function outboundMessageSizeLimit(limit: number): Extension { export function enableCredssp(enable: boolean): Extension { return new Extension('enable_credssp', enable); } + +export function vmConnect(vm_id: string): Extension { + return new Extension('vmconnect', vm_id); +} diff --git a/web-client/iron-svelte-client/src/lib/login/login.svelte b/web-client/iron-svelte-client/src/lib/login/login.svelte index 78a823edf..21c56e4ec 100644 --- a/web-client/iron-svelte-client/src/lib/login/login.svelte +++ b/web-client/iron-svelte-client/src/lib/login/login.svelte @@ -2,7 +2,13 @@ import { currentSession, setCurrentSessionActive, userInteractionService } from '../../services/session.service'; import type { IronError, UserInteraction } from '../../../static/iron-remote-desktop'; import type { Session } from '../../models/session'; - import { preConnectionBlob, displayControl, kdcProxyUrl, init } from '../../../static/iron-remote-desktop-rdp'; + import { + preConnectionBlob, + displayControl, + kdcProxyUrl, + init, + vmConnect, + } from '../../../static/iron-remote-desktop-rdp'; import { toast } from '$lib/messages/message-store'; import { showLogin } from '$lib/login/login-store'; import { onMount } from 'svelte'; @@ -16,6 +22,7 @@ let kdc_proxy_url = ''; let desktopSize = { width: 1280, height: 720 }; let pcb = ''; + let vmconnect = ''; let pop_up = false; let enable_clipboard = true; @@ -119,6 +126,10 @@ configBuilder.withExtension(preConnectionBlob(pcb)); } + if (vmconnect !== '') { + configBuilder.withExtension(vmConnect(vmconnect)); + } + if (kdc_proxy_url !== '') { configBuilder.withExtension(kdcProxyUrl(kdc_proxy_url)); } @@ -171,6 +182,46 @@ }; onMount(async () => { + const envGateway = import.meta.env.VITE_IRON_GATEWAY_ADDRESS as string | undefined; + if (envGateway !== undefined && envGateway.trim() !== '') { + gatewayAddress = envGateway; + } + + const envHostname = import.meta.env.VITE_IRON_HOSTNAME as string | undefined; + if (envHostname !== undefined && envHostname.trim() !== '') { + hostname = envHostname; + } + + const envUsername = import.meta.env.VITE_IRON_USERNAME as string | undefined; + if (envUsername !== undefined && envUsername.trim() !== '') { + username = envUsername; + } + + const envPassword = import.meta.env.VITE_IRON_PASSWORD as string | undefined; + if (envPassword !== undefined && envPassword.trim() !== '') { + password = envPassword; + } + + const envDomain = import.meta.env.VITE_IRON_DOMAIN as string | undefined; + if (envDomain !== undefined && envDomain.trim() !== '') { + domain = envDomain; + } + + const envKdcProxy = import.meta.env.VITE_IRON_KDC_PROXY_URL as string | undefined; + if (envKdcProxy !== undefined && envKdcProxy.trim() !== '') { + kdc_proxy_url = envKdcProxy; + } + + const envPcb = import.meta.env.VITE_IRON_RDP_PCB as string | undefined; + if (envPcb !== undefined && envPcb.trim() !== '') { + pcb = envPcb; + } + + const envVmconnect = import.meta.env.VITE_IRON_VMCONNECT_ID as string | undefined; + if (envVmconnect !== undefined && envVmconnect.trim() !== '') { + vmconnect = envVmconnect; + } + await init('INFO'); }); @@ -212,6 +263,10 @@ +
+ + +