diff --git a/Cargo.lock b/Cargo.lock index d3fe1b31..e53df07c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,15 +102,24 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -140,9 +149,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -173,9 +182,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" @@ -249,15 +258,15 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -265,19 +274,34 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crypto" version = "0.5.1" @@ -289,9 +313,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", @@ -359,9 +383,18 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "delog" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2b93368262340c9d4441251b824500d1b641a50957ecf4219a2cc41b9eac8f" +dependencies = [ + "log", +] [[package]] name = "der" @@ -376,9 +409,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -406,9 +439,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest", @@ -430,23 +463,24 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", + "subtle", "zeroize", ] [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -484,14 +518,20 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -510,11 +550,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "ffi-types" +version = "0.1.0" + [[package]] name = "fiat-crypto" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] [[package]] name = "fnv" @@ -524,18 +579,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -548,9 +603,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -558,15 +613,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -575,15 +630,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -592,21 +647,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -633,9 +688,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -654,9 +709,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "group" @@ -671,9 +726,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -688,11 +743,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heapless" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] [[package]] name = "heck" @@ -702,15 +779,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -737,9 +814,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -748,9 +825,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -771,9 +848,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -786,7 +863,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -817,13 +894,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] @@ -843,9 +930,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2", "widestring", - "windows-sys", + "windows-sys 0.48.0", "winreg 0.50.0", ] @@ -855,26 +942,45 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "iso7816" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3af73ac9c821e7aea3280532118e15cdf9e7bb45c923cbf0e319ae25b27d20c" +dependencies = [ + "delog", + "heapless", +] + +[[package]] +name = "iso7816-tlv" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d47365efc3b4c252f8a3384445c0f7e8a4e0ae5c22bf3bedd2dd16f9bb45016a" +dependencies = [ + "untrusted", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -890,9 +996,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -900,6 +1006,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libz-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -908,9 +1025,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -973,9 +1090,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -985,22 +1102,22 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1031,6 +1148,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.1" @@ -1094,9 +1217,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1112,9 +1235,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -1178,7 +1301,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1194,9 +1317,9 @@ dependencies = [ [[package]] name = "pcsc" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4" +checksum = "45ed9d7f816b7d9ce9ddb0062dd2f393b3af31411a95a35411809b4b9116ea08" dependencies = [ "bitflags 1.3.2", "pcsc-sys", @@ -1222,9 +1345,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "picky" @@ -1364,15 +1487,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "platforms" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "polyval" @@ -1409,37 +1532,37 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primeorder" -version = "0.13.3" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.1", + "bitflags 2.4.2", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", @@ -1453,9 +1576,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1519,13 +1642,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -1540,9 +1663,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -1555,12 +1678,6 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - [[package]] name = "regex-syntax" version = "0.8.2" @@ -1569,9 +1686,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64", "bytes", @@ -1596,6 +1713,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -1630,23 +1748,23 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", "libc", "spin 0.9.8", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -1656,6 +1774,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core", + "sha1", "signature", "spki", "subtle", @@ -1679,22 +1798,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", @@ -1716,9 +1835,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] @@ -1747,17 +1866,17 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1815,33 +1934,33 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -1850,9 +1969,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -1914,9 +2033,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -1933,19 +2052,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -1954,7 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1968,12 +2077,15 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -1986,7 +2098,7 @@ dependencies = [ "async-dnssd", "async-recursion", "base64", - "bitflags 2.4.1", + "bitflags 2.4.2", "byteorder", "cfg-if", "crypto-mac", @@ -2025,8 +2137,9 @@ dependencies = [ "whoami", "winapi", "windows", - "windows-sys", + "windows-sys 0.48.0", "winreg 0.51.0", + "winscard", "zeroize", ] @@ -2035,17 +2148,26 @@ name = "sspi-ffi" version = "0.10.1" dependencies = [ "cfg-if", + "ffi-types", "libc", "num-traits", + "picky-asn1-der", "sspi", "symbol-rename-macro", "tracing", "tracing-subscriber", "whoami", "winapi", - "windows-sys", + "windows-sys 0.48.0", + "winscard", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2064,15 +2186,21 @@ version = "0.0.0" [[package]] name = "syn" -version = "2.0.39" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2096,31 +2224,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", @@ -2139,13 +2267,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", "libc", + "num-conv", "num_threads", "powerfmt", "serde", @@ -2161,10 +2290,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -2185,9 +2315,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2195,8 +2325,8 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.5", - "windows-sys", + "socket2", + "windows-sys 0.48.0", ] [[package]] @@ -2263,9 +2393,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", @@ -2274,9 +2404,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -2304,7 +2434,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.4.0", "ipnet", "once_cell", "rand", @@ -2339,9 +2469,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -2357,9 +2487,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2394,20 +2524,20 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] [[package]] name = "uuid" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", "serde", @@ -2419,6 +2549,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2451,9 +2587,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2461,9 +2597,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -2476,9 +2612,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -2488,9 +2624,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2498,9 +2634,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -2511,15 +2647,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -2527,9 +2663,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" @@ -2576,7 +2712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ "windows-core", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2585,7 +2721,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2594,7 +2730,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -2603,13 +2748,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -2618,42 +2778,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" @@ -2661,7 +2863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2671,7 +2873,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "winscard" +version = "0.1.0" +dependencies = [ + "bitflags 2.4.2", + "flate2", + "iso7816", + "iso7816-tlv", + "picky", + "picky-asn1-x509", + "proptest", + "rand", + "rand_core", + "rsa", + "sha1", + "time", + "tracing", + "uuid", ] [[package]] @@ -2688,9 +2910,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index fe70b65a..7d9eb760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,10 @@ required-features = ["network_client"] members = [ "ffi", "ffi/symbol-rename-macro", + "crates/winscard", + "crates/ffi-types" ] -exclude = [ - "tools/sspi-ffi-attacker", - "tools/wasm-testcompile", -] +exclude = ["tools/sspi-ffi-attacker", "tools/wasm-testcompile"] [features] default = [] @@ -31,7 +30,7 @@ dns_resolver = ["dep:trust-dns-resolver", "dep:tokio", "tokio/rt-multi-thread"] # TSSSP should be used only on Windows as a native CREDSSP replacement tsssp = ["dep:rustls"] # Turns on Kerberos smart card login (available only on Windows and users WinSCard API) -scard = ["dep:pcsc"] +scard = ["dep:pcsc", "dep:winscard"] [dependencies] byteorder = "1.4" @@ -70,6 +69,7 @@ zeroize = { version = "1.6", features = ["zeroize_derive"] } tokio = { version = "1.32", features = ["time", "rt"], optional = true } pcsc = { version = "2.8", optional = true } async-recursion = "1.0.5" +winscard = { path = "./crates/winscard", optional = true } [target.'cfg(windows)'.dependencies] winreg = "0.51" @@ -90,3 +90,4 @@ whoami = "1.4" picky = { version = "7.0.0-rc.8", features = ["x509"] } tracing-subscriber = "0.3" proptest = "1.3.1" +cfg-if = "1" diff --git a/crates/ffi-types/Cargo.toml b/crates/ffi-types/Cargo.toml new file mode 100644 index 00000000..7200eb44 --- /dev/null +++ b/crates/ffi-types/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ffi-types" +version = "0.1.0" +edition = "2021" + +[features] +default = [] +winscard = [] + +[dependencies] diff --git a/crates/ffi-types/src/common.rs b/crates/ffi-types/src/common.rs new file mode 100644 index 00000000..5ba6f7ae --- /dev/null +++ b/crates/ffi-types/src/common.rs @@ -0,0 +1,35 @@ +use std::ffi::c_void; + +pub type LpStr = *mut u8; +pub type LpCStr = *const u8; +pub type LpDword = *mut u32; +pub type LpWStr = *mut u16; +pub type LpCWStr = *const u16; +pub type LpCByte = *const u8; +pub type LpByte = *mut u8; +pub type LpCVoid = *const c_void; +pub type LpVoid = *mut c_void; +pub type Handle = *mut c_void; +pub type Bool = i32; + +/// [Guid](https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid) +/// +/// ```not_rust +/// typedef struct _GUID { +/// unsigned long Data1; +/// unsigned short Data2; +/// unsigned short Data3; +/// unsigned char Data4[8]; +/// } GUID; +/// ``` +#[repr(C)] +pub struct Guid { + pub data1: u32, + pub data2: u32, + pub data3: u32, + pub data4: [u8; 8], +} +pub type LpCGuid = *const Guid; +pub type LpGuid = *mut Guid; +pub type Uuid = Guid; +pub type LpUuid = *mut Uuid; diff --git a/crates/ffi-types/src/lib.rs b/crates/ffi-types/src/lib.rs new file mode 100644 index 00000000..eed19fe6 --- /dev/null +++ b/crates/ffi-types/src/lib.rs @@ -0,0 +1,5 @@ +pub mod common; +#[cfg(feature = "winscard")] +pub mod winscard; + +pub use common::*; diff --git a/crates/ffi-types/src/winscard/functions.rs b/crates/ffi-types/src/winscard/functions.rs new file mode 100644 index 00000000..60cd30a0 --- /dev/null +++ b/crates/ffi-types/src/winscard/functions.rs @@ -0,0 +1,199 @@ +use std::ffi::c_void; + +use super::{ + LpOpenCardNameA, LpOpenCardNameExA, LpOpenCardNameExW, LpOpenCardNameW, LpScardAtrMask, LpScardContext, + LpScardHandle, LpScardIoRequest, LpScardReaderStateA, LpScardReaderStateW, ScardContext, ScardHandle, ScardStatus, +}; +use crate::{ + Handle, LpByte, LpCByte, LpCGuid, LpCStr, LpCVoid, LpCWStr, LpDword, LpGuid, LpStr, LpUuid, LpVoid, LpWStr, +}; + +pub type SCardEstablishContextFn = + unsafe extern "system" fn(u32, *const c_void, *const c_void, LpScardContext) -> ScardStatus; +pub type SCardReleaseContextFn = unsafe extern "system" fn(ScardContext) -> ScardStatus; +pub type SCardIsValidContextFn = unsafe extern "system" fn(ScardContext) -> ScardStatus; +pub type SCardListReaderGroupsAFn = extern "system" fn(ScardContext, LpStr, LpDword) -> ScardStatus; +pub type SCardListReaderGroupsWFn = extern "system" fn(ScardContext, LpWStr, LpDword) -> ScardStatus; +pub type SCardListReadersAFn = unsafe extern "system" fn(ScardContext, LpCStr, LpStr, LpDword) -> ScardStatus; +pub type SCardListReadersWFn = unsafe extern "system" fn(ScardContext, LpCWStr, LpWStr, LpDword) -> ScardStatus; +pub type SCardListCardsAFn = + unsafe extern "system" fn(ScardContext, LpCByte, LpCGuid, u32, *mut u8, LpDword) -> ScardStatus; +pub type SCardListCardsWFn = + unsafe extern "system" fn(ScardContext, LpCByte, LpCGuid, u32, *mut u16, LpDword) -> ScardStatus; +pub type SCardListInterfacesAFn = extern "system" fn(ScardContext, LpCStr, LpGuid, LpDword) -> ScardStatus; +pub type SCardListInterfacesWFn = extern "system" fn(ScardContext, LpCWStr, LpGuid, LpDword) -> ScardStatus; +pub type SCardGetProviderIdAFn = extern "system" fn(ScardContext, LpCStr, LpGuid) -> ScardStatus; +pub type SCardGetProviderIdWFn = extern "system" fn(ScardContext, LpCWStr, LpGuid) -> ScardStatus; +pub type SCardGetCardTypeProviderNameAFn = + unsafe extern "system" fn(ScardContext, LpCStr, u32, *mut u8, LpDword) -> ScardStatus; +pub type SCardGetCardTypeProviderNameWFn = + unsafe extern "system" fn(ScardContext, LpCWStr, u32, *mut u16, LpDword) -> ScardStatus; +pub type SCardIntroduceReaderGroupAFn = extern "system" fn(ScardContext, LpCStr) -> ScardStatus; +pub type SCardIntroduceReaderGroupWFn = extern "system" fn(ScardContext, LpCWStr) -> ScardStatus; +pub type SCardForgetReaderGroupAFn = extern "system" fn(ScardContext, LpCStr) -> ScardStatus; +pub type SCardForgetReaderGroupWFn = extern "system" fn(ScardContext, LpCWStr) -> ScardStatus; +pub type SCardIntroduceReaderAFn = extern "system" fn(ScardContext, LpCStr, LpCStr) -> ScardStatus; +pub type SCardIntroduceReaderWFn = extern "system" fn(ScardContext, LpCWStr, LpCWStr) -> ScardStatus; +pub type SCardForgetReaderAFn = extern "system" fn(ScardContext, LpCStr) -> ScardStatus; +pub type SCardForgetReaderWFn = extern "system" fn(ScardContext, LpCWStr) -> ScardStatus; +pub type SCardAddReaderToGroupAFn = extern "system" fn(ScardContext, LpCStr, LpCStr) -> ScardStatus; +pub type SCardAddReaderToGroupWFn = extern "system" fn(ScardContext, LpCWStr, LpCWStr) -> ScardStatus; +pub type SCardRemoveReaderFromGroupAFn = extern "system" fn(ScardContext, LpCStr, LpCStr) -> ScardStatus; +pub type SCardRemoveReaderFromGroupWFn = extern "system" fn(ScardContext, LpCWStr, LpCWStr) -> ScardStatus; +pub type SCardIntroduceCardTypeAFn = + extern "system" fn(ScardContext, LpCStr, LpCGuid, LpCGuid, u32, LpCByte, LpCByte, u32) -> ScardStatus; +pub type SCardIntroduceCardTypeWFn = + extern "system" fn(ScardContext, LpCWStr, LpCGuid, LpCGuid, u32, LpCByte, LpCByte, u32) -> ScardStatus; +pub type SCardSetCardTypeProviderNameAFn = extern "system" fn(ScardContext, LpCStr, u32, LpCStr) -> ScardStatus; +pub type SCardSetCardTypeProviderNameWFn = extern "system" fn(ScardContext, LpCWStr, u32, LpCWStr) -> ScardStatus; +pub type SCardForgetCardTypeAFn = extern "system" fn(ScardContext, LpCStr) -> ScardStatus; +pub type SCardForgetCardTypeWFn = extern "system" fn(ScardContext, LpCWStr) -> ScardStatus; +pub type SCardFreeMemoryFn = unsafe extern "system" fn(ScardContext, LpCVoid) -> ScardStatus; +pub type SCardAccessStartedEventFn = extern "system" fn() -> Handle; +pub type SCardReleaseStartedEventFn = extern "system" fn(); +pub type SCardLocateCardsAFn = extern "system" fn(ScardContext, LpCStr, LpScardReaderStateA, u32) -> ScardStatus; +pub type SCardLocateCardsWFn = extern "system" fn(ScardContext, LpCWStr, LpScardReaderStateW, u32) -> ScardStatus; +pub type SCardLocateCardsByATRAFn = + extern "system" fn(ScardContext, LpScardAtrMask, u32, LpScardReaderStateA, u32) -> ScardStatus; +pub type SCardLocateCardsByATRWFn = + extern "system" fn(ScardContext, LpScardAtrMask, u32, LpScardReaderStateW, u32) -> ScardStatus; +pub type SCardGetStatusChangeAFn = + unsafe extern "system" fn(ScardContext, u32, LpScardReaderStateA, u32) -> ScardStatus; +pub type SCardGetStatusChangeWFn = + unsafe extern "system" fn(ScardContext, u32, LpScardReaderStateW, u32) -> ScardStatus; +pub type SCardCancelFn = extern "system" fn(ScardContext) -> ScardStatus; +pub type SCardReadCacheAFn = + unsafe extern "system" fn(ScardContext, LpUuid, u32, LpStr, LpByte, LpDword) -> ScardStatus; +pub type SCardReadCacheWFn = + unsafe extern "system" fn(ScardContext, LpUuid, u32, LpWStr, LpByte, LpDword) -> ScardStatus; +pub type SCardWriteCacheAFn = unsafe extern "system" fn(ScardContext, LpUuid, u32, LpStr, LpByte, u32) -> ScardStatus; +pub type SCardWriteCacheWFn = unsafe extern "system" fn(ScardContext, LpUuid, u32, LpWStr, LpByte, u32) -> ScardStatus; +pub type SCardGetReaderIconAFn = unsafe extern "system" fn(ScardContext, LpCStr, LpByte, LpDword) -> ScardStatus; +pub type SCardGetReaderIconWFn = unsafe extern "system" fn(ScardContext, LpCWStr, LpByte, LpDword) -> ScardStatus; +pub type SCardGetDeviceTypeIdAFn = unsafe extern "system" fn(ScardContext, LpCStr, LpDword) -> ScardStatus; +pub type SCardGetDeviceTypeIdWFn = unsafe extern "system" fn(ScardContext, LpCWStr, LpDword) -> ScardStatus; +pub type SCardGetReaderDeviceInstanceIdAFn = extern "system" fn(ScardContext, LpCStr, LpStr, LpDword) -> ScardStatus; +pub type SCardGetReaderDeviceInstanceIdWFn = extern "system" fn(ScardContext, LpCWStr, LpWStr, LpDword) -> ScardStatus; +pub type SCardListReadersWithDeviceInstanceIdAFn = + extern "system" fn(ScardContext, LpCStr, LpStr, LpDword) -> ScardStatus; +pub type SCardListReadersWithDeviceInstanceIdWFn = + extern "system" fn(ScardContext, LpCWStr, LpWStr, LpDword) -> ScardStatus; +pub type SCardAuditFn = extern "system" fn(ScardContext, u32) -> ScardStatus; +pub type SCardConnectAFn = + unsafe extern "system" fn(ScardContext, LpCStr, u32, u32, LpScardHandle, LpDword) -> ScardStatus; +pub type SCardConnectWFn = + unsafe extern "system" fn(ScardContext, LpCWStr, u32, u32, LpScardHandle, LpDword) -> ScardStatus; +pub type SCardReconnectFn = extern "system" fn(ScardHandle, u32, u32, u32, LpDword) -> ScardStatus; +pub type SCardDisconnectFn = unsafe extern "system" fn(ScardHandle, u32) -> ScardStatus; +pub type SCardBeginTransactionFn = unsafe extern "system" fn(ScardHandle) -> ScardStatus; +pub type SCardEndTransactionFn = unsafe extern "system" fn(ScardHandle, u32) -> ScardStatus; +pub type SCardCancelTransactionFn = extern "system" fn(ScardHandle) -> ScardStatus; +pub type SCardStateFn = extern "system" fn(ScardHandle, LpDword, LpDword, LpByte, LpDword) -> ScardStatus; +pub type SCardStatusAFn = + unsafe extern "system" fn(ScardHandle, LpStr, LpDword, LpDword, LpDword, LpByte, LpDword) -> ScardStatus; +pub type SCardStatusWFn = + unsafe extern "system" fn(ScardHandle, LpWStr, LpDword, LpDword, LpDword, LpByte, LpDword) -> ScardStatus; +pub type SCardTransmitFn = unsafe extern "system" fn( + ScardHandle, + LpScardIoRequest, + LpCByte, + u32, + LpScardIoRequest, + LpByte, + LpDword, +) -> ScardStatus; +pub type SCardGetTransmitCountFn = extern "system" fn(ScardHandle, LpDword) -> ScardStatus; +pub type SCardControlFn = + unsafe extern "system" fn(ScardHandle, u32, LpCVoid, u32, LpVoid, u32, LpDword) -> ScardStatus; +pub type SCardGetAttribFn = extern "system" fn(ScardHandle, u32, LpByte, LpDword) -> ScardStatus; +pub type SCardSetAttribFn = extern "system" fn(ScardHandle, u32, LpCByte, u32) -> ScardStatus; +pub type SCardUIDlgSelectCardAFn = extern "system" fn(LpOpenCardNameExA) -> ScardStatus; +pub type SCardUIDlgSelectCardWFn = extern "system" fn(LpOpenCardNameExW) -> ScardStatus; +pub type GetOpenCardNameAFn = extern "system" fn(LpOpenCardNameA) -> ScardStatus; +pub type GetOpenCardNameWFn = extern "system" fn(LpOpenCardNameW) -> ScardStatus; +// Not a part of the standard winscard.h API +pub type GetSCardApiFunctionTableFn = extern "system" fn() -> PSCardApiFunctionTable; + +// https://github.com/FreeRDP/FreeRDP/blob/88f79c5748f4031cb50dfae3ebadcc6619b69f1c/winpr/include/winpr/smartcard.h#L1114 +#[repr(C)] +#[allow(non_snake_case)] +pub struct SCardApiFunctionTable { + pub dw_version: u32, + pub dw_flags: u32, + + pub SCardEstablishContext: SCardEstablishContextFn, + pub SCardReleaseContext: SCardReleaseContextFn, + pub SCardIsValidContext: SCardIsValidContextFn, + pub SCardListReaderGroupsA: SCardListReaderGroupsAFn, + pub SCardListReaderGroupsW: SCardListReaderGroupsWFn, + pub SCardListReadersA: SCardListReadersAFn, + pub SCardListReadersW: SCardListReadersWFn, + pub SCardListCardsA: SCardListCardsAFn, + pub SCardListCardsW: SCardListCardsWFn, + pub SCardListInterfacesA: SCardListInterfacesAFn, + pub SCardListInterfacesW: SCardListInterfacesWFn, + pub SCardGetProviderIdA: SCardGetProviderIdAFn, + pub SCardGetProviderIdW: SCardGetProviderIdWFn, + pub SCardGetCardTypeProviderNameA: SCardGetCardTypeProviderNameAFn, + pub SCardGetCardTypeProviderNameW: SCardGetCardTypeProviderNameWFn, + pub SCardIntroduceReaderGroupA: SCardIntroduceReaderGroupAFn, + pub SCardIntroduceReaderGroupW: SCardIntroduceReaderGroupWFn, + pub SCardForgetReaderGroupA: SCardForgetReaderGroupAFn, + pub SCardForgetReaderGroupW: SCardForgetReaderGroupWFn, + pub SCardIntroduceReaderA: SCardIntroduceReaderAFn, + pub SCardIntroduceReaderW: SCardIntroduceReaderWFn, + pub SCardForgetReaderA: SCardForgetReaderAFn, + pub SCardForgetReaderW: SCardForgetReaderWFn, + pub SCardAddReaderToGroupA: SCardAddReaderToGroupAFn, + pub SCardAddReaderToGroupW: SCardAddReaderToGroupWFn, + pub SCardRemoveReaderFromGroupA: SCardRemoveReaderFromGroupAFn, + pub SCardRemoveReaderFromGroupW: SCardRemoveReaderFromGroupWFn, + pub SCardIntroduceCardTypeA: SCardIntroduceCardTypeAFn, + pub SCardIntroduceCardTypeW: SCardIntroduceCardTypeWFn, + pub SCardSetCardTypeProviderNameA: SCardSetCardTypeProviderNameAFn, + pub SCardSetCardTypeProviderNameW: SCardSetCardTypeProviderNameWFn, + pub SCardFreeMemory: SCardFreeMemoryFn, + pub SCardAccessStartedEvent: SCardAccessStartedEventFn, + pub SCardReleaseStartedEvent: SCardReleaseStartedEventFn, + pub SCardLocateCardsA: SCardLocateCardsAFn, + pub SCardLocateCardsW: SCardLocateCardsWFn, + pub SCardLocateCardsByATRA: SCardLocateCardsByATRAFn, + pub SCardLocateCardsByATRW: SCardLocateCardsByATRWFn, + pub SCardGetStatusChangeA: SCardGetStatusChangeAFn, + pub SCardGetStatusChangeW: SCardGetStatusChangeWFn, + pub SCardCancel: SCardCancelFn, + pub SCardConnectA: SCardConnectAFn, + pub SCardConnectW: SCardConnectWFn, + pub SCardReconnect: SCardReconnectFn, + pub SCardDisconnect: SCardDisconnectFn, + pub SCardBeginTransaction: SCardBeginTransactionFn, + pub SCardEndTransaction: SCardEndTransactionFn, + pub SCardCancelTransaction: SCardCancelTransactionFn, + pub SCardState: SCardStateFn, + pub SCardStatusA: SCardStatusAFn, + pub SCardStatusW: SCardStatusWFn, + pub SCardTransmit: SCardTransmitFn, + pub SCardGetTransmitCount: SCardGetTransmitCountFn, + pub SCardControl: SCardControlFn, + pub SCardGetAttrib: SCardGetAttribFn, + pub SCardSetAttrib: SCardSetAttribFn, + pub SCardUIDlgSelectCardA: SCardUIDlgSelectCardAFn, + pub SCardUIDlgSelectCardW: SCardUIDlgSelectCardWFn, + pub GetOpenCardNameA: GetOpenCardNameAFn, + pub GetOpenCardNameW: GetOpenCardNameWFn, + pub SCardReadCacheA: SCardReadCacheAFn, + pub SCardReadCacheW: SCardReadCacheWFn, + pub SCardWriteCacheA: SCardWriteCacheAFn, + pub SCardWriteCacheW: SCardWriteCacheWFn, + pub SCardGetReaderIconA: SCardGetReaderIconAFn, + pub SCardGetReaderIconW: SCardGetReaderIconWFn, + pub SCardGetDeviceTypeIdA: SCardGetDeviceTypeIdAFn, + pub SCardGetDeviceTypeIdW: SCardGetDeviceTypeIdWFn, + pub SCardGetReaderDeviceInstanceIdA: SCardGetReaderDeviceInstanceIdAFn, + pub SCardGetReaderDeviceInstanceIdW: SCardGetReaderDeviceInstanceIdWFn, + pub SCardListReadersWithDeviceInstanceIdA: SCardListReadersWithDeviceInstanceIdAFn, + pub SCardListReadersWithDeviceInstanceIdW: SCardListReadersWithDeviceInstanceIdWFn, + pub SCardAudit: SCardAuditFn, +} + +pub type PSCardApiFunctionTable = *mut SCardApiFunctionTable; diff --git a/crates/ffi-types/src/winscard/mod.rs b/crates/ffi-types/src/winscard/mod.rs new file mode 100644 index 00000000..1028781c --- /dev/null +++ b/crates/ffi-types/src/winscard/mod.rs @@ -0,0 +1,394 @@ +pub mod functions; + +use crate::common::{Bool, Handle, LpCGuid, LpCStr, LpCWStr, LpStr, LpVoid, LpWStr}; + +pub type ScardStatus = u32; + +pub type ScardContext = usize; +pub type LpScardContext = *mut ScardContext; + +pub type ScardHandle = usize; +pub type LpScardHandle = *mut ScardHandle; +pub type Hwnd = Handle; +pub type Hicon = Handle; +// https://docs.rs/winapi/latest/winapi/um/winscard/type.LPOCNCHKPROC.html +pub type LpOcnChkProc = Option Bool>; +// https://docs.rs/winapi/latest/winapi/um/winscard/type.LPOCNCONNPROCA.html +pub type LpOcnConnProcA = + Option ScardHandle>; +// https://docs.rs/winapi/latest/winapi/um/winscard/type.LPOCNCONNPROCW.html +pub type LpOcnConnProcW = + Option ScardHandle>; +// https://docs.rs/winapi/latest/winapi/um/winscard/type.LPOCNDSCPROC.html +pub type LpOcnDscProc = Option; + +/// [SCARD_READERSTATEA](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-scard_readerstatea) +/// +/// ```not_rut +/// typedef struct { +/// LPCSTR szReader; +/// LPVOID pvUserData; +/// DWORD dwCurrentState; +/// DWORD dwEventState; +/// DWORD cbAtr; +/// BYTE rgbAtr[36]; +/// } SCARD_READERSTATEA, *PSCARD_READERSTATEA, *LPSCARD_READERSTATEA; +/// ``` +#[repr(C)] +pub struct ScardReaderStateA { + pub sz_reader: LpCStr, + pub pv_user_data: LpVoid, + pub dw_current_state: u32, + pub dw_event_state: u32, + pub cb_atr: u32, + pub rgb_atr: [u8; 36], +} + +pub type LpScardReaderStateA = *mut ScardReaderStateA; + +/// [SCARD_READERSTATEW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-scard_readerstatew) +/// +/// ```not_rut +/// typedef struct { +/// LPCWSTR szReader; +/// LPVOID pvUserData; +/// DWORD dwCurrentState; +/// DWORD dwEventState; +/// DWORD cbAtr; +/// BYTE rgbAtr[36]; +/// } SCARD_READERSTATEW, *PSCARD_READERSTATEW, *LPSCARD_READERSTATEW; +/// ``` +#[derive(Debug)] +#[repr(C)] +pub struct ScardReaderStateW { + pub sz_reader: LpCWStr, + pub pv_user_data: LpVoid, + pub dw_current_state: u32, + pub dw_event_state: u32, + pub cb_atr: u32, + pub rgb_atr: [u8; 36], +} + +pub type LpScardReaderStateW = *mut ScardReaderStateW; + +/// [SCARD_ATRMASK](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-scard_atrmask) +/// +/// ```not_rust +/// typedef struct _SCARD_ATRMASK { +/// DWORD cbAtr; +/// BYTE rgbAtr[36]; +/// BYTE rgbMask[36]; +/// } SCARD_ATRMASK, *PSCARD_ATRMASK, *LPSCARD_ATRMASK; +/// ``` +#[repr(C)] +pub struct ScardAtrMask { + cb_atr: u32, + rgb_atr: [u8; 36], + rgb_mask: [u8; 36], +} + +pub type LpScardAtrMask = *mut ScardAtrMask; + +/// [SCARD_IO_REQUEST](https://learn.microsoft.com/en-us/windows/win32/secauthn/scard-io-request) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwProtocol; +/// DWORD cbPciLength; +/// } SCARD_IO_REQUEST; +/// ``` +#[repr(C)] +pub struct ScardIoRequest { + pub dw_protocol: u32, + pub cb_pci_length: u32, +} + +pub type LpScardIoRequest = *mut ScardIoRequest; + +/// [OPENCARD_SEARCH_CRITERIAA](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencard_search_criteriaa) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// LPSTR lpstrGroupNames; +/// DWORD nMaxGroupNames; +/// LPCGUID rgguidInterfaces; +/// DWORD cguidInterfaces; +/// LPSTR lpstrCardNames; +/// DWORD nMaxCardNames; +/// LPOCNCHKPROC lpfnCheck; +/// LPOCNCONNPROCA lpfnConnect; +/// LPOCNDSCPROC lpfnDisconnect; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// } OPENCARD_SEARCH_CRITERIAA, *POPENCARD_SEARCH_CRITERIAA, *LPOPENCARD_SEARCH_CRITERIAA; +/// ``` +#[repr(C)] +pub struct OpenCardSearchCriteriaA { + dw_struct_size: u32, + lpstr_group_names: LpStr, + n_max_group_names: u32, + rgguid_interfaces: LpCGuid, + cguid_interfaces: u32, + lpstr_card_names: LpStr, + n_max_card_names: u32, + lpfn_check: LpOcnChkProc, + lpfn_connect: LpOcnConnProcA, + lpfn_disconnect: LpOcnChkProc, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, +} + +pub type LpOpenCardSearchCriteriaA = *mut OpenCardSearchCriteriaA; + +/// [OPENCARD_SEARCH_CRITERIAW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencard_search_criteriaw) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// LPWSTR lpstrGroupNames; +/// DWORD nMaxGroupNames; +/// LPCGUID rgguidInterfaces; +/// DWORD cguidInterfaces; +/// LPWSTR lpstrCardNames; +/// DWORD nMaxCardNames; +/// LPOCNCHKPROC lpfnCheck; +/// LPOCNCONNPROCW lpfnConnect; +/// LPOCNDSCPROC lpfnDisconnect; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// } OPENCARD_SEARCH_CRITERIAW, *POPENCARD_SEARCH_CRITERIAW, *LPOPENCARD_SEARCH_CRITERIAW; +/// ``` +#[repr(C)] +pub struct OpenCardSearchCriteriaW { + dw_struct_size: u32, + lpstr_group_names: LpWStr, + n_max_group_names: u32, + rgguid_interfaces: LpCGuid, + cguid_interfaces: u32, + lpstr_card_names: LpWStr, + n_max_card_names: u32, + lpfn_check: LpOcnChkProc, + lpfn_connect: LpOcnConnProcW, + lpfn_disconnect: LpOcnChkProc, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, +} + +pub type LpOpenCardSearchCriteriaW = *mut OpenCardSearchCriteriaW; + +/// [OPENCARDNAME_EXA](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencardname_exa) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// SCARDCONTEXT hSCardContext; +/// HWND hwndOwner; +/// DWORD dwFlags; +/// LPCSTR lpstrTitle; +/// LPCSTR lpstrSearchDesc; +/// HICON hIcon; +/// POPENCARD_SEARCH_CRITERIAA pOpenCardSearchCriteria; +/// LPOCNCONNPROCA lpfnConnect; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// LPSTR lpstrRdr; +/// DWORD nMaxRdr; +/// LPSTR lpstrCard; +/// DWORD nMaxCard; +/// DWORD dwActiveProtocol; +/// SCARDHANDLE hCardHandle; +/// } OPENCARDNAME_EXA, *POPENCARDNAME_EXA, *LPOPENCARDNAME_EXA; +/// ``` +#[repr(C)] +pub struct OpenCardNameExA { + dw_struct_size: u32, + h_scard_context: ScardContext, + hwnd_owner: Hwnd, + dw_flags: u32, + lpstr_title: LpCStr, + lpstr_search_sesc: LpCStr, + h_icon: Hicon, + p_open_card_search_criteria: LpOpenCardSearchCriteriaA, + lpfn_connect: LpOcnConnProcA, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, + lpstr_rdr: LpStr, + n_max_rdr: u32, + lpstr_card: LpStr, + n_max_card: u32, + dw_active_protocol: u32, + h_card_handle: ScardHandle, +} + +pub type LpOpenCardNameExA = *mut OpenCardNameExA; + +/// [OPENCARDNAME_EXW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencardname_exw) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// SCARDCONTEXT hSCardContext; +/// HWND hwndOwner; +/// DWORD dwFlags; +/// LPCWSTR lpstrTitle; +/// LPCWSTR lpstrSearchDesc; +/// HICON hIcon; +/// POPENCARD_SEARCH_CRITERIAW pOpenCardSearchCriteria; +/// LPOCNCONNPROCW lpfnConnect; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// LPWSTR lpstrRdr; +/// DWORD nMaxRdr; +/// LPWSTR lpstrCard; +/// DWORD nMaxCard; +/// DWORD dwActiveProtocol; +/// SCARDHANDLE hCardHandle; +/// } OPENCARDNAME_EXW, *POPENCARDNAME_EXW, *LPOPENCARDNAME_EXW; +/// ``` +#[repr(C)] +pub struct OpenCardNameExW { + dw_struct_size: u32, + h_scard_context: ScardContext, + hwnd_owner: Hwnd, + dw_flags: u32, + lpstr_title: LpCWStr, + lpstr_search_sesc: LpCWStr, + h_icon: Hicon, + p_open_card_search_criteria: LpOpenCardSearchCriteriaW, + lpfn_connect: LpOcnConnProcW, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, + lpstr_rdr: LpStr, + n_max_rdr: u32, + lpstr_card: LpStr, + n_max_card: u32, + dw_active_protocol: u32, + h_card_handle: ScardHandle, +} + +pub type LpOpenCardNameExW = *mut OpenCardNameExW; + +/// [OPENCARDNAMEA](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencardnamea) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// HWND hwndOwner; +/// SCARDCONTEXT hSCardContext; +/// LPSTR lpstrGroupNames; +/// DWORD nMaxGroupNames; +/// LPSTR lpstrCardNames; +/// DWORD nMaxCardNames; +/// LPCGUID rgguidInterfaces; +/// DWORD cguidInterfaces; +/// LPSTR lpstrRdr; +/// DWORD nMaxRdr; +/// LPSTR lpstrCard; +/// DWORD nMaxCard; +/// LPCSTR lpstrTitle; +/// DWORD dwFlags; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// DWORD dwActiveProtocol; +/// LPOCNCONNPROCA lpfnConnect; +/// LPOCNCHKPROC lpfnCheck; +/// LPOCNDSCPROC lpfnDisconnect; +/// SCARDHANDLE hCardHandle; +/// } OPENCARDNAMEA, *POPENCARDNAMEA, *LPOPENCARDNAMEA; +/// ``` +#[repr(C)] +pub struct OpenCardNameA { + dw_struct_size: u32, + hwnd_owner: Hwnd, + h_scard_context: ScardContext, + lpstr_group_names: LpStr, + n_max_group_names: u32, + lpstr_card_names: LpStr, + n_max_card_names: u32, + rgguid_interfaces: LpCGuid, + cguid_interfaces: u32, + lpstr_rdr: LpStr, + n_max_rdr: u32, + lpstr_card: LpStr, + n_max_card: u32, + lpstr_title: u32, + dw_flags: u32, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, + dw_active_protocol: u32, + lpfn_connect: LpOcnConnProcA, + lpfn_check: LpOcnChkProc, + lpfn_disconnect: LpOcnDscProc, + h_card_handle: ScardHandle, +} + +pub type LpOpenCardNameA = *mut OpenCardNameA; + +/// [OPENCARDNAMEW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/ns-winscard-opencardnamew) +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwStructSize; +/// HWND hwndOwner; +/// SCARDCONTEXT hSCardContext; +/// LPWSTR lpstrGroupNames; +/// DWORD nMaxGroupNames; +/// LPWSTR lpstrCardNames; +/// DWORD nMaxCardNames; +/// LPCGUID rgguidInterfaces; +/// DWORD cguidInterfaces; +/// LPWSTR lpstrRdr; +/// DWORD nMaxRdr; +/// LPWSTR lpstrCard; +/// DWORD nMaxCard; +/// LPCWSTR lpstrTitle; +/// DWORD dwFlags; +/// LPVOID pvUserData; +/// DWORD dwShareMode; +/// DWORD dwPreferredProtocols; +/// DWORD dwActiveProtocol; +/// LPOCNCONNPROCW lpfnConnect; +/// LPOCNCHKPROC lpfnCheck; +/// LPOCNDSCPROC lpfnDisconnect; +/// SCARDHANDLE hCardHandle; +/// } OPENCARDNAMEW, *POPENCARDNAMEW, *LPOPENCARDNAMEW; +/// ``` +#[repr(C)] +pub struct OpenCardNameW { + dw_struct_size: u32, + hwnd_owner: Hwnd, + h_scard_context: ScardContext, + lpstr_group_names: LpWStr, + n_max_group_names: u32, + lpstr_card_names: LpWStr, + n_max_card_names: u32, + rgguid_interfaces: LpCGuid, + cguid_interfaces: u32, + lpstr_rdr: LpStr, + n_max_rdr: u32, + lpstr_card: LpStr, + n_max_card: u32, + lpstr_title: u32, + dw_flags: u32, + pv_user_data: LpVoid, + dw_share_mode: u32, + dw_preferred_protocols: u32, + dw_active_protocol: u32, + lpfn_connect: LpOcnConnProcW, + lpfn_check: LpOcnChkProc, + lpfn_disconnect: LpOcnDscProc, + h_card_handle: ScardHandle, +} + +pub type LpOpenCardNameW = *mut OpenCardNameW; diff --git a/crates/winscard/Cargo.toml b/crates/winscard/Cargo.toml new file mode 100644 index 00000000..a1bd8206 --- /dev/null +++ b/crates/winscard/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "winscard" +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. "] + +[features] +std = ["tracing/std"] + +[lib] +name = "winscard" + +[dependencies] +bitflags = "2.4.0" +iso7816 = "0.1.1" +iso7816-tlv = "0.4.3" +picky = { version = "7.0.0-rc.8", default-features = false, features = ["x509"] } +picky-asn1-x509 = { version = "0.12.0" } +tracing = { version = "0.1.37", features = ["attributes"], default-features = false } +time = { version = "0.3.28", default-features = false, features = [ + "formatting", +] } +uuid = { version = "1.4.1", features = ["v4"], default-features = false } +flate2 = { version = "1.0.28", features = ["zlib", "rust_backend"], default-features = false } +rsa = { version = "0.9.6", features = ["hazmat", "sha1"], default-features = false } +rand_core = "0.6.4" +sha1 = { version = "0.10.6", default-features = false } + +[dev-dependencies] +proptest = "1.2.0" +rand = "0.8.5" diff --git a/crates/winscard/README.md b/crates/winscard/README.md new file mode 100644 index 00000000..322b6729 --- /dev/null +++ b/crates/winscard/README.md @@ -0,0 +1 @@ +# winscard-rs diff --git a/crates/winscard/assets/reader_icon.bmp b/crates/winscard/assets/reader_icon.bmp new file mode 100644 index 00000000..8d3578be Binary files /dev/null and b/crates/winscard/assets/reader_icon.bmp differ diff --git a/crates/winscard/src/ber_tlv.rs b/crates/winscard/src/ber_tlv.rs new file mode 100644 index 00000000..84f27f3f --- /dev/null +++ b/crates/winscard/src/ber_tlv.rs @@ -0,0 +1,20 @@ +use alloc::vec; +use alloc::vec::Vec; + +/// Encodes the length in BER encoding. +pub fn ber_tlv_length_encoding(length: usize) -> Vec { + // ISO/IEC 7816-4, Section 5.2.2.2 + if length <= 0x7F { + // length consists of 1 byte + vec![length.try_into().unwrap()] + } else { + // if the length is > 0x7F, it consists of N consecutive bytes, where N is the first 7 lower bits of the first byte in the sequence + let mut len_bytes: Vec<_> = length.to_be_bytes().into_iter().skip_while(|x| *x == 0).collect(); + // add the first byte that indicates how many consecutive bytes represent the object's actual length + len_bytes.insert( + 0, + 0x80 | u8::try_from(len_bytes.len()).expect("length bytes amount < u8::MAX"), + ); + len_bytes + } +} diff --git a/crates/winscard/src/card_capability_container.rs b/crates/winscard/src/card_capability_container.rs new file mode 100644 index 00000000..9ad67368 --- /dev/null +++ b/crates/winscard/src/card_capability_container.rs @@ -0,0 +1,43 @@ +use alloc::vec::Vec; + +use crate::tlv_tags; + +// ccc - Card Capability Container +// Table 8. Card Capability Container +// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=39 +pub fn build_ccc() -> Vec { + let mut ccc = Vec::new(); + + ccc.extend_from_slice(&[tlv_tags::DATA, 0x33]); + + // Card Identifier + ccc.extend_from_slice(&[ + 0xf0, 21, 160, 0, 0, 1, 22, 255, 2, 62, 243, 197, 122, 122, 55, 197, 117, 56, 169, 61, 186, 177, 253, + ]); + // Capability Container version number + ccc.extend_from_slice(&[0xf1, 0x01, 0x21]); + // Capability Grammar version number + ccc.extend_from_slice(&[0xf2, 0x01, 0x21]); + // Applications CardURL + ccc.extend_from_slice(&[0xf3, 0x00]); + // PKCS#15 + ccc.extend_from_slice(&[0xf4, 0x01, 0x00]); + // Registered Data Model number + ccc.extend_from_slice(&[0xf5, 0x01, 0x10]); + // Access Control Rule Table + ccc.extend_from_slice(&[0xf6, 0x00]); + // Card APDUs + ccc.extend_from_slice(&[0xf7, 0x00]); + // Redirection Tag + ccc.extend_from_slice(&[0xfa, 0x00]); + // Capability Tuples (CTs) + ccc.extend_from_slice(&[0xfb, 0x00]); + // Status Tuples (STs) + ccc.extend_from_slice(&[0xfc, 0x00]); + // Next CCC + ccc.extend_from_slice(&[0xfd, 0x00]); + // Error Detection Code + ccc.extend_from_slice(&[0xfe, 0x00]); + + ccc +} diff --git a/crates/winscard/src/chuid.rs b/crates/winscard/src/chuid.rs new file mode 100644 index 00000000..7f7c8d22 --- /dev/null +++ b/crates/winscard/src/chuid.rs @@ -0,0 +1,57 @@ +use alloc::format; +use alloc::vec::Vec; + +use time::{format_description, Duration, OffsetDateTime}; +use uuid::Uuid; + +use crate::{tlv_tags, Error, ErrorKind, WinScardResult}; + +// CHUID will always have a fixed length when excluding optional fields and asymmetric signature +pub const CHUID_LENGTH: usize = 61; + +// The CHUID has to be encoded manually because for some weird reason all nested tags use the SIMPLE-TLV encoding. +// This makes it impossible to encode this particular object using iso7816_tlv crate (or any other BER-TLV crate out there) +pub(crate) fn build_chuid() -> WinScardResult<[u8; CHUID_LENGTH]> { + // We do this by hand, because iso7816_tlv uses Vecs when constructing a new TLV value + // By avoiding using Tlv::new(), we can avoid allocating a new Vec for each TLV value and use slices instead + let mut chuid = Vec::with_capacity(CHUID_LENGTH); + let data_length = 0x3B; + chuid.extend_from_slice(&[tlv_tags::DATA, data_length]); + let fasc_n_length = 0x19; + chuid.extend_from_slice(&[tlv_tags::FASC_N, fasc_n_length]); + // The FASC-N number is encoded using the BCD 4-Bit decimal format with odd parity as per https://www.idmanagement.gov/docs/pacs-tig-scepacs.pdf + // The unencoded value is (whitespaces were added for readability): SS 9999 FS 9999 FS 999999 FS 0 FS 1 FS 0000000000 3 0000 1 ES LRC + // The Agency Code is set to 9999 as stated in section 6.4 + // The system code and credential number can both be set to any number + chuid.extend_from_slice(&[ + 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, + 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, + ]); + let guid_length = 0x10; + chuid.extend_from_slice(&[tlv_tags::GUID, guid_length]); + // Section 3.4.1 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf + let uuid = Uuid::new_v4(); + chuid.extend_from_slice(uuid.as_bytes()); + let expiration_date_length = 0x8; + chuid.extend_from_slice(&[tlv_tags::EXPIRATION_DATE, expiration_date_length]); + // Section 3.1.2 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf + let year_from_today = OffsetDateTime::now_utc() + Duration::weeks(48); + let expiration_date_format = format_description::parse("[year][month][day]").map_err(|e| { + Error::new( + ErrorKind::InternalError, + format!("error while trying to parse the date format: {}", e), + ) + })?; + let expiration_date = year_from_today.format(&expiration_date_format).map_err(|e| { + Error::new( + ErrorKind::InternalError, + format!("error while trying to format a date: {}", e), + ) + })?; + chuid.extend_from_slice(expiration_date.as_bytes()); + // both ISSUER_SIGNATURE and EDC don't have any value and both have a length of 0 + chuid.extend_from_slice(&[tlv_tags::ISSUER_SIGNATURE, 0x0]); + chuid.extend_from_slice(&[tlv_tags::ERROR_DETECTION_CODE, 0x0]); + // won't fail, as chuid is of fixed length + Ok(chuid.try_into().unwrap()) +} diff --git a/crates/winscard/src/compression.rs b/crates/winscard/src/compression.rs new file mode 100644 index 00000000..e8560a4a --- /dev/null +++ b/crates/winscard/src/compression.rs @@ -0,0 +1,52 @@ +use alloc::format; +use alloc::vec::Vec; + +use flate2::{Compress, Compression, FlushCompress, Status}; + +use crate::{Error, ErrorKind, WinScardResult}; + +// This function compresses the user-der-encoded certificate using the Zlib algorithm from the flate2 crate. +// We need to compress the certificate before storing it in the smart card cache. +// The resulting array slice corresponds to the compressed certificate in the buffer. +pub fn compress_cert<'c>(cert: &'_ [u8], buff: &'c mut Vec) -> WinScardResult<&'c [u8]> { + let mut data_to_compress = cert; + let mut total_written = 0; + + let mut compressor = Compress::new(Compression::fast(), /* zlib header */ true); + + loop { + let read_before = compressor.total_in() as usize; + let written_before = compressor.total_out() as usize; + + let status = compressor + .compress(data_to_compress, &mut buff[total_written..], FlushCompress::Finish) + .map_err(|err| { + Error::new( + ErrorKind::InternalError, + format!("can not compress sc certificate: {:?}", err), + ) + })?; + + let read_after = compressor.total_in() as usize; + let written_after = compressor.total_out() as usize; + + let read_len = read_after - read_before; + let written_len = written_after - written_before; + + total_written += written_len; + data_to_compress = &data_to_compress[read_len..]; + + match status { + Status::BufError => { + // This case should never happen because usually, buff len is equal to the uncompressed data len. + // But we check it just in case. + let len = buff.len(); + buff.resize(len * 2, 0); + } + Status::StreamEnd => break, + Status::Ok => {} + } + } + + Ok(&buff[0..total_written]) +} diff --git a/crates/winscard/src/dummy_rng.rs b/crates/winscard/src/dummy_rng.rs new file mode 100644 index 00000000..849b913c --- /dev/null +++ b/crates/winscard/src/dummy_rng.rs @@ -0,0 +1,22 @@ +use rand_core::{CryptoRng, Error, RngCore}; + +// We use this generator only as a type parameter for the `rsa::hazmat::rsa_decrypt_and_check` function. +pub struct Dummy; + +impl RngCore for Dummy { + fn next_u32(&mut self) -> u32 { + 0 + } + + fn next_u64(&mut self) -> u64 { + 0 + } + + fn fill_bytes(&mut self, _: &mut [u8]) {} + + fn try_fill_bytes(&mut self, _: &mut [u8]) -> Result<(), Error> { + Ok(()) + } +} + +impl CryptoRng for Dummy {} diff --git a/crates/winscard/src/env.rs b/crates/winscard/src/env.rs new file mode 100644 index 00000000..e1cf6971 --- /dev/null +++ b/crates/winscard/src/env.rs @@ -0,0 +1,10 @@ +/// Emulated smart card PIN code. +pub const WINSCARD_PIN_ENV: &str = "WINSCARD_PIN"; +/// Path to the user certificate to be used in emulated smart card. +pub const WINSCARD_CERT_PATH_ENV: &str = "WINSCARD_CERT_PATH"; +/// Path to the certificate private key. +pub const WINSCARD_PK_PATH_ENV: &str = "WINSCARD_PK_PATH"; +/// Emulated smart card container name. +pub const WINSCARD_CONTAINER_NAME_ENV: &str = "WINSCARD_CONTAINER"; +/// Emulated smart card reader name. +pub const WINSCARD_READER_NAME_ENV: &str = "WINSCARD_READER"; diff --git a/crates/winscard/src/lib.rs b/crates/winscard/src/lib.rs new file mode 100644 index 00000000..c9c50c7a --- /dev/null +++ b/crates/winscard/src/lib.rs @@ -0,0 +1,340 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +#[macro_use] +extern crate tracing; + +#[macro_use] +mod macros; + +mod ber_tlv; +mod card_capability_container; +mod chuid; +mod compression; +mod dummy_rng; +/// Contains env variables names that represent smart card credentials. +#[cfg(feature = "std")] +pub mod env; +mod piv_cert; +mod scard; +mod scard_context; +/// Constants with most popular tags used in this PIV smart card implementation. +pub mod tlv_tags; +/// The [winscard] module contains traits for easier interop between WinSCard API and our emulated scard. +pub mod winscard; + +extern crate alloc; + +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; +use core::num::TryFromIntError; +use core::{fmt, result}; + +pub use ber_tlv::ber_tlv_length_encoding; +use iso7816_tlv::TlvError; +use picky::key::KeyError; +use picky::x509::certificate::CertError; +pub use scard::{SmartCard, ATR, PIV_AID}; +pub use scard_context::{Reader, ScardContext, SmartCardInfo}; + +/// The [WinScardResult] type. +pub type WinScardResult = result::Result; + +/// Represents a response after the APDU command execution. +#[derive(Debug)] +pub struct Response { + /// Resulting APDU status. + pub status: Status, + /// Output APDU. + pub data: Option>, +} + +impl Response { + /// Creates a new [Response] based on the [status] and [data]. + pub fn new(status: Status, data: Option>) -> Self { + Response { status, data } + } +} + +impl From for Response { + fn from(value: Status) -> Self { + Response::new(value, None) + } +} + +impl From for Vec { + fn from(value: Response) -> Self { + let status_as_bytes: [u8; 2] = value.status.into(); + let vec_capacity = status_as_bytes.len() + value.data.as_ref().map(|data| data.len()).unwrap_or(0); + let mut encoded: Vec = Vec::with_capacity(vec_capacity); + if let Some(bytes) = value.data { + encoded.extend(bytes); + } + encoded.extend(status_as_bytes); + encoded + } +} + +/// Represents general WinSCard error. +#[derive(Debug)] +pub struct Error { + /// Represents on the defined [Smart Card Return Values](https://learn.microsoft.com/en-us/windows/win32/secauthn/authentication-return-values). + pub error_kind: ErrorKind, + /// Additional error description. + pub description: String, +} + +impl Error { + /// Creates a new [Error] based on the [error_kind] and [description]. + pub fn new(error_kind: ErrorKind, description: impl Into) -> Self { + Error { + error_kind, + description: description.into(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error{{ {:?}: {} }}", self.error_kind, self.description)?; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl From for Error { + fn from(value: KeyError) -> Self { + Error::new( + ErrorKind::InternalError, + format!("error: an unexpected KeyError happened: {}", value), + ) + } +} + +impl From for Error { + fn from(value: rsa::Error) -> Self { + Error::new( + ErrorKind::InternalError, + format!("Error: an unexpected RsaError happened: {}", value), + ) + } +} + +impl From for Error { + fn from(value: TlvError) -> Self { + Error::new( + ErrorKind::InternalError, + format!("error: an unexpected TlvError happened: {}", value), + ) + } +} + +impl From for Error { + fn from(value: TryFromIntError) -> Self { + Error::new( + ErrorKind::InsufficientBuffer, + format!("error: can not convert integers: {}", value), + ) + } +} + +impl From for Error { + fn from(value: CertError) -> Self { + Error::new(ErrorKind::InsufficientBuffer, format!("certificate error: {}", value)) + } +} + +/// [Smart Card Return Values](https://learn.microsoft.com/en-us/windows/win32/secauthn/authentication-return-values). +#[derive(Debug, PartialEq)] +#[repr(u32)] +pub enum ErrorKind { + /// The client attempted a smart card operation in a remote session, such as a client session running on a terminal server, + /// and the operating system in use does not support smart card redirection. + BrokenPipe = 0x00000109, + /// An error occurred in setting the smart card file object pointer. + BadSeek = 0x80100029, + /// The action was canceled by an SCardCancel request. + Canceled = 0x80100002, + /// The system could not dispose of the media in the requested manner. + CantDispose = 0x8010000E, + /// The smart card does not meet minimal requirements for support. + CardUnsupported = 0x8010001C, + /// The requested certificate could not be obtained. + CertificateUnavailable = 0x8010002D, + /// A communications error with the smart card has been detected. + CommDataLost = 0x8010002F, + /// The specified directory does not exist in the smart card. + DirNotFound = 0x80100023, + /// The reader driver did not produce a unique reader name. + DuplicateReader = 0x8010001B, + /// The specified file does not exist in the smart card. + FileNotFound = 0x80100024, + /// The requested order of object creation is not supported. + IccCreateOrder = 0x80100021, + /// No primary provider can be found for the smart card. + IccInstallation = 0x80100020, + /// The data buffer for returned data is too small for the returned data. + InsufficientBuffer = 0x80100008, + /// An ATR string obtained from the registry is not a valid ATR string. + InvalidAtr = 0x80100015, + /// The supplied PIN is incorrect. + InvalidChv = 0x8010002A, + /// The supplied handle was not valid. + InvalidHandle = 0x80100003, + /// One or more of the supplied parameters could not be properly interpreted. + InvalidParameter = 0x80100004, + /// Registry startup information is missing or not valid. + InvalidTarget = 0x80100005, + /// One or more of the supplied parameter values could not be properly interpreted. + InvalidValue = 0x80100011, + /// Access is denied to the file. + NoAccess = 0x80100027, + /// The supplied path does not represent a smart card directory. + NoDir = 0x80100025, + /// The supplied path does not represent a smart card file. + NoFile = 0x80100026, + /// The requested key container does not exist on the smart card. + NoKeyContainer = 0x80100030, + /// Not enough memory available to complete this command. + NoMemory = 0x80100006, + /// The smart card PIN cannot be cached. + NoPinCache = 0x80100033, + /// No smart card reader is available. + NoReadersAvailable = 0x8010002E, + /// The smart card resource manager is not running. + NoService = 0x8010001D, + /// The operation requires a smart card, but no smart card is currently in the device. + NoSmartCard = 0x8010000C, + /// The requested certificate does not exist. + NoSuchCertificate = 0x8010002C, + /// The reader or card is not ready to accept commands. + NotReady = 0x80100010, + /// An attempt was made to end a nonexistent transaction. + NotTransacted = 0x80100016, + /// The PCI receive buffer was too small. + PciTooSmall = 0x80100019, + /// The smart card PIN cache has expired. + PinCacheExpired = 0x80100032, + /// The requested protocols are incompatible with the protocol currently in use with the card. + ProtoMismatch = 0x8010000F, + /// The smart card is read-only and cannot be written to. + ReadOnlyCard = 0x80100034, + /// The specified reader is not currently available for use. + ReaderUnavailable = 0x80100017, + /// The reader driver does not meet minimal requirements for support. + ReaderUnsupported = 0x8010001A, + /// The smart card resource manager is too busy to complete this operation. + ServerTooBusy = 0x80100031, + /// The smart card resource manager has shut down. + ServiceStopped = 0x8010001E, + /// The smart card cannot be accessed because of other outstanding connections. + SharingViolation = 0x8010000B, + /// The action was canceled by the system, presumably to log off or shut down. + SystemCanceled = 0x80100012, + /// The user-specified time-out value has expired. + Timeout = 0x8010000A, + /// An unexpected card error has occurred. + Unexpected = 0x8010001F, + /// The specified smart card name is not recognized. + UnknownCard = 0x8010000D, + /// The specified reader name is not recognized. + UnknownReader = 0x80100009, + /// An unrecognized error code was returned. + UnknownResMng = 0x8010002B, + /// This smart card does not support the requested feature. + UnsupportedFeature = 0x80100022, + /// An attempt was made to write more data than would fit in the target object. + WriteTooMany = 0x80100028, + /// An internal communications error has been detected. + CommError = 0x80100013, + /// An internal consistency check failed. + InternalError = 0x80100001, + /// An internal error has been detected, but the source is unknown. + UnknownError = 0x80100014, + /// An internal consistency timer has expired. + WaitedTooLong = 0x80100007, + /// The operation has been aborted to allow the server application to exit. + Shutdown = 0x80100018, + /// No error was encountered. + Success = 0, + /// The action was canceled by the user. + CanceledByUser = 0x8010006E, + /// The requested item could not be found in the cache. + CacheItemNotFound = 0x80100070, + /// The requested cache item is too old and was deleted from the cache. + CacheItemStale = 0x80100071, + /// The new cache item exceeds the maximum per-item size defined for the cache. + CacheItemTooBig = 0x80100072, + /// No PIN was presented to the smart card. + CardNotAuthenticated = 0x8010006F, + /// The card cannot be accessed because the maximum number of PIN entry attempts has been reached. + ChvBlocked = 0x8010006C, + /// The end of the smart card file has been reached. + Eof = 0x8010006D, + /// The smart card has been removed, so further communication is not possible. + RemovedCard = 0x80100069, + /// The smart card was reset. + ResetCard = 0x80100068, + /// Access was denied because of a security violation. + SecurityViolation = 0x8010006A, + /// Power has been removed from the smart card, so that further communication is not possible. + UnpoweredCard = 0x80100067, + /// The smart card is not responding to a reset. + UnresponsiveCard = 0x80100066, + /// The reader cannot communicate with the card, due to ATR string configuration conflicts. + UnsupportedCard = 0x80100065, + /// The card cannot be accessed because the wrong PIN was presented. + WrongChv = 0x8010006B, +} + +impl From for u32 { + fn from(value: ErrorKind) -> Self { + value as u32 + } +} + +/// Represents Status Word (SW) - a 2-byte value returned by a card command at the card edge. +/// [Table 6. Status Words](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36) +#[derive(Debug, Clone, PartialEq)] +pub enum Status { + /// Data object or application not found. + NotFound, + /// Successful execution. + OK, + /// Verification failed, X indicates the number of further allowed retries or resets. + /// Number of allowed retries is always 9. + VerificationFailedWithRetries, + /// Successful execution where SW2 encodes the number of response data bytes still available. + MoreAvailable(u8), + /// Referenced data or reference data not found. + KeyReferenceNotFound, + /// Security status not satisfied. + SecurityStatusNotSatisfied, + /// Incorrect parameter in P1 or P2. + IncorrectP1orP2, + /// Incorrect parameter in command data field. + IncorrectDataField, + /// Instruction code not supported or invalid. + InstructionNotSupported, +} + +// ISO/IEC 7816-4, Section 5.1.3, Tables 5-6 +impl From for [u8; 2] { + fn from(value: Status) -> Self { + match value { + Status::NotFound => [0x6A, 0x82], + Status::OK => [0x90, 0x00], + Status::VerificationFailedWithRetries => [0x63, 0xC9], + Status::MoreAvailable(bytes_left) => [0x61, bytes_left], + Status::KeyReferenceNotFound => [0x6A, 0x88], + Status::SecurityStatusNotSatisfied => [0x69, 0x82], + Status::IncorrectP1orP2 => [0x6A, 0x86], + Status::IncorrectDataField => [0x6A, 0x80], + Status::InstructionNotSupported => [0x6D, 0x00], + } + } +} diff --git a/crates/winscard/src/macros.rs b/crates/winscard/src/macros.rs new file mode 100644 index 00000000..4d2a6d5a --- /dev/null +++ b/crates/winscard/src/macros.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "std")] +macro_rules! env { + ($name:expr) => {{ + std::env::var($name).map_err(|_| { + crate::Error::new( + crate::ErrorKind::InvalidParameter, + format!("The {} env var is not present or invalid", $name), + ) + }) + }}; +} diff --git a/crates/winscard/src/piv_cert.rs b/crates/winscard/src/piv_cert.rs new file mode 100644 index 00000000..d74ac54e --- /dev/null +++ b/crates/winscard/src/piv_cert.rs @@ -0,0 +1,32 @@ +use alloc::vec::Vec; + +use iso7816_tlv::simple::{Tag, Tlv}; + +use crate::ber_tlv::ber_tlv_length_encoding; +use crate::{tlv_tags, WinScardResult}; + +// The X.509 Certificate for PIV Authentication has to be encoded manually because for some weird reason all nested tags use the SIMPLE-TLV encoding. +// This makes it impossible to encode this particular object using iso7816_tlv crate (or any other BER-TLV crate out there) +pub(crate) fn build_auth_cert(auth_cert: Vec) -> WinScardResult> { + // SIMPLE-TLV encoding + // We do use Tlv::new() here to avoid calculating SIMPLE-TLV length ourselves (as a certificate is most certainly > 254 bytes in length) + let certificate = Tlv::new(Tag::try_from(tlv_tags::CERTIFICATE)?, auth_cert)?.to_vec(); + let cert_info_length = 0x01; + // 0x00 indicates that the certificate is uncompressed + // NIST.SP.800-73-4, Part 1, Appendix A, table 39 + let cert_info_value = 0x00; + let cert_info = &[tlv_tags::CERT_INFO, cert_info_length, cert_info_value]; + // NIST.SP.800-73-4, Part 1, Appendix A, table 10 + let edc_length = 0x00; + let edc = &[tlv_tags::ERROR_DETECTION_CODE, edc_length]; + let data_value_len = certificate.len() + cert_info.len() + edc.len(); + // BER-TLV encoding of the data_value_len + let encoded_data_value_len = ber_tlv_length_encoding(data_value_len); + let mut result: Vec = Vec::with_capacity(1 + encoded_data_value_len.len() + data_value_len); + result.extend_from_slice(&[tlv_tags::DATA]); + result.extend_from_slice(&encoded_data_value_len); + result.extend_from_slice(&certificate); + result.extend_from_slice(cert_info); + result.extend_from_slice(edc); + Ok(result) +} diff --git a/crates/winscard/src/scard.rs b/crates/winscard/src/scard.rs new file mode 100644 index 00000000..93f7f373 --- /dev/null +++ b/crates/winscard/src/scard.rs @@ -0,0 +1,937 @@ +use alloc::borrow::Cow; +use alloc::string::ToString; +use alloc::vec::Vec; +use alloc::{format, vec}; + +use iso7816::{Aid, Command, Instruction}; +use iso7816_tlv::ber::{Tag, Tlv, Value}; +use picky::key::PrivateKey; +use rsa::traits::PublicKeyParts; +use rsa::{Pkcs1v15Sign, RsaPrivateKey}; +use sha1::Sha1; + +use crate::card_capability_container::build_ccc; +use crate::chuid::{build_chuid, CHUID_LENGTH}; +use crate::piv_cert::build_auth_cert; +use crate::winscard::{ControlCode, IoRequest, TransmitOutData, WinScard}; +use crate::{tlv_tags, winscard, Error, ErrorKind, Response, Status, WinScardResult}; + +/// [NIST.SP.800-73-4, part 1, section 2.2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=16). +pub const PIV_AID: Aid = Aid::new_truncatable(&[0xA0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00], 9); +// the max amount of data one APDU response can transmit +const CHUNK_SIZE: usize = 256; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const CARD_AUTH_CERT_TAG: &[u8] = &[0x5F, 0xC1, 0x01]; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const CHUID_TAG: &[u8] = &[0x5F, 0xC1, 0x02]; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const PIV_CERT_TAG: &[u8] = &[0x5F, 0xC1, 0x05]; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const CARD_CAPABILITY_CONTAINER_TAG: &[u8] = &[0x5F, 0xC1, 0x07]; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const DIGITAL_SIGNATURE_CERT_TAG: &[u8] = &[0x5F, 0xC1, 0x0A]; +// NIST.SP.800-73-4, part 1, section 4.3, Table 3 +const KEY_MANAGEMENT_CERT_TAG: &[u8] = &[0x5F, 0xC1, 0x0B]; +// NIST.SP.800-73-4 part 2, section 2.4.3 +const PIN_LENGTH_RANGE_LOW_BOUND: usize = 6; +// NIST.SP.800-73-4 part 2, section 2.4.3 +const PIN_LENGTH_RANGE_HIGH_BOUND: usize = 8; + +/// The original winscard ATR is not suitable because it contains AID bytes. +/// So we need to construct our own. Read more about our constructed ATR string: +/// https://smartcard-atr.apdu.fr/parse?ATR=3B+8D+01+80+FB+A0+00+00+03+08+00+00+10+00+01+00+4D +#[rustfmt::skip] +pub const ATR: [u8; 17] = [ + // TS. Direct Convention + 0x3b, + // T0. Y(1): b1000, K: 13 (historical bytes) + 0x8d, + // TD. Y(i+1) = b0000, Protocol T=1 + 0x01, + // Historical bytes + 0x80, + // Tag: 15, Len: 11. + 0xfb, + // PIV AID + 0xa0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + // TCK (Checksum) + 0x4d, +]; + +/// Emulated smart card. +/// +/// Currently, we support one key container per smart card. +#[derive(Debug, Clone)] +pub struct SmartCard<'a> { + reader_name: Cow<'a, str>, + chuid: [u8; CHUID_LENGTH], + ccc: Vec, + pin: Vec, + auth_cert: Vec, + auth_pk: PrivateKey, + state: SCardState, + // We don't need to track actual transactions for the emulated smart card. + // We are using this flag to track incorrect smart card usage. + transaction: bool, + pending_command: Option>, + pending_response: Option>, +} + +impl SmartCard<'_> { + /// Creates a smart card instance based on the provided data. + pub fn new( + reader_name: Cow, + pin: Vec, + auth_cert_der: Vec, + auth_pk: PrivateKey, + ) -> WinScardResult> { + let chuid = build_chuid()?; + let auth_cert = build_auth_cert(auth_cert_der)?; + + Ok(SmartCard { + reader_name, + chuid, + ccc: build_ccc(), + pin: SmartCard::validate_and_pad_pin(pin)?, + auth_cert, + auth_pk, + state: SCardState::Ready, + transaction: false, + pending_command: None, + pending_response: None, + }) + } + + fn validate_and_pad_pin(pin: Vec) -> WinScardResult> { + // All PIN requirements can be found here: NIST.SP.800-73-4 part 2, section 2.4.3 + if !(PIN_LENGTH_RANGE_LOW_BOUND..=PIN_LENGTH_RANGE_HIGH_BOUND).contains(&pin.len()) { + return Err(Error::new( + ErrorKind::InvalidValue, + "PIN should be no shorter than 6 bytes and no longer than 8", + )); + } + if pin.iter().any(|byte| !byte.is_ascii_digit()) { + return Err(Error::new( + ErrorKind::InvalidValue, + "PIN should consist only of ASCII values representing decimal digits (0-9)", + )); + }; + + Ok(Self::pad_pin(pin)) + } + + fn pad_pin(mut pin: Vec) -> Vec { + if pin.len() < PIN_LENGTH_RANGE_HIGH_BOUND { + // NIST.SP.800-73-4 part 2, section 2.4.3 + const PIN_PAD_VALUE: u8 = 0xFF; + pin.resize(PIN_LENGTH_RANGE_HIGH_BOUND, PIN_PAD_VALUE); + } + + pin + } + + /// This functions handles one APDU command. + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + pub fn handle_command(&mut self, data: &[u8]) -> WinScardResult { + let cmd = Command::<1024>::try_from(data).map_err(|error| { + error!(?error, "APDU command parsing error"); + Error::new( + ErrorKind::InternalError, + format!("error: an error happened while parsing an APDU command: {:?}", error), + ) + })?; + let cmd = if let Some(mut chained) = self.pending_command.take() { + chained.extend_from_command(&cmd).map_err(|_| { + Error::new( + ErrorKind::InternalError, + "error: an error happened while trying to build a chained APDU command", + ) + })?; + chained + } else { + cmd + }; + + if cmd.class().chain().not_the_last() { + self.pending_command = Some(cmd); + return Ok(Status::OK.into()); + } + if self.state == SCardState::Ready && cmd.instruction() != Instruction::Select { + // if the application wasn't selected, only the SELECT command can be used + return Ok(Status::NotFound.into()); + } else if self.state == SCardState::PivAppSelected && cmd.instruction() == Instruction::GeneralAuthenticate { + // GENERAL AUTHENTICATE can only be used if the smart card has already been unlocked using the PIN code + return Ok(Status::SecurityStatusNotSatisfied.into()); + } + match cmd.instruction() { + Instruction::Select => self.select(cmd), + Instruction::GetData => self.get_data(cmd), + Instruction::Verify => self.verify(cmd), + Instruction::GeneralAuthenticate => self.general_authenticate(cmd), + Instruction::GetResponse => self.get_response(), + _ => { + error!(instruction = ?cmd.instruction(), "unimplemented instruction"); + Ok(Status::InstructionNotSupported.into()) + } + } + } + + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + fn select(&mut self, cmd: Command<1024>) -> WinScardResult { + // NIST.SP.800-73-4, Part 2, Section 3.1.1 + // PIV SELECT command + // CLA - 0x00 + // INS - 0xA4 + // P1 - 0x04 + // P2 - 0x00 + + // ISO/IEC 7816-4, Section 7.1.1, Table 39 + const APPLICATION_IDENTIFIER: u8 = 0x04; + // ISO/IEC 7816-4, Section 7.1.1, Table 40 + const FIRST_OR_ONLY_OCCURRENCE: u8 = 0x00; + + if cmd.p1 != APPLICATION_IDENTIFIER || cmd.p2 != FIRST_OR_ONLY_OCCURRENCE || !PIV_AID.matches(cmd.data()) { + return Ok(Status::NotFound.into()); + } + let data = Tlv::new( + Tag::try_from(tlv_tags::APPLICATION_PROPERTY_TEMPLATE)?, + Value::Constructed(vec![ + Tlv::new( + Tag::try_from(tlv_tags::APPLICATION_IDENTIFIER)?, + // application portion + version portion of the PIV AID + // NIST.SP.800-73-4 Part 1, section 2.2 + Value::Primitive(vec![0x00, 0x00, 0x10, 0x00, 0x01, 0x00]), + )?, + Tlv::new( + Tag::try_from(tlv_tags::COEXISTING_TAG_ALLOCATION_AUTHORITY)?, + Value::Constructed(vec![Tlv::new( + Tag::try_from(tlv_tags::APPLICATION_IDENTIFIER)?, + Value::Primitive(PIV_AID.to_vec()), + )?]), + )?, + ]), + )?; + self.state = SCardState::PivAppSelected; + Ok(Response::new(Status::OK, Some(data.to_vec()))) + } + + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + fn verify(&mut self, cmd: Command<1024>) -> WinScardResult { + // NIST.SP.800-73-4, Part 2, Section 3.2.1 + // PIV VERIFY command + // CLA - 0x00 + // INS - 0x20 + // P1 - 0x00 | 0xFF + // P2 - 0x80 + // Data - PIN + // + // If P1 is 0xFF, the Data field should be empty + + // ISO/IEC 7816-4, Section 7.5.1 + const NO_IDENTIFIER: u8 = 0x00; + // NIST.SP.800-73-4, Part 2, Section 3.2.1 + const RESET_SECURITY_STATUS: u8 = 0xFF; + // ISO/IEC 7816-4, Section 7.5.1, Table 65 + const SPECIFIC_REFERENCE_DATA: u8 = 0x80; + + if cmd.p1 == RESET_SECURITY_STATUS && !cmd.data().is_empty() { + return Ok(Status::IncorrectP1orP2.into()); + } + if cmd.p2 != SPECIFIC_REFERENCE_DATA { + return Ok(Status::KeyReferenceNotFound.into()); + } + match cmd.p1 { + NO_IDENTIFIER => { + // PIN was already verified -> return OK + if self.state != SCardState::PinVerified { + if !cmd.data().is_empty() + && !(PIN_LENGTH_RANGE_LOW_BOUND..=PIN_LENGTH_RANGE_HIGH_BOUND).contains(&cmd.data().len()) + { + // Incorrect PIN length -> do not proceed and return an error + return Ok(Status::IncorrectDataField.into()); + } + // Retrieve the number of further allowed retries if the data field is absent + // Otherwise just compare the provided PIN with the stored one + if cmd.data().is_empty() || cmd.data() != self.pin.as_slice() { + return Ok(Status::VerificationFailedWithRetries.into()); + } else { + // data field is present and the provided PIN is correct -> change state and return OK + self.state = SCardState::PinVerified; + } + } + } + RESET_SECURITY_STATUS => { + // p1 is 0xFF and the data field is absent -> reset the security status and return OK + self.state = SCardState::PivAppSelected; + } + _ => return Ok(Status::IncorrectP1orP2.into()), + }; + Ok(Status::OK.into()) + } + + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + fn get_data(&mut self, cmd: Command<1024>) -> WinScardResult { + // NIST.SP.800-73-4, Part 2, Section 3.1.2 + // PIV GET DATA command + // CLA - 0x00 + // INS - 0xCB + // P1 - 0x3F + // P2 - 0xFF + // Data - a single BER-TLV tag of the data object to be retrieved + // + // Our PIV smart card only supports: + // 5FC102 - Card Holder Unique Identifier + // 5FC105 - X.509 Certificate for PIV Authentication + + // ISO/IEC 7816-4, Section 7.4.1 + const FIRST_BYTE_OF_CURRENT_DF: u8 = 0x3F; + const SECOND_BYTE_OF_CURRENT_DF: u8 = 0xFF; + + if cmd.p1 != FIRST_BYTE_OF_CURRENT_DF || cmd.p2 != SECOND_BYTE_OF_CURRENT_DF { + return Ok(Status::IncorrectP1orP2.into()); + } + let request = Tlv::from_bytes(cmd.data())?; + if request.tag() != &Tag::try_from(tlv_tags::TAG_LIST)? { + return Ok(Status::NotFound.into()); + } + + match request.value() { + Value::Primitive(tag) => match tag.as_slice() { + CHUID_TAG => Ok(Response::new(Status::OK, Some(self.chuid.to_vec()))), + PIV_CERT_TAG | CARD_AUTH_CERT_TAG | KEY_MANAGEMENT_CERT_TAG | DIGITAL_SIGNATURE_CERT_TAG => { + // certificate is almost certainly longer than 256 bytes, so we can just set a pending response and call the GET RESPONSE handler + self.pending_response = Some(self.auth_cert.clone()); + self.get_response() + } + CARD_CAPABILITY_CONTAINER_TAG => Ok(Response::new(Status::OK, Some(self.ccc.clone()))), + _ => Ok(Status::NotFound.into()), + }, + Value::Constructed(_) => Ok(Status::NotFound.into()), + } + } + + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + fn get_response(&mut self) -> WinScardResult { + // ISO/IEC 7816-4, Section 7.6.1 + // The smart card uses the standard (short) APDU response form, so the maximum amount of data transferred in one response is 256 bytes + match self.get_next_response_chunk() { + Some((chunk, bytes_left)) => { + let status = if bytes_left == 0 { + self.pending_response = None; + Status::OK + } else if bytes_left < CHUNK_SIZE { + // conversion is safe as we know that bytes_left isn't bigger than 256 + Status::MoreAvailable(bytes_left.try_into().unwrap()) + } else { + // 0 indicates that we have 256 or more bytes left to be read + Status::MoreAvailable(0) + }; + Ok(Response::new(status, Some(chunk))) + } + None => Ok(Status::NotFound.into()), + } + } + + #[instrument(level = "debug", ret, fields(state = ?self.state), skip(self))] + fn general_authenticate(&mut self, cmd: Command<1024>) -> WinScardResult { + // NIST.SP.800-73-4, Part 2, Section 3.2.4 + // PIV GENERAL AUTHENTICATE command + // CLA - 0x00 | 0x10 (command chaining) + // INS - 0x87 + // P1 - 0x07 - RSA + // P2 - 0x9A - PIV Authentication Key + // Data - Dynamic Authentication Template with Challenge inside + // + // There are many possible P1 and P2 values in this command, but our smart card only supports the RSA algorithm and data signing using the PIV Authentication Key + + // NIST.SP.800-73-4, Part 1, Table 5 + const RSA_ALGORITHM: u8 = 0x07; + // NIST.SP.800-73-4, Part 1, Table 4b + const PIV_DIGITAL_SIGNATURE_KEY: u8 = 0x9C; + + if cmd.p1 != RSA_ALGORITHM || cmd.p2 != PIV_DIGITAL_SIGNATURE_KEY { + return Err(Error::new( + ErrorKind::UnsupportedFeature, + format!("Provided algorithm or key reference isn't supported: got algorithm {:x}, expected 0x07; got key reference {:x}, expected 0x9A", cmd.p1, cmd.p2) + )); + } + let request = Tlv::from_bytes(cmd.data())?; + if request.tag() != &Tag::try_from(tlv_tags::DYNAMIC_AUTHENTICATION_TEMPLATE)? + || !request.value().is_constructed() + { + // wrong TLV request structure + return Err(Error::new( + ErrorKind::InvalidValue, + "TLV structure is invalid: wrong top-level tag structure".to_string(), + )); + } + let inner_tlv = match request.value() { + // we already know that the value is constructed at this point + Value::Primitive(_) => unreachable!(), + Value::Constructed(tlv_vec) => tlv_vec, + }; + // to avoid constructing the tag on each iteration + let challenge_tag = Tag::try_from(tlv_tags::DAT_CHALLENGE)?; + let challenge = inner_tlv + .iter() + .find(|&tlv| tlv.tag() == &challenge_tag) + .ok_or(Error::new( + ErrorKind::InvalidValue, + "TLV structure is invalid: no challenge field is present in the request".to_string(), + ))?; + // Signature creation is described in NIST.SP.800-73-4, Part 2, Appendix A, Sections A.1-3 and Section A.4.1 + let challenge = match challenge.value() { + Value::Primitive(ref challenge) => challenge, + Value::Constructed(_) => { + // this tag must contain a primitive value + return Err(Error::new( + ErrorKind::InvalidValue, + "TLV structure is invalid: challenge field contains constructed value".to_string(), + )); + } + }; + let signed_challenge = self.sign_padded(challenge)?; + let response = Tlv::new( + Tag::try_from(tlv_tags::DYNAMIC_AUTHENTICATION_TEMPLATE)?, + Value::Constructed(vec![Tlv::new( + Tag::try_from(tlv_tags::DAT_RESPONSE)?, + Value::Primitive(signed_challenge), + )?]), + )? + .to_vec(); + self.pending_response = Some(response); + self.get_response() + } + + fn sign_padded(&self, data: impl AsRef<[u8]>) -> WinScardResult> { + use rsa::BigUint; + + let rsa_private_key = RsaPrivateKey::try_from(&self.auth_pk)?; + // According to the specification, the PIV smart card accepts already padded digest. + // So, it's safe to use the `rsa_decrypt_and_check` function here. + let signature = rsa::hazmat::rsa_decrypt_and_check( + &rsa_private_key, + None::<&mut crate::dummy_rng::Dummy>, + &BigUint::from_bytes_be(data.as_ref()), + )?; + + let mut signature = signature.to_bytes_be(); + + while signature.len() < rsa_private_key.size() { + signature.insert(0, 0); + } + + Ok(signature) + } + + /// Signs the provided data using the smart card private key. + /// *Warning 1*. The input data should be a *SHA1* hash of the actually you want to sign. + pub fn sign_hashed(&self, data: impl AsRef<[u8]>) -> WinScardResult> { + let rsa_private_key = RsaPrivateKey::try_from(&self.auth_pk)?; + let signature = rsa_private_key.sign(Pkcs1v15Sign::new::(), data.as_ref())?; + + Ok(signature) + } + + /// Verifies the PIN code. This method alters the scard state. + pub fn verify_pin(&mut self, pin: &[u8]) -> WinScardResult<()> { + if self.pin != Self::pad_pin(pin.into()) { + return Err(Error::new( + ErrorKind::InvalidValue, + "PIN verification error: Invalid PIN", + )); + } + + self.state = SCardState::PinVerified; + + Ok(()) + } + + fn get_next_response_chunk(&mut self) -> Option<(Vec, usize)> { + let vec = self.pending_response.as_mut()?; + if vec.is_empty() { + return None; + } + let next_chunk_length = CHUNK_SIZE.min(vec.len()); + let chunk = vec.drain(0..next_chunk_length).collect::>(); + Some((chunk, vec.len())) + } +} + +#[derive(Debug, Clone, PartialEq)] +enum SCardState { + Ready, + PivAppSelected, + PinVerified, +} + +impl<'a> WinScard for SmartCard<'a> { + fn status(&self) -> WinScardResult { + Ok(winscard::Status { + readers: vec![self.reader_name.clone()], + // The original winscard always returns SCARD_SPECIFIC for a working inserted card + state: winscard::State::Specific, + // We are always using the T1 protocol as the original Windows TPM smart card does + protocol: winscard::Protocol::T1, + atr: ATR.into(), + }) + } + + fn control(&mut self, code: ControlCode, _input: &[u8]) -> WinScardResult> { + if code != ControlCode::IoCtl { + return Err(Error::new( + ErrorKind::InvalidValue, + format!("unsupported control code: {:?}", code), + )); + } + + Ok(Vec::new()) + } + + fn transmit(&mut self, _send_pci: IoRequest, input_apdu: &[u8]) -> WinScardResult { + let Response { status, data } = self.handle_command(input_apdu)?; + + let mut output_apdu = data.unwrap_or_default(); + let status_data: [u8; 2] = status.into(); + output_apdu.extend_from_slice(&status_data); + + Ok(TransmitOutData { + output_apdu, + receive_pci: None, + }) + } + + fn begin_transaction(&mut self) -> WinScardResult<()> { + if self.transaction { + return Err(Error::new( + ErrorKind::InternalError, + "the transaction already in progress", + )); + } + self.transaction = true; + Ok(()) + } + + fn end_transaction(&mut self) -> WinScardResult<()> { + if !self.transaction { + return Err(Error::new(ErrorKind::NotTransacted, "the transaction is not started")); + } + self.transaction = false; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + extern crate std; + + use picky::hash::HashAlgorithm; + use picky::signature::SignatureAlgorithm; + use proptest::prelude::*; + use proptest::{collection, option, prop_compose}; + use rand::distributions::Uniform; + use rand::Rng; + use rsa::traits::PublicKeyParts; + use rsa::BigUint; + + use super::*; + use crate::ber_tlv::ber_tlv_length_encoding; + + fn arb_status() -> impl Strategy { + prop_oneof![ + Just(Status::NotFound), + Just(Status::OK), + Just(Status::VerificationFailedWithRetries), + any::().prop_map(Status::MoreAvailable), + Just(Status::KeyReferenceNotFound), + Just(Status::SecurityStatusNotSatisfied), + Just(Status::IncorrectP1orP2), + Just(Status::IncorrectDataField), + Just(Status::InstructionNotSupported) + ] + } + + prop_compose! { + fn arb_response()(status in arb_status(), data in option::of(collection::vec(any::(), 0..256))) -> Response { + Response::new(status, data) + } + } + + proptest! { + #[test] + fn response_is_encoded_correctly(arb_response in arb_response()) { + let data = arb_response.data.clone(); + let status: [u8; 2] = arb_response.status.clone().into(); + let expected_result = if let Some(mut bytes) = data { + bytes.extend(status); + bytes + } else { + Vec::from(status) + }; + assert_eq!(expected_result, Vec::from(arb_response)); + } + } + + fn new_scard() -> SmartCard<'static> { + let rsa_2048_private_key = "-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAiJ/d1/2d1CQYlJfZ02TOH7F/5U53a6IZc8QwTQEsBQbVGfQO +RN/+b09NzJJZmtyuLdBAXLzP8lEzKcfgn4JNl5G7DuKOxRreE5tq8uA+j2SQCw7m +Sm6todEOvkWG5Dov3Q9QnlPbvqp871pfbRsfKwOo2RxJIjbjpM5FQnlqOd+3gu2I +TF8dt+/PY+wl1w6kPTUZg/mzElY95WSnOE9bFlHcVL//Sl3caW85AB0lLUbd96b/ +7PMO6IWJQyvS0ssG0emcyQYllvvSCFSpVWA/e1EGzKrwbtG1Xn9je5L4mIKiSw/p +gbjnYE9g+pibLJNobBBLkzGdo/KzyCQbMWirkQIDAQABAoIBAEbAm28mXNymkMAq +31g1BPWuwy/p8bggqxOjjuvh5nz369XT6KvMYAQeyohdZd/n1p/ND/e2o+22FUvW +wcF5Bluu0XNE6nCymD0JKFp8vIkfp+TCI4p6RJrfG8Z3VQLOC0lsi/BiNxNHUQnX +AEINYJey/nboygrY6AzJ8V4aaGNtbtnz7tfyALJHUK0qRa+AmyLCzaZR5RSbDgB5 +srCX9J5OCxH2s5tVSfqg48Z0RIiBcDFPYbJDakZWLRNLD8ByW3e0jEFDA1vQPsaj +CsyY4E6UZwYNZemC60zW0e8BYJYnOAhcmwaYnaxvL5xy0aW5pUGr+FgnO4NrNr33 +pKT2eFECgYEA2LJdjjFGdTsuW8esbTn+9hGyNnUR9gxYGdNhcINhPHMhoR8GkakC +5sLOlpgCDpdzHDduW2GjhIAUnXt50yZNpkXQuSWdjucbYGc2G5ySc8eHaP+5tHAr +svyZBchE+Kf4p2nNoXoQxsgxY2Qgz/ctUgCR7SnbgRW0cHDH7HIXlJ0CgYEAoWeY +rt2q8PFW3sEWy1RK0dxD+7UnuN76x5rd0IUxi2HS5F4tyfiDy3LgVs0XJqF9IN6K +IQ7pX/0C1g91NbUl8pAnu+k7R/CiynqGAmQumkMscIRO4VoR+v3+Hta9NV6sy/0U +fDfQSK9AnrFXGCpHPLC+YrmgbVnKqJ526vBxboUCgYEAvx4pJ0TMWI62p1nm+HrD +JLGc1SzRh4mBll15PeuRsef1DA66E3PVzEKaQ/WTMt1eN8+ntE7cEfuIsxB49MJ+ +j5xZp0HGwYeQ/Khq71VbUWP0SKXqWnrn/7eLGq90LT6wLq9BHh7zdu6PqJJh4iml +vgIkseBN6X6EIvtFSIOjyn0CgYBRvEiRpSd/xHedbmLArPsGs2ip+t8Wu7R7iG1z +vz+Lugo2I4tEkFkNmisJSerDYVwgXRHOE+MS/OmGxWUxwX5qC55ThpTCpZWKu+lJ +JLqE3CeRAy9+50HbvOwHae9/K2aOFqddEFaluDodIulcD2zrywVesWoQdjwuj7Dg +4MpQkQKBgA4vlTf+n8kpOJWls2YMyZaauY48xcNzDdhpBGFCjVm+aiKX5dyIjAQK +9LX8/iVau8ZRM+qSLpuEP+o8qGR11TbGZrLH/wITc7r9cWnaGDsozmPAnxMcu1zz +9IRTY9zr9QWzxGiSqr834q5IZIQ/5uDBW/857MP0bpMl6cTdxzg0 +-----END RSA PRIVATE KEY-----"; + let auth_pk = PrivateKey::from_pem_str(rsa_2048_private_key).unwrap(); + let certificate_stub = vec![0xff; 1024]; + let pin = vec![0x39; 6]; + SmartCard::new(Cow::Borrowed("Reader 0"), pin, certificate_stub, auth_pk).unwrap() + } + + // Helper function that calls the GET RESPONSE handler until there is no more data to read + fn get_all_available_data(mut response: Response, scard: &mut SmartCard) -> Vec { + let mut complete_response = vec![]; + while let Status::MoreAvailable(bytes_left) = response.status { + complete_response.extend_from_slice(&response.data.expect("Data should be present")); + let apdu_get_response = vec![0x00, 0xC0, 0x00, 0x00, bytes_left]; + response = scard + .handle_command(&apdu_get_response) + .expect("Can't retrieve all available data"); + } + assert_eq!(response.status, Status::OK); + complete_response.extend_from_slice(&response.data.expect("The last chunk of data isn't present")); + complete_response + } + + #[test] + fn invalid_apdu_command() { + // Verify that smart card correctly handles invalid APDU commands + let mut scard = new_scard(); + + let bad_apdu_command = vec![0x00; 2048]; + let response = scard.handle_command(&bad_apdu_command); + assert!(response.is_err_and(|err| err.error_kind == ErrorKind::InternalError)); + } + + #[test] + fn wrong_command_order() { + // Verify that the smart card prohibits using any commands besides SELECT when no app was selected + let mut scard = new_scard(); + + let mut apdu_verify_cmd = vec![0x00, 0x20, 0x00, 0x80, 0x08]; + // add pin + apdu_verify_cmd.extend_from_slice(&[0xA9; 8]); + let response = scard.handle_command(&apdu_verify_cmd); + assert!(response.is_ok_and(|resp| resp.status == Status::NotFound)); + } + + #[test] + fn invalid_select_command() { + // Verify that the SELECT handler correctly responds if called with an invalid AID + let mut scard = new_scard(); + + let bad_aid = vec![0xff; 11]; + + let mut apdu_select_cmd = vec![0x00, 0xA4, 0x04, 0x00, 0x0B]; + apdu_select_cmd.extend_from_slice(&bad_aid); + let response = scard.handle_command(&apdu_select_cmd); + assert!(response.is_ok_and(|resp| resp.status == Status::NotFound)); + } + + #[test] + fn select_command() { + // Verify that the SELECT command works as expected and returns expected output + let mut scard = new_scard(); + + let mut expected_response = vec![ + 0x61, 0x17, 0x4F, 0x06, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x79, 0x0D, 0x4F, 0x0B, + ]; + expected_response.extend_from_slice(&PIV_AID); + + let mut apdu_select_cmd = vec![0x00, 0xA4, 0x04, 0x00, 0x0B]; + apdu_select_cmd.extend_from_slice(&PIV_AID); + let response = scard.handle_command(&apdu_select_cmd); + assert!(response.is_ok_and( + |resp| resp.status == Status::OK && resp.data.expect("Data should be present") == expected_response + )); + } + + #[test] + fn unsupported_command() { + // Verify that smart card correctly handles unsupported commands + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + // RESET RETRY COUNTER APDU command + let apdu_reset_retry_cmd = vec![0x00, 0x2C, 0x00, 0x80, 0x00]; + let response = scard.handle_command(&apdu_reset_retry_cmd); + assert!(response.is_ok_and(|resp| resp.status == Status::InstructionNotSupported)); + } + + #[test] + fn invalid_verify_commands() { + // Verify that the VERIFY command handler correctly handles badly structured or malformed requests + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + // p1 can only be 0x00 or 0xFF + let apdu_verify_bad_p1 = vec![0x00, 0x20, 0xAA, 0x80, 0x00]; + let response = scard.handle_command(&apdu_verify_bad_p1); + assert!(response.is_ok_and(|resp| resp.status == Status::IncorrectP1orP2)); + + // if p1 is 0xFF, the data field should be empty + let apdu_verify_bad_p1_data = vec![0x00, 0x20, 0xFF, 0x80, 0x02, 0xFF, 0xFF]; + let response = scard.handle_command(&apdu_verify_bad_p1_data); + assert!(response.is_ok_and(|resp| resp.status == Status::IncorrectP1orP2)); + + // p2 should always be 0x80 + let apdu_verify_bad_p2 = vec![0x00, 0x20, 0x00, 0x81, 0x02, 0xFF, 0xFF]; + let response = scard.handle_command(&apdu_verify_bad_p2); + assert!(response.is_ok_and(|resp| resp.status == Status::KeyReferenceNotFound)); + + // PIN should be no shorter than six bytes and no longer than 8 + let apdu_verify_bad_pin = vec![0x00, 0x20, 0x00, 0x80, 0x02, 0xAA, 0xAA]; + let response = scard.handle_command(&apdu_verify_bad_pin); + assert!(response.is_ok_and(|resp| resp.status == Status::IncorrectDataField)); + } + + #[test] + fn verify_command() { + // Verify that the VERIFY command handler correctly handles all supported types of requests + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + // retrieve number of allowed retries by omitting the data field + let apdu_verify_no_data = vec![0x00, 0x20, 0x00, 0x80, 0x00]; + let response = scard.handle_command(&apdu_verify_no_data); + assert!(response.is_ok_and(|resp| resp.status == Status::VerificationFailedWithRetries)); + + // VERIFY command with the wrong PIN code + let mut apdu_verify_wrong_pin = vec![0x00, 0x20, 0x00, 0x80, 0x08]; + apdu_verify_wrong_pin.extend_from_slice(&[0xCC; 8]); + let response = scard.handle_command(&apdu_verify_wrong_pin); + assert!(response.is_ok_and(|resp| resp.status == Status::VerificationFailedWithRetries)); + + // VERIFY command with the correct PIN code + let mut apdu_verify_correct_pin = vec![0x00, 0x20, 0x00, 0x80, 0x08]; + apdu_verify_correct_pin.extend_from_slice(&[0x39; 6]); + // 0xFF padding + apdu_verify_correct_pin.extend_from_slice(&[0xFF; 2]); + let response = scard.handle_command(&apdu_verify_correct_pin); + assert!(response.is_ok_and(|resp| resp.status == Status::OK)); + assert_eq!(scard.state, SCardState::PinVerified); + + // Reset the security status + let apdu_verify_reset = vec![0x00, 0x20, 0xFF, 0x80, 0x00]; + let response = scard.handle_command(&apdu_verify_reset); + assert!(response.is_ok_and(|resp| resp.status == Status::OK)); + assert_eq!(scard.state, SCardState::PivAppSelected); + } + + #[test] + fn get_response_command() { + // Verify that the GET RESPONSE handler correctly sends the data + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + let mut rng = rand::thread_rng(); + + // get a random Vec of length 513 + let data: Vec = (0..513).map(|_| rng.sample(Uniform::new(0, 255))).collect(); + // we will have to make 3 calls to get this data + scard.pending_response = Some(data.clone()); + + let mut received_result = vec![]; + // 0 means any valid number in range 0..=256 + // We set this to 0 on the first call so that the smart card returns whatever it got + let bytes_left = 0; + let mut apdu_get_response = vec![0x00, 0xC0, 0x00, 0x00, bytes_left]; + + let response = scard.handle_command(&apdu_get_response); + assert!(response + .as_ref() + .is_ok_and(|resp| resp.status == Status::MoreAvailable(0) + && resp.data.is_some() + && resp.data.as_ref().unwrap() == &data[0..256])); + received_result.extend_from_slice(&response.unwrap().data.unwrap()); + + let response = scard.handle_command(&apdu_get_response); + assert!(response + .as_ref() + .is_ok_and(|resp| resp.status == Status::MoreAvailable(1) + && resp.data.is_some() + && resp.data.as_ref().unwrap() == &data[256..512])); + received_result.extend_from_slice(&response.unwrap().data.unwrap()); + + // set the Le field to 1 so that we get the last remaining byte + apdu_get_response[4] = 1; + let response = scard.handle_command(&apdu_get_response); + assert!(response.as_ref().is_ok_and(|resp| resp.status == Status::OK + && resp.data.is_some() + && resp.data.as_ref().unwrap() == &data[512..])); + received_result.extend_from_slice(&response.unwrap().data.unwrap()); + + assert_eq!(received_result, data); + } + + #[test] + fn invalid_get_data_command() { + // Verify that the GET DATA handler correctly handles invalid requests + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + // p1 should always be 0x3F; p2 should always be 0xFF + let apdu_get_data_bad_p1_p2 = vec![0x00, 0xCB, 0x10, 0x21, 0x00]; + let response = scard.handle_command(&apdu_get_data_bad_p1_p2); + assert!(response.is_ok_and(|resp| resp.status == Status::IncorrectP1orP2)); + + // bad object tag in the data field + let apdu_get_data_bad_tag = vec![0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x08]; + let response = scard.handle_command(&apdu_get_data_bad_tag); + assert!(response.is_ok_and(|resp| resp.status == Status::NotFound)); + } + + #[test] + fn get_data_command() { + // Verify that the GET DATA handler correctly handles all supported requests and returns correct data + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + // get CHUID + let apdu_get_data_chuid = vec![0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x02]; + let response = scard.handle_command(&apdu_get_data_chuid); + assert!( + response.is_ok_and(|resp| resp.status == Status::OK && resp.data.expect("Expected CHUID") == scard.chuid) + ); + + // get PIV authentication certificate + let apdu_get_data_chuid = vec![0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x05]; + let response = scard.handle_command(&apdu_get_data_chuid); + // verify the contents + assert!(response.is_ok_and(|resp| { + // as the certificate is larger than 256 bytes, we have to call the GET RESPONSE function a few times + let complete_response = get_all_available_data(resp, &mut scard); + complete_response == scard.auth_cert + })); + } + + #[test] + fn general_authenticate_no_pin() { + // Verify that the GENERAL AUTHENTICATE handler can't be used without unlocking the smart card first + let mut scard = new_scard(); + scard.state = SCardState::PivAppSelected; + + let apdu_general_authenticate = vec![0x00, 0x87, 0x07, 0x9A, 0x00]; + let response = scard.handle_command(&apdu_general_authenticate); + assert!(response.is_ok_and(|resp| resp.status == Status::SecurityStatusNotSatisfied)); + } + + #[test] + fn invalid_general_authenticate_command() { + // Verify that the GENERAL AUTHENTICATE handler correctly handles invalid requests + let mut scard = new_scard(); + scard.state = SCardState::PinVerified; + + // p1 should always be 0x07; p2 should always be 0x9A + let apdu_general_authenticate = vec![0x00, 0x87, 0xFF, 0xCC, 0x00]; + let response = scard.handle_command(&apdu_general_authenticate); + assert!(response.is_err_and(|err| err.error_kind == ErrorKind::UnsupportedFeature)); + } + + #[test] + fn general_authenticate_command() { + // Verify that the GENERAL AUTHENTICATE handler correctly encrypts the provided data + let mut scard = new_scard(); + scard.state = SCardState::PinVerified; + + let data = "My message".as_bytes(); + + let rsa_pk = rsa::RsaPrivateKey::try_from(&scard.auth_pk).expect("Can't convert the private key"); + // sign the data using the PKCS1-v1.5 padding scheme + let signature_algorithm = SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512); + let signed_data = signature_algorithm + .sign(data, &scard.auth_pk) + .expect("Error while signing the data"); + // extract the padded hash by decrypting the signature using the public key + // we need to extract the padded hash to calculate the signature ourselves + let padded_hash = BigUint::from_bytes_be(&signed_data) + .modpow(rsa_pk.e(), rsa_pk.n()) + .to_bytes_be(); + + // the hash is bigger than 127, so we have to use BER-TLV encoding + let encoded_hash_length = ber_tlv_length_encoding(padded_hash.len()); + // encoded_hash_length + tag + hash + let dat_tag_data_length = ber_tlv_length_encoding(1 + encoded_hash_length.len() + padded_hash.len()); + + // use command chaining to send the data that is bigger than 255 bytes + let mut apdu_general_authenticate = vec![0x10, 0x87, 0x07, 0x9A, 0xFF, 0x7C]; + apdu_general_authenticate.extend_from_slice(&dat_tag_data_length); + apdu_general_authenticate.extend_from_slice(&[0x81]); + apdu_general_authenticate.extend_from_slice(&encoded_hash_length); + apdu_general_authenticate.extend_from_slice(&padded_hash[..248]); + + let response = scard.handle_command(&apdu_general_authenticate); + assert!(response.is_ok_and(|resp| resp.status == Status::OK)); + + // send the remaining data and end command chaining by setting the CLA byte to 0x00 + let mut apdu_general_authenticate = vec![0x00, 0x87, 0x07, 0x9A, 0x07]; + apdu_general_authenticate.extend_from_slice(&padded_hash[248..]); + // Le + apdu_general_authenticate.extend_from_slice(&[0x00]); + + let response = scard + .handle_command(&apdu_general_authenticate) + .expect("Shouldn't have failed"); + + let complete_response = get_all_available_data(response, &mut scard); + let parsed_response = + Tlv::from_bytes(&complete_response).expect("Couldn't parse a TLV object sent from the smart card"); + + // verify the structure of the response and extract the signed hash + assert_eq!( + parsed_response.tag(), + &Tag::try_from(tlv_tags::DYNAMIC_AUTHENTICATION_TEMPLATE).expect("Couldn't construct a TLV tag") + ); + let response_tag = match parsed_response.value() { + Value::Constructed(data) => data + .iter() + .find(|tlv_object| { + tlv_object.tag() == &Tag::try_from(tlv_tags::DAT_RESPONSE).expect("Couldn't construct a TLV tag") + }) + .expect("The inner TLV object should contain a Response tag"), + Value::Primitive(_) => panic!("Dynamic Authentication Template should contain constructed value"), + }; + let signed_hash = match response_tag.value() { + Value::Constructed(_) => panic!("Response tag should contain a primitive value"), + Value::Primitive(signed_hash) => signed_hash, + }; + // verify that the returned signature can be verified using the corresponding public key + assert!(signature_algorithm + .verify( + &scard + .auth_pk + .to_public_key() + .expect("Error while creating public key from a private key"), + data, + signed_hash + ) + .is_ok()); + } +} diff --git a/crates/winscard/src/scard_context.rs b/crates/winscard/src/scard_context.rs new file mode 100644 index 00000000..fe965736 --- /dev/null +++ b/crates/winscard/src/scard_context.rs @@ -0,0 +1,464 @@ +use alloc::borrow::{Cow, ToOwned}; +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::{format, vec}; + +use picky::key::PrivateKey; +use picky::x509::Cert; +use picky_asn1_x509::{PublicKey, SubjectPublicKeyInfo}; + +use crate::scard::SmartCard; +use crate::winscard::{DeviceTypeId, Icon, Protocol, ShareMode, WinScard, WinScardContext}; +use crate::{Error, ErrorKind, WinScardResult}; + +/// Describes a smart card reader. +#[derive(Debug, Clone)] +pub struct Reader<'a> { + /// Reader name. + pub name: Cow<'a, str>, + /// Reader icon buff. + pub icon: Icon<'a>, + /// Device Type Id. + pub device_type_id: DeviceTypeId, +} + +/// Describes smart card info used for the smart card creation. +#[derive(Debug, Clone)] +pub struct SmartCardInfo<'a> { + /// Container name which stores the certificate along with its private key. + pub container_name: Cow<'a, str>, + /// Smart card PIN code. + pub pin: Vec, + /// DER-encoded smart card certificate. + pub auth_cert_der: Vec, + /// Encoded private key (pem). + pub auth_pk_pem: Cow<'a, str>, + /// Private key. + pub auth_pk: PrivateKey, + /// Information about smart card reader. + pub reader: Reader<'a>, +} + +impl<'a> SmartCardInfo<'a> { + fn reader_icon() -> &'static [u8] { + include_bytes!("../assets/reader_icon.bmp") + } + + /// Tries to create [SmartCardInfo] structure based on environment variables. + /// Required environment variables are listed in the `env` module of this crate. + #[cfg(feature = "std")] + pub fn try_from_env() -> WinScardResult { + use std::fs; + + use crate::env::{ + WINSCARD_CERT_PATH_ENV, WINSCARD_CONTAINER_NAME_ENV, WINSCARD_PIN_ENV, WINSCARD_PK_PATH_ENV, + WINSCARD_READER_NAME_ENV, + }; + + let container_name = env!(WINSCARD_CONTAINER_NAME_ENV)?.into(); + let reader_name: Cow<'_, str> = env!(WINSCARD_READER_NAME_ENV)?.into(); + let pin = env!(WINSCARD_PIN_ENV)?.into(); + + let cert_path = env!(WINSCARD_CERT_PATH_ENV)?; + let raw_certificate = fs::read_to_string(cert_path).map_err(|e| { + Error::new( + ErrorKind::InvalidParameter, + format!("Unable to read certificate from the provided file: {}", e), + ) + })?; + let auth_cert_der = Cert::from_pem_str(&raw_certificate)?.to_der()?; + let pk_path = env!(WINSCARD_PK_PATH_ENV)?; + let raw_private_key = fs::read_to_string(pk_path).map_err(|e| { + Error::new( + ErrorKind::InvalidParameter, + format!("Unable to read private key from the provided file: {}", e), + ) + })?; + let private_key = PrivateKey::from_pem_str(&raw_private_key).map_err(|e| { + Error::new( + ErrorKind::InvalidParameter, + format!( + "Error while trying to read a private key from a pem-encoded string: {}", + e + ), + ) + })?; + + // Standard Windows Reader Icon + let icon: &[u8] = Self::reader_icon(); + let reader: Reader<'_> = Reader { + name: reader_name, + icon: Icon::from(icon), + device_type_id: DeviceTypeId::Tpm, + }; + + Ok(Self { + container_name, + pin, + auth_cert_der, + auth_pk_pem: raw_private_key.into(), + auth_pk: private_key, + reader, + }) + } + + /// Creates a new [ScardContext] based on the provided data. + pub fn new( + container_name: Cow<'a, str>, + reader_name: Cow<'a, str>, + pin: Vec, + auth_cert_der: Vec, + auth_pk_pem: Cow<'a, str>, + auth_pk: PrivateKey, + ) -> Self { + // Standard Windows Reader Icon + let icon: &[u8] = Self::reader_icon(); + let reader: Reader<'_> = Reader { + name: reader_name, + icon: Icon::from(icon), + device_type_id: DeviceTypeId::Tpm, + }; + SmartCardInfo { + container_name, + pin, + auth_cert_der, + auth_pk_pem, + auth_pk, + reader, + } + } +} + +/// Represents the resource manager context (the scope). +/// +/// Currently, we support only one smart card per smart card context. +#[derive(Debug, Clone)] +pub struct ScardContext<'a> { + smart_card_info: SmartCardInfo<'a>, + cache: BTreeMap>, +} + +impl<'a> ScardContext<'a> { + /// Creates a new smart card based on the list of smart card readers + pub fn new(smart_card_info: SmartCardInfo<'a>) -> WinScardResult { + // Freshness values may vary at different points in time. + // We do not need to change them in runtime, so we hardcode them here. + // Those values do not mean anything special. They are just extracted from the real TPM smart card. + const PIN_FRESHNESS: [u8; 2] = [0x00, 0x00]; + const CONTAINER_FRESHNESS: [u8; 2] = [0x01, 0x00]; + const FILE_FRESHNESS: [u8; 2] = [0x0b, 0x00]; + + // The following header is formed based on the extracted information from the Windows Smart Card Minidriver (`msclmd.dll`). + // Do not change it unless you know what you are doing. + // A broken cache will break the entire authentication. + const CACHE_ITEM_HEADER: [u8; 6] = { + let mut header = [0; 6]; + + // reference: msclmd!I_GetPIVCache + header[0] = 1; + header[1] = PIN_FRESHNESS[1]; + header[2] = CONTAINER_FRESHNESS[0] + 1; + header[3] = CONTAINER_FRESHNESS[1]; + header[4] = FILE_FRESHNESS[0] + 1; + header[5] = FILE_FRESHNESS[1]; + + header + }; + + let mut cache = BTreeMap::new(); + cache.insert("Cached_CardProperty_Read Only Mode_0".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&4_u32.to_le_bytes()); + // false + value.extend_from_slice(&0_u32.to_le_bytes()); + + value + }); + cache.insert("Cached_CardProperty_Cache Mode_0".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&4_u32.to_le_bytes()); + // true + value.extend_from_slice(&1_u32.to_le_bytes()); + + value + }); + cache.insert("Cached_CardProperty_Supports Windows x.509 Enrollment_0".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&4_u32.to_le_bytes()); + // true + value.extend_from_slice(&1_u32.to_le_bytes()); + + value + }); + cache.insert("Cached_GeneralFile/mscp/cmapfile".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len: size_of() + // https://github.com/selfrender/Windows-Server-2003/blob/5c6fe3db626b63a384230a1aa6b92ac416b0765f/ds/security/csps/wfsccsp/inc/basecsp.h#L104-L110 + value.extend_from_slice(&86_u32.to_le_bytes()); + // CONTAINER_MAP_RECORD: + let container = smart_card_info + .container_name + .as_ref() + .encode_utf16() + .chain(core::iter::once(0)) + .flat_map(|v| v.to_le_bytes()) + .collect::>(); + value.extend_from_slice(&container); // wszGuid + value.extend_from_slice(&[3, 0]); // bFlags + value.extend_from_slice(&[0, 0]); // wSigKeySizeBits + value.extend_from_slice(&[0, 8]); // wKeyExchangeKeySizeBits + + value + }); + cache.insert("Cached_CardmodFile\\Cached_CMAPFile".into(), { + // CONTAINER_MAP_RECORD: + let mut value = smart_card_info + .container_name + .as_ref() + .encode_utf16() + .chain(core::iter::once(0)) + .flat_map(|v| v.to_le_bytes()) + .collect::>(); // wszGuid + value.extend_from_slice(&[3, 0]); // bFlags + value.extend_from_slice(&[0, 0]); // wSigKeySizeBits + value.extend_from_slice(&[0, 8]); // wKeyExchangeKeySizeBits + + value + }); + cache.insert("Cached_ContainerProperty_PIN Identifier_0".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&4_u32.to_le_bytes()); + // PIN identifier + value.extend_from_slice(&1_u32.to_le_bytes()); + + value + }); + cache.insert("Cached_ContainerInfo_00".into(), { + // Note. We can hardcode lengths values in this cache item because we support only 2048 RSA keys. + // RSA 4096 is not defined in the specification so we don't support it. + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=34 + // 5.3 Cryptographic Mechanism Identifiers + // '07' - RSA 2048 + + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len (precalculated) + value.extend_from_slice(&292_u32.to_le_bytes()); + + value.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x14, 0x01, 0x00, 0x00]); // container info header + + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-publickeystruc + // PUBLICKEYSTRUC + value.push(0x06); // bType = PUBLICKEYBLOB + value.push(0x02); // bVersion = 0x2 + value.extend_from_slice(&[0x00, 0x00]); // reserved + value.extend_from_slice(&[0x00, 0xa4, 0x00, 0x00]); // aiKeyAlg = CALG_RSA_KEYX + + // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-rsapubkey + // RSAPUBKEY + value.extend_from_slice(b"RSA1"); // magic = RSA1 + value.extend_from_slice(&2048_u32.to_le_bytes()); // bitlen = 2048 + + // let pub_key = smart_cards_info. + let public_key = smart_card_info + .auth_pk + .to_public_key() + .expect("rsa private key to public key"); + let public_key: &SubjectPublicKeyInfo = public_key.as_ref(); + let (modulus, public_exponent) = match &public_key.subject_public_key { + PublicKey::Rsa(rsa) => ( + { + let mut modulus = rsa.0.modulus.to_vec(); + modulus.reverse(); + modulus.resize(256, 0); + modulus + }, + { + let mut pub_exp = rsa.0.public_exponent.to_vec(); + pub_exp.reverse(); + pub_exp.resize(4, 0); + pub_exp + }, + ), + _ => { + return Err(Error::new( + ErrorKind::UnsupportedFeature, + "only RSA 2048 keys are supported", + )) + } + }; + + value.extend_from_slice(&public_exponent); // pubexp + value.extend_from_slice(&modulus); // public key + + value + }); + cache.insert("Cached_GeneralFile/mscp/kxc00".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + + let mut compressed_cert = vec![0; smart_card_info.auth_cert_der.len()]; + let compressed = crate::compression::compress_cert(&smart_card_info.auth_cert_der, &mut compressed_cert)?; + + let total_value_len = + (compressed.len() + 2 /* unknown flags */ + 2/* uncompressed certificate len */) as u32; + value.extend_from_slice(&total_value_len.to_le_bytes()); + + value.extend_from_slice(&[0x01, 0x00]); // unknown flags + value.extend_from_slice(&(smart_card_info.auth_cert_der.len() as u16).to_le_bytes()); // uncompressed certificate data len + value.extend_from_slice(&compressed_cert); + + value + }); + cache.insert("Cached_CardProperty_Capabilities_0".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&12_u32.to_le_bytes()); + // Here should be the CARD_CAPABILITIES struct but the actual extracted data is different. + // So, we just insert the extracted data from a real smart card. + // Card capabilities: + value.extend_from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); + + value + }); + + cache.insert("Cached_CardProperty_Key Sizes_2".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&20_u32.to_le_bytes()); + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/secsmart/card-key-sizes + value.extend_from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); + value.extend_from_slice(&[ + 1, 0, 0, 0, // dwVersion = 1 + 0, 4, 0, 0, // dwMinimumBitlen = 1024 + 0, 4, 0, 0, // dwDefaultBitlen = 1048 + 0, 8, 0, 0, // dwMaximumBitlen = 2048 + 0, 4, 0, 0, // dwIncrementalBitlen = 1024 + ]); + + value + }); + + cache.insert("Cached_CardProperty_Key Sizes_1".into(), { + let mut value = CACHE_ITEM_HEADER.to_vec(); + // unkown flags + value.extend_from_slice(&[0, 0, 0, 0, 0, 0]); + // actual data len + value.extend_from_slice(&20_u32.to_le_bytes()); + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/secsmart/card-key-sizes + value.extend_from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); + value.extend_from_slice(&[ + 1, 0, 0, 0, // dwVersion = 1 + 0, 4, 0, 0, // dwMinimumBitlen = 1024 + 0, 4, 0, 0, // dwDefaultBitlen = 1048 + 0, 8, 0, 0, // dwMaximumBitlen = 2048 + 0, 4, 0, 0, // dwIncrementalBitlen = 1024 + ]); + + value + }); + + cache.insert( + "Cached_CardmodFile\\Cached_Pin_Freshness".into(), + PIN_FRESHNESS.to_vec(), + ); + cache.insert( + "Cached_CardmodFile\\Cached_File_Freshness".into(), + FILE_FRESHNESS.to_vec(), + ); + cache.insert( + "Cached_CardmodFile\\Cached_Container_Freshness".into(), + CONTAINER_FRESHNESS.to_vec(), + ); + + Ok(Self { smart_card_info, cache }) + } + + /// Returns available smart card reader name. + pub fn reader_name(&self) -> &str { + self.smart_card_info.reader.name.as_ref() + } +} + +impl<'a> WinScardContext for ScardContext<'a> { + fn connect( + &self, + reader_name: &str, + _share_mode: ShareMode, + _protocol: Option, + ) -> WinScardResult> { + if self.smart_card_info.reader.name != reader_name { + return Err(Error::new( + ErrorKind::UnknownReader, + format!("reader {:?} not found", reader_name), + )); + } + + Ok(Box::new(SmartCard::new( + Cow::Owned(reader_name.to_owned()), + self.smart_card_info.pin.clone(), + self.smart_card_info.auth_cert_der.clone(), + self.smart_card_info.auth_pk.clone(), + )?)) + } + + fn list_readers(&self) -> Vec> { + vec![self.smart_card_info.reader.name.clone()] + } + + fn device_type_id(&self, reader_name: &str) -> WinScardResult { + if self.smart_card_info.reader.name != reader_name { + return Err(Error::new( + ErrorKind::UnknownReader, + format!("reader {:?} not found", reader_name), + )); + } + + Ok(self.smart_card_info.reader.device_type_id) + } + + fn reader_icon(&self, reader_name: &str) -> WinScardResult { + if self.smart_card_info.reader.name != reader_name { + return Err(Error::new( + ErrorKind::UnknownReader, + format!("reader {:?} not found", reader_name), + )); + } + + Ok(self.smart_card_info.reader.icon.clone()) + } + + fn is_valid(&self) -> bool { + true + } + + fn read_cache(&self, key: &str) -> Option<&[u8]> { + self.cache.get(key).map(|item| item.as_slice()) + } + + fn write_cache(&mut self, key: String, value: Vec) { + self.cache.insert(key, value); + } +} diff --git a/crates/winscard/src/tlv_tags.rs b/crates/winscard/src/tlv_tags.rs new file mode 100644 index 00000000..299cbdd7 --- /dev/null +++ b/crates/winscard/src/tlv_tags.rs @@ -0,0 +1,31 @@ +/// BER-TLV with the tag `53` containing in the value field of the +/// requested data object. +pub const DATA: u8 = 0x53; +/// [FASC-N](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36). +pub const FASC_N: u8 = 0x30; +/// [GUID](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36). +pub const GUID: u8 = 0x34; +/// [Expiration Date](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36). +pub const EXPIRATION_DATE: u8 = 0x35; +/// [Issuer Asymmetric Signature](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36). +pub const ISSUER_SIGNATURE: u8 = 0x3E; +/// [Error Detection Code](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=39). +pub const ERROR_DETECTION_CODE: u8 = 0xFE; +/// [PIV Card Application Property Template](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=84). +pub const APPLICATION_PROPERTY_TEMPLATE: u8 = 0x61; +/// [PIV Card Application AID](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=36). +pub const APPLICATION_IDENTIFIER: u8 = 0x4F; +/// [Coexistent Tag Allocation Authority Template](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=84). +pub const COEXISTING_TAG_ALLOCATION_AUTHORITY: u8 = 0x79; +/// [Tag list](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=85). +pub const TAG_LIST: u8 = 0x5C; +/// [Certificate](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=40). +pub const CERTIFICATE: u8 = 0x70; +/// [CertInfo](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=40). +pub const CERT_INFO: u8 = 0x71; +/// [Dynamic Authentication Template](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93). +pub const DYNAMIC_AUTHENTICATION_TEMPLATE: u8 = 0x7C; +/// [Challenge](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93). +pub const DAT_CHALLENGE: u8 = 0x81; +/// [Response](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93). +pub const DAT_RESPONSE: u8 = 0x82; diff --git a/crates/winscard/src/winscard.rs b/crates/winscard/src/winscard.rs new file mode 100644 index 00000000..7f6f312f --- /dev/null +++ b/crates/winscard/src/winscard.rs @@ -0,0 +1,342 @@ +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +use bitflags::bitflags; + +use crate::{Error, ErrorKind, WinScardResult}; + +/// ATR string. +/// +/// A sequence of bytes returned from a smart card when it is turned on. +/// These bytes are used to identify the card to the system. +/// +/// [SCardStatusW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardstatusw) +/// `pbAtr` parameter: +/// A 32-byte buffer that holds the ATR string from the currently inserted card. +/// Note: 32 is a maximum ATR string len. In reality, the original Windows TPM smart card always returns 17-bytes len ATR string. +#[derive(Debug, Clone)] +pub struct Atr(Vec); + +impl AsRef<[u8]> for Atr { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl From> for Atr { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From<[u8; 17]> for Atr { + fn from(value: [u8; 17]) -> Self { + Self(value.into()) + } +} + +/// A buffer that contains a BLOB of the smart card reader icon as read from the icon file. +#[derive(Debug, Clone)] +pub struct Icon<'a>(Cow<'a, [u8]>); + +impl<'a> AsRef<[u8]> for Icon<'a> { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl<'a> From<&'a [u8]> for Icon<'a> { + fn from(value: &'a [u8]) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl From> for Icon<'_> { + fn from(value: Vec) -> Self { + Self(Cow::Owned(value)) + } +} + +/// [SCardGetDeviceTypeIdW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetdevicetypeidw) +/// The actual device type identifier. The list of reader types returned +/// by this function are listed under ReaderType member in the SCARD_READER_CAPABILITIES structure. +/// +/// [SCARD_READER_CAPABILITIES](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/smclib/ns-smclib-_scard_reader_capabilities) +/// `ReaderType` parameter: +/// This member contains the reader type and is required. This member can have one of the values in the following table. +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum DeviceTypeId { + /// Serial reader + Serial = 0x01, + /// Parallel reader + Paralell = 0x02, + /// Keyboard-attached reader + Keyboard = 0x04, + /// SCSI reader + Scsi = 0x08, + /// IDE reader + Ide = 0x10, + /// USB reader + Usb = 0x20, + /// PCMCIA reader + Pcmcia = 0x40, + /// Reader that uses a TPM chip for key material storage and cryptographic operations + Tpm = 0x80, + /// Reader that uses a proprietary vendor bus + Vendor = 0xf0, +} + +impl From for u32 { + fn from(value: DeviceTypeId) -> Self { + value as u32 + } +} + +/// [SCardConnectW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardconnectw) +/// +/// `dwShareMode` parameter: +/// A flag that indicates whether other applications may form connections to the card. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum ShareMode { + /// This application is not willing to share the card with other applications. + Exclusive = 1, + /// This application is willing to share the card with other applications. + Shared = 2, + /// This application is allocating the reader for its private use, and will be controlling it directly. + /// No other applications are allowed access to it. + Direct = 3, +} + +impl TryFrom for ShareMode { + type Error = Error; + + fn try_from(value: u32) -> Result { + match value { + 1 => Ok(Self::Exclusive), + 2 => Ok(Self::Shared), + 3 => Ok(Self::Direct), + _ => Err(Error::new( + ErrorKind::InvalidParameter, + format!("Invalid ShareMode value: {}", value), + )), + } + } +} + +bitflags! { + /// [SCardConnectW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardconnectw) + /// + /// `dwPreferredProtocols` and `pdwActiveProtocol` parameters: + /// A bitmask of acceptable protocols for the connection. + /// Possible values may be combined with the OR operation. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct Protocol: u32 { + /// This parameter may be zero only if dwShareMode is set to SCARD_SHARE_DIRECT. + /// In this case, no protocol negotiation will be performed by the drivers + /// until an IOCTL_SMARTCARD_SET_PROTOCOL control directive is sent with SCardControl. + const UNDEFINED = 0x00000000; + /// The ISO 7816/3 T=0 protocol is in use. + /// An asynchronous, character-oriented half-duplex transmission protocol. + const T0 = 0x00000001; + /// The ISO 7816/3 T=1 protocol is in use. + /// An asynchronous, block-oriented half-duplex transmission protocol. + const T1 = 0x00000002; + /// The Raw Transfer protocol is in use. + /// This flags can be used **only** in the `SCardStatusA/W` function in the `pdwProtocol` parameter. + const Raw = 0x00010000; + } +} + +/// [SCardStatusW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardstatusw) +/// +/// `pdwState` parameter: +/// Current state of the smart card in the reader. Upon success, it receives one of the following state indicators. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum State { + /// Unknown smart card status. + Unknown = 0, + /// There is no card in the reader. + Absent = 1, + /// There is a card in the reader, but it has not been moved into position for use. + Present = 2, + /// There is a card in the reader in position for use. The card is not powered. + Swallowed = 3, + /// Power is being provided to the card, but the reader driver is unaware of the mode of the card. + Powered = 4, + /// The card has been reset and is awaiting PTS negotiation. + Negotiable = 5, + /// The card has been reset and specific communication protocols have been established. + Specific = 6, +} + +impl From for u32 { + fn from(value: State) -> Self { + value as u32 + } +} + +/// This structure described the current status and basic info about the smart card reader. +#[derive(Debug, Clone)] +pub struct Status<'a> { + /// List of display names (multiple string) by which the currently connected reader is known. + pub readers: Vec>, + /// Current state of the smart card in the reader + pub state: State, + /// Current protocol, if any. The returned value is meaningful only if the returned value of pdwState is SCARD_SPECIFICMODE. + pub protocol: Protocol, + /// Buffer that receives the ATR string from the currently inserted card, if available. + /// [ATR string](https://learn.microsoft.com/en-us/windows/win32/secgloss/a-gly) + pub atr: Atr, +} + +/// [SCardControl](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardcontrol) +/// +/// `dwControlCode` parameter: +/// Control code for the operation. This value identifies the specific operation to be performed. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum ControlCode { + /// `#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)` + /// Request features described in the *PC/SC 2.0 Specification Part 10* + IoCtl = 0x00313520, +} + +impl TryFrom for ControlCode { + type Error = Error; + + fn try_from(value: u32) -> WinScardResult { + match value { + 0x00313520 => Ok(ControlCode::IoCtl), + _ => Err(Error::new( + ErrorKind::InvalidParameter, + format!("Unsupported control code: {:x?}", value), + )), + } + } +} + +/// [SCARD_IO_REQUEST](https://learn.microsoft.com/en-us/windows/win32/secauthn/scard-io-request) +/// +/// The SCARD_IO_REQUEST structure begins a protocol control information structure. +/// Any protocol-specific information then immediately follows this structure. +/// +/// ```not_rust +/// typedef struct { +/// DWORD dwProtocol; +/// DWORD cbPciLength; +/// } SCARD_IO_REQUEST; +/// ``` +#[derive(Debug, Clone)] +pub struct IoRequest { + /// Protocol in use. + pub protocol: Protocol, + /// PCI-specific information. + pub pci_info: Vec, +} + +/// This structure represents the result of the `SCardTransmit` function. +#[derive(Debug, Clone)] +pub struct TransmitOutData { + /// Data returned from the card. If no data is returned from the card, + /// then this buffer will only contain the SW1 and SW2 status bytes. + pub output_apdu: Vec, + /// Returned protocol control information (PCI) specific to the protocol in use. + pub receive_pci: Option, +} + +/// This trait provides interface for all available smart card related functions in the `winscard.h`. +/// +/// # MSDN +/// +/// * [winscard.h](https://learn.microsoft.com/en-us/windows/win32/api/winscard/) +pub trait WinScard { + /// [SCardStatusW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardstatusw) + /// + /// The SCardStatus function provides the current status of a smart card in a reader. + /// You can call it any time after a successful call to `SCardConnect` and before a successful + /// call to `SCardDisconnect`. It does not affect the state of the reader or reader driver. + fn status(&self) -> WinScardResult; + + /// [SCardControl](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardcontrol) + /// + /// The SCardControl function gives you direct control of the reader. + /// You can call it any time after a successful call to SCardConnect and before a successful call to SCardDisconnect. + /// The effect on the state of the reader depends on the control code. + fn control(&mut self, code: ControlCode, input: &[u8]) -> WinScardResult>; + + /// [SCardTransmit](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardtransmit) + /// + /// The SCardTransmit function sends a service request to the smart card and expects to receive data back from the card. + fn transmit(&mut self, send_pci: IoRequest, input_apdu: &[u8]) -> WinScardResult; + + /// [SCardBeginTransaction](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardbegintransaction) + /// + /// The SCardBeginTransaction function starts a transaction. + /// The function waits for the completion of all other transactions before it begins. + /// After the transaction starts, all other applications are blocked from accessing the smart card while the transaction is in progress. + fn begin_transaction(&mut self) -> WinScardResult<()>; + + /// [SCardEndTransaction](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardendtransaction) + /// + /// The SCardEndTransaction function completes a previously declared transaction, + /// allowing other applications to resume interactions with the card. + fn end_transaction(&mut self) -> WinScardResult<()>; +} + +/// This trait provides interface for all available smart card context (resource manager) related +/// functions in the `winscard.h`. +/// +/// # MSDN +/// +/// * [winscard.h](https://learn.microsoft.com/en-us/windows/win32/api/winscard/) +pub trait WinScardContext { + /// [SCardConnectW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardconnectw) + /// + /// The SCardConnect function establishes a connection (using a specific resource manager context) between + /// the calling application and a smart card contained by a specific reader. + /// If no card exists in the specified reader, an error is returned. + fn connect( + &self, + reader_name: &str, + share_mode: ShareMode, + protocol: Option, + ) -> WinScardResult>; + + /// [SCardListReadersW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardlistreadersw) + /// + /// Provides the list of readers within a set of named reader groups, eliminating duplicates. + fn list_readers(&self) -> Vec>; + + /// [SCardGetDeviceTypeIdW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetdevicetypeidw) + /// + /// Gets the device type identifier of the card reader for the given reader name. + /// This function does not affect the state of the reader. + fn device_type_id(&self, reader_name: &str) -> WinScardResult; + + /// [SCardGetReaderIconW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetreadericonw) + /// + /// The SCardGetReaderIcon function gets an icon of the smart card reader for a given reader's name. + /// This function does not affect the state of the card reader. + fn reader_icon(&self, reader_name: &str) -> WinScardResult; + + /// [SCardIsValidContext](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardisvalidcontext) + /// + /// The SCardIsValidContext function determines whether a smart card context handle is valid. + fn is_valid(&self) -> bool; + + /// [SCardReadCacheW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardreadcachew) + /// + /// The SCardReadCache function retrieves the value portion of a name-value pair from the global cache maintained by the Smart Card Resource Manager. + fn read_cache(&self, key: &str) -> Option<&[u8]>; + + /// [SCardWriteCacheW](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardwritecachew) + /// + /// The SCardWriteCache function writes a name-value pair from a smart card to the global cache maintained by the Smart Card Resource Manager. + fn write_cache(&mut self, key: String, value: Vec); +} diff --git a/examples/kerberos.rs b/examples/kerberos.rs index eefe46b2..f3c0cc00 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -1,3 +1,5 @@ +use std::error::Error; + use base64::Engine; use reqwest::header::{ ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CONNECTION, CONTENT_LENGTH, HOST, USER_AGENT, @@ -7,11 +9,9 @@ use reqwest::StatusCode; use sspi::builders::EmptyInitializeSecurityContext; use sspi::{ AcquireCredentialsHandleResult, ClientRequestFlags, CredentialsBuffers, DataRepresentation, - InitializeSecurityContextResult, KerberosConfig, SecurityBuffer, SecurityBufferType, SecurityStatus, Sspi, - Username, + InitializeSecurityContextResult, Kerberos, KerberosConfig, SecurityBuffer, SecurityBufferType, SecurityStatus, + Sspi, SspiImpl, Username, }; -use sspi::{Kerberos, SspiImpl}; -use std::error::Error; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 601e4010..bc22c62b 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -15,7 +15,7 @@ crate-type = ["cdylib"] [features] default = [] tsssp = ["sspi/tsssp"] -scard = ["sspi/scard"] +scard = ["sspi/scard", "dep:ffi-types", "dep:winscard"] [dependencies] cfg-if = "1" @@ -23,6 +23,10 @@ libc = "0.2" num-traits = "0.2" whoami = "1.4" sspi = { path = "..", features = ["network_client", "dns_resolver"] } +ffi-types = { path = "../crates/ffi-types", features = ["winscard"], optional = true } +picky-asn1-der = "0.4" + +winscard = { path = "../crates/winscard", features = ["std"], optional = true } # logging tracing = { version = "0.1" } @@ -30,5 +34,5 @@ tracing-subscriber = { version = "0.3", features = ["std", "fmt", "local-time", [target.'cfg(windows)'.dependencies] symbol-rename-macro = { path = "./symbol-rename-macro" } -winapi = "0.3" # FIXME: replace by windows / windows-sys crates +winapi = { version = "0.3", features = ["winerror"] } # FIXME: replace by windows / windows-sys crates windows-sys = { version = "0.48", features = ["Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Foundation", "Win32_Graphics_Gdi"] } diff --git a/ffi/build.rs b/ffi/build.rs index 04dcda71..886df567 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -16,6 +16,9 @@ fn main() { // https://docs.microsoft.com/en-us/cpp/build/reference/exports let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + #[cfg(feature = "scard")] + let sspi_def_file = manifest_dir.join("sspi_winscard.def"); + #[cfg(not(feature = "scard"))] let sspi_def_file = manifest_dir.join("sspi.def"); println!("cargo:rustc-link-arg=/DEF:{}", sspi_def_file.display()); } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index bddddac5..ef19f680 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,18 +1,12 @@ #![allow(clippy::missing_safety_doc)] +#![allow(non_snake_case)] #[macro_use] extern crate tracing; -#[macro_use] -mod macros; - -pub mod common; -pub mod credentials_attributes; pub mod logging; -pub mod sec_buffer; -pub mod sec_handle; -pub mod sec_pkg_info; -pub mod sec_winnt_auth_identity; -pub mod security_tables; -pub mod sspi_data_types; +pub mod sspi; mod utils; +#[cfg(feature = "scard")] +#[deny(unsafe_op_in_unsafe_fn)] +pub mod winscard; diff --git a/ffi/src/logging.rs b/ffi/src/logging.rs index ffa438ce..33f66496 100644 --- a/ffi/src/logging.rs +++ b/ffi/src/logging.rs @@ -21,7 +21,7 @@ pub fn setup_logger() { let file = match OpenOptions::new().create(true).append(true).open(path) { Ok(f) => f, Err(e) => { - println!("[SSPI-DEBUG] Couldn’t open log file: {e}"); + println!("[SSPI-DEBUG] Couldn't open log file: {e}"); return; } }; diff --git a/ffi/src/common.rs b/ffi/src/sspi/common.rs similarity index 97% rename from ffi/src/common.rs rename to ffi/src/sspi/common.rs index bacf45d3..749c7de2 100644 --- a/ffi/src/common.rs +++ b/ffi/src/sspi/common.rs @@ -10,11 +10,12 @@ use sspi::{ #[cfg(windows)] use symbol_rename_macro::rename_symbol; -use crate::credentials_attributes::CredentialsAttributes; -use crate::sec_buffer::{copy_to_c_sec_buffer, p_sec_buffers_to_security_buffers, PSecBuffer, PSecBufferDesc}; -use crate::sec_handle::{p_ctxt_handle_to_sspi_context, CredentialsHandle, PCredHandle, PCtxtHandle}; -use crate::sspi_data_types::{PTimeStamp, SecurityStatus}; -use crate::utils::{into_raw_ptr, transform_credentials_handle}; +use super::credentials_attributes::CredentialsAttributes; +use super::sec_buffer::{copy_to_c_sec_buffer, p_sec_buffers_to_security_buffers, PSecBuffer, PSecBufferDesc}; +use super::sec_handle::{p_ctxt_handle_to_sspi_context, CredentialsHandle, PCredHandle, PCtxtHandle}; +use super::sspi_data_types::{PTimeStamp, SecurityStatus}; +use super::utils::transform_credentials_handle; +use crate::utils::into_raw_ptr; #[instrument(skip_all)] #[cfg_attr(windows, rename_symbol(to = "Rust_FreeCredentialsHandle"))] diff --git a/ffi/src/credentials_attributes.rs b/ffi/src/sspi/credentials_attributes.rs similarity index 97% rename from ffi/src/credentials_attributes.rs rename to ffi/src/sspi/credentials_attributes.rs index 8404180c..0f1dfb11 100644 --- a/ffi/src/credentials_attributes.rs +++ b/ffi/src/sspi/credentials_attributes.rs @@ -3,7 +3,7 @@ use std::slice::from_raw_parts; use libc::c_void; -use crate::sspi_data_types::{SecChar, SecWChar}; +use super::sspi_data_types::{SecChar, SecWChar}; #[derive(Debug)] pub struct KdcProxySettings { diff --git a/ffi/src/macros.rs b/ffi/src/sspi/macros.rs similarity index 100% rename from ffi/src/macros.rs rename to ffi/src/sspi/macros.rs diff --git a/ffi/src/sspi/mod.rs b/ffi/src/sspi/mod.rs new file mode 100644 index 00000000..48446ef0 --- /dev/null +++ b/ffi/src/sspi/mod.rs @@ -0,0 +1,12 @@ +#[macro_use] +mod macros; + +pub mod common; +pub mod credentials_attributes; +pub mod sec_buffer; +pub mod sec_handle; +pub mod sec_pkg_info; +pub mod sec_winnt_auth_identity; +pub mod security_tables; +pub mod sspi_data_types; +pub mod utils; diff --git a/ffi/src/sec_buffer.rs b/ffi/src/sspi/sec_buffer.rs similarity index 100% rename from ffi/src/sec_buffer.rs rename to ffi/src/sspi/sec_buffer.rs diff --git a/ffi/src/sec_handle.rs b/ffi/src/sspi/sec_handle.rs similarity index 97% rename from ffi/src/sec_handle.rs rename to ffi/src/sspi/sec_handle.rs index fe84625a..3ceeeada 100644 --- a/ffi/src/sec_handle.rs +++ b/ffi/src/sspi/sec_handle.rs @@ -28,17 +28,18 @@ cfg_if::cfg_if! { } } -use crate::credentials_attributes::{ +use super::credentials_attributes::{ extract_kdc_proxy_settings, CredentialsAttributes, SecPkgCredentialsKdcUrlA, SecPkgCredentialsKdcUrlW, }; -use crate::sec_buffer::{copy_to_c_sec_buffer, p_sec_buffers_to_security_buffers, PSecBuffer, PSecBufferDesc}; -use crate::sec_pkg_info::{SecNegoInfoA, SecNegoInfoW, SecPkgInfoA, SecPkgInfoW}; -use crate::sec_winnt_auth_identity::auth_data_to_identity_buffers; -use crate::sspi_data_types::{ +use super::sec_buffer::{copy_to_c_sec_buffer, p_sec_buffers_to_security_buffers, PSecBuffer, PSecBufferDesc}; +use super::sec_pkg_info::{SecNegoInfoA, SecNegoInfoW, SecPkgInfoA, SecPkgInfoW}; +use super::sec_winnt_auth_identity::auth_data_to_identity_buffers; +use super::sspi_data_types::{ CertTrustStatus, LpStr, LpcWStr, PSecurityString, PTimeStamp, SecChar, SecGetKeyFn, SecPkgContextConnectionInfo, SecPkgContextFlags, SecPkgContextSizes, SecPkgContextStreamSizes, SecWChar, SecurityStatus, }; -use crate::utils::{c_w_str_to_string, into_raw_ptr, transform_credentials_handle}; +use super::utils::transform_credentials_handle; +use crate::utils::{c_w_str_to_string, into_raw_ptr}; pub const SECPKG_NEGOTIATION_COMPLETE: u32 = 0; pub const SECPKG_NEGOTIATION_OPTIMISTIC: u32 = 1; @@ -172,7 +173,7 @@ pub(crate) unsafe fn p_ctxt_handle_to_sspi_context( ))?), _ => { return Err(Error::new( - ErrorKind::InvalidParameter, + ErrorKind::SecurityPackageNotFound, format!("security package name `{}` is not supported", name), )); } @@ -187,6 +188,18 @@ pub(crate) unsafe fn p_ctxt_handle_to_sspi_context( Ok((*(*context)).dw_lower as *mut SspiContext) } +fn verify_security_package(package_name: &str) -> Result<()> { + match package_name { + negotiate::PKG_NAME | pku2u::PKG_NAME | kerberos::PKG_NAME | ntlm::PKG_NAME => Ok(()), + #[cfg(feature = "tsssp")] + sspi_cred_ssp::PKG_NAME => Ok(()), + _ => Err(Error::new( + ErrorKind::SecurityPackageNotFound, + format!("security package name `{}` is not supported", package_name), + )), + } +} + #[instrument(skip_all)] #[cfg_attr(windows, rename_symbol(to = "Rust_AcquireCredentialsHandleA"))] #[no_mangle] @@ -208,6 +221,8 @@ pub unsafe extern "system" fn AcquireCredentialsHandleA( let security_package_name = try_execute!(CStr::from_ptr(psz_package).to_str(), ErrorKind::InvalidParameter).to_owned(); + try_execute!(verify_security_package(&security_package_name)); + debug!(?security_package_name); let mut package_list: Option = None; @@ -256,6 +271,8 @@ pub unsafe extern "system" fn AcquireCredentialsHandleW( check_null!(ph_credential); let security_package_name = c_w_str_to_string(psz_package); + try_execute!(verify_security_package(&security_package_name)); + debug!(?security_package_name); let mut package_list: Option = None; diff --git a/ffi/src/sec_pkg_info.rs b/ffi/src/sspi/sec_pkg_info.rs similarity index 93% rename from ffi/src/sec_pkg_info.rs rename to ffi/src/sspi/sec_pkg_info.rs index 3eb03a69..6d24e294 100644 --- a/ffi/src/sec_pkg_info.rs +++ b/ffi/src/sspi/sec_pkg_info.rs @@ -6,8 +6,8 @@ use sspi::{enumerate_security_packages, PackageInfo, KERBEROS_VERSION}; #[cfg(windows)] use symbol_rename_macro::rename_symbol; -use crate::sspi_data_types::{SecChar, SecWChar, SecurityStatus}; -use crate::utils::c_w_str_to_string; +use super::sspi_data_types::{SecChar, SecWChar, SecurityStatus}; +use crate::utils::{c_w_str_to_string, str_to_w_buff}; #[derive(Debug)] #[repr(C)] @@ -25,14 +25,10 @@ pub type PSecPkgInfoW = *mut SecPkgInfoW; #[allow(clippy::useless_conversion)] impl From for &mut SecPkgInfoW { fn from(pkg_info: PackageInfo) -> Self { - let mut pkg_name = pkg_info.name.to_string().encode_utf16().collect::>(); - // null-terminator - pkg_name.push(0); + let pkg_name = str_to_w_buff(pkg_info.name.as_ref()); let name_bytes_len = pkg_name.len() * 2; - let mut pkg_comment = pkg_info.comment.encode_utf16().collect::>(); - // null-terminator - pkg_comment.push(0); + let pkg_comment = str_to_w_buff(&pkg_info.comment); let comment_bytes_len = pkg_comment.len() * 2; let pkg_info_w_size = size_of::(); @@ -83,12 +79,12 @@ pub type PSecPkgInfoA = *mut SecPkgInfoA; impl From for &mut SecPkgInfoA { fn from(pkg_info: PackageInfo) -> Self { let mut pkg_name = pkg_info.name.to_string().as_bytes().to_vec(); - // null-terminator + // We need to add the null-terminator during the conversion from Rust to C string. pkg_name.push(0); let name_bytes_len = pkg_name.len(); let mut pkg_comment = pkg_info.comment.as_bytes().to_vec(); - // null-terminator + // We need to add the null-terminator during the conversion from Rust to C string. pkg_comment.push(0); let comment_bytes_len = pkg_comment.len(); @@ -181,14 +177,14 @@ pub unsafe extern "system" fn EnumerateSecurityPackagesA( pkg_info_a.cb_max_token = pkg_info.max_token_len; let mut name = pkg_info.name.as_ref().as_bytes().to_vec(); - // null-terminator + // We need to add the null-terminator during the conversion from Rust to C string. name.push(0); copy_nonoverlapping(name.as_ptr(), data_ptr as *mut _, name.len()); pkg_info_a.name = data_ptr as *mut _; data_ptr = data_ptr.add(name.len()); let mut comment = pkg_info.comment.as_bytes().to_vec(); - // null-terminator + // We need to add the null-terminator during the conversion from Rust to C string. comment.push(0); copy_nonoverlapping(comment.as_ptr(), data_ptr as *mut _, comment.len()); pkg_info_a.comment = data_ptr as *mut _; @@ -225,12 +221,8 @@ pub unsafe extern "system" fn EnumerateSecurityPackagesW( let mut comments = Vec::with_capacity(packages.len()); for package in &packages { - let mut name = package.name.to_string().encode_utf16().collect::>(); - // null-terminator - name.push(0); - let mut comment = package.comment.encode_utf16().collect::>(); - // null-terminator - comment.push(0); + let name = str_to_w_buff(package.name.as_ref()); + let comment = str_to_w_buff(&package.comment); size += (name.len() + comment.len()) * 2; diff --git a/ffi/src/sec_winnt_auth_identity.rs b/ffi/src/sspi/sec_winnt_auth_identity.rs similarity index 83% rename from ffi/src/sec_winnt_auth_identity.rs rename to ffi/src/sspi/sec_winnt_auth_identity.rs index 4375fb47..a14003e5 100644 --- a/ffi/src/sec_winnt_auth_identity.rs +++ b/ffi/src/sspi/sec_winnt_auth_identity.rs @@ -15,9 +15,7 @@ use winapi::um::wincred::CredIsMarshaledCredentialW; #[cfg(feature = "tsssp")] use windows_sys::Win32::Security::Credentials::{CredUIPromptForWindowsCredentialsW, CREDUI_INFOW}; -use crate::sspi_data_types::{SecWChar, SecurityStatus}; -#[cfg(feature = "tsssp")] -use crate::utils::raw_wide_str_trim_nulls; +use super::sspi_data_types::{SecWChar, SecurityStatus}; use crate::utils::{c_w_str_to_string, into_raw_ptr, raw_str_into_bytes}; pub const SEC_WINNT_AUTH_IDENTITY_ANSI: u32 = 0x1; @@ -234,8 +232,8 @@ pub unsafe fn auth_data_to_identity_buffers( p_auth_data: *const c_void, package_list: &mut Option, ) -> Result { - let rawcreds = std::slice::from_raw_parts(p_auth_data as *const u8, 128); - debug!(?rawcreds); + let raw_creds = from_raw_parts(p_auth_data as *const u8, 128); + debug!(?raw_creds); #[cfg(feature = "tsssp")] if _security_package_name == sspi::credssp::sspi_cred_ssp::PKG_NAME { @@ -304,7 +302,7 @@ pub unsafe fn auth_data_to_identity_buffers_w( ) .into(); - // only marshaled smart card creds starts with '@' char + // Only marshaled smart card creds starts with '@' char. #[cfg(feature = "scard")] if CredIsMarshaledCredentialW(user.as_ptr() as *const _) != 0 { return handle_smart_card_creds(user, password); @@ -324,7 +322,7 @@ pub unsafe fn auth_data_to_identity_buffers_w( ) .into(); - // only marshaled smart card creds starts with '@' char + // Only marshaled smart card creds starts with '@' char. #[cfg(feature = "scard")] if CredIsMarshaledCredentialW(user.as_ptr() as *const _) != 0 { return handle_smart_card_creds(user, password); @@ -351,19 +349,19 @@ unsafe fn get_sec_winnt_auth_identity_ex2_size(p_auth_data: *const c_void) -> u3 // https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_ex2 // https://github.com/FreeRDP/FreeRDP/blob/master/winpr/libwinpr/sspi/sspi_winpr.c#L473 - // username length is placed after the first 8 bytes + // Username length is placed after the first 8 bytes. let user_len_ptr = (p_auth_data as *const u16).add(4); let user_buffer_len = *user_len_ptr as u32; - // domain length is placed after 16 bytes from the username length + // Domain length is placed after 16 bytes from the username length. let domain_len_ptr = user_len_ptr.add(8); let domain_buffer_len = *domain_len_ptr as u32; - // packet credentials length is placed after 16 bytes from the domain length + // Packet credentials length is placed after 16 bytes from the domain length. let creds_len_ptr = domain_len_ptr.add(8); let creds_buffer_len = *creds_len_ptr as u32; - // header size + buffers size + // The resulting size is queal to header size + buffers size. 64 /* size of the SEC_WINNT_AUTH_IDENTITY_EX2 */ + user_buffer_len + domain_buffer_len + creds_buffer_len } @@ -384,7 +382,7 @@ pub unsafe fn unpack_sec_winnt_auth_identity_ex2_a(p_auth_data: *const c_void) - let mut domain_len = 0; let mut password_len = 0; - // the first call is just to query the username, domain, and password length + // The first call is just to query the username, domain, and password lengths. CredUnPackAuthenticationBufferA( CRED_PACK_PROTECTED_CREDENTIALS, p_auth_data, @@ -401,6 +399,7 @@ pub unsafe fn unpack_sec_winnt_auth_identity_ex2_a(p_auth_data: *const c_void) - let mut domain = vec![0_u8; domain_len as usize]; let mut password = Secret::new(vec![0_u8; password_len as usize]); + // Knowing the actual sizes, we can unpack credentials into prepared buffers. let result = CredUnPackAuthenticationBufferA( CRED_PACK_PROTECTED_CREDENTIALS, p_auth_data, @@ -422,23 +421,29 @@ pub unsafe fn unpack_sec_winnt_auth_identity_ex2_a(p_auth_data: *const c_void) - let mut auth_identity_buffers = AuthIdentityBuffers::default(); - // remove null + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, username data is a C string and we need to delete the NULL terminator. username.pop(); auth_identity_buffers.user = username; if domain_len == 0 { - // sometimes username can be formatted as `DOMAIN\username` + // Sometimes username can be formatted as `DOMAIN\username`. if let Some(index) = auth_identity_buffers.user.iter().position(|b| *b == b'\\') { auth_identity_buffers.domain = auth_identity_buffers.user[0..index].to_vec(); auth_identity_buffers.user = auth_identity_buffers.user[(index + 1)..].to_vec(); } } else { - // remove null + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, domain data is a C string and we need to delete the NULL terminator. domain.pop(); auth_identity_buffers.domain = domain; } - // remove null + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, password data is a C string and we need to delete the NULL terminator. password.as_mut().pop(); auth_identity_buffers.password = password; @@ -465,7 +470,8 @@ unsafe fn handle_smart_card_creds(mut username: Vec, password: Secret, password: Secret { + raw_wide_str_trim_nulls(&mut username); + // In the `SmartCardIdentityBuffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, password data is a wide C string and we need to delete the NULL terminator. + let new_len = password.as_ref().len() - 2; + password.as_mut().truncate(new_len); + + return Ok(CredentialsBuffers::SmartCard(SmartCardIdentityBuffers { + username, + certificate: smart_card_info.auth_cert_der.clone(), + card_name: Some(str_encode_utf16(DEFAULT_CARD_NAME)), + reader_name: str_encode_utf16(smart_card_info.reader.name.as_ref()), + container_name: str_encode_utf16(smart_card_info.container_name.as_ref()), + csp_name: str_encode_utf16(MICROSOFT_DEFAULT_CSP), + pin: password, + private_key_file_index: None, + private_key_pem: Some(smart_card_info.auth_pk_pem.as_bytes().to_vec()), + })); + } + Err(err) => { + debug!(?err); + } + }; + } + + // Only marshaled smart card creds starts with '@' char. #[cfg(feature = "scard")] if CredIsMarshaledCredentialW(username.as_ptr() as *const _) != 0 { - // remove null + // The `handle_smart_card_creds` function expects credentials in a form of raw wide strings without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, password data is a wide C string and we need to delete the NULL terminator. let new_len = password.as_ref().len() - 2; password.as_mut().truncate(new_len); @@ -594,23 +640,29 @@ pub unsafe fn unpack_sec_winnt_auth_identity_ex2_w_sized( let mut auth_identity_buffers = AuthIdentityBuffers::default(); - // remove null chars + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, username data is a wide C string and we need to delete the NULL terminator. raw_wide_str_trim_nulls(&mut username); auth_identity_buffers.user = username; if domain_len == 0 { - // sometimes username can be formatted as `DOMAIN\username` + // Sometimes username can be formatted as `DOMAIN\username`. if let Some(index) = auth_identity_buffers.user.iter().position(|b| *b == b'\\') { auth_identity_buffers.domain = auth_identity_buffers.user[0..index].to_vec(); auth_identity_buffers.user = auth_identity_buffers.user[(index + 2)..].to_vec(); } } else { - // remove null + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, domain data is a wide C string and we need to delete the NULL terminator. domain.truncate(domain.len() - 2); auth_identity_buffers.domain = domain; } - // remove null + // In the `auth_identity_buffers` structure we hold credentials as raw wide string without NULL-terminator bytes. + // The `CredUnPackAuthenticationBufferW` function always returns credentials as strings. + // So, password data is a wide C string and we need to delete the NULL terminator. let new_len = password.as_ref().len() - 2; password.as_mut().truncate(new_len); auth_identity_buffers.password = password; @@ -679,8 +731,7 @@ mod tests { use num_traits::ToPrimitive; use sspi::ErrorKind; - use super::{SecWinntAuthIdentityW, SspiEncodeStringsAsAuthIdentity}; - use crate::sec_winnt_auth_identity::SspiFreeAuthIdentity; + use super::{SecWinntAuthIdentityW, SspiEncodeStringsAsAuthIdentity, SspiFreeAuthIdentity}; fn get_user_credentials() -> ([u16; 5], [u16; 5], [u16; 7]) { // (user, pass, domain) diff --git a/ffi/src/security_tables.rs b/ffi/src/sspi/security_tables.rs similarity index 99% rename from ffi/src/security_tables.rs rename to ffi/src/sspi/security_tables.rs index bf4fc202..4fd97b37 100644 --- a/ffi/src/security_tables.rs +++ b/ffi/src/sspi/security_tables.rs @@ -7,7 +7,7 @@ use sspi::KERBEROS_VERSION; #[cfg(windows)] use symbol_rename_macro::rename_symbol; -use crate::common::{ +use super::common::{ AcceptSecurityContext, AcceptSecurityContextFn, ApplyControlToken, ApplyControlTokenFn, CompleteAuthToken, CompleteAuthTokenFn, DecryptMessage, DecryptMessageFn, DeleteSecurityContext, DeleteSecurityContextFn, EncryptMessage, EncryptMessageFn, ExportSecurityContext, ExportSecurityContextFn, FreeContextBuffer, @@ -15,7 +15,7 @@ use crate::common::{ ImpersonateSecurityContextFn, MakeSignature, MakeSignatureFn, QuerySecurityContextToken, QuerySecurityContextTokenFn, RevertSecurityContext, RevertSecurityContextFn, VerifySignature, VerifySignatureFn, }; -use crate::sec_handle::{ +use super::sec_handle::{ AcquireCredentialsHandleA, AcquireCredentialsHandleFnA, AcquireCredentialsHandleFnW, AcquireCredentialsHandleW, AddCredentialsA, AddCredentialsFnA, AddCredentialsFnW, AddCredentialsW, ChangeAccountPasswordA, ChangeAccountPasswordFnA, ChangeAccountPasswordFnW, ChangeAccountPasswordW, ImportSecurityContextA, @@ -29,7 +29,7 @@ use crate::sec_handle::{ SetContextAttributesW, SetCredentialsAttributesA, SetCredentialsAttributesFnA, SetCredentialsAttributesFnW, SetCredentialsAttributesW, }; -use crate::sec_pkg_info::{ +use super::sec_pkg_info::{ EnumerateSecurityPackagesA, EnumerateSecurityPackagesFnA, EnumerateSecurityPackagesFnW, EnumerateSecurityPackagesW, QuerySecurityPackageInfoA, QuerySecurityPackageInfoFnA, QuerySecurityPackageInfoFnW, QuerySecurityPackageInfoW, }; diff --git a/ffi/src/sspi_data_types.rs b/ffi/src/sspi/sspi_data_types.rs similarity index 100% rename from ffi/src/sspi_data_types.rs rename to ffi/src/sspi/sspi_data_types.rs diff --git a/ffi/src/sspi/utils.rs b/ffi/src/sspi/utils.rs new file mode 100644 index 00000000..a306c36e --- /dev/null +++ b/ffi/src/sspi/utils.rs @@ -0,0 +1,30 @@ +use sspi::CredentialsBuffers; + +use super::credentials_attributes::CredentialsAttributes; +use super::sec_handle::CredentialsHandle; + +pub unsafe fn transform_credentials_handle<'a>( + credentials_handle: *mut CredentialsHandle, +) -> Option<(CredentialsBuffers, &'a str, &'a CredentialsAttributes)> { + if credentials_handle.is_null() { + None + } else { + let cred_handle = credentials_handle.as_mut().unwrap(); + Some(( + cred_handle.credentials.clone(), + cred_handle.security_package_name.as_str(), + &cred_handle.attributes, + )) + } +} + +// when encoding an UTF-16 character using two code units, the 16-bit values are chosen from the UTF-16 surrogate range 0xD800–0xDFFF, +// and thus only \0 is encoded by two consecutive null bytes +#[cfg(any(feature = "tsssp", feature = "scard"))] +pub fn raw_wide_str_trim_nulls(raw_str: &mut Vec) { + let mut len = raw_str.len(); + while len > 2 && raw_str[len - 2..] == [0, 0] { + raw_str.truncate(len - 2); + len = raw_str.len(); + } +} diff --git a/ffi/src/utils.rs b/ffi/src/utils.rs index 69cf8c93..203d6ada 100644 --- a/ffi/src/utils.rs +++ b/ffi/src/utils.rs @@ -1,52 +1,33 @@ use std::slice::from_raw_parts; use libc::c_char; -use sspi::CredentialsBuffers; - -use crate::credentials_attributes::CredentialsAttributes; -use crate::sec_handle::CredentialsHandle; -use crate::sspi_data_types::SecWChar; pub fn into_raw_ptr(value: T) -> *mut T { Box::into_raw(Box::new(value)) } -pub unsafe fn c_w_str_to_string(s: *const SecWChar) -> String { +pub unsafe fn c_w_str_to_string(s: *const u16) -> String { let mut len = 0; - while *(s.add(len)) != 0 { + while unsafe { *(s.add(len)) } != 0 { len += 1; } - String::from_utf16_lossy(from_raw_parts(s, len)) + String::from_utf16_lossy(unsafe { from_raw_parts(s, len) }) } pub unsafe fn raw_str_into_bytes(raw_buffer: *const c_char, len: usize) -> Vec { - from_raw_parts(raw_buffer, len).iter().map(|c| *c as u8).collect() + unsafe { from_raw_parts(raw_buffer, len) } + .iter() + .map(|c| *c as u8) + .collect() } -pub unsafe fn transform_credentials_handle<'a>( - credentials_handle: *mut CredentialsHandle, -) -> Option<(CredentialsBuffers, &'a str, &'a CredentialsAttributes)> { - if credentials_handle.is_null() { - None - } else { - let cred_handle = credentials_handle.as_mut().unwrap(); - Some(( - cred_handle.credentials.clone(), - cred_handle.security_package_name.as_str(), - &cred_handle.attributes, - )) - } +pub fn str_to_w_buff(data: &str) -> Vec { + data.encode_utf16().chain(std::iter::once(0)).collect() } -// when encoding an UTF-16 character using two code units, the 16-bit values are chosen from the UTF-16 surrogate range 0xD800–0xDFFF, -// and thus only \0 is encoded by two consecutive null bytes -#[cfg(feature = "tsssp")] -pub fn raw_wide_str_trim_nulls(raw_str: &mut Vec) { - let mut len = raw_str.len(); - while len > 2 && raw_str[len - 2..] == [0, 0] { - raw_str.truncate(len - 2); - len = raw_str.len(); - } +#[cfg(all(feature = "scard", feature = "tsssp"))] +pub fn str_encode_utf16(data: &str) -> Vec { + data.encode_utf16().flat_map(|c| c.to_le_bytes()).collect() } diff --git a/ffi/src/winscard/buf_alloc.rs b/ffi/src/winscard/buf_alloc.rs new file mode 100644 index 00000000..3d2a360a --- /dev/null +++ b/ffi/src/winscard/buf_alloc.rs @@ -0,0 +1,121 @@ +use std::iter::once; +use std::slice::from_raw_parts_mut; + +use ffi_types::{LpByte, LpDword, LpStr, LpWStr}; +use winscard::{Error, ErrorKind, WinScardResult}; + +use super::scard_handle::WinScardContextHandle; +use crate::utils::str_to_w_buff; + +pub const SCARD_AUTOALLOCATE: u32 = 0xffffffff; + +pub unsafe fn copy_buff( + context: &mut WinScardContextHandle, + raw_buff: LpByte, + raw_buff_len: LpDword, + buff_to_copy: &[u8], +) -> WinScardResult<()> { + let buff_to_copy_len = buff_to_copy.len().try_into()?; + + if raw_buff.is_null() { + unsafe { + *raw_buff_len = buff_to_copy_len; + } + return Ok(()); + } + + if unsafe { *raw_buff_len } == SCARD_AUTOALLOCATE { + // Allocate a new buffer and write an address into raw_buff. + let allocated = context.allocate_buffer(buff_to_copy.len())?; + unsafe { + *(raw_buff as *mut *mut u8) = allocated; + *raw_buff_len = buff_to_copy_len; + from_raw_parts_mut(allocated, buff_to_copy.len()).copy_from_slice(buff_to_copy); + } + } else { + if buff_to_copy_len > unsafe { *raw_buff_len } { + return Err(Error::new( + ErrorKind::InsufficientBuffer, + format!( + "expected at least {} bytes but got {}.", + buff_to_copy_len, *raw_buff_len + ), + )); + } + unsafe { + *raw_buff_len = buff_to_copy_len; + from_raw_parts_mut(raw_buff, buff_to_copy.len()).copy_from_slice(buff_to_copy); + } + } + + Ok(()) +} + +pub unsafe fn copy_w_buff( + context: &mut WinScardContextHandle, + raw_buf: LpWStr, + raw_buf_len: LpDword, + buff_to_copy: &[u16], +) -> WinScardResult<()> { + let buff_to_copy_len = buff_to_copy.len().try_into()?; + + if raw_buf.is_null() { + unsafe { + *raw_buf_len = buff_to_copy_len; + } + return Ok(()); + } + + if unsafe { *raw_buf_len } == SCARD_AUTOALLOCATE { + // Allocate a new buffer and write an address into raw_buff. + let allocated = context.allocate_buffer(buff_to_copy.len() * 2)? as *mut u16; + unsafe { + *(raw_buf as *mut *mut u16) = allocated; + *raw_buf_len = buff_to_copy_len; + from_raw_parts_mut(allocated, buff_to_copy.len()).copy_from_slice(buff_to_copy); + } + } else { + if buff_to_copy_len > unsafe { *raw_buf_len } { + return Err(Error::new( + ErrorKind::InsufficientBuffer, + format!("expected at least {} bytes but got {}.", buff_to_copy_len, *raw_buf_len), + )); + } + unsafe { + *raw_buf_len = buff_to_copy_len; + from_raw_parts_mut(raw_buf, buff_to_copy.len()).copy_from_slice(buff_to_copy); + } + } + + Ok(()) +} + +pub unsafe fn write_multistring_a( + context: &mut WinScardContextHandle, + strings: &[&str], + dest: LpStr, + dest_len: LpDword, +) -> WinScardResult<()> { + let buffer: Vec = strings + .iter() + .flat_map(|reader| reader.as_bytes().iter().cloned().chain(once(0))) + .chain(once(0)) + .collect(); + + unsafe { copy_buff(context, dest, dest_len, &buffer) } +} + +pub unsafe fn write_multistring_w( + context: &mut WinScardContextHandle, + strings: &[&str], + dest: LpWStr, + dest_len: LpDword, +) -> WinScardResult<()> { + let buffer: Vec = strings + .iter() + .flat_map(|reader| str_to_w_buff(reader)) + .chain(once(0)) + .collect(); + + unsafe { copy_w_buff(context, dest, dest_len, &buffer) } +} diff --git a/ffi/src/winscard/macros.rs b/ffi/src/winscard/macros.rs new file mode 100644 index 00000000..3dc07986 --- /dev/null +++ b/ffi/src/winscard/macros.rs @@ -0,0 +1,36 @@ +macro_rules! check_handle { + ($x:expr) => {{ + if $x == 0 { + return u32::from(winscard::ErrorKind::InvalidHandle); + } + }}; +} + +macro_rules! try_execute { + ($x:expr) => {{ + match $x { + Ok(value) => value, + Err(err) => { + error!(%err, "an error occurred"); + return u32::from(err.error_kind); + } + } + }}; + ($x:expr, $err_value:expr) => {{ + match $x { + Ok(val) => val, + Err(err) => { + error!(%err, "an error occurred"); + return $err_value.into(); + } + } + }}; +} + +macro_rules! check_null { + ($x:expr) => {{ + if $x.is_null() { + return u32::from(winscard::ErrorKind::InvalidParameter); + } + }}; +} diff --git a/ffi/src/winscard/mod.rs b/ffi/src/winscard/mod.rs new file mode 100644 index 00000000..05cca4aa --- /dev/null +++ b/ffi/src/winscard/mod.rs @@ -0,0 +1,117 @@ +use ffi_types::winscard::functions::{PSCardApiFunctionTable, SCardApiFunctionTable}; +use ffi_types::winscard::ScardIoRequest; +use winscard::winscard::Protocol; + +use self::scard::*; +use self::scard_context::*; +use crate::utils::into_raw_ptr; + +#[macro_use] +mod macros; +mod buf_alloc; +pub mod scard; +pub mod scard_context; +mod scard_handle; + +// The constants below are not documented anywhere and were discovered during debugging. +// Related example: https://github.com/bluetech/pcsc-rust/blob/b397cc8e3834a1dc791631105f37f34d321c8696/pcsc/src/lib.rs#L605-L613 +#[no_mangle] +pub static Rust_g_rgSCardT1Pci: ScardIoRequest = ScardIoRequest { + dw_protocol: Protocol::T1.bits(), + cb_pci_length: 8, +}; + +#[no_mangle] +pub static Rust_g_rgSCardT0Pci: ScardIoRequest = ScardIoRequest { + dw_protocol: Protocol::T0.bits(), + cb_pci_length: 8, +}; + +#[no_mangle] +pub static Rust_g_rgSCardRawPci: ScardIoRequest = ScardIoRequest { + dw_protocol: Protocol::Raw.bits(), + cb_pci_length: 8, +}; + +pub extern "system" fn GetSCardApiFunctionTable() -> PSCardApiFunctionTable { + crate::logging::setup_logger(); + + into_raw_ptr(SCardApiFunctionTable { + dw_version: 0, + dw_flags: 0, + + SCardEstablishContext, + SCardReleaseContext, + SCardIsValidContext, + SCardListReaderGroupsA, + SCardListReaderGroupsW, + SCardListReadersA, + SCardListReadersW, + SCardListCardsA, + SCardListCardsW, + SCardListInterfacesA, + SCardListInterfacesW, + SCardGetProviderIdA, + SCardGetProviderIdW, + SCardGetCardTypeProviderNameA, + SCardGetCardTypeProviderNameW, + SCardIntroduceReaderGroupA, + SCardIntroduceReaderGroupW, + SCardForgetReaderGroupA, + SCardForgetReaderGroupW, + SCardIntroduceReaderA, + SCardIntroduceReaderW, + SCardForgetReaderA, + SCardForgetReaderW, + SCardAddReaderToGroupA, + SCardAddReaderToGroupW, + SCardRemoveReaderFromGroupA, + SCardRemoveReaderFromGroupW, + SCardIntroduceCardTypeA, + SCardIntroduceCardTypeW, + SCardSetCardTypeProviderNameA, + SCardSetCardTypeProviderNameW, + SCardFreeMemory, + SCardAccessStartedEvent, + SCardReleaseStartedEvent, + SCardLocateCardsA, + SCardLocateCardsW, + SCardLocateCardsByATRA, + SCardLocateCardsByATRW, + SCardGetStatusChangeA, + SCardGetStatusChangeW, + SCardCancel, + SCardConnectA, + SCardConnectW, + SCardReconnect, + SCardDisconnect, + SCardBeginTransaction, + SCardEndTransaction, + SCardCancelTransaction, + SCardState, + SCardStatusA, + SCardStatusW, + SCardTransmit, + SCardGetTransmitCount, + SCardControl, + SCardGetAttrib, + SCardSetAttrib, + SCardUIDlgSelectCardA, + SCardUIDlgSelectCardW, + GetOpenCardNameA, + GetOpenCardNameW, + SCardReadCacheA, + SCardReadCacheW, + SCardWriteCacheA, + SCardWriteCacheW, + SCardGetReaderIconA, + SCardGetReaderIconW, + SCardGetDeviceTypeIdA, + SCardGetDeviceTypeIdW, + SCardGetReaderDeviceInstanceIdA, + SCardGetReaderDeviceInstanceIdW, + SCardListReadersWithDeviceInstanceIdA, + SCardListReadersWithDeviceInstanceIdW, + SCardAudit, + }) +} diff --git a/ffi/src/winscard/scard.rs b/ffi/src/winscard/scard.rs new file mode 100644 index 00000000..7130afeb --- /dev/null +++ b/ffi/src/winscard/scard.rs @@ -0,0 +1,419 @@ +use std::ffi::CStr; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +use ffi_types::winscard::{ + LpOpenCardNameA, LpOpenCardNameExA, LpOpenCardNameExW, LpOpenCardNameW, LpScardHandle, LpScardIoRequest, + ScardContext, ScardHandle, ScardStatus, +}; +use ffi_types::{LpByte, LpCByte, LpCStr, LpCVoid, LpCWStr, LpDword, LpStr, LpVoid, LpWStr}; +use symbol_rename_macro::rename_symbol; +use winscard::winscard::Protocol; +use winscard::{ErrorKind, WinScardResult}; + +use super::buf_alloc::{copy_buff, write_multistring_a, write_multistring_w}; +use crate::utils::{c_w_str_to_string, into_raw_ptr}; +use crate::winscard::scard_handle::{ + copy_io_request_to_scard_io_request, scard_context_to_winscard_context, scard_handle_to_winscard, + scard_io_request_to_io_request, WinScardContextHandle, WinScardHandle, +}; + +unsafe fn connect( + context: ScardContext, + reader_name: &str, + dw_share_mode: u32, + dw_preferred_protocols: u32, + ph_card: LpScardHandle, + pdw_active_protocol: LpDword, +) -> WinScardResult<()> { + let share_mode = dw_share_mode.try_into()?; + let protocol = Protocol::from_bits(dw_preferred_protocols); + + let scard_context = unsafe { scard_context_to_winscard_context(context)? }; + let scard = scard_context.connect(reader_name, share_mode, protocol)?; + let protocol = scard.status()?.protocol.bits(); + + let scard = WinScardHandle::new(scard, context); + + let raw_card_handle = into_raw_ptr(scard) as ScardHandle; + + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + context.add_scard(raw_card_handle)?; + + unsafe { + *ph_card = raw_card_handle; + *pdw_active_protocol = protocol; + } + + Ok(()) +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardConnectA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardConnectA( + context: ScardContext, + sz_reader: LpCStr, + dw_share_mode: u32, + dw_preferred_protocols: u32, + ph_card: LpScardHandle, + pdw_active_protocol: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader); + check_null!(ph_card); + check_null!(pdw_active_protocol); + + let reader_name = try_execute!( + unsafe { CStr::from_ptr(sz_reader as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + + try_execute!(connect( + context, + &reader_name, + dw_share_mode, + dw_preferred_protocols, + ph_card, + pdw_active_protocol + )); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardConnectW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardConnectW( + context: ScardContext, + sz_reader: LpCWStr, + dw_share_mode: u32, + dw_preferred_protocols: u32, + ph_card: LpScardHandle, + pdw_active_protocol: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader); + check_null!(ph_card); + check_null!(pdw_active_protocol); + + let reader_name = unsafe { c_w_str_to_string(sz_reader) }; + + try_execute!(unsafe { + connect( + context, + &reader_name, + dw_share_mode, + dw_preferred_protocols, + ph_card, + pdw_active_protocol, + ) + }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardReconnect"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardReconnect( + _handle: ScardHandle, + _dw_share_mode: u32, + _dw_preferred_protocols: u32, + _dw_initialization: u32, + _pdw_active_protocol: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardDisconnect"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardDisconnect(handle: ScardHandle, _dw_disposition: u32) -> ScardStatus { + check_handle!(handle); + + let scard = unsafe { Box::from_raw(handle as *mut WinScardHandle) }; + if let Some(context) = unsafe { (scard.context() as *mut WinScardContextHandle).as_mut() } { + if context.remove_scard(handle) { + info!(?handle, "Successfully disconnected!"); + } else { + warn!("ScardHandle does not belong to the specified context.") + } + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardBeginTransaction"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardBeginTransaction(handle: ScardHandle) -> ScardStatus { + check_handle!(handle); + let scard = try_execute!(unsafe { scard_handle_to_winscard(handle) }); + + try_execute!(scard.begin_transaction()); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardEndTransaction"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardEndTransaction(handle: ScardHandle, _dw_disposition: u32) -> ScardStatus { + check_handle!(handle); + let scard = try_execute!(unsafe { scard_handle_to_winscard(handle) }); + + try_execute!(scard.end_transaction()); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardCancelTransaction"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardCancelTransaction(_handle: ScardHandle) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardState"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardState( + _handle: ScardHandle, + _pdw_state: LpDword, + _pdw_protocol: LpDword, + _pb_atr: LpByte, + _pcb_atr_len: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardStatusA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardStatusA( + handle: ScardHandle, + msz_reader_names: LpStr, + pcch_reader_len: LpDword, + pdw_state: LpDword, + pdw_protocol: LpDword, + pb_atr: LpByte, + pcb_atr_len: LpDword, +) -> ScardStatus { + check_handle!(handle); + check_null!(msz_reader_names); + check_null!(pcch_reader_len); + check_null!(pdw_state); + check_null!(pdw_protocol); + // pb_atr can be null. + // it's not specified in a docs, but `msclmd.dll` can invoke this function with pb_atr = 0. + check_null!(pcb_atr_len); + + let scard = unsafe { (handle as *mut WinScardHandle).as_ref().unwrap() }; + let status = try_execute!(scard.scard().status()); + check_handle!(scard.context()); + + let readers = status.readers.iter().map(|reader| reader.as_ref()).collect::>(); + let context = unsafe { (scard.context() as *mut WinScardContextHandle).as_mut() }.unwrap(); + try_execute!(unsafe { write_multistring_a(context, &readers, msz_reader_names, pcch_reader_len) }); + unsafe { + *pdw_state = status.state.into(); + *pdw_protocol = status.protocol.bits(); + } + + if !pb_atr.is_null() { + try_execute!(unsafe { copy_buff(context, pb_atr, pcb_atr_len, status.atr.as_ref()) }); + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardStatusW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardStatusW( + handle: ScardHandle, + msz_reader_names: LpWStr, + pcch_reader_len: LpDword, + pdw_state: LpDword, + pdw_protocol: LpDword, + pb_atr: LpByte, + pcb_atr_len: LpDword, +) -> ScardStatus { + check_handle!(handle); + check_null!(msz_reader_names); + check_null!(pcch_reader_len); + check_null!(pdw_state); + check_null!(pdw_protocol); + // pb_atr can be null. + // it's not specified in a docs, but `msclmd.dll` can invoke this function with pb_atr = 0. + check_null!(pcb_atr_len); + + let scard = unsafe { (handle as *mut WinScardHandle).as_ref() }.unwrap(); + let status = try_execute!(scard.scard().status()); + check_handle!(scard.context()); + + let readers = status.readers.iter().map(|reader| reader.as_ref()).collect::>(); + let context = unsafe { (scard.context() as *mut WinScardContextHandle).as_mut() }.unwrap(); + try_execute!(unsafe { write_multistring_w(context, &readers, msz_reader_names, pcch_reader_len) }); + unsafe { + *pdw_state = status.state.into(); + *pdw_protocol = status.protocol.bits(); + } + + if !pb_atr.is_null() { + try_execute!(unsafe { copy_buff(context, pb_atr, pcb_atr_len, status.atr.as_ref()) }); + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardTransmit"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardTransmit( + handle: ScardHandle, + pio_send_pci: LpScardIoRequest, + pb_send_buffer: LpCByte, + cb_send_length: u32, + pio_recv_pci: LpScardIoRequest, + pb_recv_buffer: LpByte, + pcb_recv_length: LpDword, +) -> ScardStatus { + check_handle!(handle); + check_null!(pio_send_pci); + let scard = try_execute!(unsafe { scard_handle_to_winscard(handle) }); + + let io_request = try_execute!(unsafe { scard_io_request_to_io_request(pio_send_pci) }); + let input_apdu = unsafe { + from_raw_parts( + pb_send_buffer, + try_execute!(cb_send_length.try_into(), ErrorKind::InsufficientBuffer), + ) + }; + + let out_data = try_execute!(scard.transmit(io_request, input_apdu)); + + let out_apdu_len = out_data.output_apdu.len(); + if out_apdu_len > try_execute!(unsafe { *pcb_recv_length }.try_into(), ErrorKind::InsufficientBuffer) + || pb_recv_buffer.is_null() + { + return ErrorKind::InsufficientBuffer.into(); + } + + let recv_buffer = unsafe { from_raw_parts_mut(pb_recv_buffer, out_apdu_len) }; + recv_buffer.copy_from_slice(&out_data.output_apdu); + + if !pio_recv_pci.is_null() && out_data.receive_pci.is_some() { + try_execute!(unsafe { + copy_io_request_to_scard_io_request(out_data.receive_pci.as_ref().unwrap(), pio_recv_pci) + }); + } + + unsafe { + *pcb_recv_length = try_execute!(out_apdu_len.try_into(), ErrorKind::InsufficientBuffer); + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetTransmitCount"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetTransmitCount(_handle: ScardHandle, pc_transmit_count: LpDword) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardControl"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardControl( + handle: ScardHandle, + dw_control_code: u32, + lp_in_buffer: LpCVoid, + cb_in_buffer_size: u32, + lp_out_buffer: LpVoid, + cb_out_buffer_size: u32, + lp_bytes_returned: LpDword, +) -> ScardStatus { + check_handle!(handle); + let scard = try_execute!(unsafe { scard_handle_to_winscard(handle) }); + + let in_buffer = if !lp_in_buffer.is_null() { + unsafe { + from_raw_parts( + lp_in_buffer as *const u8, + try_execute!(cb_in_buffer_size.try_into(), ErrorKind::InsufficientBuffer), + ) + } + } else { + &[] + }; + let out_buffer = try_execute!(scard.control(try_execute!(dw_control_code.try_into()), in_buffer)); + let out_buffer_len = try_execute!(out_buffer.len().try_into(), ErrorKind::InsufficientBuffer); + + if !lp_out_buffer.is_null() { + if out_buffer_len > cb_out_buffer_size { + return ErrorKind::InsufficientBuffer.into(); + } + + let lp_out_buffer = unsafe { from_raw_parts_mut(lp_out_buffer as *mut u8, out_buffer.len()) }; + lp_out_buffer.copy_from_slice(&out_buffer); + unsafe { + *lp_bytes_returned = out_buffer_len; + } + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetAttrib"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetAttrib( + _handle: ScardHandle, + _dw_attr_id: u32, + _pb_attr: LpByte, + _pcb_attrLen: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardSetAttrib"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardSetAttrib( + _handle: ScardHandle, + _dw_attr_id: u32, + _pb_attr: LpCByte, + _cb_attrLen: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardUIDlgSelectCardA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardUIDlgSelectCardA(_p: LpOpenCardNameExA) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardUIDlgSelectCardW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardUIDlgSelectCardW(_p: LpOpenCardNameExW) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_GetOpenCardNameA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn GetOpenCardNameA(_p: LpOpenCardNameA) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_GetOpenCardNameW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn GetOpenCardNameW(_p: LpOpenCardNameW) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} diff --git a/ffi/src/winscard/scard_context.rs b/ffi/src/winscard/scard_context.rs new file mode 100644 index 00000000..842fd46e --- /dev/null +++ b/ffi/src/winscard/scard_context.rs @@ -0,0 +1,947 @@ +use std::borrow::Cow; +use std::ffi::CStr; +use std::slice::from_raw_parts_mut; + +use ffi_types::winscard::{ + LpScardAtrMask, LpScardContext, LpScardReaderStateA, LpScardReaderStateW, ScardContext, ScardStatus, +}; +use ffi_types::{Handle, LpByte, LpCByte, LpCGuid, LpCStr, LpCVoid, LpCWStr, LpDword, LpGuid, LpStr, LpUuid, LpWStr}; +use libc::c_void; +use symbol_rename_macro::rename_symbol; +use winscard::winscard::WinScardContext; +use winscard::{ErrorKind, ScardContext as PivCardContext, SmartCardInfo, WinScardResult, ATR}; + +use super::buf_alloc::{copy_w_buff, write_multistring_a, write_multistring_w}; +use crate::utils::{c_w_str_to_string, into_raw_ptr, str_to_w_buff}; +use crate::winscard::buf_alloc::copy_buff; +use crate::winscard::scard_handle::{scard_context_to_winscard_context, WinScardContextHandle}; + +const SCARD_STATE_CHANGED: u32 = 0x00000002; +const SCARD_STATE_INUSE: u32 = 0x00000100; +const SCARD_STATE_PRESENT: u32 = 0x00000020; +// Undocumented constant that appears in all API captures +const SCARD_STATE_UNNAMED_CONSTANT: u32 = 0x00010000; + +// https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetcardtypeprovidernamew +// `dwProviderId` function parameter:: +// The function retrieves the name of the smart card's primary service provider as a GUID string. +const SCARD_PROVIDER_PRIMARY: u32 = 1; +// The function retrieves the name of the cryptographic service provider. +const SCARD_PROVIDER_CSP: u32 = 2; +// The function retrieves the name of the smart card key storage provider (KSP). +const SCARD_PROVIDER_KSP: u32 = 3; +// The function retrieves the name of the card module. +const SCARD_PROVIDER_CARD_MODULE: u32 = 0x80000001; + +pub const MICROSOFT_DEFAULT_CSP: &str = "Microsoft Base Smart Card Crypto Provider"; +const MICROSOFT_DEFAULT_KSP: &str = "Microsoft Smart Card Key Storage Provider"; +const MICROSOFT_SCARD_DRIVER_LOCATION: &str = "C:\\Windows\\System32\\msclmd.dll"; + +pub const DEFAULT_CARD_NAME: &str = "Cool card"; + +// https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardgetstatuschangew +// To be notified of the arrival of a new smart card reader, +// set the szReader member of a SCARD_READERSTATE structure to "\\?PnP?\Notification", +const NEW_READER_NOTIFICATION: &str = "\\\\?PnP?\\Notification"; + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardEstablishContext"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardEstablishContext( + _dw_scope: u32, + _r1: *const c_void, + _r2: *const c_void, + context: LpScardContext, +) -> ScardStatus { + crate::logging::setup_logger(); + check_null!(context); + + let scard_info = try_execute!(SmartCardInfo::try_from_env()); + // We have only one available reader + let scard_context: Box = Box::new(try_execute!(PivCardContext::new(scard_info))); + + let scard_context = WinScardContextHandle::with_scard_context(scard_context); + + let raw_ptr = into_raw_ptr(scard_context) as ScardContext; + info!(new_established_context = ?raw_ptr); + unsafe { + *context = raw_ptr; + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardReleaseContext"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardReleaseContext(context: ScardContext) -> ScardStatus { + check_handle!(context); + + let _ = unsafe { Box::from_raw(context as *mut WinScardContextHandle) }; + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIsValidContext"))] +#[no_mangle] +pub unsafe extern "system" fn SCardIsValidContext(context: ScardContext) -> ScardStatus { + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + + if context.is_valid() { + ErrorKind::Success.into() + } else { + ErrorKind::InvalidHandle.into() + } +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReaderGroupsA"))] +#[no_mangle] +pub extern "system" fn SCardListReaderGroupsA( + _context: ScardContext, + _gmsz_groups: LpStr, + _pcch_groups: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReaderGroupsW"))] +#[no_mangle] +pub extern "system" fn SCardListReaderGroupsW( + _context: ScardContext, + _gmsz_groups: LpWStr, + _pcch_groups: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReadersA"))] +#[no_mangle] +pub unsafe extern "system" fn SCardListReadersA( + context: ScardContext, + _msz_groups: LpCStr, + msz_readers: LpStr, + pcch_readers: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(msz_readers); + check_null!(pcch_readers); + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + let readers = context.scard_context().list_readers(); + let readers = readers.iter().map(|reader| reader.to_string()).collect::>(); + let readers = readers.iter().map(|reader| reader.as_ref()).collect::>(); + + try_execute!(unsafe { write_multistring_a(context, &readers, msz_readers, pcch_readers) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReadersW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardListReadersW( + context: ScardContext, + _msz_groups: LpCWStr, + msz_readers: LpWStr, + pcch_readers: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(msz_readers); + check_null!(pcch_readers); + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + let readers = context.scard_context().list_readers(); + let readers = readers.iter().map(|reader| reader.to_string()).collect::>(); + let readers = readers.iter().map(|reader| reader.as_ref()).collect::>(); + + try_execute!(unsafe { write_multistring_w(context, &readers, msz_readers, pcch_readers) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListCardsA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardListCardsA( + context: ScardContext, + _pb_atr: LpCByte, + _rgquid_nterfaces: LpCGuid, + _cguid_interface_count: u32, + msz_cards: *mut u8, + pcch_cards: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(msz_cards); + check_null!(pcch_cards); + + // safe: checked above + let context = (context as *mut WinScardContextHandle).as_mut().unwrap(); + // we have only one smart card with only one default name + try_execute!(unsafe { write_multistring_a(context, &[DEFAULT_CARD_NAME], msz_cards, pcch_cards) }); + + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListCardsW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardListCardsW( + context: ScardContext, + _pb_atr: LpCByte, + _rgquid_nterfaces: LpCGuid, + _cguid_interface_count: u32, + msz_cards: *mut u16, + pcch_cards: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(msz_cards); + check_null!(pcch_cards); + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + // we have only one smart card with only one default name + try_execute!(unsafe { write_multistring_w(context, &[DEFAULT_CARD_NAME], msz_cards, pcch_cards) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListInterfacesA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardListInterfacesA( + _context: ScardContext, + _sz_scard: LpCStr, + _pguid_interfaces: LpGuid, + _pcguid_interfaces: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListInterfacesW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardListInterfacesW( + _context: ScardContext, + _sz_scard: LpCWStr, + _pguid_interfaces: LpGuid, + _pcguid_interfaces: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetProviderIdA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetProviderIdA( + _context: ScardContext, + _sz_card: LpCStr, + _pguid_provider_id: LpGuid, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetProviderIdW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetProviderIdW( + _context: ScardContext, + _sz_card: LpCWStr, + _pguid_provider_id: LpGuid, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetCardTypeProviderNameA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetCardTypeProviderNameA( + context: ScardContext, + _sz_card_name: LpCStr, + dw_provide_id: u32, + szProvider: *mut u8, + pcch_provider: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(szProvider); + check_null!(pcch_provider); + + let provider = match dw_provide_id { + SCARD_PROVIDER_PRIMARY => { + error!("Unsupported dw_provider_id: SCARD_PROVIDER_PRIMARY"); + return ErrorKind::UnsupportedFeature.into(); + } + SCARD_PROVIDER_CSP => MICROSOFT_DEFAULT_CSP, + SCARD_PROVIDER_KSP => MICROSOFT_DEFAULT_KSP, + SCARD_PROVIDER_CARD_MODULE => MICROSOFT_SCARD_DRIVER_LOCATION, + _ => { + error!(?dw_provide_id, "Unsupported dw_provider_id."); + return ErrorKind::InvalidParameter.into(); + } + }; + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + try_execute!(unsafe { copy_buff(context, szProvider, pcch_provider, provider.as_bytes()) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetCardTypeProviderNameW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetCardTypeProviderNameW( + context: ScardContext, + _sz_card_name: LpCWStr, + dw_provide_id: u32, + szProvider: *mut u16, + pcch_provider: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(szProvider); + check_null!(pcch_provider); + + let provider = match dw_provide_id { + SCARD_PROVIDER_PRIMARY => { + error!("Unsupported dw_provider_id: SCARD_PROVIDER_PRIMARY"); + return ErrorKind::UnsupportedFeature.into(); + } + SCARD_PROVIDER_CSP => MICROSOFT_DEFAULT_CSP, + SCARD_PROVIDER_KSP => MICROSOFT_DEFAULT_KSP, + SCARD_PROVIDER_CARD_MODULE => MICROSOFT_SCARD_DRIVER_LOCATION, + _ => { + error!(?dw_provide_id, "Unsupported dw_provider_id."); + return ErrorKind::InvalidParameter.into(); + } + }; + let encoded = str_to_w_buff(provider); + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + try_execute!(unsafe { copy_w_buff(context, szProvider, pcch_provider, &encoded) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceReaderGroupA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceReaderGroupA(_context: ScardContext, _sz_group_name: LpCStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceReaderGroupW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceReaderGroupW(_context: ScardContext, _sz_group_name: LpCWStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetReaderGroupA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetReaderGroupA(_context: ScardContext, _sz_group_name: LpCStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetReaderGroupW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetReaderGroupW(_context: ScardContext, _sz_group_name: LpCWStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceReaderA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceReaderA( + _context: ScardContext, + _sz_reader_name: LpCStr, + _sz_device_name: LpCStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceReaderW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceReaderW( + _context: ScardContext, + _sz_reader_name: LpCWStr, + _sz_device_name: LpCWStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetReaderA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetReaderA(_context: ScardContext, _sz_reader_name: LpCStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetReaderW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetReaderW(_context: ScardContext, _sz_reader_name: LpCWStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardAddReaderToGroupA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardAddReaderToGroupA( + _context: ScardContext, + _sz_reader_name: LpCStr, + _sz_group_name: LpCStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardAddReaderToGroupW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardAddReaderToGroupW( + _context: ScardContext, + _sz_reader_name: LpCWStr, + _sz_group_name: LpCWStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardRemoveReaderFromGroupA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardRemoveReaderFromGroupA( + _context: ScardContext, + _sz_reader_name: LpCStr, + _sz_group_name: LpCStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardRemoveReaderFromGroupW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardRemoveReaderFromGroupW( + _context: ScardContext, + _sz_reader_name: LpCWStr, + _sz_group_name: LpCWStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceCardTypeA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceCardTypeA( + _context: ScardContext, + _sz_card_name: LpCStr, + _pguid_primary_provider: LpCGuid, + _rgguid_interfaces: LpCGuid, + _dw_interface_count: u32, + _pb_atr: LpCByte, + _pb_atr_mask: LpCByte, + _cb_atr_len: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardIntroduceCardTypeW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardIntroduceCardTypeW( + _context: ScardContext, + _sz_card_name: LpCWStr, + _pguid_primary_provider: LpCGuid, + _rgguid_interfaces: LpCGuid, + _dw_interface_count: u32, + _pb_atr: LpCByte, + _pb_atr_mask: LpCByte, + _cb_atr_len: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardSetCardTypeProviderNameA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardSetCardTypeProviderNameA( + _context: ScardContext, + _sz_card_name: LpCStr, + _dw_provider_id: u32, + _sz_provider: LpCStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardSetCardTypeProviderNameW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardSetCardTypeProviderNameW( + _context: ScardContext, + _sz_card_name: LpCWStr, + _dw_provider_id: u32, + _sz_provider: LpCWStr, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetCardTypeA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetCardTypeA(_context: ScardContext, _sz_card_name: LpCStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardForgetCardTypeW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardForgetCardTypeW(_context: ScardContext, _sz_card_name: LpCWStr) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardFreeMemory"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardFreeMemory(context: ScardContext, pv_mem: LpCVoid) -> ScardStatus { + if let Some(context) = unsafe { (context as *mut WinScardContextHandle).as_mut() } { + if context.free_buffer(pv_mem) { + info!("Allocated buffer successfully freed."); + } else { + warn!(?pv_mem, "Attempt to free unknown buffer"); + } + + ErrorKind::Success.into() + } else { + ErrorKind::InvalidHandle.into() + } +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardAccessStartedEvent"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardAccessStartedEvent() -> Handle { + // This value has been extracted from the original winscard SCardAccessStartedEvent call. + 0x0000000000000eb0 as Handle +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardReleaseStartedEvent"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardReleaseStartedEvent() {} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardLocateCardsA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardLocateCardsA( + _context: ScardContext, + _msz_cards: LpCStr, + _rg_reader_states: LpScardReaderStateA, + _c_readers: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardLocateCardsW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardLocateCardsW( + _context: ScardContext, + _msz_cards: LpCWStr, + _rg_reader_states: LpScardReaderStateW, + _c_readers: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardLocateCardsByATRA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardLocateCardsByATRA( + _context: ScardContext, + _rg_atr_masks: LpScardAtrMask, + _c_atrs: u32, + _rg_reader_states: LpScardReaderStateA, + _c_readers: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardLocateCardsByATRW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardLocateCardsByATRW( + _context: ScardContext, + _rg_atr_masks: LpScardAtrMask, + _c_atrs: u32, + _rg_reader_states: LpScardReaderStateW, + _c_readers: u32, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetStatusChangeA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetStatusChangeA( + context: ScardContext, + _dw_timeout: u32, + rg_reader_states: LpScardReaderStateA, + c_readers: u32, +) -> ScardStatus { + check_handle!(context); + check_null!(rg_reader_states); + + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let supported_readers = context.list_readers(); + + let reader_states = unsafe { + from_raw_parts_mut( + rg_reader_states, + try_execute!(c_readers.try_into(), ErrorKind::InsufficientBuffer), + ) + }; + + for reader_state in reader_states { + let reader = try_execute!( + unsafe { CStr::from_ptr(reader_state.sz_reader as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + + if supported_readers.contains(&Cow::Borrowed(&reader)) { + reader_state.dw_event_state = + SCARD_STATE_UNNAMED_CONSTANT | SCARD_STATE_INUSE | SCARD_STATE_PRESENT | SCARD_STATE_CHANGED; + reader_state.cb_atr = try_execute!(ATR.len().try_into(), ErrorKind::InsufficientBuffer); + reader_state.rgb_atr[0..ATR.len()].copy_from_slice(ATR.as_slice()); + } else if reader == NEW_READER_NOTIFICATION { + reader_state.dw_event_state = SCARD_STATE_UNNAMED_CONSTANT; + } else { + error!(?reader, "Unsupported reader"); + } + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetStatusChangeW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetStatusChangeW( + context: ScardContext, + _dw_timeout: u32, + rg_reader_states: LpScardReaderStateW, + c_readers: u32, +) -> ScardStatus { + check_handle!(context); + check_null!(rg_reader_states); + + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let supported_readers = context.list_readers(); + + let reader_states = unsafe { + from_raw_parts_mut( + rg_reader_states, + try_execute!(c_readers.try_into(), ErrorKind::InsufficientBuffer), + ) + }; + for reader_state in reader_states { + let reader = unsafe { c_w_str_to_string(reader_state.sz_reader) }; + if supported_readers.contains(&Cow::Borrowed(&reader)) { + reader_state.dw_event_state = + SCARD_STATE_UNNAMED_CONSTANT | SCARD_STATE_INUSE | SCARD_STATE_PRESENT | SCARD_STATE_CHANGED; + reader_state.cb_atr = try_execute!(ATR.len().try_into(), ErrorKind::InsufficientBuffer); + reader_state.rgb_atr[0..ATR.len()].copy_from_slice(ATR.as_slice()); + } else if reader == NEW_READER_NOTIFICATION { + reader_state.dw_event_state = SCARD_STATE_UNNAMED_CONSTANT; + } else { + error!(?reader, "Unsupported reader"); + } + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardCancel"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardCancel(_context: ScardContext) -> ScardStatus { + // https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardcancel + // The SCardCancel function terminates all outstanding actions within a specific resource manager context. + // + // We do not have such actions in an emulated scard context + ErrorKind::Success.into() +} + +unsafe fn read_cache(context: ScardContext, lookup_name: &str, data: LpByte, data_len: LpDword) -> WinScardResult<()> { + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + + if let Some(cached_value) = context.scard_context().read_cache(lookup_name) { + let cached_value = cached_value.to_vec(); + unsafe { copy_buff(context, data, data_len, &cached_value) } + } else { + warn!(cache = ?ErrorKind::CacheItemNotFound); + Ok(()) + } +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardReadCacheA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardReadCacheA( + context: ScardContext, + _card_identifier: LpUuid, + _freshness_counter: u32, + lookup_name: LpStr, + data: LpByte, + data_len: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(lookup_name); + check_null!(data_len); + + let lookup_name = try_execute!( + unsafe { CStr::from_ptr(lookup_name as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + + try_execute!(unsafe { read_cache(context, lookup_name, data, data_len) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardReadCacheW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardReadCacheW( + context: ScardContext, + _card_identifier: LpUuid, + _freshness_counter: u32, + lookup_name: LpWStr, + data: LpByte, + data_len: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(lookup_name); + check_null!(data_len); + + let lookup_name = unsafe { c_w_str_to_string(lookup_name) }; + + try_execute!(unsafe { read_cache(context, &lookup_name, data, data_len) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardWriteCacheA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardWriteCacheA( + context: ScardContext, + _card_identifier: LpUuid, + _freshness_counter: u32, + lookup_name: LpStr, + data: LpByte, + data_len: u32, +) -> ScardStatus { + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let lookup_name = try_execute!( + unsafe { CStr::from_ptr(lookup_name as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + let data = from_raw_parts_mut(data, try_execute!(data_len.try_into(), ErrorKind::InsufficientBuffer)).to_vec(); + info!(write_lookup_name = lookup_name, ?data); + + context.write_cache(lookup_name.to_owned(), data); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardWriteCacheW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardWriteCacheW( + context: ScardContext, + _card_identifier: LpUuid, + _freshness_counter: u32, + lookup_name: LpWStr, + data: LpByte, + data_len: u32, +) -> ScardStatus { + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let lookup_name = unsafe { c_w_str_to_string(lookup_name) }; + let data = from_raw_parts_mut(data, try_execute!(data_len.try_into(), ErrorKind::InsufficientBuffer)).to_vec(); + info!(write_lookup_name = lookup_name, ?data); + + context.write_cache(lookup_name, data); + + ErrorKind::Success.into() +} + +unsafe fn get_reader_icon( + context: &mut WinScardContextHandle, + reader_name: &str, + pb_icon: LpByte, + pcb_icon: LpDword, +) -> WinScardResult<()> { + let icon = context.scard_context().reader_icon(reader_name)?.as_ref().to_vec(); + + unsafe { copy_buff(context, pb_icon, pcb_icon, icon.as_ref()) } +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetReaderIconA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetReaderIconA( + context: ScardContext, + sz_reader_name: LpCStr, + pb_icon: LpByte, + pcb_icon: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader_name); + // `pb_icon` can be null. + check_null!(pcb_icon); + + // safe: checked above + let context = unsafe { (context as *mut WinScardContextHandle).as_mut() }.unwrap(); + let reader_name = try_execute!( + unsafe { CStr::from_ptr(sz_reader_name as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + + try_execute!(unsafe { get_reader_icon(context, &reader_name, pb_icon, pcb_icon) }); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetReaderIconW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetReaderIconW( + context: ScardContext, + sz_reader_name: LpCWStr, + pb_icon: LpByte, + pcb_icon: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader_name); + // `pb_icon` can be null. + check_null!(pcb_icon); + + let (context, reader_name) = unsafe { + ( + // safe: checked above + (context as *mut WinScardContextHandle).as_mut().unwrap(), + c_w_str_to_string(sz_reader_name), + ) + }; + + try_execute!(get_reader_icon(context, &reader_name, pb_icon, pcb_icon)); + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetDeviceTypeIdA"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetDeviceTypeIdA( + context: ScardContext, + sz_reader_name: LpCStr, + pdw_device_type_id: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader_name); + check_null!(pdw_device_type_id); + + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let reader_name = try_execute!( + unsafe { CStr::from_ptr(sz_reader_name as *const i8) }.to_str(), + ErrorKind::InvalidParameter + ); + + let type_id = try_execute!(context.device_type_id(&reader_name)); + unsafe { + *pdw_device_type_id = type_id.into(); + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetDeviceTypeIdW"))] +#[instrument(ret)] +#[no_mangle] +pub unsafe extern "system" fn SCardGetDeviceTypeIdW( + context: ScardContext, + sz_reader_name: LpCWStr, + pdw_device_type_id: LpDword, +) -> ScardStatus { + check_handle!(context); + check_null!(sz_reader_name); + check_null!(pdw_device_type_id); + + let context = try_execute!(unsafe { scard_context_to_winscard_context(context) }); + let reader_name = unsafe { c_w_str_to_string(sz_reader_name) }; + + let type_id = try_execute!(context.device_type_id(&reader_name)); + unsafe { + *pdw_device_type_id = type_id.into(); + } + + ErrorKind::Success.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetReaderDeviceInstanceIdA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetReaderDeviceInstanceIdA( + _context: ScardContext, + _sz_reader_name: LpCStr, + _sz_device_instance_id: LpStr, + _pcch_device_instance_id: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardGetReaderDeviceInstanceIdW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardGetReaderDeviceInstanceIdW( + _context: ScardContext, + _sz_reader_name: LpCWStr, + _sz_device_instance_id: LpWStr, + _pcch_device_instance_id: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReadersWithDeviceInstanceIdA"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardListReadersWithDeviceInstanceIdA( + _context: ScardContext, + _sz_device_instance_id: LpCStr, + _msz_readers: LpStr, + _pcch_readers: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardListReadersWithDeviceInstanceIdW"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardListReadersWithDeviceInstanceIdW( + _context: ScardContext, + _sz_device_instance_id: LpCWStr, + _msz_readers: LpWStr, + _pcch_readers: LpDword, +) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} + +#[cfg_attr(windows, rename_symbol(to = "Rust_SCardAudit"))] +#[instrument(ret)] +#[no_mangle] +pub extern "system" fn SCardAudit(_context: ScardContext, _dw_event: u32) -> ScardStatus { + ErrorKind::UnsupportedFeature.into() +} diff --git a/ffi/src/winscard/scard_handle.rs b/ffi/src/winscard/scard_handle.rs new file mode 100644 index 00000000..9d64c199 --- /dev/null +++ b/ffi/src/winscard/scard_handle.rs @@ -0,0 +1,199 @@ +use std::mem::size_of; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +use ffi_types::winscard::{LpScardIoRequest, ScardContext, ScardHandle, ScardIoRequest}; +use ffi_types::LpCVoid; +use winscard::winscard::{IoRequest, Protocol, WinScard, WinScardContext}; +use winscard::{Error, ErrorKind, WinScardResult}; + +/// Scard context handle representation. +/// +/// Additionally, it holds allocated buffers and created smart card handles. +/// We need them because during the smart card context deletion, we need to free all allcated resources. +pub struct WinScardContextHandle { + /// Context of the emulated smart card. + scard_context: Box, + /// Created smart card handles during the API usage. + scards: Vec, + /// Allocated buffers in our smart card context. + /// All buffers are `[u8]`, so we need only pointer and don't need to remember its type. + allocations: Vec, +} + +impl WinScardContextHandle { + /// Creates a new [WinScardContextHandle] based on the provided inner scard context. + pub fn with_scard_context(scard_context: Box) -> Self { + Self { + scard_context, + scards: Vec::new(), + allocations: Vec::new(), + } + } + + /// Returns the shared reference to the inner [WinScardContext]. + pub fn scard_context(&self) -> &dyn WinScardContext { + self.scard_context.as_ref() + } + + /// Adds a new [ScardHandle] to the context handles. + pub fn add_scard(&mut self, scard: ScardHandle) -> WinScardResult<()> { + if scard == 0 { + return Err(Error::new(ErrorKind::InvalidHandle, "ScardHandle can not be NULL")); + } + + self.scards.push(scard); + + Ok(()) + } + + /// Removes the [ScardHandle] from the scard context. + pub fn remove_scard(&mut self, scard: ScardHandle) -> bool { + if let Some(index) = self.scards.iter().position(|x| *x == scard) { + self.scards.remove(index); + + true + } else { + false + } + } + + /// Allocated a new buffer inside the scard context. + pub fn allocate_buffer(&mut self, size: usize) -> WinScardResult<*mut u8> { + let buff = unsafe { libc::malloc(size) as *mut u8 }; + if buff.is_null() { + return Err(Error::new( + ErrorKind::NoMemory, + format!("Can not allocate {} bytes", size), + )); + } + self.allocations.push(buff as usize); + + Ok(buff) + } + + /// Deletes the buffer inside the scard context. + pub fn free_buffer(&mut self, buff: LpCVoid) -> bool { + let buff = buff as usize; + + if let Some(index) = self.allocations.iter().position(|x| *x == buff) { + self.allocations.remove(index); + + unsafe { + libc::free(buff as _); + } + + true + } else { + false + } + } +} + +impl Drop for WinScardContextHandle { + fn drop(&mut self) { + // [SCardReleaseContext](https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardreleasecontext) + // ...freeing any resources allocated under that context, including SCARDHANDLE objects + unsafe { + for scard in &self.scards { + let _ = Box::from_raw(*scard as *mut WinScardHandle); + } + } + // ...and memory allocated using the SCARD_AUTOALLOCATE length designator. + unsafe { + for buff in &self.allocations { + libc::free(*buff as _); + } + } + } +} + +/// Scard handle representation. +/// +/// It also holds a pointer to the smart card context to which it belongs. +pub struct WinScardHandle { + /// The emulated smart card. + scard: Box, + /// Pointer to the smart card context to which it belongs. + context: ScardContext, +} + +impl WinScardHandle { + /// Creates a new [WinSCardHandle] based on the provided data. + pub fn new(scard: Box, context: ScardContext) -> Self { + Self { scard, context } + } + + /// Returns the [WinScard] handle. + pub fn scard(&self) -> &dyn WinScard { + self.scard.as_ref() + } + + /// Returns the parent [ScardContext] it belongs. + pub fn context(&self) -> ScardContext { + self.context + } +} + +pub unsafe fn scard_handle_to_winscard<'a>(handle: ScardHandle) -> WinScardResult<&'a mut dyn WinScard> { + if let Some(scard) = unsafe { (handle as *mut WinScardHandle).as_mut() } { + Ok(scard.scard.as_mut()) + } else { + Err(Error::new( + ErrorKind::InvalidHandle, + "Invalid smart card context handle.", + )) + } +} + +pub unsafe fn scard_context_to_winscard_context<'a>( + handle: ScardContext, +) -> WinScardResult<&'a mut dyn WinScardContext> { + if let Some(context) = unsafe { (handle as *mut WinScardContextHandle).as_mut() } { + Ok(context.scard_context.as_mut()) + } else { + Err(Error::new( + ErrorKind::InvalidHandle, + "Invalid smart card context handle.", + )) + } +} + +pub unsafe fn scard_io_request_to_io_request(pio_send_pci: LpScardIoRequest) -> WinScardResult { + let (cb_pci_length, dw_protocol) = unsafe { ((*pio_send_pci).cb_pci_length, (*pio_send_pci).dw_protocol) }; + let buffer_len = cb_pci_length.try_into()?; + let buffer = unsafe { (pio_send_pci as *const u8).add(size_of::()) }; + + Ok(IoRequest { + protocol: Protocol::from_bits(dw_protocol).unwrap_or(Protocol::empty()), + pci_info: unsafe { from_raw_parts(buffer, buffer_len) }.to_vec(), + }) +} + +pub unsafe fn copy_io_request_to_scard_io_request( + io_request: &IoRequest, + scard_io_request: LpScardIoRequest, +) -> WinScardResult<()> { + let pci_info_len = io_request.pci_info.len(); + let scard_pci_info_len = unsafe { (*scard_io_request).cb_pci_length }.try_into()?; + + if pci_info_len > scard_pci_info_len { + return Err(Error::new( + ErrorKind::InsufficientBuffer, + format!( + "ScardIoRequest::cb_pci_length is too small. Expected at least {} but got {}", + pci_info_len, scard_pci_info_len + ), + )); + } + + unsafe { + (*scard_io_request).dw_protocol = io_request.protocol.bits(); + (*scard_io_request).cb_pci_length = pci_info_len.try_into()?; + } + + let pci_buffer_ptr = unsafe { (scard_io_request as *mut u8).add(size_of::()) }; + let pci_buffer = unsafe { from_raw_parts_mut(pci_buffer_ptr, pci_info_len) }; + pci_buffer.copy_from_slice(&io_request.pci_info); + + Ok(()) +} diff --git a/ffi/sspi_winscard.def b/ffi/sspi_winscard.def new file mode 100644 index 00000000..c1fe472a --- /dev/null +++ b/ffi/sspi_winscard.def @@ -0,0 +1,125 @@ +LIBRARY "sspi" +EXPORTS + InitSecurityInterfaceA=Rust_InitSecurityInterfaceA + InitSecurityInterfaceW=Rust_InitSecurityInterfaceW + EnumerateSecurityPackagesA=Rust_EnumerateSecurityPackagesA + EnumerateSecurityPackagesW=Rust_EnumerateSecurityPackagesW + QueryCredentialsAttributesA=Rust_QueryCredentialsAttributesA + QueryCredentialsAttributesW=Rust_QueryCredentialsAttributesW + AcquireCredentialsHandleA=Rust_AcquireCredentialsHandleA + AcquireCredentialsHandleW=Rust_AcquireCredentialsHandleW + FreeCredentialsHandle=Rust_FreeCredentialsHandle + InitializeSecurityContextA=Rust_InitializeSecurityContextA + InitializeSecurityContextW=Rust_InitializeSecurityContextW + AcceptSecurityContext=Rust_AcceptSecurityContext + CompleteAuthToken=Rust_CompleteAuthToken + DeleteSecurityContext=Rust_DeleteSecurityContext + ApplyControlToken=Rust_ApplyControlToken + QueryContextAttributesA=Rust_QueryContextAttributesA + QueryContextAttributesW=Rust_QueryContextAttributesW + ImpersonateSecurityContext=Rust_ImpersonateSecurityContext + RevertSecurityContext=Rust_RevertSecurityContext + RustSspiSetupLogger=RustSspiSetupLogger + MakeSignature=Rust_MakeSignature + VerifySignature=Rust_VerifySignature + FreeContextBuffer=Rust_FreeContextBuffer + QuerySecurityPackageInfoA=Rust_QuerySecurityPackageInfoA + QuerySecurityPackageInfoW=Rust_QuerySecurityPackageInfoW + ExportSecurityContext=Rust_ExportSecurityContext + ImportSecurityContextA=Rust_ImportSecurityContextA + ImportSecurityContextW=Rust_ImportSecurityContextW + AddCredentialsA=Rust_AddCredentialsA + AddCredentialsW=Rust_AddCredentialsW + QuerySecurityContextToken=Rust_QuerySecurityContextToken + EncryptMessage=Rust_EncryptMessage + DecryptMessage=Rust_DecryptMessage + SetContextAttributesA=Rust_SetContextAttributesA + SetContextAttributesW=Rust_SetContextAttributesW + SetCredentialsAttributesA=Rust_SetCredentialsAttributesA + SetCredentialsAttributesW=Rust_SetCredentialsAttributesW + ChangeAccountPasswordA=Rust_ChangeAccountPasswordA + ChangeAccountPasswordW=Rust_ChangeAccountPasswordW + QueryContextAttributesExA=Rust_QueryContextAttributesExA + QueryContextAttributesExW=Rust_QueryContextAttributesExW + QueryCredentialsAttributesExA=Rust_QueryCredentialsAttributesExA + QueryCredentialsAttributesExW=Rust_QueryCredentialsAttributesExW + SspiEncodeStringsAsAuthIdentity=Rust_SspiEncodeStringsAsAuthIdentity + SspiFreeAuthIdentity=Rust_SspiFreeAuthIdentity + SCardEstablishContext=Rust_SCardEstablishContext + SCardReleaseContext=Rust_SCardReleaseContext + SCardIsValidContext=Rust_SCardIsValidContext + SCardListReaderGroupsA=Rust_SCardListReaderGroupsA + SCardListReaderGroupsW=Rust_SCardListReaderGroupsW + SCardListReadersA=Rust_SCardListReadersA + SCardListReadersW=Rust_SCardListReadersW + SCardListCardsA=Rust_SCardListCardsA + SCardListCardsW=Rust_SCardListCardsW + SCardListInterfacesA=Rust_SCardListInterfacesA + SCardListInterfacesW=Rust_SCardListInterfacesW + SCardGetProviderIdA=Rust_SCardGetProviderIdA + SCardGetProviderIdW=Rust_SCardGetProviderIdW + SCardGetCardTypeProviderNameA=Rust_SCardGetCardTypeProviderNameA + SCardGetCardTypeProviderNameW=Rust_SCardGetCardTypeProviderNameW + SCardIntroduceReaderGroupA=Rust_SCardIntroduceReaderGroupA + SCardIntroduceReaderGroupW=Rust_SCardIntroduceReaderGroupW + SCardForgetReaderGroupA=Rust_SCardForgetReaderGroupA + SCardForgetReaderGroupW=Rust_SCardForgetReaderGroupW + SCardIntroduceReaderA=Rust_SCardIntroduceReaderA + SCardIntroduceReaderW=Rust_SCardIntroduceReaderW + SCardForgetReaderA=Rust_SCardForgetReaderA + SCardForgetReaderW=Rust_SCardForgetReaderW + SCardAddReaderToGroupA=Rust_SCardAddReaderToGroupA + SCardAddReaderToGroupW=Rust_SCardAddReaderToGroupW + SCardRemoveReaderFromGroupA=Rust_SCardRemoveReaderFromGroupA + SCardRemoveReaderFromGroupW=Rust_SCardRemoveReaderFromGroupW + SCardIntroduceCardTypeA=Rust_SCardIntroduceCardTypeA + SCardIntroduceCardTypeW=Rust_SCardIntroduceCardTypeW + SCardSetCardTypeProviderNameA=Rust_SCardSetCardTypeProviderNameA + SCardSetCardTypeProviderNameW=Rust_SCardSetCardTypeProviderNameW + SCardForgetCardTypeA=Rust_SCardForgetCardTypeA + SCardForgetCardTypeW=Rust_SCardForgetCardTypeW + SCardFreeMemory=Rust_SCardFreeMemory + SCardAccessStartedEvent=Rust_SCardAccessStartedEvent + SCardReleaseStartedEvent=Rust_SCardReleaseStartedEvent + SCardLocateCardsA=Rust_SCardLocateCardsA + SCardLocateCardsW=Rust_SCardLocateCardsW + SCardLocateCardsByATRA=Rust_SCardLocateCardsByATRA + SCardLocateCardsByATRW=Rust_SCardLocateCardsByATRW + SCardGetStatusChangeA=Rust_SCardGetStatusChangeA + SCardGetStatusChangeW=Rust_SCardGetStatusChangeW + SCardCancel=Rust_SCardCancel + SCardConnectA=Rust_SCardConnectA + SCardConnectW=Rust_SCardConnectW + SCardReconnect=Rust_SCardReconnect + SCardDisconnect=Rust_SCardDisconnect + SCardBeginTransaction=Rust_SCardBeginTransaction + SCardEndTransaction=Rust_SCardEndTransaction + SCardCancelTransaction=Rust_SCardCancelTransaction + SCardState=Rust_SCardState + SCardStatusA=Rust_SCardStatusA + SCardStatusW=Rust_SCardStatusW + SCardTransmit=Rust_SCardTransmit + SCardGetTransmitCount=Rust_SCardGetTransmitCount + SCardControl=Rust_SCardControl + SCardGetAttrib=Rust_SCardGetAttrib + SCardSetAttrib=Rust_SCardSetAttrib + SCardUIDlgSelectCardA=Rust_SCardUIDlgSelectCardA + SCardUIDlgSelectCardW=Rust_SCardUIDlgSelectCardW + GetOpenCardNameA=Rust_GetOpenCardNameA + GetOpenCardNameW=Rust_GetOpenCardNameW + SCardReadCacheA=Rust_SCardReadCacheA + SCardReadCacheW=Rust_SCardReadCacheW + SCardWriteCacheA=Rust_SCardWriteCacheA + SCardWriteCacheW=Rust_SCardWriteCacheW + SCardGetReaderIconA=Rust_SCardGetReaderIconA + SCardGetReaderIconW=Rust_SCardGetReaderIconW + SCardGetReaderDeviceInstanceIdA=Rust_SCardGetReaderDeviceInstanceIdA + SCardGetReaderDeviceInstanceIdW=Rust_SCardGetReaderDeviceInstanceIdW + SCardListReadersWithDeviceInstanceIdA=Rust_SCardListReadersWithDeviceInstanceIdA + SCardListReadersWithDeviceInstanceIdW=Rust_SCardListReadersWithDeviceInstanceIdW + SCardAudit=Rust_SCardAudit + SCardGetDeviceTypeIdA=Rust_SCardGetDeviceTypeIdA + SCardGetDeviceTypeIdW=Rust_SCardGetDeviceTypeIdW + g_rgSCardT1Pci=Rust_g_rgSCardT1Pci + g_rgSCardT0Pci=Rust_g_rgSCardT0Pci + g_rgSCardRawPci=Rust_g_rgSCardRawPci diff --git a/src/auth_identity.rs b/src/auth_identity.rs index ab23c9f2..bb821f0f 100644 --- a/src/auth_identity.rs +++ b/src/auth_identity.rs @@ -225,9 +225,11 @@ impl TryFrom for AuthIdentity { #[cfg(feature = "scard")] mod scard_credentials { + use picky::key::PrivateKey; use picky_asn1_x509::Certificate; - use crate::{utils, Error, Secret}; + use crate::secret::SecretPrivateKey; + use crate::{utils, Error, ErrorKind, Secret}; /// Represents raw data needed for smart card authentication #[derive(Clone, Eq, PartialEq, Debug)] @@ -249,6 +251,8 @@ mod scard_credentials { /// Private key file index /// This value is used for the APDU message generation pub private_key_file_index: Option, + /// UTF-16 string with PEM-encoded RSA 2048-bit private key + pub private_key_pem: Option>, } /// Represents data needed for smart card authentication @@ -271,12 +275,24 @@ mod scard_credentials { /// Private key file index /// This value is used for the APDU message generation pub private_key_file_index: Option, + /// RSA 2048-bit private key + pub private_key: Option, } impl TryFrom for SmartCardIdentityBuffers { type Error = Error; fn try_from(value: SmartCardIdentity) -> Result { + let private_key = if let Some(key) = value.private_key { + Some(utils::string_to_utf16(key.as_ref().to_pem_str().map_err(|e| { + Error::new( + ErrorKind::InternalError, + format!("Unable to serialize a smart card private key: {}", e), + ) + })?)) + } else { + None + }; Ok(Self { certificate: picky_asn1_der::to_vec(&value.certificate)?, reader_name: utils::string_to_utf16(value.reader_name), @@ -286,6 +302,7 @@ mod scard_credentials { container_name: utils::string_to_utf16(value.container_name), csp_name: utils::string_to_utf16(value.csp_name), private_key_file_index: value.private_key_file_index, + private_key_pem: private_key, }) } } @@ -294,6 +311,18 @@ mod scard_credentials { type Error = Error; fn try_from(value: SmartCardIdentityBuffers) -> Result { + let private_key = if let Some(key) = value.private_key_pem { + Some(SecretPrivateKey::new( + PrivateKey::from_pem_str(&utils::bytes_to_utf16_string(&key)).map_err(|e| { + Error::new( + ErrorKind::InternalError, + format!("Unable to create a PrivateKey from a PEM string: {}", e), + ) + })?, + )) + } else { + None + }; Ok(Self { certificate: picky_asn1_der::from_bytes(&value.certificate)?, reader_name: utils::bytes_to_utf16_string(&value.reader_name), @@ -303,6 +332,7 @@ mod scard_credentials { container_name: utils::bytes_to_utf16_string(&value.container_name), csp_name: utils::bytes_to_utf16_string(&value.csp_name), private_key_file_index: value.private_key_file_index, + private_key, }) } } diff --git a/src/credssp/sspi_cred_ssp/mod.rs b/src/credssp/sspi_cred_ssp/mod.rs index 7cc64ce6..385cb5ab 100644 --- a/src/credssp/sspi_cred_ssp/mod.rs +++ b/src/credssp/sspi_cred_ssp/mod.rs @@ -2,6 +2,7 @@ mod tls_connection; use std::sync::Arc; +use async_recursion::async_recursion; use lazy_static::lazy_static; use picky_asn1_x509::Certificate; use rand::rngs::OsRng; @@ -20,7 +21,6 @@ use crate::{ Error, ErrorKind, InitializeSecurityContextResult, PackageCapabilities, PackageInfo, Result, SecurityBuffer, SecurityBufferType, SecurityPackageType, SecurityStatus, Sspi, SspiEx, SspiImpl, StreamSizes, PACKAGE_ID_NONE, }; -use async_recursion::async_recursion; pub const PKG_NAME: &str = "CREDSSP"; diff --git a/src/kerberos/client/extractors.rs b/src/kerberos/client/extractors.rs index 909b401a..d6d36d10 100644 --- a/src/kerberos/client/extractors.rs +++ b/src/kerberos/client/extractors.rs @@ -29,7 +29,7 @@ pub fn extract_salt_from_krb_error(error: &KrbError) -> Result> { Ok(None) } -#[instrument(level = "trace", ret)] +#[instrument(level = "trace", ret, skip(password))] pub fn extract_session_key_from_as_rep( as_rep: &AsRep, salt: &str, diff --git a/src/kerberos/client/generators.rs b/src/kerberos/client/generators.rs index 6003301a..6512b3a8 100644 --- a/src/kerberos/client/generators.rs +++ b/src/kerberos/client/generators.rs @@ -95,7 +95,7 @@ pub struct GenerateAsPaDataOptions<'a> { pub with_pre_auth: bool, } -#[instrument(level = "trace", ret)] +#[instrument(level = "trace", ret, skip_all, fields(options.salt, options.enc_params, options.with_pre_auth))] pub fn generate_pa_datas_for_as_req(options: &GenerateAsPaDataOptions) -> Result> { let GenerateAsPaDataOptions { password, @@ -377,6 +377,9 @@ 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(); diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index 3a849d92..1fc511d1 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -770,7 +770,14 @@ impl<'a> Kerberos { CredentialsBuffers::SmartCard(smart_card) => { let pin = utf16_bytes_to_utf8_string(smart_card.pin.as_ref()).into_bytes(); let reader_name = utf16_bytes_to_utf8_string(&smart_card.reader_name); - let private_key_file_index = smart_card.private_key_file_index; + let private_key_pem = String::from_utf8( + smart_card + .private_key_pem + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InternalError, "scard private key is missing"))? + .to_vec(), + )?; + let certificate = smart_card.certificate.clone(); self.dh_parameters = Some(generate_client_dh_parameters(&mut OsRng)?); @@ -784,8 +791,12 @@ impl<'a> Kerberos { sha1.update(data_to_sign); let hash = sha1.finalize().to_vec(); - let smart_card = - SmartCard::new(pin.clone(), &reader_name, private_key_file_index.unwrap_or(1))?; + let mut smart_card = SmartCard::new_emulated( + reader_name.clone().into(), + pin.clone(), + private_key_pem.as_ref(), + certificate.clone(), + )?; smart_card.sign(hash) }), with_pre_auth: false, diff --git a/src/kerberos/pa_datas.rs b/src/kerberos/pa_datas.rs index c545b333..9afaabc9 100644 --- a/src/kerberos/pa_datas.rs +++ b/src/kerberos/pa_datas.rs @@ -76,7 +76,7 @@ pub enum AsRepSessionKeyExtractor<'a> { } impl AsRepSessionKeyExtractor<'_> { - #[instrument(level = "trace", ret)] + #[instrument(level = "trace", ret, skip(self))] pub fn session_key(&mut self, as_rep: &AsRep) -> Result> { match self { AsRepSessionKeyExtractor::AuthIdentity { diff --git a/src/lib.rs b/src/lib.rs index 14d7847f..e8a13215 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1921,6 +1921,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: std::string::FromUtf8Error) -> Self { + Self::new(ErrorKind::InternalError, format!("UTF-8 error: {:?}", err)) + } +} + impl From for Error { fn from(err: string::FromUtf16Error) -> Self { Self::new(ErrorKind::InternalError, format!("UTF-16 error: {:?}", err)) @@ -1942,3 +1948,20 @@ impl From for Error { Self::new(ErrorKind::InternalError, "pcsc error".to_owned()) } } + +#[cfg(feature = "scard")] +impl From for Error { + fn from(err: picky::key::KeyError) -> Self { + Self::new(ErrorKind::InternalError, format!("RSA key error: {:?}", err)) + } +} + +#[cfg(feature = "scard")] +impl From for Error { + fn from(value: winscard::Error) -> Self { + Self::new( + ErrorKind::InternalError, + format!("Error while using a smart card: {}", value), + ) + } +} diff --git a/src/network_client.rs b/src/network_client.rs index 9b8512a7..4e293bf8 100644 --- a/src/network_client.rs +++ b/src/network_client.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; -use crate::{generator::NetworkRequest, Result}; +use crate::generator::NetworkRequest; +use crate::Result; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum NetworkProtocol { diff --git a/src/ntlm/messages/computations/test.rs b/src/ntlm/messages/computations/test.rs index 48f6a69b..cde6ba7c 100644 --- a/src/ntlm/messages/computations/test.rs +++ b/src/ntlm/messages/computations/test.rs @@ -3,8 +3,7 @@ use time::{Date, Month, OffsetDateTime}; use crate::ntlm::messages::av_pair::*; use crate::ntlm::messages::computations::*; use crate::ntlm::messages::test::*; -use crate::AuthIdentity; -use crate::Username; +use crate::{AuthIdentity, Username}; #[test] fn get_system_time_as_file_time_test_one_second_diff() { diff --git a/src/secret.rs b/src/secret.rs index 2a8d3c3f..dbfb0da2 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -47,7 +47,7 @@ impl From for Secret { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct SecretPrivateKey(PrivateKey); impl SecretPrivateKey { diff --git a/src/smartcard.rs b/src/smartcard.rs index 47b4b96c..76128754 100644 --- a/src/smartcard.rs +++ b/src/smartcard.rs @@ -1,19 +1,32 @@ +use std::borrow::Cow; use std::fmt; use pcsc::{Card, Context, Protocols, Scope, ShareMode}; +use picky::key::PrivateKey; use picky_asn1::wrapper::OctetStringAsn1; use picky_asn1_x509::{AlgorithmIdentifier, DigestInfo}; +use winscard::SmartCard as PivSmartCard; use crate::{Error, ErrorKind, Result}; +// ISO/IEC 7816-4 +const CLA_BYTE_NO_CHAINING: u8 = 0x00; +const CLA_BYTE_CHAINING: u8 = 0x10; +// the max amount of data a one APDU command can contain +const APDU_COMMAND_DATA_SIZE: usize = 255; +// tag is always 1 byte in length +const TLV_TAG_LENGTH: usize = 1; + pub enum SmartCardApi { WinSCard(Card), + PivSmartCard(Box>), } impl fmt::Debug for SmartCardApi { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::WinSCard { .. } => f.write_str("SmartCardApi::WinSCard"), + Self::PivSmartCard { .. } => f.write_str("SmartCardApi::PivSmartCard"), } } } @@ -45,9 +58,29 @@ impl SmartCard { }) } - pub fn sign(&self, data: impl AsRef<[u8]>) -> Result> { - match &self.smart_card_type { - SmartCardApi::WinSCard(scard) => { + pub fn new_emulated( + reader_name: Cow<'_, str>, + pin: Vec, + private_key_pem: &str, + auth_cert_der: Vec, + ) -> Result { + let owned_reader_name = match reader_name { + Cow::Borrowed(name) => Cow::Owned(name.to_owned()), + Cow::Owned(name) => Cow::Owned(name), + }; + let private_key = PrivateKey::from_pem_str(private_key_pem)?; + let scard = PivSmartCard::new(owned_reader_name, pin.clone(), auth_cert_der, private_key)?; + Ok(Self { + smart_card_type: SmartCardApi::PivSmartCard(Box::new(scard)), + pin, + // we don't need it when using the PIV card + private_key_file_index: 0, + }) + } + + pub fn sign(&mut self, data: impl AsRef<[u8]>) -> Result> { + match self.smart_card_type { + SmartCardApi::WinSCard(ref scard) => { // https://www.eftlab.com/knowledge-base/complete-list-of-apdu-responses const APDU_RESPONSE_OK: [u8; 2] = [0x90, 0x00]; @@ -137,6 +170,10 @@ impl SmartCard { Ok(signature) } + SmartCardApi::PivSmartCard(ref mut scard) => { + scard.verify_pin(&self.pin)?; + Ok(scard.sign_hashed(data)?) + } } } }