diff --git a/Cargo.lock b/Cargo.lock index 7a05ebb9..c5124743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.6", + "generic-array", +] + [[package]] name = "aes" version = "0.8.4" @@ -28,6 +38,29 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-kw" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +dependencies = [ + "aes", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -95,9 +128,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.2" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" +checksum = "4cd755adf9707cf671e31d944a189be3deaaeee11c8bc1d669bb8022ac90fbd0" dependencies = [ "aws-lc-sys", "paste", @@ -106,9 +139,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491" +checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" dependencies = [ "bindgen", "cc", @@ -210,6 +243,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -248,9 +290,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.12" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "jobserver", "libc", @@ -284,7 +326,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "inout", ] @@ -301,19 +343,34 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.53" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f" + [[package]] name = "core-foundation" version = "0.10.0" @@ -360,7 +417,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf1e6e5492f8f0830c37f301f6349e0dac8b2466e4fe89eef90e9eef906cd046" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", ] [[package]] @@ -382,9 +439,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa" +dependencies = [ + "getrandom 0.2.15", + "hybrid-array", + "rand_core", +] + [[package]] name = "crypto-mac" version = "0.11.0" @@ -395,6 +464,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -404,7 +482,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -424,9 +502,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "delog" @@ -443,7 +521,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -472,9 +550,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-pre.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" +dependencies = [ + "block-buffer 0.11.0-rc.3", + "const-oid 0.10.0-rc.3", + "crypto-common 0.2.0-rc.1", "subtle", ] @@ -489,6 +579,55 @@ dependencies = [ "syn", ] +[[package]] +name = "dpapi" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "aes-kw", + "bitflags 2.8.0", + "byteorder", + "concat-kdf", + "digest 0.11.0-pre.9", + "elliptic-curve", + "hmac 0.12.1", + "hmac 0.13.0-pre.4", + "kbkdf", + "num-bigint-dig", + "num-derive", + "num-traits", + "p256", + "p384", + "p521", + "paste", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "rand", + "regex", + "sha1 0.10.6", + "sha1 0.11.0-pre.4", + "sha2 0.10.8", + "sha2 0.11.0-pre.4", + "sspi", + "thiserror 2.0.11", + "tracing", + "typenum", + "url", + "uuid", + "whoami", +] + +[[package]] +name = "dpapi-cli-client" +version = "0.1.0" +dependencies = [ + "dpapi", + "tracing-subscriber", + "xflags", +] + [[package]] name = "dunce" version = "1.0.5" @@ -502,7 +641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -529,7 +668,7 @@ dependencies = [ "ed25519", "rand_core", "serde", - "sha2", + "sha2 0.10.8", "subtle", "zeroize", ] @@ -548,7 +687,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -766,6 +905,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -825,9 +974,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.24.2" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", "cfg-if", @@ -874,7 +1023,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -883,7 +1032,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58" +dependencies = [ + "digest 0.11.0-pre.9", ] [[package]] @@ -946,6 +1104,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.6.0" @@ -1143,9 +1310,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array", @@ -1222,6 +1389,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kbkdf" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b758ac9cc629a963ae38718148729d65d4e401f0e516862fa7820f6b76666aa0" +dependencies = [ + "digest 0.11.0-pre.9", +] + [[package]] name = "keccak" version = "0.1.5" @@ -1248,9 +1424,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -1309,9 +1485,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "lru-cache" @@ -1344,7 +1520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -1353,7 +1529,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1376,9 +1552,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", ] @@ -1508,9 +1684,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -1533,19 +1715,19 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.8", ] [[package]] name = "p384" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -1559,7 +1741,7 @@ dependencies = [ "elliptic-curve", "primeorder", "rand_core", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -1597,9 +1779,9 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", - "hmac", - "sha1", + "digest 0.10.7", + "hmac 0.12.1", + "sha1 0.10.6", ] [[package]] @@ -1624,7 +1806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b11f32016338b5bdb81179ec286a7d466041c27c5d45810da090c08ae451925" dependencies = [ "base64", - "digest", + "digest 0.10.7", "ed25519-dalek", "hex", "md-5", @@ -1639,8 +1821,8 @@ dependencies = [ "rand_core", "rsa", "serde", - "sha1", - "sha2", + "sha1 0.10.6", + "sha2 0.10.8", "sha3", "thiserror 1.0.69", "x25519-dalek", @@ -1689,16 +1871,16 @@ dependencies = [ [[package]] name = "picky-krb" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fae52a9b162382374ce33a2d0dc71a0f5f00a656ba18a97cc4326cb4401afd" +checksum = "322b38c31330193b1d428df5b88f1b0d3d9e91548d302844c4f221dd839b1a7d" dependencies = [ "aes", "byteorder", "cbc", "crypto", "des", - "hmac", + "hmac 0.12.1", "num-bigint-dig", "oid", "pbkdf2", @@ -1707,7 +1889,7 @@ dependencies = [ "picky-asn1-x509", "rand", "serde", - "sha1", + "sha1 0.10.6", "thiserror 1.0.69", "uuid", ] @@ -1751,6 +1933,18 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portpicker" version = "0.1.1" @@ -1839,7 +2033,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "socket2", "thiserror 2.0.11", @@ -1857,7 +2051,7 @@ dependencies = [ "getrandom 0.2.15", "rand", "ring", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", @@ -1869,9 +2063,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ "cfg_aliases", "libc", @@ -1931,9 +2125,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags 2.8.0", ] @@ -2042,21 +2236,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -2067,15 +2260,15 @@ version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core", - "sha1", + "sha1 0.10.6", "signature", "spki", "subtle", @@ -2096,9 +2289,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2124,9 +2317,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.22" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "aws-lc-rs", "log", @@ -2293,9 +2486,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -2323,7 +2516,18 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9540978cef7a8498211c1b1c14e5ce920fe5bd524ea84f4a3d72d4602515ae93" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-pre.9", ] [[package]] @@ -2334,7 +2538,18 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-pre.9", ] [[package]] @@ -2343,7 +2558,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -2368,7 +2583,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -2383,9 +2598,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -2429,7 +2644,7 @@ dependencies = [ "crypto-mac", "futures", "hickory-resolver", - "hmac", + "hmac 0.12.1", "lazy_static", "md-5", "md4", @@ -2451,8 +2666,8 @@ dependencies = [ "rustls-native-certs", "serde", "serde_derive", - "sha1", - "sha2", + "sha1 0.10.6", + "sha2 0.10.8", "static_assertions", "time", "tokio", @@ -2474,6 +2689,7 @@ version = "0.0.0" dependencies = [ "bitflags 2.8.0", "cfg-if", + "dpapi", "ffi-types", "libc", "num-traits", @@ -2545,9 +2761,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.16.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", @@ -2787,9 +3003,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unarray" @@ -2799,9 +3015,19 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.6", + "subtle", +] [[package]] name = "untrusted" @@ -2839,7 +3065,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" dependencies = [ "getrandom 0.3.1", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -3059,8 +3287,8 @@ checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ "windows-implement", "windows-interface", - "windows-result 0.3.0", - "windows-strings 0.3.0", + "windows-result 0.3.1", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -3086,6 +3314,12 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-registry" version = "0.2.0" @@ -3108,11 +3342,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" dependencies = [ - "windows-targets 0.53.0", + "windows-link", ] [[package]] @@ -3127,11 +3361,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-targets 0.53.0", + "windows-link", ] [[package]] @@ -3384,7 +3618,7 @@ dependencies = [ "rand", "rand_core", "rsa", - "sha1", + "sha1 0.10.6", "time", "tracing", "uuid", @@ -3423,6 +3657,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xflags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9e15fbb3de55454b0106e314b28e671279009b363e6f1d8e39fdc3bf048944" +dependencies = [ + "xflags-macros", +] + +[[package]] +name = "xflags-macros" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "672423d4fea7ffa2f6c25ba60031ea13dc6258070556f125cc4d790007d4a155" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index db3ed594..86c66ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ members = [ "ffi/symbol-rename-macro", "crates/winscard", "crates/ffi-types", + "crates/dpapi", + "crates/dpapi-cli-client", ] exclude = [ "tools/wasm-testcompile", @@ -33,20 +35,28 @@ rand = "0.8" cfg-if = "1" time = { version = "0.3", default-features = false } sha1 = { version = "0.10", default-features = false } +sha2 = "0.10" num-derive = "0.4" num-traits = { version = "0.2", default-features = false } picky = { version = "7.0.0-rc.12", default-features = false } +picky-asn1 = "0.10" picky-asn1-der = "0.5" picky-asn1-x509 = "0.14" +picky-krb = "0.9" tokio = "1.43" ffi-types = { path = "crates/ffi-types" } winscard = { version = "0.2", path = "crates/winscard" } +dpapi = { version = "0.1.0", path = "crates/dpapi" } rsa = { version = "0.9.7", default-features = false } windows-sys = "0.59" base64 = "0.22" whoami = "1.5" tracing-subscriber = "0.3" proptest = "1.6" +serde = "1" +byteorder = "1.5" +num-bigint-dig = "0.8" +hmac = "0.12" [features] default = ["aws-lc-rs"] @@ -77,6 +87,7 @@ cfg-if.workspace = true time = { workspace = true, features = ["std"] } picky.workspace = true sha1.workspace = true +sha2.workspace = true num-derive.workspace = true num-traits = { workspace = true, default-features = true } picky-asn1-der.workspace = true @@ -87,26 +98,24 @@ tokio = { workspace = true, optional = true, features = ["time", "rt", "rt-multi winscard = { workspace = true, optional = true } rsa = { workspace = true, features = ["sha1"] } tracing = { workspace = true, default-features = true } +serde.workspace = true +picky-krb.workspace = true +picky-asn1 = { workspace = true, features = ["time_conversion"] } +byteorder.workspace = true +num-bigint-dig.workspace = true +hmac.workspace = true +url = "2.5" -byteorder = "1.5" md-5 = "0.10" md4 = "0.10" -sha2 = "0.10" -hmac = "0.12" crypto-mac = "0.11" lazy_static = "1.5" -serde = "1" serde_derive = "1" -url = "2.5" oid = "0.2" -picky-krb = "0.9" -picky-asn1 = { version = "0.10", features = ["time_conversion"] } - reqwest = { version = "0.12", optional = true, default-features = false, features = ["blocking", "rustls-tls-no-provider"] } hickory-resolver = { version = "0.24", optional = true } portpicker = { version = "0.1", optional = true } -num-bigint-dig = "0.8" rustls = { version = "0.23", optional = true, default-features = false, features = ["logging", "std", "tls12"] } rustls-native-certs = { version = "0.8", optional = true } zeroize = { version = "1.8", features = ["zeroize_derive"] } @@ -117,6 +126,9 @@ winreg = "0.55" windows = { version = "0.59", features = [ "Win32_Foundation", "Win32_NetworkManagement_Dns"] } windows-sys = { workspace = true, features = ["Win32_Security_Cryptography", "Win32_Foundation"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +uuid = { workspace = true, features = ["js"]} + [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] async-dnssd = "0.5" futures = "0.3" diff --git a/README.md b/README.md index d7cd61ca..0805c1ee 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The usage of the SSPs is as simple as creating an instance of the security provi Here is an example of acquiring a credentials handle and a timestamp of their validity: ```rust -use sspi::{CredentialUse, Ntlm, Sspi, Username, builders::EmptyInitializeSecurityContext, OwnedSecurityBuffer, ClientRequestFlags, DataRepresentation, SecurityBufferType, SspiImpl}; +use sspi::{CredentialUse, Ntlm, Sspi, Username, builders::EmptyInitializeSecurityContext, SecurityBuffer, ClientRequestFlags, DataRepresentation, BufferType, SspiImpl}; fn main() { let account_name = "example_user"; @@ -41,9 +41,9 @@ fn main() { .execute() .unwrap(); - let mut output_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; // first time calling initialize_security_context, the input buffer should be empty - let mut input_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut input_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; // create a builder for the first call to initialize_security_context // the target should start with the protocol name, e.g. "HTTP/example.com" or "LDAP/example.com" diff --git a/crates/dpapi-cli-client/Cargo.toml b/crates/dpapi-cli-client/Cargo.toml new file mode 100644 index 00000000..44c34997 --- /dev/null +++ b/crates/dpapi-cli-client/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dpapi-cli-client" +version = "0.1.0" +edition = "2021" + +[dependencies] +xflags = "0.3" +dpapi = { path = "../dpapi" } +tracing-subscriber = { workspace = true, features = ["std", "fmt", "local-time", "env-filter"] } diff --git a/crates/dpapi-cli-client/src/cli.rs b/crates/dpapi-cli-client/src/cli.rs new file mode 100644 index 00000000..1bf5cf44 --- /dev/null +++ b/crates/dpapi-cli-client/src/cli.rs @@ -0,0 +1,38 @@ +use std::path::PathBuf; + +xflags::xflags! { + /// DPAPI cli client. This app is used to encrypt/decrypt secrets using the DPAPI. + cmd dpapi { + /// Target server hostname. + /// For example, win-956cqossjtf.tbt.com. + required --server server: String + + /// The username to decrypt/encrypt the DPAPI blob. + /// The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format. + required --username username: String + + /// User's password. + required --password password: String + + /// Client's computer name. This parameter is optional. + /// If not provided, the current computer name will be used. + optional --computer-name computer_name: String + + /// Encrypt secret. + /// This command simulates the `NCryptProtectSecret` function. The encrypted secret (DPAPI blob) will be printed to stdout. + cmd encrypt { + /// User's SID. + required --sid sid: String + + /// Secret to encrypt. + /// This parameter is optional. If not provided, the app will try to read the secret from stdin. + optional --secret secret: String + } + + cmd decrypt { + /// Path to file that contains DPAPI blob. + /// This parameter is optional. If not provided, the app will try to read the DPAPI blob from stdin. + optional --file file: PathBuf + } + } +} diff --git a/crates/dpapi-cli-client/src/logging.rs b/crates/dpapi-cli-client/src/logging.rs new file mode 100644 index 00000000..5ad55c5d --- /dev/null +++ b/crates/dpapi-cli-client/src/logging.rs @@ -0,0 +1,36 @@ +use std::fs::OpenOptions; + +use tracing_subscriber::prelude::*; +use tracing_subscriber::EnvFilter; + +const DPAPI_LOG_PATH_ENV: &str = "DPAPI_LOG_PATH"; + +pub fn init_logging() { + let path = if let Ok(path) = std::env::var(DPAPI_LOG_PATH_ENV) { + path + } else { + eprintln!( + "[DPAPI] {} environment variable is not set. Logging is disabled.", + DPAPI_LOG_PATH_ENV + ); + return; + }; + + let file = match OpenOptions::new().create(true).append(true).open(&path) { + Ok(f) => f, + Err(e) => { + eprintln!("[DPAPI] Couldn't open log file: {e}. File path: {}", path); + return; + } + }; + + let fmt_layer = tracing_subscriber::fmt::layer() + .pretty() + .with_thread_names(true) + .with_writer(file); + + tracing_subscriber::registry() + .with(fmt_layer) + .with(EnvFilter::from_env("DPAPI_LOG_LEVEL")) + .init(); +} diff --git a/crates/dpapi-cli-client/src/main.rs b/crates/dpapi-cli-client/src/main.rs new file mode 100644 index 00000000..3f8ec287 --- /dev/null +++ b/crates/dpapi-cli-client/src/main.rs @@ -0,0 +1,63 @@ +mod cli; +mod logging; + +use std::fs; +use std::io::{stdin, stdout, Read, Result, Write}; + +use crate::cli::{Decrypt, Dpapi, DpapiCmd, Encrypt}; + +fn run(data: Dpapi) -> Result<()> { + logging::init_logging(); + + let Dpapi { + server, + username, + password, + computer_name, + subcommand, + } = data; + + match subcommand { + DpapiCmd::Encrypt(Encrypt { sid, secret }) => { + let secret = if let Some(secret) = secret { + secret.into_bytes() + } else { + stdin().bytes().collect::>>()? + }; + + let blob = dpapi::n_crypt_protect_secret( + secret.into(), + sid, + None, + &server, + &username, + password.into(), + computer_name, + ) + .unwrap(); + + stdout().write_all(&blob)?; + } + DpapiCmd::Decrypt(Decrypt { file }) => { + let blob = if let Some(file) = file { + fs::read(file)? + } else { + stdin().bytes().collect::>>()? + }; + + let secret = + dpapi::n_crypt_unprotect_secret(&blob, &server, &username, password.into(), computer_name).unwrap(); + + stdout().write_all(secret.as_ref())?; + } + } + + Ok(()) +} + +fn main() -> Result<()> { + match Dpapi::from_env() { + Ok(flags) => run(flags), + Err(err) => err.exit(), + } +} diff --git a/crates/dpapi/Cargo.toml b/crates/dpapi/Cargo.toml new file mode 100644 index 00000000..722d9e83 --- /dev/null +++ b/crates/dpapi/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "dpapi" +version = "0.1.0" +edition = "2021" +readme = "README.md" +license = "MIT/Apache-2.0" +homepage = "https://github.com/devolutions/sspi-rs" +repository = "https://github.com/devolutions/sspi-rs" +authors = ["Devolutions Inc. "] +description = "A Rust implementation of Windows DPAPI" + +[lib] +name = "dpapi" + +[features] +tsssp = ["sspi/tsssp"] + +[dependencies] +bitflags.workspace = true +byteorder.workspace = true +num-derive.workspace = true +num-traits = { workspace = true, default-features = true } +uuid = { workspace = true, features = ["std"] } +picky-asn1.workspace = true +picky-asn1-der.workspace = true +picky-krb.workspace = true +picky-asn1-x509 = { workspace = true, features = ["pkcs7"] } +num-bigint-dig.workspace = true +sha1.workspace = true +sha2.workspace = true +rand.workspace = true +hmac.workspace = true +tracing = { workspace = true, default-features = true } +whoami.workspace = true + +kbkdf = "0.0.1" +sha1-pre = { version = "0.11.0-pre.2", package = "sha1" } +sha2-pre = { version = "0.11.0-pre.2", package = "sha2" } +hmac-pre = { version = "0.13.0-pre.4", package = "hmac" } +digest-pre = { version = "0.11.0-pre.9", package = "digest", default-features = false } + +elliptic-curve = { version = "0.13", features = ["sec1", "std"] } +p521 = { version = "0.13", features = ["ecdh"] } +p256 = { version = "0.13", features = ["ecdh"] } +p384 = { version = "0.13", features = ["ecdh"] } +concat-kdf = { version = "0.1", features = ["std"] } +typenum = "1.17" +aes-kw = { version = "0.2", features = ["std"] } +aes-gcm = { version = "0.10", features = ["std"] } +url = "2.5" + +thiserror = "2.0" +regex = "1.11" + +sspi = { path = "../..", features = ["network_client"] } + +[dev-dependencies] +paste = "1.0" + diff --git a/crates/dpapi/README.md b/crates/dpapi/README.md new file mode 100644 index 00000000..6e0154f4 --- /dev/null +++ b/crates/dpapi/README.md @@ -0,0 +1,9 @@ +# dpapi-rs + +This crate contains a Windows [DPAPI](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi) implementation. It can encrypt the data/decrypt DPAPI blobs using the domain's root key. + +It automatically makes RPC calls to obtain the root key. The user must provide credentials to authenticate in the DC. + +It implements the [MS-GKDI Group Key Distribution Protocol](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a). + +The original DPAPI supports many [protection descriptors](https://learn.microsoft.com/en-us/windows/win32/seccng/protection-descriptors). This library implements only SID protection descriptor. \ No newline at end of file diff --git a/crates/dpapi/src/blob.rs b/crates/dpapi/src/blob.rs new file mode 100644 index 00000000..1c6ea009 --- /dev/null +++ b/crates/dpapi/src/blob.rs @@ -0,0 +1,416 @@ +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use picky_asn1::restricted_string::Utf8String; +use picky_asn1::wrapper::{ + Asn1SequenceOf, ExplicitContextTag0, ImplicitContextTag0, ObjectIdentifierAsn1, OctetStringAsn1, Optional, + Utf8StringAsn1, +}; +use picky_asn1_der::Asn1RawDer; +use picky_asn1_x509::cmsversion::CmsVersion; +use picky_asn1_x509::enveloped_data::{ + ContentEncryptionAlgorithmIdentifier, ContentInfo, ContentType, EncryptedContent, EncryptedContentInfo, + EncryptedKey, EnvelopedData, GeneralProtectionDescriptor, KekIdentifier, KekRecipientInfo, + KeyEncryptionAlgorithmIdentifier, OtherKeyAttribute, ProtectionDescriptor, RecipientInfo, RecipientInfos, +}; +use picky_asn1_x509::oids; +use thiserror::Error; +use uuid::Uuid; + +use crate::rpc::{read_buf, read_to_end, read_vec, write_buf, Decode, Encode, EncodeExt}; +use crate::sid::{ace_to_bytes, sd_to_bytes}; +use crate::str::{encode_utf16_le, from_utf16_le}; +use crate::{Error, Result}; + +#[derive(Debug, Error)] +pub enum BlobError { + #[error("unsupported protection descriptor: {0}")] + UnsupportedProtectionDescriptor(String), + + #[error("invalid {name}: expected {expected} but got {actual}")] + InvalidOid { + name: &'static str, + expected: String, + actual: String, + }, + + #[error("invalid {name} version: expected {expected:?} but got {actual:?}")] + InvalidCmsVersion { + name: &'static str, + expected: CmsVersion, + actual: CmsVersion, + }, + + #[error("bad recipient infos amount: expected {expected} but got {actual}")] + RecipientInfosAmount { expected: usize, actual: usize }, + + #[error("missing {0} value")] + MissingValue(&'static str), +} + +/// Key Identifier +/// +/// This contains the key identifier info that can be used by MS-GKDI GetKey to retrieve the group key seed values. +/// This structure is not defined publicly by Microsoft but it closely matches the [GroupKeyEnvelope] structure. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct KeyIdentifier { + /// The version of the structure. + pub version: u32, + /// Flags describing the values inside the structure. + pub flags: u32, + + /// The L0 index of the key. + pub l0: i32, + /// The L1 index of the key. + pub l1: i32, + /// The L2 index of the key. + pub l2: i32, + /// A GUID that identifies a root key. + pub root_key_identifier: Uuid, + + /// Key info. + pub key_info: Vec, + /// The domain name of the server in DNS format. + pub domain_name: String, + /// The forest name of the server in DNS format. + pub forest_name: String, +} + +impl KeyIdentifier { + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7 + const MAGIC: [u8; 4] = [0x4b, 0x44, 0x53, 0x4b]; + + pub fn is_public_key(&self) -> bool { + self.flags & 1 != 0 + } +} + +impl Encode for KeyIdentifier { + fn encode(&self, mut writer: impl Write) -> Result<()> { + let domain_name = encode_utf16_le(&self.domain_name); + let forest_name = encode_utf16_le(&self.forest_name); + + writer.write_u32::(self.version)?; + write_buf(&KeyIdentifier::MAGIC, &mut writer)?; + writer.write_u32::(self.flags)?; + + writer.write_i32::(self.l0)?; + writer.write_i32::(self.l1)?; + writer.write_i32::(self.l2)?; + + self.root_key_identifier.encode(&mut writer)?; + + writer.write_u32::(self.key_info.len().try_into()?)?; + writer.write_u32::(domain_name.len().try_into()?)?; + writer.write_u32::(forest_name.len().try_into()?)?; + + write_buf(&self.key_info, &mut writer)?; + write_buf(&domain_name, &mut writer)?; + write_buf(&forest_name, &mut writer)?; + + Ok(()) + } +} + +impl Decode for KeyIdentifier { + fn decode(mut reader: impl Read) -> Result { + let version = reader.read_u32::()?; + + let mut magic = [0; 4]; + read_buf(&mut reader, &mut magic)?; + + if magic != Self::MAGIC { + return Err(Error::InvalidMagic { + name: "KeyIdentifier", + expected: Self::MAGIC.as_slice(), + actual: magic.to_vec(), + }); + } + + let flags = reader.read_u32::()?; + + let l0 = reader.read_i32::()?; + let l1 = reader.read_i32::()?; + let l2 = reader.read_i32::()?; + let root_key_identifier = Uuid::decode(&mut reader)?; + + let key_info_len = reader.read_u32::()?; + + let domain_len = reader.read_u32::()?.try_into()?; + if domain_len <= 2 { + return Err(Error::InvalidLength { + name: "KeyIdentifier domain name", + expected: 2, + actual: domain_len, + }); + } + let domain_len = domain_len - 2 /* UTF16 null terminator */; + + let forest_len = reader.read_u32::()?.try_into()?; + if forest_len <= 2 { + return Err(Error::InvalidLength { + name: "KeyIdentifier forest name", + expected: 2, + actual: forest_len, + }); + } + let forest_len = forest_len - 2 /* UTF16 null terminator */; + + let key_info = read_vec(key_info_len.try_into()?, &mut reader)?; + + let domain_name = read_vec(domain_len, &mut reader)?; + // Read UTF16 null terminator. + reader.read_u16::()?; + + let forest_name = read_vec(forest_len, &mut reader)?; + // Read UTF16 null terminator. + reader.read_u16::()?; + + Ok(Self { + version, + flags, + l0, + l1, + l2, + root_key_identifier, + key_info, + domain_name: from_utf16_le(&domain_name)?, + forest_name: from_utf16_le(&forest_name)?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum ProtectionDescriptorType { + #[default] + Sid, + KeyFile, + Sddl, + Local, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SidProtectionDescriptor { + pub sid: String, +} + +impl SidProtectionDescriptor { + pub fn get_target_sd(&self) -> Result> { + // Build the target security descriptor from the SID passed in. This SD + // contains an ACE per target user with a mask of 0x3 and a final ACE of + // the current user with a mask of 0x2. When viewing this over the wire + // the current user is set as S-1-1-0 (World) and the owner/group is + // S-1-5-18 (SYSTEM). + sd_to_bytes( + "S-1-5-18", + "S-1-5-18", + None, + Some(&[ace_to_bytes(&self.sid, 3)?, ace_to_bytes("S-1-1-0", 2)?]), + ) + } + + pub fn encode_asn1(&self) -> Result> { + Ok(picky_asn1_der::to_vec(&GeneralProtectionDescriptor { + descriptor_type: ObjectIdentifierAsn1::from(oids::sid_protection_descriptor()), + descriptors: Asn1SequenceOf::from(vec![Asn1SequenceOf::from(vec![ProtectionDescriptor { + descriptor_type: Utf8StringAsn1::from(Utf8String::from_string("SID".to_owned())?), + descriptor_value: Utf8StringAsn1::from(Utf8String::from_string(self.sid.clone())?), + }])]), + })?) + } + + pub fn decode_asn1(data: &[u8]) -> Result { + let general_protection_descriptor: GeneralProtectionDescriptor = picky_asn1_der::from_bytes(data)?; + + if general_protection_descriptor.descriptor_type.0 != oids::sid_protection_descriptor() { + Err(BlobError::UnsupportedProtectionDescriptor( + general_protection_descriptor.descriptor_type.0.into(), + ))?; + } + + let ProtectionDescriptor { + descriptor_type, + descriptor_value, + } = general_protection_descriptor + .descriptors + .0 + .first() + .ok_or(BlobError::MissingValue("protection descriptor"))? + .0 + .first() + .ok_or(BlobError::MissingValue("protection descriptor"))?; + + if descriptor_type.0.as_utf8() != "SID" { + Err(BlobError::UnsupportedProtectionDescriptor( + descriptor_type.0.as_utf8().to_owned(), + ))?; + } + + Ok(Self { + sid: descriptor_value.0.as_utf8().to_owned(), + }) + } +} + +/// Represents DPAPI blob. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DpapiBlob { + /// The key identifier for the KEK. + pub key_identifier: KeyIdentifier, + /// The protection descriptor that protects the key. + pub protection_descriptor: SidProtectionDescriptor, + /// The encrypted CEK. + pub enc_cek: Vec, + /// CEK encryption algorithm. + pub enc_cek_algorithm_id: KeyEncryptionAlgorithmIdentifier, + /// The encrypted content. + pub enc_content: Vec, + /// Content encryption algorithm. + pub enc_content_algorithm_id: ContentEncryptionAlgorithmIdentifier, +} + +impl DpapiBlob { + // blob_in_envelope: + // * `true` to store the encrypted blob in the EnvelopedData structure (NCryptProtectSecret general). + // * `false` to append the encrypted blob after the EnvelopedData structure (LAPS style). + pub fn encode(&self, blob_in_envelope: bool, mut writer: impl Write) -> Result<()> { + picky_asn1_der::to_writer( + &ContentInfo { + content_type: ObjectIdentifierAsn1::from(oids::enveloped_data()), + content: ExplicitContextTag0::from(Asn1RawDer(picky_asn1_der::to_vec(&EnvelopedData { + version: CmsVersion::V2, + originator_info: Optional::from(None), + recipient_infos: RecipientInfos::from(vec![RecipientInfo::Kek(KekRecipientInfo { + version: CmsVersion::V4, + kek_id: KekIdentifier { + key_identifier: OctetStringAsn1::from(self.key_identifier.encode_to_vec()?), + date: Optional::from(None), + other: Optional::from(Some(OtherKeyAttribute { + key_attr_id: ObjectIdentifierAsn1::from(oids::protection_descriptor_type()), + key_attr: Some(Asn1RawDer(self.protection_descriptor.encode_asn1()?)), + })), + }, + key_encryption_algorithm: self.enc_cek_algorithm_id.clone(), + encrypted_key: EncryptedKey::from(self.enc_cek.clone()), + })]), + encrypted_content_info: EncryptedContentInfo { + content_type: ContentType::from(oids::content_info_type_data()), + content_encryption_algorithm: self.enc_content_algorithm_id.clone(), + encrypted_content: Optional::from(if blob_in_envelope { + Some(ImplicitContextTag0::from(EncryptedContent::from( + self.enc_content.clone(), + ))) + } else { + None + }), + }, + unprotected_attrs: Optional::from(None), + })?)), + }, + &mut writer, + )?; + + if !blob_in_envelope { + write_buf(&self.enc_content, &mut writer)?; + } + + Ok(()) + } +} + +impl Decode for DpapiBlob { + fn decode(mut reader: impl Read) -> Result { + let content_info: ContentInfo = picky_asn1_der::from_reader(&mut reader)?; + + if content_info.content_type.0 != oids::enveloped_data() { + let expected_content_type: String = oids::enveloped_data().into(); + let actual_content_type: String = content_info.content_type.0.into(); + + Err(BlobError::InvalidOid { + name: "blob content type", + expected: expected_content_type, + actual: actual_content_type, + })?; + } + + let enveloped_data: EnvelopedData = picky_asn1_der::from_bytes(&content_info.content.0 .0)?; + + if enveloped_data.version != CmsVersion::V2 { + Err(BlobError::InvalidCmsVersion { + name: "enveloped data", + expected: CmsVersion::V2, + actual: enveloped_data.version, + })?; + } + + if enveloped_data.recipient_infos.0.len() != 1 { + Err(BlobError::RecipientInfosAmount { + expected: 1, + actual: enveloped_data.recipient_infos.0.len(), + })?; + } + + let RecipientInfo::Kek(kek_info) = enveloped_data.recipient_infos.0.first().unwrap(); + + if kek_info.version != CmsVersion::V4 { + Err(BlobError::InvalidCmsVersion { + name: "KEK info", + expected: CmsVersion::V4, + actual: kek_info.version, + })?; + } + + let key_identifier = KeyIdentifier::decode(&kek_info.kek_id.key_identifier.0 as &[u8])?; + + let protection_descriptor = if let Some(OtherKeyAttribute { key_attr_id, key_attr }) = &kek_info.kek_id.other.0 + { + if key_attr_id.0 != oids::protection_descriptor_type() { + let expected_descriptor: String = oids::protection_descriptor_type().into(); + let actual_descriptor: String = (&key_attr_id.0).into(); + + Err(BlobError::InvalidOid { + name: "KEK recipient info OtherAttribute OID", + expected: expected_descriptor, + actual: actual_descriptor, + })?; + } + + if let Some(encoded_protection_descriptor) = key_attr { + SidProtectionDescriptor::decode_asn1(&encoded_protection_descriptor.0)? + } else { + Err(BlobError::MissingValue("KEK recipient info OtherAttribute"))? + } + } else { + Err(BlobError::MissingValue("KEK recipient info protection descriptor"))? + }; + + let enc_content = if let Some(enc_content) = enveloped_data.encrypted_content_info.encrypted_content.0 { + // Some DPAPI blobs don't include the content in the PKCS7 payload but + // just append it after the blob. + if enc_content.0 .0.is_empty() { + read_to_end(reader)? + } else { + enc_content.0 .0 + } + } else { + read_to_end(reader)? + }; + let enc_content_algorithm_id = enveloped_data.encrypted_content_info.content_encryption_algorithm; + + let KekRecipientInfo { + encrypted_key, + key_encryption_algorithm, + version: _, + kek_id: _, + } = kek_info; + + Ok(Self { + key_identifier, + protection_descriptor, + enc_cek: encrypted_key.0.clone(), + enc_cek_algorithm_id: key_encryption_algorithm.clone(), + enc_content, + enc_content_algorithm_id, + }) + } +} diff --git a/crates/dpapi/src/client.rs b/crates/dpapi/src/client.rs new file mode 100644 index 00000000..f8b38d94 --- /dev/null +++ b/crates/dpapi/src/client.rs @@ -0,0 +1,377 @@ +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{AesMode, AesParameters}; +use sspi::credssp::SspiContext; +use sspi::{AuthIdentity, Credentials, Kerberos, KerberosConfig, Secret, Username}; +use thiserror::Error; +use url::Url; +use uuid::Uuid; + +use crate::blob::{DpapiBlob, SidProtectionDescriptor}; +use crate::crypto::{cek_decrypt, cek_encrypt, cek_generate, content_decrypt, content_encrypt}; +use crate::epm::{build_tcpip_tower, EptMap, EptMapResult, Floor, EPM}; +use crate::gkdi::{GetKey, GroupKeyEnvelope, ISD_KEY}; +use crate::rpc::auth::AuthError; +use crate::rpc::bind::{BindAck, BindTimeFeatureNegotiationBitmask, ContextElement, ContextResultCode}; +use crate::rpc::pdu::SecurityTrailer; +use crate::rpc::request::Response; +use crate::rpc::verification::{Command, CommandFlags, CommandPContext, VerificationTrailer}; +use crate::rpc::{bind_time_feature_negotiation, AuthProvider, Decode, EncodeExt, RpcClient, NDR, NDR64}; +use crate::{Error, Result}; + +const DEFAULT_RPC_PORT: u16 = 135; + +#[derive(Debug, Error)] +pub enum ClientError { + #[error("BindAcknowledge doesn't contain desired context element")] + MissingDesiredContext, + + #[error("TCP floor is missing in EptMap response")] + MissingTcpFloor, + + #[error("bad EptMap response status: {0}")] + BadEptMapStatus(u32), +} + +pub type ClientResult = std::result::Result; + +fn get_epm_contexts() -> Vec { + vec![ContextElement { + context_id: 0, + abstract_syntax: EPM, + transfer_syntaxes: vec![NDR64], + }] +} + +fn get_isd_key_key_contexts() -> Vec { + vec![ + ContextElement { + context_id: 0, + abstract_syntax: ISD_KEY, + transfer_syntaxes: vec![NDR64], + }, + ContextElement { + context_id: 1, + abstract_syntax: ISD_KEY, + transfer_syntaxes: vec![bind_time_feature_negotiation(BindTimeFeatureNegotiationBitmask::None)], + }, + ] +} + +fn get_ept_map_isd_key() -> EptMap { + EptMap { + obj: None, + tower: build_tcpip_tower(ISD_KEY, NDR, 135, 0), + entry_handle: None, + max_towers: 4, + } +} + +fn get_verification_trailer() -> VerificationTrailer { + VerificationTrailer { + commands: vec![Command::Pcontext(CommandPContext { + flags: CommandFlags::SecVtCommandEnd, + interface_id: ISD_KEY, + transfer_syntax: NDR64, + })], + } +} + +#[instrument(level = "trace", ret)] +fn process_bind_result(requested_contexts: &[ContextElement], bind_ack: BindAck, desired_context: u16) -> Result<()> { + bind_ack + .results + .iter() + .enumerate() + .filter_map(|(index, result)| { + if result.result == ContextResultCode::Acceptance { + requested_contexts.get(index).map(|ctx| ctx.context_id) + } else { + None + } + }) + .find(|context_id| *context_id == desired_context) + .ok_or(ClientError::MissingDesiredContext)?; + + Ok(()) +} + +#[instrument(level = "trace", ret)] +fn process_ept_map_result(response: &Response) -> Result { + let map_response = EptMapResult::decode(response.stub_data.as_slice())?; + + if map_response.status != 0 { + Err(ClientError::BadEptMapStatus(map_response.status))?; + } + + for tower in map_response.towers { + for floor in tower { + if let Floor::Tcp(tcp_floor) = floor { + return Ok(tcp_floor.port); + } + } + } + + Err(Error::from(ClientError::MissingTcpFloor)) +} + +#[instrument(level = "trace", ret)] +fn process_get_key_result(response: &Response, security_trailer: Option) -> Result { + let pad_length = response.stub_data.len() + - security_trailer + .as_ref() + .map(|sec_trailer| usize::from(sec_trailer.pad_length)) + .unwrap_or_default(); + trace!(pad_length); + + let data = &response.stub_data[..pad_length]; + + GetKey::unpack_response(data) +} + +#[instrument(ret)] +fn decrypt_blob(blob: &DpapiBlob, key: &GroupKeyEnvelope) -> Result> { + let kek = key.get_kek(&blob.key_identifier)?; + + // With the kek we can unwrap the encrypted cek in the LAPS payload. + let cek = cek_decrypt(&blob.enc_cek_algorithm_id, &kek, &blob.enc_cek)?; + + // With the cek we can decrypt the encrypted content in the LAPS payload. + Ok(content_decrypt( + &blob.enc_content_algorithm_id, + &cek, + &blob.enc_content, + )?) +} + +#[instrument(ret)] +fn encrypt_blob( + data: &[u8], + key: &GroupKeyEnvelope, + protection_descriptor: SidProtectionDescriptor, +) -> Result> { + let enc_cek_algorithm_id = KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap); + let (cek, iv) = cek_generate(&enc_cek_algorithm_id)?; + + let enc_content_algorithm_id = + ContentEncryptionAlgorithmIdentifier::new_aes256(AesMode::Gcm, AesParameters::InitializationVector(iv.into())); + + let enc_content = content_encrypt(&enc_content_algorithm_id, &cek, data)?; + + let (kek, key_identifier) = key.new_kek()?; + let enc_cek = cek_encrypt(&enc_cek_algorithm_id, &kek, &cek)?; + + let mut buf = Vec::new(); + + DpapiBlob { + key_identifier, + protection_descriptor, + enc_cek, + enc_cek_algorithm_id, + enc_content, + enc_content_algorithm_id, + } + .encode(true, &mut buf)?; + + Ok(buf) +} + +struct GetKeyArgs<'server, 'username> { + server: &'server str, + target_sd: Vec, + root_key_id: Option, + l0: i32, + l1: i32, + l2: i32, + username: &'username str, + password: Secret, + kerberos_config: KerberosConfig, +} + +#[instrument(level = "trace", ret)] +fn get_key( + GetKeyArgs { + server, + target_sd, + root_key_id, + l0, + l1, + l2, + username, + password, + kerberos_config, + }: GetKeyArgs, +) -> Result { + let username = Username::parse(username).expect("correct username"); + + let isd_key_port = { + let mut rpc = RpcClient::connect( + (server, DEFAULT_RPC_PORT), + AuthProvider::new( + SspiContext::Kerberos( + Kerberos::new_client_from_config(kerberos_config.clone()).map_err(AuthError::from)?, + ), + Credentials::AuthIdentity(AuthIdentity { + username: username.clone(), + password: password.clone(), + }), + server, + )?, + )?; + + info!("RPC connection has been established."); + + let epm_contexts = get_epm_contexts(); + let context_id = epm_contexts[0].context_id; + let bind_ack = rpc.bind(&epm_contexts)?; + + info!("RPC bind/bind_ack finished successfully."); + + process_bind_result(&epm_contexts, bind_ack, context_id)?; + + let ept_map = get_ept_map_isd_key(); + let response = rpc.request(0, EptMap::OPNUM, ept_map.encode_to_vec()?)?; + process_ept_map_result(&response.try_into_response()?)? + }; + + info!(isd_key_port); + + let mut rpc = RpcClient::connect( + (server, isd_key_port), + AuthProvider::new( + SspiContext::Kerberos(Kerberos::new_client_from_config(kerberos_config).map_err(AuthError::from)?), + Credentials::AuthIdentity(AuthIdentity { username, password }), + server, + )?, + )?; + + info!("RPC connection has been established."); + + let isd_key_contexts = get_isd_key_key_contexts(); + let context_id = isd_key_contexts[0].context_id; + let bind_ack = rpc.bind_authenticate(&isd_key_contexts)?; + + info!("RPC bind/bind_ack finished successfully."); + + process_bind_result(&isd_key_contexts, bind_ack, context_id)?; + + let get_key = GetKey { + target_sd, + root_key_id, + l0_key_id: l0, + l1_key_id: l1, + l2_key_id: l2, + }; + + let response_pdu = rpc.authenticated_request( + context_id, + GetKey::OPNUM, + get_key.encode_to_vec()?, + Some(get_verification_trailer()), + )?; + let security_trailer = response_pdu.security_trailer.clone(); + + info!("RPC GetKey Request finished successfully!"); + + process_get_key_result(&response_pdu.try_into_response()?, security_trailer) +} + +fn try_get_kerberos_config(server: &str, client_computer_name: Option) -> Result { + let kdc_url = if let Ok(kdc_url) = std::env::var("SSPI_KDC_URL") { + kdc_url + } else { + format!("tcp://{}:88", server) + }; + + let client_computer_name = if let Some(name) = client_computer_name { + name + } else { + whoami::fallible::hostname()? + }; + + Ok(KerberosConfig { + kdc_url: Some(Url::parse(&kdc_url).map_err(|error| Error::InvalidUrl { + name: "KDC", + url: kdc_url, + error, + })?), + client_computer_name: Some(client_computer_name), + }) +} + +/// Decrypt the DPAPI blob. +/// +/// This function simulated the `NCryptUnprotectSecret` function. Decryption requires making RPC calls to the domain. +/// The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format. +/// _Note_: `server` value should be target domain server hostname. Do not use IP address here. +/// +/// MSDN: +/// * [NCryptUnprotectSecret function (ncryptprotect.h)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptunprotectsecret). +#[instrument(err)] +pub fn n_crypt_unprotect_secret( + blob: &[u8], + server: &str, + username: &str, + password: Secret, + client_computer_name: Option, +) -> Result>> { + let dpapi_blob = DpapiBlob::decode(blob)?; + let target_sd = dpapi_blob.protection_descriptor.get_target_sd()?; + + let root_key = get_key(GetKeyArgs { + server, + target_sd, + root_key_id: Some(dpapi_blob.key_identifier.root_key_identifier), + l0: dpapi_blob.key_identifier.l0, + l1: dpapi_blob.key_identifier.l1, + l2: dpapi_blob.key_identifier.l2, + username, + password, + kerberos_config: try_get_kerberos_config(server, client_computer_name)?, + })?; + + info!("Successfully requested root key."); + + Ok(decrypt_blob(&dpapi_blob, &root_key)?.into()) +} + +/// Encrypts data to a specified protection descriptor. +/// +/// This function simulated the `NCryptProtectSecret` function. Encryption requires making RPCs call to the domain. +/// The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format. +/// _Note_: `server` value should be target domain server hostname. Do not use IP address here. +/// +/// MSDN: +/// * [NCryptProtectSecret function (`ncryptprotect.h`)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret). +#[instrument(ret)] +pub fn n_crypt_protect_secret( + data: Secret>, + sid: String, + root_key_id: Option, + server: &str, + username: &str, + password: Secret, + client_computer_name: Option, +) -> Result> { + let l0 = -1; + let l1 = -1; + let l2 = -1; + + let descriptor = SidProtectionDescriptor { sid }; + let target_sd = descriptor.get_target_sd()?; + + let root_key = get_key(GetKeyArgs { + server, + target_sd, + root_key_id, + l0, + l1, + l2, + username, + password, + kerberos_config: try_get_kerberos_config(server, client_computer_name)?, + })?; + + info!("Successfully requested root key."); + + encrypt_blob(data.as_ref(), &root_key, descriptor) +} diff --git a/crates/dpapi/src/crypto/kout.rs b/crates/dpapi/src/crypto/kout.rs new file mode 100644 index 00000000..e90c7956 --- /dev/null +++ b/crates/dpapi/src/crypto/kout.rs @@ -0,0 +1,12 @@ +macro_rules! kout { + ($name:ident, $size:ident) => { + pub struct $name; + + impl digest_pre::crypto_common::KeySizeUser for $name { + type KeySize = digest_pre::consts::$size; + } + }; +} + +kout!(Kout32, U32); +kout!(Kout64, U64); diff --git a/crates/dpapi/src/crypto/mod.rs b/crates/dpapi/src/crypto/mod.rs new file mode 100644 index 00000000..aff5d815 --- /dev/null +++ b/crates/dpapi/src/crypto/mod.rs @@ -0,0 +1,534 @@ +mod kout; + +use aes_gcm::aead::{Aead, KeyInit, OsRng}; +use aes_gcm::{Aes256Gcm, Key}; +use aes_kw::KekAes256; +use num_bigint_dig::BigUint; +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{oids, AesParameters, AlgorithmIdentifierParameters}; +use rand::Rng; +use thiserror::Error; +use uuid::Uuid; + +use crate::gkdi::{EcdhKey, EllipticCurve, FfcdhKey, GroupKeyEnvelope, HashAlg}; +use crate::rpc::{Decode, EncodeExt}; +use crate::str::encode_utf16_le; +use crate::Result; + +#[derive(Debug, Error)] +pub enum CryptoError { + #[error("invalid {name} algorithm id: expected {expected} but got {actual}")] + InvalidAlgorithm { + name: &'static str, + expected: String, + actual: String, + }, + + #[error("invalid AES parameters: {reason}")] + InvalidAesParams { + reason: &'static str, + parameters: AlgorithmIdentifierParameters, + }, + + #[error("invalid elliptic curve point")] + InvalidEllipticCurvePoint, + + #[error("invalid or unsupported secret algorithm: {0}")] + InvalidSecretAlg(String), + + #[error("missing elliptic curve point {0} coordinate")] + MissingPointCoordinate(&'static str), + + #[error(transparent)] + EllipticCurve(#[from] elliptic_curve::Error), + + #[error(transparent)] + ConcatKdf(#[from] concat_kdf::Error), + + #[error(transparent)] + IntConversion(#[from] std::num::TryFromIntError), + + #[error(transparent)] + AesGcm(#[from] aes_gcm::Error), + + #[error(transparent)] + AesKw(#[from] aes_kw::Error), + + #[error("{0} is uninitialized")] + Uninitialized(&'static str), + + #[error("invalid key length: expected {expected} bytes but got {actual}")] + InvalidKeyLength { expected: usize, actual: usize }, + + #[error("unsupported KBKDF output key length: {0}")] + UnsupportedKbkdfOutputKeyLength(usize), + + #[error("key derivation error: {0}")] + Kbkdf(#[from] kbkdf::Error), +} + +pub type CryptoResult = std::result::Result; + +// "KDS service\0" encoded in UTF16 le. +pub const KDS_SERVICE_LABEL: &[u8] = &[ + 75, 0, 68, 0, 83, 0, 32, 0, 115, 0, 101, 0, 114, 0, 118, 0, 105, 0, 99, 0, 101, 0, 0, 0, +]; + +pub fn cek_decrypt( + algorithm: &KeyEncryptionAlgorithmIdentifier, + kek: &[u8], + wrapped_key: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let kek = KekAes256::new(kek.into()); + + Ok(kek.unwrap_vec(wrapped_key)?) +} + +pub fn cek_encrypt(algorithm: &KeyEncryptionAlgorithmIdentifier, kek: &[u8], key: &[u8]) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let kek = KekAes256::new(kek.into()); + + Ok(kek.wrap_vec(key)?) +} + +pub fn cek_generate(algorithm: &KeyEncryptionAlgorithmIdentifier) -> CryptoResult<(Vec, Vec)> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let mut rng = OsRng; + let cek = Aes256Gcm::generate_key(&mut rng); + let iv = rng.gen::<[u8; 12]>(); + + Ok((cek.to_vec(), iv.to_vec())) +} + +fn extract_iv(parameters: &AlgorithmIdentifierParameters) -> CryptoResult<&[u8]> { + if let AlgorithmIdentifierParameters::Aes(aes_parameters) = parameters { + if let AesParameters::InitializationVector(iv) = aes_parameters { + Ok(iv.0.as_slice()) + } else { + Err(CryptoError::InvalidAesParams { + reason: "expected AES initialization vector", + parameters: parameters.clone(), + }) + } + } else { + Err(CryptoError::InvalidAesParams { + reason: "provided ones are not AES parameters", + parameters: parameters.clone(), + }) + } +} + +pub fn content_decrypt( + algorithm: &ContentEncryptionAlgorithmIdentifier, + cek: &[u8], + data: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_gcm() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-gcm", + expected: oids::aes256_gcm().into(), + actual: algorithm.oid().into(), + }); + } + + let iv = extract_iv(algorithm.parameters())?; + + let cipher = Aes256Gcm::new(Key::::from_slice(cek)); + Ok(cipher.decrypt(iv.into(), data)?) +} + +pub fn content_encrypt( + algorithm: &ContentEncryptionAlgorithmIdentifier, + cek: &[u8], + plaintext: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_gcm() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-gcm", + expected: oids::aes256_gcm().into(), + actual: algorithm.oid().into(), + }); + } + + let iv = extract_iv(algorithm.parameters())?; + + let cipher = Aes256Gcm::new(Key::::from_slice(cek)); + Ok(cipher.encrypt(iv.into(), plaintext)?) +} + +pub fn kdf(algorithm: HashAlg, secret: &[u8], label: &[u8], context: &[u8], length: usize) -> CryptoResult> { + use hmac_pre::Hmac; + use kbkdf::{Counter, Kbkdf, Params}; + use kout::*; + + macro_rules! derive_key { + ($prf_ty:ty, { $($l_value:expr => $l_ty:ty,)* }) => { + $( + if $l_value == length { + return Ok(Counter::<$prf_ty, $l_ty>::default() + .derive(Params::builder(secret).with_label(label).with_context(context).use_counter(true).use_l(true).build())? + .to_vec()); + } + )* + }; + } + + match algorithm { + HashAlg::Sha1 => { + derive_key!(Hmac, { 32 => Kout32, 64 => Kout64, }); + } + HashAlg::Sha256 => { + derive_key!(Hmac, { 32 => Kout32, 64 => Kout64, }); + } + HashAlg::Sha384 => { + derive_key!(Hmac, { 32 => Kout32, 64 => Kout64, }); + } + HashAlg::Sha512 => { + derive_key!(Hmac, { 32 => Kout32, 64 => Kout64, }); + } + } + + Err(CryptoError::UnsupportedKbkdfOutputKeyLength(length)) +} + +fn kdf_concat( + algorithm: HashAlg, + shared_secret: &[u8], + algorithm_id: &[u8], + party_uinfo: &[u8], + party_vinfo: &[u8], +) -> CryptoResult> { + let mut other_info = algorithm_id.to_vec(); + other_info.extend_from_slice(party_uinfo); + other_info.extend_from_slice(party_vinfo); + + Ok(match algorithm { + HashAlg::Sha1 => concat_kdf::derive_key::(shared_secret, &other_info, 20)?, + HashAlg::Sha256 => concat_kdf::derive_key::(shared_secret, &other_info, 32)?, + HashAlg::Sha384 => concat_kdf::derive_key::(shared_secret, &other_info, 48)?, + HashAlg::Sha512 => concat_kdf::derive_key::(shared_secret, &other_info, 64)?, + }) +} + +fn compute_kdf_context(key_guid: Uuid, l0: i32, l1: i32, l2: i32) -> Vec { + let mut buf = vec![0; 28]; + + buf[0..16].copy_from_slice(&key_guid.to_bytes_le()); + buf[16..20].copy_from_slice(&l0.to_le_bytes()); + buf[20..24].copy_from_slice(&l1.to_le_bytes()); + buf[24..28].copy_from_slice(&l2.to_le_bytes()); + + buf +} + +pub fn compute_l1_key( + target_sd: &[u8], + root_key_id: Uuid, + l0: i32, + root_key: &[u8], + algorithm: HashAlg, +) -> CryptoResult> { + // Note: 512 is number of bits, we use byte length here + // Key(SD, RK, L0, -1, -1) = KDF( + // HashAlg, + // RK.msKds-RootKeyData, + // "KDS service", + // RKID || L0 || 0xffffffff || 0xffffffff, + // 512 + // ) + let l0_seed = kdf( + algorithm, + root_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(root_key_id, l0, -1, -1), + 64, + )?; + + // Key(SD, RK, L0, 31, -1) = KDF( + // HashAlg, + // Key(SD, RK, L0, -1, -1), + // "KDS service", + // RKID || L0 || 31 || 0xffffffff || SD, + // 512 + // ) + let mut kdf_context = compute_kdf_context(root_key_id, l0, 31, -1); + kdf_context.extend_from_slice(target_sd); + + kdf(algorithm, &l0_seed, KDS_SERVICE_LABEL, &kdf_context, 64) +} + +pub fn compute_l2_key( + algorithm: HashAlg, + request_l1: i32, + request_l2: i32, + rk: &GroupKeyEnvelope, +) -> CryptoResult> { + let mut l1 = rk.l1; + let mut l1_key = rk.l1_key.clone(); + let mut l2 = rk.l2; + let mut l2_key = rk.l2_key.clone(); + let mut reseed_l2 = l2 == 31 || rk.l1 != request_l1; + + // MS-GKDI 2.2.4 Group key Envelope + // If the value in the L2 index field is equal to 31, this contains the + // L1 key with group key identifier (L0 index, L1 index, -1). In all + // other cases, this field contains the L1 key with group key identifier + // (L0 index, L1 index - 1, -1). If this field is present, its length + // MUST be equal to 64 bytes. + if l2 != 31 && l1 != request_l1 { + l1 -= 1; + } + + while l1 != request_l1 { + reseed_l2 = true; + l1 -= 1; + + l1_key = kdf( + algorithm, + &l1_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, -1), + 64, + )?; + } + + if reseed_l2 { + l2 = 31; + l2_key = kdf( + algorithm, + &l1_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, l2), + 64, + )?; + } + + while l2 != request_l2 { + l2 -= 1; + + l2_key = kdf( + algorithm, + &l2_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, l2), + 64, + )?; + } + + Ok(l2_key) +} + +pub fn compute_kek_from_public_key( + algorithm: HashAlg, + seed: &[u8], + secret_algorithm: &str, + public_key: &[u8], + private_key_length: usize, +) -> Result> { + let encoded_secret_algorithm = encode_utf16_le(secret_algorithm); + + let private_key = kdf( + algorithm, + seed, + KDS_SERVICE_LABEL, + &encoded_secret_algorithm, + private_key_length, + )?; + + compute_kek(algorithm, secret_algorithm, &private_key, public_key) +} + +pub fn compute_kek( + algorithm: HashAlg, + secret_algorithm: &str, + private_key: &[u8], + public_key: &[u8], +) -> Result> { + let (shared_secret, secret_hash_algorithm) = if secret_algorithm == "DH" { + let dh_pub_key = FfcdhKey::decode(public_key)?; + let shared_secret = dh_pub_key + .public_key + .modpow(&BigUint::from_bytes_be(private_key), &dh_pub_key.field_order); + let mut shared_secret = shared_secret.to_bytes_be(); + + while shared_secret.len() < dh_pub_key.key_length.try_into()? { + shared_secret.insert(0, 0); + } + + (shared_secret, HashAlg::Sha256) + } else if secret_algorithm.starts_with("ECDH_P") { + use elliptic_curve::scalar::ScalarPrimitive; + use elliptic_curve::sec1::FromEncodedPoint; + use elliptic_curve::{PublicKey, SecretKey}; + + let ecdh_pub_key_info = EcdhKey::decode(public_key)?; + + match ecdh_pub_key_info.curve { + EllipticCurve::P256 => { + let public_key: p256::PublicKey = Option::from(PublicKey::from_encoded_point( + &p256::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p256::ecdh::SharedSecret = + p256::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha256) + } + EllipticCurve::P384 => { + let public_key: p384::PublicKey = Option::from(PublicKey::from_encoded_point( + &p384::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p384::ecdh::SharedSecret = + p384::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha384) + } + EllipticCurve::P521 => { + let public_key: p521::PublicKey = Option::from(PublicKey::from_encoded_point( + &p521::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p521::ecdh::SharedSecret = + p384::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha512) + } + } + } else { + Err(CryptoError::InvalidSecretAlg(secret_algorithm.to_owned()))? + }; + + // "KDS public key\0" encoded in UTF16 le. + let kek_context = &[ + 75, 0, 68, 0, 83, 0, 32, 0, 112, 0, 117, 0, 98, 0, 108, 0, 105, 0, 99, 0, 32, 0, 107, 0, 101, 0, 121, 0, 0, 0, + ]; + + // This part isn't documented but we use the key derivation algorithm + // SP 800-56A to derive the kek secret input value. On Windows this uses + // BCryptDeriveKey with the following parameters. + // KDF_ALGORITHMID - SHA512 + // KDF_PARTYUINFO - KDS public key + // KDF_PARTYVINFO - KDS service + // Each of these is just appended to the otherinfo value used in + // cryptography as the UTF-16-LE NULL terminated strings. + let secret = kdf_concat( + secret_hash_algorithm, + &shared_secret, + // "SHA512\0" encoded in UTF16 le. + &[83, 0, 72, 0, 65, 0, 53, 0, 49, 0, 50, 0, 0, 0], + kek_context, + KDS_SERVICE_LABEL, + )?; + + Ok(kdf(algorithm, &secret, KDS_SERVICE_LABEL, kek_context, 32)?) +} + +pub fn compute_public_key(secret_algorithm: &str, private_key: &[u8], peer_public_key: &[u8]) -> Result> { + if secret_algorithm == "DH" { + let FfcdhKey { + key_length, + field_order, + generator, + public_key: _, + } = FfcdhKey::decode(peer_public_key)?; + + let my_pub_key = generator.modpow(&BigUint::from_bytes_be(private_key), &field_order); + + FfcdhKey { + key_length, + field_order, + generator, + public_key: my_pub_key, + } + .encode_to_vec() + } else if secret_algorithm.starts_with("ECDH_P") { + use elliptic_curve::scalar::ScalarPrimitive; + use elliptic_curve::sec1::ToEncodedPoint; + + let ecdh_pub_key_info = EcdhKey::decode(peer_public_key)?; + + let (x, y) = match ecdh_pub_key_info.curve { + EllipticCurve::P256 => { + let secret_key = + p256::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + EllipticCurve::P384 => { + let secret_key = + p384::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + EllipticCurve::P521 => { + let secret_key = + p521::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + }; + + EcdhKey { + curve: ecdh_pub_key_info.curve, + key_length: ecdh_pub_key_info.key_length, + x, + y, + } + .encode_to_vec() + } else { + Ok(Err(CryptoError::InvalidSecretAlg(secret_algorithm.to_owned()))?) + } +} diff --git a/crates/dpapi/src/epm.rs b/crates/dpapi/src/epm.rs new file mode 100644 index 00000000..28acf8da --- /dev/null +++ b/crates/dpapi/src/epm.rs @@ -0,0 +1,487 @@ +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use thiserror::Error; +use uuid::{uuid, Uuid}; + +use crate::rpc::bind::SyntaxId; +use crate::rpc::{read_buf, read_padding, read_vec, write_buf, write_padding, Decode, Encode}; +use crate::Result; + +#[derive(Debug, Error)] +pub enum EpmError { + #[error("invalid floor protocol: {0}")] + InvalidFloorProtocol(u8), + + #[error("invalid floor value: {0}")] + InvalidFloorValue(&'static str), + + #[error("unsupported floor protocol: {0:?}")] + UnsupportedFloor(FloorProtocol), +} + +pub const EPM: SyntaxId = SyntaxId { + uuid: uuid!("e1af8308-5d1f-11c9-91a4-08002b14a0fa"), + version: 3, + version_minor: 0, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(u8)] +pub enum FloorProtocol { + Osi = 0x00, + DnaSessionControl = 0x02, + DnaSessionControlV3 = 0x03, + DnaNspTransport = 0x04, + Tp4 = 0x05, + Clns = 0x06, + Tcp = 0x07, + Udp = 0x08, + Ip = 0x09, + RpcConnectionless = 0x0a, + RpcConnectionOriented = 0x0b, + UuidId = 0x0d, + NamedPipes = 0x10, + NetBios = 0x11, + NetBeui = 0x12, + NetWareSpx = 0x13, + NetWareIpx = 0x14, + AppleTalkStream = 0x16, + AppleTalkDataram = 0x17, + AppleTalk = 0x18, + NetBios2 = 0x19, + VinesSpp = 0x1a, + VinesIpc = 0x1b, + StreetTalk = 0x1c, + UnixDomainSocket = 0x20, + Null = 0x21, + NetBios3 = 0x22, +} + +impl FloorProtocol { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +pub struct BaseFloor { + pub protocol: FloorProtocol, + pub lhs: Vec, + pub rhs: Vec, +} + +impl BaseFloor { + pub fn new(protocol: FloorProtocol, lhs: Vec, rhs: Vec) -> Self { + Self { protocol, lhs, rhs } + } +} + +impl Encode for BaseFloor { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::((self.lhs.len() + 1/* protocol byte */).try_into()?)?; + writer.write_u8(self.protocol.as_u8())?; + write_buf(&self.lhs, &mut writer)?; + + writer.write_u16::(self.rhs.len().try_into()?)?; + write_buf(&self.rhs, &mut writer)?; + + Ok(()) + } +} + +impl Decode for BaseFloor { + fn decode(mut reader: impl Read) -> Result { + let lhs_len = reader.read_u16::()?; + + let protocol_value = reader.read_u8()?; + let protocol = FloorProtocol::from_u8(protocol_value).ok_or(EpmError::InvalidFloorProtocol(protocol_value))?; + + let lhs = read_vec(usize::from(lhs_len - 1), &mut reader)?; + + let rhs_len = reader.read_u16::()?; + let rhs = read_vec(usize::from(rhs_len), &mut reader)?; + + Ok(Self { protocol, lhs, rhs }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TcpFloor { + pub port: u16, +} + +impl Encode for TcpFloor { + fn encode(&self, writer: impl Write) -> Result<()> { + BaseFloor::new(FloorProtocol::Tcp, Vec::new(), self.port.to_be_bytes().to_vec()).encode(writer) + } +} + +impl TcpFloor { + fn decode(_lhs: &[u8], rhs: &[u8]) -> Result { + if rhs.len() != 2 { + Err(EpmError::InvalidFloorValue( + "invalid TcpFloor rhs value length: expected exactly 2 bytes", + ))?; + } + + Ok(Self { + port: u16::from_be_bytes(rhs.try_into().unwrap()), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IpFloor { + pub addr: u32, +} + +impl Encode for IpFloor { + fn encode(&self, writer: impl Write) -> Result<()> { + BaseFloor::new(FloorProtocol::Ip, Vec::new(), self.addr.to_be_bytes().to_vec()).encode(writer) + } +} + +impl IpFloor { + fn decode(_lhs: &[u8], rhs: &[u8]) -> Result { + if rhs.len() != 4 { + Err(EpmError::InvalidFloorValue( + "invalid IpFloor rhs value length: expected exactly 4 bytes", + ))?; + } + + Ok(Self { + addr: u32::from_be_bytes(rhs.try_into().unwrap()), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RpcConnectionOrientedFloor { + pub version_minor: u16, +} + +impl Encode for RpcConnectionOrientedFloor { + fn encode(&self, writer: impl Write) -> Result<()> { + BaseFloor::new( + FloorProtocol::RpcConnectionOriented, + Vec::new(), + self.version_minor.to_le_bytes().to_vec(), + ) + .encode(writer) + } +} + +impl RpcConnectionOrientedFloor { + fn decode(_lhs: &[u8], rhs: &[u8]) -> Result { + if rhs.len() != 2 { + Err(EpmError::InvalidFloorValue( + "invalid RpcConnectionOrientedFloor rhs value length: expected exactly 2 bytes", + ))?; + } + + Ok(Self { + version_minor: u16::from_be_bytes(rhs.try_into().unwrap()), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UuidFloor { + pub uuid: Uuid, + pub version: u16, + pub version_minor: u16, +} + +impl Encode for UuidFloor { + fn encode(&self, writer: impl Write) -> Result<()> { + let mut lhs = self.uuid.to_bytes_le().to_vec(); + lhs.extend_from_slice(&self.version.to_le_bytes()); + + BaseFloor::new(FloorProtocol::UuidId, lhs, self.version_minor.to_le_bytes().to_vec()).encode(writer) + } +} + +impl UuidFloor { + fn decode(lhs: &[u8], rhs: &[u8]) -> Result { + if lhs.len() != 18 { + Err(EpmError::InvalidFloorValue( + "invalid UuidFloor lhs value length: expected exactly 18 bytes", + ))?; + } + + if rhs.len() != 2 { + Err(EpmError::InvalidFloorValue( + "invalid UuidFloor rhs value length: expected exactly 2 bytes", + ))?; + } + + Ok(Self { + uuid: Uuid::from_slice_le(&lhs[0..16])?, + version: u16::from_le_bytes(lhs[16..].try_into().unwrap()), + version_minor: u16::from_le_bytes(rhs.try_into().unwrap()), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Floor { + Tcp(TcpFloor), + Ip(IpFloor), + RpcConnectionOriented(RpcConnectionOrientedFloor), + Uuid(UuidFloor), +} + +impl Encode for Floor { + fn encode(&self, writer: impl Write) -> Result<()> { + match self { + Floor::Tcp(tcp_floor) => tcp_floor.encode(writer), + Floor::Ip(ip_floor) => ip_floor.encode(writer), + Floor::RpcConnectionOriented(rpc_connection_oriented_floor) => rpc_connection_oriented_floor.encode(writer), + Floor::Uuid(uuid_floor) => uuid_floor.encode(writer), + } + } +} + +impl Decode for Floor { + fn decode(reader: impl Read) -> Result { + let BaseFloor { protocol, lhs, rhs } = BaseFloor::decode(reader)?; + + Ok(match protocol { + FloorProtocol::Tcp => Floor::Tcp(TcpFloor::decode(&lhs, &rhs)?), + FloorProtocol::Ip => Floor::Ip(IpFloor::decode(&lhs, &rhs)?), + FloorProtocol::RpcConnectionOriented => { + Floor::RpcConnectionOriented(RpcConnectionOrientedFloor::decode(&lhs, &rhs)?) + } + FloorProtocol::UuidId => Floor::Uuid(UuidFloor::decode(&lhs, &rhs)?), + protocol => Err(EpmError::UnsupportedFloor(protocol))?, + }) + } +} + +pub type Tower = Vec; + +impl Encode for Tower { + fn encode(&self, mut writer: impl Write) -> Result<()> { + for floor in self { + floor.encode(&mut writer)?; + } + + Ok(()) + } +} + +pub fn build_tcpip_tower(service: SyntaxId, data_rep: SyntaxId, port: u16, addr: u32) -> Tower { + vec![ + Floor::Uuid(UuidFloor { + uuid: service.uuid, + version: service.version, + version_minor: service.version_minor, + }), + Floor::Uuid(UuidFloor { + uuid: data_rep.uuid, + version: data_rep.version, + version_minor: data_rep.version_minor, + }), + Floor::RpcConnectionOriented(RpcConnectionOrientedFloor { version_minor: 0 }), + Floor::Tcp(TcpFloor { port }), + Floor::Ip(IpFloor { addr }), + ] +} + +pub type EntryHandle = (u32, Uuid); + +impl Encode for Option { + fn encode(&self, mut writer: impl Write) -> Result<()> { + if let Some(entry_handle) = self { + writer.write_u32::(entry_handle.0)?; + entry_handle.1.encode(writer)?; + } else { + write_buf(&[0; 20], &mut writer)?; + } + + Ok(()) + } +} + +impl Decode for Option { + fn decode(mut reader: impl Read) -> Result { + let mut entry_handle_buf = [0; 20]; + read_buf(&mut reader, &mut entry_handle_buf)?; + + Ok(if entry_handle_buf != [0; 20] { + Some(( + u32::from_le_bytes(entry_handle_buf[0..4].try_into().unwrap()), + Uuid::from_slice_le(&entry_handle_buf[4..])?, + )) + } else { + None + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EptMap { + pub obj: Option, + pub tower: Tower, + pub entry_handle: Option, + pub max_towers: u32, +} + +impl EptMap { + pub const OPNUM: u16 = 3; +} + +impl Encode for EptMap { + fn encode(&self, mut writer: impl Write) -> Result<()> { + // obj with a referent id of 1 + write_buf(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], &mut writer)?; + + if let Some(uuid) = self.obj { + uuid.encode(&mut writer)?; + } else { + write_buf(&[0; 16], &mut writer)?; + } + + // Tower referent id 2 + write_buf(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], &mut writer)?; + + let mut encoded_tower = u16::try_from(self.tower.len())?.to_le_bytes().to_vec(); + self.tower.encode(&mut encoded_tower)?; + + writer.write_u64::(u64::try_from(encoded_tower.len())?)?; + writer.write_u32::(u32::try_from(encoded_tower.len())?)?; + + write_buf(&encoded_tower, &mut writer)?; + write_padding::<8>(encoded_tower.len() + 4, &mut writer)?; + + self.entry_handle.encode(&mut writer)?; + writer.write_u32::(self.max_towers)?; + + Ok(()) + } +} + +impl Decode for EptMap { + fn decode(mut reader: impl Read) -> Result { + // obj with a referent id of 1 + reader.read_u64::()?; + + let mut obj = [0; 16]; + read_buf(&mut reader, &mut obj)?; + + let obj = if obj != [0; 16] { + Some(Uuid::from_slice_le(&obj)?) + } else { + None + }; + + // Tower referent id 2 + reader.read_u64::()?; + + let tower_length = reader.read_u64::()?; + reader.read_u32::()?; + + let floor_length = usize::from(reader.read_u16::()?); + let tower = (0..floor_length) + .map(|_| Floor::decode(&mut reader)) + .collect::>>()?; + + read_padding::<8>((tower_length + 4).try_into()?, &mut reader)?; + + let entry_handle = Option::decode(&mut reader)?; + let max_towers = reader.read_u32::()?; + + Ok(Self { + obj, + tower, + entry_handle, + max_towers, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EptMapResult { + pub entry_handle: Option, + pub towers: Vec, + pub status: u32, +} + +impl Encode for EptMapResult { + fn encode(&self, mut writer: impl Write) -> Result<()> { + self.entry_handle.encode(&mut writer)?; + + writer.write_u32::(u32::try_from(self.towers.len())?)?; + // max_tower_count + writer.write_u64::(u64::try_from(self.towers.len())?)?; + + // Tower pointer offset + writer.write_u64::(0)?; + + writer.write_u64::(u64::try_from(self.towers.len())?)?; + + for idx in 0..self.towers.len() { + writer.write_u64::((idx + 3).try_into()?)?; + } + + for tower in &self.towers { + let mut encoded_tower = u16::try_from(tower.len())?.to_le_bytes().to_vec(); + tower.encode(&mut encoded_tower)?; + + writer.write_u64::(u64::try_from(encoded_tower.len())?)?; + writer.write_u32::(u32::try_from(encoded_tower.len())?)?; + write_buf(&encoded_tower, &mut writer)?; + write_padding::<4>(encoded_tower.len(), &mut writer)?; + } + + writer.write_u32::(self.status)?; + + Ok(()) + } +} + +impl Decode for EptMapResult { + fn decode(mut reader: impl Read) -> Result { + let entry_handle = Option::decode(&mut reader)?; + + // num towers + reader.read_u32::()?; + // mac tower count + reader.read_u64::()?; + // tower offset + reader.read_u64::()?; + + let tower_count = usize::try_from(reader.read_u64::()?)?; + // Ignore referent ids + for _ in 0..tower_count { + reader.read_u64::()?; + } + + let towers = (0..tower_count) + .map(|_| { + let tower_length = reader.read_u64::()?; + + reader.read_u32::()?; + + let floor_length = reader.read_u16::()?; + let tower = (0..floor_length) + .map(|_| Floor::decode(&mut reader)) + .collect::>>()?; + + read_padding::<8>((tower_length + 4).try_into()?, &mut reader)?; + + Ok(tower) + }) + .collect::>>()?; + + let status = reader.read_u32::()?; + + Ok(Self { + entry_handle, + towers, + status, + }) + } +} diff --git a/crates/dpapi/src/error.rs b/crates/dpapi/src/error.rs new file mode 100644 index 00000000..c4483b28 --- /dev/null +++ b/crates/dpapi/src/error.rs @@ -0,0 +1,91 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("invalid {name} magic bytes")] + InvalidMagic { + name: &'static str, + expected: &'static [u8], + actual: Vec, + }, + + #[error("invalid {name} length: expected at least {expected} bytes but got {actual}")] + InvalidLength { + name: &'static str, + expected: usize, + actual: usize, + }, + + #[error("invalid {name} url: {url}")] + InvalidUrl { + name: &'static str, + url: String, + error: url::ParseError, + }, + + #[error(transparent)] + Gkdi(#[from] crate::gkdi::GkdiError), + + #[error(transparent)] + Blob(#[from] crate::blob::BlobError), + + #[error(transparent)] + Rpc(#[from] crate::rpc::RpcError), + + #[error(transparent)] + Sid(#[from] crate::sid::SidError), + + #[error(transparent)] + Crypto(#[from] crate::crypto::CryptoError), + + #[error(transparent)] + RpcClient(#[from] crate::rpc::client::RpcClientError), + + #[error(transparent)] + Command(#[from] crate::rpc::verification::CommandError), + + #[error(transparent)] + Auth(#[from] crate::rpc::auth::AuthError), + + #[error(transparent)] + Epm(#[from] crate::epm::EpmError), + + #[error(transparent)] + Client(#[from] crate::client::ClientError), + + #[error("IO error")] + Io(#[from] std::io::Error), + + #[error("UUID error: {0}")] + Uuid(#[from] uuid::Error), + + #[error(transparent)] + IntConversion(#[from] std::num::TryFromIntError), + + #[error("provided buf contains invalid UTF-8 data")] + Utf8(#[from] std::string::FromUtf8Error), + + #[error("{description}: {value}: {error}")] + ParseInt { + description: &'static str, + value: String, + error: std::num::ParseIntError, + }, + + #[error(transparent)] + Asn1(#[from] picky_asn1_der::Asn1DerError), + + #[error(transparent)] + CharSet(#[from] picky_asn1::restricted_string::CharSetError), + + #[error("{0}")] + FromUtf16(String), +} + +impl From for Error { + fn from(err: std::string::FromUtf16Error) -> Self { + Self::FromUtf16(err.to_string()) + } +} + +pub type Result = std::result::Result; diff --git a/crates/dpapi/src/gkdi.rs b/crates/dpapi/src/gkdi.rs new file mode 100644 index 00000000..5317513c --- /dev/null +++ b/crates/dpapi/src/gkdi.rs @@ -0,0 +1,774 @@ +use std::fmt; +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use num_bigint_dig::BigUint; +use rand::rngs::OsRng; +use rand::Rng; +use thiserror::Error; +use uuid::{uuid, Uuid}; + +use crate::blob::KeyIdentifier; +use crate::crypto::{ + compute_kek, compute_kek_from_public_key, compute_l2_key, compute_public_key, kdf, KDS_SERVICE_LABEL, +}; +use crate::rpc::bind::SyntaxId; +use crate::rpc::{read_buf, read_c_str_utf16_le, read_padding, read_vec, write_buf, write_padding, Decode, Encode}; +use crate::str::{encode_utf16_le, from_utf16_le}; +use crate::{Error, Result}; + +pub const ISD_KEY: SyntaxId = SyntaxId { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, +}; + +#[derive(Debug, Error)] +pub enum GkdiError { + #[error("invalid hash algorithm name: {0}")] + InvalidHashName(String), + + #[error("invalid {name} version: expected {expected} but got {actual}")] + InvalidVersion { + name: &'static str, + expected: u32, + actual: u32, + }, + + #[error("invalid elliptic curve id")] + InvalidEllipticCurveId(Vec), + + #[error("invalid kdf algorithm name: expected {expected} but got {actual}")] + InvalidKdfAlgName { expected: &'static str, actual: String }, + + #[error("current user is not authorized to retrieve the KEK information")] + IsNotAuthorized, + + #[error("l0 index does not match requested l0 index")] + InvalidL0Index, + + #[error("bad GetKey response: {0}")] + BadResponse(&'static str), + + #[error("bad GetKey hresult: {0:x?}")] + BadHresult(u32), +} + +pub type GkdiResult = std::result::Result; + +const KDF_ALGORITHM_NAME: &str = "SP800_108_CTR_HMAC"; + +/// GetKey RPC Request +/// +/// This can be used to build the stub data for the GetKey RPC request. +/// The syntax for this function is defined in []`MS-GKDI 3.1.4.1 GetKey (Opnum 0)`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/4cac87a3-521e-4918-a272-240f8fabed39) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GetKey { + /// The the security descriptor for which the group key is being requested. + pub target_sd: Vec, + /// This parameter represents the root key identifier of the requested key. It can be set to NULL. + pub root_key_id: Option, + /// This parameter represents the L0 index of the requested group key. + /// It MUST be a signed 32-bit integer greater than or equal to -1. + pub l0_key_id: i32, + /// This parameter represents the L1 index of the requested group key. + /// It MUST be a signed 32-bit integer between -1 and 31 (inclusive). + pub l1_key_id: i32, + /// This parameter represents the L2 index of the requested group key. + /// It MUST be a 32-bit integer between -1 and 31 (inclusive). + pub l2_key_id: i32, +} + +impl GetKey { + pub const OPNUM: u16 = 0; + + /// Checks the RPC GetKey Response status (`hresult`) and tries to parse the data into [GroupKeyEnvelope]. + pub fn unpack_response(data: &[u8]) -> Result { + if data.len() < 4 { + Err(GkdiError::BadResponse("response data length is too small"))?; + } + let (key_buf, mut hresult_buf) = data.split_at(data.len() - 4); + + let hresult = hresult_buf.read_u32::()?; + if hresult != 0 { + Err(GkdiError::BadHresult(hresult))?; + } + + let mut reader = key_buf; + + let _key_length = reader.read_u32::()?; + // Skip padding + reader.read_u32::()?; + + // Skip the referent id and double up on pointer size + reader.read_u64::()?; + reader.read_u64::()?; + + GroupKeyEnvelope::decode(reader) + } +} + +impl Encode for GetKey { + fn encode(&self, mut writer: impl Write) -> Result<()> { + let target_sd_len = self.target_sd.len().try_into()?; + // cbTargetSD + writer.write_u64::(target_sd_len)?; + // pbTargetSD - pointer header includes the length + padding + writer.write_u64::(target_sd_len)?; + + write_buf(&self.target_sd, &mut writer)?; + + write_padding::<8>(target_sd_len.try_into()?, &mut writer)?; + + if let Some(root_key_id) = self.root_key_id.as_ref() { + write_buf(&[0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00], &mut writer)?; + write_buf(root_key_id.to_bytes_le().as_ref(), &mut writer)?; + } else { + writer.write_u64::(0)?; + }; + + writer.write_i32::(self.l0_key_id)?; + writer.write_i32::(self.l1_key_id)?; + writer.write_i32::(self.l2_key_id)?; + + Ok(()) + } +} + +impl Decode for GetKey { + fn decode(mut reader: impl Read) -> Result { + let target_sd_len = reader.read_u64::()?; + let _offset = reader.read_u64::()?; + + let target_sd = read_vec(target_sd_len.try_into()?, &mut reader)?; + + read_padding::<8>(target_sd_len.try_into()?, &mut reader)?; + + let root_key_id = if reader.read_u64::()? != 0 { + Some(Uuid::decode(&mut reader)?) + } else { + None + }; + + let l0_key_id = reader.read_i32::()?; + let l1_key_id = reader.read_i32::()?; + let l2_key_id = reader.read_i32::()?; + + Ok(Self { + target_sd, + root_key_id, + l0_key_id, + l1_key_id, + l2_key_id, + }) + } +} + +/// Supported hash algorithms. +/// +/// It contains hash algorithms that are listed in the documentation: +/// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/9946aeff-a914-45e9-b9e5-6cb5b4059187 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HashAlg { + Sha1, + Sha256, + Sha384, + Sha512, +} + +impl fmt::Display for HashAlg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HashAlg::Sha1 => write!(f, "SHA1"), + HashAlg::Sha256 => write!(f, "SHA256"), + HashAlg::Sha384 => write!(f, "SHA384"), + HashAlg::Sha512 => write!(f, "SHA512"), + } + } +} + +impl TryFrom<&str> for HashAlg { + type Error = GkdiError; + + fn try_from(data: &str) -> GkdiResult { + match data { + "SHA1" => Ok(HashAlg::Sha1), + "SHA256" => Ok(HashAlg::Sha256), + "SHA384" => Ok(HashAlg::Sha384), + "SHA512" => Ok(HashAlg::Sha512), + _ => Err(GkdiError::InvalidHashName(data.to_owned())), + } + } +} + +/// [KDF Parameters](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/9946aeff-a914-45e9-b9e5-6cb5b4059187) +/// +/// The following specifies the format and field descriptions for the key derivation function (KDF) parameters structure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KdfParameters { + pub hash_alg: HashAlg, +} + +impl KdfParameters { + // The following magic identifiers are specified in the Microsoft documentation: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/9946aeff-a914-45e9-b9e5-6cb5b4059187 + const MAGIC_IDENTIFIER_1: &[u8] = &[0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]; + const MAGIC_IDENTIFIER_2: &[u8] = &[0x00, 0x00, 0x00, 0x00]; +} + +impl Encode for KdfParameters { + fn encode(&self, mut writer: impl Write) -> Result<()> { + let encoded_hash_alg = encode_utf16_le(&self.hash_alg.to_string()); + + write_buf(KdfParameters::MAGIC_IDENTIFIER_1, &mut writer)?; + writer.write_u32::(encoded_hash_alg.len().try_into()?)?; + write_buf(KdfParameters::MAGIC_IDENTIFIER_2, &mut writer)?; + write_buf(&encoded_hash_alg, &mut writer)?; + + Ok(()) + } +} + +impl Decode for KdfParameters { + fn decode(mut reader: impl Read) -> Result { + let mut magic_identifier_1 = [0; 8]; + read_buf(&mut reader, &mut magic_identifier_1)?; + + if magic_identifier_1 != Self::MAGIC_IDENTIFIER_1 { + return Err(Error::InvalidMagic { + name: "KdfParameters::MAGIC_IDENTIFIER_1", + expected: Self::MAGIC_IDENTIFIER_1, + actual: magic_identifier_1.to_vec(), + }); + } + + let hash_name_len: usize = reader.read_u32::()?.try_into()?; + + let mut magic_identifier_2 = [0; 4]; + read_buf(&mut reader, &mut magic_identifier_2)?; + + if magic_identifier_2 != Self::MAGIC_IDENTIFIER_2 { + return Err(Error::InvalidMagic { + name: "KdfParameters::MAGIC_IDENTIFIER_1", + expected: Self::MAGIC_IDENTIFIER_2, + actual: magic_identifier_2.to_vec(), + }); + } + + // The smallest possible hash algorithm name is "SHA1\0", 10 bytes long in UTF-16 encoding. + if hash_name_len < 10 { + Err(Error::InvalidLength { + name: "KdfParameters hash id", + expected: 10, + actual: hash_name_len, + })?; + } + + let buf = read_vec(hash_name_len - 2 /* UTF-16 null terminator char */, &mut reader)?; + // Skip UTF-16 null terminator char. + reader.read_u16::()?; + + Ok(Self { + hash_alg: from_utf16_le(&buf)?.as_str().try_into()?, + }) + } +} + +/// [FFC DH Parameters](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/e15ae269-ee21-446a-a480-de3ea243db5f) +/// +/// This structure specifies field parameters for use in deriving finite field cryptography (FFC) Diffie-Hellman (DH) +/// ([SP800-56A](https://csrc.nist.gov/pubs/sp/800/56/a/r1/final) section 5.7.1) keys, +/// as specified in section [3.1.4.1.2](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/5d373568-dd68-499b-bd06-a3ce16ca7117). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FfcdhParameters { + /// A 32-bit unsigned integer. This field MUST be the length, in bytes, of the public key. + /// This field is encoded using little-endian format. + pub key_length: u32, + /// This is the large prime field order, and is a domain parameter for the FFC DH algorithm ([SP800-56A] section 5.7.1). + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value of the Key length field. + pub field_order: BigUint, + /// The generator of the subgroup, a domain parameter for the FFC DH algorithm ([SP800-56A] section 5.7.1). + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value of the Key length field. + pub generator: BigUint, +} + +impl FfcdhParameters { + // The following magic value is defined in the Microsoft documentation: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/e15ae269-ee21-446a-a480-de3ea243db5f + const MAGIC: &[u8] = &[0x44, 0x48, 0x50, 0x4d]; +} + +impl Encode for FfcdhParameters { + fn encode(&self, mut writer: impl Write) -> Result<()> { + // Calculate total structure length and write it. + // + // Length (4 bytes): A 32-bit unsigned integer. This field MUST be the length, in bytes, of the entire structure. This field is encoded using little-endian format: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/e15ae269-ee21-446a-a480-de3ea243db5f + writer.write_u32::(12 + self.key_length * 2)?; + + write_buf(FfcdhParameters::MAGIC, &mut writer)?; + writer.write_u32::(self.key_length)?; + + let key_len = self.key_length.try_into()?; + + let mut field_order = self.field_order.to_bytes_be(); + field_order.resize(key_len, 0); + write_buf(&field_order, &mut writer)?; + + let mut generator = self.generator.to_bytes_be(); + generator.resize(key_len, 0); + write_buf(&generator, &mut writer)?; + + Ok(()) + } +} + +impl Decode for FfcdhParameters { + fn decode(mut reader: impl Read) -> Result { + let _total_len = reader.read_u32::()?; + + let mut magic = [0; 4]; + read_buf(&mut reader, &mut magic)?; + + if magic != Self::MAGIC { + return Err(Error::InvalidMagic { + name: "FfcdhParameters", + expected: Self::MAGIC, + actual: magic.to_vec(), + }); + } + + let key_length = reader.read_u32::()?; + + let field_order = BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?); + + let generator = BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?); + + Ok(Self { + key_length, + field_order, + generator, + }) + } +} + +/// [FFC DH Key](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/f8770f01-036d-4bf6-a4cf-1bd0e3913404) +/// +/// The following specifies the format and field descriptions for the FFC DH Key structure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FfcdhKey { + /// A 32-bit unsigned integer. The value in this field MUST be equal to the length, in bytes, + /// of the Public key field. This parameter is encoded using little-endian format. + pub key_length: u32, + /// This is the large prime field order, and is a domain parameter for the FFC DH algorithm ([SP800-56A](https://csrc.nist.gov/pubs/sp/800/56/a/r1/final) section 5.7.1). + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value in the Key length field. + pub field_order: BigUint, + /// The generator of the subgroup, a domain parameter for the FFC DH algorithm ([SP800-56A](https://csrc.nist.gov/pubs/sp/800/56/a/r1/final) section 5.7.1). + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value in the Key length field. + pub generator: BigUint, + /// The public key for the FFC DH algorithm ([SP800-56A](https://csrc.nist.gov/pubs/sp/800/56/a/r1/final) section 5.7.1). + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value of the Key length field. + pub public_key: BigUint, +} + +impl FfcdhKey { + // The following magic value is defined in the Microsoft documentation: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/f8770f01-036d-4bf6-a4cf-1bd0e3913404 + const MAGIC: &[u8] = &[0x44, 0x48, 0x50, 0x42]; +} + +impl Encode for FfcdhKey { + fn encode(&self, mut writer: impl Write) -> Result<()> { + write_buf(FfcdhKey::MAGIC, &mut writer)?; + + writer.write_u32::(self.key_length)?; + + let key_len = self.key_length.try_into()?; + + let mut field_order = self.field_order.to_bytes_be(); + field_order.resize(key_len, 0); + write_buf(&field_order, &mut writer)?; + + let mut generator = self.generator.to_bytes_be(); + generator.resize(key_len, 0); + write_buf(&generator, &mut writer)?; + + let mut public_key = self.public_key.to_bytes_be(); + public_key.resize(key_len, 0); + write_buf(&public_key, &mut writer)?; + + Ok(()) + } +} + +impl Decode for FfcdhKey { + fn decode(mut reader: impl Read) -> Result { + let mut magic = [0; 4]; + read_buf(&mut reader, &mut magic)?; + + if magic != FfcdhKey::MAGIC { + return Err(Error::InvalidMagic { + name: "FfcdhKey", + expected: Self::MAGIC, + actual: magic.to_vec(), + }); + } + + let key_length = reader.read_u32::()?; + + Ok(Self { + key_length, + field_order: BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?), + generator: BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?), + public_key: BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?), + }) + } +} + +/// Supported elliptic curves. +/// +/// It contains elliptic curves that are listed in the documentation: +/// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/24876a37-9a92-4187-9052-222bb6f85d4a +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EllipticCurve { + P256, + P384, + P521, +} + +impl From for &[u8] { + fn from(curve: EllipticCurve) -> Self { + match curve { + EllipticCurve::P256 => b"ECK1", + EllipticCurve::P384 => b"ECK3", + EllipticCurve::P521 => b"ECK5", + } + } +} + +impl TryFrom<&[u8]> for EllipticCurve { + type Error = GkdiError; + + fn try_from(value: &[u8]) -> GkdiResult { + match value { + b"ECK1" => Ok(EllipticCurve::P256), + b"ECK3" => Ok(EllipticCurve::P384), + b"ECK5" => Ok(EllipticCurve::P521), + _ => Err(GkdiError::InvalidEllipticCurveId(value.to_vec())), + } + } +} + +/// [ECDH Key](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/24876a37-9a92-4187-9052-222bb6f85d4a) +/// +/// The following specifies the format and field descriptions for the Elliptic Curve Diffie-Hellman (ECDH) Key structure [RFC5114](https://www.rfc-editor.org/info/rfc5114). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EcdhKey { + /// Represents the ECDH field parameters. + pub curve: EllipticCurve, + /// A 32-bit unsigned integer. This field MUST be the length, in bytes, of the public key. + /// This field is encoded using little-endian format. + pub key_length: u32, + /// The x coordinate of the point P that represents the ECDH [RFC5114](https://www.rfc-editor.org/info/rfc5114) public key. + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value in the Key length field. + pub x: BigUint, + /// The y coordinate of the point P that represents the ECDH public key. + /// It MUST be encoded in big-endian format. The length of this field, in bytes, + /// MUST be equal to the value in the Key length field. + pub y: BigUint, +} + +impl Encode for EcdhKey { + fn encode(&self, mut writer: impl Write) -> Result<()> { + write_buf(self.curve.into(), &mut writer)?; + + writer.write_u32::(self.key_length)?; + + let key_len: usize = self.key_length.try_into()?; + + let mut x = self.x.to_bytes_be(); + x.resize(key_len, 0); + write_buf(&x, &mut writer)?; + + let mut y = self.y.to_bytes_be(); + y.resize(key_len, 0); + write_buf(&y, &mut writer)?; + + Ok(()) + } +} + +impl Decode for EcdhKey { + fn decode(mut reader: impl Read) -> Result { + let mut curve_id = [0; 4]; + read_buf(&mut reader, &mut curve_id)?; + let curve = EllipticCurve::try_from(curve_id.as_ref())?; + + let key_length = reader.read_u32::()?; + + Ok(Self { + curve, + key_length, + x: BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?), + y: BigUint::from_bytes_be(&read_vec(key_length.try_into()?, &mut reader)?), + }) + } +} + +/// [Group Key Envelope](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7) +/// +/// The following specifies the format and field descriptions for the Group Key Envelope structure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GroupKeyEnvelope { + /// A 32-bit unsigned integer. Bit 31 (LSB) MUST be set to 1 when this structure is being used to + /// transport a public key, otherwise set to 0. Bit 30 MUST be set to 1 when the key being transported + /// by this structure might be used for encryption and decryption, otherwise it should only be used for decryption. + /// This field is encoded using little-endian format. + pub flags: u32, + /// This field MUST be the L0 index of the key being enveloped. This field is encoded using little-endian format. + pub l0: i32, + /// This field MUST be the L1 index of the key being enveloped, and therefore MUST be a number between 0 and 31, inclusive. + /// This field is encoded using little-endian format. + pub l1: i32, + /// This field MUST be the L2 index of the key being enveloped, and therefore MUST be a number between 0 and 31, inclusive. + /// This field is encoded using little-endian format. + pub l2: i32, + /// A GUID containing the root key identifier of the key being enveloped. + pub root_key_identifier: Uuid, + /// This field MUST be the ADM element KDF algorithm name associated with the ADM element root key, + /// whose identifier is in the Root key identifier field. + pub kdf_alg: String, + /// This field MUST contain the KDF parameters associated with the ADM element root key, + /// whose identifier is in the Root key identifier field. + pub kdf_parameters: Vec, + /// This field MUST be the ADM element Secret agreement algorithm name associated with the ADM element root key, + /// whose identifier is in the Root key identifier field. + pub secret_algorithm: String, + /// This field MUST contain the ADM element Secret agreement algorithm associated with the ADM element root key, + /// whose identifier is in the Root key identifier field. + pub secret_parameters: Vec, + /// A 32-bit unsigned integer. This field MUST be the private key length associated with the root key, + /// whose identifier is in the Root key identifier field. This field is encoded using little-endian format. + pub private_key_length: u32, + /// A 32-bit unsigned integer. This field MUST be the public key length associated with the root key, + /// whose identifier is in the Root key identifier field. This field is encoded using little-endian format. + pub public_key_length: u32, + /// This field MUST be the domain name of the server in DNS format. + pub domain_name: String, + /// This field MUST be the forest name of the server in DNS format. + pub forest_name: String, + /// An L1 seed key ADM element in binary form. + pub l1_key: Vec, + /// The L2 seed key ADM element or the group public key ADM element with group key identifier in binary form. + pub l2_key: Vec, +} + +impl GroupKeyEnvelope { + // The following magic value is defined in the Microsoft documentation: + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7 + const MAGIC: &[u8] = &[0x4B, 0x44, 0x53, 0x4B]; + const VERSION: u32 = 1; + + pub fn is_public_key(&self) -> bool { + self.flags & 1 != 0 + } + + pub fn new_kek(&self) -> Result<(Vec, KeyIdentifier)> { + if self.kdf_alg != KDF_ALGORITHM_NAME { + Err(GkdiError::InvalidKdfAlgName { + expected: KDF_ALGORITHM_NAME, + actual: self.kdf_alg.clone(), + })?; + } + + let kdf_parameters = KdfParameters::decode(self.kdf_parameters.as_slice())?; + let hash_alg = kdf_parameters.hash_alg; + + let mut rand = OsRng; + + let (kek, key_info) = if self.is_public_key() { + // the L2 key is the peer's public key + + let mut private_key = vec![self.private_key_length.div_ceil(8).try_into()?]; + rand.fill(private_key.as_mut_slice()); + + let kek = compute_kek(hash_alg, &self.secret_algorithm, &private_key, &self.l2_key)?; + let key_info = compute_public_key(&self.secret_algorithm, &private_key, &self.l2_key)?; + + (kek, key_info) + } else { + let key_info = rand.gen::<[u8; 32]>(); + let kek = kdf(hash_alg, &self.l2_key, KDS_SERVICE_LABEL, &key_info, 32)?; + + (kek, key_info.to_vec()) + }; + + Ok(( + kek, + KeyIdentifier { + version: 1, + flags: self.flags, + + l0: self.l0, + l1: self.l1, + l2: self.l2, + root_key_identifier: self.root_key_identifier, + + key_info, + domain_name: self.domain_name.clone(), + forest_name: self.forest_name.clone(), + }, + )) + } + + pub fn get_kek(&self, key_identifier: &KeyIdentifier) -> Result> { + if self.is_public_key() { + Err(GkdiError::IsNotAuthorized)?; + } + + if self.l0 != key_identifier.l0 { + Err(GkdiError::InvalidL0Index)?; + } + + if self.kdf_alg != KDF_ALGORITHM_NAME { + Err(GkdiError::InvalidKdfAlgName { + expected: KDF_ALGORITHM_NAME, + actual: self.kdf_alg.clone(), + })?; + } + + let kdf_parameters = KdfParameters::decode(self.kdf_parameters.as_slice())?; + let hash_alg = kdf_parameters.hash_alg; + let l2_key = compute_l2_key(hash_alg, key_identifier.l1, key_identifier.l2, self)?; + + if key_identifier.is_public_key() { + Ok(compute_kek_from_public_key( + hash_alg, + &l2_key, + &self.secret_algorithm, + &key_identifier.key_info, + self.private_key_length.div_ceil(8).try_into()?, + )?) + } else { + Ok(kdf(hash_alg, &l2_key, KDS_SERVICE_LABEL, &key_identifier.key_info, 32)?) + } + } +} + +impl Encode for GroupKeyEnvelope { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u32::(Self::VERSION)?; + + write_buf(GroupKeyEnvelope::MAGIC, &mut writer)?; + writer.write_u32::(self.flags)?; + writer.write_i32::(self.l0)?; + writer.write_i32::(self.l1)?; + writer.write_i32::(self.l2)?; + self.root_key_identifier.encode(&mut writer)?; + + let encoded_kdf_alg = encode_utf16_le(&self.kdf_alg); + let encoded_secret_alg = encode_utf16_le(&self.secret_algorithm); + let encoded_domain_name = encode_utf16_le(&self.domain_name); + let encoded_forest_name = encode_utf16_le(&self.forest_name); + + writer.write_u32::(encoded_kdf_alg.len().try_into()?)?; + writer.write_u32::(self.kdf_parameters.len().try_into()?)?; + writer.write_u32::(encoded_secret_alg.len().try_into()?)?; + writer.write_u32::(self.secret_parameters.len().try_into()?)?; + writer.write_u32::(self.private_key_length)?; + writer.write_u32::(self.public_key_length)?; + writer.write_u32::(self.l1_key.len().try_into()?)?; + writer.write_u32::(self.l2_key.len().try_into()?)?; + writer.write_u32::(encoded_domain_name.len().try_into()?)?; + writer.write_u32::(encoded_forest_name.len().try_into()?)?; + + write_buf(&encoded_kdf_alg, &mut writer)?; + write_buf(&self.kdf_parameters, &mut writer)?; + write_buf(&encoded_secret_alg, &mut writer)?; + write_buf(&self.secret_parameters, &mut writer)?; + write_buf(&encoded_domain_name, &mut writer)?; + write_buf(&encoded_forest_name, &mut writer)?; + write_buf(&self.l1_key, &mut writer)?; + write_buf(&self.l2_key, &mut writer)?; + + Ok(()) + } +} + +impl Decode for GroupKeyEnvelope { + fn decode(mut reader: impl Read) -> Result { + let version = reader.read_u32::()?; + + if version != Self::VERSION { + Err(GkdiError::InvalidVersion { + name: "GroupKeyEnvelope", + expected: Self::VERSION, + actual: version, + })?; + } + + let mut magic = [0; 4]; + read_buf(&mut reader, &mut magic)?; + + if magic != Self::MAGIC { + return Err(Error::InvalidMagic { + name: "GroupKeyEnvelope", + expected: Self::MAGIC, + actual: magic.to_vec(), + }); + } + + let flags = reader.read_u32::()?; + let l0 = reader.read_i32::()?; + let l1 = reader.read_i32::()?; + let l2 = reader.read_i32::()?; + let root_key_identifier = Uuid::decode(&mut reader)?; + + let kdf_alg_len = reader.read_u32::()?; + let kdf_parameters_len = reader.read_u32::()?; + let secret_alg_len = reader.read_u32::()?; + let secret_parameters_len = reader.read_u32::()?; + let private_key_length = reader.read_u32::()?; + let public_key_length = reader.read_u32::()?; + let l1_key_len = reader.read_u32::()?; + let l2_key_len = reader.read_u32::()?; + let domain_len = reader.read_u32::()?; + let forest_len = reader.read_u32::()?; + + let kdf_alg = read_c_str_utf16_le(kdf_alg_len.try_into()?, &mut reader)?; + let kdf_parameters = read_vec(kdf_parameters_len.try_into()?, &mut reader)?; + + let secret_algorithm = read_c_str_utf16_le(secret_alg_len.try_into()?, &mut reader)?; + let secret_parameters = read_vec(secret_parameters_len.try_into()?, &mut reader)?; + + let domain_name = read_c_str_utf16_le(domain_len.try_into()?, &mut reader)?; + let forest_name = read_c_str_utf16_le(forest_len.try_into()?, &mut reader)?; + + let l1_key = read_vec(l1_key_len.try_into()?, &mut reader)?; + let l2_key = read_vec(l2_key_len.try_into()?, &mut reader)?; + + Ok(Self { + flags, + l0, + l1, + l2, + root_key_identifier, + kdf_alg, + kdf_parameters, + secret_algorithm, + secret_parameters, + private_key_length, + public_key_length, + domain_name, + forest_name, + l1_key, + l2_key, + }) + } +} diff --git a/crates/dpapi/src/lib.rs b/crates/dpapi/src/lib.rs new file mode 100644 index 00000000..54d7be45 --- /dev/null +++ b/crates/dpapi/src/lib.rs @@ -0,0 +1,19 @@ +#![doc = include_str!("../README.md")] +#![allow(dead_code)] + +#[macro_use] +extern crate tracing; + +pub mod blob; +mod client; +pub mod crypto; +pub mod epm; +pub mod error; +pub mod gkdi; +pub mod rpc; +pub(crate) mod sid; +pub(crate) mod str; + +pub use client::{n_crypt_protect_secret, n_crypt_unprotect_secret}; +pub use error::{Error, Result}; +pub use sspi::Secret; diff --git a/crates/dpapi/src/rpc/auth.rs b/crates/dpapi/src/rpc/auth.rs new file mode 100644 index 00000000..a291b2a0 --- /dev/null +++ b/crates/dpapi/src/rpc/auth.rs @@ -0,0 +1,240 @@ +use sspi::builders::{AcquireCredentialsHandle, WithoutCredentialUse}; +use sspi::credssp::SspiContext; +use sspi::{ + AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialUse, Credentials, CredentialsBuffers, + DataRepresentation, EncryptionFlags, SecurityBuffer, SecurityBufferFlags, SecurityBufferRef, SecurityStatus, Sspi, +}; +use thiserror::Error; + +use crate::rpc::pdu::{AuthenticationLevel, SecurityProvider, SecurityTrailer}; + +#[derive(Error, Debug)] +pub enum AuthError { + #[error("{0} security provider is not supported")] + SecurityProviderNotSupported(&'static str), + + #[error("SSPI authorization error: {0}")] + Sspi(#[from] sspi::Error), + + #[error("{0}")] + IntConversion(String), +} + +pub type AuthResult = Result; + +/// Performs RPC authentication using underlying SSPI provider. +/// +/// Basically, this is a convenient wrapper over [SspiContext]. +/// It allows to perform RPC authentication without going into details of SSPI configuration and +/// authentication parameters. +#[derive(Debug)] +pub struct AuthProvider { + security_type: SecurityProvider, + security_context: SspiContext, + credentials_handle: Option, + is_finished: bool, + target_name: String, +} + +impl AuthProvider { + /// Creates a new [AuthProvider]. + pub fn new(mut security_context: SspiContext, credentials: Credentials, target_host: &str) -> AuthResult { + let security_type = match &security_context { + SspiContext::Ntlm(_) => SecurityProvider::Winnt, + SspiContext::Kerberos(_) => SecurityProvider::GssKerberos, + SspiContext::Negotiate(_) => SecurityProvider::GssNegotiate, + SspiContext::Pku2u(_) => Err(AuthError::SecurityProviderNotSupported("PKU2U"))?, + #[cfg(feature = "tsssp")] + SspiContext::CredSsp(_) => Err(AuthError::SecurityProviderNotSupported("CredSSP"))?, + }; + + let builder = AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new(); + let AcquireCredentialsHandleResult { credentials_handle, .. } = builder + .with_auth_data(&credentials) + .with_credential_use(CredentialUse::Outbound) + .execute(&mut security_context)?; + + Ok(Self { + security_type, + security_context, + is_finished: false, + credentials_handle, + target_name: format!("host/{}", target_host), + }) + } + + /// Returns a `bool` value indicating whether authentication is complete. + pub fn is_finished(&self) -> bool { + self.is_finished + } + + /// Returns an empty [SecurityTrailer] with correct parameters. + pub fn empty_trailer(&mut self, pad_length: u8) -> AuthResult { + let security_trailer_len = self.security_context.query_context_sizes()?.security_trailer; + Ok(SecurityTrailer { + security_type: self.security_type, + level: AuthenticationLevel::PktPrivacy, + pad_length, + context_id: 0, + auth_value: vec![ + 0; + security_trailer_len + .try_into() + .map_err(|_| AuthError::IntConversion(format!( + "cannot convert security trailer length ({}) to usize", + security_trailer_len + )))? + ], + }) + } + + /// Encrypts input buffers using inner SSPI security context. + /// + /// This method is an equivalent to `GSS_WrapEx()`. More info: [Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550). + /// + /// **Important**. `header`, `body`, `security_trailer_header`, `security_trailer_data` are parts of one RPC PDU: + /// * `header`: RPC PDU header + header data from the RPC PDU body. + /// * `body` contains data from the RPC PDU body that must be encrypted. + /// * `security_trailer_header`: RPC PDU security trailer header data (i.e. security trailer without `auth_value`). + /// * `security_trailer_data`: RPC PDU security trailer `auth_value`. Basically, it's a Kerberos Wrap Token. + /// + /// All encryption is performed in-place. + #[instrument(ret, skip(self))] + pub fn wrap_with_header_sign( + &mut self, + header: &mut [u8], + body: &mut [u8], + security_trailer_header: &mut [u8], + security_trailer_data: &mut [u8], + ) -> AuthResult<()> { + let mut message = vec![ + SecurityBufferRef::data_buf(header).with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::data_buf(body), + SecurityBufferRef::data_buf(security_trailer_header) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::token_buf(security_trailer_data), + ]; + + self.security_context + .encrypt_message(EncryptionFlags::empty(), &mut message, 0)?; + + Ok(()) + } + + /// Encrypts input buffers using inner SSPI security context. + /// + /// This method is an equivalent to `GSS_WrapEx()`. More info: [Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550). + /// + /// **Important**. `body` and `security_trailer_data` are parts of one RPC PDU: + /// * `body` contains data from the RPC PDU body that must be encrypted. + /// * `security_trailer_data`: RPC PDU security trailer `auth_value`. Basically, it's a Kerberos Wrap Token. + /// + /// All encryption is performed in-place. + pub fn wrap(&mut self, body: &mut [u8], security_trailer_data: &mut [u8]) -> AuthResult<()> { + let mut message = vec![ + SecurityBufferRef::data_buf(body), + SecurityBufferRef::token_buf(security_trailer_data), + ]; + + self.security_context + .encrypt_message(EncryptionFlags::empty(), &mut message, 0)?; + + Ok(()) + } + + /// Decrypts input buffers using inner SSPI security context. + /// + /// This method is an equivalent to `GSS_UnwrapEx()`. More info: [Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550) and [GSS_UnwrapEx() Call](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/9e3981a9-6564-4db6-a70e-4af4c07d03b3). + /// + /// **Important**. `header`, `body`, `security_trailer_header`, and `security_trailer_data` are parts of one RPC PDU: + /// * `header`: RPC PDU header + header data from the RPC PDU body. + /// * `body` contains data from the RPC PDU body that needs to be decrypted. + /// * `security_trailer_header`: RPC PDU security trailer header data (i.e. security trailer without `auth_value`). + /// * `security_trailer_data`: `auth_value` of the RPC PDU security trailer. Basically, it's a Kerberos Wrap Token. + /// + /// All decryption is performed in-place. + #[instrument(ret, skip(self))] + pub fn unwrap_with_header_sign( + &mut self, + header: &mut [u8], + body: &mut [u8], + security_trailer_header: &mut [u8], + security_trailer_data: &mut [u8], + ) -> AuthResult> { + let mut message = vec![ + SecurityBufferRef::data_buf(header).with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::data_buf(body), + SecurityBufferRef::data_buf(security_trailer_header) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::token_buf(security_trailer_data), + ]; + + self.security_context.decrypt_message(&mut message, 0)?; + + Ok(message[1].data().to_vec()) + } + + /// Decrypts input buffers using inner SSPI security context. + /// + /// This method is an equivalent to `GSS_UnwrapEx()`. More info: [Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550) and [GSS_UnwrapEx() Call](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/9e3981a9-6564-4db6-a70e-4af4c07d03b3). + /// + /// **Important**. `body` and `security_trailer_data` are parts of one RPC PDU: + /// * `body` contains data from the RPC PDU body that needs to be decrypted. + /// * `security_trailer_data`: `auth_value` of the RPC PDU security trailer. Basically, it's a Kerberos Wrap Token. + /// + /// All decryption is performed in-place. + #[instrument(ret, skip(self))] + pub fn unwrap(&mut self, body: &mut [u8], security_trailer_data: &mut [u8]) -> AuthResult> { + let mut message = vec![ + SecurityBufferRef::data_buf(body), + SecurityBufferRef::token_buf(security_trailer_data), + ]; + + self.security_context.decrypt_message(&mut message, 0)?; + + Ok(message[1].data().to_vec()) + } + + /// Performs one step in authorization process. + /// + /// The client should call this method until `self.is_finished()` is `true`. + #[instrument(ret, fields(state = ?self.is_finished), skip(self))] + pub fn initialize_security_context(&mut self, in_token: Vec) -> AuthResult { + let mut input_token = [SecurityBuffer::new(in_token, BufferType::Token)]; + let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; + let mut credentials_handle = self.credentials_handle.take(); + + let mut builder = self + .security_context + .initialize_security_context() + .with_credentials_handle(&mut credentials_handle) + .with_context_requirements( + // Warning: do not change these flags if you don't know what you are doing. + // The absence or presence of some flags can break the RPC auth. For example, + // if you enable the `ClientRequestFlags::USER_TO_USER`, then it will fail. + ClientRequestFlags::MUTUAL_AUTH + | ClientRequestFlags::INTEGRITY + | ClientRequestFlags::USE_DCE_STYLE + | ClientRequestFlags::SEQUENCE_DETECT + | ClientRequestFlags::REPLAY_DETECT + | ClientRequestFlags::CONFIDENTIALITY, + ) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(&self.target_name) + .with_input(&mut input_token) + .with_output(&mut output_token); + let result = self.security_context.initialize_security_context_sync(&mut builder)?; + self.is_finished = result.status == SecurityStatus::Ok; + + self.credentials_handle = credentials_handle; + let auth_value = output_token.remove(0).buffer; + + Ok(SecurityTrailer { + security_type: self.security_type, + level: AuthenticationLevel::PktPrivacy, + pad_length: 0, + context_id: 0, + auth_value, + }) + } +} diff --git a/crates/dpapi/src/rpc/bind.rs b/crates/dpapi/src/rpc/bind.rs new file mode 100644 index 00000000..3c359bb9 --- /dev/null +++ b/crates/dpapi/src/rpc/bind.rs @@ -0,0 +1,362 @@ +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use thiserror::Error; +use uuid::Uuid; + +use crate::rpc::{read_padding, read_vec, write_buf, write_padding, Decode, Encode}; +use crate::Result; + +#[derive(Debug, Error)] +pub enum BindError { + #[error("invalid context result code value: {0}")] + InvalidContextResultCode(u16), +} + +pub type BindResult = std::result::Result; + +/// [BindTimeFeatureNegotiationBitmask](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/cef529cc-77b5-4794-85dc-91e1467e80f0) +/// +/// The bind time feature negotiation bitmask is an array of eight octets, each of which is interpreted as a bitmask. +/// **Bitmask**: Currently, only the two least significant bits in the first element of the array are defined. +/// +/// ```not_rust +/// typedef struct { +/// unsigned char Bitmask[8]; +/// } BindTimeFeatureNegotiationBitmask; +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u64)] +pub enum BindTimeFeatureNegotiationBitmask { + None = 0x0, + /// Client supports security context multiplexing, as specified in section [3.3.1.5.4](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/c8b3c80f-b2ba-4a78-bf36-dabba4278194). + SecurityContextMultiplexingSupported = 0x01, + /// Client supports keeping the connection open after sending the orphaned PDU, as specified in section [3.3.1.5.10](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/44d6f02e-55f3-4814-973e-cf0bc3287c44). + KeepConnectionOnOrphanSupported = 0x02, +} + +impl BindTimeFeatureNegotiationBitmask { + pub fn as_u64(&self) -> u64 { + *self as u64 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SyntaxId { + pub uuid: Uuid, + pub version: u16, + pub version_minor: u16, +} + +impl Encode for SyntaxId { + fn encode(&self, mut writer: impl Write) -> Result<()> { + self.uuid.encode(&mut writer)?; + writer.write_u16::(self.version)?; + writer.write_u16::(self.version_minor)?; + + Ok(()) + } +} + +impl Decode for SyntaxId { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + uuid: Uuid::decode(&mut reader)?, + version: reader.read_u16::()?, + version_minor: reader.read_u16::()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContextElement { + pub context_id: u16, + pub abstract_syntax: SyntaxId, + pub transfer_syntaxes: Vec, +} + +impl Encode for ContextElement { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.context_id)?; + writer.write_u16::(self.transfer_syntaxes.len().try_into()?)?; + + self.abstract_syntax.encode(&mut writer)?; + + for transfer_syntax in &self.transfer_syntaxes { + transfer_syntax.encode(&mut writer)?; + } + + Ok(()) + } +} + +impl Decode for ContextElement { + fn decode(mut reader: impl Read) -> Result { + let context_id = reader.read_u16::()?; + let transfer_syntaxes_count = usize::from(reader.read_u16::()?); + let abstract_syntax = SyntaxId::decode(&mut reader)?; + + let transfer_syntaxes = (0..transfer_syntaxes_count) + .map(|_| SyntaxId::decode(&mut reader)) + .collect::>()?; + + Ok(Self { + context_id, + abstract_syntax, + transfer_syntaxes, + }) + } +} + +/// [`p_cont_def_result_t` Enumerator](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/8df5c4d4-364d-468c-81fe-ec94c1b40917) +/// +/// These extensions specify a new member, `negotiate_ack`, which is added to the `p_cont_def_result_t` enumeration +/// (specified in C706 section 12.6), with the numeric value of `3`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ContextResultCode { + Acceptance = 0, + UserRejection = 1, + ProviderRejection = 2, + NegotiateAck = 3, // MS-RPCE extension +} + +impl ContextResultCode { + pub fn as_u16(&self) -> u16 { + *self as u16 + } +} + +impl TryFrom for ContextResultCode { + type Error = BindError; + + fn try_from(v: u16) -> BindResult { + match v { + 0 => Ok(Self::Acceptance), + 1 => Ok(Self::UserRejection), + 2 => Ok(Self::ProviderRejection), + 3 => Ok(Self::NegotiateAck), + v => Err(BindError::InvalidContextResultCode(v)), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContextResult { + pub result: ContextResultCode, + pub reason: u16, + pub syntax: Uuid, + pub syntax_version: u32, +} + +impl Encode for ContextResult { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.result.as_u16())?; + writer.write_u16::(self.reason)?; + self.syntax.encode(&mut writer)?; + writer.write_u32::(self.syntax_version)?; + + Ok(()) + } +} + +impl Decode for ContextResult { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + result: reader.read_u16::()?.try_into()?, + reason: reader.read_u16::()?, + syntax: Uuid::decode(&mut reader)?, + syntax_version: reader.read_u32::()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bind { + pub max_xmit_frag: u16, + pub max_recv_frag: u16, + pub assoc_group: u32, + pub contexts: Vec, +} + +impl Encode for Bind { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.max_xmit_frag)?; + writer.write_u16::(self.max_recv_frag)?; + writer.write_u32::(self.assoc_group)?; + writer.write_u32::(self.contexts.len().try_into()?)?; + + for context in &self.contexts { + context.encode(&mut writer)?; + } + + Ok(()) + } +} + +impl Decode for Bind { + fn decode(mut reader: impl Read) -> Result { + let max_xmit_frag = reader.read_u16::()?; + let max_recv_frag = reader.read_u16::()?; + let assoc_group = reader.read_u32::()?; + + let contexts_count = reader.read_u32::()?; + let contexts = (0..contexts_count) + .map(|_| ContextElement::decode(&mut reader)) + .collect::>()?; + + Ok(Self { + max_xmit_frag, + max_recv_frag, + assoc_group, + contexts, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BindAck { + pub max_xmit_frag: u16, + pub max_recv_frag: u16, + pub assoc_group: u32, + pub sec_addr: String, + pub results: Vec, +} + +impl Encode for BindAck { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.max_xmit_frag)?; + writer.write_u16::(self.max_recv_frag)?; + writer.write_u32::(self.assoc_group)?; + + let sec_addr_len = if !self.sec_addr.is_empty() { + let sec_addr_len = self.sec_addr.len() + 1; + writer.write_u16::(sec_addr_len.try_into()?)?; + + write_buf(self.sec_addr.as_bytes(), &mut writer)?; + writer.write_u8(0)?; + + sec_addr_len + } else { + writer.write_u16::(0)?; + + 0 + } + 2; + + write_padding::<4>(sec_addr_len, &mut writer)?; + + writer.write_u32::(self.results.len().try_into()?)?; + for result in &self.results { + result.encode(&mut writer)?; + } + + Ok(()) + } +} + +impl Decode for BindAck { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + max_xmit_frag: reader.read_u16::()?, + max_recv_frag: reader.read_u16::()?, + assoc_group: reader.read_u32::()?, + sec_addr: { + let sec_addr_len = usize::from(reader.read_u16::()?); + let sec_addr = if sec_addr_len > 0 { + let buf = read_vec(sec_addr_len - 1 /* null byte */, &mut reader)?; + + // Read null-terminator byte. + reader.read_u8()?; + + String::from_utf8(buf)? + } else { + String::new() + }; + + read_padding::<4>(sec_addr_len + 2 /* len */, &mut reader)?; + + sec_addr + }, + results: { + let results_count = reader.read_u32::()?; + (0..results_count) + .map(|_| ContextResult::decode(&mut reader)) + .collect::>()? + }, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BindNak { + pub reason: u16, + pub versions: Vec<(u8, u8)>, +} + +impl Encode for BindNak { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.reason)?; + + writer.write_u8(self.versions.len().try_into()?)?; + for version in &self.versions { + writer.write_u8(version.0)?; + writer.write_u8(version.1)?; + } + + let versions_buf_len = 1 /* len */ + 2 /* version size */ * self.versions.len(); + write_padding::<4>(versions_buf_len, &mut writer)?; + + Ok(()) + } +} + +impl Decode for BindNak { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + reason: reader.read_u16::()?, + versions: { + let versions_count = reader.read_u8()?; + let versions = (0..versions_count) + .map(|_| Ok((reader.read_u8()?, reader.read_u8()?))) + .collect::>>()?; + + let versions_buf_len = 1 /* len */ + 2 /* version size */ * versions.len(); + read_padding::<4>(versions_buf_len, reader)?; + + versions + }, + }) + } +} + +// `AlterContext` has the same layout as `Bind`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AlterContext(pub Bind); + +impl Encode for AlterContext { + fn encode(&self, writer: impl Write) -> Result<()> { + self.0.encode(writer) + } +} + +impl Decode for AlterContext { + fn decode(reader: impl Read) -> Result { + Ok(Self(Bind::decode(reader)?)) + } +} + +// `AlterContextResponse` has the same layout as `BindAck`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AlterContextResponse(pub BindAck); + +impl Encode for AlterContextResponse { + fn encode(&self, writer: impl Write) -> Result<()> { + self.0.encode(writer) + } +} + +impl Decode for AlterContextResponse { + fn decode(reader: impl Read) -> Result { + Ok(Self(BindAck::decode(reader)?)) + } +} diff --git a/crates/dpapi/src/rpc/client.rs b/crates/dpapi/src/rpc/client.rs new file mode 100644 index 00000000..fa487dea --- /dev/null +++ b/crates/dpapi/src/rpc/client.rs @@ -0,0 +1,388 @@ +use std::net::{TcpStream, ToSocketAddrs}; + +use thiserror::Error; +use uuid::{uuid, Uuid}; + +use crate::rpc::auth::AuthProvider; +use crate::rpc::bind::{ + AlterContext, Bind, BindAck, BindTimeFeatureNegotiationBitmask, ContextElement, ContextResultCode, SyntaxId, +}; +use crate::rpc::pdu::{DataRepr, PacketFlags, PacketType, Pdu, PduData, PduHeader, SecurityTrailer}; +use crate::rpc::request::Request; +use crate::rpc::verification::VerificationTrailer; +use crate::rpc::{read_buf, read_vec, write_padding, Decode, EncodeExt}; +use crate::Result; + +pub const NDR64: SyntaxId = SyntaxId { + uuid: uuid!("71710533-beba-4937-8319-b5dbef9ccc36"), + version: 1, + version_minor: 0, +}; +pub const NDR: SyntaxId = SyntaxId { + uuid: uuid!("8a885d04-1ceb-11c9-9fe8-08002b104860"), + version: 2, + version_minor: 0, +}; +pub const CALL_ID: u32 = 1; + +pub fn bind_time_feature_negotiation(flags: BindTimeFeatureNegotiationBitmask) -> SyntaxId { + SyntaxId { + uuid: Uuid::from_fields(0x6cb71c2c, 0x9812, 0x4540, &flags.as_u64().to_be_bytes()), + version: 1, + version_minor: 0, + } +} + +#[derive(Debug, Error)] +pub enum RpcClientError { + #[error("invalid encryption offset: {0}")] + InvalidEncryptionOffset(&'static str), +} + +/// Represents structural offsets in RPC PDU. +/// +/// This structure is used to split the encoded RPC PDU into separate parts before encryption or decryption. +#[derive(Debug, Copy, Clone)] +struct EncryptionOffsets { + /// RPC PDU header length. + pub pdu_header_len: usize, + /// Indicated how many bytes precede RPC PDU security trailer. + pub security_trailer_offset: usize, +} + +impl EncryptionOffsets { + /// RPC PDU header length + RPC Request header data length. + const REQUEST_PDU_HEADER_LEN: usize = 24; +} + +/// General RPC client. +/// +/// All RPC communication is done using this RPC client. It can connect to RPC server, +/// authenticate, and send RPC requests. +pub struct RpcClient { + stream: TcpStream, + sign_header: bool, + auth: AuthProvider, +} + +impl RpcClient { + /// Connects to the RPC server. + /// + /// Returns a new RPC client that is ready to send/receive data. + pub fn connect(addr: A, auth: AuthProvider) -> Result { + Ok(Self { + stream: TcpStream::connect(addr)?, + sign_header: false, + auth, + }) + } + + fn create_pdu_header(packet_type: PacketType, packet_flags: PacketFlags, auth_len: u16, call_id: u32) -> PduHeader { + PduHeader { + version: 5, + version_minor: 0, + packet_type, + packet_flags: packet_flags | PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr::default(), + // We will set `frag_len` later after building the PDU. + frag_len: 0, + auth_len, + call_id, + } + } + + fn create_bind_pdu(contexts: Vec, security_trailer: Option) -> Result { + let (auth_len, packet_flags) = if let Some(security_trailer) = security_trailer.as_ref() { + (security_trailer.auth_value.len(), PacketFlags::PfcSupportHeaderSign) + } else { + (0, PacketFlags::None) + }; + + Ok(Pdu { + header: Self::create_pdu_header(PacketType::Bind, packet_flags, auth_len.try_into()?, CALL_ID), + data: PduData::Bind(Bind { + max_xmit_frag: 5840, + max_recv_frag: 5840, + assoc_group: 0, + contexts, + }), + security_trailer, + }) + } + + fn create_alter_context_pdu(&self, contexts: Vec, sec_trailer: SecurityTrailer) -> Result { + let packet_flags = if self.sign_header { + PacketFlags::PfcSupportHeaderSign + } else { + PacketFlags::None + }; + + Ok(Pdu { + header: Self::create_pdu_header( + PacketType::AlterContext, + packet_flags, + sec_trailer.auth_value.len().try_into()?, + CALL_ID, + ), + data: PduData::AlterContext(AlterContext(Bind { + max_xmit_frag: 5840, + max_recv_frag: 5840, + assoc_group: 0, + contexts, + })), + security_trailer: Some(sec_trailer), + }) + } + + #[instrument(level = "trace", ret, skip(self))] + fn create_authenticated_request( + &mut self, + context_id: u16, + opnum: u16, + mut stub_data: Vec, + verification_trailer: Option, + ) -> Result<(Pdu, EncryptionOffsets)> { + if let Some(verification_trailer) = verification_trailer.as_ref() { + write_padding::<4>(stub_data.len(), &mut stub_data)?; + let encoded_verification_trailer = verification_trailer.encode_to_vec()?; + stub_data.extend_from_slice(&encoded_verification_trailer); + } + + // The security trailer must be aligned to the next 16 byte boundary after the stub data. + // This padding is included as part of the stub data to be encrypted. + let padding_len = write_padding::<16>(stub_data.len(), &mut stub_data)?; + let security_trailer = self.auth.empty_trailer(padding_len.try_into()?)?; + + let encrypt_offsets = EncryptionOffsets { + pdu_header_len: EncryptionOffsets::REQUEST_PDU_HEADER_LEN, + security_trailer_offset: EncryptionOffsets::REQUEST_PDU_HEADER_LEN + stub_data.len(), + }; + + Ok(( + Pdu { + header: Self::create_pdu_header( + PacketType::Request, + PacketFlags::None, + security_trailer.auth_value.len().try_into()?, + CALL_ID, + ), + data: PduData::Request(Request { + alloc_hint: stub_data.len().try_into()?, + context_id, + opnum, + obj: None, + stub_data, + }), + security_trailer: Some(security_trailer), + }, + encrypt_offsets, + )) + } + + #[instrument(level = "trace", ret, skip(self))] + fn create_request(&self, context_id: u16, opnum: u16, stub_data: Vec) -> Result { + Ok(Pdu { + header: Self::create_pdu_header(PacketType::Request, PacketFlags::None, 0, CALL_ID), + data: PduData::Request(Request { + alloc_hint: stub_data.len().try_into()?, + context_id, + opnum, + obj: None, + stub_data, + }), + security_trailer: None, + }) + } + + #[instrument(level = "trace", ret, skip(self))] + fn encrypt_pdu(&mut self, pdu_encoded: &mut [u8], encrypt_offsets: EncryptionOffsets) -> Result<()> { + let EncryptionOffsets { + pdu_header_len, + security_trailer_offset, + } = encrypt_offsets; + + if pdu_encoded.len() < security_trailer_offset + SecurityTrailer::HEADER_LEN { + Err(RpcClientError::InvalidEncryptionOffset( + "security trailer offset is too big or PDU is corrupted", + ))?; + } + + let (header, data) = pdu_encoded.split_at_mut(pdu_header_len); + let (body, data) = data.split_at_mut(security_trailer_offset - pdu_header_len); + let (sec_trailer_header, sec_trailer_auth_value) = data.split_at_mut(SecurityTrailer::HEADER_LEN); + + if self.sign_header { + self.auth + .wrap_with_header_sign(header, body, sec_trailer_header, sec_trailer_auth_value)?; + } else { + self.auth.wrap(body, sec_trailer_auth_value)?; + } + + Ok(()) + } + + fn process_bind_ack(ack: &BindAck, contexts: &[ContextElement]) -> Vec { + contexts + .iter() + .enumerate() + .filter_map(|(index, context)| { + if let Some(result) = ack.results.get(index) { + if result.result == ContextResultCode::Acceptance { + Some(context.clone()) + } else { + None + } + } else { + None + } + }) + .collect() + } + + #[instrument(level = "trace", ret, skip(self))] + fn decrypt_response( + &mut self, + response: &mut [u8], + pdu_header: &PduHeader, + encrypt_offsets: EncryptionOffsets, + ) -> Result<()> { + let EncryptionOffsets { + pdu_header_len, + security_trailer_offset: _, + } = encrypt_offsets; + + let security_trailer_offset = + usize::from(pdu_header.frag_len) - usize::from(pdu_header.auth_len) - SecurityTrailer::HEADER_LEN; + + if response.len() < security_trailer_offset + SecurityTrailer::HEADER_LEN { + Err(RpcClientError::InvalidEncryptionOffset( + "security trailer offset is too big or PDU is corrupted", + ))?; + } + + let (header, data) = response.split_at_mut(pdu_header_len); + let (body, data) = data.split_at_mut(security_trailer_offset - pdu_header_len); + let (sec_trailer_header, sec_trailer_data) = data.split_at_mut(SecurityTrailer::HEADER_LEN); + + if self.sign_header { + self.auth + .unwrap_with_header_sign(header, body, sec_trailer_header, sec_trailer_data)?; + } else { + self.auth.unwrap(body, sec_trailer_data)?; + } + + Ok(()) + } + + #[instrument(level = "trace", ret, skip(self))] + fn send_pdu(&mut self, pdu: Pdu, encrypt_offsets: Option) -> Result { + let mut pdu_encoded = pdu.encode_to_vec()?; + let frag_len = u16::try_from(pdu_encoded.len())?; + // Set `frag_len` in the PDU header. + pdu_encoded[8..10].copy_from_slice(&frag_len.to_le_bytes()); + + if let Some(encrypt_offsets) = encrypt_offsets { + self.encrypt_pdu(&mut pdu_encoded, encrypt_offsets)?; + } + + super::write_buf(&pdu_encoded, &mut self.stream)?; + + // Read PDU header + let mut pdu_buf = read_vec(PduHeader::LENGTH, &mut self.stream)?; + let pdu_header = PduHeader::decode(pdu_buf.as_slice())?; + + pdu_buf.resize(usize::from(pdu_header.frag_len), 0); + read_buf(&mut self.stream, &mut pdu_buf[PduHeader::LENGTH..])?; + + if let (true, Some(encrypt_offsets)) = (pdu_header.auth_len > 0, encrypt_offsets) { + self.decrypt_response(&mut pdu_buf, &pdu_header, encrypt_offsets)?; + } + + let mut pdu = Pdu::decode(pdu_buf.as_slice())?; + pdu.data = pdu.data.into_error()?; + + Ok(pdu) + } + + /// Sends the authenticated RPC request. + #[instrument(level = "trace", ret, skip(self))] + pub fn authenticated_request( + &mut self, + context_id: u16, + opnum: u16, + stub_data: Vec, + verification_trailer: Option, + ) -> Result { + let (pdu, encrypt_offsets) = + self.create_authenticated_request(context_id, opnum, stub_data, verification_trailer)?; + + self.send_pdu(pdu, Some(encrypt_offsets)) + } + + /// Sends the RPC request. + #[instrument(level = "trace", ret, skip(self))] + pub fn request(&mut self, context_id: u16, opnum: u16, stub_data: Vec) -> Result { + let pdu = self.create_request(context_id, opnum, stub_data)?; + + self.send_pdu(pdu, None) + } + + /// Performs the RPC bind/bind_ack exchange. + #[instrument(level = "trace", ret, skip(self))] + pub fn bind(&mut self, contexts: &[ContextElement]) -> Result { + let bind = Self::create_bind_pdu(contexts.to_vec(), None)?; + let pdu_resp = self.send_pdu(bind, None)?; + + let Pdu { + header: _, + data, + security_trailer: _, + } = pdu_resp; + + Ok(data.bind_ack()?) + } + + /// Performs the RPC bind/bind_ack exchange. + /// + /// The bind/bind_ack exchange continues until authentication is finished. + #[instrument(level = "trace", ret, skip(self))] + pub fn bind_authenticate(&mut self, contexts: &[ContextElement]) -> Result { + // The first `initialize_security_context` call is Negotiation in our Kerberos implementation. + // We don't need its result in RPC authentication. + let _security_trailer = self.auth.initialize_security_context(Vec::new())?; + + let security_trailer = self.auth.initialize_security_context(Vec::new())?; + let bind = Self::create_bind_pdu(contexts.to_vec(), Some(security_trailer))?; + + self.sign_header = true; + + let pdu_resp = self.send_pdu(bind, None)?; + + let Pdu { + header: _, + data, + security_trailer, + } = pdu_resp; + let bind_ack = data.bind_ack()?; + + let final_contexts = Self::process_bind_ack(&bind_ack, contexts); + let mut in_token = security_trailer.map(|security_trailer| security_trailer.auth_value); + + loop { + let security_trailer = self.auth.initialize_security_context(in_token.unwrap_or_default())?; + + if self.auth.is_finished() { + break; + } + + let alter_context = self.create_alter_context_pdu(final_contexts.clone(), security_trailer)?; + let alter_context_resp = self.send_pdu(alter_context, None)?; + + in_token = alter_context_resp + .security_trailer + .map(|security_trailer| security_trailer.auth_value); + } + + Ok(bind_ack) + } +} diff --git a/crates/dpapi/src/rpc/mod.rs b/crates/dpapi/src/rpc/mod.rs new file mode 100644 index 00000000..46e6469e --- /dev/null +++ b/crates/dpapi/src/rpc/mod.rs @@ -0,0 +1,155 @@ +pub mod auth; +pub mod bind; +pub mod client; +pub mod pdu; +pub mod request; +pub mod verification; + +use std::io::{ErrorKind as IoErrorKind, Read, Write}; + +pub use auth::AuthProvider; +pub use client::{bind_time_feature_negotiation, RpcClient, NDR, NDR64}; +use thiserror::Error; +use uuid::Uuid; + +use self::bind::BindError; +use self::pdu::PduError; +use crate::{Error, Result}; + +#[derive(Debug, Error)] +pub enum RpcError { + #[error(transparent)] + Bind(BindError), + + #[error(transparent)] + Pdu(PduError), +} + +impl From for Error { + fn from(err: PduError) -> Self { + Error::from(RpcError::Pdu(err)) + } +} + +impl From for Error { + fn from(err: BindError) -> Self { + Error::from(RpcError::Bind(err)) + } +} + +pub trait Encode { + fn encode(&self, writer: impl Write) -> Result<()>; +} + +pub trait EncodeExt: Encode { + fn encode_to_vec(&self) -> Result> { + let mut buf = Vec::new(); + + self.encode(&mut buf)?; + + Ok(buf) + } +} + +impl EncodeExt for T {} + +pub trait Decode: Sized { + fn decode(reader: impl Read) -> Result; +} + +impl Encode for Uuid { + fn encode(&self, writer: impl Write) -> Result<()> { + write_buf(&self.to_bytes_le(), writer)?; + + Ok(()) + } +} + +impl Decode for Uuid { + fn decode(reader: impl Read) -> Result { + let mut uuid_buf = [0; 16]; + read_buf(reader, &mut uuid_buf)?; + + Ok(Uuid::from_slice_le(&uuid_buf)?) + } +} + +pub fn write_padding(buf_len: usize, writer: impl Write) -> Result { + let padding_len = (ALIGNMENT - (buf_len % ALIGNMENT)) % ALIGNMENT; + let padding_buf = vec![0; padding_len]; + + write_buf(&padding_buf, writer)?; + + Ok(padding_len) +} + +pub fn read_padding(buf_len: usize, reader: impl Read) -> Result<()> { + let padding_len = (ALIGNMENT - (buf_len % ALIGNMENT)) % ALIGNMENT; + let mut padding_buf = vec![0; padding_len]; + + read_buf(reader, &mut padding_buf)?; + + Ok(()) +} + +pub fn read_to_end(mut reader: impl Read) -> Result> { + let mut buf = Vec::new(); + reader.read_to_end(&mut buf)?; + + Ok(buf) +} + +pub fn write_buf(mut data: &[u8], mut writer: impl Write) -> Result<()> { + while !data.is_empty() { + let bytes_written = writer.write(data)?; + data = &data[bytes_written..]; + + if bytes_written == 0 { + return Err(Error::Io(IoErrorKind::WriteZero.into())); + } + } + + Ok(()) +} + +pub fn read_buf(mut reader: impl Read, mut buf: &mut [u8]) -> Result<()> { + while !buf.is_empty() { + let bytes_read = reader.read(buf)?; + buf = &mut buf[bytes_read..]; + + if bytes_read == 0 { + return Err(Error::Io(IoErrorKind::UnexpectedEof.into())); + } + } + + Ok(()) +} + +pub fn read_vec(len: usize, reader: impl Read) -> Result> { + let mut buf = vec![0; len]; + + read_buf(reader, &mut buf)?; + + Ok(buf) +} + +pub fn read_c_str_utf16_le(len: usize, mut reader: impl Read) -> Result { + use byteorder::{LittleEndian, ReadBytesExt}; + + use crate::str::from_utf16_le; + + if len < 2 { + return Err(Error::InvalidLength { + name: "UTF-16 string", + expected: 2, + actual: len, + }); + } + + let buf = read_vec(len - 2 /* UTF16 null terminator */, &mut reader)?; + + // Read UTF16 null terminator. + reader.read_u16::()?; + + from_utf16_le(&buf) +} diff --git a/crates/dpapi/src/rpc/pdu.rs b/crates/dpapi/src/rpc/pdu.rs new file mode 100644 index 00000000..ad890116 --- /dev/null +++ b/crates/dpapi/src/rpc/pdu.rs @@ -0,0 +1,505 @@ +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use thiserror::Error; + +use super::{read_to_end, read_vec, write_buf, Decode, Encode}; +use crate::rpc::bind::{AlterContext, AlterContextResponse, Bind, BindAck, BindNak}; +use crate::rpc::request::{Request, Response}; +use crate::Result; + +#[derive(Error, Debug)] +pub enum PduError { + #[error("invalid integer representation value: {0}")] + InvalidIntRepr(u8), + + #[error("invalid character representation value: {0}")] + InvalidCharacterRepr(u8), + + #[error("invalid floating point representation value: {0}")] + InvalidFloatingPointRepr(u8), + + #[error("invalid packet type value: {0}")] + InvalidPacketType(u8), + + #[error("invalid packet flags value: {0}")] + InvalidPacketFlags(u8), + + #[error("invalid security provider value: {0}")] + InvalidSecurityProvider(u8), + + #[error("invalid authentication level value: {0}")] + InvalidAuthenticationLevel(u8), + + #[error("invalid fault flags value: {0}")] + InvalidFaultFlags(u8), + + #[error("{0:?} PDU is not supported")] + PduNotSupported(PacketType), + + #[error("invalid fragment (PDU) length: {0}")] + InvalidFragLength(u16), + + #[error("RPC failed: {0}")] + RpcFail(&'static str), +} + +pub type PduResult = std::result::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromPrimitive)] +#[repr(u8)] +pub enum IntRepr { + BigEndian = 0, + #[default] + LittleEndian = 1, +} + +impl IntRepr { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromPrimitive)] +#[repr(u8)] +pub enum CharacterRepr { + #[default] + Ascii = 0, + Ebcdic = 1, +} + +impl CharacterRepr { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromPrimitive)] +#[repr(u8)] +pub enum FloatingPointRepr { + #[default] + Ieee = 0, + Vax = 1, + Cray = 2, + Ibm = 3, +} + +impl FloatingPointRepr { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(u8)] +pub enum PacketType { + Request = 0, + Ping = 1, + Response = 2, + Fault = 3, + Working = 4, + Nocall = 5, + Reject = 6, + Ack = 7, + ClCancel = 8, + Fack = 9, + CancelAck = 10, + Bind = 11, + BindAck = 12, + BindNak = 13, + AlterContext = 14, + AlterContextResponse = 15, + Shutdown = 17, + CoCancel = 18, + Orphaned = 19, +} + +impl PacketType { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +bitflags::bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] + pub struct PacketFlags: u8 { + const None = 0x00; + const PfcFirstFrag = 0x01; + const PfcLastFrag = 0x02; + // PfcPendingCancel = 0x04, + const PfcSupportHeaderSign = 0x04; // MS-RPCE extension used in Bind/AlterContext + const PfcReserved1 = 0x08; + const PfcConcMpx = 0x10; + const PfcDidNotExecute = 0x20; + const PfcMaybe = 0x40; + const PfcObjectUuid = 0x80; + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DataRepr { + pub byte_order: IntRepr, + pub character: CharacterRepr, + pub floating_point: FloatingPointRepr, +} + +impl Encode for DataRepr { + fn encode(&self, mut writer: impl Write) -> Result<()> { + let first_octet = (self.byte_order.as_u8()) << 4 | self.character.as_u8(); + writer.write_u8(first_octet)?; + writer.write_u8(self.floating_point.as_u8())?; + + // Padding + writer.write_u16::(0)?; + + Ok(()) + } +} + +impl Decode for DataRepr { + fn decode(mut reader: impl Read) -> Result { + let first_octet = reader.read_u8()?; + + let integer_representation = (first_octet & 0b11110000) >> 4; + let character_representation = first_octet & 0b00001111; + let floating_representation = reader.read_u8()?; + + let data_representation = Self { + byte_order: IntRepr::from_u8(integer_representation) + .ok_or(PduError::InvalidIntRepr(integer_representation))?, + character: CharacterRepr::from_u8(character_representation) + .ok_or(PduError::InvalidCharacterRepr(character_representation))?, + floating_point: FloatingPointRepr::from_u8(floating_representation) + .ok_or(PduError::InvalidFloatingPointRepr(floating_representation))?, + }; + + // Padding. + reader.read_u16::()?; + + Ok(data_representation) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PduHeader { + pub version: u8, + pub version_minor: u8, + pub packet_type: PacketType, + pub packet_flags: PacketFlags, + pub data_rep: DataRepr, + pub frag_len: u16, + pub auth_len: u16, + pub call_id: u32, +} + +impl PduHeader { + /// Length of the encoded [PduHeader]. + pub const LENGTH: usize = 16; +} + +impl Encode for PduHeader { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u8(self.version)?; + writer.write_u8(self.version_minor)?; + writer.write_u8(self.packet_type.as_u8())?; + writer.write_u8(self.packet_flags.bits())?; + self.data_rep.encode(&mut writer)?; + writer.write_u16::(self.frag_len)?; + writer.write_u16::(self.auth_len)?; + writer.write_u32::(self.call_id)?; + + Ok(()) + } +} + +impl Decode for PduHeader { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + version: reader.read_u8()?, + version_minor: reader.read_u8()?, + packet_type: { + let packet_type = reader.read_u8()?; + PacketType::from_u8(packet_type).ok_or(PduError::InvalidPacketType(packet_type))? + }, + packet_flags: { + let packet_flags = reader.read_u8()?; + PacketFlags::from_bits(packet_flags).ok_or(PduError::InvalidPacketFlags(packet_flags))? + }, + data_rep: DataRepr::decode(&mut reader)?, + frag_len: reader.read_u16::()?, + auth_len: reader.read_u16::()?, + call_id: reader.read_u32::()?, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(u8)] +pub enum SecurityProvider { + None = 0x00, + GssNegotiate = 0x09, + Winnt = 0x0a, + GssSchannel = 0x0e, + GssKerberos = 0x10, + Netlogon = 0x44, + Default = 0xff, +} + +impl SecurityProvider { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(u8)] +pub enum AuthenticationLevel { + Default = 0x00, + None = 0x01, + Connect = 0x02, + Call = 0x03, + Pkt = 0x04, + PktIntegrity = 0x05, + PktPrivacy = 0x06, +} + +impl AuthenticationLevel { + pub fn as_u8(&self) -> u8 { + *self as u8 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SecurityTrailer { + pub security_type: SecurityProvider, + pub level: AuthenticationLevel, + pub pad_length: u8, + pub context_id: u32, + pub auth_value: Vec, +} + +impl SecurityTrailer { + // `SecurityTrailer` size but without `auth_value`. + pub const HEADER_LEN: usize = 8; +} + +impl Encode for SecurityTrailer { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u8(self.security_type.as_u8())?; + writer.write_u8(self.level.as_u8())?; + writer.write_u8(self.pad_length)?; + writer.write_u8(0)?; // Auth-Rsrvd + writer.write_u32::(self.context_id)?; + write_buf(&self.auth_value, writer)?; + + Ok(()) + } +} + +impl Decode for SecurityTrailer { + fn decode(mut reader: impl Read) -> Result { + let security_provider = reader.read_u8()?; + let authentication_level = reader.read_u8()?; + + Ok(Self { + security_type: SecurityProvider::from_u8(security_provider) + .ok_or(PduError::InvalidSecurityProvider(security_provider))?, + level: AuthenticationLevel::from_u8(authentication_level) + .ok_or(PduError::InvalidAuthenticationLevel(authentication_level))?, + pad_length: reader.read_u8()?, + context_id: { + // Skip Auth-Rsrvd. + reader.read_u8()?; + + reader.read_u32::()? + }, + auth_value: read_to_end(reader)?, + }) + } +} + +bitflags::bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] + pub struct FaultFlags: u8 { + const None = 0x00; + const ExtendedErrorPresent = 0x01; + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Fault { + pub alloc_hint: u32, + pub context_id: u16, + pub cancel_count: u8, + // Extension of MS-RPCE. + pub flags: FaultFlags, + pub status: u32, + pub stub_data: Vec, +} + +impl Encode for Fault { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u32::(self.alloc_hint)?; + writer.write_u16::(self.context_id)?; + writer.write_u8(self.cancel_count)?; + writer.write_u8(self.flags.bits())?; + writer.write_u32::(self.status)?; + // alignment padding + writer.write_u32::(0)?; + write_buf(&self.stub_data, writer)?; + + Ok(()) + } +} + +impl Decode for Fault { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + alloc_hint: reader.read_u32::()?, + context_id: reader.read_u16::()?, + cancel_count: reader.read_u8()?, + flags: { + let fault_flags = reader.read_u8()?; + FaultFlags::from_bits(fault_flags).ok_or(PduError::InvalidFaultFlags(fault_flags))? + }, + status: reader.read_u32::()?, + stub_data: read_to_end(reader)?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PduData { + Bind(Bind), + BindAck(BindAck), + BindNak(BindNak), + AlterContext(AlterContext), + AlterContextResponse(AlterContextResponse), + Request(Request), + Response(Response), + Fault(Fault), +} + +impl PduData { + /// Returns [BindAck] extracted from the inner data. + /// + /// Returns an error if the inner data is not `BindAck` or `AlterContextResponse`. + pub fn bind_ack(self) -> PduResult { + match self { + PduData::BindAck(bind_ack) => Ok(bind_ack), + PduData::AlterContextResponse(alter_context) => Ok(alter_context.0), + _ => Err(PduError::RpcFail("BindAcknowledge PDU is expected")), + } + } + + /// Checks if the [PduData] contains any error PDU inside. Returns an error if so. + pub fn into_error(self) -> PduResult { + if let PduData::Fault(_) = self { + Err(PduError::RpcFail("got unexpected Fault PDU")) + } else if let PduData::BindNak(_) = self { + Err(PduError::RpcFail("got unexpected BindAcknowledge PDU")) + } else { + Ok(self) + } + } + + pub fn decode(pdu_header: &PduHeader, data_len: usize, reader: impl Read) -> Result { + let buf = read_vec(data_len, reader)?; + + match pdu_header.packet_type { + PacketType::Bind => Ok(PduData::Bind(Bind::decode(buf.as_slice())?)), + PacketType::BindAck => Ok(PduData::BindAck(BindAck::decode(buf.as_slice())?)), + PacketType::BindNak => Ok(PduData::BindNak(BindNak::decode(buf.as_slice())?)), + PacketType::AlterContext => Ok(PduData::AlterContext(AlterContext::decode(buf.as_slice())?)), + PacketType::AlterContextResponse => Ok(PduData::AlterContextResponse(AlterContextResponse::decode( + buf.as_slice(), + )?)), + PacketType::Request => Ok(PduData::Request(Request::decode(pdu_header, buf.as_slice())?)), + PacketType::Response => Ok(PduData::Response(Response::decode(buf.as_slice())?)), + PacketType::Fault => Ok(PduData::Fault(Fault::decode(buf.as_slice())?)), + packet_type => Err(PduError::PduNotSupported(packet_type))?, + } + } +} + +impl Encode for PduData { + fn encode(&self, writer: impl Write) -> Result<()> { + match self { + PduData::Bind(bind) => bind.encode(writer), + PduData::BindAck(bind_ack) => bind_ack.encode(writer), + PduData::BindNak(bind_nak) => bind_nak.encode(writer), + PduData::AlterContext(alter_context) => alter_context.encode(writer), + PduData::AlterContextResponse(alter_context_response) => alter_context_response.encode(writer), + PduData::Request(request) => request.encode(writer), + PduData::Response(response) => response.encode(writer), + PduData::Fault(fault) => fault.encode(writer), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Pdu { + pub header: PduHeader, + pub data: PduData, + pub security_trailer: Option, +} + +impl Pdu { + /// Tries to extract PDU Response from the inner data. + /// + /// Return an error if the PDU is any type then `Response`. + pub fn try_into_response(self) -> PduResult { + if let PduData::Response(response) = self.data { + Ok(response) + } else { + Err(PduError::RpcFail("got unexpected PDU: expected Response PDU")) + } + } +} + +impl Encode for Pdu { + fn encode(&self, mut writer: impl Write) -> Result<()> { + self.header.encode(&mut writer)?; + self.data.encode(&mut writer)?; + + if let Some(security_trailer) = self.security_trailer.as_ref() { + security_trailer.encode(writer)?; + } + + Ok(()) + } +} + +impl Decode for Pdu { + fn decode(mut reader: impl Read) -> Result { + let header = PduHeader::decode(&mut reader)?; + + let security_trailer_len = if header.auth_len > 0 { + SecurityTrailer::HEADER_LEN + } else { + 0 + } + usize::from(header.auth_len); + + let data = PduData::decode( + &header, + usize::from(header.frag_len) + .checked_sub(security_trailer_len + PduHeader::LENGTH) + .ok_or(PduError::InvalidFragLength(header.frag_len))?, + &mut reader, + )?; + + let security_trailer = if header.auth_len > 0 { + Some(SecurityTrailer::decode(reader)?) + } else { + None + }; + + Ok(Self { + header, + data, + security_trailer, + }) + } +} diff --git a/crates/dpapi/src/rpc/request.rs b/crates/dpapi/src/rpc/request.rs new file mode 100644 index 00000000..3023564c --- /dev/null +++ b/crates/dpapi/src/rpc/request.rs @@ -0,0 +1,87 @@ +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use uuid::Uuid; + +use super::{read_to_end, write_buf, Decode, Encode}; +use crate::rpc::pdu::{PacketFlags, PduHeader}; +use crate::Result; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Request { + pub alloc_hint: u32, + pub context_id: u16, + pub opnum: u16, + pub obj: Option, + pub stub_data: Vec, +} + +impl Encode for Request { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u32::(self.alloc_hint)?; + writer.write_u16::(self.context_id)?; + writer.write_u16::(self.opnum)?; + if let Some(obj) = self.obj.as_ref() { + obj.encode(&mut writer)?; + } + write_buf(&self.stub_data, writer)?; + + Ok(()) + } +} + +impl Request { + pub fn decode(pdu_header: &PduHeader, mut reader: impl Read) -> Result { + Ok(Self { + alloc_hint: reader.read_u32::()?, + context_id: reader.read_u16::()?, + opnum: reader.read_u16::()?, + obj: if pdu_header.packet_flags.contains(PacketFlags::PfcObjectUuid) { + Some(Uuid::decode(&mut reader)?) + } else { + None + }, + stub_data: read_to_end(reader)?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Response { + pub alloc_hint: u32, + pub context_id: u16, + pub cancel_count: u8, + pub stub_data: Vec, +} + +impl Encode for Response { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u32::(self.alloc_hint)?; + writer.write_u16::(self.context_id)?; + writer.write_u8(self.cancel_count)?; + // Reserved. + writer.write_u8(0)?; + + write_buf(&self.stub_data, writer)?; + + Ok(()) + } +} + +impl Decode for Response { + fn decode(mut reader: impl Read) -> Result { + Ok(Self { + alloc_hint: reader.read_u32::()?, + context_id: reader.read_u16::()?, + cancel_count: { + let cancel_count = reader.read_u8()?; + + // Reserved + reader.read_u8()?; + + cancel_count + }, + stub_data: read_to_end(reader)?, + }) + } +} diff --git a/crates/dpapi/src/rpc/verification.rs b/crates/dpapi/src/rpc/verification.rs new file mode 100644 index 00000000..718d63b0 --- /dev/null +++ b/crates/dpapi/src/rpc/verification.rs @@ -0,0 +1,259 @@ +use std::io::{Read, Write}; + +use bitflags::bitflags; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use thiserror::Error; + +use crate::rpc::bind::SyntaxId; +use crate::rpc::pdu::{DataRepr, PacketType}; +use crate::rpc::{read_vec, write_buf, Decode, Encode, EncodeExt}; +use crate::Result; + +#[derive(Debug, Error)] +pub enum CommandError { + #[error("invalid RPC command type: {0}")] + InvalidCommandType(u16), + + #[error("invalid RPC command flags: {0}")] + InvalidCommandFlags(u16), + + #[error("invalid RPC bitmask command value length: expected exactly 4 bytes but got {0} bytes")] + InvalidCommandBitmaskValueLength(usize), + + #[error("invalid packet RPC type value in RPC command: {0}")] + InvalidPacketType(u8), + + #[error("invalid VerificationTrailer signature")] + InvalidVerificationTrailerSignature { expected: &'static [u8], actual: Vec }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(u16)] +pub enum CommandType { + Bitmask1 = 0x0001, + Pcontext = 0x0002, + Header2 = 0x0003, +} + +impl CommandType { + pub fn as_u16(self) -> u16 { + self as u16 + } +} + +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] + pub struct CommandFlags: u16 { + const None = 0; + const SecVtCommandEnd = 0x4000; + const SecVtMustProcessCommand = 0x8000; + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Command { + Bitmask1(CommandBitmask), + Pcontext(CommandPContext), + Header2(CommandHeader2), +} + +impl Command { + pub fn flags(&self) -> CommandFlags { + match self { + Command::Bitmask1(command) => command.flags, + Command::Pcontext(command) => command.flags, + Command::Header2(command) => command.flags, + } + } + + pub fn command_type(&self) -> CommandType { + match self { + Command::Bitmask1(_) => CommandType::Bitmask1, + Command::Pcontext(_) => CommandType::Pcontext, + Command::Header2(_) => CommandType::Header2, + } + } +} + +impl Encode for Command { + fn encode(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(self.command_type().as_u16() | self.flags().bits())?; + + match self { + Command::Bitmask1(command) => command.encode_value(writer), + Command::Pcontext(command) => command.encode_value(writer), + Command::Header2(command) => command.encode_value(writer), + } + } +} + +impl Decode for Command { + fn decode(mut reader: impl Read) -> Result { + let cmd_field = reader.read_u16::()?; + + let command_type = cmd_field & 0x3fff; + let command_flags = cmd_field & 0xc000; + + let command = CommandType::from_u16(command_type).ok_or(CommandError::InvalidCommandType(command_type))?; + let flags = CommandFlags::from_bits(command_flags).ok_or(CommandError::InvalidCommandFlags(command_flags))?; + + let value_len = reader.read_u16::()?; + let value = read_vec(usize::from(value_len), reader)?; + + Ok(match command { + CommandType::Bitmask1 => Self::Bitmask1(CommandBitmask::from_flags_and_value(flags, &value)?), + CommandType::Pcontext => Self::Pcontext(CommandPContext::from_flags_and_value(flags, &value)?), + CommandType::Header2 => Self::Header2(CommandHeader2::from_flags_and_value(flags, &value)?), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommandBitmask { + pub bits: u32, + pub flags: CommandFlags, +} + +impl CommandBitmask { + fn from_flags_and_value(flags: CommandFlags, value: &[u8]) -> Result { + if value.len() != 4 { + Err(CommandError::InvalidCommandBitmaskValueLength(value.len()))?; + } + + let bits: [u8; 4] = value.try_into().expect("length is checked above"); + + Ok(Self { + flags, + bits: u32::from_le_bytes(bits), + }) + } + + fn encode_value(&self, mut writer: impl Write) -> Result<()> { + writer.write_u16::(4)?; + write_buf(self.bits.to_le_bytes().as_slice(), writer) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommandPContext { + pub flags: CommandFlags, + pub interface_id: SyntaxId, + pub transfer_syntax: SyntaxId, +} + +impl CommandPContext { + fn from_flags_and_value(flags: CommandFlags, value: &[u8]) -> Result { + let mut reader = value; + let interface_id = SyntaxId::decode(&mut reader)?; + let transfer_syntax = SyntaxId::decode(&mut reader)?; + + Ok(Self { + flags, + interface_id, + transfer_syntax, + }) + } + + fn encode_value(&self, mut writer: impl Write) -> Result<()> { + let mut value = self.interface_id.encode_to_vec()?; + self.transfer_syntax.encode(&mut value)?; + + writer.write_u16::(value.len().try_into()?)?; + write_buf(&value, writer) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommandHeader2 { + pub flags: CommandFlags, + pub packet_type: PacketType, + pub data_rep: DataRepr, + pub call_id: u32, + pub context_id: u16, + pub opnum: u16, +} + +impl CommandHeader2 { + fn from_flags_and_value(flags: CommandFlags, value: &[u8]) -> Result { + let mut reader = value; + + Ok(Self { + flags, + packet_type: { + let packet_type = reader.read_u8()?; + reader.read_u8()?; + reader.read_u8()?; + reader.read_u8()?; + + PacketType::from_u8(packet_type).ok_or(CommandError::InvalidPacketType(packet_type))? + }, + data_rep: DataRepr::decode(&mut reader)?, + call_id: reader.read_u32::()?, + context_id: reader.read_u16::()?, + opnum: reader.read_u16::()?, + }) + } + + fn encode_value(&self, mut writer: impl Write) -> Result<()> { + let mut value = vec![self.packet_type as u8]; + // Reserved + value.extend_from_slice(&[0, 0, 0]); + self.data_rep.encode(&mut value)?; + value.write_u32::(self.call_id)?; + value.write_u16::(self.context_id)?; + value.write_u16::(self.opnum)?; + + writer.write_u16::(value.len().try_into()?)?; + write_buf(&value, writer) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VerificationTrailer { + pub commands: Vec, +} + +impl VerificationTrailer { + const SIGNATURE: &[u8] = &[138, 227, 19, 113, 2, 244, 54, 113]; +} + +impl Encode for VerificationTrailer { + fn encode(&self, mut writer: impl Write) -> Result<()> { + write_buf(VerificationTrailer::SIGNATURE, &mut writer)?; + + for command in &self.commands { + command.encode(&mut writer)?; + } + + Ok(()) + } +} + +impl Decode for VerificationTrailer { + fn decode(mut reader: impl Read) -> Result { + let signature = read_vec(VerificationTrailer::SIGNATURE.len(), &mut reader)?; + + if signature != VerificationTrailer::SIGNATURE { + Err(CommandError::InvalidVerificationTrailerSignature { + expected: VerificationTrailer::SIGNATURE, + actual: signature, + })?; + } + + let mut commands = Vec::new(); + loop { + let command = Command::decode(&mut reader)?; + let flags = command.flags(); + + commands.push(command); + + if flags.contains(CommandFlags::SecVtCommandEnd) { + break; + } + } + + Ok(Self { commands }) + } +} diff --git a/crates/dpapi/src/sid.rs b/crates/dpapi/src/sid.rs new file mode 100644 index 00000000..15420cef --- /dev/null +++ b/crates/dpapi/src/sid.rs @@ -0,0 +1,144 @@ +use std::sync::LazyLock; + +use regex::Regex; +use thiserror::Error; + +use crate::{Error, Result}; + +#[derive(Debug, Error)] +pub enum SidError { + #[error("invalid sid value: {0}")] + InvalidSid(String), +} + +static SID_PATTERN: LazyLock = + LazyLock::new(|| Regex::new(r"^S-(\d)-(\d+)(?:-\d+){1,15}$").expect("valid SID regex")); + +pub fn sid_to_bytes(sid: &str) -> Result> { + if !SID_PATTERN.is_match(sid) { + Err(SidError::InvalidSid(sid.to_owned()))?; + } + + let parts = sid.split('-').collect::>(); + + if parts.len() < 3 { + Err(SidError::InvalidSid(sid.to_owned()))?; + } + + let revision = parts[1].parse::().map_err(|error| Error::ParseInt { + description: "cannot parse SID part", + value: parts[1].to_owned(), + error, + })?; + let authority = parts[2].parse::().map_err(|error| Error::ParseInt { + description: "cannot parse SID part", + value: parts[2].to_owned(), + error, + })?; + + let mut data = Vec::new(); + data.extend_from_slice(&authority.to_be_bytes()); + data[0] = revision; + data[1] = u8::try_from(parts.len() - 3)?; + + for part in parts.iter().skip(3) { + let sub_auth = part.parse::().map_err(|error| Error::ParseInt { + description: "cannot parse SID part", + value: part.to_string(), + error, + })?; + data.extend_from_slice(&sub_auth.to_le_bytes()); + } + + Ok(data) +} + +pub fn ace_to_bytes(sid: &str, access_mask: u32) -> Result> { + let sid = sid_to_bytes(sid)?; + + let mut data = Vec::new(); + + // AceType, AceFlags - ACCESS_ALLOWED_ACE_TYPE. + data.extend_from_slice(&[0, 0]); + data.extend_from_slice(&u16::try_from(8 + sid.len())?.to_le_bytes()); + data.extend_from_slice(&access_mask.to_le_bytes()); + data.extend_from_slice(&sid); + + Ok(data) +} + +pub fn acl_to_bytes(aces: &[Vec]) -> Result> { + let ace_data_len = aces.iter().map(|a| a.len()).sum::(); + + let mut data = Vec::new(); + + // AclRevision, Sbz1 - ACL_REVISION. + data.extend_from_slice(&[0x02, 0x00]); + data.extend_from_slice(&u16::try_from(8 + ace_data_len)?.to_le_bytes()); + data.extend_from_slice(&u16::try_from(aces.len())?.to_le_bytes()); + // Sbz1. + data.extend_from_slice(&[0x00, 0x00]); + for ace in aces { + data.extend_from_slice(ace); + } + + Ok(data) +} + +pub fn sd_to_bytes(owner: &str, group: &str, sacl: Option<&[Vec]>, dacl: Option<&[Vec]>) -> Result> { + // Self-Relative. + let mut control: u16 = 0b10000000 << 8; + + // While MS-DTYP state there is no required order for the dynamic data, it + // is important that the raw bytes are exactly what Microsoft uses on the + // server side when it computes the seed key values. Luckily the footnote + // give the correct order the MS-GKDI expects: Sacl, Dacl, Owner, Group + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/11e1608c-6169-4fbc-9c33-373fc9b224f4#Appendix_A_72 + let mut dynamic_data = Vec::new(); + + // Length of the SD header bytes + let mut current_offset: u32 = 20; + + let mut sacl_offset = 0; + if let Some(sacl) = sacl { + let sacl_bytes = acl_to_bytes(sacl)?; + sacl_offset = current_offset; + current_offset += u32::try_from(sacl_bytes.len())?; + + // SACL Present. + control |= 0b00010000; + dynamic_data.extend_from_slice(&sacl_bytes); + } + + let mut dacl_offset = 0; + if let Some(dacl) = dacl { + let dacl_bytes = acl_to_bytes(dacl)?; + dacl_offset = current_offset; + current_offset += u32::try_from(dacl_bytes.len())?; + + // DACL Present. + control |= 0b00000100; + dynamic_data.extend_from_slice(&dacl_bytes); + } + + let owner_bytes = sid_to_bytes(owner)?; + let owner_offset = current_offset; + current_offset += u32::try_from(owner_bytes.len())?; + dynamic_data.extend_from_slice(&owner_bytes); + + let group_bytes = sid_to_bytes(group)?; + let group_offset = current_offset; + dynamic_data.extend_from_slice(&group_bytes); + + // Revision and Sbz1. + let mut data = [0x01, 0x00].to_vec(); + + data.extend_from_slice(&control.to_le_bytes()); + data.extend_from_slice(&owner_offset.to_le_bytes()); + data.extend_from_slice(&group_offset.to_le_bytes()); + data.extend_from_slice(&sacl_offset.to_le_bytes()); + data.extend_from_slice(&dacl_offset.to_le_bytes()); + data.extend_from_slice(&dynamic_data); + + Ok(data) +} diff --git a/crates/dpapi/src/str.rs b/crates/dpapi/src/str.rs new file mode 100644 index 00000000..7488cc8c --- /dev/null +++ b/crates/dpapi/src/str.rs @@ -0,0 +1,32 @@ +use crate::{Error, Result}; + +/// Decodes a UTF-16–encoded byte slice into a [String]. +/// +/// The input `data` slice should has the size multiple of two (`data.len() % 2 == 0`). +/// Otherwise, the function will return an error. +/// +/// *Note*: this function does not expect a NULL-char at the end of the byte slice. +pub fn from_utf16_le(data: &[u8]) -> Result { + if data.len() % 2 != 0 { + return Err(Error::FromUtf16( + "invalid UTF-16: byte slice should has the size multiple of two".into(), + )); + } + + Ok(String::from_utf16( + &data + .chunks(2) + .map(|c| u16::from_le_bytes(c.try_into().unwrap())) + .collect::>(), + )?) +} + +/// Encodes str into a UTF-16 encoded byte array. +/// +/// *Note*: this function automatically appends a NULL-char. +pub fn encode_utf16_le(data: &str) -> Vec { + data.encode_utf16() + .chain(std::iter::once(0)) + .flat_map(|v| v.to_le_bytes()) + .collect::>() +} diff --git a/crates/dpapi/tests/dpapi/blob.rs b/crates/dpapi/tests/dpapi/blob.rs new file mode 100644 index 00000000..4b64b8a6 --- /dev/null +++ b/crates/dpapi/tests/dpapi/blob.rs @@ -0,0 +1,168 @@ +use std::str::FromStr; + +use dpapi::blob::*; +use dpapi::rpc::Decode; +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{AesAuthEncParams, AesMode, AesParameters}; +use uuid::Uuid; + +const DPAPI_BLOB_DATA: &[u8] = &[ + 48, 130, 4, 77, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 3, 160, 130, 4, 62, 48, 130, 4, 58, 2, 1, 2, 49, 130, 4, 6, + 162, 130, 4, 2, 2, 1, 4, 48, 130, 3, 196, 4, 130, 3, 108, 1, 0, 0, 0, 75, 68, 83, 75, 3, 0, 0, 0, 105, 1, 0, 0, 16, + 0, 0, 0, 3, 0, 0, 0, 113, 194, 120, 215, 37, 144, 130, 154, 246, 220, 184, 150, 11, 138, 216, 197, 8, 3, 0, 0, 24, + 0, 0, 0, 24, 0, 0, 0, 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, + 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, + 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, + 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, + 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, + 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, + 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, + 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, + 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, + 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, + 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, + 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, + 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, + 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, + 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, + 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, + 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, + 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, + 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, + 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, + 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89, 45, 48, 255, 175, + 224, 178, 34, 113, 55, 121, 103, 94, 57, 230, 149, 227, 2, 8, 211, 56, 135, 63, 75, 228, 67, 79, 182, 168, 130, 79, + 28, 56, 65, 78, 255, 48, 67, 5, 243, 1, 170, 131, 242, 24, 216, 174, 93, 89, 249, 12, 215, 25, 248, 12, 146, 191, + 38, 9, 239, 136, 197, 113, 125, 222, 79, 184, 149, 180, 198, 185, 10, 161, 28, 53, 69, 19, 173, 197, 112, 73, 23, + 172, 239, 88, 66, 170, 206, 185, 238, 228, 152, 153, 163, 198, 94, 147, 212, 117, 120, 83, 30, 158, 8, 70, 1, 73, + 134, 237, 77, 162, 147, 56, 224, 231, 179, 30, 110, 19, 55, 253, 176, 115, 101, 171, 146, 59, 227, 37, 145, 200, + 156, 20, 33, 186, 8, 34, 118, 162, 125, 114, 229, 11, 202, 36, 115, 124, 83, 60, 251, 141, 83, 244, 164, 213, 197, + 199, 2, 130, 173, 22, 120, 61, 63, 196, 111, 60, 184, 58, 17, 34, 166, 237, 250, 238, 19, 150, 192, 123, 172, 162, + 70, 227, 90, 165, 58, 139, 124, 87, 199, 135, 30, 146, 142, 203, 133, 133, 54, 26, 54, 229, 134, 122, 117, 207, 31, + 184, 148, 68, 232, 89, 132, 91, 246, 40, 87, 225, 14, 74, 23, 81, 228, 241, 146, 171, 106, 211, 196, 222, 192, 142, + 81, 207, 169, 185, 24, 161, 88, 75, 138, 97, 111, 92, 43, 214, 190, 140, 12, 124, 177, 67, 125, 237, 147, 195, 41, + 40, 100, 0, 111, 0, 109, 0, 97, 0, 105, 0, 110, 0, 46, 0, 116, 0, 101, 0, 115, 0, 116, 0, 0, 0, 100, 0, 111, 0, + 109, 0, 97, 0, 105, 0, 110, 0, 46, 0, 116, 0, 101, 0, 115, 0, 116, 0, 0, 0, 48, 82, 6, 9, 43, 6, 1, 4, 1, 130, 55, + 74, 1, 48, 69, 6, 10, 43, 6, 1, 4, 1, 130, 55, 74, 1, 1, 48, 55, 48, 53, 48, 51, 12, 3, 83, 73, 68, 12, 44, 83, 45, + 49, 45, 53, 45, 50, 49, 45, 51, 51, 51, 55, 51, 51, 55, 57, 55, 51, 45, 51, 50, 57, 55, 48, 55, 56, 48, 50, 56, 45, + 52, 51, 55, 51, 56, 54, 48, 54, 54, 45, 53, 49, 50, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 1, 45, 4, 40, 137, + 127, 196, 63, 116, 142, 253, 9, 87, 39, 221, 233, 143, 78, 26, 111, 251, 157, 65, 99, 211, 159, 179, 116, 208, 73, + 199, 61, 137, 105, 12, 126, 250, 69, 230, 190, 17, 158, 13, 107, 48, 43, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, + 48, 30, 6, 9, 96, 134, 72, 1, 101, 3, 4, 1, 46, 48, 17, 4, 12, 158, 91, 46, 23, 194, 63, 4, 252, 53, 37, 225, 24, + 2, 1, 16, 228, 205, 246, 84, 114, 42, 73, 213, 95, 83, 8, 85, 14, 196, 232, 170, 198, 208, 190, 73, 81, 22, 246, + 19, 42, 77, 89, 23, 159, 215, 19, 142, 201, 75, 83, 110, 37, 17, 213, 202, 13, 55, 141, 236, 60, 66, 61, 85, 197, + 10, 96, 220, 65, 143, 144, 23, 130, 72, 70, 224, 43, 98, 4, 200, 179, 39, 60, 159, 196, 67, 55, 99, 148, 71, 59, + 249, 123, 220, 85, 128, 9, 81, 173, 249, 35, 141, 138, 2, 255, 224, 56, 205, 77, 123, 22, 1, 47, 122, 232, 184, + 121, 3, 224, 80, 0, 216, 227, 16, 222, 27, 45, 28, 163, 68, 178, 242, 103, 58, 61, 90, 92, 77, 228, 99, 38, 75, + 149, 100, 235, 158, 176, 76, 82, 113, 28, 51, 197, 167, 169, 116, 13, 102, 84, 136, 85, 182, +]; + +fn testing_blob() -> DpapiBlob { + DpapiBlob { + key_identifier: KeyIdentifier { + version: 1, + flags: 3, + l0: 361, + l1: 16, + l2: 3, + root_key_identifier: Uuid::from_str("d778c271-9025-9a82-f6dc-b8960b8ad8c5").unwrap(), + key_info: vec![ + 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, + 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, + 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, + 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, + 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, + 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, + 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, + 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, + 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, + 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, + 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, + 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, + 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, + 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, + 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, + 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, + 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, + 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, + 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, + 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, + 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, + 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, + 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, + 196, 22, 89, 45, 48, 255, 175, 224, 178, 34, 113, 55, 121, 103, 94, 57, 230, 149, 227, 2, 8, 211, 56, + 135, 63, 75, 228, 67, 79, 182, 168, 130, 79, 28, 56, 65, 78, 255, 48, 67, 5, 243, 1, 170, 131, 242, 24, + 216, 174, 93, 89, 249, 12, 215, 25, 248, 12, 146, 191, 38, 9, 239, 136, 197, 113, 125, 222, 79, 184, + 149, 180, 198, 185, 10, 161, 28, 53, 69, 19, 173, 197, 112, 73, 23, 172, 239, 88, 66, 170, 206, 185, + 238, 228, 152, 153, 163, 198, 94, 147, 212, 117, 120, 83, 30, 158, 8, 70, 1, 73, 134, 237, 77, 162, + 147, 56, 224, 231, 179, 30, 110, 19, 55, 253, 176, 115, 101, 171, 146, 59, 227, 37, 145, 200, 156, 20, + 33, 186, 8, 34, 118, 162, 125, 114, 229, 11, 202, 36, 115, 124, 83, 60, 251, 141, 83, 244, 164, 213, + 197, 199, 2, 130, 173, 22, 120, 61, 63, 196, 111, 60, 184, 58, 17, 34, 166, 237, 250, 238, 19, 150, + 192, 123, 172, 162, 70, 227, 90, 165, 58, 139, 124, 87, 199, 135, 30, 146, 142, 203, 133, 133, 54, 26, + 54, 229, 134, 122, 117, 207, 31, 184, 148, 68, 232, 89, 132, 91, 246, 40, 87, 225, 14, 74, 23, 81, 228, + 241, 146, 171, 106, 211, 196, 222, 192, 142, 81, 207, 169, 185, 24, 161, 88, 75, 138, 97, 111, 92, 43, + 214, 190, 140, 12, 124, 177, 67, 125, 237, 147, 195, 41, 40, + ], + domain_name: "domain.test".into(), + forest_name: "domain.test".into(), + }, + protection_descriptor: SidProtectionDescriptor { + sid: "S-1-5-21-3337337973-3297078028-437386066-512".into(), + }, + enc_cek: vec![ + 137, 127, 196, 63, 116, 142, 253, 9, 87, 39, 221, 233, 143, 78, 26, 111, 251, 157, 65, 99, 211, 159, 179, + 116, 208, 73, 199, 61, 137, 105, 12, 126, 250, 69, 230, 190, 17, 158, 13, 107, + ], + enc_cek_algorithm_id: KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap), + enc_content: vec![ + 228, 205, 246, 84, 114, 42, 73, 213, 95, 83, 8, 85, 14, 196, 232, 170, 198, 208, 190, 73, 81, 22, 246, 19, + 42, 77, 89, 23, 159, 215, 19, 142, 201, 75, 83, 110, 37, 17, 213, 202, 13, 55, 141, 236, 60, 66, 61, 85, + 197, 10, 96, 220, 65, 143, 144, 23, 130, 72, 70, 224, 43, 98, 4, 200, 179, 39, 60, 159, 196, 67, 55, 99, + 148, 71, 59, 249, 123, 220, 85, 128, 9, 81, 173, 249, 35, 141, 138, 2, 255, 224, 56, 205, 77, 123, 22, 1, + 47, 122, 232, 184, 121, 3, 224, 80, 0, 216, 227, 16, 222, 27, 45, 28, 163, 68, 178, 242, 103, 58, 61, 90, + 92, 77, 228, 99, 38, 75, 149, 100, 235, 158, 176, 76, 82, 113, 28, 51, 197, 167, 169, 116, 13, 102, 84, + 136, 85, 182, + ], + enc_content_algorithm_id: ContentEncryptionAlgorithmIdentifier::new_aes256( + AesMode::Gcm, + AesParameters::AuthenticatedEncryptionParameters(AesAuthEncParams::new( + vec![158, 91, 46, 23, 194, 63, 4, 252, 53, 37, 225, 24], + 16, + )), + ), + } +} + +#[test] +fn dpapi_blob_decoding() { + let blob = DpapiBlob::decode(DPAPI_BLOB_DATA).unwrap(); + + assert_eq!(testing_blob(), blob); +} + +#[test] +fn dpapi_blob_encoding() { + let blob = testing_blob(); + + let mut buf = Vec::new(); + blob.encode(false, &mut buf).unwrap(); + + assert_eq!(DPAPI_BLOB_DATA, &buf); +} + +#[test] +fn get_target_sd() { + let expected = [ + 1, 0, 4, 128, 84, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 0, 64, 0, 2, 0, 0, 0, 0, 0, 36, 0, 3, 0, 0, + 0, 1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 223, 243, 137, 88, 86, 131, 83, 53, 105, 218, 109, 33, 80, 4, 0, 0, 0, + 0, 20, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 5, 18, 0, 0, 0, + ]; + let sid = SidProtectionDescriptor { + sid: "S-1-5-21-1485435871-894665558-560847465-1104".to_owned(), + }; + + let target_sd = sid.get_target_sd().unwrap(); + + assert_eq!(expected[..], target_sd[..]); +} diff --git a/crates/dpapi/tests/dpapi/crypto.rs b/crates/dpapi/tests/dpapi/crypto.rs new file mode 100644 index 00000000..a92aa314 --- /dev/null +++ b/crates/dpapi/tests/dpapi/crypto.rs @@ -0,0 +1,247 @@ +use dpapi::crypto::{ + cek_decrypt, cek_encrypt, compute_kek, compute_public_key, content_decrypt, content_encrypt, kdf, KDS_SERVICE_LABEL, +}; +use dpapi::gkdi::HashAlg; +use picky_asn1::wrapper::OctetStringAsn1; +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{AesMode, AesParameters}; + +const SECRET_KEY: &[u8] = &[ + 213, 85, 238, 100, 120, 222, 109, 53, 48, 101, 43, 187, 152, 206, 110, 105, 123, 251, 227, 253, 232, 85, 197, 24, + 217, 190, 118, 74, 54, 226, 8, 188, 163, 141, 155, 170, 208, 164, 97, 125, 32, 172, 65, 183, 251, 135, 229, 224, + 214, 22, 98, 18, 170, 254, 220, 105, 217, 11, 142, 135, 141, 104, 82, 189, +]; +const CONTEXT: &[u8] = &[ + 228, 137, 183, 195, 107, 83, 44, 167, 62, 235, 215, 116, 108, 38, 108, 149, 107, 206, 154, 191, 189, 219, 105, 175, + 72, 213, 172, 131, 94, 207, 58, 208, +]; +const PRIVATE_KEY_DH: &[u8] = &[ + 139, 93, 50, 184, 90, 214, 77, 2, 57, 23, 5, 0, 155, 2, 202, 140, 58, 27, 111, 51, 97, 204, 165, 167, 18, 41, 158, + 25, 48, 44, 42, 198, 74, 238, 245, 201, 107, 49, 243, 27, 164, 205, 223, 112, 31, 100, 146, 48, 90, 81, 126, 112, + 38, 0, 194, 4, 195, 140, 122, 134, 104, 123, 211, 100, +]; +const PUBLIC_KEY_DH: &[u8] = &[ + 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, + 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, + 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, + 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, + 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, + 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, + 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, + 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, + 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, + 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, + 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, + 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, + 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, + 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, + 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, + 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, + 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, + 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, + 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, + 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, + 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89, 63, 246, 158, 197, 238, 228, + 177, 87, 255, 7, 170, 179, 251, 65, 155, 170, 131, 138, 187, 46, 97, 142, 5, 165, 60, 250, 49, 231, 45, 194, 253, + 138, 19, 51, 17, 14, 58, 138, 220, 159, 243, 234, 232, 20, 213, 21, 252, 63, 24, 156, 7, 240, 21, 148, 36, 254, + 147, 3, 29, 43, 52, 13, 13, 153, 78, 16, 128, 153, 153, 114, 253, 211, 219, 59, 84, 206, 244, 233, 243, 222, 144, + 228, 133, 135, 176, 87, 48, 253, 16, 188, 170, 171, 53, 228, 102, 234, 1, 38, 120, 251, 65, 104, 155, 189, 160, 74, + 150, 128, 13, 242, 122, 148, 52, 206, 158, 237, 95, 106, 96, 236, 190, 43, 173, 141, 144, 42, 198, 40, 92, 242, + 100, 42, 54, 249, 250, 249, 97, 19, 168, 10, 109, 182, 35, 138, 248, 158, 153, 45, 181, 175, 160, 65, 6, 210, 191, + 73, 164, 230, 167, 12, 140, 148, 222, 156, 10, 62, 149, 64, 13, 150, 200, 169, 109, 49, 149, 52, 227, 37, 250, 250, + 208, 109, 50, 187, 91, 242, 40, 102, 200, 248, 182, 96, 18, 87, 71, 238, 88, 8, 220, 157, 201, 237, 178, 250, 211, + 77, 95, 21, 98, 248, 205, 24, 172, 212, 96, 169, 101, 35, 47, 40, 187, 132, 216, 243, 39, 131, 24, 135, 88, 17, + 245, 208, 83, 162, 58, 194, 84, 192, 227, 105, 145, 134, 13, 44, 216, 9, 102, 61, 99, 224, 239, 153, 66, 232, 191, + 24, +]; + +#[test] +fn test_kdf_sha1() { + let expected_key = [ + 117, 25, 15, 198, 42, 170, 180, 156, 140, 156, 188, 164, 163, 245, 40, 18, 53, 43, 149, 141, 135, 152, 11, 248, + 80, 84, 1, 195, 212, 7, 149, 35, + ]; + + let key = kdf(HashAlg::Sha1, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 32).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha256() { + let expected_key = [ + 95, 246, 71, 210, 202, 186, 163, 251, 24, 175, 54, 107, 191, 107, 87, 35, 241, 202, 64, 106, 34, 201, 185, 5, + 213, 175, 222, 111, 249, 145, 238, 162, + ]; + + let key = kdf(HashAlg::Sha256, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 32).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha384() { + let expected_key = [ + 91, 218, 125, 86, 51, 207, 96, 224, 6, 253, 16, 137, 142, 10, 95, 156, 163, 217, 31, 186, 206, 88, 81, 141, + 231, 62, 224, 200, 168, 156, 189, 71, 60, 220, 166, 65, 141, 47, 92, 145, 241, 112, 91, 39, 27, 237, 88, 122, + 103, 38, 115, 222, 26, 214, 185, 78, 34, 7, 170, 54, 74, 18, 206, 75, + ]; + + let key = kdf(HashAlg::Sha384, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 64).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha512() { + let expected_key = [ + 56, 219, 230, 175, 76, 173, 241, 49, 216, 97, 145, 27, 74, 153, 173, 79, 201, 145, 64, 135, 166, 0, 111, 19, + 164, 112, 171, 230, 130, 28, 71, 240, 122, 88, 46, 26, 192, 243, 50, 182, 242, 217, 179, 190, 12, 13, 85, 1, + 202, 211, 212, 169, 83, 208, 162, 227, 217, 30, 33, 226, 101, 230, 8, 109, + ]; + + let key = kdf(HashAlg::Sha512, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 64).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_compute_public_key_dh() { + let expected_key = [ + 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, + 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, + 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, + 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, + 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, + 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, + 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, + 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, + 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, + 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, + 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, + 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, + 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, + 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, + 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, + 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, + 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, + 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, + 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, + 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, + 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, + 130, 102, 75, 76, 15, 108, 196, 22, 89, 112, 124, 225, 37, 170, 121, 200, 204, 39, 82, 73, 239, 179, 79, 50, + 51, 207, 130, 16, 9, 49, 150, 137, 59, 156, 72, 231, 118, 10, 79, 87, 132, 54, 160, 121, 120, 82, 45, 130, 61, + 11, 207, 93, 176, 13, 49, 155, 223, 213, 26, 171, 188, 84, 184, 62, 16, 149, 16, 26, 35, 72, 12, 173, 68, 176, + 48, 84, 175, 37, 188, 209, 38, 57, 183, 57, 184, 123, 249, 56, 131, 229, 224, 39, 66, 9, 178, 36, 254, 21, 73, + 60, 212, 212, 119, 130, 245, 84, 33, 111, 156, 95, 19, 172, 13, 82, 37, 38, 109, 52, 223, 45, 162, 130, 115, + 64, 186, 53, 50, 42, 119, 173, 13, 128, 224, 12, 40, 93, 71, 136, 205, 137, 185, 138, 201, 202, 158, 184, 18, + 77, 242, 208, 18, 49, 124, 69, 105, 20, 3, 114, 204, 98, 30, 64, 153, 254, 165, 198, 129, 124, 53, 251, 168, + 187, 150, 176, 245, 34, 42, 159, 27, 186, 65, 126, 35, 175, 148, 173, 231, 57, 68, 198, 175, 117, 130, 17, 248, + 234, 224, 220, 238, 197, 226, 200, 190, 121, 5, 81, 66, 133, 13, 41, 74, 89, 29, 106, 14, 56, 186, 246, 156, + 51, 204, 84, 247, 202, 39, 58, 62, 38, 170, 170, 191, 8, 18, 15, 65, 53, 239, 223, 98, 245, 69, 40, 70, 147, + 81, 9, 177, 119, 78, 158, 68, 179, 94, 183, 150, 34, 134, 172, 28, 86, 63, 192, 65, 4, 216, + ]; + + let public_key = compute_public_key("DH", PRIVATE_KEY_DH, PUBLIC_KEY_DH).unwrap(); + + assert_eq!(expected_key[..], public_key[..]); +} + +#[test] +fn test_compute_kek_dh() { + let expected_key = [ + 9, 171, 213, 100, 174, 219, 112, 33, 135, 63, 151, 51, 231, 55, 121, 167, 132, 216, 251, 190, 174, 207, 209, + 164, 141, 125, 85, 196, 84, 60, 232, 36, + ]; + + let kek = compute_kek(HashAlg::Sha512, "DH", PRIVATE_KEY_DH, PUBLIC_KEY_DH).unwrap(); + + assert_eq!(expected_key[..], kek[..]); +} + +#[test] +fn test_cek_encrypt() { + let expected_key = [ + 177, 34, 69, 51, 190, 164, 94, 127, 38, 205, 148, 208, 11, 108, 215, 29, 178, 61, 153, 114, 42, 203, 15, 82, + 30, 72, 228, 118, 78, 34, 29, 117, 181, 56, 147, 124, 62, 48, 255, 39, + ]; + + let wrapped_key = cek_encrypt( + &KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap), + &[ + 9, 171, 213, 100, 174, 219, 112, 33, 135, 63, 151, 51, 231, 55, 121, 167, 132, 216, 251, 190, 174, 207, + 209, 164, 141, 125, 85, 196, 84, 60, 232, 36, + ], + &[ + 206, 232, 113, 60, 84, 106, 53, 122, 24, 150, 171, 198, 170, 126, 87, 228, 7, 22, 212, 151, 162, 93, 220, + 211, 115, 74, 24, 231, 235, 112, 110, 133, + ], + ) + .unwrap(); + + assert_eq!(expected_key[..], wrapped_key[..]); +} + +#[test] +fn test_cek_decrypt() { + let expected_key = [ + 237, 217, 97, 116, 100, 107, 229, 54, 97, 127, 233, 172, 141, 83, 124, 250, 21, 115, 218, 160, 137, 22, 103, + 96, 167, 25, 59, 35, 65, 126, 69, 192, + ]; + + let key = cek_decrypt( + &KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap), + &[ + 166, 59, 66, 26, 83, 122, 242, 219, 236, 155, 114, 107, 185, 13, 252, 191, 239, 219, 244, 91, 42, 197, 34, + 82, 11, 8, 251, 120, 137, 197, 250, 110, + ], + &[ + 79, 59, 241, 186, 249, 240, 229, 63, 50, 183, 56, 137, 17, 64, 57, 136, 49, 12, 176, 219, 163, 106, 132, + 25, 1, 87, 85, 16, 179, 52, 21, 138, 173, 143, 110, 15, 16, 0, 99, 244, + ], + ) + .unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +const PLAINTEXT: &[u8] = &[84, 104, 101, 66, 101, 115, 116, 84, 118, 97, 114, 121, 110, 107, 97]; +const CIPHER_TEXT: &[u8] = &[ + 141, 73, 82, 191, 110, 35, 212, 200, 182, 19, 135, 174, 143, 253, 167, 179, 170, 9, 181, 213, 130, 114, 20, 4, 145, + 63, 224, 92, 231, 37, 18, +]; +const AES256_GCM_IV: &[u8] = &[127, 98, 187, 173, 250, 133, 155, 4, 74, 60, 109, 245]; +const AES256_GCM_KEY: &[u8] = &[ + 237, 217, 97, 116, 100, 107, 229, 54, 97, 127, 233, 172, 141, 83, 124, 250, 21, 115, 218, 160, 137, 22, 103, 96, + 167, 25, 59, 35, 65, 126, 69, 192, +]; + +#[test] +fn test_content_decrypt() { + let plaintext = content_decrypt( + &ContentEncryptionAlgorithmIdentifier::new_aes256( + AesMode::Gcm, + AesParameters::InitializationVector(OctetStringAsn1::from(AES256_GCM_IV.to_vec())), + ), + AES256_GCM_KEY, + CIPHER_TEXT, + ) + .unwrap(); + + assert_eq!(PLAINTEXT[..], plaintext[..]); +} + +#[test] +fn test_content_encrypt() { + let cipher_text = content_encrypt( + &ContentEncryptionAlgorithmIdentifier::new_aes256( + AesMode::Gcm, + AesParameters::InitializationVector(OctetStringAsn1::from(AES256_GCM_IV.to_vec())), + ), + AES256_GCM_KEY, + PLAINTEXT, + ) + .unwrap(); + + assert_eq!(CIPHER_TEXT[..], cipher_text[..]); +} diff --git a/crates/dpapi/tests/dpapi/epm.rs b/crates/dpapi/tests/dpapi/epm.rs new file mode 100644 index 00000000..2fca26d9 --- /dev/null +++ b/crates/dpapi/tests/dpapi/epm.rs @@ -0,0 +1,88 @@ +use dpapi::epm::{EptMap, EptMapResult, Floor, IpFloor, RpcConnectionOrientedFloor, TcpFloor, UuidFloor}; +use uuid::uuid; + +test_encoding_decoding! { + ept_map, + EptMap, + EptMap { + obj: None, + tower: vec![ + Floor::Uuid(UuidFloor { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }), + Floor::Uuid(UuidFloor { + uuid: uuid!("8a885d04-1ceb-11c9-9fe8-08002b104860"), + version: 2, + version_minor: 0, + }), + Floor::RpcConnectionOriented(RpcConnectionOrientedFloor { + version_minor: 0, + }), + Floor::Tcp(TcpFloor { + port: 135, + }), + Floor::Ip(IpFloor { + addr: 0, + }), + ], + entry_handle: None, + max_towers: 4, + }, + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 5, 0, 19, 0, 13, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 2, 0, 0, 0, 19, 0, 13, 4, 93, 136, 138, 235, 28, 201, 17, 159, 232, 8, 0, 43, 16, 72, 96, 2, 0, 2, 0, 0, 0, 1, 0, 11, 2, 0, 0, 0, 1, 0, 7, 2, 0, 0, 135, 1, 0, 9, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0] +} + +test_encoding_decoding! { + ept_map_result, + EptMapResult, + EptMapResult { + entry_handle: None, + towers: vec![ + vec![ + Floor::Uuid(UuidFloor { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }), + Floor::Uuid(UuidFloor { + uuid: uuid!("8a885d04-1ceb-11c9-9fe8-08002b104860"), + version: 2, + version_minor: 0, + }), + Floor::RpcConnectionOriented(RpcConnectionOrientedFloor { + version_minor: 0, + }), + Floor::Tcp(TcpFloor { + port: 49668, + }), + Floor::Ip(IpFloor { + addr: u32::from_be_bytes([192, 168, 1, 104]), + }), + ], + vec![ + Floor::Uuid(UuidFloor { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }), + Floor::Uuid(UuidFloor { + uuid: uuid!("8a885d04-1ceb-11c9-9fe8-08002b104860"), + version: 2, + version_minor: 0, + }), + Floor::RpcConnectionOriented(RpcConnectionOrientedFloor { + version_minor: 0, + }), + Floor::Tcp(TcpFloor { + port: 49664, + }), + Floor::Ip(IpFloor { + addr: u32::from_be_bytes([192, 168, 1, 104]), + }), + ], + ], + status: 0, + }, + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 5, 0, 19, 0, 13, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 2, 0, 0, 0, 19, 0, 13, 4, 93, 136, 138, 235, 28, 201, 17, 159, 232, 8, 0, 43, 16, 72, 96, 2, 0, 2, 0, 0, 0, 1, 0, 11, 2, 0, 0, 0, 1, 0, 7, 2, 0, 194, 4, 1, 0, 9, 4, 0, 192, 168, 1, 104, 0, 75, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 5, 0, 19, 0, 13, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 2, 0, 0, 0, 19, 0, 13, 4, 93, 136, 138, 235, 28, 201, 17, 159, 232, 8, 0, 43, 16, 72, 96, 2, 0, 2, 0, 0, 0, 1, 0, 11, 2, 0, 0, 0, 1, 0, 7, 2, 0, 194, 0, 1, 0, 9, 4, 0, 192, 168, 1, 104, 0, 0, 0, 0, 0] +} diff --git a/crates/dpapi/tests/dpapi/gkdi.rs b/crates/dpapi/tests/dpapi/gkdi.rs new file mode 100644 index 00000000..be9afd43 --- /dev/null +++ b/crates/dpapi/tests/dpapi/gkdi.rs @@ -0,0 +1,87 @@ +use std::str::FromStr; + +use dpapi::gkdi::{ + EcdhKey, EllipticCurve, FfcdhKey, FfcdhParameters, GetKey, GroupKeyEnvelope, HashAlg, KdfParameters, +}; +use num_bigint_dig::BigUint; +use uuid::Uuid; + +test_encoding_decoding! { + get_key, + GetKey, + GetKey { + target_sd: vec![1, 2, 3, 4], + root_key_id: Some(Uuid::from_str("73294420-917f-416a-9ec3-86082afafb9e").unwrap()), + l0_key_id: -1, + l1_key_id: 1, + l2_key_id: 31, + }, + [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x44, 0x29, 0x73, 0x7f, 0x91, 0x6a, 0x41, 0x9e, 0xc3, 0x86, 0x08, 0x2a, 0xfa, 0xfb, 0x9e, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00] +} + +test_encoding_decoding! { + kdf_parameters, + KdfParameters, + KdfParameters { + hash_alg: HashAlg::Sha512, + }, + [0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x48, 0x00, 0x41, 0x00, 0x35, 0x00, 0x31, 0x00, 0x32, 0x00, 0x00, 0x00] +} + +test_encoding_decoding! { + ffcdh_parameters, + FfcdhParameters, + FfcdhParameters { + key_length: 256, + field_order: BigUint::from_bytes_be(&[135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151]), + generator: BigUint::from_bytes_be(&[63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89]), + }, + [12, 2, 0, 0, 68, 72, 80, 77, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89] +} + +test_encoding_decoding! { + ffcdh_key, + FfcdhKey, + FfcdhKey { + key_length: 256, + field_order: BigUint::from_bytes_be(&[135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151]), + generator: BigUint::from_bytes_be(&[63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89]), + public_key: BigUint::from_bytes_be(&[45, 48, 255, 175, 224, 178, 34, 113, 55, 121, 103, 94, 57, 230, 149, 227, 2, 8, 211, 56, 135, 63, 75, 228, 67, 79, 182, 168, 130, 79, 28, 56, 65, 78, 255, 48, 67, 5, 243, 1, 170, 131, 242, 24, 216, 174, 93, 89, 249, 12, 215, 25, 248, 12, 146, 191, 38, 9, 239, 136, 197, 113, 125, 222, 79, 184, 149, 180, 198, 185, 10, 161, 28, 53, 69, 19, 173, 197, 112, 73, 23, 172, 239, 88, 66, 170, 206, 185, 238, 228, 152, 153, 163, 198, 94, 147, 212, 117, 120, 83, 30, 158, 8, 70, 1, 73, 134, 237, 77, 162, 147, 56, 224, 231, 179, 30, 110, 19, 55, 253, 176, 115, 101, 171, 146, 59, 227, 37, 145, 200, 156, 20, 33, 186, 8, 34, 118, 162, 125, 114, 229, 11, 202, 36, 115, 124, 83, 60, 251, 141, 83, 244, 164, 213, 197, 199, 2, 130, 173, 22, 120, 61, 63, 196, 111, 60, 184, 58, 17, 34, 166, 237, 250, 238, 19, 150, 192, 123, 172, 162, 70, 227, 90, 165, 58, 139, 124, 87, 199, 135, 30, 146, 142, 203, 133, 133, 54, 26, 54, 229, 134, 122, 117, 207, 31, 184, 148, 68, 232, 89, 132, 91, 246, 40, 87, 225, 14, 74, 23, 81, 228, 241, 146, 171, 106, 211, 196, 222, 192, 142, 81, 207, 169, 185, 24, 161, 88, 75, 138, 97, 111, 92, 43, 214, 190, 140, 12, 124, 177, 67, 125, 237, 147, 195, 41, 40]), + }, + [68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89, 45, 48, 255, 175, 224, 178, 34, 113, 55, 121, 103, 94, 57, 230, 149, 227, 2, 8, 211, 56, 135, 63, 75, 228, 67, 79, 182, 168, 130, 79, 28, 56, 65, 78, 255, 48, 67, 5, 243, 1, 170, 131, 242, 24, 216, 174, 93, 89, 249, 12, 215, 25, 248, 12, 146, 191, 38, 9, 239, 136, 197, 113, 125, 222, 79, 184, 149, 180, 198, 185, 10, 161, 28, 53, 69, 19, 173, 197, 112, 73, 23, 172, 239, 88, 66, 170, 206, 185, 238, 228, 152, 153, 163, 198, 94, 147, 212, 117, 120, 83, 30, 158, 8, 70, 1, 73, 134, 237, 77, 162, 147, 56, 224, 231, 179, 30, 110, 19, 55, 253, 176, 115, 101, 171, 146, 59, 227, 37, 145, 200, 156, 20, 33, 186, 8, 34, 118, 162, 125, 114, 229, 11, 202, 36, 115, 124, 83, 60, 251, 141, 83, 244, 164, 213, 197, 199, 2, 130, 173, 22, 120, 61, 63, 196, 111, 60, 184, 58, 17, 34, 166, 237, 250, 238, 19, 150, 192, 123, 172, 162, 70, 227, 90, 165, 58, 139, 124, 87, 199, 135, 30, 146, 142, 203, 133, 133, 54, 26, 54, 229, 134, 122, 117, 207, 31, 184, 148, 68, 232, 89, 132, 91, 246, 40, 87, 225, 14, 74, 23, 81, 228, 241, 146, 171, 106, 211, 196, 222, 192, 142, 81, 207, 169, 185, 24, 161, 88, 75, 138, 97, 111, 92, 43, 214, 190, 140, 12, 124, 177, 67, 125, 237, 147, 195, 41, 40] +} + +test_encoding_decoding! { + ecdh_key, + EcdhKey, + EcdhKey { + curve: EllipticCurve::P256, + key_length: 32, + x: BigUint::from_bytes_be(&[55, 207, 128, 106, 197, 198, 140, 63, 65, 0, 159, 14, 21, 210, 20, 185, 6, 206, 148, 114, 80, 216, 60, 7, 162, 43, 89, 58, 4, 185, 244, 146]), + y: BigUint::from_bytes_be(&[12, 96, 47, 29, 213, 226, 140, 169, 155, 108, 148, 93, 27, 55, 236, 228, 100, 7, 103, 201, 181, 118, 34, 92, 72, 181, 88, 110, 92, 34, 255, 192]), + }, + [69, 67, 75, 49, 32, 0, 0, 0, 55, 207, 128, 106, 197, 198, 140, 63, 65, 0, 159, 14, 21, 210, 20, 185, 6, 206, 148, 114, 80, 216, 60, 7, 162, 43, 89, 58, 4, 185, 244, 146, 12, 96, 47, 29, 213, 226, 140, 169, 155, 108, 148, 93, 27, 55, 236, 228, 100, 7, 103, 201, 181, 118, 34, 92, 72, 181, 88, 110, 92, 34, 255, 192] +} + +test_encoding_decoding! { + group_key_envelope, + GroupKeyEnvelope, + GroupKeyEnvelope { + flags: 2, + l0: 361, + l1: 17, + l2: 8, + root_key_identifier: Uuid::from_str("d778c271-9025-9a82-f6dc-b8960b8ad8c5").unwrap(), + kdf_alg: "SP800_108_CTR_HMAC".into(), + kdf_parameters: vec![0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x48, 0x00, 0x41, 0x00, 0x35, 0x00, 0x31, 0x00, 0x32, 0x00, 0x00, 0x00], + secret_algorithm: "DH".into(), + secret_parameters: vec![12, 2, 0, 0, 68, 72, 80, 77, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89], + private_key_length: 512, + public_key_length: 2048, + domain_name: "domain.test".into(), + forest_name: "domain.test".into(), + l1_key: vec![0x9C, 0x8F, 0x03, 0x85, 0xD7, 0x46, 0x06, 0x2A, 0xFB, 0x90, 0xBA, 0x9D, 0x02, 0x3A, 0x3A, 0x5C, 0x24, 0x2E, 0xB5, 0x33, 0x43, 0x41, 0xBE, 0xFA, 0xDC, 0x49, 0xE2, 0x7A, 0x90, 0x8F, 0xC3, 0x39, 0x3B, 0xAC, 0x40, 0x14, 0x56, 0xA8, 0x65, 0x61, 0x04, 0xC8, 0x72, 0xD0, 0xC9, 0x96, 0xAA, 0x25, 0x9A, 0x95, 0x4B, 0xF5, 0xA3, 0x8B, 0x8D, 0x6E, 0xC7, 0xCD, 0xBA, 0xC1, 0x35, 0x9E, 0x5A, 0x09], + l2_key: vec![0x1B, 0xAC, 0x68, 0xA1, 0xA7, 0xC8, 0xB9, 0xAC, 0x94, 0x4C, 0x8E, 0xB1, 0xEA, 0x39, 0x6C, 0xC3, 0x66, 0x68, 0x5E, 0x17, 0xA4, 0x11, 0x0A, 0x1F, 0xB5, 0x5E, 0x7C, 0x44, 0x11, 0xA6, 0xFA, 0xA5, 0x8F, 0x8E, 0x5B, 0xE1, 0x25, 0x24, 0xFA, 0xBB, 0xC3, 0x44, 0xC5, 0x9B, 0xEA, 0xF9, 0xB3, 0xEC, 0xE2, 0x18, 0xEA, 0x8E, 0x4F, 0x81, 0x1B, 0x6C, 0xAF, 0xEA, 0x4B, 0x77, 0xE7, 0xEF, 0x0A, 0xED], + }, + [1, 0, 0, 0, 75, 68, 83, 75, 2, 0, 0, 0, 105, 1, 0, 0, 17, 0, 0, 0, 8, 0, 0, 0, 113, 194, 120, 215, 37, 144, 130, 154, 246, 220, 184, 150, 11, 138, 216, 197, 38, 0, 0, 0, 30, 0, 0, 0, 6, 0, 0, 0, 12, 2, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 83, 0, 80, 0, 56, 0, 48, 0, 48, 0, 95, 0, 49, 0, 48, 0, 56, 0, 95, 0, 67, 0, 84, 0, 82, 0, 95, 0, 72, 0, 77, 0, 65, 0, 67, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 83, 0, 72, 0, 65, 0, 53, 0, 49, 0, 50, 0, 0, 0, 68, 0, 72, 0, 0, 0, 12, 2, 0, 0, 68, 72, 80, 77, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89, 100, 0, 111, 0, 109, 0, 97, 0, 105, 0, 110, 0, 46, 0, 116, 0, 101, 0, 115, 0, 116, 0, 0, 0, 100, 0, 111, 0, 109, 0, 97, 0, 105, 0, 110, 0, 46, 0, 116, 0, 101, 0, 115, 0, 116, 0, 0, 0, 156, 143, 3, 133, 215, 70, 6, 42, 251, 144, 186, 157, 2, 58, 58, 92, 36, 46, 181, 51, 67, 65, 190, 250, 220, 73, 226, 122, 144, 143, 195, 57, 59, 172, 64, 20, 86, 168, 101, 97, 4, 200, 114, 208, 201, 150, 170, 37, 154, 149, 75, 245, 163, 139, 141, 110, 199, 205, 186, 193, 53, 158, 90, 9, 27, 172, 104, 161, 167, 200, 185, 172, 148, 76, 142, 177, 234, 57, 108, 195, 102, 104, 94, 23, 164, 17, 10, 31, 181, 94, 124, 68, 17, 166, 250, 165, 143, 142, 91, 225, 37, 36, 250, 187, 195, 68, 197, 155, 234, 249, 179, 236, 226, 24, 234, 142, 79, 129, 27, 108, 175, 234, 75, 119, 231, 239, 10, 237] +} diff --git a/crates/dpapi/tests/dpapi/macros.rs b/crates/dpapi/tests/dpapi/macros.rs new file mode 100644 index 00000000..912570bc --- /dev/null +++ b/crates/dpapi/tests/dpapi/macros.rs @@ -0,0 +1,18 @@ +macro_rules! test_encoding_decoding { + ($name:ident, $type:ty, $expected:expr, $data:expr) => { + paste::paste! { + #[test] + fn [<$name:lower _encoding_decoding>]() { + use dpapi::rpc::{EncodeExt, Decode}; + + let data = $data; + + let parsed = $type::decode(data.as_slice()).unwrap(); + let encoded = parsed.encode_to_vec().unwrap(); + + assert_eq!($expected, parsed); + assert_eq!(data[..], encoded[..]); + } + } + }; +} diff --git a/crates/dpapi/tests/dpapi/main.rs b/crates/dpapi/tests/dpapi/main.rs new file mode 100644 index 00000000..7ab715f0 --- /dev/null +++ b/crates/dpapi/tests/dpapi/main.rs @@ -0,0 +1,7 @@ +#[macro_use] +mod macros; +mod blob; +mod crypto; +mod epm; +mod gkdi; +mod rpc; diff --git a/crates/dpapi/tests/dpapi/rpc/bind.rs b/crates/dpapi/tests/dpapi/rpc/bind.rs new file mode 100644 index 00000000..d647502d --- /dev/null +++ b/crates/dpapi/tests/dpapi/rpc/bind.rs @@ -0,0 +1,46 @@ +use dpapi::rpc::bind::{ContextElement, ContextResult, ContextResultCode, SyntaxId}; +use uuid::uuid; + +test_encoding_decoding! { + syntax_id, + SyntaxId, + SyntaxId { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }, + [96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0] +} + +test_encoding_decoding! { + context_element, + ContextElement, + ContextElement { + context_id: 0, + abstract_syntax: SyntaxId { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }, + transfer_syntaxes: vec![ + SyntaxId { + uuid: uuid!("71710533-beba-4937-8319-b5dbef9ccc36"), + version: 1, + version_minor: 0, + } + ], + }, + [0, 0, 1, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0] +} + +test_encoding_decoding! { + context_result, + ContextResult, + ContextResult { + result: ContextResultCode::Acceptance, + reason: 0, + syntax: uuid!("71710533-beba-4937-8319-b5dbef9ccc36"), + syntax_version: 1, + }, + [0, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0] +} diff --git a/crates/dpapi/tests/dpapi/rpc/mod.rs b/crates/dpapi/tests/dpapi/rpc/mod.rs new file mode 100644 index 00000000..e5c0c0be --- /dev/null +++ b/crates/dpapi/tests/dpapi/rpc/mod.rs @@ -0,0 +1,3 @@ +mod bind; +mod pdu; +mod verification; diff --git a/crates/dpapi/tests/dpapi/rpc/pdu.rs b/crates/dpapi/tests/dpapi/rpc/pdu.rs new file mode 100644 index 00000000..edb6eea0 --- /dev/null +++ b/crates/dpapi/tests/dpapi/rpc/pdu.rs @@ -0,0 +1,266 @@ +use std::str::FromStr; + +use dpapi::rpc::bind::{AlterContext, Bind, BindAck, ContextElement, ContextResult, ContextResultCode, SyntaxId}; +use dpapi::rpc::pdu::{ + AuthenticationLevel, CharacterRepr, DataRepr, FloatingPointRepr, IntRepr, PacketFlags, PacketType, Pdu, PduData, + PduHeader, SecurityProvider, SecurityTrailer, +}; +use dpapi::rpc::request::{Request, Response}; +use uuid::Uuid; + +test_encoding_decoding! { + data_rep, + DataRepr, + DataRepr::default(), + [0x10, 0, 0, 0] +} + +test_encoding_decoding! { + sec_trailer, + SecurityTrailer, + SecurityTrailer { + security_type: SecurityProvider::GssKerberos, + level: AuthenticationLevel::PktPrivacy, + pad_length: 0, + context_id: 0, + auth_value: vec![111, 129, 135, 48, 129, 132, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 120, 48, 118, 160, 3, 2, 1, 18, 162, 111, 4, 109, 119, 103, 226, 62, 224, 40, 10, 92, 235, 148, 195, 168, 140, 247, 167, 45, 22, 189, 35, 181, 182, 57, 109, 10, 207, 215, 253, 118, 167, 212, 69, 43, 39, 201, 54, 64, 99, 241, 39, 189, 178, 98, 111, 37, 181, 177, 174, 239, 217, 11, 149, 100, 143, 41, 205, 36, 175, 207, 83, 14, 69, 197, 91, 154, 186, 114, 47, 121, 9, 37, 33, 107, 120, 161, 209, 114, 38, 201, 202, 210, 13, 59, 9, 29, 146, 85, 134, 67, 107, 99, 129, 40, 249, 200, 138, 117, 235, 104, 139, 93, 199, 167, 84, 119, 12, 90, 55, 27, 109], + }, + [16, 6, 0, 0, 0, 0, 0, 0, 111, 129, 135, 48, 129, 132, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 120, 48, 118, 160, 3, 2, 1, 18, 162, 111, 4, 109, 119, 103, 226, 62, 224, 40, 10, 92, 235, 148, 195, 168, 140, 247, 167, 45, 22, 189, 35, 181, 182, 57, 109, 10, 207, 215, 253, 118, 167, 212, 69, 43, 39, 201, 54, 64, 99, 241, 39, 189, 178, 98, 111, 37, 181, 177, 174, 239, 217, 11, 149, 100, 143, 41, 205, 36, 175, 207, 83, 14, 69, 197, 91, 154, 186, 114, 47, 121, 9, 37, 33, 107, 120, 161, 209, 114, 38, 201, 202, 210, 13, 59, 9, 29, 146, 85, 134, 67, 107, 99, 129, 40, 249, 200, 138, 117, 235, 104, 139, 93, 199, 167, 84, 119, 12, 90, 55, 27, 109] +} + +test_encoding_decoding! { + pdu_bind, + Pdu, + Pdu { + header: PduHeader { + version: 5, + version_minor: 0, + packet_type: PacketType::Bind, + packet_flags: PacketFlags::PfcSupportHeaderSign | PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + frag_len: 1624, + auth_len: 1500, + call_id: 1, + }, + data: PduData::Bind(Bind { + max_xmit_frag: 5840, + max_recv_frag: 5840, + assoc_group: 0, + contexts: vec![ + ContextElement { + context_id: 0, + abstract_syntax: SyntaxId { + uuid: Uuid::from_str("b9785960-524f-11df-8b6d-83dcded72085").unwrap(), + version: 1, + version_minor: 0, + }, + transfer_syntaxes: vec![ + SyntaxId { + uuid: Uuid::from_str("71710533-beba-4937-8319-b5dbef9ccc36").unwrap(), + version: 1, + version_minor: 0, + } + ] + }, + ContextElement { + context_id: 1, + abstract_syntax: SyntaxId { + uuid: Uuid::from_str("b9785960-524f-11df-8b6d-83dcded72085").unwrap(), + version: 1, + version_minor: 0, + }, + transfer_syntaxes: vec![ + SyntaxId { + uuid: Uuid::from_str("6cb71c2c-9812-4540-0000-000000000000").unwrap(), + version: 1, + version_minor: 0, + } + ] + }, + ], + }), + security_trailer: Some(SecurityTrailer { + security_type: SecurityProvider::GssKerberos, + level: AuthenticationLevel::PktPrivacy, + pad_length: 0, + context_id: 0, + auth_value: vec![110, 130, 5, 216, 48, 130, 5, 212, 160, 3, 2, 1, 5, 161, 3, 2, 1, 14, 162, 7, 3, 5, 0, 32, 0, 0, 0, 163, 130, 4, 122, 97, 130, 4, 118, 48, 130, 4, 114, 160, 3, 2, 1, 5, 161, 9, 27, 7, 84, 66, 84, 46, 67, 79, 77, 162, 42, 48, 40, 160, 3, 2, 1, 2, 161, 33, 48, 31, 27, 4, 104, 111, 115, 116, 27, 23, 119, 105, 110, 45, 57, 53, 54, 99, 113, 111, 115, 115, 106, 116, 102, 46, 116, 98, 116, 46, 99, 111, 109, 163, 130, 4, 50, 48, 130, 4, 46, 160, 3, 2, 1, 18, 161, 3, 2, 1, 9, 162, 130, 4, 32, 4, 130, 4, 28, 44, 103, 214, 219, 239, 134, 71, 190, 93, 33, 211, 36, 190, 6, 172, 121, 2, 89, 207, 145, 220, 145, 172, 231, 91, 117, 132, 111, 90, 170, 93, 68, 125, 232, 140, 82, 149, 113, 166, 160, 177, 128, 211, 60, 148, 255, 76, 218, 44, 251, 207, 172, 107, 5, 100, 116, 150, 169, 166, 9, 243, 215, 68, 138, 147, 181, 172, 57, 147, 162, 119, 199, 59, 114, 24, 246, 77, 200, 11, 70, 50, 177, 82, 16, 66, 204, 205, 184, 46, 235, 136, 252, 175, 19, 54, 232, 224, 42, 167, 220, 22, 230, 36, 196, 53, 64, 242, 190, 202, 121, 185, 201, 34, 254, 147, 167, 94, 244, 59, 7, 50, 175, 224, 79, 20, 81, 165, 16, 10, 139, 62, 188, 123, 240, 61, 227, 185, 45, 183, 229, 204, 78, 87, 196, 197, 234, 229, 130, 158, 133, 212, 167, 240, 86, 39, 192, 130, 213, 211, 136, 250, 130, 143, 151, 0, 242, 199, 20, 5, 218, 217, 222, 115, 183, 135, 28, 162, 0, 206, 176, 200, 131, 43, 121, 200, 78, 64, 202, 103, 223, 65, 195, 173, 108, 127, 210, 56, 103, 73, 27, 111, 57, 221, 127, 168, 81, 65, 65, 48, 231, 188, 175, 218, 158, 56, 220, 28, 51, 18, 78, 65, 9, 117, 136, 225, 226, 155, 211, 182, 155, 116, 29, 12, 235, 39, 120, 61, 238, 228, 78, 78, 29, 178, 197, 255, 52, 185, 164, 93, 132, 148, 163, 18, 168, 33, 44, 134, 83, 29, 249, 125, 166, 9, 211, 185, 82, 34, 99, 148, 121, 5, 114, 121, 41, 237, 194, 95, 80, 109, 247, 67, 238, 79, 200, 238, 178, 171, 47, 139, 138, 11, 26, 108, 22, 209, 244, 74, 6, 17, 164, 91, 111, 118, 100, 139, 205, 38, 213, 121, 250, 105, 51, 79, 228, 85, 111, 255, 26, 253, 154, 168, 212, 164, 22, 152, 185, 219, 58, 205, 182, 239, 137, 180, 82, 235, 101, 23, 93, 224, 96, 190, 43, 11, 183, 88, 237, 137, 193, 232, 156, 146, 174, 202, 44, 39, 49, 111, 198, 3, 44, 201, 32, 103, 132, 89, 10, 94, 203, 184, 64, 222, 78, 213, 92, 99, 74, 36, 229, 181, 181, 194, 62, 89, 102, 10, 98, 47, 241, 137, 250, 255, 219, 151, 85, 145, 205, 7, 34, 127, 226, 95, 200, 46, 36, 17, 243, 26, 38, 130, 139, 167, 215, 248, 100, 188, 6, 116, 142, 149, 249, 213, 198, 117, 43, 155, 240, 53, 202, 154, 253, 60, 78, 131, 30, 53, 59, 239, 67, 192, 197, 112, 100, 93, 255, 141, 85, 67, 172, 12, 167, 0, 13, 188, 129, 67, 127, 145, 220, 87, 22, 210, 46, 194, 105, 142, 151, 239, 192, 137, 218, 176, 178, 100, 62, 229, 212, 215, 195, 160, 29, 14, 177, 139, 124, 62, 142, 182, 34, 86, 149, 18, 106, 107, 215, 34, 130, 75, 181, 147, 5, 244, 131, 18, 25, 81, 63, 243, 228, 110, 188, 37, 142, 244, 25, 11, 210, 75, 26, 58, 37, 17, 46, 43, 179, 68, 0, 128, 84, 65, 169, 180, 244, 47, 114, 9, 96, 248, 216, 27, 157, 209, 39, 252, 25, 61, 203, 232, 148, 172, 157, 1, 48, 35, 24, 149, 87, 0, 154, 185, 121, 29, 233, 191, 234, 241, 109, 98, 30, 221, 214, 82, 238, 90, 212, 107, 205, 91, 222, 55, 181, 48, 156, 197, 78, 157, 139, 235, 169, 24, 243, 88, 230, 248, 87, 238, 146, 162, 45, 99, 222, 148, 133, 169, 41, 129, 46, 223, 223, 43, 251, 56, 5, 195, 101, 79, 15, 122, 137, 119, 192, 109, 211, 56, 33, 101, 49, 243, 82, 92, 93, 112, 115, 91, 202, 166, 57, 203, 165, 206, 134, 5, 10, 67, 157, 231, 38, 184, 188, 160, 206, 222, 183, 207, 212, 239, 167, 45, 121, 230, 184, 55, 147, 79, 5, 148, 176, 170, 74, 84, 17, 230, 112, 247, 198, 248, 70, 223, 205, 183, 133, 40, 7, 243, 102, 236, 53, 69, 67, 73, 50, 138, 50, 36, 199, 25, 146, 141, 162, 178, 93, 110, 156, 202, 72, 232, 51, 29, 156, 254, 42, 94, 113, 105, 138, 3, 45, 89, 58, 145, 99, 87, 246, 65, 118, 229, 216, 220, 169, 127, 206, 169, 142, 95, 155, 28, 43, 128, 13, 76, 5, 138, 15, 76, 239, 59, 248, 230, 97, 240, 3, 172, 68, 191, 165, 101, 68, 233, 66, 3, 218, 174, 118, 118, 81, 56, 127, 53, 156, 74, 150, 188, 12, 47, 11, 251, 197, 169, 70, 110, 67, 209, 139, 45, 200, 57, 206, 205, 22, 75, 53, 87, 63, 34, 207, 81, 153, 183, 54, 251, 107, 193, 139, 66, 237, 104, 5, 33, 38, 93, 190, 136, 235, 164, 58, 115, 109, 177, 34, 15, 208, 193, 175, 21, 5, 128, 255, 161, 158, 100, 4, 99, 30, 237, 212, 167, 208, 170, 31, 20, 137, 217, 213, 244, 100, 6, 110, 139, 131, 67, 44, 100, 24, 246, 35, 135, 139, 135, 221, 254, 168, 247, 177, 9, 200, 13, 92, 163, 162, 253, 192, 153, 10, 118, 71, 66, 65, 132, 227, 136, 104, 11, 103, 164, 63, 190, 181, 135, 140, 162, 237, 223, 52, 53, 211, 156, 28, 171, 224, 69, 40, 77, 196, 54, 99, 220, 214, 128, 5, 177, 177, 188, 78, 180, 83, 219, 160, 122, 140, 79, 244, 53, 57, 92, 94, 186, 17, 148, 52, 99, 202, 1, 121, 199, 28, 121, 175, 89, 251, 144, 39, 117, 252, 84, 253, 109, 68, 121, 82, 235, 176, 76, 83, 119, 16, 186, 94, 145, 11, 42, 60, 137, 18, 217, 69, 150, 69, 244, 232, 31, 76, 183, 58, 140, 111, 57, 149, 40, 26, 177, 79, 222, 235, 18, 227, 170, 47, 39, 177, 96, 106, 15, 170, 96, 36, 32, 147, 189, 227, 195, 40, 255, 180, 223, 9, 169, 68, 170, 149, 62, 72, 131, 193, 152, 7, 243, 75, 73, 97, 132, 115, 90, 80, 21, 214, 19, 182, 153, 198, 139, 68, 249, 21, 148, 89, 39, 108, 149, 5, 129, 96, 26, 21, 144, 236, 179, 160, 213, 108, 237, 111, 188, 51, 164, 130, 1, 63, 48, 130, 1, 59, 160, 3, 2, 1, 18, 162, 130, 1, 50, 4, 130, 1, 46, 132, 58, 70, 180, 118, 76, 164, 13, 174, 223, 44, 210, 119, 10, 168, 231, 247, 137, 253, 0, 147, 51, 147, 79, 64, 225, 162, 243, 64, 198, 106, 116, 122, 159, 132, 137, 232, 183, 137, 33, 162, 232, 196, 68, 112, 126, 64, 155, 62, 200, 181, 67, 40, 221, 74, 128, 117, 140, 57, 200, 172, 159, 121, 52, 122, 50, 39, 240, 175, 114, 10, 88, 171, 54, 116, 167, 7, 124, 93, 163, 59, 179, 206, 210, 91, 126, 205, 57, 115, 78, 180, 28, 107, 61, 141, 6, 140, 62, 77, 85, 238, 185, 48, 140, 110, 207, 21, 19, 215, 208, 77, 240, 165, 86, 2, 229, 151, 16, 91, 105, 6, 94, 158, 76, 182, 8, 244, 219, 144, 3, 186, 128, 170, 213, 97, 69, 240, 124, 236, 93, 147, 248, 221, 9, 43, 164, 185, 248, 67, 205, 74, 138, 9, 38, 149, 13, 198, 28, 40, 27, 84, 11, 17, 216, 24, 158, 156, 247, 65, 97, 65, 24, 187, 83, 92, 147, 203, 255, 213, 15, 109, 70, 251, 65, 36, 237, 175, 239, 41, 141, 249, 223, 134, 52, 53, 45, 193, 159, 184, 133, 93, 114, 189, 62, 16, 153, 182, 134, 210, 232, 230, 224, 31, 87, 142, 243, 63, 220, 180, 223, 196, 21, 52, 70, 254, 208, 122, 5, 169, 160, 148, 100, 219, 162, 142, 128, 131, 201, 197, 111, 208, 225, 174, 58, 77, 146, 16, 72, 221, 17, 132, 154, 11, 34, 102, 199, 154, 25, 111, 228, 229, 86, 208, 103, 90, 93, 239, 143, 131, 17, 122, 68, 45, 135, 227, 213, 105, 238, 55, 56, 254, 133, 76, 167, 163, 44, 163, 19, 29, 76, 244, 42, 72, 96, 219, 91, 235, 28, 9, 103, 117, 237] + }), + }, + [5, 0, 11, 7, 16, 0, 0, 0, 88, 6, 220, 5, 1, 0, 0, 0, 208, 22, 208, 22, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0, 1, 0, 1, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 44, 28, 183, 108, 18, 152, 64, 69, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 16, 6, 0, 0, 0, 0, 0, 0, 110, 130, 5, 216, 48, 130, 5, 212, 160, 3, 2, 1, 5, 161, 3, 2, 1, 14, 162, 7, 3, 5, 0, 32, 0, 0, 0, 163, 130, 4, 122, 97, 130, 4, 118, 48, 130, 4, 114, 160, 3, 2, 1, 5, 161, 9, 27, 7, 84, 66, 84, 46, 67, 79, 77, 162, 42, 48, 40, 160, 3, 2, 1, 2, 161, 33, 48, 31, 27, 4, 104, 111, 115, 116, 27, 23, 119, 105, 110, 45, 57, 53, 54, 99, 113, 111, 115, 115, 106, 116, 102, 46, 116, 98, 116, 46, 99, 111, 109, 163, 130, 4, 50, 48, 130, 4, 46, 160, 3, 2, 1, 18, 161, 3, 2, 1, 9, 162, 130, 4, 32, 4, 130, 4, 28, 44, 103, 214, 219, 239, 134, 71, 190, 93, 33, 211, 36, 190, 6, 172, 121, 2, 89, 207, 145, 220, 145, 172, 231, 91, 117, 132, 111, 90, 170, 93, 68, 125, 232, 140, 82, 149, 113, 166, 160, 177, 128, 211, 60, 148, 255, 76, 218, 44, 251, 207, 172, 107, 5, 100, 116, 150, 169, 166, 9, 243, 215, 68, 138, 147, 181, 172, 57, 147, 162, 119, 199, 59, 114, 24, 246, 77, 200, 11, 70, 50, 177, 82, 16, 66, 204, 205, 184, 46, 235, 136, 252, 175, 19, 54, 232, 224, 42, 167, 220, 22, 230, 36, 196, 53, 64, 242, 190, 202, 121, 185, 201, 34, 254, 147, 167, 94, 244, 59, 7, 50, 175, 224, 79, 20, 81, 165, 16, 10, 139, 62, 188, 123, 240, 61, 227, 185, 45, 183, 229, 204, 78, 87, 196, 197, 234, 229, 130, 158, 133, 212, 167, 240, 86, 39, 192, 130, 213, 211, 136, 250, 130, 143, 151, 0, 242, 199, 20, 5, 218, 217, 222, 115, 183, 135, 28, 162, 0, 206, 176, 200, 131, 43, 121, 200, 78, 64, 202, 103, 223, 65, 195, 173, 108, 127, 210, 56, 103, 73, 27, 111, 57, 221, 127, 168, 81, 65, 65, 48, 231, 188, 175, 218, 158, 56, 220, 28, 51, 18, 78, 65, 9, 117, 136, 225, 226, 155, 211, 182, 155, 116, 29, 12, 235, 39, 120, 61, 238, 228, 78, 78, 29, 178, 197, 255, 52, 185, 164, 93, 132, 148, 163, 18, 168, 33, 44, 134, 83, 29, 249, 125, 166, 9, 211, 185, 82, 34, 99, 148, 121, 5, 114, 121, 41, 237, 194, 95, 80, 109, 247, 67, 238, 79, 200, 238, 178, 171, 47, 139, 138, 11, 26, 108, 22, 209, 244, 74, 6, 17, 164, 91, 111, 118, 100, 139, 205, 38, 213, 121, 250, 105, 51, 79, 228, 85, 111, 255, 26, 253, 154, 168, 212, 164, 22, 152, 185, 219, 58, 205, 182, 239, 137, 180, 82, 235, 101, 23, 93, 224, 96, 190, 43, 11, 183, 88, 237, 137, 193, 232, 156, 146, 174, 202, 44, 39, 49, 111, 198, 3, 44, 201, 32, 103, 132, 89, 10, 94, 203, 184, 64, 222, 78, 213, 92, 99, 74, 36, 229, 181, 181, 194, 62, 89, 102, 10, 98, 47, 241, 137, 250, 255, 219, 151, 85, 145, 205, 7, 34, 127, 226, 95, 200, 46, 36, 17, 243, 26, 38, 130, 139, 167, 215, 248, 100, 188, 6, 116, 142, 149, 249, 213, 198, 117, 43, 155, 240, 53, 202, 154, 253, 60, 78, 131, 30, 53, 59, 239, 67, 192, 197, 112, 100, 93, 255, 141, 85, 67, 172, 12, 167, 0, 13, 188, 129, 67, 127, 145, 220, 87, 22, 210, 46, 194, 105, 142, 151, 239, 192, 137, 218, 176, 178, 100, 62, 229, 212, 215, 195, 160, 29, 14, 177, 139, 124, 62, 142, 182, 34, 86, 149, 18, 106, 107, 215, 34, 130, 75, 181, 147, 5, 244, 131, 18, 25, 81, 63, 243, 228, 110, 188, 37, 142, 244, 25, 11, 210, 75, 26, 58, 37, 17, 46, 43, 179, 68, 0, 128, 84, 65, 169, 180, 244, 47, 114, 9, 96, 248, 216, 27, 157, 209, 39, 252, 25, 61, 203, 232, 148, 172, 157, 1, 48, 35, 24, 149, 87, 0, 154, 185, 121, 29, 233, 191, 234, 241, 109, 98, 30, 221, 214, 82, 238, 90, 212, 107, 205, 91, 222, 55, 181, 48, 156, 197, 78, 157, 139, 235, 169, 24, 243, 88, 230, 248, 87, 238, 146, 162, 45, 99, 222, 148, 133, 169, 41, 129, 46, 223, 223, 43, 251, 56, 5, 195, 101, 79, 15, 122, 137, 119, 192, 109, 211, 56, 33, 101, 49, 243, 82, 92, 93, 112, 115, 91, 202, 166, 57, 203, 165, 206, 134, 5, 10, 67, 157, 231, 38, 184, 188, 160, 206, 222, 183, 207, 212, 239, 167, 45, 121, 230, 184, 55, 147, 79, 5, 148, 176, 170, 74, 84, 17, 230, 112, 247, 198, 248, 70, 223, 205, 183, 133, 40, 7, 243, 102, 236, 53, 69, 67, 73, 50, 138, 50, 36, 199, 25, 146, 141, 162, 178, 93, 110, 156, 202, 72, 232, 51, 29, 156, 254, 42, 94, 113, 105, 138, 3, 45, 89, 58, 145, 99, 87, 246, 65, 118, 229, 216, 220, 169, 127, 206, 169, 142, 95, 155, 28, 43, 128, 13, 76, 5, 138, 15, 76, 239, 59, 248, 230, 97, 240, 3, 172, 68, 191, 165, 101, 68, 233, 66, 3, 218, 174, 118, 118, 81, 56, 127, 53, 156, 74, 150, 188, 12, 47, 11, 251, 197, 169, 70, 110, 67, 209, 139, 45, 200, 57, 206, 205, 22, 75, 53, 87, 63, 34, 207, 81, 153, 183, 54, 251, 107, 193, 139, 66, 237, 104, 5, 33, 38, 93, 190, 136, 235, 164, 58, 115, 109, 177, 34, 15, 208, 193, 175, 21, 5, 128, 255, 161, 158, 100, 4, 99, 30, 237, 212, 167, 208, 170, 31, 20, 137, 217, 213, 244, 100, 6, 110, 139, 131, 67, 44, 100, 24, 246, 35, 135, 139, 135, 221, 254, 168, 247, 177, 9, 200, 13, 92, 163, 162, 253, 192, 153, 10, 118, 71, 66, 65, 132, 227, 136, 104, 11, 103, 164, 63, 190, 181, 135, 140, 162, 237, 223, 52, 53, 211, 156, 28, 171, 224, 69, 40, 77, 196, 54, 99, 220, 214, 128, 5, 177, 177, 188, 78, 180, 83, 219, 160, 122, 140, 79, 244, 53, 57, 92, 94, 186, 17, 148, 52, 99, 202, 1, 121, 199, 28, 121, 175, 89, 251, 144, 39, 117, 252, 84, 253, 109, 68, 121, 82, 235, 176, 76, 83, 119, 16, 186, 94, 145, 11, 42, 60, 137, 18, 217, 69, 150, 69, 244, 232, 31, 76, 183, 58, 140, 111, 57, 149, 40, 26, 177, 79, 222, 235, 18, 227, 170, 47, 39, 177, 96, 106, 15, 170, 96, 36, 32, 147, 189, 227, 195, 40, 255, 180, 223, 9, 169, 68, 170, 149, 62, 72, 131, 193, 152, 7, 243, 75, 73, 97, 132, 115, 90, 80, 21, 214, 19, 182, 153, 198, 139, 68, 249, 21, 148, 89, 39, 108, 149, 5, 129, 96, 26, 21, 144, 236, 179, 160, 213, 108, 237, 111, 188, 51, 164, 130, 1, 63, 48, 130, 1, 59, 160, 3, 2, 1, 18, 162, 130, 1, 50, 4, 130, 1, 46, 132, 58, 70, 180, 118, 76, 164, 13, 174, 223, 44, 210, 119, 10, 168, 231, 247, 137, 253, 0, 147, 51, 147, 79, 64, 225, 162, 243, 64, 198, 106, 116, 122, 159, 132, 137, 232, 183, 137, 33, 162, 232, 196, 68, 112, 126, 64, 155, 62, 200, 181, 67, 40, 221, 74, 128, 117, 140, 57, 200, 172, 159, 121, 52, 122, 50, 39, 240, 175, 114, 10, 88, 171, 54, 116, 167, 7, 124, 93, 163, 59, 179, 206, 210, 91, 126, 205, 57, 115, 78, 180, 28, 107, 61, 141, 6, 140, 62, 77, 85, 238, 185, 48, 140, 110, 207, 21, 19, 215, 208, 77, 240, 165, 86, 2, 229, 151, 16, 91, 105, 6, 94, 158, 76, 182, 8, 244, 219, 144, 3, 186, 128, 170, 213, 97, 69, 240, 124, 236, 93, 147, 248, 221, 9, 43, 164, 185, 248, 67, 205, 74, 138, 9, 38, 149, 13, 198, 28, 40, 27, 84, 11, 17, 216, 24, 158, 156, 247, 65, 97, 65, 24, 187, 83, 92, 147, 203, 255, 213, 15, 109, 70, 251, 65, 36, 237, 175, 239, 41, 141, 249, 223, 134, 52, 53, 45, 193, 159, 184, 133, 93, 114, 189, 62, 16, 153, 182, 134, 210, 232, 230, 224, 31, 87, 142, 243, 63, 220, 180, 223, 196, 21, 52, 70, 254, 208, 122, 5, 169, 160, 148, 100, 219, 162, 142, 128, 131, 201, 197, 111, 208, 225, 174, 58, 77, 146, 16, 72, 221, 17, 132, 154, 11, 34, 102, 199, 154, 25, 111, 228, 229, 86, 208, 103, 90, 93, 239, 143, 131, 17, 122, 68, 45, 135, 227, 213, 105, 238, 55, 56, 254, 133, 76, 167, 163, 44, 163, 19, 29, 76, 244, 42, 72, 96, 219, 91, 235, 28, 9, 103, 117, 237] +} + +test_encoding_decoding! { + pdu_bind_ack, + Pdu, + Pdu { + header: PduHeader { + version: 5, + version_minor: 0, + packet_type: PacketType::BindAck, + packet_flags: PacketFlags::PfcSupportHeaderSign | PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + frag_len: 230, + auth_len: 138, + call_id: 1, + }, + data: PduData::BindAck(BindAck { + max_xmit_frag: 5840, + max_recv_frag: 5840, + assoc_group: 0x00007320, + sec_addr: String::from("49668"), + results: vec![ + ContextResult { + result: ContextResultCode::Acceptance, + reason: 0, + syntax: Uuid::from_str("71710533-beba-4937-8319-b5dbef9ccc36").unwrap(), + syntax_version: 1, + }, + ContextResult { + result: ContextResultCode::NegotiateAck, + reason: 0, + syntax: Uuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(), + syntax_version: 0, + }, + ], + }), + security_trailer: Some(SecurityTrailer { + security_type: SecurityProvider::GssKerberos, + level: AuthenticationLevel::PktPrivacy, + pad_length: 0, + context_id: 0, + auth_value: vec![111, 129, 135, 48, 129, 132, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 120, 48, 118, 160, 3, 2, 1, 18, 162, 111, 4, 109, 119, 103, 226, 62, 224, 40, 10, 92, 235, 148, 195, 168, 140, 247, 167, 45, 22, 189, 35, 181, 182, 57, 109, 10, 207, 215, 253, 118, 167, 212, 69, 43, 39, 201, 54, 64, 99, 241, 39, 189, 178, 98, 111, 37, 181, 177, 174, 239, 217, 11, 149, 100, 143, 41, 205, 36, 175, 207, 83, 14, 69, 197, 91, 154, 186, 114, 47, 121, 9, 37, 33, 107, 120, 161, 209, 114, 38, 201, 202, 210, 13, 59, 9, 29, 146, 85, 134, 67, 107, 99, 129, 40, 249, 200, 138, 117, 235, 104, 139, 93, 199, 167, 84, 119, 12, 90, 55, 27, 109] + }), + }, + [5, 0, 12, 7, 16, 0, 0, 0, 230, 0, 138, 0, 1, 0, 0, 0, 208, 22, 208, 22, 32, 115, 0, 0, 6, 0, 52, 57, 54, 54, 56, 0, 2, 0, 0, 0, 0, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 6, 0, 0, 0, 0, 0, 0, 111, 129, 135, 48, 129, 132, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 120, 48, 118, 160, 3, 2, 1, 18, 162, 111, 4, 109, 119, 103, 226, 62, 224, 40, 10, 92, 235, 148, 195, 168, 140, 247, 167, 45, 22, 189, 35, 181, 182, 57, 109, 10, 207, 215, 253, 118, 167, 212, 69, 43, 39, 201, 54, 64, 99, 241, 39, 189, 178, 98, 111, 37, 181, 177, 174, 239, 217, 11, 149, 100, 143, 41, 205, 36, 175, 207, 83, 14, 69, 197, 91, 154, 186, 114, 47, 121, 9, 37, 33, 107, 120, 161, 209, 114, 38, 201, 202, 210, 13, 59, 9, 29, 146, 85, 134, 67, 107, 99, 129, 40, 249, 200, 138, 117, 235, 104, 139, 93, 199, 167, 84, 119, 12, 90, 55, 27, 109] +} + +test_encoding_decoding! { + pdu_alter_context, + Pdu, + Pdu { + header: PduHeader { + version: 5, + version_minor: 0, + packet_type: PacketType::AlterContext, + packet_flags: PacketFlags::PfcSupportHeaderSign | PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + frag_len: 173, + auth_len: 93, + call_id: 1, + }, + data: PduData::AlterContext(AlterContext(Bind { + max_xmit_frag: 5840, + max_recv_frag: 5840, + assoc_group: 0, + contexts: vec![ + ContextElement { + context_id: 0, + abstract_syntax: SyntaxId { + uuid: Uuid::from_str("b9785960-524f-11df-8b6d-83dcded72085").unwrap(), + version: 1, + version_minor: 0, + }, + transfer_syntaxes: vec![ + SyntaxId { + uuid: Uuid::from_str("71710533-beba-4937-8319-b5dbef9ccc36").unwrap(), + version: 1, + version_minor: 0, + } + ] + }, + ], + })), + security_trailer: Some(SecurityTrailer { + security_type: SecurityProvider::GssKerberos, + level: AuthenticationLevel::PktPrivacy, + pad_length: 0, + context_id: 0, + auth_value: vec![111, 91, 48, 89, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 77, 48, 75, 160, 3, 2, 1, 18, 162, 68, 4, 66, 169, 200, 55, 118, 91, 23, 32, 40, 237, 31, 41, 10, 235, 96, 11, 206, 91, 184, 138, 167, 37, 44, 224, 129, 132, 69, 220, 201, 123, 20, 243, 60, 251, 187, 228, 62, 104, 246, 170, 121, 102, 22, 16, 1, 222, 154, 38, 2, 94, 168, 232, 219, 6, 47, 32, 21, 238, 30, 254, 203, 201, 245, 242, 109, 43, 132] + }), + }, + [5, 0, 14, 7, 16, 0, 0, 0, 173, 0, 93, 0, 1, 0, 0, 0, 208, 22, 208, 22, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0, 16, 6, 0, 0, 0, 0, 0, 0, 111, 91, 48, 89, 160, 3, 2, 1, 5, 161, 3, 2, 1, 15, 162, 77, 48, 75, 160, 3, 2, 1, 18, 162, 68, 4, 66, 169, 200, 55, 118, 91, 23, 32, 40, 237, 31, 41, 10, 235, 96, 11, 206, 91, 184, 138, 167, 37, 44, 224, 129, 132, 69, 220, 201, 123, 20, 243, 60, 251, 187, 228, 62, 104, 246, 170, 121, 102, 22, 16, 1, 222, 154, 38, 2, 94, 168, 232, 219, 6, 47, 32, 21, 238, 30, 254, 203, 201, 245, 242, 109, 43, 132] +} + +test_encoding_decoding! { + pdu_request, + Pdu, + Pdu { + header: PduHeader { + version: 5, + version_minor: 0, + packet_type: PacketType::Request, + packet_flags: PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + frag_len: 332, + auth_len: 76, + call_id: 1, + }, + data: PduData::Request(Request { + alloc_hint: 224, + context_id: 0, + opnum: 0, + obj: None, + stub_data: vec![70, 145, 235, 30, 109, 26, 31, 173, 254, 42, 137, 229, 243, 197, 44, 158, 238, 241, 41, 183, 81, 67, 57, 200, 254, 191, 147, 127, 205, 26, 3, 40, 255, 194, 91, 96, 55, 224, 130, 204, 168, 191, 33, 234, 237, 111, 175, 214, 140, 82, 127, 41, 174, 170, 228, 93, 51, 220, 223, 202, 204, 131, 102, 248, 202, 155, 5, 129, 117, 2, 229, 154, 46, 85, 137, 43, 189, 80, 105, 195, 207, 206, 50, 225, 121, 213, 208, 156, 244, 102, 76, 112, 244, 57, 173, 67, 116, 129, 185, 143, 232, 121, 52, 62, 241, 0, 14, 31, 208, 226, 155, 175, 16, 174, 156, 17, 53, 0, 163, 190, 217, 253, 107, 13, 206, 7, 225, 139, 156, 203, 149, 7, 247, 94, 222, 106, 236, 20, 57, 137, 82, 83, 240, 38, 131, 217, 130, 188, 85, 50, 55, 154, 150, 64, 148, 170, 48, 56, 219, 253, 162, 223, 243, 244, 116, 25, 228, 155, 93, 106, 187, 240, 80, 24, 0, 146, 192, 248, 239, 98, 144, 160, 17, 70, 74, 18, 17, 117, 215, 151, 189, 241, 77, 32, 193, 180, 71, 172, 118, 69, 103, 165, 79, 159, 190, 42, 51, 243, 86, 224, 148, 94, 89, 138, 70, 16, 158, 43, 179, 125, 70, 252, 89, 109], + }), + security_trailer: Some(SecurityTrailer { + security_type: SecurityProvider::GssNegotiate, + level: AuthenticationLevel::PktPrivacy, + pad_length: 8, + context_id: 0, + auth_value: vec![5, 4, 6, 255, 0, 16, 0, 28, 0, 0, 0, 0, 79, 12, 105, 32, 144, 245, 113, 202, 80, 221, 101, 212, 65, 96, 235, 157, 134, 111, 198, 10, 115, 62, 240, 22, 254, 69, 248, 210, 242, 96, 170, 195, 58, 55, 129, 156, 207, 68, 71, 29, 72, 179, 60, 55, 242, 152, 3, 186, 10, 255, 63, 87, 127, 71, 33, 237, 173, 182, 94, 104, 149, 226, 47, 85], + }), + }, + [5, 0, 0, 3, 16, 0, 0, 0, 76, 1, 76, 0, 1, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 70, 145, 235, 30, 109, 26, 31, 173, 254, 42, 137, 229, 243, 197, 44, 158, 238, 241, 41, 183, 81, 67, 57, 200, 254, 191, 147, 127, 205, 26, 3, 40, 255, 194, 91, 96, 55, 224, 130, 204, 168, 191, 33, 234, 237, 111, 175, 214, 140, 82, 127, 41, 174, 170, 228, 93, 51, 220, 223, 202, 204, 131, 102, 248, 202, 155, 5, 129, 117, 2, 229, 154, 46, 85, 137, 43, 189, 80, 105, 195, 207, 206, 50, 225, 121, 213, 208, 156, 244, 102, 76, 112, 244, 57, 173, 67, 116, 129, 185, 143, 232, 121, 52, 62, 241, 0, 14, 31, 208, 226, 155, 175, 16, 174, 156, 17, 53, 0, 163, 190, 217, 253, 107, 13, 206, 7, 225, 139, 156, 203, 149, 7, 247, 94, 222, 106, 236, 20, 57, 137, 82, 83, 240, 38, 131, 217, 130, 188, 85, 50, 55, 154, 150, 64, 148, 170, 48, 56, 219, 253, 162, 223, 243, 244, 116, 25, 228, 155, 93, 106, 187, 240, 80, 24, 0, 146, 192, 248, 239, 98, 144, 160, 17, 70, 74, 18, 17, 117, 215, 151, 189, 241, 77, 32, 193, 180, 71, 172, 118, 69, 103, 165, 79, 159, 190, 42, 51, 243, 86, 224, 148, 94, 89, 138, 70, 16, 158, 43, 179, 125, 70, 252, 89, 109, 9, 6, 8, 0, 0, 0, 0, 0, 5, 4, 6, 255, 0, 16, 0, 28, 0, 0, 0, 0, 79, 12, 105, 32, 144, 245, 113, 202, 80, 221, 101, 212, 65, 96, 235, 157, 134, 111, 198, 10, 115, 62, 240, 22, 254, 69, 248, 210, 242, 96, 170, 195, 58, 55, 129, 156, 207, 68, 71, 29, 72, 179, 60, 55, 242, 152, 3, 186, 10, 255, 63, 87, 127, 71, 33, 237, 173, 182, 94, 104, 149, 226, 47, 85] +} + +test_encoding_decoding! { + pdu_response, + Pdu, + Pdu { + header: PduHeader { + version: 5, + version_minor: 0, + packet_type: PacketType::Response, + packet_flags: PacketFlags::PfcLastFrag | PacketFlags::PfcFirstFrag, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + frag_len: 988, + auth_len: 76, + call_id: 1, + }, + data: PduData::Response(Response { + alloc_hint: 868, + context_id: 0, + cancel_count: 0, + stub_data: vec![140, 22, 170, 99, 118, 14, 170, 89, 139, 87, 80, 46, 170, 201, 100, 178, 117, 90, 91, 192, 162, 184, 19, 212, 23, 128, 138, 18, 254, 148, 164, 176, 99, 3, 173, 76, 201, 138, 131, 120, 250, 252, 185, 253, 65, 241, 2, 186, 42, 19, 121, 1, 56, 123, 222, 239, 124, 245, 220, 6, 164, 22, 214, 134, 144, 90, 18, 29, 229, 134, 221, 54, 240, 230, 26, 15, 183, 249, 98, 170, 169, 13, 141, 38, 170, 51, 82, 88, 193, 175, 211, 154, 234, 11, 120, 56, 240, 19, 15, 136, 13, 165, 63, 206, 201, 2, 9, 53, 183, 29, 88, 92, 143, 244, 110, 255, 33, 255, 9, 164, 168, 238, 77, 141, 6, 49, 232, 211, 232, 67, 105, 186, 181, 12, 147, 155, 165, 12, 73, 47, 8, 63, 114, 12, 1, 119, 37, 88, 209, 138, 30, 193, 104, 26, 204, 45, 221, 177, 79, 4, 80, 120, 16, 48, 168, 28, 112, 192, 173, 111, 216, 0, 229, 10, 241, 0, 179, 123, 144, 120, 181, 45, 149, 22, 121, 85, 167, 150, 73, 171, 76, 123, 5, 51, 58, 235, 34, 173, 73, 96, 1, 231, 83, 68, 203, 207, 59, 172, 137, 103, 1, 47, 188, 188, 72, 162, 133, 233, 185, 129, 155, 35, 73, 16, 197, 86, 236, 182, 255, 170, 26, 28, 107, 235, 192, 25, 233, 58, 230, 85, 181, 124, 234, 193, 229, 193, 13, 228, 61, 90, 160, 247, 223, 86, 113, 113, 233, 164, 118, 29, 108, 140, 188, 74, 59, 94, 73, 241, 159, 3, 113, 28, 212, 36, 111, 141, 154, 108, 79, 109, 134, 117, 54, 188, 18, 219, 148, 76, 2, 102, 5, 150, 51, 29, 121, 251, 142, 73, 0, 169, 202, 237, 139, 213, 78, 61, 152, 81, 120, 35, 96, 5, 105, 156, 72, 85, 252, 158, 1, 103, 55, 143, 39, 64, 16, 225, 118, 137, 22, 239, 139, 203, 140, 120, 196, 170, 15, 247, 249, 173, 206, 49, 156, 75, 167, 89, 138, 238, 6, 61, 254, 124, 56, 187, 179, 236, 94, 108, 119, 151, 255, 148, 20, 57, 141, 125, 38, 56, 235, 77, 239, 74, 97, 67, 217, 43, 231, 154, 164, 168, 131, 90, 140, 173, 247, 93, 215, 67, 111, 162, 255, 42, 161, 7, 37, 216, 94, 246, 125, 27, 45, 198, 172, 118, 137, 6, 216, 65, 106, 142, 54, 200, 151, 220, 174, 145, 45, 145, 16, 70, 202, 204, 202, 244, 91, 50, 0, 36, 147, 175, 167, 20, 47, 228, 211, 2, 12, 56, 72, 107, 161, 6, 55, 209, 89, 45, 176, 95, 140, 212, 175, 99, 203, 43, 102, 59, 188, 43, 57, 178, 155, 166, 213, 125, 4, 68, 252, 236, 202, 188, 235, 35, 17, 249, 247, 133, 93, 49, 158, 87, 195, 167, 201, 40, 168, 18, 239, 164, 176, 52, 45, 137, 9, 243, 47, 80, 147, 49, 56, 176, 212, 198, 127, 46, 50, 108, 135, 76, 27, 34, 242, 99, 199, 36, 93, 22, 41, 65, 157, 80, 69, 68, 109, 160, 141, 197, 104, 127, 151, 200, 37, 200, 4, 168, 185, 206, 19, 240, 126, 191, 73, 169, 223, 222, 118, 240, 123, 176, 140, 184, 117, 180, 116, 194, 231, 223, 126, 134, 67, 223, 11, 52, 233, 59, 188, 121, 131, 65, 235, 134, 141, 55, 115, 84, 29, 125, 12, 108, 128, 123, 4, 253, 70, 37, 161, 15, 23, 198, 135, 37, 234, 123, 123, 107, 161, 237, 38, 116, 13, 116, 2, 99, 181, 75, 10, 18, 253, 115, 56, 250, 239, 17, 153, 89, 8, 199, 121, 67, 223, 178, 18, 115, 6, 22, 183, 105, 238, 77, 167, 54, 59, 171, 149, 228, 107, 235, 183, 59, 224, 211, 227, 7, 198, 165, 27, 206, 9, 249, 49, 229, 19, 158, 195, 80, 162, 185, 187, 6, 12, 105, 75, 209, 197, 133, 232, 143, 178, 56, 247, 210, 254, 96, 227, 94, 103, 170, 146, 149, 234, 138, 229, 84, 227, 191, 133, 168, 2, 158, 38, 17, 147, 0, 169, 84, 197, 61, 230, 69, 62, 204, 224, 85, 78, 106, 161, 171, 100, 77, 118, 217, 162, 198, 130, 211, 94, 189, 87, 163, 235, 44, 121, 156, 211, 82, 203, 196, 238, 113, 190, 225, 155, 209, 9, 141, 97, 155, 187, 222, 153, 224, 41, 107, 85, 198, 26, 170, 41, 20, 246, 170, 120, 87, 224, 40, 241, 118, 87, 195, 240, 45, 119, 19, 31, 48, 88, 134, 196, 129, 13, 23, 246, 89, 53, 175, 210, 14, 225, 198, 192, 159, 201, 51, 131, 42, 115, 220, 41, 11, 92, 22, 35, 148, 150, 224, 49, 14, 105, 92, 89, 67, 73, 230, 6, 236, 200, 210, 171, 170, 179, 201, 225, 37, 209, 67, 17, 59, 65, 44, 27, 75, 29, 133, 43, 121, 171, 206, 138, 112, 65, 206, 2, 96, 29, 250, 87, 170, 131, 178, 248, 130, 249, 228, 87, 37, 47, 79, 220, 166, 70, 254, 118, 165, 223, 62, 6, 17, 242, 61, 210, 255, 137, 9, 229, 155, 39, 171, 33, 2, 238, 93, 198, 146, 131, 236, 116, 236, 179, 184, 102, 59], + }), + security_trailer: Some(SecurityTrailer { + security_type: SecurityProvider::GssNegotiate, + level: AuthenticationLevel::PktPrivacy, + pad_length: 12, + context_id: 0, + auth_value: vec![5, 4, 7, 255, 0, 16, 0, 28, 0, 0, 0, 0, 51, 128, 170, 35, 94, 238, 241, 97, 204, 124, 66, 162, 119, 57, 190, 117, 249, 25, 174, 246, 194, 102, 133, 211, 241, 188, 128, 195, 227, 189, 65, 195, 40, 30, 231, 115, 38, 58, 165, 66, 11, 106, 157, 183, 70, 85, 36, 135, 69, 247, 93, 97, 111, 229, 75, 25, 99, 208, 247, 253, 227, 122, 252, 85], + }), + }, + [5, 0, 2, 3, 16, 0, 0, 0, 220, 3, 76, 0, 1, 0, 0, 0, 100, 3, 0, 0, 0, 0, 0, 0, 140, 22, 170, 99, 118, 14, 170, 89, 139, 87, 80, 46, 170, 201, 100, 178, 117, 90, 91, 192, 162, 184, 19, 212, 23, 128, 138, 18, 254, 148, 164, 176, 99, 3, 173, 76, 201, 138, 131, 120, 250, 252, 185, 253, 65, 241, 2, 186, 42, 19, 121, 1, 56, 123, 222, 239, 124, 245, 220, 6, 164, 22, 214, 134, 144, 90, 18, 29, 229, 134, 221, 54, 240, 230, 26, 15, 183, 249, 98, 170, 169, 13, 141, 38, 170, 51, 82, 88, 193, 175, 211, 154, 234, 11, 120, 56, 240, 19, 15, 136, 13, 165, 63, 206, 201, 2, 9, 53, 183, 29, 88, 92, 143, 244, 110, 255, 33, 255, 9, 164, 168, 238, 77, 141, 6, 49, 232, 211, 232, 67, 105, 186, 181, 12, 147, 155, 165, 12, 73, 47, 8, 63, 114, 12, 1, 119, 37, 88, 209, 138, 30, 193, 104, 26, 204, 45, 221, 177, 79, 4, 80, 120, 16, 48, 168, 28, 112, 192, 173, 111, 216, 0, 229, 10, 241, 0, 179, 123, 144, 120, 181, 45, 149, 22, 121, 85, 167, 150, 73, 171, 76, 123, 5, 51, 58, 235, 34, 173, 73, 96, 1, 231, 83, 68, 203, 207, 59, 172, 137, 103, 1, 47, 188, 188, 72, 162, 133, 233, 185, 129, 155, 35, 73, 16, 197, 86, 236, 182, 255, 170, 26, 28, 107, 235, 192, 25, 233, 58, 230, 85, 181, 124, 234, 193, 229, 193, 13, 228, 61, 90, 160, 247, 223, 86, 113, 113, 233, 164, 118, 29, 108, 140, 188, 74, 59, 94, 73, 241, 159, 3, 113, 28, 212, 36, 111, 141, 154, 108, 79, 109, 134, 117, 54, 188, 18, 219, 148, 76, 2, 102, 5, 150, 51, 29, 121, 251, 142, 73, 0, 169, 202, 237, 139, 213, 78, 61, 152, 81, 120, 35, 96, 5, 105, 156, 72, 85, 252, 158, 1, 103, 55, 143, 39, 64, 16, 225, 118, 137, 22, 239, 139, 203, 140, 120, 196, 170, 15, 247, 249, 173, 206, 49, 156, 75, 167, 89, 138, 238, 6, 61, 254, 124, 56, 187, 179, 236, 94, 108, 119, 151, 255, 148, 20, 57, 141, 125, 38, 56, 235, 77, 239, 74, 97, 67, 217, 43, 231, 154, 164, 168, 131, 90, 140, 173, 247, 93, 215, 67, 111, 162, 255, 42, 161, 7, 37, 216, 94, 246, 125, 27, 45, 198, 172, 118, 137, 6, 216, 65, 106, 142, 54, 200, 151, 220, 174, 145, 45, 145, 16, 70, 202, 204, 202, 244, 91, 50, 0, 36, 147, 175, 167, 20, 47, 228, 211, 2, 12, 56, 72, 107, 161, 6, 55, 209, 89, 45, 176, 95, 140, 212, 175, 99, 203, 43, 102, 59, 188, 43, 57, 178, 155, 166, 213, 125, 4, 68, 252, 236, 202, 188, 235, 35, 17, 249, 247, 133, 93, 49, 158, 87, 195, 167, 201, 40, 168, 18, 239, 164, 176, 52, 45, 137, 9, 243, 47, 80, 147, 49, 56, 176, 212, 198, 127, 46, 50, 108, 135, 76, 27, 34, 242, 99, 199, 36, 93, 22, 41, 65, 157, 80, 69, 68, 109, 160, 141, 197, 104, 127, 151, 200, 37, 200, 4, 168, 185, 206, 19, 240, 126, 191, 73, 169, 223, 222, 118, 240, 123, 176, 140, 184, 117, 180, 116, 194, 231, 223, 126, 134, 67, 223, 11, 52, 233, 59, 188, 121, 131, 65, 235, 134, 141, 55, 115, 84, 29, 125, 12, 108, 128, 123, 4, 253, 70, 37, 161, 15, 23, 198, 135, 37, 234, 123, 123, 107, 161, 237, 38, 116, 13, 116, 2, 99, 181, 75, 10, 18, 253, 115, 56, 250, 239, 17, 153, 89, 8, 199, 121, 67, 223, 178, 18, 115, 6, 22, 183, 105, 238, 77, 167, 54, 59, 171, 149, 228, 107, 235, 183, 59, 224, 211, 227, 7, 198, 165, 27, 206, 9, 249, 49, 229, 19, 158, 195, 80, 162, 185, 187, 6, 12, 105, 75, 209, 197, 133, 232, 143, 178, 56, 247, 210, 254, 96, 227, 94, 103, 170, 146, 149, 234, 138, 229, 84, 227, 191, 133, 168, 2, 158, 38, 17, 147, 0, 169, 84, 197, 61, 230, 69, 62, 204, 224, 85, 78, 106, 161, 171, 100, 77, 118, 217, 162, 198, 130, 211, 94, 189, 87, 163, 235, 44, 121, 156, 211, 82, 203, 196, 238, 113, 190, 225, 155, 209, 9, 141, 97, 155, 187, 222, 153, 224, 41, 107, 85, 198, 26, 170, 41, 20, 246, 170, 120, 87, 224, 40, 241, 118, 87, 195, 240, 45, 119, 19, 31, 48, 88, 134, 196, 129, 13, 23, 246, 89, 53, 175, 210, 14, 225, 198, 192, 159, 201, 51, 131, 42, 115, 220, 41, 11, 92, 22, 35, 148, 150, 224, 49, 14, 105, 92, 89, 67, 73, 230, 6, 236, 200, 210, 171, 170, 179, 201, 225, 37, 209, 67, 17, 59, 65, 44, 27, 75, 29, 133, 43, 121, 171, 206, 138, 112, 65, 206, 2, 96, 29, 250, 87, 170, 131, 178, 248, 130, 249, 228, 87, 37, 47, 79, 220, 166, 70, 254, 118, 165, 223, 62, 6, 17, 242, 61, 210, 255, 137, 9, 229, 155, 39, 171, 33, 2, 238, 93, 198, 146, 131, 236, 116, 236, 179, 184, 102, 59, 9, 6, 12, 0, 0, 0, 0, 0, 5, 4, 7, 255, 0, 16, 0, 28, 0, 0, 0, 0, 51, 128, 170, 35, 94, 238, 241, 97, 204, 124, 66, 162, 119, 57, 190, 117, 249, 25, 174, 246, 194, 102, 133, 211, 241, 188, 128, 195, 227, 189, 65, 195, 40, 30, 231, 115, 38, 58, 165, 66, 11, 106, 157, 183, 70, 85, 36, 135, 69, 247, 93, 97, 111, 229, 75, 25, 99, 208, 247, 253, 227, 122, 252, 85] +} diff --git a/crates/dpapi/tests/dpapi/rpc/verification.rs b/crates/dpapi/tests/dpapi/rpc/verification.rs new file mode 100644 index 00000000..8479ae8d --- /dev/null +++ b/crates/dpapi/tests/dpapi/rpc/verification.rs @@ -0,0 +1,76 @@ +use dpapi::rpc::bind::SyntaxId; +use dpapi::rpc::pdu::{CharacterRepr, DataRepr, FloatingPointRepr, IntRepr, PacketType}; +use dpapi::rpc::verification::{ + Command, CommandBitmask, CommandFlags, CommandHeader2, CommandPContext, VerificationTrailer, +}; +use uuid::uuid; + +test_encoding_decoding! { + verification_trailer_pcontext_end, + VerificationTrailer, + VerificationTrailer { + commands: vec![ + Command::Pcontext(CommandPContext { + flags: CommandFlags::SecVtCommandEnd, + interface_id: SyntaxId { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }, + transfer_syntax: SyntaxId { + uuid: uuid!("71710533-beba-4937-8319-b5dbef9ccc36"), + version: 1, + version_minor: 0, + }, + }), + ], + }, + [138, 227, 19, 113, 2, 244, 54, 113, 2, 64, 40, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0] +} + +test_encoding_decoding! { + command_bitmask, + Command, + Command::Bitmask1(CommandBitmask { + bits: 1, + flags: CommandFlags::None, + }), + [1, 0, 4, 0, 1, 0, 0, 0] +} + +test_encoding_decoding! { + command_pcontext, + Command, + Command::Pcontext(CommandPContext { + flags: CommandFlags::SecVtCommandEnd, + interface_id: SyntaxId { + uuid: uuid!("b9785960-524f-11df-8b6d-83dcded72085"), + version: 1, + version_minor: 0, + }, + transfer_syntax: SyntaxId { + uuid: uuid!("71710533-beba-4937-8319-b5dbef9ccc36"), + version: 1, + version_minor: 0, + }, + }), + [2, 64, 40, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0] +} + +test_encoding_decoding! { + command_header2, + Command, + Command::Header2(CommandHeader2 { + flags: CommandFlags::SecVtMustProcessCommand, + packet_type: PacketType::Request, + data_rep: DataRepr { + byte_order: IntRepr::LittleEndian, + character: CharacterRepr::Ascii, + floating_point: FloatingPointRepr::Ieee, + }, + call_id: 1, + context_id: 2, + opnum: 3, + }), + [3, 128, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0] +} diff --git a/crates/ffi-types/src/common.rs b/crates/ffi-types/src/common.rs index 63a5bb9f..f95c5ede 100644 --- a/crates/ffi-types/src/common.rs +++ b/crates/ffi-types/src/common.rs @@ -24,6 +24,7 @@ pub type Bool = i32; /// unsigned char Data4[8]; /// } GUID; /// ``` +#[derive(Clone, Copy)] #[repr(C)] pub struct Guid { pub data1: u32, diff --git a/examples/client.rs b/examples/client.rs index 202d619e..a441c7f5 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -9,8 +9,8 @@ use std::net::TcpStream; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use sspi::{ - AuthIdentity, ClientRequestFlags, CredentialUse, DataRepresentation, Ntlm, OwnedSecurityBuffer, SecurityBuffer, - SecurityBufferType, SecurityStatus, Sspi, SspiImpl, Username, + AuthIdentity, BufferType, ClientRequestFlags, CredentialUse, DataRepresentation, Ntlm, SecurityBuffer, + SecurityBufferRef, SecurityStatus, Sspi, SspiImpl, Username, }; const IP: &str = "127.0.0.1:8080"; @@ -52,7 +52,10 @@ fn main() -> Result<(), io::Error> { println!("Encrypted message: {:?}", data); - let mut msg_buffer = vec![SecurityBuffer::Token(&mut trailer), SecurityBuffer::Data(&mut data)]; + let mut msg_buffer = vec![ + SecurityBufferRef::token_buf(&mut trailer), + SecurityBufferRef::data_buf(&mut data), + ]; let _decryption_flags = ntlm.decrypt_message(&mut msg_buffer, 0)?; @@ -74,7 +77,7 @@ fn do_authentication(ntlm: &mut Ntlm, identity: &AuthIdentity, mut stream: &mut .with_auth_data(identity) .execute(ntlm)?; - let mut output_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let username = whoami::username(); let mut builder = ntlm @@ -91,7 +94,7 @@ fn do_authentication(ntlm: &mut Ntlm, identity: &AuthIdentity, mut stream: &mut write_message(&mut stream, &output_buffer[0].buffer)?; - let mut input_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut input_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; loop { output_buffer[0].buffer.clear(); diff --git a/examples/kerberos.rs b/examples/kerberos.rs index 8c71aa59..e5a3879e 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -7,9 +7,9 @@ use reqwest::header::{ }; use reqwest::StatusCode; use sspi::{ - AcquireCredentialsHandleResult, ClientRequestFlags, CredentialsBuffers, DataRepresentation, - InitializeSecurityContextResult, Kerberos, KerberosConfig, OwnedSecurityBuffer, SecurityBufferType, SecurityStatus, - Sspi, SspiImpl, Username, + AcquireCredentialsHandleResult, BufferType, ClientRequestFlags, CredentialsBuffers, DataRepresentation, + InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, SecurityStatus, Sspi, SspiImpl, + Username, }; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; @@ -120,8 +120,8 @@ pub(crate) fn send_http( fn step_helper( kerberos: &mut Kerberos, cred_handle: &mut ::CredentialsHandle, - input_buffer: &mut [OwnedSecurityBuffer], - output_buffer: &mut [OwnedSecurityBuffer], + input_buffer: &mut [SecurityBuffer], + output_buffer: &mut [SecurityBuffer], hostname: &str, ) -> Result> { let target_name = format!("HTTP/{}", hostname); @@ -148,8 +148,8 @@ pub fn step( hostname: &str, ) -> (String, SecurityStatus) { let input_buffer = base64::engine::general_purpose::STANDARD.decode(input_token).unwrap(); - let mut secure_input_buffer = vec![OwnedSecurityBuffer::new(input_buffer, SecurityBufferType::Token)]; - let mut secure_output_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut secure_input_buffer = vec![SecurityBuffer::new(input_buffer, BufferType::Token)]; + let mut secure_output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; match step_helper( kerberos, cred_handle, diff --git a/examples/server.rs b/examples/server.rs index c927ce76..cb223301 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -8,8 +8,8 @@ use std::net::{TcpListener, TcpStream}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use sspi::{ - AuthIdentity, CredentialUse, DataRepresentation, EncryptionFlags, Ntlm, OwnedSecurityBuffer, SecurityBuffer, - SecurityBufferType, SecurityStatus, ServerRequestFlags, Sspi, Username, + AuthIdentity, BufferType, CredentialUse, DataRepresentation, EncryptionFlags, Ntlm, SecurityBuffer, + SecurityBufferRef, SecurityStatus, ServerRequestFlags, Sspi, Username, }; const IP: &str = "127.0.0.1:8080"; @@ -49,8 +49,8 @@ fn main() -> Result<(), io::Error> { let mut token = vec![0u8; ntlm.query_context_sizes()?.security_trailer as usize]; let mut data = msg.as_bytes().to_vec(); let mut msg_buffer = vec![ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; println!("Unencrypted message: [{}]", msg); @@ -78,8 +78,8 @@ fn do_authentication(ntlm: &mut Ntlm, identity: &AuthIdentity, mut stream: &mut .with_auth_data(identity) .execute(ntlm)?; - let mut input_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; - let mut output_buffer = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut input_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; + let mut output_buffer = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; loop { read_message(&mut stream, &mut input_buffer[0].buffer)?; diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index fdd7a129..ff2fbd5f 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -13,11 +13,12 @@ name = "sspi" crate-type = ["cdylib"] [features] -default = ["aws-lc-rs", "scard"] -tsssp = ["sspi/tsssp"] +default = ["aws-lc-rs", "scard", "dpapi"] +tsssp = ["sspi/tsssp", "dpapi/tsssp"] scard = ["sspi/scard", "dep:ffi-types", "dep:winscard", "dep:bitflags", "dep:picky-asn1-x509", "dep:picky"] aws-lc-rs = ["sspi/aws-lc-rs"] ring = ["sspi/ring"] +dpapi = ["dep:dpapi", "dep:ffi-types"] [dependencies] cfg-if.workspace = true @@ -27,6 +28,7 @@ ffi-types = { workspace = true, features = ["winscard"], optional = true } picky-asn1-der.workspace = true uuid.workspace = true winscard = { workspace = true, features = ["std"], optional = true } +dpapi = { workspace = true, optional = true } # logging tracing = { workspace = true, default-features = true } tracing-subscriber = { workspace = true, features = ["std", "fmt", "local-time", "env-filter"] } diff --git a/ffi/src/dpapi/api.rs b/ffi/src/dpapi/api.rs new file mode 100644 index 00000000..1b8e1722 --- /dev/null +++ b/ffi/src/dpapi/api.rs @@ -0,0 +1,43 @@ +#[cfg(not(any(test, miri)))] +mod inner { + pub use dpapi::{n_crypt_protect_secret, n_crypt_unprotect_secret}; +} + +#[cfg(any(test, miri))] +mod inner { + //! We have FFI wrappers for DPAPI functions from the [dpapi] crate and we want to test them. + //! The DPAPI implementation is complex and makes calls to the RPC and KDC servers. + //! Implementing a mock for KDF and RPC servers is too hard and unreasonable. So, we wrote a simple + //! high-level mock of [n_crypt_protect_secret] and [n_crypt_unprotect_secret] functions. + //! + //! **Note**: The goal is to test FFI functions, not the DPAPI implementation correctness. + //! The FFI tests should not care about returned data correctness but rather check + //! for memory corruptions and memory leaks. + + use sspi::Secret; + use uuid::Uuid; + + pub fn n_crypt_unprotect_secret( + _blob: &[u8], + _server: &str, + _username: &str, + _password: Secret, + _client_computer_name: Option, + ) -> dpapi::Result>> { + Ok(b"secret-to-encrypt".to_vec().into()) + } + + pub fn n_crypt_protect_secret( + _data: Secret>, + _sid: String, + _root_key_id: Option, + _server: &str, + _username: &str, + _password: Secret, + _client_computer_name: Option, + ) -> dpapi::Result> { + Ok(b"DPAPI_blob".to_vec()) + } +} + +pub use inner::*; diff --git a/ffi/src/dpapi/macros.rs b/ffi/src/dpapi/macros.rs new file mode 100644 index 00000000..a617ee64 --- /dev/null +++ b/ffi/src/dpapi/macros.rs @@ -0,0 +1,31 @@ +macro_rules! check_null { + ($x:expr) => {{ + if $x.is_null() { + // https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret#return-value + return crate::dpapi::NTE_INVALID_PARAMETER; + } + }}; +} + +macro_rules! try_execute { + ($x:expr, $err_value:expr) => {{ + match $x { + Ok(val) => val, + Err(err) => { + error!(%err, "an error occurred"); + return $err_value; + } + } + }}; +} + +macro_rules! catch_panic { + ($($tokens:tt)*) => {{ + match std::panic::catch_unwind(move || { $($tokens)* }) { + Ok(val) => val, + Err(_) => { + return crate::dpapi::NTE_INTERNAL_ERROR; + } + } + }}; +} diff --git a/ffi/src/dpapi/mod.rs b/ffi/src/dpapi/mod.rs new file mode 100644 index 00000000..60238f68 --- /dev/null +++ b/ffi/src/dpapi/mod.rs @@ -0,0 +1,349 @@ +#[macro_use] +mod macros; +mod api; + +use std::ffi::CStr; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +use ffi_types::common::{Dword, LpByte, LpCByte, LpCStr, LpCUuid}; +use uuid::Uuid; + +use self::api::{n_crypt_protect_secret, n_crypt_unprotect_secret}; + +// https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret#return-value +const ERROR_SUCCESS: u32 = 0; +const NTE_INVALID_PARAMETER: u32 = 0x80090027; +const NTE_INTERNAL_ERROR: u32 = 0x8009002d; +const NTE_NO_MEMORY: u32 = 0x8009000e; + +/// Encrypts the secret using the DPAPI. +/// +/// This function simulated the `NCryptProtectSecret` function. Encryption requires making RPCs call to the domain. +/// +/// # Safety +/// +/// Input parameters must meet the following requirements: +/// +/// * `secret` must be a valid pointer to the secret buffer. This parameter **cannot be NULL**. +/// * `secret_len` is a length of the `secret` buffer. +/// * `sid` must be a valid pointer to the UTF-8 SID string (with a null-terminator character). This parameter **cannot be NULL**. +/// * `root_key` is a pointer to the root key UUID. This parameter is optional and can be NULL. +/// * `server` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing target server hostname. +/// Do not use IP address. This parameter **cannot be NULL**. +/// * `username` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing username. +/// This parameter **cannot be NULL**. The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format +/// * `password` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing user's password. +/// This parameter **cannot be NULL**. +/// * `computer_name` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing client's computer name. +/// This parameter can be NULL. If it's NULL, the current computer name will be used. +/// * `blob` is a pointer to the output buffer containing DPAPI blob. This parameter **cannot be NULL**. +/// The caller is responsible for freeing the memory using the [DpapiFree] function. +/// * `blob_len` is a length of the output `blob` buffer. This parameter **cannot be NULL**. +/// +/// MSDN: +/// * [NCryptProtectSecret function (`ncryptprotect.h`)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret). +#[instrument(skip_all)] +#[no_mangle] +pub unsafe extern "system" fn DpapiProtectSecret( + secret: LpCByte, + secret_len: Dword, + sid: LpCStr, + root_key: LpCUuid, + server: LpCStr, + username: LpCStr, + password: LpCStr, + computer_name: LpCStr, + blob: *mut LpByte, + blob_len: *mut Dword, +) -> u32 { + catch_panic! { + check_null!(secret); + check_null!(sid); + check_null!(server); + check_null!(username); + check_null!(password); + check_null!(blob); + check_null!(blob_len); + + let secret = + // SAFETY: The `secret` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { from_raw_parts(secret, try_execute!(secret_len.try_into(), NTE_INVALID_PARAMETER)) }.to_owned(); + let sid = try_execute!( + // SAFETY: The `sid` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(sid as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ) + .to_owned(); + let root_key = if !root_key.is_null() { + // SAFETY: The `root_key` pointer is not NULL (checked above). + let id = unsafe { *root_key }; + let root_key = Uuid::from_fields(id.data1, id.data2, id.data3, &id.data4); + + Some(root_key) + } else { + None + }; + let server = try_execute!( + // SAFETY: The `server` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(server as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ); + let username = try_execute!( + // SAFETY: The `username` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(username as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ); + let password = try_execute!( + // SAFETY: The `password` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(password as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ) + .to_owned(); + let computer_name = if !computer_name.is_null() { + Some( + try_execute!( + // SAFETY: The `computer_name` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(computer_name as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ) + .to_owned(), + ) + } else { + None + }; + + let blob_data = try_execute!( + n_crypt_protect_secret( + secret.into(), + sid, + root_key, + server, + username, + password.into(), + computer_name + ), + NTE_INTERNAL_ERROR + ); + + if blob_data.is_empty() { + error!("Output DPAPI blob is empty"); + return NTE_INTERNAL_ERROR; + } + + // SAFETY: Memory allocation should be safe. Moreover, we check for the null value below. + let blob_buf = unsafe { libc::malloc(blob_data.len()) as *mut u8 }; + if blob_buf.is_null() { + error!("Failed to allocate memory for the output DPAPI blob: blob buf pointer is NULL"); + return NTE_NO_MEMORY; + } + + // SAFETY: The `blob_buf` pointer is not NULL (checked above). The slice construction is safe because `blob_buf` + // points to allocated, properly aligned, and not-empty bytes range. + let buf = unsafe { from_raw_parts_mut(blob_buf, blob_data.len()) }; + buf.copy_from_slice(blob_data.as_ref()); + + // SAFETY: The `blob` pointer is not NULL (checked above). + unsafe { + *blob = blob_buf; + *blob_len = try_execute!(blob_data.len().try_into(), NTE_INTERNAL_ERROR); + } + + ERROR_SUCCESS + } +} + +/// Decrypt the DPAPI blob. +/// +/// This function simulated the `NCryptUnprotectSecret` function. Decryption requires making RPC calls to the domain. +/// +/// # Safety +/// +/// Input parameters must meet the following requirements: +/// +/// * `blob` must be a valid pointer to the DPAPI blob buffer. This parameter **cannot be NULL**. +/// * `blob_len` is a length of the `blob` buffer. +/// * `server` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing target server hostname. +/// Do not use IP address. This parameter **cannot be NULL**. +/// * `username` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing username. +/// This parameter **cannot be NULL**. The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format +/// * `password` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing user's password. +/// This parameter **cannot be NULL**. +/// * `computer_name` must be a valid pointer to the UTF-8 string (with a null-terminator character) containing client's computer name. +/// This parameter can be NULL. If it's NULL, the current computer name will be used. +/// * `secret` is a pointer to the output buffer containing decrypted secret. This parameter **cannot be NULL**. +/// The caller is responsible for freeing the memory using the [DpapiFree] function. +/// * `secret_len` is a length of the output `secret` buffer. This parameter **cannot be NULL**. +/// +/// MSDN: +/// * [NCryptUnprotectSecret function (ncryptprotect.h)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptunprotectsecret). +#[instrument(skip_all)] +#[no_mangle] +pub unsafe extern "system" fn DpapiUnprotectSecret( + blob: LpCByte, + blob_len: Dword, + server: LpCStr, + username: LpCStr, + password: LpCStr, + computer_name: LpCStr, + secret: *mut LpByte, + secret_len: *mut Dword, +) -> u32 { + catch_panic! { + check_null!(blob); + check_null!(server); + check_null!(username); + check_null!(password); + check_null!(secret); + + // SAFETY: The `blob` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + let blob = unsafe { from_raw_parts(blob, try_execute!(blob_len.try_into(), NTE_INVALID_PARAMETER)) }; + let server = try_execute!( + // SAFETY: The `server` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(server as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ); + let username = try_execute!( + // SAFETY: The `username` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(username as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ); + let password = try_execute!( + // SAFETY: The `password` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(password as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ) + .to_owned(); + let computer_name = if !computer_name.is_null() { + Some( + try_execute!( + // SAFETY: The `computer_name` pointer is not NULL (checked above). Other guarantees should be upheld by the caller. + unsafe { CStr::from_ptr(computer_name as *const _) }.to_str(), + NTE_INVALID_PARAMETER + ) + .to_owned(), + ) + } else { + None + }; + + let secret_data = try_execute!( + n_crypt_unprotect_secret(blob, server, username, password.into(), computer_name), + NTE_INTERNAL_ERROR + ); + + if secret_data.as_ref().is_empty() { + error!("Decrypted secret is empty"); + return NTE_INTERNAL_ERROR; + } + + // SAFETY: Memory allocation should be safe. Moreover, we check for the null value below. + let secret_buf = unsafe { libc::malloc(secret_data.as_ref().len()) as *mut u8 }; + if secret_buf.is_null() { + error!("Failed to allocate memory for the output DPAPI blob: blob buf pointer is NULL"); + return NTE_NO_MEMORY; + } + + // SAFETY: The `secret_buf` pointer is not NULL (checked above). The slice construction is safe because `secret_buf` + // points to allocated, properly aligned, and not-empty bytes range. + let buf = unsafe { from_raw_parts_mut(secret_buf, secret_data.as_ref().len()) }; + buf.copy_from_slice(secret_data.as_ref()); + + // SAFETY: The `secret` pointer is not NULL (checked above). + unsafe { + *secret = secret_buf; + *secret_len = try_execute!(secret_data.as_ref().len().try_into(), NTE_INTERNAL_ERROR); + } + + ERROR_SUCCESS + } +} + +/// Frees the memory allocated by [DpapiProtectSecret] and [DpapiUnprotectSecret] functions. +/// +/// # Safety +/// +/// The `data` parameter must be a valid pointer to the memory allocated by the [DpapiProtectSecret] or +/// [DpapiUnprotectSecret] functions and **cannot be NULL**. +#[instrument(skip_all)] +#[no_mangle] +pub unsafe extern "system" fn DpapiFree(buf: LpCByte) -> u32 { + catch_panic! { + check_null!(buf); + + // SAFETY: The user should uphold that the passed pointer is a memory allocated by an out DPAPI functions. + unsafe { + libc::free(buf as _); + } + + ERROR_SUCCESS + } +} + +#[cfg(test)] +mod tests { + use std::ptr::{null, null_mut}; + + use super::*; + + /// This test simulates `DpapiProtectSecret`, `DpapiUnprotectSecret`, and `DpapiFree` function calls. + /// It's better to run it using Miri: https://github.com/rust-lang/miri. + /// cargo +nightly miri test + /// + /// Note: this test aims to check only the implementation of FFI functions. + /// Checking the correctness of DPAPI functions is not a goal of this test. + #[test] + fn test_dpapi_protect_secret() { + let secret = b"secret-to-encrypt"; + let secret_len = secret.len() as u32; + let sid = "S-1-5-21-1485435871-894665558-560847465-1104\0"; + let server = "win-956cqossjtf.tbt.com\0"; + let username = "t2@tbt.com\0"; + let password = "qqqQQQ111!!!\0"; + let mut blob: LpByte = null_mut(); + let mut blob_len = 0; + + let result = unsafe { + DpapiProtectSecret( + secret.as_ptr(), + secret_len, + sid.as_ptr(), + null(), + server.as_ptr(), + username.as_ptr(), + password.as_ptr(), + null(), + &mut blob, + &mut blob_len, + ) + }; + + assert_eq!(result, ERROR_SUCCESS); + assert!(!blob.is_null()); + assert!(blob_len > 0); + + let mut decrypted_secret: LpByte = null_mut(); + let mut secret_len = 0; + + let result = unsafe { + DpapiUnprotectSecret( + blob, + blob_len, + server.as_ptr(), + username.as_ptr(), + password.as_ptr(), + null(), + &mut decrypted_secret, + &mut secret_len, + ) + }; + + assert_eq!(result, ERROR_SUCCESS); + assert!(!decrypted_secret.is_null()); + assert!(secret_len > 0); + + unsafe { + DpapiFree(blob); + DpapiFree(decrypted_secret); + } + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index c5420cbe..6de7e445 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -5,6 +5,10 @@ #[macro_use] extern crate tracing; +#[cfg(feature = "dpapi")] +#[deny(unsafe_op_in_unsafe_fn)] +#[warn(clippy::undocumented_unsafe_blocks)] +pub mod dpapi; pub mod logging; pub mod sspi; mod utils; diff --git a/ffi/src/sspi/common.rs b/ffi/src/sspi/common.rs index 19749417..5a7df832 100644 --- a/ffi/src/sspi/common.rs +++ b/ffi/src/sspi/common.rs @@ -3,7 +3,7 @@ use std::slice::{from_raw_parts, from_raw_parts_mut}; use libc::{c_ulonglong, c_void}; use num_traits::cast::{FromPrimitive, ToPrimitive}; use sspi::{ - DataRepresentation, DecryptionFlags, EncryptionFlags, ErrorKind, OwnedSecurityBuffer, SecurityBuffer, + BufferType, DataRepresentation, DecryptionFlags, EncryptionFlags, ErrorKind, SecurityBuffer, SecurityBufferRef, SecurityBufferType, ServerRequestFlags, Sspi, }; #[cfg(windows)] @@ -78,7 +78,7 @@ pub unsafe extern "system" fn AcceptSecurityContext( let mut input_tokens = p_sec_buffers_to_security_buffers(from_raw_parts((*p_input).p_buffers, (*p_input).c_buffers as usize)); - let mut output_tokens = vec![OwnedSecurityBuffer::new(Vec::with_capacity(1024), SecurityBufferType::Token)]; + let mut output_tokens = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let result_status = sspi_context.accept_security_context() .with_credentials_handle(&mut Some(auth_data)) @@ -341,28 +341,22 @@ pub unsafe extern "system" fn DecryptMessage( } } -/// Creates a vector of [SecurityBuffer]s from the input C buffers. +/// Creates a vector of [SecurityBufferRef]s from the input C buffers. /// /// *Attention*: after this function call, no one should touch [raw_buffers]. Otherwise, we can get UB. /// It's because this function creates exclusive (mutable) Rust references to the input buffers. #[allow(clippy::useless_conversion)] -unsafe fn p_sec_buffers_to_decrypt_buffers(raw_buffers: &[SecBuffer]) -> sspi::Result> { +unsafe fn p_sec_buffers_to_decrypt_buffers(raw_buffers: &[SecBuffer]) -> sspi::Result> { let mut buffers = Vec::with_capacity(raw_buffers.len()); for raw_buffer in raw_buffers { - let buf = SecurityBuffer::with_security_buffer_type( - SecurityBufferType::from_u32(raw_buffer.buffer_type).ok_or_else(|| { - sspi::Error::new( - ErrorKind::InternalError, - format!("u32({}) to SecurityBufferType conversion error", raw_buffer.buffer_type), - ) - })?, - )?; + let buf = + SecurityBufferRef::with_owned_security_buffer_type(SecurityBufferType::try_from(raw_buffer.buffer_type)?)?; - buffers.push(if let SecurityBuffer::Missing(_) = buf { + buffers.push(if BufferType::Missing == buf.buffer_type() { // https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer // SECBUFFER_MISSING: ...The pvBuffer member is ignored in this type. - SecurityBuffer::Missing(raw_buffer.cb_buffer.try_into()?) + SecurityBufferRef::missing_buf(raw_buffer.cb_buffer.try_into()?) } else { let data = if raw_buffer.pv_buffer.is_null() || raw_buffer.cb_buffer == 0 { &mut [] @@ -381,7 +375,7 @@ unsafe fn p_sec_buffers_to_decrypt_buffers(raw_buffers: &[SecBuffer]) -> sspi::R /// /// This function accepts owned [from_buffers] to avoid UB and other errors. Rust-buffers should /// not be used after the data is copied into C-buffers. -unsafe fn copy_decrypted_buffers(to_buffers: PSecBuffer, from_buffers: Vec) -> sspi::Result<()> { +unsafe fn copy_decrypted_buffers(to_buffers: PSecBuffer, from_buffers: Vec) -> sspi::Result<()> { // SAFETY: the safety contract [to_buffers] must be upheld by the caller. let to_buffers = unsafe { from_raw_parts_mut(to_buffers, from_buffers.len()) }; @@ -392,24 +386,16 @@ unsafe fn copy_decrypted_buffers(to_buffers: PSecBuffer, from_buffers: Vec Vec { +pub(crate) unsafe fn p_sec_buffers_to_security_buffers(raw_buffers: &[SecBuffer]) -> Vec { raw_buffers .iter() - .map(|raw_buffer| OwnedSecurityBuffer { - buffer: from_raw_parts(raw_buffer.pv_buffer, raw_buffer.cb_buffer as usize) - .iter() - .map(|v| *v as u8) - .collect(), - buffer_type: SecurityBufferType::from_u32(raw_buffer.buffer_type.try_into().unwrap()).unwrap(), + .map(|raw_buffer| SecurityBuffer { + buffer: if raw_buffer.pv_buffer.is_null() { + Vec::new() + } else { + from_raw_parts(raw_buffer.pv_buffer, raw_buffer.cb_buffer as usize) + .iter() + .map(|v| *v as u8) + .collect() + }, + buffer_type: SecurityBufferType::try_from(u32::try_from(raw_buffer.buffer_type).unwrap()).unwrap(), }) .collect() } -pub(crate) unsafe fn copy_to_c_sec_buffer( - to_buffers: PSecBuffer, - from_buffers: &[OwnedSecurityBuffer], - allocate: bool, -) { +pub(crate) unsafe fn copy_to_c_sec_buffer(to_buffers: PSecBuffer, from_buffers: &[SecurityBuffer], allocate: bool) { let to_buffers = from_raw_parts_mut(to_buffers, from_buffers.len()); for i in 0..from_buffers.len() { let buffer = &from_buffers[i]; let buffer_size = buffer.buffer.len(); to_buffers[i].cb_buffer = buffer_size.try_into().unwrap(); - to_buffers[i].buffer_type = buffer.buffer_type.to_u32().unwrap(); + to_buffers[i].buffer_type = buffer.buffer_type.into(); if allocate || to_buffers[i].pv_buffer.is_null() { to_buffers[i].pv_buffer = libc::malloc(buffer_size) as *mut c_char; } diff --git a/ffi/src/sspi/sec_handle.rs b/ffi/src/sspi/sec_handle.rs index 940833c3..6fe5cfff 100644 --- a/ffi/src/sspi/sec_handle.rs +++ b/ffi/src/sspi/sec_handle.rs @@ -129,14 +129,14 @@ impl SspiImpl for SspiHandle { } impl Sspi for SspiHandle { - fn complete_auth_token(&mut self, token: &mut [sspi::OwnedSecurityBuffer]) -> Result { + fn complete_auth_token(&mut self, token: &mut [sspi::SecurityBuffer]) -> Result { self.sspi_context.lock()?.complete_auth_token(token) } fn encrypt_message( &mut self, flags: sspi::EncryptionFlags, - message: &mut [sspi::SecurityBuffer], + message: &mut [sspi::SecurityBufferRef], sequence_number: u32, ) -> Result { self.sspi_context @@ -146,7 +146,7 @@ impl Sspi for SspiHandle { fn decrypt_message( &mut self, - message: &mut [sspi::SecurityBuffer], + message: &mut [sspi::SecurityBufferRef], sequence_number: u32, ) -> Result { self.sspi_context.lock()?.decrypt_message(message, sequence_number) @@ -195,7 +195,7 @@ impl Sspi for SspiHandle { fn make_signature( &mut self, _flags: u32, - _message: &mut [sspi::SecurityBuffer], + _message: &mut [sspi::SecurityBufferRef], _sequence_number: u32, ) -> Result<()> { Err(Error::new( @@ -204,7 +204,7 @@ impl Sspi for SspiHandle { )) } - fn verify_signature(&mut self, _message: &mut [sspi::SecurityBuffer], _sequence_number: u32) -> Result { + fn verify_signature(&mut self, _message: &mut [sspi::SecurityBufferRef], _sequence_number: u32) -> Result { Err(Error::new( ErrorKind::UnsupportedFunction, "verify_signature is not supported", diff --git a/src/builders/accept_sec_context.rs b/src/builders/accept_sec_context.rs index bc80c5b5..39adb0d1 100644 --- a/src/builders/accept_sec_context.rs +++ b/src/builders/accept_sec_context.rs @@ -6,9 +6,7 @@ use super::{ ToAssign, WithContextRequirements, WithCredentialsHandle, WithOutput, WithTargetDataRepresentation, WithoutContextRequirements, WithoutCredentialsHandle, WithoutOutput, WithoutTargetDataRepresentation, }; -use crate::{ - DataRepresentation, OwnedSecurityBuffer, SecurityStatus, ServerRequestFlags, ServerResponseFlags, SspiPackage, -}; +use crate::{DataRepresentation, SecurityBuffer, SecurityStatus, ServerRequestFlags, ServerResponseFlags, SspiPackage}; pub type EmptyAcceptSecurityContext<'a, C> = AcceptSecurityContext< 'a, @@ -67,9 +65,9 @@ pub struct AcceptSecurityContext< pub credentials_handle: Option<&'a mut CredsHandle>, pub context_requirements: ServerRequestFlags, pub target_data_representation: DataRepresentation, - pub output: &'a mut [OwnedSecurityBuffer], + pub output: &'a mut [SecurityBuffer], - pub input: Option<&'a mut [OwnedSecurityBuffer]>, + pub input: Option<&'a mut [SecurityBuffer]>, } impl< @@ -187,13 +185,13 @@ impl< } } - /// Specifies a mutable reference to a buffer with [OwnedSecurityBuffer] that contains the output buffer descriptor. + /// Specifies a mutable reference to a buffer with [SecurityBuffer] that contains the output buffer descriptor. /// This buffer is sent to the client for input into additional calls to `initialize_security_context`. An output /// buffer may be generated even if the function returns `SecurityStatus::Ok`. Any buffer generated must be sent /// back to the client application. pub fn with_output( self, - output: &'a mut [OwnedSecurityBuffer], + output: &'a mut [SecurityBuffer], ) -> AcceptSecurityContext< 'a, CredsHandle, @@ -217,9 +215,9 @@ impl< } } - /// Specifies a mutable reference to an [OwnedSecurityBuffer] generated by a client call to `initialize_security_context`. + /// Specifies a mutable reference to an [SecurityBuffer] generated by a client call to `initialize_security_context`. /// The structure contains the input buffer descriptor. - pub fn with_input(self, input: &'a mut [OwnedSecurityBuffer]) -> Self { + pub fn with_input(self, input: &'a mut [SecurityBuffer]) -> Self { Self { input: Some(input), ..self diff --git a/src/builders/acq_cred_handle.rs b/src/builders/acq_cred_handle.rs index 595c2633..8770ff44 100644 --- a/src/builders/acq_cred_handle.rs +++ b/src/builders/acq_cred_handle.rs @@ -74,7 +74,7 @@ impl<'a, CredsHandle, AuthData, CredentialUseSet> AcquireCredentialsHandle<'a, C where CredentialUseSet: ToAssign, { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { phantom_cred_handle: PhantomData, phantom_cred_use_set: PhantomData, @@ -128,6 +128,16 @@ where } } +impl<'a, CredsHandle, AuthData, CredentialUseSet> Default + for AcquireCredentialsHandle<'a, CredsHandle, AuthData, CredentialUseSet> +where + CredentialUseSet: ToAssign, +{ + fn default() -> Self { + Self::new() + } +} + impl<'b, 'a: 'b, CredsHandle, AuthData> FilledAcquireCredentialsHandle<'a, CredsHandle, AuthData> { /// Transforms the builder into new one with the other `AuthData` and `CredsHandle` types. /// Useful when we need to pass the builder into the security package with other `AuthData` and `CredsHandle` types. diff --git a/src/builders/change_password.rs b/src/builders/change_password.rs index 438e1f67..e7d49ec3 100644 --- a/src/builders/change_password.rs +++ b/src/builders/change_password.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::ops::DerefMut; -use crate::{Error, ErrorKind, OwnedSecurityBuffer, Result, Secret}; +use crate::{Error, ErrorKind, Result, Secret, SecurityBuffer}; pub struct ChangePassword<'a> { pub domain_name: String, @@ -9,7 +9,7 @@ pub struct ChangePassword<'a> { pub old_password: Secret, pub new_password: Secret, pub impersonating: bool, - pub output: &'a mut [OwnedSecurityBuffer], + pub output: &'a mut [SecurityBuffer], } #[derive(Default)] @@ -19,7 +19,7 @@ struct ChangePasswordBuilderInner<'a> { old_password: Option>, new_password: Option>, impersonating: bool, - output: Option<&'a mut [OwnedSecurityBuffer]>, + output: Option<&'a mut [SecurityBuffer]>, } pub struct ChangePasswordBuilder<'a> { @@ -64,7 +64,7 @@ impl<'a> ChangePasswordBuilder<'a> { } /// Required - pub fn with_output(&self, output: &'a mut [OwnedSecurityBuffer]) -> &Self { + pub fn with_output(&self, output: &'a mut [SecurityBuffer]) -> &Self { self.inner.borrow_mut().output = Some(output); self } diff --git a/src/builders/init_sec_context.rs b/src/builders/init_sec_context.rs index 71e54389..3fea0ccc 100644 --- a/src/builders/init_sec_context.rs +++ b/src/builders/init_sec_context.rs @@ -7,7 +7,7 @@ use super::{ ToAssign, WithContextRequirements, WithCredentialsHandle, WithOutput, WithTargetDataRepresentation, WithoutContextRequirements, WithoutCredentialsHandle, WithoutOutput, WithoutTargetDataRepresentation, }; -use crate::{ClientRequestFlags, ClientResponseFlags, DataRepresentation, OwnedSecurityBuffer, SecurityStatus}; +use crate::{ClientRequestFlags, ClientResponseFlags, DataRepresentation, SecurityBuffer, SecurityStatus}; pub type EmptyInitializeSecurityContext<'a, C> = InitializeSecurityContext< 'a, @@ -67,10 +67,10 @@ pub struct InitializeSecurityContext< pub credentials_handle: Option<&'a mut CredsHandle>, pub context_requirements: ClientRequestFlags, pub target_data_representation: DataRepresentation, - pub output: &'a mut [OwnedSecurityBuffer], + pub output: &'a mut [SecurityBuffer], pub target_name: Option<&'a str>, - pub input: Option<&'a mut [OwnedSecurityBuffer]>, + pub input: Option<&'a mut [SecurityBuffer]>, } impl< @@ -247,10 +247,10 @@ impl< } } - /// Specifies a mutable reference to a buffer with [OwnedSecurityBuffer] that receives the output data. + /// Specifies a mutable reference to a buffer with [SecurityBuffer] that receives the output data. pub fn with_output( self, - output: &'a mut [OwnedSecurityBuffer], + output: &'a mut [SecurityBuffer], ) -> InitializeSecurityContext< 'a, CredsHandle, @@ -282,10 +282,10 @@ impl< } } - /// Specifies a mutable reference to a buffer with [OwnedSecurityBuffer] structures. Don't call this method on during + /// Specifies a mutable reference to a buffer with [SecurityBuffer] structures. Don't call this method on during /// the first execution of the builder. On the second execution, this parameter is a reference to the partially /// formed context returned during the first call. - pub fn with_input(self, input: &'a mut [OwnedSecurityBuffer]) -> Self { + pub fn with_input(self, input: &'a mut [SecurityBuffer]) -> Self { Self { input: Some(input), ..self diff --git a/src/credssp/mod.rs b/src/credssp/mod.rs index 7632f6c1..f4cf4d27 100644 --- a/src/credssp/mod.rs +++ b/src/credssp/mod.rs @@ -25,11 +25,11 @@ use crate::ntlm::{self, Ntlm, NtlmConfig, SIGNATURE_SIZE}; use crate::pku2u::{self, Pku2u, Pku2uConfig}; use crate::{ negotiate, AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, - CertContext, CertTrustStatus, ClientRequestFlags, ConnectionInfo, ContextNames, ContextSizes, CredentialUse, - Credentials, CredentialsBuffers, DataRepresentation, DecryptionFlags, EncryptionFlags, Error, ErrorKind, - FilledAcceptSecurityContext, FilledAcquireCredentialsHandle, FilledInitializeSecurityContext, - InitializeSecurityContextResult, Negotiate, NegotiateConfig, OwnedSecurityBuffer, PackageInfo, SecurityBuffer, - SecurityBufferType, SecurityStatus, ServerRequestFlags, Sspi, SspiEx, SspiImpl, StreamSizes, Username, + BufferType, CertContext, CertTrustStatus, ClientRequestFlags, ConnectionInfo, ContextNames, ContextSizes, + CredentialUse, Credentials, CredentialsBuffers, DataRepresentation, DecryptionFlags, EncryptionFlags, Error, + ErrorKind, FilledAcceptSecurityContext, FilledAcquireCredentialsHandle, FilledInitializeSecurityContext, + InitializeSecurityContextResult, Negotiate, NegotiateConfig, PackageInfo, SecurityBuffer, SecurityBufferRef, + SecurityStatus, ServerRequestFlags, Sspi, SspiEx, SspiImpl, StreamSizes, Username, }; pub const EARLY_USER_AUTH_RESULT_PDU_SIZE: usize = 4; @@ -276,14 +276,11 @@ impl CredSspClient { match self.state { CredSspState::NegoToken => { - let mut input_token = [OwnedSecurityBuffer::new( + let mut input_token = [SecurityBuffer::new( ts_request.nego_tokens.take().unwrap_or_default(), - SecurityBufferType::Token, - )]; - let mut output_token = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, + BufferType::Token, )]; + let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials_handle = self.credentials_handle.take(); let cred_ssp_context = self @@ -506,11 +503,8 @@ impl> CredSspServer { } CredSspState::NegoToken => { let input = ts_request.nego_tokens.take().unwrap_or_default(); - let input_token = OwnedSecurityBuffer::new(input, SecurityBufferType::Token); - let mut output_token = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let input_token = SecurityBuffer::new(input, BufferType::Token); + let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials_handle = self.credentials_handle.take(); let sspi_context = &mut self.context.as_mut().unwrap().sspi_context; @@ -805,7 +799,7 @@ impl<'a> SspiContext { impl Sspi for SspiContext { #[instrument(ret, fields(security_package = self.package_name()), skip(self))] - fn complete_auth_token(&mut self, token: &mut [OwnedSecurityBuffer]) -> crate::Result { + fn complete_auth_token(&mut self, token: &mut [SecurityBuffer]) -> crate::Result { match self { SspiContext::Ntlm(ntlm) => ntlm.complete_auth_token(token), SspiContext::Kerberos(kerberos) => kerberos.complete_auth_token(token), @@ -820,7 +814,7 @@ impl Sspi for SspiContext { fn encrypt_message( &mut self, flags: EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result { match self { @@ -836,7 +830,7 @@ impl Sspi for SspiContext { #[instrument(ret, fields(security_package = self.package_name()), skip(self))] fn decrypt_message( &mut self, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result { match self { @@ -957,7 +951,7 @@ impl Sspi for SspiContext { fn make_signature( &mut self, flags: u32, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result<()> { match self { @@ -970,7 +964,7 @@ impl Sspi for SspiContext { } } - fn verify_signature(&mut self, message: &mut [SecurityBuffer], sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, message: &mut [SecurityBufferRef], sequence_number: u32) -> crate::Result { match self { SspiContext::Ntlm(ntlm) => ntlm.verify_signature(message, sequence_number), SspiContext::Kerberos(kerberos) => kerberos.verify_signature(message, sequence_number), @@ -1191,8 +1185,8 @@ impl CredSspContext { let mut data = input.to_vec(); let mut buffers = vec![ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; let send_seq_num = self.send_seq_num; @@ -1200,10 +1194,10 @@ impl CredSspContext { self.sspi_context .encrypt_message(EncryptionFlags::empty(), &mut buffers, send_seq_num)?; - let mut output = SecurityBuffer::find_buffer(&buffers, SecurityBufferType::Token)? + let mut output = SecurityBufferRef::find_buffer(&buffers, BufferType::Token)? .data() .to_vec(); - output.extend_from_slice(SecurityBuffer::find_buffer_mut(&mut buffers, SecurityBufferType::Data)?.data()); + output.extend_from_slice(SecurityBufferRef::find_buffer_mut(&mut buffers, BufferType::Data)?.data()); self.send_seq_num += 1; @@ -1213,13 +1207,16 @@ impl CredSspContext { fn decrypt_message(&mut self, input: &[u8]) -> crate::Result> { let mut input = input.to_vec(); let (signature, data) = input.split_at_mut(SIGNATURE_SIZE); - let mut buffers = vec![SecurityBuffer::Data(data), SecurityBuffer::Token(signature)]; + let mut buffers = vec![ + SecurityBufferRef::data_buf(data), + SecurityBufferRef::token_buf(signature), + ]; let recv_seq_num = self.recv_seq_num; self.sspi_context.decrypt_message(&mut buffers, recv_seq_num)?; - let output = SecurityBuffer::buf_data(&buffers, SecurityBufferType::Data)?.to_vec(); + let output = SecurityBufferRef::buf_data(&buffers, BufferType::Data)?.to_vec(); self.recv_seq_num += 1; diff --git a/src/credssp/sspi_cred_ssp/mod.rs b/src/credssp/sspi_cred_ssp/mod.rs index 7725c9de..eda53770 100644 --- a/src/credssp/sspi_cred_ssp/mod.rs +++ b/src/credssp/sspi_cred_ssp/mod.rs @@ -16,12 +16,12 @@ use super::{CredSspContext, CredSspMode, EndpointType, SspiContext, TsRequest}; use crate::credssp::sspi_cred_ssp::tls_connection::{DecryptionResult, DecryptionResultBuffers}; use crate::generator::{GeneratorChangePassword, GeneratorInitSecurityContext, YieldPointLocal}; use crate::{ - builders, negotiate, AcquireCredentialsHandleResult, CertContext, CertEncodingType, CertTrustErrorStatus, - CertTrustInfoStatus, CertTrustStatus, ClientRequestFlags, ClientResponseFlags, ConnectionInfo, ContextNames, - ContextSizes, CredentialUse, Credentials, CredentialsBuffers, DataRepresentation, DecryptionFlags, EncryptionFlags, - Error, ErrorKind, InitializeSecurityContextResult, OwnedSecurityBuffer, PackageCapabilities, PackageInfo, Result, - SecurityBuffer, SecurityBufferType, SecurityPackageType, SecurityStatus, Sspi, SspiEx, SspiImpl, StreamSizes, - PACKAGE_ID_NONE, + builders, negotiate, AcquireCredentialsHandleResult, BufferType, CertContext, CertEncodingType, + CertTrustErrorStatus, CertTrustInfoStatus, CertTrustStatus, ClientRequestFlags, ClientResponseFlags, + ConnectionInfo, ContextNames, ContextSizes, CredentialUse, Credentials, CredentialsBuffers, DataRepresentation, + DecryptionFlags, EncryptionFlags, Error, ErrorKind, InitializeSecurityContextResult, PackageCapabilities, + PackageInfo, Result, SecurityBuffer, SecurityBufferRef, SecurityPackageType, SecurityStatus, Sspi, SspiEx, + SspiImpl, StreamSizes, PACKAGE_ID_NONE, }; pub const PKG_NAME: &str = "CREDSSP"; @@ -106,8 +106,8 @@ impl SspiCredSsp { Ok(raw_public_key) } - fn decrypt_and_decode_ts_request(&mut self, input: &mut [OwnedSecurityBuffer]) -> Result { - let encrypted_ts_request = OwnedSecurityBuffer::find_buffer_mut(input, SecurityBufferType::Token)?; + fn decrypt_and_decode_ts_request(&mut self, input: &mut [SecurityBuffer]) -> Result { + let encrypted_ts_request = SecurityBuffer::find_buffer_mut(input, BufferType::Token)?; let DecryptionResult::Success(DecryptionResultBuffers { header: _, decrypted: raw_ts_request, @@ -140,7 +140,7 @@ impl SspiCredSsp { impl Sspi for SspiCredSsp { #[instrument(level = "debug", ret, fields(state = ?self.state), skip_all)] - fn complete_auth_token(&mut self, _token: &mut [OwnedSecurityBuffer]) -> Result { + fn complete_auth_token(&mut self, _token: &mut [SecurityBuffer]) -> Result { Ok(SecurityStatus::Ok) } @@ -148,7 +148,7 @@ impl Sspi for SspiCredSsp { fn encrypt_message( &mut self, _flags: EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], _sequence_number: u32, ) -> Result { // CredSsp decrypt_message function just calls corresponding function from the Schannel @@ -161,28 +161,28 @@ impl Sspi for SspiCredSsp { )); } - let plain_message = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + let plain_message = SecurityBufferRef::find_buffer_mut(message, BufferType::Data)?; let encrypted_data = self.tls_connection_mut()?.encrypt_tls(plain_message.data())?; let encrypted_data = encrypted_data.as_slice(); - let stream_header_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::StreamHeader)?; + let stream_header_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::StreamHeader)?; let (stream_header_data, encrypted_data) = encrypted_data.split_at(stream_header_buffer.buf_len().min(encrypted_data.len())); stream_header_buffer.write_data(stream_header_data)?; - let data_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + let data_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::Data)?; let (data_data, encrypted_data) = encrypted_data.split_at(data_buffer.buf_len().min(encrypted_data.len())); data_buffer.write_data(data_data)?; - let stream_trailer_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::StreamTrailer)?; + let stream_trailer_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::StreamTrailer)?; stream_trailer_buffer.write_data(encrypted_data)?; Ok(SecurityStatus::Ok) } #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self, _sequence_number))] - fn decrypt_message(&mut self, message: &mut [SecurityBuffer], _sequence_number: u32) -> Result { + fn decrypt_message(&mut self, message: &mut [SecurityBufferRef], _sequence_number: u32) -> Result { // CredSsp decrypt_message function just calls corresponding function from the Schannel // MSDN: message must contain four buffers // https://learn.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--schannel @@ -195,7 +195,7 @@ impl Sspi for SspiCredSsp { match self .tls_connection_mut()? - .decrypt_tls(SecurityBuffer::take_buf_data_mut(message, SecurityBufferType::Data)?)? + .decrypt_tls(SecurityBufferRef::take_buf_data_mut(message, BufferType::Data)?)? { DecryptionResult::Success(DecryptionResultBuffers { header, @@ -203,14 +203,14 @@ impl Sspi for SspiCredSsp { extra, }) => { // buffers order is important. MSTSC won't work with another buffers order. - message[0] = SecurityBuffer::StreamHeader(header); - message[1] = SecurityBuffer::Data(decrypted); + message[0] = SecurityBufferRef::stream_header_buf(header); + message[1] = SecurityBufferRef::data_buf(decrypted); // https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer // SECBUFFER_STREAM_TRAILER: It is not usually of interest to callers. // // So, we can just set an empty buffer here. - message[2] = SecurityBuffer::StreamTrailer(&mut []); - message[3] = SecurityBuffer::Extra(extra); + message[2] = SecurityBufferRef::stream_trailer_buf(&mut []); + message[3] = SecurityBufferRef::extra_buf(extra); Ok(DecryptionFlags::empty()) } @@ -221,8 +221,8 @@ impl Sspi for SspiCredSsp { // * https://stackoverflow.com/a/6832633/9123725 // * https://stackoverflow.com/a/65101172 - message[0] = SecurityBuffer::Missing(needed_bytes_amount); - message[1] = SecurityBuffer::Missing(needed_bytes_amount); + message[0] = SecurityBufferRef::missing_buf(needed_bytes_amount); + message[1] = SecurityBufferRef::missing_buf(needed_bytes_amount); Err(Error::new(ErrorKind::IncompleteMessage, "Got incomplete TLS message")) } @@ -297,7 +297,7 @@ impl Sspi for SspiCredSsp { fn make_signature( &mut self, _flags: u32, - _message: &mut [SecurityBuffer], + _message: &mut [SecurityBufferRef], _sequence_number: u32, ) -> crate::Result<()> { Err(Error::new( @@ -307,7 +307,7 @@ impl Sspi for SspiCredSsp { } #[instrument(level = "debug", ret, fields(state = ?self.state), skip_all)] - fn verify_signature(&mut self, _message: &mut [SecurityBuffer], _sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, _message: &mut [SecurityBufferRef], _sequence_number: u32) -> crate::Result { Err(Error::new( ErrorKind::UnsupportedFunction, "verify_signature is not supported", @@ -420,7 +420,7 @@ impl SspiCredSsp { let input_token = builder .input .as_mut() - .and_then(|buffers| OwnedSecurityBuffer::find_buffer_mut(buffers, SecurityBufferType::Token).ok()) + .and_then(|buffers| SecurityBuffer::find_buffer_mut(buffers, BufferType::Token).ok()) .map(|sec_buffer| sec_buffer.buffer.as_slice()) .unwrap_or_default(); let (bytes_written, tls_buffer) = self.tls_connection_mut()?.process_tls_packets(input_token)?; @@ -434,7 +434,7 @@ impl SspiCredSsp { return self.initialize_security_context_impl(yield_point, builder).await; } - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer = tls_buffer; SecurityStatus::ContinueNeeded @@ -449,15 +449,12 @@ impl SspiCredSsp { self.cred_ssp_context.check_peer_version(ts_request.version)?; - let mut input_token = vec![OwnedSecurityBuffer::new( + let mut input_token = vec![SecurityBuffer::new( ts_request.nego_tokens.take().unwrap_or_default(), - SecurityBufferType::Token, + BufferType::Token, )]; - let mut output_token = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut inner_builder = self .cred_ssp_context @@ -511,7 +508,7 @@ impl SspiCredSsp { let mut encoded_ts_request = Vec::new(); ts_request.encode_ts_request(&mut encoded_ts_request)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer = self.tls_connection_mut()?.encrypt_tls(&encoded_ts_request)?; SecurityStatus::ContinueNeeded @@ -557,7 +554,7 @@ impl SspiCredSsp { let mut encoded_ts_request = Vec::new(); ts_request.encode_ts_request(&mut encoded_ts_request)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer = self.tls_connection_mut()?.encrypt_tls(&encoded_ts_request)?; self.state = CredSspState::Final; diff --git a/src/kerberos/client/generators.rs b/src/kerberos/client/generators.rs index 8ba71447..8ff9fbdd 100644 --- a/src/kerberos/client/generators.rs +++ b/src/kerberos/client/generators.rs @@ -19,24 +19,27 @@ use picky_asn1_x509::oids; use picky_krb::constants::gss_api::{ ACCEPT_COMPLETE, ACCEPT_INCOMPLETE, AP_REQ_TOKEN_ID, AUTHENTICATOR_CHECKSUM_TYPE, TGT_REQ_TOKEN_ID, }; -use picky_krb::constants::key_usages::{AP_REQ_AUTHENTICATOR, KRB_PRIV_ENC_PART, TGS_REQ_PA_DATA_AP_REQ_AUTHENTICATOR}; +use picky_krb::constants::key_usages::{ + AP_REP_ENC, AP_REQ_AUTHENTICATOR, KRB_PRIV_ENC_PART, TGS_REQ_PA_DATA_AP_REQ_AUTHENTICATOR, +}; use picky_krb::constants::types::{ - AD_AUTH_DATA_AP_OPTION_TYPE, AP_REQ_MSG_TYPE, AS_REQ_MSG_TYPE, KERB_AP_OPTIONS_CBT, KRB_PRIV, NET_BIOS_ADDR_TYPE, - NT_ENTERPRISE, NT_PRINCIPAL, NT_SRV_INST, PA_ENC_TIMESTAMP, PA_ENC_TIMESTAMP_KEY_USAGE, PA_PAC_OPTIONS_TYPE, - PA_PAC_REQUEST_TYPE, PA_TGS_REQ_TYPE, TGS_REQ_MSG_TYPE, TGT_REQ_MSG_TYPE, + AD_AUTH_DATA_AP_OPTION_TYPE, AP_REP_MSG_TYPE, AP_REQ_MSG_TYPE, AS_REQ_MSG_TYPE, KERB_AP_OPTIONS_CBT, KRB_PRIV, + NET_BIOS_ADDR_TYPE, NT_ENTERPRISE, NT_PRINCIPAL, NT_SRV_INST, PA_ENC_TIMESTAMP, PA_ENC_TIMESTAMP_KEY_USAGE, + PA_PAC_OPTIONS_TYPE, PA_PAC_REQUEST_TYPE, PA_TGS_REQ_TYPE, TGS_REQ_MSG_TYPE, TGT_REQ_MSG_TYPE, }; use picky_krb::crypto::CipherSuite; use picky_krb::data_types::{ - ApOptions, Authenticator, AuthenticatorInner, AuthorizationData, AuthorizationDataInner, Checksum, EncKrbPrivPart, - EncKrbPrivPartInner, EncryptedData, EncryptionKey, HostAddress, KerbPaPacRequest, KerberosFlags, - KerberosStringAsn1, KerberosTime, PaData, PaEncTsEnc, PaPacOptions, PrincipalName, Realm, Ticket, + ApOptions, Authenticator, AuthenticatorInner, AuthorizationData, AuthorizationDataInner, Checksum, EncApRepPart, + EncApRepPartInner, EncKrbPrivPart, EncKrbPrivPartInner, EncryptedData, EncryptionKey, HostAddress, + KerbPaPacRequest, KerberosFlags, KerberosStringAsn1, KerberosTime, PaData, PaEncTsEnc, PaPacOptions, PrincipalName, + Realm, Ticket, }; use picky_krb::gss_api::{ ApplicationTag0, GssApiNegInit, KrbMessage, MechType, MechTypeList, NegTokenInit, NegTokenTarg, NegTokenTarg1, }; use picky_krb::messages::{ - ApMessage, ApReq, ApReqInner, AsReq, KdcRep, KdcReq, KdcReqBody, KrbPriv, KrbPrivInner, KrbPrivMessage, TgsReq, - TgtReq, + ApMessage, ApRep, ApRepInner, ApReq, ApReqInner, AsReq, KdcRep, KdcReq, KdcReqBody, KrbPriv, KrbPrivInner, + KrbPrivMessage, TgsReq, TgtReq, }; use rand::rngs::OsRng; use rand::Rng; @@ -66,9 +69,12 @@ const DEFAULT_TGS_REQ_OPTIONS: [u8; 4] = [0x00, 0x81, 0x00, 0x08]; const DEFAULT_PA_PAC_OPTIONS: [u8; 4] = [0x40, 0x00, 0x00, 0x00]; /// [Authenticator Checksum](https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1) +/// +/// **Important**: the last 4 bytes are [Checksum Flags Field](https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1). +/// This value should be set separately based on provided [CLientRequestFlags] or [GssFlags]. pub const AUTHENTICATOR_DEFAULT_CHECKSUM: [u8; 24] = [ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, ]; // [MS-KILE] 3.3.5.6.1 Client Principal Lookup @@ -150,10 +156,7 @@ pub fn generate_pa_datas_for_as_req(options: &GenerateAsPaDataOptions) -> Result let mut pa_datas = if *with_pre_auth { let current_date = OffsetDateTime::now_utc(); - let mut microseconds = current_date.microsecond(); - if microseconds > MAX_MICROSECONDS_IN_SECOND { - microseconds = MAX_MICROSECONDS_IN_SECOND; - } + let microseconds = current_date.microsecond().min(MAX_MICROSECONDS_IN_SECOND); let timestamp = PaEncTsEnc { patimestamp: ExplicitContextTag0::from(KerberosTime::from(GeneralizedTime::from(current_date))), @@ -421,9 +424,6 @@ impl From<[u8; 24]> for ChecksumValues { } impl ChecksumValues { - // FIXME: This code is unused because of the comment in the Kerberos implementation. - // Currently, we have an authentication error when using flags in the checksum. - #[allow(dead_code)] pub(crate) fn set_flags(&mut self, flags: GssFlags) { let flag_bits = flags.bits(); let flag_bytes = flag_bits.to_le_bytes(); @@ -436,17 +436,38 @@ impl ChecksumValues { } bitflags::bitflags! { - // https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1, this is crucial for LDAPS + /// The checksum "Flags" field is used to convey service options or extension negotiation information. + /// More info: + /// * https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1 #[derive(Debug,Clone,Copy)] pub(crate) struct GssFlags: u32 { - const GSS_C_DELEG_FLAG = 0b00000001; - const GSS_C_MUTUAL_FLAG = 0b00000010; - const GSS_C_REPLAY_FLAG = 0b00000100; - const GSS_C_SEQUENCE_FLAG = 0b00001000; - const GSS_C_CONF_FLAG = 0b00010000; - const GSS_C_INTEG_FLAG = 0b00100000; - const GSS_C_ANON_FLAG = 0b01000000; - const GSS_C_PROT_READY_FLAG = 0b10000000; + // [Checksum Flags Field](https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1). + const GSS_C_DELEG_FLAG = 1; + const GSS_C_MUTUAL_FLAG = 2; + const GSS_C_REPLAY_FLAG = 4; + const GSS_C_SEQUENCE_FLAG = 8; + const GSS_C_CONF_FLAG = 16; + const GSS_C_INTEG_FLAG = 32; + + const GSS_C_ANON_FLAG = 64; + const GSS_C_PROT_READY_FLAG = 128; + const GSS_C_TRANS_FLAG = 256; + const GSS_C_DELEG_POLICY_FLAG = 32768; + + // Additional GSS flags from MS-KILE specification: + // * [3.2.5.2 Authenticator Checksum Flags](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/387806fc-ed78-445e-afd8-c5639fe4a90a) + + // [Mechanism Specific Changes](https://www.rfc-editor.org/rfc/rfc4757.html#section-7.1): + // Setting this flag indicates that the client wants to be informed of extended error information. In + // particular, Windows 2000 status codes may be returned in the data field of a Kerberos error message. + // This allows the client to understand a server failure more precisely. + const GSS_C_EXTENDED_ERROR_FLAG = 0x4000; + // This flag allows the client to indicate to the server that it should only allow the server application to identify + // the client by name and ID, but not to impersonate the client. + const GSS_C_IDENTIFY_FLAG = 0x2000; + // This flag was added for use with Microsoft's implementation of Distributed Computing Environment Remote Procedure + // Call (DCE RPC), which initially expected three legs of authentication. + const GSS_C_DCE_STYLE = 0x1000; } } @@ -485,6 +506,10 @@ impl From for GssFlags { flags &= !GssFlags::GSS_C_INTEG_FLAG; } + if value.contains(ClientRequestFlags::USE_DCE_STYLE) { + flags |= GssFlags::GSS_C_DCE_STYLE; + } + flags } } @@ -584,6 +609,36 @@ pub fn generate_authenticator(options: GenerateAuthenticatorOptions) -> Result, enc_params: &EncryptionParams) -> Result { + let current_date = OffsetDateTime::now_utc(); + let microseconds = current_date.microsecond().min(MAX_MICROSECONDS_IN_SECOND); + + let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE); + + let enc_ap_rep_part = EncApRepPart::from(EncApRepPartInner { + ctime: ExplicitContextTag0::from(KerberosTime::from(GeneralizedTime::from(current_date))), + cusec: ExplicitContextTag1::from(IntegerAsn1::from(microseconds.to_be_bytes().to_vec())), + subkey: Optional::from(None), + seq_number: Optional::from(Some(ExplicitContextTag3::from(IntegerAsn1::from(seq_number)))), + }); + + let cipher = encryption_type.cipher(); + + let encoded_enc_ap_rep_part = picky_asn1_der::to_vec(&enc_ap_rep_part)?; + let encrypted_enc_ap_rep_part = cipher.encrypt(session_key, AP_REP_ENC, &encoded_enc_ap_rep_part)?; + + Ok(ApRep::from(ApRepInner { + pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])), + msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![AP_REP_MSG_TYPE])), + enc_part: ExplicitContextTag2::from(EncryptedData { + etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])), + kvno: Optional::from(None), + cipher: ExplicitContextTag2::from(OctetStringAsn1::from(encrypted_enc_ap_rep_part)), + }), + })) +} + pub fn generate_tgs_ap_req( ticket: Ticket, session_key: &[u8], @@ -820,7 +875,7 @@ mod tests { let username = ""; let domain = "TBT.com"; - let realm = get_client_principal_realm_impl(&vec![Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); + let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); assert_eq!(realm, "TBT.COM"); } @@ -830,7 +885,7 @@ mod tests { let username = "user@tbt.com"; let domain = ""; - let realm = get_client_principal_realm_impl(&vec![Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); + let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); assert_eq!(realm, "TBT.COM"); } @@ -840,7 +895,7 @@ mod tests { let username = ""; let domain = "s.tbt.com"; - let realm = get_client_principal_realm_impl(&vec![Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); + let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain); assert_eq!(realm, "TBT.COM"); } diff --git a/src/kerberos/encryption_params.rs b/src/kerberos/encryption_params.rs index e7d3caa7..cbffe338 100644 --- a/src/kerberos/encryption_params.rs +++ b/src/kerberos/encryption_params.rs @@ -9,6 +9,15 @@ pub struct EncryptionParams { pub sub_session_key: Option>, pub sspi_encrypt_key_usage: i32, pub sspi_decrypt_key_usage: i32, + /// EC field of the Kerberos Wrap token. + /// + /// Related documentation: + /// * [RFC 4121: EC Field](https://www.rfc-editor.org/rfc/rfc4121#section-4.2.3). + /// * [3.4.5.4.1 Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550). + /// + /// This value is different during RDP and RPC authentication. + /// We negotiate it during the authentication process. + pub ec: u16, } impl EncryptionParams { @@ -19,6 +28,7 @@ impl EncryptionParams { sub_session_key: None, sspi_encrypt_key_usage: INITIATOR_SEAL, sspi_decrypt_key_usage: ACCEPTOR_SEAL, + ec: 0, } } @@ -29,6 +39,7 @@ impl EncryptionParams { sub_session_key: None, sspi_encrypt_key_usage: ACCEPTOR_SEAL, sspi_decrypt_key_usage: INITIATOR_SEAL, + ec: 0, } } diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index c4d3e013..b0872968 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -17,7 +17,7 @@ use picky_asn1::wrapper::{ExplicitContextTag0, ExplicitContextTag1, OctetStringA use picky_asn1_x509::oids; use picky_krb::constants::gss_api::AUTHENTICATOR_CHECKSUM_TYPE; use picky_krb::constants::key_usages::ACCEPTOR_SIGN; -use picky_krb::crypto::CipherSuite; +use picky_krb::crypto::{CipherSuite, DecryptWithoutChecksum, EncryptWithoutChecksum}; use picky_krb::data_types::{KerberosStringAsn1, KrbResult, ResultExt}; use picky_krb::gss_api::{NegTokenTarg1, WrapToken}; use picky_krb::messages::{ApReq, AsRep, KdcProxyMessage, KdcReqBody, KrbPrivMessage, TgsRep}; @@ -38,17 +38,20 @@ use self::client::generators::{ }; use self::config::KerberosConfig; use self::pa_datas::AsReqPaDataOptions; -use self::server::extractors::extract_tgt_ticket; +use self::server::extractors::extract_tgt_ticket_with_oid; use self::utils::{serialize_message, unwrap_hostname}; use super::channel_bindings::ChannelBindings; use crate::builders::ChangePassword; use crate::generator::{GeneratorChangePassword, GeneratorInitSecurityContext, NetworkRequest, YieldPointLocal}; use crate::kerberos::client::extractors::{extract_salt_from_krb_error, extract_status_code_from_krb_priv_response}; use crate::kerberos::client::generators::{ - generate_authenticator, generate_final_neg_token_targ, get_mech_list, GenerateTgsReqOptions, GssFlags, + generate_ap_rep, generate_authenticator, generate_final_neg_token_targ, get_mech_list, GenerateTgsReqOptions, + GssFlags, }; use crate::kerberos::pa_datas::AsRepSessionKeyExtractor; -use crate::kerberos::server::extractors::{extract_ap_rep_from_neg_token_targ, extract_sub_session_key_from_ap_rep}; +use crate::kerberos::server::extractors::{ + extract_ap_rep_from_neg_token_targ, extract_seq_number_from_ap_rep, extract_sub_session_key_from_ap_rep, +}; use crate::kerberos::utils::{generate_initiator_raw, validate_mic_token}; use crate::network_client::NetworkProtocol; use crate::pk_init::{self, DhParameters}; @@ -59,9 +62,9 @@ use crate::utils::{ }; use crate::{ check_if_empty, detect_kdc_url, AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, - ClientRequestFlags, ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, Credentials, - CredentialsBuffers, DecryptionFlags, Error, ErrorKind, InitializeSecurityContextResult, OwnedSecurityBuffer, - PackageCapabilities, PackageInfo, Result, SecurityBuffer, SecurityBufferType, SecurityPackageType, SecurityStatus, + BufferType, ClientRequestFlags, ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, Credentials, + CredentialsBuffers, DecryptionFlags, Error, ErrorKind, InitializeSecurityContextResult, PackageCapabilities, + PackageInfo, Result, SecurityBuffer, SecurityBufferFlags, SecurityBufferRef, SecurityPackageType, SecurityStatus, ServerResponseFlags, Sspi, SspiEx, SspiImpl, PACKAGE_ID_NONE, }; @@ -74,17 +77,25 @@ pub const CHANGE_PASSWORD_SERVICE_NAME: &str = "changepw"; // pub const SSPI_KDC_URL_ENV: &str = "SSPI_KDC_URL"; pub const DEFAULT_ENCRYPTION_TYPE: CipherSuite = CipherSuite::Aes256CtsHmacSha196; -/// [MS-KILE](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-KILE/%5bMS-KILE%5d.pdf) +/// [3.4.5.4.1 Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550) /// The RRC field is 12 if no encryption is requested or 28 if encryption is requested pub const RRC: u16 = 28; // wrap token header len pub const MAX_SIGNATURE: usize = 16; -// minimal len to fit encrypted public key in wrap token +/// Required `TOKEN` buffer length during data encryption (`encrypt_message` method call). +/// +/// **Note**: Actual security trailer len is `SECURITY_TRAILER` + `EC`. The `EC` field is negotiated +// during the authentication process. pub const SECURITY_TRAILER: usize = 60; /// [Kerberos Change Password and Set Password Protocols](https://datatracker.ietf.org/doc/html/rfc3244#section-2) /// "The service accepts requests on UDP port 464 and TCP port 464 as well." const KPASSWD_PORT: u16 = 464; +/// [3.4.5.4.1 Kerberos Binding of GSS_WrapEx()](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550) +/// +/// The extra count (EC) must not be zero. The sender should set extra count (EC) to 1 block - 16 bytes. +const EC: u16 = 16; + pub static PACKAGE_INFO: LazyLock = LazyLock::new(|| PackageInfo { capabilities: PackageCapabilities::empty(), rpc_id: PACKAGE_ID_NONE, @@ -98,6 +109,7 @@ pub enum KerberosState { Negotiate, Preauthentication, ApExchange, + ApRepDce, PubKeyAuth, Credentials, Final, @@ -114,6 +126,7 @@ pub struct Kerberos { kdc_url: Option, channel_bindings: Option, dh_parameters: Option, + krb5_user_to_user: bool, } impl Kerberos { @@ -130,6 +143,7 @@ impl Kerberos { kdc_url, channel_bindings: None, dh_parameters: None, + krb5_user_to_user: false, }) } @@ -146,6 +160,7 @@ impl Kerberos { kdc_url, channel_bindings: None, dh_parameters: None, + krb5_user_to_user: false, }) } @@ -231,6 +246,26 @@ impl Kerberos { Err(Error::new(ErrorKind::NoAuthenticatingAuthority, "No KDC server found")) } + fn prepare_final_neg_token( + &mut self, + builder: &mut crate::builders::FilledInitializeSecurityContext<'_, ::CredentialsHandle>, + ) -> Result<()> { + let neg_token_targ = generate_final_neg_token_targ(Some(generate_initiator_raw( + picky_asn1_der::to_vec(&get_mech_list())?, + self.seq_number as u64, + self.encryption_params + .sub_session_key + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InternalError, "kerberos sub-session key is not set"))?, + )?)); + + let encoded_final_neg_token_targ = picky_asn1_der::to_vec(&neg_token_targ)?; + + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; + output_token.buffer.write_all(&encoded_final_neg_token_targ)?; + Ok(()) + } + pub async fn as_exchange( &mut self, yield_point: &mut YieldPointLocal, @@ -286,7 +321,7 @@ impl Kerberos { impl Sspi for Kerberos { #[instrument(level = "debug", ret, fields(state = ?self.state), skip_all)] - fn complete_auth_token(&mut self, _token: &mut [OwnedSecurityBuffer]) -> Result { + fn complete_auth_token(&mut self, _token: &mut [SecurityBuffer]) -> Result { Ok(SecurityStatus::Ok) } @@ -294,14 +329,16 @@ impl Sspi for Kerberos { fn encrypt_message( &mut self, _flags: crate::EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], _sequence_number: u32, ) -> Result { trace!(encryption_params = ?self.encryption_params); // checks if the Token buffer present - let _ = SecurityBuffer::find_buffer(message, SecurityBufferType::Token)?; - let data_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + let _ = SecurityBufferRef::find_buffer(message, BufferType::Token)?; + // Find `Data` buffers but skip `Data` buffers with the `READONLY_WITH_CHECKSUM`/`READONLY` flag. + let data_to_encrypt = + SecurityBufferRef::buffers_of_type_and_flags(message, BufferType::Data, SecurityBufferFlags::NONE); let cipher = self .encryption_params @@ -317,28 +354,77 @@ impl Sspi for Kerberos { let key_usage = self.encryption_params.sspi_encrypt_key_usage; let mut wrap_token = WrapToken::with_seq_number(seq_number as u64); + wrap_token.ec = self.encryption_params.ec; - let mut payload = data_buffer.data().to_vec(); + let mut payload = data_to_encrypt.fold(Vec::new(), |mut acc, buffer| { + acc.extend_from_slice(buffer.data()); + acc + }); + // Add filler bytes to payload vector. + // More info: + // * [4.2.3. EC Field](https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.3): + // In Wrap tokens with confidentiality, the EC field SHALL be used to encode the number of octets in the filler. + // * [4.2.4. Encryption and Checksum Operations](https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.4): + // payload = plaintext-data | filler | "header" + payload.extend_from_slice(&vec![0; usize::from(self.encryption_params.ec)]); payload.extend_from_slice(&wrap_token.header()); - let mut checksum = cipher.encrypt(key, key_usage, &payload)?; - checksum.rotate_right(RRC.into()); + let EncryptWithoutChecksum { + mut encrypted, + confounder, + ki: _, + } = cipher.encrypt_no_checksum(key, key_usage, &payload)?; + + // Find `Data` buffers (including `Data` buffers with the `READONLY_WITH_CHECKSUM` flag). + let mut data_to_sign = + SecurityBufferRef::buffers_of_type(message, BufferType::Data).fold(confounder, |mut acc, buffer| { + acc.extend_from_slice(buffer.data()); + acc + }); + // Add filler bytes to payload vector. + // More info: + // * [4.2.3. EC Field](https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.3): + // In Wrap tokens with confidentiality, the EC field SHALL be used to encode the number of octets in the filler. + // * [4.2.4. Encryption and Checksum Operations](https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.4): + // payload = plaintext-data | filler | "header" + data_to_sign.extend_from_slice(&vec![0; usize::from(self.encryption_params.ec)]); + data_to_sign.extend_from_slice(&wrap_token.header()); + + let checksum = cipher.encryption_checksum(key, key_usage, &data_to_sign)?; + + encrypted.extend_from_slice(&checksum); + + // [3.4.5.4.1 Kerberos Binding of GSS_WrapEx()](learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550): + // The trailing metadata H1 is rotated by RRC+EC bytes, which is different from RRC alone. + encrypted.rotate_right(usize::from(RRC + self.encryption_params.ec)); wrap_token.set_rrc(RRC); - wrap_token.set_checksum(checksum); + wrap_token.set_checksum(encrypted); - let mut raw_wrap_token = Vec::with_capacity(92); + let mut raw_wrap_token = Vec::with_capacity(wrap_token.checksum.len() + WrapToken::header_len()); wrap_token.encode(&mut raw_wrap_token)?; match self.state { KerberosState::PubKeyAuth | KerberosState::Credentials | KerberosState::Final => { - if raw_wrap_token.len() < SECURITY_TRAILER { - return Err(Error::new(ErrorKind::EncryptFailure, "Cannot encrypt the data")); - } + let security_trailer_len = self.query_context_sizes()?.security_trailer.try_into()?; + + let (token, data) = if raw_wrap_token.len() < security_trailer_len { + (raw_wrap_token.as_slice(), &[] as &[u8]) + } else { + raw_wrap_token.split_at(security_trailer_len) + }; + + let data_buffer = SecurityBufferRef::buffers_of_type_and_flags_mut( + message, + BufferType::Data, + SecurityBufferFlags::NONE, + ) + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "no buffer was provided with type Data"))?; - let (token, data) = raw_wrap_token.split_at(SECURITY_TRAILER); data_buffer.write_data(data)?; - let token_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Token)?; + + let token_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::Token)?; token_buffer.write_data(token)?; } _ => { @@ -353,7 +439,7 @@ impl Sspi for Kerberos { } #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self, _sequence_number))] - fn decrypt_message(&mut self, message: &mut [SecurityBuffer], _sequence_number: u32) -> Result { + fn decrypt_message(&mut self, message: &mut [SecurityBufferRef], _sequence_number: u32) -> Result { trace!(encryption_params = ?self.encryption_params); let encrypted = extract_encrypted_data(message)?; @@ -369,15 +455,55 @@ impl Sspi for Kerberos { let key_usage = self.encryption_params.sspi_decrypt_key_usage; - let mut wrap_token = WrapToken::decode(encrypted.as_slice())?; + let wrap_token = WrapToken::decode(encrypted.as_slice())?; - wrap_token.checksum.rotate_left(RRC.into()); + let mut checksum = wrap_token.checksum; + // [3.4.5.4.1 Kerberos Binding of GSS_WrapEx()](learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/e94b3acd-8415-4d0d-9786-749d0c39d550): + // The trailing metadata H1 is rotated by RRC+EC bytes, which is different from RRC alone. + checksum.rotate_left((RRC + wrap_token.ec).into()); - let mut decrypted = cipher.decrypt(key, key_usage, &wrap_token.checksum)?; - // remove wrap token header - decrypted.truncate(decrypted.len() - WrapToken::header_len()); + let DecryptWithoutChecksum { + plaintext: decrypted, + confounder, + checksum, + ki: _, + } = cipher.decrypt_no_checksum(key, key_usage, &checksum)?; - save_decrypted_data(&decrypted, message)?; + if decrypted.len() < usize::from(wrap_token.ec) + WrapToken::header_len() { + return Err(Error::new(ErrorKind::DecryptFailure, "decrypted data is too short")); + } + + let plaintext_len = decrypted.len() - usize::from(wrap_token.ec) - WrapToken::header_len(); + + let plaintext = &decrypted[0..plaintext_len]; + let wrap_token_header = &decrypted[plaintext_len..]; + + // Find `Data` buffers (including `Data` buffers with the `READONLY_WITH_CHECKSUM` flag). + let mut data_to_sign = + SecurityBufferRef::buffers_of_type(message, BufferType::Data).fold(confounder, |mut acc, buffer| { + if buffer + .buffer_flags() + .contains(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM) + { + acc.extend_from_slice(buffer.data()); + } else { + // The `Data` buffer contains encrypted data, but the checksum was calculated over the decrypted data. + // So, we replace encrypted data with decrypted one. + // Note: our implementation expect maximum one plain `DATA` buffer but multiple `DATA` buffers + // with `SECBUFFER_READONLY_WITH_CHECKSUM` flag are allowed. + acc.extend_from_slice(plaintext); + } + acc + }); + data_to_sign.extend_from_slice(wrap_token_header); + + let calculated_checksum = cipher.encryption_checksum(key, key_usage, &data_to_sign)?; + + if calculated_checksum != checksum { + return Err(picky_krb::crypto::KerberosCryptoError::IntegrityCheck.into()); + } + + save_decrypted_data(plaintext, message)?; match self.state { KerberosState::PubKeyAuth => { @@ -394,12 +520,22 @@ impl Sspi for Kerberos { #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] fn query_context_sizes(&mut self) -> Result { - Ok(ContextSizes { - max_token: PACKAGE_INFO.max_token_len, - max_signature: MAX_SIGNATURE as u32, - block: 0, - security_trailer: SECURITY_TRAILER as u32, - }) + // We prevent users from calling `query_context_sizes` on a non-established security context + // because it can lead to invalid values being returned. + match self.state { + KerberosState::PubKeyAuth | KerberosState::Credentials | KerberosState::Final => Ok(ContextSizes { + max_token: PACKAGE_INFO.max_token_len, + max_signature: MAX_SIGNATURE as u32, + block: 0, + security_trailer: SECURITY_TRAILER as u32 + u32::from(self.encryption_params.ec), + }), + _ => { + return Err(Error::new( + ErrorKind::OutOfSequence, + "Kerberos context is not established", + )) + } + } } #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] @@ -445,7 +581,7 @@ impl Sspi for Kerberos { fn make_signature( &mut self, _flags: u32, - _message: &mut [SecurityBuffer], + _message: &mut [SecurityBufferRef], _sequence_number: u32, ) -> crate::Result<()> { Err(Error::new( @@ -454,7 +590,7 @@ impl Sspi for Kerberos { )) } - fn verify_signature(&mut self, _message: &mut [SecurityBuffer], _sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, _message: &mut [SecurityBufferRef], _sequence_number: u32) -> crate::Result { Err(Error::new( ErrorKind::UnsupportedFunction, "verify_signature is not supported. use decrypt_message to verify signatures instead", @@ -502,7 +638,7 @@ impl SspiImpl for Kerberos { let status = match &self.state { KerberosState::ApExchange => { - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; let _ap_req: ApReq = picky_asn1_der::from_bytes(&input_token.buffer) .map_err(|e| Error::new(ErrorKind::DecryptFailure, format!("{:?}", e)))?; @@ -687,7 +823,7 @@ impl<'a> Kerberos { let encoded_neg_token_init = picky_asn1_der::to_vec(&generate_neg_token_init(&username, service_name)?)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer.write_all(&encoded_neg_token_init)?; self.state = KerberosState::Preauthentication; @@ -700,16 +836,21 @@ impl<'a> Kerberos { .as_ref() .ok_or_else(|| crate::Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; - if let Ok(sec_buffer) = OwnedSecurityBuffer::find_buffer( - builder.input.as_ref().unwrap(), - SecurityBufferType::ChannelBindings, - ) { + if let Ok(sec_buffer) = + SecurityBuffer::find_buffer(builder.input.as_ref().unwrap(), BufferType::ChannelBindings) + { self.channel_bindings = Some(ChannelBindings::from_bytes(&sec_buffer.buffer)?); } - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; - let tgt_ticket = extract_tgt_ticket(&input_token.buffer)?; + let (tgt_ticket, mech_id) = + if let Some((tbt_ticket, mech_oid)) = extract_tgt_ticket_with_oid(&input_token.buffer)? { + (Some(tbt_ticket), mech_oid.0) + } else { + (None, oids::krb5()) + }; + self.krb5_user_to_user = mech_id == oids::krb5_user_to_user(); let credentials = builder .credentials_handle @@ -857,8 +998,6 @@ impl<'a> Kerberos { self.encryption_params.session_key = Some(session_key_2); - let seq_num = self.next_seq_number(); - let enc_type = self .encryption_params .encryption_type @@ -892,7 +1031,10 @@ impl<'a> Kerberos { let authenticator_options = GenerateAuthenticatorOptions { kdc_rep: &tgs_rep.0, - seq_num: Some(seq_num), + // The AP_REQ Authenticator sequence number should be the same as `seq_num` in the first Kerberos Wrap token generated + // by the `encrypt_message` method. So, we set the next sequence number but do not increment the counter, + // which will be incremented on each `encrypt_message` method call. + seq_num: Some(self.seq_number + 1), sub_key: Some(EncKey { key_type: enc_type.clone(), key_value: authenticator_sub_key, @@ -910,27 +1052,34 @@ impl<'a> Kerberos { let encoded_auth = picky_asn1_der::to_vec(&authenticator)?; info!(encoded_ap_req_authenticator = ?encoded_auth); - // FIXME: properly negotiate mech id - Windows always does KRB5 U2U - let mech_id = oids::krb5_user_to_user(); - let mut context_requirements = builder.context_requirements; - if mech_id == oids::krb5_user_to_user() { - // KRB5 U2U always needs the use-session-key flag + if self.krb5_user_to_user && !context_requirements.contains(ClientRequestFlags::USE_SESSION_KEY) { + warn!("KRB5 U2U has been negotiated (selected by the server) but the USE_SESSION_KEY flag is not set. Forcibly turning it on..."); context_requirements.set(ClientRequestFlags::USE_SESSION_KEY, true); } let ap_req = generate_ap_req( tgs_rep.0.ticket.0, - self.encryption_params.session_key.as_ref().unwrap(), + self.encryption_params + .session_key + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InternalError, "session key is not set"))?, &authenticator, &self.encryption_params, context_requirements.into(), )?; - let encoded_neg_ap_req = picky_asn1_der::to_vec(&generate_neg_ap_req(ap_req, mech_id)?)?; + let encoded_neg_ap_req = if !builder.context_requirements.contains(ClientRequestFlags::USE_DCE_STYLE) { + // Wrap in a NegToken. + picky_asn1_der::to_vec(&generate_neg_ap_req(ap_req, mech_id)?)? + } else { + // Do not wrap if the `USE_DCE_STYLE` flag is set. + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/190ab8de-dc42-49cf-bf1b-ea5705b7a087 + picky_asn1_der::to_vec(&ap_req)? + }; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer.write_all(&encoded_neg_ap_req)?; self.state = KerberosState::ApExchange; @@ -942,39 +1091,69 @@ impl<'a> Kerberos { .input .as_ref() .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; - let neg_token_targ = { - let mut d = picky_asn1_der::Deserializer::new_from_bytes(&input_token.buffer); - let neg_token_targ: NegTokenTarg1 = KrbResult::deserialize(&mut d)??; - neg_token_targ - }; + if builder.context_requirements.contains(ClientRequestFlags::USE_DCE_STYLE) { + // The `EC` field depends on the authentication type. For example, during RDP auth + // it is equal to 0, but during RPC auth it is equal to EC. + self.encryption_params.ec = EC; - let ap_rep = extract_ap_rep_from_neg_token_targ(&neg_token_targ)?; + use picky_krb::messages::ApRep; - let sub_session_key = extract_sub_session_key_from_ap_rep( - &ap_rep, - self.encryption_params.session_key.as_ref().unwrap(), - &self.encryption_params, - )?; + let ap_rep: ApRep = picky_asn1_der::from_bytes(&input_token.buffer)?; - self.encryption_params.sub_session_key = Some(sub_session_key); + let session_key = self + .encryption_params + .session_key + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InternalError, "session key is not set"))?; + let sub_session_key = + extract_sub_session_key_from_ap_rep(&ap_rep, session_key, &self.encryption_params)?; + let seq_number = extract_seq_number_from_ap_rep(&ap_rep, session_key, &self.encryption_params)?; - if let Some(ref token) = neg_token_targ.0.mech_list_mic.0 { - validate_mic_token(&token.0 .0, ACCEPTOR_SIGN, &self.encryption_params)?; - } + trace!(?sub_session_key, "DCE AP_REP sub-session key"); - let neg_token_targ = generate_final_neg_token_targ(Some(generate_initiator_raw( - picky_asn1_der::to_vec(&get_mech_list())?, - self.seq_number as u64, - self.encryption_params.sub_session_key.as_ref().unwrap(), - )?)); + self.encryption_params.sub_session_key = Some(sub_session_key); - let encoded_final_neg_token_targ = picky_asn1_der::to_vec(&neg_token_targ)?; + let ap_rep = generate_ap_rep(session_key, seq_number, &self.encryption_params)?; + let ap_rep = picky_asn1_der::to_vec(&ap_rep)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; - output_token.buffer.write_all(&encoded_final_neg_token_targ)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; + output_token.buffer.write_all(&ap_rep)?; + + self.state = KerberosState::ApRepDce; + + SecurityStatus::ContinueNeeded + } else { + let neg_token_targ = { + let mut d = picky_asn1_der::Deserializer::new_from_bytes(&input_token.buffer); + let neg_token_targ: NegTokenTarg1 = KrbResult::deserialize(&mut d)??; + neg_token_targ + }; + let ap_rep = extract_ap_rep_from_neg_token_targ(&neg_token_targ)?; + + let session_key = self + .encryption_params + .session_key + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InternalError, "session key is not set"))?; + let sub_session_key = + extract_sub_session_key_from_ap_rep(&ap_rep, session_key, &self.encryption_params)?; + + self.encryption_params.sub_session_key = Some(sub_session_key); + + if let Some(ref token) = neg_token_targ.0.mech_list_mic.0 { + validate_mic_token(&token.0 .0, ACCEPTOR_SIGN, &self.encryption_params)?; + } + + self.prepare_final_neg_token(builder)?; + self.state = KerberosState::PubKeyAuth; + SecurityStatus::Ok + } + } + KerberosState::ApRepDce => { + self.prepare_final_neg_token(builder)?; self.state = KerberosState::PubKeyAuth; SecurityStatus::Ok @@ -1037,12 +1216,14 @@ pub mod test_data { sub_session_key: Some(SUB_SESSION_KEY.to_vec()), sspi_encrypt_key_usage: INITIATOR_SEAL, sspi_decrypt_key_usage: ACCEPTOR_SEAL, + ec: 0, }, seq_number: 1234, realm: None, kdc_url: None, channel_bindings: None, dh_parameters: None, + krb5_user_to_user: false, } } @@ -1060,19 +1241,25 @@ pub mod test_data { sub_session_key: Some(SUB_SESSION_KEY.to_vec()), sspi_encrypt_key_usage: ACCEPTOR_SEAL, sspi_decrypt_key_usage: INITIATOR_SEAL, + ec: 0, }, seq_number: 0, realm: None, kdc_url: None, channel_bindings: None, dh_parameters: None, + krb5_user_to_user: false, } } } #[cfg(test)] mod tests { - use crate::{EncryptionFlags, SecurityBuffer, Sspi}; + use picky_krb::constants::key_usages::{ACCEPTOR_SEAL, INITIATOR_SEAL}; + use picky_krb::crypto::CipherSuite; + + use super::{EncryptionParams, KerberosConfig, KerberosState}; + use crate::{EncryptionFlags, Kerberos, SecurityBufferFlags, SecurityBufferRef, Sspi}; #[test] fn stream_buffer_decryption() { @@ -1086,8 +1273,8 @@ mod tests { let mut token = [0; 1024]; let mut data = plain_message.to_vec(); let mut message = [ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; kerberos_server @@ -1097,10 +1284,158 @@ mod tests { let mut buffer = message[0].data().to_vec(); buffer.extend_from_slice(message[1].data()); - let mut message = [SecurityBuffer::Stream(&mut buffer), SecurityBuffer::Data(&mut [])]; + let mut message = [ + SecurityBufferRef::stream_buf(&mut buffer), + SecurityBufferRef::data_buf(&mut []), + ]; kerberos_client.decrypt_message(&mut message, 0).unwrap(); assert_eq!(message[1].data(), plain_message); } + + #[test] + fn secbuffer_readonly_with_checksum() { + // All values in this test (session keys, sequence number, encrypted and decrypted data) were extracted + // from the original Windows Kerberos implementation calls. + // We keep this test to guarantee full compatibility with the original Kerberos. + + let session_key = [ + 114, 67, 55, 26, 76, 210, 61, 0, 164, 44, 11, 133, 108, 220, 234, 145, 61, 144, 123, 45, 54, 175, 164, 168, + 99, 18, 99, 240, 242, 157, 95, 134, + ]; + let sub_session_key = [ + 91, 11, 188, 227, 10, 91, 180, 246, 64, 129, 251, 200, 118, 82, 109, 65, 241, 177, 109, 32, 124, 39, 127, + 171, 222, 132, 199, 199, 126, 110, 3, 166, + ]; + + let mut kerberos_server = Kerberos { + state: KerberosState::Final, + config: KerberosConfig { + kdc_url: None, + client_computer_name: None, + }, + auth_identity: None, + encryption_params: EncryptionParams { + encryption_type: Some(CipherSuite::Aes256CtsHmacSha196), + session_key: Some(session_key.to_vec()), + sub_session_key: Some(sub_session_key.to_vec()), + sspi_encrypt_key_usage: ACCEPTOR_SEAL, + sspi_decrypt_key_usage: INITIATOR_SEAL, + ec: 16, + }, + seq_number: 681238048, + realm: None, + kdc_url: None, + channel_bindings: None, + dh_parameters: None, + krb5_user_to_user: false, + }; + + // RPC header + let header = [ + 5, 0, 0, 3, 16, 0, 0, 0, 60, 1, 76, 0, 1, 0, 0, 0, 208, 0, 0, 0, 0, 0, 0, 0, + ]; + // RPC security trailer header + let trailer = [16, 6, 8, 0, 0, 0, 0, 0]; + // Encrypted data in RPC Request + let enc_data = [ + 41, 85, 192, 239, 104, 188, 180, 100, 229, 73, 83, 199, 77, 83, 79, 17, 163, 206, 241, 29, 90, 28, 89, 203, + 83, 176, 160, 252, 197, 221, 76, 113, 185, 141, 16, 200, 149, 55, 32, 96, 29, 49, 57, 124, 181, 147, 110, + 198, 125, 116, 150, 47, 35, 224, 117, 25, 10, 229, 201, 222, 153, 101, 131, 93, 204, 32, 9, 145, 186, 45, + 224, 160, 131, 23, 236, 111, 88, 48, 54, 4, 118, 114, 129, 119, 130, 164, 178, 4, 110, 74, 37, 1, 215, 177, + 16, 204, 238, 83, 255, 40, 240, 32, 209, 213, 90, 19, 126, 58, 34, 33, 72, 15, 206, 96, 67, 15, 169, 248, + 176, 9, 173, 196, 159, 239, 250, 120, 206, 52, 53, 229, 230, 66, 64, 109, 100, 21, 77, 193, 3, 40, 183, + 209, 177, 152, 165, 171, 108, 151, 112, 134, 53, 165, 128, 145, 147, 167, 5, 72, 35, 101, 42, 183, 67, 101, + 48, 255, 84, 208, 112, 199, 154, 62, 185, 87, 204, 228, 45, 30, 184, 47, 129, 145, 245, 168, 118, 174, 48, + 98, 174, 167, 208, 0, 113, 246, 219, 29, 192, 171, 97, 117, 115, 120, 115, 45, 44, 113, 62, 39, + ]; + // Unencrypted data in RPC Request + let plaintext = [ + 108, 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 128, 84, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 20, + 0, 0, 0, 2, 0, 64, 0, 2, 0, 0, 0, 0, 0, 36, 0, 3, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 223, 243, + 137, 88, 86, 131, 83, 53, 105, 218, 109, 33, 80, 4, 0, 0, 0, 0, 20, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 138, 227, 19, 113, 2, 244, 54, + 113, 2, 64, 40, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, + 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ]; + // RPC Request security trailer data. Basically, it's a GSS API Wrap token + let security_trailer_data = [ + 5, 4, 6, 255, 0, 16, 0, 28, 0, 0, 0, 0, 40, 154, 222, 33, 170, 177, 218, 93, 176, 5, 210, 44, 38, 242, 179, + 168, 249, 202, 242, 199, 63, 162, 33, 40, 106, 186, 187, 28, 11, 229, 207, 219, 66, 86, 243, 16, 158, 100, + 133, 159, 87, 153, 196, 14, 251, 169, 164, 12, 18, 85, 182, 56, 72, 30, 137, 238, 50, 122, 73, 95, 109, + 194, 60, 120, + ]; + + let mut header_data = header.to_vec(); + let mut encrypted_data = enc_data.to_vec(); + let mut trailer_data = trailer.to_vec(); + let mut token_data = security_trailer_data.to_vec(); + let mut message = vec![ + SecurityBufferRef::data_buf(&mut header_data) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::data_buf(&mut encrypted_data), + SecurityBufferRef::data_buf(&mut trailer_data) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::token_buf(&mut token_data), + ]; + + kerberos_server.decrypt_message(&mut message, 0).unwrap(); + + assert_eq!(header[..], message[0].data()[..]); + assert_eq!(plaintext[..], message[1].data()[..]); + assert_eq!(trailer[..], message[2].data()[..]); + } + + #[test] + fn rpc_request_encryption() { + let mut kerberos_server = super::test_data::fake_server(); + let mut kerberos_client = super::test_data::fake_client(); + + // RPC header + let header = [ + 5, 0, 0, 3, 16, 0, 0, 0, 60, 1, 76, 0, 1, 0, 0, 0, 208, 0, 0, 0, 0, 0, 0, 0, + ]; + // Unencrypted data in RPC Request + let plaintext = [ + 108, 0, 0, 0, 0, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 128, 84, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 20, + 0, 0, 0, 2, 0, 64, 0, 2, 0, 0, 0, 0, 0, 36, 0, 3, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 223, 243, + 137, 88, 86, 131, 83, 53, 105, 218, 109, 33, 80, 4, 0, 0, 0, 0, 20, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 138, 227, 19, 113, 2, 244, 54, + 113, 2, 64, 40, 0, 96, 89, 120, 185, 79, 82, 223, 17, 139, 109, 131, 220, 222, 215, 32, 133, 1, 0, 0, 0, + 51, 5, 113, 113, 186, 190, 55, 73, 131, 25, 181, 219, 239, 156, 204, 54, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ]; + // RPC security trailer header + let trailer = [16, 6, 8, 0, 0, 0, 0, 0]; + + let mut header_data = header.to_vec(); + let mut data = plaintext.to_vec(); + let mut trailer_data = trailer.to_vec(); + let mut token_data = vec![0; 76]; + let mut message = vec![ + SecurityBufferRef::data_buf(&mut header_data) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::data_buf(&mut data), + SecurityBufferRef::data_buf(&mut trailer_data) + .with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM), + SecurityBufferRef::token_buf(&mut token_data), + ]; + + kerberos_client + .encrypt_message(EncryptionFlags::empty(), &mut message, 0) + .unwrap(); + + assert_eq!(header[..], message[0].data()[..]); + assert_eq!(trailer[..], message[2].data()[..]); + + kerberos_server.decrypt_message(&mut message, 0).unwrap(); + + assert_eq!(header[..], message[0].data()[..]); + assert_eq!(message[1].data(), plaintext); + assert_eq!(trailer[..], message[2].data()[..]); + } } diff --git a/src/kerberos/server/extractors.rs b/src/kerberos/server/extractors.rs index 3044f800..51b5780e 100644 --- a/src/kerberos/server/extractors.rs +++ b/src/kerberos/server/extractors.rs @@ -1,5 +1,6 @@ use std::io::Read; +use picky_asn1::wrapper::ObjectIdentifierAsn1; use picky_asn1_der::application_tag::ApplicationTag; use picky_asn1_der::Asn1RawDer; use picky_krb::constants::key_usages::AP_REP_ENC; @@ -16,7 +17,7 @@ pub fn extract_ap_rep_from_neg_token_targ(token: &NegTokenTarg1) -> Result Result Result> { + let cipher = enc_params + .encryption_type + .as_ref() + .unwrap_or(&DEFAULT_ENCRYPTION_TYPE) + .cipher(); + + let res = cipher + .decrypt(session_key, AP_REP_ENC, &ap_rep.0.enc_part.cipher.0 .0) + .map_err(|err| { + Error::new( + ErrorKind::DecryptFailure, + format!("cannot decrypt ap_rep.enc_part: {:?}", err), + ) + })?; + + let ap_rep_enc_part: EncApRepPart = picky_asn1_der::from_bytes(&res)?; + + Ok(ap_rep_enc_part + .0 + .seq_number + .0 + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "missing sequence number in ap_rep"))? + .0 + .0) +} + #[instrument(level = "trace", ret)] pub fn extract_sub_session_key_from_ap_rep( ap_rep: &ApRep, @@ -46,7 +79,7 @@ pub fn extract_sub_session_key_from_ap_rep( .map_err(|err| { Error::new( ErrorKind::DecryptFailure, - format!("Cannot decrypt ap_rep.enc_part: {:?}", err), + format!("cannot decrypt ap_rep.enc_part: {:?}", err), ) })?; @@ -56,21 +89,32 @@ pub fn extract_sub_session_key_from_ap_rep( .0 .subkey .0 - .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Missing sub-key in ap_req"))? + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "missing sub-key in ap_req"))? .0 .key_value .0 .0) } +/// Extracts TGT Ticket from encoded [NegTokenTarg1]. +/// +/// Returned OID means the selected authentication mechanism by the target server. More info: +/// * [3.2.1. Syntax](https://datatracker.ietf.org/doc/html/rfc2478#section-3.2.1): `responseToken` field; +/// +/// We use this oid to choose between the regular Kerberos 5 and Kerberos 5 User-to-User authentication. #[instrument(level = "trace", ret)] -pub fn extract_tgt_ticket(data: &[u8]) -> Result> { +pub fn extract_tgt_ticket_with_oid(data: &[u8]) -> Result> { + if data.is_empty() { + return Ok(None); + } + let neg_token_targ: NegTokenTarg1 = picky_asn1_der::from_bytes(data)?; if let Some(resp_token) = neg_token_targ.0.response_token.0.as_ref().map(|ticket| &ticket.0 .0) { let mut c = resp_token.as_slice(); - let _oid: ApplicationTag = picky_asn1_der::from_reader(&mut c)?; + let oid: ApplicationTag = picky_asn1_der::from_reader(&mut c)?; + let oid: ObjectIdentifierAsn1 = picky_asn1_der::from_bytes(&oid.0 .0)?; let mut t = [0, 0]; @@ -78,7 +122,7 @@ pub fn extract_tgt_ticket(data: &[u8]) -> Result> { let tgt_rep: TgtRep = picky_asn1_der::from_reader(&mut c)?; - Ok(Some(tgt_rep.ticket.0)) + Ok(Some((tgt_rep.ticket.0, oid))) } else { Ok(None) } diff --git a/src/kerberos/utils.rs b/src/kerberos/utils.rs index 3855cc5c..4a0d73a2 100644 --- a/src/kerberos/utils.rs +++ b/src/kerberos/utils.rs @@ -68,6 +68,6 @@ pub fn unwrap_hostname(hostname: Option<&str>) -> Result { if let Some(hostname) = hostname { Ok(hostname.into()) } else { - Err(Error::new(ErrorKind::InvalidParameter, "The hostname is not provided")) + Err(Error::new(ErrorKind::InvalidParameter, "the hostname is not provided")) } } diff --git a/src/lib.rs b/src/lib.rs index a027cc29..30c4fae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,9 +30,9 @@ //! .execute(&mut ntlm) //! .expect("AcquireCredentialsHandle resulted in error"); //! -//! let mut output = vec![sspi::OwnedSecurityBuffer::new( +//! let mut output = vec![sspi::SecurityBuffer::new( //! Vec::new(), -//! sspi::SecurityBufferType::Token, +//! sspi::BufferType::Token, //! )]; //! //! let mut builder = ntlm.initialize_security_context() @@ -93,7 +93,7 @@ use picky_asn1_der::Asn1DerError; use picky_asn1_x509::Certificate; use picky_krb::gss_api::GssApiMessageError; use picky_krb::messages::KrbError; -pub use security_buffer::SecurityBuffer; +pub use security_buffer::SecurityBufferRef; use utils::map_keb_error_code_to_sspi_error; pub use utils::string_to_utf16; @@ -155,7 +155,7 @@ pub fn query_security_package_info(package_type: SecurityPackageType) -> Result< SecurityPackageType::CredSsp => Ok(sspi_cred_ssp::PACKAGE_INFO.clone()), SecurityPackageType::Other(s) => Err(Error::new( ErrorKind::Unknown, - format!("Queried info about unknown package: {:?}", s), + format!("queried info about unknown package: {:?}", s), )), } } @@ -290,7 +290,7 @@ where /// /// let mut credentials_handle = acq_cred_result.credentials_handle; /// - /// let mut output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// #[allow(unused_variables)] /// let mut builder = ntlm.initialize_security_context() @@ -352,7 +352,7 @@ where /// .execute(&mut client_ntlm) /// .unwrap(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let mut builder = client_ntlm.initialize_security_context() /// .with_credentials_handle(&mut client_acq_cred_result.credentials_handle) @@ -369,7 +369,7 @@ where /// .unwrap(); /// /// let mut ntlm = sspi::Ntlm::new(); - /// let mut output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let mut server_acq_cred_result = ntlm /// .acquire_credentials_handle() @@ -404,7 +404,7 @@ where /// /// # Parameters /// - /// * `token`: `SecurityBuffer` that contains the buffer descriptor for the entire message + /// * `token`: `SecurityBufferRef` that contains the buffer descriptor for the entire message /// /// # Returns /// @@ -422,8 +422,8 @@ where /// let mut client_ntlm = sspi::Ntlm::new(); /// let mut ntlm = sspi::Ntlm::new(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; - /// let mut output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; + /// let mut output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let identity = sspi::AuthIdentity { /// username: Username::parse("user").unwrap(), @@ -488,7 +488,7 @@ where /// # MSDN /// /// * [CompleteAuthToken function](https://docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-completeauthtoken) - fn complete_auth_token(&mut self, token: &mut [OwnedSecurityBuffer]) -> Result; + fn complete_auth_token(&mut self, token: &mut [SecurityBuffer]) -> Result; /// Encrypts a message to provide privacy. The function allows the application to choose among cryptographic algorithms supported by the chosen mechanism. /// Some packages do not have messages to be encrypted or decrypted but rather provide an integrity hash that can be checked. @@ -496,7 +496,7 @@ where /// # Parameters /// /// * `flags`: package-specific flags that indicate the quality of protection. A security package can use this parameter to enable the selection of cryptographic algorithms - /// * `message`: on input, the structure accepts one or more `SecurityBuffer` structures that can be of type `SecurityBufferType::Data`. + /// * `message`: on input, the structure accepts one or more `SecurityBufferRef` structures that can be of type `BufferType::Data`. /// That buffer contains the message to be encrypted. The message is encrypted in place, overwriting the original contents of the structure. /// * `sequence_number`: the sequence number that the transport application assigned to the message. If the transport application does not maintain sequence numbers, this parameter must be zero /// @@ -511,8 +511,8 @@ where /// let mut client_ntlm = sspi::Ntlm::new(); /// let mut ntlm = sspi::Ntlm::new(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; - /// let mut server_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; + /// let mut server_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let identity = sspi::AuthIdentity { /// username: Username::parse("user").unwrap(), @@ -575,8 +575,8 @@ where /// let mut token = [0; 128]; /// let mut data = "This is a message".as_bytes().to_vec(); /// let mut msg_buffer = vec![ - /// sspi::SecurityBuffer::Token(token.as_mut_slice()), - /// sspi::SecurityBuffer::Data(data.as_mut_slice()), + /// sspi::SecurityBufferRef::token_buf(token.as_mut_slice()), + /// sspi::SecurityBufferRef::data_buf(data.as_mut_slice()), /// ]; /// /// println!("Unencrypted: {:?}", msg_buffer[1].data()); @@ -599,7 +599,7 @@ where fn encrypt_message( &mut self, flags: EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> Result; @@ -608,8 +608,8 @@ where /// /// # Parameters /// * `flags`: package-specific flags that indicate the quality of protection. A security package can use this parameter to enable the selection of cryptographic algorithms - /// * `message`: On input, the structure references one or more `SecurityBuffer` structures of `type SecurityBufferType::Data` that contain the message to be signed, - /// and a `SecurityBuffer` of type `SecurityBufferType::Token` that receives the signature. + /// * `message`: On input, the structure references one or more `SecurityBufferRef` structures of type `BufferType::Data` that contain the message to be signed, + /// and a `SecurityBufferRef` of type `BufferType::Token` that receives the signature. /// * `sequence_number`: the sequence number that the transport application assigned to the message. If the transport application does not maintain sequence numbers, this parameter must be zero /// /// # Returns @@ -627,8 +627,8 @@ where /// let mut client_ntlm = sspi::Ntlm::new(); /// let mut ntlm = sspi::Ntlm::new(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; - /// let mut server_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; + /// let mut server_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let identity = sspi::AuthIdentity { /// username: Username::parse("user").unwrap(), @@ -691,8 +691,8 @@ where /// let mut token = [0; 128]; /// let mut data = "This is a message to be signed".as_bytes().to_vec(); /// let mut msg_buffer = vec![ - /// sspi::SecurityBuffer::Token(token.as_mut_slice()), - /// sspi::SecurityBuffer::Data(data.as_mut_slice()), + /// sspi::SecurityBufferRef::token_buf(token.as_mut_slice()), + /// sspi::SecurityBufferRef::data_buf(data.as_mut_slice()), /// ]; /// /// println!("Input data: {:?}", msg_buffer[1].data()); @@ -706,14 +706,18 @@ where /// /// # MSDN /// * [MakeSignature function](https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-makesignature) - fn make_signature(&mut self, flags: u32, message: &mut [SecurityBuffer], sequence_number: u32) - -> crate::Result<()>; + fn make_signature( + &mut self, + flags: u32, + message: &mut [SecurityBufferRef], + sequence_number: u32, + ) -> crate::Result<()>; /// Verifies that a message signed by using the `make_signature` function was received in the correct sequence and has not been modified. /// /// # Parameters - /// * `message`: On input, the structure references one or more `SecurityBuffer` structures of `type SecurityBufferType::Data` that contain the message to be verified, - /// and a `SecurityBuffer` of type `SecurityBufferType::Token` that contains the signature. + /// * `message`: On input, the structure references one or more `SecurityBufferRef` structures of type `BufferType::Data` that contain the message to be verified, + /// and a `SecurityBufferRef` of type `BufferType::Token` that contains the signature. /// * `sequence_number`: the sequence number that the transport application assigned to the message. If the transport application does not maintain sequence numbers, this parameter must be zero /// /// # Returns @@ -731,8 +735,8 @@ where /// let mut ntlm = sspi::Ntlm::new(); /// let mut server_ntlm = sspi::Ntlm::new(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; - /// let mut server_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; + /// let mut server_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let identity = sspi::AuthIdentity { /// username: Username::parse("user").unwrap(), @@ -795,8 +799,8 @@ where /// let mut token = [0; 128]; /// let mut data = "This is a message".as_bytes().to_vec(); /// let mut msg = [ - /// sspi::SecurityBuffer::Token(token.as_mut_slice()), - /// sspi::SecurityBuffer::Data(data.as_mut_slice()), + /// sspi::SecurityBufferRef::token_buf(token.as_mut_slice()), + /// sspi::SecurityBufferRef::data_buf(data.as_mut_slice()), /// ]; /// /// let _result = server_ntlm @@ -805,8 +809,8 @@ where /// let [mut token, mut data] = msg; /// /// let mut msg_buffer = vec![ - /// sspi::SecurityBuffer::Token(token.take_data()), - /// sspi::SecurityBuffer::Data(data.take_data()), + /// sspi::SecurityBufferRef::token_buf(token.take_data()), + /// sspi::SecurityBufferRef::data_buf(data.take_data()), /// ]; /// /// #[allow(unused_variables)] @@ -819,14 +823,14 @@ where /// /// # MSDN /// * [VerifySignature function](https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-verifysignature) - fn verify_signature(&mut self, message: &mut [SecurityBuffer], sequence_number: u32) -> crate::Result; + fn verify_signature(&mut self, message: &mut [SecurityBufferRef], sequence_number: u32) -> crate::Result; /// Decrypts a message. Some packages do not encrypt and decrypt messages but rather perform and check an integrity hash. /// /// # Parameters /// - /// * `message`: on input, the structure references one or more `SecurityBuffer` structures. - /// At least one of these must be of type `SecurityBufferType::Data`. + /// * `message`: on input, the structure references one or more `SecurityBufferRef` structures. + /// At least one of these must be of type `BufferType::Data`. /// That buffer contains the encrypted message. The encrypted message is decrypted in place, overwriting the original contents of its buffer /// * `sequence_number`: the sequence number that the transport application assigned to the message. If the transport application does not maintain sequence numbers, this parameter must be zero /// @@ -846,8 +850,8 @@ where /// let mut ntlm = sspi::Ntlm::new(); /// let mut server_ntlm = sspi::Ntlm::new(); /// - /// let mut client_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; - /// let mut server_output_buffer = vec![sspi::OwnedSecurityBuffer::new(Vec::new(), sspi::SecurityBufferType::Token)]; + /// let mut client_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; + /// let mut server_output_buffer = vec![sspi::SecurityBuffer::new(Vec::new(), sspi::BufferType::Token)]; /// /// let identity = sspi::AuthIdentity { /// username: Username::parse("user").unwrap(), @@ -910,8 +914,8 @@ where /// let mut token = [0; 128]; /// let mut data = "This is a message".as_bytes().to_vec(); /// let mut msg = [ - /// sspi::SecurityBuffer::Token(token.as_mut_slice()), - /// sspi::SecurityBuffer::Data(data.as_mut_slice()), + /// sspi::SecurityBufferRef::token_buf(token.as_mut_slice()), + /// sspi::SecurityBufferRef::data_buf(data.as_mut_slice()), /// ]; /// /// let _result = server_ntlm @@ -920,8 +924,8 @@ where /// let [mut token, mut data] = msg; /// /// let mut msg_buffer = vec![ - /// sspi::SecurityBuffer::Token(token.take_data()), - /// sspi::SecurityBuffer::Data(data.take_data()), + /// sspi::SecurityBufferRef::token_buf(token.take_data()), + /// sspi::SecurityBufferRef::data_buf(data.take_data()), /// ]; /// /// #[allow(unused_variables)] @@ -935,7 +939,7 @@ where /// # MSDN /// /// * [DecryptMessage function](https://docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-decryptmessage) - fn decrypt_message(&mut self, message: &mut [SecurityBuffer], sequence_number: u32) -> Result; + fn decrypt_message(&mut self, message: &mut [SecurityBufferRef], sequence_number: u32) -> Result; /// Retrieves information about the bounds of sizes of authentication information of the current security principal. /// @@ -1496,14 +1500,18 @@ pub enum DataRepresentation { /// /// * [SecBuffer structure](https://docs.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer) #[derive(Clone)] -pub struct OwnedSecurityBuffer { +pub struct SecurityBuffer { pub buffer: Vec, pub buffer_type: SecurityBufferType, } -impl fmt::Debug for OwnedSecurityBuffer { +impl fmt::Debug for SecurityBuffer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SecurityBuffer {{ buffer_type: {:?}, buffer: 0x", self.buffer_type)?; + write!( + f, + "SecurityBufferRef {{ buffer_type: {:?}, buffer: 0x", + self.buffer_type + )?; self.buffer.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?; write!(f, " }}")?; @@ -1511,34 +1519,37 @@ impl fmt::Debug for OwnedSecurityBuffer { } } -impl OwnedSecurityBuffer { - pub fn new(buffer: Vec, buffer_type: SecurityBufferType) -> Self { - Self { buffer, buffer_type } +impl SecurityBuffer { + pub fn new(buffer: Vec, buffer_type: BufferType) -> Self { + Self { + buffer, + buffer_type: SecurityBufferType { + buffer_type, + buffer_flags: SecurityBufferFlags::NONE, + }, + } } - pub fn find_buffer( - buffers: &[OwnedSecurityBuffer], - buffer_type: SecurityBufferType, - ) -> Result<&OwnedSecurityBuffer> { - buffers.iter().find(|b| b.buffer_type == buffer_type).ok_or_else(|| { - Error::new( - ErrorKind::InvalidToken, - format!("No buffer was provided with type {:?}", buffer_type), - ) - }) + pub fn find_buffer(buffers: &[SecurityBuffer], buffer_type: BufferType) -> Result<&SecurityBuffer> { + buffers + .iter() + .find(|b| b.buffer_type.buffer_type == buffer_type) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidToken, + format!("no buffer was provided with type {:?}", buffer_type), + ) + }) } - pub fn find_buffer_mut( - buffers: &mut [OwnedSecurityBuffer], - buffer_type: SecurityBufferType, - ) -> Result<&mut OwnedSecurityBuffer> { + pub fn find_buffer_mut(buffers: &mut [SecurityBuffer], buffer_type: BufferType) -> Result<&mut SecurityBuffer> { buffers .iter_mut() - .find(|b| b.buffer_type == buffer_type) + .find(|b| b.buffer_type.buffer_type == buffer_type) .ok_or_else(|| { Error::new( ErrorKind::InvalidToken, - format!("No buffer was provided with type {:?}", buffer_type), + format!("no buffer was provided with type {:?}", buffer_type), ) }) } @@ -1551,7 +1562,7 @@ impl OwnedSecurityBuffer { /// * [SecBuffer structure (BufferType parameter)](https://docs.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer) #[repr(u32)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, FromPrimitive, ToPrimitive)] -pub enum SecurityBufferType { +pub enum BufferType { #[default] Empty = 0, /// The buffer contains common data. The security package can read and write this data, for example, to encrypt some or all of it. @@ -1593,6 +1604,83 @@ pub enum SecurityBufferType { ReadOnlyWithChecksum = 0x1000_0000, } +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] + /// Security buffer flags. + /// + /// [`SecBuffer` structure (sspi.h)](https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer). + pub struct SecurityBufferFlags: u32 { + /// There is no flags for the buffer. + const NONE = 0x0; + /// The buffer is read-only with no checksum. This flag is intended for sending header information to the security package for + /// computing the checksum. The package can read this buffer, but cannot modify it. + const SECBUFFER_READONLY = 0x80000000; + /// The buffer is read-only with a checksum. + const SECBUFFER_READONLY_WITH_CHECKSUM = 0x10000000; + } +} + +/// Security buffer type. +/// +/// Contains the actual security buffer type and its flags. +#[derive(Clone, Copy, Eq, PartialEq, Default)] +pub struct SecurityBufferType { + /// Security buffer type. + pub buffer_type: BufferType, + /// Security buffer flags. + pub buffer_flags: SecurityBufferFlags, +} + +impl SecurityBufferType { + /// The buffer contains a bitmask for a `SECBUFFER_READONLY_WITH_CHECKSUM` buffer. + /// + /// [`SecBuffer` structure (sspi.h)](https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-secbuffer) + pub const SECBUFFER_ATTRMASK: u32 = 0xf0000000; +} + +impl TryFrom for SecurityBufferType { + type Error = Error; + + fn try_from(value: u32) -> Result { + use num_traits::cast::FromPrimitive; + + let buffer_type = value & !Self::SECBUFFER_ATTRMASK; + let buffer_type = BufferType::from_u32(buffer_type).ok_or_else(|| { + Error::new( + ErrorKind::InternalError, + format!("u32({}) to UnflaggedSecurityBuffer conversion error", buffer_type), + ) + })?; + + let buffer_flags = value & Self::SECBUFFER_ATTRMASK; + let buffer_flags = SecurityBufferFlags::from_bits(buffer_flags).ok_or_else(|| { + Error::new( + ErrorKind::InternalError, + format!("invalid SecurityBufferFlags: {}", buffer_flags), + ) + })?; + + Ok(Self { + buffer_type, + buffer_flags, + }) + } +} + +impl From for u32 { + fn from(value: SecurityBufferType) -> u32 { + use num_traits::cast::ToPrimitive; + + value.buffer_type.to_u32().unwrap() | value.buffer_flags.bits() + } +} + +impl fmt::Debug for SecurityBufferType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("({:?}, {:?})", self.buffer_type, self.buffer_flags)) + } +} + /// A flag that indicates how the credentials are used. /// /// # MSDN @@ -2064,7 +2152,7 @@ impl From for Error { fn from(value: rsa::Error) -> Self { Error::new( ErrorKind::InternalError, - format!("Error: an unexpected RsaError happened: {}", value), + format!("an unexpected RsaError happened: {}", value), ) } } @@ -2168,13 +2256,13 @@ impl From for Error { impl From for Error { fn from(err: rand::Error) -> Self { - Self::new(ErrorKind::InternalError, format!("Rand error: {:?}", err)) + Self::new(ErrorKind::InternalError, format!("rand error: {:?}", err)) } } impl From for Error { fn from(err: std::str::Utf8Error) -> Self { - Self::new(ErrorKind::InternalError, format!("UTF-8 error: {:?}", err)) + Self::new(ErrorKind::InternalError, err) } } @@ -2201,13 +2289,13 @@ impl From for io::Error { impl From for Error { fn from(_: std::num::TryFromIntError) -> Self { - Self::new(ErrorKind::InternalError, "Integer conversion error") + Self::new(ErrorKind::InternalError, "integer conversion error") } } impl From> for Error { fn from(_: std::sync::PoisonError) -> Self { - Self::new(ErrorKind::InternalError, "Can not lock SspiHandle mutex") + Self::new(ErrorKind::InternalError, "can not lock SspiHandle mutex") } } diff --git a/src/negotiate.rs b/src/negotiate.rs index 8025a6a6..e607d522 100644 --- a/src/negotiate.rs +++ b/src/negotiate.rs @@ -11,8 +11,8 @@ use crate::utils::is_azure_ad_domain; use crate::{ builders, kerberos, ntlm, pku2u, AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, CertTrustStatus, ContextNames, ContextSizes, CredentialUse, Credentials, CredentialsBuffers, DecryptionFlags, - Error, ErrorKind, InitializeSecurityContextResult, Kerberos, KerberosConfig, Ntlm, OwnedSecurityBuffer, - PackageCapabilities, PackageInfo, Pku2u, Result, SecurityBuffer, SecurityPackageType, SecurityStatus, Sspi, SspiEx, + Error, ErrorKind, InitializeSecurityContextResult, Kerberos, KerberosConfig, Ntlm, PackageCapabilities, + PackageInfo, Pku2u, Result, SecurityBuffer, SecurityBufferRef, SecurityPackageType, SecurityStatus, Sspi, SspiEx, SspiImpl, PACKAGE_ID_NONE, }; @@ -290,7 +290,7 @@ impl SspiEx for Negotiate { impl Sspi for Negotiate { #[instrument(ret, fields(protocol = self.protocol.protocol_name()), skip(self))] - fn complete_auth_token(&mut self, token: &mut [OwnedSecurityBuffer]) -> Result { + fn complete_auth_token(&mut self, token: &mut [SecurityBuffer]) -> Result { match &mut self.protocol { NegotiatedProtocol::Pku2u(pku2u) => pku2u.complete_auth_token(token), NegotiatedProtocol::Kerberos(kerberos) => kerberos.complete_auth_token(token), @@ -302,7 +302,7 @@ impl Sspi for Negotiate { fn encrypt_message( &mut self, flags: crate::EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> Result { match &mut self.protocol { @@ -315,7 +315,7 @@ impl Sspi for Negotiate { #[instrument(ret, fields(protocol = self.protocol.protocol_name()), skip_all)] fn decrypt_message<'data>( &mut self, - message: &mut [SecurityBuffer<'data>], + message: &mut [SecurityBufferRef<'data>], sequence_number: u32, ) -> Result { match &mut self.protocol { @@ -378,7 +378,7 @@ impl Sspi for Negotiate { fn make_signature( &mut self, flags: u32, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result<()> { match &mut self.protocol { @@ -388,7 +388,7 @@ impl Sspi for Negotiate { } } - fn verify_signature(&mut self, message: &mut [SecurityBuffer], sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, message: &mut [SecurityBufferRef], sequence_number: u32) -> crate::Result { match &mut self.protocol { NegotiatedProtocol::Pku2u(pku2u) => pku2u.verify_signature(message, sequence_number), NegotiatedProtocol::Kerberos(kerberos) => kerberos.verify_signature(message, sequence_number), diff --git a/src/ntlm/mod.rs b/src/ntlm/mod.rs index a4715ea3..9c6c121e 100644 --- a/src/ntlm/mod.rs +++ b/src/ntlm/mod.rs @@ -17,12 +17,12 @@ use crate::crypto::{compute_hmac_md5, Rc4, HASH_SIZE}; use crate::generator::GeneratorInitSecurityContext; use crate::utils::{extract_encrypted_data, save_decrypted_data}; use crate::{ - AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, CertTrustStatus, - ClientRequestFlags, ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, DecryptionFlags, - EncryptionFlags, Error, ErrorKind, FilledAcceptSecurityContext, FilledAcquireCredentialsHandle, - FilledInitializeSecurityContext, InitializeSecurityContextResult, OwnedSecurityBuffer, PackageCapabilities, - PackageInfo, SecurityBuffer, SecurityBufferType, SecurityPackageType, SecurityStatus, ServerResponseFlags, Sspi, - SspiEx, SspiImpl, PACKAGE_ID_NONE, + AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, BufferType, + CertTrustStatus, ClientRequestFlags, ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, + DecryptionFlags, EncryptionFlags, Error, ErrorKind, FilledAcceptSecurityContext, FilledAcquireCredentialsHandle, + FilledInitializeSecurityContext, InitializeSecurityContextResult, PackageCapabilities, PackageInfo, SecurityBuffer, + SecurityBufferFlags, SecurityBufferRef, SecurityPackageType, SecurityStatus, ServerResponseFlags, Sspi, SspiEx, + SspiImpl, PACKAGE_ID_NONE, }; pub const PKG_NAME: &str = "NTLM"; @@ -256,8 +256,8 @@ impl SspiImpl for Ntlm { .ok_or_else(|| crate::Error::new(crate::ErrorKind::InvalidToken, "Input buffers must be specified"))?; let status = match self.state { NtlmState::Initial => { - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; self.state = NtlmState::Negotiate; server::read_negotiate(self, input_token.buffer.as_slice())?; @@ -265,11 +265,11 @@ impl SspiImpl for Ntlm { server::write_challenge(self, &mut output_token.buffer)? } NtlmState::Authenticate => { - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; self.identity = builder.credentials_handle.cloned().flatten(); - if let Ok(sec_buffer) = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::ChannelBindings) { + if let Ok(sec_buffer) = SecurityBuffer::find_buffer(input, BufferType::ChannelBindings) { self.channel_bindings = Some(ChannelBindings::from_bytes(&sec_buffer.buffer)?); } @@ -308,7 +308,7 @@ impl Ntlm { let status = match self.state { NtlmState::Initial => { - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; self.state = NtlmState::Negotiate; self.signing = builder.context_requirements.contains(ClientRequestFlags::INTEGRITY); @@ -329,13 +329,12 @@ impl Ntlm { "Input buffers must be specified on subsequent calls", ) })?; - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; - if let Ok(sec_buffer) = OwnedSecurityBuffer::find_buffer( - builder.input.as_ref().unwrap(), - SecurityBufferType::ChannelBindings, - ) { + if let Ok(sec_buffer) = + SecurityBuffer::find_buffer(builder.input.as_ref().unwrap(), BufferType::ChannelBindings) + { self.channel_bindings = Some(ChannelBindings::from_bytes(&sec_buffer.buffer)?); } @@ -371,7 +370,7 @@ impl Ntlm { fn compute_checksum( &mut self, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, digest: &[u8; 16], ) -> crate::Result<()> { @@ -381,9 +380,9 @@ impl Ntlm { .unwrap() .process(&digest[0..SIGNATURE_CHECKSUM_SIZE]); - let signature_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Token)?; + let signature_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::Token)?; if signature_buffer.buf_len() < SIGNATURE_SIZE { - return Err(Error::new(ErrorKind::BufferTooSmall, "the token buffer is too small")); + return Err(Error::new(ErrorKind::BufferTooSmall, "the Token buffer is too small")); } let signature = compute_signature(&checksum, sequence_number); signature_buffer.write_data(signature.as_slice())?; @@ -412,7 +411,7 @@ impl Ntlm { impl Sspi for Ntlm { #[instrument(level = "debug", ret, fields(state = ?self.state), skip_all)] - fn complete_auth_token(&mut self, _token: &mut [OwnedSecurityBuffer]) -> crate::Result { + fn complete_auth_token(&mut self, _token: &mut [SecurityBuffer]) -> crate::Result { server::complete_authenticate(self) } @@ -420,21 +419,33 @@ impl Sspi for Ntlm { fn encrypt_message( &mut self, _flags: EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result { if self.send_sealing_key.is_none() { self.complete_auth_token(&mut [])?; } - SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Token)?; // check if exists - let data = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + // check if exists + SecurityBufferRef::find_buffer_mut(message, BufferType::Token)?; + // Find `Data` buffers (including `Data` buffers with the `READONLY_WITH_CHECKSUM` flag). + let data_to_sign = + SecurityBufferRef::buffers_of_type(message, BufferType::Data).fold(Vec::new(), |mut acc, buffer| { + acc.extend_from_slice(buffer.data()); + acc + }); - let digest = compute_digest(&self.send_signing_key, sequence_number, data.data())?; + let digest = compute_digest(&self.send_signing_key, sequence_number, &data_to_sign)?; + + // Find `Data` buffers without the `READONLY_WITH_CHECKSUM`/`READONLY` flag. + let data = + SecurityBufferRef::buffers_of_type_and_flags_mut(message, BufferType::Data, SecurityBufferFlags::NONE) + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "no buffer was provided with type Data"))?; let encrypted_data = self.send_sealing_key.as_mut().unwrap().process(data.data()); if encrypted_data.len() < data.buf_len() { - return Err(Error::new(ErrorKind::BufferTooSmall, "The Data buffer is too small")); + return Err(Error::new(ErrorKind::BufferTooSmall, "the Data buffer is too small")); } data.write_data(&encrypted_data)?; @@ -446,7 +457,7 @@ impl Sspi for Ntlm { #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self, sequence_number))] fn decrypt_message( &mut self, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result { if self.recv_sealing_key.is_none() { @@ -456,7 +467,7 @@ impl Sspi for Ntlm { let encrypted = extract_encrypted_data(message)?; if encrypted.len() < 16 { - return Err(Error::new(ErrorKind::MessageAltered, "Invalid encrypted message size!")); + return Err(Error::new(ErrorKind::MessageAltered, "invalid encrypted message size")); } let (signature, encrypted_message) = encrypted.split_at(16); @@ -465,7 +476,22 @@ impl Sspi for Ntlm { save_decrypted_data(&decrypted, message)?; - let digest = compute_digest(&self.recv_signing_key, sequence_number, &decrypted)?; + // Find `Data` buffers (including `Data` buffers with the `READONLY_WITH_CHECKSUM` flag). + let data_to_sign = + SecurityBufferRef::buffers_of_type(message, BufferType::Data).fold(Vec::new(), |mut acc, buffer| { + if buffer + .buffer_flags() + .contains(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM) + { + acc.extend_from_slice(buffer.data()); + } else { + // The `Data` buffer contains encrypted data, but the checksum was calculated over the decrypted data. + // So, we replace encrypted data with decrypted one. + acc.extend_from_slice(&decrypted); + } + acc + }); + let digest = compute_digest(&self.recv_signing_key, sequence_number, &data_to_sign)?; self.check_signature(sequence_number, &digest, signature)?; Ok(DecryptionFlags::empty()) @@ -524,16 +550,16 @@ impl Sspi for Ntlm { fn make_signature( &mut self, _flags: u32, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> crate::Result<()> { if self.send_sealing_key.is_none() { self.complete_auth_token(&mut [])?; } - SecurityBuffer::find_buffer(message, SecurityBufferType::Token)?; // check if exists + SecurityBufferRef::find_buffer(message, BufferType::Token)?; // check if exists - let data = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + let data = SecurityBufferRef::find_buffer_mut(message, BufferType::Data)?; let digest = compute_digest(&self.send_signing_key, sequence_number, data.data())?; self.compute_checksum(message, sequence_number, &digest)?; @@ -541,17 +567,17 @@ impl Sspi for Ntlm { Ok(()) } - fn verify_signature(&mut self, message: &mut [SecurityBuffer], sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, message: &mut [SecurityBufferRef], sequence_number: u32) -> crate::Result { if self.recv_sealing_key.is_none() { self.complete_auth_token(&mut [])?; } - SecurityBuffer::find_buffer(message, SecurityBufferType::Token)?; // check if exists + SecurityBufferRef::find_buffer(message, BufferType::Token)?; // check if exists - let data = SecurityBuffer::find_buffer(message, SecurityBufferType::Data)?; + let data = SecurityBufferRef::find_buffer(message, BufferType::Data)?; let digest = compute_digest(&self.recv_signing_key, sequence_number, data.data())?; - let signature = SecurityBuffer::find_buffer(message, SecurityBufferType::Token)?; + let signature = SecurityBufferRef::find_buffer(message, BufferType::Token)?; self.check_signature(sequence_number, &digest, signature.data())?; Ok(0) diff --git a/src/ntlm/test.rs b/src/ntlm/test.rs index 73677acb..4ecf44b7 100644 --- a/src/ntlm/test.rs +++ b/src/ntlm/test.rs @@ -34,15 +34,15 @@ fn encrypt_message_crypts_data() { let mut token = [0; 100]; let mut data = TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; let expected = &ENCRYPTED_TEST_DATA; let result = context .encrypt_message(EncryptionFlags::empty(), &mut buffers, 0) .unwrap(); - let output = SecurityBuffer::find_buffer(&buffers, SecurityBufferType::Data).unwrap(); + let output = SecurityBufferRef::find_buffer(&buffers, BufferType::Data).unwrap(); assert_eq!(result, SecurityStatus::Ok); assert_eq!(expected, output.data()); @@ -57,15 +57,15 @@ fn encrypt_message_correct_computes_digest() { let mut token = [0; 100]; let mut data = TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; let expected = &DIGEST_FOR_TEST_DATA; let result = context .encrypt_message(EncryptionFlags::empty(), &mut buffers, TEST_SEQ_NUM) .unwrap(); - let signature = SecurityBuffer::find_buffer(&buffers, SecurityBufferType::Token).unwrap(); + let signature = SecurityBufferRef::find_buffer(&buffers, BufferType::Token).unwrap(); assert_eq!(result, SecurityStatus::Ok); assert_eq!(expected, &signature.data()[4..12]); @@ -80,15 +80,15 @@ fn encrypt_message_writes_seq_num_to_signature() { let mut token = [0; 100]; let mut data = TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; let expected = TEST_SEQ_NUM.to_le_bytes(); let result = context .encrypt_message(EncryptionFlags::empty(), &mut buffers, TEST_SEQ_NUM) .unwrap(); - let signature = SecurityBuffer::find_buffer(&buffers, SecurityBufferType::Token).unwrap(); + let signature = SecurityBufferRef::find_buffer(&buffers, BufferType::Token).unwrap(); assert_eq!(result, SecurityStatus::Ok); assert_eq!(expected, signature.data()[12..SIGNATURE_SIZE]); @@ -104,13 +104,13 @@ fn decrypt_message_decrypts_data() { let mut signature_test_data = SIGNATURE_FOR_TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; let expected = TEST_DATA; context.decrypt_message(&mut buffers, TEST_SEQ_NUM).unwrap(); - let data = SecurityBuffer::find_buffer(&buffers, SecurityBufferType::Data).unwrap(); + let data = SecurityBufferRef::find_buffer(&buffers, BufferType::Data).unwrap(); assert_eq!(expected, data.data()); } @@ -125,8 +125,8 @@ fn decrypt_message_does_not_fail_on_correct_signature() { let mut signature_test_data = SIGNATURE_FOR_TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; context.decrypt_message(&mut buffers, TEST_SEQ_NUM).unwrap(); @@ -144,8 +144,8 @@ fn decrypt_message_fails_on_incorrect_version() { ]; let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut token), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut token), ]; assert!(context.decrypt_message(&mut buffers, TEST_SEQ_NUM).is_err()); @@ -163,8 +163,8 @@ fn decrypt_message_fails_on_incorrect_checksum() { ]; let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut token), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut token), ]; assert!(context.decrypt_message(&mut buffers, TEST_SEQ_NUM).is_err()); @@ -182,8 +182,8 @@ fn decrypt_message_fails_on_incorrect_seq_num() { ]; let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut token), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut token), ]; assert!(context.decrypt_message(&mut buffers, TEST_SEQ_NUM).is_err()); @@ -200,8 +200,8 @@ fn decrypt_message_fails_on_incorrect_signing_key() { let mut signature_test_data = SIGNATURE_FOR_TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; assert!(context.decrypt_message(&mut buffers, TEST_SEQ_NUM).is_err()); @@ -218,8 +218,8 @@ fn decrypt_message_fails_on_incorrect_sealing_key() { let mut signature_test_data = SIGNATURE_FOR_TEST_DATA.to_vec(); let mut buffers = vec![ - SecurityBuffer::Data(&mut encrypted_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut encrypted_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; assert!(context.decrypt_message(&mut buffers, TEST_SEQ_NUM).is_err()); @@ -239,16 +239,16 @@ fn make_signature_verified_by_verify_signature() { let mut plain_test_data = TEST_DATA.to_vec(); let mut signature_test_data = [0u8; 16]; let mut make_signature_buffers = vec![ - SecurityBuffer::Data(&mut plain_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut plain_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; assert!(sender .make_signature(0, &mut make_signature_buffers, TEST_SEQ_NUM) .is_ok()); let mut verify_signature_buffers = vec![ - SecurityBuffer::Data(&mut plain_test_data), - SecurityBuffer::Token(&mut signature_test_data), + SecurityBufferRef::data_buf(&mut plain_test_data), + SecurityBufferRef::token_buf(&mut signature_test_data), ]; assert!(reciever .verify_signature(&mut verify_signature_buffers, TEST_SEQ_NUM) @@ -267,7 +267,10 @@ fn verify_signature_fails_on_invalid_signature() { 0x01, 0x00, 0x00, 0x00, 0x2e, 0xdf, 0xff, 0x61, 0x29, 0xd6, 0x4d, 0xa9, 0xd2, 0x02, 0x96, 0x49, ]; - let mut verify_signature_buffers = vec![SecurityBuffer::Data(&mut test_data), SecurityBuffer::Token(&mut token)]; + let mut verify_signature_buffers = vec![ + SecurityBufferRef::data_buf(&mut test_data), + SecurityBufferRef::token_buf(&mut token), + ]; assert!(context .verify_signature(&mut verify_signature_buffers, TEST_SEQ_NUM) .is_err()); @@ -278,7 +281,7 @@ fn initialize_security_context_wrong_state_negotiate() { let mut context = Ntlm::new(); context.state = NtlmState::Negotiate; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -297,7 +300,7 @@ fn initialize_security_context_wrong_state_authenticate() { let mut context = Ntlm::new(); context.state = NtlmState::Authenticate; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -316,7 +319,7 @@ fn initialize_security_context_wrong_state_completion() { let mut context = Ntlm::new(); context.state = NtlmState::Completion; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -335,7 +338,7 @@ fn initialize_security_context_wrong_state_final() { let mut context = Ntlm::new(); context.state = NtlmState::Final; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -355,10 +358,7 @@ fn initialize_security_context_writes_negotiate_message() { context.state = NtlmState::Initial; - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -371,7 +371,7 @@ fn initialize_security_context_writes_negotiate_message() { let result = context.initialize_security_context_impl(&mut builder).unwrap(); assert_eq!(result.status, SecurityStatus::ContinueNeeded); - let output = OwnedSecurityBuffer::find_buffer(&output, SecurityBufferType::Token).unwrap(); + let output = SecurityBuffer::find_buffer(&output, BufferType::Token).unwrap(); assert_eq!(context.state, NtlmState::Challenge); assert!(!output.buffer.is_empty()); } @@ -383,19 +383,16 @@ fn initialize_security_context_reads_challenge_message() { context.state = NtlmState::Challenge; context.negotiate_message = Some(NegotiateMessage::new(Vec::new())); - let mut input = [OwnedSecurityBuffer::new( + let mut input = [SecurityBuffer::new( vec![ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x30, 0x00, 0x00, 0x00, 0x97, 0x82, 0x88, 0xe0, 0xfe, 0x14, 0x51, 0x74, 0x06, 0x57, 0x92, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], - SecurityBufferType::Token, - )]; - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, + BufferType::Token, )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -417,19 +414,16 @@ fn initialize_security_context_writes_authenticate_message() { context.state = NtlmState::Challenge; context.negotiate_message = Some(NegotiateMessage::new(Vec::new())); - let mut input = [OwnedSecurityBuffer::new( + let mut input = [SecurityBuffer::new( vec![ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x30, 0x00, 0x00, 0x00, 0x97, 0x82, 0x88, 0xe0, 0xfe, 0x14, 0x51, 0x74, 0x06, 0x57, 0x92, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], - SecurityBufferType::Token, - )]; - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, + BufferType::Token, )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -443,7 +437,7 @@ fn initialize_security_context_writes_authenticate_message() { let result = context.initialize_security_context_impl(&mut builder).unwrap(); assert_eq!(result.status, SecurityStatus::Ok); - let output = OwnedSecurityBuffer::find_buffer(&output, SecurityBufferType::Token).unwrap(); + let output = SecurityBuffer::find_buffer(&output, BufferType::Token).unwrap(); assert_eq!(context.state, NtlmState::Final); assert!(!output.buffer.is_empty()); } @@ -453,10 +447,7 @@ fn initialize_security_context_fails_on_empty_output_on_challenge_state() { let mut context = Ntlm::new(); context.state = NtlmState::Challenge; - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let mut credentials = Some(TEST_CREDENTIALS.clone()); let mut builder = context @@ -474,7 +465,7 @@ fn accept_security_context_wrong_state_negotiate() { let mut context = Ntlm::new(); context.state = NtlmState::Negotiate; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; assert!(context .accept_security_context() @@ -492,7 +483,7 @@ fn accept_security_context_wrong_state_challenge() { let mut context = Ntlm::new(); context.state = NtlmState::Challenge; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; assert!(context .accept_security_context() @@ -510,7 +501,7 @@ fn accept_security_context_wrong_state_completion() { let mut context = Ntlm::new(); context.state = NtlmState::Completion; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; assert!(context .accept_security_context() @@ -528,7 +519,7 @@ fn accept_security_context_wrong_state_final() { let mut context = Ntlm::new(); context.state = NtlmState::Final; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; assert!(context .accept_security_context() @@ -546,17 +537,14 @@ fn accept_security_context_reads_negotiate_message() { let mut context = Ntlm::new(); context.state = NtlmState::Initial; - let input = OwnedSecurityBuffer::new( + let input = SecurityBuffer::new( vec![ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x97, 0x82, 0x08, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, ], - SecurityBufferType::Token, + BufferType::Token, ); - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let result = context .accept_security_context() @@ -576,17 +564,14 @@ fn accept_security_context_writes_challenge_message() { let mut context = Ntlm::new(); context.state = NtlmState::Initial; - let input = OwnedSecurityBuffer::new( + let input = SecurityBuffer::new( vec![ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x97, 0x82, 0x08, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, ], - SecurityBufferType::Token, + BufferType::Token, ); - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let result = context .accept_security_context() .with_credentials_handle(&mut Some(TEST_CREDENTIALS.clone())) @@ -598,7 +583,7 @@ fn accept_security_context_writes_challenge_message() { .unwrap(); assert_eq!(result.status, SecurityStatus::ContinueNeeded); - let output = OwnedSecurityBuffer::find_buffer(&output, SecurityBufferType::Token).unwrap(); + let output = SecurityBuffer::find_buffer(&output, BufferType::Token).unwrap(); assert_eq!(context.state, NtlmState::Authenticate); assert!(!output.buffer.is_empty()); } @@ -615,7 +600,7 @@ fn accept_security_context_reads_authenticate() { 0, )); - let input = OwnedSecurityBuffer::new( + let input = SecurityBuffer::new( vec![ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, // signature 0x03, 0x00, 0x00, 0x00, // message type @@ -637,12 +622,9 @@ fn accept_security_context_reads_authenticate() { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // encrypted key ], - SecurityBufferType::Token, + BufferType::Token, ); - let mut output = vec![OwnedSecurityBuffer::new( - Vec::with_capacity(1024), - SecurityBufferType::Token, - )]; + let mut output = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)]; let result = context .accept_security_context() @@ -664,7 +646,7 @@ fn accept_security_context_fails_on_empty_output_on_negotiate_state() { context.state = NtlmState::Initial; - let mut output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; assert!(context .accept_security_context() diff --git a/src/pku2u/mod.rs b/src/pku2u/mod.rs index 69e2c197..933cad03 100644 --- a/src/pku2u/mod.rs +++ b/src/pku2u/mod.rs @@ -51,10 +51,10 @@ use crate::pku2u::extractors::extract_krb_rep; use crate::pku2u::generators::generate_as_req_username_from_certificate; use crate::utils::{extract_encrypted_data, generate_random_symmetric_key, get_encryption_key, save_decrypted_data}; use crate::{ - AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, CertTrustStatus, - ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, DecryptionFlags, EncryptionFlags, Error, ErrorKind, - InitializeSecurityContextResult, OwnedSecurityBuffer, PackageCapabilities, PackageInfo, Result, SecurityBuffer, - SecurityBufferType, SecurityPackageType, SecurityStatus, Sspi, SspiEx, SspiImpl, PACKAGE_ID_NONE, + AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, BufferType, + CertTrustStatus, ClientResponseFlags, ContextNames, ContextSizes, CredentialUse, DecryptionFlags, EncryptionFlags, + Error, ErrorKind, InitializeSecurityContextResult, PackageCapabilities, PackageInfo, Result, SecurityBuffer, + SecurityBufferRef, SecurityPackageType, SecurityStatus, Sspi, SspiEx, SspiImpl, PACKAGE_ID_NONE, }; pub const PKG_NAME: &str = "Pku2u"; @@ -187,7 +187,7 @@ impl Pku2u { impl Sspi for Pku2u { #[instrument(level = "debug", ret, fields(state = ?self.state), skip_all)] - fn complete_auth_token(&mut self, _token: &mut [OwnedSecurityBuffer]) -> Result { + fn complete_auth_token(&mut self, _token: &mut [SecurityBuffer]) -> Result { Ok(SecurityStatus::Ok) } @@ -195,14 +195,14 @@ impl Sspi for Pku2u { fn encrypt_message( &mut self, _flags: EncryptionFlags, - message: &mut [SecurityBuffer], + message: &mut [SecurityBufferRef], sequence_number: u32, ) -> Result { trace!(encryption_params = ?self.encryption_params); // checks if the Token buffer present - let _ = SecurityBuffer::find_buffer(message, SecurityBufferType::Token)?; - let data_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Data)?; + let _ = SecurityBufferRef::find_buffer(message, BufferType::Token)?; + let data_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::Data)?; let cipher = self .encryption_params @@ -240,7 +240,7 @@ impl Sspi for Pku2u { let (token, data) = raw_wrap_token.split_at(SECURITY_TRAILER); data_buffer.write_data(data)?; - let token_buffer = SecurityBuffer::find_buffer_mut(message, SecurityBufferType::Token)?; + let token_buffer = SecurityBufferRef::find_buffer_mut(message, BufferType::Token)?; token_buffer.write_data(token)?; } _ => { @@ -255,7 +255,7 @@ impl Sspi for Pku2u { } #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self, _sequence_number))] - fn decrypt_message(&mut self, message: &mut [SecurityBuffer], _sequence_number: u32) -> Result { + fn decrypt_message(&mut self, message: &mut [SecurityBufferRef], _sequence_number: u32) -> Result { trace!(encryption_params = ?self.encryption_params); let encrypted = extract_encrypted_data(message)?; @@ -343,7 +343,7 @@ impl Sspi for Pku2u { fn make_signature( &mut self, _flags: u32, - _message: &mut [SecurityBuffer], + _message: &mut [SecurityBufferRef], _sequence_number: u32, ) -> crate::Result<()> { Err(Error::new( @@ -352,7 +352,7 @@ impl Sspi for Pku2u { )) } - fn verify_signature(&mut self, _message: &mut [SecurityBuffer], _sequence_number: u32) -> crate::Result { + fn verify_signature(&mut self, _message: &mut [SecurityBufferRef], _sequence_number: u32) -> crate::Result { Err(Error::new( ErrorKind::UnsupportedFunction, "verify_signature is not supported", @@ -446,7 +446,7 @@ impl Pku2u { let encoded_neg_token_init = picky_asn1_der::to_vec(&generate_neg_token_init(mech_token)?)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer.write_all(&encoded_neg_token_init)?; self.state = Pku2uState::Preauthentication; @@ -458,7 +458,7 @@ impl Pku2u { .input .as_ref() .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; let neg_token_targ: NegTokenTarg1 = picky_asn1_der::from_bytes(&input_token.buffer)?; let buffer = neg_token_targ @@ -563,7 +563,7 @@ impl Pku2u { let response_token = picky_asn1_der::to_vec(&generate_neg_token_targ(mech_token)?)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer.write_all(&response_token)?; self.state = Pku2uState::AsExchange; @@ -575,7 +575,7 @@ impl Pku2u { .input .as_ref() .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; let neg_token_targ: NegTokenTarg1 = picky_asn1_der::from_bytes(&input_token.buffer)?; let buffer = neg_token_targ @@ -716,7 +716,7 @@ impl Pku2u { let encoded_neg_token_targ = picky_asn1_der::to_vec(&generate_neg_token_targ(mech_token)?)?; - let output_token = OwnedSecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, BufferType::Token)?; output_token.buffer.write_all(&encoded_neg_token_targ)?; self.state = Pku2uState::ApExchange; @@ -728,7 +728,7 @@ impl Pku2u { .input .as_ref() .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; - let input_token = OwnedSecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + let input_token = SecurityBuffer::find_buffer(input, BufferType::Token)?; let neg_token_targ: NegTokenTarg1 = picky_asn1_der::from_bytes(&input_token.buffer)?; @@ -834,7 +834,7 @@ mod tests { use super::generators::{generate_client_dh_parameters, generate_server_dh_parameters}; use super::Pku2uMode; use crate::kerberos::EncryptionParams; - use crate::{EncryptionFlags, Pku2u, Pku2uConfig, Pku2uState, SecurityBuffer, Sspi}; + use crate::{EncryptionFlags, Pku2u, Pku2uConfig, Pku2uState, SecurityBufferRef, Sspi}; #[test] fn stream_buffer_decryption() { @@ -938,6 +938,7 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1 sub_session_key: Some(sub_session_key.clone()), sspi_encrypt_key_usage: INITIATOR_SEAL, sspi_decrypt_key_usage: ACCEPTOR_SEAL, + ec: 0, }, auth_identity: None, conversation_id: Uuid::new_v4(), @@ -963,6 +964,7 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1 sub_session_key: Some(sub_session_key), sspi_encrypt_key_usage: ACCEPTOR_SEAL, sspi_decrypt_key_usage: INITIATOR_SEAL, + ec: 0, }, auth_identity: None, conversation_id: Uuid::new_v4(), @@ -979,8 +981,8 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1 let mut token = [0; 1024]; let mut data = plain_message.to_vec(); let mut message = [ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; pku2u_server @@ -990,7 +992,10 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1 let mut buffer = message[0].data().to_vec(); buffer.extend_from_slice(message[1].data()); - let mut message = [SecurityBuffer::Stream(&mut buffer), SecurityBuffer::Data(&mut [])]; + let mut message = [ + SecurityBufferRef::stream_buf(&mut buffer), + SecurityBufferRef::data_buf(&mut []), + ]; pku2u_client.decrypt_message(&mut message, 0).unwrap(); diff --git a/src/security_buffer.rs b/src/security_buffer.rs index 1a10f7bc..89cb8ebc 100644 --- a/src/security_buffer.rs +++ b/src/security_buffer.rs @@ -1,20 +1,13 @@ use std::fmt; use std::mem::take; -use crate::{Error, ErrorKind, Result, SecurityBufferType}; +use crate::{BufferType, Error, ErrorKind, Result, SecurityBufferFlags, SecurityBufferType}; -/// A special security buffer type is used for the data decryption. Basically, it's almost the same -/// as `OwnedSecurityBuffer` but for decryption. -/// -/// [DecryptMessage](https://learn.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--general) -/// "The encrypted message is decrypted in place, overwriting the original contents of its buffer." -/// -/// So, the already defined `OwnedSecurityBuffer` is not suitable for decryption because it uses [Vec] inside. -/// We use reference in the [SecurityBuffer] structure to avoid data cloning as much as possible. -/// Decryption/encryption input buffers can be very large. Even up to 32 KiB if we are using this crate as a TSSSP(CREDSSP) -/// security package. +/// A security buffer type with a mutable reference to the buffer data. +/// unflagged +/// Basically, it is a security buffer but without buffer flags. #[non_exhaustive] -pub enum SecurityBuffer<'data> { +enum UnflaggedSecurityBuffer<'data> { Data(&'data mut [u8]), Token(&'data mut [u8]), StreamHeader(&'data mut [u8]), @@ -26,113 +19,290 @@ pub enum SecurityBuffer<'data> { Empty, } -impl<'data> SecurityBuffer<'data> { - /// Created a [SecurityBuffer] from based on provided [SecurityBufferType]. +/// A special security buffer type is used for the data decryption. Basically, it's almost the same +/// as `SecurityBuffer` but for decryption. +/// +/// [DecryptMessage](https://learn.microsoft.com/en-us/windows/win32/secauthn/decryptmessage--general) +/// "The encrypted message is decrypted in place, overwriting the original contents of its buffer." +/// +/// So, the already defined `SecurityBuffer` is not suitable for decryption because it uses [Vec] inside. +/// We use reference in the [SecurityBufferRef] structure to avoid data cloning as much as possible. +/// Decryption/encryption input buffers can be very large. Even up to 32 KiB if we are using this crate as a TSSSP(CREDSSP) +/// security package. +pub struct SecurityBufferRef<'data> { + buffer_type: UnflaggedSecurityBuffer<'data>, + buffer_flags: SecurityBufferFlags, +} + +impl<'data> SecurityBufferRef<'data> { + /// Creates a [SecurityBufferRef] with a `Data` buffer type and empty buffer flags. + pub fn data_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Data(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `Token` buffer type and empty buffer flags. + pub fn token_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Token(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `StreamHeader` buffer type and empty buffer flags. + pub fn stream_header_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::StreamHeader(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `StreamTrailer` buffer type and empty buffer flags. + pub fn stream_trailer_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::StreamTrailer(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `Stream` buffer type and empty buffer flags. + pub fn stream_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Stream(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `Extra` buffer type and empty buffer flags. + pub fn extra_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Extra(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `Padding` buffer type and empty buffer flags. + pub fn padding_buf(data: &mut [u8]) -> SecurityBufferRef { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Padding(data), + buffer_flags: Default::default(), + } + } + + /// Creates a [SecurityBufferRef] with a `Missing` buffer type and empty buffer flags. + pub fn missing_buf<'a>(count: usize) -> SecurityBufferRef<'a> { + SecurityBufferRef { + buffer_type: UnflaggedSecurityBuffer::Missing(count), + buffer_flags: Default::default(), + } + } + + /// Set buffer flags. + pub fn with_flags(self, buffer_flags: SecurityBufferFlags) -> Self { + let Self { + buffer_type, + buffer_flags: _, + } = self; + + Self { + buffer_type, + buffer_flags, + } + } + + /// Creates a [SecurityBufferRef] from based on provided [BufferType]. /// /// Inner buffers will be empty. - pub fn with_security_buffer_type(security_buffer_type: SecurityBufferType) -> Result { - match security_buffer_type { - SecurityBufferType::Empty => Ok(SecurityBuffer::Empty), - SecurityBufferType::Data => Ok(SecurityBuffer::Data(&mut [])), - SecurityBufferType::Token => Ok(SecurityBuffer::Token(&mut [])), - SecurityBufferType::Missing => Ok(SecurityBuffer::Missing(0)), - SecurityBufferType::Extra => Ok(SecurityBuffer::Extra(&mut [])), - SecurityBufferType::Padding => Ok(SecurityBuffer::Padding(&mut [])), - SecurityBufferType::StreamTrailer => Ok(SecurityBuffer::StreamTrailer(&mut [])), - SecurityBufferType::StreamHeader => Ok(SecurityBuffer::StreamHeader(&mut [])), - SecurityBufferType::Stream => Ok(SecurityBuffer::Stream(&mut [])), - _ => Err(Error::new(ErrorKind::UnsupportedFunction, "")), - } + pub fn with_security_buffer_type(security_buffer_type: BufferType) -> Result { + Ok(Self { + buffer_type: match security_buffer_type { + BufferType::Empty => UnflaggedSecurityBuffer::Empty, + BufferType::Data => UnflaggedSecurityBuffer::Data(&mut []), + BufferType::Token => UnflaggedSecurityBuffer::Token(&mut []), + BufferType::Missing => UnflaggedSecurityBuffer::Missing(0), + BufferType::Extra => UnflaggedSecurityBuffer::Extra(&mut []), + BufferType::Padding => UnflaggedSecurityBuffer::Padding(&mut []), + BufferType::StreamTrailer => UnflaggedSecurityBuffer::StreamTrailer(&mut []), + BufferType::StreamHeader => UnflaggedSecurityBuffer::StreamHeader(&mut []), + BufferType::Stream => UnflaggedSecurityBuffer::Stream(&mut []), + _ => return Err(Error::new(ErrorKind::UnsupportedFunction, "")), + }, + buffer_flags: SecurityBufferFlags::NONE, + }) + } + + /// Created a [SecurityBufferRef] from based on provided [BufferType]. + /// + /// Inner buffers will be empty. + pub fn with_owned_security_buffer_type(security_buffer_type: SecurityBufferType) -> Result { + Ok(Self { + buffer_type: match security_buffer_type.buffer_type { + BufferType::Empty => UnflaggedSecurityBuffer::Empty, + BufferType::Data => UnflaggedSecurityBuffer::Data(&mut []), + BufferType::Token => UnflaggedSecurityBuffer::Token(&mut []), + BufferType::Missing => UnflaggedSecurityBuffer::Missing(0), + BufferType::Extra => UnflaggedSecurityBuffer::Extra(&mut []), + BufferType::Padding => UnflaggedSecurityBuffer::Padding(&mut []), + BufferType::StreamTrailer => UnflaggedSecurityBuffer::StreamTrailer(&mut []), + BufferType::StreamHeader => UnflaggedSecurityBuffer::StreamHeader(&mut []), + BufferType::Stream => UnflaggedSecurityBuffer::Stream(&mut []), + _ => return Err(Error::new(ErrorKind::UnsupportedFunction, "")), + }, + buffer_flags: security_buffer_type.buffer_flags, + }) } - /// Creates a new [SecurityBuffer] with the provided buffer data saving the old buffer type. + /// Creates a new [SecurityBufferRef] with the provided buffer data saving the old buffer type. /// - /// *Attention*: the buffer type must not be [SecurityBufferType::Missing]. + /// *Attention*: the buffer type must not be [BufferType::Missing]. pub fn with_data(self, data: &'data mut [u8]) -> Result { - Ok(match self { - SecurityBuffer::Data(_) => SecurityBuffer::Data(data), - SecurityBuffer::Token(_) => SecurityBuffer::Token(data), - SecurityBuffer::StreamHeader(_) => SecurityBuffer::StreamHeader(data), - SecurityBuffer::StreamTrailer(_) => SecurityBuffer::StreamTrailer(data), - SecurityBuffer::Stream(_) => SecurityBuffer::Stream(data), - SecurityBuffer::Extra(_) => SecurityBuffer::Extra(data), - SecurityBuffer::Padding(_) => SecurityBuffer::Padding(data), - SecurityBuffer::Missing(_) => { - return Err(Error::new( - ErrorKind::InternalError, - "the missing buffer type does not hold any buffers inside", - )) - } - SecurityBuffer::Empty => SecurityBuffer::Empty, + Ok(Self { + buffer_type: match &self.buffer_type { + UnflaggedSecurityBuffer::Data(_) => UnflaggedSecurityBuffer::Data(data), + UnflaggedSecurityBuffer::Token(_) => UnflaggedSecurityBuffer::Token(data), + UnflaggedSecurityBuffer::StreamHeader(_) => UnflaggedSecurityBuffer::StreamHeader(data), + UnflaggedSecurityBuffer::StreamTrailer(_) => UnflaggedSecurityBuffer::StreamTrailer(data), + UnflaggedSecurityBuffer::Stream(_) => UnflaggedSecurityBuffer::Stream(data), + UnflaggedSecurityBuffer::Extra(_) => UnflaggedSecurityBuffer::Extra(data), + UnflaggedSecurityBuffer::Padding(_) => UnflaggedSecurityBuffer::Padding(data), + UnflaggedSecurityBuffer::Missing(_) => { + return Err(Error::new( + ErrorKind::InternalError, + "the missing buffer type does not hold any buffers inside", + )) + } + UnflaggedSecurityBuffer::Empty => UnflaggedSecurityBuffer::Empty, + }, + buffer_flags: self.buffer_flags, }) } /// Sets the buffer data. /// - /// *Attention*: the buffer type must not be [SecurityBufferType::Missing]. + /// *Attention*: the buffer type must not be [BufferType::Missing]. pub fn set_data(&mut self, buf: &'data mut [u8]) -> Result<()> { - match self { - SecurityBuffer::Data(data) => *data = buf, - SecurityBuffer::Token(data) => *data = buf, - SecurityBuffer::StreamHeader(data) => *data = buf, - SecurityBuffer::StreamTrailer(data) => *data = buf, - SecurityBuffer::Stream(data) => *data = buf, - SecurityBuffer::Extra(data) => *data = buf, - SecurityBuffer::Padding(data) => *data = buf, - SecurityBuffer::Missing(_) => { + match &mut self.buffer_type { + UnflaggedSecurityBuffer::Data(data) => *data = buf, + UnflaggedSecurityBuffer::Token(data) => *data = buf, + UnflaggedSecurityBuffer::StreamHeader(data) => *data = buf, + UnflaggedSecurityBuffer::StreamTrailer(data) => *data = buf, + UnflaggedSecurityBuffer::Stream(data) => *data = buf, + UnflaggedSecurityBuffer::Extra(data) => *data = buf, + UnflaggedSecurityBuffer::Padding(data) => *data = buf, + UnflaggedSecurityBuffer::Missing(_) => { return Err(Error::new( ErrorKind::InternalError, "the missing buffer type does not hold any buffers inside", )) } - SecurityBuffer::Empty => {} + UnflaggedSecurityBuffer::Empty => {} }; Ok(()) } - /// Determines the [SecurityBufferType] of the decrypt buffer. - pub fn security_buffer_type(&self) -> SecurityBufferType { - match self { - SecurityBuffer::Data(_) => SecurityBufferType::Data, - SecurityBuffer::Token(_) => SecurityBufferType::Token, - SecurityBuffer::StreamHeader(_) => SecurityBufferType::StreamHeader, - SecurityBuffer::StreamTrailer(_) => SecurityBufferType::StreamTrailer, - SecurityBuffer::Stream(_) => SecurityBufferType::Stream, - SecurityBuffer::Extra(_) => SecurityBufferType::Extra, - SecurityBuffer::Padding(_) => SecurityBufferType::Padding, - SecurityBuffer::Missing(_) => SecurityBufferType::Missing, - SecurityBuffer::Empty => SecurityBufferType::Empty, + /// Determines the [BufferType] of security buffer. + pub fn buffer_type(&self) -> BufferType { + match &self.buffer_type { + UnflaggedSecurityBuffer::Data(_) => BufferType::Data, + UnflaggedSecurityBuffer::Token(_) => BufferType::Token, + UnflaggedSecurityBuffer::StreamHeader(_) => BufferType::StreamHeader, + UnflaggedSecurityBuffer::StreamTrailer(_) => BufferType::StreamTrailer, + UnflaggedSecurityBuffer::Stream(_) => BufferType::Stream, + UnflaggedSecurityBuffer::Extra(_) => BufferType::Extra, + UnflaggedSecurityBuffer::Padding(_) => BufferType::Padding, + UnflaggedSecurityBuffer::Missing(_) => BufferType::Missing, + UnflaggedSecurityBuffer::Empty => BufferType::Empty, + } + } + + pub fn buffer_flags(&self) -> SecurityBufferFlags { + self.buffer_flags + } + + pub fn owned_security_buffer_type(&self) -> SecurityBufferType { + let buffer_type = match &self.buffer_type { + UnflaggedSecurityBuffer::Data(_) => BufferType::Data, + UnflaggedSecurityBuffer::Token(_) => BufferType::Token, + UnflaggedSecurityBuffer::StreamHeader(_) => BufferType::StreamHeader, + UnflaggedSecurityBuffer::StreamTrailer(_) => BufferType::StreamTrailer, + UnflaggedSecurityBuffer::Stream(_) => BufferType::Stream, + UnflaggedSecurityBuffer::Extra(_) => BufferType::Extra, + UnflaggedSecurityBuffer::Padding(_) => BufferType::Padding, + UnflaggedSecurityBuffer::Missing(_) => BufferType::Missing, + UnflaggedSecurityBuffer::Empty => BufferType::Empty, + }; + + SecurityBufferType { + buffer_type, + buffer_flags: self.buffer_flags, } } - /// Returns the immutable reference to the [SecurityBuffer] with specified buffer type. + /// Returns the immutable reference to the [SecurityBufferRef] with specified buffer type. /// /// If a slice contains more than one buffer with a specified buffer type, then the first one will be returned. pub fn find_buffer<'a>( - buffers: &'a [SecurityBuffer<'data>], - buffer_type: SecurityBufferType, - ) -> Result<&'a SecurityBuffer<'data>> { + buffers: &'a [SecurityBufferRef<'data>], + buffer_type: BufferType, + ) -> Result<&'a SecurityBufferRef<'data>> { + buffers.iter().find(|b| b.buffer_type() == buffer_type).ok_or_else(|| { + Error::new( + ErrorKind::InvalidToken, + format!("no buffer was provided with type {:?}", buffer_type), + ) + }) + } + + /// Returns the vector of immutable references to the [SecurityBufferRef] with specified buffer type. + pub fn buffers_of_type<'a>( + buffers: &'a [SecurityBufferRef<'data>], + buffer_type: BufferType, + ) -> impl Iterator> { + buffers.iter().filter(move |b| b.buffer_type() == buffer_type) + } + + /// Returns the vector of immutable references to the [SecurityBufferRef] with specified buffer type. + pub fn buffers_of_type_mut<'a>( + buffers: &'a mut [SecurityBufferRef<'data>], + buffer_type: BufferType, + ) -> impl Iterator> { + buffers.iter_mut().filter(move |b| b.buffer_type() == buffer_type) + } + + /// Returns the vector of immutable references to the [SecurityBufferRef] with specified buffer type and flags. + pub fn buffers_of_type_and_flags<'a>( + buffers: &'a [SecurityBufferRef<'data>], + buffer_type: BufferType, + buffer_flags: SecurityBufferFlags, + ) -> impl Iterator> { buffers .iter() - .find(|b| b.security_buffer_type() == buffer_type) - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidToken, - format!("no buffer was provided with type {:?}", buffer_type), - ) - }) + .filter(move |b| b.buffer_type() == buffer_type && b.buffer_flags() == buffer_flags) + } + + /// Returns the vector of immutable references to the [SecurityBufferRef] with specified buffer type and flags. + pub fn buffers_of_type_and_flags_mut<'a>( + buffers: &'a mut [SecurityBufferRef<'data>], + buffer_type: BufferType, + buffer_flags: SecurityBufferFlags, + ) -> impl Iterator> { + buffers + .iter_mut() + .filter(move |b| b.buffer_type() == buffer_type && b.buffer_flags() == buffer_flags) } - /// Returns the mutable reference to the [SecurityBuffer] with specified buffer type. + /// Returns the mutable reference to the [SecurityBufferRef] with specified buffer type. /// /// If a slice contains more than one buffer with a specified buffer type, then the first one will be returned. pub fn find_buffer_mut<'a>( - buffers: &'a mut [SecurityBuffer<'data>], - buffer_type: SecurityBufferType, - ) -> Result<&'a mut SecurityBuffer<'data>> { + buffers: &'a mut [SecurityBufferRef<'data>], + buffer_type: BufferType, + ) -> Result<&'a mut SecurityBufferRef<'data>> { buffers .iter_mut() - .find(|b| b.security_buffer_type() == buffer_type) + .find(|b| b.buffer_type() == buffer_type) .ok_or_else(|| { Error::new( ErrorKind::InvalidToken, @@ -142,64 +312,64 @@ impl<'data> SecurityBuffer<'data> { } /// Returns the immutable reference to the inner buffer data. - pub fn buf_data<'a>(buffers: &'a [SecurityBuffer<'a>], buffer_type: SecurityBufferType) -> Result<&'a [u8]> { - Ok(SecurityBuffer::find_buffer(buffers, buffer_type)?.data()) + pub fn buf_data<'a>(buffers: &'a [SecurityBufferRef<'a>], buffer_type: BufferType) -> Result<&'a [u8]> { + Ok(SecurityBufferRef::find_buffer(buffers, buffer_type)?.data()) } /// Returns the immutable reference to the inner data. /// /// Some buffer types can not hold the data, so the empty slice will be returned. pub fn data(&self) -> &[u8] { - match self { - SecurityBuffer::Data(data) => data, - SecurityBuffer::Token(data) => data, - SecurityBuffer::StreamHeader(data) => data, - SecurityBuffer::StreamTrailer(data) => data, - SecurityBuffer::Stream(data) => data, - SecurityBuffer::Extra(data) => data, - SecurityBuffer::Padding(data) => data, - SecurityBuffer::Missing(_) => &[], - SecurityBuffer::Empty => &[], + match &self.buffer_type { + UnflaggedSecurityBuffer::Data(data) => data, + UnflaggedSecurityBuffer::Token(data) => data, + UnflaggedSecurityBuffer::StreamHeader(data) => data, + UnflaggedSecurityBuffer::StreamTrailer(data) => data, + UnflaggedSecurityBuffer::Stream(data) => data, + UnflaggedSecurityBuffer::Extra(data) => data, + UnflaggedSecurityBuffer::Padding(data) => data, + UnflaggedSecurityBuffer::Missing(_) => &[], + UnflaggedSecurityBuffer::Empty => &[], } } /// Calculates the buffer data length. pub fn buf_len(&self) -> usize { - match self { - SecurityBuffer::Data(data) => data.len(), - SecurityBuffer::Token(data) => data.len(), - SecurityBuffer::StreamHeader(data) => data.len(), - SecurityBuffer::StreamTrailer(data) => data.len(), - SecurityBuffer::Stream(data) => data.len(), - SecurityBuffer::Extra(data) => data.len(), - SecurityBuffer::Padding(data) => data.len(), - SecurityBuffer::Missing(needed_bytes_amount) => *needed_bytes_amount, - SecurityBuffer::Empty => 0, + match &self.buffer_type { + UnflaggedSecurityBuffer::Data(data) => data.len(), + UnflaggedSecurityBuffer::Token(data) => data.len(), + UnflaggedSecurityBuffer::StreamHeader(data) => data.len(), + UnflaggedSecurityBuffer::StreamTrailer(data) => data.len(), + UnflaggedSecurityBuffer::Stream(data) => data.len(), + UnflaggedSecurityBuffer::Extra(data) => data.len(), + UnflaggedSecurityBuffer::Padding(data) => data.len(), + UnflaggedSecurityBuffer::Missing(needed_bytes_amount) => *needed_bytes_amount, + UnflaggedSecurityBuffer::Empty => 0, } } /// Returns the mutable reference to the inner buffer data leaving the empty buffer on its place. pub fn take_buf_data_mut<'a>( - buffers: &'a mut [SecurityBuffer<'data>], - buffer_type: SecurityBufferType, + buffers: &'a mut [SecurityBufferRef<'data>], + buffer_type: BufferType, ) -> Result<&'data mut [u8]> { - Ok(SecurityBuffer::find_buffer_mut(buffers, buffer_type)?.take_data()) + Ok(SecurityBufferRef::find_buffer_mut(buffers, buffer_type)?.take_data()) } /// Returns the mutable reference to the inner data leaving the empty buffer on its place. /// /// Some buffer types can not hold the data, so the empty slice will be returned. pub fn take_data(&mut self) -> &'data mut [u8] { - match self { - SecurityBuffer::Data(data) => take(data), - SecurityBuffer::Token(data) => take(data), - SecurityBuffer::StreamHeader(data) => take(data), - SecurityBuffer::StreamTrailer(data) => take(data), - SecurityBuffer::Stream(data) => take(data), - SecurityBuffer::Extra(data) => take(data), - SecurityBuffer::Padding(data) => take(data), - SecurityBuffer::Missing(_) => &mut [], - SecurityBuffer::Empty => &mut [], + match &mut self.buffer_type { + UnflaggedSecurityBuffer::Data(data) => take(data), + UnflaggedSecurityBuffer::Token(data) => take(data), + UnflaggedSecurityBuffer::StreamHeader(data) => take(data), + UnflaggedSecurityBuffer::StreamTrailer(data) => take(data), + UnflaggedSecurityBuffer::Stream(data) => take(data), + UnflaggedSecurityBuffer::Extra(data) => take(data), + UnflaggedSecurityBuffer::Padding(data) => take(data), + UnflaggedSecurityBuffer::Missing(_) => &mut [], + UnflaggedSecurityBuffer::Empty => &mut [], } } @@ -225,19 +395,20 @@ impl<'data> SecurityBuffer<'data> { } } -impl fmt::Debug for SecurityBuffer<'_> { +impl fmt::Debug for SecurityBufferRef<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SecurityBuffer {{ ")?; - match self { - SecurityBuffer::Data(data) => write_buffer(data, "Data", f)?, - SecurityBuffer::Token(data) => write_buffer(data, "Token", f)?, - SecurityBuffer::StreamHeader(data) => write_buffer(data, "StreamHeader", f)?, - SecurityBuffer::StreamTrailer(data) => write_buffer(data, "StreamTrailer", f)?, - SecurityBuffer::Stream(data) => write_buffer(data, "Stream", f)?, - SecurityBuffer::Extra(data) => write_buffer(data, "Extra", f)?, - SecurityBuffer::Padding(data) => write_buffer(data, "Padding", f)?, - SecurityBuffer::Missing(needed_bytes_amount) => write!(f, "Missing({})", *needed_bytes_amount)?, - SecurityBuffer::Empty => f.write_str("Empty")?, + write!(f, "SecurityBufferRef {{ ")?; + f.write_fmt(format_args!("{:?},", self.buffer_flags))?; + match &self.buffer_type { + UnflaggedSecurityBuffer::Data(data) => write_buffer(data, "Data", f)?, + UnflaggedSecurityBuffer::Token(data) => write_buffer(data, "Token", f)?, + UnflaggedSecurityBuffer::StreamHeader(data) => write_buffer(data, "StreamHeader", f)?, + UnflaggedSecurityBuffer::StreamTrailer(data) => write_buffer(data, "StreamTrailer", f)?, + UnflaggedSecurityBuffer::Stream(data) => write_buffer(data, "Stream", f)?, + UnflaggedSecurityBuffer::Extra(data) => write_buffer(data, "Extra", f)?, + UnflaggedSecurityBuffer::Padding(data) => write_buffer(data, "Padding", f)?, + UnflaggedSecurityBuffer::Missing(needed_bytes_amount) => write!(f, "Missing({})", *needed_bytes_amount)?, + UnflaggedSecurityBuffer::Empty => f.write_str("Empty")?, }; write!(f, " }}") } diff --git a/src/utils.rs b/src/utils.rs index 93ecb08d..3c16f1cf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,7 @@ use rand::rngs::OsRng; use rand::Rng; use crate::kerberos::EncryptionParams; -use crate::{Error, ErrorKind, Result, SecurityBuffer, SecurityBufferType}; +use crate::{BufferType, Error, ErrorKind, Result, SecurityBufferFlags, SecurityBufferRef}; pub fn string_to_utf16(value: impl AsRef) -> Vec { value @@ -241,15 +241,15 @@ pub fn get_encryption_key(enc_params: &EncryptionParams) -> Result<&[u8]> { } } -/// Copies a decrypted data into the [SecurityBufferType::Data] or [SecurityBufferType::Stream]. +/// Copies a decrypted data into the [BufferType::Data] or [BufferType::Stream]. /// /// There are two choices for how we should save the decrypted data in security buffers: /// * If the `SECBUFFER_STREAM` is present, we should save all data in the `SECBUFFER_DATA` buffer. /// But in such a case, the `SECBUFFER_DATA` buffer is empty. So, we take the inner buffer from /// the `SECBUFFER_STREAM` buffer, write decrypted data into it, and assign it to the `SECBUFFER_DATA` buffer. /// * If the `SECBUFFER_STREAM` is not present, we should just save all data in the `SECBUFFER_DATA` buffer. -pub fn save_decrypted_data<'a>(decrypted: &'a [u8], buffers: &'a mut [SecurityBuffer]) -> Result<()> { - if let Ok(buffer) = SecurityBuffer::find_buffer_mut(buffers, SecurityBufferType::Stream) { +pub fn save_decrypted_data<'a>(decrypted: &'a [u8], buffers: &'a mut [SecurityBufferRef]) -> Result<()> { + if let Ok(buffer) = SecurityBufferRef::find_buffer_mut(buffers, BufferType::Stream) { let decrypted_len = decrypted.len(); if buffer.buf_len() < decrypted_len { @@ -266,14 +266,21 @@ pub fn save_decrypted_data<'a>(decrypted: &'a [u8], buffers: &'a mut [SecurityBu let stream_buffer = buffer.take_data(); let stream_buffer_len = stream_buffer.len(); - let data_buffer = SecurityBuffer::find_buffer_mut(buffers, SecurityBufferType::Data)?; + let data_buffer = SecurityBufferRef::find_buffer_mut(buffers, BufferType::Data)?; let data = &mut stream_buffer[stream_buffer_len - decrypted_len..]; data.copy_from_slice(decrypted); data_buffer.set_data(data) } else { - let data_buffer = SecurityBuffer::find_buffer_mut(buffers, SecurityBufferType::Data)?; + let mut data_buffers = + SecurityBufferRef::buffers_of_type_and_flags_mut(buffers, BufferType::Data, SecurityBufferFlags::NONE); + let data_buffer = data_buffers.next().ok_or_else(|| { + Error::new( + ErrorKind::InvalidToken, + "no buffer was provided with type Data and without READONLY_WITH_CHECKSUM flag", + ) + })?; if data_buffer.buf_len() < decrypted.len() { return Err(Error::new( @@ -293,16 +300,22 @@ pub fn save_decrypted_data<'a>(decrypted: &'a [u8], buffers: &'a mut [SecurityBu /// Extracts data to decrypt from the incoming buffers. /// /// Data to decrypt is `Token` + `Stream`/`Data` buffers concatenated together. -pub fn extract_encrypted_data(buffers: &[SecurityBuffer]) -> Result> { - let mut encrypted = SecurityBuffer::buf_data(buffers, SecurityBufferType::Token) +pub fn extract_encrypted_data(buffers: &[SecurityBufferRef]) -> Result> { + let mut encrypted = SecurityBufferRef::buf_data(buffers, BufferType::Token) .unwrap_or_default() .to_vec(); encrypted.extend_from_slice( - if let Ok(buffer) = SecurityBuffer::buf_data(buffers, SecurityBufferType::Stream) { + if let Ok(buffer) = SecurityBufferRef::buf_data(buffers, BufferType::Stream) { buffer } else { - SecurityBuffer::buf_data(buffers, SecurityBufferType::Data)? + use crate::SecurityBufferFlags; + + // Find `Data` buffers but skip `Data` buffers with the `READONLY_WITH_CHECKSUM`/`READONLY` flag. + SecurityBufferRef::buffers_of_type_and_flags(buffers, BufferType::Data, SecurityBufferFlags::NONE) + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "no buffer was provided with type Data"))? + .data() }, ); diff --git a/tests/common.rs b/tests/common.rs index 9ab66ba4..cb1d97aa 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,9 +2,9 @@ use std::io; use std::sync::LazyLock; use sspi::{ - credssp, AcquireCredentialsHandleResult, AuthIdentity, ClientRequestFlags, ContextNames, CredentialUse, - DataRepresentation, EncryptionFlags, OwnedSecurityBuffer, SecurityBuffer, SecurityBufferType, SecurityStatus, - ServerRequestFlags, Sspi, SspiEx, Username, + credssp, AcquireCredentialsHandleResult, AuthIdentity, BufferType, ClientRequestFlags, ContextNames, CredentialUse, + DataRepresentation, EncryptionFlags, SecurityBuffer, SecurityBufferRef, SecurityStatus, ServerRequestFlags, Sspi, + SspiEx, Username, }; use time::OffsetDateTime; @@ -100,7 +100,7 @@ where let mut server_status = SecurityStatus::ContinueNeeded; loop { - let mut client_output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut client_output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let mut builder = client .initialize_security_context() @@ -119,7 +119,7 @@ where return Ok((client_status, server_status)); } - server_output = vec![OwnedSecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + server_output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)]; let server_result = server .accept_security_context() @@ -179,8 +179,8 @@ pub fn check_messages_encryption(client: &mut impl Sspi, server: &mut impl Sspi) let mut token = vec![0; server_sizes.security_trailer as usize]; let mut data = MESSAGE_TO_CLIENT.to_vec(); let mut messages = [ - SecurityBuffer::Token(token.as_mut_slice()), - SecurityBuffer::Data(data.as_mut_slice()), + SecurityBufferRef::token_buf(token.as_mut_slice()), + SecurityBufferRef::data_buf(data.as_mut_slice()), ]; server.encrypt_message(EncryptionFlags::empty(), &mut messages, sequence_number)?; assert_ne!(MESSAGE_TO_CLIENT, messages[1].data()); @@ -195,8 +195,8 @@ pub fn check_messages_encryption(client: &mut impl Sspi, server: &mut impl Sspi) let [mut token, mut data] = messages; let mut messages = vec![ - SecurityBuffer::Data(data.take_data()), - SecurityBuffer::Token(token.take_data()), + SecurityBufferRef::data_buf(data.take_data()), + SecurityBufferRef::token_buf(token.take_data()), ]; client.decrypt_message(&mut messages, sequence_number)?;