diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/authenticator | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator')
79 files changed, 10095 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/.cargo-checksum.json b/third_party/rust/authenticator/.cargo-checksum.json new file mode 100644 index 0000000000..ce451ad09d --- /dev/null +++ b/third_party/rust/authenticator/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"abaed4932db2206e5fdb7cb73a8c100f6c91fc84a8f33e8763677040ae8ea9bf","Cargo.toml":"9b56d5495021e7cd8ab7e019cceda45e906a2a3629a68e9019c6e5cb682dbc43","Cross.toml":"8d132da818d48492aa9f4b78a348f0df3adfae45d988d42ebd6be8a5adadb6c3","LICENSE":"e866c8f5864d4cacfe403820e722e9dc03fe3c7565efa5e4dad9051d827bb92a","README.md":"c87d9c7cc44f1dd4ef861a3a9f8cd2eb68aedd3814768871f5fb63c2070806cd","build.rs":"bc308b771ae9741d775370e3efe45e9cca166fd1d0335f4214b00497042ccc55","examples/main.rs":"d899646fa396776d0bb66efb86099ffb195566ecdb6fc4c1765ae3d54d696a8d","rustfmt.toml":"ceb6615363d6fff16426eb56f5727f98a7f7ed459ba9af735b1d8b672e2c3b9b","src/authenticatorservice.rs":"9fc5bcdd1e4f32e58ae920f96f40619a870b0a1b8d05db650803b2402a37fbf9","src/capi.rs":"1d3145ce81293bec697b0d385357fb1b0b495b0c356e2da5e6f15d028d328c70","src/consts.rs":"3dbcdfced6241822062e1aa2e6c8628af5f539ea18ee41edab51a3d33ebb77c6","src/errors.rs":"de89e57435ed1f9ff10f1f2d997a5b29d61cb215551e0ab40861a08ca52d1447","src/freebsd/device.rs":"595df4b3f66b90dd73f8df67e1a2ba9a20c0b5fd893afbadbec564aa34f89981","src/freebsd/mod.rs":"42dcb57fbeb00140003a8ad39acac9b547062b8f281a3fa5deb5f92a6169dde6","src/freebsd/monitor.rs":"c10b154632fbedc3dca27197f7fc890c3d50ac1744b927e9f1e44a9e8a13506e","src/freebsd/transaction.rs":"bfb92dcf2edeb5d620a019907fff1025eb36ef322055e78649a3055b074fa851","src/freebsd/uhid.rs":"84f564d337637c1cd107ccc536b8fce2230628e144e4031e8db4d7163c9c0cb3","src/hidproto.rs":"362fc8e24b94ba431aad5ee0002f5a3364badd937c706c0ae119a5a7a2abc7c2","src/lib.rs":"12f62285a3d33347f95236b71341462a76ea1ded67651fc96ba25d7bd1dd8298","src/linux/device.rs":"d27c5f877cf96b97668579ac5db0f2685f7c969e7a5d0ddc68043eb16bfcddb8","src/linux/hidraw.rs":"ed55caa40fd518d67bb67d5af08f9adcab34f89e0ca591142d45b87f172926dd","src/linux/hidwrapper.h":"72785db3a9b27ea72b6cf13a958fee032af54304522d002f56322473978a20f9","src/linux/hidwrapper.rs":"4be65676cf3220929700bf4906938dcbd1538ba53d40c60b08f9ba8890c910f6","src/linux/ioctl_aarch64le.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/linux/ioctl_armle.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/linux/ioctl_mips64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_mipsbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_mipsle.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_powerpc64be.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_powerpc64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_powerpcbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/linux/ioctl_s390xbe.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/linux/ioctl_x86.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/linux/ioctl_x86_64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/linux/mod.rs":"446e435126d2a58f167f648dd95cba28e8ac9c17f1f799e1eaeab80ea800fc57","src/linux/monitor.rs":"9ef4e22fdcf005dd5201b42595d958ea462998c75dbfc68c8a403e7be64328e4","src/linux/transaction.rs":"bfb92dcf2edeb5d620a019907fff1025eb36ef322055e78649a3055b074fa851","src/macos/device.rs":"cc97b773254a89526164987e4b8e4181910fc3decb32acf51ca86c596ad0147b","src/macos/iokit.rs":"7dc4e7bbf8e42e2fcde0cee8e48d14d6234a5a910bd5d3c4e966d8ba6b73992f","src/macos/mod.rs":"333e561554fc901d4f6092f6e4c85823e2b0c4ff31c9188d0e6d542b71a0a07c","src/macos/monitor.rs":"d059861b4739c9272fa305b6dd91ebeb08530bd0e70a013dd999565d6f06fb30","src/macos/transaction.rs":"935b4bc79b0e50a984604a1ada96a7ef723cc283b7d33ca07f3150b1752b99f7","src/manager.rs":"5a4cdc26b9fde20e1a3dc2389f15d38d9153109bfee5119c092fbfdbd19bad8d","src/netbsd/device.rs":"3a99a989a7a8411ddb9893c371644076662a3b488d40b436601c27fd92fdf159","src/netbsd/fd.rs":"260f1a8ae04896c0eb35ab0914e11ca9291e7317a086c94328aa219c0e1fc1d2","src/netbsd/mod.rs":"b1c52aa29537330cebe67427062d6c94871cab2a9b0c04b2305d686f07e88fd5","src/netbsd/monitor.rs":"dfd68e026c52271b68a3a9263837c793127e9d54ed19b748ef6d13ab4c44e09a","src/netbsd/transaction.rs":"9334a832a57e717a981c13c364ed4ee80ce9798460fc6c8954723d2fcf20585a","src/netbsd/uhid.rs":"154a4587767f151e3f846cc0b79f615d5137de67afed84f19176f27ac9097908","src/openbsd/device.rs":"ae1c8de90bb515a12d571372a30322fadb5122bc69ab71caf154452caa8a644f","src/openbsd/mod.rs":"514274d414042ff84b3667a41a736e78581e22fda87ccc97c2bc05617e381a30","src/openbsd/monitor.rs":"5eb071dd3719ea305eac21ec20596463f63790f8cd1f908a59e3f9cb0b71b5ad","src/openbsd/transaction.rs":"2380c9430f4c95a1fefaaab729d8ece0d149674708d705a71dd5d2513d9e1a4c","src/statecallback.rs":"6b16f97176db1ae3fc3851fe8394e4ffc324bc6fe59313845ac3a88132fd52f1","src/statemachine.rs":"27e2655411ebc1077c200f0aa2ba429ca656fc7dd6f90e08b51492b59ec72e61","src/stub/device.rs":"5e378147e113e20160a45d395b717bd3deecb327247c24b6735035f7d50861b7","src/stub/mod.rs":"6a7fec504a52d403b0241b18cd8b95088a31807571f4c0a67e4055afc74f4453","src/stub/transaction.rs":"4a2ccb2d72070a8bc61442254e063278c68212d5565ba5bfe4d47cacebf5bd1c","src/u2fhid-capi.h":"10f2658df774bb7f7f197a9f217b9e20d67b232b60a554e8ee3c3f71480ea1f6","src/u2fprotocol.rs":"72120773a948ffd667b5976c26ae27a4327769d97b0eef7a3b1e6b2b4bbb46a9","src/u2ftypes.rs":"a02d2c29790c5edfec9af320b1d4bcb93be0bbf02b881fa5aa403cfb687a25ae","src/util.rs":"d2042b2db4864f2b1192606c3251709361de7fb7521e1519190ef26a77de8e64","src/virtualdevices/mod.rs":"2c7df7691d5c150757304241351612aed4260d65b70ab0f483edbc1a5cfb5674","src/virtualdevices/software_u2f.rs":"1b86b94c6eadec6a22dffdd2b003c5324247c6412eeddb28a6094feb1c523f8e","src/virtualdevices/webdriver/mod.rs":"4a36e6dfa9f45f941d863b4039bfbcfa8eaca660bd6ed78aeb1a2962db64be5a","src/virtualdevices/webdriver/testtoken.rs":"7146e02f1a5dad2c8827dd11c12ee408c0e42a0706ac65f139998feffd42570f","src/virtualdevices/webdriver/virtualmanager.rs":"a55a28995c81b5affb0a74207b6dd556d272086a554676df2e675fe991d730a9","src/virtualdevices/webdriver/web_api.rs":"27206ee09c83fe25b34cad62174e42383defd8c8a5e917d30691412aacdae08f","src/windows/device.rs":"bc3f9587677c185a624c0aae7537baf9f780484ab8337929db994800b9064ba9","src/windows/mod.rs":"218e7f2fe91ecb390c12bba5a5ffdad2c1f0b22861c937f4d386262e5b3dd617","src/windows/monitor.rs":"3804dc67de46a1a6b7925c83e0df95d94ddfa1aa53a88fc845f4ff26aede57f8","src/windows/transaction.rs":"ee639f28b2dcdb7e00c922d8762fe6aa33def8c7aaeb46ec93e3a772407a9d86","src/windows/winapi.rs":"de92afb17df26216161138f18eb3b9162f3fb2cdeb74aa78173afe804ba02e00","testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile":"d7463ff4376e3e0ca3fed879fab4aa975c4c0a3e7924c5b88aef9381a5d013de","testing/cross/x86_64-unknown-linux-gnu.Dockerfile":"11c79c04b07a171b0c9b63ef75fa75f33263ce76e3c1eda0879a3e723ebd0c24","testing/run_cross.sh":"cc2a7e0359f210eba2e7121f81eb8ab0125cea6e0d0f2698177b0fe2ad0c33d8","webdriver-tools/requirements.txt":"8236aa3dedad886f213c9b778fec80b037212d30e640b458984110211d546005","webdriver-tools/webdriver-driver.py":"82327c26ba271d1689acc87b612ab8436cb5475f0a3c0dba7baa06e7f6f5e19c"},"package":"08cee7a0952628fde958e149507c2bb321ab4fccfafd225da0b20adc956ef88a"}
\ No newline at end of file diff --git a/third_party/rust/authenticator/Cargo.lock b/third_party/rust/authenticator/Cargo.lock new file mode 100644 index 0000000000..9f284b468d --- /dev/null +++ b/third_party/rust/authenticator/Cargo.lock @@ -0,0 +1,1603 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "assert_matches" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "authenticator" +version = "0.3.1" +dependencies = [ + "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "devd-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "warp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bindgen" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cexpr 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "which 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cexpr" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clang-sys" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "devd-rs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env_logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "once_cell 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "h2" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hashbrown" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "headers" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "headers-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libudev" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multipart" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pin-project" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "runloop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha-1" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cpuid-bool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tokio" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tungstenite 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tracing" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-core" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tungstenite" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tinyvec 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "urlencoding" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "try-lock 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "warp" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "headers 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "multipart 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-futures 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "urlencoding 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +"checksum bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +"checksum bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" +"checksum cexpr 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" +"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb" +"checksum core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6" +"checksum cpuid-bool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +"checksum devd-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1945ccb7caedabdfb9347766ead740fb1e0582b7425598325f546adbd832cce1" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +"checksum dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" +"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" +"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum generic-array 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum h2 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53" +"checksum hashbrown 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7" +"checksum headers 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" +"checksum headers-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +"checksum hyper 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3e68a8dd9716185d9e64ea473ea6ef63529252e3e27623295a0378a19665d5eb" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +"checksum indexmap 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +"checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)" = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9" +"checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +"checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" +"checksum libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +"checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum multipart 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8209c33c951f07387a8497841122fc6f712165e3f9bda3e6be4645b58188f676" +"checksum net2 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum nom 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +"checksum once_cell 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +"checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum pin-project 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +"checksum pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +"checksum pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" +"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +"checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +"checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +"checksum runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd" +"checksum rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +"checksum serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)" = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" +"checksum serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)" = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +"checksum sha-1 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770" +"checksum sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum socket2 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +"checksum tinyvec 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" +"checksum tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" +"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +"checksum tokio-tungstenite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d9e878ad426ca286e4dcae09cbd4e1973a7f8987d97570e2469703dd7f5720c" +"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +"checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" +"checksum tracing 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" +"checksum tracing-core 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a" +"checksum tracing-futures 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +"checksum try-lock 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +"checksum tungstenite 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" +"checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +"checksum urlencoding 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593" +"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +"checksum warp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f41be6df54c97904af01aa23e613d4521eed7ab23537cede692d4058f6449407" +"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +"checksum which 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/third_party/rust/authenticator/Cargo.toml b/third_party/rust/authenticator/Cargo.toml new file mode 100644 index 0000000000..57d24bd66b --- /dev/null +++ b/third_party/rust/authenticator/Cargo.toml @@ -0,0 +1,99 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "authenticator" +version = "0.3.1" +authors = ["J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>", "Kyle Machulis <kyle@nonpolynomial.com>"] +description = "Library for interacting with CTAP1/2 security keys for Web Authentication. Used by Firefox." +keywords = ["ctap2", "u2f", "fido", "webauthn"] +categories = ["cryptography", "hardware-support", "os"] +license = "MPL-2.0" +repository = "https://github.com/mozilla/authenticator-rs/" +[dependencies.base64] +version = "^0.10" +optional = true + +[dependencies.bitflags] +version = "1.0" + +[dependencies.bytes] +version = "0.5" +features = ["serde"] +optional = true + +[dependencies.libc] +version = "0.2" + +[dependencies.log] +version = "0.4" + +[dependencies.rand] +version = "0.7" + +[dependencies.runloop] +version = "0.1.0" + +[dependencies.serde] +version = "1.0" +features = ["derive"] +optional = true + +[dependencies.serde_json] +version = "1.0" +optional = true + +[dependencies.tokio] +version = "0.2" +features = ["macros"] +optional = true + +[dependencies.warp] +version = "0.2.4" +optional = true +[dev-dependencies.assert_matches] +version = "1.2" + +[dev-dependencies.base64] +version = "^0.10" + +[dev-dependencies.env_logger] +version = "^0.6" + +[dev-dependencies.getopts] +version = "^0.2" + +[dev-dependencies.sha2] +version = "^0.8.2" +[build-dependencies.bindgen] +version = "^0.51" +optional = true + +[features] +binding-recompile = ["bindgen"] +webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"] +[target."cfg(target_os = \"freebsd\")".dependencies.devd-rs] +version = "0.3" +[target."cfg(target_os = \"linux\")".dependencies.libudev] +version = "^0.2" +[target."cfg(target_os = \"macos\")".dependencies.core-foundation] +version = "0.9" +[target."cfg(target_os = \"windows\")".dependencies.winapi] +version = "^0.3" +features = ["handleapi", "hidclass", "hidpi", "hidusage", "setupapi"] +[badges.maintenance] +status = "actively-developed" + +[badges.travis-ci] +branch = "master" +repository = "mozilla/authenticator-rs" diff --git a/third_party/rust/authenticator/Cross.toml b/third_party/rust/authenticator/Cross.toml new file mode 100644 index 0000000000..cde355d84f --- /dev/null +++ b/third_party/rust/authenticator/Cross.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-gnu] +image = "local_cross:x86_64-unknown-linux-gnu" + +[target.powerpc64le-unknown-linux-gnu] +image = "local_cross:powerpc64le-unknown-linux-gnu"
\ No newline at end of file diff --git a/third_party/rust/authenticator/LICENSE b/third_party/rust/authenticator/LICENSE new file mode 100644 index 0000000000..3f4f7ac8b1 --- /dev/null +++ b/third_party/rust/authenticator/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + +(a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the +terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + +(a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered +Software; or + +(b) any new file in Source Code Form that contains any Covered +Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/rust/authenticator/README.md b/third_party/rust/authenticator/README.md new file mode 100644 index 0000000000..74d8cc2e5c --- /dev/null +++ b/third_party/rust/authenticator/README.md @@ -0,0 +1,50 @@ +# A Rust library for interacting with CTAP1/CTAP2 Security Keys + +[![Build Status](https://travis-ci.org/mozilla/authenticator-rs.svg?branch=master)](https://travis-ci.org/mozilla/authenticator-rs) +![Maturity Level](https://img.shields.io/badge/maturity-release-green.svg) + +This is a cross-platform library for interacting with Security Key-type devices via Rust. + +* **Supported Platforms**: Windows, Linux, FreeBSD, NetBSD, OpenBSD, and macOS. +* **Supported Transports**: USB HID. +* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html). [CTAP2 support](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html) is forthcoming, with work being done in the **unstable** [`ctap2` branch](https://github.com/mozilla/authenticator-rs/tree/ctap2). + +This library currently focuses on USB security keys, but is expected to be extended to +support additional transports. + +## Usage + +There's only a simple example function that tries to register and sign right now. It uses +[env_logger](http://rust-lang-nursery.github.io/log/env_logger/) for logging, which you +configure with the `RUST_LOG` environment variable: + +``` +cargo build --example main +RUST_LOG=debug cargo run --example main +``` + +Proper usage should be to call into this library from something else - e.g., Firefox. There are +some [C headers exposed for the purpose](./src/u2fhid-capi.h). + +## Tests + +There are some tests of the cross-platform runloop logic and the protocol decoder: + +``` +cargo test +``` + +## Fuzzing + +There are fuzzers for the USB protocol reader, basically fuzzing inputs from the HID layer. +There are not (yet) fuzzers for the C API used by callers (such as Gecko). + +To fuzz, you will need cargo-fuzz (the latest version from GitHub) as well as Rust Nightly. + +``` +rustup install nightly +cargo install cargo-fuzz + +cargo +nightly fuzz run u2f_read -- -max_len=512 +cargo +nightly fuzz run u2f_read_write -- -max_len=512 +``` diff --git a/third_party/rust/authenticator/build.rs b/third_party/rust/authenticator/build.rs new file mode 100644 index 0000000000..299e4df6d7 --- /dev/null +++ b/third_party/rust/authenticator/build.rs @@ -0,0 +1,54 @@ +#[cfg(all(target_os = "linux", feature = "binding-recompile"))] +extern crate bindgen; + +#[cfg(all(target_os = "linux", feature = "binding-recompile"))] +use std::path::PathBuf; + +#[cfg(any(not(target_os = "linux"), not(feature = "binding-recompile")))] +fn main() {} + +#[cfg(all(target_os = "linux", feature = "binding-recompile"))] +fn main() { + let bindings = bindgen::Builder::default() + .header("src/linux/hidwrapper.h") + .whitelist_var("_HIDIOCGRDESCSIZE") + .whitelist_var("_HIDIOCGRDESC") + .generate() + .expect("Unable to get hidraw bindings"); + + let out_path = PathBuf::new(); + let name = if cfg!(target_arch = "x86") { + "ioctl_x86.rs" + } else if cfg!(target_arch = "x86_64") { + "ioctl_x86_64.rs" + } else if cfg!(all(target_arch = "mips", target_endian = "big")) { + "ioctl_mipsbe.rs" + } else if cfg!(all(target_arch = "mips", target_endian = "little")) { + "ioctl_mipsle.rs" + } else if cfg!(all(target_arch = "mips64", target_endian = "little")) { + "ioctl_mips64le.rs" + } else if cfg!(all(target_arch = "powerpc", target_endian = "little")) { + "ioctl_powerpcle.rs" + } else if cfg!(all(target_arch = "powerpc", target_endian = "big")) { + "ioctl_powerpcbe.rs" + } else if cfg!(all(target_arch = "powerpc64", target_endian = "little")) { + "ioctl_powerpc64le.rs" + } else if cfg!(all(target_arch = "powerpc64", target_endian = "big")) { + "ioctl_powerpc64be.rs" + } else if cfg!(all(target_arch = "arm", target_endian = "little")) { + "ioctl_armle.rs" + } else if cfg!(all(target_arch = "arm", target_endian = "big")) { + "ioctl_armbe.rs" + } else if cfg!(all(target_arch = "aarch64", target_endian = "little")) { + "ioctl_aarch64le.rs" + } else if cfg!(all(target_arch = "aarch64", target_endian = "big")) { + "ioctl_aarch64be.rs" + } else if cfg!(all(target_arch = "s390x", target_endian = "big")) { + "ioctl_s390xbe.rs" + } else { + panic!("architecture not supported"); + }; + bindings + .write_to_file(out_path.join("src").join("linux").join(name)) + .expect("Couldn't write hidraw bindings"); +} diff --git a/third_party/rust/authenticator/examples/main.rs b/third_party/rust/authenticator/examples/main.rs new file mode 100644 index 0000000000..3922a25dde --- /dev/null +++ b/third_party/rust/authenticator/examples/main.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use authenticator::{ + authenticatorservice::AuthenticatorService, statecallback::StateCallback, + AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate, +}; +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, io, thread}; + +fn u2f_get_key_handle_from_register_response(register_response: &[u8]) -> io::Result<Vec<u8>> { + if register_response[0] != 0x05 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Reserved byte not set correctly", + )); + } + + let key_handle_len = register_response[66] as usize; + let mut public_key = register_response.to_owned(); + let mut key_handle = public_key.split_off(67); + let _attestation = key_handle.split_off(key_handle_len); + + Ok(key_handle) +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + print!("{}", opts.usage(&brief)); +} + +fn main() { + env_logger::init(); + + let args: Vec<String> = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms"); + #[cfg(feature = "webdriver")] + opts.optflag("w", "webdriver", "enable WebDriver virtual bus"); + + opts.optflag("h", "help", "print this help menu").optopt( + "t", + "timeout", + "timeout in seconds", + "SEC", + ); + + opts.optflag("h", "help", "print this help menu"); + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => panic!(f.to_string()), + }; + if matches.opt_present("help") { + print_usage(&program, opts); + return; + } + + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + #[cfg(feature = "webdriver")] + { + if matches.opt_present("webdriver") { + manager.add_webdriver_virtual_bus(); + } + } + + let timeout_ms = match matches.opt_get_default::<u64>("timeout", 15) { + Ok(timeout_s) => { + println!("Using {}s as the timeout", &timeout_s); + timeout_s * 1_000 + } + Err(e) => { + println!("{}", e); + print_usage(&program, opts); + return; + } + }; + + println!("Asking a security key to register now..."); + let challenge_str = format!( + "{}{}", + r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#, + r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"# + ); + let mut challenge = Sha256::default(); + challenge.input(challenge_str.as_bytes()); + let chall_bytes = challenge.result().to_vec(); + + let mut application = Sha256::default(); + application.input(b"http://demo.yubico.com"); + let app_bytes = application.result().to_vec(); + + let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::<StatusUpdate>(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(StatusUpdate::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device unavailable: {}", dev_info) + } + Ok(StatusUpdate::Success { dev_info }) => { + println!("STATUS: success using device: {}", dev_info); + } + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + manager + .register( + flags, + timeout_ms, + chall_bytes.clone(), + app_bytes.clone(), + vec![], + status_tx.clone(), + callback, + ) + .expect("Couldn't register"); + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + let (register_data, device_info) = register_result.expect("Registration failed"); + + println!("Register result: {}", base64::encode(®ister_data)); + println!("Device info: {}", &device_info); + println!("Asking a security key to sign now, with the data from the register..."); + let credential = u2f_get_key_handle_from_register_response(®ister_data).unwrap(); + let key_handle = KeyHandle { + credential, + transports: AuthenticatorTransports::empty(), + }; + + let flags = SignFlags::empty(); + let (sign_tx, sign_rx) = channel(); + + let callback = StateCallback::new(Box::new(move |rv| { + sign_tx.send(rv).unwrap(); + })); + + if let Err(e) = manager.sign( + flags, + timeout_ms, + chall_bytes, + vec![app_bytes], + vec![key_handle], + status_tx, + callback, + ) { + panic!("Couldn't register: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + let (_, handle_used, sign_data, device_info) = sign_result.expect("Sign failed"); + + println!("Sign result: {}", base64::encode(&sign_data)); + println!("Key handle used: {}", base64::encode(&handle_used)); + println!("Device info: {}", &device_info); + println!("Done."); +} diff --git a/third_party/rust/authenticator/rustfmt.toml b/third_party/rust/authenticator/rustfmt.toml new file mode 100644 index 0000000000..b3e96e305b --- /dev/null +++ b/third_party/rust/authenticator/rustfmt.toml @@ -0,0 +1,3 @@ +comment_width = 200 +wrap_comments = true +edition = "2018" diff --git a/third_party/rust/authenticator/src/authenticatorservice.rs b/third_party/rust/authenticator/src/authenticatorservice.rs new file mode 100644 index 0000000000..fcb05dc63c --- /dev/null +++ b/third_party/rust/authenticator/src/authenticatorservice.rs @@ -0,0 +1,635 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::sync::{mpsc::Sender, Arc, Mutex}; + +use crate::consts::PARAMETER_SIZE; +use crate::errors::*; +use crate::statecallback::StateCallback; + +pub trait AuthenticatorTransport { + /// The implementation of this method must return quickly and should + /// report its status via the status and callback methods + fn register( + &mut self, + flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec<u8>, + application: crate::AppId, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) -> crate::Result<()>; + + /// The implementation of this method must return quickly and should + /// report its status via the status and callback methods + fn sign( + &mut self, + flags: crate::SignFlags, + timeout: u64, + challenge: Vec<u8>, + app_ids: Vec<crate::AppId>, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) -> crate::Result<()>; + + fn cancel(&mut self) -> crate::Result<()>; +} + +pub struct AuthenticatorService { + transports: Vec<Arc<Mutex<Box<dyn AuthenticatorTransport + Send>>>>, +} + +fn clone_and_configure_cancellation_callback<T>( + mut callback: StateCallback<T>, + transports_to_cancel: Vec<Arc<Mutex<Box<dyn AuthenticatorTransport + Send>>>>, +) -> StateCallback<T> { + callback.add_uncloneable_observer(Box::new(move || { + debug!( + "Callback observer is running, cancelling \ + {} unchosen transports...", + transports_to_cancel.len() + ); + for transport_mutex in &transports_to_cancel { + if let Err(e) = transport_mutex.lock().unwrap().cancel() { + error!("Cancellation failed: {:?}", e); + } + } + })); + callback +} + +impl AuthenticatorService { + pub fn new() -> crate::Result<Self> { + Ok(Self { + transports: Vec::new(), + }) + } + + /// Add any detected platform transports + pub fn add_detected_transports(&mut self) { + self.add_u2f_usb_hid_platform_transports(); + } + + fn add_transport(&mut self, boxed_token: Box<dyn AuthenticatorTransport + Send>) { + self.transports.push(Arc::new(Mutex::new(boxed_token))) + } + + pub fn add_u2f_usb_hid_platform_transports(&mut self) { + match crate::U2FManager::new() { + Ok(token) => self.add_transport(Box::new(token)), + Err(e) => error!("Could not add U2F HID transport: {}", e), + } + } + + #[cfg(feature = "webdriver")] + pub fn add_webdriver_virtual_bus(&mut self) { + match crate::virtualdevices::webdriver::VirtualManager::new() { + Ok(token) => { + println!("WebDriver ready, listening at {}", &token.url()); + self.add_transport(Box::new(token)); + } + Err(e) => error!("Could not add WebDriver virtual bus: {}", e), + } + } + + pub fn register( + &mut self, + flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec<u8>, + application: crate::AppId, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) -> crate::Result<()> { + if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + for key_handle in &key_handles { + if key_handle.credential.len() > 256 { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + let iterable_transports = self.transports.clone(); + if iterable_transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + debug!( + "register called with {} transports, iterable is {}", + self.transports.len(), + iterable_transports.len() + ); + + for (idx, transport_mutex) in iterable_transports.iter().enumerate() { + let mut transports_to_cancel = iterable_transports.clone(); + transports_to_cancel.remove(idx); + + debug!( + "register transports_to_cancel {}", + transports_to_cancel.len() + ); + + transport_mutex.lock().unwrap().register( + flags, + timeout, + challenge.clone(), + application.clone(), + key_handles.clone(), + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } + + pub fn sign( + &mut self, + flags: crate::SignFlags, + timeout: u64, + challenge: Vec<u8>, + app_ids: Vec<crate::AppId>, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) -> crate::Result<()> { + if challenge.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + if app_ids.is_empty() { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + for app_id in &app_ids { + if app_id.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + for key_handle in &key_handles { + if key_handle.credential.len() > 256 { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + let iterable_transports = self.transports.clone(); + if iterable_transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + for (idx, transport_mutex) in iterable_transports.iter().enumerate() { + let mut transports_to_cancel = iterable_transports.clone(); + transports_to_cancel.remove(idx); + + transport_mutex.lock().unwrap().sign( + flags, + timeout, + challenge.clone(), + app_ids.clone(), + key_handles.clone(), + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } + + pub fn cancel(&mut self) -> crate::Result<()> { + if self.transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + for transport_mutex in &mut self.transports { + transport_mutex.lock().unwrap().cancel()?; + } + + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::{AuthenticatorService, AuthenticatorTransport}; + use crate::consts::PARAMETER_SIZE; + use crate::statecallback::StateCallback; + use crate::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::{channel, Sender}; + use std::sync::Arc; + use std::{io, thread}; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + pub struct TestTransportDriver { + consent: bool, + was_cancelled: Arc<AtomicBool>, + } + + impl TestTransportDriver { + pub fn new(consent: bool) -> io::Result<Self> { + Ok(Self { + consent, + was_cancelled: Arc::new(AtomicBool::new(false)), + }) + } + } + + impl TestTransportDriver { + fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo { + crate::u2ftypes::U2FDeviceInfo { + vendor_name: String::from("Mozilla").into_bytes(), + device_name: String::from("Test Transport Token").into_bytes(), + version_interface: 0, + version_major: 1, + version_minor: 2, + version_build: 3, + cap_flags: 0, + } + } + } + + impl AuthenticatorTransport for TestTransportDriver { + fn register( + &mut self, + _flags: crate::RegisterFlags, + _timeout: u64, + _challenge: Vec<u8>, + _application: crate::AppId, + _key_handles: Vec<crate::KeyHandle>, + _status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) -> crate::Result<()> { + if self.consent { + let rv = Ok((vec![0u8; 16], self.dev_info())); + thread::spawn(move || callback.call(rv)); + } + Ok(()) + } + + fn sign( + &mut self, + _flags: crate::SignFlags, + _timeout: u64, + _challenge: Vec<u8>, + _app_ids: Vec<crate::AppId>, + _key_handles: Vec<crate::KeyHandle>, + _status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) -> crate::Result<()> { + if self.consent { + let rv = Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info())); + thread::spawn(move || callback.call(rv)); + } + Ok(()) + } + + fn cancel(&mut self) -> crate::Result<()> { + self.was_cancelled + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .map_or( + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::InvalidState, + )), + |_| Ok(()), + ) + } + } + + fn mk_key() -> KeyHandle { + KeyHandle { + credential: vec![0], + transports: AuthenticatorTransports::USB, + } + } + + fn mk_challenge() -> Vec<u8> { + vec![0x11; PARAMETER_SIZE] + } + + fn mk_appid() -> Vec<u8> { + vec![0x22; PARAMETER_SIZE] + } + + #[test] + fn test_no_challenge() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); + + assert_matches!( + s.register( + RegisterFlags::empty(), + 1_000, + vec![], + mk_appid(), + vec![mk_key()], + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + + assert_matches!( + s.sign( + SignFlags::empty(), + 1_000, + vec![], + vec![mk_appid()], + vec![mk_key()], + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + } + + #[test] + fn test_no_appids() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); + + assert_matches!( + s.register( + RegisterFlags::empty(), + 1_000, + mk_challenge(), + vec![], + vec![mk_key()], + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + + assert_matches!( + s.sign( + SignFlags::empty(), + 1_000, + mk_challenge(), + vec![], + vec![mk_key()], + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + } + + #[test] + fn test_no_keys() { + init(); + // No Keys is a resident-key use case. For U2F this would time out, + // but the actual reactions are up to the service implementation. + // This test yields OKs. + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); + + assert_matches!( + s.register( + RegisterFlags::empty(), + 100, + mk_challenge(), + mk_appid(), + vec![], + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ), + Ok(()) + ); + + assert_matches!( + s.sign( + SignFlags::empty(), + 100, + mk_challenge(), + vec![mk_appid()], + vec![], + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ), + Ok(()) + ); + } + + #[test] + fn test_large_keys() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let large_key = KeyHandle { + credential: vec![0; 257], + transports: AuthenticatorTransports::USB, + }; + + let mut s = AuthenticatorService::new().unwrap(); + s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); + + assert_matches!( + s.register( + RegisterFlags::empty(), + 1_000, + mk_challenge(), + mk_appid(), + vec![large_key.clone()], + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + + assert_matches!( + s.sign( + SignFlags::empty(), + 1_000, + mk_challenge(), + vec![mk_appid()], + vec![large_key], + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::InvalidRelyingPartyInput + ); + } + + #[test] + fn test_no_transports() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + assert_matches!( + s.register( + RegisterFlags::empty(), + 1_000, + mk_challenge(), + mk_appid(), + vec![mk_key()], + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::NoConfiguredTransports + ); + + assert_matches!( + s.sign( + SignFlags::empty(), + 1_000, + mk_challenge(), + vec![mk_appid()], + vec![mk_key()], + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ) + .unwrap_err(), + crate::errors::AuthenticatorError::NoConfiguredTransports + ); + + assert_matches!( + s.cancel().unwrap_err(), + crate::errors::AuthenticatorError::NoConfiguredTransports + ); + } + + #[test] + fn test_cancellation_register() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + let ttd_one = TestTransportDriver::new(true).unwrap(); + let ttd_two = TestTransportDriver::new(false).unwrap(); + let ttd_three = TestTransportDriver::new(false).unwrap(); + + let was_cancelled_one = ttd_one.was_cancelled.clone(); + let was_cancelled_two = ttd_two.was_cancelled.clone(); + let was_cancelled_three = ttd_three.was_cancelled.clone(); + + s.add_transport(Box::new(ttd_one)); + s.add_transport(Box::new(ttd_two)); + s.add_transport(Box::new(ttd_three)); + + let callback = StateCallback::new(Box::new(move |_rv| {})); + assert!(s + .register( + RegisterFlags::empty(), + 1_000, + mk_challenge(), + mk_appid(), + vec![], + status_tx, + callback.clone(), + ) + .is_ok()); + callback.wait(); + + assert_eq!(was_cancelled_one.load(Ordering::SeqCst), false); + assert_eq!(was_cancelled_two.load(Ordering::SeqCst), true); + assert_eq!(was_cancelled_three.load(Ordering::SeqCst), true); + } + + #[test] + fn test_cancellation_sign() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + let ttd_one = TestTransportDriver::new(true).unwrap(); + let ttd_two = TestTransportDriver::new(false).unwrap(); + let ttd_three = TestTransportDriver::new(false).unwrap(); + + let was_cancelled_one = ttd_one.was_cancelled.clone(); + let was_cancelled_two = ttd_two.was_cancelled.clone(); + let was_cancelled_three = ttd_three.was_cancelled.clone(); + + s.add_transport(Box::new(ttd_one)); + s.add_transport(Box::new(ttd_two)); + s.add_transport(Box::new(ttd_three)); + + let callback = StateCallback::new(Box::new(move |_rv| {})); + assert!(s + .sign( + SignFlags::empty(), + 1_000, + mk_challenge(), + vec![mk_appid()], + vec![mk_key()], + status_tx, + callback.clone(), + ) + .is_ok()); + callback.wait(); + + assert_eq!(was_cancelled_one.load(Ordering::SeqCst), false); + assert_eq!(was_cancelled_two.load(Ordering::SeqCst), true); + assert_eq!(was_cancelled_three.load(Ordering::SeqCst), true); + } + + #[test] + fn test_cancellation_race() { + init(); + let (status_tx, _) = channel::<StatusUpdate>(); + + let mut s = AuthenticatorService::new().unwrap(); + // Let both of these race which one provides consent. + let ttd_one = TestTransportDriver::new(true).unwrap(); + let ttd_two = TestTransportDriver::new(true).unwrap(); + + let was_cancelled_one = ttd_one.was_cancelled.clone(); + let was_cancelled_two = ttd_two.was_cancelled.clone(); + + s.add_transport(Box::new(ttd_one)); + s.add_transport(Box::new(ttd_two)); + + let callback = StateCallback::new(Box::new(move |_rv| {})); + assert!(s + .register( + RegisterFlags::empty(), + 1_000, + mk_challenge(), + mk_appid(), + vec![], + status_tx, + callback.clone(), + ) + .is_ok()); + callback.wait(); + + let one = was_cancelled_one.load(Ordering::SeqCst); + let two = was_cancelled_two.load(Ordering::SeqCst); + assert_eq!( + one ^ two, + true, + "asserting that one={} xor two={} is true", + one, + two + ); + } +} diff --git a/third_party/rust/authenticator/src/capi.rs b/third_party/rust/authenticator/src/capi.rs new file mode 100644 index 0000000000..ea7123503f --- /dev/null +++ b/third_party/rust/authenticator/src/capi.rs @@ -0,0 +1,377 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::authenticatorservice::AuthenticatorService; +use crate::errors; +use crate::statecallback::StateCallback; +use crate::{RegisterResult, SignResult}; +use libc::size_t; +use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::sync::mpsc::channel; +use std::thread; +use std::{ptr, slice}; + +type U2FAppIds = Vec<crate::AppId>; +type U2FKeyHandles = Vec<crate::KeyHandle>; +type U2FCallback = extern "C" fn(u64, *mut U2FResult); + +pub enum U2FResult { + Success(HashMap<u8, Vec<u8>>), + Error(errors::AuthenticatorError), +} + +const RESBUF_ID_REGISTRATION: u8 = 0; +const RESBUF_ID_KEYHANDLE: u8 = 1; +const RESBUF_ID_SIGNATURE: u8 = 2; +const RESBUF_ID_APPID: u8 = 3; +const RESBUF_ID_VENDOR_NAME: u8 = 4; +const RESBUF_ID_DEVICE_NAME: u8 = 5; +const RESBUF_ID_FIRMWARE_MAJOR: u8 = 6; +const RESBUF_ID_FIRMWARE_MINOR: u8 = 7; +const RESBUF_ID_FIRMWARE_BUILD: u8 = 8; + +// Generates a new 64-bit transaction id with collision probability 2^-32. +fn new_tid() -> u64 { + thread_rng().gen::<u64>() +} + +unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> { + slice::from_raw_parts(ptr, len).to_vec() +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +#[no_mangle] +pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService { + if let Ok(mut mgr) = AuthenticatorService::new() { + mgr.add_detected_transports(); + Box::into_raw(Box::new(mgr)) + } else { + ptr::null_mut() + } +} + +/// # Safety +/// +/// This method must not be called on a handle twice, and the handle is unusable +/// after. +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + Box::from_raw(mgr); + } +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_app_ids_new() -> *mut U2FAppIds { + Box::into_raw(Box::new(vec![])) +} + +/// # Safety +/// +/// This method must be used on an actual U2FAppIds handle +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_app_ids_add( + ids: *mut U2FAppIds, + id_ptr: *const u8, + id_len: usize, +) { + (*ids).push(from_raw(id_ptr, id_len)); +} + +/// # Safety +/// +/// This method must not be called on a handle twice, and the handle is unusable +/// after. +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) { + if !ids.is_null() { + Box::from_raw(ids); + } +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles { + Box::into_raw(Box::new(vec![])) +} + +/// # Safety +/// +/// This method must be used on an actual U2FKeyHandles handle +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_khs_add( + khs: *mut U2FKeyHandles, + key_handle_ptr: *const u8, + key_handle_len: usize, + transports: u8, +) { + (*khs).push(crate::KeyHandle { + credential: from_raw(key_handle_ptr, key_handle_len), + transports: crate::AuthenticatorTransports::from_bits_truncate(transports), + }); +} + +/// # Safety +/// +/// This method must not be called on a handle twice, and the handle is unusable +/// after. +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) { + if !khs.is_null() { + Box::from_raw(khs); + } +} + +/// # Safety +/// +/// This method must be used on an actual U2FResult handle +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 { + if res.is_null() { + return errors::U2FTokenError::Unknown as u8; + } + + if let U2FResult::Error(ref err) = *res { + return err.as_u2f_errorcode(); + } + + 0 /* No error, the request succeeded. */ +} + +/// # Safety +/// +/// This method must be used before rust_u2f_resbuf_copy +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_resbuf_length( + res: *const U2FResult, + bid: u8, + len: *mut size_t, +) -> bool { + if res.is_null() { + return false; + } + + if let U2FResult::Success(ref bufs) = *res { + if let Some(buf) = bufs.get(&bid) { + *len = buf.len(); + return true; + } + } + + false +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_u2f_resbuf_length) +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_resbuf_copy( + res: *const U2FResult, + bid: u8, + dst: *mut u8, +) -> bool { + if res.is_null() { + return false; + } + + if let U2FResult::Success(ref bufs) = *res { + if let Some(buf) = bufs.get(&bid) { + ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len()); + return true; + } + } + + false +} + +/// # Safety +/// +/// This method should not be called on U2FResult handles after they've been +/// freed or a double-free will occur +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) { + if !res.is_null() { + Box::from_raw(res); + } +} + +/// # Safety +/// +/// This method should not be called on AuthenticatorService handles after +/// they've been freed +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_mgr_register( + mgr: *mut AuthenticatorService, + flags: u64, + timeout: u64, + callback: U2FCallback, + challenge_ptr: *const u8, + challenge_len: usize, + application_ptr: *const u8, + application_len: usize, + khs: *const U2FKeyHandles, +) -> u64 { + if mgr.is_null() { + return 0; + } + + // Check buffers. + if challenge_ptr.is_null() || application_ptr.is_null() { + return 0; + } + + let flags = crate::RegisterFlags::from_bits_truncate(flags); + let challenge = from_raw(challenge_ptr, challenge_len); + let application = from_raw(application_ptr, application_len); + let key_handles = (*khs).clone(); + + let (status_tx, status_rx) = channel::<crate::StatusUpdate>(); + thread::spawn(move || loop { + // Issue https://github.com/mozilla/authenticator-rs/issues/132 will + // plumb the status channel through to the actual C API signatures + match status_rx.recv() { + Ok(_) => {} + Err(_recv_error) => return, + } + }); + + let tid = new_tid(); + + let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| { + let result = match rv { + Ok((registration, dev_info)) => { + let mut bufs = HashMap::new(); + bufs.insert(RESBUF_ID_REGISTRATION, registration); + bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name); + bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name); + bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]); + bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]); + bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]); + U2FResult::Success(bufs) + } + Err(e) => U2FResult::Error(e), + }; + + callback(tid, Box::into_raw(Box::new(result))); + })); + + let res = (*mgr).register( + flags, + timeout, + challenge, + application, + key_handles, + status_tx, + state_callback, + ); + + if res.is_ok() { + tid + } else { + 0 + } +} + +/// # Safety +/// +/// This method should not be called on AuthenticatorService handles after +/// they've been freed +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_mgr_sign( + mgr: *mut AuthenticatorService, + flags: u64, + timeout: u64, + callback: U2FCallback, + challenge_ptr: *const u8, + challenge_len: usize, + app_ids: *const U2FAppIds, + khs: *const U2FKeyHandles, +) -> u64 { + if mgr.is_null() || khs.is_null() { + return 0; + } + + // Check buffers. + if challenge_ptr.is_null() { + return 0; + } + + // Need at least one app_id. + if (*app_ids).is_empty() { + return 0; + } + + let flags = crate::SignFlags::from_bits_truncate(flags); + let challenge = from_raw(challenge_ptr, challenge_len); + let app_ids = (*app_ids).clone(); + let key_handles = (*khs).clone(); + + let (status_tx, status_rx) = channel::<crate::StatusUpdate>(); + thread::spawn(move || loop { + // Issue https://github.com/mozilla/authenticator-rs/issues/132 will + // plumb the status channel through to the actual C API signatures + match status_rx.recv() { + Ok(_) => {} + Err(_recv_error) => return, + } + }); + + let tid = new_tid(); + let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| { + let result = match rv { + Ok((app_id, key_handle, signature, dev_info)) => { + let mut bufs = HashMap::new(); + bufs.insert(RESBUF_ID_KEYHANDLE, key_handle); + bufs.insert(RESBUF_ID_SIGNATURE, signature); + bufs.insert(RESBUF_ID_APPID, app_id); + bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name); + bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name); + bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]); + bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]); + bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]); + U2FResult::Success(bufs) + } + Err(e) => U2FResult::Error(e), + }; + + callback(tid, Box::into_raw(Box::new(result))); + })); + + let res = (*mgr).sign( + flags, + timeout, + challenge, + app_ids, + key_handles, + status_tx, + state_callback, + ); + + if res.is_ok() { + tid + } else { + 0 + } +} + +/// # Safety +/// +/// This method should not be called AuthenticatorService handles after they've +/// been freed +#[no_mangle] +pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + // Ignore return value. + let _ = (*mgr).cancel(); + } +} diff --git a/third_party/rust/authenticator/src/consts.rs b/third_party/rust/authenticator/src/consts.rs new file mode 100644 index 0000000000..5f27bb9378 --- /dev/null +++ b/third_party/rust/authenticator/src/consts.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Allow dead code in this module, since it's all packet consts anyways. +#![allow(dead_code)] + +pub const MAX_HID_RPT_SIZE: usize = 64; +pub const U2FAPDUHEADER_SIZE: usize = 7; +pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff]; +pub const TYPE_MASK: u8 = 0x80; +pub const TYPE_INIT: u8 = 0x80; +pub const TYPE_CONT: u8 = 0x80; + +// Size of header in U2F Init USB HID Packets +pub const INIT_HEADER_SIZE: usize = 7; +// Size of header in U2F Cont USB HID Packets +pub const CONT_HEADER_SIZE: usize = 5; + +pub const PARAMETER_SIZE: usize = 32; + +pub const FIDO_USAGE_PAGE: u16 = 0xf1d0; // FIDO alliance HID usage page +pub const FIDO_USAGE_U2FHID: u16 = 0x01; // U2FHID usage for top-level collection +pub const FIDO_USAGE_DATA_IN: u8 = 0x20; // Raw IN data report +pub const FIDO_USAGE_DATA_OUT: u8 = 0x21; // Raw OUT data report + +// General pub constants + +pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation version +pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms +pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms + +// U2FHID native commands +pub const U2FHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only +pub const U2FHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame +pub const U2FHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command +pub const U2FHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization +pub const U2FHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink +pub const U2FHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response + +// U2FHID_MSG commands +pub const U2F_VENDOR_FIRST: u8 = TYPE_INIT | 0x40; // First vendor defined command +pub const U2F_VENDOR_LAST: u8 = TYPE_INIT | 0x7f; // Last vendor defined command +pub const U2F_REGISTER: u8 = 0x01; // Registration command +pub const U2F_AUTHENTICATE: u8 = 0x02; // Authenticate/sign command +pub const U2F_VERSION: u8 = 0x03; // Read version string command + +pub const YKPIV_INS_GET_VERSION: u8 = 0xfd; // Get firmware version, yubico ext + +// U2F_REGISTER command defines +pub const U2F_REGISTER_ID: u8 = 0x05; // Version 2 registration identifier +pub const U2F_REGISTER_HASH_ID: u8 = 0x00; // Version 2 hash identintifier + +// U2F_AUTHENTICATE command defines +pub const U2F_REQUEST_USER_PRESENCE: u8 = 0x03; // Verify user presence and sign +pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is registered + +// U2FHID_INIT command defines +pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge +pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command +pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command + +// Low-level error codes. Return as negatives. + +pub const ERR_NONE: u8 = 0x00; // No error +pub const ERR_INVALID_CMD: u8 = 0x01; // Invalid command +pub const ERR_INVALID_PAR: u8 = 0x02; // Invalid parameter +pub const ERR_INVALID_LEN: u8 = 0x03; // Invalid message length +pub const ERR_INVALID_SEQ: u8 = 0x04; // Invalid message sequencing +pub const ERR_MSG_TIMEOUT: u8 = 0x05; // Message has timed out +pub const ERR_CHANNEL_BUSY: u8 = 0x06; // Channel busy +pub const ERR_LOCK_REQUIRED: u8 = 0x0a; // Command requires channel lock +pub const ERR_INVALID_CID: u8 = 0x0b; // Command not allowed on this cid +pub const ERR_OTHER: u8 = 0x7f; // Other unspecified error + +// These are ISO 7816-4 defined response status words. +pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00]; +pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85]; +pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80]; +pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00]; diff --git a/third_party/rust/authenticator/src/errors.rs b/third_party/rust/authenticator/src/errors.rs new file mode 100644 index 0000000000..ee63cfacf3 --- /dev/null +++ b/third_party/rust/authenticator/src/errors.rs @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::fmt; +use std::io; +use std::sync::mpsc; + +// This composite error type is patterned from Phil Daniels' blog: +// https://www.philipdaniels.com/blog/2019/defining-rust-error-types/ + +#[derive(Debug)] +pub enum AuthenticatorError { + // Errors from external libraries... + Io(io::Error), + // Errors raised by us... + InvalidRelyingPartyInput, + NoConfiguredTransports, + Platform, + InternalError(String), + U2FToken(U2FTokenError), + Custom(String), +} + +impl AuthenticatorError { + pub fn as_u2f_errorcode(&self) -> u8 { + match *self { + AuthenticatorError::U2FToken(ref err) => *err as u8, + _ => U2FTokenError::Unknown as u8, + } + } +} + +impl std::error::Error for AuthenticatorError {} + +impl fmt::Display for AuthenticatorError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + AuthenticatorError::Io(ref err) => err.fmt(f), + AuthenticatorError::InvalidRelyingPartyInput => { + write!(f, "invalid input from relying party") + } + AuthenticatorError::NoConfiguredTransports => write!( + f, + "no transports were configured in the authenticator service" + ), + AuthenticatorError::Platform => write!(f, "unknown platform error"), + AuthenticatorError::InternalError(ref err) => write!(f, "internal error: {}", err), + AuthenticatorError::U2FToken(ref err) => { + write!(f, "A u2f token error occurred {:?}", err) + } + AuthenticatorError::Custom(ref err) => write!(f, "A custom error occurred {:?}", err), + } + } +} + +impl From<io::Error> for AuthenticatorError { + fn from(err: io::Error) -> AuthenticatorError { + AuthenticatorError::Io(err) + } +} + +impl<T> From<mpsc::SendError<T>> for AuthenticatorError { + fn from(err: mpsc::SendError<T>) -> AuthenticatorError { + AuthenticatorError::InternalError(err.to_string()) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum U2FTokenError { + Unknown = 1, + NotSupported = 2, + InvalidState = 3, + ConstraintError = 4, + NotAllowed = 5, +} + +impl U2FTokenError { + fn as_str(&self) -> &str { + match *self { + U2FTokenError::Unknown => "unknown", + U2FTokenError::NotSupported => "not supported", + U2FTokenError::InvalidState => "invalid state", + U2FTokenError::ConstraintError => "constraint error", + U2FTokenError::NotAllowed => "not allowed", + } + } +} + +impl std::fmt::Display for U2FTokenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl std::error::Error for U2FTokenError {} diff --git a/third_party/rust/authenticator/src/freebsd/device.rs b/third_party/rust/authenticator/src/freebsd/device.rs new file mode 100644 index 0000000000..32069cd5f9 --- /dev/null +++ b/third_party/rust/authenticator/src/freebsd/device.rs @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::ffi::{CString, OsString}; +use std::io; +use std::io::{Read, Write}; +use std::os::unix::prelude::*; + +use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; +use crate::platform::uhid; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use crate::util::from_unix_result; + +#[derive(Debug)] +pub struct Device { + path: OsString, + fd: libc::c_int, + cid: [u8; 4], + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(path: OsString) -> io::Result<Self> { + let cstr = CString::new(path.as_bytes())?; + let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) }; + let fd = from_unix_result(fd)?; + Ok(Self { + path, + fd, + cid: CID_BROADCAST, + dev_info: None, + }) + } + + pub fn is_u2f(&self) -> bool { + uhid::is_u2f_device(self.fd) + } +} + +impl Drop for Device { + fn drop(&mut self) { + // Close the fd, ignore any errors. + let _ = unsafe { libc::close(self.fd) }; + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + let bufp = buf.as_mut_ptr() as *mut libc::c_void; + let rv = unsafe { libc::read(self.fd, bufp, buf.len()) }; + from_unix_result(rv as usize) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let report_id = buf[0] as i64; + // Skip report number when not using numbered reports. + let start = if report_id == 0x0 { 1 } else { 0 }; + let data = &buf[start..]; + + let data_ptr = data.as_ptr() as *const libc::c_void; + let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) }; + from_unix_result(rv as usize + 1) + } + + // USB HID writes don't buffer, so this will be a nop. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl U2FDevice for Device { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, _prop_name: &str) -> io::Result<String> { + Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/freebsd/mod.rs b/third_party/rust/authenticator/src/freebsd/mod.rs new file mode 100644 index 0000000000..7ed5727157 --- /dev/null +++ b/third_party/rust/authenticator/src/freebsd/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod monitor; +mod uhid; diff --git a/third_party/rust/authenticator/src/freebsd/monitor.rs b/third_party/rust/authenticator/src/freebsd/monitor.rs new file mode 100644 index 0000000000..d9153e2ef2 --- /dev/null +++ b/third_party/rust/authenticator/src/freebsd/monitor.rs @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use devd_rs; +use std::collections::HashMap; +use std::ffi::OsString; +use std::sync::Arc; +use std::{fs, io}; + +use runloop::RunLoop; + +const POLL_TIMEOUT: usize = 100; + +pub enum Event { + Add(OsString), + Remove(OsString), +} + +impl Event { + fn from_devd(event: devd_rs::Event) -> Option<Self> { + match event { + devd_rs::Event::Attach { + ref dev, + parent: _, + location: _, + } if dev.starts_with("uhid") => Some(Event::Add(("/dev/".to_owned() + dev).into())), + devd_rs::Event::Detach { + ref dev, + parent: _, + location: _, + } if dev.starts_with("uhid") => Some(Event::Remove(("/dev/".to_owned() + dev).into())), + _ => None, + } + } +} + +fn convert_error(e: devd_rs::Error) -> io::Error { + e.into() +} + +pub struct Monitor<F> +where + F: Fn(OsString, &dyn Fn() -> bool) + Sync, +{ + runloops: HashMap<OsString, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + let mut ctx = devd_rs::Context::new().map_err(convert_error)?; + + // Iterate all existing devices. + for dev in fs::read_dir("/dev")? { + if let Ok(dev) = dev { + let filename_ = dev.file_name(); + let filename = filename_.to_str().unwrap_or(""); + if filename.starts_with("uhid") { + self.add_device(("/dev/".to_owned() + filename).into()); + } + } + } + + // Loop until we're stopped by the controlling thread, or fail. + while alive() { + // Wait for new events, break on failure. + match ctx.wait_for_event(POLL_TIMEOUT) { + Err(devd_rs::Error::Timeout) => (), + Err(e) => return Err(e.into()), + Ok(event) => { + if let Some(event) = Event::from_devd(event) { + self.process_event(event); + } + } + } + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn process_event(&mut self, event: Event) { + match event { + Event::Add(path) => { + self.add_device(path); + } + Event::Remove(path) => { + self.remove_device(path); + } + } + } + + fn add_device(&mut self, path: OsString) { + let f = self.new_device_cb.clone(); + let key = path.clone(); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(path, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + + fn remove_device(&mut self, path: OsString) { + if let Some(runloop) = self.runloops.remove(&path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(path); + } + } +} diff --git a/third_party/rust/authenticator/src/freebsd/transaction.rs b/third_party/rust/authenticator/src/freebsd/transaction.rs new file mode 100644 index 0000000000..e7cd00f184 --- /dev/null +++ b/third_party/rust/authenticator/src/freebsd/transaction.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use runloop::RunLoop; +use std::ffi::OsString; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/freebsd/uhid.rs b/third_party/rust/authenticator/src/freebsd/uhid.rs new file mode 100644 index 0000000000..deb197ea8f --- /dev/null +++ b/third_party/rust/authenticator/src/freebsd/uhid.rs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::io; +use std::os::unix::io::RawFd; +use std::ptr; + +use crate::hidproto::*; +use crate::util::from_unix_result; + +#[allow(non_camel_case_types)] +#[repr(C)] +#[derive(Debug)] +pub struct GenDescriptor { + ugd_data: *mut u8, + ugd_lang_id: u16, + ugd_maxlen: u16, + ugd_actlen: u16, + ugd_offset: u16, + ugd_config_index: u8, + ugd_string_index: u8, + ugd_iface_index: u8, + ugd_altif_index: u8, + ugd_endpt_index: u8, + ugd_report_index: u8, + reserved: [u8; 16], +} + +impl Default for GenDescriptor { + fn default() -> GenDescriptor { + GenDescriptor { + ugd_data: ptr::null_mut(), + ugd_lang_id: 0, + ugd_maxlen: 65535, + ugd_actlen: 0, + ugd_offset: 0, + ugd_config_index: 0, + ugd_string_index: 0, + ugd_iface_index: 0, + ugd_altif_index: 0, + ugd_endpt_index: 0, + ugd_report_index: 0, + reserved: [0; 16], + } + } +} + +const IOWR: u32 = 0x40000000 | 0x80000000; + +const IOCPARM_SHIFT: u32 = 13; +const IOCPARM_MASK: u32 = (1 << IOCPARM_SHIFT) - 1; + +const TYPESHIFT: u32 = 8; +const SIZESHIFT: u32 = 16; + +macro_rules! ioctl { + ($dir:expr, $name:ident, $ioty:expr, $nr:expr, $size:expr; $ty:ty) => { + pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> { + let ioc = ($dir as u32) + | (($size as u32 & IOCPARM_MASK) << SIZESHIFT) + | (($ioty as u32) << TYPESHIFT) + | ($nr as u32); + from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val)) + } + }; +} + +// https://github.com/freebsd/freebsd/blob/master/sys/dev/usb/usb_ioctl.h +ioctl!(IOWR, usb_get_report_desc, b'U', 21, 32; /*struct*/ GenDescriptor); + +fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> { + let mut desc = GenDescriptor::default(); + let _ = unsafe { usb_get_report_desc(fd, &mut desc)? }; + desc.ugd_maxlen = desc.ugd_actlen; + let mut value = Vec::with_capacity(desc.ugd_actlen as usize); + unsafe { + value.set_len(desc.ugd_actlen as usize); + } + desc.ugd_data = value.as_mut_ptr(); + let _ = unsafe { usb_get_report_desc(fd, &mut desc)? }; + Ok(ReportDescriptor { value }) +} + +pub fn is_u2f_device(fd: RawFd) -> bool { + match read_report_descriptor(fd) { + Ok(desc) => has_fido_usage(desc), + Err(_) => false, // Upon failure, just say it's not a U2F device. + } +} diff --git a/third_party/rust/authenticator/src/hidproto.rs b/third_party/rust/authenticator/src/hidproto.rs new file mode 100644 index 0000000000..5679e6e5d7 --- /dev/null +++ b/third_party/rust/authenticator/src/hidproto.rs @@ -0,0 +1,253 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Shared code for platforms that use raw HID access (Linux, FreeBSD, etc.) + +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::cast_lossless, clippy::needless_lifetimes) +)] + +use std::io; +use std::mem; + +use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, INIT_HEADER_SIZE, MAX_HID_RPT_SIZE}; + +// The 4 MSBs (the tag) are set when it's a long item. +const HID_MASK_LONG_ITEM_TAG: u8 = 0b1111_0000; +// The 2 LSBs denote the size of a short item. +const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b0000_0011; +// The 6 MSBs denote the tag (4) and type (2). +const HID_MASK_ITEM_TAGTYPE: u8 = 0b1111_1100; +// tag=0000, type=10 (local) +const HID_ITEM_TAGTYPE_USAGE: u8 = 0b0000_1000; +// tag=0000, type=01 (global) +const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b0000_0100; +// tag=1000, type=00 (main) +const HID_ITEM_TAGTYPE_INPUT: u8 = 0b1000_0000; +// tag=1001, type=00 (main) +const HID_ITEM_TAGTYPE_OUTPUT: u8 = 0b1001_0000; +// tag=1001, type=01 (global) +const HID_ITEM_TAGTYPE_REPORT_COUNT: u8 = 0b1001_0100; + +pub struct ReportDescriptor { + pub value: Vec<u8>, +} + +impl ReportDescriptor { + fn iter(self) -> ReportDescriptorIterator { + ReportDescriptorIterator::new(self) + } +} + +#[derive(Debug)] +pub enum Data { + UsagePage { data: u32 }, + Usage { data: u32 }, + Input, + Output, + ReportCount { data: u32 }, +} + +pub struct ReportDescriptorIterator { + desc: ReportDescriptor, + pos: usize, +} + +impl ReportDescriptorIterator { + fn new(desc: ReportDescriptor) -> Self { + Self { desc, pos: 0 } + } + + fn next_item(&mut self) -> Option<Data> { + let item = get_hid_item(&self.desc.value[self.pos..]); + if item.is_none() { + self.pos = self.desc.value.len(); // Close, invalid data. + return None; + } + + let (tag_type, key_len, data) = item.unwrap(); + + // Advance if we have a valid item. + self.pos += key_len + data.len(); + + // We only check short items. + if key_len > 1 { + return None; // Check next item. + } + + // Short items have max. length of 4 bytes. + assert!(data.len() <= mem::size_of::<u32>()); + + // Convert data bytes to a uint. + let data = read_uint_le(data); + match tag_type { + HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }), + HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }), + HID_ITEM_TAGTYPE_INPUT => Some(Data::Input), + HID_ITEM_TAGTYPE_OUTPUT => Some(Data::Output), + HID_ITEM_TAGTYPE_REPORT_COUNT => Some(Data::ReportCount { data }), + _ => None, + } + } +} + +impl Iterator for ReportDescriptorIterator { + type Item = Data; + + fn next(&mut self) -> Option<Self::Item> { + if self.pos >= self.desc.value.len() { + return None; + } + + self.next_item().or_else(|| self.next()) + } +} + +fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> { + if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG { + get_hid_long_item(buf) + } else { + get_hid_short_item(buf) + } +} + +fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> { + // A valid long item has at least three bytes. + if buf.len() < 3 { + return None; + } + + let len = buf[1] as usize; + + // Ensure that there are enough bytes left in the buffer. + if len > buf.len() - 3 { + return None; + } + + Some((buf[2], 3 /* key length */, &buf[3..])) +} + +fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> { + // This is a short item. The bottom two bits of the key + // contain the length of the data section (value) for this key. + let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE { + s @ 0..=2 => s as usize, + _ => 4, /* _ == 3 */ + }; + + // Ensure that there are enough bytes left in the buffer. + if len > buf.len() - 1 { + return None; + } + + Some(( + buf[0] & HID_MASK_ITEM_TAGTYPE, + 1, /* key length */ + &buf[1..=len], + )) +} + +fn read_uint_le(buf: &[u8]) -> u32 { + assert!(buf.len() <= 4); + // Parse the number in little endian byte order. + buf.iter() + .rev() + .fold(0, |num, b| (num << 8) | (u32::from(*b))) +} + +pub fn has_fido_usage(desc: ReportDescriptor) -> bool { + let mut usage_page = None; + let mut usage = None; + + for data in desc.iter() { + match data { + Data::UsagePage { data } => usage_page = Some(data), + Data::Usage { data } => usage = Some(data), + _ => {} + } + + // Check the values we found. + if let (Some(usage_page), Some(usage)) = (usage_page, usage) { + return usage_page == u32::from(FIDO_USAGE_PAGE) + && usage == u32::from(FIDO_USAGE_U2FHID); + } + } + + false +} + +pub fn read_hid_rpt_sizes(desc: ReportDescriptor) -> io::Result<(usize, usize)> { + let mut in_rpt_count = None; + let mut out_rpt_count = None; + let mut last_rpt_count = None; + + for data in desc.iter() { + match data { + Data::ReportCount { data } => { + if last_rpt_count != None { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Duplicate HID_ReportCount", + )); + } + last_rpt_count = Some(data as usize); + } + Data::Input => { + if last_rpt_count.is_none() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "HID_Input should be preceded by HID_ReportCount", + )); + } + if in_rpt_count.is_some() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Duplicate HID_ReportCount", + )); + } + in_rpt_count = last_rpt_count; + last_rpt_count = None + } + Data::Output => { + if last_rpt_count.is_none() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "HID_Output should be preceded by HID_ReportCount", + )); + } + if out_rpt_count.is_some() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Duplicate HID_ReportCount", + )); + } + out_rpt_count = last_rpt_count; + last_rpt_count = None; + } + _ => {} + } + } + + match (in_rpt_count, out_rpt_count) { + (Some(in_count), Some(out_count)) => { + if in_count > INIT_HEADER_SIZE + && in_count <= MAX_HID_RPT_SIZE + && out_count > INIT_HEADER_SIZE + && out_count <= MAX_HID_RPT_SIZE + { + Ok((in_count, out_count)) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "Report size is too small or too large", + )) + } + } + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Failed to extract report sizes from report descriptor", + )), + } +} diff --git a/third_party/rust/authenticator/src/lib.rs b/third_party/rust/authenticator/src/lib.rs new file mode 100644 index 0000000000..cfe82deb2b --- /dev/null +++ b/third_party/rust/authenticator/src/lib.rs @@ -0,0 +1,129 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[macro_use] +mod util; + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +pub mod hidproto; + +#[cfg(any(target_os = "linux"))] +extern crate libudev; + +#[cfg(any(target_os = "linux"))] +#[path = "linux/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "freebsd"))] +extern crate devd_rs; + +#[cfg(any(target_os = "freebsd"))] +#[path = "freebsd/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "netbsd"))] +#[path = "netbsd/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "openbsd"))] +#[path = "openbsd/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "macos"))] +extern crate core_foundation; + +#[cfg(any(target_os = "macos"))] +#[path = "macos/mod.rs"] +pub mod platform; + +#[cfg(any(target_os = "windows"))] +#[path = "windows/mod.rs"] +pub mod platform; + +#[cfg(not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "windows" +)))] +#[path = "stub/mod.rs"] +pub mod platform; + +extern crate libc; +#[macro_use] +extern crate log; +extern crate rand; +extern crate runloop; + +#[macro_use] +extern crate bitflags; + +pub mod authenticatorservice; +mod consts; +mod statemachine; +mod u2fprotocol; +mod u2ftypes; + +mod manager; +pub use crate::manager::U2FManager; + +mod capi; +pub use crate::capi::*; + +pub mod errors; +pub mod statecallback; +mod virtualdevices; + +// Keep this in sync with the constants in u2fhid-capi.h. +bitflags! { + pub struct RegisterFlags: u64 { + const REQUIRE_RESIDENT_KEY = 1; + const REQUIRE_USER_VERIFICATION = 2; + const REQUIRE_PLATFORM_ATTACHMENT = 4; + } +} +bitflags! { + pub struct SignFlags: u64 { + const REQUIRE_USER_VERIFICATION = 1; + } +} +bitflags! { + pub struct AuthenticatorTransports: u8 { + const USB = 1; + const NFC = 2; + const BLE = 4; + } +} + +#[derive(Clone)] +pub struct KeyHandle { + pub credential: Vec<u8>, + pub transports: AuthenticatorTransports, +} + +pub type AppId = Vec<u8>; +pub type RegisterResult = (Vec<u8>, u2ftypes::U2FDeviceInfo); +pub type SignResult = (AppId, Vec<u8>, Vec<u8>, u2ftypes::U2FDeviceInfo); + +pub type Result<T> = std::result::Result<T, errors::AuthenticatorError>; + +#[derive(Debug, Clone)] +pub enum StatusUpdate { + DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo }, + DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo }, + Success { dev_info: u2ftypes::U2FDeviceInfo }, +} + +#[cfg(test)] +#[macro_use] +extern crate assert_matches; + +#[cfg(fuzzing)] +pub use consts::*; +#[cfg(fuzzing)] +pub use u2fprotocol::*; +#[cfg(fuzzing)] +pub use u2ftypes::*; diff --git a/third_party/rust/authenticator/src/linux/device.rs b/third_party/rust/authenticator/src/linux/device.rs new file mode 100644 index 0000000000..4a0d58d6d7 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/device.rs @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::ffi::{CString, OsString}; +use std::io; +use std::io::{Read, Write}; +use std::os::unix::prelude::*; + +use crate::consts::CID_BROADCAST; +use crate::platform::{hidraw, monitor}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use crate::util::from_unix_result; + +#[derive(Debug)] +pub struct Device { + path: OsString, + fd: libc::c_int, + in_rpt_size: usize, + out_rpt_size: usize, + cid: [u8; 4], + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(path: OsString) -> io::Result<Self> { + let cstr = CString::new(path.as_bytes())?; + let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) }; + let fd = from_unix_result(fd)?; + let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd); + Ok(Self { + path, + fd, + in_rpt_size, + out_rpt_size, + cid: CID_BROADCAST, + dev_info: None, + }) + } + + pub fn is_u2f(&self) -> bool { + hidraw::is_u2f_device(self.fd) + } +} + +impl Drop for Device { + fn drop(&mut self) { + // Close the fd, ignore any errors. + let _ = unsafe { libc::close(self.fd) }; + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + let bufp = buf.as_mut_ptr() as *mut libc::c_void; + let rv = unsafe { libc::read(self.fd, bufp, buf.len()) }; + from_unix_result(rv as usize) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let bufp = buf.as_ptr() as *const libc::c_void; + let rv = unsafe { libc::write(self.fd, bufp, buf.len()) }; + from_unix_result(rv as usize) + } + + // USB HID writes don't buffer, so this will be a nop. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl U2FDevice for Device { + fn get_cid(&self) -> &[u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + self.in_rpt_size + } + + fn out_rpt_size(&self) -> usize { + self.out_rpt_size + } + + fn get_property(&self, prop_name: &str) -> io::Result<String> { + monitor::get_property_linux(&self.path, prop_name) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/linux/hidraw.rs b/third_party/rust/authenticator/src/linux/hidraw.rs new file mode 100644 index 0000000000..662ea83298 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/hidraw.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] + +extern crate libc; + +use std::io; +use std::os::unix::io::RawFd; + +use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE}; +use crate::consts::MAX_HID_RPT_SIZE; +use crate::hidproto::*; +use crate::util::{from_unix_result, io_err}; + +#[allow(non_camel_case_types)] +#[repr(C)] +pub struct LinuxReportDescriptor { + size: ::libc::c_int, + value: [u8; 4096], +} + +const HID_MAX_DESCRIPTOR_SIZE: usize = 4096; + +#[cfg(not(target_env = "musl"))] +type IocType = libc::c_ulong; +#[cfg(target_env = "musl")] +type IocType = libc::c_int; + +pub unsafe fn hidiocgrdescsize( + fd: libc::c_int, + val: *mut ::libc::c_int, +) -> io::Result<libc::c_int> { + from_unix_result(libc::ioctl(fd, _HIDIOCGRDESCSIZE as IocType, val)) +} + +pub unsafe fn hidiocgrdesc( + fd: libc::c_int, + val: *mut LinuxReportDescriptor, +) -> io::Result<libc::c_int> { + from_unix_result(libc::ioctl(fd, _HIDIOCGRDESC as IocType, val)) +} + +pub fn is_u2f_device(fd: RawFd) -> bool { + match read_report_descriptor(fd) { + Ok(desc) => has_fido_usage(desc), + Err(_) => false, // Upon failure, just say it's not a U2F device. + } +} + +pub fn read_hid_rpt_sizes_or_defaults(fd: RawFd) -> (usize, usize) { + let default_rpt_sizes = (MAX_HID_RPT_SIZE, MAX_HID_RPT_SIZE); + let desc = read_report_descriptor(fd); + if let Ok(desc) = desc { + if let Ok(rpt_sizes) = read_hid_rpt_sizes(desc) { + rpt_sizes + } else { + default_rpt_sizes + } + } else { + default_rpt_sizes + } +} + +fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> { + let mut desc = LinuxReportDescriptor { + size: 0, + value: [0; HID_MAX_DESCRIPTOR_SIZE], + }; + + let _ = unsafe { hidiocgrdescsize(fd, &mut desc.size)? }; + if desc.size == 0 || desc.size as usize > desc.value.len() { + return Err(io_err("unexpected hidiocgrdescsize() result")); + } + + let _ = unsafe { hidiocgrdesc(fd, &mut desc)? }; + let mut value = Vec::from(&desc.value[..]); + value.truncate(desc.size as usize); + Ok(ReportDescriptor { value }) +} diff --git a/third_party/rust/authenticator/src/linux/hidwrapper.h b/third_party/rust/authenticator/src/linux/hidwrapper.h new file mode 100644 index 0000000000..ce77e0f1ca --- /dev/null +++ b/third_party/rust/authenticator/src/linux/hidwrapper.h @@ -0,0 +1,12 @@ +#include<sys/ioctl.h> +#include<linux/hidraw.h> + +/* we define these constants to work around the fact that bindgen + can't deal with the _IOR macro function. We let cpp deal with it + for us. */ + +const __u32 _HIDIOCGRDESCSIZE = HIDIOCGRDESCSIZE; +#undef HIDIOCGRDESCSIZE + +const __u32 _HIDIOCGRDESC = HIDIOCGRDESC; +#undef HIDIOCGRDESC diff --git a/third_party/rust/authenticator/src/linux/hidwrapper.rs b/third_party/rust/authenticator/src/linux/hidwrapper.rs new file mode 100644 index 0000000000..ea1a39051b --- /dev/null +++ b/third_party/rust/authenticator/src/linux/hidwrapper.rs @@ -0,0 +1,48 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +// sadly we need this file so we can avoid the suprious warnings that +// would come with bindgen, as well as to avoid cluttering the mod.rs +// with spurious architecture specific modules. + +#[cfg(target_arch = "x86")] +include!("ioctl_x86.rs"); + +#[cfg(target_arch = "x86_64")] +include!("ioctl_x86_64.rs"); + +#[cfg(all(target_arch = "mips", target_endian = "little"))] +include!("ioctl_mipsle.rs"); + +#[cfg(all(target_arch = "mips", target_endian = "big"))] +include!("ioctl_mipsbe.rs"); + +#[cfg(all(target_arch = "mips64", target_endian = "little"))] +include!("ioctl_mips64le.rs"); + +#[cfg(all(target_arch = "powerpc", target_endian = "little"))] +include!("ioctl_powerpcle.rs"); + +#[cfg(all(target_arch = "powerpc", target_endian = "big"))] +include!("ioctl_powerpcbe.rs"); + +#[cfg(all(target_arch = "powerpc64", target_endian = "little"))] +include!("ioctl_powerpc64le.rs"); + +#[cfg(all(target_arch = "powerpc64", target_endian = "big"))] +include!("ioctl_powerpc64be.rs"); + +#[cfg(all(target_arch = "arm", target_endian = "little"))] +include!("ioctl_armle.rs"); + +#[cfg(all(target_arch = "arm", target_endian = "big"))] +include!("ioctl_armbe.rs"); + +#[cfg(all(target_arch = "aarch64", target_endian = "little"))] +include!("ioctl_aarch64le.rs"); + +#[cfg(all(target_arch = "aarch64", target_endian = "big"))] +include!("ioctl_aarch64be.rs"); + +#[cfg(all(target_arch = "s390x", target_endian = "big"))] +include!("ioctl_s390xbe.rs"); diff --git a/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs b/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs new file mode 100644 index 0000000000..a784e9bf46 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225; +pub const _HIDIOCGRDESC: __u32 = 2416199682; diff --git a/third_party/rust/authenticator/src/linux/ioctl_armle.rs b/third_party/rust/authenticator/src/linux/ioctl_armle.rs new file mode 100644 index 0000000000..a784e9bf46 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_armle.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225; +pub const _HIDIOCGRDESC: __u32 = 2416199682; diff --git a/third_party/rust/authenticator/src/linux/ioctl_mips64le.rs b/third_party/rust/authenticator/src/linux/ioctl_mips64le.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_mips64le.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs b/third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_mipsle.rs b/third_party/rust/authenticator/src/linux/ioctl_mipsle.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_mipsle.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs new file mode 100644 index 0000000000..1ca187fa1f --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401; +pub const _HIDIOCGRDESC: __u32 = 1342457858; diff --git a/third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs b/third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs new file mode 100644 index 0000000000..a784e9bf46 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225; +pub const _HIDIOCGRDESC: __u32 = 2416199682; diff --git a/third_party/rust/authenticator/src/linux/ioctl_x86.rs b/third_party/rust/authenticator/src/linux/ioctl_x86.rs new file mode 100644 index 0000000000..a784e9bf46 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_x86.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225; +pub const _HIDIOCGRDESC: __u32 = 2416199682; diff --git a/third_party/rust/authenticator/src/linux/ioctl_x86_64.rs b/third_party/rust/authenticator/src/linux/ioctl_x86_64.rs new file mode 100644 index 0000000000..a784e9bf46 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/ioctl_x86_64.rs @@ -0,0 +1,5 @@ +/* automatically generated by rust-bindgen */ + +pub type __u32 = ::std::os::raw::c_uint; +pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225; +pub const _HIDIOCGRDESC: __u32 = 2416199682; diff --git a/third_party/rust/authenticator/src/linux/mod.rs b/third_party/rust/authenticator/src/linux/mod.rs new file mode 100644 index 0000000000..c4d490ecee --- /dev/null +++ b/third_party/rust/authenticator/src/linux/mod.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(clippy::unreadable_literal)] + +pub mod device; +pub mod transaction; + +mod hidraw; +mod hidwrapper; +mod monitor; diff --git a/third_party/rust/authenticator/src/linux/monitor.rs b/third_party/rust/authenticator/src/linux/monitor.rs new file mode 100644 index 0000000000..595e2f3ddf --- /dev/null +++ b/third_party/rust/authenticator/src/linux/monitor.rs @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use libc::{c_int, c_short, c_ulong}; +use libudev::EventType; +use runloop::RunLoop; +use std::collections::HashMap; +use std::ffi::OsString; +use std::io; +use std::os::unix::io::AsRawFd; +use std::sync::Arc; + +const UDEV_SUBSYSTEM: &str = "hidraw"; +const POLLIN: c_short = 0x0001; +const POLL_TIMEOUT: c_int = 100; + +fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> { + let nfds = fds.len() as c_ulong; + + let rv = unsafe { ::libc::poll((&mut fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) }; + + if rv < 0 { + Err(io::Error::from_raw_os_error(rv)) + } else { + Ok(()) + } +} + +pub struct Monitor<F> +where + F: Fn(OsString, &dyn Fn() -> bool) + Sync, +{ + runloops: HashMap<OsString, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + let ctx = libudev::Context::new()?; + + let mut enumerator = libudev::Enumerator::new(&ctx)?; + enumerator.match_subsystem(UDEV_SUBSYSTEM)?; + + // Iterate all existing devices. + for dev in enumerator.scan_devices()? { + if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) { + self.add_device(path); + } + } + + let mut monitor = libudev::Monitor::new(&ctx)?; + monitor.match_subsystem(UDEV_SUBSYSTEM)?; + + // Start listening for new devices. + let mut socket = monitor.listen()?; + let mut fds = vec![::libc::pollfd { + fd: socket.as_raw_fd(), + events: POLLIN, + revents: 0, + }]; + + while alive() { + // Wait for new events, break on failure. + poll(&mut fds)?; + + if let Some(event) = socket.receive_event() { + self.process_event(&event); + } + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn process_event(&mut self, event: &libudev::Event) { + let path = event + .device() + .devnode() + .map(|dn| dn.to_owned().into_os_string()); + + match (event.event_type(), path) { + (EventType::Add, Some(path)) => { + self.add_device(path); + } + (EventType::Remove, Some(path)) => { + self.remove_device(&path); + } + _ => { /* ignore other types and failures */ } + } + } + + fn add_device(&mut self, path: OsString) { + let f = self.new_device_cb.clone(); + let key = path.clone(); + + debug!("Adding device {}", path.to_string_lossy()); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(path, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + + fn remove_device(&mut self, path: &OsString) { + debug!("Removing device {}", path.to_string_lossy()); + + if let Some(runloop) = self.runloops.remove(path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(&path); + } + } +} + +pub fn get_property_linux(path: &OsString, prop_name: &str) -> io::Result<String> { + let ctx = libudev::Context::new()?; + + let mut enumerator = libudev::Enumerator::new(&ctx)?; + enumerator.match_subsystem(UDEV_SUBSYSTEM)?; + + // Iterate all existing devices, since we don't have a syspath + // and libudev-rs doesn't implement opening by devnode. + for dev in enumerator.scan_devices()? { + if dev.devnode().is_some() && dev.devnode().unwrap() == path { + debug!( + "get_property_linux Querying property {} from {}", + prop_name, + dev.syspath().display() + ); + + let value = dev + .attribute_value(prop_name) + .ok_or(io::ErrorKind::Other)? + .to_string_lossy(); + + debug!("get_property_linux Fetched Result, {}={}", prop_name, value); + return Ok(value.to_string()); + } + } + + Err(io::Error::new( + io::ErrorKind::Other, + "Unable to find device", + )) +} diff --git a/third_party/rust/authenticator/src/linux/transaction.rs b/third_party/rust/authenticator/src/linux/transaction.rs new file mode 100644 index 0000000000..e7cd00f184 --- /dev/null +++ b/third_party/rust/authenticator/src/linux/transaction.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use runloop::RunLoop; +use std::ffi::OsString; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/macos/device.rs b/third_party/rust/authenticator/src/macos/device.rs new file mode 100644 index 0000000000..425a27959b --- /dev/null +++ b/third_party/rust/authenticator/src/macos/device.rs @@ -0,0 +1,156 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate log; + +use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; +use crate::platform::iokit::*; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use core_foundation::base::*; +use core_foundation::string::*; +use std::convert::TryInto; +use std::io; +use std::io::{Read, Write}; +use std::sync::mpsc::{Receiver, RecvTimeoutError}; +use std::time::Duration; + +const READ_TIMEOUT: u64 = 15; + +pub struct Device { + device_ref: IOHIDDeviceRef, + cid: [u8; 4], + report_rx: Receiver<Vec<u8>>, + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(dev_ids: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self> { + let (device_ref, report_rx) = dev_ids; + Ok(Self { + device_ref, + cid: CID_BROADCAST, + report_rx, + dev_info: None, + }) + } + + pub fn is_u2f(&self) -> bool { + true + } + + unsafe fn get_property_macos(&self, prop_name: &str) -> io::Result<String> { + let prop_ref = IOHIDDeviceGetProperty( + self.device_ref, + CFString::new(prop_name).as_concrete_TypeRef(), + ); + if prop_ref.is_null() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "IOHIDDeviceGetProperty received nullptr for property {}", + prop_name + ), + )); + } + + if CFGetTypeID(prop_ref) != CFStringGetTypeID() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "IOHIDDeviceGetProperty returned non-string type for property {}", + prop_name + ), + )); + } + + Ok(CFString::from_void(prop_ref).to_string()) + } +} + +impl PartialEq for Device { + fn eq(&self, other_device: &Device) -> bool { + self.device_ref == other_device.device_ref + } +} + +impl Read for Device { + fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> { + let timeout = Duration::from_secs(READ_TIMEOUT); + let data = match self.report_rx.recv_timeout(timeout) { + Ok(v) => v, + Err(e) if e == RecvTimeoutError::Timeout => { + return Err(io::Error::new(io::ErrorKind::TimedOut, e)); + } + Err(e) => { + return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e)); + } + }; + bytes.write(&data) + } +} + +impl Write for Device { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + assert_eq!(bytes.len(), self.out_rpt_size() + 1); + + let report_id = i64::from(bytes[0]); + // Skip report number when not using numbered reports. + let start = if report_id == 0x0 { 1 } else { 0 }; + let data = &bytes[start..]; + + let result = unsafe { + IOHIDDeviceSetReport( + self.device_ref, + kIOHIDReportTypeOutput, + report_id.try_into().unwrap(), + data.as_ptr(), + data.len() as CFIndex, + ) + }; + if result != 0 { + warn!("set_report sending failure = {0:X}", result); + return Err(io::Error::from_raw_os_error(result)); + } + trace!("set_report sending success = {0:X}", result); + + Ok(bytes.len()) + } + + // USB HID writes don't buffer, so this will be a nop. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl U2FDevice for Device { + fn get_cid(&self) -> &[u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, prop_name: &str) -> io::Result<String> { + unsafe { self.get_property_macos(prop_name) } + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/macos/iokit.rs b/third_party/rust/authenticator/src/macos/iokit.rs new file mode 100644 index 0000000000..656cdb045d --- /dev/null +++ b/third_party/rust/authenticator/src/macos/iokit.rs @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] + +extern crate libc; + +use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; +use core_foundation::array::*; +use core_foundation::base::*; +use core_foundation::dictionary::*; +use core_foundation::number::*; +use core_foundation::runloop::*; +use core_foundation::string::*; +use std::ops::Deref; +use std::os::raw::c_void; + +type IOOptionBits = u32; + +pub type IOReturn = libc::c_int; + +pub type IOHIDManagerRef = *mut __IOHIDManager; +pub type IOHIDManagerOptions = IOOptionBits; + +pub type IOHIDDeviceCallback = extern "C" fn( + context: *mut c_void, + result: IOReturn, + sender: *mut c_void, + device: IOHIDDeviceRef, +); + +pub type IOHIDReportType = IOOptionBits; +pub type IOHIDReportCallback = extern "C" fn( + context: *mut c_void, + result: IOReturn, + sender: IOHIDDeviceRef, + report_type: IOHIDReportType, + report_id: u32, + report: *mut u8, + report_len: CFIndex, +); + +pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0; + +pub const kIOHIDReportTypeOutput: IOHIDReportType = 1; + +#[repr(C)] +pub struct __IOHIDManager { + __private: c_void, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct IOHIDDeviceRef(*const c_void); + +unsafe impl Send for IOHIDDeviceRef {} +unsafe impl Sync for IOHIDDeviceRef {} + +pub struct SendableRunLoop(CFRunLoopRef); + +impl SendableRunLoop { + pub fn new(runloop: CFRunLoopRef) -> Self { + // Keep the CFRunLoop alive for as long as we are. + unsafe { CFRetain(runloop as *mut c_void) }; + + SendableRunLoop(runloop) + } +} + +unsafe impl Send for SendableRunLoop {} + +impl Deref for SendableRunLoop { + type Target = CFRunLoopRef; + + fn deref(&self) -> &CFRunLoopRef { + &self.0 + } +} + +impl Drop for SendableRunLoop { + fn drop(&mut self) { + unsafe { CFRelease(self.0 as *mut c_void) }; + } +} + +#[repr(C)] +pub struct CFRunLoopObserverContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>, + pub release: Option<extern "C" fn(info: *const c_void)>, + pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>, +} + +impl CFRunLoopObserverContext { + pub fn new(context: *mut c_void) -> Self { + Self { + version: 0 as CFIndex, + info: context, + retain: None, + release: None, + copyDescription: None, + } + } +} + +pub struct CFRunLoopEntryObserver { + observer: CFRunLoopObserverRef, + // Keep alive until the observer goes away. + context_ptr: *mut CFRunLoopObserverContext, +} + +impl CFRunLoopEntryObserver { + pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self { + let context = CFRunLoopObserverContext::new(context); + let context_ptr = Box::into_raw(Box::new(context)); + + let observer = unsafe { + CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopEntry, + false as Boolean, + 0, + callback, + context_ptr, + ) + }; + + Self { + observer, + context_ptr, + } + } + + pub fn add_to_current_runloop(&self) { + unsafe { + CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode) + }; + } +} + +impl Drop for CFRunLoopEntryObserver { + fn drop(&mut self) { + unsafe { + CFRelease(self.observer as *mut c_void); + + // Drop the CFRunLoopObserverContext. + let _ = Box::from_raw(self.context_ptr); + }; + } +} + +pub struct IOHIDDeviceMatcher { + pub dict: CFDictionary<CFString, CFNumber>, +} + +impl IOHIDDeviceMatcher { + pub fn new() -> Self { + let dict = CFDictionary::<CFString, CFNumber>::from_CFType_pairs(&[ + ( + CFString::from_static_string("DeviceUsage"), + CFNumber::from(i32::from(FIDO_USAGE_U2FHID)), + ), + ( + CFString::from_static_string("DeviceUsagePage"), + CFNumber::from(i32::from(FIDO_USAGE_PAGE)), + ), + ]); + Self { dict } + } +} + +#[link(name = "IOKit", kind = "framework")] +extern "C" { + // CFRunLoop + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + + // IOHIDManager + pub fn IOHIDManagerCreate( + allocator: CFAllocatorRef, + options: IOHIDManagerOptions, + ) -> IOHIDManagerRef; + pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef); + pub fn IOHIDManagerRegisterDeviceMatchingCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerRegisterDeviceRemovalCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerRegisterInputReportCallback( + manager: IOHIDManagerRef, + callback: IOHIDReportCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerScheduleWithRunLoop( + manager: IOHIDManagerRef, + runLoop: CFRunLoopRef, + runLoopMode: CFStringRef, + ); + + // IOHIDDevice + pub fn IOHIDDeviceSetReport( + device: IOHIDDeviceRef, + reportType: IOHIDReportType, + reportID: CFIndex, + report: *const u8, + reportLength: CFIndex, + ) -> IOReturn; + pub fn IOHIDDeviceGetProperty(device: IOHIDDeviceRef, key: CFStringRef) -> CFTypeRef; +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use std::os::raw::c_void; + use std::ptr; + use std::sync::mpsc::{channel, Sender}; + use std::thread; + + extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { + let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) }; + + // Send the current runloop to the receiver to unblock it. + let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); + } + + #[test] + fn test_sendable_runloop() { + let (tx, rx) = channel(); + + let thread = thread::spawn(move || { + // Send the runloop to the owning thread. + let context = &tx as *const _ as *mut c_void; + let obs = CFRunLoopEntryObserver::new(observe, context); + obs.add_to_current_runloop(); + + unsafe { + // We need some source for the runloop to run. + let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0); + assert!(!manager.is_null()); + + IOHIDManagerScheduleWithRunLoop( + manager, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode, + ); + IOHIDManagerSetDeviceMatching(manager, ptr::null_mut()); + + let rv = IOHIDManagerOpen(manager, 0); + assert_eq!(rv, 0); + + // This will run until `CFRunLoopStop()` is called. + CFRunLoopRun(); + + let rv = IOHIDManagerClose(manager, 0); + assert_eq!(rv, 0); + + CFRelease(manager as *mut c_void); + } + }); + + // Block until we enter the CFRunLoop. + let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop"); + + // Stop the runloop. + unsafe { CFRunLoopStop(*runloop) }; + + // Stop the thread. + thread.join().expect("failed to join the thread"); + + // Try to stop the runloop again (without crashing). + unsafe { CFRunLoopStop(*runloop) }; + } +} diff --git a/third_party/rust/authenticator/src/macos/mod.rs b/third_party/rust/authenticator/src/macos/mod.rs new file mode 100644 index 0000000000..44e85094d0 --- /dev/null +++ b/third_party/rust/authenticator/src/macos/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod iokit; +mod monitor; diff --git a/third_party/rust/authenticator/src/macos/monitor.rs b/third_party/rust/authenticator/src/macos/monitor.rs new file mode 100644 index 0000000000..189366f9d1 --- /dev/null +++ b/third_party/rust/authenticator/src/macos/monitor.rs @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; +extern crate log; + +use crate::platform::iokit::*; +use crate::util::io_err; +use core_foundation::base::*; +use core_foundation::runloop::*; +use runloop::RunLoop; +use std::collections::HashMap; +use std::os::raw::c_void; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::{io, slice}; + +struct DeviceData { + tx: Sender<Vec<u8>>, + runloop: RunLoop, +} + +pub struct Monitor<F> +where + F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync, +{ + manager: IOHIDManagerRef, + // Keep alive until the monitor goes away. + _matcher: IOHIDDeviceMatcher, + map: HashMap<IOHIDDeviceRef, DeviceData>, + new_device_cb: F, +} + +impl<F> Monitor<F> +where + F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) }; + + // Match FIDO devices only. + let _matcher = IOHIDDeviceMatcher::new(); + unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.dict.as_concrete_TypeRef()) }; + + Self { + manager, + _matcher, + new_device_cb, + map: HashMap::new(), + } + } + + pub fn start(&mut self) -> io::Result<()> { + let context = self as *mut Self as *mut c_void; + + unsafe { + IOHIDManagerRegisterDeviceMatchingCallback( + self.manager, + Monitor::<F>::on_device_matching, + context, + ); + IOHIDManagerRegisterDeviceRemovalCallback( + self.manager, + Monitor::<F>::on_device_removal, + context, + ); + IOHIDManagerRegisterInputReportCallback( + self.manager, + Monitor::<F>::on_input_report, + context, + ); + + IOHIDManagerScheduleWithRunLoop( + self.manager, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode, + ); + + let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone); + if rv == 0 { + Ok(()) + } else { + Err(io_err(&format!("Couldn't open HID Manager, rv={}", rv))) + } + } + } + + pub fn stop(&mut self) { + // Remove all devices. + while !self.map.is_empty() { + let device_ref = *self.map.keys().next().unwrap(); + self.remove_device(device_ref); + } + + // Close the manager and its devices. + unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) }; + } + + fn remove_device(&mut self, device_ref: IOHIDDeviceRef) { + if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) { + // Dropping `tx` will make Device::read() fail eventually. + drop(tx); + + // Wait until the runloop stopped. + runloop.cancel(); + } + } + + extern "C" fn on_input_report( + context: *mut c_void, + _: IOReturn, + device_ref: IOHIDDeviceRef, + _: IOHIDReportType, + _: u32, + report: *mut u8, + report_len: CFIndex, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + let mut send_failed = false; + + // Ignore the report if we can't find a device for it. + if let Some(&DeviceData { ref tx, .. }) = this.map.get(&device_ref) { + let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() }; + send_failed = tx.send(data).is_err(); + } + + // Remove the device if sending fails. + if send_failed { + this.remove_device(device_ref); + } + } + + extern "C" fn on_device_matching( + context: *mut c_void, + _: IOReturn, + _: *mut c_void, + device_ref: IOHIDDeviceRef, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + + let (tx, rx) = channel(); + let f = &this.new_device_cb; + + // Create a new per-device runloop. + let runloop = RunLoop::new(move |alive| { + // Ensure that the runloop is still alive. + if alive() { + f((device_ref, rx), alive); + } + }); + + if let Ok(runloop) = runloop { + this.map.insert(device_ref, DeviceData { tx, runloop }); + } + } + + extern "C" fn on_device_removal( + context: *mut c_void, + _: IOReturn, + _: *mut c_void, + device_ref: IOHIDDeviceRef, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + this.remove_device(device_ref); + } +} + +impl<F> Drop for Monitor<F> +where + F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync, +{ + fn drop(&mut self) { + unsafe { CFRelease(self.manager as *mut c_void) }; + } +} diff --git a/third_party/rust/authenticator/src/macos/transaction.rs b/third_party/rust/authenticator/src/macos/transaction.rs new file mode 100644 index 0000000000..697730a41a --- /dev/null +++ b/third_party/rust/authenticator/src/macos/transaction.rs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use crate::errors; +use crate::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop}; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use core_foundation::runloop::*; +use std::os::raw::c_void; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread; + +// A transaction will run the given closure in a new thread, thereby using a +// separate per-thread state machine for each HID. It will either complete or +// fail through user action, timeout, or be cancelled when overridden by a new +// transaction. +pub struct Transaction { + runloop: Option<SendableRunLoop>, + thread: Option<thread::JoinHandle<()>>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let (tx, rx) = channel(); + let timeout = (timeout as f64) / 1000.0; + + let builder = thread::Builder::new(); + let thread = builder + .spawn(move || { + // Add a runloop observer that will be notified when we enter the + // runloop and tx.send() the current runloop to the owning thread. + // We need to ensure the runloop was entered before unblocking + // Transaction::new(), so we can always properly cancel. + let context = &tx as *const _ as *mut c_void; + let obs = CFRunLoopEntryObserver::new(Transaction::observe, context); + obs.add_to_current_runloop(); + + // Create a new HID device monitor and start polling. + let mut monitor = Monitor::new(new_device_cb); + try_or!(monitor.start(), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // This will block until completion, abortion, or timeout. + unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) }; + + // Close the monitor and its devices. + monitor.stop(); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + // Block until we enter the CFRunLoop. + let runloop = rx + .recv() + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + runloop: Some(runloop), + thread: Some(thread), + }) + } + + extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { + let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) }; + + // Send the current runloop to the receiver to unblock it. + let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); + } + + pub fn cancel(&mut self) { + // This must never be None. This won't block. + unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) }; + + // This must never be None. Ignore return value. + let _ = self.thread.take().unwrap().join(); + } +} diff --git a/third_party/rust/authenticator/src/manager.rs b/third_party/rust/authenticator/src/manager.rs new file mode 100644 index 0000000000..06f972dba4 --- /dev/null +++ b/third_party/rust/authenticator/src/manager.rs @@ -0,0 +1,197 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::sync::mpsc::{channel, RecvTimeoutError, Sender}; +use std::time::Duration; + +use crate::authenticatorservice::AuthenticatorTransport; +use crate::consts::PARAMETER_SIZE; +use crate::errors::*; +use crate::statecallback::StateCallback; +use crate::statemachine::StateMachine; +use runloop::RunLoop; + +enum QueueAction { + Register { + flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec<u8>, + application: crate::AppId, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + }, + Sign { + flags: crate::SignFlags, + timeout: u64, + challenge: Vec<u8>, + app_ids: Vec<crate::AppId>, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + }, + Cancel, +} + +pub struct U2FManager { + queue: RunLoop, + tx: Sender<QueueAction>, +} + +impl U2FManager { + pub fn new() -> io::Result<Self> { + let (tx, rx) = channel(); + + // Start a new work queue thread. + let queue = RunLoop::new(move |alive| { + let mut sm = StateMachine::new(); + + while alive() { + match rx.recv_timeout(Duration::from_millis(50)) { + Ok(QueueAction::Register { + flags, + timeout, + challenge, + application, + key_handles, + status, + callback, + }) => { + // This must not block, otherwise we can't cancel. + sm.register( + flags, + timeout, + challenge, + application, + key_handles, + status, + callback, + ); + } + Ok(QueueAction::Sign { + flags, + timeout, + challenge, + app_ids, + key_handles, + status, + callback, + }) => { + // This must not block, otherwise we can't cancel. + sm.sign( + flags, + timeout, + challenge, + app_ids, + key_handles, + status, + callback, + ); + } + Ok(QueueAction::Cancel) => { + // Cancelling must block so that we don't start a new + // polling thread before the old one has shut down. + sm.cancel(); + } + Err(RecvTimeoutError::Disconnected) => { + break; + } + _ => { /* continue */ } + } + } + + // Cancel any ongoing activity. + sm.cancel(); + })?; + + Ok(Self { queue, tx }) + } +} + +impl AuthenticatorTransport for U2FManager { + fn register( + &mut self, + flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec<u8>, + application: crate::AppId, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) -> crate::Result<()> { + if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + for key_handle in &key_handles { + if key_handle.credential.len() > 256 { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + let action = QueueAction::Register { + flags, + timeout, + challenge, + application, + key_handles, + status, + callback, + }; + Ok(self.tx.send(action)?) + } + + fn sign( + &mut self, + flags: crate::SignFlags, + timeout: u64, + challenge: Vec<u8>, + app_ids: Vec<crate::AppId>, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) -> crate::Result<()> { + if challenge.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + if app_ids.is_empty() { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + for app_id in &app_ids { + if app_id.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + for key_handle in &key_handles { + if key_handle.credential.len() > 256 { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + } + + let action = QueueAction::Sign { + flags, + timeout, + challenge, + app_ids, + key_handles, + status, + callback, + }; + Ok(self.tx.send(action)?) + } + + fn cancel(&mut self) -> crate::Result<()> { + Ok(self.tx.send(QueueAction::Cancel)?) + } +} + +impl Drop for U2FManager { + fn drop(&mut self) { + self.queue.cancel(); + } +} diff --git a/third_party/rust/authenticator/src/netbsd/device.rs b/third_party/rust/authenticator/src/netbsd/device.rs new file mode 100644 index 0000000000..92e7c22ea1 --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/device.rs @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::io; +use std::io::Read; +use std::io::Write; +use std::mem; + +use crate::consts::CID_BROADCAST; +use crate::consts::MAX_HID_RPT_SIZE; +use crate::platform::fd::Fd; +use crate::platform::uhid; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use crate::util::io_err; + +#[derive(Debug)] +pub struct Device { + fd: Fd, + cid: [u8; 4], + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(fd: Fd) -> io::Result<Self> { + Ok(Self { + fd, + cid: CID_BROADCAST, + dev_info: None, + }) + } + + pub fn is_u2f(&mut self) -> bool { + if !uhid::is_u2f_device(&self.fd) { + return false; + } + // This step is not strictly necessary -- NetBSD puts fido + // devices into raw mode automatically by default, but in + // principle that might change, and this serves as a test to + // verify that we're running on a kernel with support for raw + // mode at all so we don't get confused issuing writes that try + // to set the report descriptor rather than transfer data on + // the output interrupt pipe as we need. + match uhid::hid_set_raw(&self.fd, true) { + Ok(_) => (), + Err(_) => return false, + } + if let Err(_) = self.ping() { + return false; + } + true + } + + fn ping(&mut self) -> io::Result<()> { + for i in 0..10 { + let mut buf = vec![0u8; 1 + MAX_HID_RPT_SIZE]; + + buf[0] = 0; // report number + buf[1] = 0xff; // CID_BROADCAST + buf[2] = 0xff; + buf[3] = 0xff; + buf[4] = 0xff; + buf[5] = 0x81; // ping + buf[6] = 0; + buf[7] = 1; // one byte + + self.write(&buf[..])?; + + // Wait for response + let mut pfd: libc::pollfd = unsafe { mem::zeroed() }; + pfd.fd = self.fd.fileno; + pfd.events = libc::POLLIN; + let nfds = unsafe { libc::poll(&mut pfd, 1, 100) }; + if nfds == -1 { + return Err(io::Error::last_os_error()); + } + if nfds == 0 { + debug!("device timeout {}", i); + continue; + } + + // Read response + self.read(&mut buf[..])?; + + return Ok(()); + } + + Err(io_err("no response from device")) + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.fd == other.fd + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + let bufp = buf.as_mut_ptr() as *mut libc::c_void; + let nread = unsafe { libc::read(self.fd.fileno, bufp, buf.len()) }; + if nread == -1 { + return Err(io::Error::last_os_error()); + } + Ok(nread as usize) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + // Always skip the first byte (report number) + let data = &buf[1..]; + let data_ptr = data.as_ptr() as *const libc::c_void; + let nwrit = unsafe { libc::write(self.fd.fileno, data_ptr, data.len()) }; + if nwrit == -1 { + return Err(io::Error::last_os_error()); + } + // Pretend we wrote the report number byte + Ok(nwrit as usize + 1) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl U2FDevice for Device { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, _prop_name: &str) -> io::Result<String> { + Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/netbsd/fd.rs b/third_party/rust/authenticator/src/netbsd/fd.rs new file mode 100644 index 0000000000..c011b7fcc8 --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/fd.rs @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::ffi::CString; +use std::io; +use std::mem; +use std::os::raw::c_int; +use std::os::unix::io::RawFd; + +#[derive(Debug)] +pub struct Fd { + pub fileno: RawFd, +} + +impl Fd { + pub fn open(path: &str, flags: c_int) -> io::Result<Fd> { + let cpath = CString::new(path.as_bytes())?; + let rv = unsafe { libc::open(cpath.as_ptr(), flags) }; + if rv == -1 { + return Err(io::Error::last_os_error()); + } + Ok(Fd { fileno: rv }) + } +} + +impl Drop for Fd { + fn drop(&mut self) { + unsafe { libc::close(self.fileno) }; + } +} + +impl PartialEq for Fd { + fn eq(&self, other: &Fd) -> bool { + let mut st: libc::stat = unsafe { mem::zeroed() }; + let mut sto: libc::stat = unsafe { mem::zeroed() }; + if unsafe { libc::fstat(self.fileno, &mut st) } == -1 { + return false; + } + if unsafe { libc::fstat(other.fileno, &mut sto) } == -1 { + return false; + } + (st.st_dev == sto.st_dev) & (st.st_ino == sto.st_ino) + } +} diff --git a/third_party/rust/authenticator/src/netbsd/mod.rs b/third_party/rust/authenticator/src/netbsd/mod.rs new file mode 100644 index 0000000000..a0eabb6e06 --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/mod.rs @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod fd; +mod monitor; +mod uhid; diff --git a/third_party/rust/authenticator/src/netbsd/monitor.rs b/third_party/rust/authenticator/src/netbsd/monitor.rs new file mode 100644 index 0000000000..c78cff6ee1 --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/monitor.rs @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::ffi::OsString; +use std::io; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use runloop::RunLoop; + +use crate::platform::fd::Fd; + +// XXX Should use drvctl, but it doesn't do pubsub properly yet so +// DRVGETEVENT requires write access to /dev/drvctl. Instead, for now, +// just poll every 500ms. +const POLL_TIMEOUT: u64 = 500; + +pub struct Monitor<F> +where + F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + runloops: HashMap<OsString, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + while alive() { + for n in 0..100 { + let uhidpath = format!("/dev/uhid{}", n); + match Fd::open(&uhidpath, libc::O_RDWR | libc::O_CLOEXEC) { + Ok(uhid) => { + self.add_device(uhid, OsString::from(&uhidpath)); + } + Err(ref err) => match err.raw_os_error() { + Some(libc::EBUSY) => continue, + Some(libc::ENOENT) => break, + _ => self.remove_device(OsString::from(&uhidpath)), + }, + } + } + thread::sleep(Duration::from_millis(POLL_TIMEOUT)); + } + self.remove_all_devices(); + Ok(()) + } + + fn add_device(&mut self, fd: Fd, path: OsString) { + let f = self.new_device_cb.clone(); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(fd, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(path.clone(), runloop); + } + } + + fn remove_device(&mut self, path: OsString) { + if let Some(runloop) = self.runloops.remove(&path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(path); + } + } +} diff --git a/third_party/rust/authenticator/src/netbsd/transaction.rs b/third_party/rust/authenticator/src/netbsd/transaction.rs new file mode 100644 index 0000000000..21ac212569 --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/transaction.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::fd::Fd; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use runloop::RunLoop; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(Fd, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/netbsd/uhid.rs b/third_party/rust/authenticator/src/netbsd/uhid.rs new file mode 100644 index 0000000000..f8d711553d --- /dev/null +++ b/third_party/rust/authenticator/src/netbsd/uhid.rs @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::io; +use std::mem; +use std::os::raw::c_int; +use std::os::raw::c_uchar; + +use crate::hidproto::has_fido_usage; +use crate::hidproto::ReportDescriptor; +use crate::platform::fd::Fd; +use crate::util::io_err; + +/* sys/ioccom.h */ + +const IOCPARM_MASK: u32 = 0x1fff; +const IOCPARM_SHIFT: u32 = 16; +const IOCGROUP_SHIFT: u32 = 8; + +//const IOC_VOID: u32 = 0x20000000; +const IOC_OUT: u32 = 0x40000000; +const IOC_IN: u32 = 0x80000000; +//const IOC_INOUT: u32 = IOC_IN|IOC_OUT; + +macro_rules! ioctl { + ($dir:expr, $name:ident, $group:expr, $nr:expr, $ty:ty) => { + unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> { + let ioc = ($dir as u32) + | ((mem::size_of::<$ty>() as u32 & IOCPARM_MASK) << IOCPARM_SHIFT) + | (($group as u32) << IOCGROUP_SHIFT) + | ($nr as u32); + let rv = libc::ioctl(fd, ioc as libc::c_ulong, val); + if rv == -1 { + return Err(io::Error::last_os_error()); + } + Ok(rv) + } + }; +} + +#[allow(non_camel_case_types)] +#[repr(C)] +struct usb_ctl_report_desc { + ucrd_size: c_int, + ucrd_data: [c_uchar; 1024], +} + +ioctl!(IOC_OUT, usb_get_report_desc, b'U', 21, usb_ctl_report_desc); + +fn read_report_descriptor(fd: &Fd) -> io::Result<ReportDescriptor> { + let mut desc = unsafe { mem::zeroed() }; + unsafe { usb_get_report_desc(fd.fileno, &mut desc) }?; + if desc.ucrd_size < 0 { + return Err(io_err("negative report descriptor size")); + } + let size = desc.ucrd_size as usize; + let value = Vec::from(&desc.ucrd_data[..size]); + Ok(ReportDescriptor { value }) +} + +pub fn is_u2f_device(fd: &Fd) -> bool { + match read_report_descriptor(fd) { + Ok(desc) => has_fido_usage(desc), + Err(_) => false, + } +} + +ioctl!(IOC_IN, usb_hid_set_raw_ioctl, b'h', 2, c_int); + +pub fn hid_set_raw(fd: &Fd, raw: bool) -> io::Result<()> { + let mut raw_int: c_int = if raw { 1 } else { 0 }; + unsafe { usb_hid_set_raw_ioctl(fd.fileno, &mut raw_int) }?; + Ok(()) +} diff --git a/third_party/rust/authenticator/src/openbsd/device.rs b/third_party/rust/authenticator/src/openbsd/device.rs new file mode 100644 index 0000000000..2238e034e2 --- /dev/null +++ b/third_party/rust/authenticator/src/openbsd/device.rs @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::ffi::OsString; +use std::io; +use std::io::{Read, Result, Write}; +use std::mem; + +use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; +use crate::platform::monitor::FidoDev; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use crate::util::{from_unix_result, io_err}; + +#[derive(Debug)] +pub struct Device { + path: OsString, + fd: libc::c_int, + cid: [u8; 4], + out_len: usize, + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(fido: FidoDev) -> Result<Self> { + debug!("device found: {:?}", fido); + Ok(Self { + path: fido.os_path, + fd: fido.fd, + cid: CID_BROADCAST, + out_len: 64, + dev_info: None, + }) + } + + pub fn is_u2f(&mut self) -> bool { + debug!("device {:?} is U2F/FIDO", self.path); + + // From OpenBSD's libfido2 in 6.6-current: + // "OpenBSD (as of 201910) has a bug that causes it to lose + // track of the DATA0/DATA1 sequence toggle across uhid device + // open and close. This is a terrible hack to work around it." + match self.ping() { + Ok(_) => true, + Err(err) => { + debug!("device {:?} is not responding: {}", self.path, err); + false + } + } + } + + fn ping(&mut self) -> Result<()> { + let capacity = 256; + + for _ in 0..10 { + let mut data = vec![0u8; capacity]; + + // Send 1 byte ping + self.write_all(&[0, 0xff, 0xff, 0xff, 0xff, 0x81, 0, 1])?; + + // Wait for response + let mut pfd: libc::pollfd = unsafe { mem::zeroed() }; + pfd.fd = self.fd; + pfd.events = libc::POLLIN; + if from_unix_result(unsafe { libc::poll(&mut pfd, 1, 100) })? == 0 { + debug!("device {:?} timeout", self.path); + continue; + } + + // Read response + self.read(&mut data[..])?; + + return Ok(()); + } + + Err(io_err("no response from device")) + } +} + +impl Drop for Device { + fn drop(&mut self) { + // Close the fd, ignore any errors. + let _ = unsafe { libc::close(self.fd) }; + debug!("device {:?} closed", self.path); + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> Result<usize> { + let buf_ptr = buf.as_mut_ptr() as *mut libc::c_void; + let rv = unsafe { libc::read(self.fd, buf_ptr, buf.len()) }; + from_unix_result(rv as usize) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> Result<usize> { + // Always skip the first byte (report number) + let data = &buf[1..]; + let data_ptr = data.as_ptr() as *const libc::c_void; + let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) }; + Ok(from_unix_result(rv as usize)? + 1) + } + + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} + +impl U2FDevice for Device { + fn get_cid(&self) -> &[u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, _prop_name: &str) -> io::Result<String> { + Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/openbsd/mod.rs b/third_party/rust/authenticator/src/openbsd/mod.rs new file mode 100644 index 0000000000..fa02132e67 --- /dev/null +++ b/third_party/rust/authenticator/src/openbsd/mod.rs @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod monitor; diff --git a/third_party/rust/authenticator/src/openbsd/monitor.rs b/third_party/rust/authenticator/src/openbsd/monitor.rs new file mode 100644 index 0000000000..2f3930497f --- /dev/null +++ b/third_party/rust/authenticator/src/openbsd/monitor.rs @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::ffi::{CString, OsString}; +use std::io; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::RawFd; +use std::path::PathBuf; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use crate::util::from_unix_result; +use runloop::RunLoop; + +const POLL_TIMEOUT: u64 = 500; + +#[derive(Debug)] +pub struct FidoDev { + pub fd: RawFd, + pub os_path: OsString, +} + +pub struct Monitor<F> +where + F: Fn(FidoDev, &dyn Fn() -> bool) + Sync, +{ + runloops: HashMap<OsString, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(FidoDev, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + // Loop until we're stopped by the controlling thread, or fail. + while alive() { + // Iterate the first 10 fido(4) devices. + for path in (0..10) + .map(|unit| PathBuf::from(&format!("/dev/fido/{}", unit))) + .filter(|path| path.exists()) + { + let os_path = path.as_os_str().to_os_string(); + let cstr = CString::new(os_path.as_bytes())?; + + // Try to open the device. + let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) }; + match from_unix_result(fd) { + Ok(fd) => { + // The device is available if it can be opened. + self.add_device(FidoDev { fd, os_path }); + } + Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => { + // The device is available but currently in use. + } + _ => { + // libc::ENODEV or any other error. + self.remove_device(os_path); + } + } + } + + thread::sleep(Duration::from_millis(POLL_TIMEOUT)); + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn add_device(&mut self, fido: FidoDev) { + if !self.runloops.contains_key(&fido.os_path) { + let f = self.new_device_cb.clone(); + let key = fido.os_path.clone(); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(fido, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + } + + fn remove_device(&mut self, path: OsString) { + if let Some(runloop) = self.runloops.remove(&path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(path); + } + } +} diff --git a/third_party/rust/authenticator/src/openbsd/transaction.rs b/third_party/rust/authenticator/src/openbsd/transaction.rs new file mode 100644 index 0000000000..4b85db2785 --- /dev/null +++ b/third_party/rust/authenticator/src/openbsd/transaction.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::monitor::{FidoDev, Monitor}; +use crate::statecallback::StateCallback; +use runloop::RunLoop; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(FidoDev, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/statecallback.rs b/third_party/rust/authenticator/src/statecallback.rs new file mode 100644 index 0000000000..ce1caf3e7c --- /dev/null +++ b/third_party/rust/authenticator/src/statecallback.rs @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::sync::{Arc, Condvar, Mutex}; + +pub struct StateCallback<T> { + callback: Arc<Mutex<Option<Box<dyn Fn(T) + Send>>>>, + observer: Arc<Mutex<Option<Box<dyn Fn() + Send>>>>, + condition: Arc<(Mutex<bool>, Condvar)>, +} + +impl<T> StateCallback<T> { + // This is used for the Condvar, which requires this kind of construction + #[allow(clippy::mutex_atomic)] + pub fn new(cb: Box<dyn Fn(T) + Send>) -> Self { + Self { + callback: Arc::new(Mutex::new(Some(cb))), + observer: Arc::new(Mutex::new(None)), + condition: Arc::new((Mutex::new(true), Condvar::new())), + } + } + + pub fn add_uncloneable_observer(&mut self, obs: Box<dyn Fn() + Send>) { + let mut opt = self.observer.lock().unwrap(); + if opt.is_some() { + error!("Replacing an already-set observer.") + } + opt.replace(obs); + } + + pub fn call(&self, rv: T) { + if let Some(cb) = self.callback.lock().unwrap().take() { + cb(rv); + + if let Some(obs) = self.observer.lock().unwrap().take() { + obs(); + } + } + + let (lock, cvar) = &*self.condition; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_all(); + } + + pub fn wait(&self) { + let (lock, cvar) = &*self.condition; + let _useless_guard = cvar + .wait_while(lock.lock().unwrap(), |pending| *pending) + .unwrap(); + } +} + +impl<T> Clone for StateCallback<T> { + fn clone(&self) -> Self { + Self { + callback: self.callback.clone(), + observer: Arc::new(Mutex::new(None)), + condition: self.condition.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::StateCallback; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Arc, Barrier}; + use std::thread; + + #[test] + fn test_statecallback_is_single_use() { + let counter = Arc::new(AtomicUsize::new(0)); + let counter_clone = counter.clone(); + let sc = StateCallback::new(Box::new(move |_| { + counter_clone.fetch_add(1, Ordering::SeqCst); + })); + + assert_eq!(counter.load(Ordering::SeqCst), 0); + for _ in 0..10 { + sc.call(()); + assert_eq!(counter.load(Ordering::SeqCst), 1); + } + + for _ in 0..10 { + sc.clone().call(()); + assert_eq!(counter.load(Ordering::SeqCst), 1); + } + } + + #[test] + fn test_statecallback_observer_is_single_use() { + let counter = Arc::new(AtomicUsize::new(0)); + let counter_clone = counter.clone(); + let mut sc = StateCallback::<()>::new(Box::new(move |_| {})); + + sc.add_uncloneable_observer(Box::new(move || { + counter_clone.fetch_add(1, Ordering::SeqCst); + })); + + assert_eq!(counter.load(Ordering::SeqCst), 0); + for _ in 0..10 { + sc.call(()); + assert_eq!(counter.load(Ordering::SeqCst), 1); + } + + for _ in 0..10 { + sc.clone().call(()); + assert_eq!(counter.load(Ordering::SeqCst), 1); + } + } + + #[test] + fn test_statecallback_observer_only_runs_for_completing_callback() { + let cb_counter = Arc::new(AtomicUsize::new(0)); + let cb_counter_clone = cb_counter.clone(); + let sc = StateCallback::new(Box::new(move |_| { + cb_counter_clone.fetch_add(1, Ordering::SeqCst); + })); + + let obs_counter = Arc::new(AtomicUsize::new(0)); + + for _ in 0..10 { + let obs_counter_clone = obs_counter.clone(); + let mut c = sc.clone(); + c.add_uncloneable_observer(Box::new(move || { + obs_counter_clone.fetch_add(1, Ordering::SeqCst); + })); + + c.call(()); + + assert_eq!(cb_counter.load(Ordering::SeqCst), 1); + assert_eq!(obs_counter.load(Ordering::SeqCst), 1); + } + } + + #[test] + #[allow(clippy::redundant_clone)] + fn test_statecallback_observer_unclonable() { + let mut sc = StateCallback::<()>::new(Box::new(move |_| {})); + sc.add_uncloneable_observer(Box::new(move || {})); + + assert!(sc.observer.lock().unwrap().is_some()); + // This is deliberate, to force an extra clone + assert!(sc.clone().observer.lock().unwrap().is_none()); + } + + #[test] + fn test_statecallback_wait() { + let sc = StateCallback::<()>::new(Box::new(move |_| {})); + let barrier = Arc::new(Barrier::new(2)); + + { + let c = sc.clone(); + let b = barrier.clone(); + thread::spawn(move || { + b.wait(); + c.call(()); + }); + } + + barrier.wait(); + sc.wait(); + } +} diff --git a/third_party/rust/authenticator/src/statemachine.rs b/third_party/rust/authenticator/src/statemachine.rs new file mode 100644 index 0000000000..32552f8d0a --- /dev/null +++ b/third_party/rust/authenticator/src/statemachine.rs @@ -0,0 +1,283 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::consts::PARAMETER_SIZE; +use crate::errors; +use crate::platform::device::Device; +use crate::platform::transaction::Transaction; +use crate::statecallback::StateCallback; +use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign}; +use crate::u2ftypes::U2FDevice; + +use std::sync::mpsc::Sender; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; + +fn is_valid_transport(transports: crate::AuthenticatorTransports) -> bool { + transports.is_empty() || transports.contains(crate::AuthenticatorTransports::USB) +} + +fn find_valid_key_handles<'a, F>( + app_ids: &'a [crate::AppId], + key_handles: &'a [crate::KeyHandle], + mut is_valid: F, +) -> (&'a crate::AppId, Vec<&'a crate::KeyHandle>) +where + F: FnMut(&Vec<u8>, &crate::KeyHandle) -> bool, +{ + // Try all given app_ids in order. + for app_id in app_ids { + // Find all valid key handles for the current app_id. + let valid_handles = key_handles + .iter() + .filter(|key_handle| is_valid(app_id, key_handle)) + .collect::<Vec<_>>(); + + // If there's at least one, stop. + if !valid_handles.is_empty() { + return (app_id, valid_handles); + } + } + + (&app_ids[0], vec![]) +} + +fn send_status(status_mutex: &Mutex<Sender<crate::StatusUpdate>>, msg: crate::StatusUpdate) { + match status_mutex.lock() { + Ok(s) => match s.send(msg) { + Ok(_) => {} + Err(e) => error!("Couldn't send status: {:?}", e), + }, + Err(e) => { + error!("Couldn't obtain status mutex: {:?}", e); + } + }; +} + +#[derive(Default)] +pub struct StateMachine { + transaction: Option<Transaction>, +} + +impl StateMachine { + pub fn new() -> Self { + Default::default() + } + + pub fn register( + &mut self, + flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec<u8>, + application: crate::AppId, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + + let cbc = callback.clone(); + let status_mutex = Mutex::new(status); + + let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| { + // Create a new device. + let dev = &mut match Device::new(info) { + Ok(dev) => dev, + _ => return, + }; + + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; + } + + // We currently support none of the authenticator selection + // criteria because we can't ask tokens whether they do support + // those features. If flags are set, ignore all tokens for now. + // + // Technically, this is a ConstraintError because we shouldn't talk + // to this authenticator in the first place. But the result is the + // same anyway. + if !flags.is_empty() { + return; + } + + send_status( + &status_mutex, + crate::StatusUpdate::DeviceAvailable { + dev_info: dev.get_device_info(), + }, + ); + + // Iterate the exclude list and see if there are any matches. + // If so, we'll keep polling the device anyway to test for user + // consent, to be consistent with CTAP2 device behavior. + let excluded = key_handles.iter().any(|key_handle| { + is_valid_transport(key_handle.transports) + && u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential) + .unwrap_or(false) /* no match on failure */ + }); + + while alive() { + if excluded { + let blank = vec![0u8; PARAMETER_SIZE]; + if u2f_register(dev, &blank, &blank).is_ok() { + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::InvalidState, + ))); + break; + } + } else if let Ok(bytes) = u2f_register(dev, &challenge, &application) { + let dev_info = dev.get_device_info(); + send_status( + &status_mutex, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + callback.call(Ok((bytes, dev_info))); + break; + } + + // Sleep a bit before trying again. + thread::sleep(Duration::from_millis(100)); + } + + send_status( + &status_mutex, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); + }); + + self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); + } + + pub fn sign( + &mut self, + flags: crate::SignFlags, + timeout: u64, + challenge: Vec<u8>, + app_ids: Vec<crate::AppId>, + key_handles: Vec<crate::KeyHandle>, + status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + + let cbc = callback.clone(); + + let status_mutex = Mutex::new(status); + + let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| { + // Create a new device. + let dev = &mut match Device::new(info) { + Ok(dev) => dev, + _ => return, + }; + + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; + } + + // We currently don't support user verification because we can't + // ask tokens whether they do support that. If the flag is set, + // ignore all tokens for now. + // + // Technically, this is a ConstraintError because we shouldn't talk + // to this authenticator in the first place. But the result is the + // same anyway. + if !flags.is_empty() { + return; + } + + // For each appId, try all key handles. If there's at least one + // valid key handle for an appId, we'll use that appId below. + let (app_id, valid_handles) = + find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| { + u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential) + .unwrap_or(false) /* no match on failure */ + }); + + // Aggregate distinct transports from all given credentials. + let transports = key_handles + .iter() + .fold(crate::AuthenticatorTransports::empty(), |t, k| { + t | k.transports + }); + + // We currently only support USB. If the RP specifies transports + // and doesn't include USB it's probably lying. + if !is_valid_transport(transports) { + return; + } + + send_status( + &status_mutex, + crate::StatusUpdate::DeviceAvailable { + dev_info: dev.get_device_info(), + }, + ); + + 'outer: while alive() { + // If the device matches none of the given key handles + // then just make it blink with bogus data. + if valid_handles.is_empty() { + let blank = vec![0u8; PARAMETER_SIZE]; + if u2f_register(dev, &blank, &blank).is_ok() { + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::InvalidState, + ))); + break; + } + } else { + // Otherwise, try to sign. + for key_handle in &valid_handles { + if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential) + { + let dev_info = dev.get_device_info(); + send_status( + &status_mutex, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + callback.call(Ok(( + app_id.clone(), + key_handle.credential.clone(), + bytes, + dev_info, + ))); + break 'outer; + } + } + } + + // Sleep a bit before trying again. + thread::sleep(Duration::from_millis(100)); + } + + send_status( + &status_mutex, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); + }); + + self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); + } + + // This blocks. + pub fn cancel(&mut self) { + if let Some(mut transaction) = self.transaction.take() { + transaction.cancel(); + } + } +} diff --git a/third_party/rust/authenticator/src/stub/device.rs b/third_party/rust/authenticator/src/stub/device.rs new file mode 100644 index 0000000000..283da8ed66 --- /dev/null +++ b/third_party/rust/authenticator/src/stub/device.rs @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use std::io; +use std::io::{Read, Write}; + +pub struct Device {} + +impl Device { + pub fn new(path: String) -> io::Result<Self> { + panic!("not implemented"); + } + + pub fn is_u2f(&self) -> bool { + panic!("not implemented"); + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + panic!("not implemented"); + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + panic!("not implemented"); + } + + fn flush(&mut self) -> io::Result<()> { + panic!("not implemented"); + } +} + +impl U2FDevice for Device { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + panic!("not implemented"); + } + + fn set_cid(&mut self, cid: [u8; 4]) { + panic!("not implemented"); + } + + fn in_rpt_size(&self) -> usize { + panic!("not implemented"); + } + + fn out_rpt_size(&self) -> usize { + panic!("not implemented"); + } + + fn get_property(&self, prop_name: &str) -> io::Result<String> { + panic!("not implemented") + } + + fn get_device_info(&self) -> U2FDeviceInfo { + panic!("not implemented") + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + panic!("not implemented") + } +} diff --git a/third_party/rust/authenticator/src/stub/mod.rs b/third_party/rust/authenticator/src/stub/mod.rs new file mode 100644 index 0000000000..0fab62d495 --- /dev/null +++ b/third_party/rust/authenticator/src/stub/mod.rs @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// No-op module to permit compiling token HID support for Android, where +// no results are returned. + +#![allow(unused_variables)] + +pub mod device; +pub mod transaction; diff --git a/third_party/rust/authenticator/src/stub/transaction.rs b/third_party/rust/authenticator/src/stub/transaction.rs new file mode 100644 index 0000000000..bdf48ef56d --- /dev/null +++ b/third_party/rust/authenticator/src/stub/transaction.rs @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::statecallback::StateCallback; + +pub struct Transaction {} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(String, &dyn Fn() -> bool), + { + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotSupported, + ))); + + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotSupported, + )) + } + + pub fn cancel(&mut self) { + /* No-op. */ + } +} diff --git a/third_party/rust/authenticator/src/u2fhid-capi.h b/third_party/rust/authenticator/src/u2fhid-capi.h new file mode 100644 index 0000000000..bb81b28f5f --- /dev/null +++ b/third_party/rust/authenticator/src/u2fhid-capi.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __U2FHID_CAPI +#define __U2FHID_CAPI +#include <stdlib.h> +#include "nsString.h" + +extern "C" { + +const uint8_t U2F_RESBUF_ID_REGISTRATION = 0; +const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1; +const uint8_t U2F_RESBUF_ID_SIGNATURE = 2; +const uint8_t U2F_RESBUF_ID_APPID = 3; +const uint8_t U2F_RESBUF_ID_VENDOR_NAME = 4; +const uint8_t U2F_RESBUF_ID_DEVICE_NAME = 5; +const uint8_t U2F_RESBUF_ID_FIRMWARE_MAJOR = 6; +const uint8_t U2F_RESBUF_ID_FIRMWARE_MINOR = 7; +const uint8_t U2F_RESBUF_ID_FIRMWARE_BUILD = 8; + +const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1; +const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2; +const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4; + +const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1; +const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2; +const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4; +const uint8_t CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL = 8; + +const uint8_t U2F_ERROR_UKNOWN = 1; +const uint8_t U2F_ERROR_NOT_SUPPORTED = 2; +const uint8_t U2F_ERROR_INVALID_STATE = 3; +const uint8_t U2F_ERROR_CONSTRAINT = 4; +const uint8_t U2F_ERROR_NOT_ALLOWED = 5; + +// NOTE: Preconditions +// * All rust_u2f_mgr* pointers must refer to pointers which are returned +// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free. +// * All rust_u2f_khs* pointers must refer to pointers which are returned +// by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free. +// * All rust_u2f_res* pointers must refer to pointers passed to the +// register() and sign() callbacks. They can be null on failure. + +// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager` +struct rust_u2f_manager; + +// The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds` +struct rust_u2f_app_ids; + +// The `rust_u2f_key_handles` opaque type is equivalent to the rust type +// `U2FKeyHandles` +struct rust_u2f_key_handles; + +// The `rust_u2f_res` opaque type is equivalent to the rust type `U2FResult` +struct rust_u2f_result; + +// The callback passed to register() and sign(). +typedef void (*rust_u2f_callback)(uint64_t, rust_u2f_result*); + +/// U2FManager functions. + +rust_u2f_manager* rust_u2f_mgr_new(); +/* unsafe */ void rust_u2f_mgr_free(rust_u2f_manager* mgr); + +uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags, + uint64_t timeout, rust_u2f_callback, + const uint8_t* challenge_ptr, + size_t challenge_len, + const uint8_t* application_ptr, + size_t application_len, + const rust_u2f_key_handles* khs); + +uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr, uint64_t flags, + uint64_t timeout, rust_u2f_callback, + const uint8_t* challenge_ptr, size_t challenge_len, + const rust_u2f_app_ids* app_ids, + const rust_u2f_key_handles* khs); + +void rust_u2f_mgr_cancel(rust_u2f_manager* mgr); + +/// U2FAppIds functions. + +rust_u2f_app_ids* rust_u2f_app_ids_new(); +void rust_u2f_app_ids_add(rust_u2f_app_ids* ids, const uint8_t* id, + size_t id_len); +/* unsafe */ void rust_u2f_app_ids_free(rust_u2f_app_ids* ids); + +/// U2FKeyHandles functions. + +rust_u2f_key_handles* rust_u2f_khs_new(); +void rust_u2f_khs_add(rust_u2f_key_handles* khs, const uint8_t* key_handle, + size_t key_handle_len, uint8_t transports); +/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs); + +/// U2FResult functions. + +// Returns 0 for success, or the U2F_ERROR error code >= 1. +uint8_t rust_u2f_result_error(const rust_u2f_result* res); + +// Call this before `[..]_copy()` to allocate enough space. +bool rust_u2f_resbuf_length(const rust_u2f_result* res, uint8_t bid, + size_t* len); +bool rust_u2f_resbuf_copy(const rust_u2f_result* res, uint8_t bid, + uint8_t* dst); +/* unsafe */ void rust_u2f_res_free(rust_u2f_result* res); +} + +#endif // __U2FHID_CAPI diff --git a/third_party/rust/authenticator/src/u2fprotocol.rs b/third_party/rust/authenticator/src/u2fprotocol.rs new file mode 100644 index 0000000000..ca9f704387 --- /dev/null +++ b/third_party/rust/authenticator/src/u2fprotocol.rs @@ -0,0 +1,457 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![cfg_attr(feature = "cargo-clippy", allow(clippy::needless_lifetimes))] + +extern crate std; + +use rand::{thread_rng, RngCore}; +use std::ffi::CString; +use std::io; +use std::io::{Read, Write}; + +use crate::consts::*; +use crate::u2ftypes::*; +use crate::util::io_err; + +//////////////////////////////////////////////////////////////////////// +// Device Commands +//////////////////////////////////////////////////////////////////////// + +pub fn u2f_init_device<T>(dev: &mut T) -> bool +where + T: U2FDevice + Read + Write, +{ + let mut nonce = [0u8; 8]; + thread_rng().fill_bytes(&mut nonce); + + // Initialize the device and check its version. + init_device(dev, &nonce).is_ok() && is_v2_device(dev).unwrap_or(false) +} + +pub fn u2f_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>> +where + T: U2FDevice + Read + Write, +{ + if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid parameter sizes", + )); + } + + let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE); + register_data.extend(challenge); + register_data.extend(application); + + let flags = U2F_REQUEST_USER_PRESENCE; + let (resp, status) = send_apdu(dev, U2F_REGISTER, flags, ®ister_data)?; + status_word_to_result(status, resp) +} + +pub fn u2f_sign<T>( + dev: &mut T, + challenge: &[u8], + application: &[u8], + key_handle: &[u8], +) -> io::Result<Vec<u8>> +where + T: U2FDevice + Read + Write, +{ + if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid parameter sizes", + )); + } + + if key_handle.len() > 256 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Key handle too large", + )); + } + + let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len()); + sign_data.extend(challenge); + sign_data.extend(application); + sign_data.push(key_handle.len() as u8); + sign_data.extend(key_handle); + + let flags = U2F_REQUEST_USER_PRESENCE; + let (resp, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?; + status_word_to_result(status, resp) +} + +pub fn u2f_is_keyhandle_valid<T>( + dev: &mut T, + challenge: &[u8], + application: &[u8], + key_handle: &[u8], +) -> io::Result<bool> +where + T: U2FDevice + Read + Write, +{ + if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid parameter sizes", + )); + } + + if key_handle.len() > 256 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Key handle too large", + )); + } + + let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len()); + sign_data.extend(challenge); + sign_data.extend(application); + sign_data.push(key_handle.len() as u8); + sign_data.extend(key_handle); + + let flags = U2F_CHECK_IS_REGISTERED; + let (_, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?; + Ok(status == SW_CONDITIONS_NOT_SATISFIED) +} + +//////////////////////////////////////////////////////////////////////// +// Internal Device Commands +//////////////////////////////////////////////////////////////////////// + +fn init_device<T>(dev: &mut T, nonce: &[u8]) -> io::Result<()> +where + T: U2FDevice + Read + Write, +{ + assert_eq!(nonce.len(), INIT_NONCE_SIZE); + let raw = sendrecv(dev, U2FHID_INIT, nonce)?; + let rsp = U2FHIDInitResp::read(&raw, nonce)?; + dev.set_cid(rsp.cid); + + let vendor = dev + .get_property("Manufacturer") + .unwrap_or_else(|_| String::from("Unknown Vendor")); + let product = dev + .get_property("Product") + .unwrap_or_else(|_| String::from("Unknown Device")); + + dev.set_device_info(U2FDeviceInfo { + vendor_name: vendor.as_bytes().to_vec(), + device_name: product.as_bytes().to_vec(), + version_interface: rsp.version_interface, + version_major: rsp.version_major, + version_minor: rsp.version_minor, + version_build: rsp.version_build, + cap_flags: rsp.cap_flags, + }); + + Ok(()) +} + +fn is_v2_device<T>(dev: &mut T) -> io::Result<bool> +where + T: U2FDevice + Read + Write, +{ + let (data, status) = send_apdu(dev, U2F_VERSION, 0x00, &[])?; + let actual = CString::new(data)?; + let expected = CString::new("U2F_V2")?; + status_word_to_result(status, actual == expected) +} + +//////////////////////////////////////////////////////////////////////// +// Error Handling +//////////////////////////////////////////////////////////////////////// + +fn status_word_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> { + use self::io::ErrorKind::{InvalidData, InvalidInput}; + + match status { + SW_NO_ERROR => Ok(val), + SW_WRONG_DATA => Err(io::Error::new(InvalidData, "wrong data")), + SW_WRONG_LENGTH => Err(io::Error::new(InvalidInput, "wrong length")), + SW_CONDITIONS_NOT_SATISFIED => Err(io_err("conditions not satisfied")), + _ => Err(io_err(&format!("failed with status {:?}", status))), + } +} + +//////////////////////////////////////////////////////////////////////// +// Device Communication Functions +//////////////////////////////////////////////////////////////////////// + +pub fn sendrecv<T>(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result<Vec<u8>> +where + T: U2FDevice + Read + Write, +{ + // Send initialization packet. + let mut count = U2FHIDInit::write(dev, cmd, send)?; + + // Send continuation packets. + let mut sequence = 0u8; + while count < send.len() { + count += U2FHIDCont::write(dev, sequence, &send[count..])?; + sequence += 1; + } + + // Now we read. This happens in 2 chunks: The initial packet, which has the + // size we expect overall, then continuation packets, which will fill in + // data until we have everything. + let mut data = U2FHIDInit::read(dev)?; + + let mut sequence = 0u8; + while data.len() < data.capacity() { + let max = data.capacity() - data.len(); + data.extend_from_slice(&U2FHIDCont::read(dev, sequence, max)?); + sequence += 1; + } + + Ok(data) +} + +fn send_apdu<T>(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])> +where + T: U2FDevice + Read + Write, +{ + let apdu = U2FAPDUHeader::serialize(cmd, p1, send)?; + let mut data = sendrecv(dev, U2FHID_MSG, &apdu)?; + + if data.len() < 2 { + return Err(io_err("unexpected response")); + } + + let split_at = data.len() - 2; + let status = data.split_off(split_at); + Ok((data, [status[0], status[1]])) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use rand::{thread_rng, RngCore}; + + use super::{init_device, send_apdu, sendrecv, U2FDevice}; + use crate::consts::{CID_BROADCAST, SW_NO_ERROR, U2FHID_INIT, U2FHID_MSG, U2FHID_PING}; + + mod platform { + use std::io; + use std::io::{Read, Write}; + + use crate::consts::CID_BROADCAST; + use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; + + const IN_HID_RPT_SIZE: usize = 64; + const OUT_HID_RPT_SIZE: usize = 64; + + pub struct TestDevice { + cid: [u8; 4], + reads: Vec<[u8; IN_HID_RPT_SIZE]>, + writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>, + dev_info: Option<U2FDeviceInfo>, + } + + impl TestDevice { + pub fn new() -> TestDevice { + TestDevice { + cid: CID_BROADCAST, + reads: vec![], + writes: vec![], + dev_info: None, + } + } + + pub fn add_write(&mut self, packet: &[u8], fill_value: u8) { + // Add one to deal with record index check + let mut write = [fill_value; OUT_HID_RPT_SIZE + 1]; + // Make sure we start with a 0, for HID record index + write[0] = 0; + // Clone packet data in at 1, since front is padded with HID record index + write[1..=packet.len()].clone_from_slice(packet); + self.writes.push(write); + } + + pub fn add_read(&mut self, packet: &[u8], fill_value: u8) { + let mut read = [fill_value; IN_HID_RPT_SIZE]; + read[..packet.len()].clone_from_slice(packet); + self.reads.push(read); + } + } + + impl Write for TestDevice { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + // Pop a vector from the expected writes, check for quality + // against bytes array. + assert!(!self.writes.is_empty(), "Ran out of expected write values!"); + let check = self.writes.remove(0); + assert_eq!(check.len(), bytes.len()); + assert_eq!(&check[..], bytes); + Ok(bytes.len()) + } + + // nop + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl Read for TestDevice { + fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> { + assert!(!self.reads.is_empty(), "Ran out of read values!"); + let check = self.reads.remove(0); + assert_eq!(check.len(), bytes.len()); + bytes.clone_from_slice(&check[..]); + Ok(check.len()) + } + } + + impl Drop for TestDevice { + fn drop(&mut self) { + assert!(self.reads.is_empty()); + assert!(self.writes.is_empty()); + } + } + + impl U2FDevice for TestDevice { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + IN_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + OUT_HID_RPT_SIZE + } + + fn get_property(&self, prop_name: &str) -> io::Result<String> { + Ok(format!("{} not implemented", prop_name)) + } + fn get_device_info(&self) -> U2FDeviceInfo { + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } + } + } + + #[test] + fn test_init_device() { + let mut device = platform::TestDevice::new(); + let nonce = vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]; + + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + // init packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(vec![U2FHID_INIT, 0x00, 0x08]); // cmd + bcnt + msg.extend_from_slice(&nonce); + device.add_write(&msg, 0); + + // init_resp packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(vec![U2FHID_INIT, 0x00, 0x11]); // cmd + bcnt + msg.extend_from_slice(&nonce); + msg.extend_from_slice(&cid); // new channel id + msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags + device.add_read(&msg, 0); + + init_device(&mut device, &nonce).unwrap(); + assert_eq!(device.get_cid(), &cid); + + let dev_info = device.get_device_info(); + assert_eq!(dev_info.version_interface, 0x02); + assert_eq!(dev_info.version_major, 0x04); + assert_eq!(dev_info.version_minor, 0x01); + assert_eq!(dev_info.version_build, 0x08); + assert_eq!(dev_info.cap_flags, 0x01); + } + + #[test] + fn test_sendrecv_multiple() { + let mut device = platform::TestDevice::new(); + let cid = [0x01, 0x02, 0x03, 0x04]; + device.set_cid(cid); + + // init packet + let mut msg = cid.to_vec(); + msg.extend(vec![U2FHID_PING, 0x00, 0xe4]); // cmd + length = 228 + // write msg, append [1u8; 57], 171 bytes remain + device.add_write(&msg, 1); + device.add_read(&msg, 1); + + // cont packet + let mut msg = cid.to_vec(); + msg.push(0x00); // seq = 0 + // write msg, append [1u8; 59], 112 bytes remaining + device.add_write(&msg, 1); + device.add_read(&msg, 1); + + // cont packet + let mut msg = cid.to_vec(); + msg.push(0x01); // seq = 1 + // write msg, append [1u8; 59], 53 bytes remaining + device.add_write(&msg, 1); + device.add_read(&msg, 1); + + // cont packet + let mut msg = cid.to_vec(); + msg.push(0x02); // seq = 2 + msg.extend_from_slice(&[1u8; 53]); + // write msg, append remaining 53 bytes. + device.add_write(&msg, 0); + device.add_read(&msg, 0); + + let data = [1u8; 228]; + let d = sendrecv(&mut device, U2FHID_PING, &data).unwrap(); + assert_eq!(d.len(), 228); + assert_eq!(d, &data[..]); + } + + #[test] + fn test_sendapdu() { + let cid = [0x01, 0x02, 0x03, 0x04]; + let data = [0x01, 0x02, 0x03, 0x04, 0x05]; + let mut device = platform::TestDevice::new(); + device.set_cid(cid); + + let mut msg = cid.to_vec(); + // sendrecv header + msg.extend(vec![U2FHID_MSG, 0x00, 0x0e]); // len = 14 + // apdu header + msg.extend(vec![0x00, U2FHID_PING, 0xaa, 0x00, 0x00, 0x00, 0x05]); + // apdu data + msg.extend_from_slice(&data); + device.add_write(&msg, 0); + + // Send data back + let mut msg = cid.to_vec(); + msg.extend(vec![U2FHID_MSG, 0x00, 0x07]); + msg.extend_from_slice(&data); + msg.extend_from_slice(&SW_NO_ERROR); + device.add_read(&msg, 0); + + let (result, status) = send_apdu(&mut device, U2FHID_PING, 0xaa, &data).unwrap(); + assert_eq!(result, &data); + assert_eq!(status, SW_NO_ERROR); + } + + #[test] + fn test_get_property() { + let device = platform::TestDevice::new(); + + assert_eq!(device.get_property("a").unwrap(), "a not implemented"); + } +} diff --git a/third_party/rust/authenticator/src/u2ftypes.rs b/third_party/rust/authenticator/src/u2ftypes.rs new file mode 100644 index 0000000000..8360e8adbd --- /dev/null +++ b/third_party/rust/authenticator/src/u2ftypes.rs @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{cmp, fmt, io, str}; + +use crate::consts::*; +use crate::util::io_err; + +pub fn to_hex(data: &[u8], joiner: &str) -> String { + let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect(); + parts.join(joiner) +} + +pub fn trace_hex(data: &[u8]) { + if log_enabled!(log::Level::Trace) { + trace!("USB send: {}", to_hex(data, "")); + } +} + +// Trait for representing U2F HID Devices. Requires getters/setters for the +// channel ID, created during device initialization. +pub trait U2FDevice { + fn get_cid(&self) -> &[u8; 4]; + fn set_cid(&mut self, cid: [u8; 4]); + + fn in_rpt_size(&self) -> usize; + fn in_init_data_size(&self) -> usize { + self.in_rpt_size() - INIT_HEADER_SIZE + } + fn in_cont_data_size(&self) -> usize { + self.in_rpt_size() - CONT_HEADER_SIZE + } + + fn out_rpt_size(&self) -> usize; + fn out_init_data_size(&self) -> usize { + self.out_rpt_size() - INIT_HEADER_SIZE + } + fn out_cont_data_size(&self) -> usize { + self.out_rpt_size() - CONT_HEADER_SIZE + } + + fn get_property(&self, prop_name: &str) -> io::Result<String>; + fn get_device_info(&self) -> U2FDeviceInfo; + fn set_device_info(&mut self, dev_info: U2FDeviceInfo); +} + +// Init structure for U2F Communications. Tells the receiver what channel +// communication is happening on, what command is running, and how much data to +// expect to receive over all. +// +// Spec at https://fidoalliance.org/specs/fido-u2f-v1. +// 0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.html#message--and-packet-structure +pub struct U2FHIDInit {} + +impl U2FHIDInit { + pub fn read<T>(dev: &mut T) -> io::Result<Vec<u8>> + where + T: U2FDevice + io::Read, + { + let mut frame = vec![0u8; dev.in_rpt_size()]; + let mut count = dev.read(&mut frame)?; + + while dev.get_cid() != &frame[..4] { + count = dev.read(&mut frame)?; + } + + if count != dev.in_rpt_size() { + return Err(io_err("invalid init packet")); + } + + let cap = (frame[5] as usize) << 8 | (frame[6] as usize); + let mut data = Vec::with_capacity(cap); + + let len = cmp::min(cap, dev.in_init_data_size()); + data.extend_from_slice(&frame[7..7 + len]); + + Ok(data) + } + + pub fn write<T>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize> + where + T: U2FDevice + io::Write, + { + if data.len() > 0xffff { + return Err(io_err("payload length > 2^16")); + } + + let mut frame = vec![0u8; dev.out_rpt_size() + 1]; + frame[1..5].copy_from_slice(dev.get_cid()); + frame[5] = cmd; + frame[6] = (data.len() >> 8) as u8; + frame[7] = data.len() as u8; + + let count = cmp::min(data.len(), dev.out_init_data_size()); + frame[8..8 + count].copy_from_slice(&data[..count]); + trace_hex(&frame); + + if dev.write(&frame)? != frame.len() { + return Err(io_err("device write failed")); + } + + Ok(count) + } +} + +// Continuation structure for U2F Communications. After an Init structure is +// sent, continuation structures are used to transmit all extra data that +// wouldn't fit in the initial packet. The sequence number increases with every +// packet, until all data is received. +// +// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol. +// html#message--and-packet-structure +pub struct U2FHIDCont {} + +impl U2FHIDCont { + pub fn read<T>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>> + where + T: U2FDevice + io::Read, + { + let mut frame = vec![0u8; dev.in_rpt_size()]; + let mut count = dev.read(&mut frame)?; + + while dev.get_cid() != &frame[..4] { + count = dev.read(&mut frame)?; + } + + if count != dev.in_rpt_size() { + return Err(io_err("invalid cont packet")); + } + + if seq != frame[4] { + return Err(io_err("invalid sequence number")); + } + + let max = cmp::min(max, dev.in_cont_data_size()); + Ok(frame[5..5 + max].to_vec()) + } + + pub fn write<T>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize> + where + T: U2FDevice + io::Write, + { + let mut frame = vec![0u8; dev.out_rpt_size() + 1]; + frame[1..5].copy_from_slice(dev.get_cid()); + frame[5] = seq; + + let count = cmp::min(data.len(), dev.out_cont_data_size()); + frame[6..6 + count].copy_from_slice(&data[..count]); + trace_hex(&frame); + + if dev.write(&frame)? != frame.len() { + return Err(io_err("device write failed")); + } + + Ok(count) + } +} + +// Reply sent after initialization command. Contains information about U2F USB +// Key versioning, as well as the communication channel to be used for all +// further requests. +// +// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol. +// html#u2fhid_init +pub struct U2FHIDInitResp { + pub cid: [u8; 4], + pub version_interface: u8, + pub version_major: u8, + pub version_minor: u8, + pub version_build: u8, + pub cap_flags: u8, +} + +impl U2FHIDInitResp { + pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<U2FHIDInitResp> { + assert_eq!(nonce.len(), INIT_NONCE_SIZE); + + if data.len() != INIT_NONCE_SIZE + 9 { + return Err(io_err("invalid init response")); + } + + if nonce != &data[..INIT_NONCE_SIZE] { + return Err(io_err("invalid nonce")); + } + + let rsp = U2FHIDInitResp { + cid: [ + data[INIT_NONCE_SIZE], + data[INIT_NONCE_SIZE + 1], + data[INIT_NONCE_SIZE + 2], + data[INIT_NONCE_SIZE + 3], + ], + version_interface: data[INIT_NONCE_SIZE + 4], + version_major: data[INIT_NONCE_SIZE + 5], + version_minor: data[INIT_NONCE_SIZE + 6], + version_build: data[INIT_NONCE_SIZE + 7], + cap_flags: data[INIT_NONCE_SIZE + 8], + }; + + Ok(rsp) + } +} + +// https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit +// https://fidoalliance.org/specs/fido-u2f-v1. +// 0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing +pub struct U2FAPDUHeader {} + +impl U2FAPDUHeader { + pub fn serialize(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> { + if data.len() > 0xffff { + return Err(io_err("payload length > 2^16")); + } + + // Size of header + data + 2 zero bytes for maximum return size. + let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data.len() + 2]; + // cla is always 0 for our requirements + bytes[1] = ins; + bytes[2] = p1; + // p2 is always 0, at least, for our requirements. + // lc[0] should always be 0. + bytes[5] = (data.len() >> 8) as u8; + bytes[6] = data.len() as u8; + bytes[7..7 + data.len()].copy_from_slice(data); + + Ok(bytes) + } +} + +#[derive(Clone, Debug)] +pub struct U2FDeviceInfo { + pub vendor_name: Vec<u8>, + pub device_name: Vec<u8>, + pub version_interface: u8, + pub version_major: u8, + pub version_minor: u8, + pub version_build: u8, + pub cap_flags: u8, +} + +impl fmt::Display for U2FDeviceInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Vendor: {}, Device: {}, Interface: {}, Firmware: v{}.{}.{}, Capabilities: {}", + str::from_utf8(&self.vendor_name).unwrap(), + str::from_utf8(&self.device_name).unwrap(), + &self.version_interface, + &self.version_major, + &self.version_minor, + &self.version_build, + to_hex(&[self.cap_flags], ":"), + ) + } +} diff --git a/third_party/rust/authenticator/src/util.rs b/third_party/rust/authenticator/src/util.rs new file mode 100644 index 0000000000..4ccb3c9703 --- /dev/null +++ b/third_party/rust/authenticator/src/util.rs @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use std::io; + +macro_rules! try_or { + ($val:expr, $or:expr) => { + match $val { + Ok(v) => v, + Err(e) => { + return $or(e); + } + } + }; +} + +pub trait Signed { + fn is_negative(&self) -> bool; +} + +impl Signed for i32 { + fn is_negative(&self) -> bool { + *self < (0 as i32) + } +} + +impl Signed for usize { + fn is_negative(&self) -> bool { + (*self as isize) < (0 as isize) + } +} + +#[cfg(any(target_os = "linux"))] +pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> { + if rv.is_negative() { + let errno = unsafe { *libc::__errno_location() }; + Err(io::Error::from_raw_os_error(errno)) + } else { + Ok(rv) + } +} + +#[cfg(any(target_os = "freebsd"))] +pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> { + if rv.is_negative() { + let errno = unsafe { *libc::__error() }; + Err(io::Error::from_raw_os_error(errno)) + } else { + Ok(rv) + } +} + +#[cfg(any(target_os = "openbsd"))] +pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> { + if rv.is_negative() { + Err(io::Error::last_os_error()) + } else { + Ok(rv) + } +} + +pub fn io_err(msg: &str) -> io::Error { + io::Error::new(io::ErrorKind::Other, msg) +} diff --git a/third_party/rust/authenticator/src/virtualdevices/mod.rs b/third_party/rust/authenticator/src/virtualdevices/mod.rs new file mode 100644 index 0000000000..5c0a9d39fc --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/mod.rs @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(feature = "webdriver")] +pub mod webdriver; + +pub mod software_u2f; diff --git a/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs new file mode 100644 index 0000000000..a88e74de50 --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub struct SoftwareU2FToken {} + +// This is simply for platforms that aren't using the U2F Token, usually for builds +// without --feature webdriver +#[allow(dead_code)] + +impl SoftwareU2FToken { + pub fn new() -> SoftwareU2FToken { + Self {} + } + + pub fn register( + &self, + _flags: crate::RegisterFlags, + _timeout: u64, + _challenge: Vec<u8>, + _application: crate::AppId, + _key_handles: Vec<crate::KeyHandle>, + ) -> crate::Result<crate::RegisterResult> { + Ok((vec![0u8; 16], self.dev_info())) + } + + /// The implementation of this method must return quickly and should + /// report its status via the status and callback methods + pub fn sign( + &self, + _flags: crate::SignFlags, + _timeout: u64, + _challenge: Vec<u8>, + _app_ids: Vec<crate::AppId>, + _key_handles: Vec<crate::KeyHandle>, + ) -> crate::Result<crate::SignResult> { + Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info())) + } + + pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo { + crate::u2ftypes::U2FDeviceInfo { + vendor_name: b"Mozilla".to_vec(), + device_name: b"Authenticator Webdriver Token".to_vec(), + version_interface: 0, + version_major: 1, + version_minor: 2, + version_build: 3, + cap_flags: 0, + } + } +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests {} diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs new file mode 100644 index 0000000000..b1ef27d813 --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +mod testtoken; +mod virtualmanager; +mod web_api; + +pub use virtualmanager::VirtualManager; diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs new file mode 100644 index 0000000000..9bf60bbaf5 --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::virtualdevices::software_u2f::SoftwareU2FToken; +use crate::{RegisterFlags, RegisterResult, SignFlags, SignResult}; + +pub enum TestWireProtocol { + CTAP1, + CTAP2, +} + +impl TestWireProtocol { + pub fn to_webdriver_string(&self) -> String { + match self { + TestWireProtocol::CTAP1 => "ctap1/u2f".to_string(), + TestWireProtocol::CTAP2 => "ctap2".to_string(), + } + } +} + +pub struct TestTokenCredential { + pub credential: Vec<u8>, + pub privkey: Vec<u8>, + pub user_handle: Vec<u8>, + pub sign_count: u64, + pub is_resident_credential: bool, + pub rp_id: String, +} + +pub struct TestToken { + pub id: u64, + pub protocol: TestWireProtocol, + pub transport: String, + pub is_user_consenting: bool, + pub has_user_verification: bool, + pub is_user_verified: bool, + pub has_resident_key: bool, + pub u2f_impl: Option<SoftwareU2FToken>, + pub credentials: Vec<TestTokenCredential>, +} + +impl TestToken { + pub fn new( + id: u64, + protocol: TestWireProtocol, + transport: String, + is_user_consenting: bool, + has_user_verification: bool, + is_user_verified: bool, + has_resident_key: bool, + ) -> TestToken { + match protocol { + TestWireProtocol::CTAP1 => Self { + id, + protocol, + transport, + is_user_consenting, + has_user_verification, + is_user_verified, + has_resident_key, + u2f_impl: Some(SoftwareU2FToken::new()), + credentials: Vec::new(), + }, + _ => unreachable!(), + } + } + + pub fn insert_credential( + &mut self, + credential: &[u8], + privkey: &[u8], + rp_id: String, + is_resident_credential: bool, + user_handle: &[u8], + sign_count: u64, + ) { + let c = TestTokenCredential { + credential: credential.to_vec(), + privkey: privkey.to_vec(), + rp_id, + is_resident_credential, + user_handle: user_handle.to_vec(), + sign_count, + }; + + match self + .credentials + .binary_search_by_key(&credential, |probe| &probe.credential) + { + Ok(_) => {} + Err(idx) => self.credentials.insert(idx, c), + } + } + + pub fn delete_credential(&mut self, credential: &[u8]) -> bool { + debug!("Asking to delete credential",); + if let Ok(idx) = self + .credentials + .binary_search_by_key(&credential, |probe| &probe.credential) + { + debug!("Asking to delete credential from idx {}", idx); + self.credentials.remove(idx); + return true; + } + + false + } + + pub fn register(&self) -> crate::Result<RegisterResult> { + if self.u2f_impl.is_some() { + return self.u2f_impl.as_ref().unwrap().register( + RegisterFlags::empty(), + 10_000, + vec![0; 32], + vec![0; 32], + vec![], + ); + } + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + + pub fn sign(&self) -> crate::Result<SignResult> { + if self.u2f_impl.is_some() { + return self.u2f_impl.as_ref().unwrap().sign( + SignFlags::empty(), + 10_000, + vec![0; 32], + vec![vec![0; 32]], + vec![], + ); + } + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } +} diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs new file mode 100644 index 0000000000..dc26df07ee --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use runloop::RunLoop; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::ops::Deref; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; +use std::vec; +use std::{io, string, thread}; + +use crate::authenticatorservice::AuthenticatorTransport; +use crate::errors; +use crate::statecallback::StateCallback; +use crate::virtualdevices::webdriver::{testtoken, web_api}; + +pub struct VirtualManagerState { + pub authenticator_counter: u64, + pub tokens: vec::Vec<testtoken::TestToken>, +} + +impl VirtualManagerState { + pub fn new() -> Arc<Mutex<VirtualManagerState>> { + Arc::new(Mutex::new(VirtualManagerState { + authenticator_counter: 0, + tokens: vec![], + })) + } +} + +pub struct VirtualManager { + addr: SocketAddr, + state: Arc<Mutex<VirtualManagerState>>, + rloop: Option<RunLoop>, +} + +impl VirtualManager { + pub fn new() -> io::Result<Self> { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080); + let state = VirtualManagerState::new(); + let stateclone = state.clone(); + + let builder = thread::Builder::new().name("WebDriver Command Server".into()); + builder.spawn(move || { + web_api::serve(stateclone, addr); + })?; + + Ok(Self { + addr, + state, + rloop: None, + }) + } + + pub fn url(&self) -> string::String { + format!("http://{}/webauthn/authenticator", &self.addr) + } +} + +impl AuthenticatorTransport for VirtualManager { + fn register( + &mut self, + _flags: crate::RegisterFlags, + timeout: u64, + _challenge: Vec<u8>, + _application: crate::AppId, + _key_handles: Vec<crate::KeyHandle>, + _status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::RegisterResult>>, + ) -> crate::Result<()> { + if self.rloop.is_some() { + error!("WebDriver state error, prior operation never cancelled."); + return Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )); + } + + let state = self.state.clone(); + let rloop = try_or!( + RunLoop::new_with_timeout( + move |alive| { + while alive() { + let state_obj = state.lock().unwrap(); + + for token in state_obj.tokens.deref() { + if token.is_user_consenting { + let register_result = token.register(); + thread::spawn(move || { + callback.call(register_result); + }); + return; + } + } + } + }, + timeout + ), + |_| Err(errors::AuthenticatorError::Platform) + ); + + self.rloop = Some(rloop); + Ok(()) + } + + fn sign( + &mut self, + _flags: crate::SignFlags, + timeout: u64, + _challenge: Vec<u8>, + _app_ids: Vec<crate::AppId>, + _key_handles: Vec<crate::KeyHandle>, + _status: Sender<crate::StatusUpdate>, + callback: StateCallback<crate::Result<crate::SignResult>>, + ) -> crate::Result<()> { + if self.rloop.is_some() { + error!("WebDriver state error, prior operation never cancelled."); + return Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )); + } + + let state = self.state.clone(); + let rloop = try_or!( + RunLoop::new_with_timeout( + move |alive| { + while alive() { + let state_obj = state.lock().unwrap(); + + for token in state_obj.tokens.deref() { + if token.is_user_consenting { + let sign_result = token.sign(); + thread::spawn(move || { + callback.call(sign_result); + }); + return; + } + } + } + }, + timeout + ), + |_| Err(errors::AuthenticatorError::Platform) + ); + + self.rloop = Some(rloop); + Ok(()) + } + + fn cancel(&mut self) -> crate::Result<()> { + if let Some(r) = self.rloop.take() { + debug!("WebDriver operation cancelled."); + r.cancel(); + } + Ok(()) + } +} diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs new file mode 100644 index 0000000000..9093938664 --- /dev/null +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs @@ -0,0 +1,965 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use std::string; +use std::sync::{Arc, Mutex}; +use warp::Filter; + +use crate::virtualdevices::webdriver::{testtoken, virtualmanager::VirtualManagerState}; + +fn default_as_false() -> bool { + false +} +fn default_as_true() -> bool { + false +} + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub struct AuthenticatorConfiguration { + protocol: string::String, + transport: string::String, + #[serde(rename = "hasResidentKey")] + #[serde(default = "default_as_false")] + has_resident_key: bool, + #[serde(rename = "hasUserVerification")] + #[serde(default = "default_as_false")] + has_user_verification: bool, + #[serde(rename = "isUserConsenting")] + #[serde(default = "default_as_true")] + is_user_consenting: bool, + #[serde(rename = "isUserVerified")] + #[serde(default = "default_as_false")] + is_user_verified: bool, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub struct CredentialParameters { + #[serde(rename = "credentialId")] + credential_id: String, + #[serde(rename = "isResidentCredential")] + is_resident_credential: bool, + #[serde(rename = "rpId")] + rp_id: String, + #[serde(rename = "privateKey")] + private_key: String, + #[serde(rename = "userHandle")] + #[serde(default)] + user_handle: String, + #[serde(rename = "signCount")] + sign_count: u64, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub struct UserVerificationParameters { + #[serde(rename = "isUserVerified")] + is_user_verified: bool, +} + +impl CredentialParameters { + fn new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters { + let credential_id = base64::encode_config(&tc.credential, base64::URL_SAFE); + + let private_key = base64::encode_config(&tc.privkey, base64::URL_SAFE); + + let user_handle = base64::encode_config(&tc.user_handle, base64::URL_SAFE); + + CredentialParameters { + credential_id, + is_resident_credential: tc.is_resident_credential, + rp_id: tc.rp_id.clone(), + private_key, + user_handle, + sign_count: tc.sign_count, + } + } +} + +fn with_state( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone +{ + warp::any().map(move || state.clone()) +} + +fn authenticator_add( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator") + .and(warp::post()) + .and(warp::body::json()) + .and(with_state(state)) + .and_then(handlers::authenticator_add) +} + +fn authenticator_delete( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64) + .and(warp::delete()) + .and(with_state(state)) + .and_then(handlers::authenticator_delete) +} + +fn authenticator_set_uv( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64 / "uv") + .and(warp::post()) + .and(warp::body::json()) + .and(with_state(state)) + .and_then(handlers::authenticator_set_uv) +} + +// This is not part of the specification, but it's useful for debugging +fn authenticator_get( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64) + .and(warp::get()) + .and(with_state(state)) + .and_then(handlers::authenticator_get) +} + +fn authenticator_credential_add( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64 / "credential") + .and(warp::post()) + .and(warp::body::json()) + .and(with_state(state)) + .and_then(handlers::authenticator_credential_add) +} + +fn authenticator_credential_delete( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64 / "credentials" / String) + .and(warp::delete()) + .and(with_state(state)) + .and_then(handlers::authenticator_credential_delete) +} + +fn authenticator_credentials_get( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64 / "credentials") + .and(warp::get()) + .and(with_state(state)) + .and_then(handlers::authenticator_credentials_get) +} + +fn authenticator_credentials_clear( + state: Arc<Mutex<VirtualManagerState>>, +) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone { + warp::path!("webauthn" / "authenticator" / u64 / "credentials") + .and(warp::delete()) + .and(with_state(state)) + .and_then(handlers::authenticator_credentials_clear) +} + +mod handlers { + use super::{CredentialParameters, UserVerificationParameters}; + use crate::virtualdevices::webdriver::{ + testtoken, virtualmanager::VirtualManagerState, web_api::AuthenticatorConfiguration, + }; + use serde::Serialize; + use std::convert::Infallible; + use std::ops::DerefMut; + use std::sync::{Arc, Mutex}; + use std::vec; + use warp::http::{uri, StatusCode}; + + #[derive(Serialize)] + struct JsonSuccess {} + + impl JsonSuccess { + pub fn blank() -> JsonSuccess { + JsonSuccess {} + } + } + + #[derive(Serialize)] + struct JsonError { + #[serde(skip_serializing_if = "Option::is_none")] + line: Option<u32>, + error: String, + details: String, + } + + impl JsonError { + pub fn new(error: &str, line: u32, details: &str) -> JsonError { + JsonError { + details: details.to_string(), + error: error.to_string(), + line: Some(line), + } + } + pub fn from_status_code(code: StatusCode) -> JsonError { + JsonError { + details: code.canonical_reason().unwrap().to_string(), + line: None, + error: "".to_string(), + } + } + pub fn from_error(error: &str) -> JsonError { + JsonError { + details: "".to_string(), + error: error.to_string(), + line: None, + } + } + } + + macro_rules! reply_error { + ($status:expr) => { + warp::reply::with_status( + warp::reply::json(&JsonError::from_status_code($status)), + $status, + ) + }; + } + + macro_rules! try_json { + ($val:expr, $status:expr) => { + match $val { + Ok(v) => v, + Err(e) => { + return Ok(warp::reply::with_status( + warp::reply::json(&JsonError::new( + $status.canonical_reason().unwrap(), + line!(), + &e.to_string(), + )), + $status, + )); + } + } + }; + } + + pub fn validate_rp_id(rp_id: &str) -> crate::Result<()> { + if let Ok(uri) = rp_id.parse::<uri::Uri>().map_err(|_| { + crate::errors::AuthenticatorError::U2FToken(crate::errors::U2FTokenError::Unknown) + }) { + if uri.scheme().is_none() + && uri.path_and_query().is_none() + && uri.port().is_none() + && uri.host().is_some() + && uri.authority().unwrap() == uri.host().unwrap() + // Don't try too hard to ensure it's a valid domain, just + // ensure there's a label delim in there somewhere + && uri.host().unwrap().find('.').is_some() + { + return Ok(()); + } + } + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + } + + pub async fn authenticator_add( + auth: AuthenticatorConfiguration, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let protocol = match auth.protocol.as_str() { + "ctap1/u2f" => testtoken::TestWireProtocol::CTAP1, + "ctap2" => testtoken::TestWireProtocol::CTAP2, + _ => { + return Ok(warp::reply::with_status( + warp::reply::json(&JsonError::from_error( + format!("unknown protocol: {}", auth.protocol).as_str(), + )), + StatusCode::BAD_REQUEST, + )) + } + }; + + let mut state_lock = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + let mut state_obj = state_lock.deref_mut(); + state_obj.authenticator_counter += 1; + + let tt = testtoken::TestToken::new( + state_obj.authenticator_counter, + protocol, + auth.transport, + auth.is_user_consenting, + auth.has_user_verification, + auth.is_user_verified, + auth.has_resident_key, + ); + + match state_obj + .tokens + .binary_search_by_key(&state_obj.authenticator_counter, |probe| probe.id) + { + Ok(_) => panic!("unexpected repeat of authenticator_id"), + Err(idx) => state_obj.tokens.insert(idx, tt), + } + + #[derive(Serialize)] + struct AddResult { + #[serde(rename = "authenticatorId")] + authenticator_id: u64, + } + + Ok(warp::reply::with_status( + warp::reply::json(&AddResult { + authenticator_id: state_obj.authenticator_counter, + }), + StatusCode::CREATED, + )) + } + + pub async fn authenticator_delete( + id: u64, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + match state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + Ok(idx) => state_obj.tokens.remove(idx), + Err(_) => { + return Ok(reply_error!(StatusCode::NOT_FOUND)); + } + }; + + Ok(warp::reply::with_status( + warp::reply::json(&JsonSuccess::blank()), + StatusCode::OK, + )) + } + + pub async fn authenticator_get( + id: u64, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + + let data = AuthenticatorConfiguration { + protocol: tt.protocol.to_webdriver_string(), + transport: tt.transport.clone(), + has_resident_key: tt.has_resident_key, + has_user_verification: tt.has_user_verification, + is_user_consenting: tt.is_user_consenting, + is_user_verified: tt.is_user_verified, + }; + + return Ok(warp::reply::with_status( + warp::reply::json(&data), + StatusCode::OK, + )); + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } + + pub async fn authenticator_set_uv( + id: u64, + uv: UserVerificationParameters, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + tt.is_user_verified = uv.is_user_verified; + return Ok(warp::reply::with_status( + warp::reply::json(&JsonSuccess::blank()), + StatusCode::OK, + )); + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } + + pub async fn authenticator_credential_add( + id: u64, + auth: CredentialParameters, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let credential = try_json!( + base64::decode_config(&auth.credential_id, base64::URL_SAFE), + StatusCode::BAD_REQUEST + ); + + let privkey = try_json!( + base64::decode_config(&auth.private_key, base64::URL_SAFE), + StatusCode::BAD_REQUEST + ); + + let userhandle = try_json!( + base64::decode_config(&auth.user_handle, base64::URL_SAFE), + StatusCode::BAD_REQUEST + ); + + try_json!(validate_rp_id(&auth.rp_id), StatusCode::BAD_REQUEST); + + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + + tt.insert_credential( + &credential, + &privkey, + auth.rp_id, + auth.is_resident_credential, + &userhandle, + auth.sign_count, + ); + + return Ok(warp::reply::with_status( + warp::reply::json(&JsonSuccess::blank()), + StatusCode::CREATED, + )); + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } + + pub async fn authenticator_credential_delete( + id: u64, + credential_id: String, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let credential = try_json!( + base64::decode_config(&credential_id, base64::URL_SAFE), + StatusCode::BAD_REQUEST + ); + + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + + debug!("Asking to delete {}", &credential_id); + + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + debug!("Asking to delete from token {}", tt.id); + if tt.delete_credential(&credential) { + return Ok(warp::reply::with_status( + warp::reply::json(&JsonSuccess::blank()), + StatusCode::OK, + )); + } + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } + + pub async fn authenticator_credentials_get( + id: u64, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + let mut creds: vec::Vec<CredentialParameters> = vec![]; + for ttc in &tt.credentials { + creds.push(CredentialParameters::new_from_test_token_credential(ttc)); + } + + return Ok(warp::reply::with_status( + warp::reply::json(&creds), + StatusCode::OK, + )); + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } + + pub async fn authenticator_credentials_clear( + id: u64, + state: Arc<Mutex<VirtualManagerState>>, + ) -> Result<impl warp::Reply, Infallible> { + let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR); + if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) { + let tt = &mut state_obj.tokens[idx]; + + tt.credentials.clear(); + + return Ok(warp::reply::with_status( + warp::reply::json(&JsonSuccess::blank()), + StatusCode::OK, + )); + } + + Ok(reply_error!(StatusCode::NOT_FOUND)) + } +} + +#[tokio::main] +pub async fn serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr) { + let routes = authenticator_add(state.clone()) + .or(authenticator_delete(state.clone())) + .or(authenticator_get(state.clone())) + .or(authenticator_set_uv(state.clone())) + .or(authenticator_credential_add(state.clone())) + .or(authenticator_credential_delete(state.clone())) + .or(authenticator_credentials_get(state.clone())) + .or(authenticator_credentials_clear(state.clone())); + + warp::serve(routes).run(addr).await; +} + +#[cfg(test)] +mod tests { + use super::handlers::validate_rp_id; + use super::testtoken::*; + use super::*; + use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState; + use bytes::Buf; + use std::sync::{Arc, Mutex}; + use warp::http::StatusCode; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + #[test] + fn test_validate_rp_id() { + init(); + + assert_matches!( + validate_rp_id(&String::from("http://example.com")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("https://example.com")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("example.com:443")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("example.com/path")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("example.com:443/path")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("user:pass@example.com")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!( + validate_rp_id(&String::from("com")), + Err(crate::errors::AuthenticatorError::U2FToken( + crate::errors::U2FTokenError::Unknown, + )) + ); + assert_matches!(validate_rp_id(&String::from("example.com")), Ok(())); + } + + fn mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>> { + let state = VirtualManagerState::new(); + + { + let mut state_obj = state.lock().unwrap(); + for id in ids { + state_obj.tokens.push(TestToken::new( + *id, + TestWireProtocol::CTAP1, + "internal".to_string(), + true, + true, + true, + true, + )); + } + + state_obj.tokens.sort_by_key(|probe| probe.id) + } + + state + } + + fn assert_success_rsp_blank(body: &bytes::Bytes) { + assert_eq!(String::from_utf8_lossy(body.bytes()), r#"{}"#) + } + + fn assert_creds_equals_test_token_params( + a: &[CredentialParameters], + b: &[TestTokenCredential], + ) { + assert_eq!(a.len(), b.len()); + + for (i, j) in a.iter().zip(b.iter()) { + assert_eq!( + i.credential_id, + base64::encode_config(&j.credential, base64::URL_SAFE) + ); + assert_eq!( + i.user_handle, + base64::encode_config(&j.user_handle, base64::URL_SAFE) + ); + assert_eq!( + i.private_key, + base64::encode_config(&j.privkey, base64::URL_SAFE) + ); + assert_eq!(i.rp_id, j.rp_id); + assert_eq!(i.sign_count, j.sign_count); + assert_eq!(i.is_resident_credential, j.is_resident_credential); + } + } + + #[tokio::test] + async fn test_authenticator_add() { + init(); + let filter = authenticator_add(mk_state_with_token_list(&[])); + + { + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator") + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + let valid_add = AuthenticatorConfiguration { + protocol: "ctap1/u2f".to_string(), + transport: "usb".to_string(), + has_resident_key: false, + has_user_verification: false, + is_user_consenting: false, + is_user_verified: false, + }; + + { + let mut invalid = valid_add.clone(); + invalid.protocol = "unknown".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator") + .json(&invalid) + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + assert!(String::from_utf8_lossy(res.body().bytes()) + .contains(&String::from("unknown protocol: unknown"))); + } + + { + let mut unknown = valid_add.clone(); + unknown.transport = "unknown".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator") + .json(&unknown) + .reply(&filter) + .await; + assert!(res.status().is_success()); + assert_eq!( + String::from_utf8_lossy(res.body().bytes()), + r#"{"authenticatorId":1}"# + ) + } + + { + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator") + .json(&valid_add) + .reply(&filter) + .await; + assert!(res.status().is_success()); + assert_eq!( + String::from_utf8_lossy(res.body().bytes()), + r#"{"authenticatorId":2}"# + ) + } + } + + #[tokio::test] + async fn test_authenticator_delete() { + init(); + let filter = authenticator_delete(mk_state_with_token_list(&[32])); + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/3") + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/32") + .reply(&filter) + .await; + assert!(res.status().is_success()); + assert_success_rsp_blank(res.body()); + } + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/42") + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + } + + #[tokio::test] + async fn test_authenticator_change_uv() { + init(); + let state = mk_state_with_token_list(&[1]); + let filter = authenticator_set_uv(state.clone()); + + { + let state_obj = state.lock().unwrap(); + assert_eq!(true, state_obj.tokens[0].is_user_verified); + } + + { + // Empty POST is bad + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/uv") + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + // Unexpected POST structure is bad + #[derive(Serialize)] + struct Unexpected { + id: u64, + } + let unexpected = Unexpected { id: 4 }; + + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/uv") + .json(&unexpected) + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + let param_false = UserVerificationParameters { + is_user_verified: false, + }; + + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/uv") + .json(¶m_false) + .reply(&filter) + .await; + assert_eq!(res.status(), 200); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(false, state_obj.tokens[0].is_user_verified); + } + + { + let param_false = UserVerificationParameters { + is_user_verified: true, + }; + + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/uv") + .json(¶m_false) + .reply(&filter) + .await; + assert_eq!(res.status(), 200); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(true, state_obj.tokens[0].is_user_verified); + } + } + + #[tokio::test] + async fn test_authenticator_credentials() { + init(); + let state = mk_state_with_token_list(&[1]); + let filter = authenticator_credential_add(state.clone()) + .or(authenticator_credential_delete(state.clone())) + .or(authenticator_credentials_get(state.clone())) + .or(authenticator_credentials_clear(state.clone())); + + let valid_add_credential = CredentialParameters { + credential_id: r"c3VwZXIgcmVhZGVy".to_string(), + is_resident_credential: true, + rp_id: "valid.rpid".to_string(), + private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE), + user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE), + sign_count: 0, + }; + + { + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + let mut invalid = valid_add_credential.clone(); + invalid.credential_id = "!@#$ invalid base64".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&invalid) + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + let mut invalid = valid_add_credential.clone(); + invalid.rp_id = "example".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&invalid) + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + } + + { + let mut invalid = valid_add_credential.clone(); + invalid.rp_id = "https://example.com".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&invalid) + .reply(&filter) + .await; + assert!(res.status().is_client_error()); + + let state_obj = state.lock().unwrap(); + assert_eq!(0, state_obj.tokens[0].credentials.len()); + } + + { + let mut no_user_handle = valid_add_credential.clone(); + no_user_handle.user_handle = "".to_string(); + no_user_handle.credential_id = "YQo=".to_string(); + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&no_user_handle) + .reply(&filter) + .await; + assert!(res.status().is_success()); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(1, state_obj.tokens[0].credentials.len()); + let c = &state_obj.tokens[0].credentials[0]; + assert!(c.user_handle.is_empty()); + } + + { + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&valid_add_credential) + .reply(&filter) + .await; + assert_eq!(res.status(), StatusCode::CREATED); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(2, state_obj.tokens[0].credentials.len()); + let c = &state_obj.tokens[0].credentials[1]; + assert!(!c.user_handle.is_empty()); + } + + { + // Duplicate, should still be two credentials + let res = warp::test::request() + .method("POST") + .path("/webauthn/authenticator/1/credential") + .json(&valid_add_credential) + .reply(&filter) + .await; + assert_eq!(res.status(), StatusCode::CREATED); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(2, state_obj.tokens[0].credentials.len()); + } + + { + let res = warp::test::request() + .method("GET") + .path("/webauthn/authenticator/1/credentials") + .reply(&filter) + .await; + assert_eq!(res.status(), 200); + let (_, body) = res.into_parts(); + let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap(); + + let state_obj = state.lock().unwrap(); + assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials); + } + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/1/credentials/YmxhbmsK") + .reply(&filter) + .await; + assert_eq!(res.status(), 404); + } + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy") + .reply(&filter) + .await; + assert_eq!(res.status(), 200); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(1, state_obj.tokens[0].credentials.len()); + } + + { + let res = warp::test::request() + .method("DELETE") + .path("/webauthn/authenticator/1/credentials") + .reply(&filter) + .await; + assert!(res.status().is_success()); + assert_success_rsp_blank(res.body()); + + let state_obj = state.lock().unwrap(); + assert_eq!(0, state_obj.tokens[0].credentials.len()); + } + } +} diff --git a/third_party/rust/authenticator/src/windows/device.rs b/third_party/rust/authenticator/src/windows/device.rs new file mode 100644 index 0000000000..183ba71f44 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/device.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::{Read, Write}; +use std::os::windows::io::AsRawHandle; + +use super::winapi::DeviceCapabilities; +use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; + +#[derive(Debug)] +pub struct Device { + path: String, + file: File, + cid: [u8; 4], + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(path: String) -> io::Result<Self> { + let file = OpenOptions::new().read(true).write(true).open(&path)?; + Ok(Self { + path, + file, + cid: CID_BROADCAST, + dev_info: None, + }) + } + + pub fn is_u2f(&self) -> bool { + match DeviceCapabilities::new(self.file.as_raw_handle()) { + Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE, + _ => false, + } + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Read for Device { + fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> { + // Windows always includes the report ID. + let mut input = [0u8; MAX_HID_RPT_SIZE + 1]; + let _ = self.file.read(&mut input)?; + bytes.clone_from_slice(&input[1..]); + Ok(bytes.len() as usize) + } +} + +impl Write for Device { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + self.file.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } +} + +impl U2FDevice for Device { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, _prop_name: &str) -> io::Result<String> { + Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/windows/mod.rs b/third_party/rust/authenticator/src/windows/mod.rs new file mode 100644 index 0000000000..09135391dd --- /dev/null +++ b/third_party/rust/authenticator/src/windows/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod monitor; +mod winapi; diff --git a/third_party/rust/authenticator/src/windows/monitor.rs b/third_party/rust/authenticator/src/windows/monitor.rs new file mode 100644 index 0000000000..4c977bde03 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/monitor.rs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::platform::winapi::DeviceInfoSet; +use runloop::RunLoop; +use std::collections::{HashMap, HashSet}; +use std::io; +use std::iter::FromIterator; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +pub struct Monitor<F> +where + F: Fn(String, &dyn Fn() -> bool) + Sync, +{ + runloops: HashMap<String, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(String, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + let mut stored = HashSet::new(); + + while alive() { + let device_info_set = DeviceInfoSet::new()?; + let devices = HashSet::from_iter(device_info_set.devices()); + + // Remove devices that are gone. + for path in stored.difference(&devices) { + self.remove_device(path); + } + + // Add devices that were plugged in. + for path in devices.difference(&stored) { + self.add_device(path); + } + + // Remember the new set. + stored = devices; + + // Wait a little before looking for devices again. + thread::sleep(Duration::from_millis(100)); + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn add_device(&mut self, path: &String) { + let f = self.new_device_cb.clone(); + let path = path.clone(); + let key = path.clone(); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(path, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + + fn remove_device(&mut self, path: &String) { + if let Some(runloop) = self.runloops.remove(path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(&path); + } + } +} diff --git a/third_party/rust/authenticator/src/windows/transaction.rs b/third_party/rust/authenticator/src/windows/transaction.rs new file mode 100644 index 0000000000..74e856b690 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/transaction.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use runloop::RunLoop; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(String, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/windows/winapi.rs b/third_party/rust/authenticator/src/windows/winapi.rs new file mode 100644 index 0000000000..d3388cebfc --- /dev/null +++ b/third_party/rust/authenticator/src/windows/winapi.rs @@ -0,0 +1,274 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::mem; +use std::ptr; +use std::slice; + +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; + +use crate::util::io_err; + +extern crate libc; +extern crate winapi; + +use crate::platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef}; +use crate::platform::winapi::winapi::shared::{hidclass, hidpi, hidusage}; +use crate::platform::winapi::winapi::um::{handleapi, setupapi}; + +#[link(name = "setupapi")] +extern "system" { + fn SetupDiGetClassDevsW( + ClassGuid: *const guiddef::GUID, + Enumerator: ntdef::PCSTR, + hwndParent: windef::HWND, + flags: minwindef::DWORD, + ) -> setupapi::HDEVINFO; + + fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: setupapi::HDEVINFO) -> minwindef::BOOL; + + fn SetupDiEnumDeviceInterfaces( + DeviceInfoSet: setupapi::HDEVINFO, + DeviceInfoData: setupapi::PSP_DEVINFO_DATA, + InterfaceClassGuid: *const guiddef::GUID, + MemberIndex: minwindef::DWORD, + DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA, + ) -> minwindef::BOOL; + + fn SetupDiGetDeviceInterfaceDetailW( + DeviceInfoSet: setupapi::HDEVINFO, + DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA, + DeviceInterfaceDetailData: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W, + DeviceInterfaceDetailDataSize: minwindef::DWORD, + RequiredSize: minwindef::PDWORD, + DeviceInfoData: setupapi::PSP_DEVINFO_DATA, + ) -> minwindef::BOOL; +} + +#[link(name = "hid")] +extern "system" { + fn HidD_GetPreparsedData( + HidDeviceObject: ntdef::HANDLE, + PreparsedData: *mut hidpi::PHIDP_PREPARSED_DATA, + ) -> ntdef::BOOLEAN; + + fn HidD_FreePreparsedData(PreparsedData: hidpi::PHIDP_PREPARSED_DATA) -> ntdef::BOOLEAN; + + fn HidP_GetCaps( + PreparsedData: hidpi::PHIDP_PREPARSED_DATA, + Capabilities: hidpi::PHIDP_CAPS, + ) -> ntdef::NTSTATUS; +} + +macro_rules! offset_of { + ($ty:ty, $field:ident) => { + unsafe { &(*(0 as *const $ty)).$field as *const _ as usize } + }; +} + +fn from_wide_ptr(ptr: *const u16, len: usize) -> String { + assert!(!ptr.is_null() && len % 2 == 0); + let slice = unsafe { slice::from_raw_parts(ptr, len / 2) }; + OsString::from_wide(slice).to_string_lossy().into_owned() +} + +pub struct DeviceInfoSet { + set: setupapi::HDEVINFO, +} + +impl DeviceInfoSet { + pub fn new() -> io::Result<Self> { + let flags = setupapi::DIGCF_PRESENT | setupapi::DIGCF_DEVICEINTERFACE; + let set = unsafe { + SetupDiGetClassDevsW( + &hidclass::GUID_DEVINTERFACE_HID, + ptr::null_mut(), + ptr::null_mut(), + flags, + ) + }; + if set == handleapi::INVALID_HANDLE_VALUE { + return Err(io_err("SetupDiGetClassDevsW failed!")); + } + + Ok(Self { set }) + } + + pub fn get(&self) -> setupapi::HDEVINFO { + self.set + } + + pub fn devices(&self) -> DeviceInfoSetIter { + DeviceInfoSetIter::new(self) + } +} + +impl Drop for DeviceInfoSet { + fn drop(&mut self) { + let _ = unsafe { SetupDiDestroyDeviceInfoList(self.set) }; + } +} + +pub struct DeviceInfoSetIter<'a> { + set: &'a DeviceInfoSet, + index: minwindef::DWORD, +} + +impl<'a> DeviceInfoSetIter<'a> { + fn new(set: &'a DeviceInfoSet) -> Self { + Self { set, index: 0 } + } +} + +impl<'a> Iterator for DeviceInfoSetIter<'a> { + type Item = String; + + fn next(&mut self) -> Option<Self::Item> { + let mut device_interface_data = + mem::MaybeUninit::<setupapi::SP_DEVICE_INTERFACE_DATA>::zeroed(); + unsafe { + (*device_interface_data.as_mut_ptr()).cbSize = + mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DATA>() as minwindef::UINT; + } + + let rv = unsafe { + SetupDiEnumDeviceInterfaces( + self.set.get(), + ptr::null_mut(), + &hidclass::GUID_DEVINTERFACE_HID, + self.index, + device_interface_data.as_mut_ptr(), + ) + }; + if rv == 0 { + return None; // We're past the last device index. + } + + // Determine the size required to hold a detail struct. + let mut required_size = 0; + unsafe { + SetupDiGetDeviceInterfaceDetailW( + self.set.get(), + device_interface_data.as_mut_ptr(), + ptr::null_mut(), + required_size, + &mut required_size, + ptr::null_mut(), + ) + }; + if required_size == 0 { + return None; // An error occurred. + } + + let detail = DeviceInterfaceDetailData::new(required_size as usize); + if detail.is_none() { + return None; // malloc() failed. + } + + let detail = detail.unwrap(); + let rv = unsafe { + SetupDiGetDeviceInterfaceDetailW( + self.set.get(), + device_interface_data.as_mut_ptr(), + detail.get(), + required_size, + ptr::null_mut(), + ptr::null_mut(), + ) + }; + if rv == 0 { + return None; // An error occurred. + } + + self.index += 1; + Some(detail.path()) + } +} + +struct DeviceInterfaceDetailData { + data: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W, + path_len: usize, +} + +impl DeviceInterfaceDetailData { + fn new(size: usize) -> Option<Self> { + let mut cb_size = mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W>(); + if cfg!(target_pointer_width = "32") { + cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate. + } + + if size < cb_size { + warn!("DeviceInterfaceDetailData is too small. {}", size); + return None; + } + + let data = unsafe { libc::malloc(size) as setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W }; + if data.is_null() { + return None; + } + + // Set total size of the structure. + unsafe { (*data).cbSize = cb_size as minwindef::UINT }; + + // Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`. + let offset = offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); + + Some(Self { + data, + path_len: size - offset, + }) + } + + fn get(&self) -> setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W { + self.data + } + + fn path(&self) -> String { + unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) } + } +} + +impl Drop for DeviceInterfaceDetailData { + fn drop(&mut self) { + unsafe { libc::free(self.data as *mut libc::c_void) }; + } +} + +pub struct DeviceCapabilities { + caps: hidpi::HIDP_CAPS, +} + +impl DeviceCapabilities { + pub fn new(handle: ntdef::HANDLE) -> io::Result<Self> { + let mut preparsed_data = ptr::null_mut(); + let rv = unsafe { HidD_GetPreparsedData(handle, &mut preparsed_data) }; + if rv == 0 || preparsed_data.is_null() { + return Err(io_err("HidD_GetPreparsedData failed!")); + } + + let mut caps = mem::MaybeUninit::<hidpi::HIDP_CAPS>::uninit(); + unsafe { + let rv = HidP_GetCaps(preparsed_data, caps.as_mut_ptr()); + HidD_FreePreparsedData(preparsed_data); + + if rv != hidpi::HIDP_STATUS_SUCCESS { + return Err(io_err("HidP_GetCaps failed!")); + } + + Ok(Self { + caps: caps.assume_init(), + }) + } + } + + pub fn usage(&self) -> hidusage::USAGE { + self.caps.Usage + } + + pub fn usage_page(&self) -> hidusage::USAGE { + self.caps.UsagePage + } +} diff --git a/third_party/rust/authenticator/testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile b/third_party/rust/authenticator/testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile new file mode 100644 index 0000000000..f24da41678 --- /dev/null +++ b/third_party/rust/authenticator/testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile @@ -0,0 +1,8 @@ +FROM rustembedded/cross:powerpc64le-unknown-linux-gnu-0.2.1 + +RUN dpkg --add-architecture powerpc64le && \ + apt-get update && \ + apt-get install --assume-yes libudev-dev + +RUN pkg-config --list-all && pkg-config --libs libudev && \ + pkg-config --modversion libudev
\ No newline at end of file diff --git a/third_party/rust/authenticator/testing/cross/x86_64-unknown-linux-gnu.Dockerfile b/third_party/rust/authenticator/testing/cross/x86_64-unknown-linux-gnu.Dockerfile new file mode 100644 index 0000000000..016ad4a362 --- /dev/null +++ b/third_party/rust/authenticator/testing/cross/x86_64-unknown-linux-gnu.Dockerfile @@ -0,0 +1,7 @@ +FROM rustembedded/cross:x86_64-unknown-linux-gnu-0.2.1 + +RUN apt-get update && \ + apt-get install --assume-yes libudev-dev + +RUN pkg-config --list-all && pkg-config --libs libudev && \ + pkg-config --modversion libudev
\ No newline at end of file diff --git a/third_party/rust/authenticator/testing/run_cross.sh b/third_party/rust/authenticator/testing/run_cross.sh new file mode 100755 index 0000000000..e62b8bd492 --- /dev/null +++ b/third_party/rust/authenticator/testing/run_cross.sh @@ -0,0 +1,11 @@ +#!/bin/bash -xe + +pushd testing/cross/ +docker build -t local_cross:x86_64-unknown-linux-gnu -f x86_64-unknown-linux-gnu.Dockerfile . +docker build -t local_cross:powerpc64le-unknown-linux-gnu -f powerpc64le-unknown-linux-gnu.Dockerfile . +popd + +cross test --target x86_64-unknown-linux-gnu +cross build --target x86_64-unknown-netbsd +cross build --target powerpc64le-unknown-linux-gnu +cross build --target x86_64-pc-windows-gnu diff --git a/third_party/rust/authenticator/webdriver-tools/requirements.txt b/third_party/rust/authenticator/webdriver-tools/requirements.txt new file mode 100644 index 0000000000..ba2e06d163 --- /dev/null +++ b/third_party/rust/authenticator/webdriver-tools/requirements.txt @@ -0,0 +1,2 @@ +requests>=2.23.0 +rich>=3.0 diff --git a/third_party/rust/authenticator/webdriver-tools/webdriver-driver.py b/third_party/rust/authenticator/webdriver-tools/webdriver-driver.py new file mode 100644 index 0000000000..6647d595d3 --- /dev/null +++ b/third_party/rust/authenticator/webdriver-tools/webdriver-driver.py @@ -0,0 +1,207 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from rich.console import Console +from rich.logging import RichHandler + +import argparse +import logging +import requests + +console = Console() +log = logging.getLogger("webdriver-driver") + +parser = argparse.ArgumentParser() +subparsers = parser.add_subparsers(help="sub-command help") + +parser.add_argument( + "--verbose", "-v", help="Be more verbose", action="count", default=0 +) +parser.add_argument( + "--url", + default="http://localhost:8080/webauthn/authenticator", + help="webdriver url", +) + + +def device_add(args): + data = { + "protocol": args.protocol, + "transport": args.transport, + "hasResidentKey": args.residentkey in ["true", "yes"], + "isUserConsenting": args.consent in ["true", "yes"], + "hasUserVerification": args.uv in ["available", "verified"], + "isUserVerified": args.uv in ["verified"], + } + console.print("Adding new device: ", data) + rsp = requests.post(args.url, json=data) + console.print(rsp) + console.print(rsp.json()) + + +parser_add = subparsers.add_parser("add", help="Add a device") +parser_add.set_defaults(func=device_add) +parser_add.add_argument( + "--consent", + choices=["yes", "no", "true", "false"], + default="true", + help="consent automatically", +) +parser_add.add_argument( + "--residentkey", + choices=["yes", "no", "true", "false"], + default="no", + help="indicate a resident key", +) +parser_add.add_argument( + "--uv", + choices=["no", "available", "verified"], + default="no", + help="indicate user verification", +) +parser_add.add_argument( + "--protocol", choices=["ctap1/u2f", "ctap2"], default="ctap1/u2f", help="protocol" +) +parser_add.add_argument("--transport", default="usb", help="transport type(s)") + + +def device_delete(args): + rsp = requests.delete(f"{args.url}/{args.id}") + console.print(rsp) + console.print(rsp.json()) + + +parser_delete = subparsers.add_parser("delete", help="Delete a device") +parser_delete.set_defaults(func=device_delete) +parser_delete.add_argument("id", type=int, help="device ID to delete") + + +def device_view(args): + rsp = requests.get(f"{args.url}/{args.id}") + console.print(rsp) + console.print(rsp.json()) + + +parser_view = subparsers.add_parser("view", help="View data about a device") +parser_view.set_defaults(func=device_view) +parser_view.add_argument("id", type=int, help="device ID to view") + + +def device_update_uv(args): + data = {"isUserVerified": args.uv in ["verified", "yes"]} + rsp = requests.post(f"{args.url}/{args.id}/uv", json=data) + console.print(rsp) + console.print(rsp.json()) + + +parser_update_uv = subparsers.add_parser( + "update-uv", help="Update the User Verified setting" +) +parser_update_uv.set_defaults(func=device_update_uv) +parser_update_uv.add_argument("id", type=int, help="device ID to update") +parser_update_uv.add_argument( + "uv", + choices=["no", "yes", "verified"], + default="no", + help="indicate user verification", +) + + +def credential_add(args): + data = { + "credentialId": args.credentialId, + "isResidentCredential": args.isResidentCredential in ["true", "yes"], + "rpId": args.rpId, + "privateKey": args.privateKey, + "signCount": args.signCount, + } + if args.userHandle: + data["userHandle"] = (args.userHandle,) + + console.print(f"Adding new credential to device {args.id}: ", data) + rsp = requests.post(f"{args.url}/{args.id}/credential", json=data) + console.print(rsp) + console.print(rsp.json()) + + +parser_credential_add = subparsers.add_parser("addcred", help="Add a credential") +parser_credential_add.set_defaults(func=credential_add) +parser_credential_add.add_argument( + "--id", required=True, type=int, help="device ID to use" +) +parser_credential_add.add_argument( + "--credentialId", required=True, help="base64url-encoded credential ID" +) +parser_credential_add.add_argument( + "--isResidentCredential", + choices=["yes", "no", "true", "false"], + default="no", + help="indicate a resident key", +) +parser_credential_add.add_argument("--rpId", required=True, help="RP id (hostname)") +parser_credential_add.add_argument( + "--privateKey", required=True, help="base64url-encoded private key per RFC 5958" +) +parser_credential_add.add_argument("--userHandle", help="base64url-encoded user handle") +parser_credential_add.add_argument( + "--signCount", default=0, type=int, help="initial signature counter" +) + + +def credentials_get(args): + rsp = requests.get(f"{args.url}/{args.id}/credentials") + console.print(rsp) + console.print(rsp.json()) + + +parser_credentials_get = subparsers.add_parser("getcreds", help="Get credentials") +parser_credentials_get.set_defaults(func=credentials_get) +parser_credentials_get.add_argument("id", type=int, help="device ID to query") + + +def credential_delete(args): + rsp = requests.delete(f"{args.url}/{args.id}/credentials/{args.credentialId}") + console.print(rsp) + console.print(rsp.json()) + + +parser_credentials_get = subparsers.add_parser("delcred", help="Delete a credential") +parser_credentials_get.set_defaults(func=credential_delete) +parser_credentials_get.add_argument("id", type=int, help="device ID to affect") +parser_credentials_get.add_argument( + "credentialId", help="base64url-encoded credential ID" +) + + +def credentials_clear(args): + rsp = requests.delete(f"{args.url}/{args.id}/credentials") + console.print(rsp) + console.print(rsp.json()) + + +parser_credentials_get = subparsers.add_parser( + "clearcreds", help="Clear all credentials for a device" +) +parser_credentials_get.set_defaults(func=credentials_clear) +parser_credentials_get.add_argument("id", type=int, help="device ID to affect") + + +def main(): + args = parser.parse_args() + + loglevel = logging.INFO + if args.verbose > 0: + loglevel = logging.DEBUG + logging.basicConfig( + level=loglevel, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()] + ) + + try: + args.func(args) + except requests.exceptions.ConnectionError as ce: + log.error(f"Connection refused to {args.url}: {ce}") + + +if __name__ == "__main__": + main() |