summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/authenticator
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/authenticator/.cargo-checksum.json1
-rw-r--r--third_party/rust/authenticator/Cargo.lock1603
-rw-r--r--third_party/rust/authenticator/Cargo.toml99
-rw-r--r--third_party/rust/authenticator/Cross.toml5
-rw-r--r--third_party/rust/authenticator/LICENSE373
-rw-r--r--third_party/rust/authenticator/README.md50
-rw-r--r--third_party/rust/authenticator/build.rs54
-rw-r--r--third_party/rust/authenticator/examples/main.rs183
-rw-r--r--third_party/rust/authenticator/rustfmt.toml3
-rw-r--r--third_party/rust/authenticator/src/authenticatorservice.rs635
-rw-r--r--third_party/rust/authenticator/src/capi.rs377
-rw-r--r--third_party/rust/authenticator/src/consts.rs80
-rw-r--r--third_party/rust/authenticator/src/errors.rs96
-rw-r--r--third_party/rust/authenticator/src/freebsd/device.rs112
-rw-r--r--third_party/rust/authenticator/src/freebsd/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/freebsd/monitor.rs133
-rw-r--r--third_party/rust/authenticator/src/freebsd/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/freebsd/uhid.rs92
-rw-r--r--third_party/rust/authenticator/src/hidproto.rs253
-rw-r--r--third_party/rust/authenticator/src/lib.rs129
-rw-r--r--third_party/rust/authenticator/src/linux/device.rs112
-rw-r--r--third_party/rust/authenticator/src/linux/hidraw.rs80
-rw-r--r--third_party/rust/authenticator/src/linux/hidwrapper.h12
-rw-r--r--third_party/rust/authenticator/src/linux/hidwrapper.rs48
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_armle.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mips64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mipsle.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_x86.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_x86_64.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/mod.rs12
-rw-r--r--third_party/rust/authenticator/src/linux/monitor.rs168
-rw-r--r--third_party/rust/authenticator/src/linux/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/macos/device.rs156
-rw-r--r--third_party/rust/authenticator/src/macos/iokit.rs292
-rw-r--r--third_party/rust/authenticator/src/macos/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/macos/monitor.rs175
-rw-r--r--third_party/rust/authenticator/src/macos/transaction.rs92
-rw-r--r--third_party/rust/authenticator/src/manager.rs197
-rw-r--r--third_party/rust/authenticator/src/netbsd/device.rs159
-rw-r--r--third_party/rust/authenticator/src/netbsd/fd.rs47
-rw-r--r--third_party/rust/authenticator/src/netbsd/mod.rs10
-rw-r--r--third_party/rust/authenticator/src/netbsd/monitor.rs87
-rw-r--r--third_party/rust/authenticator/src/netbsd/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/netbsd/uhid.rs77
-rw-r--r--third_party/rust/authenticator/src/openbsd/device.rs148
-rw-r--r--third_party/rust/authenticator/src/openbsd/mod.rs8
-rw-r--r--third_party/rust/authenticator/src/openbsd/monitor.rs111
-rw-r--r--third_party/rust/authenticator/src/openbsd/transaction.rs52
-rw-r--r--third_party/rust/authenticator/src/statecallback.rs166
-rw-r--r--third_party/rust/authenticator/src/statemachine.rs283
-rw-r--r--third_party/rust/authenticator/src/stub/device.rs65
-rw-r--r--third_party/rust/authenticator/src/stub/mod.rs11
-rw-r--r--third_party/rust/authenticator/src/stub/transaction.rs31
-rw-r--r--third_party/rust/authenticator/src/u2fhid-capi.h111
-rw-r--r--third_party/rust/authenticator/src/u2fprotocol.rs457
-rw-r--r--third_party/rust/authenticator/src/u2ftypes.rs256
-rw-r--r--third_party/rust/authenticator/src/util.rs67
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/mod.rs8
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/software_u2f.rs58
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs140
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs157
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs965
-rw-r--r--third_party/rust/authenticator/src/windows/device.rs97
-rw-r--r--third_party/rust/authenticator/src/windows/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/windows/monitor.rs91
-rw-r--r--third_party/rust/authenticator/src/windows/transaction.rs52
-rw-r--r--third_party/rust/authenticator/src/windows/winapi.rs274
-rw-r--r--third_party/rust/authenticator/testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile8
-rw-r--r--third_party/rust/authenticator/testing/cross/x86_64-unknown-linux-gnu.Dockerfile7
-rwxr-xr-xthird_party/rust/authenticator/testing/run_cross.sh11
-rw-r--r--third_party/rust/authenticator/webdriver-tools/requirements.txt2
-rw-r--r--third_party/rust/authenticator/webdriver-tools/webdriver-driver.py207
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(&register_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(&register_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, &register_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(&param_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(&param_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()