From 4832b7d0f1820ffe1084dd2c4ec0742ac97a305b Mon Sep 17 00:00:00 2001 From: John Schanck Date: Tue, 6 Dec 2022 18:09:53 +0000 Subject: [PATCH] Bug 1530373 - vendor the latest version of authenticator. r=dveditz,supply-chain-reviewers Differential Revision: https://phabricator.services.mozilla.com/D163864 --- Cargo.lock | 48 +- supply-chain/config.toml | 4 - .../rust/authenticator/.cargo-checksum.json | 2 +- third_party/rust/authenticator/Cargo.lock | 1800 +++++++++-------- third_party/rust/authenticator/Cargo.toml | 130 +- third_party/rust/authenticator/build.rs | 2 + .../rust/authenticator/examples/ctap1.rs | 203 ++ .../rust/authenticator/examples/ctap2.rs | 290 +++ .../examples/ctap2_discoverable_creds.rs | 354 ++++ .../rust/authenticator/examples/main.rs | 78 +- .../rust/authenticator/examples/reset.rs | 137 ++ .../rust/authenticator/examples/set_pin.rs | 144 ++ .../examples/test_exclude_list.rs | 211 ++ .../authenticator/src/authenticatorservice.rs | 610 +++++- third_party/rust/authenticator/src/capi.rs | 61 +- third_party/rust/authenticator/src/consts.rs | 98 +- .../rust/authenticator/src/crypto/dummy.rs | 42 + .../rust/authenticator/src/crypto/mod.rs | 924 +++++++++ .../rust/authenticator/src/crypto/nss.rs | 543 +++++ .../rust/authenticator/src/crypto/openssl.rs | 283 +++ .../rust/authenticator/src/crypto/ring.rs | 168 ++ .../rust/authenticator/src/ctap2-capi.h | 254 +++ .../authenticator/src/ctap2/attestation.rs | 799 ++++++++ .../authenticator/src/ctap2/client_data.rs | 399 ++++ .../src/ctap2/commands/client_pin.rs | 784 +++++++ .../src/ctap2/commands/get_assertion.rs | 1282 ++++++++++++ .../src/ctap2/commands/get_info.rs | 733 +++++++ .../src/ctap2/commands/get_next_assertion.rs | 53 + .../src/ctap2/commands/get_version.rs | 118 ++ .../src/ctap2/commands/make_credentials.rs | 1033 ++++++++++ .../authenticator/src/ctap2/commands/mod.rs | 461 +++++ .../authenticator/src/ctap2/commands/reset.rs | 129 ++ .../src/ctap2/commands/selection.rs | 129 ++ .../rust/authenticator/src/ctap2/mod.rs | 9 + .../rust/authenticator/src/ctap2/server.rs | 510 +++++ .../rust/authenticator/src/ctap2/utils.rs | 14 + .../rust/authenticator/src/ctap2_capi.rs | 945 +++++++++ third_party/rust/authenticator/src/errors.rs | 44 + third_party/rust/authenticator/src/lib.rs | 77 +- .../rust/authenticator/src/linux/device.rs | 112 - third_party/rust/authenticator/src/manager.rs | 477 ++++- .../rust/authenticator/src/statemachine.rs | 906 +++++++-- .../rust/authenticator/src/status_update.rs | 101 + .../authenticator/src/stub/transaction.rs | 31 - .../src/transport/device_selector.rs | 514 +++++ .../authenticator/src/transport/errors.rs | 98 + .../src/{ => transport}/freebsd/device.rs | 50 +- .../src/{ => transport}/freebsd/mod.rs | 0 .../src/{ => transport}/freebsd/monitor.rs | 0 .../{ => transport}/freebsd/transaction.rs | 2 +- .../src/{ => transport}/freebsd/uhid.rs | 0 .../rust/authenticator/src/transport/hid.rs | 136 ++ .../src/{ => transport}/hidproto.rs | 0 .../src/transport/linux/device.rs | 182 ++ .../src/{ => transport}/linux/hidraw.rs | 2 +- .../src/{ => transport}/linux/hidwrapper.h | 0 .../src/{ => transport}/linux/hidwrapper.rs | 3 + .../{ => transport}/linux/ioctl_aarch64le.rs | 0 .../src/{ => transport}/linux/ioctl_armle.rs | 0 .../{ => transport}/linux/ioctl_mips64le.rs | 0 .../src/{ => transport}/linux/ioctl_mipsbe.rs | 0 .../src/{ => transport}/linux/ioctl_mipsle.rs | 0 .../linux/ioctl_powerpc64be.rs | 0 .../linux/ioctl_powerpc64le.rs | 0 .../{ => transport}/linux/ioctl_powerpcbe.rs | 0 .../linux/ioctl_riscv64.rs} | 0 .../linux/ioctl_s390xbe.rs} | 0 .../linux/ioctl_x86.rs} | 0 .../src/transport/linux/ioctl_x86_64.rs | 5 + .../src/{ => transport}/linux/mod.rs | 0 .../src/{ => transport}/linux/monitor.rs | 68 +- .../linux}/transaction.rs | 33 +- .../src/{ => transport}/macos/device.rs | 125 +- .../src/{ => transport}/macos/iokit.rs | 0 .../src/{ => transport}/macos/mod.rs | 0 .../src/{ => transport}/macos/monitor.rs | 51 +- .../src/{ => transport}/macos/transaction.rs | 28 +- .../src/transport/mock/device.rs | 194 ++ .../authenticator/src/transport/mock/mod.rs | 6 + .../src/transport/mock/transaction.rs | 35 + .../rust/authenticator/src/transport/mod.rs | 263 +++ .../src/{ => transport}/netbsd/device.rs | 14 +- .../src/{ => transport}/netbsd/fd.rs | 14 + .../src/{ => transport}/netbsd/mod.rs | 0 .../src/{ => transport}/netbsd/monitor.rs | 43 +- .../src/transport/netbsd/transaction.rs | 72 + .../src/{ => transport}/netbsd/uhid.rs | 2 +- .../src/{ => transport}/openbsd/device.rs | 2 +- .../src/{ => transport}/openbsd/mod.rs | 0 .../src/{ => transport}/openbsd/monitor.rs | 0 .../{ => transport}/openbsd/transaction.rs | 2 +- .../src/{ => transport}/stub/device.rs | 65 +- .../src/{ => transport}/stub/mod.rs | 0 .../src/transport/stub/transaction.rs | 52 + .../src/transport/windows/device.rs | 171 ++ .../src/{ => transport}/windows/mod.rs | 0 .../src/{ => transport}/windows/monitor.rs | 44 +- .../windows}/transaction.rs | 33 +- .../src/{ => transport}/windows/winapi.rs | 14 +- .../rust/authenticator/src/u2fhid-capi.h | 20 +- .../rust/authenticator/src/u2fprotocol.rs | 217 +- .../rust/authenticator/src/u2ftypes.rs | 151 +- third_party/rust/authenticator/src/util.rs | 12 +- .../src/virtualdevices/software_u2f.rs | 13 +- .../webdriver/virtualmanager.rs | 12 +- .../src/virtualdevices/webdriver/web_api.rs | 11 +- .../rust/authenticator/src/windows/device.rs | 97 - .../authenticator/src/windows/transaction.rs | 52 - third_party/rust/half/.cargo-checksum.json | 1 + third_party/rust/half/CHANGELOG.md | 248 +++ third_party/rust/half/Cargo.toml | 69 + third_party/rust/half/LICENSE | 1 + third_party/rust/half/LICENSES/Apache-2.0.txt | 73 + third_party/rust/half/LICENSES/MIT.txt | 9 + third_party/rust/half/Makefile.toml | 58 + third_party/rust/half/README.md | 82 + third_party/rust/half/benches/convert.rs | 327 +++ third_party/rust/half/src/bfloat.rs | 1555 ++++++++++++++ third_party/rust/half/src/bfloat/convert.rs | 135 ++ third_party/rust/half/src/binary16.rs | 1711 ++++++++++++++++ third_party/rust/half/src/binary16/convert.rs | 491 +++++ third_party/rust/half/src/lib.rs | 217 ++ third_party/rust/half/src/num_traits.rs | 1461 +++++++++++++ third_party/rust/half/src/slice.rs | 952 +++++++++ third_party/rust/half/src/vec.rs | 286 +++ .../rust/nss-gk-api/.cargo-checksum.json | 1 + third_party/rust/nss-gk-api/Cargo.toml | 54 + third_party/rust/nss-gk-api/README.md | 6 + .../rust/nss-gk-api/bindings/bindings.toml | 280 +++ .../rust/nss-gk-api/bindings/mozpkix.hpp | 1 + .../rust/nss-gk-api/bindings/nspr_err.h | 7 + .../rust/nss-gk-api/bindings/nspr_error.h | 7 + .../rust/nss-gk-api/bindings/nspr_io.h | 7 + .../rust/nss-gk-api/bindings/nspr_time.h | 7 + .../rust/nss-gk-api/bindings/nspr_types.h | 7 + .../rust/nss-gk-api/bindings/nss_ciphers.h | 8 + .../rust/nss-gk-api/bindings/nss_init.h | 8 + .../rust/nss-gk-api/bindings/nss_p11.h | 9 + .../rust/nss-gk-api/bindings/nss_prelude.h | 8 + .../rust/nss-gk-api/bindings/nss_secerr.h | 7 + .../rust/nss-gk-api/bindings/nss_ssl.h | 9 + .../rust/nss-gk-api/bindings/nss_sslerr.h | 7 + .../rust/nss-gk-api/bindings/nss_sslopt.h | 7 + third_party/rust/nss-gk-api/build.rs | 445 ++++ third_party/rust/nss-gk-api/src/err.rs | 257 +++ third_party/rust/nss-gk-api/src/exp.rs | 25 + third_party/rust/nss-gk-api/src/lib.rs | 173 ++ third_party/rust/nss-gk-api/src/p11.rs | 194 ++ third_party/rust/nss-gk-api/src/prio.rs | 21 + third_party/rust/nss-gk-api/src/prtypes.rs | 22 + third_party/rust/nss-gk-api/src/ssl.rs | 157 ++ third_party/rust/nss-gk-api/src/time.rs | 255 +++ third_party/rust/nss-gk-api/src/util.rs | 246 +++ .../rust/serde_cbor/.cargo-checksum.json | 1 + third_party/rust/serde_cbor/CONTRIBUTING.md | 29 + third_party/rust/serde_cbor/Cargo.lock | 68 + third_party/rust/serde_cbor/Cargo.toml | 44 + third_party/rust/serde_cbor/LICENSE-APACHE | 201 ++ third_party/rust/serde_cbor/LICENSE-MIT | 19 + third_party/rust/serde_cbor/README.md | 97 + .../rust/serde_cbor/examples/readme.rs | 39 + third_party/rust/serde_cbor/examples/tags.rs | 84 + third_party/rust/serde_cbor/examples/tux.cbor | 1 + third_party/rust/serde_cbor/src/de.rs | 1360 +++++++++++++ third_party/rust/serde_cbor/src/error.rs | 318 +++ third_party/rust/serde_cbor/src/lib.rs | 369 ++++ third_party/rust/serde_cbor/src/read.rs | 637 ++++++ third_party/rust/serde_cbor/src/ser.rs | 743 +++++++ third_party/rust/serde_cbor/src/tags.rs | 220 ++ third_party/rust/serde_cbor/src/value/de.rs | 166 ++ third_party/rust/serde_cbor/src/value/mod.rs | 156 ++ third_party/rust/serde_cbor/src/value/ser.rs | 443 ++++ third_party/rust/serde_cbor/src/write.rs | 175 ++ third_party/rust/serde_cbor/tests/bennofs.rs | 60 + .../rust/serde_cbor/tests/canonical.rs | 104 + third_party/rust/serde_cbor/tests/crash.cbor | Bin 0 -> 190 bytes third_party/rust/serde_cbor/tests/de.rs | 747 +++++++ third_party/rust/serde_cbor/tests/enum.rs | 236 +++ .../rust/serde_cbor/tests/kietaub.cbor | Bin 0 -> 212 bytes third_party/rust/serde_cbor/tests/ser.rs | 254 +++ .../rust/serde_cbor/tests/std_types.rs | 186 ++ third_party/rust/serde_cbor/tests/tags.rs | 48 + third_party/rust/serde_cbor/tests/value.rs | 100 + toolkit/library/rust/shared/Cargo.toml | 2 +- 184 files changed, 33321 insertions(+), 1937 deletions(-) create mode 100644 third_party/rust/authenticator/examples/ctap1.rs create mode 100644 third_party/rust/authenticator/examples/ctap2.rs create mode 100644 third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs create mode 100644 third_party/rust/authenticator/examples/reset.rs create mode 100644 third_party/rust/authenticator/examples/set_pin.rs create mode 100644 third_party/rust/authenticator/examples/test_exclude_list.rs create mode 100644 third_party/rust/authenticator/src/crypto/dummy.rs create mode 100644 third_party/rust/authenticator/src/crypto/mod.rs create mode 100644 third_party/rust/authenticator/src/crypto/nss.rs create mode 100644 third_party/rust/authenticator/src/crypto/openssl.rs create mode 100644 third_party/rust/authenticator/src/crypto/ring.rs create mode 100644 third_party/rust/authenticator/src/ctap2-capi.h create mode 100644 third_party/rust/authenticator/src/ctap2/attestation.rs create mode 100644 third_party/rust/authenticator/src/ctap2/client_data.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/client_pin.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/get_info.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/get_next_assertion.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/get_version.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/mod.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/reset.rs create mode 100644 third_party/rust/authenticator/src/ctap2/commands/selection.rs create mode 100644 third_party/rust/authenticator/src/ctap2/mod.rs create mode 100644 third_party/rust/authenticator/src/ctap2/server.rs create mode 100644 third_party/rust/authenticator/src/ctap2/utils.rs create mode 100644 third_party/rust/authenticator/src/ctap2_capi.rs delete mode 100644 third_party/rust/authenticator/src/linux/device.rs create mode 100644 third_party/rust/authenticator/src/status_update.rs delete mode 100644 third_party/rust/authenticator/src/stub/transaction.rs create mode 100644 third_party/rust/authenticator/src/transport/device_selector.rs create mode 100644 third_party/rust/authenticator/src/transport/errors.rs rename third_party/rust/authenticator/src/{ => transport}/freebsd/device.rs (68%) rename third_party/rust/authenticator/src/{ => transport}/freebsd/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/freebsd/monitor.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/freebsd/transaction.rs (97%) rename third_party/rust/authenticator/src/{ => transport}/freebsd/uhid.rs (100%) create mode 100644 third_party/rust/authenticator/src/transport/hid.rs rename third_party/rust/authenticator/src/{ => transport}/hidproto.rs (100%) create mode 100644 third_party/rust/authenticator/src/transport/linux/device.rs rename third_party/rust/authenticator/src/{ => transport}/linux/hidraw.rs (98%) rename third_party/rust/authenticator/src/{ => transport}/linux/hidwrapper.h (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/hidwrapper.rs (94%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_aarch64le.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_armle.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_mips64le.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_mipsbe.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_mipsle.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_powerpc64be.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_powerpc64le.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/ioctl_powerpcbe.rs (100%) rename third_party/rust/authenticator/src/{linux/ioctl_s390xbe.rs => transport/linux/ioctl_riscv64.rs} (100%) rename third_party/rust/authenticator/src/{linux/ioctl_x86.rs => transport/linux/ioctl_s390xbe.rs} (100%) rename third_party/rust/authenticator/src/{linux/ioctl_x86_64.rs => transport/linux/ioctl_x86.rs} (100%) create mode 100644 third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs rename third_party/rust/authenticator/src/{ => transport}/linux/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/linux/monitor.rs (65%) rename third_party/rust/authenticator/src/{netbsd => transport/linux}/transaction.rs (56%) rename third_party/rust/authenticator/src/{ => transport}/macos/device.rs (56%) rename third_party/rust/authenticator/src/{ => transport}/macos/iokit.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/macos/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/macos/monitor.rs (75%) rename third_party/rust/authenticator/src/{ => transport}/macos/transaction.rs (77%) create mode 100644 third_party/rust/authenticator/src/transport/mock/device.rs create mode 100644 third_party/rust/authenticator/src/transport/mock/mod.rs create mode 100644 third_party/rust/authenticator/src/transport/mock/transaction.rs create mode 100644 third_party/rust/authenticator/src/transport/mod.rs rename third_party/rust/authenticator/src/{ => transport}/netbsd/device.rs (92%) rename third_party/rust/authenticator/src/{ => transport}/netbsd/fd.rs (79%) rename third_party/rust/authenticator/src/{ => transport}/netbsd/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/netbsd/monitor.rs (62%) create mode 100644 third_party/rust/authenticator/src/transport/netbsd/transaction.rs rename third_party/rust/authenticator/src/{ => transport}/netbsd/uhid.rs (98%) rename third_party/rust/authenticator/src/{ => transport}/openbsd/device.rs (98%) rename third_party/rust/authenticator/src/{ => transport}/openbsd/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/openbsd/monitor.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/openbsd/transaction.rs (96%) rename third_party/rust/authenticator/src/{ => transport}/stub/device.rs (51%) rename third_party/rust/authenticator/src/{ => transport}/stub/mod.rs (100%) create mode 100644 third_party/rust/authenticator/src/transport/stub/transaction.rs create mode 100644 third_party/rust/authenticator/src/transport/windows/device.rs rename third_party/rust/authenticator/src/{ => transport}/windows/mod.rs (100%) rename third_party/rust/authenticator/src/{ => transport}/windows/monitor.rs (57%) rename third_party/rust/authenticator/src/{linux => transport/windows}/transaction.rs (56%) rename third_party/rust/authenticator/src/{ => transport}/windows/winapi.rs (94%) delete mode 100644 third_party/rust/authenticator/src/windows/device.rs delete mode 100644 third_party/rust/authenticator/src/windows/transaction.rs create mode 100644 third_party/rust/half/.cargo-checksum.json create mode 100644 third_party/rust/half/CHANGELOG.md create mode 100644 third_party/rust/half/Cargo.toml create mode 100644 third_party/rust/half/LICENSE create mode 100644 third_party/rust/half/LICENSES/Apache-2.0.txt create mode 100644 third_party/rust/half/LICENSES/MIT.txt create mode 100644 third_party/rust/half/Makefile.toml create mode 100644 third_party/rust/half/README.md create mode 100644 third_party/rust/half/benches/convert.rs create mode 100644 third_party/rust/half/src/bfloat.rs create mode 100644 third_party/rust/half/src/bfloat/convert.rs create mode 100644 third_party/rust/half/src/binary16.rs create mode 100644 third_party/rust/half/src/binary16/convert.rs create mode 100644 third_party/rust/half/src/lib.rs create mode 100644 third_party/rust/half/src/num_traits.rs create mode 100644 third_party/rust/half/src/slice.rs create mode 100644 third_party/rust/half/src/vec.rs create mode 100644 third_party/rust/nss-gk-api/.cargo-checksum.json create mode 100644 third_party/rust/nss-gk-api/Cargo.toml create mode 100644 third_party/rust/nss-gk-api/README.md create mode 100644 third_party/rust/nss-gk-api/bindings/bindings.toml create mode 100644 third_party/rust/nss-gk-api/bindings/mozpkix.hpp create mode 100644 third_party/rust/nss-gk-api/bindings/nspr_err.h create mode 100644 third_party/rust/nss-gk-api/bindings/nspr_error.h create mode 100644 third_party/rust/nss-gk-api/bindings/nspr_io.h create mode 100644 third_party/rust/nss-gk-api/bindings/nspr_time.h create mode 100644 third_party/rust/nss-gk-api/bindings/nspr_types.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_ciphers.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_init.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_p11.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_prelude.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_secerr.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_ssl.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_sslerr.h create mode 100644 third_party/rust/nss-gk-api/bindings/nss_sslopt.h create mode 100644 third_party/rust/nss-gk-api/build.rs create mode 100644 third_party/rust/nss-gk-api/src/err.rs create mode 100644 third_party/rust/nss-gk-api/src/exp.rs create mode 100644 third_party/rust/nss-gk-api/src/lib.rs create mode 100644 third_party/rust/nss-gk-api/src/p11.rs create mode 100644 third_party/rust/nss-gk-api/src/prio.rs create mode 100644 third_party/rust/nss-gk-api/src/prtypes.rs create mode 100644 third_party/rust/nss-gk-api/src/ssl.rs create mode 100644 third_party/rust/nss-gk-api/src/time.rs create mode 100644 third_party/rust/nss-gk-api/src/util.rs create mode 100644 third_party/rust/serde_cbor/.cargo-checksum.json create mode 100644 third_party/rust/serde_cbor/CONTRIBUTING.md create mode 100644 third_party/rust/serde_cbor/Cargo.lock create mode 100644 third_party/rust/serde_cbor/Cargo.toml create mode 100644 third_party/rust/serde_cbor/LICENSE-APACHE create mode 100644 third_party/rust/serde_cbor/LICENSE-MIT create mode 100644 third_party/rust/serde_cbor/README.md create mode 100644 third_party/rust/serde_cbor/examples/readme.rs create mode 100644 third_party/rust/serde_cbor/examples/tags.rs create mode 100644 third_party/rust/serde_cbor/examples/tux.cbor create mode 100644 third_party/rust/serde_cbor/src/de.rs create mode 100644 third_party/rust/serde_cbor/src/error.rs create mode 100644 third_party/rust/serde_cbor/src/lib.rs create mode 100644 third_party/rust/serde_cbor/src/read.rs create mode 100644 third_party/rust/serde_cbor/src/ser.rs create mode 100644 third_party/rust/serde_cbor/src/tags.rs create mode 100644 third_party/rust/serde_cbor/src/value/de.rs create mode 100644 third_party/rust/serde_cbor/src/value/mod.rs create mode 100644 third_party/rust/serde_cbor/src/value/ser.rs create mode 100644 third_party/rust/serde_cbor/src/write.rs create mode 100644 third_party/rust/serde_cbor/tests/bennofs.rs create mode 100644 third_party/rust/serde_cbor/tests/canonical.rs create mode 100644 third_party/rust/serde_cbor/tests/crash.cbor create mode 100644 third_party/rust/serde_cbor/tests/de.rs create mode 100644 third_party/rust/serde_cbor/tests/enum.rs create mode 100644 third_party/rust/serde_cbor/tests/kietaub.cbor create mode 100644 third_party/rust/serde_cbor/tests/ser.rs create mode 100644 third_party/rust/serde_cbor/tests/std_types.rs create mode 100644 third_party/rust/serde_cbor/tests/tags.rs create mode 100644 third_party/rust/serde_cbor/tests/value.rs diff --git a/Cargo.lock b/Cargo.lock index 7a0012d3fd63..a3283b6a16ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,18 +378,29 @@ dependencies = [ [[package]] name = "authenticator" -version = "0.3.1" +version = "0.4.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08cee7a0952628fde958e149507c2bb321ab4fccfafd225da0b20adc956ef88a" +checksum = "671c5d49eab8c93b8aea310cef8a7fd0846eb9417e3c31e4f4d6ec7012aae842" dependencies = [ + "base64", "bitflags", + "cfg-if 1.0.0", "core-foundation", "devd-rs", "libc", "libudev", "log", - "rand 0.7.999", + "memoffset 0.6.5", + "nom 7.1.1", + "nss-gk-api", + "pkcs11-bindings", + "rand 0.8.5", "runloop", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "sha2", "winapi", ] @@ -2461,6 +2472,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.11.999" @@ -3770,6 +3787,21 @@ dependencies = [ "nsstring", ] +[[package]] +name = "nss-gk-api" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352" +dependencies = [ + "bindgen 0.63.0", + "mozbuild", + "once_cell", + "pkcs11-bindings", + "serde", + "serde_derive", + "toml", +] + [[package]] name = "nss_build_common" version = "0.1.0" @@ -4763,6 +4795,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.144" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 3f1ca8a97eb1..f31969195be8 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -243,10 +243,6 @@ criteria = "safe-to-deploy" version = "0.26.1" criteria = "safe-to-deploy" -[[exemptions.authenticator]] -version = "0.3.1" -criteria = "safe-to-deploy" - [[exemptions.base64]] version = "0.13.0" criteria = "safe-to-deploy" diff --git a/third_party/rust/authenticator/.cargo-checksum.json b/third_party/rust/authenticator/.cargo-checksum.json index ce451ad09df4..4cff4c5baefa 100644 --- a/third_party/rust/authenticator/.cargo-checksum.json +++ b/third_party/rust/authenticator/.cargo-checksum.json @@ -1 +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 +{"files":{"Cargo.lock":"78495716eef6d717573731a0ebb1cd9e08aa8de836f6d909fd01b18394d296ac","Cargo.toml":"c34624d02a27468747e0a911e3e9cd47c84992c047357cad7d3c9a271ea5cdeb","Cross.toml":"8d132da818d48492aa9f4b78a348f0df3adfae45d988d42ebd6be8a5adadb6c3","LICENSE":"e866c8f5864d4cacfe403820e722e9dc03fe3c7565efa5e4dad9051d827bb92a","README.md":"c87d9c7cc44f1dd4ef861a3a9f8cd2eb68aedd3814768871f5fb63c2070806cd","build.rs":"a459ee1ace052f9692817b15c702cb6e5a6dac7c7dfe74fa075662dbcf808dbe","examples/ctap1.rs":"5d88094250acf7d27781584609210e1e1dcebd829aa2bcc46b33946365708c92","examples/ctap2.rs":"2eafc8a046cf1e08d667b47a7b51fd4f906f06e591308bb4943b2e014e7d4d90","examples/ctap2_discoverable_creds.rs":"0a78458a6e739d44a0c73f94f22ac38515b6fde946519e35fe39b7bb8b412d41","examples/main.rs":"26550f1829ba04872837b013c1a2b824d9baebdee6100fc3d8d9c34457699c6b","examples/reset.rs":"7799bdf8643a77b8d067cbc28c00cd7e5d5bb7aea28e8aa33e880f1a67f01507","examples/set_pin.rs":"7b25cdbe1a083cd2b61546816cc840d3522340916d5f936dace2c3eace6aa069","examples/test_exclude_list.rs":"89cfe3ad30db2847db0e0d5c2012a11bcca6a909ebf160e66cb1b60046a3ec22","rustfmt.toml":"ceb6615363d6fff16426eb56f5727f98a7f7ed459ba9af735b1d8b672e2c3b9b","src/authenticatorservice.rs":"337ac3b45180a2b97b5b463d145fd3abb0a66976ff4fd2e8f8793fd1da48defb","src/capi.rs":"0dcb1b853e8c950a45103f646315b5d8d6bf4b94c45ff4bc19ec3d29b4b686a6","src/consts.rs":"8c03c2608a0b71910d154c56c81d070f5c27988d386bed3d090b1690b248e1c9","src/crypto/dummy.rs":"85eca89d6d7290b5b98325e8b502a3dcf8a36814571c41e6cbc32aaaf9052cba","src/crypto/mod.rs":"7cf6e730050dd9109c389ea177d17e2f781cf1328ab2cdf2c55e2f9e3922509e","src/crypto/nss.rs":"1d3b44b1708c80b5d5811ee97a45b0593503537c8a375e9fff0229aaa8d10255","src/crypto/openssl.rs":"7ef05e1d46dda414c3894f354a61343685c3a862ef3164ac6788d3d3e20b28e2","src/crypto/ring.rs":"dd93b8bfafd1c35c7f23b66fb4881b884a60c7fcff964ae8966ff260e283b7c2","src/ctap2-capi.h":"841b27358112c453dabeb06c16c7e58a8b2c6d8c685d8bd8a9af63d9a76a00b2","src/ctap2/attestation.rs":"82c69f81f99db2ba872f74230057bfce20a5affcb27e6f942917e9563f8cad92","src/ctap2/client_data.rs":"9533ee4469109c25ecd093a7e61175f2ea7dede7ccc351f99da573bc422db249","src/ctap2/commands/client_pin.rs":"4d67dbe754dc132de5a65a437e5304459fde0c439ac3591de82dfaefb37d02b9","src/ctap2/commands/get_assertion.rs":"4cd603ddc5c0d8a5e53850c751ce6ae582d4694ff06a3bd9efe0a2e6301c72f3","src/ctap2/commands/get_info.rs":"16e2b7595c8d13cb213d821d80be6e63264fe114ddc8c18a644df9ea1c9bd6fc","src/ctap2/commands/get_next_assertion.rs":"1120b0301197ada11751c4c42fc0a4147368ec5427f62da3868347567d28ba3a","src/ctap2/commands/get_version.rs":"1ea96278bfbdcaff164209d9082a78af5d9cecf44eef7b692e6054bc31ac892d","src/ctap2/commands/make_credentials.rs":"d741148f1519e67683bf29e70d0ab2e045ed676ad628650012a6a12051b434f0","src/ctap2/commands/mod.rs":"2ae842f41730e2b31546e18f913146aceced68cafd7390273493fcc996493d8f","src/ctap2/commands/reset.rs":"006396cc8361ce3d72a28f7578254c02aae63dbb98ce326dc844b3b065b0f97c","src/ctap2/commands/selection.rs":"be878778fbb377976ef61c6d8001c0f69e084ee05ffca1a620057960b548458a","src/ctap2/mod.rs":"dc278046fb6df377cd0fb00aac8b2ed94f19669f3529bc5792dff13605a0cbfc","src/ctap2/server.rs":"64e20830b4d1517875807514535a8a8fae4a91491e92635200ab242a822ef70b","src/ctap2/utils.rs":"ad0aa36a0dbeb510b7f37789329f1957eab206eb529dc083e6176b142984e26e","src/ctap2_capi.rs":"492598c6d1e93a900c7076cfb551c9ff210bdf9323e49f7cb59de4da2cba8268","src/errors.rs":"5f4620dbb1da549ae0c12f49c260c15a33d0ecf0f9f97c018a0e40570f806278","src/lib.rs":"03903890773e6526b667ca961bbec1dadff83922fce54cce1a65da9df617d389","src/manager.rs":"9ef43a69c583e8768d2b161540c7dac289213ad0e0c55ffbbd6d5ea775f74089","src/statecallback.rs":"6b16f97176db1ae3fc3851fe8394e4ffc324bc6fe59313845ac3a88132fd52f1","src/statemachine.rs":"d7d07c512bcf9f4a054982822a13ef87a72d308646d701f7ec878810ea61b9e3","src/status_update.rs":"ccea47e8c2c99450ed397fe8feddba0f2e63705f08b025876afde1d2d54a1162","src/transport/device_selector.rs":"4914fe559f61a3d4aab7e15b9f8ba428f2ea908b6b7bb662ada7b12e60c310a4","src/transport/errors.rs":"0b7e4c3af8cca7d024c9031879ca3a750fd28d4e8dac6e58f5ecb65267f79a0e","src/transport/freebsd/device.rs":"959f2c43a52fabbf04ef0d2adf610bad126fb078141cef95eb813aceb813e59e","src/transport/freebsd/mod.rs":"42dcb57fbeb00140003a8ad39acac9b547062b8f281a3fa5deb5f92a6169dde6","src/transport/freebsd/monitor.rs":"c10b154632fbedc3dca27197f7fc890c3d50ac1744b927e9f1e44a9e8a13506e","src/transport/freebsd/transaction.rs":"eaca1e478eb1c22c908669fc8e1f815bbf4410afdb230ac06a99203d55c99d43","src/transport/freebsd/uhid.rs":"84f564d337637c1cd107ccc536b8fce2230628e144e4031e8db4d7163c9c0cb3","src/transport/hid.rs":"24248cf56e3617e8043c1e81f2fb3d5e7f93b30080f0dc4ebaf78e1ba275a9d5","src/transport/hidproto.rs":"362fc8e24b94ba431aad5ee0002f5a3364badd937c706c0ae119a5a7a2abc7c2","src/transport/linux/device.rs":"7c9ecb1cc5cb4d6191466a92c26a5240db9211a590d42cd43366393728c32676","src/transport/linux/hidraw.rs":"c7a0df9b4e51cb2736218ffffa02b2b2547b7c515d69f9bae2c9a8c8f1cb547b","src/transport/linux/hidwrapper.h":"72785db3a9b27ea72b6cf13a958fee032af54304522d002f56322473978a20f9","src/transport/linux/hidwrapper.rs":"753c7459dbb73befdd186b6269ac33f7a4537b4c935928f50f2b2131756e787d","src/transport/linux/ioctl_aarch64le.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_armle.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_mips64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsle.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64be.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpcbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_riscv64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_s390xbe.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86_64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/mod.rs":"446e435126d2a58f167f648dd95cba28e8ac9c17f1f799e1eaeab80ea800fc57","src/transport/linux/monitor.rs":"448c6e0b7ef649ee0d1253594e1648639d3a5b5616fbf8228d81bd0816bc562c","src/transport/linux/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/macos/device.rs":"79eb3cd92e87dca17127dacf20a900360cade53ddd821ebf09ce7010f5118e35","src/transport/macos/iokit.rs":"7dc4e7bbf8e42e2fcde0cee8e48d14d6234a5a910bd5d3c4e966d8ba6b73992f","src/transport/macos/mod.rs":"333e561554fc901d4f6092f6e4c85823e2b0c4ff31c9188d0e6d542b71a0a07c","src/transport/macos/monitor.rs":"7a14f01634f2a604e22bce3bdb78a89bd0ca49f228f45c29a75f3c38dffd4912","src/transport/macos/transaction.rs":"b034104484c6864668425bbd4499540c3117b0de2e80b0b03ee452d00323ed50","src/transport/mock/device.rs":"56ccd61501034b618de74209dfbc86d82701d5323855b1573bc91a704ae716c5","src/transport/mock/mod.rs":"9c4c87efd19adddc1a91c699a6c328063cfbac5531b76346a5ff92e986aded8f","src/transport/mock/transaction.rs":"be3ed8c389dfa04122364b82515edd76fad6f5d5f72d15cacd45a84fb8397292","src/transport/mod.rs":"0c088c6bf0932155d52bc3b58f0b8823455a389dff159693647fb4bc53b20341","src/transport/netbsd/device.rs":"106262e36bac76c9e2c0a2db92bb23975be051f8b3653c082f859eb9fc0818cd","src/transport/netbsd/fd.rs":"39025cc01e8e894b8c3105aa9f91550e4f755d28b3af6cfe00d256280be025db","src/transport/netbsd/mod.rs":"b1c52aa29537330cebe67427062d6c94871cab2a9b0c04b2305d686f07e88fd5","src/transport/netbsd/monitor.rs":"8ef8a69c3061a238ace5b0752fa2a263096754b40ea381bc5573542234019b0c","src/transport/netbsd/transaction.rs":"48bd87aa447b133e33fd97c33b308e3de51154af05d7414dd2088f70554d49e2","src/transport/netbsd/uhid.rs":"4ea06d0877a306ec30e8ad25d7e2b75e5846505722391b2453a88996446563eb","src/transport/openbsd/device.rs":"ec7f4b8cc70d6ee0d772c73a9d5c0466d5cc394b22ac0b8864ca3c5da5f3988b","src/transport/openbsd/mod.rs":"514274d414042ff84b3667a41a736e78581e22fda87ccc97c2bc05617e381a30","src/transport/openbsd/monitor.rs":"5eb071dd3719ea305eac21ec20596463f63790f8cd1f908a59e3f9cb0b71b5ad","src/transport/openbsd/transaction.rs":"df504c8a02d4b037d4021999876e82562dc382399b3c496c30056669708657ef","src/transport/stub/device.rs":"3e05c88d521828315657d8d864b1ab72e7247eaeb5b8f0e1c6188ce89510b8d6","src/transport/stub/mod.rs":"6a7fec504a52d403b0241b18cd8b95088a31807571f4c0a67e4055afc74f4453","src/transport/stub/transaction.rs":"a3dbea5ccf691cff69278ed8a8ba67d27baba0ea2e1c6a53c9b0b04027283221","src/transport/windows/device.rs":"ca2b5c0e95e8812898169a4f9a1d1fdda9eb5886892429d3c2f6911dc20a2bab","src/transport/windows/mod.rs":"218e7f2fe91ecb390c12bba5a5ffdad2c1f0b22861c937f4d386262e5b3dd617","src/transport/windows/monitor.rs":"f0d70393f2e640c6a3354bc54a7a97216d7b6ad4167f22ad5a5013f39d96457b","src/transport/windows/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/windows/winapi.rs":"23636b523058c0ea87a12d642d0b75fc9ed05cd512c15db6ccca65c3b00d3a76","src/u2fhid-capi.h":"30e37cbc746b682400fcddcede29527635d122fea01e21413e0b63bb58ba4d75","src/u2fprotocol.rs":"858f70d15a3de5a4368b0c5c6e6c919d00b5d378cde66b35cb8f13f825835fad","src/u2ftypes.rs":"96ceec55bb1c31dac2d9ea233de7e66523b43b461f67599c0f81ce4a23cedbc7","src/util.rs":"8332fd9c37611993321ee0b43c0f7364ac16ee4df319b4f855c91a5fdf5e4ba0","src/virtualdevices/mod.rs":"2c7df7691d5c150757304241351612aed4260d65b70ab0f483edbc1a5cfb5674","src/virtualdevices/software_u2f.rs":"83e63c0c4a597e71d87b5cd1f33a49646d00b3062edbdd05c51623b80fb60168","src/virtualdevices/webdriver/mod.rs":"4a36e6dfa9f45f941d863b4039bfbcfa8eaca660bd6ed78aeb1a2962db64be5a","src/virtualdevices/webdriver/testtoken.rs":"7146e02f1a5dad2c8827dd11c12ee408c0e42a0706ac65f139998feffd42570f","src/virtualdevices/webdriver/virtualmanager.rs":"7205a0397833628fc0847aa942a6a314dc1e23306858b546053e0de6a360ebe1","src/virtualdevices/webdriver/web_api.rs":"9032525af458b6fe9a3274c36b6ef8c791ecc4ec46d38ae36583fc9a4535b59d","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":"671c5d49eab8c93b8aea310cef8a7fd0846eb9417e3c31e4f4d6ec7012aae842"} \ No newline at end of file diff --git a/third_party/rust/authenticator/Cargo.lock b/third_party/rust/authenticator/Cargo.lock index 9f284b468dea..50a882082d95 100644 --- a/third_party/rust/authenticator/Cargo.lock +++ b/third_party/rust/authenticator/Cargo.lock @@ -1,1603 +1,1693 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "assert_matches" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 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)", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "authenticator" -version = "0.3.1" +version = "0.4.0-alpha.3" 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)", + "assert_matches", + "base64", + "bindgen 0.58.1", + "bitflags", + "bytes 0.5.6", + "cfg-if", + "core-foundation", + "devd-rs", + "env_logger 0.6.2", + "getopts", + "libc", + "libudev", + "log", + "memoffset", + "nom 7.1.1", + "nss-gk-api", + "openssl", + "openssl-sys", + "pkcs11-bindings", + "rand", + "ring", + "rpassword", + "runloop", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "sha2", + "tokio", + "warp", + "winapi", ] [[package]] name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[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" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bindgen" -version = "0.51.1" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" 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)", + "bitflags", + "cexpr 0.4.0", + "clang-sys", + "clap", + "env_logger 0.8.4", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bindgen" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a022e58a142a46fea340d68012b9201c094e93ec3d033a944a24f8fd4a4f09a" +dependencies = [ + "bitflags", + "cexpr 0.6.0", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", ] [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 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)", + "generic-array", ] [[package]] name = "buf_redux" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" 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)", + "memchr", + "safemem", ] [[package]] -name = "byte-tools" -version = "0.3.1" +name = "bumpalo" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" dependencies = [ - "serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] -name = "cc" -version = "1.0.58" +name = "bytes" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cc" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cexpr" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ - "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 5.1.2", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.1", ] [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "0.28.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" 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)", + "glob", + "libc", + "libloading", ] [[package]] name = "clap" -version = "2.33.1" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 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)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "core-foundation" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 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)", + "core-foundation-sys", + "libc", ] [[package]] name = "core-foundation-sys" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "cpufeatures" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] [[package]] name = "devd-rs" -version = "0.3.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9313f104b590510b46fc01c0a324fc76505c13871454d3c48490468d04c8d395" 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)", + "libc", + "nom 7.1.1", ] [[package]] name = "digest" -version = "0.8.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "crypto-common", ] -[[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" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" 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)", + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", ] [[package]] -name = "fake-simd" -version = "0.1.2" +name = "env_logger" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime 2.1.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[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" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 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)", + "foreign-types-shared", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "futures" -version = "0.3.5" +name = "form_urlencoded" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 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)", + "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.5" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 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-core", + "futures-sink", ] [[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" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-sink" -version = "0.3.5" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.5" +version = "0.3.25" 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)", -] +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.5" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 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)", + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 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)", + "typenum", + "version_check", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 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)", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.2.6" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 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)", + "bytes 1.2.1", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "hashbrown" -version = "0.9.0" +name = "half" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "headers" -version = "0.3.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" 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)", + "base64", + "bitflags", + "bytes 1.2.1", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", ] [[package]] name = "headers-core" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "http", ] [[package]] name = "hermit-abi" -version = "0.1.15" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ - "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "http" -version = "0.2.1" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 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)", + "bytes 1.2.1", + "fnv", + "itoa", ] [[package]] name = "http-body" -version = "0.3.1" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 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)", + "bytes 1.2.1", + "http", + "pin-project-lite", ] [[package]] name = "httparse" -version = "1.3.4" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error", ] [[package]] -name = "hyper" -version = "0.13.7" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" 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)", + "bytes 1.2.1", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] name = "idna" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 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)", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.6.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 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)", + "autocfg", + "hashbrown", ] [[package]] -name = "input_buffer" -version = "0.3.1" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 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)", + "cfg-if", ] [[package]] name = "itoa" -version = "0.4.6" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "js-sys" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 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)", + "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.73" +version = "0.2.136" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" [[package]] name = "libloading" -version = "0.5.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 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)", + "cfg-if", + "winapi", ] [[package]] name = "libudev" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" 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)", + "libc", + "libudev-sys", ] [[package]] name = "libudev-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" 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)", + "libc", + "pkg-config", ] [[package]] name = "log" -version = "0.4.11" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] -name = "matches" -version = "0.1.8" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "memchr" -version = "2.3.3" +name = "memoffset" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 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)", + "mime", + "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" -version = "0.6.22" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 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)", + "libc", + "log", + "wasi", + "windows-sys", ] [[package]] -name = "miow" -version = "0.2.1" +name = "mozbuild" +version = "0.1.0" 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)", -] +checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616" [[package]] name = "multipart" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" 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)", + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", ] [[package]] name = "nom" version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 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)", + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nss-gk-api" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352" +dependencies = [ + "bindgen 0.61.0", + "mozbuild", + "once_cell", + "pkcs11-bindings", + "serde", + "serde_derive", + "toml", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] name = "once_cell" -version = "1.4.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] -name = "opaque-debug" -version = "0.2.3" +name = "openssl" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "openssl-macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "0.4.23" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ - "pin-project-internal 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.23" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 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)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.1.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs11-bindings" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20556de5b64f5d7213b8ea103b92261cac789b59978652d9cd831ba9f477c53" +dependencies = [ + "bindgen 0.61.0", +] [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-ident", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.7" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ - "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rand" -version = "0.6.5" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 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)", + "libc", + "rand_chacha", + "rand_core", ] [[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" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core", ] [[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" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 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)", + "getrandom", ] [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] [[package]] name = "regex" -version = "1.3.9" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 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)", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.18" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rpassword" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +dependencies = [ + "libc", + "winapi", ] [[package]] name = "runloop" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "serde" -version = "1.0.116" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ - "serde_derive 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", ] [[package]] name = "serde_derive" -version = "1.0.116" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 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)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" 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)", + "itoa", + "ryu", + "serde", ] [[package]] name = "serde_urlencoded" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 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)", + "form_urlencoded", + "itoa", + "ryu", + "serde", ] [[package]] name = "sha-1" -version = "0.8.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 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)", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "sha-1" -version = "0.9.1" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 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)", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] name = "sha2" -version = "0.8.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 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)", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] name = "shlex" -version = "0.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "slab" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "socket2" -version = "0.3.15" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 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)", + "libc", + "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.41" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 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)", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "tempfile" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 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)", + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] name = "termcolor" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] -name = "thread_local" -version = "1.0.1" +name = "thiserror" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl", ] [[package]] -name = "time" -version = "0.1.44" +name = "thiserror-impl" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 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)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tinyvec" -version = "0.3.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "0.2.22" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" 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)", + "autocfg", + "bytes 1.2.1", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", ] [[package]] name = "tokio-macros" -version = "0.2.5" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 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)", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] name = "tokio-tungstenite" -version = "0.11.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" 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)", + "futures-util", + "log", + "tokio", + "tungstenite", ] [[package]] name = "tokio-util" -version = "0.3.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 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)", + "bytes 1.2.1", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", ] [[package]] name = "tower-service" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.19" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 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)", + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.16" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 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)", + "once_cell", ] [[package]] name = "try-lock" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.11.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" 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)", + "base64", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", ] [[package]] name = "twoway" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "typenum" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" -version = "0.1.13" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ - "tinyvec 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "unicode-xid" -version = "0.2.1" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 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)", + "form_urlencoded", + "idna", + "percent-encoding", ] [[package]] -name = "urlencoding" -version = "1.1.1" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] -name = "utf-8" -version = "0.7.5" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[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" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 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)", + "log", + "try-lock", ] [[package]] name = "warp" -version = "0.2.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" 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)", + "bytes 1.2.1", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", ] [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "wasm-bindgen" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "which" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" dependencies = [ - "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] -[[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" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 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)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] -[[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" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 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)", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[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" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/third_party/rust/authenticator/Cargo.toml b/third_party/rust/authenticator/Cargo.toml index 57d24bd66b94..d3751d3000ef 100644 --- a/third_party/rust/authenticator/Cargo.toml +++ b/third_party/rust/authenticator/Cargo.toml @@ -3,26 +3,39 @@ # 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 +# 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) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "authenticator" -version = "0.3.1" -authors = ["J.C. Jones ", "Tim Taubert ", "Kyle Machulis "] +version = "0.4.0-alpha.3" +authors = [ + "J.C. Jones ", + "Tim Taubert ", + "Kyle Machulis ", +] 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"] +readme = "README.md" +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 +version = "^0.13" [dependencies.bitflags] version = "1.0" @@ -32,14 +45,42 @@ version = "0.5" features = ["serde"] optional = true +[dependencies.cfg-if] +version = "1.0" + [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" +[dependencies.nom] +version = "^7.1.1" +features = ["std"] +default-features = false + +[dependencies.nss-gk-api] +version = "0.2.1" +optional = true + +[dependencies.openssl] +version = "0.10" +optional = true + +[dependencies.openssl-sys] +version = "0.9" +optional = true + +[dependencies.pkcs11-bindings] +version = "0.1.4" +optional = true + [dependencies.rand] -version = "0.7" +version = "0.8" + +[dependencies.ring] +version = "0.16" +optional = true [dependencies.runloop] version = "0.1.0" @@ -47,50 +88,89 @@ version = "0.1.0" [dependencies.serde] version = "1.0" features = ["derive"] -optional = true + +[dependencies.serde_bytes] +version = "0.11" + +[dependencies.serde_cbor] +version = "0.11" [dependencies.serde_json] version = "1.0" -optional = true + +[dependencies.sha2] +version = "^0.10.0" [dependencies.tokio] -version = "0.2" -features = ["macros"] +version = "1.17" +features = [ + "macros", + "rt-multi-thread", +] optional = true [dependencies.warp] -version = "0.2.4" +version = "0.3.2" 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" +[dev-dependencies.rpassword] +version = "5.0" + [build-dependencies.bindgen] -version = "^0.51" +version = "^0.58.1" optional = true [features] binding-recompile = ["bindgen"] -webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"] +crypto_dummy = [] +crypto_nss = [ + "nss-gk-api", + "pkcs11-bindings", +] +crypto_openssl = [ + "openssl", + "openssl-sys", +] +crypto_ring = ["ring"] +default = ["crypto_nss"] +gecko = ["nss-gk-api/gecko"] +webdriver = [ + "bytes", + "warp", + "tokio", +] + [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.memoffset] +version = "0.6" + [target."cfg(target_os = \"windows\")".dependencies.winapi] version = "^0.3" -features = ["handleapi", "hidclass", "hidpi", "hidusage", "setupapi"] +features = [ + "handleapi", + "hidclass", + "hidpi", + "hidusage", + "setupapi", +] + [badges.maintenance] status = "actively-developed" diff --git a/third_party/rust/authenticator/build.rs b/third_party/rust/authenticator/build.rs index 299e4df6d733..c972d85b898e 100644 --- a/third_party/rust/authenticator/build.rs +++ b/third_party/rust/authenticator/build.rs @@ -45,6 +45,8 @@ fn main() { "ioctl_aarch64be.rs" } else if cfg!(all(target_arch = "s390x", target_endian = "big")) { "ioctl_s390xbe.rs" + } else if cfg!(all(target_arch = "riscv64", target_endian = "little")) { + "ioctl_riscv64.rs" } else { panic!("architecture not supported"); }; diff --git a/third_party/rust/authenticator/examples/ctap1.rs b/third_party/rust/authenticator/examples/ctap1.rs new file mode 100644 index 000000000000..444a2ef573a4 --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap1.rs @@ -0,0 +1,203 @@ +/* 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, CtapVersion, RegisterArgsCtap1, SignArgsCtap1}, + statecallback::StateCallback, + AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult, + 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> { + 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 = 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(CtapVersion::CTAP2) + .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::("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::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + let mut application = Sha256::new(); + application.update(b"http://demo.yubico.com"); + let app_bytes = application.finalize().to_vec(); + + let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::PinError(..)) + | Ok(StatusUpdate::SelectDeviceNotice) + | Ok(StatusUpdate::DeviceSelected(..)) => { + panic!("STATUS: This can't happen for CTAP1!"); + } + 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(); + })); + + let ctap_args = RegisterArgsCtap1 { + flags, + challenge: chall_bytes.clone(), + application: app_bytes.clone(), + key_handles: vec![], + }; + manager + .register(timeout_ms, ctap_args.into(), 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) = match register_result { + Ok(RegisterResult::CTAP1(r, d)) => (r, d), + Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"), + Err(e) => panic!("Registration failed {:?}", e), + }; + + println!("Register result: {}", base64::encode(®ister_data)); + println!("Device info: {}", &device_info); + println!(""); + println!("*********************************************************************"); + println!("Asking a security key to sign now, with the data from the register..."); + println!("*********************************************************************"); + + let credential = u2f_get_key_handle_from_register_response(®ister_data).unwrap(); + let key_handle = KeyHandle { + credential, + transports: AuthenticatorTransports::empty(), + }; + + let flags = SignFlags::empty(); + let (sign_tx, sign_rx) = channel(); + + let callback = StateCallback::new(Box::new(move |rv| { + sign_tx.send(rv).unwrap(); + })); + + if let Err(e) = manager.sign( + timeout_ms, + SignArgsCtap1 { + flags, + challenge: chall_bytes, + app_ids: vec![app_bytes], + key_handles: vec![key_handle], + } + .into(), + status_tx, + callback, + ) { + panic!("Couldn't register: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + if let SignResult::CTAP1(_, 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."); + } else { + panic!("Expected CTAP version 1 for sign result!"); + } +} diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs new file mode 100644 index 000000000000..2067a0e921c9 --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap2.rs @@ -0,0 +1,290 @@ +/* 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, CtapVersion, GetAssertionExtensions, GetAssertionOptions, + HmacSecretExtension, MakeCredentialsExtensions, MakeCredentialsOptions, RegisterArgsCtap2, + SignArgsCtap2, + }, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::PinError, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, SignResult, StatusUpdate, +}; +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, thread}; + +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 = 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"); + opts.optflag("h", "help", "print this help menu").optopt( + "t", + "timeout", + "timeout in seconds", + "SEC", + ); + opts.optflag("s", "hmac_secret", "With hmac-secret"); + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + let timeout_ms = match matches.opt_get_default::("timeout", 25) { + 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://example.com"}"# + ); + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + // TODO(MS): Needs to be added to RegisterArgsCtap2 + // let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: "user_id".as_bytes().to_vec(), + icon: None, + name: Some("A. User".to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + pub_cred_params: vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + exclude_list: vec![PublicKeyCredentialDescriptor { + id: vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ], + transports: vec![Transport::USB, Transport::NFC], + }], + options: MakeCredentialsOptions { + resident_key: None, + user_verification: None, + }, + extensions: MakeCredentialsExtensions { + hmac_secret: if matches.opt_present("hmac_secret") { + Some(true) + } else { + None + }, + ..Default::default() + }, + pin: None, + }; + + let attestation_object; + let client_data; + loop { + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + if let Err(e) = manager.register( + timeout_ms, + ctap_args.clone().into(), + status_tx.clone(), + callback, + ) { + panic!("Couldn't register: {:?}", e); + }; + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + match register_result { + Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, c)) => { + println!("Ok!"); + attestation_object = a; + client_data = c; + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + + println!("Register result: {:?}", &attestation_object); + println!("Collected client data: {:?}", &client_data); + + println!(""); + println!("*********************************************************************"); + println!("Asking a security key to sign now, with the data from the register..."); + println!("*********************************************************************"); + + let allow_list; + if let Some(cred_data) = attestation_object.auth_data.credential_data { + allow_list = vec![PublicKeyCredentialDescriptor { + id: cred_data.credential_id.clone(), + transports: vec![Transport::USB], + }]; + } else { + allow_list = Vec::new(); + } + + let ctap_args = SignArgsCtap2 { + challenge: chall_bytes, + origin, + relying_party_id: "example.com".to_string(), + allow_list, + options: GetAssertionOptions::default(), + extensions: GetAssertionExtensions { + hmac_secret: if matches.opt_present("hmac_secret") { + Some(HmacSecretExtension::new( + vec![ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, + ], + None, + )) + } else { + None + }, + }, + pin: None, + }; + + loop { + 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( + timeout_ms, + ctap_args.clone().into(), + status_tx.clone(), + callback, + ) { + panic!("Couldn't sign: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + + match sign_result { + Ok(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"), + Ok(SignResult::CTAP2(assertion_object, _client_data)) => { + println!("Assertion Object: {:?}", assertion_object); + println!("Done."); + break; + } + Err(e) => panic!("Signing failed: {:?}", e), + } + } +} diff --git a/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs new file mode 100644 index 000000000000..ebbbd766ed96 --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs @@ -0,0 +1,354 @@ +/* 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, CtapVersion, GetAssertionOptions, MakeCredentialsOptions, + RegisterArgsCtap2, SignArgsCtap2, + }, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::PinError, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, SignResult, StatusUpdate, +}; +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, thread}; + +fn print_usage(program: &str, opts: Options) { + println!("------------------------------------------------------------------------"); + println!("This program registers 3x the same origin with different users and"); + println!("requests 'discoverable credentials' for them."); + println!("After that, we try to log in to that origin and list all credentials found."); + println!("------------------------------------------------------------------------"); + let brief = format!("Usage: {} [options]", program); + print!("{}", opts.usage(&brief)); +} + +fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) { + println!(""); + println!("*********************************************************************"); + println!( + "Asking a security key to register now with user: {}", + username + ); + println!("*********************************************************************"); + + println!("Asking a security key to register now..."); + let challenge_str = format!( + "{}{}{}{}", + r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#, + r#" "version": "U2F_V2", "appId": "http://example.com", "username": ""#, + username, + r#""}"# + ); + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + // TODO(MS): Needs to be added to RegisterArgsCtap2 + // let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: username.as_bytes().to_vec(), + icon: None, + name: Some(username.to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + pub_cred_params: vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + exclude_list: vec![PublicKeyCredentialDescriptor { + id: vec![], + transports: vec![Transport::USB, Transport::NFC], + }], + options: MakeCredentialsOptions { + resident_key: Some(true), + user_verification: Some(true), + }, + extensions: Default::default(), + pin: None, + }; + + let attestation_object; + let client_data; + loop { + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + if let Err(e) = manager.register( + timeout_ms, + ctap_args.clone().into(), + status_tx.clone(), + callback, + ) { + panic!("Couldn't register: {:?}", e); + }; + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + match register_result { + Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, c)) => { + println!("Ok!"); + attestation_object = a; + client_data = c; + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + + println!("Register result: {:?}", &attestation_object); + println!("Collected client data: {:?}", &client_data); +} + +fn main() { + env_logger::init(); + + let args: Vec = 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"); + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + let timeout_ms = match matches.opt_get_default::("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; + } + }; + + for username in vec!["A. User", "A. Nother", "Dr. Who"] { + register_user(&mut manager, username, timeout_ms) + } + + println!(""); + println!("*********************************************************************"); + println!("Asking a security key to sign now, with the data from the register..."); + println!("*********************************************************************"); + + // Discovering creds: + let allow_list = Vec::new(); + let origin = "https://example.com".to_string(); + let challenge_str = format!( + "{}{}", + r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#, + r#" "version": "U2F_V2", "appId": "http://example.com" "#, + ); + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + let ctap_args = SignArgsCtap2 { + challenge: chall_bytes, + origin, + relying_party_id: "example.com".to_string(), + allow_list, + options: GetAssertionOptions::default(), + extensions: Default::default(), + pin: None, + }; + + loop { + 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( + timeout_ms, + ctap_args.clone().into(), + status_tx.clone(), + callback, + ) { + panic!("Couldn't sign: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + + match sign_result { + Ok(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"), + Ok(SignResult::CTAP2(assertion_object, _client_data)) => { + println!("Assertion Object: {:?}", assertion_object); + println!("-----------------------------------------------------------------"); + println!("Found credentials:"); + println!( + "{:?}", + assertion_object + .0 + .iter() + .map(|x| x.user.clone().unwrap().name.unwrap()) // Unwrapping here, as these shouldn't fail + .collect::>() + ); + println!("-----------------------------------------------------------------"); + println!("Done."); + break; + } + Err(e) => panic!("Signing failed: {:?}", e), + } + } +} diff --git a/third_party/rust/authenticator/examples/main.rs b/third_party/rust/authenticator/examples/main.rs index 3922a25ddeb1..decc877b34d9 100644 --- a/third_party/rust/authenticator/examples/main.rs +++ b/third_party/rust/authenticator/examples/main.rs @@ -3,8 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use authenticator::{ - authenticatorservice::AuthenticatorService, statecallback::StateCallback, - AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate, + authenticatorservice::{AuthenticatorService, CtapVersion, RegisterArgsCtap1, SignArgsCtap1}, + statecallback::StateCallback, + AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult, + StatusUpdate, }; use getopts::Options; use sha2::{Digest, Sha256}; @@ -53,15 +55,15 @@ fn main() { opts.optflag("h", "help", "print this help menu"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, - Err(f) => panic!(f.to_string()), + 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"); + let mut manager = AuthenticatorService::new(CtapVersion::CTAP1) + .expect("The auth service should initialize safely"); if !matches.opt_present("no-u2f-usb-hid") { manager.add_u2f_usb_hid_platform_transports(); @@ -92,13 +94,13 @@ fn main() { 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 challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); - let mut application = Sha256::default(); - application.input(b"http://demo.yubico.com"); - let app_bytes = application.result().to_vec(); + let mut application = Sha256::new(); + application.update(b"http://demo.yubico.com"); + let app_bytes = application.finalize().to_vec(); let flags = RegisterFlags::empty(); @@ -114,6 +116,11 @@ fn main() { Ok(StatusUpdate::Success { dev_info }) => { println!("STATUS: success using device: {}", dev_info); } + Ok(StatusUpdate::PinError(..)) + | Ok(StatusUpdate::SelectDeviceNotice) + | Ok(StatusUpdate::DeviceSelected(..)) => { + panic!("STATUS: This can't happen for CTAP1!"); + } Err(RecvError) => { println!("STATUS: end"); return; @@ -126,22 +133,24 @@ fn main() { register_tx.send(rv).unwrap(); })); + let ctap_args = RegisterArgsCtap1 { + flags, + challenge: chall_bytes.clone(), + application: app_bytes.clone(), + key_handles: vec![], + }; manager - .register( - flags, - timeout_ms, - chall_bytes.clone(), - app_bytes.clone(), - vec![], - status_tx.clone(), - callback, - ) + .register(timeout_ms, ctap_args.into(), 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"); + let (register_data, device_info) = match register_result { + Ok(RegisterResult::CTAP1(r, d)) => (r, d), + Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"), + Err(_) => panic!("Registration failed"), + }; println!("Register result: {}", base64::encode(®ister_data)); println!("Device info: {}", &device_info); @@ -160,11 +169,14 @@ fn main() { })); if let Err(e) = manager.sign( - flags, timeout_ms, - chall_bytes, - vec![app_bytes], - vec![key_handle], + SignArgsCtap1 { + flags, + challenge: chall_bytes, + app_ids: vec![app_bytes], + key_handles: vec![key_handle], + } + .into(), status_tx, callback, ) { @@ -174,10 +186,14 @@ fn main() { 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."); + if let SignResult::CTAP1(_, 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."); + } else { + panic!("Expected CTAP version 1 for sign result!"); + } } diff --git a/third_party/rust/authenticator/examples/reset.rs b/third_party/rust/authenticator/examples/reset.rs new file mode 100644 index 000000000000..96bc6e853586 --- /dev/null +++ b/third_party/rust/authenticator/examples/reset.rs @@ -0,0 +1,137 @@ +/* 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, CtapVersion}, + ctap2::commands::StatusCode, + errors::{AuthenticatorError, CommandError, HIDError}, + statecallback::StateCallback, + StatusUpdate, +}; +use getopts::Options; +use std::env; +use std::sync::mpsc::{channel, RecvError}; + +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 = 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"); + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + let timeout_ms = match matches.opt_get_default::("timeout", 25) { + 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!( + "NOTE: Please unplug all devices, type in 'yes' and plug in the device that should be reset." + ); + loop { + let mut s = String::new(); + println!("ATTENTION: Resetting a device will wipe all credentials! Do you wish to continue? [yes/N]"); + std::io::stdin() + .read_line(&mut s) + .expect("Did not enter a correct string"); + let trimmed = s.trim(); + if trimmed.is_empty() || trimmed == "N" || trimmed == "n" { + println!("Exiting without reset."); + return; + } + if trimmed == "y" { + println!("Please type in the whole word 'yes'"); + continue; + } + if trimmed == "yes" { + break; + } + } + + let (status_tx, status_rx) = channel::(); + let (reset_tx, reset_rx) = channel(); + let rs_tx = reset_tx.clone(); + let callback = StateCallback::new(Box::new(move |rv| { + let _ = rs_tx.send(rv); + })); + + if let Err(e) = manager.reset(timeout_ms, status_tx.clone(), callback) { + panic!("Couldn't register: {:?}", e); + }; + + loop { + match status_rx.recv() { + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("ERROR: Please unplug all other tokens that should not be reset!"); + // Needed to give the tokens enough time to start blinking + // otherwise we may cancel pre-maturely and this binary will hang + std::thread::sleep(std::time::Duration::from_millis(200)); + manager.cancel().unwrap(); + return; + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + break; + } + Ok(StatusUpdate::PinError(..)) => panic!("Reset should never ask for a PIN!"), + Ok(_) => { /* Ignore all other updates */ } + Err(RecvError) => { + println!("RecvError"); + return; + } + } + } + + let reset_result = reset_rx + .recv() + .expect("Problem receiving, unable to continue"); + match reset_result { + Ok(()) => { + println!("Token successfully reset!"); + } + Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::NotAllowed, + _, + )))) => { + println!("Resetting is only allowed within the first 10 seconds after powering up."); + println!("Please unplug your device, plug it back in and try again."); + } + Err(e) => panic!("Reset failed: {:?}", e), + }; +} diff --git a/third_party/rust/authenticator/examples/set_pin.rs b/third_party/rust/authenticator/examples/set_pin.rs new file mode 100644 index 000000000000..b5fd810e09ec --- /dev/null +++ b/third_party/rust/authenticator/examples/set_pin.rs @@ -0,0 +1,144 @@ +/* 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, CtapVersion}, + statecallback::StateCallback, + Pin, PinError, StatusUpdate, +}; +use getopts::Options; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, thread}; + +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 = 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"); + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + let timeout_ms = match matches.opt_get_default::("timeout", 25) { + Ok(timeout_s) => { + println!("Using {}s as the timeout", &timeout_s); + timeout_s * 1_000 + } + Err(e) => { + println!("{}", e); + print_usage(&program, opts); + return; + } + }; + + let new_pin = rpassword::prompt_password_stderr("Enter new PIN: ").expect("Failed to read PIN"); + let repeat_new_pin = + rpassword::prompt_password_stderr("Enter it again: ").expect("Failed to read PIN"); + if new_pin != repeat_new_pin { + println!("PINs did not match!"); + return; + } + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let (reset_tx, reset_rx) = channel(); + let rs_tx = reset_tx.clone(); + let callback = StateCallback::new(Box::new(move |rv| { + let _ = rs_tx.send(rv); + })); + + if let Err(e) = manager.set_pin(timeout_ms, Pin::new(&new_pin), status_tx.clone(), callback) { + panic!("Couldn't call set_pin: {:?}", e); + }; + + let reset_result = reset_rx + .recv() + .expect("Problem receiving, unable to continue"); + match reset_result { + Ok(()) => { + println!("PIN successfully set!"); + } + Err(e) => panic!("Setting PIN failed: {:?}", e), + }; +} diff --git a/third_party/rust/authenticator/examples/test_exclude_list.rs b/third_party/rust/authenticator/examples/test_exclude_list.rs new file mode 100644 index 000000000000..c5b019287b77 --- /dev/null +++ b/third_party/rust/authenticator/examples/test_exclude_list.rs @@ -0,0 +1,211 @@ +/* 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, CtapVersion, MakeCredentialsOptions, RegisterArgsCtap2, + }, + ctap2::commands::StatusCode, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::{AuthenticatorError, CommandError, HIDError, PinError}, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, StatusUpdate, +}; + +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, thread}; + +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 = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + manager.add_u2f_usb_hid_platform_transports(); + + let timeout_ms = match matches.opt_get_default::("timeout", 25) { + 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://example.com"}"# + ); + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + // TODO(MS): Needs to be added to RegisterArgsCtap2 + // let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::(); + 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); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: "user_id".as_bytes().to_vec(), + icon: None, + name: Some("A. User".to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let mut ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + pub_cred_params: vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + exclude_list: vec![], + options: MakeCredentialsOptions { + resident_key: None, + user_verification: None, + }, + extensions: Default::default(), + pin: None, + }; + + loop { + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + if let Err(e) = manager.register( + timeout_ms, + ctap_args.clone().into(), + status_tx.clone(), + callback, + ) { + panic!("Couldn't register: {:?}", e); + }; + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + match register_result { + Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, _c)) => { + println!("Ok!"); + println!("Registering again with the key_handle we just got back. This should result in a 'already registered' error."); + let registered_key_handle = + a.auth_data.credential_data.unwrap().credential_id.clone(); + ctap_args.exclude_list = vec![PublicKeyCredentialDescriptor { + id: registered_key_handle.clone(), + transports: vec![Transport::USB], + }]; + continue; + } + Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::CredentialExcluded, + None, + )))) => { + println!("Got an 'already registered' error. Quitting."); + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + println!("Done") +} diff --git a/third_party/rust/authenticator/src/authenticatorservice.rs b/third_party/rust/authenticator/src/authenticatorservice.rs index fcb05dc63ca5..93195cd83344 100644 --- a/third_party/rust/authenticator/src/authenticatorservice.rs +++ b/third_party/rust/authenticator/src/authenticatorservice.rs @@ -2,22 +2,118 @@ * 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::ctap2::commands::client_pin::Pin; +pub use crate::ctap2::commands::get_assertion::{ + GetAssertionExtensions, GetAssertionOptions, HmacSecretExtension, +}; +pub use crate::ctap2::commands::make_credentials::{ + MakeCredentialsExtensions, MakeCredentialsOptions, +}; +use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User, +}; +use crate::errors::*; +use crate::manager::Manager; +use crate::statecallback::StateCallback; use std::sync::{mpsc::Sender, Arc, Mutex}; -use crate::consts::PARAMETER_SIZE; -use crate::errors::*; -use crate::statecallback::StateCallback; +// TODO(MS): Once U2FManager gets completely removed, this can be removed as well +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CtapVersion { + CTAP1, + CTAP2, +} + +#[derive(Debug, Clone)] +pub struct RegisterArgsCtap1 { + pub flags: crate::RegisterFlags, + pub challenge: Vec, + pub application: crate::AppId, + pub key_handles: Vec, +} + +#[derive(Debug, Clone)] +pub struct RegisterArgsCtap2 { + pub challenge: Vec, + pub relying_party: RelyingParty, + pub origin: String, + pub user: User, + pub pub_cred_params: Vec, + pub exclude_list: Vec, + pub options: MakeCredentialsOptions, + pub extensions: MakeCredentialsExtensions, + pub pin: Option, +} + +#[derive(Debug)] +pub enum RegisterArgs { + CTAP1(RegisterArgsCtap1), + CTAP2(RegisterArgsCtap2), +} + +impl From for RegisterArgs { + fn from(args: RegisterArgsCtap1) -> Self { + RegisterArgs::CTAP1(args) + } +} + +impl From for RegisterArgs { + fn from(args: RegisterArgsCtap2) -> Self { + RegisterArgs::CTAP2(args) + } +} + +#[derive(Debug, Clone)] +pub struct SignArgsCtap1 { + pub flags: crate::SignFlags, + pub challenge: Vec, + pub app_ids: Vec, + pub key_handles: Vec, +} + +#[derive(Debug, Clone)] +pub struct SignArgsCtap2 { + pub challenge: Vec, + pub origin: String, + pub relying_party_id: String, + pub allow_list: Vec, + pub options: GetAssertionOptions, + pub extensions: GetAssertionExtensions, + pub pin: Option, + // Todo: Extensions +} + +#[derive(Debug)] +pub enum SignArgs { + CTAP1(SignArgsCtap1), + CTAP2(SignArgsCtap2), +} + +impl From for SignArgs { + fn from(args: SignArgsCtap1) -> Self { + SignArgs::CTAP1(args) + } +} + +impl From for SignArgs { + fn from(args: SignArgsCtap2) -> Self { + SignArgs::CTAP2(args) + } +} + +#[derive(Debug, Clone, Default)] +pub struct AssertionExtensions { + pub hmac_secret: Option, +} 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, - application: crate::AppId, - key_handles: Vec, + ctap_args: RegisterArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()>; @@ -26,20 +122,31 @@ pub trait AuthenticatorTransport { /// report its status via the status and callback methods fn sign( &mut self, - flags: crate::SignFlags, timeout: u64, - challenge: Vec, - app_ids: Vec, - key_handles: Vec, + ctap_args: SignArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()>; fn cancel(&mut self) -> crate::Result<()>; + fn reset( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()>; + fn set_pin( + &mut self, + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()>; } pub struct AuthenticatorService { transports: Vec>>>, + ctap_version: CtapVersion, } fn clone_and_configure_cancellation_callback( @@ -62,9 +169,10 @@ fn clone_and_configure_cancellation_callback( } impl AuthenticatorService { - pub fn new() -> crate::Result { + pub fn new(ctap_version: CtapVersion) -> crate::Result { Ok(Self { transports: Vec::new(), + ctap_version, }) } @@ -78,9 +186,16 @@ impl AuthenticatorService { } 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), + if self.ctap_version == CtapVersion::CTAP1 { + match crate::U2FManager::new() { + Ok(token) => self.add_transport(Box::new(token)), + Err(e) => error!("Could not add U2F HID transport: {}", e), + } + } else { + match Manager::new() { + Ok(token) => self.add_transport(Box::new(token)), + Err(e) => error!("Could not add CTAP2 HID transport: {}", e), + } } } @@ -97,20 +212,30 @@ impl AuthenticatorService { pub fn register( &mut self, - flags: crate::RegisterFlags, timeout: u64, - challenge: Vec, - application: crate::AppId, - key_handles: Vec, + ctap_args: RegisterArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + match ctap_args { + RegisterArgs::CTAP1(a) => self.register_ctap1(timeout, a, status, callback), + RegisterArgs::CTAP2(a) => self.register_ctap2(timeout, a, status, callback), + } + } + + fn register_ctap1( + &mut self, + timeout: u64, + args: RegisterArgsCtap1, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + if args.challenge.len() != PARAMETER_SIZE || args.application.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - for key_handle in &key_handles { - if key_handle.credential.len() > 256 { + for key_handle in &args.key_handles { + if key_handle.credential.len() >= 256 { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } @@ -136,11 +261,46 @@ impl AuthenticatorService { ); transport_mutex.lock().unwrap().register( - flags, timeout, - challenge.clone(), - application.clone(), - key_handles.clone(), + RegisterArgs::CTAP1(args.clone()), + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } + + fn register_ctap2( + &mut self, + timeout: u64, + args: RegisterArgsCtap2, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + 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( + timeout, + RegisterArgs::CTAP2(args.clone()), status.clone(), clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), )?; @@ -151,30 +311,40 @@ impl AuthenticatorService { pub fn sign( &mut self, - flags: crate::SignFlags, timeout: u64, - challenge: Vec, - app_ids: Vec, - key_handles: Vec, + ctap_args: SignArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE { + match ctap_args { + SignArgs::CTAP1(a) => self.sign_ctap1(timeout, a, status, callback), + SignArgs::CTAP2(a) => self.sign_ctap2(timeout, a, status, callback), + } + } + + pub fn sign_ctap1( + &mut self, + timeout: u64, + args: SignArgsCtap1, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + if args.challenge.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - if app_ids.is_empty() { + if args.app_ids.is_empty() { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - for app_id in &app_ids { + for app_id in &args.app_ids { if app_id.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } - for key_handle in &key_handles { - if key_handle.credential.len() > 256 { + for key_handle in &args.key_handles { + if key_handle.credential.len() >= 256 { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } @@ -189,11 +359,35 @@ impl AuthenticatorService { transports_to_cancel.remove(idx); transport_mutex.lock().unwrap().sign( - flags, timeout, - challenge.clone(), - app_ids.clone(), - key_handles.clone(), + SignArgs::CTAP1(args.clone()), + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } + + pub fn sign_ctap2( + &mut self, + timeout: u64, + args: SignArgsCtap2, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + 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( + timeout, + SignArgs::CTAP2(args.clone()), status.clone(), clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), )?; @@ -213,6 +407,74 @@ impl AuthenticatorService { Ok(()) } + + pub fn reset( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + let iterable_transports = self.transports.clone(); + if iterable_transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + debug!( + "reset 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!("reset transports_to_cancel {}", transports_to_cancel.len()); + + transport_mutex.lock().unwrap().reset( + timeout, + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } + + pub fn set_pin( + &mut self, + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + let iterable_transports = self.transports.clone(); + if iterable_transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + debug!( + "reset 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!("reset transports_to_cancel {}", transports_to_cancel.len()); + + transport_mutex.lock().unwrap().set_pin( + timeout, + new_pin.clone(), + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } } //////////////////////////////////////////////////////////////////////// @@ -221,10 +483,17 @@ impl AuthenticatorService { #[cfg(test)] mod tests { - use super::{AuthenticatorService, AuthenticatorTransport}; + use super::{ + AuthenticatorService, AuthenticatorTransport, CtapVersion, Pin, + PublicKeyCredentialDescriptor, RegisterArgs, RegisterArgsCtap1, RegisterArgsCtap2, + SignArgs, SignArgsCtap1, SignArgsCtap2, User, + }; + use crate::consts::Capability; use crate::consts::PARAMETER_SIZE; + use crate::ctap2::server::RelyingParty; use crate::statecallback::StateCallback; use crate::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate}; + use crate::{RegisterResult, SignResult}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; @@ -257,7 +526,7 @@ mod tests { version_major: 1, version_minor: 2, version_build: 3, - cap_flags: 0, + cap_flags: Capability::empty(), } } } @@ -265,16 +534,13 @@ mod tests { impl AuthenticatorTransport for TestTransportDriver { fn register( &mut self, - _flags: crate::RegisterFlags, _timeout: u64, - _challenge: Vec, - _application: crate::AppId, - _key_handles: Vec, + _args: RegisterArgs, _status: Sender, callback: StateCallback>, ) -> crate::Result<()> { if self.consent { - let rv = Ok((vec![0u8; 16], self.dev_info())); + let rv = Ok(RegisterResult::CTAP1(vec![0u8; 16], self.dev_info())); thread::spawn(move || callback.call(rv)); } Ok(()) @@ -282,16 +548,18 @@ mod tests { fn sign( &mut self, - _flags: crate::SignFlags, _timeout: u64, - _challenge: Vec, - _app_ids: Vec, - _key_handles: Vec, + _ctap_args: SignArgs, _status: Sender, callback: StateCallback>, ) -> crate::Result<()> { if self.consent { - let rv = Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info())); + let rv = Ok(SignResult::CTAP1( + vec![0u8; 0], + vec![0u8; 0], + vec![0u8; 0], + self.dev_info(), + )); thread::spawn(move || callback.call(rv)); } Ok(()) @@ -307,6 +575,25 @@ mod tests { |_| Ok(()), ) } + + fn reset( + &mut self, + _timeout: u64, + _status: Sender, + _callback: StateCallback>, + ) -> crate::Result<()> { + unimplemented!(); + } + + fn set_pin( + &mut self, + _timeout: u64, + _new_pin: Pin, + _status: Sender, + _callback: StateCallback>, + ) -> crate::Result<()> { + unimplemented!(); + } } fn mk_key() -> KeyHandle { @@ -329,16 +616,19 @@ mod tests { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); assert_matches!( s.register( - RegisterFlags::empty(), 1_000, - vec![], - mk_appid(), - vec![mk_key()], + RegisterArgsCtap1 { + challenge: vec![], + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![mk_key()], + } + .into(), status_tx.clone(), StateCallback::new(Box::new(move |_rv| {})), ) @@ -348,11 +638,14 @@ mod tests { assert_matches!( s.sign( - SignFlags::empty(), 1_000, - vec![], - vec![mk_appid()], - vec![mk_key()], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: vec![], + app_ids: vec![mk_appid()], + key_handles: vec![mk_key()] + } + .into(), status_tx, StateCallback::new(Box::new(move |_rv| {})), ) @@ -366,16 +659,19 @@ mod tests { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); assert_matches!( s.register( - RegisterFlags::empty(), 1_000, - mk_challenge(), - vec![], - vec![mk_key()], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: vec![], + key_handles: vec![mk_key()], + } + .into(), status_tx.clone(), StateCallback::new(Box::new(move |_rv| {})), ) @@ -385,11 +681,14 @@ mod tests { assert_matches!( s.sign( - SignFlags::empty(), 1_000, - mk_challenge(), - vec![], - vec![mk_key()], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: mk_challenge(), + app_ids: vec![], + key_handles: vec![mk_key()] + } + .into(), status_tx, StateCallback::new(Box::new(move |_rv| {})), ) @@ -406,16 +705,19 @@ mod tests { // This test yields OKs. let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); assert_matches!( s.register( - RegisterFlags::empty(), 100, - mk_challenge(), - mk_appid(), - vec![], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![], + } + .into(), status_tx.clone(), StateCallback::new(Box::new(move |_rv| {})), ), @@ -424,11 +726,14 @@ mod tests { assert_matches!( s.sign( - SignFlags::empty(), 100, - mk_challenge(), - vec![mk_appid()], - vec![], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: mk_challenge(), + app_ids: vec![mk_appid()], + key_handles: vec![] + } + .into(), status_tx, StateCallback::new(Box::new(move |_rv| {})), ), @@ -446,16 +751,19 @@ mod tests { transports: AuthenticatorTransports::USB, }; - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).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()], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![large_key.clone()], + } + .into(), status_tx.clone(), StateCallback::new(Box::new(move |_rv| {})), ) @@ -465,11 +773,14 @@ mod tests { assert_matches!( s.sign( - SignFlags::empty(), 1_000, - mk_challenge(), - vec![mk_appid()], - vec![large_key], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: mk_challenge(), + app_ids: vec![mk_appid()], + key_handles: vec![large_key] + } + .into(), status_tx, StateCallback::new(Box::new(move |_rv| {})), ) @@ -478,19 +789,84 @@ mod tests { ); } + #[test] + fn test_large_keys_ctap2() { + init(); + let (status_tx, _) = channel::(); + + let large_key = KeyHandle { + credential: vec![0; 1000], + transports: AuthenticatorTransports::USB, + }; + + let mut s = AuthenticatorService::new(CtapVersion::CTAP2).unwrap(); + s.add_transport(Box::new(TestTransportDriver::new(true).unwrap())); + + let ctap2_register_args = RegisterArgsCtap2 { + challenge: mk_challenge(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: "example.com".to_string(), + user: User { + id: "user_id".as_bytes().to_vec(), + icon: None, + name: Some("A. User".to_string()), + display_name: None, + }, + pub_cred_params: vec![], + exclude_list: vec![(&large_key).into()], + options: Default::default(), + extensions: Default::default(), + pin: None, + }; + + assert!(s + .register( + 1_000, + ctap2_register_args.into(), + status_tx.clone(), + StateCallback::new(Box::new(move |_rv| {})), + ) + .is_ok(),); + + let ctap2_sign_args = SignArgsCtap2 { + challenge: mk_challenge(), + origin: "example.com".to_string(), + relying_party_id: "example.com".to_string(), + allow_list: vec![(&large_key).into()], + options: Default::default(), + extensions: Default::default(), + pin: None, + }; + assert!(s + .sign( + 1_000, + ctap2_sign_args.into(), + status_tx, + StateCallback::new(Box::new(move |_rv| {})), + ) + .is_ok(),); + } + #[test] fn test_no_transports() { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); assert_matches!( s.register( - RegisterFlags::empty(), 1_000, - mk_challenge(), - mk_appid(), - vec![mk_key()], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![mk_key()], + } + .into(), status_tx.clone(), StateCallback::new(Box::new(move |_rv| {})), ) @@ -500,11 +876,14 @@ mod tests { assert_matches!( s.sign( - SignFlags::empty(), 1_000, - mk_challenge(), - vec![mk_appid()], - vec![mk_key()], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: mk_challenge(), + app_ids: vec![mk_appid()], + key_handles: vec![mk_key()] + } + .into(), status_tx, StateCallback::new(Box::new(move |_rv| {})), ) @@ -523,7 +902,7 @@ mod tests { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); let ttd_one = TestTransportDriver::new(true).unwrap(); let ttd_two = TestTransportDriver::new(false).unwrap(); let ttd_three = TestTransportDriver::new(false).unwrap(); @@ -539,11 +918,14 @@ mod tests { let callback = StateCallback::new(Box::new(move |_rv| {})); assert!(s .register( - RegisterFlags::empty(), 1_000, - mk_challenge(), - mk_appid(), - vec![], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![], + } + .into(), status_tx, callback.clone(), ) @@ -560,7 +942,7 @@ mod tests { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); let ttd_one = TestTransportDriver::new(true).unwrap(); let ttd_two = TestTransportDriver::new(false).unwrap(); let ttd_three = TestTransportDriver::new(false).unwrap(); @@ -576,11 +958,14 @@ mod tests { let callback = StateCallback::new(Box::new(move |_rv| {})); assert!(s .sign( - SignFlags::empty(), 1_000, - mk_challenge(), - vec![mk_appid()], - vec![mk_key()], + SignArgsCtap1 { + flags: SignFlags::empty(), + challenge: mk_challenge(), + app_ids: vec![mk_appid()], + key_handles: vec![mk_key()] + } + .into(), status_tx, callback.clone(), ) @@ -597,7 +982,7 @@ mod tests { init(); let (status_tx, _) = channel::(); - let mut s = AuthenticatorService::new().unwrap(); + let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap(); // Let both of these race which one provides consent. let ttd_one = TestTransportDriver::new(true).unwrap(); let ttd_two = TestTransportDriver::new(true).unwrap(); @@ -611,11 +996,14 @@ mod tests { let callback = StateCallback::new(Box::new(move |_rv| {})); assert!(s .register( - RegisterFlags::empty(), 1_000, - mk_challenge(), - mk_appid(), - vec![], + RegisterArgsCtap1 { + challenge: mk_challenge(), + flags: RegisterFlags::empty(), + application: mk_appid(), + key_handles: vec![], + } + .into(), status_tx, callback.clone(), ) diff --git a/third_party/rust/authenticator/src/capi.rs b/third_party/rust/authenticator/src/capi.rs index ea7123503fba..078f976e6c32 100644 --- a/third_party/rust/authenticator/src/capi.rs +++ b/third_party/rust/authenticator/src/capi.rs @@ -2,7 +2,9 @@ * 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::authenticatorservice::{ + AuthenticatorService, CtapVersion, RegisterArgsCtap1, SignArgsCtap1, +}; use crate::errors; use crate::statecallback::StateCallback; use crate::{RegisterResult, SignResult}; @@ -46,7 +48,7 @@ unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec { /// 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() { + if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP1) { mgr.add_detected_transports(); Box::into_raw(Box::new(mgr)) } else { @@ -61,7 +63,7 @@ pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService { #[no_mangle] pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut AuthenticatorService) { if !mgr.is_null() { - Box::from_raw(mgr); + drop(Box::from_raw(mgr)); } } @@ -92,7 +94,7 @@ pub unsafe extern "C" fn rust_u2f_app_ids_add( #[no_mangle] pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) { if !ids.is_null() { - Box::from_raw(ids); + drop(Box::from_raw(ids)); } } @@ -127,7 +129,7 @@ pub unsafe extern "C" fn rust_u2f_khs_add( #[no_mangle] pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) { if !khs.is_null() { - Box::from_raw(khs); + drop(Box::from_raw(khs)); } } @@ -147,6 +149,22 @@ pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 { 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_contains(res: *const U2FResult, bid: u8) -> bool { + if res.is_null() { + return false; + } + + if let U2FResult::Success(ref bufs) = *res { + return bufs.contains_key(&bid); + } + + false +} + /// # Safety /// /// This method must be used before rust_u2f_resbuf_copy @@ -201,7 +219,7 @@ pub unsafe extern "C" fn rust_u2f_resbuf_copy( #[no_mangle] pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) { if !res.is_null() { - Box::from_raw(res); + drop(Box::from_raw(res)); } } @@ -249,7 +267,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_register( let state_callback = StateCallback::>::new(Box::new(move |rv| { let result = match rv { - Ok((registration, dev_info)) => { + Ok(RegisterResult::CTAP1(registration, dev_info)) => { let mut bufs = HashMap::new(); bufs.insert(RESBUF_ID_REGISTRATION, registration); bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name); @@ -259,21 +277,22 @@ pub unsafe extern "C" fn rust_u2f_mgr_register( bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]); U2FResult::Success(bufs) } + Ok(RegisterResult::CTAP2(..)) => U2FResult::Error( + errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_register", 1), + ), Err(e) => U2FResult::Error(e), }; callback(tid, Box::into_raw(Box::new(result))); })); - - let res = (*mgr).register( + let ctap_args = RegisterArgsCtap1 { flags, - timeout, challenge, application, key_handles, - status_tx, - state_callback, - ); + }; + + let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback); if res.is_ok() { tid @@ -329,7 +348,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign( let tid = new_tid(); let state_callback = StateCallback::>::new(Box::new(move |rv| { let result = match rv { - Ok((app_id, key_handle, signature, dev_info)) => { + Ok(SignResult::CTAP1(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); @@ -341,6 +360,9 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign( bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]); U2FResult::Success(bufs) } + Ok(SignResult::CTAP2(..)) => U2FResult::Error( + errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_sign", 1), + ), Err(e) => U2FResult::Error(e), }; @@ -348,11 +370,14 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign( })); let res = (*mgr).sign( - flags, timeout, - challenge, - app_ids, - key_handles, + SignArgsCtap1 { + flags, + challenge, + app_ids, + key_handles, + } + .into(), status_tx, state_callback, ); diff --git a/third_party/rust/authenticator/src/consts.rs b/third_party/rust/authenticator/src/consts.rs index 5f27bb93789c..e96bd202c6a3 100644 --- a/third_party/rust/authenticator/src/consts.rs +++ b/third_party/rust/authenticator/src/consts.rs @@ -5,8 +5,18 @@ // Allow dead code in this module, since it's all packet consts anyways. #![allow(dead_code)] +use serde::Serialize; + pub const MAX_HID_RPT_SIZE: usize = 64; + +/// Minimum size of the U2F Raw Message header (FIDO v1.x) in extended mode, +/// including expected response length (Le). +/// +/// Fields `CLA`, `INS`, `P1` and `P2` are 1 byte each, and Le is 3 +/// bytes. If there is a data payload, add 2 bytes (Lc is 3 bytes, +/// and Le is 2 bytes). 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; @@ -30,13 +40,65 @@ pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation versio 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 +// CTAPHID native commands +const CTAPHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only +const CTAPHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame +const CTAPHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command +const CTAPHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization +const CTAPHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink +const CTAPHID_CBOR: u8 = TYPE_INIT | 0x10; // Encapsulated CBOR encoded message +const CTAPHID_CANCEL: u8 = TYPE_INIT | 0x11; // Cancel outstanding requests +const CTAPHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; // Keepalive sent to authenticator every 100ms and whenever a status changes +const CTAPHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[repr(u8)] +pub enum HIDCmd { + Ping, + Msg, + Lock, + Init, + Wink, + Cbor, + Cancel, + Keepalive, + Error, + Unknown(u8), +} + +impl Into for HIDCmd { + fn into(self) -> u8 { + match self { + HIDCmd::Ping => CTAPHID_PING, + HIDCmd::Msg => CTAPHID_MSG, + HIDCmd::Lock => CTAPHID_LOCK, + HIDCmd::Init => CTAPHID_INIT, + HIDCmd::Wink => CTAPHID_WINK, + HIDCmd::Cbor => CTAPHID_CBOR, + HIDCmd::Cancel => CTAPHID_CANCEL, + HIDCmd::Keepalive => CTAPHID_KEEPALIVE, + HIDCmd::Error => CTAPHID_ERROR, + HIDCmd::Unknown(v) => v, + } + } +} + +impl From for HIDCmd { + fn from(v: u8) -> HIDCmd { + match v { + CTAPHID_PING => HIDCmd::Ping, + CTAPHID_MSG => HIDCmd::Msg, + CTAPHID_LOCK => HIDCmd::Lock, + CTAPHID_INIT => HIDCmd::Init, + CTAPHID_WINK => HIDCmd::Wink, + CTAPHID_CBOR => HIDCmd::Cbor, + CTAPHID_CANCEL => HIDCmd::Cancel, + CTAPHID_KEEPALIVE => HIDCmd::Keepalive, + CTAPHID_ERROR => HIDCmd::Error, + v => HIDCmd::Unknown(v), + } + } +} // U2FHID_MSG commands pub const U2F_VENDOR_FIRST: u8 = TYPE_INIT | 0x40; // First vendor defined command @@ -57,8 +119,26 @@ pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is regi // 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 + +bitflags! { + #[derive(Serialize)] + pub struct Capability: u8 { + const WINK = 0x01; + const LOCK = 0x02; + const CBOR = 0x04; + const NMSG = 0x08; + } +} + +impl Capability { + pub fn has_fido1(self) -> bool { + !self.contains(Capability::NMSG) + } + + pub fn has_fido2(self) -> bool { + self.contains(Capability::CBOR) + } +} // Low-level error codes. Return as negatives. diff --git a/third_party/rust/authenticator/src/crypto/dummy.rs b/third_party/rust/authenticator/src/crypto/dummy.rs new file mode 100644 index 000000000000..c37b2cbc211d --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/dummy.rs @@ -0,0 +1,42 @@ +use super::{ByteBuf, COSEKey, ECDHSecret, ECDSACurve}; +use serde::Serialize; +/* +This is a dummy implementation for CI, to avoid having to install NSS or openSSL in the CI-pipeline +*/ + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum BackendError {} + +pub(crate) fn serialize_key(_curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> { + // Copy from NSS + let length = key[1..].len() / 2; + let chunks: Vec<_> = key[1..].chunks_exact(length).collect(); + Ok(( + ByteBuf::from(chunks[0].to_vec()), + ByteBuf::from(chunks[1].to_vec()), + )) +} + +pub(crate) fn encapsulate(_key: &COSEKey) -> Result { + unimplemented!() +} + +pub(crate) fn encrypt( + _key: &[u8], + _plain_text: &[u8], /*PlainText*/ +) -> Result /*CypherText*/> { + unimplemented!() +} + +pub(crate) fn decrypt( + _key: &[u8], + _cypher_text: &[u8], /*CypherText*/ +) -> Result /*PlainText*/> { + unimplemented!() +} + +pub(crate) fn authenticate(_token: &[u8], _input: &[u8]) -> Result> { + unimplemented!() +} diff --git a/third_party/rust/authenticator/src/crypto/mod.rs b/third_party/rust/authenticator/src/crypto/mod.rs new file mode 100644 index 000000000000..627ab32088c0 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/mod.rs @@ -0,0 +1,924 @@ +/* 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::AuthenticatorError; +use crate::{ctap2::commands::CommandError, transport::errors::HIDError}; +use serde::{ + de::{Error as SerdeError, MapAccess, Unexpected, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; +use serde_bytes::ByteBuf; +use serde_cbor::Value; +use std::convert::TryFrom; +use std::fmt; + +cfg_if::cfg_if! { + if #[cfg(feature = "crypto_ring")] { + #[path = "ring.rs"] + pub mod imp; + } else if #[cfg(feature = "crypto_openssl")] { + #[path = "openssl.rs"] + pub mod imp; + } else if #[cfg(feature = "crypto_dummy")] { + #[path = "dummy.rs"] + pub mod imp; + } else { + #[path = "nss.rs"] + pub mod imp; + } +} + +pub(crate) use imp::{authenticate, decrypt, encapsulate, encrypt, serialize_key, BackendError}; + +/// An ECDSACurve identifier. You probably will never need to alter +/// or use this value, as it is set inside the Credential for you. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ECDSACurve { + // +---------+-------+----------+------------------------------------+ + // | Name | Value | Key Type | Description | + // +---------+-------+----------+------------------------------------+ + // | P-256 | 1 | EC2 | NIST P-256 also known as secp256r1 | + // | P-384 | 2 | EC2 | NIST P-384 also known as secp384r1 | + // | P-521 | 3 | EC2 | NIST P-521 also known as secp521r1 | + // | X25519 | 4 | OKP | X25519 for use w/ ECDH only | + // | X448 | 5 | OKP | X448 for use w/ ECDH only | + // | Ed25519 | 6 | OKP | Ed25519 for use w/ EdDSA only | + // | Ed448 | 7 | OKP | Ed448 for use w/ EdDSA only | + // +---------+-------+----------+------------------------------------+ + /// Identifies this curve as SECP256R1 (X9_62_PRIME256V1 in OpenSSL) + SECP256R1 = 1, + /// Identifies this curve as SECP384R1 + SECP384R1 = 2, + /// Identifies this curve as SECP521R1 + SECP521R1 = 3, + /// Identifieds this as OKP X25519 for use w/ ECDH only + X25519 = 4, + /// Identifieds this as OKP X448 for use w/ ECDH only + X448 = 5, + /// Identifieds this as OKP Ed25519 for use w/ EdDSA only + Ed25519 = 6, + /// Identifieds this as OKP Ed448 for use w/ EdDSA only + Ed448 = 7, +} + +impl TryFrom for ECDSACurve { + type Error = CryptoError; + fn try_from(i: u64) -> Result { + match i { + 1 => Ok(ECDSACurve::SECP256R1), + 2 => Ok(ECDSACurve::SECP384R1), + 3 => Ok(ECDSACurve::SECP521R1), + 4 => Ok(ECDSACurve::X25519), + 5 => Ok(ECDSACurve::X448), + 6 => Ok(ECDSACurve::Ed25519), + 7 => Ok(ECDSACurve::Ed448), + _ => Err(CryptoError::UnknownKeyType), + } + } +} +/// A COSE signature algorithm, indicating the type of key and hash type +/// that should be used. +/// see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum COSEAlgorithm { + // /// Identifies this key as ECDSA (recommended SECP256R1) with SHA256 hashing + // //#[serde(alias = "ECDSA_SHA256")] + // ES256 = -7, // recommends curve SECP256R1 + // /// Identifies this key as ECDSA (recommended SECP384R1) with SHA384 hashing + // //#[serde(alias = "ECDSA_SHA384")] + // ES384 = -35, // recommends curve SECP384R1 + // /// Identifies this key as ECDSA (recommended SECP521R1) with SHA512 hashing + // //#[serde(alias = "ECDSA_SHA512")] + // ES512 = -36, // recommends curve SECP521R1 + // /// Identifies this key as RS256 aka RSASSA-PKCS1-v1_5 w/ SHA-256 + // RS256 = -257, + // /// Identifies this key as RS384 aka RSASSA-PKCS1-v1_5 w/ SHA-384 + // RS384 = -258, + // /// Identifies this key as RS512 aka RSASSA-PKCS1-v1_5 w/ SHA-512 + // RS512 = -259, + // /// Identifies this key as PS256 aka RSASSA-PSS w/ SHA-256 + // PS256 = -37, + // /// Identifies this key as PS384 aka RSASSA-PSS w/ SHA-384 + // PS384 = -38, + // /// Identifies this key as PS512 aka RSASSA-PSS w/ SHA-512 + // PS512 = -39, + // /// Identifies this key as EdDSA (likely curve ed25519) + // EDDSA = -8, + // /// Identifies this as an INSECURE RS1 aka RSASSA-PKCS1-v1_5 using SHA-1. This is not + // /// used by validators, but can exist in some windows hello tpm's + // INSECURE_RS1 = -65535, + INSECURE_RS1 = -65535, // RSASSA-PKCS1-v1_5 using SHA-1 + RS512 = -259, // RSASSA-PKCS1-v1_5 using SHA-512 + RS384 = -258, // RSASSA-PKCS1-v1_5 using SHA-384 + RS256 = -257, // RSASSA-PKCS1-v1_5 using SHA-256 + ES256K = -47, // ECDSA using secp256k1 curve and SHA-256 + HSS_LMS = -46, // HSS/LMS hash-based digital signature + SHAKE256 = -45, // SHAKE-256 512-bit Hash Value + SHA512 = -44, // SHA-2 512-bit Hash + SHA384 = -43, // SHA-2 384-bit Hash + RSAES_OAEP_SHA_512 = -42, // RSAES-OAEP w/ SHA-512 + RSAES_OAEP_SHA_256 = -41, // RSAES-OAEP w/ SHA-256 + RSAES_OAEP_RFC_8017_default = -40, // RSAES-OAEP w/ SHA-1 + PS512 = -39, // RSASSA-PSS w/ SHA-512 + PS384 = -38, // RSASSA-PSS w/ SHA-384 + PS256 = -37, // RSASSA-PSS w/ SHA-256 + ES512 = -36, // ECDSA w/ SHA-512 + ES384 = -35, // ECDSA w/ SHA-384 + ECDH_SS_A256KW = -34, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key + ECDH_SS_A192KW = -33, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key + ECDH_SS_A128KW = -32, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key + ECDH_ES_A256KW = -31, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key + ECDH_ES_A192KW = -30, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key + ECDH_ES_A128KW = -29, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key + ECDH_SS_HKDF512 = -28, // ECDH SS w/ HKDF - generate key directly + ECDH_SS_HKDF256 = -27, // ECDH SS w/ HKDF - generate key directly + ECDH_ES_HKDF512 = -26, // ECDH ES w/ HKDF - generate key directly + ECDH_ES_HKDF256 = -25, // ECDH ES w/ HKDF - generate key directly + SHAKE128 = -18, // SHAKE-128 256-bit Hash Value + SHA512_256 = -17, // SHA-2 512-bit Hash truncated to 256-bits + SHA256 = -16, // SHA-2 256-bit Hash + SHA256_64 = -15, // SHA-2 256-bit Hash truncated to 64-bits + SHA1 = -14, // SHA-1 Hash + Direct_HKDF_AES256 = -13, // Shared secret w/ AES-MAC 256-bit key + Direct_HKDF_AES128 = -12, // Shared secret w/ AES-MAC 128-bit key + Direct_HKDF_SHA512 = -11, // Shared secret w/ HKDF and SHA-512 + Direct_HKDF_SHA256 = -10, // Shared secret w/ HKDF and SHA-256 + EDDSA = -8, // EdDSA + ES256 = -7, // ECDSA w/ SHA-256 + Direct = -6, // Direct use of CEK + A256KW = -5, // AES Key Wrap w/ 256-bit key + A192KW = -4, // AES Key Wrap w/ 192-bit key + A128KW = -3, // AES Key Wrap w/ 128-bit key + A128GCM = 1, // AES-GCM mode w/ 128-bit key, 128-bit tag + A192GCM = 2, // AES-GCM mode w/ 192-bit key, 128-bit tag + A256GCM = 3, // AES-GCM mode w/ 256-bit key, 128-bit tag + HMAC256_64 = 4, // HMAC w/ SHA-256 truncated to 64 bits + HMAC256_256 = 5, // HMAC w/ SHA-256 + HMAC384_384 = 6, // HMAC w/ SHA-384 + HMAC512_512 = 7, // HMAC w/ SHA-512 + AES_CCM_16_64_128 = 10, // AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce + AES_CCM_16_64_256 = 11, // AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce + AES_CCM_64_64_128 = 12, // AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce + AES_CCM_64_64_256 = 13, // AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce + AES_MAC_128_64 = 14, // AES-MAC 128-bit key, 64-bit tag + AES_MAC_256_64 = 15, // AES-MAC 256-bit key, 64-bit tag + ChaCha20_Poly1305 = 24, // ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag + AES_MAC_128_128 = 25, // AES-MAC 128-bit key, 128-bit tag + AES_MAC_256_128 = 26, // AES-MAC 256-bit key, 128-bit tag + AES_CCM_16_128_128 = 30, // AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce + AES_CCM_16_128_256 = 31, // AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce + AES_CCM_64_128_128 = 32, // AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce + AES_CCM_64_128_256 = 33, // AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce + IV_GENERATION = 34, // For doing IV generation for symmetric algorithms. +} + +impl Serialize for COSEAlgorithm { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + COSEAlgorithm::RS512 => serializer.serialize_i16(-259), + COSEAlgorithm::RS384 => serializer.serialize_i16(-258), + COSEAlgorithm::RS256 => serializer.serialize_i16(-257), + COSEAlgorithm::ES256K => serializer.serialize_i8(-47), + COSEAlgorithm::HSS_LMS => serializer.serialize_i8(-46), + COSEAlgorithm::SHAKE256 => serializer.serialize_i8(-45), + COSEAlgorithm::SHA512 => serializer.serialize_i8(-44), + COSEAlgorithm::SHA384 => serializer.serialize_i8(-43), + COSEAlgorithm::RSAES_OAEP_SHA_512 => serializer.serialize_i8(-42), + COSEAlgorithm::RSAES_OAEP_SHA_256 => serializer.serialize_i8(-41), + COSEAlgorithm::RSAES_OAEP_RFC_8017_default => serializer.serialize_i8(-40), + COSEAlgorithm::PS512 => serializer.serialize_i8(-39), + COSEAlgorithm::PS384 => serializer.serialize_i8(-38), + COSEAlgorithm::PS256 => serializer.serialize_i8(-37), + COSEAlgorithm::ES512 => serializer.serialize_i8(-36), + COSEAlgorithm::ES384 => serializer.serialize_i8(-35), + COSEAlgorithm::ECDH_SS_A256KW => serializer.serialize_i8(-34), + COSEAlgorithm::ECDH_SS_A192KW => serializer.serialize_i8(-33), + COSEAlgorithm::ECDH_SS_A128KW => serializer.serialize_i8(-32), + COSEAlgorithm::ECDH_ES_A256KW => serializer.serialize_i8(-31), + COSEAlgorithm::ECDH_ES_A192KW => serializer.serialize_i8(-30), + COSEAlgorithm::ECDH_ES_A128KW => serializer.serialize_i8(-29), + COSEAlgorithm::ECDH_SS_HKDF512 => serializer.serialize_i8(-28), + COSEAlgorithm::ECDH_SS_HKDF256 => serializer.serialize_i8(-27), + COSEAlgorithm::ECDH_ES_HKDF512 => serializer.serialize_i8(-26), + COSEAlgorithm::ECDH_ES_HKDF256 => serializer.serialize_i8(-25), + COSEAlgorithm::SHAKE128 => serializer.serialize_i8(-18), + COSEAlgorithm::SHA512_256 => serializer.serialize_i8(-17), + COSEAlgorithm::SHA256 => serializer.serialize_i8(-16), + COSEAlgorithm::SHA256_64 => serializer.serialize_i8(-15), + COSEAlgorithm::SHA1 => serializer.serialize_i8(-14), + COSEAlgorithm::Direct_HKDF_AES256 => serializer.serialize_i8(-13), + COSEAlgorithm::Direct_HKDF_AES128 => serializer.serialize_i8(-12), + COSEAlgorithm::Direct_HKDF_SHA512 => serializer.serialize_i8(-11), + COSEAlgorithm::Direct_HKDF_SHA256 => serializer.serialize_i8(-10), + COSEAlgorithm::EDDSA => serializer.serialize_i8(-8), + COSEAlgorithm::ES256 => serializer.serialize_i8(-7), + COSEAlgorithm::Direct => serializer.serialize_i8(-6), + COSEAlgorithm::A256KW => serializer.serialize_i8(-5), + COSEAlgorithm::A192KW => serializer.serialize_i8(-4), + COSEAlgorithm::A128KW => serializer.serialize_i8(-3), + COSEAlgorithm::A128GCM => serializer.serialize_i8(1), + COSEAlgorithm::A192GCM => serializer.serialize_i8(2), + COSEAlgorithm::A256GCM => serializer.serialize_i8(3), + COSEAlgorithm::HMAC256_64 => serializer.serialize_i8(4), + COSEAlgorithm::HMAC256_256 => serializer.serialize_i8(5), + COSEAlgorithm::HMAC384_384 => serializer.serialize_i8(6), + COSEAlgorithm::HMAC512_512 => serializer.serialize_i8(7), + COSEAlgorithm::AES_CCM_16_64_128 => serializer.serialize_i8(10), + COSEAlgorithm::AES_CCM_16_64_256 => serializer.serialize_i8(11), + COSEAlgorithm::AES_CCM_64_64_128 => serializer.serialize_i8(12), + COSEAlgorithm::AES_CCM_64_64_256 => serializer.serialize_i8(13), + COSEAlgorithm::AES_MAC_128_64 => serializer.serialize_i8(14), + COSEAlgorithm::AES_MAC_256_64 => serializer.serialize_i8(15), + COSEAlgorithm::ChaCha20_Poly1305 => serializer.serialize_i8(24), + COSEAlgorithm::AES_MAC_128_128 => serializer.serialize_i8(25), + COSEAlgorithm::AES_MAC_256_128 => serializer.serialize_i8(26), + COSEAlgorithm::AES_CCM_16_128_128 => serializer.serialize_i8(30), + COSEAlgorithm::AES_CCM_16_128_256 => serializer.serialize_i8(31), + COSEAlgorithm::AES_CCM_64_128_128 => serializer.serialize_i8(32), + COSEAlgorithm::AES_CCM_64_128_256 => serializer.serialize_i8(33), + COSEAlgorithm::IV_GENERATION => serializer.serialize_i8(34), + COSEAlgorithm::INSECURE_RS1 => serializer.serialize_i32(-65535), + } + } +} + +impl<'de> Deserialize<'de> for COSEAlgorithm { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct COSEAlgorithmVisitor; + + impl<'de> Visitor<'de> for COSEAlgorithmVisitor { + type Value = COSEAlgorithm; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a signed integer") + } + + fn visit_i64(self, v: i64) -> Result + where + E: SerdeError, + { + COSEAlgorithm::try_from(v).map_err(|_| { + SerdeError::invalid_value(Unexpected::Signed(v), &"valid COSEAlgorithm") + }) + } + } + + deserializer.deserialize_any(COSEAlgorithmVisitor) + } +} + +impl TryFrom for COSEAlgorithm { + type Error = CryptoError; + fn try_from(i: i64) -> Result { + match i { + -259 => Ok(COSEAlgorithm::RS512), + -258 => Ok(COSEAlgorithm::RS384), + -257 => Ok(COSEAlgorithm::RS256), + -47 => Ok(COSEAlgorithm::ES256K), + -46 => Ok(COSEAlgorithm::HSS_LMS), + -45 => Ok(COSEAlgorithm::SHAKE256), + -44 => Ok(COSEAlgorithm::SHA512), + -43 => Ok(COSEAlgorithm::SHA384), + -42 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_512), + -41 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_256), + -40 => Ok(COSEAlgorithm::RSAES_OAEP_RFC_8017_default), + -39 => Ok(COSEAlgorithm::PS512), + -38 => Ok(COSEAlgorithm::PS384), + -37 => Ok(COSEAlgorithm::PS256), + -36 => Ok(COSEAlgorithm::ES512), + -35 => Ok(COSEAlgorithm::ES384), + -34 => Ok(COSEAlgorithm::ECDH_SS_A256KW), + -33 => Ok(COSEAlgorithm::ECDH_SS_A192KW), + -32 => Ok(COSEAlgorithm::ECDH_SS_A128KW), + -31 => Ok(COSEAlgorithm::ECDH_ES_A256KW), + -30 => Ok(COSEAlgorithm::ECDH_ES_A192KW), + -29 => Ok(COSEAlgorithm::ECDH_ES_A128KW), + -28 => Ok(COSEAlgorithm::ECDH_SS_HKDF512), + -27 => Ok(COSEAlgorithm::ECDH_SS_HKDF256), + -26 => Ok(COSEAlgorithm::ECDH_ES_HKDF512), + -25 => Ok(COSEAlgorithm::ECDH_ES_HKDF256), + -18 => Ok(COSEAlgorithm::SHAKE128), + -17 => Ok(COSEAlgorithm::SHA512_256), + -16 => Ok(COSEAlgorithm::SHA256), + -15 => Ok(COSEAlgorithm::SHA256_64), + -14 => Ok(COSEAlgorithm::SHA1), + -13 => Ok(COSEAlgorithm::Direct_HKDF_AES256), + -12 => Ok(COSEAlgorithm::Direct_HKDF_AES128), + -11 => Ok(COSEAlgorithm::Direct_HKDF_SHA512), + -10 => Ok(COSEAlgorithm::Direct_HKDF_SHA256), + -8 => Ok(COSEAlgorithm::EDDSA), + -7 => Ok(COSEAlgorithm::ES256), + -6 => Ok(COSEAlgorithm::Direct), + -5 => Ok(COSEAlgorithm::A256KW), + -4 => Ok(COSEAlgorithm::A192KW), + -3 => Ok(COSEAlgorithm::A128KW), + 1 => Ok(COSEAlgorithm::A128GCM), + 2 => Ok(COSEAlgorithm::A192GCM), + 3 => Ok(COSEAlgorithm::A256GCM), + 4 => Ok(COSEAlgorithm::HMAC256_64), + 5 => Ok(COSEAlgorithm::HMAC256_256), + 6 => Ok(COSEAlgorithm::HMAC384_384), + 7 => Ok(COSEAlgorithm::HMAC512_512), + 10 => Ok(COSEAlgorithm::AES_CCM_16_64_128), + 11 => Ok(COSEAlgorithm::AES_CCM_16_64_256), + 12 => Ok(COSEAlgorithm::AES_CCM_64_64_128), + 13 => Ok(COSEAlgorithm::AES_CCM_64_64_256), + 14 => Ok(COSEAlgorithm::AES_MAC_128_64), + 15 => Ok(COSEAlgorithm::AES_MAC_256_64), + 24 => Ok(COSEAlgorithm::ChaCha20_Poly1305), + 25 => Ok(COSEAlgorithm::AES_MAC_128_128), + 26 => Ok(COSEAlgorithm::AES_MAC_256_128), + 30 => Ok(COSEAlgorithm::AES_CCM_16_128_128), + 31 => Ok(COSEAlgorithm::AES_CCM_16_128_256), + 32 => Ok(COSEAlgorithm::AES_CCM_64_128_128), + 33 => Ok(COSEAlgorithm::AES_CCM_64_128_256), + 34 => Ok(COSEAlgorithm::IV_GENERATION), + -65535 => Ok(COSEAlgorithm::INSECURE_RS1), + _ => Err(CryptoError::UnknownAlgorithm), + } + } +} +/// A COSE Elliptic Curve Public Key. This is generally the provided credential +/// that an authenticator registers, and is used to authenticate the user. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSEEC2Key { + /// The curve that this key references. + pub curve: ECDSACurve, + /// The key's public X coordinate. + pub x: Vec, + /// The key's public Y coordinate. + pub y: Vec, +} + +/// A Octet Key Pair (OKP). +/// The other version uses only the x-coordinate as the y-coordinate is +/// either to be recomputed or not needed for the key agreement operation ('OKP'). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSEOKPKey { + /// The curve that this key references. + pub curve: ECDSACurve, + /// The key's public X coordinate. + pub x: Vec, +} + +/// A COSE RSA PublicKey. This is a provided credential from a registered +/// authenticator. +/// You will likely never need to interact with this value, as it is part of the Credential +/// API. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSERSAKey { + /// An RSA modulus + pub n: Vec, + /// An RSA exponent + pub e: Vec, +} + +/// A Octet Key Pair (OKP). +/// The other version uses only the x-coordinate as the y-coordinate is +/// either to be recomputed or not needed for the key agreement operation ('OKP'). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSESymmetricKey { + /// The key + pub key: Vec, +} + +// https://tools.ietf.org/html/rfc8152#section-13 +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[repr(i64)] +pub enum COSEKeyTypeId { + // Reserved is invalid + // Reserved = 0, + /// Octet Key Pair + OKP = 1, + /// Elliptic Curve Keys w/ x- and y-coordinate + EC2 = 2, + /// RSA + RSA = 3, + /// Symmetric + Symmetric = 4, +} + +impl TryFrom for COSEKeyTypeId { + type Error = CryptoError; + fn try_from(i: u64) -> Result { + match i { + 1 => Ok(COSEKeyTypeId::OKP), + 2 => Ok(COSEKeyTypeId::EC2), + 3 => Ok(COSEKeyTypeId::RSA), + 4 => Ok(COSEKeyTypeId::Symmetric), + _ => Err(CryptoError::UnknownKeyType), + } + } +} + +/// The type of Key contained within a COSE value. You should never need +/// to alter or change this type. +#[allow(non_camel_case_types)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum COSEKeyType { + // +-----------+-------+-----------------------------------------------+ + // | Name | Value | Description | + // +-----------+-------+-----------------------------------------------+ + // | OKP | 1 | Octet Key Pair | + // | EC2 | 2 | Elliptic Curve Keys w/ x- and y-coordinate | + // | | | pair | + // | Symmetric | 4 | Symmetric Keys | + // | Reserved | 0 | This value is reserved | + // +-----------+-------+-----------------------------------------------+ + // Reserved, // should always be invalid. + /// Identifies this as an Elliptic Curve octet key pair + OKP(COSEOKPKey), // Not used here + /// Identifies this as an Elliptic Curve EC2 key + EC2(COSEEC2Key), + /// Identifies this as an RSA key + RSA(COSERSAKey), // Not used here + /// Identifies this as a Symmetric key + Symmetric(COSESymmetricKey), // Not used here +} + +/// A COSE Key as provided by the Authenticator. You should never need +/// to alter or change these values. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSEKey { + /// COSE signature algorithm, indicating the type of key and hash type + /// that should be used. + pub alg: COSEAlgorithm, + /// The public key + pub key: COSEKeyType, +} + +impl<'de> Deserialize<'de> for COSEKey { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + struct COSEKeyVisitor; + + impl<'de> Visitor<'de> for COSEKeyVisitor { + type Value = COSEKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut map: M) -> std::result::Result + where + M: MapAccess<'de>, + { + let mut curve: Option = None; + let mut key_type: Option = None; + let mut alg: Option = None; + let mut x: Option> = None; + let mut y: Option> = None; + + while let Some(key) = map.next_key()? { + trace!("cose key {:?}", key); + match key { + 1 => { + if key_type.is_some() { + return Err(SerdeError::duplicate_field("key_type")); + } + let value: u64 = map.next_value()?; + let val = COSEKeyTypeId::try_from(value).map_err(|_| { + SerdeError::custom(format!("unsupported key_type {}", value)) + })?; + key_type = Some(val); + // key_type = Some(map.next_value()?); + } + -1 => { + let key_type = key_type.ok_or(SerdeError::missing_field("key_type"))?; + if key_type == COSEKeyTypeId::RSA { + if y.is_some() { + return Err(SerdeError::duplicate_field("y")); + } + let value: ByteBuf = map.next_value()?; + y = Some(value.to_vec()); + } else { + if curve.is_some() { + return Err(SerdeError::duplicate_field("curve")); + } + let value: u64 = map.next_value()?; + let val = ECDSACurve::try_from(value).map_err(|_| { + SerdeError::custom(format!("unsupported curve {}", value)) + })?; + curve = Some(val); + // curve = Some(map.next_value()?); + } + } + -2 => { + if x.is_some() { + return Err(SerdeError::duplicate_field("x")); + } + let value: ByteBuf = map.next_value()?; + x = Some(value.to_vec()); + } + -3 => { + if y.is_some() { + return Err(SerdeError::duplicate_field("y")); + } + let value: ByteBuf = map.next_value()?; + y = Some(value.to_vec()); + } + 3 => { + if alg.is_some() { + return Err(SerdeError::duplicate_field("alg")); + } + let value: i64 = map.next_value()?; + let val = COSEAlgorithm::try_from(value).map_err(|_| { + SerdeError::custom(format!("unsupported algorithm {}", value)) + })?; + alg = Some(val); + // alg = map.next_value()?; + } + _ => { + // This unknown field should raise an error, but + // there is a couple of field I(baloo) do not understand + // yet. I(baloo) chose to ignore silently the + // error instead because of that + let value: Value = map.next_value()?; + trace!("cose unknown value {:?}:{:?}", key, value); + } + }; + } + + let key_type = key_type.ok_or(SerdeError::missing_field("key_type"))?; + let x = x.ok_or(SerdeError::missing_field("x"))?; + let alg = alg.ok_or(SerdeError::missing_field("alg"))?; + + let res = match key_type { + COSEKeyTypeId::OKP => { + let curve = curve.ok_or(SerdeError::missing_field("curve"))?; + COSEKeyType::OKP(COSEOKPKey { curve, x }) + } + COSEKeyTypeId::EC2 => { + let curve = curve.ok_or(SerdeError::missing_field("curve"))?; + let y = y.ok_or(SerdeError::missing_field("y"))?; + COSEKeyType::EC2(COSEEC2Key { curve, x, y }) + } + COSEKeyTypeId::RSA => { + let e = y.ok_or(SerdeError::missing_field("y"))?; + COSEKeyType::RSA(COSERSAKey { e, n: x }) + } + COSEKeyTypeId::Symmetric => COSEKeyType::Symmetric(COSESymmetricKey { key: x }), + }; + Ok(COSEKey { alg, key: res }) + } + } + + deserializer.deserialize_bytes(COSEKeyVisitor) + } +} + +impl Serialize for COSEKey { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + let map_len = match &self.key { + COSEKeyType::OKP(_) => 3, + COSEKeyType::EC2(_) => 5, + COSEKeyType::RSA(_) => 4, + COSEKeyType::Symmetric(_) => 3, + }; + let mut map = serializer.serialize_map(Some(map_len))?; + match &self.key { + COSEKeyType::OKP(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::OKP)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &key.curve)?; + map.serialize_entry(&-2, &key.x)?; + } + COSEKeyType::EC2(key) => { + map.serialize_entry(&1, &(COSEKeyTypeId::EC2 as u8))?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &(key.curve as u8))?; + map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.x))?; + map.serialize_entry(&-3, &serde_bytes::Bytes::new(&key.y))?; + } + COSEKeyType::RSA(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::RSA)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &key.n)?; + map.serialize_entry(&-2, &key.e)?; + } + COSEKeyType::Symmetric(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::Symmetric)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &key.key)?; + } + } + + map.end() + } +} + +/// Errors that can be returned from COSE functions. +#[derive(Debug)] +pub enum CryptoError { + // DecodingFailure, + // LibraryFailure, + MalformedInput, + // MissingHeader, + // UnexpectedHeaderValue, + // UnexpectedTag, + // UnexpectedType, + // Unimplemented, + // VerificationFailed, + // SigningFailed, + // InvalidArgument, + UnknownKeyType, + UnknownSignatureScheme, + UnknownAlgorithm, + WrongSaltLength, + Backend(BackendError), +} + +impl From for CryptoError { + fn from(e: BackendError) -> Self { + CryptoError::Backend(e) + } +} + +impl From for CommandError { + fn from(e: CryptoError) -> Self { + CommandError::Crypto(e) + } +} + +impl From for AuthenticatorError { + fn from(e: CryptoError) -> Self { + AuthenticatorError::HIDError(HIDError::Command(CommandError::Crypto(e))) + } +} + +#[derive(Clone)] +pub struct ECDHSecret { + remote: COSEKey, + my: COSEKey, + shared_secret: Vec, +} + +impl ECDHSecret { + pub fn my_public_key(&self) -> &COSEKey { + &self.my + } + + pub fn shared_secret(&self) -> &[u8] { + &self.shared_secret + } +} + +impl fmt::Debug for ECDHSecret { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "ECDHSecret(remote: {:?}, my: {:?})", + self.remote, + self.my_public_key() + ) + } +} + +pub struct U2FRegisterAnswer<'a> { + pub certificate: &'a [u8], + pub signature: &'a [u8], +} + +// We will only return MalformedInput here +pub fn parse_u2f_der_certificate(data: &[u8]) -> Result { + // So we don't panic below, when accessing individual bytes + if data.len() < 4 { + return Err(CryptoError::MalformedInput); + } + // Check if it is a SEQUENCE + if data[0] != 0x30 { + return Err(CryptoError::MalformedInput); + } + + // This algorithm is taken from mozilla-central/security/nss/lib/mozpkix/lib/pkixder.cpp + // The short form of length is a single byte with the high order bit set + // to zero. The long form of length is one byte with the high order bit + // set, followed by N bytes, where N is encoded in the lowest 7 bits of + // the first byte. + let end = if (data[1] & 0x80) == 0 { + 2 + data[1] as usize + } else if data[1] == 0x81 { + // The next byte specifies the length + + if data[2] < 128 { + // Not shortest possible encoding + // Forbidden by DER-format + return Err(CryptoError::MalformedInput); + } + 3 + data[2] as usize + } else if data[1] == 0x82 { + // The next 2 bytes specify the length + let l = u16::from_be_bytes([data[2], data[3]]); + if l < 256 { + // Not shortest possible encoding + // Forbidden by DER-format + return Err(CryptoError::MalformedInput); + } + 4 + l as usize + } else { + // We don't support lengths larger than 2^16 - 1. + return Err(CryptoError::MalformedInput); + }; + + if data.len() < end { + return Err(CryptoError::MalformedInput); + } + + Ok(U2FRegisterAnswer { + certificate: &data[0..end], + signature: &data[end..], + }) +} + +#[cfg(all(test, not(feature = "crypto_dummy")))] +mod test { + use super::{ + authenticate, decrypt, encrypt, imp::parse_key, imp::test_encapsulate, serialize_key, + COSEAlgorithm, COSEKey, ECDSACurve, + }; + use crate::crypto::{COSEEC2Key, COSEKeyType}; + use crate::ctap2::commands::client_pin::Pin; + use crate::util::decode_hex; + use serde_cbor::de::from_slice; + + #[test] + fn test_serialize_key() { + let x = [ + 0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75, + 0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33, + 0x05, 0xe3, 0x1a, 0x80, + ]; + let y = [ + 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, 0xac, 0xf9, 0xd8, + 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, 0x2c, 0x9a, 0xad, + 0x6d, 0xfa, 0x8b, 0x27, + ]; + let serialized_key = [ + 0x04, 0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, + 0x75, 0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, + 0x33, 0x05, 0xe3, 0x1a, 0x80, 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, + 0x8d, 0xe0, 0xac, 0xf9, 0xd8, 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, + 0xd4, 0xd3, 0x2c, 0x9a, 0xad, 0x6d, 0xfa, 0x8b, 0x27, + ]; + + let (res_x, res_y) = + serialize_key(ECDSACurve::SECP256R1, &serialized_key).expect("Failed to serialize key"); + assert_eq!(res_x, x); + assert_eq!(res_y, y); + + let res_key = parse_key(ECDSACurve::SECP256R1, &x, &y).expect("Failed to parse key"); + + assert_eq!(res_key, serialized_key) + } + + #[test] + fn test_parse_es256_serialize_key() { + // Test values taken from https://github.com/Yubico/python-fido2/blob/master/test/test_cose.py + let key_data = decode_hex("A5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C"); + let key: COSEKey = from_slice(&key_data).unwrap(); + assert_eq!(key.alg, COSEAlgorithm::ES256); + if let COSEKeyType::EC2(ec2key) = &key.key { + assert_eq!(ec2key.curve, ECDSACurve::SECP256R1); + assert_eq!( + ec2key.x, + decode_hex("A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1") + ); + assert_eq!( + ec2key.y, + decode_hex("FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C") + ); + } else { + panic!("Wrong key type!"); + } + + let serialized = serde_cbor::to_vec(&key).expect("Failed to serialize key"); + assert_eq!(key_data, serialized); + } + + #[test] + #[allow(non_snake_case)] + fn test_shared_secret() { + // Test values taken from https://github.com/Yubico/python-fido2/blob/master/test/test_ctap2.py + let EC_PRIV = + decode_hex("7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684"); + let EC_PUB_X = + decode_hex("44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F"); + let EC_PUB_Y = + decode_hex("EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9"); + let DEV_PUB_X = + decode_hex("0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168"); + let DEV_PUB_Y = + decode_hex("D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47"); + let SHARED = decode_hex("c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c"); + let TOKEN_ENC = decode_hex("7A9F98E31B77BE90F9C64D12E9635040"); + let TOKEN = decode_hex("aff12c6dcfbf9df52f7a09211e8865cd"); + let PIN_HASH_ENC = decode_hex("afe8327ce416da8ee3d057589c2ce1a9"); + + // let peer_key = parse_key(ECDSACurve::SECP256R1, &DEV_PUB_X, &DEV_PUB_Y).unwrap(); + let my_pub_key_data = parse_key(ECDSACurve::SECP256R1, &EC_PUB_X, &EC_PUB_Y).unwrap(); + + let peer_key = COSEEC2Key { + curve: ECDSACurve::SECP256R1, + x: DEV_PUB_X, + y: DEV_PUB_Y, + }; + + // let my_pub_key = COSEKey { + // alg: COSEAlgorithm::ES256, + // key: COSEKeyType::EC2(COSEEC2Key { + // curve: ECDSACurve::SECP256R1, + // x: EC_PUB_X, + // y: EC_PUB_Y, + // }), + // }; + // We are using `test_encapsulate()` here, because we need a way to hand in the private key + // which would be generated on the fly otherwise (ephemeral keys), to predict the outputs + let shared_secret = + test_encapsulate(&peer_key, COSEAlgorithm::ES256, &my_pub_key_data, &EC_PRIV).unwrap(); + assert_eq!(shared_secret.shared_secret, SHARED); + + let token_enc = encrypt(&shared_secret.shared_secret(), &TOKEN).unwrap(); + assert_eq!(token_enc, TOKEN_ENC); + + let token = decrypt(&shared_secret.shared_secret(), &TOKEN_ENC).unwrap(); + assert_eq!(token, TOKEN); + + let pin = Pin::new("1234"); + let pin_hash_enc = + encrypt(&shared_secret.shared_secret(), pin.for_pin_token().as_ref()).unwrap(); + assert_eq!(pin_hash_enc, PIN_HASH_ENC); + } + + #[test] + fn test_authenticate() { + let key = "key"; + let message = "The quick brown fox jumps over the lazy dog"; + let expected = + decode_hex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); + + let result = + authenticate(key.as_bytes(), message.as_bytes()).expect("Failed to authenticate"); + assert_eq!(result, expected); + + let key = "The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog"; + let message = "message"; + let expected = + decode_hex("5597b93a2843078cbb0c920ae41dfe20f1685e10c67e423c11ab91adfc319d12"); + + let result = + authenticate(key.as_bytes(), message.as_bytes()).expect("Failed to authenticate"); + assert_eq!(result, expected); + } + + #[test] + fn test_pin_encryption_and_hashing() { + let pin = "1234"; + + let shared_secret = vec![ + 0x82, 0xE3, 0xD8, 0x41, 0xE2, 0x5C, 0x5C, 0x13, 0x46, 0x2C, 0x12, 0x3C, 0xC3, 0xD3, + 0x98, 0x78, 0x65, 0xBA, 0x3D, 0x20, 0x46, 0x74, 0xFB, 0xED, 0xD4, 0x7E, 0xF5, 0xAB, + 0xAB, 0x8D, 0x13, 0x72, + ]; + let expected_new_pin_enc = vec![ + 0x70, 0x66, 0x4B, 0xB5, 0x81, 0xE2, 0x57, 0x45, 0x1A, 0x3A, 0xB9, 0x1B, 0xF1, 0xAA, + 0xD8, 0xE4, 0x5F, 0x6C, 0xE9, 0xB5, 0xC3, 0xB0, 0xF3, 0x2B, 0x5E, 0xCD, 0x62, 0xD0, + 0xBA, 0x3B, 0x60, 0x5F, 0xD9, 0x18, 0x31, 0x66, 0xF6, 0xC5, 0xFA, 0xF3, 0xE4, 0xDA, + 0x24, 0x81, 0x50, 0x2C, 0xD0, 0xCE, 0xE0, 0x15, 0x8B, 0x35, 0x1F, 0xC3, 0x92, 0x08, + 0xA7, 0x7C, 0xB2, 0x74, 0x4B, 0xD4, 0x3C, 0xF9, + ]; + let expected_pin_auth = vec![ + 0x8E, 0x7F, 0x01, 0x69, 0x97, 0xF3, 0xB0, 0xA2, 0x7B, 0xA4, 0x34, 0x7A, 0x0E, 0x49, + 0xFD, 0xF5, + ]; + + // Padding to 64 bytes + let input: Vec = pin + .as_bytes() + .iter() + .chain(std::iter::repeat(&0x00)) + .take(64) + .cloned() + .collect(); + + let new_pin_enc = encrypt(&shared_secret, &input).expect("Failed to encrypt pin"); + assert_eq!(new_pin_enc, expected_new_pin_enc); + + let pin_auth = authenticate(&shared_secret, &new_pin_enc).expect("Failed to authenticate"); + assert_eq!(pin_auth[0..16], expected_pin_auth); + } +} diff --git a/third_party/rust/authenticator/src/crypto/nss.rs b/third_party/rust/authenticator/src/crypto/nss.rs new file mode 100644 index 000000000000..c469fd7d06a8 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/nss.rs @@ -0,0 +1,543 @@ +use super::{COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, ECDHSecret, ECDSACurve}; +use nss_gk_api::p11::{ + PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp, + PK11_Encrypt, PK11_GenerateKeyPairWithOpFlags, PK11_HashBuf, PK11_ImportSymKey, + PK11_PubDeriveWithKDF, PrivateKey, PublicKey, SECKEY_DecodeDERSubjectPublicKeyInfo, + SECKEY_ExtractPublicKey, SECOidTag, Slot, SubjectPublicKeyInfo, AES_BLOCK_SIZE, + PK11_ATTR_SESSION, SHA256_LENGTH, +}; +use nss_gk_api::{Error as NSSError, IntoResult, SECItem, SECItemBorrowed, PR_FALSE}; +use pkcs11_bindings::{ + CKA_DERIVE, CKA_ENCRYPT, CKA_SIGN, CKD_NULL, CKF_DERIVE, CKM_AES_CBC, CKM_ECDH1_DERIVE, + CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC, +}; +use serde::Serialize; +use serde_bytes::ByteBuf; +use std::convert::{TryFrom, TryInto}; +use std::num::TryFromIntError; +use std::os::raw::c_uint; +use std::ptr; + +#[cfg(test)] +use nss_gk_api::p11::{PK11_ImportDERPrivateKeyInfoAndReturnKey, SECKEY_ConvertToPublicKey}; + +/// Errors that can be returned from COSE functions. +#[derive(Clone, Debug, Serialize)] +pub enum BackendError { + NSSError(String), + TryFromError, + UnsupportedAlgorithm(COSEAlgorithm), + UnsupportedCurve(ECDSACurve), + UnsupportedKeyType, +} + +impl From for BackendError { + fn from(e: NSSError) -> Self { + BackendError::NSSError(format!("{}", e)) + } +} + +impl From for BackendError { + fn from(_: TryFromIntError) -> Self { + BackendError::TryFromError + } +} + +pub type Result = std::result::Result; + +// Object identifiers in DER tag-length-value form + +const DER_OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[ + 0x06, 0x07, + /* {iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1)} */ + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, +]; + +const DER_OID_P256_BYTES: &[u8] = &[ + 0x06, 0x08, + /* {iso(1) member-body(2) us(840) ansi-x962(10045) curves(3) prime(1) prime256v1(7)} */ + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, +]; +const DER_OID_P384_BYTES: &[u8] = &[ + 0x06, 0x05, + /* {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} */ + 0x2b, 0x81, 0x04, 0x00, 0x22, +]; +const DER_OID_P521_BYTES: &[u8] = &[ + 0x06, 0x05, + /* {iso(1) identified-organization(3) certicom(132) curve(0) ansip521r1(35)} */ + 0x2b, 0x81, 0x04, 0x00, 0x23, +]; + +/* From CTAP2.1 spec: + +initialize() + + This is run by the platform when starting a series of transactions with a specific authenticator. +encapsulate(peerCoseKey) → (coseKey, sharedSecret) | error + + Generates an encapsulation for the authenticator’s public key and returns the message to transmit and the shared secret. +encrypt(key, demPlaintext) → ciphertext + + Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +decrypt(key, ciphertext) → plaintext | error + + Decrypts a ciphertext and returns the plaintext. +authenticate(key, message) → signature + + Computes a MAC of the given message. +*/ + +// TODO(MS): Maybe remove ByteBuf and return Vec's instead for a cleaner interface +pub(crate) fn serialize_key(_curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> { + // TODO(MS): I actually have NO idea how to do this with NSS + let length = key[1..].len() / 2; + let chunks: Vec<_> = key[1..].chunks_exact(length).collect(); + Ok(( + ByteBuf::from(chunks[0].to_vec()), + ByteBuf::from(chunks[1].to_vec()), + )) +} + +pub(crate) fn parse_key(_curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result> { + if x.len() != y.len() { + return Err(BackendError::NSSError( + "EC coordinates not equally long".to_string(), + )); + } + let mut buf = Vec::with_capacity(2 * x.len() + 1); + // The uncompressed point format is defined in Section 2.3.3 of "SEC 1: Elliptic Curve + // Cryptography" https://www.secg.org/sec1-v2.pdf. + buf.push(0x04); + buf.extend_from_slice(x); + buf.extend_from_slice(y); + Ok(buf) +} + +fn der_spki_from_cose(cose_key: &COSEKey) -> Result> { + let ec2key = match cose_key.key { + COSEKeyType::EC2(ref ec2key) => ec2key, + _ => return Err(BackendError::UnsupportedKeyType), + }; + + let (curve_oid, seq_len, alg_len, spk_len) = match ec2key.curve { + ECDSACurve::SECP256R1 => ( + DER_OID_P256_BYTES, + [0x59].as_slice(), + [0x13].as_slice(), + [0x42].as_slice(), + ), + ECDSACurve::SECP384R1 => ( + DER_OID_P384_BYTES, + [0x76].as_slice(), + [0x10].as_slice(), + [0x62].as_slice(), + ), + ECDSACurve::SECP521R1 => ( + DER_OID_P521_BYTES, + [0x81, 0x9b].as_slice(), + [0x10].as_slice(), + [0x8a, 0xdf].as_slice(), + ), + x => return Err(BackendError::UnsupportedCurve(x)), + }; + + let cose_key_sec1 = parse_key(ec2key.curve, &ec2key.x, &ec2key.y)?; + + // [RFC 5280] + let mut spki: Vec = vec![]; + // SubjectPublicKeyInfo + spki.push(0x30); + spki.extend_from_slice(seq_len); + // AlgorithmIdentifier + spki.push(0x30); + spki.extend_from_slice(alg_len); + // ObjectIdentifier + spki.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES); + // RFC 5480 ECParameters + spki.extend_from_slice(curve_oid); + // BIT STRING encoding uncompressed SEC1 public point + spki.push(0x03); + spki.extend_from_slice(spk_len); + spki.push(0x0); // no trailing zeros + spki.extend_from_slice(&cose_key_sec1); + + Ok(spki) +} + +/// This is run by the platform when starting a series of transactions with a specific authenticator. +//pub(crate) fn initialize() { } + +/// Generates an encapsulation for the authenticator's public key and returns the message +/// to transmit and the shared secret. +/// +/// `peer_cose_key` is the authenticator's (peer's) public key. +pub(crate) fn encapsulate(peer_cose_key: &COSEKey) -> Result { + nss_gk_api::init(); + // Generate an ephmeral keypair to do ECDH with the authenticator. + // This is "platformKeyAgreementKey". + let ec2key = match peer_cose_key.key { + COSEKeyType::EC2(ref ec2key) => ec2key, + _ => return Err(BackendError::UnsupportedKeyType), + }; + + let mut oid = match ec2key.curve { + ECDSACurve::SECP256R1 => SECItemBorrowed::wrap(DER_OID_P256_BYTES), + ECDSACurve::SECP384R1 => SECItemBorrowed::wrap(DER_OID_P384_BYTES), + ECDSACurve::SECP521R1 => SECItemBorrowed::wrap(DER_OID_P521_BYTES), + x => return Err(BackendError::UnsupportedCurve(x)), + }; + let oid_ptr: *mut SECItem = oid.as_mut(); + + let slot = Slot::internal()?; + + let mut client_public_ptr = ptr::null_mut(); + + // We have to be careful with error handling between the `PK11_GenerateKeyPairWithOpFlags` and + // `PublicKey::from_ptr` calls here, so I've wrapped them in the same unsafe block as a + // warning. TODO(jms) Replace this once there is a safer alternative. + // https://github.com/mozilla/nss-gk-api/issues/1 + let (client_private, client_public) = unsafe { + let client_private = + // Type of `param` argument depends on mechanism. For EC keygen it is + // `SECKEYECParams *` which is a typedef for `SECItem *`. + PK11_GenerateKeyPairWithOpFlags( + *slot, + CKM_EC_KEY_PAIR_GEN, + oid_ptr.cast(), + &mut client_public_ptr, + PK11_ATTR_SESSION, + CKF_DERIVE, + CKF_DERIVE, + ptr::null_mut(), + ) + .into_result()?; + + let client_public = PublicKey::from_ptr(client_public_ptr)?; + + (client_private, client_public) + }; + + let peer_spki = der_spki_from_cose(peer_cose_key)?; + let peer_public = nss_public_key_from_der_spki(&peer_spki)?; + let shared_secret = encapsulate_helper(peer_public, client_private)?; + + let client_cose_key = cose_key_from_nss_public(peer_cose_key.alg, ec2key.curve, client_public)?; + + Ok(ECDHSecret { + remote: COSEKey { + alg: peer_cose_key.alg, + key: COSEKeyType::EC2(ec2key.clone()), + }, + my: client_cose_key, + shared_secret, + }) +} + +fn nss_public_key_from_der_spki(spki: &[u8]) -> Result { + let mut spki_item = SECItemBorrowed::wrap(&spki); + let spki_item_ptr: *mut SECItem = spki_item.as_mut(); + let nss_spki = unsafe { + SubjectPublicKeyInfo::from_ptr(SECKEY_DecodeDERSubjectPublicKeyInfo(spki_item_ptr))? + }; + let public_key = unsafe { PublicKey::from_ptr(SECKEY_ExtractPublicKey(*nss_spki))? }; + Ok(public_key) +} + +fn cose_key_from_nss_public( + alg: COSEAlgorithm, + curve: ECDSACurve, + nss_public: PublicKey, +) -> Result { + let public_data = nss_public.key_data()?; + let (public_x, public_y) = serialize_key(curve, &public_data)?; + Ok(COSEKey { + alg, + key: COSEKeyType::EC2(COSEEC2Key { + curve, + x: public_x.to_vec(), + y: public_y.to_vec(), + }), + }) +} + +/// `peer_public`: The authenticator's public key. +/// `client_private`: Our ephemeral private key. +fn encapsulate_helper(peer_public: PublicKey, client_private: PrivateKey) -> Result> { + let ecdh_x_coord = unsafe { + PK11_PubDeriveWithKDF( + *client_private, + *peer_public, + PR_FALSE, + std::ptr::null_mut(), + std::ptr::null_mut(), + CKM_ECDH1_DERIVE, + CKM_SHA512_HMAC, // unused + CKA_DERIVE, // unused + 0, + CKD_NULL, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + .into_result()? + }; + let ecdh_x_coord_bytes = ecdh_x_coord.as_bytes()?; + let mut shared_secret = [0u8; SHA256_LENGTH]; + unsafe { + PK11_HashBuf( + SECOidTag::SEC_OID_SHA256, + shared_secret.as_mut_ptr(), + ecdh_x_coord_bytes.as_ptr(), + ecdh_x_coord_bytes.len() as i32, + ) + }; + Ok(shared_secret.to_vec()) +} + +/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. +/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +pub(crate) fn encrypt(key: &[u8], data: &[u8]) -> Result> { + nss_gk_api::init(); + + if key.len() != 32 { + return Err(BackendError::NSSError( + "Invalid AES-256 key length".to_string(), + )); + } + + // The input must be a multiple of the AES block size, 16 + if data.len() % AES_BLOCK_SIZE != 0 { + return Err(BackendError::NSSError( + "Input to encrypt is too long".to_string(), + )); + } + let in_len = c_uint::try_from(data.len())?; + + let slot = Slot::internal()?; + + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_AES_CBC, + PK11Origin::PK11_OriginUnwrap, + CKA_ENCRYPT, + SECItemBorrowed::wrap(key).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + + let iv = [0u8; AES_BLOCK_SIZE]; + let mut params = SECItemBorrowed::wrap(&iv); + let params_ptr: *mut SECItem = params.as_mut(); + let mut out_len: c_uint = 0; + let mut out = vec![0; in_len as usize]; + unsafe { + PK11_Encrypt( + *sym_key, + CKM_AES_CBC, + params_ptr, + out.as_mut_ptr(), + &mut out_len, + in_len, + data.as_ptr(), + in_len, + ) + .into_result()? + } + // CKM_AES_CBC should have output length equal to input length. + debug_assert_eq!(out_len, in_len); + + Ok(out) +} + +/// Decrypts a ciphertext and returns the plaintext. +pub(crate) fn decrypt(key: &[u8], data: &[u8]) -> Result> { + nss_gk_api::init(); + let slot = Slot::internal()?; + + if key.len() != 32 { + return Err(BackendError::NSSError( + "Invalid AES-256 key length".to_string(), + )); + } + + // The input must be a multiple of the AES block size, 16 + if data.len() % AES_BLOCK_SIZE != 0 { + return Err(BackendError::NSSError( + "Invalid input to decrypt".to_string(), + )); + } + let in_len = c_uint::try_from(data.len())?; + + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_AES_CBC, + PK11Origin::PK11_OriginUnwrap, + CKA_ENCRYPT, + SECItemBorrowed::wrap(key).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + + let iv = [0u8; AES_BLOCK_SIZE]; + let mut params = SECItemBorrowed::wrap(&iv); + let params_ptr: *mut SECItem = params.as_mut(); + let mut out_len: c_uint = 0; + let mut out = vec![0; in_len as usize]; + unsafe { + PK11_Decrypt( + *sym_key, + CKM_AES_CBC, + params_ptr, + out.as_mut_ptr(), + &mut out_len, + in_len, + data.as_ptr(), + in_len, + ) + .into_result()? + } + // CKM_AES_CBC should have output length equal to input length. + debug_assert_eq!(out_len, in_len); + + Ok(out) +} + +/// Computes a MAC of the given message. +pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result> { + nss_gk_api::init(); + let slot = Slot::internal()?; + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_SHA256_HMAC, + PK11Origin::PK11_OriginUnwrap, + CKA_SIGN, + SECItemBorrowed::wrap(token).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + let param = SECItemBorrowed::make_empty(); + let context = unsafe { + PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, *sym_key, param.as_ref()) + .into_result()? + }; + unsafe { PK11_DigestOp(*context, input.as_ptr(), input.len().try_into()?).into_result()? }; + let mut digest = vec![0u8; 32]; + let mut digest_len = 0u32; + unsafe { + PK11_DigestFinal( + *context, + digest.as_mut_ptr(), + &mut digest_len, + digest.len() as u32, + ) + .into_result()? + } + assert_eq!(digest_len, 32); + Ok(digest) +} + +#[cfg(test)] +pub(crate) fn test_encapsulate( + peer_coseec2_key: &COSEEC2Key, + alg: COSEAlgorithm, + my_pub_key: &[u8], + my_priv_key: &[u8], +) -> Result { + nss_gk_api::init(); + + let peer_cose_key = COSEKey { + alg: alg, + key: COSEKeyType::EC2(peer_coseec2_key.clone()), + }; + let spki = der_spki_from_cose(&peer_cose_key)?; + let peer_public = nss_public_key_from_der_spki(&spki)?; + + /* NSS has no mechanism to import a raw elliptic curve coordinate as a private key. + * We need to encode it in a key storage format such as PKCS#8. To avoid a dependency + * on an ASN.1 encoder for this test, we'll do it manually. */ + let pkcs8_private_key_info_version = &[0x02, 0x01, 0x00]; + let rfc5915_ec_private_key_version = &[0x02, 0x01, 0x01]; + + let (curve_oid, seq_len, alg_len, attr_len, ecpriv_len, param_len, spk_len) = + match peer_coseec2_key.curve { + ECDSACurve::SECP256R1 => ( + DER_OID_P256_BYTES, + [0x81, 0x87].as_slice(), + [0x13].as_slice(), + [0x6d].as_slice(), + [0x6b].as_slice(), + [0x44].as_slice(), + [0x42].as_slice(), + ), + x => return Err(BackendError::UnsupportedCurve(x)), + }; + + let priv_len = my_priv_key.len() as u8; // < 127 + + let mut pkcs8_priv: Vec = vec![]; + // RFC 5208 PrivateKeyInfo + pkcs8_priv.push(0x30); + pkcs8_priv.extend_from_slice(seq_len); + // Integer (0) + pkcs8_priv.extend_from_slice(pkcs8_private_key_info_version); + // AlgorithmIdentifier + pkcs8_priv.push(0x30); + pkcs8_priv.extend_from_slice(alg_len); + // ObjectIdentifier + pkcs8_priv.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES); + // RFC 5480 ECParameters + pkcs8_priv.extend_from_slice(DER_OID_P256_BYTES); + // Attributes + pkcs8_priv.push(0x04); + pkcs8_priv.extend_from_slice(attr_len); + // RFC 5915 ECPrivateKey + pkcs8_priv.push(0x30); + pkcs8_priv.extend_from_slice(ecpriv_len); + pkcs8_priv.extend_from_slice(rfc5915_ec_private_key_version); + pkcs8_priv.push(0x04); + pkcs8_priv.push(priv_len); + pkcs8_priv.extend_from_slice(my_priv_key); + pkcs8_priv.push(0xa1); + pkcs8_priv.extend_from_slice(param_len); + pkcs8_priv.push(0x03); + pkcs8_priv.extend_from_slice(spk_len); + pkcs8_priv.push(0x0); + pkcs8_priv.extend_from_slice(&my_pub_key); + + // Now we can import the private key. + let slot = Slot::internal()?; + let mut pkcs8_priv_item = SECItemBorrowed::wrap(&pkcs8_priv); + let pkcs8_priv_item_ptr: *mut SECItem = pkcs8_priv_item.as_mut(); + let mut client_private_ptr = ptr::null_mut(); + unsafe { + PK11_ImportDERPrivateKeyInfoAndReturnKey( + *slot, + pkcs8_priv_item_ptr, + ptr::null_mut(), + ptr::null_mut(), + PR_FALSE, + PR_FALSE, + 255, /* todo: expose KU_ flags in nss-gk-api */ + &mut client_private_ptr, + ptr::null_mut(), + ) + }; + + let client_private = unsafe { PrivateKey::from_ptr(client_private_ptr) }?; + let client_public = unsafe { PublicKey::from_ptr(SECKEY_ConvertToPublicKey(*client_private))? }; + let client_cose_key = cose_key_from_nss_public(alg, peer_coseec2_key.curve, client_public)?; + + let shared_secret = encapsulate_helper(peer_public, client_private)?; + + Ok(ECDHSecret { + remote: peer_cose_key, + my: client_cose_key, + shared_secret, + }) +} diff --git a/third_party/rust/authenticator/src/crypto/openssl.rs b/third_party/rust/authenticator/src/crypto/openssl.rs new file mode 100644 index 000000000000..28a09bd3eb37 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/openssl.rs @@ -0,0 +1,283 @@ +use super::{ + /*Signature,*/ COSEAlgorithm, COSEEC2Key, /*PlainText*/ COSEKey, COSEKeyType, + /*CypherText,*/ ECDHSecret, ECDSACurve, +}; +use openssl::bn::{BigNum, BigNumContext}; +use openssl::derive::Deriver; +#[cfg(test)] +use openssl::ec::PointConversionForm; +use openssl::ec::{EcGroup, EcKey, EcPoint}; +use openssl::error::ErrorStack; +use openssl::hash::{hash, MessageDigest}; +use openssl::nid::Nid; +use openssl::pkey::{PKey, Private}; +use openssl::sign::{Signer, Verifier}; +use openssl::symm::{Cipher, Crypter, Mode}; +use openssl::x509::X509; +use serde::{Serialize, Serializer}; +use serde_bytes::ByteBuf; + +fn openssl_string(_: &ErrorStack, s: S) -> std::result::Result +where + S: Serializer, +{ + s.serialize_str("OpenSSLError") +} + +/// Errors that can be returned from COSE functions. +#[derive(Clone, Debug, Serialize)] +pub enum BackendError { + #[serde(serialize_with = "openssl_string")] + OpenSSL(ErrorStack), + UnsupportedCurve(ECDSACurve), + UnsupportedKeyType, +} + +impl From for BackendError { + fn from(e: ErrorStack) -> Self { + BackendError::OpenSSL(e) + } +} + +impl From<&ErrorStack> for BackendError { + fn from(e: &ErrorStack) -> Self { + BackendError::OpenSSL(e.clone()) + } +} + +pub type Result = std::result::Result; + +/* From CTAP2.1 spec: + +initialize() + + This is run by the platform when starting a series of transactions with a specific authenticator. +encapsulate(peerCoseKey) → (coseKey, sharedSecret) | error + + Generates an encapsulation for the authenticator’s public key and returns the message to transmit and the shared secret. +encrypt(key, demPlaintext) → ciphertext + + Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +decrypt(key, ciphertext) → plaintext | error + + Decrypts a ciphertext and returns the plaintext. +authenticate(key, message) → signature + + Computes a MAC of the given message. +*/ + +fn to_openssl_name(curve: ECDSACurve) -> Result { + match curve { + ECDSACurve::SECP256R1 => Ok(Nid::X9_62_PRIME256V1), + ECDSACurve::SECP384R1 => Ok(Nid::SECP384R1), + ECDSACurve::SECP521R1 => Ok(Nid::SECP521R1), + x => Err(BackendError::UnsupportedCurve(x)), + } +} + +fn affine_coordinates(curve: ECDSACurve, bytes: &[u8]) -> Result<(ByteBuf, ByteBuf)> { + let name = to_openssl_name(curve)?; + let group = EcGroup::from_curve_name(name)?; + + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, bytes, &mut ctx)?; + + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; + + point.affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?; + //point.affine_coordinates_gf2m(&group, &mut x, &mut y, &mut ctx)?; + + Ok((ByteBuf::from(x.to_vec()), ByteBuf::from(y.to_vec()))) +} + +// TODO(MS): Maybe remove ByteBuf and return Vec's instead for a cleaner interface +pub(crate) fn serialize_key(curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> { + affine_coordinates(curve, key) +} + +#[cfg(test)] +pub(crate) fn parse_key(curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result> { + let name = to_openssl_name(curve)?; + let group = EcGroup::from_curve_name(name)?; + + let mut ctx = BigNumContext::new()?; + let x = BigNum::from_slice(x)?; + let y = BigNum::from_slice(y)?; + + let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?; + // TODO(baloo): what is uncompressed?! + let pub_key = key.public_key(); + + Ok(pub_key.to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx)?) +} + +/// This is run by the platform when starting a series of transactions with a specific authenticator. +//pub(crate) fn initialize() { +// +//} + +/// Generates an encapsulation for the authenticator’s public key and returns the message +/// to transmit and the shared secret. +pub(crate) fn encapsulate(key: &COSEKey) -> Result { + if let COSEKeyType::EC2(ec2key) = &key.key { + let curve_name = to_openssl_name(ec2key.curve)?; + let group = EcGroup::from_curve_name(curve_name)?; + let my_key = EcKey::generate(&group)?; + + encapsulate_helper(&ec2key, key.alg, group, my_key) + } else { + Err(BackendError::UnsupportedKeyType) + } +} + +pub(crate) fn encapsulate_helper( + key: &COSEEC2Key, + alg: COSEAlgorithm, + group: EcGroup, + my_key: EcKey, +) -> Result { + let mut ctx = BigNumContext::new()?; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; + + my_key + .public_key() + .affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?; + + let my_public_key = COSEKey { + alg, + key: COSEKeyType::EC2(COSEEC2Key { + curve: key.curve.clone(), + x: x.to_vec(), + y: y.to_vec(), + }), + }; + + // let point = EcPoint::from_bytes(&group, &key.key, &mut ctx)?; + let peer_public_key = PKey::from_ec_key(EcKey::from_public_key_affine_coordinates( + &group, + BigNum::from_slice(&key.x).as_ref()?, + BigNum::from_slice(&key.y).as_ref()?, + )?)?; + + let my_ec_key = PKey::from_ec_key(my_key)?; + let mut deriver = Deriver::new(my_ec_key.as_ref())?; + deriver.set_peer(&peer_public_key)?; + let shared_sec = deriver.derive_to_vec()?; + + // Hashing the key material + let digest = hash(MessageDigest::sha256(), &shared_sec)?; + + Ok(ECDHSecret { + remote: COSEKey { + alg, + key: COSEKeyType::EC2(key.clone()), + }, + my: my_public_key, + shared_secret: digest.as_ref().to_vec(), + }) +} + +#[cfg(test)] +pub(crate) fn test_encapsulate( + key: &COSEEC2Key, + alg: COSEAlgorithm, + my_pub_key: &[u8], + my_priv_key: &[u8], +) -> Result { + let curve_name = to_openssl_name(key.curve)?; + let group = EcGroup::from_curve_name(curve_name)?; + + let mut ctx = BigNumContext::new()?; + let my_pub_point = EcPoint::from_bytes(&group, &my_pub_key, &mut ctx)?; + let my_priv_bignum = BigNum::from_slice(my_priv_key)?; + let my_key = EcKey::from_private_components(&group, &my_priv_bignum, &my_pub_point)?; + + encapsulate_helper(key, alg, group, my_key) +} + +/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. +/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +pub(crate) fn encrypt( + key: &[u8], + plain_text: &[u8], /*PlainText*/ +) -> Result /*CypherText*/> { + let cipher = Cipher::aes_256_cbc(); + + // TODO(baloo): This might trigger a panic if size is not big enough + let mut cypher_text = vec![0; plain_text.len() * 2]; + cypher_text.resize(plain_text.len() * 2, 0); + // Spec says explicitly IV=0 + let iv = [0u8; 16]; + let mut encrypter = Crypter::new(cipher, Mode::Encrypt, key, Some(&iv))?; + encrypter.pad(false); + let mut out_size = 0; + out_size += encrypter.update(plain_text, cypher_text.as_mut_slice())?; + out_size += encrypter.finalize(cypher_text.as_mut_slice())?; + cypher_text.truncate(out_size); + Ok(cypher_text) +} + +/// Decrypts a ciphertext and returns the plaintext. +pub(crate) fn decrypt( + key: &[u8], + cypher_text: &[u8], /*CypherText*/ +) -> Result /*PlainText*/> { + let cipher = Cipher::aes_256_cbc(); + + // TODO(baloo): This might trigger a panic if size is not big enough + let mut plain_text = vec![0; cypher_text.len() * 2]; + plain_text.resize(cypher_text.len() * 2, 0); + // Spec says explicitly IV=0 + let iv = [0u8; 16]; + let mut encrypter = Crypter::new(cipher, Mode::Decrypt, key, Some(&iv))?; + encrypter.pad(false); + let mut out_size = 0; + out_size += encrypter.update(cypher_text, plain_text.as_mut_slice())?; + out_size += encrypter.finalize(plain_text.as_mut_slice())?; + plain_text.truncate(out_size); + + Ok(plain_text) +} + +/// Computes a MAC of the given message. +pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result> { + // Create a PKey + let key = PKey::hmac(token)?; + + // Compute the HMAC + let mut signer = Signer::new(MessageDigest::sha256(), &key)?; + signer.update(input)?; + let hmac = signer.sign_to_vec()?; + Ok(hmac) +} + +// Currently unsued, because rc_crypto does not expose PKCS12 of NSS, so we can't parse the cert there +// To use it in statemachine.rs for example, do: +// if let Ok(cdhash) = client_data.hash() { +// let verification_data: Vec = attestation +// .auth_data +// .to_vec() +// .iter() +// .chain(cdhash.as_ref().iter()) +// .copied() +// .collect(); +// let res = attestation.att_statement.verify(&verification_data); +// ... +// } +#[allow(dead_code)] +pub(crate) fn verify( + sig_alg: ECDSACurve, + pub_key: &[u8], + signature: &[u8], + data: &[u8], +) -> Result { + let _alg = to_openssl_name(sig_alg)?; // TODO(MS): Actually use this to determine the right MessageDigest below + let pkey = X509::from_der(&pub_key)?; + let pubkey = pkey.public_key()?; + let mut verifier = Verifier::new(MessageDigest::sha256(), &pubkey)?; + verifier.update(data)?; + let res = verifier.verify(signature)?; + Ok(res) +} diff --git a/third_party/rust/authenticator/src/crypto/ring.rs b/third_party/rust/authenticator/src/crypto/ring.rs new file mode 100644 index 000000000000..ff8178ae5732 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/ring.rs @@ -0,0 +1,168 @@ +use super::{ + /*Signature,*/ COSEAlgorithm, COSEEC2Key, /*PlainText*/ COSEKey, COSEKeyType, + /*CypherText,*/ ECDHSecret, ECDSACurve, +}; +use ring::agreement::{ + agree_ephemeral, Algorithm, EphemeralPrivateKey, UnparsedPublicKey, ECDH_P256, ECDH_P384, +}; +use ring::digest; +use ring::hmac; +use ring::rand::SystemRandom; +use ring::signature::KeyPair; +use serde::Serialize; +use serde_bytes::ByteBuf; +/* +initialize() + + This is run by the platform when starting a series of transactions with a specific authenticator. +encapsulate(peerCoseKey) → (coseKey, sharedSecret) | error + + Generates an encapsulation for the authenticator’s public key and returns the message to transmit and the shared secret. +encrypt(key, demPlaintext) → ciphertext + + Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +decrypt(key, ciphertext) → plaintext | error + + Decrypts a ciphertext and returns the plaintext. +authenticate(key, message) → signature + + Computes a MAC of the given message. +*/ + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum BackendError { + AgreementError, + UnspecifiedRingError, + KeyRejected, + UnsupportedKeyType, + UnsupportedCurve(ECDSACurve), +} + +fn to_ring_curve(curve: ECDSACurve) -> Result<&'static Algorithm> { + match curve { + ECDSACurve::SECP256R1 => Ok(&ECDH_P256), + ECDSACurve::SECP384R1 => Ok(&ECDH_P384), + x => Err(BackendError::UnsupportedCurve(x)), + } +} + +impl From for BackendError { + fn from(e: ring::error::Unspecified) -> Self { + BackendError::UnspecifiedRingError + } +} + +impl From for BackendError { + fn from(e: ring::error::KeyRejected) -> Self { + BackendError::KeyRejected + } +} + +pub(crate) fn parse_key(curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result> { + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +// TODO(MS): Maybe remove ByteBuf and return Vec's instead for a cleaner interface +pub(crate) fn serialize_key(curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> { + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +/// This is run by the platform when starting a series of transactions with a specific authenticator. +pub(crate) fn initialize() { + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +/// Generates an encapsulation for the authenticator’s public key and returns the message +/// to transmit and the shared secret. +pub(crate) fn encapsulate(key: &COSEKey) -> Result { + if let COSEKeyType::EC2(ec2key) = &key.key { + // let curve_name = to_openssl_name(ec2key.curve)?; + // let group = EcGroup::from_curve_name(curve_name)?; + // let my_key = EcKey::generate(&group)?; + + // encapsulate_helper(&ec2key, key.alg, group, my_key) + let rng = SystemRandom::new(); + let peer_public_key_alg = to_ring_curve(ec2key.curve)?; + let private_key = EphemeralPrivateKey::generate(peer_public_key_alg, &rng)?; + let my_public_key = private_key.compute_public_key()?; + let (x, y) = serialize_key(ec2key.curve, my_public_key.as_ref())?; + let my_public_key = COSEKey { + alg: key.alg, + key: COSEKeyType::EC2(COSEEC2Key { + curve: ec2key.curve, + x: x.to_vec(), + y: y.to_vec(), + }), + }; + + let key_bytes = parse_key(ec2key.curve, &ec2key.x, &ec2key.y)?; + let peer_public_key = UnparsedPublicKey::new(peer_public_key_alg, &key_bytes); + + let shared_secret = agree_ephemeral(private_key, &peer_public_key, (), |key_material| { + let digest = digest::digest(&digest::SHA256, key_material); + Ok(Vec::from(digest.as_ref())) + }) + .map_err(|_| BackendError::AgreementError)?; + + Ok(ECDHSecret { + remote: COSEKey { + alg: key.alg, + key: COSEKeyType::EC2(ec2key.clone()), + }, + my: my_public_key, + shared_secret, + }) + } else { + Err(BackendError::UnsupportedKeyType) + } +} + +#[cfg(test)] +pub(crate) fn test_encapsulate( + key: &COSEEC2Key, + alg: COSEAlgorithm, + my_pub_key: &[u8], + my_priv_key: &[u8], +) -> Result { + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. +/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. +pub(crate) fn encrypt( + key: &[u8], + plain_text: &[u8], /*PlainText*/ +) -> Result /*CypherText*/> { + // Ring doesn't support AES-CBC yet + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +/// Decrypts a ciphertext and returns the plaintext. +pub(crate) fn decrypt( + key: &[u8], + cypher_text: &[u8], /*CypherText*/ +) -> Result /*PlainText*/> { + // Ring doesn't support AES-CBC yet + compile_error!( + "Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now." + ) +} + +/// Computes a MAC of the given message. +pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result> { + let s_key = hmac::Key::new(hmac::HMAC_SHA256, token); + let tag = hmac::sign(&s_key, input); + Ok(tag.as_ref().to_vec()) +} diff --git a/third_party/rust/authenticator/src/ctap2-capi.h b/third_party/rust/authenticator/src/ctap2-capi.h new file mode 100644 index 000000000000..d9f5b903b9d9 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2-capi.h @@ -0,0 +1,254 @@ +/* -*- 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 __CTAP2_CAPI +#define __CTAP2_CAPI +#include +#include "nsString.h" + +extern "C" { +const uint8_t CTAP2_SIGN_RESULT_PUBKEY_CRED_ID = 1; +const uint8_t CTAP2_SIGN_RESULT_AUTH_DATA = 2; +const uint8_t CTAP2_SIGN_RESULT_SIGNATURE = 3; +const uint8_t CTAP2_SIGN_RESULT_USER_ID = 4; +const uint8_t CTAP2_SIGN_RESULT_USER_NAME = 5; + +typedef struct { + const uint8_t *id_ptr; + size_t id_len; + const char *name; +} AuthenticatorArgsUser; + +typedef struct { + const uint8_t *ptr; + size_t len; +} AuthenticatorArgsChallenge; + +typedef struct { + const int32_t *ptr; + size_t len; +} AuthenticatorArgsPubCred; + +typedef struct { + bool resident_key; + bool user_verification; + bool user_presence; + bool force_none_attestation; +} AuthenticatorArgsOptions; + +// 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_pkcd_new, and must be freed with rust_u2f_pkcd_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_key_handles` opaque type is equivalent to the rust type +// `Ctap2PubKeyCredDescriptors` +struct rust_ctap2_pub_key_cred_descriptors; + +/// Ctap2PubKeyCredDescriptors functions. +rust_ctap2_pub_key_cred_descriptors* rust_ctap2_pkcd_new(); +void rust_ctap2_pkcd_add(rust_ctap2_pub_key_cred_descriptors* pkcd, const uint8_t* id_ptr, + size_t id_len, uint8_t transports); +/* unsafe */ void rust_ctap2_pkcd_free(rust_ctap2_pub_key_cred_descriptors* khs); + +// The `rust_ctap2_mgr` opaque type is equivalent to the rust type `Ctap2Manager` +// struct rust_ctap_manager; + +// The `rust_ctap2_result` opaque type is equivalent to the rust type `RegisterResult` +struct rust_ctap2_register_result; + +// The `rust_ctap2_result` opaque type is equivalent to the rust type `RegisterResult` +struct rust_ctap2_sign_result; + +// Ctap2 exposes the results directly without repackaging them. Use getter-functions. +typedef void (*rust_ctap2_register_callback)(uint64_t, rust_ctap2_register_result*); +typedef void (*rust_ctap2_sign_callback)(uint64_t, rust_ctap2_sign_result*); + +// Status updates get sent, if a device needs a PIN, if a device needs to be selected, etc. +struct rust_ctap2_status_update_res; +// May be called with NULL, in case of an error +typedef void (*rust_ctap2_status_update_callback)(rust_ctap2_status_update_res*); + +rust_ctap_manager* rust_ctap2_mgr_new(); +/* unsafe */ void rust_ctap2_mgr_free(rust_ctap_manager* mgr); + +/* unsafe */ void rust_ctap2_register_res_free(rust_ctap2_register_result* res); +/* unsafe */ void rust_ctap2_sign_res_free(rust_ctap2_sign_result* res); + +uint64_t rust_ctap2_mgr_register( + rust_ctap_manager* mgr, uint64_t timeout, rust_ctap2_register_callback, rust_ctap2_status_update_callback, + AuthenticatorArgsChallenge challenge, + const char* relying_party_id, const char *origin_ptr, + AuthenticatorArgsUser user, AuthenticatorArgsPubCred pub_cred_params, + const rust_ctap2_pub_key_cred_descriptors* exclude_list, AuthenticatorArgsOptions options, + const char *pin +); + +uint64_t rust_ctap2_mgr_sign( + rust_ctap_manager* mgr, uint64_t timeout, rust_ctap2_sign_callback, rust_ctap2_status_update_callback, + AuthenticatorArgsChallenge challenge, + const char* relying_party_id, const char *origin_ptr, + const rust_ctap2_pub_key_cred_descriptors* allow_list, AuthenticatorArgsOptions options, + const char *pin +); + +void rust_ctap2_mgr_cancel(rust_ctap_manager* mgr); + +// Returns 0 for success, or the U2F_ERROR error code >= 1. +uint8_t rust_ctap2_register_result_error(const rust_ctap2_register_result* res); +uint8_t rust_ctap2_sign_result_error(const rust_ctap2_sign_result* res); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_client_data_copy() +bool rust_ctap2_register_result_client_data_len( + const rust_ctap2_register_result *res, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_client_data_len) +bool rust_ctap2_register_result_client_data_copy( + const rust_ctap2_register_result *res, + const char *dst +); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_item_copy() +bool rust_ctap2_register_result_attestation_len( + const rust_ctap2_register_result *res, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_item_len) +bool rust_ctap2_register_result_attestation_copy( + const rust_ctap2_register_result* res, + uint8_t *dst +); +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_client_data_copy() +bool rust_ctap2_sign_result_client_data_len( + const rust_ctap2_sign_result *res, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_client_data_len) +bool rust_ctap2_sign_result_client_data_copy( + const rust_ctap2_sign_result *res, + const char *dst +); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_client_data_copy() +bool rust_ctap2_sign_result_assertions_len( + const rust_ctap2_sign_result *res, + size_t *len +); + +bool rust_ctap2_sign_result_item_contains( + const rust_ctap2_sign_result *res, + size_t assertion_idx, + uint8_t item_idx +); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_sign_result_item_copy() +bool rust_ctap2_sign_result_item_len( + const rust_ctap2_sign_result *res, + size_t assertion_idx, + uint8_t item_idx, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_item_len) +bool rust_ctap2_sign_result_item_copy( + const rust_ctap2_sign_result* res, + size_t assertion_idx, + uint8_t item_idx, + uint8_t *dst +); + +bool rust_ctap2_sign_result_contains_username( + const rust_ctap2_sign_result *res, + size_t assertion_idx +); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_sign_result_username_copy() +bool rust_ctap2_sign_result_username_len( + const rust_ctap2_sign_result *res, + size_t assertion_idx, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_username_len) +bool rust_ctap2_sign_result_username_copy( + const rust_ctap2_sign_result* res, + size_t assertion_idx, + const char *dst +); + +/// # Safety +/// +/// This function is used to get the length, prior to calling +/// rust_ctap2_status_update_copy_json() +bool rust_ctap2_status_update_len( + const rust_ctap2_status_update_res *res, + size_t *len +); + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_status_update_len) +bool rust_ctap2_status_update_copy_json( + const rust_ctap2_status_update_res *res, + const char *dst +); + +bool rust_ctap2_status_update_send_pin( + const rust_ctap2_status_update_res *res, + const char *pin +); + + +/// # Safety +/// This frees the memory of a status_update_res +bool rust_ctap2_destroy_status_update_res( + rust_ctap2_status_update_res *res +); + + +} +#endif // __CTAP2_CAPI diff --git a/third_party/rust/authenticator/src/ctap2/attestation.rs b/third_party/rust/authenticator/src/ctap2/attestation.rs new file mode 100644 index 000000000000..896db0dee3a5 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/attestation.rs @@ -0,0 +1,799 @@ +use super::utils::from_slice_stream; +use crate::crypto::COSEAlgorithm; +use crate::ctap2::commands::CommandError; +use crate::ctap2::server::RpIdHash; +use crate::{crypto::COSEKey, errors::AuthenticatorError}; +use nom::{ + bytes::complete::take, + combinator::{cond, map}, + error::Error as NomError, + number::complete::{be_u16, be_u32, be_u8}, + Err as NomErr, IResult, +}; +use serde::ser::{Error as SerError, SerializeMap, Serializer}; +use serde::{ + de::{Error as SerdeError, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use serde_bytes::ByteBuf; +use serde_cbor; +use std::fmt; + +#[derive(Debug, PartialEq, Eq)] +pub enum HmacSecretResponse { + /// This is returned by MakeCredential calls to display if CredRandom was + /// successfully generated + Confirmed(bool), + /// This is returned by GetAssertion: + /// AES256-CBC(shared_secret, HMAC-SHA265(CredRandom, salt1) || HMAC-SHA265(CredRandom, salt2)) + Secret(Vec), +} + +impl Serialize for HmacSecretResponse { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + HmacSecretResponse::Confirmed(x) => serializer.serialize_bool(*x), + HmacSecretResponse::Secret(x) => serializer.serialize_bytes(&x), + } + } +} +impl<'de> Deserialize<'de> for HmacSecretResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct HmacSecretResponseVisitor; + + impl<'de> Visitor<'de> for HmacSecretResponseVisitor { + type Value = HmacSecretResponse; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array or a boolean") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: SerdeError, + { + Ok(HmacSecretResponse::Secret(v.to_vec())) + } + + fn visit_bool(self, v: bool) -> Result + where + E: SerdeError, + { + Ok(HmacSecretResponse::Confirmed(v)) + } + } + deserializer.deserialize_any(HmacSecretResponseVisitor) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)] +pub struct Extension { + #[serde(rename = "pinMinLength", skip_serializing_if = "Option::is_none")] + pub pin_min_length: Option, + #[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")] + pub hmac_secret: Option, +} + +fn parse_extensions<'a>(input: &'a [u8]) -> IResult<&'a [u8], Extension, NomError<&'a [u8]>> { + serde_to_nom(input) +} + +#[derive(Serialize, PartialEq, Default, Eq, Clone)] +pub struct AAGuid(pub [u8; 16]); + +impl AAGuid { + pub fn from(src: &[u8]) -> Result { + let mut payload = [0u8; 16]; + if src.len() != payload.len() { + Err(()) + } else { + payload.copy_from_slice(src); + Ok(AAGuid(payload)) + } + } +} + +impl fmt::Debug for AAGuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "AAGuid({:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x})", + self.0[0], + self.0[1], + self.0[2], + self.0[3], + self.0[4], + self.0[5], + self.0[6], + self.0[7], + self.0[8], + self.0[9], + self.0[10], + self.0[11], + self.0[12], + self.0[13], + self.0[14], + self.0[15] + ) + } +} + +impl<'de> Deserialize<'de> for AAGuid { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AAGuidVisitor; + + impl<'de> Visitor<'de> for AAGuidVisitor { + type Value = AAGuid; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: SerdeError, + { + let mut buf = [0u8; 16]; + if v.len() != buf.len() { + return Err(E::invalid_length(v.len(), &"16")); + } + + buf.copy_from_slice(v); + + Ok(AAGuid(buf)) + } + } + + deserializer.deserialize_bytes(AAGuidVisitor) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AttestedCredentialData { + pub aaguid: AAGuid, + pub credential_id: Vec, + pub credential_public_key: COSEKey, +} + +fn serde_to_nom<'a, Output>(input: &'a [u8]) -> IResult<&'a [u8], Output> +where + Output: Deserialize<'a>, +{ + from_slice_stream(input) + .map_err(|_e| nom::Err::Error(nom::error::make_error(input, nom::error::ErrorKind::NoneOf))) + // can't use custom errorkind because of error type mismatch in parse_attested_cred_data + //.map_err(|e| NomErr::Error(Context::Code(input, ErrorKind::Custom(e)))) + // .map_err(|_| NomErr::Error(Context::Code(input, ErrorKind::Custom(42)))) +} + +fn parse_attested_cred_data<'a>( + input: &'a [u8], +) -> IResult<&'a [u8], AttestedCredentialData, NomError<&'a [u8]>> { + let (rest, aaguid_res) = map(take(16u8), AAGuid::from)(input)?; + // // We can unwrap here, since we _know_ the input will be 16 bytes error out before calling from() + let aaguid = aaguid_res.unwrap(); + let (rest, cred_len) = be_u16(rest)?; + let (rest, credential_id) = map(take(cred_len), Vec::from)(rest)?; + let (rest, credential_public_key) = serde_to_nom(rest)?; + Ok(( + rest, + (AttestedCredentialData { + aaguid, + credential_id, + credential_public_key: credential_public_key, + }), + )) +} + +bitflags! { + pub struct AuthenticatorDataFlags: u8 { + const USER_PRESENT = 0x01; + const USER_VERIFIED = 0x04; + const ATTESTED = 0x40; + const EXTENSION_DATA = 0x80; + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AuthenticatorData { + pub rp_id_hash: RpIdHash, + pub flags: AuthenticatorDataFlags, + pub counter: u32, + pub credential_data: Option, + pub extensions: Extension, +} + +fn parse_ad<'a>(input: &'a [u8]) -> IResult<&'a [u8], AuthenticatorData, NomError<&'a [u8]>> { + let (rest, rp_id_hash_res) = map(take(32u8), RpIdHash::from)(input)?; + // We can unwrap here, since we _know_ the input to from() will be 32 bytes or error out before calling from() + let rp_id_hash = rp_id_hash_res.unwrap(); + // be conservative, there is a couple of reserved values in + // AuthenticatorDataFlags, just truncate the one we don't know + let (rest, flags) = map(be_u8, AuthenticatorDataFlags::from_bits_truncate)(rest)?; + let (rest, counter) = be_u32(rest)?; + let (rest, credential_data) = cond( + flags.contains(AuthenticatorDataFlags::ATTESTED), + parse_attested_cred_data, + )(rest)?; + let (rest, extensions) = cond( + flags.contains(AuthenticatorDataFlags::EXTENSION_DATA), + parse_extensions, + )(rest)?; + // TODO(baloo): we should check for end of buffer and raise a parse + // parse error if data is still in the buffer + //eof!() >> + Ok(( + rest, + AuthenticatorData { + rp_id_hash, + flags, + counter, + credential_data, + extensions: extensions.unwrap_or_default(), + }, + )) +} + +impl<'de> Deserialize<'de> for AuthenticatorData { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AuthenticatorDataVisitor; + + impl<'de> Visitor<'de> for AuthenticatorDataVisitor { + type Value = AuthenticatorData; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: SerdeError, + { + parse_ad(v) + .map(|(_input, value)| value) + .map_err(|e| match e { + NomErr::Incomplete(nom::Needed::Size(len)) => { + E::invalid_length(v.len(), &format!("{}", v.len() + len.get()).as_ref()) + } + NomErr::Incomplete(nom::Needed::Unknown) => { + E::invalid_length(v.len(), &"unknown") // We don't know the expected value + } + // TODO(baloo): is that enough? should we be more + // specific on the error type? + e => E::custom(e.to_string()), + }) + } + } + + deserializer.deserialize_bytes(AuthenticatorDataVisitor) + } +} + +impl AuthenticatorData { + pub fn to_vec(&self) -> Result, AuthenticatorError> { + let mut data = Vec::new(); + data.extend(&self.rp_id_hash.0); + data.extend(&[self.flags.bits()]); + data.extend(&self.counter.to_be_bytes()); + + // TODO(baloo): need to yield credential_data and extensions, but that dependends on flags, + // should we consider another type system? + if let Some(cred) = &self.credential_data { + data.extend(&cred.aaguid.0); + data.extend(&(cred.credential_id.len() as u16).to_be_bytes()); + data.extend(&cred.credential_id); + data.extend( + &serde_cbor::to_vec(&cred.credential_public_key) + .map_err(CommandError::Serializing)?, + ); + } + Ok(data) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +/// x509 encoded attestation certificate +pub struct AttestationCertificate(#[serde(with = "serde_bytes")] pub(crate) Vec); + +impl AsRef<[u8]> for AttestationCertificate { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq)] +pub struct Signature(#[serde(with = "serde_bytes")] pub(crate) ByteBuf); + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD); + write!(f, "Signature({})", value) + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum AttestationStatement { + None, + Packed(AttestationStatementPacked), + // TODO(baloo): there is a couple other options than None and Packed: + // https://w3c.github.io/webauthn/#generating-an-attestation-object + // https://w3c.github.io/webauthn/#defined-attestation-formats + //TPM, + //AndroidKey, + //AndroidSafetyNet, + FidoU2F(AttestationStatementFidoU2F), +} + +// Not all crypto-backends currently provide "crypto::verify()", so we do not implement it yet. +// Also not sure, if we really need it. Would be a sanity-check only, to verify the signature is valid, +// before sendig it out. +// impl AttestationStatement { +// pub fn verify(&self, data: &[u8]) -> Result { +// match self { +// AttestationStatement::None => Ok(true), +// AttestationStatement::Unparsed(_) => Err(AuthenticatorError::Custom( +// "Unparsed attestation object can't be used to verify signature.".to_string(), +// )), +// AttestationStatement::FidoU2F(att) => { +// let res = crypto::verify( +// crypto::SignatureAlgorithm::ES256, +// &att.attestation_cert[0].as_ref(), +// att.sig.as_ref(), +// data, +// )?; +// Ok(res) +// } +// AttestationStatement::Packed(att) => { +// if att.alg != Alg::ES256 { +// return Err(AuthenticatorError::Custom( +// "Verification only supported for ES256".to_string(), +// )); +// } +// let res = crypto::verify( +// crypto::SignatureAlgorithm::ES256, +// att.attestation_cert[0].as_ref(), +// att.sig.as_ref(), +// data, +// )?; +// Ok(res) +// } +// } +// } +// } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +// See https://www.w3.org/TR/webauthn/#fido-u2f-attestation +pub struct AttestationStatementFidoU2F { + pub sig: Signature, + + #[serde(rename = "x5c")] + /// Certificate chain in x509 format + pub attestation_cert: Vec, +} + +#[allow(dead_code)] // TODO(MS): Remove me, once we can parse AttestationStatements and use this function +impl AttestationStatementFidoU2F { + pub fn new(cert: &[u8], signature: &[u8]) -> Self { + AttestationStatementFidoU2F { + sig: Signature(ByteBuf::from(signature)), + attestation_cert: vec![AttestationCertificate(Vec::from(cert))], + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +// TODO(baloo): there is a couple other options than x5c: +// https://www.w3.org/TR/webauthn/#packed-attestation +pub struct AttestationStatementPacked { + pub alg: COSEAlgorithm, + pub sig: Signature, + + #[serde(rename = "x5c")] + /// Certificate chain in x509 format + pub attestation_cert: Vec, +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +enum AttestationFormat { + #[serde(rename = "fido-u2f")] + FidoU2F, + Packed, + None, + // TOOD(baloo): only packed is implemented for now, see spec: + // https://www.w3.org/TR/webauthn/#defined-attestation-formats + //TPM, + //AndroidKey, + //AndroidSafetyNet, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AttestationObject { + pub auth_data: AuthenticatorData, + pub att_statement: AttestationStatement, +} + +impl<'de> Deserialize<'de> for AttestationObject { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AttestationObjectVisitor; + + impl<'de> Visitor<'de> for AttestationObjectVisitor { + type Value = AttestationObject; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a cbor map") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut format: Option = None; + let mut auth_data = None; + let mut att_statement = None; + + while let Some(key) = map.next_key()? { + match key { + // Spec for CTAP 2.0 is wrong and fmt should be numbered 1, and auth_data 2: + // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential + // Corrected in CTAP 2.1 and Webauthn spec + 1 => { + if format.is_some() { + return Err(SerdeError::duplicate_field("fmt")); + } + format = Some(map.next_value()?); + } + 2 => { + if auth_data.is_some() { + return Err(SerdeError::duplicate_field("auth_data")); + } + auth_data = Some(map.next_value()?); + } + 3 => { + let format = format + .as_ref() + .ok_or_else(|| SerdeError::missing_field("fmt"))?; + if att_statement.is_some() { + return Err(SerdeError::duplicate_field("att_statement")); + } + match format { + // This should not actually happen, but ... + AttestationFormat::None => { + att_statement = Some(AttestationStatement::None); + } + AttestationFormat::Packed => { + att_statement = + Some(AttestationStatement::Packed(map.next_value()?)); + } + AttestationFormat::FidoU2F => { + att_statement = + Some(AttestationStatement::FidoU2F(map.next_value()?)); + } + } + } + k => return Err(M::Error::custom(format!("unexpected key: {:?}", k))), + } + } + + let auth_data = + auth_data.ok_or_else(|| M::Error::custom("found no auth_data".to_string()))?; + let att_statement = att_statement.unwrap_or(AttestationStatement::None); + + Ok(AttestationObject { + auth_data, + att_statement, + }) + } + } + + deserializer.deserialize_bytes(AttestationObjectVisitor) + } +} + +impl Serialize for AttestationObject { + /// Serialize can be used to repackage the CBOR answer we get from the token using CTAP-format + /// to webauthn-format (string-keys like "authData" instead of numbers). Yes, the specs are weird. + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let map_len = 3; + let mut map = serializer.serialize_map(Some(map_len))?; + + let auth_data = self + .auth_data + .to_vec() + .map(|v| serde_cbor::Value::Bytes(v)) + .map_err(|_| SerError::custom("Failed to serialize auth_data"))?; + + map.serialize_entry(&"authData", &auth_data)?; + match self.att_statement { + AttestationStatement::None => { + // Even with Att None, an empty map is returned in the cbor! + map.serialize_entry(&"fmt", &"none")?; + let v = serde_cbor::Value::Map(std::collections::BTreeMap::new()); + map.serialize_entry(&"attStmt", &v)?; + } + AttestationStatement::Packed(ref v) => { + map.serialize_entry(&"fmt", &"packed")?; + map.serialize_entry(&"attStmt", v)?; + } + AttestationStatement::FidoU2F(ref v) => { + map.serialize_entry(&"fmt", &"fido-u2f")?; + map.serialize_entry(&"attStmt", v)?; + } + } + map.end() + } +} + +#[cfg(test)] +mod test { + use super::super::utils::from_slice_stream; + use super::*; + use serde_cbor::from_slice; + + const SAMPLE_ATTESTATION: [u8; 1006] = [ + 0xa3, 0x1, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x2, 0x58, 0xc4, 0x49, 0x96, 0xd, + 0xe5, 0x88, 0xe, 0x8c, 0x68, 0x74, 0x34, 0x17, 0xf, 0x64, 0x76, 0x60, 0x5b, 0x8f, 0xe4, + 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63, 0x41, + 0x0, 0x0, 0x0, 0x7, 0xcb, 0x69, 0x48, 0x1e, 0x8f, 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, + 0x29, 0xa1, 0x54, 0xa8, 0x0, 0x40, 0xc3, 0xcf, 0x1, 0x3b, 0xc6, 0x26, 0x93, 0x28, 0xfb, + 0x7f, 0xa9, 0x76, 0xef, 0xa8, 0x4b, 0x66, 0x71, 0xad, 0xa9, 0x64, 0xea, 0xcb, 0x58, 0x76, + 0x54, 0x51, 0xa, 0xc8, 0x86, 0x4f, 0xbb, 0x53, 0x2d, 0xfb, 0x2, 0xfc, 0xdc, 0xa9, 0x84, + 0xc2, 0x5c, 0x67, 0x8a, 0x3a, 0xab, 0x57, 0xf3, 0x71, 0x77, 0xd3, 0xd4, 0x41, 0x64, 0x1, + 0x50, 0xca, 0x6c, 0x42, 0x73, 0x1c, 0x42, 0xcb, 0x81, 0xba, 0xa5, 0x1, 0x2, 0x3, 0x26, + 0x20, 0x1, 0x21, 0x58, 0x20, 0x9, 0x2e, 0x34, 0xfe, 0xa7, 0xd7, 0x32, 0xc8, 0xae, 0x4c, + 0xf6, 0x96, 0xbe, 0x7a, 0x12, 0xdc, 0x29, 0xd5, 0xf1, 0xd3, 0xf1, 0x55, 0x4d, 0xdc, 0x87, + 0xc4, 0xc, 0x9b, 0xd0, 0x17, 0xba, 0xf, 0x22, 0x58, 0x20, 0xc9, 0xf0, 0x97, 0x33, 0x55, + 0x36, 0x58, 0xd9, 0xdb, 0x76, 0xf5, 0xef, 0x95, 0xcf, 0x8a, 0xc7, 0xfc, 0xc1, 0xb6, 0x81, + 0x25, 0x5f, 0x94, 0x6b, 0x62, 0x13, 0x7d, 0xd0, 0xc4, 0x86, 0x53, 0xdb, 0x3, 0xa3, 0x63, + 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x58, 0x48, 0x30, 0x46, 0x2, 0x21, 0x0, + 0xac, 0x2a, 0x78, 0xa8, 0xaf, 0x18, 0x80, 0x39, 0x73, 0x8d, 0x3, 0x5e, 0x4, 0x4d, 0x94, + 0x4f, 0x3f, 0x57, 0xce, 0x88, 0x41, 0xfa, 0x81, 0x50, 0x40, 0xb6, 0xd1, 0x95, 0xb5, 0xeb, + 0xe4, 0x6f, 0x2, 0x21, 0x0, 0x8f, 0xf4, 0x15, 0xc9, 0xb3, 0x6d, 0x1c, 0xd, 0x4c, 0xa3, + 0xcf, 0x99, 0x8a, 0x46, 0xd4, 0x4c, 0x8b, 0x5c, 0x26, 0x3f, 0xdf, 0x22, 0x6c, 0x9b, 0x23, + 0x83, 0x8b, 0x69, 0x47, 0x67, 0x48, 0x45, 0x63, 0x78, 0x35, 0x63, 0x81, 0x59, 0x2, 0xc1, + 0x30, 0x82, 0x2, 0xbd, 0x30, 0x82, 0x1, 0xa5, 0xa0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x4, 0x18, + 0xac, 0x46, 0xc0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, + 0x5, 0x0, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x6, 0x3, 0x55, 0x4, 0x3, 0x13, 0x23, 0x59, + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, + 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0xd, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0xf, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30, + 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0xb, 0x30, 0x9, 0x6, 0x3, + 0x55, 0x4, 0x6, 0x13, 0x2, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x6, 0x3, 0x55, 0x4, 0xa, + 0xc, 0x9, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31, 0x22, 0x30, 0x20, + 0x6, 0x3, 0x55, 0x4, 0xb, 0xc, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x31, 0x27, 0x30, 0x25, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0x1e, 0x59, 0x75, 0x62, 0x69, + 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x20, 0x34, 0x31, 0x33, 0x39, 0x34, 0x33, 0x34, 0x38, 0x38, 0x30, 0x59, 0x30, 0x13, + 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x79, 0xea, 0x3b, 0x2c, 0x7c, 0x49, 0x70, 0x10, 0x62, + 0x23, 0xc, 0xd2, 0x3f, 0xeb, 0x60, 0xe5, 0x29, 0x31, 0x71, 0xd4, 0x83, 0xf1, 0x0, 0xbe, + 0x85, 0x9d, 0x6b, 0xf, 0x83, 0x97, 0x3, 0x1, 0xb5, 0x46, 0xcd, 0xd4, 0x6e, 0xcf, 0xca, + 0xe3, 0xe3, 0xf3, 0xf, 0x81, 0xe9, 0xed, 0x62, 0xbd, 0x26, 0x8d, 0x4c, 0x1e, 0xbd, 0x37, + 0xb3, 0xbc, 0xbe, 0x92, 0xa8, 0xc2, 0xae, 0xeb, 0x4e, 0x3a, 0xa3, 0x6c, 0x30, 0x6a, 0x30, + 0x22, 0x6, 0x9, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xc4, 0xa, 0x2, 0x4, 0x15, 0x31, 0x2e, + 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34, 0x38, 0x32, + 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xe5, 0x1c, + 0x2, 0x1, 0x1, 0x4, 0x4, 0x3, 0x2, 0x5, 0x20, 0x30, 0x21, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, + 0x1, 0x82, 0xe5, 0x1c, 0x1, 0x1, 0x4, 0x4, 0x12, 0x4, 0x10, 0xcb, 0x69, 0x48, 0x1e, 0x8f, + 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, 0x29, 0xa1, 0x54, 0xa8, 0x30, 0xc, 0x6, 0x3, 0x55, + 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x97, 0x9d, 0x3, 0x97, + 0xd8, 0x60, 0xf8, 0x2e, 0xe1, 0x5d, 0x31, 0x1c, 0x79, 0x6e, 0xba, 0xfb, 0x22, 0xfa, 0xa7, + 0xe0, 0x84, 0xd9, 0xba, 0xb4, 0xc6, 0x1b, 0xbb, 0x57, 0xf3, 0xe6, 0xb4, 0xc1, 0x8a, 0x48, + 0x37, 0xb8, 0x5c, 0x3c, 0x4e, 0xdb, 0xe4, 0x83, 0x43, 0xf4, 0xd6, 0xa5, 0xd9, 0xb1, 0xce, + 0xda, 0x8a, 0xe1, 0xfe, 0xd4, 0x91, 0x29, 0x21, 0x73, 0x5, 0x8e, 0x5e, 0xe1, 0xcb, 0xdd, + 0x6b, 0xda, 0xc0, 0x75, 0x57, 0xc6, 0xa0, 0xe8, 0xd3, 0x68, 0x25, 0xba, 0x15, 0x9e, 0x7f, + 0xb5, 0xad, 0x8c, 0xda, 0xf8, 0x4, 0x86, 0x8c, 0xf9, 0xe, 0x8f, 0x1f, 0x8a, 0xea, 0x17, + 0xc0, 0x16, 0xb5, 0x5c, 0x2a, 0x7a, 0xd4, 0x97, 0xc8, 0x94, 0xfb, 0x71, 0xd7, 0x53, 0xd7, + 0x9b, 0x9a, 0x48, 0x4b, 0x6c, 0x37, 0x6d, 0x72, 0x3b, 0x99, 0x8d, 0x2e, 0x1d, 0x43, 0x6, + 0xbf, 0x10, 0x33, 0xb5, 0xae, 0xf8, 0xcc, 0xa5, 0xcb, 0xb2, 0x56, 0x8b, 0x69, 0x24, 0x22, + 0x6d, 0x22, 0xa3, 0x58, 0xab, 0x7d, 0x87, 0xe4, 0xac, 0x5f, 0x2e, 0x9, 0x1a, 0xa7, 0x15, + 0x79, 0xf3, 0xa5, 0x69, 0x9, 0x49, 0x7d, 0x72, 0xf5, 0x4e, 0x6, 0xba, 0xc1, 0xc3, 0xb4, + 0x41, 0x3b, 0xba, 0x5e, 0xaf, 0x94, 0xc3, 0xb6, 0x4f, 0x34, 0xf9, 0xeb, 0xa4, 0x1a, 0xcb, + 0x6a, 0xe2, 0x83, 0x77, 0x6d, 0x36, 0x46, 0x53, 0x78, 0x48, 0xfe, 0xe8, 0x84, 0xbd, 0xdd, + 0xf5, 0xb1, 0xba, 0x57, 0x98, 0x54, 0xcf, 0xfd, 0xce, 0xba, 0xc3, 0x44, 0x5, 0x95, 0x27, + 0xe5, 0x6d, 0xd5, 0x98, 0xf8, 0xf5, 0x66, 0x71, 0x5a, 0xbe, 0x43, 0x1, 0xdd, 0x19, 0x11, + 0x30, 0xe6, 0xb9, 0xf0, 0xc6, 0x40, 0x39, 0x12, 0x53, 0xe2, 0x29, 0x80, 0x3f, 0x3a, 0xef, + 0x27, 0x4b, 0xed, 0xbf, 0xde, 0x3f, 0xcb, 0xbd, 0x42, 0xea, 0xd6, 0x79, + ]; + + const SAMPLE_CERT_CHAIN: [u8; 709] = [ + 0x81, 0x59, 0x2, 0xc1, 0x30, 0x82, 0x2, 0xbd, 0x30, 0x82, 0x1, 0xa5, 0xa0, 0x3, 0x2, 0x1, + 0x2, 0x2, 0x4, 0x18, 0xac, 0x46, 0xc0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x6, 0x3, 0x55, 0x4, 0x3, + 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35, + 0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0xd, 0x31, 0x34, 0x30, 0x38, + 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0xf, 0x32, 0x30, 0x35, 0x30, + 0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0xb, + 0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x6, + 0x3, 0x55, 0x4, 0xa, 0xc, 0x9, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31, + 0x22, 0x30, 0x20, 0x6, 0x3, 0x55, 0x4, 0xb, 0xc, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0x1e, 0x59, + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x31, 0x33, 0x39, 0x34, 0x33, 0x34, 0x38, 0x38, 0x30, + 0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, 0x8, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x79, 0xea, 0x3b, 0x2c, 0x7c, 0x49, + 0x70, 0x10, 0x62, 0x23, 0xc, 0xd2, 0x3f, 0xeb, 0x60, 0xe5, 0x29, 0x31, 0x71, 0xd4, 0x83, + 0xf1, 0x0, 0xbe, 0x85, 0x9d, 0x6b, 0xf, 0x83, 0x97, 0x3, 0x1, 0xb5, 0x46, 0xcd, 0xd4, 0x6e, + 0xcf, 0xca, 0xe3, 0xe3, 0xf3, 0xf, 0x81, 0xe9, 0xed, 0x62, 0xbd, 0x26, 0x8d, 0x4c, 0x1e, + 0xbd, 0x37, 0xb3, 0xbc, 0xbe, 0x92, 0xa8, 0xc2, 0xae, 0xeb, 0x4e, 0x3a, 0xa3, 0x6c, 0x30, + 0x6a, 0x30, 0x22, 0x6, 0x9, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xc4, 0xa, 0x2, 0x4, 0x15, + 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34, + 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, + 0xe5, 0x1c, 0x2, 0x1, 0x1, 0x4, 0x4, 0x3, 0x2, 0x5, 0x20, 0x30, 0x21, 0x6, 0xb, 0x2b, 0x6, + 0x1, 0x4, 0x1, 0x82, 0xe5, 0x1c, 0x1, 0x1, 0x4, 0x4, 0x12, 0x4, 0x10, 0xcb, 0x69, 0x48, + 0x1e, 0x8f, 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, 0x29, 0xa1, 0x54, 0xa8, 0x30, 0xc, + 0x6, 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xd, 0x6, 0x9, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x97, 0x9d, + 0x3, 0x97, 0xd8, 0x60, 0xf8, 0x2e, 0xe1, 0x5d, 0x31, 0x1c, 0x79, 0x6e, 0xba, 0xfb, 0x22, + 0xfa, 0xa7, 0xe0, 0x84, 0xd9, 0xba, 0xb4, 0xc6, 0x1b, 0xbb, 0x57, 0xf3, 0xe6, 0xb4, 0xc1, + 0x8a, 0x48, 0x37, 0xb8, 0x5c, 0x3c, 0x4e, 0xdb, 0xe4, 0x83, 0x43, 0xf4, 0xd6, 0xa5, 0xd9, + 0xb1, 0xce, 0xda, 0x8a, 0xe1, 0xfe, 0xd4, 0x91, 0x29, 0x21, 0x73, 0x5, 0x8e, 0x5e, 0xe1, + 0xcb, 0xdd, 0x6b, 0xda, 0xc0, 0x75, 0x57, 0xc6, 0xa0, 0xe8, 0xd3, 0x68, 0x25, 0xba, 0x15, + 0x9e, 0x7f, 0xb5, 0xad, 0x8c, 0xda, 0xf8, 0x4, 0x86, 0x8c, 0xf9, 0xe, 0x8f, 0x1f, 0x8a, + 0xea, 0x17, 0xc0, 0x16, 0xb5, 0x5c, 0x2a, 0x7a, 0xd4, 0x97, 0xc8, 0x94, 0xfb, 0x71, 0xd7, + 0x53, 0xd7, 0x9b, 0x9a, 0x48, 0x4b, 0x6c, 0x37, 0x6d, 0x72, 0x3b, 0x99, 0x8d, 0x2e, 0x1d, + 0x43, 0x6, 0xbf, 0x10, 0x33, 0xb5, 0xae, 0xf8, 0xcc, 0xa5, 0xcb, 0xb2, 0x56, 0x8b, 0x69, + 0x24, 0x22, 0x6d, 0x22, 0xa3, 0x58, 0xab, 0x7d, 0x87, 0xe4, 0xac, 0x5f, 0x2e, 0x9, 0x1a, + 0xa7, 0x15, 0x79, 0xf3, 0xa5, 0x69, 0x9, 0x49, 0x7d, 0x72, 0xf5, 0x4e, 0x6, 0xba, 0xc1, + 0xc3, 0xb4, 0x41, 0x3b, 0xba, 0x5e, 0xaf, 0x94, 0xc3, 0xb6, 0x4f, 0x34, 0xf9, 0xeb, 0xa4, + 0x1a, 0xcb, 0x6a, 0xe2, 0x83, 0x77, 0x6d, 0x36, 0x46, 0x53, 0x78, 0x48, 0xfe, 0xe8, 0x84, + 0xbd, 0xdd, 0xf5, 0xb1, 0xba, 0x57, 0x98, 0x54, 0xcf, 0xfd, 0xce, 0xba, 0xc3, 0x44, 0x5, + 0x95, 0x27, 0xe5, 0x6d, 0xd5, 0x98, 0xf8, 0xf5, 0x66, 0x71, 0x5a, 0xbe, 0x43, 0x1, 0xdd, + 0x19, 0x11, 0x30, 0xe6, 0xb9, 0xf0, 0xc6, 0x40, 0x39, 0x12, 0x53, 0xe2, 0x29, 0x80, 0x3f, + 0x3a, 0xef, 0x27, 0x4b, 0xed, 0xbf, 0xde, 0x3f, 0xcb, 0xbd, 0x42, 0xea, 0xd6, 0x79, + ]; + + const SAMPLE_AUTH_DATA_MAKE_CREDENTIAL: [u8; 164] = [ + 0x58, 0xA2, // bytes(162) + // authData + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, + 0x27, // rp_id_hash + 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, + 0x87, // rp_id_hash + 0x05, 0x1d, // rp_id_hash + 0xC1, // authData Flags + 0x00, 0x00, 0x00, 0x0b, // authData counter + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, + 0x7d, // AAGUID + 0x00, 0x10, // credential id length + 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, + 0x6f, // credential id + // credential public key + 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, + 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, + 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, + 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, + 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, + 0xa6, 0x1c, // pub key end + // Extensions + 0xA1, // map(1) + 0x6B, // text(11) + 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret" + 0xF5, // true + ]; + + const SAMPLE_AUTH_DATA_GET_ASSERTION: [u8; 229] = [ + 0x58, 0xE3, // bytes(227) + // authData + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, + 0x27, // rp_id_hash + 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, + 0x87, // rp_id_hash + 0x05, 0x1d, // rp_id_hash + 0xC1, // authData Flags + 0x00, 0x00, 0x00, 0x0b, // authData counter + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, + 0x7d, // AAGUID + 0x00, 0x10, // credential id length + 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, + 0x6f, // credential id + // credential public key + 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, + 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, + 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, + 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, + 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, + 0xa6, 0x1c, // pub key end + // Extensions + 0xA1, // map(1) + 0x6B, // text(11) + 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret" + 0x58, 0x40, // bytes(64) + 0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB, 0x87, + 0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22, 0x69, 0xAF, + 0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95, 0xA6, 0x8E, 0x79, + 0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58, 0x87, 0xD6, 0x29, 0x0F, + 0xD4, 0x7A, 0x40, 0xC4, + ]; + + #[test] + fn parse_cert_chain() { + let cert: AttestationCertificate = from_slice(&SAMPLE_CERT_CHAIN[1..]).unwrap(); + assert_eq!(&cert.0, &SAMPLE_CERT_CHAIN[4..]); + + let _cert: Vec = from_slice(&SAMPLE_CERT_CHAIN).unwrap(); + } + + #[test] + fn parse_attestation_object() { + let value: AttestationObject = from_slice(&SAMPLE_ATTESTATION).unwrap(); + println!("{:?}", value); + + //assert_eq!(true, false); + } + + #[test] + fn parse_reader() { + let v: Vec = vec![ + 0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, + ]; + let (rest, value): (&[u8], String) = from_slice_stream(&v).unwrap(); + assert_eq!(value, "foobar"); + assert_eq!(rest, &[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]); + let (rest, value): (&[u8], String) = from_slice_stream(rest).unwrap(); + assert_eq!(value, "foobar"); + assert!(rest.is_empty()); + } + + #[test] + fn parse_extensions() { + let auth_make: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL).unwrap(); + assert_eq!( + auth_make.extensions.hmac_secret, + Some(HmacSecretResponse::Confirmed(true)) + ); + let auth_get: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_GET_ASSERTION).unwrap(); + assert_eq!( + auth_get.extensions.hmac_secret, + Some(HmacSecretResponse::Secret(vec![ + 0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB, + 0x87, 0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22, + 0x69, 0xAF, 0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95, + 0xA6, 0x8E, 0x79, 0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58, + 0x87, 0xD6, 0x29, 0x0F, 0xD4, 0x7A, 0x40, 0xC4, + ])) + ); + } + + /// See: https://github.com/mozilla/authenticator-rs/issues/187 + #[test] + fn test_aaguid_output() { + let input = [ + 0xcb, 0x69, 0x48, 0x1e, 0x8f, 0xf0, 0x00, 0x39, 0x93, 0xec, 0x0a, 0x27, 0x29, 0xa1, + 0x54, 0xa8, + ]; + let expected = "AAGuid(cb69481e-8ff0-0039-93ec-0a2729a154a8)"; + let result = AAGuid::from(&input).expect("Failed to parse AAGuid"); + let res_str = format!("{:?}", result); + assert_eq!(expected, &res_str); + } +} diff --git a/third_party/rust/authenticator/src/ctap2/client_data.rs b/third_party/rust/authenticator/src/ctap2/client_data.rs new file mode 100644 index 000000000000..6e214b17ff64 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/client_data.rs @@ -0,0 +1,399 @@ +use super::commands::CommandError; +use crate::transport::errors::HIDError; +use serde::de::{self, Deserializer, Error as SerdeError, MapAccess, Visitor}; +use serde::ser::SerializeMap; +use serde::{Deserialize, Serialize, Serializer}; +use serde_json as json; +use sha2::{Digest, Sha256}; +use std::fmt; + +/// https://w3c.github.io/webauthn/#dom-collectedclientdata-tokenbinding +// tokenBinding, of type TokenBinding +// +// This OPTIONAL member contains information about the state of the Token +// Binding protocol [TokenBinding] used when communicating with the Relying +// Party. Its absence indicates that the client doesn’t support token +// binding. +// +// status, of type TokenBindingStatus +// +// This member is one of the following: +// +// supported +// +// Indicates the client supports token binding, but it was not +// negotiated when communicating with the Relying Party. +// +// present +// +// Indicates token binding was used when communicating with the +// Relying Party. In this case, the id member MUST be present. +// +// id, of type DOMString +// +// This member MUST be present if status is present, and MUST be a +// base64url encoding of the Token Binding ID that was used when +// communicating with the Relying Party. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenBinding { + Present(String), + Supported, +} + +impl Serialize for TokenBinding { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + match *self { + TokenBinding::Supported => { + map.serialize_entry(&"status", &"supported")?; + } + TokenBinding::Present(ref v) => { + map.serialize_entry(&"status", "present")?; + // Verify here, that `v` is valid base64 encoded? + // base64::decode_config(&v, base64::URL_SAFE_NO_PAD); + // For now: Let the token do that. + map.serialize_entry(&"id", &v)?; + } + } + map.end() + } +} + +impl<'de> Deserialize<'de> for TokenBinding { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TokenBindingVisitor; + + impl<'de> Visitor<'de> for TokenBindingVisitor { + type Value = TokenBinding; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte string") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut id = None; + let mut status = None; + + while let Some(key) = map.next_key()? { + match key { + "status" => { + status = Some(map.next_value()?); + } + "id" => { + id = Some(map.next_value()?); + } + k => { + return Err(M::Error::custom(format!("unexpected key: {:?}", k))); + } + } + } + + if let Some(stat) = status { + match stat { + "present" => { + if let Some(id) = id { + Ok(TokenBinding::Present(id)) + } else { + Err(SerdeError::missing_field("id")) + } + } + "supported" => Ok(TokenBinding::Supported), + k => { + return Err(M::Error::custom(format!( + "unexpected status key: {:?}", + k + ))); + } + } + } else { + Err(SerdeError::missing_field("status")) + } + } + } + + deserializer.deserialize_map(TokenBindingVisitor) + } +} + +/// https://w3c.github.io/webauthn/#dom-collectedclientdata-type +// type, of type DOMString +// +// This member contains the string "webauthn.create" when creating new +// credentials, and "webauthn.get" when getting an assertion from an +// existing credential. The purpose of this member is to prevent certain +// types of signature confusion attacks (where an attacker substitutes one +// legitimate signature for another). +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum WebauthnType { + Create, + Get, +} + +impl Serialize for WebauthnType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + WebauthnType::Create => serializer.serialize_str(&"webauthn.create"), + WebauthnType::Get => serializer.serialize_str(&"webauthn.get"), + } + } +} + +impl<'de> Deserialize<'de> for WebauthnType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct WebauthnTypeVisitor; + + impl<'de> Visitor<'de> for WebauthnTypeVisitor { + type Value = WebauthnType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match v { + "webauthn.create" => Ok(WebauthnType::Create), + "webauthn.get" => Ok(WebauthnType::Get), + _ => Err(E::custom("unexpected webauthn_type")), + } + } + } + + deserializer.deserialize_str(WebauthnTypeVisitor) + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct Challenge(pub String); + +impl Challenge { + pub fn new(input: Vec) -> Self { + let value = base64::encode_config(&input, base64::URL_SAFE_NO_PAD); + Challenge(value) + } +} + +impl From> for Challenge { + fn from(v: Vec) -> Challenge { + Challenge::new(v) + } +} + +impl AsRef<[u8]> for Challenge { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +pub type Origin = String; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct CollectedClientData { + #[serde(rename = "type")] + pub webauthn_type: WebauthnType, + pub challenge: Challenge, + pub origin: Origin, + // It is optional, according to https://www.w3.org/TR/webauthn/#collectedclientdata-hash-of-the-serialized-client-data + // But we are serializing it, so we *have to* set crossOrigin (if not given, we have to set it to false) + // Thus, on our side, it is not optional. For deserializing, we provide a default (bool's default == False) + #[serde(rename = "crossOrigin", default)] + pub cross_origin: bool, + #[serde(rename = "tokenBinding", skip_serializing_if = "Option::is_none")] + pub token_binding: Option, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct ClientDataHash([u8; 32]); + +impl PartialEq<[u8]> for ClientDataHash { + fn eq(&self, other: &[u8]) -> bool { + self.0.eq(other) + } +} + +impl AsRef<[u8]> for ClientDataHash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Serialize for ClientDataHash { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +#[cfg(test)] +impl<'de> Deserialize<'de> for ClientDataHash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ClientDataHashVisitor; + + impl<'de> Visitor<'de> for ClientDataHashVisitor { + type Value = ClientDataHash; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + let mut out = [0u8; 32]; + if out.len() != v.len() { + return Err(E::invalid_length(v.len(), &"32")); + } + out.copy_from_slice(v); + Ok(ClientDataHash(out)) + } + } + + deserializer.deserialize_bytes(ClientDataHashVisitor) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CollectedClientDataWrapper { + pub client_data: CollectedClientData, + pub serialized_data: Vec, +} + +impl CollectedClientDataWrapper { + pub fn new(client_data: CollectedClientData) -> Result { + let serialized_data = json::to_vec(&client_data).map_err(CommandError::Json)?; + Ok(CollectedClientDataWrapper { + client_data, + serialized_data, + }) + } + + pub fn hash(&self) -> ClientDataHash { + // WebIDL's dictionary definition specifies that the order of the struct + // is exactly as the WebIDL specification declares it, with an algorithm + // for partial dictionaries, so that's how interop works for these + // things. + // See: https://heycam.github.io/webidl/#dfn-dictionary + let mut hasher = Sha256::new(); + hasher.update(&self.serialized_data); + + let mut output = [0u8; 32]; + output.copy_from_slice(hasher.finalize().as_slice()); + + ClientDataHash(output) + } +} + +#[cfg(test)] +mod test { + use crate::CollectedClientDataWrapper; + + use super::{Challenge, ClientDataHash, CollectedClientData, TokenBinding, WebauthnType}; + use serde_json as json; + + #[test] + fn test_token_binding_status() { + let tok = TokenBinding::Present("AAECAw".to_string()); + + let json_value = json::to_string(&tok).unwrap(); + assert_eq!(json_value, "{\"status\":\"present\",\"id\":\"AAECAw\"}"); + + let tok = TokenBinding::Supported; + + let json_value = json::to_string(&tok).unwrap(); + assert_eq!(json_value, "{\"status\":\"supported\"}"); + } + + #[test] + fn test_webauthn_type() { + let t = WebauthnType::Create; + + let json_value = json::to_string(&t).unwrap(); + assert_eq!(json_value, "\"webauthn.create\""); + + let t = WebauthnType::Get; + let json_value = json::to_string(&t).unwrap(); + assert_eq!(json_value, "\"webauthn.get\""); + } + + #[test] + fn test_collected_client_data_parsing() { + let original_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}"; + let parsed: CollectedClientData = serde_json::from_str(&original_str).unwrap(); + let expected = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present("AAECAw".to_string())), + }; + assert_eq!(parsed, expected); + + let back_again = serde_json::to_string(&expected).unwrap(); + assert_eq!(back_again, original_str); + } + + #[test] + fn test_collected_client_data_defaults() { + let cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}"; + let no_cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}"; + let parsed: CollectedClientData = serde_json::from_str(&no_cross_origin_str).unwrap(); + let expected = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present("AAECAw".to_string())), + }; + assert_eq!(parsed, expected); + + let back_again = serde_json::to_string(&expected).unwrap(); + assert_eq!(back_again, cross_origin_str); + } + + #[test] + fn test_collected_client_data() { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present("AAECAw".to_string())), + }; + let c = + CollectedClientDataWrapper::new(client_data).expect("Failed to serialize client_data"); + assert_eq!( + c.hash(), + // echo -n '{"type":"webauthn.create","challenge":"AAECAw","origin":"example.com","crossOrigin":false,"tokenBinding":{"status":"present","id":"AAECAw"}}' | sha256sum -t + ClientDataHash { + 0: [ + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, + 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, + 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80 + ] + } + ); + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs b/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs new file mode 100644 index 000000000000..839fc3127032 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs @@ -0,0 +1,784 @@ +use super::{get_info::AuthenticatorInfo, Command, CommandError, RequestCtap2, StatusCode}; +use crate::crypto::{ + authenticate, decrypt, encapsulate, encrypt, BackendError, COSEKey, CryptoError, ECDHSecret, +}; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde::{ + de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; +use serde_bytes::ByteBuf; +use serde_cbor::de::from_slice; +use serde_cbor::ser::to_vec; +use serde_cbor::Value; +use sha2::{Digest, Sha256}; +use std::error::Error as StdErrorT; +use std::fmt; + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum PINSubcommand { + GetRetries = 0x01, + GetKeyAgreement = 0x02, + SetPIN = 0x03, + ChangePIN = 0x04, + GetPINToken = 0x05, // superseded by GetPinUvAuth* + GetPinUvAuthTokenUsingUvWithPermissions = 0x06, + GetUVRetries = 0x07, + GetPinUvAuthTokenUsingPinWithPermissions = 0x09, // Yes, 0x08 is missing +} + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum PinUvAuthTokenPermission { + MakeCredential = 0x01, // rp_id required + GetAssertion = 0x02, // rp_id required + CredentialManagement = 0x04, // rp_id optional + BioEnrollment = 0x08, // rp_id ignored + LargeBlobWrite = 0x10, // rp_id ignored + AuthenticatorConfiguration = 0x20, // rp_id ignored +} + +#[derive(Debug)] +pub struct ClientPIN { + pin_protocol: Option, + subcommand: PINSubcommand, + key_agreement: Option, + pin_auth: Option, + new_pin_enc: Option, + pin_hash_enc: Option, + permissions: Option, + rp_id: Option, +} + +impl Default for ClientPIN { + fn default() -> Self { + ClientPIN { + pin_protocol: None, + subcommand: PINSubcommand::GetRetries, + key_agreement: None, + pin_auth: None, + new_pin_enc: None, + pin_hash_enc: None, + permissions: None, + rp_id: None, + } + } +} + +impl Serialize for ClientPIN { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Need to define how many elements are going to be in the map + // beforehand + let mut map_len = 1; + if self.pin_protocol.is_some() { + map_len += 1; + } + if self.key_agreement.is_some() { + map_len += 1; + } + if self.pin_auth.is_some() { + map_len += 1; + } + if self.new_pin_enc.is_some() { + map_len += 1; + } + if self.pin_hash_enc.is_some() { + map_len += 1; + } + if self.permissions.is_some() { + map_len += 1; + } + if self.rp_id.is_some() { + map_len += 1; + } + + let mut map = serializer.serialize_map(Some(map_len))?; + if let Some(ref pin_protocol) = self.pin_protocol { + map.serialize_entry(&1, pin_protocol)?; + } + let command: u8 = self.subcommand as u8; + map.serialize_entry(&2, &command)?; + if let Some(ref key_agreement) = self.key_agreement { + map.serialize_entry(&3, key_agreement)?; + } + if let Some(ref pin_auth) = self.pin_auth { + map.serialize_entry(&4, &ByteBuf::from(pin_auth.as_ref()))?; + } + if let Some(ref new_pin_enc) = self.new_pin_enc { + map.serialize_entry(&5, new_pin_enc)?; + } + if let Some(ref pin_hash_enc) = self.pin_hash_enc { + map.serialize_entry(&6, pin_hash_enc)?; + } + if let Some(ref permissions) = self.permissions { + map.serialize_entry(&9, permissions)?; + } + if let Some(ref rp_id) = self.rp_id { + map.serialize_entry(&0x0A, rp_id)?; + } + + map.end() + } +} + +pub trait ClientPINSubCommand { + type Output; + fn as_client_pin(&self) -> Result; + fn parse_response_payload(&self, input: &[u8]) -> Result; +} + +struct ClientPinResponse { + key_agreement: Option, + pin_token: Option, + /// Number of PIN attempts remaining before lockout. + pin_retries: Option, + power_cycle_state: Option, + uv_retries: Option, +} + +impl<'de> Deserialize<'de> for ClientPinResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ClientPinResponseVisitor; + + impl<'de> Visitor<'de> for ClientPinResponseVisitor { + type Value = ClientPinResponse; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut key_agreement = None; + let mut pin_token = None; + let mut pin_retries = None; + let mut power_cycle_state = None; + let mut uv_retries = None; + while let Some(key) = map.next_key()? { + match key { + 0x01 => { + if key_agreement.is_some() { + return Err(SerdeError::duplicate_field("key_agreement")); + } + key_agreement = map.next_value()?; + } + 0x02 => { + if pin_token.is_some() { + return Err(SerdeError::duplicate_field("pin_token")); + } + pin_token = map.next_value()?; + } + 0x03 => { + if pin_retries.is_some() { + return Err(SerdeError::duplicate_field("pin_retries")); + } + pin_retries = Some(map.next_value()?); + } + 0x04 => { + if power_cycle_state.is_some() { + return Err(SerdeError::duplicate_field("power_cycle_state")); + } + power_cycle_state = Some(map.next_value()?); + } + 0x05 => { + if uv_retries.is_some() { + return Err(SerdeError::duplicate_field("uv_retries")); + } + uv_retries = Some(map.next_value()?); + } + k => { + warn!("ClientPinResponse: unexpected key: {:?}", k); + let _ = map.next_value::()?; + continue; + } + } + } + Ok(ClientPinResponse { + key_agreement, + pin_token, + pin_retries, + power_cycle_state, + uv_retries, + }) + } + } + deserializer.deserialize_bytes(ClientPinResponseVisitor) + } +} + +#[derive(Debug)] +pub struct GetKeyAgreement { + pin_protocol: u8, +} + +impl GetKeyAgreement { + pub fn new(info: &AuthenticatorInfo) -> Result { + if info.pin_protocols.contains(&1) { + Ok(GetKeyAgreement { pin_protocol: 1 }) + } else { + Err(CommandError::UnsupportedPinProtocol) + } + } +} + +impl ClientPINSubCommand for GetKeyAgreement { + type Output = KeyAgreement; + + fn as_client_pin(&self) -> Result { + Ok(ClientPIN { + pin_protocol: Some(self.pin_protocol), + subcommand: PINSubcommand::GetKeyAgreement, + ..ClientPIN::default() + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + let value: Value = from_slice(input).map_err(CommandError::Deserializing)?; + debug!("GetKeyAgreement::parse_response_payload {:?}", value); + + let get_pin_response: ClientPinResponse = + from_slice(input).map_err(CommandError::Deserializing)?; + if let Some(key_agreement) = get_pin_response.key_agreement { + Ok(KeyAgreement(key_agreement)) + } else { + Err(CommandError::MissingRequiredField("key_agreement")) + } + } +} + +#[derive(Debug)] +/// Superseded by GetPinUvAuthTokenUsingUvWithPermissions or GetPinUvAuthTokenUsingPinWithPermissions, +/// thus for backwards compatibility only +pub struct GetPinToken<'sc, 'pin> { + pin_protocol: u8, + shared_secret: &'sc ECDHSecret, + pin: &'pin Pin, +} + +impl<'sc, 'pin> GetPinToken<'sc, 'pin> { + pub fn new( + info: &AuthenticatorInfo, + shared_secret: &'sc ECDHSecret, + pin: &'pin Pin, + ) -> Result { + if info.pin_protocols.contains(&1) { + Ok(GetPinToken { + pin_protocol: 1, + shared_secret, + pin, + }) + } else { + Err(CommandError::UnsupportedPinProtocol) + } + } +} + +impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> { + type Output = PinToken; + + fn as_client_pin(&self) -> Result { + let input = self.pin.for_pin_token(); + trace!("pin_hash = {:#04X?}", &input.as_ref()); + let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref()) + .map_err(|e| CryptoError::Backend(e))?; + trace!("pin_hash_enc = {:#04X?}", &pin_hash_enc); + + Ok(ClientPIN { + pin_protocol: Some(self.pin_protocol), + subcommand: PINSubcommand::GetPINToken, + key_agreement: Some(self.shared_secret.my_public_key().clone()), + pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)), + ..ClientPIN::default() + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + let value: Value = from_slice(input).map_err(CommandError::Deserializing)?; + debug!("GetKeyAgreement::parse_response_payload {:?}", value); + + let get_pin_response: ClientPinResponse = + from_slice(input).map_err(CommandError::Deserializing)?; + match get_pin_response.pin_token { + Some(encrypted_pin_token) => { + let pin_token = decrypt( + self.shared_secret.shared_secret(), + encrypted_pin_token.as_ref(), + ) + .map_err(|e| CryptoError::Backend(e))?; + let pin_token = PinToken(pin_token); + Ok(pin_token) + } + None => Err(CommandError::MissingRequiredField("key_agreement")), + } + } +} + +#[derive(Debug)] +pub struct GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> { + pin_protocol: u8, + shared_secret: &'sc ECDHSecret, + pin: &'pin Pin, + permissions: PinUvAuthTokenPermission, + rp_id: Option, +} + +impl<'sc, 'pin> GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> { + pub fn new( + info: &AuthenticatorInfo, + shared_secret: &'sc ECDHSecret, + pin: &'pin Pin, + permissions: PinUvAuthTokenPermission, + rp_id: Option, + ) -> Result { + // TODO(MS): Actually handle protocol 2! + if info.pin_protocols.contains(&1) { + Ok(GetPinUvAuthTokenUsingPinWithPermissions { + pin_protocol: 1, + shared_secret, + pin, + permissions, + rp_id, + }) + } else { + Err(CommandError::UnsupportedPinProtocol) + } + } +} + +impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> { + type Output = PinToken; + + fn as_client_pin(&self) -> Result { + let input = self.pin.for_pin_token(); + let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref()) + .map_err(|e| CryptoError::Backend(e))?; + + Ok(ClientPIN { + pin_protocol: Some(self.pin_protocol), + subcommand: PINSubcommand::GetPinUvAuthTokenUsingPinWithPermissions, + key_agreement: Some(self.shared_secret.my_public_key().clone()), + pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)), + permissions: Some(self.permissions as u8), + rp_id: self.rp_id.clone(), // TODO: This could probably be done less wasteful with &str all the way + ..ClientPIN::default() + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + let value: Value = from_slice(input).map_err(CommandError::Deserializing)?; + debug!("GetKeyAgreement::parse_response_payload {:?}", value); + + let get_pin_response: ClientPinResponse = + from_slice(input).map_err(CommandError::Deserializing)?; + match get_pin_response.pin_token { + Some(encrypted_pin_token) => { + let pin_token = decrypt( + self.shared_secret.shared_secret(), + encrypted_pin_token.as_ref(), + ) + .map_err(|e| CryptoError::Backend(e))?; + let pin_token = PinToken(pin_token); + Ok(pin_token) + } + None => Err(CommandError::MissingRequiredField("key_agreement")), + } + } +} + +#[derive(Debug)] +pub struct GetRetries {} + +impl GetRetries { + pub fn new() -> Self { + GetRetries {} + } +} + +impl ClientPINSubCommand for GetRetries { + type Output = u8; + + fn as_client_pin(&self) -> Result { + Ok(ClientPIN { + subcommand: PINSubcommand::GetRetries, + ..ClientPIN::default() + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + let value: Value = from_slice(input).map_err(CommandError::Deserializing)?; + debug!("GetKeyAgreement::parse_response_payload {:?}", value); + + let get_pin_response: ClientPinResponse = + from_slice(input).map_err(CommandError::Deserializing)?; + match get_pin_response.pin_retries { + Some(pin_retries) => Ok(pin_retries), + None => Err(CommandError::MissingRequiredField("pin_retries")), + } + } +} + +#[derive(Debug)] +pub struct SetNewPin<'sc, 'pin> { + pin_protocol: u8, + shared_secret: &'sc ECDHSecret, + new_pin: &'pin Pin, +} + +impl<'sc, 'pin> SetNewPin<'sc, 'pin> { + pub fn new( + info: &AuthenticatorInfo, + shared_secret: &'sc ECDHSecret, + new_pin: &'pin Pin, + ) -> Result { + if info.pin_protocols.contains(&1) { + Ok(SetNewPin { + pin_protocol: 1, + shared_secret, + new_pin, + }) + } else { + Err(CommandError::UnsupportedPinProtocol) + } + } +} + +impl<'sc, 'pin> ClientPINSubCommand for SetNewPin<'sc, 'pin> { + type Output = (); + + fn as_client_pin(&self) -> Result { + if self.new_pin.as_bytes().len() > 63 { + return Err(CommandError::StatusCode( + StatusCode::PinPolicyViolation, + None, + )); + } + // Padding the PIN with trailing zeros, according to spec + let input: Vec = self + .new_pin + .as_bytes() + .iter() + .chain(std::iter::repeat(&0x00)) + .take(64) + .cloned() + .collect(); + + let shared_secret = self.shared_secret.shared_secret(); + // AES256-CBC(sharedSecret, IV=0, newPin) + let new_pin_enc = + encrypt(shared_secret, input.as_ref()).map_err(|e| CryptoError::Backend(e))?; + + // LEFT(HMAC-SHA-265(sharedSecret, newPinEnc), 16) + let pin_auth = PinToken(shared_secret.to_vec()) + .auth(&new_pin_enc) + .map_err(CommandError::Crypto)?; + + Ok(ClientPIN { + pin_protocol: Some(self.pin_protocol), + subcommand: PINSubcommand::SetPIN, + key_agreement: Some(self.shared_secret.my_public_key().clone()), + new_pin_enc: Some(ByteBuf::from(new_pin_enc)), + pin_auth: Some(pin_auth), + ..ClientPIN::default() + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + // Should be an empty response or a valid cbor-value (which we ignore) + if input.is_empty() { + Ok(()) + } else { + let _: Value = from_slice(input).map_err(CommandError::Deserializing)?; + Ok(()) + } + } +} + +#[derive(Debug)] +pub struct ChangeExistingPin<'sc, 'pin> { + pin_protocol: u8, + shared_secret: &'sc ECDHSecret, + current_pin: &'pin Pin, + new_pin: &'pin Pin, +} + +impl<'sc, 'pin> ChangeExistingPin<'sc, 'pin> { + pub fn new( + info: &AuthenticatorInfo, + shared_secret: &'sc ECDHSecret, + current_pin: &'pin Pin, + new_pin: &'pin Pin, + ) -> Result { + if info.pin_protocols.contains(&1) { + Ok(ChangeExistingPin { + pin_protocol: 1, + shared_secret, + current_pin, + new_pin, + }) + } else { + Err(CommandError::UnsupportedPinProtocol) + } + } +} + +impl<'sc, 'pin> ClientPINSubCommand for ChangeExistingPin<'sc, 'pin> { + type Output = (); + + fn as_client_pin(&self) -> Result { + if self.new_pin.as_bytes().len() > 63 { + return Err(CommandError::StatusCode( + StatusCode::PinPolicyViolation, + None, + )); + } + // Padding the PIN with trailing zeros, according to spec + let input: Vec = self + .new_pin + .as_bytes() + .iter() + .chain(std::iter::repeat(&0x00)) + .take(64) + .cloned() + .collect(); + + let shared_secret = self.shared_secret.shared_secret(); + // AES256-CBC(sharedSecret, IV=0, newPin) + let new_pin_enc = + encrypt(shared_secret, input.as_ref()).map_err(|e| CryptoError::Backend(e))?; + + // AES256-CBC(sharedSecret, IV=0, LEFT(SHA-256(oldPin), 16)) + let input = self.current_pin.for_pin_token(); + let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref()) + .map_err(|e| CryptoError::Backend(e))?; + + // LEFT(HMAC-SHA-265(sharedSecret, newPinEnc), 16) + let pin_auth = PinToken(shared_secret.to_vec()) + .auth(&[new_pin_enc.as_slice(), pin_hash_enc.as_slice()].concat()) + .map_err(CommandError::Crypto)?; + + Ok(ClientPIN { + pin_protocol: Some(self.pin_protocol), + subcommand: PINSubcommand::ChangePIN, + key_agreement: Some(self.shared_secret.my_public_key().clone()), + new_pin_enc: Some(ByteBuf::from(new_pin_enc)), + pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)), + pin_auth: Some(pin_auth), + permissions: None, + rp_id: None, + }) + } + + fn parse_response_payload(&self, input: &[u8]) -> Result { + // Should be an empty response or a valid cbor-value (which we ignore) + if input.is_empty() { + Ok(()) + } else { + let _: Value = from_slice(input).map_err(CommandError::Deserializing)?; + Ok(()) + } + } +} + +impl RequestCtap2 for T +where + T: ClientPINSubCommand, + T: fmt::Debug, +{ + type Output = ::Output; + + fn command() -> Command { + Command::ClientPin + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + let client_pin = self.as_client_pin()?; + let output = to_vec(&client_pin).map_err(CommandError::Serializing)?; + trace!("client subcommmand: {:04X?}", &output); + + Ok(output) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + trace!("Client pin subcomand response:{:04X?}", &input); + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + debug!("response status code: {:?}", status); + if status.is_ok() { + let res = ::parse_response_payload(self, &input[1..]) + .map_err(HIDError::Command); + res + } else { + let add_data = if input.len() > 1 { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Some(data) + } else { + None + }; + Err(CommandError::StatusCode(status, add_data).into()) + } + } +} + +#[derive(Debug)] +pub struct KeyAgreement(COSEKey); + +impl KeyAgreement { + pub fn shared_secret(&self) -> Result { + encapsulate(&self.0).map_err(|e| CommandError::Crypto(CryptoError::Backend(e))) + } +} + +#[derive(Debug, Deserialize)] +pub struct EncryptedPinToken(ByteBuf); + +impl AsRef<[u8]> for EncryptedPinToken { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[derive(Debug)] +pub struct PinToken(Vec); + +impl PinToken { + pub fn auth(&self, payload: &[u8]) -> Result { + let hmac = authenticate(self.as_ref(), payload)?; + + let mut out = [0u8; 16]; + out.copy_from_slice(&hmac[0..16]); + + Ok(PinAuth(out.to_vec())) + } +} + +impl AsRef<[u8]> for PinToken { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Deserialize))] +pub struct PinAuth(Vec); + +impl PinAuth { + pub(crate) fn empty_pin_auth() -> Self { + PinAuth(vec![]) + } +} + +impl AsRef<[u8]> for PinAuth { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Serialize for PinAuth { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_bytes::serialize(&self.0[..], serializer) + } +} + +#[derive(Clone)] +pub struct Pin(String); + +impl fmt::Debug for Pin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Pin(redacted)") + } +} + +impl Pin { + pub fn new(value: &str) -> Pin { + Pin(String::from(value)) + } + + pub fn for_pin_token(&self) -> PinAuth { + let mut hasher = Sha256::new(); + hasher.update(&self.0.as_bytes()); + + let mut output = [0u8; 16]; + let len = output.len(); + output.copy_from_slice(&hasher.finalize().as_slice()[..len]); + + PinAuth(output.to_vec()) + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +#[derive(Clone, Debug, Serialize)] +pub enum PinError { + PinRequired, + PinIsTooShort, + PinIsTooLong(usize), + InvalidKeyLen, + InvalidPin(Option), + PinAuthBlocked, + PinBlocked, + Backend(BackendError), +} + +impl fmt::Display for PinError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + PinError::PinRequired => write!(f, "PinError: Pin required."), + PinError::PinIsTooShort => write!(f, "PinError: pin is too short"), + PinError::PinIsTooLong(len) => write!(f, "PinError: pin is too long ({})", len), + PinError::InvalidKeyLen => write!(f, "PinError: invalid key len"), + PinError::InvalidPin(ref e) => { + let mut res = write!(f, "PinError: Invalid Pin."); + if let Some(pin_retries) = e { + res = write!(f, " Retries left: {:?}", pin_retries) + } + res + } + PinError::PinAuthBlocked => write!( + f, + "PinError: Pin authentication blocked. Device needs power cycle." + ), + PinError::PinBlocked => write!( + f, + "PinError: No retries left. Pin blocked. Device needs reset." + ), + PinError::Backend(ref e) => write!(f, "PinError: Crypto backend error: {:?}", e), + } + } +} + +impl StdErrorT for PinError {} + +impl From for PinError { + fn from(e: BackendError) -> Self { + PinError::Backend(e) + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs b/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs new file mode 100644 index 000000000000..16704f0ba826 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs @@ -0,0 +1,1282 @@ +use super::{ + Command, CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable, + StatusCode, +}; +use crate::consts::{ + PARAMETER_SIZE, U2F_AUTHENTICATE, U2F_CHECK_IS_REGISTERED, U2F_REQUEST_USER_PRESENCE, +}; +use crate::crypto::{authenticate, encrypt, COSEKey, CryptoError, ECDHSecret}; +use crate::ctap2::attestation::{AuthenticatorData, AuthenticatorDataFlags}; +use crate::ctap2::client_data::{ClientDataHash, CollectedClientData, CollectedClientDataWrapper}; +use crate::ctap2::commands::client_pin::{Pin, PinAuth}; +use crate::ctap2::commands::get_next_assertion::GetNextAssertion; +use crate::ctap2::commands::make_credentials::UserVerification; +use crate::ctap2::server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper, User}; +use crate::errors::AuthenticatorError; +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::transport::FidoDevice; +use crate::u2ftypes::{CTAP1RequestAPDU, U2FDevice}; +use nom::{ + error::VerboseError, + number::complete::{be_u32, be_u8}, + sequence::tuple, +}; +use serde::{ + de::{Error as DesError, MapAccess, Visitor}, + ser::{Error as SerError, SerializeMap}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use serde_bytes::ByteBuf; +use serde_cbor::{de::from_slice, ser, Value}; +use std::convert::TryInto; +use std::fmt; +use std::io; + +#[derive(Debug, PartialEq)] +pub enum GetAssertionResult { + CTAP1(Vec), + CTAP2(AssertionObject, CollectedClientDataWrapper), +} + +#[derive(Clone, Copy, Debug, Serialize)] +#[cfg_attr(test, derive(Deserialize))] +pub struct GetAssertionOptions { + #[serde(rename = "uv", skip_serializing_if = "Option::is_none")] + pub user_verification: Option, + #[serde(rename = "up", skip_serializing_if = "Option::is_none")] + pub user_presence: Option, +} + +impl Default for GetAssertionOptions { + fn default() -> Self { + Self { + user_presence: Some(true), + user_verification: None, + } + } +} + +impl GetAssertionOptions { + pub(crate) fn has_some(&self) -> bool { + self.user_presence.is_some() || self.user_verification.is_some() + } +} + +impl UserVerification for GetAssertionOptions { + fn ask_user_verification(&self) -> bool { + if let Some(e) = self.user_verification { + e + } else { + false + } + } +} + +#[derive(Debug, Clone)] +pub struct CalculatedHmacSecretExtension { + pub public_key: COSEKey, + pub salt_enc: Vec, + pub salt_auth: [u8; 16], +} + +#[derive(Debug, Clone, Default)] +pub struct HmacSecretExtension { + pub salt1: Vec, + pub salt2: Option>, + calculated_hmac: Option, +} + +impl HmacSecretExtension { + pub fn new(salt1: Vec, salt2: Option>) -> Self { + HmacSecretExtension { + salt1, + salt2, + calculated_hmac: None, + } + } + + pub fn calculate(&mut self, secret: &ECDHSecret) -> Result<(), AuthenticatorError> { + if self.salt1.len() < 32 { + return Err(CryptoError::WrongSaltLength.into()); + } + let salt_enc = match &self.salt2 { + Some(salt2) => { + if salt2.len() < 32 { + return Err(CryptoError::WrongSaltLength.into()); + } + let salts = [&self.salt1[..32], &salt2[..32]].concat(); // salt1 || salt2 + encrypt(secret.shared_secret(), &salts) + } + None => encrypt(secret.shared_secret(), &self.salt1[..32]), + } + .map_err(|e| CryptoError::Backend(e))?; + let salt_auth_full = + authenticate(secret.shared_secret(), &salt_enc).map_err(|e| CryptoError::Backend(e))?; + let salt_auth = salt_auth_full + .windows(16) + .next() + .ok_or(AuthenticatorError::InternalError(String::from( + "salt_auth too short", + )))? + .try_into() + .map_err(|_| { + AuthenticatorError::InternalError(String::from( + "salt_auth conversion failed. Should never happen.", + )) + })?; + let public_key = secret.my_public_key().clone(); + self.calculated_hmac = Some(CalculatedHmacSecretExtension { + public_key, + salt_enc, + salt_auth, + }); + + Ok(()) + } +} + +impl Serialize for HmacSecretExtension { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if let Some(calc) = &self.calculated_hmac { + let mut map = serializer.serialize_map(Some(3))?; + map.serialize_entry(&1, &calc.public_key)?; + map.serialize_entry(&2, serde_bytes::Bytes::new(&calc.salt_enc))?; + map.serialize_entry(&3, serde_bytes::Bytes::new(&calc.salt_auth))?; + map.end() + } else { + Err(SerError::custom( + "hmac secret has not been calculated before being serialized", + )) + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct GetAssertionExtensions { + pub hmac_secret: Option, +} + +impl Serialize for GetAssertionExtensions { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry(&"hmac-secret", &self.hmac_secret)?; + map.end() + } +} + +impl GetAssertionExtensions { + fn has_extensions(&self) -> bool { + self.hmac_secret.is_some() + } +} + +#[derive(Debug, Clone)] +pub struct GetAssertion { + pub(crate) client_data_wrapper: CollectedClientDataWrapper, + pub(crate) rp: RelyingPartyWrapper, + pub(crate) allow_list: Vec, + + // https://www.w3.org/TR/webauthn/#client-extension-input + // The client extension input, which is a value that can be encoded in JSON, + // is passed from the WebAuthn Relying Party to the client in the get() or + // create() call, while the CBOR authenticator extension input is passed + // from the client to the authenticator for authenticator extensions during + // the processing of these calls. + pub(crate) extensions: GetAssertionExtensions, + pub(crate) options: GetAssertionOptions, + pub(crate) pin: Option, + pub(crate) pin_auth: Option, + //TODO(MS): pinProtocol +} + +impl GetAssertion { + pub fn new( + client_data_wrapper: CollectedClientData, + rp: RelyingPartyWrapper, + allow_list: Vec, + options: GetAssertionOptions, + extensions: GetAssertionExtensions, + pin: Option, + ) -> Result { + let client_data_wrapper = CollectedClientDataWrapper::new(client_data_wrapper)?; + Ok(Self { + client_data_wrapper, + rp, + allow_list, + extensions, + options, + pin, + pin_auth: None, + }) + } +} + +impl PinAuthCommand for GetAssertion { + fn pin(&self) -> &Option { + &self.pin + } + + fn set_pin(&mut self, pin: Option) { + self.pin = pin; + } + + fn pin_auth(&self) -> &Option { + &self.pin_auth + } + + fn set_pin_auth(&mut self, pin_auth: Option, _pint_auth_protocol: Option) { + self.pin_auth = pin_auth; + } + + fn client_data_hash(&self) -> ClientDataHash { + self.client_data_wrapper.hash() + } + + fn unset_uv_option(&mut self) { + self.options.user_verification = None; + } +} + +impl Serialize for GetAssertion { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Need to define how many elements are going to be in the map + // beforehand + let mut map_len = 2; + if !self.allow_list.is_empty() { + map_len += 1; + } + if self.extensions.has_extensions() { + map_len += 1; + } + if self.options.has_some() { + map_len += 1; + } + if self.pin_auth.is_some() { + map_len += 2; + } + + let mut map = serializer.serialize_map(Some(map_len))?; + match self.rp { + RelyingPartyWrapper::Data(ref d) => { + map.serialize_entry(&1, &d.id)?; + } + _ => { + return Err(S::Error::custom( + "Can't serialize a RelyingParty::Hash for CTAP2", + )); + } + } + + let client_data_hash = self.client_data_hash(); + map.serialize_entry(&2, &client_data_hash)?; + if !self.allow_list.is_empty() { + map.serialize_entry(&3, &self.allow_list)?; + } + if self.extensions.has_extensions() { + map.serialize_entry(&4, &self.extensions)?; + } + if self.options.has_some() { + map.serialize_entry(&5, &self.options)?; + } + if let Some(pin_auth) = &self.pin_auth { + map.serialize_entry(&6, &pin_auth)?; + map.serialize_entry(&7, &1)?; + } + map.end() + } +} + +impl Request for GetAssertion { + fn is_ctap2_request(&self) -> bool { + match self.rp { + RelyingPartyWrapper::Data(_) => true, + RelyingPartyWrapper::Hash(_) => false, + } + } +} + +/// This command is used to check which key_handle is valid for this +/// token. This is sent before a GetAssertion command, to determine which +/// is valid for a specific token and which key_handle GetAssertion +/// should send to the token. Or before a MakeCredential command, to determine +/// if this token is already registered or not. +#[derive(Debug)] +pub(crate) struct CheckKeyHandle<'assertion> { + pub(crate) key_handle: &'assertion [u8], + pub(crate) client_data_wrapper: &'assertion CollectedClientDataWrapper, + pub(crate) rp: &'assertion RelyingPartyWrapper, +} + +impl<'assertion> RequestCtap1 for CheckKeyHandle<'assertion> { + type Output = (); + + fn ctap1_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice + io::Read + io::Write + fmt::Debug, + { + let flags = U2F_CHECK_IS_REGISTERED; + // TODO(MS): Need to check "up" here. If up==false, set to 0x08? Or not? Spec is + // ambiguous + let mut auth_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + self.key_handle.len()); + + auth_data.extend_from_slice(self.client_data_wrapper.hash().as_ref()); + auth_data.extend_from_slice(self.rp.hash().as_ref()); + auth_data.extend_from_slice(&[self.key_handle.len() as u8]); + auth_data.extend_from_slice(self.key_handle); + let cmd = U2F_AUTHENTICATE; + let apdu = CTAP1RequestAPDU::serialize(cmd, flags, &auth_data)?; + Ok(apdu) + } + + fn handle_response_ctap1( + &self, + status: Result<(), ApduErrorStatus>, + _input: &[u8], + ) -> Result> { + // From the U2F-spec: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-request-message---u2f_register + // if the control byte is set to 0x07 by the FIDO Client, the U2F token is supposed to + // simply check whether the provided key handle was originally created by this token, + // and whether it was created for the provided application parameter. If so, the U2F + // token MUST respond with an authentication response message:error:test-of-user-presence-required + // (note that despite the name this signals a success condition). + // If the key handle was not created by this U2F token, or if it was created for a different + // application parameter, the token MUST respond with an authentication response message:error:bad-key-handle. + match status { + Ok(_) | Err(ApduErrorStatus::ConditionsNotSatisfied) => Ok(()), + Err(e) => Err(Retryable::Error(HIDError::ApduStatus(e))), + } + } +} + +impl RequestCtap1 for GetAssertion { + type Output = GetAssertionResult; + + fn ctap1_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: io::Read + io::Write + fmt::Debug + FidoDevice, + { + let key_handle = self + .allow_list + .iter() + // key-handles in CTAP1 are limited to 255 bytes, but are not limited in CTAP2. + // Filter out key-handles that are too long (can happen if this is a CTAP2-request, + // but the token only speaks CTAP1). If none is found, return an error. + .filter(|allowed_handle| allowed_handle.id.len() < 256) + .find_map(|allowed_handle| { + let check_command = CheckKeyHandle { + key_handle: allowed_handle.id.as_ref(), + client_data_wrapper: &self.client_data_wrapper, + rp: &self.rp, + }; + let res = dev.send_ctap1(&check_command); + match res { + Ok(_) => Some(allowed_handle.id.clone()), + _ => None, + } + }) + .ok_or(HIDError::DeviceNotSupported)?; + + debug!("sending key_handle = {:?}", key_handle); + + let flags = if self.options.user_presence.unwrap_or(false) { + U2F_REQUEST_USER_PRESENCE + } else { + 0 + }; + let mut auth_data = + Vec::with_capacity(2 * PARAMETER_SIZE + 1 /* key_handle_len */ + key_handle.len()); + + if self.is_ctap2_request() { + auth_data.extend_from_slice(self.client_data_hash().as_ref()); + } else { + let decoded = base64::decode_config( + &self.client_data_wrapper.client_data.challenge.0, + base64::URL_SAFE_NO_PAD, + ) + .map_err(|_| HIDError::DeviceError)?; // We encoded it, so this should never fail + auth_data.extend_from_slice(&decoded); + } + auth_data.extend_from_slice(self.rp.hash().as_ref()); + auth_data.extend_from_slice(&[key_handle.len() as u8]); + auth_data.extend_from_slice(key_handle.as_ref()); + + let cmd = U2F_AUTHENTICATE; + let apdu = CTAP1RequestAPDU::serialize(cmd, flags, &auth_data)?; + Ok(apdu) + } + + fn handle_response_ctap1( + &self, + status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result> { + if Err(ApduErrorStatus::ConditionsNotSatisfied) == status { + return Err(Retryable::Retry); + } + if let Err(err) = status { + return Err(Retryable::Error(HIDError::ApduStatus(err))); + } + + if self.is_ctap2_request() { + let parse_authentication = |input| { + // Parsing an u8, then a u32, and the rest is the signature + let (rest, (user_presence, counter)) = tuple((be_u8, be_u32))(input)?; + let signature = Vec::from(rest); + Ok((user_presence, counter, signature)) + }; + let (user_presence, counter, signature) = parse_authentication(input) + .map_err(|e: nom::Err>| { + error!("error while parsing authentication: {:?}", e); + CommandError::Deserializing(DesError::custom("unable to parse authentication")) + }) + .map_err(HIDError::Command) + .map_err(Retryable::Error)?; + + let mut flags = AuthenticatorDataFlags::empty(); + if user_presence == 1 { + flags |= AuthenticatorDataFlags::USER_PRESENT; + } + let auth_data = AuthenticatorData { + rp_id_hash: self.rp.hash(), + flags, + counter, + credential_data: None, + extensions: Default::default(), + }; + let assertion = Assertion { + credentials: None, + signature, + user: None, + auth_data, + }; + + Ok(GetAssertionResult::CTAP2( + AssertionObject(vec![assertion]), + self.client_data_wrapper.clone(), + )) + } else { + Ok(GetAssertionResult::CTAP1(input.to_vec())) + } + } +} + +impl RequestCtap2 for GetAssertion { + type Output = GetAssertionResult; + + fn command() -> Command { + Command::GetAssertion + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: FidoDevice + io::Read + io::Write + fmt::Debug, + { + Ok(ser::to_vec(&self).map_err(CommandError::Serializing)?) + } + + fn handle_response_ctap2( + &self, + dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: FidoDevice + io::Read + io::Write + fmt::Debug, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + debug!( + "response status code: {:?}, rest: {:?}", + status, + &input[1..] + ); + if input.len() > 1 { + if status.is_ok() { + let assertion: GetAssertionResponse = + from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + let number_of_credentials = assertion.number_of_credentials.unwrap_or(1); + let mut assertions = Vec::with_capacity(number_of_credentials); + assertions.push(assertion.into()); + + let msg = GetNextAssertion; + // We already have one, so skipping 0 + for _ in 1..number_of_credentials { + let new_cred = dev.send_cbor(&msg)?; + assertions.push(new_cred.into()); + } + + Ok(GetAssertionResult::CTAP2( + AssertionObject(assertions), + self.client_data_wrapper.clone(), + )) + } else { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Err(CommandError::StatusCode(status, Some(data)).into()) + } + } else if status.is_ok() { + Err(CommandError::InputTooSmall.into()) + } else { + Err(CommandError::StatusCode(status, None).into()) + } + } +} + +#[derive(Debug, PartialEq)] +pub struct Assertion { + pub credentials: Option, /* Was optional in CTAP2.0, is + * mandatory in CTAP2.1 */ + pub auth_data: AuthenticatorData, + pub signature: Vec, + pub user: Option, +} + +impl From for Assertion { + fn from(r: GetAssertionResponse) -> Self { + Assertion { + credentials: r.credentials, + auth_data: r.auth_data, + signature: r.signature, + user: r.user, + } + } +} + +// TODO(baloo): Move this to src/ctap2/mod.rs? +#[derive(Debug, PartialEq)] +pub struct AssertionObject(pub Vec); + +impl AssertionObject { + pub fn u2f_sign_data(&self) -> Vec { + if let Some(first) = self.0.first() { + let mut res = Vec::new(); + res.push(first.auth_data.flags.bits()); + res.extend(&first.auth_data.counter.to_be_bytes()); + res.extend(&first.signature); + res + // first.signature.clone() + } else { + Vec::new() + } + } +} + +pub(crate) struct GetAssertionResponse { + credentials: Option, + auth_data: AuthenticatorData, + signature: Vec, + user: Option, + number_of_credentials: Option, +} + +impl<'de> Deserialize<'de> for GetAssertionResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct GetAssertionResponseVisitor; + + impl<'de> Visitor<'de> for GetAssertionResponseVisitor { + type Value = GetAssertionResponse; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut credentials = None; + let mut auth_data = None; + let mut signature = None; + let mut user = None; + let mut number_of_credentials = None; + + while let Some(key) = map.next_key()? { + match key { + 1 => { + if credentials.is_some() { + return Err(M::Error::duplicate_field("credentials")); + } + credentials = Some(map.next_value()?); + } + 2 => { + if auth_data.is_some() { + return Err(M::Error::duplicate_field("auth_data")); + } + auth_data = Some(map.next_value()?); + } + 3 => { + if signature.is_some() { + return Err(M::Error::duplicate_field("signature")); + } + let signature_bytes: ByteBuf = map.next_value()?; + let signature_bytes: Vec = signature_bytes.into_vec(); + signature = Some(signature_bytes); + } + 4 => { + if user.is_some() { + return Err(M::Error::duplicate_field("user")); + } + user = map.next_value()?; + } + 5 => { + if number_of_credentials.is_some() { + return Err(M::Error::duplicate_field("number_of_credentials")); + } + number_of_credentials = Some(map.next_value()?); + } + k => return Err(M::Error::custom(format!("unexpected key: {:?}", k))), + } + } + + let auth_data = auth_data.ok_or_else(|| M::Error::missing_field("auth_data"))?; + let signature = signature.ok_or_else(|| M::Error::missing_field("signature"))?; + + Ok(GetAssertionResponse { + credentials, + auth_data, + signature, + user, + number_of_credentials, + }) + } + } + + deserializer.deserialize_bytes(GetAssertionResponseVisitor) + } +} + +#[cfg(test)] +pub mod test { + use super::{Assertion, GetAssertion, GetAssertionOptions, GetAssertionResult, HIDError}; + use crate::consts::{ + HIDCmd, SW_CONDITIONS_NOT_SATISFIED, SW_NO_ERROR, U2F_CHECK_IS_REGISTERED, + U2F_REQUEST_USER_PRESENCE, + }; + use crate::ctap2::attestation::{AuthenticatorData, AuthenticatorDataFlags}; + use crate::ctap2::client_data::{ + Challenge, CollectedClientData, CollectedClientDataWrapper, TokenBinding, WebauthnType, + }; + use crate::ctap2::commands::get_assertion::AssertionObject; + use crate::ctap2::commands::RequestCtap1; + use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, RpIdHash, Transport, User, + }; + use crate::transport::device_selector::Device; + use crate::transport::hid::HIDDevice; + use crate::transport::FidoDevice; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + + #[test] + fn test_get_assertion_ctap2() { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::from(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present(String::from("AAECAw"))), + }; + let assertion = GetAssertion::new( + client_data.clone(), + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("example.com"), + name: Some(String::from("Acme")), + icon: None, + }), + vec![PublicKeyCredentialDescriptor { + id: vec![ + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, + 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, + 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, + 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, + 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, + ], + transports: vec![Transport::USB], + }], + GetAssertionOptions { + user_presence: Some(true), + user_verification: None, + }, + Default::default(), + None, + ) + .expect("Failed to create GetAssertion"); + let mut device = Device::new("commands/get_assertion").unwrap(); + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + device.set_cid(cid); + + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x90]); + msg.extend(vec![0x2]); // u2f command + msg.extend(vec![ + 0xa4, // map(4) + 0x1, // rpid + 0x6b, // text(11) + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, // example.com + 0x2, // clientDataHash + 0x58, 0x20, //bytes(32) + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, 0x32, + 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, 0x10, 0x87, + 0x54, 0xc3, 0x2d, 0x80, // hash + 0x3, //allowList + 0x81, // array(1) + 0xa2, // map(2) + 0x64, // text(4), + 0x74, 0x79, 0x70, // typ + ]); + device.add_write(&msg, 0); + + msg = cid.to_vec(); + msg.extend(&[0x0]); //SEQ + msg.extend(vec![ + 0x65, // e (continuation of type) + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // public-key + 0x62, // text(2) + 0x69, 0x64, // id + 0x58, 0x40, // bytes(64) + ]); + msg.extend(&assertion.allow_list[0].id[..42]); + device.add_write(&msg, 0); + + msg = cid.to_vec(); + msg.extend(&[0x1]); //SEQ + msg.extend(&assertion.allow_list[0].id[42..]); + msg.extend(vec![ + 0x5, // options + 0xa1, // map(1) + 0x62, // text(2) + 0x75, 0x70, // up + 0xf5, // true + ]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Cbor.into(), 0x1, 0x5c]); // cmd + bcnt + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[..57]); + device.add_read(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(&[0x0]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[57..116]); + device.add_read(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(&[0x1]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[116..175]); + device.add_read(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(&[0x2]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[175..234]); + device.add_read(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(&[0x3]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[234..293]); + device.add_read(&msg, 0); + let mut msg = cid.to_vec(); + msg.extend(&[0x4]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[293..]); + device.add_read(&msg, 0); + + // Check if response is correct + let expected_auth_data = AuthenticatorData { + rp_id_hash: RpIdHash([ + 0x62, 0x5d, 0xda, 0xdf, 0x74, 0x3f, 0x57, 0x27, 0xe6, 0x6b, 0xba, 0x8c, 0x2e, 0x38, + 0x79, 0x22, 0xd1, 0xaf, 0x43, 0xc5, 0x03, 0xd9, 0x11, 0x4a, 0x8f, 0xba, 0x10, 0x4d, + 0x84, 0xd0, 0x2b, 0xfa, + ]), + flags: AuthenticatorDataFlags::USER_PRESENT, + counter: 0x11, + credential_data: None, + extensions: Default::default(), + }; + + let expected_assertion = Assertion { + credentials: Some(PublicKeyCredentialDescriptor { + id: vec![ + 242, 32, 6, 222, 79, 144, 90, 246, 138, 67, 148, 47, 2, 79, 42, 94, 206, 96, + 61, 156, 109, 75, 61, 248, 190, 8, 237, 1, 252, 68, 38, 70, 208, 52, 133, 138, + 199, 91, 237, 63, 213, 128, 191, 152, 8, 217, 79, 203, 238, 130, 185, 178, 239, + 102, 119, 175, 10, 220, 195, 88, 82, 234, 107, 158, + ], + transports: vec![], + }), + signature: vec![ + 0x30, 0x45, 0x02, 0x20, 0x4a, 0x5a, 0x9d, 0xd3, 0x92, 0x98, 0x14, 0x9d, 0x90, 0x47, + 0x69, 0xb5, 0x1a, 0x45, 0x14, 0x33, 0x00, 0x6f, 0x18, 0x2a, 0x34, 0xfb, 0xdf, 0x66, + 0xde, 0x5f, 0xc7, 0x17, 0xd7, 0x5f, 0xb3, 0x50, 0x02, 0x21, 0x00, 0xa4, 0x6b, 0x8e, + 0xa3, 0xc3, 0xb9, 0x33, 0x82, 0x1c, 0x6e, 0x7f, 0x5e, 0xf9, 0xda, 0xae, 0x94, 0xab, + 0x47, 0xf1, 0x8d, 0xb4, 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7, + 0xa0, + ], + user: Some(User { + id: vec![ + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, + ], + icon: Some("https://pics.example.com/00/p/aBjjjpqPb.png".to_string()), + name: Some("johnpsmith@example.com".to_string()), + display_name: Some("John P. Smith".to_string()), + }), + auth_data: expected_auth_data, + }; + + let expected = GetAssertionResult::CTAP2( + AssertionObject(vec![expected_assertion]), + CollectedClientDataWrapper { + client_data, + serialized_data: CLIENT_DATA_VEC.to_vec(), + }, + ); + let response = device.send_cbor(&assertion).unwrap(); + assert_eq!(response, expected); + } + + fn fill_device_ctap1(device: &mut Device, cid: [u8; 4], flags: u8, answer_status: [u8; 2]) { + // ctap2 request + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x00, 0x8A]); // cmd + bcnt + msg.extend(&[0x00, 0x2]); // U2F_AUTHENTICATE + msg.extend(&[flags]); + msg.extend(&[0x00, 0x00, 0x00]); + msg.extend(&[0x81]); // Data len - 7 + msg.extend(&CLIENT_DATA_HASH); + msg.extend(&RELYING_PARTY_HASH[..18]); + device.add_write(&msg, 0); + + // Continuation package + let mut msg = cid.to_vec(); + msg.extend(vec![0x00]); // SEQ + msg.extend(&RELYING_PARTY_HASH[18..]); + msg.extend(&[KEY_HANDLE.len() as u8]); + msg.extend(&KEY_HANDLE[..44]); + device.add_write(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(vec![0x01]); // SEQ + msg.extend(&KEY_HANDLE[44..]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x4D]); // cmd + bcnt + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP1[0..57]); + device.add_read(&msg, 0); + + let mut msg = cid.to_vec(); + msg.extend(&[0x0]); // SEQ + msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP1[57..]); + msg.extend(&answer_status); + device.add_read(&msg, 0); + } + + #[test] + fn test_get_assertion_ctap1() { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::from(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present(String::from("AAECAw"))), + }; + let assertion = GetAssertion::new( + client_data.clone(), + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("example.com"), + name: Some(String::from("Acme")), + icon: None, + }), + vec![PublicKeyCredentialDescriptor { + id: vec![ + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, + 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, + 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, + 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, + 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, + ], + transports: vec![Transport::USB], + }], + GetAssertionOptions { + user_presence: Some(true), + user_verification: None, + }, + Default::default(), + None, + ) + .expect("Failed to create GetAssertion"); + let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it) + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + device.set_cid(cid); + + // ctap1 request + fill_device_ctap1( + &mut device, + cid, + U2F_CHECK_IS_REGISTERED, + SW_CONDITIONS_NOT_SATISFIED, + ); + let ctap1_request = assertion.ctap1_format(&mut device).unwrap(); + // Check if the request is going to be correct + assert_eq!(ctap1_request, GET_ASSERTION_SAMPLE_REQUEST_CTAP1); + + // Now do it again, but parse the actual response + fill_device_ctap1( + &mut device, + cid, + U2F_CHECK_IS_REGISTERED, + SW_CONDITIONS_NOT_SATISFIED, + ); + fill_device_ctap1(&mut device, cid, U2F_REQUEST_USER_PRESENCE, SW_NO_ERROR); + + let response = device.send_ctap1(&assertion).unwrap(); + + // Check if response is correct + let expected_auth_data = AuthenticatorData { + rp_id_hash: RpIdHash(RELYING_PARTY_HASH), + flags: AuthenticatorDataFlags::USER_PRESENT, + counter: 0x3B, + credential_data: None, + extensions: Default::default(), + }; + + let expected_assertion = Assertion { + credentials: None, + signature: vec![ + 0x30, 0x44, 0x02, 0x20, 0x7B, 0xDE, 0x0A, 0x52, 0xAC, 0x1F, 0x4C, 0x8B, 0x27, 0xE0, + 0x03, 0xA3, 0x70, 0xCD, 0x66, 0xA4, 0xC7, 0x11, 0x8D, 0xD2, 0x2D, 0x54, 0x47, 0x83, + 0x5F, 0x45, 0xB9, 0x9C, 0x68, 0x42, 0x3F, 0xF7, 0x02, 0x20, 0x3C, 0x51, 0x7B, 0x47, + 0x87, 0x7F, 0x85, 0x78, 0x2D, 0xE1, 0x00, 0x86, 0xA7, 0x83, 0xD1, 0xE7, 0xDF, 0x4E, + 0x36, 0x39, 0xE7, 0x71, 0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E, + ], + user: None, + auth_data: expected_auth_data, + }; + + let expected = GetAssertionResult::CTAP2( + AssertionObject(vec![expected_assertion]), + CollectedClientDataWrapper { + client_data, + serialized_data: CLIENT_DATA_VEC.to_vec(), + }, + ); + + assert_eq!(response, expected); + } + + #[test] + fn test_get_assertion_ctap1_long_keys() { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::from(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present(String::from("AAECAw"))), + }; + + let too_long_key_handle = PublicKeyCredentialDescriptor { + id: vec![0; 1000], + transports: vec![Transport::USB], + }; + let mut assertion = GetAssertion::new( + client_data.clone(), + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("example.com"), + name: Some(String::from("Acme")), + icon: None, + }), + vec![too_long_key_handle.clone()], + GetAssertionOptions { + user_presence: Some(true), + user_verification: None, + }, + Default::default(), + None, + ) + .expect("Failed to create GetAssertion"); + + let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it) + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + device.set_cid(cid); + + assert_matches!( + assertion.ctap1_format(&mut device), + Err(HIDError::DeviceNotSupported) + ); + + assertion.allow_list = vec![too_long_key_handle.clone(); 5]; + + assert_matches!( + assertion.ctap1_format(&mut device), + Err(HIDError::DeviceNotSupported) + ); + let ok_key_handle = PublicKeyCredentialDescriptor { + id: vec![ + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, 0xEF, + 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, + 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, + 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, + 0xD4, 0x15, 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, + ], + transports: vec![Transport::USB], + }; + assertion.allow_list = vec![ + too_long_key_handle.clone(), + too_long_key_handle.clone(), + too_long_key_handle.clone(), + ok_key_handle, + too_long_key_handle.clone(), + ]; + + // ctap1 request + fill_device_ctap1( + &mut device, + cid, + U2F_CHECK_IS_REGISTERED, + SW_CONDITIONS_NOT_SATISFIED, + ); + let ctap1_request = assertion.ctap1_format(&mut device).unwrap(); + // Check if the request is going to be correct + assert_eq!(ctap1_request, GET_ASSERTION_SAMPLE_REQUEST_CTAP1); + + // Now do it again, but parse the actual response + fill_device_ctap1( + &mut device, + cid, + U2F_CHECK_IS_REGISTERED, + SW_CONDITIONS_NOT_SATISFIED, + ); + fill_device_ctap1(&mut device, cid, U2F_REQUEST_USER_PRESENCE, SW_NO_ERROR); + + let response = device.send_ctap1(&assertion).unwrap(); + + // Check if response is correct + let expected_auth_data = AuthenticatorData { + rp_id_hash: RpIdHash(RELYING_PARTY_HASH), + flags: AuthenticatorDataFlags::USER_PRESENT, + counter: 0x3B, + credential_data: None, + extensions: Default::default(), + }; + + let expected_assertion = Assertion { + credentials: None, + signature: vec![ + 0x30, 0x44, 0x02, 0x20, 0x7B, 0xDE, 0x0A, 0x52, 0xAC, 0x1F, 0x4C, 0x8B, 0x27, 0xE0, + 0x03, 0xA3, 0x70, 0xCD, 0x66, 0xA4, 0xC7, 0x11, 0x8D, 0xD2, 0x2D, 0x54, 0x47, 0x83, + 0x5F, 0x45, 0xB9, 0x9C, 0x68, 0x42, 0x3F, 0xF7, 0x02, 0x20, 0x3C, 0x51, 0x7B, 0x47, + 0x87, 0x7F, 0x85, 0x78, 0x2D, 0xE1, 0x00, 0x86, 0xA7, 0x83, 0xD1, 0xE7, 0xDF, 0x4E, + 0x36, 0x39, 0xE7, 0x71, 0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E, + ], + user: None, + auth_data: expected_auth_data, + }; + + let expected = GetAssertionResult::CTAP2( + AssertionObject(vec![expected_assertion]), + CollectedClientDataWrapper { + client_data, + serialized_data: CLIENT_DATA_VEC.to_vec(), + }, + ); + + assert_eq!(response, expected); + } + + // Manually assembled according to https://www.w3.org/TR/webauthn-2/#clientdatajson-serialization + const CLIENT_DATA_VEC: [u8; 140] = [ + 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, // {"type": + 0x22, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x22, // "webauthn.create" + 0x2c, 0x22, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x22, + 0x3a, // (,"challenge": + 0x22, 0x41, 0x41, 0x45, 0x43, 0x41, 0x77, 0x22, // challenge in base64 + 0x2c, 0x22, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, 0x3a, // ,"origin": + 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x22, // "example.com" + 0x2c, 0x22, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, + 0x3a, // ,"crossOrigin": + 0x66, 0x61, 0x6c, 0x73, 0x65, // false + 0x2c, 0x22, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, + 0x3a, // ,"tokenBinding": + 0x7b, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, // {"status": + 0x22, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x22, // "present" + 0x2c, 0x22, 0x69, 0x64, 0x22, 0x3a, // ,"id": + 0x22, 0x41, 0x41, 0x45, 0x43, 0x41, 0x77, 0x22, // "AAECAw" + 0x7d, // } + 0x7d, // } + ]; + + const CLIENT_DATA_HASH: [u8; 32] = [ + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, // hash + 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, // hash + 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80, // hash + ]; + + const RELYING_PARTY_HASH: [u8; 32] = [ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, 0x34, 0xE2, + 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x12, 0x55, 0x86, 0xCE, + 0x19, 0x47, + ]; + const KEY_HANDLE: [u8; 64] = [ + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, 0xEF, 0xAA, + 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, + 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, + 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, + ]; + + const GET_ASSERTION_SAMPLE_REQUEST_CTAP1: [u8; 138] = [ + // CBOR Header + 0x0, // CLA + 0x2, // INS U2F_Authenticate + 0x3, // P1 Flags (user presence) + 0x0, // P2 + 0x0, 0x0, 0x81, // Lc + // NOTE: This has been taken from CTAP2.0 spec, but the clientDataHash has been replaced + // to be able to operate with known values for CollectedClientData (spec doesn't say + // what values led to the provided example hash) + // clientDataHash: + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, // hash + 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, // hash + 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80, // hash + // rpIdHash: + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, 0x34, 0xE2, + 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x12, 0x55, 0x86, 0xCE, + 0x19, 0x47, // .. + // Key Handle Length (1 Byte): + 0x40, // .. + // Key Handle (Key Handle Length Bytes): + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, 0xEF, 0xAA, + 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, + 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, + 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, // .. + // Le (Ne=65536): + 0x0, 0x0, + ]; + + const GET_ASSERTION_SAMPLE_REQUEST_CTAP2: [u8; 138] = [ + // CBOR Header + 0x0, // leading zero + 0x2, // CMD U2F_Authenticate + 0x3, // Flags (user presence) + 0x0, 0x0, // zero bits + 0x0, 0x81, // size + // NOTE: This has been taken from CTAP2.0 spec, but the clientDataHash has been replaced + // to be able to operate with known values for CollectedClientData (spec doesn't say + // what values led to the provided example hash) + // clientDataHash: + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, 0x32, 0x64, + 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, 0x10, 0x87, 0x54, 0xc3, + 0x2d, 0x80, // hash + // rpIdHash: + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, 0x34, 0xE2, + 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x12, 0x55, 0x86, 0xCE, + 0x19, 0x47, // .. + // Key Handle Length (1 Byte): + 0x40, // .. + // Key Handle (Key Handle Length Bytes): + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, 0xEF, 0xAA, + 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, + 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, + 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, 0x0, 0x0, // 2 trailing zeros from protocol + ]; + + const GET_ASSERTION_SAMPLE_RESPONSE_CTAP1: [u8; 75] = [ + 0x01, // User Presence (1 Byte) + 0x00, 0x00, 0x00, 0x3B, // Sign Count (4 Bytes) + // Signature (variable Length) + 0x30, 0x44, 0x02, 0x20, 0x7B, 0xDE, 0x0A, 0x52, 0xAC, 0x1F, 0x4C, 0x8B, 0x27, 0xE0, 0x03, + 0xA3, 0x70, 0xCD, 0x66, 0xA4, 0xC7, 0x11, 0x8D, 0xD2, 0x2D, 0x54, 0x47, 0x83, 0x5F, 0x45, + 0xB9, 0x9C, 0x68, 0x42, 0x3F, 0xF7, 0x02, 0x20, 0x3C, 0x51, 0x7B, 0x47, 0x87, 0x7F, 0x85, + 0x78, 0x2D, 0xE1, 0x00, 0x86, 0xA7, 0x83, 0xD1, 0xE7, 0xDF, 0x4E, 0x36, 0x39, 0xE7, 0x71, + 0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E, + ]; + + const GET_ASSERTION_SAMPLE_RESPONSE_CTAP2: [u8; 348] = [ + 0x00, // status == success + 0xA5, // map(5) + 0x01, // unsigned(1) + 0xA2, // map(2) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x40, // bytes(0x64, ) + 0xF2, 0x20, 0x06, 0xDE, 0x4F, 0x90, 0x5A, 0xF6, 0x8A, 0x43, 0x94, 0x2F, 0x02, 0x4F, 0x2A, + 0x5E, 0xCE, 0x60, 0x3D, 0x9C, 0x6D, 0x4B, 0x3D, 0xF8, 0xBE, 0x08, 0xED, 0x01, 0xFC, 0x44, + 0x26, 0x46, 0xD0, 0x34, 0x85, 0x8A, 0xC7, 0x5B, 0xED, 0x3F, 0xD5, 0x80, 0xBF, 0x98, 0x08, + 0xD9, 0x4F, 0xCB, 0xEE, 0x82, 0xB9, 0xB2, 0xEF, 0x66, 0x77, 0xAF, 0x0A, 0xDC, 0xC3, 0x58, + 0x52, 0xEA, 0x6B, + 0x9E, // "\x0xF2, \x0x06, \x0xDE, O\x0x90, Z\x0xF6, \x0x8A, C\x0x94, /\x0x02, O*^\x0xCE, `=\x0x9C, mK=\x0xF8, \x0xBE, \b\x0xED, \x0x01, \x0xFC, D&F\x0xD0, 4\x0x85, \x0x8A, \x0xC7, [\x0xED, ?\x0xD5, \x0x80, \x0xBF, \x0x98, \b\x0xD9, O\x0xCB, \x0xEE, \x0x82, \x0xB9, \x0xB2, \x0xEF, fw\x0xAF, \n\x0xDC, \x0xC3, 0xXR, \x0xEA, k\x0x9E, " + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6A, // text(0x10, ) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key" + 0x02, // unsigned(2) + 0x58, 0x25, // bytes(0x37, ) + 0x62, 0x5D, 0xDA, 0xDF, 0x74, 0x3F, 0x57, 0x27, 0xE6, 0x6B, 0xBA, 0x8C, 0x2E, 0x38, 0x79, + 0x22, 0xD1, 0xAF, 0x43, 0xC5, 0x03, 0xD9, 0x11, 0x4A, 0x8F, 0xBA, 0x10, 0x4D, 0x84, 0xD0, + 0x2B, 0xFA, 0x01, 0x00, 0x00, 0x00, + 0x11, // "b]\x0xDA, \x0xDF, t?W'\x0xE6, k\x0xBA, \x0x8C, .8y\"\x0xD1, \x0xAF, C\x0xC5, \x0x03, \x0xD9, \x0x11, J\x0x8F, \x0xBA, \x0x10, M\x0x84, \x0xD0, +\x0xFA, \x0x01, \x0x00, \x0x00, \x0x00, \x0x11, " + 0x03, // unsigned(3) + 0x58, 0x47, // bytes(0x71, ) + 0x30, 0x45, 0x02, 0x20, 0x4A, 0x5A, 0x9D, 0xD3, 0x92, 0x98, 0x14, 0x9D, 0x90, 0x47, 0x69, + 0xB5, 0x1A, 0x45, 0x14, 0x33, 0x00, 0x6F, 0x18, 0x2A, 0x34, 0xFB, 0xDF, 0x66, 0xDE, 0x5F, + 0xC7, 0x17, 0xD7, 0x5F, 0xB3, 0x50, 0x02, 0x21, 0x00, 0xA4, 0x6B, 0x8E, 0xA3, 0xC3, 0xB9, + 0x33, 0x82, 0x1C, 0x6E, 0x7F, 0x5E, 0xF9, 0xDA, 0xAE, 0x94, 0xAB, 0x47, 0xF1, 0x8D, 0xB4, + 0x74, 0xC7, 0x47, 0x90, 0xEA, 0xAB, 0xB1, 0x44, 0x11, 0xE7, + 0xA0, // "0x0E, \x0x02, 0xJZ, \x0x9D, \x0xD3, \x0x92, \x0x98, \x0x14, \x0x9D, \x0x90, Gi\x0xB5, \x0x1A, E\x0x14, 3\x0x00, o\x0x18, *4\x0xFB, \x0xDF, f\x0xDE, _\x0xC7, \x0x17, \x0xD7, _\x0xB3, P\x0x02, !\x0x00, \x0xA4, k\x0x8E, \x0xA3, \x0xC3, \x0xB9, 3\x0x82, \x0x1C, n\x0x7F, ^\x0xF9, \x0xDA, \x0xAE, \x0x94, \x0xAB, G\x0xF1, \x0x8D, \x0xB4, t\x0xC7, G\x0x90, \x0xEA, \x0xAB, \x0xB1, D\x0x11, \x0xE7, \x0xA0, " + 0x04, // unsigned(4) + 0xA4, // map(4) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x20, // bytes(0x32, ) + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, + 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, + 0x30, + 0x82, // "0\x0x82, \x0x01, \x0x93, 0\x0x82, \x0x01, 8\x0xA0, \x0x03, \x0x02, \x0x01, \x0x02, 0\x0x82, \x0x01, \x0x93, 0\x0x82, \x0x01, 8\x0xA0, \x0x03, \x0x02, \x0x01, \x0x02, 0\x0x82, \x0x01, \x0x93, 0\x0x82, " + 0x64, // text(4) + 0x69, 0x63, 0x6F, 0x6E, // "icon" + 0x78, 0x2B, // text(0x43, ) + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A, 0x2F, 0x2F, 0x70, 0x69, 0x63, 0x73, 0x2E, 0x65, 0x78, + 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x30, 0x30, 0x2F, 0x70, 0x2F, + 0x61, 0x42, 0x6A, 0x6A, 0x6A, 0x70, 0x71, 0x50, 0x62, 0x2E, 0x70, 0x6E, + 0x67, // "https://pics.example.com/0x00, /p/aBjjjpqPb.png" + 0x64, // text(4) + 0x6E, 0x61, 0x6D, 0x65, // "name" + 0x76, // text(0x22, ) + 0x6A, 0x6F, 0x68, 0x6E, 0x70, 0x73, 0x6D, 0x69, 0x74, 0x68, 0x40, 0x65, 0x78, 0x61, 0x6D, + 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, // "johnpsmith@example.com" + 0x6B, // text(0x11, ) + 0x64, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79, 0x4E, 0x61, 0x6D, 0x65, // "displayName" + 0x6D, // text(0x13, ) + 0x4A, 0x6F, 0x68, 0x6E, 0x20, 0x50, 0x2E, 0x20, 0x53, 0x6D, 0x69, 0x74, + 0x68, // "John P. Smith" + 0x05, // unsigned(5) + 0x01, // unsigned(1) + ]; +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_info.rs b/third_party/rust/authenticator/src/ctap2/commands/get_info.rs new file mode 100644 index 000000000000..ffad083682a7 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/get_info.rs @@ -0,0 +1,733 @@ +use super::{Command, CommandError, RequestCtap2, StatusCode}; +use crate::ctap2::attestation::AAGuid; +use crate::ctap2::server::PublicKeyCredentialParameters; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde::{ + de::{Error as SError, IgnoredAny, MapAccess, Visitor}, + Deserialize, Deserializer, +}; +use serde_cbor::{de::from_slice, Value}; +use std::collections::BTreeMap; +use std::fmt; + +#[derive(Debug)] +pub struct GetInfo {} + +impl Default for GetInfo { + fn default() -> GetInfo { + GetInfo {} + } +} + +impl RequestCtap2 for GetInfo { + type Output = AuthenticatorInfo; + + fn command() -> Command { + Command::GetInfo + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + Ok(Vec::new()) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + + if input.len() > 1 { + if status.is_ok() { + trace!("parsing authenticator info data: {:#04X?}", &input); + let authenticator_info = + from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Ok(authenticator_info) + } else { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Err(CommandError::StatusCode(status, Some(data)).into()) + } + } else { + Err(CommandError::InputTooSmall.into()) + } + } +} + +fn true_val() -> bool { + true +} + +#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] +pub(crate) struct AuthenticatorOptions { + /// Indicates that the device is attached to the client and therefore can’t + /// be removed and used on another client. + #[serde(rename = "plat", default)] + pub(crate) platform_device: bool, + /// Indicates that the device is capable of storing keys on the device + /// itself and therefore can satisfy the authenticatorGetAssertion request + /// with allowList parameter not specified or empty. + #[serde(rename = "rk", default)] + pub(crate) resident_key: bool, + + /// Client PIN: + /// If present and set to true, it indicates that the device is capable of + /// accepting a PIN from the client and PIN has been set. + /// If present and set to false, it indicates that the device is capable of + /// accepting a PIN from the client and PIN has not been set yet. + /// If absent, it indicates that the device is not capable of accepting a + /// PIN from the client. + /// Client PIN is one of the ways to do user verification. + #[serde(rename = "clientPin")] + pub(crate) client_pin: Option, + + /// Indicates that the device is capable of testing user presence. + #[serde(rename = "up", default = "true_val")] + pub(crate) user_presence: bool, + + /// Indicates that the device is capable of verifying the user within + /// itself. For example, devices with UI, biometrics fall into this + /// category. + /// If present and set to true, it indicates that the device is capable of + /// user verification within itself and has been configured. + /// If present and set to false, it indicates that the device is capable of + /// user verification within itself and has not been yet configured. For + /// example, a biometric device that has not yet been configured will + /// return this parameter set to false. + /// If absent, it indicates that the device is not capable of user + /// verification within itself. + /// A device that can only do Client PIN will not return the "uv" parameter. + /// If a device is capable of verifying the user within itself as well as + /// able to do Client PIN, it will return both "uv" and the Client PIN + /// option. + // TODO(MS): My Token (key-ID FIDO2) does return Some(false) here, even though + // it has no built-in verification method. Not to be trusted... + #[serde(rename = "uv")] + pub(crate) user_verification: Option, +} + +impl Default for AuthenticatorOptions { + fn default() -> Self { + AuthenticatorOptions { + platform_device: false, + resident_key: false, + client_pin: None, + user_presence: true, + user_verification: None, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AuthenticatorInfo { + pub(crate) versions: Vec, + pub(crate) extensions: Vec, + pub(crate) aaguid: AAGuid, + pub(crate) options: AuthenticatorOptions, + pub(crate) max_msg_size: Option, + pub(crate) pin_protocols: Vec, + // CTAP 2.1 + pub(crate) max_credential_count_in_list: Option, + pub(crate) max_credential_id_length: Option, + pub(crate) transports: Option>, + pub(crate) algorithms: Option>, + pub(crate) max_ser_large_blob_array: Option, + pub(crate) force_pin_change: Option, + pub(crate) min_pin_length: Option, + pub(crate) firmware_version: Option, + pub(crate) max_cred_blob_length: Option, + pub(crate) max_rpids_for_set_min_pin_length: Option, + pub(crate) preferred_platform_uv_attempts: Option, + pub(crate) uv_modality: Option, + pub(crate) certifications: Option>, + pub(crate) remaining_discoverable_credentials: Option, + pub(crate) vendor_prototype_config_commands: Option>, +} + +impl AuthenticatorInfo { + pub fn supports_hmac_secret(&self) -> bool { + self.extensions.contains(&"hmac-secret".to_string()) + } +} + +macro_rules! parse_next_optional_value { + ($name:expr, $map:expr) => { + if $name.is_some() { + return Err(serde::de::Error::duplicate_field("$name")); + } + $name = Some($map.next_value()?); + }; +} + +impl<'de> Deserialize<'de> for AuthenticatorInfo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AuthenticatorInfoVisitor; + + impl<'de> Visitor<'de> for AuthenticatorInfoVisitor { + type Value = AuthenticatorInfo; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut versions = Vec::new(); + let mut extensions = Vec::new(); + let mut aaguid = None; + let mut options = AuthenticatorOptions::default(); + let mut max_msg_size = None; + let mut pin_protocols = Vec::new(); + let mut max_credential_count_in_list = None; + let mut max_credential_id_length = None; + let mut transports = None; + let mut algorithms = None; + let mut max_ser_large_blob_array = None; + let mut force_pin_change = None; + let mut min_pin_length = None; + let mut firmware_version = None; + let mut max_cred_blob_length = None; + let mut max_rpids_for_set_min_pin_length = None; + let mut preferred_platform_uv_attempts = None; + let mut uv_modality = None; + let mut certifications = None; + let mut remaining_discoverable_credentials = None; + let mut vendor_prototype_config_commands = None; + while let Some(key) = map.next_key()? { + match key { + 0x01 => { + if !versions.is_empty() { + return Err(serde::de::Error::duplicate_field("versions")); + } + versions = map.next_value()?; + } + 0x02 => { + if !extensions.is_empty() { + return Err(serde::de::Error::duplicate_field("extensions")); + } + extensions = map.next_value()?; + } + 0x03 => { + parse_next_optional_value!(aaguid, map); + } + 0x04 => { + options = map.next_value()?; + } + 0x05 => { + parse_next_optional_value!(max_msg_size, map); + } + 0x06 => { + if !pin_protocols.is_empty() { + return Err(serde::de::Error::duplicate_field("pin_protocols")); + } + pin_protocols = map.next_value()?; + } + 0x07 => { + parse_next_optional_value!(max_credential_count_in_list, map); + } + 0x08 => { + parse_next_optional_value!(max_credential_id_length, map); + } + 0x09 => { + parse_next_optional_value!(transports, map); + } + 0x0a => { + parse_next_optional_value!(algorithms, map); + } + 0x0b => { + parse_next_optional_value!(max_ser_large_blob_array, map); + } + 0x0c => { + parse_next_optional_value!(force_pin_change, map); + } + 0x0d => { + parse_next_optional_value!(min_pin_length, map); + } + 0x0e => { + parse_next_optional_value!(firmware_version, map); + } + 0x0f => { + parse_next_optional_value!(max_cred_blob_length, map); + } + 0x10 => { + parse_next_optional_value!(max_rpids_for_set_min_pin_length, map); + } + 0x11 => { + parse_next_optional_value!(preferred_platform_uv_attempts, map); + } + 0x12 => { + parse_next_optional_value!(uv_modality, map); + } + 0x13 => { + parse_next_optional_value!(certifications, map); + } + 0x14 => { + parse_next_optional_value!(remaining_discoverable_credentials, map); + } + 0x15 => { + parse_next_optional_value!(vendor_prototype_config_commands, map); + } + k => { + warn!("GetInfo: unexpected key: {:?}", k); + let _ = map.next_value::()?; + continue; + } + } + } + + if versions.is_empty() { + return Err(M::Error::custom( + "expected at least one version, got none".to_string(), + )); + } + + if let Some(aaguid) = aaguid { + Ok(AuthenticatorInfo { + versions, + extensions, + aaguid, + options, + max_msg_size, + pin_protocols, + max_credential_count_in_list, + max_credential_id_length, + transports, + algorithms, + max_ser_large_blob_array, + force_pin_change, + min_pin_length, + firmware_version, + max_cred_blob_length, + max_rpids_for_set_min_pin_length, + preferred_platform_uv_attempts, + uv_modality, + certifications, + remaining_discoverable_credentials, + vendor_prototype_config_commands, + }) + } else { + Err(M::Error::custom("No AAGuid specified".to_string())) + } + } + } + + deserializer.deserialize_bytes(AuthenticatorInfoVisitor) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::consts::{Capability, HIDCmd, CID_BROADCAST}; + use crate::crypto::COSEAlgorithm; + use crate::transport::device_selector::Device; + use crate::transport::platform::device::IN_HID_RPT_SIZE; + use crate::transport::{hid::HIDDevice, FidoDevice, Nonce}; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + use serde_cbor::de::from_slice; + + // Raw data take from https://github.com/Yubico/python-fido2/blob/master/test/test_ctap2.py + pub const AAGUID_RAW: [u8; 16] = [ + 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC, + 0x7D, + ]; + + pub const AUTHENTICATOR_INFO_PAYLOAD: [u8; 89] = [ + 0xa6, // map(6) + 0x01, // unsigned(1) + 0x82, // array(2) + 0x66, // text(6) + 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, // "U2F_V2" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, // "FIDO_2_0" + 0x02, // unsigned(2) + 0x82, // array(2) + 0x63, // text(3) + 0x75, 0x76, 0x6d, // "uvm" + 0x6b, // text(11) + 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret" + 0x03, // unsigned(3) + 0x50, // bytes(16) + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, + 0x7d, // "\xF8\xA0\u0011\xF3\x8C\nM\u0015\x80\u0006\u0017\u0011\u001F\x9E\xDC}" + 0x04, // unsigned(4) + 0xa4, // map(4) + 0x62, // text(2) + 0x72, 0x6b, // "rk" + 0xf5, // primitive(21) + 0x62, // text(2) + 0x75, 0x70, // "up" + 0xf5, // primitive(21) + 0x64, // text(4) + 0x70, 0x6c, 0x61, 0x74, // "plat" + 0xf4, // primitive(20) + 0x69, // text(9) + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x69, 0x6e, // "clientPin" + 0xf4, // primitive(20) + 0x05, // unsigned(5) + 0x19, 0x04, 0xb0, // unsigned(1200) + 0x06, // unsigned(6) + 0x81, // array(1) + 0x01, // unsigned(1) + ]; + + // Real world example from Yubikey Bio + pub const AUTHENTICATOR_INFO_PAYLOAD_YK_BIO_5C: [u8; 409] = [ + 0xB3, // map(19) + 0x01, // unsigned(1) + 0x84, // array(4) + 0x66, // text(6) + 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, // "U2F_V2" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, // "FIDO_2_0" + 0x6C, // text(12) + 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x31, 0x5F, 0x50, 0x52, + 0x45, // "FIDO_2_1_PRE" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x31, // "FIDO_2_1" + 0x02, // unsigned(2) + 0x85, // array(5) + 0x6B, // text(11) + 0x63, 0x72, 0x65, 0x64, 0x50, 0x72, 0x6F, 0x74, 0x65, 0x63, 0x74, // "credProtect" + 0x6B, // text(11) + 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret" + 0x6C, // text(12) + 0x6C, 0x61, 0x72, 0x67, 0x65, 0x42, 0x6C, 0x6F, 0x62, 0x4B, 0x65, + 0x79, // "largeBlobKey" + 0x68, // text(8) + 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, // "credBlob" + 0x6C, // text(12) + 0x6D, 0x69, 0x6E, 0x50, 0x69, 0x6E, 0x4C, 0x65, 0x6E, 0x67, 0x74, + 0x68, // "minPinLength" + 0x03, // unsigned(3) + 0x50, // bytes(16) + 0xD8, 0x52, 0x2D, 0x9F, 0x57, 0x5B, 0x48, 0x66, 0x88, 0xA9, 0xBA, 0x99, 0xFA, 0x02, 0xF3, + 0x5B, // "\xD8R-\x9FW[Hf\x88\xA9\xBA\x99\xFA\u0002\xF3[" + 0x04, // unsigned(4) + 0xB0, // map(16) + 0x62, // text(2) + 0x72, 0x6B, // "rk" + 0xF5, // primitive(21) + 0x62, // text(2) + 0x75, 0x70, // "up" + 0xF5, // primitive(21) + 0x62, // text(2) + 0x75, 0x76, // "uv" + 0xF5, // primitive(21) + 0x64, // text(4) + 0x70, 0x6C, 0x61, 0x74, // "plat" + 0xF4, // primitive(20) + 0x67, // text(7) + 0x75, 0x76, 0x54, 0x6F, 0x6B, 0x65, 0x6E, // "uvToken" + 0xF5, // primitive(21) + 0x68, // text(8) + 0x61, 0x6C, 0x77, 0x61, 0x79, 0x73, 0x55, 0x76, // "alwaysUv" + 0xF5, // primitive(21) + 0x68, // text(8) + 0x63, 0x72, 0x65, 0x64, 0x4D, 0x67, 0x6D, 0x74, // "credMgmt" + 0xF5, // primitive(21) + 0x69, // text(9) + 0x61, 0x75, 0x74, 0x68, 0x6E, 0x72, 0x43, 0x66, 0x67, // "authnrCfg" + 0xF5, // primitive(21) + 0x69, // text(9) + 0x62, 0x69, 0x6F, 0x45, 0x6E, 0x72, 0x6F, 0x6C, 0x6C, // "bioEnroll" + 0xF5, // primitive(21) + 0x69, // text(9) + 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, // "clientPin" + 0xF5, // primitive(21) + 0x6A, // text(10) + 0x6C, 0x61, 0x72, 0x67, 0x65, 0x42, 0x6C, 0x6F, 0x62, 0x73, // "largeBlobs" + 0xF5, // primitive(21) + 0x6E, // text(14) + 0x70, 0x69, 0x6E, 0x55, 0x76, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6F, 0x6B, 0x65, + 0x6E, // "pinUvAuthToken" + 0xF5, // primitive(21) + 0x6F, // text(15) + 0x73, 0x65, 0x74, 0x4D, 0x69, 0x6E, 0x50, 0x49, 0x4E, 0x4C, 0x65, 0x6E, 0x67, 0x74, + 0x68, // "setMinPINLength" + 0xF5, // primitive(21) + 0x70, // text(16) + 0x6D, 0x61, 0x6B, 0x65, 0x43, 0x72, 0x65, 0x64, 0x55, 0x76, 0x4E, 0x6F, 0x74, 0x52, 0x71, + 0x64, // "makeCredUvNotRqd" + 0xF4, // primitive(20) + 0x75, // text(21) + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x4D, 0x67, 0x6D, 0x74, 0x50, + 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, // "credentialMgmtPreview" + 0xF5, // primitive(21) + 0x78, 0x1B, // text(27) + 0x75, 0x73, 0x65, 0x72, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, + 0x6E, 0x4D, 0x67, 0x6D, 0x74, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, + 0x77, // "userVerificationMgmtPreview" + 0xF5, // primitive(21) + 0x05, // unsigned(5) + 0x19, 0x04, 0xB0, // unsigned(1200) + 0x06, // unsigned(6) + 0x82, // array(2) + 0x02, // unsigned(2) + 0x01, // unsigned(1) + 0x07, // unsigned(7) + 0x08, // unsigned(8) + 0x08, // unsigned(8) + 0x18, 0x80, // unsigned(128) + 0x09, // unsigned(9) + 0x81, // array(1) + 0x63, // text(3) + 0x75, 0x73, 0x62, // "usb" + 0x0A, // unsigned(10) + 0x82, // array(2) + 0xA2, // map(2) + 0x63, // text(3) + 0x61, 0x6C, 0x67, // "alg" + 0x26, // negative(6) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6A, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key" + 0xA2, // map(2) + 0x63, // text(3) + 0x61, 0x6C, 0x67, // "alg" + 0x27, // negative(7) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6A, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key" + 0x0B, // unsigned(11) + 0x19, 0x04, 0x00, // unsigned(1024) + 0x0C, // unsigned(12) + 0xF4, // primitive(20) + 0x0D, // unsigned(13) + 0x04, // unsigned(4) + 0x0E, // unsigned(14) + 0x1A, 0x00, 0x05, 0x05, 0x06, // unsigned(328966) + 0x0F, // unsigned(15) + 0x18, 0x20, // unsigned(32) + 0x10, // unsigned(16) + 0x01, // unsigned(1) + 0x11, // unsigned(17) + 0x03, // unsigned(3) + 0x12, // unsigned(18) + 0x02, // unsigned(2) + 0x14, // unsigned(20) + 0x18, 0x18, // unsigned(24) + ]; + + #[test] + fn parse_authenticator_info() { + let authenticator_info: AuthenticatorInfo = + from_slice(&AUTHENTICATOR_INFO_PAYLOAD).unwrap(); + + let expected = AuthenticatorInfo { + versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()], + extensions: vec!["uvm".to_string(), "hmac-secret".to_string()], + aaguid: AAGuid(AAGUID_RAW), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(false), + user_presence: true, + user_verification: None, + }, + max_msg_size: Some(1200), + pin_protocols: vec![1], + max_credential_count_in_list: None, + max_credential_id_length: None, + transports: None, + algorithms: None, + max_ser_large_blob_array: None, + force_pin_change: None, + min_pin_length: None, + firmware_version: None, + max_cred_blob_length: None, + max_rpids_for_set_min_pin_length: None, + preferred_platform_uv_attempts: None, + uv_modality: None, + certifications: None, + remaining_discoverable_credentials: None, + vendor_prototype_config_commands: None, + }; + + assert_eq!(authenticator_info, expected); + + // Test broken auth info + let mut broken_payload = AUTHENTICATOR_INFO_PAYLOAD.to_vec(); + // Have one more entry in the map + broken_payload[0] += 1; + // Add the additional entry at the back with an invalid key + broken_payload.extend_from_slice(&[ + 0x17, // unsigned(23) -> invalid key-number. CTAP2.1 goes only to 0x15 + 0x6B, // text(11) + 0x69, 0x6E, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x5F, 0x6B, 0x65, 0x79, // "invalid_key" + ]); + + let authenticator_info: AuthenticatorInfo = from_slice(&broken_payload).unwrap(); + assert_eq!(authenticator_info, expected); + } + + #[test] + fn parse_authenticator_info_yk_bio_5c() { + let authenticator_info: AuthenticatorInfo = + from_slice(&AUTHENTICATOR_INFO_PAYLOAD_YK_BIO_5C).unwrap(); + + let expected = AuthenticatorInfo { + versions: vec![ + "U2F_V2".to_string(), + "FIDO_2_0".to_string(), + "FIDO_2_1_PRE".to_string(), + "FIDO_2_1".to_string(), + ], + extensions: vec![ + "credProtect".to_string(), + "hmac-secret".to_string(), + "largeBlobKey".to_string(), + "credBlob".to_string(), + "minPinLength".to_string(), + ], + aaguid: AAGuid([ + 0xd8, 0x52, 0x2d, 0x9f, 0x57, 0x5b, 0x48, 0x66, 0x88, 0xa9, 0xba, 0x99, 0xfa, 0x02, + 0xf3, 0x5b, + ]), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(true), + user_presence: true, + user_verification: Some(true), + }, + max_msg_size: Some(1200), + pin_protocols: vec![2, 1], + max_credential_count_in_list: Some(8), + max_credential_id_length: Some(128), + transports: Some(vec!["usb".to_string()]), + algorithms: Some(vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::EDDSA, + }, + ]), + max_ser_large_blob_array: Some(1024), + force_pin_change: Some(false), + min_pin_length: Some(4), + firmware_version: Some(328966), + max_cred_blob_length: Some(32), + max_rpids_for_set_min_pin_length: Some(1), + preferred_platform_uv_attempts: Some(3), + uv_modality: Some(2), + certifications: None, + remaining_discoverable_credentials: Some(24), + vendor_prototype_config_commands: None, + }; + + assert_eq!(authenticator_info, expected); + } + + #[test] + fn test_get_info_ctap2_only() { + let mut device = Device::new("commands/get_info").unwrap(); + let nonce = [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![HIDCmd::Init.into(), 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![ + 0x06, /*HIDCmd::Init without TYPE_INIT*/ + 0x00, 0x11, + ]); // cmd + bcnt + msg.extend_from_slice(&nonce); + msg.extend_from_slice(&cid); // new channel id + + // We are setting NMSG, to signal that the device does not support CTAP1 + msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01 | 0x04 | 0x08]); // versions + flags (wink+cbor+nmsg) + device.add_read(&msg, 0); + + // ctap2 request + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt + msg.extend(vec![0x04]); // authenticatorGetInfo + device.add_write(&msg, 0); + + // ctap2 response + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x5A]); // cmd + bcnt + msg.extend(vec![0]); // Status code: Success + msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[0..(IN_HID_RPT_SIZE - 8)]); + device.add_read(&msg, 0); + // Continuation package + let mut msg = cid.to_vec(); + msg.extend(vec![0x00]); // SEQ + msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[(IN_HID_RPT_SIZE - 8)..]); + device.add_read(&msg, 0); + device + .init(Nonce::Use(nonce)) + .expect("Failed to init device"); + + assert_eq!(device.get_cid(), &cid); + + let dev_info = device.get_device_info(); + assert_eq!( + dev_info.cap_flags, + Capability::WINK | Capability::CBOR | Capability::NMSG + ); + + let result = device + .get_authenticator_info() + .expect("Didn't get any authenticator_info"); + let expected = AuthenticatorInfo { + versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()], + extensions: vec!["uvm".to_string(), "hmac-secret".to_string()], + aaguid: AAGuid(AAGUID_RAW), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(false), + user_presence: true, + user_verification: None, + }, + max_msg_size: Some(1200), + pin_protocols: vec![1], + max_credential_count_in_list: None, + max_credential_id_length: None, + transports: None, + algorithms: None, + max_ser_large_blob_array: None, + force_pin_change: None, + min_pin_length: None, + firmware_version: None, + max_cred_blob_length: None, + max_rpids_for_set_min_pin_length: None, + preferred_platform_uv_attempts: None, + uv_modality: None, + certifications: None, + remaining_discoverable_credentials: None, + vendor_prototype_config_commands: None, + }; + + assert_eq!(result, &expected); + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_next_assertion.rs b/third_party/rust/authenticator/src/ctap2/commands/get_next_assertion.rs new file mode 100644 index 000000000000..dd5b23edcd69 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/get_next_assertion.rs @@ -0,0 +1,53 @@ +use super::{Command, CommandError, RequestCtap2, StatusCode}; +use crate::ctap2::commands::get_assertion::GetAssertionResponse; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde_cbor::{de::from_slice, Value}; + +#[derive(Debug)] +pub(crate) struct GetNextAssertion; + +impl RequestCtap2 for GetNextAssertion { + type Output = GetAssertionResponse; + + fn command() -> Command { + Command::GetNextAssertion + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + Ok(Vec::new()) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + debug!("response status code: {:?}", status); + if input.len() > 1 { + if status.is_ok() { + let assertion = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + // TODO(baloo): check assertion response does not have numberOfCredentials + Ok(assertion) + } else { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Err(CommandError::StatusCode(status, Some(data)).into()) + } + } else if status.is_ok() { + Err(CommandError::InputTooSmall.into()) + } else { + Err(CommandError::StatusCode(status, None).into()) + } + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_version.rs b/third_party/rust/authenticator/src/ctap2/commands/get_version.rs new file mode 100644 index 000000000000..6f088da1bf03 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/get_version.rs @@ -0,0 +1,118 @@ +use super::{CommandError, RequestCtap1, Retryable}; +use crate::consts::U2F_VERSION; +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::u2ftypes::CTAP1RequestAPDU; +use crate::u2ftypes::U2FDevice; + +#[allow(non_camel_case_types)] +pub enum U2FInfo { + U2F_V2, +} + +#[derive(Debug)] +// TODO(baloo): if one does not issue U2F_VERSION before makecredentials or getassertion, token +// will return error (ConditionsNotSatified), test this in unit tests +pub struct GetVersion {} + +impl Default for GetVersion { + fn default() -> GetVersion { + GetVersion {} + } +} + +impl RequestCtap1 for GetVersion { + type Output = U2FInfo; + + fn handle_response_ctap1( + &self, + _status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result> { + if input.is_empty() { + return Err(Retryable::Error(HIDError::Command( + CommandError::InputTooSmall, + ))); + } + + let expected = String::from("U2F_V2"); + let result = String::from_utf8_lossy(input); + match result { + ref data if data == &expected => Ok(U2FInfo::U2F_V2), + _ => Err(Retryable::Error(HIDError::UnexpectedVersion)), + } + } + + fn ctap1_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + let flags = 0; + + let cmd = U2F_VERSION; + let data = CTAP1RequestAPDU::serialize(cmd, flags, &[])?; + Ok(data) + } +} + +#[cfg(test)] +pub mod tests { + use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR}; + use crate::transport::device_selector::Device; + use crate::transport::{hid::HIDDevice, FidoDevice, Nonce}; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + + #[test] + fn test_get_version_ctap1_only() { + let mut device = Device::new("commands/get_version").unwrap(); + let nonce = [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(&[HIDCmd::Init.into(), 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![ + 0x06, /*HIDCmd::Init without !TYPE_INIT*/ + 0x00, 0x11, + ]); // cmd + bcnt + msg.extend_from_slice(&nonce); + msg.extend_from_slice(&cid); // new channel id + + // We are not setting CBOR, to signal that the device does not support CTAP1 + msg.extend(&[0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags (wink) + device.add_read(&msg, 0); + + // ctap1 U2F_VERSION request + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x7]); // cmd + bcnt + msg.extend(&[0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x08]); // cmd + bcnt + msg.extend(&[0x55, 0x32, 0x46, 0x5f, 0x56, 0x32]); // 'U2F_V2' + msg.extend(&SW_NO_ERROR); + device.add_read(&msg, 0); + + device + .init(Nonce::Use(nonce)) + .expect("Failed to init device"); + + assert_eq!(device.get_cid(), &cid); + + let dev_info = device.get_device_info(); + assert_eq!(dev_info.cap_flags, Capability::WINK); + + let result = device.get_authenticator_info(); + assert!(result.is_none()); + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs b/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs new file mode 100644 index 000000000000..7f3fa0bf79b8 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs @@ -0,0 +1,1033 @@ +use super::{ + Command, CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable, + StatusCode, +}; +use crate::consts::{PARAMETER_SIZE, U2F_REGISTER, U2F_REQUEST_USER_PRESENCE}; +use crate::crypto::{ + parse_u2f_der_certificate, serialize_key, COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, + ECDSACurve, +}; +use crate::ctap2::attestation::{ + AAGuid, AttestationObject, AttestationStatement, AttestationStatementFidoU2F, + AttestedCredentialData, AuthenticatorData, AuthenticatorDataFlags, +}; +use crate::ctap2::client_data::{ + Challenge, ClientDataHash, CollectedClientData, CollectedClientDataWrapper, WebauthnType, +}; +use crate::ctap2::commands::client_pin::{Pin, PinAuth}; +use crate::ctap2::commands::get_assertion::CheckKeyHandle; +use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + RelyingPartyWrapper, User, +}; +use crate::transport::{ + errors::{ApduErrorStatus, HIDError}, + FidoDevice, +}; +use crate::u2ftypes::{CTAP1RequestAPDU, U2FDevice}; +use nom::{ + bytes::complete::{tag, take}, + error::VerboseError, + number::complete::be_u8, +}; +#[cfg(test)] +use serde::Deserialize; +use serde::{ + de::Error as DesError, + ser::{Error as SerError, SerializeMap}, + Serialize, Serializer, +}; +use serde_cbor::{self, de::from_slice, ser, Value}; +use std::fmt; +use std::io; + +#[derive(Debug)] +pub enum MakeCredentialsResult { + CTAP1(Vec), + CTAP2(AttestationObject, CollectedClientDataWrapper), +} + +#[derive(Copy, Clone, Debug, Serialize)] +#[cfg_attr(test, derive(Deserialize))] +pub struct MakeCredentialsOptions { + #[serde(rename = "rk", skip_serializing_if = "Option::is_none")] + pub resident_key: Option, + #[serde(rename = "uv", skip_serializing_if = "Option::is_none")] + pub user_verification: Option, + // TODO(MS): ctap2.1 supports user_presence, but ctap2.0 does not and tokens will error out + // Commands need a version-flag to know what to de/serialize and what to ignore. +} + +impl Default for MakeCredentialsOptions { + fn default() -> Self { + Self { + resident_key: None, + user_verification: None, + } + } +} + +impl MakeCredentialsOptions { + pub(crate) fn has_some(&self) -> bool { + self.resident_key.is_some() || self.user_verification.is_some() + } +} + +pub(crate) trait UserVerification { + fn ask_user_verification(&self) -> bool; +} + +impl UserVerification for MakeCredentialsOptions { + fn ask_user_verification(&self) -> bool { + if let Some(e) = self.user_verification { + e + } else { + false + } + } +} + +#[derive(Debug, Clone, Serialize, Default)] +pub struct MakeCredentialsExtensions { + #[serde(rename = "pinMinLength", skip_serializing_if = "Option::is_none")] + pub pin_min_length: Option, + #[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")] + pub hmac_secret: Option, +} + +impl MakeCredentialsExtensions { + fn has_extensions(&self) -> bool { + self.pin_min_length.or(self.hmac_secret).is_some() + } +} + +#[derive(Debug, Clone)] +pub struct MakeCredentials { + pub(crate) client_data_wrapper: CollectedClientDataWrapper, + pub(crate) rp: RelyingPartyWrapper, + // Note(baloo): If none -> ctap1 + pub(crate) user: Option, + pub(crate) pub_cred_params: Vec, + pub(crate) exclude_list: Vec, + + // https://www.w3.org/TR/webauthn/#client-extension-input + // The client extension input, which is a value that can be encoded in JSON, + // is passed from the WebAuthn Relying Party to the client in the get() or + // create() call, while the CBOR authenticator extension input is passed + // from the client to the authenticator for authenticator extensions during + // the processing of these calls. + pub(crate) extensions: MakeCredentialsExtensions, + pub(crate) options: MakeCredentialsOptions, + pub(crate) pin: Option, + pub(crate) pin_auth: Option, + pub(crate) pin_auth_protocol: Option, + pub(crate) enterprise_attestation: Option, +} + +impl MakeCredentials { + pub fn new( + client_data: CollectedClientData, + rp: RelyingPartyWrapper, + user: Option, + pub_cred_params: Vec, + exclude_list: Vec, + options: MakeCredentialsOptions, + extensions: MakeCredentialsExtensions, + pin: Option, + ) -> Result { + let client_data_wrapper = CollectedClientDataWrapper::new(client_data)?; + Ok(Self { + client_data_wrapper, + rp, + user, + pub_cred_params, + exclude_list, + extensions, + options, + pin, + pin_auth: None, + pin_auth_protocol: None, + enterprise_attestation: None, + }) + } +} + +impl PinAuthCommand for MakeCredentials { + fn pin(&self) -> &Option { + &self.pin + } + + fn set_pin(&mut self, pin: Option) { + self.pin = pin; + } + + fn pin_auth(&self) -> &Option { + &self.pin_auth + } + + fn set_pin_auth(&mut self, pin_auth: Option, pin_auth_protocol: Option) { + self.pin_auth = pin_auth; + self.pin_auth_protocol = pin_auth_protocol; + } + + fn client_data_hash(&self) -> ClientDataHash { + self.client_data_wrapper.hash() + } + + fn unset_uv_option(&mut self) { + self.options.user_verification = None; + } +} + +impl Serialize for MakeCredentials { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + debug!("Serialize MakeCredentials"); + // Need to define how many elements are going to be in the map + // beforehand + let mut map_len = 4; + if !self.exclude_list.is_empty() { + map_len += 1; + } + if self.extensions.has_extensions() { + map_len += 1; + } + if self.options.has_some() { + map_len += 1; + } + if self.pin_auth.is_some() { + map_len += 1; + } + if self.pin_auth_protocol.is_some() { + map_len += 1; + } + if self.enterprise_attestation.is_some() { + map_len += 1; + } + + let mut map = serializer.serialize_map(Some(map_len))?; + let client_data_hash = self.client_data_hash(); + map.serialize_entry(&0x01, &client_data_hash)?; + match self.rp { + RelyingPartyWrapper::Data(ref d) => { + map.serialize_entry(&0x02, &d)?; + } + _ => { + return Err(S::Error::custom( + "Can't serialize a RelyingParty::Hash for CTAP2", + )); + } + } + map.serialize_entry(&0x03, &self.user)?; + map.serialize_entry(&0x04, &self.pub_cred_params)?; + if !self.exclude_list.is_empty() { + map.serialize_entry(&0x05, &self.exclude_list)?; + } + if self.extensions.has_extensions() { + map.serialize_entry(&0x06, &self.extensions)?; + } + if self.options.has_some() { + map.serialize_entry(&0x07, &self.options)?; + } + if let Some(pin_auth) = &self.pin_auth { + map.serialize_entry(&0x08, &pin_auth)?; + } + if let Some(pin_auth_protocol) = &self.pin_auth_protocol { + map.serialize_entry(&0x09, &pin_auth_protocol)?; + } + if let Some(enterprise_attestation) = self.enterprise_attestation { + map.serialize_entry(&0x0a, &enterprise_attestation)?; + } + map.end() + } +} + +impl Request for MakeCredentials { + fn is_ctap2_request(&self) -> bool { + self.user.is_some() + } +} + +impl RequestCtap1 for MakeCredentials { + type Output = MakeCredentialsResult; + + fn ctap1_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: io::Read + io::Write + fmt::Debug + FidoDevice, + { + // TODO(MS): Mandatory sanity checks are missing: + // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-authenticatorMakeCredential-interoperability + // If any of the below conditions is not true, platform errors out with CTAP2_ERR_UNSUPPORTED_OPTION. + // * pubKeyCredParams must use the ES256 algorithm (-7). + // * Options must not include "rk" set to true. + // * Options must not include "uv" set to true. + + let is_already_registered = self + .exclude_list + .iter() + // key-handles in CTAP1 are limited to 255 bytes, but are not limited in CTAP2. + // Filter out key-handles that are too long (can happen if this is a CTAP2-request, + // but the token only speaks CTAP1). If none is found, return an error. + .filter(|exclude_handle| exclude_handle.id.len() < 256) + .map(|exclude_handle| { + let check_command = CheckKeyHandle { + key_handle: exclude_handle.id.as_ref(), + client_data_wrapper: &self.client_data_wrapper, + rp: &self.rp, + }; + let res = dev.send_ctap1(&check_command); + res.is_ok() + }) + .any(|x| x == true); + + if is_already_registered { + // Now we need to send a dummy registration request, to make the token blink + // Spec says "dummy appid and invalid challenge". We use the same, as we do for + // making the token blink upon device selection. + let msg = dummy_make_credentials_cmd()?; + let _ = dev.send_ctap1(&msg); // Ignore answer, return "CrednetialExcluded" + return Err(HIDError::Command(CommandError::StatusCode( + StatusCode::CredentialExcluded, + None, + ))); + } + + let flags = if self.options.ask_user_verification() { + U2F_REQUEST_USER_PRESENCE + } else { + 0 + }; + + let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE); + if self.is_ctap2_request() { + register_data.extend_from_slice(self.client_data_hash().as_ref()); + } else { + let decoded = base64::decode_config( + &self.client_data_wrapper.client_data.challenge.0, + base64::URL_SAFE_NO_PAD, + ) + .map_err(|_| HIDError::DeviceError)?; // We encoded it, so this should never fail + register_data.extend_from_slice(&decoded); + } + register_data.extend_from_slice(self.rp.hash().as_ref()); + let cmd = U2F_REGISTER; + let apdu = CTAP1RequestAPDU::serialize(cmd, flags, ®ister_data)?; + + Ok(apdu) + } + + fn handle_response_ctap1( + &self, + status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result> { + if Err(ApduErrorStatus::ConditionsNotSatisfied) == status { + return Err(Retryable::Retry); + } + if let Err(err) = status { + return Err(Retryable::Error(HIDError::ApduStatus(err))); + } + + if self.is_ctap2_request() { + let parse_register = |input| { + let (rest, _) = tag(&[0x05])(input)?; + let (rest, public_key) = take(65u8)(rest)?; + let (rest, key_handle_len) = be_u8(rest)?; + let (rest, key_handle) = take(key_handle_len)(rest)?; + Ok((rest, public_key, key_handle)) + }; + let (rest, public_key, key_handle) = parse_register(input) + .map_err(|e: nom::Err>| { + error!("error while parsing registration: {:?}", e); + CommandError::Deserializing(DesError::custom("unable to parse registration")) + }) + .map_err(HIDError::Command) + .map_err(Retryable::Error)?; + + let cert_and_sig = parse_u2f_der_certificate(rest) + .map_err(|e| HIDError::Command(CommandError::Crypto(e))) + .map_err(Retryable::Error)?; + + let (x, y) = serialize_key(ECDSACurve::SECP256R1, public_key) + .map_err(|e| HIDError::Command(CommandError::Crypto(e.into()))) + .map_err(Retryable::Error)?; + let credential_public_key = COSEKey { + alg: COSEAlgorithm::ES256, + key: COSEKeyType::EC2(COSEEC2Key { + curve: ECDSACurve::SECP256R1, + x: x.to_vec(), + y: y.to_vec(), + }), + }; + let auth_data = AuthenticatorData { + rp_id_hash: self.rp.hash(), + // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-authenticatorMakeCredential-interoperability + // "Let flags be a byte whose zeroth bit (bit 0, UP) is set, and whose sixth bit + // (bit 6, AT) is set, and all other bits are zero (bit zero is the least significant bit)" + flags: AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::ATTESTED, + counter: 0, + credential_data: Some(AttestedCredentialData { + aaguid: AAGuid::default(), + credential_id: Vec::from(key_handle), + credential_public_key, + }), + extensions: Default::default(), + }; + + let att_statement = AttestationStatement::FidoU2F(AttestationStatementFidoU2F::new( + cert_and_sig.certificate, + cert_and_sig.signature, + )); + + let attestation_object = AttestationObject { + auth_data, + att_statement, + }; + let client_data = self.client_data_wrapper.clone(); + + Ok(MakeCredentialsResult::CTAP2( + attestation_object, + client_data, + )) + } else { + Ok(MakeCredentialsResult::CTAP1(input.to_vec())) + } + } +} + +impl RequestCtap2 for MakeCredentials { + type Output = MakeCredentialsResult; + + fn command() -> Command { + Command::MakeCredentials + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice + io::Read + io::Write + fmt::Debug, + { + Ok(ser::to_vec(&self).map_err(CommandError::Serializing)?) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice + io::Read + io::Write + fmt::Debug, + { + if input.is_empty() { + return Err(HIDError::Command(CommandError::InputTooSmall)); + } + + let status: StatusCode = input[0].into(); + debug!("response status code: {:?}", status); + if input.len() > 1 { + if status.is_ok() { + let attestation = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + let client_data = self.client_data_wrapper.clone(); + Ok(MakeCredentialsResult::CTAP2(attestation, client_data)) + } else { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Err(HIDError::Command(CommandError::StatusCode( + status, + Some(data), + ))) + } + } else if status.is_ok() { + Err(HIDError::Command(CommandError::InputTooSmall)) + } else { + Err(HIDError::Command(CommandError::StatusCode(status, None))) + } + } +} + +pub(crate) fn dummy_make_credentials_cmd() -> Result { + MakeCredentials::new( + CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::new(vec![0, 1, 2, 3, 4]), + origin: String::new(), + cross_origin: false, + token_binding: None, + }, + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("make.me.blink"), + ..Default::default() + }), + Some(User { + id: vec![0], + name: Some(String::from("make.me.blink")), + ..Default::default() + }), + vec![PublicKeyCredentialParameters { + alg: crate::COSEAlgorithm::ES256, + }], + vec![], + MakeCredentialsOptions::default(), + MakeCredentialsExtensions::default(), + None, + ) +} + +#[cfg(test)] +pub mod test { + use super::{MakeCredentials, MakeCredentialsOptions, MakeCredentialsResult}; + use crate::crypto::{COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, ECDSACurve}; + use crate::ctap2::attestation::{ + AAGuid, AttestationCertificate, AttestationObject, AttestationStatement, + AttestationStatementFidoU2F, AttestationStatementPacked, AttestedCredentialData, + AuthenticatorData, AuthenticatorDataFlags, Signature, + }; + use crate::ctap2::client_data::{Challenge, CollectedClientData, TokenBinding, WebauthnType}; + use crate::ctap2::commands::{RequestCtap1, RequestCtap2}; + use crate::ctap2::server::RpIdHash; + use crate::ctap2::server::{ + PublicKeyCredentialParameters, RelyingParty, RelyingPartyWrapper, User, + }; + use crate::transport::device_selector::Device; + use crate::transport::hid::HIDDevice; + use serde_bytes::ByteBuf; + + #[test] + fn test_make_credentials_ctap2() { + let req = MakeCredentials::new( + CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::from(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present(String::from("AAECAw"))), + }, + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("example.com"), + name: Some(String::from("Acme")), + icon: None, + }), + Some(User { + id: base64::decode_config( + "MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(), + icon: Some("https://pics.example.com/00/p/aBjjjpqPb.png".to_string()), + name: Some(String::from("johnpsmith@example.com")), + display_name: Some(String::from("John P. Smith")), + }), + vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + Vec::new(), + MakeCredentialsOptions { + resident_key: Some(true), + user_verification: None, + }, + Default::default(), + None, + ) + .expect("Failed to create MakeCredentials"); + + let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it) + let req_serialized = req + .wire_format(&mut device) + .expect("Failed to serialize MakeCredentials request"); + assert_eq!(req_serialized, MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP2); + let (attestation_object, _collected_client_data) = match req + .handle_response_ctap2(&mut device, &MAKE_CREDENTIALS_SAMPLE_RESPONSE_CTAP2) + .expect("Failed to handle CTAP2 response") + { + MakeCredentialsResult::CTAP2(attestation_object, _collected_client_data) => { + (attestation_object, _collected_client_data) + } + _ => panic!("Got CTAP1 Result, but CTAP2 expected"), + }; + + let expected = AttestationObject { + auth_data: AuthenticatorData { + rp_id_hash: RpIdHash::from(&[ + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, + 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, + 0xbe, 0x59, 0x7a, 0x87, 0x5, 0x1d, + ]) + .unwrap(), + flags: AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::ATTESTED, + counter: 11, + credential_data: Some(AttestedCredentialData { + aaguid: AAGuid::from(&[ + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, + 0x1f, 0x9e, 0xdc, 0x7d, + ]) + .unwrap(), + credential_id: vec![ + 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, + 0xd9, 0x43, 0x5c, 0x6f, + ], + credential_public_key: COSEKey { + alg: COSEAlgorithm::ES256, + key: COSEKeyType::EC2(COSEEC2Key { + curve: ECDSACurve::SECP256R1, + x: vec![ + 0xA5, 0xFD, 0x5C, 0xE1, 0xB1, 0xC4, 0x58, 0xC5, 0x30, 0xA5, 0x4F, + 0xA6, 0x1B, 0x31, 0xBF, 0x6B, 0x04, 0xBE, 0x8B, 0x97, 0xAF, 0xDE, + 0x54, 0xDD, 0x8C, 0xBB, 0x69, 0x27, 0x5A, 0x8A, 0x1B, 0xE1, + ], + y: vec![ + 0xFA, 0x3A, 0x32, 0x31, 0xDD, 0x9D, 0xEE, 0xD9, 0xD1, 0x89, 0x7B, + 0xE5, 0xA6, 0x22, 0x8C, 0x59, 0x50, 0x1E, 0x4B, 0xCD, 0x12, 0x97, + 0x5D, 0x3D, 0xFF, 0x73, 0x0F, 0x01, 0x27, 0x8E, 0xA6, 0x1C, + ], + }), + }, + }), + extensions: Default::default(), + }, + att_statement: AttestationStatement::Packed(AttestationStatementPacked { + alg: COSEAlgorithm::ES256, + sig: Signature(ByteBuf::from([ + 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, 0xc1, + 0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, 0x62, 0xd5, + 0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, 0x02, 0x21, 0x00, + 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, 0xb5, 0x96, 0x51, 0x19, + 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, 0x99, 0x59, 0x94, 0x80, 0x78, + 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29, + ])), + attestation_cert: vec![AttestationCertificate(vec![ + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x47, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62, + 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32, + 0x30, 0x34, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36, + 0x31, 0x32, 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, + 0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, + 0xe5, 0x3a, 0xd5, 0xdf, 0xed, 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, + 0xe1, 0xaf, 0x8f, 0x22, 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, + 0xc3, 0xd5, 0x04, 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, + 0xc4, 0x4c, 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, + 0x60, 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, 0x46, 0x02, + 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, 0xf7, 0x37, 0x3e, + 0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, 0xd0, 0xc0, 0x3f, 0x3f, + 0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, 0x02, 0x21, 0x00, 0xc4, 0xfa, + 0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, + 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, + 0xa2, 0x37, 0x23, 0xf3, + ])], + }), + }; + + assert_eq!(attestation_object, expected); + } + + #[test] + fn test_make_credentials_ctap1() { + let req = MakeCredentials::new( + CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]), + origin: String::from("example.com"), + cross_origin: false, + token_binding: Some(TokenBinding::Present(String::from("AAECAw"))), + }, + RelyingPartyWrapper::Data(RelyingParty { + id: String::from("example.com"), + name: Some(String::from("Acme")), + icon: None, + }), + Some(User { + id: base64::decode_config( + "MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(), + icon: Some("https://pics.example.com/00/p/aBjjjpqPb.png".to_string()), + name: Some(String::from("johnpsmith@example.com")), + display_name: Some(String::from("John P. Smith")), + }), + vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + Vec::new(), + MakeCredentialsOptions { + resident_key: Some(true), + user_verification: None, + }, + Default::default(), + None, + ) + .expect("Failed to create MakeCredentials"); + + let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it) + let req_serialized = req + .ctap1_format(&mut device) + .expect("Failed to serialize MakeCredentials request"); + assert_eq!( + req_serialized, MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP1, + "\nGot: {:X?}\nExpected: {:X?}", + req_serialized, MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP1 + ); + let (attestation_object, _collected_client_data) = match req + .handle_response_ctap1(Ok(()), &MAKE_CREDENTIALS_SAMPLE_RESPONSE_CTAP1) + .expect("Failed to handle CTAP1 response") + { + MakeCredentialsResult::CTAP2(attestation_object, _collected_client_data) => { + (attestation_object, _collected_client_data) + } + _ => panic!("Got CTAP1 Result, but CTAP2 expected"), + }; + + let expected = AttestationObject { + auth_data: AuthenticatorData { + rp_id_hash: RpIdHash::from(&[ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, + ]) + .unwrap(), + flags: AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::ATTESTED, + counter: 0, + credential_data: Some(AttestedCredentialData { + aaguid: AAGuid::default(), + credential_id: vec![ + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, + 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, + 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, + 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, + 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, + ], + credential_public_key: COSEKey { + alg: COSEAlgorithm::ES256, + key: COSEKeyType::EC2(COSEEC2Key { + curve: ECDSACurve::SECP256R1, + x: vec![ + 0xE8, 0x76, 0x25, 0x89, 0x6E, 0xE4, 0xE4, 0x6D, 0xC0, 0x32, 0x76, + 0x6E, 0x80, 0x87, 0x96, 0x2F, 0x36, 0xDF, 0x9D, 0xFE, 0x8B, 0x56, + 0x7F, 0x37, 0x63, 0x01, 0x5B, 0x19, 0x90, 0xA6, 0x0E, 0x14, + ], + y: vec![ + 0x27, 0xDE, 0x61, 0x2D, 0x66, 0x41, 0x8B, 0xDA, 0x19, 0x50, 0x58, + 0x1E, 0xBC, 0x5C, 0x8C, 0x1D, 0xAD, 0x71, 0x0C, 0xB1, 0x4C, 0x22, + 0xF8, 0xC9, 0x70, 0x45, 0xF4, 0x61, 0x2F, 0xB2, 0x0C, 0x91, + ], + }), + }, + }), + extensions: Default::default(), + }, + att_statement: AttestationStatement::FidoU2F(AttestationStatementFidoU2F { + sig: Signature(ByteBuf::from([ + 0x30, 0x45, 0x02, 0x20, 0x32, 0x47, 0x79, 0xC6, 0x8F, 0x33, 0x80, 0x28, 0x8A, + 0x11, 0x97, 0xB6, 0x09, 0x5F, 0x7A, 0x6E, 0xB9, 0xB1, 0xB1, 0xC1, 0x27, 0xF6, + 0x6A, 0xE1, 0x2A, 0x99, 0xFE, 0x85, 0x32, 0xEC, 0x23, 0xB9, 0x02, 0x21, 0x00, + 0xE3, 0x95, 0x16, 0xAC, 0x4D, 0x61, 0xEE, 0x64, 0x04, 0x4D, 0x50, 0xB4, 0x15, + 0xA6, 0xA4, 0xD4, 0xD8, 0x4B, 0xA6, 0xD8, 0x95, 0xCB, 0x5A, 0xB7, 0xA1, 0xAA, + 0x7D, 0x08, 0x1D, 0xE3, 0x41, 0xFA, + ])), + attestation_cert: vec![AttestationCertificate(vec![ + 0x30, 0x82, 0x02, 0x4A, 0x30, 0x82, 0x01, 0x32, 0xA0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x04, 0x04, 0x6C, 0x88, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, + 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x30, 0x2E, 0x31, 0x2C, 0x30, + 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, + 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, + 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0D, 0x31, 0x34, 0x30, 0x38, 0x30, + 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, 0x0F, 0x32, 0x30, 0x35, + 0x30, 0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, + 0x2C, 0x31, 0x2A, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x21, 0x59, + 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x34, 0x39, 0x31, 0x38, 0x32, + 0x33, 0x32, 0x34, 0x37, 0x37, 0x30, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, + 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, + 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x3C, 0xCA, 0xB9, 0x2C, 0xCB, 0x97, + 0x28, 0x7E, 0xE8, 0xE6, 0x39, 0x43, 0x7E, 0x21, 0xFC, 0xD6, 0xB6, 0xF1, 0x65, + 0xB2, 0xD5, 0xA3, 0xF3, 0xDB, 0x13, 0x1D, 0x31, 0xC1, 0x6B, 0x74, 0x2B, 0xB4, + 0x76, 0xD8, 0xD1, 0xE9, 0x90, 0x80, 0xEB, 0x54, 0x6C, 0x9B, 0xBD, 0xF5, 0x56, + 0xE6, 0x21, 0x0F, 0xD4, 0x27, 0x85, 0x89, 0x9E, 0x78, 0xCC, 0x58, 0x9E, 0xBE, + 0x31, 0x0F, 0x6C, 0xDB, 0x9F, 0xF4, 0xA3, 0x3B, 0x30, 0x39, 0x30, 0x22, 0x06, + 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xC4, 0x0A, 0x02, 0x04, 0x15, 0x31, + 0x2E, 0x33, 0x2E, 0x36, 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x34, 0x31, + 0x34, 0x38, 0x32, 0x2E, 0x31, 0x2E, 0x32, 0x30, 0x13, 0x06, 0x0B, 0x2B, 0x06, + 0x01, 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, + 0x04, 0x30, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x01, 0x0B, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x9F, 0x9B, 0x05, 0x22, + 0x48, 0xBC, 0x4C, 0xF4, 0x2C, 0xC5, 0x99, 0x1F, 0xCA, 0xAB, 0xAC, 0x9B, 0x65, + 0x1B, 0xBE, 0x5B, 0xDC, 0xDC, 0x8E, 0xF0, 0xAD, 0x2C, 0x1C, 0x1F, 0xFB, 0x36, + 0xD1, 0x87, 0x15, 0xD4, 0x2E, 0x78, 0xB2, 0x49, 0x22, 0x4F, 0x92, 0xC7, 0xE6, + 0xE7, 0xA0, 0x5C, 0x49, 0xF0, 0xE7, 0xE4, 0xC8, 0x81, 0xBF, 0x2E, 0x94, 0xF4, + 0x5E, 0x4A, 0x21, 0x83, 0x3D, 0x74, 0x56, 0x85, 0x1D, 0x0F, 0x6C, 0x14, 0x5A, + 0x29, 0x54, 0x0C, 0x87, 0x4F, 0x30, 0x92, 0xC9, 0x34, 0xB4, 0x3D, 0x22, 0x2B, + 0x89, 0x62, 0xC0, 0xF4, 0x10, 0xCE, 0xF1, 0xDB, 0x75, 0x89, 0x2A, 0xF1, 0x16, + 0xB4, 0x4A, 0x96, 0xF5, 0xD3, 0x5A, 0xDE, 0xA3, 0x82, 0x2F, 0xC7, 0x14, 0x6F, + 0x60, 0x04, 0x38, 0x5B, 0xCB, 0x69, 0xB6, 0x5C, 0x99, 0xE7, 0xEB, 0x69, 0x19, + 0x78, 0x67, 0x03, 0xC0, 0xD8, 0xCD, 0x41, 0xE8, 0xF7, 0x5C, 0xCA, 0x44, 0xAA, + 0x8A, 0xB7, 0x25, 0xAD, 0x8E, 0x79, 0x9F, 0xF3, 0xA8, 0x69, 0x6A, 0x6F, 0x1B, + 0x26, 0x56, 0xE6, 0x31, 0xB1, 0xE4, 0x01, 0x83, 0xC0, 0x8F, 0xDA, 0x53, 0xFA, + 0x4A, 0x8F, 0x85, 0xA0, 0x56, 0x93, 0x94, 0x4A, 0xE1, 0x79, 0xA1, 0x33, 0x9D, + 0x00, 0x2D, 0x15, 0xCA, 0xBD, 0x81, 0x00, 0x90, 0xEC, 0x72, 0x2E, 0xF5, 0xDE, + 0xF9, 0x96, 0x5A, 0x37, 0x1D, 0x41, 0x5D, 0x62, 0x4B, 0x68, 0xA2, 0x70, 0x7C, + 0xAD, 0x97, 0xBC, 0xDD, 0x17, 0x85, 0xAF, 0x97, 0xE2, 0x58, 0xF3, 0x3D, 0xF5, + 0x6A, 0x03, 0x1A, 0xA0, 0x35, 0x6D, 0x8E, 0x8D, 0x5E, 0xBC, 0xAD, 0xC7, 0x4E, + 0x07, 0x16, 0x36, 0xC6, 0xB1, 0x10, 0xAC, 0xE5, 0xCC, 0x9B, 0x90, 0xDF, 0xEA, + 0xCA, 0xE6, 0x40, 0xFF, 0x1B, 0xB0, 0xF1, 0xFE, 0x5D, 0xB4, 0xEF, 0xF7, 0xA9, + 0x5F, 0x06, 0x07, 0x33, 0xF5, + ])], + }), + }; + + assert_eq!(attestation_object, expected); + } + + #[rustfmt::skip] + pub const MAKE_CREDENTIALS_SAMPLE_RESPONSE_CTAP2: [u8; 660] = [ + 0x00, // status = success + 0xa3, // map(3) + 0x01, // unsigned(1) + 0x66, // text(6) + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, // "packed" + 0x02, // unsigned(2) + 0x58, 0x94, // bytes(148) + // authData + 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, 0x27, // rp_id_hash + 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, // rp_id_hash + 0x05, 0x1d, // rp_id_hash + 0x41, // authData Flags + 0x00, 0x00, 0x00, 0x0b, // authData counter + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, // AAGUID + 0x00, 0x10, // credential id length + 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, // credential id + // credential public key + 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, 0xc4, + 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde, + 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, + 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b, + 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, + 0x03, // unsigned(3) + 0xa3, // map(3) + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + 0x26, // -7 (ES256) + 0x63, // text(3) + 0x73, 0x69, 0x67, // "sig" + 0x58, 0x47, // bytes(71) + 0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, 0xc1, 0x5c, 0xc9, // signature + 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, 0x62, 0xd5, 0xf0, 0x56, 0x12, 0x35, // .. + 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, // .. + 0xb5, 0x6a, 0xc5, 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, // .. + 0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29, // .. + 0x63, // text(3) + 0x78, 0x35, 0x63, // "x5c" + 0x81, // array(1) + 0x59, 0x01, 0x97, // bytes(407) + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, //certificate... + 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, + 0x4c, 0x29, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, + 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, + 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, + 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x1e, 0x17, + 0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, + 0x0d, 0x32, 0x36, 0x31, 0x32, 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, + 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63, + 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, + 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, 0x30, + 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52, + 0xe5, 0x3a, 0xd5, 0xdf, 0xed, 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf, + 0x8f, 0x22, 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, 0xff, + 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, 0xb4, 0x84, 0x99, 0x79, + 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, + 0x30, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, 0x46, + 0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, 0xf7, 0x37, 0x3e, 0x10, + 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2, + 0x2d, 0xb3, 0xd0, 0x30, 0xe7, 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20, + 0xcf, 0x43, 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, 0xc5, + 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3, + ]; + + #[rustfmt::skip] + pub const MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP2: [u8; 260] = [ + // NOTE: This has been taken from CTAP2.0 spec, but the clientDataHash has been replaced + // to be able to operate with known values for CollectedClientData (spec doesn't say + // what values led to the provided example hash (see client_data.rs)) + 0xa5, // map(5) + 0x01, // unsigned(1) - clientDataHash + 0x58, 0x20, // bytes(32) + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, // hash + 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, // hash + 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80, // hash + 0x02, // unsigned(2) - rp + 0xa2, // map(2) Replace line below with this one, once RelyingParty supports "name" + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x6b, // text(11) + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, // "example.com" + 0x64, // text(4) + 0x6e, 0x61, 0x6d, 0x65, // "name" + 0x64, // text(4) + 0x41, 0x63, 0x6d, 0x65, // "Acme" + 0x03, // unsigned(3) - user + 0xa4, // map(4) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x20, // bytes(32) + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, // userid + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, // ... + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, // ... + 0x64, // text(4) + 0x69, 0x63, 0x6f, 0x6e, // "icon" + 0x78, 0x2b, // text(43) + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, // "https://pics.example.com/00/p/aBjjjpqPb.png" + 0x2f, 0x70, 0x69, 0x63, 0x73, 0x2e, 0x65, 0x78, // .. + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x30, 0x2f, 0x70, // .. + 0x2f, 0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71, 0x50, 0x62, 0x2e, 0x70, 0x6e, 0x67, // .. + 0x64, // text(4) + 0x6e, 0x61, 0x6d, 0x65, // "name" + 0x76, // text(22) + 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, // "johnpsmith@example.com" + 0x68, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, // ... + 0x6b, // text(11) + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, // "displayName" + 0x6d, // text(13) + 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, 0x69, 0x74, 0x68, // "John P. Smith" + 0x04, // unsigned(4) - pubKeyCredParams + 0x82, // array(2) + 0xa2, // map(2) + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + 0x26, // -7 (ES256) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key" + 0xa2, // map(2) + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + 0x39, 0x01, 0x00, // -257 (RS256) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key" + // TODO(MS): Options seem to be parsed differently than in the example here. + 0x07, // unsigned(7) - options + 0xa1, // map(1) + 0x62, // text(2) + 0x72, 0x6b, // "rk" + 0xf5, // primitive(21) + ]; + + pub const MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP1: [u8; 73] = [ + // CBOR Header + 0x0, // CLA + 0x1, // INS U2F_Register + 0x0, // P1 Flags + 0x0, // P2 + 0x0, 0x0, 0x40, // Lc + // NOTE: This has been taken from CTAP2.0 spec, but the clientDataHash has been replaced + // to be able to operate with known values for CollectedClientData (spec doesn't say + // what values led to the provided example hash) + // clientDataHash: + 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11, // hash + 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f, // hash + 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80, // hash + // rpIdHash: + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, 0x34, 0xE2, + 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x12, 0x55, 0x86, 0xCE, + 0x19, 0x47, // .. + // Le (Ne=65536): + 0x0, 0x0, + ]; + + pub const MAKE_CREDENTIALS_SAMPLE_RESPONSE_CTAP1: [u8; 792] = [ + 0x05, // Reserved Byte (1 Byte) + // User Public Key (65 Bytes) + 0x04, 0xE8, 0x76, 0x25, 0x89, 0x6E, 0xE4, 0xE4, 0x6D, 0xC0, 0x32, 0x76, 0x6E, 0x80, 0x87, + 0x96, 0x2F, 0x36, 0xDF, 0x9D, 0xFE, 0x8B, 0x56, 0x7F, 0x37, 0x63, 0x01, 0x5B, 0x19, 0x90, + 0xA6, 0x0E, 0x14, 0x27, 0xDE, 0x61, 0x2D, 0x66, 0x41, 0x8B, 0xDA, 0x19, 0x50, 0x58, 0x1E, + 0xBC, 0x5C, 0x8C, 0x1D, 0xAD, 0x71, 0x0C, 0xB1, 0x4C, 0x22, 0xF8, 0xC9, 0x70, 0x45, 0xF4, + 0x61, 0x2F, 0xB2, 0x0C, 0x91, // ... + 0x40, // Key Handle Length (1 Byte) + // Key Handle (Key Handle Length Bytes) + 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, 0x35, 0xEF, 0xAA, + 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, + 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, + 0x39, 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, + 0xFE, 0x42, 0x00, 0x38, // ... + // X.509 Cert (Variable length Cert) + 0x30, 0x82, 0x02, 0x4A, 0x30, 0x82, 0x01, 0x32, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, + 0x04, 0x6C, 0x88, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x01, 0x0B, 0x05, 0x00, 0x30, 0x2E, 0x31, 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6F, + 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x34, 0x35, + 0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0D, 0x31, 0x34, 0x30, 0x38, + 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, 0x0F, 0x32, 0x30, 0x35, 0x30, + 0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2C, 0x31, 0x2A, + 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x21, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, + 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, + 0x32, 0x34, 0x39, 0x31, 0x38, 0x32, 0x33, 0x32, 0x34, 0x37, 0x37, 0x30, 0x30, 0x59, 0x30, + 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, + 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x3C, 0xCA, 0xB9, 0x2C, 0xCB, 0x97, + 0x28, 0x7E, 0xE8, 0xE6, 0x39, 0x43, 0x7E, 0x21, 0xFC, 0xD6, 0xB6, 0xF1, 0x65, 0xB2, 0xD5, + 0xA3, 0xF3, 0xDB, 0x13, 0x1D, 0x31, 0xC1, 0x6B, 0x74, 0x2B, 0xB4, 0x76, 0xD8, 0xD1, 0xE9, + 0x90, 0x80, 0xEB, 0x54, 0x6C, 0x9B, 0xBD, 0xF5, 0x56, 0xE6, 0x21, 0x0F, 0xD4, 0x27, 0x85, + 0x89, 0x9E, 0x78, 0xCC, 0x58, 0x9E, 0xBE, 0x31, 0x0F, 0x6C, 0xDB, 0x9F, 0xF4, 0xA3, 0x3B, + 0x30, 0x39, 0x30, 0x22, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xC4, 0x0A, 0x02, + 0x04, 0x15, 0x31, 0x2E, 0x33, 0x2E, 0x36, 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x34, + 0x31, 0x34, 0x38, 0x32, 0x2E, 0x31, 0x2E, 0x32, 0x30, 0x13, 0x06, 0x0B, 0x2B, 0x06, 0x01, + 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x04, 0x30, 0x30, + 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x01, 0x00, 0x9F, 0x9B, 0x05, 0x22, 0x48, 0xBC, 0x4C, 0xF4, 0x2C, 0xC5, 0x99, + 0x1F, 0xCA, 0xAB, 0xAC, 0x9B, 0x65, 0x1B, 0xBE, 0x5B, 0xDC, 0xDC, 0x8E, 0xF0, 0xAD, 0x2C, + 0x1C, 0x1F, 0xFB, 0x36, 0xD1, 0x87, 0x15, 0xD4, 0x2E, 0x78, 0xB2, 0x49, 0x22, 0x4F, 0x92, + 0xC7, 0xE6, 0xE7, 0xA0, 0x5C, 0x49, 0xF0, 0xE7, 0xE4, 0xC8, 0x81, 0xBF, 0x2E, 0x94, 0xF4, + 0x5E, 0x4A, 0x21, 0x83, 0x3D, 0x74, 0x56, 0x85, 0x1D, 0x0F, 0x6C, 0x14, 0x5A, 0x29, 0x54, + 0x0C, 0x87, 0x4F, 0x30, 0x92, 0xC9, 0x34, 0xB4, 0x3D, 0x22, 0x2B, 0x89, 0x62, 0xC0, 0xF4, + 0x10, 0xCE, 0xF1, 0xDB, 0x75, 0x89, 0x2A, 0xF1, 0x16, 0xB4, 0x4A, 0x96, 0xF5, 0xD3, 0x5A, + 0xDE, 0xA3, 0x82, 0x2F, 0xC7, 0x14, 0x6F, 0x60, 0x04, 0x38, 0x5B, 0xCB, 0x69, 0xB6, 0x5C, + 0x99, 0xE7, 0xEB, 0x69, 0x19, 0x78, 0x67, 0x03, 0xC0, 0xD8, 0xCD, 0x41, 0xE8, 0xF7, 0x5C, + 0xCA, 0x44, 0xAA, 0x8A, 0xB7, 0x25, 0xAD, 0x8E, 0x79, 0x9F, 0xF3, 0xA8, 0x69, 0x6A, 0x6F, + 0x1B, 0x26, 0x56, 0xE6, 0x31, 0xB1, 0xE4, 0x01, 0x83, 0xC0, 0x8F, 0xDA, 0x53, 0xFA, 0x4A, + 0x8F, 0x85, 0xA0, 0x56, 0x93, 0x94, 0x4A, 0xE1, 0x79, 0xA1, 0x33, 0x9D, 0x00, 0x2D, 0x15, + 0xCA, 0xBD, 0x81, 0x00, 0x90, 0xEC, 0x72, 0x2E, 0xF5, 0xDE, 0xF9, 0x96, 0x5A, 0x37, 0x1D, + 0x41, 0x5D, 0x62, 0x4B, 0x68, 0xA2, 0x70, 0x7C, 0xAD, 0x97, 0xBC, 0xDD, 0x17, 0x85, 0xAF, + 0x97, 0xE2, 0x58, 0xF3, 0x3D, 0xF5, 0x6A, 0x03, 0x1A, 0xA0, 0x35, 0x6D, 0x8E, 0x8D, 0x5E, + 0xBC, 0xAD, 0xC7, 0x4E, 0x07, 0x16, 0x36, 0xC6, 0xB1, 0x10, 0xAC, 0xE5, 0xCC, 0x9B, 0x90, + 0xDF, 0xEA, 0xCA, 0xE6, 0x40, 0xFF, 0x1B, 0xB0, 0xF1, 0xFE, 0x5D, 0xB4, 0xEF, 0xF7, 0xA9, + 0x5F, 0x06, 0x07, 0x33, 0xF5, // ... + // Signature (variable Length) + 0x30, 0x45, 0x02, 0x20, 0x32, 0x47, 0x79, 0xC6, 0x8F, 0x33, 0x80, 0x28, 0x8A, 0x11, 0x97, + 0xB6, 0x09, 0x5F, 0x7A, 0x6E, 0xB9, 0xB1, 0xB1, 0xC1, 0x27, 0xF6, 0x6A, 0xE1, 0x2A, 0x99, + 0xFE, 0x85, 0x32, 0xEC, 0x23, 0xB9, 0x02, 0x21, 0x00, 0xE3, 0x95, 0x16, 0xAC, 0x4D, 0x61, + 0xEE, 0x64, 0x04, 0x4D, 0x50, 0xB4, 0x15, 0xA6, 0xA4, 0xD4, 0xD8, 0x4B, 0xA6, 0xD8, 0x95, + 0xCB, 0x5A, 0xB7, 0xA1, 0xAA, 0x7D, 0x08, 0x1D, 0xE3, 0x41, 0xFA, // ... + ]; +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/mod.rs b/third_party/rust/authenticator/src/ctap2/commands/mod.rs new file mode 100644 index 000000000000..a0cd426991a8 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/mod.rs @@ -0,0 +1,461 @@ +use crate::crypto; +use crate::ctap2::client_data::ClientDataHash; +use crate::ctap2::commands::client_pin::{GetPinToken, GetRetries, Pin, PinAuth, PinError}; +use crate::errors::AuthenticatorError; +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::transport::FidoDevice; +use serde_cbor::{error::Error as CborError, Value}; +use serde_json as json; +use std::error::Error as StdErrorT; +use std::fmt; +use std::io::{Read, Write}; + +pub(crate) mod client_pin; +pub(crate) mod get_assertion; +pub(crate) mod get_info; +pub(crate) mod get_next_assertion; +pub(crate) mod get_version; +pub(crate) mod make_credentials; +pub(crate) mod reset; +pub(crate) mod selection; + +pub trait Request +where + Self: fmt::Debug, + Self: RequestCtap1, + Self: RequestCtap2, +{ + fn is_ctap2_request(&self) -> bool; +} + +/// Retryable wraps an error type and may ask manager to retry sending a +/// command, this is useful for ctap1 where token will reply with "condition not +/// sufficient" because user needs to press the button. +#[derive(Debug)] +pub enum Retryable { + Retry, + Error(T), +} + +impl Retryable { + pub fn is_retry(&self) -> bool { + matches!(*self, Retryable::Retry) + } + + pub fn is_error(&self) -> bool { + !self.is_retry() + } +} + +impl From for Retryable { + fn from(e: T) -> Self { + Retryable::Error(e) + } +} + +pub trait RequestCtap1: fmt::Debug { + type Output; + + /// Serializes a request into FIDO v1.x / CTAP1 / U2F format. + /// + /// See [`crate::u2ftypes::CTAP1RequestAPDU::serialize()`] + fn ctap1_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: FidoDevice + Read + Write + fmt::Debug; + + /// Deserializes a response from FIDO v1.x / CTAP1 / U2Fv2 format. + fn handle_response_ctap1( + &self, + status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result>; +} + +pub trait RequestCtap2: fmt::Debug { + type Output; + + fn command() -> Command; + + fn wire_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: FidoDevice + Read + Write + fmt::Debug; + + fn handle_response_ctap2( + &self, + dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: FidoDevice + Read + Write + fmt::Debug; +} + +pub(crate) trait PinAuthCommand { + fn pin(&self) -> &Option; + fn set_pin(&mut self, pin: Option); + fn pin_auth(&self) -> &Option; + fn set_pin_auth(&mut self, pin_auth: Option, pin_auth_protocol: Option); + fn client_data_hash(&self) -> ClientDataHash; + fn unset_uv_option(&mut self); + fn determine_pin_auth(&mut self, dev: &mut D) -> Result<(), AuthenticatorError> { + if !dev.supports_ctap2() { + self.set_pin_auth(None, None); + return Ok(()); + } + + let client_data_hash = self.client_data_hash(); + let pin_auth = match calculate_pin_auth(dev, &client_data_hash, &self.pin()) { + Ok(pin_auth) => pin_auth, + Err(e) => { + return Err(repackage_pin_errors(dev, e)); + } + }; + self.set_pin_auth(pin_auth, Some(1)); // TODO(MS): Currently, we only support version 1 + Ok(()) + } +} + +pub(crate) fn repackage_pin_errors( + dev: &mut D, + error: AuthenticatorError, +) -> AuthenticatorError { + match error { + AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::PinInvalid, + _, + ))) => { + // If the given PIN was wrong, determine no. of left retries + let cmd = GetRetries::new(); + let retries = dev.send_cbor(&cmd).ok(); // If we got retries, wrap it in Some, otherwise ignore err + return AuthenticatorError::PinError(PinError::InvalidPin(retries)); + } + AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::PinAuthBlocked, + _, + ))) => { + return AuthenticatorError::PinError(PinError::PinAuthBlocked); + } + AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::PinBlocked, + _, + ))) => { + return AuthenticatorError::PinError(PinError::PinBlocked); + } + AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::PinRequired, + _, + ))) => { + return AuthenticatorError::PinError(PinError::PinRequired); + } + // TODO(MS): Add "PinNotSet" + // TODO(MS): Add "PinPolicyViolated" + err => { + return err; + } + } +} + +// Spec: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api +// and: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticator-api +#[repr(u8)] +#[derive(Debug)] +pub enum Command { + MakeCredentials = 0x01, + GetAssertion = 0x02, + GetInfo = 0x04, + ClientPin = 0x06, + Reset = 0x07, + GetNextAssertion = 0x08, + Selection = 0x0B, +} + +impl Command { + #[cfg(test)] + pub fn from_u8(v: u8) -> Option { + match v { + 0x01 => Some(Command::MakeCredentials), + 0x02 => Some(Command::GetAssertion), + 0x04 => Some(Command::GetInfo), + 0x06 => Some(Command::ClientPin), + 0x07 => Some(Command::Reset), + 0x08 => Some(Command::GetNextAssertion), + _ => None, + } + } +} + +#[derive(Debug)] +pub enum StatusCode { + /// Indicates successful response. + OK, + /// The command is not a valid CTAP command. + InvalidCommand, + /// The command included an invalid parameter. + InvalidParameter, + /// Invalid message or item length. + InvalidLength, + /// Invalid message sequencing. + InvalidSeq, + /// Message timed out. + Timeout, + /// Channel busy. + ChannelBusy, + /// Command requires channel lock. + LockRequired, + /// Command not allowed on this cid. + InvalidChannel, + /// Invalid/unexpected CBOR error. + CBORUnexpectedType, + /// Error when parsing CBOR. + InvalidCBOR, + /// Missing non-optional parameter. + MissingParameter, + /// Limit for number of items exceeded. + LimitExceeded, + /// Unsupported extension. + UnsupportedExtension, + /// Valid credential found in the exclude list. + CredentialExcluded, + /// Processing (Lengthy operation is in progress). + Processing, + /// Credential not valid for the authenticator. + InvalidCredential, + /// Authentication is waiting for user interaction. + UserActionPending, + /// Processing, lengthy operation is in progress. + OperationPending, + /// No request is pending. + NoOperations, + /// Authenticator does not support requested algorithm. + UnsupportedAlgorithm, + /// Not authorized for requested operation. + OperationDenied, + /// Internal key storage is full. + KeyStoreFull, + /// No outstanding operations. + NoOperationPending, + /// Unsupported option. + UnsupportedOption, + /// Not a valid option for current operation. + InvalidOption, + /// Pending keep alive was cancelled. + KeepaliveCancel, + /// No valid credentials provided. + NoCredentials, + /// Timeout waiting for user interaction. + UserActionTimeout, + /// Continuation command, such as, authenticatorGetNextAssertion not + /// allowed. + NotAllowed, + /// PIN Invalid. + PinInvalid, + /// PIN Blocked. + PinBlocked, + /// PIN authentication,pinAuth, verification failed. + PinAuthInvalid, + /// PIN authentication,pinAuth, blocked. Requires power recycle to reset. + PinAuthBlocked, + /// No PIN has been set. + PinNotSet, + /// PIN is required for the selected operation. + PinRequired, + /// PIN policy violation. Currently only enforces minimum length. + PinPolicyViolation, + /// pinToken expired on authenticator. + PinTokenExpired, + /// Authenticator cannot handle this request due to memory constraints. + RequestTooLarge, + /// The current operation has timed out. + ActionTimeout, + /// User presence is required for the requested operation. + UpRequired, + + /// Unknown status. + Unknown(u8), +} + +impl StatusCode { + fn is_ok(&self) -> bool { + matches!(*self, StatusCode::OK) + } + + fn device_busy(&self) -> bool { + matches!(*self, StatusCode::ChannelBusy) + } +} + +impl From for StatusCode { + fn from(value: u8) -> StatusCode { + match value { + 0x00 => StatusCode::OK, + 0x01 => StatusCode::InvalidCommand, + 0x02 => StatusCode::InvalidParameter, + 0x03 => StatusCode::InvalidLength, + 0x04 => StatusCode::InvalidSeq, + 0x05 => StatusCode::Timeout, + 0x06 => StatusCode::ChannelBusy, + 0x0A => StatusCode::LockRequired, + 0x0B => StatusCode::InvalidChannel, + 0x11 => StatusCode::CBORUnexpectedType, + 0x12 => StatusCode::InvalidCBOR, + 0x14 => StatusCode::MissingParameter, + 0x15 => StatusCode::LimitExceeded, + 0x16 => StatusCode::UnsupportedExtension, + 0x19 => StatusCode::CredentialExcluded, + 0x21 => StatusCode::Processing, + 0x22 => StatusCode::InvalidCredential, + 0x23 => StatusCode::UserActionPending, + 0x24 => StatusCode::OperationPending, + 0x25 => StatusCode::NoOperations, + 0x26 => StatusCode::UnsupportedAlgorithm, + 0x27 => StatusCode::OperationDenied, + 0x28 => StatusCode::KeyStoreFull, + 0x2A => StatusCode::NoOperationPending, + 0x2B => StatusCode::UnsupportedOption, + 0x2C => StatusCode::InvalidOption, + 0x2D => StatusCode::KeepaliveCancel, + 0x2E => StatusCode::NoCredentials, + 0x2f => StatusCode::UserActionTimeout, + 0x30 => StatusCode::NotAllowed, + 0x31 => StatusCode::PinInvalid, + 0x32 => StatusCode::PinBlocked, + 0x33 => StatusCode::PinAuthInvalid, + 0x34 => StatusCode::PinAuthBlocked, + 0x35 => StatusCode::PinNotSet, + 0x36 => StatusCode::PinRequired, + 0x37 => StatusCode::PinPolicyViolation, + 0x38 => StatusCode::PinTokenExpired, + 0x39 => StatusCode::RequestTooLarge, + 0x3A => StatusCode::ActionTimeout, + 0x3B => StatusCode::UpRequired, + + othr => StatusCode::Unknown(othr), + } + } +} + +#[cfg(test)] +impl Into for StatusCode { + fn into(self) -> u8 { + match self { + StatusCode::OK => 0x00, + StatusCode::InvalidCommand => 0x01, + StatusCode::InvalidParameter => 0x02, + StatusCode::InvalidLength => 0x03, + StatusCode::InvalidSeq => 0x04, + StatusCode::Timeout => 0x05, + StatusCode::ChannelBusy => 0x06, + StatusCode::LockRequired => 0x0A, + StatusCode::InvalidChannel => 0x0B, + StatusCode::CBORUnexpectedType => 0x11, + StatusCode::InvalidCBOR => 0x12, + StatusCode::MissingParameter => 0x14, + StatusCode::LimitExceeded => 0x15, + StatusCode::UnsupportedExtension => 0x16, + StatusCode::CredentialExcluded => 0x19, + StatusCode::Processing => 0x21, + StatusCode::InvalidCredential => 0x22, + StatusCode::UserActionPending => 0x23, + StatusCode::OperationPending => 0x24, + StatusCode::NoOperations => 0x25, + StatusCode::UnsupportedAlgorithm => 0x26, + StatusCode::OperationDenied => 0x27, + StatusCode::KeyStoreFull => 0x28, + StatusCode::NoOperationPending => 0x2A, + StatusCode::UnsupportedOption => 0x2B, + StatusCode::InvalidOption => 0x2C, + StatusCode::KeepaliveCancel => 0x2D, + StatusCode::NoCredentials => 0x2E, + StatusCode::UserActionTimeout => 0x2f, + StatusCode::NotAllowed => 0x30, + StatusCode::PinInvalid => 0x31, + StatusCode::PinBlocked => 0x32, + StatusCode::PinAuthInvalid => 0x33, + StatusCode::PinAuthBlocked => 0x34, + StatusCode::PinNotSet => 0x35, + StatusCode::PinRequired => 0x36, + StatusCode::PinPolicyViolation => 0x37, + StatusCode::PinTokenExpired => 0x38, + StatusCode::RequestTooLarge => 0x39, + StatusCode::ActionTimeout => 0x3A, + StatusCode::UpRequired => 0x3B, + + StatusCode::Unknown(othr) => othr, + } + } +} + +#[derive(Debug)] +pub enum CommandError { + InputTooSmall, + MissingRequiredField(&'static str), + Deserializing(CborError), + Serializing(CborError), + StatusCode(StatusCode, Option), + Json(json::Error), + Crypto(crypto::CryptoError), + UnsupportedPinProtocol, +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CommandError::InputTooSmall => write!(f, "CommandError: Input is too small"), + CommandError::MissingRequiredField(field) => { + write!(f, "CommandError: Missing required field {}", field) + } + CommandError::Deserializing(ref e) => { + write!(f, "CommandError: Error while parsing: {}", e) + } + CommandError::Serializing(ref e) => { + write!(f, "CommandError: Error while serializing: {}", e) + } + CommandError::StatusCode(ref code, ref value) => { + write!(f, "CommandError: Unexpected code: {:?} ({:?})", code, value) + } + CommandError::Json(ref e) => write!(f, "CommandError: Json serializing error: {}", e), + CommandError::Crypto(ref e) => write!(f, "CommandError: Crypto error: {:?}", e), + CommandError::UnsupportedPinProtocol => { + write!(f, "CommandError: Pin protocol is not supported") + } + } + } +} + +impl StdErrorT for CommandError {} + +pub(crate) fn calculate_pin_auth( + dev: &mut Dev, + client_data_hash: &ClientDataHash, + pin: &Option, +) -> Result, AuthenticatorError> +where + Dev: FidoDevice, +{ + // Not reusing the shared secret here, if it exists, since we might start again + // with a different PIN (e.g. if the last one was wrong) + let (shared_secret, info) = dev.establish_shared_secret()?; + + // TODO(MS): What to do if token supports client_pin, but none has been set: Some(false) + // AND a Pin is not None? + let pin_auth = if info.options.client_pin == Some(true) { + let pin = pin + .as_ref() + .ok_or(HIDError::Command(CommandError::StatusCode( + StatusCode::PinRequired, + None, + )))?; + + let pin_command = GetPinToken::new(&info, &shared_secret, &pin)?; + let pin_token = dev.send_cbor(&pin_command)?; + + Some( + pin_token + .auth(client_data_hash.as_ref()) + .map_err(CommandError::Crypto)?, + ) + } else { + None + }; + + Ok(pin_auth) +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/reset.rs b/third_party/rust/authenticator/src/ctap2/commands/reset.rs new file mode 100644 index 000000000000..a40ae34ad091 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/reset.rs @@ -0,0 +1,129 @@ +use super::{Command, CommandError, RequestCtap2, StatusCode}; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde_cbor::{de::from_slice, Value}; + +#[derive(Debug)] +pub struct Reset {} + +impl Default for Reset { + fn default() -> Reset { + Reset {} + } +} + +impl RequestCtap2 for Reset { + type Output = (); + + fn command() -> Command { + Command::Reset + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + Ok(Vec::new()) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + + if status.is_ok() { + Ok(()) + } else { + let msg = if input.len() > 1 { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Some(data) + } else { + None + }; + Err(CommandError::StatusCode(status, msg).into()) + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::consts::HIDCmd; + use crate::transport::device_selector::Device; + use crate::transport::{hid::HIDDevice, FidoDevice}; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + use serde_cbor::{de::from_slice, Value}; + + fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> { + let mut device = Device::new("commands/Reset").unwrap(); + // ctap2 request + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + device.set_cid(cid); + + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt + msg.extend(vec![0x07]); // authenticatorReset + device.add_write(&msg, 0); + + // ctap2 response + let len = 0x1 + add.len() as u8; + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, len]); // cmd + bcnt + msg.push(cmd); // Status code + msg.extend(add); // + maybe additional data + device.add_read(&msg, 0); + + device.send_cbor(&Reset {}) + } + + #[test] + fn test_select_ctap2_only() { + // Test, if we can parse the status codes specified by the spec + + // Ok() + let response = issue_command_and_get_response(0, &vec![]).expect("Unexpected error"); + assert_eq!(response, ()); + + // Denied by the user + let response = issue_command_and_get_response(0x27, &vec![]).expect_err("Not an error!"); + assert!(matches!( + response, + HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, None)) + )); + + // Timeout + let response = issue_command_and_get_response(0x2F, &vec![]).expect_err("Not an error!"); + assert!(matches!( + response, + HIDError::Command(CommandError::StatusCode( + StatusCode::UserActionTimeout, + None + )) + )); + + // Unexpected error with more random CBOR-data + let add_data = vec![ + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + ]; + let response = issue_command_and_get_response(0x02, &add_data).expect_err("Not an error!"); + match response { + HIDError::Command(CommandError::StatusCode(StatusCode::InvalidParameter, Some(d))) => { + let expected: Value = from_slice(&add_data).unwrap(); + assert_eq!(d, expected) + } + e => panic!("Not the expected response: {:?}", e), + } + } +} diff --git a/third_party/rust/authenticator/src/ctap2/commands/selection.rs b/third_party/rust/authenticator/src/ctap2/commands/selection.rs new file mode 100644 index 000000000000..9f4491a73431 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/selection.rs @@ -0,0 +1,129 @@ +use super::{Command, CommandError, RequestCtap2, StatusCode}; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde_cbor::{de::from_slice, Value}; + +#[derive(Debug)] +pub struct Selection {} + +impl Default for Selection { + fn default() -> Selection { + Selection {} + } +} + +impl RequestCtap2 for Selection { + type Output = (); + + fn command() -> Command { + Command::Selection + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + Ok(Vec::new()) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall.into()); + } + + let status: StatusCode = input[0].into(); + + if status.is_ok() { + Ok(()) + } else { + let msg = if input.len() > 1 { + let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; + Some(data) + } else { + None + }; + Err(CommandError::StatusCode(status, msg).into()) + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::consts::HIDCmd; + use crate::transport::device_selector::Device; + use crate::transport::{hid::HIDDevice, FidoDevice}; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + use serde_cbor::{de::from_slice, Value}; + + fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> { + let mut device = Device::new("commands/selection").unwrap(); + // ctap2 request + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + device.set_cid(cid); + + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt + msg.extend(vec![0x0B]); // authenticatorSelection + device.add_write(&msg, 0); + + // ctap2 response + let len = 0x1 + add.len() as u8; + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, len]); // cmd + bcnt + msg.push(cmd); // Status code + msg.extend(add); // + maybe additional data + device.add_read(&msg, 0); + + device.send_cbor(&Selection {}) + } + + #[test] + fn test_select_ctap2_only() { + // Test, if we can parse the status codes specified by the spec + + // Ok() + let response = issue_command_and_get_response(0, &vec![]).expect("Unexpected error"); + assert_eq!(response, ()); + + // Denied by the user + let response = issue_command_and_get_response(0x27, &vec![]).expect_err("Not an error!"); + assert!(matches!( + response, + HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, None)) + )); + + // Timeout + let response = issue_command_and_get_response(0x2F, &vec![]).expect_err("Not an error!"); + assert!(matches!( + response, + HIDError::Command(CommandError::StatusCode( + StatusCode::UserActionTimeout, + None + )) + )); + + // Unexpected error with more random CBOR-data + let add_data = vec![ + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + ]; + let response = issue_command_and_get_response(0x02, &add_data).expect_err("Not an error!"); + match response { + HIDError::Command(CommandError::StatusCode(StatusCode::InvalidParameter, Some(d))) => { + let expected: Value = from_slice(&add_data).unwrap(); + assert_eq!(d, expected) + } + e => panic!("Not the expected response: {:?}", e), + } + } +} diff --git a/third_party/rust/authenticator/src/ctap2/mod.rs b/third_party/rust/authenticator/src/ctap2/mod.rs new file mode 100644 index 000000000000..aa5dd7317047 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/mod.rs @@ -0,0 +1,9 @@ +#[allow(dead_code)] // TODO(MS): Remove me asap +pub mod commands; +pub use commands::get_assertion::AssertionObject; + +pub(crate) mod attestation; + +pub mod client_data; +pub mod server; +pub(crate) mod utils; diff --git a/third_party/rust/authenticator/src/ctap2/server.rs b/third_party/rust/authenticator/src/ctap2/server.rs new file mode 100644 index 000000000000..3b46584cf598 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/server.rs @@ -0,0 +1,510 @@ +use crate::crypto::COSEAlgorithm; +use crate::{errors::AuthenticatorError, AuthenticatorTransports, KeyHandle}; +use serde::de::MapAccess; +use serde::{ + de::{Error as SerdeError, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; +use serde_bytes::ByteBuf; +use sha2::{Digest, Sha256}; +use std::convert::{Into, TryFrom}; +use std::fmt; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct RpIdHash(pub [u8; 32]); + +impl fmt::Debug for RpIdHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD); + write!(f, "RpIdHash({})", value) + } +} + +impl AsRef<[u8]> for RpIdHash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl RpIdHash { + pub fn from(src: &[u8]) -> Result { + let mut payload = [0u8; 32]; + if src.len() != payload.len() { + Err(AuthenticatorError::InvalidRelyingPartyInput) + } else { + payload.copy_from_slice(src); + Ok(RpIdHash(payload)) + } + } +} + +#[derive(Debug, Serialize, Clone, Default)] +#[cfg_attr(test, derive(Deserialize))] +pub struct RelyingParty { + // TODO(baloo): spec is wrong !!!!111 + // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands + // in the example "A PublicKeyCredentialRpEntity DOM object defined as follows:" + // inconsistent with https://w3c.github.io/webauthn/#sctn-rp-credential-params + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, +} + +// Note: This enum is provided to make old CTAP1/U2F API work. This should be deprecated at some point +#[derive(Debug, Clone)] +pub enum RelyingPartyWrapper { + Data(RelyingParty), + // CTAP1 hash can be derived from full object, see RelyingParty::hash below, + // but very old backends might still provide application IDs. + Hash(RpIdHash), +} + +impl RelyingPartyWrapper { + pub fn hash(&self) -> RpIdHash { + match *self { + RelyingPartyWrapper::Data(ref d) => { + let mut hasher = Sha256::new(); + hasher.update(&d.id); + + let mut output = [0u8; 32]; + output.copy_from_slice(&hasher.finalize().as_slice()); + + RpIdHash(output) + } + RelyingPartyWrapper::Hash(ref d) => d.clone(), + } + } +} + +// TODO(baloo): should we rename this PublicKeyCredentialUserEntity ? +#[derive(Debug, Serialize, Clone, Eq, PartialEq, Deserialize, Default)] +pub struct User { + #[serde(with = "serde_bytes")] + pub id: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, // This has been removed from Webauthn-2 + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "displayName")] + pub display_name: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicKeyCredentialParameters { + pub alg: COSEAlgorithm, +} + +impl TryFrom for PublicKeyCredentialParameters { + type Error = AuthenticatorError; + fn try_from(arg: i32) -> Result { + let alg = COSEAlgorithm::try_from(arg as i64)?; + Ok(PublicKeyCredentialParameters { alg }) + } +} + +impl Serialize for PublicKeyCredentialParameters { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("alg", &self.alg)?; + map.serialize_entry("type", "public-key")?; + map.end() + } +} + +impl<'de> Deserialize<'de> for PublicKeyCredentialParameters { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PublicKeyCredentialParametersVisitor; + + impl<'de> Visitor<'de> for PublicKeyCredentialParametersVisitor { + type Value = PublicKeyCredentialParameters; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut found_type = false; + let mut alg = None; + while let Some(key) = map.next_key()? { + match key { + "alg" => { + if alg.is_some() { + return Err(SerdeError::duplicate_field("alg")); + } + alg = Some(map.next_value()?); + } + "type" => { + if found_type { + return Err(SerdeError::duplicate_field("type")); + } + + let v: &str = map.next_value()?; + if v != "public-key" { + return Err(SerdeError::custom(format!("invalid value: {}", v))); + } + found_type = true; + } + v => { + return Err(SerdeError::unknown_field(v, &[])); + } + } + } + + if !found_type { + return Err(SerdeError::missing_field("type")); + } + + let alg = alg.ok_or(SerdeError::missing_field("alg"))?; + + Ok(PublicKeyCredentialParameters { alg }) + } + } + + deserializer.deserialize_bytes(PublicKeyCredentialParametersVisitor) + } +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum Transport { + USB, + NFC, + BLE, + Internal, +} + +impl From for Vec { + fn from(t: AuthenticatorTransports) -> Self { + let mut transports = Vec::new(); + if t.contains(AuthenticatorTransports::USB) { + transports.push(Transport::USB); + } + if t.contains(AuthenticatorTransports::NFC) { + transports.push(Transport::NFC); + } + if t.contains(AuthenticatorTransports::BLE) { + transports.push(Transport::BLE); + } + + transports + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicKeyCredentialDescriptor { + pub id: Vec, + pub transports: Vec, +} + +impl Serialize for PublicKeyCredentialDescriptor { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // TODO(MS): Transports is OPTIONAL, but some older tokens don't understand it + // and return a CBOR-Parsing error. It is only a hint for the token, + // so we'll leave it out for the moment + let mut map = serializer.serialize_map(Some(2))?; + // let mut map = serializer.serialize_map(Some(3))?; + map.serialize_entry("type", "public-key")?; + map.serialize_entry("id", &ByteBuf::from(self.id.clone()))?; + // map.serialize_entry("transports", &self.transports)?; + map.end() + } +} + +impl<'de> Deserialize<'de> for PublicKeyCredentialDescriptor { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PublicKeyCredentialDescriptorVisitor; + + impl<'de> Visitor<'de> for PublicKeyCredentialDescriptorVisitor { + type Value = PublicKeyCredentialDescriptor; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut found_type = false; + let mut id = None; + let mut transports = None; + while let Some(key) = map.next_key()? { + match key { + "id" => { + if id.is_some() { + return Err(SerdeError::duplicate_field("id")); + } + let id_bytes: ByteBuf = map.next_value()?; + id = Some(id_bytes.into_vec()); + } + "transports" => { + if transports.is_some() { + return Err(SerdeError::duplicate_field("transports")); + } + transports = Some(map.next_value()?); + } + "type" => { + if found_type { + return Err(SerdeError::duplicate_field("type")); + } + let v: &str = map.next_value()?; + if v != "public-key" { + return Err(SerdeError::custom(format!("invalid value: {}", v))); + } + found_type = true; + } + v => { + return Err(SerdeError::unknown_field(v, &[])); + } + } + } + + if !found_type { + return Err(SerdeError::missing_field("type")); + } + + let id = id.ok_or(SerdeError::missing_field("id"))?; + let transports = transports.unwrap_or(Vec::new()); + + Ok(PublicKeyCredentialDescriptor { id, transports }) + } + } + + deserializer.deserialize_bytes(PublicKeyCredentialDescriptorVisitor) + } +} + +impl From<&KeyHandle> for PublicKeyCredentialDescriptor { + fn from(kh: &KeyHandle) -> Self { + Self { + id: kh.credential.clone(), + transports: kh.transports.into(), + } + } +} + +#[cfg(test)] +mod test { + use super::{ + COSEAlgorithm, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + Transport, User, + }; + + #[test] + fn serialize_rp() { + let rp = RelyingParty { + id: String::from("Acme"), + name: None, + icon: None, + }; + + let payload = ser::to_vec(&rp).unwrap(); + assert_eq!( + &payload, + &[ + 0xa1, // map(1) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x64, // text(4) + 0x41, 0x63, 0x6d, 0x65 + ] + ); + } + + #[test] + fn serialize_user() { + let user = User { + id: vec![ + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, + 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, + 0x01, 0x93, 0x30, 0x82, + ], + icon: Some(String::from("https://pics.example.com/00/p/aBjjjpqPb.png")), + name: Some(String::from("johnpsmith@example.com")), + display_name: Some(String::from("John P. Smith")), + }; + + let payload = ser::to_vec(&user).unwrap(); + println!("payload = {:?}", payload); + assert_eq!( + payload, + vec![ + 0xa4, // map(4) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x20, // bytes(32) + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, // userid + 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, // ... + 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, // ... + 0x30, 0x82, // ... + 0x64, // text(4) + 0x69, 0x63, 0x6f, 0x6e, // "icon" + 0x78, 0x2b, // text(43) + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, + 0x69, // "https://pics.example.com/00/p/aBjjjpqPb.png" + 0x63, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, // ... + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x30, 0x2f, 0x70, 0x2f, // ... + 0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71, 0x50, 0x62, 0x2e, // ... + 0x70, 0x6e, 0x67, // ... + 0x64, // text(4) + 0x6e, 0x61, 0x6d, 0x65, // "name" + 0x76, // text(22) + 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, + 0x68, // "johnpsmith@example.com" + 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, // ... + 0x6f, 0x6d, // ... + 0x6b, // text(11) + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, // "displayName" + 0x65, // ... + 0x6d, // text(13) + 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, // "John P. Smith" + 0x69, 0x74, 0x68, // ... + ] + ); + } + + #[test] + fn serialize_user_noicon_nodisplayname() { + let user = User { + id: vec![ + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, + 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, + 0x01, 0x93, 0x30, 0x82, + ], + icon: None, + name: Some(String::from("johnpsmith@example.com")), + display_name: None, + }; + + let payload = ser::to_vec(&user).unwrap(); + println!("payload = {:?}", payload); + assert_eq!( + payload, + vec![ + 0xa2, // map(2) + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x20, // bytes(32) + 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, // userid + 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, // ... + 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, // ... + 0x30, 0x82, // ... + 0x64, // text(4) + 0x6e, 0x61, 0x6d, 0x65, // "name" + 0x76, // text(22) + 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, + 0x68, // "johnpsmith@example.com" + 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, // ... + 0x6f, 0x6d, // ... + ] + ); + } + + use serde_cbor::ser; + + #[test] + fn public_key() { + let keys = vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ]; + + let payload = ser::to_vec(&keys); + println!("payload = {:?}", payload); + let payload = payload.unwrap(); + assert_eq!( + payload, + vec![ + 0x82, // array(2) + 0xa2, // map(2) + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + 0x26, // -7 (ES256) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key" + 0x2D, 0x6B, 0x65, 0x79, // ... + 0xa2, // map(2) + 0x63, // text(3) + 0x61, 0x6c, 0x67, // "alg" + 0x39, 0x01, 0x00, // -257 (RS256) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key" + 0x2D, 0x6B, 0x65, 0x79 // ... + ] + ); + } + + #[test] + fn public_key_desc() { + let key = PublicKeyCredentialDescriptor { + id: vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ], + transports: vec![Transport::BLE, Transport::USB], + }; + + let payload = ser::to_vec(&key); + println!("payload = {:?}", payload); + let payload = payload.unwrap(); + + assert_eq!( + payload, + vec![ + // 0xa3, // map(3) + 0xa2, // map(2) + 0x64, // text(4) + 0x74, 0x79, 0x70, 0x65, // "type" + 0x6a, // text(10) + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key" + 0x2D, 0x6B, 0x65, 0x79, // ... + 0x62, // text(2) + 0x69, 0x64, // "id" + 0x58, 0x20, // bytes(32) + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, // key id + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, // ... + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, // ... + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // ... + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // ... + 0x1e, + 0x1f, // ... + + // Deactivated for now + //0x6a, // text(10) + //0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, // "transports" + //0x6f, 0x72, 0x74, 0x73, // ... + //0x82, // array(2) + //0x63, // text(3) + //0x62, 0x6c, 0x65, // "ble" + //0x63, // text(3) + //0x75, 0x73, 0x62 // "usb" + ] + ); + } +} diff --git a/third_party/rust/authenticator/src/ctap2/utils.rs b/third_party/rust/authenticator/src/ctap2/utils.rs new file mode 100644 index 000000000000..ba9c7db3b4e2 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/utils.rs @@ -0,0 +1,14 @@ +use serde::de; +use serde_cbor::error::Result; +use serde_cbor::Deserializer; + +pub fn from_slice_stream<'a, T>(slice: &'a [u8]) -> Result<(&'a [u8], T)> +where + T: de::Deserialize<'a>, +{ + let mut deserializer = Deserializer::from_slice(slice); + let value = de::Deserialize::deserialize(&mut deserializer)?; + let rest = &slice[deserializer.byte_offset()..]; + + Ok((rest, value)) +} diff --git a/third_party/rust/authenticator/src/ctap2_capi.rs b/third_party/rust/authenticator/src/ctap2_capi.rs new file mode 100644 index 000000000000..f1dc4bb8f9d2 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2_capi.rs @@ -0,0 +1,945 @@ +/* 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, CtapVersion, RegisterArgsCtap2, SignArgsCtap2, +}; +use crate::ctap2::attestation::AttestationStatement; +use crate::ctap2::commands::get_assertion::{Assertion, AssertionObject, GetAssertionOptions}; +use crate::ctap2::commands::make_credentials::MakeCredentialsOptions; +use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User, +}; +use crate::errors::{AuthenticatorError, U2FTokenError}; +use crate::statecallback::StateCallback; +use crate::{AttestationObject, CollectedClientDataWrapper, Pin, StatusUpdate}; +use crate::{RegisterResult, SignResult}; +use libc::size_t; +use rand::{thread_rng, Rng}; +use serde_cbor; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::sync::mpsc::channel; +use std::thread; +use std::{ptr, slice}; + +type Ctap2RegisterResult = Result<(AttestationObject, CString), AuthenticatorError>; +type Ctap2PubKeyCredDescriptors = Vec; +type Ctap2RegisterCallback = extern "C" fn(u64, *mut Ctap2RegisterResult); +type Ctap2SignResult = Result<(AssertionObject, CString), AuthenticatorError>; +type Ctap2SignCallback = extern "C" fn(u64, *mut Ctap2SignResult); +type Ctap2StatusUpdateCallback = extern "C" fn(*mut StatusUpdate); + +const SIGN_RESULT_PUBKEY_CRED_ID: u8 = 1; +const SIGN_RESULT_AUTH_DATA: u8 = 2; +const SIGN_RESULT_SIGNATURE: u8 = 3; +const SIGN_RESULT_USER_ID: u8 = 4; +const SIGN_RESULT_USER_NAME: u8 = 5; + +#[repr(C)] +pub struct AuthenticatorArgsUser { + id_ptr: *const u8, + id_len: usize, + name: *const c_char, +} + +#[repr(C)] +pub struct AuthenticatorArgsChallenge { + ptr: *const u8, + len: usize, +} + +#[repr(C)] +pub struct AuthenticatorArgsPubCred { + ptr: *const i32, + len: usize, +} + +#[repr(C)] +pub struct AuthenticatorArgsOptions { + resident_key: bool, + user_verification: bool, + user_presence: bool, + force_none_attestation: bool, +} + +// Generates a new 64-bit transaction id with collision probability 2^-32. +fn new_tid() -> u64 { + thread_rng().gen::() +} + +unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec { + slice::from_raw_parts(ptr, len).to_vec() +} + +/// # 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_ctap2_mgr_free(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + drop(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_ctap2_pkcd_new() -> *mut Ctap2PubKeyCredDescriptors { + Box::into_raw(Box::new(vec![])) +} + +/// # Safety +/// +/// This method must be used on an actual Ctap2PubKeyCredDescriptors CredDescriptorse +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_pkcd_add( + pkcd: *mut Ctap2PubKeyCredDescriptors, + id_ptr: *const u8, + id_len: usize, + transports: u8, +) { + (*pkcd).push(PublicKeyCredentialDescriptor { + id: from_raw(id_ptr, id_len), + transports: crate::AuthenticatorTransports::from_bits_truncate(transports).into(), + }); +} + +/// # 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_ctap2_pkcd_free(khs: *mut Ctap2PubKeyCredDescriptors) { + if !khs.is_null() { + drop(Box::from_raw(khs)); + } +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +/// The returned handle can be used with all rust_u2f_mgr_*-functions as well +/// but uses CTAP2 as the underlying protocol. CTAP1 requests will be repackaged +/// into CTAP2 (if the device supports it) +#[no_mangle] +pub extern "C" fn rust_ctap2_mgr_new() -> *mut AuthenticatorService { + if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP2) { + mgr.add_detected_transports(); + Box::into_raw(Box::new(mgr)) + } else { + ptr::null_mut() + } +} + +fn rewrap_client_data( + client_data: CollectedClientDataWrapper, +) -> Result { + let s = CString::new(client_data.serialized_data.clone()).map_err(|_| { + AuthenticatorError::Custom("Failed to transform client_data to C String".to_string()) + })?; + Ok(s) +} + +fn rewrap_register_result( + attestation_object: AttestationObject, + client_data: CollectedClientDataWrapper, +) -> Ctap2RegisterResult { + let s = rewrap_client_data(client_data)?; + Ok((attestation_object, s)) +} + +fn rewrap_sign_result( + assertion_object: AssertionObject, + client_data: CollectedClientDataWrapper, +) -> Ctap2SignResult { + let s = rewrap_client_data(client_data)?; + Ok((assertion_object, s)) +} + +/// # Safety +/// +/// This method should not be called on AuthenticatorService handles after +/// they've been freed +/// All input is copied and it is the callers responsibility to free appropriately. +/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list +/// to keep the API smaller, as they are essentially the same thing. +/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with +/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_register( + mgr: *mut AuthenticatorService, + timeout: u64, + callback: Ctap2RegisterCallback, + status_callback: Ctap2StatusUpdateCallback, + challenge: AuthenticatorArgsChallenge, + relying_party_id: *const c_char, + origin_ptr: *const c_char, + user: AuthenticatorArgsUser, + pub_cred_params: AuthenticatorArgsPubCred, + exclude_list: *const Ctap2PubKeyCredDescriptors, + options: AuthenticatorArgsOptions, + pin_ptr: *const c_char, +) -> u64 { + if mgr.is_null() { + return 0; + } + + // Check buffers. + if challenge.ptr.is_null() + || origin_ptr.is_null() + || relying_party_id.is_null() + || user.id_ptr.is_null() + || user.name.is_null() + || exclude_list.is_null() + { + return 0; + } + + let pub_cred_params = match slice::from_raw_parts(pub_cred_params.ptr, pub_cred_params.len) + .iter() + .map(|x| PublicKeyCredentialParameters::try_from(*x)) + .collect() + { + Ok(x) => x, + Err(_) => { + return 0; + } + }; + let pin = if pin_ptr.is_null() { + None + } else { + Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy())) + }; + let user = User { + id: from_raw(user.id_ptr, user.id_len), + name: Some(CStr::from_ptr(user.name).to_string_lossy().to_string()), // TODO(MS): Use to_str() and error out on failure? + display_name: None, + icon: None, + }; + let rp = RelyingParty { + id: CStr::from_ptr(relying_party_id) + .to_string_lossy() + .to_string(), + name: None, + icon: None, + }; + let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string(); + let challenge = from_raw(challenge.ptr, challenge.len); + let exclude_list = (*exclude_list).clone(); + let force_none_attestation = options.force_none_attestation; + + let (status_tx, status_rx) = channel::(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(r) => { + let rb = Box::new(r); + status_callback(Box::into_raw(rb)); + } + Err(e) => { + status_callback(ptr::null_mut()); + error!("Error when receiving status update: {:?}", e); + return; + } + } + }); + + let tid = new_tid(); + + let state_callback = StateCallback::>::new(Box::new(move |rv| { + let res = match rv { + Ok(RegisterResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch( + "rust_ctap2_mgr_register", + 2, + )), + Ok(RegisterResult::CTAP2(mut attestation_object, client_data)) => { + if force_none_attestation { + attestation_object.att_statement = AttestationStatement::None; + } + rewrap_register_result(attestation_object, client_data) + } + Err(e) => Err(e), + }; + + callback(tid, Box::into_raw(Box::new(res))); + })); + + let ctap_args = RegisterArgsCtap2 { + challenge, + relying_party: rp, + origin, + user, + pub_cred_params, + exclude_list, + options: MakeCredentialsOptions { + resident_key: options.resident_key.then(|| true), + user_verification: options.user_verification.then(|| true), + }, + extensions: Default::default(), + pin, + }; + + let res = (*mgr).register(timeout, ctap_args.into(), 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 +/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list +/// to keep the API smaller, as they are essentially the same thing. +/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with +/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_sign( + mgr: *mut AuthenticatorService, + timeout: u64, + callback: Ctap2SignCallback, + status_callback: Ctap2StatusUpdateCallback, + challenge: AuthenticatorArgsChallenge, + relying_party_id: *const c_char, + origin_ptr: *const c_char, + allow_list: *const Ctap2PubKeyCredDescriptors, + options: AuthenticatorArgsOptions, + pin_ptr: *const c_char, +) -> u64 { + if mgr.is_null() { + return 0; + } + + // Check buffers. + if challenge.ptr.is_null() + || origin_ptr.is_null() + || relying_party_id.is_null() + || allow_list.is_null() + { + return 0; + } + + let pin = if pin_ptr.is_null() { + None + } else { + Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy())) + }; + let rpid = CStr::from_ptr(relying_party_id) + .to_string_lossy() + .to_string(); + let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string(); + let challenge = from_raw(challenge.ptr, challenge.len); + let allow_list: Vec<_> = (*allow_list).clone(); + + let (status_tx, status_rx) = channel::(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(r) => { + let rb = Box::new(r); + status_callback(Box::into_raw(rb)); + } + Err(e) => { + status_callback(ptr::null_mut()); + error!("Error when receiving status update: {:?}", e); + return; + } + } + }); + + let single_key_handle = if allow_list.len() == 1 { + Some(allow_list.first().unwrap().clone()) + } else { + None + }; + + let tid = new_tid(); + let state_callback = StateCallback::>::new(Box::new(move |rv| { + let res = match rv { + Ok(SignResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch( + "rust_ctap2_mgr_register", + 2, + )), + Ok(SignResult::CTAP2(mut assertion_object, client_data)) => { + // The token can omit sending back credentials, if the allow_list had only one + // entry. Thus we re-add that here now for all found assertions before handing it out. + assertion_object.0.iter_mut().for_each(|x| { + x.credentials = x.credentials.clone().or(single_key_handle.clone()); + }); + rewrap_sign_result(assertion_object, client_data) + } + Err(e) => Err(e), + }; + + callback(tid, Box::into_raw(Box::new(res))); + })); + let ctap_args = SignArgsCtap2 { + challenge, + origin, + relying_party_id: rpid, + allow_list, + options: GetAssertionOptions { + user_presence: options.user_presence.then(|| true), + user_verification: options.user_verification.then(|| true), + }, + extensions: Default::default(), + pin, + }; + + let res = (*mgr).sign(timeout, ctap_args.into(), status_tx, state_callback); + + if res.is_ok() { + tid + } else { + 0 + } +} + +// /// # Safety +// /// +// /// This method must be used on an actual U2FResult handle +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_error(res: *const Ctap2RegisterResult) -> u8 { + if res.is_null() { + return U2FTokenError::Unknown as u8; + } + + match &*res { + Ok(..) => 0, // No error, the request succeeded. + Err(e) => e.as_u2f_errorcode(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_error(res: *const Ctap2SignResult) -> u8 { + if res.is_null() { + return U2FTokenError::Unknown as u8; + } + + match &*res { + Ok(..) => 0, // No error, the request succeeded. + Err(e) => e.as_u2f_errorcode(), + } +} + +/// # Safety +/// +/// This method should not be called on RegisterResult handles after they've been +/// freed or a double-free will occur +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_res_free(res: *mut Ctap2RegisterResult) { + if !res.is_null() { + drop(Box::from_raw(res)); + } +} + +/// # Safety +/// +/// This method should not be called on SignResult handles after they've been +/// freed or a double-free will occur +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_res_free(res: *mut Ctap2SignResult) { + if !res.is_null() { + drop(Box::from_raw(res)); + } +} + +/// # Safety +/// +/// This method should not be called AuthenticatorService handles after they've +/// been freed +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_cancel(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + // Ignore return value. + let _ = (*mgr).cancel(); + } +} + +unsafe fn client_data_len( + res: *const Result<(T, CString), AuthenticatorError>, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((_, client_data)) => { + *len = client_data.as_bytes().len(); + true + } + Err(_) => false, + } +} + +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_client_data_copy() +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_client_data_len( + res: *const Ctap2RegisterResult, + len: *mut size_t, +) -> bool { + client_data_len(res, len) +} + +/// This function is used to get the length, prior to calling +/// rust_ctap2_sign_result_client_data_copy() +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_len( + res: *const Ctap2SignResult, + len: *mut size_t, +) -> bool { + client_data_len(res, len) +} + +unsafe fn client_data_copy( + res: *const Result<(T, CString), AuthenticatorError>, + dst: *mut c_char, +) -> bool { + if dst.is_null() || res.is_null() { + return false; + } + + match &*res { + Ok((_, client_data)) => { + ptr::copy_nonoverlapping(client_data.as_ptr(), dst, client_data.as_bytes().len()); + return true; + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_client_data_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_client_data_copy( + res: *const Ctap2RegisterResult, + dst: *mut c_char, +) -> bool { + client_data_copy(res, dst) +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_client_data_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_copy( + res: *const Ctap2SignResult, + dst: *mut c_char, +) -> bool { + client_data_copy(res, dst) +} + +/// # Safety +/// +/// This function is used to get how long the specific item is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_attestation_len( + res: *const Ctap2RegisterResult, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + if let Ok((attestation, _)) = &*res { + if let Some(item_len) = serde_cbor::to_vec(&attestation).ok().map(|x| x.len()) { + *len = item_len; + return true; + } + } + false +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_item_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_attestation_copy( + res: *const Ctap2RegisterResult, + dst: *mut u8, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + if let Ok((attestation, _)) = &*res { + if let Ok(item) = serde_cbor::to_vec(&attestation) { + ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len()); + return true; + } + } + false +} + +/// This function is used to get how many assertions there are in total +/// The returned number can be used as index-maximum to access individual +/// fields +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_assertions_len( + res: *const Ctap2SignResult, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + *len = assertions.0.len(); + return true; + } + Err(_) => false, + } +} + +fn sign_result_item_len(assertion: &Assertion, item_idx: u8) -> Option { + match item_idx { + SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.len()), + // This is inefficent! Converting twice here. Once for len, once for copy + SIGN_RESULT_AUTH_DATA => assertion.auth_data.to_vec().ok().map(|x| x.len()), + SIGN_RESULT_SIGNATURE => Some(assertion.signature.len()), + SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.len()), + SIGN_RESULT_USER_NAME => assertion + .user + .as_ref() + .map(|u| { + u.display_name + .as_ref() + .or(u.name.as_ref()) + .map(|n| n.as_bytes().len()) + }) + .flatten(), + _ => None, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_contains( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, +) -> bool { + if res.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + if item_idx == SIGN_RESULT_AUTH_DATA { + // Short-cut to avoid serializing auth_data + return true; + } + sign_result_item_len(&assertions.0[assertion_idx], item_idx).is_some() + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the specific item is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_len( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(item_len) = sign_result_item_len(&assertions.0[assertion_idx], item_idx) { + *len = item_len; + true + } else { + false + } + } + Err(_) => false, + } +} + +unsafe fn sign_result_item_copy(assertion: &Assertion, item_idx: u8, dst: *mut u8) -> bool { + if dst.is_null() { + return false; + } + + let tmp_val; + let item = match item_idx { + SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.as_ref()), + // This is inefficent! Converting twice here. Once for len, once for copy + SIGN_RESULT_AUTH_DATA => { + tmp_val = assertion.auth_data.to_vec().ok(); + tmp_val.as_ref().map(|x| x.as_ref()) + } + SIGN_RESULT_SIGNATURE => Some(assertion.signature.as_ref()), + SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.as_ref()), + SIGN_RESULT_USER_NAME => assertion + .user + .as_ref() + .map(|u| { + u.display_name + .as_ref() + .or(u.name.as_ref()) + .map(|n| n.as_bytes().as_ref()) + }) + .flatten(), + _ => None, + }; + + if let Some(item) = item { + ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len()); + true + } else { + false + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_item_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_copy( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, + dst: *mut u8, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + sign_result_item_copy(&assertions.0[assertion_idx], item_idx, dst) + } + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_contains_username( + res: *const Ctap2SignResult, + assertion_idx: usize, +) -> bool { + if res.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .is_some() + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the specific username is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_username_len( + res: *const Ctap2SignResult, + assertion_idx: usize, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(name_len) = assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .flatten() + .map(|x| x.as_bytes().len()) + { + *len = name_len; + true + } else { + false + } + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_username_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_username_copy( + res: *const Ctap2SignResult, + assertion_idx: usize, + dst: *mut c_char, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(name) = assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .flatten() + .map(|u| CString::new(u.clone()).ok()) + .flatten() + { + ptr::copy_nonoverlapping(name.as_ptr(), dst, name.as_bytes().len()); + true + } else { + false + } + } + + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the JSON-representation of a status update is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_len( + res: *const StatusUpdate, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match serde_json::to_string(&*res) { + Ok(s) => { + *len = s.len(); + true + } + Err(e) => { + error!("Failed to parse {:?} into json: {:?}", &*res, e); + false + } + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_status_update_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_copy_json( + res: *const StatusUpdate, + dst: *mut c_char, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match serde_json::to_string(&*res) { + Ok(s) => { + if let Ok(cs) = CString::new(s) { + ptr::copy_nonoverlapping(cs.as_ptr(), dst, cs.as_bytes().len()); + true + } else { + error!("Failed to convert String to CString"); + false + } + } + Err(e) => { + error!("Failed to parse {:?} into json: {:?}", &*res, e); + false + } + } +} + +/// # Safety +/// +/// We copy the pin, so it is the callers responsibility to free the argument +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_send_pin( + res: *const StatusUpdate, + c_pin: *mut c_char, +) -> bool { + if res.is_null() || c_pin.is_null() { + return false; + } + + match &*res { + StatusUpdate::PinError(_, sender) => { + if let Ok(pin) = CStr::from_ptr(c_pin).to_str() { + sender + .send(Pin::new(pin)) + .map_err(|e| { + error!("Failed to send PIN to device-thread"); + e + }) + .is_ok() + } else { + error!("Failed to convert PIN from c_char to String"); + false + } + } + _ => { + error!("Wrong state!"); + false + } + } +} + +/// # Safety +/// +/// This function frees the memory of res! +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_destroy_status_update_res(res: *mut StatusUpdate) -> bool { + if res.is_null() { + return false; + } + // Dropping it when we go out of scope + drop(Box::from_raw(res)); + true +} diff --git a/third_party/rust/authenticator/src/errors.rs b/third_party/rust/authenticator/src/errors.rs index ee63cfacf3df..ee8959a9a868 100644 --- a/third_party/rust/authenticator/src/errors.rs +++ b/third_party/rust/authenticator/src/errors.rs @@ -2,6 +2,8 @@ * 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 use crate::ctap2::commands::{client_pin::PinError, CommandError}; +pub use crate::transport::errors::HIDError; use std::fmt; use std::io; use std::sync::mpsc; @@ -9,6 +11,12 @@ 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 UnsupportedOption { + MaxPinLength, + HmacSecret, +} + #[derive(Debug)] pub enum AuthenticatorError { // Errors from external libraries... @@ -20,12 +28,23 @@ pub enum AuthenticatorError { InternalError(String), U2FToken(U2FTokenError), Custom(String), + VersionMismatch(&'static str, u32), + HIDError(HIDError), + CryptoError, + PinError(PinError), + UnsupportedOption(UnsupportedOption), } impl AuthenticatorError { pub fn as_u2f_errorcode(&self) -> u8 { match *self { AuthenticatorError::U2FToken(ref err) => *err as u8, + // TODO: This is somewhat ugly, as we hardcode the error code here, instead of using the + // const defined in `u2fhid-capi.h`, which we should. + AuthenticatorError::PinError(PinError::PinRequired) => 6u8, + AuthenticatorError::PinError(PinError::InvalidPin(_)) => 7u8, + AuthenticatorError::PinError(PinError::PinAuthBlocked) => 8u8, + AuthenticatorError::PinError(PinError::PinBlocked) => 9u8, _ => U2FTokenError::Unknown as u8, } } @@ -50,6 +69,19 @@ impl fmt::Display for AuthenticatorError { write!(f, "A u2f token error occurred {:?}", err) } AuthenticatorError::Custom(ref err) => write!(f, "A custom error occurred {:?}", err), + AuthenticatorError::VersionMismatch(manager, version) => write!( + f, + "{} expected arguments of version CTAP{}", + manager, version + ), + AuthenticatorError::HIDError(ref e) => write!(f, "Device error: {}", e), + AuthenticatorError::CryptoError => { + write!(f, "The cryptography implementation encountered an error") + } + AuthenticatorError::PinError(ref e) => write!(f, "PIN Error: {}", e), + AuthenticatorError::UnsupportedOption(ref e) => { + write!(f, "Unsupported option: {:?}", e) + } } } } @@ -60,6 +92,18 @@ impl From for AuthenticatorError { } } +impl From for AuthenticatorError { + fn from(err: HIDError) -> AuthenticatorError { + AuthenticatorError::HIDError(err) + } +} + +impl From for AuthenticatorError { + fn from(err: CommandError) -> AuthenticatorError { + AuthenticatorError::HIDError(HIDError::Command(err)) + } +} + impl From> for AuthenticatorError { fn from(err: mpsc::SendError) -> AuthenticatorError { AuthenticatorError::InternalError(err.to_string()) diff --git a/third_party/rust/authenticator/src/lib.rs b/third_party/rust/authenticator/src/lib.rs index cfe82deb2b7e..1b271af31588 100644 --- a/third_party/rust/authenticator/src/lib.rs +++ b/third_party/rust/authenticator/src/lib.rs @@ -5,53 +5,15 @@ #[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; @@ -73,10 +35,26 @@ pub use crate::manager::U2FManager; mod capi; pub use crate::capi::*; +pub mod ctap2; +pub use ctap2::attestation::AttestationObject; +pub use ctap2::client_data::{CollectedClientData, CollectedClientDataWrapper}; +pub use ctap2::commands::client_pin::{Pin, PinError}; +pub use ctap2::AssertionObject; + +mod ctap2_capi; +pub use crate::ctap2_capi::*; + pub mod errors; pub mod statecallback; +mod transport; mod virtualdevices; +mod status_update; +pub use status_update::*; + +mod crypto; +pub use crypto::COSEAlgorithm; + // Keep this in sync with the constants in u2fhid-capi.h. bitflags! { pub struct RegisterFlags: u64 { @@ -98,25 +76,28 @@ bitflags! { } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct KeyHandle { pub credential: Vec, pub transports: AuthenticatorTransports, } pub type AppId = Vec; -pub type RegisterResult = (Vec, u2ftypes::U2FDeviceInfo); -pub type SignResult = (AppId, Vec, Vec, u2ftypes::U2FDeviceInfo); + +pub enum RegisterResult { + CTAP1(Vec, u2ftypes::U2FDeviceInfo), + CTAP2(AttestationObject, CollectedClientDataWrapper), +} + +pub enum SignResult { + CTAP1(AppId, Vec, Vec, u2ftypes::U2FDeviceInfo), + CTAP2(AssertionObject, CollectedClientDataWrapper), +} + +pub type ResetResult = (); pub type Result = std::result::Result; -#[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; diff --git a/third_party/rust/authenticator/src/linux/device.rs b/third_party/rust/authenticator/src/linux/device.rs deleted file mode 100644 index 4a0d58d6d745..000000000000 --- a/third_party/rust/authenticator/src/linux/device.rs +++ /dev/null @@ -1,112 +0,0 @@ -/* 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, -} - -impl Device { - pub fn new(path: OsString) -> io::Result { - 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 { - 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 { - 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 { - 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/manager.rs b/third_party/rust/authenticator/src/manager.rs index 06f972dba416..5b95c252d9a9 100644 --- a/third_party/rust/authenticator/src/manager.rs +++ b/third_party/rust/authenticator/src/manager.rs @@ -2,28 +2,40 @@ * 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::AuthenticatorTransport; +use crate::authenticatorservice::{RegisterArgs, RegisterArgsCtap1, SignArgs}; +use crate::consts::PARAMETER_SIZE; +use crate::crypto::COSEAlgorithm; +use crate::ctap2::client_data::{CollectedClientData, WebauthnType}; +use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionOptions}; +use crate::ctap2::commands::make_credentials::MakeCredentials; +use crate::ctap2::commands::make_credentials::MakeCredentialsOptions; +use crate::ctap2::server::{ + PublicKeyCredentialParameters, RelyingParty, RelyingPartyWrapper, RpIdHash, +}; +use crate::errors::*; +use crate::statecallback::StateCallback; +use crate::statemachine::{StateMachine, StateMachineCtap2}; +use crate::{Pin, SignFlags}; +use runloop::RunLoop; 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, + RegisterCtap1 { timeout: u64, - challenge: Vec, - application: crate::AppId, - key_handles: Vec, + ctap_args: RegisterArgsCtap1, status: Sender, callback: StateCallback>, }, - Sign { + RegisterCtap2 { + timeout: u64, + make_credentials: MakeCredentials, + status: Sender, + callback: StateCallback>, + }, + SignCtap1 { flags: crate::SignFlags, timeout: u64, challenge: Vec, @@ -32,7 +44,24 @@ enum QueueAction { status: Sender, callback: StateCallback>, }, + SignCtap2 { + timeout: u64, + get_assertion: GetAssertion, + status: Sender, + callback: StateCallback>, + }, Cancel, + Reset { + timeout: u64, + status: Sender, + callback: StateCallback>, + }, + SetPin { + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + }, } pub struct U2FManager { @@ -50,27 +79,24 @@ impl U2FManager { while alive() { match rx.recv_timeout(Duration::from_millis(50)) { - Ok(QueueAction::Register { - flags, + Ok(QueueAction::RegisterCtap1 { timeout, - challenge, - application, - key_handles, + ctap_args, status, callback, }) => { // This must not block, otherwise we can't cancel. sm.register( - flags, + ctap_args.flags, timeout, - challenge, - application, - key_handles, + ctap_args.challenge, + ctap_args.application, + ctap_args.key_handles, status, callback, ); } - Ok(QueueAction::Sign { + Ok(QueueAction::SignCtap1 { flags, timeout, challenge, @@ -95,6 +121,17 @@ impl U2FManager { // polling thread before the old one has shut down. sm.cancel(); } + Ok(QueueAction::RegisterCtap2 { .. }) => { + // TODO(MS): What to do here? Error out? Silently ignore? + unimplemented!(); + } + Ok(QueueAction::SignCtap2 { .. }) => { + // TODO(MS): What to do here? Error out? Silently ignore? + unimplemented!(); + } + Ok(QueueAction::Reset { .. }) | Ok(QueueAction::SetPin { .. }) => { + unimplemented!(); + } Err(RecvTimeoutError::Disconnected) => { break; } @@ -113,30 +150,30 @@ impl U2FManager { impl AuthenticatorTransport for U2FManager { fn register( &mut self, - flags: crate::RegisterFlags, timeout: u64, - challenge: Vec, - application: crate::AppId, - key_handles: Vec, + ctap_args: RegisterArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + let args = match ctap_args { + RegisterArgs::CTAP1(args) => args, + RegisterArgs::CTAP2(_) => { + return Err(AuthenticatorError::VersionMismatch("U2FManager", 1)); + } + }; + if args.challenge.len() != PARAMETER_SIZE || args.application.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - for key_handle in &key_handles { - if key_handle.credential.len() > 256 { + for key_handle in &args.key_handles { + if key_handle.credential.len() >= 256 { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } - let action = QueueAction::Register { - flags, + let action = QueueAction::RegisterCtap1 { timeout, - challenge, - application, - key_handles, + ctap_args: args, status, callback, }; @@ -145,40 +182,44 @@ impl AuthenticatorTransport for U2FManager { fn sign( &mut self, - flags: crate::SignFlags, timeout: u64, - challenge: Vec, - app_ids: Vec, - key_handles: Vec, + ctap_args: SignArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE { + let args = match ctap_args { + SignArgs::CTAP1(args) => args, + SignArgs::CTAP2(_) => { + return Err(AuthenticatorError::VersionMismatch("U2FManager", 1)); + } + }; + + if args.challenge.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - if app_ids.is_empty() { + if args.app_ids.is_empty() { return Err(AuthenticatorError::InvalidRelyingPartyInput); } - for app_id in &app_ids { + for app_id in &args.app_ids { if app_id.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } - for key_handle in &key_handles { - if key_handle.credential.len() > 256 { + for key_handle in &args.key_handles { + if key_handle.credential.len() >= 256 { return Err(AuthenticatorError::InvalidRelyingPartyInput); } } - let action = QueueAction::Sign { - flags, + let action = QueueAction::SignCtap1 { + flags: args.flags, timeout, - challenge, - app_ids, - key_handles, + challenge: args.challenge, + app_ids: args.app_ids, + key_handles: args.key_handles, status, callback, }; @@ -188,6 +229,34 @@ impl AuthenticatorTransport for U2FManager { fn cancel(&mut self) -> crate::Result<()> { Ok(self.tx.send(QueueAction::Cancel)?) } + + fn reset( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + Ok(self.tx.send(QueueAction::Reset { + timeout, + status, + callback, + })?) + } + + fn set_pin( + &mut self, + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + Ok(self.tx.send(QueueAction::SetPin { + timeout, + new_pin, + status, + callback, + })?) + } } impl Drop for U2FManager { @@ -195,3 +264,313 @@ impl Drop for U2FManager { self.queue.cancel(); } } + +pub struct Manager { + queue: RunLoop, + tx: Sender, +} + +impl Manager { + pub fn new() -> io::Result { + let (tx, rx) = channel(); + + // Start a new work queue thread. + let queue = RunLoop::new(move |alive| { + let mut sm = StateMachineCtap2::new(); + + while alive() { + match rx.recv_timeout(Duration::from_millis(50)) { + Ok(QueueAction::RegisterCtap2 { + timeout, + make_credentials, + status, + callback, + }) => { + // This must not block, otherwise we can't cancel. + sm.register(timeout, make_credentials, status, callback); + } + + Ok(QueueAction::SignCtap2 { + timeout, + get_assertion, + status, + callback, + }) => { + // This must not block, otherwise we can't cancel. + sm.sign(timeout, get_assertion, 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(); + } + + Ok(QueueAction::Reset { + timeout, + status, + callback, + }) => { + // Reset the token: Delete all keypairs, reset PIN + sm.reset(timeout, status, callback); + } + + Ok(QueueAction::SetPin { + timeout, + new_pin, + status, + callback, + }) => { + // This must not block, otherwise we can't cancel. + sm.set_pin(timeout, new_pin, status, callback); + } + + Ok(QueueAction::RegisterCtap1 { + timeout: _, + ctap_args: _, + status: _, + callback: _, + }) => { + // TODO(MS): Remove QueueAction::RegisterCtap1 once U2FManager is deleted. + // The repackaging from CTAP1 to CTAP2 happens in self.register() + unimplemented!(); + } + + Ok(QueueAction::SignCtap1 { + timeout: _, + callback: _, + flags: _, + challenge: _, + app_ids: _, + key_handles: _, + status: _, + }) => { + // TODO(MS): Remove QueueAction::SignCtap1 once U2FManager is deleted. + // The repackaging from CTAP1 to CTAP2 happens in self.sign() + unimplemented!() + } + + Err(RecvTimeoutError::Disconnected) => { + break; + } + + _ => { /* continue */ } + } + } + + // Cancel any ongoing activity. + sm.cancel(); + })?; + + Ok(Self { queue, tx }) + } +} + +impl Drop for Manager { + fn drop(&mut self) { + self.queue.cancel(); + } +} + +impl AuthenticatorTransport for Manager { + fn register( + &mut self, + timeout: u64, + ctap_args: RegisterArgs, + status: Sender, + callback: StateCallback>, + ) -> Result<(), AuthenticatorError> { + let make_credentials = match ctap_args { + RegisterArgs::CTAP2(args) => { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: args.challenge.into(), + origin: args.origin, + cross_origin: false, + token_binding: None, + }; + + MakeCredentials::new( + client_data, + RelyingPartyWrapper::Data(args.relying_party), + Some(args.user), + args.pub_cred_params, + args.exclude_list, + args.options, + args.extensions, + args.pin, + // pin_auth will be filled in Statemachine, once we have a device + ) + } + RegisterArgs::CTAP1(args) => { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: args.challenge.into(), + origin: String::new(), + cross_origin: false, + token_binding: None, + }; + + MakeCredentials::new( + client_data, + RelyingPartyWrapper::Hash(RpIdHash::from(&args.application)?), + None, + vec![PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }], + args.key_handles + .iter() + .map(|k| k.into()) + .collect::>(), + MakeCredentialsOptions { + resident_key: None, + user_verification: None, + }, + Default::default(), + None, + ) + } + }?; + + let action = QueueAction::RegisterCtap2 { + timeout, + make_credentials, + status, + callback, + }; + Ok(self.tx.send(action)?) + } + + fn sign( + &mut self, + timeout: u64, + ctap_args: SignArgs, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + match ctap_args { + SignArgs::CTAP1(args) => { + if args.challenge.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + if args.app_ids.is_empty() { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Get, + challenge: args.challenge.into(), + origin: String::new(), + cross_origin: false, + token_binding: None, + }; + let options = if args.flags == SignFlags::empty() { + GetAssertionOptions::default() + } else { + GetAssertionOptions { + user_verification: Some( + args.flags.contains(SignFlags::REQUIRE_USER_VERIFICATION), + ), + ..GetAssertionOptions::default() + } + }; + + for app_id in &args.app_ids { + if app_id.len() != PARAMETER_SIZE { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + for key_handle in &args.key_handles { + if key_handle.credential.len() >= 256 { + return Err(AuthenticatorError::InvalidRelyingPartyInput); + } + let rp = RelyingPartyWrapper::Hash(RpIdHash::from(&app_id)?); + + let allow_list = vec![key_handle.into()]; + + let get_assertion = GetAssertion::new( + client_data.clone(), + rp, + allow_list, + options, + Default::default(), + None, + )?; + + let action = QueueAction::SignCtap2 { + timeout, + get_assertion, + status: status.clone(), + callback: callback.clone(), + }; + self.tx.send(action)?; + } + } + } + + SignArgs::CTAP2(args) => { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Get, + challenge: args.challenge.into(), + origin: args.origin, + cross_origin: false, + token_binding: None, + }; + + let get_assertion = GetAssertion::new( + client_data.clone(), + RelyingPartyWrapper::Data(RelyingParty { + id: args.relying_party_id, + name: None, + icon: None, + }), + args.allow_list, + args.options, + args.extensions, + args.pin, + )?; + + let action = QueueAction::SignCtap2 { + timeout, + get_assertion, + status, + callback, + }; + self.tx.send(action)?; + } + }; + Ok(()) + } + + fn cancel(&mut self) -> Result<(), AuthenticatorError> { + Ok(self.tx.send(QueueAction::Cancel)?) + } + + fn reset( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> Result<(), AuthenticatorError> { + Ok(self.tx.send(QueueAction::Reset { + timeout, + status, + callback, + })?) + } + + fn set_pin( + &mut self, + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + Ok(self.tx.send(QueueAction::SetPin { + timeout, + new_pin, + status, + callback, + })?) + } +} diff --git a/third_party/rust/authenticator/src/statemachine.rs b/third_party/rust/authenticator/src/statemachine.rs index 32552f8d0ac1..e6a27a7550f8 100644 --- a/third_party/rust/authenticator/src/statemachine.rs +++ b/third_party/rust/authenticator/src/statemachine.rs @@ -3,15 +3,24 @@ * 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::ctap2::commands::client_pin::{ChangeExistingPin, Pin, PinError, SetNewPin}; +use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult}; +use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult}; +use crate::ctap2::commands::reset::Reset; +use crate::ctap2::commands::{ + repackage_pin_errors, CommandError, PinAuthCommand, Request, StatusCode, +}; +use crate::errors::{self, AuthenticatorError, UnsupportedOption}; use crate::statecallback::StateCallback; +use crate::transport::device_selector::{ + BlinkResult, Device, DeviceBuildParameters, DeviceCommand, DeviceSelectorEvent, +}; +use crate::transport::platform::transaction::Transaction; +use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, Nonce}; 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 crate::{send_status, RegisterResult, SignResult, StatusUpdate}; +use std::sync::mpsc::{channel, Sender}; use std::thread; use std::time::Duration; @@ -44,18 +53,6 @@ where (&app_ids[0], vec![]) } -fn send_status(status_mutex: &Mutex>, 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, @@ -80,79 +77,88 @@ impl StateMachine { 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, - }; + let transaction = Transaction::new( + timeout, + cbc.clone(), + status, + move |info, _, status, 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; + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; } - // Sleep a bit before trying again. - thread::sleep(Duration::from_millis(100)); - } + // 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::DeviceUnavailable { - dev_info: dev.get_device_info(), - }, - ); - }); + send_status( + &status, + 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, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + callback.call(Ok(RegisterResult::CTAP1(bytes, dev_info))); + break; + } + + // Sleep a bit before trying again. + thread::sleep(Duration::from_millis(100)); + } + + send_status( + &status, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); + }, + ); self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); } @@ -172,104 +178,108 @@ impl StateMachine { let cbc = callback.clone(); - let status_mutex = Mutex::new(status); + let transaction = Transaction::new( + timeout, + cbc.clone(), + status, + move |info, _, status, alive| { + // Create a new device. + let dev = &mut match Device::new(info) { + Ok(dev) => dev, + _ => return, + }; - 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; - } - } + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; } - // Sleep a bit before trying again. - thread::sleep(Duration::from_millis(100)); - } + // 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; + } - send_status( - &status_mutex, - crate::StatusUpdate::DeviceUnavailable { - dev_info: dev.get_device_info(), - }, - ); - }); + // 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, + 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, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + callback.call(Ok(SignResult::CTAP1( + 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, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); + }, + ); self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); } @@ -281,3 +291,539 @@ impl StateMachine { } } } + +#[derive(Default)] +// TODO(MS): To be renamed to `StateMachine` once U2FManager and the original StateMachine can be removed. +pub struct StateMachineCtap2 { + transaction: Option, +} + +impl StateMachineCtap2 { + pub fn new() -> Self { + Default::default() + } + + fn init_and_select( + info: DeviceBuildParameters, + selector: &Sender, + ctap2_only: bool, + ) -> Option { + // Create a new device. + let mut dev = match Device::new(info) { + Ok(dev) => dev, + Err((e, id)) => { + info!("error happened with device: {}", e); + selector.send(DeviceSelectorEvent::NotAToken(id)).ok()?; + return None; + } + }; + + // Try initializing it. + if let Err(e) = dev.init(Nonce::CreateRandom) { + warn!("error while initializing device: {}", e); + selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok(); + return None; + } + + if ctap2_only && dev.get_authenticator_info().is_none() { + info!("Device does not support CTAP2"); + selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok(); + return None; + } + + let write_only_clone = match dev.clone_device_as_write_only() { + Ok(x) => x, + Err(_) => { + // There is probably something seriously wrong here, if this happens. + // So `NotAToken()` is probably too weak a response here. + warn!("error while cloning device: {:?}", dev.id()); + selector + .send(DeviceSelectorEvent::NotAToken(dev.id())) + .ok()?; + return None; + } + }; + let (tx, rx) = channel(); + selector + .send(DeviceSelectorEvent::ImAToken((write_only_clone, tx))) + .ok()?; + + // Blocking recv. DeviceSelector will tell us what to do + loop { + match rx.recv() { + Ok(DeviceCommand::Blink) => match dev.block_and_blink() { + BlinkResult::DeviceSelected => { + // User selected us. Let DeviceSelector know, so it can cancel all other + // outstanding open blink-requests. + selector + .send(DeviceSelectorEvent::SelectedToken(dev.id())) + .ok()?; + break; + } + BlinkResult::Cancelled => { + info!("Device {:?} was not selected", dev.id()); + return None; + } + }, + Ok(DeviceCommand::Removed) => { + info!("Device {:?} was removed", dev.id()); + return None; + } + Ok(DeviceCommand::Continue) => { + break; + } + Err(_) => { + warn!("Error when trying to receive messages from DeviceSelector! Exiting."); + return None; + } + } + } + Some(dev) + } + + fn ask_user_for_pin( + error: PinError, + status: &Sender, + callback: &StateCallback>, + ) -> Result { + info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply"); + let (tx, rx) = channel(); + send_status(status, crate::StatusUpdate::PinError(error.clone(), tx)); + match rx.recv() { + Ok(pin) => Ok(pin), + Err(_) => { + // recv() can only fail, if the other side is dropping the Sender. We are using this as a trick + // to let the callback decide if this PinError is recoverable (e.g. with User input) or not (e.g. + // locked token). If it is deemed unrecoverable, we error out the 'normal' way with the same error. + error!("Callback dropped the channel, so we forward the error to the results-callback: {:?}", error); + callback.call(Err(AuthenticatorError::PinError(error))); + return Err(()); + } + } + } + + fn determine_pin_auth( + cmd: &mut T, + dev: &mut Device, + status: &Sender, + callback: &StateCallback>, + ) -> Result<(), ()> { + loop { + match cmd.determine_pin_auth(dev) { + Ok(_) => { + break; + } + Err(AuthenticatorError::PinError(e)) => { + let pin = Self::ask_user_for_pin(e, status, callback)?; + cmd.set_pin(Some(pin)); + continue; + } + Err(e) => { + error!("Error when determining pinAuth: {:?}", e); + callback.call(Err(e)); + return Err(()); + } + }; + } + + // CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request + // should either include pinAuth OR uv=true, but not both at the same time. + // Do not set user_verification, if pinAuth is provided + if cmd.pin_auth().is_some() { + cmd.unset_uv_option(); + } + + Ok(()) + } + + pub fn register( + &mut self, + timeout: u64, + params: MakeCredentials, + status: Sender, + callback: StateCallback>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + let cbc = callback.clone(); + let transaction = Transaction::new( + timeout, + cbc.clone(), + status, + move |info, selector, status, _alive| { + let mut dev = match Self::init_and_select(info, &selector, false) { + None => { + return; + } + Some(dev) => dev, + }; + + info!("Device {:?} continues with the register process", dev.id()); + // TODO(baloo): not sure about this, have to ask + // 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; + //} + + // TODO(baloo): not sure about this, have to ask + // 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 */ + //}); + + // TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me + // to modify "params" directly. + let mut makecred = params.clone(); + if params.is_ctap2_request() { + // First check if extensions have been requested that are not supported by the device + if let Some(true) = params.extensions.hmac_secret { + if let Some(auth) = dev.get_authenticator_info() { + if !auth.supports_hmac_secret() { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::HmacSecret, + ))); + return; + } + } + } + + // Second, ask for PIN and get the shared secret + if Self::determine_pin_auth(&mut makecred, &mut dev, &status, &callback) + .is_err() + { + return; + } + } + debug!("------------------------------------------------------------------"); + debug!("{:?}", makecred); + debug!("------------------------------------------------------------------"); + let resp = dev.send_msg(&makecred); + if resp.is_ok() { + send_status( + &status, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + // The DeviceSelector could already be dead, but it might also wait + // for us to respond, in order to cancel all other tokens in case + // we skipped the "blinking"-action and went straight for the actual + // request. + let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id())); + } + match resp { + Ok(MakeCredentialsResult::CTAP2(attestation, client_data)) => { + callback.call(Ok(RegisterResult::CTAP2(attestation, client_data))) + } + Ok(MakeCredentialsResult::CTAP1(data)) => { + callback.call(Ok(RegisterResult::CTAP1(data, dev.get_device_info()))) + } + + Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {} + Err(HIDError::Command(CommandError::StatusCode( + StatusCode::ChannelBusy, + _, + ))) => {} + Err(e) => { + warn!("error happened: {}", e); + callback.call(Err(AuthenticatorError::HIDError(e))); + } + } + }, + ); + + self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); + } + + pub fn sign( + &mut self, + timeout: u64, + params: GetAssertion, + status: Sender, + callback: StateCallback>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + let cbc = callback.clone(); + + let transaction = Transaction::new( + timeout, + callback.clone(), + status, + move |info, selector, status, _alive| { + let mut dev = match Self::init_and_select(info, &selector, false) { + None => { + return; + } + Some(dev) => dev, + }; + + info!("Device {:?} continues with the signing process", dev.id()); + // TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me + // to modify "params" directly. + let mut getassertion = params.clone(); + if params.is_ctap2_request() { + // First check if extensions have been requested that are not supported by the device + if params.extensions.hmac_secret.is_some() { + if let Some(auth) = dev.get_authenticator_info() { + if !auth.supports_hmac_secret() { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::HmacSecret, + ))); + return; + } + } + } + + // Second, ask for PIN and get the shared secret + if Self::determine_pin_auth(&mut getassertion, &mut dev, &status, &callback) + .is_err() + { + return; + } + + // Third, use the shared secret in the extensions, if requested + if let Some(extension) = getassertion.extensions.hmac_secret.as_mut() { + if let Some(secret) = dev.get_shared_secret() { + match extension.calculate(secret) { + Ok(x) => x, + Err(e) => { + callback.call(Err(e)); + return; + } + } + } + } + } + + debug!("------------------------------------------------------------------"); + debug!("{:?}", getassertion); + debug!("------------------------------------------------------------------"); + + let resp = dev.send_msg(&getassertion); + if resp.is_ok() { + send_status( + &status, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + // The DeviceSelector could already be dead, but it might also wait + // for us to respond, in order to cancel all other tokens in case + // we skipped the "blinking"-action and went straight for the actual + // request. + let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id())); + } + match resp { + Ok(GetAssertionResult::CTAP1(resp)) => { + let app_id = getassertion.rp.hash().as_ref().to_vec(); + let key_handle = getassertion.allow_list[0].id.clone(); + + callback.call(Ok(SignResult::CTAP1( + app_id, + key_handle, + resp, + dev.get_device_info(), + ))) + } + Ok(GetAssertionResult::CTAP2(assertion, client_data)) => { + callback.call(Ok(SignResult::CTAP2(assertion, client_data))) + } + // TODO(baloo): if key_handle is invalid for this device, it + // should reply something like: + // CTAP2_ERR_INVALID_CREDENTIAL + // have to check + Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {} + Err(HIDError::Command(CommandError::StatusCode( + StatusCode::ChannelBusy, + _, + ))) => {} + Err(e) => { + warn!("error happened: {}", e); + callback.call(Err(AuthenticatorError::HIDError(e))); + } + } + }, + ); + + self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); + } + + // This blocks. + pub fn cancel(&mut self) { + if let Some(mut transaction) = self.transaction.take() { + info!("Statemachine was cancelled. Cancelling transaction now."); + transaction.cancel(); + } + } + + pub fn reset( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + let cbc = callback.clone(); + + let transaction = Transaction::new( + timeout, + callback.clone(), + status, + move |info, selector, status, _alive| { + let reset = Reset {}; + let mut dev = match Self::init_and_select(info, &selector, true) { + None => { + return; + } + Some(dev) => dev, + }; + + info!("Device {:?} continues with the reset process", dev.id()); + debug!("------------------------------------------------------------------"); + debug!("{:?}", reset); + debug!("------------------------------------------------------------------"); + + let resp = dev.send_cbor(&reset); + if resp.is_ok() { + send_status( + &status, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + // The DeviceSelector could already be dead, but it might also wait + // for us to respond, in order to cancel all other tokens in case + // we skipped the "blinking"-action and went straight for the actual + // request. + let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id())); + } + + match resp { + Ok(()) => callback.call(Ok(())), + Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {} + Err(HIDError::Command(CommandError::StatusCode( + StatusCode::ChannelBusy, + _, + ))) => {} + Err(e) => { + warn!("error happened: {}", e); + callback.call(Err(AuthenticatorError::HIDError(e))); + } + } + }, + ); + + self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); + } + + pub fn set_pin( + &mut self, + timeout: u64, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + + let cbc = callback.clone(); + + let transaction = Transaction::new( + timeout, + callback.clone(), + status, + move |info, selector, status, _alive| { + let mut dev = match Self::init_and_select(info, &selector, true) { + None => { + return; + } + Some(dev) => dev, + }; + + let (mut shared_secret, authinfo) = match dev.establish_shared_secret() { + Ok(s) => s, + Err(e) => { + callback.call(Err(AuthenticatorError::HIDError(e))); + return; + } + }; + + // With CTAP2.1 we will have an adjustable required length for PINs + if new_pin.as_bytes().len() < 4 { + callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort))); + return; + } + + if new_pin.as_bytes().len() > 64 { + callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong( + new_pin.as_bytes().len(), + )))); + return; + } + + // Check if a client-pin is already set, or if a new one should be created + let res = if authinfo.options.client_pin.unwrap_or_default() { + let mut res; + let mut error = PinError::PinRequired; + loop { + let current_pin = match Self::ask_user_for_pin(error, &status, &callback) { + Ok(pin) => pin, + _ => { + return; + } + }; + + res = ChangeExistingPin::new( + &authinfo, + &shared_secret, + ¤t_pin, + &new_pin, + ) + .map_err(HIDError::Command) + .and_then(|msg| dev.send_cbor(&msg)) + .map_err(AuthenticatorError::HIDError) + .map_err(|e| repackage_pin_errors(&mut dev, e)); + + if let Err(AuthenticatorError::PinError(e)) = res { + error = e; + // We need to re-establish the shared secret for the next round. + match dev.establish_shared_secret() { + Ok((s, _)) => { + shared_secret = s; + } + Err(e) => { + callback.call(Err(AuthenticatorError::HIDError(e))); + return; + } + }; + + continue; + } else { + break; + } + } + res + } else { + SetNewPin::new(&authinfo, &shared_secret, &new_pin) + .map_err(HIDError::Command) + .and_then(|msg| dev.send_cbor(&msg)) + .map_err(AuthenticatorError::HIDError) + }; + callback.call(res); + }, + ); + self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); + } +} diff --git a/third_party/rust/authenticator/src/status_update.rs b/third_party/rust/authenticator/src/status_update.rs new file mode 100644 index 000000000000..528ca3afb372 --- /dev/null +++ b/third_party/rust/authenticator/src/status_update.rs @@ -0,0 +1,101 @@ +use super::{u2ftypes, Pin, PinError}; +use serde::ser::{Serialize, SerializeStruct}; +use std::sync::mpsc::Sender; + +#[derive(Debug)] +pub enum StatusUpdate { + /// Device found + DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo }, + /// Device got removed + DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo }, + /// We successfully finished the register or sign request + Success { dev_info: u2ftypes::U2FDeviceInfo }, + /// Sent if a PIN is needed (or was wrong), or some other kind of PIN-related + /// error occurred. The Sender is for sending back a PIN (if needed). + PinError(PinError, Sender), + /// Sent, if multiple devices are found and the user has to select one + SelectDeviceNotice, + /// Sent, once a device was selected (either automatically or by user-interaction) + /// and the register or signing process continues with this device + DeviceSelected(u2ftypes::U2FDeviceInfo), +} + +impl Serialize for StatusUpdate { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_struct("StatusUpdate", 1)?; + match &*self { + StatusUpdate::DeviceAvailable { dev_info } => { + map.serialize_field("DeviceAvailable", &dev_info)? + } + StatusUpdate::DeviceUnavailable { dev_info } => { + map.serialize_field("DeviceUnavailable", &dev_info)? + } + StatusUpdate::Success { dev_info } => map.serialize_field("Success", &dev_info)?, + StatusUpdate::PinError(e, _) => map.serialize_field("PinError", &e)?, + StatusUpdate::SelectDeviceNotice => map.serialize_field("SelectDeviceNotice", &())?, + StatusUpdate::DeviceSelected(dev_info) => { + map.serialize_field("DeviceSelected", &dev_info)? + } + } + map.end() + } +} + +pub(crate) fn send_status(status: &Sender, msg: StatusUpdate) { + match status.send(msg) { + Ok(_) => {} + Err(e) => error!("Couldn't send status: {:?}", e), + }; +} + +#[cfg(test)] +pub mod tests { + use crate::consts::U2F_AUTHENTICATE; + + use super::*; + use crate::consts::Capability; + use serde_json::to_string; + use std::sync::mpsc::channel; + + #[test] + fn serialize_select() { + let st = StatusUpdate::SelectDeviceNotice; + let json = to_string(&st).expect("Failed to serialize"); + assert_eq!(&json, r#"{"SelectDeviceNotice":null}"#); + } + + #[test] + fn serialize_invalid_pin() { + let (tx, _rx) = channel(); + let st = StatusUpdate::PinError(PinError::InvalidPin(Some(3)), tx.clone()); + let json = to_string(&st).expect("Failed to serialize"); + assert_eq!(&json, r#"{"PinError":{"InvalidPin":3}}"#); + + let st = StatusUpdate::PinError(PinError::InvalidPin(None), tx); + let json = to_string(&st).expect("Failed to serialize"); + assert_eq!(&json, r#"{"PinError":{"InvalidPin":null}}"#); + } + + #[test] + fn serialize_success() { + let cap = Capability::WINK | Capability::CBOR; + let dev = u2ftypes::U2FDeviceInfo { + vendor_name: String::from("ABC").into_bytes(), + device_name: String::from("DEF").into_bytes(), + version_interface: 2, + version_major: 5, + version_minor: 4, + version_build: 3, + cap_flags: cap, + }; + let st = StatusUpdate::Success { dev_info: dev }; + let json = to_string(&st).expect("Failed to serialize"); + assert_eq!( + &json, + r#"{"Success":{"vendor_name":[65,66,67],"device_name":[68,69,70],"version_interface":2,"version_major":5,"version_minor":4,"version_build":3,"cap_flags":{"bits":5}}}"# + ); + } +} diff --git a/third_party/rust/authenticator/src/stub/transaction.rs b/third_party/rust/authenticator/src/stub/transaction.rs deleted file mode 100644 index bdf48ef56da1..000000000000 --- a/third_party/rust/authenticator/src/stub/transaction.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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( - timeout: u64, - callback: StateCallback>, - new_device_cb: F, - ) -> crate::Result - 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/transport/device_selector.rs b/third_party/rust/authenticator/src/transport/device_selector.rs new file mode 100644 index 000000000000..0a7a8d53a413 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/device_selector.rs @@ -0,0 +1,514 @@ +use crate::send_status; +use crate::transport::hid::HIDDevice; +pub use crate::transport::platform::device::Device; +use crate::u2ftypes::U2FDevice; +use runloop::RunLoop; +use std::collections::{HashMap, HashSet}; +use std::sync::mpsc::{channel, RecvTimeoutError, Sender}; +use std::time::Duration; + +pub type DeviceID = ::Id; +pub type DeviceBuildParameters = ::BuildParameters; + +trait DeviceSelectorEventMarker {} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlinkResult { + DeviceSelected, + Cancelled, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DeviceCommand { + Blink, + Continue, + Removed, +} + +#[derive(Debug)] +pub enum DeviceSelectorEvent { + Cancel, + Timeout, + DevicesAdded(Vec), + DeviceRemoved(DeviceID), + NotAToken(DeviceID), + ImAToken((Device, Sender)), + SelectedToken(DeviceID), +} + +pub struct DeviceSelector { + /// How to send a message to the event loop + sender: Sender, + /// Thread of the event loop + runloop: RunLoop, +} + +impl DeviceSelector { + pub fn run(status: Sender) -> Self { + let (selector_send, selector_rec) = channel(); + // let new_device_callback = Arc::new(new_device_cb); + let runloop = RunLoop::new(move |alive| { + let mut blinking = false; + // Device was added, but we wait for its response, if it is a token or not + // We save both a write-only copy of the device (for cancellation) and it's thread + let mut waiting_for_response = HashSet::new(); + // All devices that responded with "ImAToken" + let mut tokens = HashMap::new(); + while alive() { + let d = Duration::from_secs(100); + let res = match selector_rec.recv_timeout(d) { + Err(RecvTimeoutError::Disconnected) => { + break; + } + Err(RecvTimeoutError::Timeout) => DeviceSelectorEvent::Timeout, + Ok(res) => res, + }; + + match res { + DeviceSelectorEvent::Timeout | DeviceSelectorEvent::Cancel => { + /* TODO */ + Self::cancel_all(tokens, None); + break; + } + DeviceSelectorEvent::SelectedToken(id) => { + if let Some(dev) = tokens.keys().find(|d| d.id() == id) { + send_status( + &status, + crate::StatusUpdate::DeviceSelected(dev.get_device_info()), + ); + } + Self::cancel_all(tokens, Some(&id)); + break; // We are done here. The selected device continues without us. + } + DeviceSelectorEvent::DevicesAdded(ids) => { + for id in ids { + debug!("Device added event: {:?}", id); + waiting_for_response.insert(id); + } + continue; + } + DeviceSelectorEvent::DeviceRemoved(id) => { + debug!("Device removed event: {:?}", id); + if !waiting_for_response.remove(&id) { + // Note: We _could_ check here if we had multiple tokens and are already blinking + // and the removal of this one leads to only one token left. So we could in theory + // stop blinking and select it right away. At the moment, I think this is a + // too surprising behavior and therefore, we let the remaining device keep on blinking + // since the user could add yet another device, instead of using the remaining one. + tokens.iter().for_each(|(dev, send)| { + if dev.id() == id { + let _ = send.send(DeviceCommand::Removed); + send_status( + &status, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); + } + }); + tokens.retain(|dev, _| dev.id() != id); + if tokens.is_empty() { + blinking = false; + continue; + } + } + // We are already blinking, so no need to run the code below this match + // that figures out if we should blink or not. In fact, currently, we do + // NOT want to run this code again, because if you have 2 blinking tokens + // and one got removed, we WANT the remaining one to continue blinking. + // This is a design choice, because I currently think it is the "less surprising" + // option to the user. + if blinking { + continue; + } + } + DeviceSelectorEvent::NotAToken(id) => { + debug!("Device not a token event: {:?}", id); + waiting_for_response.remove(&id); + } + DeviceSelectorEvent::ImAToken((dev, tx)) => { + send_status( + &status, + crate::StatusUpdate::DeviceAvailable { + dev_info: dev.get_device_info(), + }, + ); + let id = dev.id(); + let _ = waiting_for_response.remove(&id); + tokens.insert(dev, tx.clone()); + if blinking { + // We are already blinking, so this new device should blink too. + if tx.send(DeviceCommand::Blink).is_err() { + // Device thread died in the meantime (which shouldn't happen) + tokens.retain(|dev, _| dev.id() != id); + } + continue; + } + } + } + + // All known devices told us, whether they are tokens or not and we have at least one token + if waiting_for_response.is_empty() && !tokens.is_empty() { + if tokens.len() == 1 { + let (dev, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty + if tx.send(DeviceCommand::Continue).is_err() { + // Device thread died in the meantime (which shouldn't happen). + // Tokens is empty, so we just start over again + continue; + } + send_status( + &status, + crate::StatusUpdate::DeviceSelected(dev.get_device_info()), + ); + Self::cancel_all(tokens, Some(&dev.id())); + break; // We are done here + } else { + blinking = true; + + tokens.iter().for_each(|(_dev, tx)| { + // A send operation can only fail if the receiving end of a channel is disconnected, implying that the data could never be received. + // We ignore errors here for now, but should probably remove the device in such a case (even though it theoretically can't happen) + let _ = tx.send(DeviceCommand::Blink); + }); + send_status(&status, crate::StatusUpdate::SelectDeviceNotice); + } + } + } + }); + Self { + runloop: runloop.unwrap(), // TODO + sender: selector_send, + } + } + + pub fn clone_sender(&self) -> Sender { + self.sender.clone() + } + + fn cancel_all(tokens: HashMap>, exclude: Option<&DeviceID>) { + tokens + .into_keys() + .filter(|x| exclude.map_or(true, |y| y != &x.id())) + .for_each(|mut dev| dev.cancel().unwrap()); // TODO + } + + pub fn stop(&mut self) { + // We ignore a possible error here, since we don't really care + let _ = self.sender.send(DeviceSelectorEvent::Cancel); + self.runloop.cancel(); + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::{ + consts::Capability, + ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions}, + u2ftypes::U2FDeviceInfo, + StatusUpdate, + }; + use std::sync::mpsc::Receiver; + + enum ExpectedUpdate { + DeviceAvailable, + DeviceUnavailable, + SelectDeviceNotice, + DeviceSelected, + } + + fn gen_info(id: String) -> U2FDeviceInfo { + U2FDeviceInfo { + vendor_name: String::from("ExampleVendor").into_bytes(), + device_name: id.into_bytes(), + version_interface: 1, + version_major: 3, + version_minor: 2, + version_build: 1, + cap_flags: Capability::WINK | Capability::CBOR | Capability::NMSG, + } + } + + fn make_device_simple_u2f(dev: &mut Device) { + dev.set_device_info(gen_info(dev.id())); + dev.create_channel(); + } + + fn make_device_with_pin(dev: &mut Device) { + dev.set_device_info(gen_info(dev.id())); + dev.create_channel(); + let info = AuthenticatorInfo { + options: AuthenticatorOptions { + client_pin: Some(true), + ..Default::default() + }, + ..Default::default() + }; + dev.set_authenticator_info(info.clone()); + } + + fn send_i_am_token(dev: &Device, selector: &DeviceSelector) { + selector + .sender + .send(DeviceSelectorEvent::ImAToken(( + dev.clone_device_as_write_only().unwrap(), + dev.sender.clone().unwrap(), + ))) + .unwrap(); + } + + fn send_no_token(dev: &Device, selector: &DeviceSelector) { + selector + .sender + .send(DeviceSelectorEvent::NotAToken(dev.id())) + .unwrap() + } + + fn remove_device(dev: &Device, selector: &DeviceSelector) { + selector + .sender + .send(DeviceSelectorEvent::DeviceRemoved(dev.id())) + .unwrap(); + assert_eq!( + dev.receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Removed + ); + } + + fn recv_status(dev: &Device, status_rx: &Receiver, expected: ExpectedUpdate) { + let res = status_rx.recv().unwrap(); + // Marking it with _, as cargo warns about "unused exp", as it doesn't view the assert below as a usage + let _exp = match expected { + ExpectedUpdate::DeviceAvailable => StatusUpdate::DeviceUnavailable { + dev_info: gen_info(dev.id()), + }, + ExpectedUpdate::DeviceUnavailable => StatusUpdate::DeviceUnavailable { + dev_info: gen_info(dev.id()), + }, + ExpectedUpdate::DeviceSelected => StatusUpdate::DeviceSelected(gen_info(dev.id())), + ExpectedUpdate::SelectDeviceNotice => StatusUpdate::SelectDeviceNotice, + }; + assert!(matches!(res, _exp)); + } + + fn add_devices<'a, T>(iter: T, selector: &DeviceSelector) + where + T: Iterator, + { + selector + .sender + .send(DeviceSelectorEvent::DevicesAdded( + iter.map(|f| f.id()).collect(), + )) + .unwrap(); + } + + #[test] + fn test_device_selector_one_token_no_late_adds() { + let mut devices = vec![ + Device::new("device selector 1").unwrap(), + Device::new("device selector 2").unwrap(), + Device::new("device selector 3").unwrap(), + Device::new("device selector 4").unwrap(), + ]; + + // Make those actual tokens. The rest is interpreted as non-u2f-devices + make_device_with_pin(&mut devices[2]); + let (status_tx, status_rx) = channel(); + let selector = DeviceSelector::run(status_tx); + + // Adding all + add_devices(devices.iter(), &selector); + devices.iter().filter(|d| !d.is_u2f()).for_each(|d| { + send_no_token(d, &selector); + }); + + send_i_am_token(&devices[2], &selector); + + recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); + assert_eq!( + devices[2].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Continue + ); + recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceSelected); + } + + #[test] + fn test_device_selector_all_pins_with_late_add() { + let mut devices = vec![ + Device::new("device selector 1").unwrap(), + Device::new("device selector 2").unwrap(), + Device::new("device selector 3").unwrap(), + Device::new("device selector 4").unwrap(), + Device::new("device selector 5").unwrap(), + Device::new("device selector 6").unwrap(), + ]; + + // Make those actual tokens. The rest is interpreted as non-u2f-devices + make_device_with_pin(&mut devices[2]); + make_device_with_pin(&mut devices[4]); + make_device_with_pin(&mut devices[5]); + + let (status_tx, status_rx) = channel(); + let selector = DeviceSelector::run(status_tx); + + // Adding all, except the last one (we simulate that this one is not yet plugged in) + add_devices(devices.iter().take(5), &selector); + + // Interleave tokens and non-tokens + send_i_am_token(&devices[2], &selector); + recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); + + devices.iter().filter(|d| !d.is_u2f()).for_each(|d| { + send_no_token(d, &selector); + }); + + send_i_am_token(&devices[4], &selector); + recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); + + // We added 2 devices that are tokens. They should get the blink-command now + assert_eq!( + devices[2].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + assert_eq!( + devices[4].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); + + // Plug in late device + send_i_am_token(&devices[5], &selector); + recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable); + assert_eq!( + devices[5].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + } + + #[test] + fn test_device_selector_no_pins_late_mixed_adds() { + // Multiple tokes, none of them support a PIN + let mut devices = vec![ + Device::new("device selector 1").unwrap(), + Device::new("device selector 2").unwrap(), + Device::new("device selector 3").unwrap(), + Device::new("device selector 4").unwrap(), + Device::new("device selector 5").unwrap(), + Device::new("device selector 6").unwrap(), + Device::new("device selector 7").unwrap(), + ]; + + // Make those actual tokens. The rest is interpreted as non-u2f-devices + make_device_simple_u2f(&mut devices[2]); + make_device_simple_u2f(&mut devices[4]); + make_device_simple_u2f(&mut devices[5]); + + let (status_tx, status_rx) = channel(); + let selector = DeviceSelector::run(status_tx); + + // Adding all, except the last one (we simulate that this one is not yet plugged in) + add_devices(devices.iter().take(5), &selector); + + // Interleave tokens and non-tokens + send_i_am_token(&devices[2], &selector); + recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); + + devices.iter().filter(|d| !d.is_u2f()).for_each(|d| { + send_no_token(d, &selector); + }); + + send_i_am_token(&devices[4], &selector); + recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); + + // We added 2 devices that are tokens. They should get the blink-command now + assert_eq!( + devices[2].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + assert_eq!( + devices[4].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); + + // Plug in late device + send_i_am_token(&devices[5], &selector); + recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable); + assert_eq!( + devices[5].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + // Remove device again + remove_device(&devices[5], &selector); + recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceUnavailable); + + // Now we add a token that has a PIN, it should not get "Continue" but "Blink" + make_device_with_pin(&mut devices[6]); + send_i_am_token(&devices[6], &selector); + recv_status(&devices[6], &status_rx, ExpectedUpdate::DeviceAvailable); + assert_eq!( + devices[6].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + } + + #[test] + fn test_device_selector_mixed_pins_remove_all() { + // Multiple tokes, none of them support a PIN, so we should get Continue-commands + // for all of them + let mut devices = vec![ + Device::new("device selector 1").unwrap(), + Device::new("device selector 2").unwrap(), + Device::new("device selector 3").unwrap(), + Device::new("device selector 4").unwrap(), + Device::new("device selector 5").unwrap(), + Device::new("device selector 6").unwrap(), + ]; + + // Make those actual tokens. The rest is interpreted as non-u2f-devices + make_device_with_pin(&mut devices[2]); + make_device_with_pin(&mut devices[4]); + make_device_with_pin(&mut devices[5]); + + let (status_tx, status_rx) = channel(); + let selector = DeviceSelector::run(status_tx); + + // Adding all, except the last one (we simulate that this one is not yet plugged in) + add_devices(devices.iter(), &selector); + + devices.iter().for_each(|d| { + if d.is_u2f() { + send_i_am_token(d, &selector); + } else { + send_no_token(d, &selector); + } + }); + + for idx in [2, 4, 5] { + recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceAvailable); + assert_eq!( + devices[idx].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Blink + ); + } + recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); + + // Remove all tokens + for idx in [2, 4, 5] { + remove_device(&devices[idx], &selector); + recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceUnavailable); + } + + // Adding one again + send_i_am_token(&devices[4], &selector); + recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); + + // This should now get a "Continue" instead of "Blinking", because it's the only device + assert_eq!( + devices[4].receiver.as_ref().unwrap().recv().unwrap(), + DeviceCommand::Continue + ); + recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceSelected); + } +} diff --git a/third_party/rust/authenticator/src/transport/errors.rs b/third_party/rust/authenticator/src/transport/errors.rs new file mode 100644 index 000000000000..463b7cb5cb35 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/errors.rs @@ -0,0 +1,98 @@ +use crate::consts::{SW_CONDITIONS_NOT_SATISFIED, SW_NO_ERROR, SW_WRONG_DATA, SW_WRONG_LENGTH}; +use crate::ctap2::commands::CommandError; +use std::fmt; +use std::io; +use std::path; + +#[allow(unused)] +#[derive(Debug, PartialEq, Eq)] +pub enum ApduErrorStatus { + ConditionsNotSatisfied, + WrongData, + WrongLength, + Unknown([u8; 2]), +} + +impl ApduErrorStatus { + pub fn from(status: [u8; 2]) -> Result<(), ApduErrorStatus> { + match status { + s if s == SW_NO_ERROR => Ok(()), + s if s == SW_CONDITIONS_NOT_SATISFIED => Err(ApduErrorStatus::ConditionsNotSatisfied), + s if s == SW_WRONG_DATA => Err(ApduErrorStatus::WrongData), + s if s == SW_WRONG_LENGTH => Err(ApduErrorStatus::WrongLength), + other => Err(ApduErrorStatus::Unknown(other)), + } + } +} + +impl fmt::Display for ApduErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ApduErrorStatus::ConditionsNotSatisfied => write!(f, "Apdu: condition not satisfied"), + ApduErrorStatus::WrongData => write!(f, "Apdu: wrong data"), + ApduErrorStatus::WrongLength => write!(f, "Apdu: wrong length"), + ApduErrorStatus::Unknown(ref u) => write!(f, "Apdu: unknown error: {:?}", u), + } + } +} + +#[allow(unused)] +#[derive(Debug)] +pub enum HIDError { + /// Transport replied with a status not expected + DeviceError, + UnexpectedInitReplyLen, + NonceMismatch, + DeviceNotInitialized, + DeviceNotSupported, + UnsupportedCommand, + UnexpectedVersion, + IO(Option, io::Error), + UnexpectedCmd(u8), + Command(CommandError), + ApduStatus(ApduErrorStatus), +} + +impl From for HIDError { + fn from(e: io::Error) -> HIDError { + HIDError::IO(None, e) + } +} + +impl From for HIDError { + fn from(e: CommandError) -> HIDError { + HIDError::Command(e) + } +} + +impl From for HIDError { + fn from(e: ApduErrorStatus) -> HIDError { + HIDError::ApduStatus(e) + } +} + +impl fmt::Display for HIDError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + HIDError::UnexpectedInitReplyLen => { + write!(f, "Error: Unexpected reply len when initilizaling") + } + HIDError::NonceMismatch => write!(f, "Error: Nonce mismatch"), + HIDError::DeviceError => write!(f, "Error: device returned error"), + HIDError::DeviceNotInitialized => write!(f, "Error: using not initiliazed device"), + HIDError::DeviceNotSupported => { + write!(f, "Error: requested operation is not available on device") + } + HIDError::UnexpectedVersion => write!(f, "Error: Unexpected protocol version"), + HIDError::UnsupportedCommand => { + write!(f, "Error: command is not supported on this device") + } + HIDError::IO(ref p, ref e) => write!(f, "Error: Ioerror({:?}): {}", p, e), + HIDError::Command(ref e) => write!(f, "Error: Error issuing command: {}", e), + HIDError::UnexpectedCmd(s) => write!(f, "Error: Unexpected status: {}", s), + HIDError::ApduStatus(ref status) => { + write!(f, "Error: Unexpected apdu status: {:?}", status) + } + } + } +} diff --git a/third_party/rust/authenticator/src/freebsd/device.rs b/third_party/rust/authenticator/src/transport/freebsd/device.rs similarity index 68% rename from third_party/rust/authenticator/src/freebsd/device.rs rename to third_party/rust/authenticator/src/transport/freebsd/device.rs index 32069cd5f9ec..a35504f47fc1 100644 --- a/third_party/rust/authenticator/src/freebsd/device.rs +++ b/third_party/rust/authenticator/src/transport/freebsd/device.rs @@ -10,9 +10,10 @@ 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::transport::platform::uhid; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::from_unix_result; +use crate::util::io_err; #[derive(Debug)] pub struct Device { @@ -35,8 +36,51 @@ impl Device { }) } - pub fn is_u2f(&self) -> bool { - uhid::is_u2f_device(self.fd) + pub fn is_u2f(&mut self) -> bool { + if !uhid::is_u2f_device(self.fd) { + 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 { std::mem::zeroed() }; + pfd.fd = self.fd; + 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")) } } diff --git a/third_party/rust/authenticator/src/freebsd/mod.rs b/third_party/rust/authenticator/src/transport/freebsd/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/freebsd/mod.rs rename to third_party/rust/authenticator/src/transport/freebsd/mod.rs diff --git a/third_party/rust/authenticator/src/freebsd/monitor.rs b/third_party/rust/authenticator/src/transport/freebsd/monitor.rs similarity index 100% rename from third_party/rust/authenticator/src/freebsd/monitor.rs rename to third_party/rust/authenticator/src/transport/freebsd/monitor.rs diff --git a/third_party/rust/authenticator/src/freebsd/transaction.rs b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs similarity index 97% rename from third_party/rust/authenticator/src/freebsd/transaction.rs rename to third_party/rust/authenticator/src/transport/freebsd/transaction.rs index e7cd00f1845a..5d76847f5b97 100644 --- a/third_party/rust/authenticator/src/freebsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs @@ -3,8 +3,8 @@ * 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 crate::transport::platformmonitor::Monitor; use runloop::RunLoop; use std::ffi::OsString; diff --git a/third_party/rust/authenticator/src/freebsd/uhid.rs b/third_party/rust/authenticator/src/transport/freebsd/uhid.rs similarity index 100% rename from third_party/rust/authenticator/src/freebsd/uhid.rs rename to third_party/rust/authenticator/src/transport/freebsd/uhid.rs diff --git a/third_party/rust/authenticator/src/transport/hid.rs b/third_party/rust/authenticator/src/transport/hid.rs new file mode 100644 index 000000000000..1dba282e72a2 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/hid.rs @@ -0,0 +1,136 @@ +use crate::consts::{HIDCmd, CID_BROADCAST}; +use crate::crypto::ECDHSecret; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::transport::{errors::HIDError, Nonce}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp}; +use rand::{thread_rng, RngCore}; +use std::cmp::Eq; +use std::fmt; +use std::hash::Hash; +use std::io; + +pub trait HIDDevice +where + Self: io::Read, + Self: io::Write, + Self: U2FDevice, + Self: Sized, + Self: fmt::Debug, +{ + type BuildParameters: Sized; + type Id: fmt::Debug + PartialEq + Eq + Hash + Sized; + + // Open device, verify that it is indeed a CTAP device and potentially read initial values + fn new(parameters: Self::BuildParameters) -> Result; + fn id(&self) -> Self::Id; + fn initialized(&self) -> bool; + // Check if the device is actually a token + fn is_u2f(&self) -> bool; + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>; + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo); + fn set_shared_secret(&mut self, secret: ECDHSecret); + fn get_shared_secret(&self) -> Option<&ECDHSecret>; + fn clone_device_as_write_only(&self) -> Result; + + // Initialize on a protocol-level + fn initialize(&mut self, noncecmd: Nonce) -> Result<(), HIDError> { + if self.initialized() { + return Ok(()); + } + + let nonce = match noncecmd { + Nonce::Use(x) => x, + Nonce::CreateRandom => { + let mut nonce = [0u8; 8]; + thread_rng().fill_bytes(&mut nonce); + nonce + } + }; + + // Send Init to broadcast address to create a new channel + self.set_cid(CID_BROADCAST); + let (cmd, raw) = self.sendrecv(HIDCmd::Init, &nonce)?; + if cmd != HIDCmd::Init { + return Err(HIDError::DeviceError); + } + + let rsp = U2FHIDInitResp::read(&raw, &nonce)?; + // Get the new Channel ID + self.set_cid(rsp.cid); + + let vendor = self + .get_property("Manufacturer") + .unwrap_or_else(|_| String::from("Unknown Vendor")); + let product = self + .get_property("Product") + .unwrap_or_else(|_| String::from("Unknown Device")); + + self.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, + }); + + // A CTAPHID host SHALL accept a response size that is longer than the + // anticipated size to allow for future extensions of the protocol, yet + // maintaining backwards compatibility. Future versions will maintain + // the response structure of the current version, but additional fields + // may be added. + + Ok(()) + } + + fn sendrecv(&mut self, cmd: HIDCmd, send: &[u8]) -> io::Result<(HIDCmd, Vec)> { + let cmd: u8 = cmd.into(); + self.u2f_write(cmd, send)?; + loop { + let (cmd, data) = self.u2f_read()?; + if cmd != HIDCmd::Keepalive { + break Ok((cmd, data)); + } + } + } + + fn u2f_write(&mut self, cmd: u8, send: &[u8]) -> io::Result<()> { + let mut count = U2FHIDInit::write(self, cmd, send)?; + + // Send continuation packets. + let mut sequence = 0u8; + while count < send.len() { + count += U2FHIDCont::write(self, sequence, &send[count..])?; + sequence += 1; + } + + Ok(()) + } + + fn u2f_read(&mut self) -> io::Result<(HIDCmd, Vec)> { + // 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 (cmd, data) = { + let (cmd, mut data) = U2FHIDInit::read(self)?; + + trace!("init frame data read: {:04X?}", &data); + let mut sequence = 0u8; + while data.len() < data.capacity() { + let max = data.capacity() - data.len(); + data.extend_from_slice(&U2FHIDCont::read(self, sequence, max)?); + sequence += 1; + } + (cmd, data) + }; + trace!("u2f_read({:?}) cmd={:?}: {:04X?}", self.id(), cmd, &data); + Ok((cmd, data)) + } + + fn cancel(&mut self) -> Result<(), HIDError> { + let cancel: u8 = HIDCmd::Cancel.into(); + self.u2f_write(cancel, &[])?; + Ok(()) + } +} diff --git a/third_party/rust/authenticator/src/hidproto.rs b/third_party/rust/authenticator/src/transport/hidproto.rs similarity index 100% rename from third_party/rust/authenticator/src/hidproto.rs rename to third_party/rust/authenticator/src/transport/hidproto.rs diff --git a/third_party/rust/authenticator/src/transport/linux/device.rs b/third_party/rust/authenticator/src/transport/linux/device.rs new file mode 100644 index 000000000000..08786766d04a --- /dev/null +++ b/third_party/rust/authenticator/src/transport/linux/device.rs @@ -0,0 +1,182 @@ +/* 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::consts::CID_BROADCAST; +use crate::transport::hid::HIDDevice; +use crate::transport::platform::{hidraw, monitor}; +use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use crate::util::from_unix_result; +use std::fs::OpenOptions; +use std::hash::{Hash, Hasher}; +use std::io; +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct Device { + path: PathBuf, + fd: std::fs::File, + in_rpt_size: usize, + out_rpt_size: usize, + cid: [u8; 4], + dev_info: Option, + secret: Option, + authenticator_info: Option, +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.path == other.path + } +} + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.path.hash(state); + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let bufp = buf.as_mut_ptr() as *mut libc::c_void; + let rv = unsafe { libc::read(self.fd.as_raw_fd(), bufp, buf.len()) }; + from_unix_result(rv as usize) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result { + let bufp = buf.as_ptr() as *const libc::c_void; + let rv = unsafe { libc::write(self.fd.as_raw_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 { + 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); + } +} + +impl HIDDevice for Device { + type BuildParameters = PathBuf; + type Id = PathBuf; + + fn new(path: PathBuf) -> Result { + debug!("Opening device {:?}", path); + let fd = OpenOptions::new() + .read(true) + .write(true) + .open(&path) + .map_err(|e| (HIDError::IO(Some(path.clone()), e), path.clone()))?; + let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd.as_raw_fd()); + let res = Self { + path, + fd, + in_rpt_size, + out_rpt_size, + cid: CID_BROADCAST, + dev_info: None, + secret: None, + authenticator_info: None, + }; + if res.is_u2f() { + info!("new device {:?}", res.path); + Ok(res) + } else { + Err((HIDError::DeviceNotSupported, res.path.clone())) + } + } + + fn initialized(&self) -> bool { + // During successful init, the broadcast channel id gets repplaced by an actual one + self.cid != CID_BROADCAST + } + + fn id(&self) -> Self::Id { + self.path.clone() + } + + fn is_u2f(&self) -> bool { + hidraw::is_u2f_device(self.fd.as_raw_fd()) + } + + fn get_shared_secret(&self) -> Option<&ECDHSecret> { + self.secret.as_ref() + } + + fn set_shared_secret(&mut self, secret: ECDHSecret) { + self.secret = Some(secret); + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + /// This is used for cancellation of blocking read()-requests. + /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. + fn clone_device_as_write_only(&self) -> Result { + let fd = OpenOptions::new() + .write(true) + .open(&self.path) + .map_err(|e| (HIDError::IO(Some(self.path.clone()), e)))?; + + Ok(Self { + path: self.path.clone(), + fd, + in_rpt_size: self.in_rpt_size, + out_rpt_size: self.out_rpt_size, + cid: self.cid, + dev_info: self.dev_info.clone(), + secret: self.secret.clone(), + authenticator_info: self.authenticator_info.clone(), + }) + } +} + +impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/linux/hidraw.rs b/third_party/rust/authenticator/src/transport/linux/hidraw.rs similarity index 98% rename from third_party/rust/authenticator/src/linux/hidraw.rs rename to third_party/rust/authenticator/src/transport/linux/hidraw.rs index 662ea83298f3..16d687f358b7 100644 --- a/third_party/rust/authenticator/src/linux/hidraw.rs +++ b/third_party/rust/authenticator/src/transport/linux/hidraw.rs @@ -10,7 +10,7 @@ use std::os::unix::io::RawFd; use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE}; use crate::consts::MAX_HID_RPT_SIZE; -use crate::hidproto::*; +use crate::transport::hidproto::*; use crate::util::{from_unix_result, io_err}; #[allow(non_camel_case_types)] diff --git a/third_party/rust/authenticator/src/linux/hidwrapper.h b/third_party/rust/authenticator/src/transport/linux/hidwrapper.h similarity index 100% rename from third_party/rust/authenticator/src/linux/hidwrapper.h rename to third_party/rust/authenticator/src/transport/linux/hidwrapper.h diff --git a/third_party/rust/authenticator/src/linux/hidwrapper.rs b/third_party/rust/authenticator/src/transport/linux/hidwrapper.rs similarity index 94% rename from third_party/rust/authenticator/src/linux/hidwrapper.rs rename to third_party/rust/authenticator/src/transport/linux/hidwrapper.rs index ea1a39051b63..82aabc630101 100644 --- a/third_party/rust/authenticator/src/linux/hidwrapper.rs +++ b/third_party/rust/authenticator/src/transport/linux/hidwrapper.rs @@ -46,3 +46,6 @@ include!("ioctl_aarch64be.rs"); #[cfg(all(target_arch = "s390x", target_endian = "big"))] include!("ioctl_s390xbe.rs"); + +#[cfg(all(target_arch = "riscv64", target_endian = "little"))] +include!("ioctl_riscv64.rs"); diff --git a/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_armle.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_armle.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_mips64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_mips64le.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_mipsle.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_mipsle.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_x86.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_x86.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs diff --git a/third_party/rust/authenticator/src/linux/ioctl_x86_64.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/ioctl_x86_64.rs rename to third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs new file mode 100644 index 000000000000..a784e9bf4600 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/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/transport/linux/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/linux/mod.rs rename to third_party/rust/authenticator/src/transport/linux/mod.rs diff --git a/third_party/rust/authenticator/src/linux/monitor.rs b/third_party/rust/authenticator/src/transport/linux/monitor.rs similarity index 65% rename from third_party/rust/authenticator/src/linux/monitor.rs rename to third_party/rust/authenticator/src/transport/linux/monitor.rs index 595e2f3ddfab..003a034c87b7 100644 --- a/third_party/rust/authenticator/src/linux/monitor.rs +++ b/third_party/rust/authenticator/src/transport/linux/monitor.rs @@ -2,14 +2,16 @@ * 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::transport::device_selector::DeviceSelectorEvent; use libc::{c_int, c_short, c_ulong}; use libudev::EventType; use runloop::RunLoop; use std::collections::HashMap; -use std::ffi::OsString; +use std::error::Error; use std::io; use std::os::unix::io::AsRawFd; -use std::sync::Arc; +use std::path::PathBuf; +use std::sync::{mpsc::Sender, Arc}; const UDEV_SUBSYSTEM: &str = "hidraw"; const POLLIN: c_short = 0x0001; @@ -29,34 +31,54 @@ fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> { pub struct Monitor where - F: Fn(OsString, &dyn Fn() -> bool) + Sync, + F: Fn(PathBuf, Sender, Sender, &dyn Fn() -> bool) + + Sync, { - runloops: HashMap, + runloops: HashMap, new_device_cb: Arc, + selector_sender: Sender, + status_sender: Sender, } impl Monitor where - F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static, + F: Fn(PathBuf, Sender, Sender, &dyn Fn() -> bool) + + Send + + Sync + + 'static, { - pub fn new(new_device_cb: F) -> Self { + pub fn new( + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, + ) -> Self { Self { runloops: HashMap::new(), new_device_cb: Arc::new(new_device_cb), + selector_sender, + status_sender, } } - pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box> { 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 paths: Vec = enumerator + .scan_devices()? + .filter_map(|dev| dev.devnode().map(|p| p.to_owned())) + .collect(); + + // Add them all in one go to avoid race conditions in DeviceSelector + // (8 devices should be added, but the first returns already before all + // others are known to DeviceSelector) + self.selector_sender + .send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?; + for path in paths { + self.add_device(path); } let mut monitor = libudev::Monitor::new(&ctx)?; @@ -86,13 +108,13 @@ where } fn process_event(&mut self, event: &libudev::Event) { - let path = event - .device() - .devnode() - .map(|dn| dn.to_owned().into_os_string()); + let path = event.device().devnode().map(|dn| dn.to_owned()); match (event.event_type(), path) { (EventType::Add, Some(path)) => { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DevicesAdded(vec![path.clone()])); self.add_device(path); } (EventType::Remove, Some(path)) => { @@ -102,15 +124,16 @@ where } } - fn add_device(&mut self, path: OsString) { + fn add_device(&mut self, path: PathBuf) { let f = self.new_device_cb.clone(); let key = path.clone(); - + let selector_sender = self.selector_sender.clone(); + let status_sender = self.status_sender.clone(); debug!("Adding device {}", path.to_string_lossy()); let runloop = RunLoop::new(move |alive| { if alive() { - f(path, alive); + f(path, selector_sender, status_sender, alive); } }); @@ -119,9 +142,12 @@ where } } - fn remove_device(&mut self, path: &OsString) { - debug!("Removing device {}", path.to_string_lossy()); + fn remove_device(&mut self, path: &PathBuf) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(path.clone())); + debug!("Removing device {}", path.to_string_lossy()); if let Some(runloop) = self.runloops.remove(path) { runloop.cancel(); } @@ -135,7 +161,7 @@ where } } -pub fn get_property_linux(path: &OsString, prop_name: &str) -> io::Result { +pub fn get_property_linux(path: &PathBuf, prop_name: &str) -> io::Result { let ctx = libudev::Context::new()?; let mut enumerator = libudev::Enumerator::new(&ctx)?; diff --git a/third_party/rust/authenticator/src/netbsd/transaction.rs b/third_party/rust/authenticator/src/transport/linux/transaction.rs similarity index 56% rename from third_party/rust/authenticator/src/netbsd/transaction.rs rename to third_party/rust/authenticator/src/transport/linux/transaction.rs index 21ac212569c0..35baa8f3c438 100644 --- a/third_party/rust/authenticator/src/netbsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/linux/transaction.rs @@ -3,30 +3,45 @@ * 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 crate::transport::device_selector::{ + DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent, +}; +use crate::transport::platform::monitor::Monitor; use runloop::RunLoop; +use std::sync::mpsc::Sender; pub struct Transaction { // Handle to the thread loop. - thread: Option, + thread: RunLoop, + device_selector: DeviceSelector, } impl Transaction { pub fn new( timeout: u64, callback: StateCallback>, + status: Sender, new_device_cb: F, ) -> crate::Result where - F: Fn(Fd, &dyn Fn() -> bool) + Sync + Send + 'static, + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, T: 'static, { + let status_sender = status.clone(); + let device_selector = DeviceSelector::run(status); + let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback @@ -42,12 +57,14 @@ impl Transaction { .map_err(|_| errors::AuthenticatorError::Platform)?; Ok(Self { - thread: Some(thread), + thread, + device_selector, }) } pub fn cancel(&mut self) { - // This must never be None. - self.thread.take().unwrap().cancel(); + info!("Transaction was cancelled."); + self.device_selector.stop(); + self.thread.cancel(); } } diff --git a/third_party/rust/authenticator/src/macos/device.rs b/third_party/rust/authenticator/src/transport/macos/device.rs similarity index 56% rename from third_party/rust/authenticator/src/macos/device.rs rename to third_party/rust/authenticator/src/transport/macos/device.rs index 425a27959b27..e14d837e1c2f 100644 --- a/third_party/rust/authenticator/src/macos/device.rs +++ b/third_party/rust/authenticator/src/transport/macos/device.rs @@ -5,11 +5,15 @@ extern crate log; use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; -use crate::platform::iokit::*; +use crate::transport::hid::HIDDevice; +use crate::transport::platform::iokit::*; +use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError}; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use core_foundation::base::*; use core_foundation::string::*; use std::convert::TryInto; +use std::fmt; +use std::hash::{Hash, Hasher}; use std::io; use std::io::{Read, Write}; use std::sync::mpsc::{Receiver, RecvTimeoutError}; @@ -20,25 +24,13 @@ const READ_TIMEOUT: u64 = 15; pub struct Device { device_ref: IOHIDDeviceRef, cid: [u8; 4], - report_rx: Receiver>, + report_rx: Option>>, dev_info: Option, + secret: Option, + authenticator_info: Option, } impl Device { - pub fn new(dev_ids: (IOHIDDeviceRef, Receiver>)) -> io::Result { - 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 { let prop_ref = IOHIDDeviceGetProperty( self.device_ref, @@ -68,25 +60,45 @@ impl Device { } } +impl fmt::Debug for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Device").field("cid", &self.cid).finish() + } +} + impl PartialEq for Device { fn eq(&self, other_device: &Device) -> bool { self.device_ref == other_device.device_ref } } +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.device_ref.hash(state); + } +} + impl Read for Device { fn read(&mut self, mut bytes: &mut [u8]) -> io::Result { - 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) + if let Some(rx) = &self.report_rx { + let timeout = Duration::from_secs(READ_TIMEOUT); + let data = match 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) + } else { + Err(io::Error::from(io::ErrorKind::Unsupported)) + } } } @@ -154,3 +166,62 @@ impl U2FDevice for Device { self.dev_info = Some(dev_info); } } + +impl HIDDevice for Device { + type BuildParameters = (IOHIDDeviceRef, Receiver>); + type Id = IOHIDDeviceRef; + + fn new(dev_ids: Self::BuildParameters) -> Result { + let (device_ref, report_rx) = dev_ids; + Ok(Self { + device_ref, + cid: CID_BROADCAST, + report_rx: Some(report_rx), + dev_info: None, + secret: None, + authenticator_info: None, + }) + } + + fn initialized(&self) -> bool { + self.cid != CID_BROADCAST + } + + fn id(&self) -> Self::Id { + self.device_ref + } + + fn is_u2f(&self) -> bool { + true + } + fn get_shared_secret(&self) -> Option<&ECDHSecret> { + self.secret.as_ref() + } + + fn set_shared_secret(&mut self, secret: ECDHSecret) { + self.secret = Some(secret); + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + /// This is used for cancellation of blocking read()-requests. + /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. + fn clone_device_as_write_only(&self) -> Result { + Ok(Self { + device_ref: self.device_ref, + cid: self.cid, + report_rx: None, + dev_info: self.dev_info.clone(), + secret: self.secret.clone(), + authenticator_info: self.authenticator_info.clone(), + }) + } +} + +impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/macos/iokit.rs b/third_party/rust/authenticator/src/transport/macos/iokit.rs similarity index 100% rename from third_party/rust/authenticator/src/macos/iokit.rs rename to third_party/rust/authenticator/src/transport/macos/iokit.rs diff --git a/third_party/rust/authenticator/src/macos/mod.rs b/third_party/rust/authenticator/src/transport/macos/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/macos/mod.rs rename to third_party/rust/authenticator/src/transport/macos/mod.rs diff --git a/third_party/rust/authenticator/src/macos/monitor.rs b/third_party/rust/authenticator/src/transport/macos/monitor.rs similarity index 75% rename from third_party/rust/authenticator/src/macos/monitor.rs rename to third_party/rust/authenticator/src/transport/macos/monitor.rs index 189366f9d10f..eafa206505e9 100644 --- a/third_party/rust/authenticator/src/macos/monitor.rs +++ b/third_party/rust/authenticator/src/transport/macos/monitor.rs @@ -5,7 +5,8 @@ extern crate libc; extern crate log; -use crate::platform::iokit::*; +use crate::transport::device_selector::DeviceSelectorEvent; +use crate::transport::platform::iokit::*; use crate::util::io_err; use core_foundation::base::*; use core_foundation::runloop::*; @@ -22,20 +23,40 @@ struct DeviceData { pub struct Monitor where - F: Fn((IOHIDDeviceRef, Receiver>), &dyn Fn() -> bool) + Sync, + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, { manager: IOHIDManagerRef, // Keep alive until the monitor goes away. _matcher: IOHIDDeviceMatcher, map: HashMap, new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, } impl Monitor where - F: Fn((IOHIDDeviceRef, Receiver>), &dyn Fn() -> bool) + Sync + 'static, + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, { - pub fn new(new_device_cb: F) -> Self { + pub fn new( + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, + ) -> Self { let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) }; // Match FIDO devices only. @@ -47,6 +68,8 @@ where _matcher, new_device_cb, map: HashMap::new(), + selector_sender, + status_sender, } } @@ -98,6 +121,9 @@ where fn remove_device(&mut self, device_ref: IOHIDDeviceRef) { if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(device_ref)); // Dropping `tx` will make Device::read() fail eventually. drop(tx); @@ -137,7 +163,11 @@ where device_ref: IOHIDDeviceRef, ) { let this = unsafe { &mut *(context as *mut Self) }; - + let _ = this + .selector_sender + .send(DeviceSelectorEvent::DevicesAdded(vec![device_ref])); + let selector_sender = this.selector_sender.clone(); + let status_sender = this.status_sender.clone(); let (tx, rx) = channel(); let f = &this.new_device_cb; @@ -145,7 +175,7 @@ where let runloop = RunLoop::new(move |alive| { // Ensure that the runloop is still alive. if alive() { - f((device_ref, rx), alive); + f((device_ref, rx), selector_sender, status_sender, alive); } }); @@ -167,7 +197,14 @@ where impl Drop for Monitor where - F: Fn((IOHIDDeviceRef, Receiver>), &dyn Fn() -> bool) + Sync, + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, { 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/transport/macos/transaction.rs similarity index 77% rename from third_party/rust/authenticator/src/macos/transaction.rs rename to third_party/rust/authenticator/src/transport/macos/transaction.rs index 697730a41a41..7387a0ac8d9c 100644 --- a/third_party/rust/authenticator/src/macos/transaction.rs +++ b/third_party/rust/authenticator/src/transport/macos/transaction.rs @@ -5,12 +5,15 @@ extern crate libc; use crate::errors; -use crate::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop}; -use crate::platform::monitor::Monitor; use crate::statecallback::StateCallback; +use crate::transport::device_selector::{ + DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent, +}; +use crate::transport::platform::iokit::{CFRunLoopEntryObserver, SendableRunLoop}; +use crate::transport::platform::monitor::Monitor; use core_foundation::runloop::*; use std::os::raw::c_void; -use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::mpsc::{channel, Sender}; use std::thread; // A transaction will run the given closure in a new thread, thereby using a @@ -20,21 +23,32 @@ use std::thread; pub struct Transaction { runloop: Option, thread: Option>, + device_selector: DeviceSelector, } impl Transaction { pub fn new( timeout: u64, callback: StateCallback>, + status: Sender, new_device_cb: F, ) -> crate::Result where - F: Fn((IOHIDDeviceRef, Receiver>), &dyn Fn() -> bool) + Sync + Send + 'static, + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, T: 'static, { let (tx, rx) = channel(); let timeout = (timeout as f64) / 1000.0; - + let status_sender = status.clone(); + let device_selector = DeviceSelector::run(status); + let selector_sender = device_selector.clone_sender(); let builder = thread::Builder::new(); let thread = builder .spawn(move || { @@ -47,7 +61,7 @@ impl Transaction { obs.add_to_current_runloop(); // Create a new HID device monitor and start polling. - let mut monitor = Monitor::new(new_device_cb); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); try_or!(monitor.start(), |_| callback .call(Err(errors::AuthenticatorError::Platform))); @@ -72,6 +86,7 @@ impl Transaction { Ok(Self { runloop: Some(runloop), thread: Some(thread), + device_selector, }) } @@ -86,6 +101,7 @@ impl Transaction { // This must never be None. This won't block. unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) }; + self.device_selector.stop(); // This must never be None. Ignore return value. let _ = self.thread.take().unwrap().join(); } diff --git a/third_party/rust/authenticator/src/transport/mock/device.rs b/third_party/rust/authenticator/src/transport/mock/device.rs new file mode 100644 index 000000000000..6f501d8e073f --- /dev/null +++ b/third_party/rust/authenticator/src/transport/mock/device.rs @@ -0,0 +1,194 @@ +/* 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::CID_BROADCAST; +use crate::crypto::ECDHSecret; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::transport::device_selector::DeviceCommand; +use crate::transport::{hid::HIDDevice, FidoDevice, HIDError}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use std::hash::{Hash, Hasher}; +use std::io::{self, Read, Write}; +use std::sync::mpsc::{channel, Receiver, Sender}; + +pub(crate) const IN_HID_RPT_SIZE: usize = 64; +const OUT_HID_RPT_SIZE: usize = 64; + +#[derive(Debug)] +pub struct Device { + pub id: String, + pub cid: [u8; 4], + pub reads: Vec<[u8; IN_HID_RPT_SIZE]>, + pub writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>, + pub dev_info: Option, + pub authenticator_info: Option, + pub sender: Option>, + pub receiver: Option>, +} + +impl Device { + 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); + } + + pub fn create_channel(&mut self) { + let (tx, rx) = channel(); + self.sender = Some(tx); + self.receiver = Some(rx); + } +} + +impl Write for Device { + fn write(&mut self, bytes: &[u8]) -> io::Result { + // Pop a vector from the expected writes, check for quality + // against bytes array. + assert!( + !self.writes.is_empty(), + "Ran out of expected write values! Wanted to write {:?}", + bytes + ); + 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 Device { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + 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 Device { + fn drop(&mut self) { + if !std::thread::panicking() { + assert!(self.reads.is_empty()); + assert!(self.writes.is_empty()); + } + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.id == other.id + } +} + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +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 { + IN_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + OUT_HID_RPT_SIZE + } + + fn get_property(&self, prop_name: &str) -> io::Result { + 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); + } +} + +impl HIDDevice for Device { + type Id = String; + type BuildParameters = &'static str; // None used + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + fn set_shared_secret(&mut self, _: ECDHSecret) { + // Nothing + } + fn get_shared_secret(&self) -> std::option::Option<&ECDHSecret> { + None + } + + fn new(id: Self::BuildParameters) -> Result { + Ok(Device { + id: id.to_string(), + cid: CID_BROADCAST, + reads: vec![], + writes: vec![], + dev_info: None, + authenticator_info: None, + sender: None, + receiver: None, + }) + } + + fn initialized(&self) -> bool { + self.get_cid() != &CID_BROADCAST + } + + fn id(&self) -> Self::Id { + self.id.clone() + } + + fn clone_device_as_write_only(&self) -> Result { + Ok(Device { + id: self.id.clone(), + cid: self.cid, + reads: self.reads.clone(), + writes: self.writes.clone(), + dev_info: self.dev_info.clone(), + authenticator_info: self.authenticator_info.clone(), + sender: self.sender.clone(), + receiver: None, + }) + } + + fn is_u2f(&self) -> bool { + self.sender.is_some() + } +} + +impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/mock/mod.rs b/third_party/rust/authenticator/src/transport/mock/mod.rs new file mode 100644 index 000000000000..d0e200a7ef4f --- /dev/null +++ b/third_party/rust/authenticator/src/transport/mock/mod.rs @@ -0,0 +1,6 @@ +/* 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; diff --git a/third_party/rust/authenticator/src/transport/mock/transaction.rs b/third_party/rust/authenticator/src/transport/mock/transaction.rs new file mode 100644 index 000000000000..e19b1cb56fbf --- /dev/null +++ b/third_party/rust/authenticator/src/transport/mock/transaction.rs @@ -0,0 +1,35 @@ +/* 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::statecallback::StateCallback; +use crate::transport::device_selector::{DeviceBuildParameters, DeviceSelectorEvent}; +use std::sync::mpsc::Sender; + +pub struct Transaction {} + +impl Transaction { + pub fn new( + _timeout: u64, + _callback: StateCallback>, + _status: Sender, + _new_device_cb: F, + ) -> crate::Result + where + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, + T: 'static, + { + Ok(Self {}) + } + + pub fn cancel(&mut self) { + info!("Transaction was cancelled."); + } +} diff --git a/third_party/rust/authenticator/src/transport/mod.rs b/third_party/rust/authenticator/src/transport/mod.rs new file mode 100644 index 000000000000..c68615eedc83 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/mod.rs @@ -0,0 +1,263 @@ +use crate::consts::{Capability, HIDCmd}; +use crate::crypto::ECDHSecret; + +use crate::ctap2::commands::client_pin::{GetKeyAgreement, PinAuth}; +use crate::ctap2::commands::get_info::{AuthenticatorInfo, GetInfo}; +use crate::ctap2::commands::get_version::GetVersion; +use crate::ctap2::commands::make_credentials::dummy_make_credentials_cmd; +use crate::ctap2::commands::selection::Selection; +use crate::ctap2::commands::{ + CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable, StatusCode, +}; +use crate::transport::device_selector::BlinkResult; +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::transport::hid::HIDDevice; +use crate::util::io_err; +use std::thread; +use std::time::Duration; + +pub mod device_selector; +pub mod errors; +pub mod hid; + +#[cfg(all( + any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"), + not(test) +))] +pub mod hidproto; + +#[cfg(all(target_os = "linux", not(test)))] +#[path = "linux/mod.rs"] +pub mod platform; + +#[cfg(all(target_os = "freebsd", not(test)))] +#[path = "freebsd/mod.rs"] +pub mod platform; + +#[cfg(all(target_os = "netbsd", not(test)))] +#[path = "netbsd/mod.rs"] +pub mod platform; + +#[cfg(all(target_os = "openbsd", not(test)))] +#[path = "openbsd/mod.rs"] +pub mod platform; + +#[cfg(all(target_os = "macos", not(test)))] +#[path = "macos/mod.rs"] +pub mod platform; + +#[cfg(all(target_os = "windows", not(test)))] +#[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", + test +)))] +#[path = "stub/mod.rs"] +pub mod platform; + +#[cfg(test)] +#[path = "mock/mod.rs"] +pub mod platform; + +#[derive(Debug)] +pub enum Nonce { + CreateRandom, + Use([u8; 8]), +} + +// TODO(MS): This is the lazy way: FidoDevice currently only extends HIDDevice by more functions, +// but the goal is to remove U2FDevice entirely and copy over the trait-definition here +pub trait FidoDevice: HIDDevice { + fn send_msg<'msg, Out, Req: Request>(&mut self, msg: &'msg Req) -> Result { + if !self.initialized() { + return Err(HIDError::DeviceNotInitialized); + } + + if self.supports_ctap2() && msg.is_ctap2_request() { + self.send_cbor(msg) + } else { + self.send_ctap1(msg) + } + } + + fn send_cbor<'msg, Req: RequestCtap2>( + &mut self, + msg: &'msg Req, + ) -> Result { + debug!("sending {:?} to {:?}", msg, self); + + let mut data = msg.wire_format(self)?; + let mut buf: Vec = Vec::with_capacity(data.len() + 1); + // CTAP2 command + buf.push(Req::command() as u8); + // payload + buf.append(&mut data); + let buf = buf; + + let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf)?; + debug!( + "got from Device {:?} status={:?}: {:?}", + self.id(), + cmd, + resp + ); + if cmd == HIDCmd::Cbor { + Ok(msg.handle_response_ctap2(self, &resp)?) + } else { + Err(HIDError::UnexpectedCmd(cmd.into())) + } + } + + fn send_ctap1<'msg, Req: RequestCtap1>( + &mut self, + msg: &'msg Req, + ) -> Result { + debug!("sending {:?} to {:?}", msg, self); + let data = msg.ctap1_format(self)?; + + loop { + let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data)?; + debug!( + "got from Device {:?} status={:?}: {:?}", + self.id(), + cmd, + data + ); + if cmd == HIDCmd::Msg { + if data.len() < 2 { + return Err(io_err("Unexpected Response: shorter than expected").into()); + } + let split_at = data.len() - 2; + let status = data.split_off(split_at); + // This will bubble up error if status != no error + let status = ApduErrorStatus::from([status[0], status[1]]); + + match msg.handle_response_ctap1(status, &data) { + Ok(out) => return Ok(out), + Err(Retryable::Retry) => { + // sleep 100ms then loop again + // TODO(baloo): meh, use tokio instead? + thread::sleep(Duration::from_millis(100)); + } + Err(Retryable::Error(e)) => return Err(e), + } + } else { + return Err(HIDError::UnexpectedCmd(cmd.into())); + } + } + } + + // This is ugly as we have 2 init-functions now, but the fastest way currently. + fn init(&mut self, nonce: Nonce) -> Result<(), HIDError> { + let resp = ::initialize(self, nonce)?; + // TODO(baloo): this logic should be moved to + // transport/mod.rs::Device trait + if self.supports_ctap2() { + let command = GetInfo::default(); + let info = self.send_cbor(&command)?; + debug!("{:?} infos: {:?}", self.id(), info); + + self.set_authenticator_info(info); + } + if self.supports_ctap1() { + let command = GetVersion::default(); + // We don't really use the result here + self.send_ctap1(&command)?; + } + Ok(resp) + } + + fn block_and_blink(&mut self) -> BlinkResult { + let resp; + let supports_select_cmd = self + .get_authenticator_info() + .map_or(false, |i| i.versions.contains(&String::from("FIDO_2_1"))); + if supports_select_cmd { + let msg = Selection {}; + resp = self.send_cbor(&msg); + } else { + // We need to fake a blink-request, because FIDO2.0 forgot to specify one + // See: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorMakeCredential + let mut msg = match dummy_make_credentials_cmd() { + Ok(m) => m, + Err(_) => { + return BlinkResult::Cancelled; + } + }; + // Using a zero-length pinAuth will trigger the device to blink + // For CTAP1, this gets ignored anyways and we do a 'normal' register + // command, which also just blinks. + msg.set_pin_auth(Some(PinAuth::empty_pin_auth()), None); + info!("Trying to blink: {:?}", &msg); + // We don't care about the Ok-value, just if it is Ok or not + resp = self.send_msg(&msg).map(|_| ()); + } + match resp { + // Spec only says PinInvalid or PinNotSet should be returned on the fake touch-request, + // but Yubikeys for example return PinAuthInvalid. A successful return is also possible + // for CTAP1-tokens so we catch those here as well. + Ok(_) + | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _))) + | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _))) + | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinNotSet, _))) => { + BlinkResult::DeviceSelected + } + // We cancelled the receive, because another device was selected. + Err(HIDError::Command(CommandError::StatusCode(StatusCode::KeepaliveCancel, _))) + | Err(HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, _))) + | Err(HIDError::Command(CommandError::StatusCode(StatusCode::UserActionTimeout, _))) => { + // TODO: Repeat the request, if it is a UserActionTimeout? + debug!("Device {:?} got cancelled", &self); + BlinkResult::Cancelled + } + // Something unexpected happened, so we assume this device is not usable and interpreting + // this equivalent to being cancelled. + e => { + info!("Device {:?} received unexpected answer, so we assume an error occurred and we are NOT using this device (assuming the request was cancelled): {:?}", &self, e); + BlinkResult::Cancelled + } + } + } + fn supports_ctap1(&self) -> bool { + // CAPABILITY_NMSG: + // If set to 1, authenticator DOES NOT implement U2FHID_MSG function + !self.get_device_info().cap_flags.contains(Capability::NMSG) + } + + fn supports_ctap2(&self) -> bool { + self.get_device_info().cap_flags.contains(Capability::CBOR) + } + + fn establish_shared_secret(&mut self) -> Result<(ECDHSecret, AuthenticatorInfo), HIDError> { + if !self.supports_ctap2() { + return Err(HIDError::UnsupportedCommand); + } + + let info = if let Some(authenticator_info) = self.get_authenticator_info().cloned() { + authenticator_info + } else { + // We should already have it, since it is queried upon `init()`, but just to be safe + let info_command = GetInfo::default(); + let info = self.send_cbor(&info_command)?; + debug!("infos: {:?}", info); + + self.set_authenticator_info(info.clone()); + info + }; + + // Not reusing the shared secret here, if it exists, since we might start again + // with a different PIN (e.g. if the last one was wrong) + let pin_command = GetKeyAgreement::new(&info)?; + let device_key_agreement = self.send_cbor(&pin_command)?; + let shared_secret = device_key_agreement.shared_secret()?; + self.set_shared_secret(shared_secret.clone()); + Ok((shared_secret, info)) + } +} diff --git a/third_party/rust/authenticator/src/netbsd/device.rs b/third_party/rust/authenticator/src/transport/netbsd/device.rs similarity index 92% rename from third_party/rust/authenticator/src/netbsd/device.rs rename to third_party/rust/authenticator/src/transport/netbsd/device.rs index 92e7c22ea16d..d7464db22b63 100644 --- a/third_party/rust/authenticator/src/netbsd/device.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/device.rs @@ -11,8 +11,8 @@ 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::transport::platform::fd::Fd; +use crate::transport::platform::uhid; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::io_err; @@ -97,6 +97,16 @@ impl PartialEq for Device { } } +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.fd.hash(state); + } +} + impl Read for Device { fn read(&mut self, buf: &mut [u8]) -> io::Result { let bufp = buf.as_mut_ptr() as *mut libc::c_void; diff --git a/third_party/rust/authenticator/src/netbsd/fd.rs b/third_party/rust/authenticator/src/transport/netbsd/fd.rs similarity index 79% rename from third_party/rust/authenticator/src/netbsd/fd.rs rename to third_party/rust/authenticator/src/transport/netbsd/fd.rs index c011b7fcc832..3d64117d38a9 100644 --- a/third_party/rust/authenticator/src/netbsd/fd.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/fd.rs @@ -45,3 +45,17 @@ impl PartialEq for Fd { (st.st_dev == sto.st_dev) & (st.st_ino == sto.st_ino) } } + + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + let mut st: libc::stat = unsafe { mem::zeroed() }; + if unsafe { libc::fstat(self.fileno, &mut st) } == -1 { + return; + } + st.st_dev.hash(state); + st.st_ino.hash(state); + } +} diff --git a/third_party/rust/authenticator/src/netbsd/mod.rs b/third_party/rust/authenticator/src/transport/netbsd/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/netbsd/mod.rs rename to third_party/rust/authenticator/src/transport/netbsd/mod.rs diff --git a/third_party/rust/authenticator/src/netbsd/monitor.rs b/third_party/rust/authenticator/src/transport/netbsd/monitor.rs similarity index 62% rename from third_party/rust/authenticator/src/netbsd/monitor.rs rename to third_party/rust/authenticator/src/transport/netbsd/monitor.rs index c78cff6ee129..5c4581c8d18f 100644 --- a/third_party/rust/authenticator/src/netbsd/monitor.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/monitor.rs @@ -2,16 +2,17 @@ * 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::transport::device_selector::DeviceSelectorEvent; use std::collections::HashMap; use std::ffi::OsString; use std::io; use std::sync::Arc; use std::thread; use std::time::Duration; - +use std::sync::{mpsc::Sender, Arc}; use runloop::RunLoop; -use crate::platform::fd::Fd; +use crate::transport::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, @@ -20,24 +21,44 @@ const POLL_TIMEOUT: u64 = 500; pub struct Monitor where - F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static, + F: Fn( + Fd, + OsString, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync, { runloops: HashMap, new_device_cb: Arc, + selector_sender: Sender, + status_sender: Sender, } impl Monitor where - F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static, + F: Fn( + Fd, + OsString, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync, { - pub fn new(new_device_cb: F) -> Self { + pub fn new( + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, + ) -> Self { Self { runloops: HashMap::new(), new_device_cb: Arc::new(new_device_cb), + selector_sender, + status_sender, } } - pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box> { while alive() { for n in 0..100 { let uhidpath = format!("/dev/uhid{}", n); @@ -60,10 +81,13 @@ where fn add_device(&mut self, fd: Fd, path: OsString) { let f = self.new_device_cb.clone(); + let selector_sender = self.selector_sender.clone(); + let status_sender = self.status_sender.clone(); + debug!("Adding device {}", path.to_string_lossy()); let runloop = RunLoop::new(move |alive| { if alive() { - f(fd, alive); + f(fd.clone(), path.clone(), selector_sender, status_sender, alive); } }); @@ -73,6 +97,11 @@ where } fn remove_device(&mut self, path: OsString) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(path.clone())); + + debug!("Removing device {}", path.to_string_lossy()); if let Some(runloop) = self.runloops.remove(&path) { runloop.cancel(); } diff --git a/third_party/rust/authenticator/src/transport/netbsd/transaction.rs b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs new file mode 100644 index 000000000000..d7183147cd7c --- /dev/null +++ b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs @@ -0,0 +1,72 @@ +/* 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; +use crate::transport::platform::fd::Fd; +use crate::transport::device_selector::{ + DeviceBuildParameters, DeviceID, DeviceSelector, DeviceSelectorEvent, +}; +use crate::transport::platform::monitor::Monitor; +use runloop::RunLoop; +use std::sync::mpsc::Sender; + +pub struct Transaction { + // Handle to the thread loop. + thread: RunLoop, + device_selector: DeviceSelector, +} + +impl Transaction { + pub fn new( + timeout: u64, + callback: StateCallback>, + status: Sender, + new_device_cb: F, + ) -> crate::Result + where + F: Fn( + DeviceBuildParameters, + DeviceID, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, + T: 'static, + { + let status_sender = status.clone(); + let device_selector = DeviceSelector::run(status); + let selector_sender = device_selector.clone_sender(); + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + + // 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, + device_selector, + }) + } + + pub fn cancel(&mut self) { + info!("Transaction was cancelled."); + self.thread.cancel(); + self.device_selector.stop(); + } +} diff --git a/third_party/rust/authenticator/src/netbsd/uhid.rs b/third_party/rust/authenticator/src/transport/netbsd/uhid.rs similarity index 98% rename from third_party/rust/authenticator/src/netbsd/uhid.rs rename to third_party/rust/authenticator/src/transport/netbsd/uhid.rs index f8d711553ddc..a8fa3d0f0990 100644 --- a/third_party/rust/authenticator/src/netbsd/uhid.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/uhid.rs @@ -11,7 +11,7 @@ use std::os::raw::c_uchar; use crate::hidproto::has_fido_usage; use crate::hidproto::ReportDescriptor; -use crate::platform::fd::Fd; +use crate::transport::platform::fd::Fd; use crate::util::io_err; /* sys/ioccom.h */ diff --git a/third_party/rust/authenticator/src/openbsd/device.rs b/third_party/rust/authenticator/src/transport/openbsd/device.rs similarity index 98% rename from third_party/rust/authenticator/src/openbsd/device.rs rename to third_party/rust/authenticator/src/transport/openbsd/device.rs index 2238e034e2c5..e9e2815c0208 100644 --- a/third_party/rust/authenticator/src/openbsd/device.rs +++ b/third_party/rust/authenticator/src/transport/openbsd/device.rs @@ -10,7 +10,7 @@ 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::transport::platform::monitor::FidoDev; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::{from_unix_result, io_err}; diff --git a/third_party/rust/authenticator/src/openbsd/mod.rs b/third_party/rust/authenticator/src/transport/openbsd/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/openbsd/mod.rs rename to third_party/rust/authenticator/src/transport/openbsd/mod.rs diff --git a/third_party/rust/authenticator/src/openbsd/monitor.rs b/third_party/rust/authenticator/src/transport/openbsd/monitor.rs similarity index 100% rename from third_party/rust/authenticator/src/openbsd/monitor.rs rename to third_party/rust/authenticator/src/transport/openbsd/monitor.rs diff --git a/third_party/rust/authenticator/src/openbsd/transaction.rs b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs similarity index 96% rename from third_party/rust/authenticator/src/openbsd/transaction.rs rename to third_party/rust/authenticator/src/transport/openbsd/transaction.rs index 4b85db278522..50f934868ace 100644 --- a/third_party/rust/authenticator/src/openbsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs @@ -3,8 +3,8 @@ * 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 crate::transport::platform::monitor::{FidoDev, Monitor}; use runloop::RunLoop; pub struct Transaction { diff --git a/third_party/rust/authenticator/src/stub/device.rs b/third_party/rust/authenticator/src/transport/stub/device.rs similarity index 51% rename from third_party/rust/authenticator/src/stub/device.rs rename to third_party/rust/authenticator/src/transport/stub/device.rs index 283da8ed6638..95ca901249e2 100644 --- a/third_party/rust/authenticator/src/stub/device.rs +++ b/third_party/rust/authenticator/src/transport/stub/device.rs @@ -2,22 +2,18 @@ * 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::transport::hid::HIDDevice; +use crate::transport::FidoDevice; +use crate::transport::{AuthenticatorInfo, ECDHSecret, HIDError}; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use std::hash::{Hash, Hasher}; use std::io; use std::io::{Read, Write}; +use std::path::PathBuf; +#[derive(Debug, PartialEq, Eq)] pub struct Device {} -impl Device { - pub fn new(path: String) -> io::Result { - 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 { panic!("not implemented"); @@ -63,3 +59,52 @@ impl U2FDevice for Device { panic!("not implemented") } } + +impl HIDDevice for Device { + type BuildParameters = PathBuf; + type Id = PathBuf; + + fn new(parameters: Self::BuildParameters) -> Result { + unimplemented!(); + } + + fn initialized(&self) -> bool { + unimplemented!(); + } + + fn id(&self) -> Self::Id { + unimplemented!() + } + + fn is_u2f(&self) -> bool { + unimplemented!() + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + unimplemented!() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + unimplemented!() + } + + fn set_shared_secret(&mut self, secret: ECDHSecret) { + unimplemented!() + } + + fn get_shared_secret(&self) -> Option<&ECDHSecret> { + unimplemented!() + } + + fn clone_device_as_write_only(&self) -> Result { + unimplemented!() + } +} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + unimplemented!() + } +} + +impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/stub/mod.rs b/third_party/rust/authenticator/src/transport/stub/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/stub/mod.rs rename to third_party/rust/authenticator/src/transport/stub/mod.rs diff --git a/third_party/rust/authenticator/src/transport/stub/transaction.rs b/third_party/rust/authenticator/src/transport/stub/transaction.rs new file mode 100644 index 000000000000..21a9687ecd03 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/stub/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::statecallback::StateCallback; +use crate::transport::device_selector::{ + DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent, +}; +use std::path::PathBuf; +use std::sync::mpsc::Sender; + +pub struct Transaction {} + +impl Transaction { + pub fn new( + timeout: u64, + callback: StateCallback>, + status: Sender, + new_device_cb: F, + ) -> crate::Result + where + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, + T: 'static, + { + // Just to silence "unused"-warnings + let mut device_selector = DeviceSelector::run(status); + let _ = DeviceSelectorEvent::DevicesAdded(vec![]); + let _ = DeviceSelectorEvent::DeviceRemoved(PathBuf::new()); + let _ = device_selector.clone_sender(); + device_selector.stop(); + + 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/transport/windows/device.rs b/third_party/rust/authenticator/src/transport/windows/device.rs new file mode 100644 index 000000000000..d1b5850e0e01 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/windows/device.rs @@ -0,0 +1,171 @@ +/* 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 super::winapi::DeviceCapabilities; +use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE}; +use crate::transport::hid::HIDDevice; +use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; +use std::fs::{File, OpenOptions}; +use std::hash::{Hash, Hasher}; +use std::io::{self, Read, Write}; +use std::os::windows::io::AsRawHandle; + +#[derive(Debug)] +pub struct Device { + path: String, + file: File, + cid: [u8; 4], + dev_info: Option, + secret: Option, + authenticator_info: Option, +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.path.hash(state); + } +} + +impl Read for Device { + fn read(&mut self, bytes: &mut [u8]) -> io::Result { + // 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 { + self.file.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } +} + +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 { + 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); + } +} + +impl HIDDevice for Device { + type BuildParameters = String; + type Id = String; + + fn new(path: String) -> Result { + debug!("Opening device {:?}", path); + let file = OpenOptions::new() + .read(true) + .write(true) + .open(&path) + .map_err(|e| (HIDError::IO(Some(path.clone().into()), e), path.clone()))?; + let res = Self { + path, + file, + cid: CID_BROADCAST, + dev_info: None, + secret: None, + authenticator_info: None, + }; + if res.is_u2f() { + info!("new device {:?}", res.path); + Ok(res) + } else { + Err((HIDError::DeviceNotSupported, res.path.clone())) + } + } + + fn initialized(&self) -> bool { + // During successful init, the broadcast channel id gets repplaced by an actual one + self.cid != CID_BROADCAST + } + + fn id(&self) -> Self::Id { + self.path.clone() + } + + 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, + } + } + + fn get_shared_secret(&self) -> Option<&ECDHSecret> { + self.secret.as_ref() + } + + fn set_shared_secret(&mut self, secret: ECDHSecret) { + self.secret = Some(secret); + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + /// This is used for cancellation of blocking read()-requests. + /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. + fn clone_device_as_write_only(&self) -> Result { + let file = OpenOptions::new() + .write(true) + .open(&self.path) + .map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?; + + Ok(Self { + path: self.path.clone(), + file, + cid: self.cid, + dev_info: self.dev_info.clone(), + secret: self.secret.clone(), + authenticator_info: self.authenticator_info.clone(), + }) + } +} + +impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/windows/mod.rs b/third_party/rust/authenticator/src/transport/windows/mod.rs similarity index 100% rename from third_party/rust/authenticator/src/windows/mod.rs rename to third_party/rust/authenticator/src/transport/windows/mod.rs diff --git a/third_party/rust/authenticator/src/windows/monitor.rs b/third_party/rust/authenticator/src/transport/windows/monitor.rs similarity index 57% rename from third_party/rust/authenticator/src/windows/monitor.rs rename to third_party/rust/authenticator/src/transport/windows/monitor.rs index 4c977bde03cd..8bc4fc70d37c 100644 --- a/third_party/rust/authenticator/src/windows/monitor.rs +++ b/third_party/rust/authenticator/src/transport/windows/monitor.rs @@ -2,35 +2,48 @@ * 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 crate::transport::device_selector::DeviceSelectorEvent; +use crate::transport::platform::winapi::DeviceInfoSet; use runloop::RunLoop; use std::collections::{HashMap, HashSet}; -use std::io; +use std::error::Error; use std::iter::FromIterator; -use std::sync::Arc; +use std::sync::{mpsc::Sender, Arc}; use std::thread; use std::time::Duration; pub struct Monitor where - F: Fn(String, &dyn Fn() -> bool) + Sync, + F: Fn(String, Sender, Sender, &dyn Fn() -> bool) + + Sync, { runloops: HashMap, new_device_cb: Arc, + selector_sender: Sender, + status_sender: Sender, } impl Monitor where - F: Fn(String, &dyn Fn() -> bool) + Send + Sync + 'static, + F: Fn(String, Sender, Sender, &dyn Fn() -> bool) + + Send + + Sync + + 'static, { - pub fn new(new_device_cb: F) -> Self { + pub fn new( + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, + ) -> Self { Self { runloops: HashMap::new(), new_device_cb: Arc::new(new_device_cb), + selector_sender, + status_sender, } } - pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box> { let mut stored = HashSet::new(); while alive() { @@ -42,9 +55,12 @@ where self.remove_device(path); } + let paths: Vec<_> = devices.difference(&stored).cloned().collect(); + self.selector_sender + .send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?; // Add devices that were plugged in. - for path in devices.difference(&stored) { - self.add_device(path); + for path in paths { + self.add_device(&path); } // Remember the new set. @@ -64,10 +80,13 @@ where let f = self.new_device_cb.clone(); let path = path.clone(); let key = path.clone(); + let selector_sender = self.selector_sender.clone(); + let status_sender = self.status_sender.clone(); + debug!("Adding device {}", path); let runloop = RunLoop::new(move |alive| { if alive() { - f(path, alive); + f(path, selector_sender, status_sender, alive); } }); @@ -77,6 +96,11 @@ where } fn remove_device(&mut self, path: &String) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(path.clone())); + + debug!("Removing device {}", path); if let Some(runloop) = self.runloops.remove(path) { runloop.cancel(); } diff --git a/third_party/rust/authenticator/src/linux/transaction.rs b/third_party/rust/authenticator/src/transport/windows/transaction.rs similarity index 56% rename from third_party/rust/authenticator/src/linux/transaction.rs rename to third_party/rust/authenticator/src/transport/windows/transaction.rs index e7cd00f1845a..35baa8f3c438 100644 --- a/third_party/rust/authenticator/src/linux/transaction.rs +++ b/third_party/rust/authenticator/src/transport/windows/transaction.rs @@ -3,30 +3,45 @@ * 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 crate::transport::device_selector::{ + DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent, +}; +use crate::transport::platform::monitor::Monitor; use runloop::RunLoop; -use std::ffi::OsString; +use std::sync::mpsc::Sender; pub struct Transaction { // Handle to the thread loop. - thread: Option, + thread: RunLoop, + device_selector: DeviceSelector, } impl Transaction { pub fn new( timeout: u64, callback: StateCallback>, + status: Sender, new_device_cb: F, ) -> crate::Result where - F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static, + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, T: 'static, { + let status_sender = status.clone(); + let device_selector = DeviceSelector::run(status); + let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback @@ -42,12 +57,14 @@ impl Transaction { .map_err(|_| errors::AuthenticatorError::Platform)?; Ok(Self { - thread: Some(thread), + thread, + device_selector, }) } pub fn cancel(&mut self) { - // This must never be None. - self.thread.take().unwrap().cancel(); + info!("Transaction was cancelled."); + self.device_selector.stop(); + self.thread.cancel(); } } diff --git a/third_party/rust/authenticator/src/windows/winapi.rs b/third_party/rust/authenticator/src/transport/windows/winapi.rs similarity index 94% rename from third_party/rust/authenticator/src/windows/winapi.rs rename to third_party/rust/authenticator/src/transport/windows/winapi.rs index d3388cebfc9a..9ed88138a8c6 100644 --- a/third_party/rust/authenticator/src/windows/winapi.rs +++ b/third_party/rust/authenticator/src/transport/windows/winapi.rs @@ -15,9 +15,9 @@ 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}; +use winapi::shared::{guiddef, minwindef, ntdef, windef}; +use winapi::shared::{hidclass, hidpi, hidusage}; +use winapi::um::{handleapi, setupapi}; #[link(name = "setupapi")] extern "system" { @@ -63,12 +63,6 @@ extern "system" { ) -> 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) }; @@ -214,7 +208,7 @@ impl DeviceInterfaceDetailData { 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); + let offset = memoffset::offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); Some(Self { data, diff --git a/third_party/rust/authenticator/src/u2fhid-capi.h b/third_party/rust/authenticator/src/u2fhid-capi.h index bb81b28f5f90..b097cd5217b2 100644 --- a/third_party/rust/authenticator/src/u2fhid-capi.h +++ b/third_party/rust/authenticator/src/u2fhid-capi.h @@ -30,11 +30,16 @@ 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_OK = 0; 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; +const uint8_t CTAP_ERROR_PIN_REQUIRED = 6; +const uint8_t CTAP_ERROR_PIN_INVALID = 7; +const uint8_t CTAP_ERROR_PIN_AUTH_BLOCKED = 8; +const uint8_t CTAP_ERROR_PIN_BLOCKED = 9; // NOTE: Preconditions // * All rust_u2f_mgr* pointers must refer to pointers which are returned @@ -45,7 +50,8 @@ const uint8_t U2F_ERROR_NOT_ALLOWED = 5; // 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; +// TODO(MS): Once CTAP2 support is added, this should probably be renamed. +struct rust_ctap_manager; // The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds` struct rust_u2f_app_ids; @@ -62,10 +68,10 @@ 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); +rust_ctap_manager* rust_u2f_mgr_new(); +/* unsafe */ void rust_u2f_mgr_free(rust_ctap_manager* mgr); -uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags, +uint64_t rust_u2f_mgr_register(rust_ctap_manager* mgr, uint64_t flags, uint64_t timeout, rust_u2f_callback, const uint8_t* challenge_ptr, size_t challenge_len, @@ -73,13 +79,13 @@ uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags, 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 rust_u2f_mgr_sign(rust_ctap_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); +void rust_u2f_mgr_cancel(rust_ctap_manager* mgr); /// U2FAppIds functions. @@ -103,9 +109,11 @@ 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_contains(const rust_u2f_result* res, uint8_t bid); 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 index ca9f7043874b..08d784435171 100644 --- a/third_party/rust/authenticator/src/u2fprotocol.rs +++ b/third_party/rust/authenticator/src/u2fprotocol.rs @@ -46,7 +46,7 @@ where register_data.extend(application); let flags = U2F_REQUEST_USER_PRESENCE; - let (resp, status) = send_apdu(dev, U2F_REGISTER, flags, ®ister_data)?; + let (resp, status) = send_ctap1(dev, U2F_REGISTER, flags, ®ister_data)?; status_word_to_result(status, resp) } @@ -80,7 +80,7 @@ where sign_data.extend(key_handle); let flags = U2F_REQUEST_USER_PRESENCE; - let (resp, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?; + let (resp, status) = send_ctap1(dev, U2F_AUTHENTICATE, flags, &sign_data)?; status_word_to_result(status, resp) } @@ -114,7 +114,7 @@ where sign_data.extend(key_handle); let flags = U2F_CHECK_IS_REGISTERED; - let (_, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?; + let (_, status) = send_ctap1(dev, U2F_AUTHENTICATE, flags, &sign_data)?; Ok(status == SW_CONDITIONS_NOT_SATISFIED) } @@ -127,8 +127,10 @@ where T: U2FDevice + Read + Write, { assert_eq!(nonce.len(), INIT_NONCE_SIZE); - let raw = sendrecv(dev, U2FHID_INIT, nonce)?; + // Send Init to broadcast address to create a new channel + let raw = sendrecv(dev, HIDCmd::Init, nonce)?; let rsp = U2FHIDInitResp::read(&raw, nonce)?; + // Get the new Channel ID dev.set_cid(rsp.cid); let vendor = dev @@ -155,7 +157,7 @@ fn is_v2_device(dev: &mut T) -> io::Result where T: U2FDevice + Read + Write, { - let (data, status) = send_apdu(dev, U2F_VERSION, 0x00, &[])?; + let (data, status) = send_ctap1(dev, U2F_VERSION, 0x00, &[])?; let actual = CString::new(data)?; let expected = CString::new("U2F_V2")?; status_word_to_result(status, actual == expected) @@ -181,12 +183,12 @@ fn status_word_to_result(status: [u8; 2], val: T) -> io::Result { // Device Communication Functions //////////////////////////////////////////////////////////////////////// -pub fn sendrecv(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result> +pub fn sendrecv(dev: &mut T, cmd: HIDCmd, send: &[u8]) -> io::Result> where T: U2FDevice + Read + Write, { // Send initialization packet. - let mut count = U2FHIDInit::write(dev, cmd, send)?; + let mut count = U2FHIDInit::write(dev, cmd.into(), send)?; // Send continuation packets. let mut sequence = 0u8; @@ -198,7 +200,7 @@ where // 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 data) = U2FHIDInit::read(dev)?; let mut sequence = 0u8; while data.len() < data.capacity() { @@ -210,12 +212,12 @@ where Ok(data) } -fn send_apdu(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec, [u8; 2])> +fn send_ctap1(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec, [u8; 2])> where T: U2FDevice + Read + Write, { - let apdu = U2FAPDUHeader::serialize(cmd, p1, send)?; - let mut data = sendrecv(dev, U2FHID_MSG, &apdu)?; + let apdu = CTAP1RequestAPDU::serialize(cmd, p1, send)?; + let mut data = sendrecv(dev, HIDCmd::Msg, &apdu)?; if data.len() < 2 { return Err(io_err("unexpected response")); @@ -231,123 +233,17 @@ where //////////////////////////////////////////////////////////////////////// #[cfg(test)] -mod tests { +pub(crate) mod tests { + use super::{init_device, is_v2_device, send_ctap1, sendrecv, U2FDevice}; + use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR}; + use crate::transport::device_selector::Device; + use crate::transport::hid::HIDDevice; + use crate::u2ftypes::U2FDeviceInfo; 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, - } - - 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 { - // 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 { - 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 { - 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 mut device = Device::new("u2fprotocol").unwrap(); let nonce = vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]; // channel id @@ -356,13 +252,13 @@ mod tests { // init packet let mut msg = CID_BROADCAST.to_vec(); - msg.extend(vec![U2FHID_INIT, 0x00, 0x08]); // cmd + bcnt + msg.extend(vec![HIDCmd::Init.into(), 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(vec![HIDCmd::Init.into(), 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 @@ -376,19 +272,56 @@ mod tests { 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); + assert_eq!(dev_info.cap_flags, Capability::WINK); // 0x01 + } + + #[test] + fn test_get_version() { + let mut device = Device::new("u2fprotocol").unwrap(); + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + device.set_cid(cid.clone()); + + let info = U2FDeviceInfo { + vendor_name: Vec::new(), + device_name: Vec::new(), + version_interface: 0x02, + version_major: 0x04, + version_minor: 0x01, + version_build: 0x08, + cap_flags: Capability::WINK, + }; + device.set_device_info(info); + + // ctap1.0 U2F_VERSION request + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x7]); // cmd + bcnt + msg.extend(&[0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x08]); // cmd + bcnt + msg.extend(&[0x55, 0x32, 0x46, 0x5f, 0x56, 0x32]); // 'U2F_V2' + msg.extend(&SW_NO_ERROR); + device.add_read(&msg, 0); + + let res = is_v2_device(&mut device).expect("Failed to get version"); + assert!(res); } #[test] fn test_sendrecv_multiple() { - let mut device = platform::TestDevice::new(); + let mut device = Device::new("u2fprotocol").unwrap(); 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 + msg.extend(vec![HIDCmd::Ping.into(), 0x00, 0xe4]); // cmd + length = 228 + // write msg, append [1u8; 57], 171 bytes remain device.add_write(&msg, 1); device.add_read(&msg, 1); @@ -415,7 +348,7 @@ mod tests { device.add_read(&msg, 0); let data = [1u8; 228]; - let d = sendrecv(&mut device, U2FHID_PING, &data).unwrap(); + let d = sendrecv(&mut device, HIDCmd::Ping, &data).unwrap(); assert_eq!(d.len(), 228); assert_eq!(d, &data[..]); } @@ -424,33 +357,41 @@ mod tests { fn test_sendapdu() { let cid = [0x01, 0x02, 0x03, 0x04]; let data = [0x01, 0x02, 0x03, 0x04, 0x05]; - let mut device = platform::TestDevice::new(); + let mut device = Device::new("u2fprotocol").unwrap(); 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]); + msg.extend(vec![HIDCmd::Msg.into(), 0x00, 0x0e]); // len = 14 + // apdu header + msg.extend(vec![ + 0x00, + HIDCmd::Ping.into(), + 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(vec![HIDCmd::Msg.into(), 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(); + let (result, status) = send_ctap1(&mut device, HIDCmd::Ping.into(), 0xaa, &data).unwrap(); assert_eq!(result, &data); assert_eq!(status, SW_NO_ERROR); } #[test] fn test_get_property() { - let device = platform::TestDevice::new(); + let device = Device::new("u2fprotocol").unwrap(); 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 index 8360e8adbdbe..782c698c17cc 100644 --- a/third_party/rust/authenticator/src/u2ftypes.rs +++ b/third_party/rust/authenticator/src/u2ftypes.rs @@ -2,10 +2,10 @@ * 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; +use serde::Serialize; +use std::{cmp, fmt, io, str}; pub fn to_hex(data: &[u8], joiner: &str) -> String { let parts: Vec = data.iter().map(|byte| format!("{:02x}", byte)).collect(); @@ -54,7 +54,7 @@ pub trait U2FDevice { pub struct U2FHIDInit {} impl U2FHIDInit { - pub fn read(dev: &mut T) -> io::Result> + pub fn read(dev: &mut T) -> io::Result<(HIDCmd, Vec)> where T: U2FDevice + io::Read, { @@ -69,13 +69,15 @@ impl U2FHIDInit { return Err(io_err("invalid init packet")); } + let cmd = HIDCmd::from(frame[4] | TYPE_INIT); + 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) + Ok((cmd, data)) } pub fn write(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result @@ -169,7 +171,7 @@ pub struct U2FHIDInitResp { pub version_major: u8, pub version_minor: u8, pub version_build: u8, - pub cap_flags: u8, + pub cap_flags: Capability, } impl U2FHIDInitResp { @@ -195,40 +197,110 @@ impl U2FHIDInitResp { 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], + cap_flags: Capability::from_bits_truncate(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 {} +/// CTAP1 (FIDO v1.x / U2F / "APDU-like") request framing format, used for +/// communication with authenticators over *all* transports. +/// +/// This implementation follows the [FIDO v1.2 spec][fido12rawf], but only +/// implements extended APDUs (supported by USB HID, NFC and BLE transports). +/// +/// # Technical details +/// +/// FIDO v1.0 U2F framing [claims][fido10rawf] to be based on +/// [ISO/IEC 7816-4:2005][iso7816] (smart card) APDUs, but has several +/// differences, errors and omissions which make it incompatible. +/// +/// FIDO v1.1 and v1.2 fixed *most* of these issues, but as a result is *not* +/// fully compatible with the FIDO v1.0 specification: +/// +/// * FIDO v1.0 *only* defines extended APDUs, though +/// [v1.0 NFC implementors][fido10nfc] need to also handle short APDUs. +/// +/// FIDO v1.1 and later define *both* short and extended APDUs, but defers to +/// transport-level guidance about which to use (where extended APDU support +/// is mandatory for all transports, and short APDU support is only mandatory +/// for NFC transports). +/// +/// * FIDO v1.0 doesn't special-case Nc (command data length) = 0 +/// (ie: Lc is *always* present). +/// +/// * FIDO v1.0 declares extended Lc as a 24-bit integer, rather than +/// 16-bit with padding byte. +/// +/// * FIDO v1.0 omits Le bytes entirely, +/// [except for short APDUs over NFC][fido10nfc]. +/// +/// Unfortunately, FIDO v2.x gives ambiguous compatibility guidance: +/// +/// * [The FIDO v2.0 spec describes framing][fido20u2f] in +/// [FIDO v1.0 U2F Raw Message Format][fido10rawf], [cites][fido20u2fcite] the +/// FIDO v1.0 format by *name*, but actually links to the +/// [FIDO v1.2 format][fido12rawf]. +/// +/// * [The FIDO v2.1 spec also describes framing][fido21u2f] in +/// [FIDO v1.0 U2F Raw Message Format][fido10rawf], but [cites][fido21u2fcite] +/// the [FIDO v1.2 U2F Raw Message Format][fido12rawf] as a reference by name +/// and URL. +/// +/// [fido10nfc]: https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-nfc-protocol.html#framing +/// [fido10raw]: https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html +/// [fido10rawf]: https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing +/// [fido12rawf]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#u2f-message-framing +/// [fido20u2f]: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-framing +/// [fido20u2fcite]: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#biblio-u2frawmsgs +/// [fido21u2f]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#u2f-framing +/// [fido21u2fcite]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#biblio-u2frawmsgs +/// [iso7816]: https://www.iso.org/standard/36134.html +pub struct CTAP1RequestAPDU {} -impl U2FAPDUHeader { +impl CTAP1RequestAPDU { + /// Serializes a CTAP command into + /// [FIDO v1.2 U2F Raw Message Format][fido12raw]. See + /// [the struct documentation][Self] for implementation notes. + /// + /// # Arguments + /// + /// * `ins`: U2F command code, as documented in + /// [FIDO v1.2 U2F Raw Format][fido12cmd]. + /// * `p1`: Command parameter 1 / control byte. + /// * `data`: Request data, as documented in + /// [FIDO v1.2 Raw Message Formats][fido12raw], of up to 65535 bytes. + /// + /// [fido12cmd]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#command-and-parameter-values + /// [fido12raw]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html pub fn serialize(ins: u8, p1: u8, data: &[u8]) -> io::Result> { if data.len() > 0xffff { return Err(io_err("payload length > 2^16")); } + // Size of header + data. + let data_size = if data.is_empty() { 0 } else { 2 + data.len() }; + let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data_size]; - // 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[0] (CLA): Always 0 in FIDO v1.x. 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); + // bytes[3] (P2): Always 0 in FIDO v1.x. + // bytes[4] (Lc1/Le1): Always 0 for extended APDUs. + if !data.is_empty() { + bytes[5] = (data.len() >> 8) as u8; // Lc2 + bytes[6] = data.len() as u8; // Lc3 + + bytes[7..7 + data.len()].copy_from_slice(data); + } + + // Last two bytes (Le): Always 0 for Ne = 65536 Ok(bytes) } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub struct U2FDeviceInfo { pub vendor_name: Vec, pub device_name: Vec, @@ -236,7 +308,7 @@ pub struct U2FDeviceInfo { pub version_major: u8, pub version_minor: u8, pub version_build: u8, - pub cap_flags: u8, + pub cap_flags: Capability, } impl fmt::Display for U2FDeviceInfo { @@ -250,7 +322,42 @@ impl fmt::Display for U2FDeviceInfo { &self.version_major, &self.version_minor, &self.version_build, - to_hex(&[self.cap_flags], ":"), + to_hex(&[self.cap_flags.bits()], ":"), ) } } + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +pub(crate) mod tests { + use super::CTAP1RequestAPDU; + + #[test] + fn test_ctap1_serialize() { + // Command with no data, Lc should be omitted. + assert_eq!( + vec![0, 1, 2, 0, 0, 0, 0], + CTAP1RequestAPDU::serialize(1, 2, &[]).unwrap() + ); + + // Command with data, Lc should be included. + assert_eq!( + vec![0, 1, 2, 0, 0, 0, 1, 42, 0, 0], + CTAP1RequestAPDU::serialize(1, 2, &[42]).unwrap() + ); + + // Command with 300 bytes data, longer Lc. + let d = [0xFF; 300]; + let mut expected = vec![0, 1, 2, 0, 0, 0x1, 0x2c]; + expected.extend_from_slice(&d); + expected.extend_from_slice(&[0, 0]); // Lc + assert_eq!(expected, CTAP1RequestAPDU::serialize(1, 2, &d).unwrap()); + + // Command with 64k of data should error + let big = [0xFF; 65536]; + assert!(CTAP1RequestAPDU::serialize(1, 2, &big).is_err()); + } +} diff --git a/third_party/rust/authenticator/src/util.rs b/third_party/rust/authenticator/src/util.rs index 4ccb3c970338..221d718e3b29 100644 --- a/third_party/rust/authenticator/src/util.rs +++ b/third_party/rust/authenticator/src/util.rs @@ -23,13 +23,13 @@ pub trait Signed { impl Signed for i32 { fn is_negative(&self) -> bool { - *self < (0 as i32) + *self < 0 } } impl Signed for usize { fn is_negative(&self) -> bool { - (*self as isize) < (0 as isize) + (*self as isize) < 0 } } @@ -65,3 +65,11 @@ pub fn from_unix_result(rv: T) -> io::Result { pub fn io_err(msg: &str) -> io::Error { io::Error::new(io::ErrorKind::Other, msg) } + +#[cfg(test)] +pub fn decode_hex(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} diff --git a/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs index a88e74de50ab..cda4ca82fc16 100644 --- a/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs +++ b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs @@ -1,6 +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/. */ +use crate::consts::Capability; +use crate::{RegisterResult, SignResult}; pub struct SoftwareU2FToken {} @@ -21,7 +23,7 @@ impl SoftwareU2FToken { _application: crate::AppId, _key_handles: Vec, ) -> crate::Result { - Ok((vec![0u8; 16], self.dev_info())) + Ok(RegisterResult::CTAP1(vec![0u8; 16], self.dev_info())) } /// The implementation of this method must return quickly and should @@ -34,7 +36,12 @@ impl SoftwareU2FToken { _app_ids: Vec, _key_handles: Vec, ) -> crate::Result { - Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info())) + Ok(SignResult::CTAP1( + vec![0u8; 0], + vec![0u8; 0], + vec![0u8; 0], + self.dev_info(), + )) } pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo { @@ -45,7 +52,7 @@ impl SoftwareU2FToken { version_major: 1, version_minor: 2, version_build: 3, - cap_flags: 0, + cap_flags: Capability::empty(), } } } diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs index dc26df07ee9f..31e5d09e3a52 100644 --- a/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs @@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex}; use std::vec; use std::{io, string, thread}; -use crate::authenticatorservice::AuthenticatorTransport; +use crate::authenticatorservice::{AuthenticatorTransport, RegisterArgs, SignArgs}; use crate::errors; use crate::statecallback::StateCallback; use crate::virtualdevices::webdriver::{testtoken, web_api}; @@ -61,11 +61,8 @@ impl VirtualManager { impl AuthenticatorTransport for VirtualManager { fn register( &mut self, - _flags: crate::RegisterFlags, timeout: u64, - _challenge: Vec, - _application: crate::AppId, - _key_handles: Vec, + _ctap_args: RegisterArgs, _status: Sender, callback: StateCallback>, ) -> crate::Result<()> { @@ -105,11 +102,8 @@ impl AuthenticatorTransport for VirtualManager { fn sign( &mut self, - _flags: crate::SignFlags, timeout: u64, - _challenge: Vec, - _app_ids: Vec, - _key_handles: Vec, + _ctap_args: SignArgs, _status: Sender, callback: StateCallback>, ) -> crate::Result<()> { diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs index 909393866413..07bfc9f61248 100644 --- a/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs +++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs @@ -511,7 +511,6 @@ mod tests { use super::testtoken::*; use super::*; use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState; - use bytes::Buf; use std::sync::{Arc, Mutex}; use warp::http::StatusCode; @@ -591,8 +590,8 @@ mod tests { state } - fn assert_success_rsp_blank(body: &bytes::Bytes) { - assert_eq!(String::from_utf8_lossy(body.bytes()), r#"{}"#) + fn assert_success_rsp_blank(body: &warp::hyper::body::Bytes) { + assert_eq!(String::from_utf8_lossy(&body), r#"{}"#) } fn assert_creds_equals_test_token_params( @@ -653,7 +652,7 @@ mod tests { .reply(&filter) .await; assert!(res.status().is_client_error()); - assert!(String::from_utf8_lossy(res.body().bytes()) + assert!(String::from_utf8_lossy(&res.body()) .contains(&String::from("unknown protocol: unknown"))); } @@ -668,7 +667,7 @@ mod tests { .await; assert!(res.status().is_success()); assert_eq!( - String::from_utf8_lossy(res.body().bytes()), + String::from_utf8_lossy(&res.body()), r#"{"authenticatorId":1}"# ) } @@ -682,7 +681,7 @@ mod tests { .await; assert!(res.status().is_success()); assert_eq!( - String::from_utf8_lossy(res.body().bytes()), + String::from_utf8_lossy(&res.body()), r#"{"authenticatorId":2}"# ) } diff --git a/third_party/rust/authenticator/src/windows/device.rs b/third_party/rust/authenticator/src/windows/device.rs deleted file mode 100644 index 183ba71f44eb..000000000000 --- a/third_party/rust/authenticator/src/windows/device.rs +++ /dev/null @@ -1,97 +0,0 @@ -/* 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, -} - -impl Device { - pub fn new(path: String) -> io::Result { - 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 { - // 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 { - 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 { - 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/transaction.rs b/third_party/rust/authenticator/src/windows/transaction.rs deleted file mode 100644 index 74e856b6909f..000000000000 --- a/third_party/rust/authenticator/src/windows/transaction.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* 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, -} - -impl Transaction { - pub fn new( - timeout: u64, - callback: StateCallback>, - new_device_cb: F, - ) -> crate::Result - 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/half/.cargo-checksum.json b/third_party/rust/half/.cargo-checksum.json new file mode 100644 index 000000000000..daa061da50e3 --- /dev/null +++ b/third_party/rust/half/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"4c8b6a536c599b6024b2a4ad0028cb748af849c6c4291a8f27540e3f661ca4ab","Cargo.toml":"4971b6ea1ddd054a9cc6f9a5b82c7202cd8c101cfcccb9b4a72ec845333540d0","LICENSE":"36bb253818ac13761081556ff5c457d626da9df1eb4f56194d9ad3926c418a68","LICENSES/Apache-2.0.txt":"074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff","LICENSES/MIT.txt":"b85dcd3e453d05982552c52b5fc9e0bdd6d23c6f8e844b984a88af32570b0cc0","Makefile.toml":"4767bd456609d94162401fae19c6515fe855601cbbca1a763e217294144fb51e","README.md":"0c8e13a3fc02f4496ef37c2d119b3ab7801bc896d28c6ffa80009dda559e21bb","benches/convert.rs":"e601d5fbf21a3e6701fbe5bc95632a75cad48d814acbfbf221385b063ac8680b","src/bfloat.rs":"7d59a5d1d25f36487f2c7178a7100818d35de03d2303986282b881ae7b5956da","src/bfloat/convert.rs":"e41be00501e9f416ab48daba2a98dd73f3e5ababb44a8d24ca3b52d25b76ce44","src/binary16.rs":"dff79a878e5988357adcf4fcce083123e4278ccc795e4d8647757d6885278a30","src/binary16/convert.rs":"85a9e4b670a3043746365c443a1546333a160ae2a9312f0e64863a21b547ee00","src/lib.rs":"6501d943ff1d2b4cb50ca0a12abd0abc7596bffd8cfa78c2f7abe29a02016bee","src/num_traits.rs":"486b81b09b7135cc9025bfc046c93645db95d431eafc35780b3d65f97bc853ab","src/slice.rs":"73f9d58087177437f934c98304bf5c809aed1cb840f905e57ee21c8b74bcefb0","src/vec.rs":"8a54aedaca90787cab596ffbb55e6bebb36109f8dd43600dec97a05dbeda1225"},"package":"eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"} \ No newline at end of file diff --git a/third_party/rust/half/CHANGELOG.md b/third_party/rust/half/CHANGELOG.md new file mode 100644 index 000000000000..9dc6fe5ba2b3 --- /dev/null +++ b/third_party/rust/half/CHANGELOG.md @@ -0,0 +1,248 @@ +# Changelog + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.8.2] - 2021-10-22 +### Fixed +- Remove cargo resolver=2 from manifest to resolve errors in older versions of Rust that still + worked with 1.8.0. Going forward, MSRV increases will be major version increases. Fixes [#48]. + +## [1.8.1] - 2021-10-21 - **Yanked** +### ***Yanked*** +*Not recommended due to introducing compilation error in Rust versions that worked with 1.8.0.* +### Changed +- Now uses cargo resolver version 2 to prevent dev-dependencies from enabling `std` feature on + optional dependencies. + +### Fixed +- Fixed compile failure when `std` feature is not enabled and `num-traits` is enabled under new + resolver. Now properly uses `libm` num-traits feature. + +## [1.8.0] - 2021-10-13 +### Changed +- Now always implements `Add`, `Div`, `Mul`, `Neg`, `Rem`, and `Sub` traits. + Previously, these were only implemented under the `num-traits` feature. Keep in mind they still + convert to `f32` and back in the implementation. +- Minimum supported Rust version is now 1.51. +- Made crate package [REUSE compliant](https://reuse.software/). +- Docs now use intra-doc links instead of manual (and hard to maintain) links. +- The following methods on both `f16` and `bf16` are now `const`: + - `to_le_bytes` + - `to_be_bytes` + - `to_ne_bytes` + - `from_le_bytes` + - `from_be_bytes` + - `from_ne_bytes` + - `is_normal` + - `classify` + - `signum` + +### Added +- Added optional implementations of `zerocopy` traits `AsBytes` and `FromBytes` + under `zerocopy` cargo feature. By [@samcrow]. +- Implemented the `core::iter::Product` and `core::iter::Sum` traits, with the same caveat as above + about converting to `f32` and back under the hood. +- Added new associated const `NEG_ONE` to both `f16` and `bf16`. +- Added the following new methods on both `f16` and `bf16`: + - `copysign` + - `max` + - `min` + - `clamp` + +### Fixed +- Fixed a number of minor lints discovered due to improved CI. + +## [1.7.1] - 2021-01-17 +### Fixed +- Docs.rs now generates docs for `bytemuck` and `num-traits` optional features. + +## [1.7.0] - 2021-01-17 +### Added +- Added optional implementations of `bytemuck` traits `Zeroable` and `Pod` under `bytemuck` cargo + feature. By [@charles-r-earp]. +- Added optional implementations of `num-traits` traits `ToPrimitive` and `FromPrimitive` under + `num-traits` cargo feature. By [@charles-r-earp]. +- Added implementations of `Binary`, `Octal`, `LowerHex`, and `UpperHex` string format traits to + format raw `f16`/`bf16` bytes to string. + +### Changed +- `Debug` trait implementation now formats `f16`/`bf16` as float instead of raw bytes hex. Use newly + implemented formatting traits to format in hex instead of `Debug`. Fixes [#37]. + + +## [1.6.0] - 2020-05-09 +### Added +- Added `LOG2_10` and `LOG10_2` constants to both `f16` and `bf16`, which were added to `f32` and + `f64` in the standard library in 1.43.0. By [@tspiteri]. +- Added `to_le/be/ne_bytes` and `from_le/be/ne_bytes` to both `f16` and `bf16`, which were added to + the standard library in 1.40.0. By [@bzm3r]. + +## [1.5.0] - 2020-03-03 +### Added +- Added the `alloc` feature to support the `alloc` crate in `no_std` environments. By [@zserik]. The + `vec` module is now available with either `alloc` or `std` feature. + +## [1.4.1] - 2020-02-10 +### Fixed +- Added `#[repr(transparent)]` to `f16`/`bf16` to remove undefined behavior. By [@jfrimmel]. + +## [1.4.0] - 2019-10-13 +### Added +- Added a `bf16` type implementing the alternative + [`bfloat16`](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format) 16-bit floating point + format. By [@tspiteri]. +- `f16::from_bits`, `f16::to_bits`, `f16::is_nan`, `f16::is_infinite`, `f16::is_finite`, + `f16::is_sign_positive`, and `f16::is_sign_negative` are now `const` fns. +- `slice::HalfBitsSliceExt` and `slice::HalfBitsSliceExt` extension traits have been added for + performing efficient reinterpret casts and conversions of slices to and from `[f16]` and + `[bf16]`. These traits will use hardware SIMD conversion instructions when available and the + `use-intrinsics` cargo feature is enabled. +- `vec::HalfBitsVecExt` and `vec::HalfFloatVecExt` extension traits have been added for + performing efficient reinterpret casts to and from `Vec` and `Vec`. These traits + are only available with the `std` cargo feature. +- `prelude` has been added, for easy importing of most common functionality. Currently the + prelude imports `f16`, `bf16`, and the new slice and vec extension traits. +- New associated constants on `f16` type to replace deprecated `consts` module. + +### Fixed +- Software conversion (when not using `use-intrinsics` feature) now matches hardware rounding + by rounding to nearest, ties to even. Fixes [#24], by [@tspiteri]. +- NaN value conversions now behave like `f32` to `f64` conversions, retaining sign. Fixes [#23], + by [@tspiteri]. + +### Changed +- Minimum rustc version bumped to 1.32. +- Runtime target host feature detection is now used if both `std` and `use-intrinsics` features are + enabled and the compile target host does not support required features. +- When `use-intrinsics` feature is enabled, will now always compile and run without error correctly + regardless of compile target options. + +### Deprecated +- `consts` module and all its constants have been deprecated; use the associated constants on `f16` + instead. +- `slice::from_bits` has been deprecated; use `slice::HalfBitsSliceExt::reinterpret_cast` instead. +- `slice::from_bits_mut` has been deprecated; use `slice::HalfBitsSliceExt::reinterpret_cast_mut` + instead. +- `slice::to_bits` has been deprecated; use `slice::HalfFloatSliceExt::reinterpret_cast` instead. +- `slice::to_bits_mut` has been deprecated; use `slice::HalfFloatSliceExt::reinterpret_cast_mut` + instead. +- `vec::from_bits` has been deprecated; use `vec::HalfBitsVecExt::reinterpret_into` instead. +- `vec::to_bits` has been deprecated; use `vec::HalfFloatVecExt::reinterpret_into` instead. + +## [1.3.1] - 2019-10-04 +### Fixed +- Corrected values of constants `EPSILON`, `MAX_10_EXP`, `MAX_EXP`, `MIN_10_EXP`, and `MIN_EXP` + in `consts` module, as well as setting `consts::NAN` to match value of `f32::NAN` converted to + `f16`. By [@tspiteri]. + +## [1.3.0] - 2018-10-02 +### Added +- `slice::from_bits_mut` and `slice::to_bits_mut` for conversion between mutable `u16` and `f16` + slices. Fixes [#16], by [@johannesvollmer]. + +## [1.2.0] - 2018-09-03 +### Added +- `slice` and optional `vec` (only included with `std` feature) modules for conversions between + `u16` and `f16` buffers. Fixes [#14], by [@johannesvollmer]. +- `to_bits` added to replace `as_bits`. Fixes [#12], by [@tspiteri]. +### Fixed +- `serde` optional dependency no longer uses its default `std` feature. +### Deprecated +- `as_bits` has been deprecated; use `to_bits` instead. +- `serialize` cargo feature is deprecated; use `serde` instead. + +## [1.1.2] - 2018-07-12 +### Fixed +- Fixed compilation error in 1.1.1 on rustc < 1.27, now compiles again on rustc >= 1.10. Fixes + [#11]. + +## [1.1.1] - 2018-06-24 - **Yanked** +### ***Yanked*** +*Not recommended due to introducing compilation error on rustc versions prior to 1.27.* +### Fixed +- Fix subnormal float conversions when `use-intrinsics` is not enabled. By [@Moongoodboy-K]. + +## [1.1.0] - 2018-03-17 +### Added +- Made `to_f32` and `to_f64` public. Fixes [#7], by [@PSeitz]. + +## [1.0.2] - 2018-01-12 +### Changed +- Update behavior of `is_sign_positive` and `is_sign_negative` to match the IEEE754 conforming + behavior of the standard library since Rust 1.20.0. Fixes [#3], by [@tspiteri]. +- Small optimization on `is_nan` and `is_infinite` from [@tspiteri]. +### Fixed +- Fix comparisons of +0 to -0 and comparisons involving negative numbers. Fixes [#2], by + [@tspiteri]. +- Fix loss of sign when converting `f16` and `f32` to `f16`, and case where `f64` NaN could be + converted to `f16` infinity instead of NaN. Fixes [#5], by [@tspiteri]. + +## [1.0.1] - 2017-08-30 +### Added +- More README documentation. +- Badges and categories in crate metadata. +### Changed +- `serde` dependency updated to 1.0 stable. +- Writing changelog manually. + +## [1.0.0] - 2017-02-03 +### Added +- Update to `serde` 0.9 and stable Rust 1.15 for `serialize` feature. + +## [0.1.1] - 2017-01-08 +### Added +- Add `serde` support under new `serialize` feature. +### Changed +- Use `no_std` for crate by default. + +## 0.1.0 - 2016-03-17 +### Added +- Initial release of `f16` type. + +[#2]: https://github.com/starkat99/half-rs/issues/2 +[#3]: https://github.com/starkat99/half-rs/issues/3 +[#5]: https://github.com/starkat99/half-rs/issues/5 +[#7]: https://github.com/starkat99/half-rs/issues/7 +[#11]: https://github.com/starkat99/half-rs/issues/11 +[#12]: https://github.com/starkat99/half-rs/issues/12 +[#14]: https://github.com/starkat99/half-rs/issues/14 +[#16]: https://github.com/starkat99/half-rs/issues/16 +[#23]: https://github.com/starkat99/half-rs/issues/23 +[#24]: https://github.com/starkat99/half-rs/issues/24 +[#37]: https://github.com/starkat99/half-rs/issues/37 +[#48]: https://github.com/starkat99/half-rs/issues/48 + +[@tspiteri]: https://github.com/tspiteri +[@PSeitz]: https://github.com/PSeitz +[@Moongoodboy-K]: https://github.com/Moongoodboy-K +[@johannesvollmer]: https://github.com/johannesvollmer +[@jfrimmel]: https://github.com/jfrimmel +[@zserik]: https://github.com/zserik +[@bzm3r]: https://github.com/bzm3r +[@charles-r-earp]: https://github.com/charles-r-earp +[@samcrow]: https://github.com/samcrow + + +[Unreleased]: https://github.com/starkat99/half-rs/compare/v1.8.2...HEAD +[1.8.2]: https://github.com/starkat99/half-rs/compare/v1.8.1...v1.8.2 +[1.8.1]: https://github.com/starkat99/half-rs/compare/v1.8.0...v1.8.1 +[1.8.0]: https://github.com/starkat99/half-rs/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/starkat99/half-rs/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/starkat99/half-rs/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/starkat99/half-rs/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/starkat99/half-rs/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/starkat99/half-rs/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/starkat99/half-rs/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/starkat99/half-rs/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/starkat99/half-rs/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/starkat99/half-rs/compare/v1.1.2...v1.2.0 +[1.1.2]: https://github.com/starkat99/half-rs/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/starkat99/half-rs/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/starkat99/half-rs/compare/v1.0.2...v1.1.0 +[1.0.2]: https://github.com/starkat99/half-rs/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/starkat99/half-rs/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/starkat99/half-rs/compare/v0.1.1...v1.0.0 +[0.1.1]: https://github.com/starkat99/half-rs/compare/v0.1.0...v0.1.1 diff --git a/third_party/rust/half/Cargo.toml b/third_party/rust/half/Cargo.toml new file mode 100644 index 000000000000..588748bb9593 --- /dev/null +++ b/third_party/rust/half/Cargo.toml @@ -0,0 +1,69 @@ +# 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 are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "half" +version = "1.8.2" +authors = ["Kathryn Long "] +exclude = [".git*", ".editorconfig"] +description = "Half-precision floating point f16 and bf16 types for Rust implementing the IEEE 754-2008 standard binary16 and bfloat16 types." +readme = "README.md" +keywords = ["f16", "bfloat16", "no_std"] +categories = ["no-std", "data-structures", "encoding"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/starkat99/half-rs" +[package.metadata.docs.rs] +features = ["std", "serde", "bytemuck", "num-traits", "zerocopy"] +rustc-args = ["--cfg", "docsrs"] + +[[bench]] +name = "convert" +harness = false +[dependencies.bytemuck] +version = "1.4.1" +features = ["derive"] +optional = true +default-features = false + +[dependencies.num-traits] +version = "0.2.14" +features = ["libm"] +optional = true +default-features = false + +[dependencies.serde] +version = "1.0" +features = ["derive"] +optional = true +default-features = false + +[dependencies.zerocopy] +version = "0.6.0" +optional = true +default-features = false +[dev-dependencies.criterion] +version = "0.3.5" + +[dev-dependencies.quickcheck] +version = "1.0" + +[dev-dependencies.quickcheck_macros] +version = "1.0" + +[dev-dependencies.rand] +version = "0.8.4" + +[features] +alloc = [] +serialize = ["serde"] +std = ["alloc"] +use-intrinsics = [] diff --git a/third_party/rust/half/LICENSE b/third_party/rust/half/LICENSE new file mode 100644 index 000000000000..03f646f11f91 --- /dev/null +++ b/third_party/rust/half/LICENSE @@ -0,0 +1 @@ +MIT OR Apache-2.0 diff --git a/third_party/rust/half/LICENSES/Apache-2.0.txt b/third_party/rust/half/LICENSES/Apache-2.0.txt new file mode 100644 index 000000000000..137069b82387 --- /dev/null +++ b/third_party/rust/half/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/half/LICENSES/MIT.txt b/third_party/rust/half/LICENSES/MIT.txt new file mode 100644 index 000000000000..2071b23b0e08 --- /dev/null +++ b/third_party/rust/half/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/half/Makefile.toml b/third_party/rust/half/Makefile.toml new file mode 100644 index 000000000000..a87517e7ecad --- /dev/null +++ b/third_party/rust/half/Makefile.toml @@ -0,0 +1,58 @@ +[config] +min_version = "0.35.0" + +[env] +CI_CARGO_TEST_FLAGS = { value = "--locked -- --nocapture", condition = { env_true = [ + "CARGO_MAKE_CI", +] } } +CARGO_MAKE_CARGO_ALL_FEATURES = { source = "${CARGO_MAKE_RUST_CHANNEL}", default_value = "--features=std,serde,num-traits,bytemuck,zerocopy", mapping = { "nightly" = "--all-features" } } +CARGO_MAKE_CLIPPY_ARGS = { value = "${CARGO_MAKE_CLIPPY_ALL_FEATURES_WARN}", condition = { env_true = [ + "CARGO_MAKE_CI", +] } } + +# Override for CI flag additions +[tasks.test] +args = [ + "test", + "@@remove-empty(CARGO_MAKE_CARGO_VERBOSE_FLAGS)", + "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )", + "@@split(CI_CARGO_TEST_FLAGS, )", +] + +# Let clippy run on non-nightly CI +[tasks.clippy-ci-flow] +condition = { env_set = ["CARGO_MAKE_RUN_CLIPPY"] } + +# Let format check run on non-nightly CI +[tasks.check-format-ci-flow] +condition = { env_set = ["CARGO_MAKE_RUN_CHECK_FORMAT"] } + +[tasks.check-docs] +description = "Checks docs for errors." +category = "Documentation" +install_crate = false +env = { RUSTDOCFLAGS = "-D warnings" } +command = "cargo" +args = [ + "doc", + "--workspace", + "--no-deps", + "@@remove-empty(CARGO_MAKE_CARGO_VERBOSE_FLAGS)", + "${CARGO_MAKE_CARGO_ALL_FEATURES}", +] + +# Build & Test with no features enabled +[tasks.post-ci-flow] +run_task = [{ name = ["check-docs", "build-no-std", "test-no-std"] }] + +[tasks.build-no-std] +description = "Build without any features" +category = "Build" +env = { CARGO_MAKE_CARGO_BUILD_TEST_FLAGS = "--no-default-features" } +run_task = "build" + +[tasks.test-no-std] +description = "Run tests without any features" +category = "Test" +env = { CARGO_MAKE_CARGO_BUILD_TEST_FLAGS = "--no-default-features" } +run_task = "test" diff --git a/third_party/rust/half/README.md b/third_party/rust/half/README.md new file mode 100644 index 000000000000..ac6fa3c0aeae --- /dev/null +++ b/third_party/rust/half/README.md @@ -0,0 +1,82 @@ +# `f16` and `bf16` floating point types for Rust +[![Crates.io](https://img.shields.io/crates/v/half.svg)](https://crates.io/crates/half/) [![Documentation](https://docs.rs/half/badge.svg)](https://docs.rs/half/) ![Crates.io](https://img.shields.io/crates/l/half) [![Build status](https://github.com/starkat99/half-rs/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/starkat99/half-rs/actions/workflows/rust.yml) + +This crate implements a half-precision floating point `f16` type for Rust implementing the IEEE +754-2008 standard [`binary16`](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) +a.k.a `half` format, as well as a `bf16` type implementing the +[`bfloat16`](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format) format. + +## Usage + +The `f16` and `bf16` types provides conversion operations as a normal Rust floating point type, but +since they are primarily leveraged for minimal floating point storage and most major hardware does +not implement them, all math operations are done as an `f32` type under the hood. Complex arithmetic +should manually convert to and from `f32` for better performance. + +This crate provides [`no_std`](https://rust-embedded.github.io/book/intro/no-std.html) support by +default so can easily be used in embedded code where a smaller float format is most useful. + +*Requires Rust 1.51 or greater.* If you need support for older versions of Rust, use versions 1.7.1 +and earlier of this crate. + +See the [crate documentation](https://docs.rs/half/) for more details. + +### Optional Features + +- **`serde`** - Implement `Serialize` and `Deserialize` traits for `f16` and `bf16`. This adds a + dependency on the [`serde`](https://crates.io/crates/serde) crate. + +- **`use-intrinsics`** - Use hardware intrinsics for `f16` and `bf16` conversions if available on + the compiler host target. By default, without this feature, conversions are done only in software, + which will be the fallback if the host target does not have hardware support. **Available only on + Rust nightly channel.** + +- **`alloc`** - Enable use of the [`alloc`](https://doc.rust-lang.org/alloc/) crate when not using + the `std` library. + + This enables the `vec` module, which contains zero-copy conversions for the `Vec` type. This + allows fast conversion between raw `Vec` bits and `Vec` or `Vec` arrays, and vice + versa. + +- **`std`** - Enable features that depend on the Rust `std` library, including everything in the + `alloc` feature. + + Enabling the `std` feature enables runtime CPU feature detection when the `use-intrsincis` feature + is also enabled. + Without this feature detection, intrinsics are only used when compiler host target supports them. + +- **`num-traits`** - Enable `ToPrimitive`, `FromPrimitive`, `Num`, `Float`, `FloatCore` and + `Bounded` trait implementations from the [`num-traits`](https://crates.io/crates/num-traits) crate. + +- **`bytemuck`** - Enable `Zeroable` and `Pod` trait implementations from the + [`bytemuck`](https://crates.io/crates/bytemuck) crate. + +- **`zerocopy`** - Enable `AsBytes` and `FromBytes` trait implementations from the + [`zerocopy`](https://crates.io/crates/zerocopy) crate. + +### More Documentation + +- [Crate API Reference](https://docs.rs/half/) +- [Latest Changes](CHANGELOG.md) + +## License + +This library is distributed under the terms of either of: + +* [MIT License](LICENSES/MIT.txt) + ([http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) +* [Apache License, Version 2.0](LICENSES/Apache-2.0.txt) + ([http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) + +at your option. + +This project is [REUSE-compliant](https://reuse.software/spec/). Copyrights are retained by their +contributors. Some files may include explicit copyright notices and/or license +[SPDX identifiers](https://spdx.dev/ids/). For full authorship information, see the version control +history. + +### Contributing + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/third_party/rust/half/benches/convert.rs b/third_party/rust/half/benches/convert.rs new file mode 100644 index 000000000000..53bae7020881 --- /dev/null +++ b/third_party/rust/half/benches/convert.rs @@ -0,0 +1,327 @@ +use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; +use half::prelude::*; +use std::{f32, f64, iter}; + +const SIMD_LARGE_BENCH_SLICE_LEN: usize = 1024; + +fn bench_f32_to_f16(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert f16 From f32"); + for val in &[ + 0., + -0., + 1., + f32::MIN, + f32::MAX, + f32::MIN_POSITIVE, + f32::NEG_INFINITY, + f32::INFINITY, + f32::NAN, + f32::consts::E, + f32::consts::PI, + ] { + group.bench_with_input(BenchmarkId::new("f16::from_f32", val), val, |b, i| { + b.iter(|| f16::from_f32(*i)) + }); + } +} + +fn bench_f64_to_f16(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert f16 From f64"); + for val in &[ + 0., + -0., + 1., + f64::MIN, + f64::MAX, + f64::MIN_POSITIVE, + f64::NEG_INFINITY, + f64::INFINITY, + f64::NAN, + f64::consts::E, + f64::consts::PI, + ] { + group.bench_with_input(BenchmarkId::new("f16::from_f64", val), val, |b, i| { + b.iter(|| f16::from_f64(*i)) + }); + } +} + +fn bench_f16_to_f32(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert f16 to f32"); + for val in &[ + f16::ZERO, + f16::NEG_ZERO, + f16::ONE, + f16::MIN, + f16::MAX, + f16::MIN_POSITIVE, + f16::NEG_INFINITY, + f16::INFINITY, + f16::NAN, + f16::E, + f16::PI, + ] { + group.bench_with_input(BenchmarkId::new("f16::to_f32", val), val, |b, i| { + b.iter(|| i.to_f32()) + }); + } +} + +fn bench_f16_to_f64(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert f16 to f64"); + for val in &[ + f16::ZERO, + f16::NEG_ZERO, + f16::ONE, + f16::MIN, + f16::MAX, + f16::MIN_POSITIVE, + f16::NEG_INFINITY, + f16::INFINITY, + f16::NAN, + f16::E, + f16::PI, + ] { + group.bench_with_input(BenchmarkId::new("f16::to_f64", val), val, |b, i| { + b.iter(|| i.to_f64()) + }); + } +} + +criterion_group!( + f16_sisd, + bench_f32_to_f16, + bench_f64_to_f16, + bench_f16_to_f32, + bench_f16_to_f64 +); + +fn bench_slice_f32_to_f16(c: &mut Criterion) { + let mut constant_buffer = [f16::ZERO; 11]; + let constants = [ + 0., + -0., + 1., + f32::MIN, + f32::MAX, + f32::MIN_POSITIVE, + f32::NEG_INFINITY, + f32::INFINITY, + f32::NAN, + f32::consts::E, + f32::consts::PI, + ]; + c.bench_function( + "HalfFloatSliceExt::convert_from_f32_slice/constants", + |b: &mut Bencher<'_>| b.iter(|| constant_buffer.convert_from_f32_slice(&constants)), + ); + + let large: Vec<_> = iter::repeat(0) + .enumerate() + .map(|(i, _)| i as f32) + .take(SIMD_LARGE_BENCH_SLICE_LEN) + .collect(); + let mut large_buffer = [f16::ZERO; SIMD_LARGE_BENCH_SLICE_LEN]; + c.bench_function( + "HalfFloatSliceExt::convert_from_f32_slice/large", + |b: &mut Bencher<'_>| b.iter(|| large_buffer.convert_from_f32_slice(&large)), + ); +} + +fn bench_slice_f64_to_f16(c: &mut Criterion) { + let mut constant_buffer = [f16::ZERO; 11]; + let constants = [ + 0., + -0., + 1., + f64::MIN, + f64::MAX, + f64::MIN_POSITIVE, + f64::NEG_INFINITY, + f64::INFINITY, + f64::NAN, + f64::consts::E, + f64::consts::PI, + ]; + c.bench_function( + "HalfFloatSliceExt::convert_from_f64_slice/constants", + |b: &mut Bencher<'_>| b.iter(|| constant_buffer.convert_from_f64_slice(&constants)), + ); + + let large: Vec<_> = iter::repeat(0) + .enumerate() + .map(|(i, _)| i as f64) + .take(SIMD_LARGE_BENCH_SLICE_LEN) + .collect(); + let mut large_buffer = [f16::ZERO; SIMD_LARGE_BENCH_SLICE_LEN]; + c.bench_function( + "HalfFloatSliceExt::convert_from_f64_slice/large", + |b: &mut Bencher<'_>| b.iter(|| large_buffer.convert_from_f64_slice(&large)), + ); +} + +fn bench_slice_f16_to_f32(c: &mut Criterion) { + let mut constant_buffer = [0f32; 11]; + let constants = [ + f16::ZERO, + f16::NEG_ZERO, + f16::ONE, + f16::MIN, + f16::MAX, + f16::MIN_POSITIVE, + f16::NEG_INFINITY, + f16::INFINITY, + f16::NAN, + f16::E, + f16::PI, + ]; + c.bench_function( + "HalfFloatSliceExt::convert_to_f32_slice/constants", + |b: &mut Bencher<'_>| b.iter(|| constants.convert_to_f32_slice(&mut constant_buffer)), + ); + + let large: Vec<_> = iter::repeat(0) + .enumerate() + .map(|(i, _)| f16::from_f32(i as f32)) + .take(SIMD_LARGE_BENCH_SLICE_LEN) + .collect(); + let mut large_buffer = [0f32; SIMD_LARGE_BENCH_SLICE_LEN]; + c.bench_function( + "HalfFloatSliceExt::convert_to_f32_slice/large", + |b: &mut Bencher<'_>| b.iter(|| large.convert_to_f32_slice(&mut large_buffer)), + ); +} + +fn bench_slice_f16_to_f64(c: &mut Criterion) { + let mut constant_buffer = [0f64; 11]; + let constants = [ + f16::ZERO, + f16::NEG_ZERO, + f16::ONE, + f16::MIN, + f16::MAX, + f16::MIN_POSITIVE, + f16::NEG_INFINITY, + f16::INFINITY, + f16::NAN, + f16::E, + f16::PI, + ]; + c.bench_function( + "HalfFloatSliceExt::convert_to_f64_slice/constants", + |b: &mut Bencher<'_>| b.iter(|| constants.convert_to_f64_slice(&mut constant_buffer)), + ); + + let large: Vec<_> = iter::repeat(0) + .enumerate() + .map(|(i, _)| f16::from_f64(i as f64)) + .take(SIMD_LARGE_BENCH_SLICE_LEN) + .collect(); + let mut large_buffer = [0f64; SIMD_LARGE_BENCH_SLICE_LEN]; + c.bench_function( + "HalfFloatSliceExt::convert_to_f64_slice/large", + |b: &mut Bencher<'_>| b.iter(|| large.convert_to_f64_slice(&mut large_buffer)), + ); +} + +criterion_group!( + f16_simd, + bench_slice_f32_to_f16, + bench_slice_f64_to_f16, + bench_slice_f16_to_f32, + bench_slice_f16_to_f64 +); + +fn bench_f32_to_bf16(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert bf16 From f32"); + for val in &[ + 0., + -0., + 1., + f32::MIN, + f32::MAX, + f32::MIN_POSITIVE, + f32::NEG_INFINITY, + f32::INFINITY, + f32::NAN, + f32::consts::E, + f32::consts::PI, + ] { + group.bench_with_input(BenchmarkId::new("bf16::from_f32", val), val, |b, i| { + b.iter(|| bf16::from_f32(*i)) + }); + } +} + +fn bench_f64_to_bf16(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert bf16 From f64"); + for val in &[ + 0., + -0., + 1., + f64::MIN, + f64::MAX, + f64::MIN_POSITIVE, + f64::NEG_INFINITY, + f64::INFINITY, + f64::NAN, + f64::consts::E, + f64::consts::PI, + ] { + group.bench_with_input(BenchmarkId::new("bf16::from_f64", val), val, |b, i| { + b.iter(|| bf16::from_f64(*i)) + }); + } +} + +fn bench_bf16_to_f32(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert bf16 to f32"); + for val in &[ + bf16::ZERO, + bf16::NEG_ZERO, + bf16::ONE, + bf16::MIN, + bf16::MAX, + bf16::MIN_POSITIVE, + bf16::NEG_INFINITY, + bf16::INFINITY, + bf16::NAN, + bf16::E, + bf16::PI, + ] { + group.bench_with_input(BenchmarkId::new("bf16::to_f32", val), val, |b, i| { + b.iter(|| i.to_f32()) + }); + } +} + +fn bench_bf16_to_f64(c: &mut Criterion) { + let mut group = c.benchmark_group("Convert bf16 to f64"); + for val in &[ + bf16::ZERO, + bf16::NEG_ZERO, + bf16::ONE, + bf16::MIN, + bf16::MAX, + bf16::MIN_POSITIVE, + bf16::NEG_INFINITY, + bf16::INFINITY, + bf16::NAN, + bf16::E, + bf16::PI, + ] { + group.bench_with_input(BenchmarkId::new("bf16::to_f64", val), val, |b, i| { + b.iter(|| i.to_f64()) + }); + } +} + +criterion_group!( + bf16_sisd, + bench_f32_to_bf16, + bench_f64_to_bf16, + bench_bf16_to_f32, + bench_bf16_to_f64 +); + +criterion_main!(f16_sisd, bf16_sisd, f16_simd); diff --git a/third_party/rust/half/src/bfloat.rs b/third_party/rust/half/src/bfloat.rs new file mode 100644 index 000000000000..f7a840ebe1c3 --- /dev/null +++ b/third_party/rust/half/src/bfloat.rs @@ -0,0 +1,1555 @@ +#[cfg(feature = "bytemuck")] +use bytemuck::{Pod, Zeroable}; +use core::{ + cmp::Ordering, + fmt::{ + Binary, Debug, Display, Error, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex, + }, + iter::{Product, Sum}, + num::{FpCategory, ParseFloatError}, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, + str::FromStr, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "zerocopy")] +use zerocopy::{AsBytes, FromBytes}; + +pub(crate) mod convert; + +/// A 16-bit floating point type implementing the [`bfloat16`] format. +/// +/// The [`bfloat16`] floating point format is a truncated 16-bit version of the IEEE 754 standard +/// `binary32`, a.k.a [`f32`]. [`bf16`] has approximately the same dynamic range as [`f32`] by +/// having a lower precision than [`f16`][crate::f16]. While [`f16`][crate::f16] has a precision of +/// 11 bits, [`bf16`] has a precision of only 8 bits. +/// +/// Like [`f16`][crate::f16], [`bf16`] does not offer arithmetic operations as it is intended for +/// compact storage rather than calculations. Operations should be performed with [`f32`] or +/// higher-precision types and converted to/from [`bf16`] as necessary. +/// +/// [`bfloat16`]: https://en.wikipedia.org/wiki/Bfloat16_floating-point_format +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))] +#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))] +pub struct bf16(u16); + +impl bf16 { + /// Constructs a [`bf16`] value from the raw bits. + #[inline] + pub const fn from_bits(bits: u16) -> bf16 { + bf16(bits) + } + + /// Constructs a [`bf16`] value from a 32-bit floating point value. + /// + /// If the 32-bit value is too large to fit, ±∞ will result. NaN values are preserved. + /// Subnormal values that are too tiny to be represented will result in ±0. All other values + /// are truncated and rounded to the nearest representable value. + #[inline] + pub fn from_f32(value: f32) -> bf16 { + bf16(convert::f32_to_bf16(value)) + } + + /// Constructs a [`bf16`] value from a 64-bit floating point value. + /// + /// If the 64-bit value is to large to fit, ±∞ will result. NaN values are preserved. + /// 64-bit subnormal values are too tiny to be represented and result in ±0. Exponents that + /// underflow the minimum exponent will result in subnormals or ±0. All other values are + /// truncated and rounded to the nearest representable value. + #[inline] + pub fn from_f64(value: f64) -> bf16 { + bf16(convert::f64_to_bf16(value)) + } + + /// Converts a [`bf16`] into the underlying bit representation. + #[inline] + pub const fn to_bits(self) -> u16 { + self.0 + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// little-endian byte order. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = bf16::from_f32(12.5).to_le_bytes(); + /// assert_eq!(bytes, [0x48, 0x41]); + /// ``` + #[inline] + pub const fn to_le_bytes(self) -> [u8; 2] { + self.0.to_le_bytes() + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// big-endian (network) byte order. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = bf16::from_f32(12.5).to_be_bytes(); + /// assert_eq!(bytes, [0x41, 0x48]); + /// ``` + #[inline] + pub const fn to_be_bytes(self) -> [u8; 2] { + self.0.to_be_bytes() + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// native byte order. + /// + /// As the target platform's native endianness is used, portable code should use + /// [`to_be_bytes`][bf16::to_be_bytes] or [`to_le_bytes`][bf16::to_le_bytes], as appropriate, + /// instead. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = bf16::from_f32(12.5).to_ne_bytes(); + /// assert_eq!(bytes, if cfg!(target_endian = "big") { + /// [0x41, 0x48] + /// } else { + /// [0x48, 0x41] + /// }); + /// ``` + #[inline] + pub const fn to_ne_bytes(self) -> [u8; 2] { + self.0.to_ne_bytes() + } + + /// Creates a floating point value from its representation as a byte array in little endian. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = bf16::from_le_bytes([0x48, 0x41]); + /// assert_eq!(value, bf16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_le_bytes(bytes: [u8; 2]) -> bf16 { + bf16::from_bits(u16::from_le_bytes(bytes)) + } + + /// Creates a floating point value from its representation as a byte array in big endian. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = bf16::from_be_bytes([0x41, 0x48]); + /// assert_eq!(value, bf16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_be_bytes(bytes: [u8; 2]) -> bf16 { + bf16::from_bits(u16::from_be_bytes(bytes)) + } + + /// Creates a floating point value from its representation as a byte array in native endian. + /// + /// As the target platform's native endianness is used, portable code likely wants to use + /// [`from_be_bytes`][bf16::from_be_bytes] or [`from_le_bytes`][bf16::from_le_bytes], as + /// appropriate instead. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = bf16::from_ne_bytes(if cfg!(target_endian = "big") { + /// [0x41, 0x48] + /// } else { + /// [0x48, 0x41] + /// }); + /// assert_eq!(value, bf16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_ne_bytes(bytes: [u8; 2]) -> bf16 { + bf16::from_bits(u16::from_ne_bytes(bytes)) + } + + /// Converts a [`bf16`] value into an [`f32`] value. + /// + /// This conversion is lossless as all values can be represented exactly in [`f32`]. + #[inline] + pub fn to_f32(self) -> f32 { + convert::bf16_to_f32(self.0) + } + + /// Converts a [`bf16`] value into an [`f64`] value. + /// + /// This conversion is lossless as all values can be represented exactly in [`f64`]. + #[inline] + pub fn to_f64(self) -> f64 { + convert::bf16_to_f64(self.0) + } + + /// Returns `true` if this value is NaN and `false` otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = bf16::NAN; + /// let f = bf16::from_f32(7.0_f32); + /// + /// assert!(nan.is_nan()); + /// assert!(!f.is_nan()); + /// ``` + #[inline] + pub const fn is_nan(self) -> bool { + self.0 & 0x7FFFu16 > 0x7F80u16 + } + + /// Returns `true` if this value is ±∞ and `false` otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = bf16::from_f32(7.0f32); + /// let inf = bf16::INFINITY; + /// let neg_inf = bf16::NEG_INFINITY; + /// let nan = bf16::NAN; + /// + /// assert!(!f.is_infinite()); + /// assert!(!nan.is_infinite()); + /// + /// assert!(inf.is_infinite()); + /// assert!(neg_inf.is_infinite()); + /// ``` + #[inline] + pub const fn is_infinite(self) -> bool { + self.0 & 0x7FFFu16 == 0x7F80u16 + } + + /// Returns `true` if this number is neither infinite nor NaN. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = bf16::from_f32(7.0f32); + /// let inf = bf16::INFINITY; + /// let neg_inf = bf16::NEG_INFINITY; + /// let nan = bf16::NAN; + /// + /// assert!(f.is_finite()); + /// + /// assert!(!nan.is_finite()); + /// assert!(!inf.is_finite()); + /// assert!(!neg_inf.is_finite()); + /// ``` + #[inline] + pub const fn is_finite(self) -> bool { + self.0 & 0x7F80u16 != 0x7F80u16 + } + + /// Returns `true` if the number is neither zero, infinite, subnormal, or NaN. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let min = bf16::MIN_POSITIVE; + /// let max = bf16::MAX; + /// let lower_than_min = bf16::from_f32(1.0e-39_f32); + /// let zero = bf16::from_f32(0.0_f32); + /// + /// assert!(min.is_normal()); + /// assert!(max.is_normal()); + /// + /// assert!(!zero.is_normal()); + /// assert!(!bf16::NAN.is_normal()); + /// assert!(!bf16::INFINITY.is_normal()); + /// // Values between 0 and `min` are subnormal. + /// assert!(!lower_than_min.is_normal()); + /// ``` + #[inline] + pub const fn is_normal(self) -> bool { + let exp = self.0 & 0x7F80u16; + exp != 0x7F80u16 && exp != 0 + } + + /// Returns the floating point category of the number. + /// + /// If only one property is going to be tested, it is generally faster to use the specific + /// predicate instead. + /// + /// # Examples + /// + /// ```rust + /// use std::num::FpCategory; + /// # use half::prelude::*; + /// + /// let num = bf16::from_f32(12.4_f32); + /// let inf = bf16::INFINITY; + /// + /// assert_eq!(num.classify(), FpCategory::Normal); + /// assert_eq!(inf.classify(), FpCategory::Infinite); + /// ``` + pub const fn classify(self) -> FpCategory { + let exp = self.0 & 0x7F80u16; + let man = self.0 & 0x007Fu16; + match (exp, man) { + (0, 0) => FpCategory::Zero, + (0, _) => FpCategory::Subnormal, + (0x7F80u16, 0) => FpCategory::Infinite, + (0x7F80u16, _) => FpCategory::Nan, + _ => FpCategory::Normal, + } + } + + /// Returns a number that represents the sign of `self`. + /// + /// * 1.0 if the number is positive, +0.0 or [`INFINITY`][bf16::INFINITY] + /// * −1.0 if the number is negative, −0.0` or [`NEG_INFINITY`][bf16::NEG_INFINITY] + /// * [`NAN`][bf16::NAN] if the number is NaN + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = bf16::from_f32(3.5_f32); + /// + /// assert_eq!(f.signum(), bf16::from_f32(1.0)); + /// assert_eq!(bf16::NEG_INFINITY.signum(), bf16::from_f32(-1.0)); + /// + /// assert!(bf16::NAN.signum().is_nan()); + /// ``` + pub const fn signum(self) -> bf16 { + if self.is_nan() { + self + } else if self.0 & 0x8000u16 != 0 { + Self::NEG_ONE + } else { + Self::ONE + } + } + + /// Returns `true` if and only if `self` has a positive sign, including +0.0, NaNs with a + /// positive sign bit and +∞. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = bf16::NAN; + /// let f = bf16::from_f32(7.0_f32); + /// let g = bf16::from_f32(-7.0_f32); + /// + /// assert!(f.is_sign_positive()); + /// assert!(!g.is_sign_positive()); + /// // NaN can be either positive or negative + /// assert!(nan.is_sign_positive() != nan.is_sign_negative()); + /// ``` + #[inline] + pub const fn is_sign_positive(self) -> bool { + self.0 & 0x8000u16 == 0 + } + + /// Returns `true` if and only if `self` has a negative sign, including −0.0, NaNs with a + /// negative sign bit and −∞. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = bf16::NAN; + /// let f = bf16::from_f32(7.0f32); + /// let g = bf16::from_f32(-7.0f32); + /// + /// assert!(!f.is_sign_negative()); + /// assert!(g.is_sign_negative()); + /// // NaN can be either positive or negative + /// assert!(nan.is_sign_positive() != nan.is_sign_negative()); + /// ``` + #[inline] + pub const fn is_sign_negative(self) -> bool { + self.0 & 0x8000u16 != 0 + } + + /// Returns a number composed of the magnitude of `self` and the sign of `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is NaN, then NaN with the sign of `sign` is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let f = bf16::from_f32(3.5); + /// + /// assert_eq!(f.copysign(bf16::from_f32(0.42)), bf16::from_f32(3.5)); + /// assert_eq!(f.copysign(bf16::from_f32(-0.42)), bf16::from_f32(-3.5)); + /// assert_eq!((-f).copysign(bf16::from_f32(0.42)), bf16::from_f32(3.5)); + /// assert_eq!((-f).copysign(bf16::from_f32(-0.42)), bf16::from_f32(-3.5)); + /// + /// assert!(bf16::NAN.copysign(bf16::from_f32(1.0)).is_nan()); + /// ``` + #[inline] + pub const fn copysign(self, sign: bf16) -> bf16 { + bf16((sign.0 & 0x8000u16) | (self.0 & 0x7FFFu16)) + } + + /// Returns the maximum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let x = bf16::from_f32(1.0); + /// let y = bf16::from_f32(2.0); + /// + /// assert_eq!(x.max(y), y); + /// ``` + #[inline] + pub fn max(self, other: bf16) -> bf16 { + if other > self && !other.is_nan() { + other + } else { + self + } + } + + /// Returns the minimum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let x = bf16::from_f32(1.0); + /// let y = bf16::from_f32(2.0); + /// + /// assert_eq!(x.min(y), x); + /// ``` + #[inline] + pub fn min(self, other: bf16) -> bf16 { + if other < self && !other.is_nan() { + other + } else { + self + } + } + + /// Restrict a value to a certain interval unless it is NaN. + /// + /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`. + /// Otherwise this returns `self`. + /// + /// Note that this function returns NaN if the initial value was NaN as well. + /// + /// # Panics + /// Panics if `min > max`, `min` is NaN, or `max` is NaN. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// assert!(bf16::from_f32(-3.0).clamp(bf16::from_f32(-2.0), bf16::from_f32(1.0)) == bf16::from_f32(-2.0)); + /// assert!(bf16::from_f32(0.0).clamp(bf16::from_f32(-2.0), bf16::from_f32(1.0)) == bf16::from_f32(0.0)); + /// assert!(bf16::from_f32(2.0).clamp(bf16::from_f32(-2.0), bf16::from_f32(1.0)) == bf16::from_f32(1.0)); + /// assert!(bf16::NAN.clamp(bf16::from_f32(-2.0), bf16::from_f32(1.0)).is_nan()); + /// ``` + #[inline] + pub fn clamp(self, min: bf16, max: bf16) -> bf16 { + assert!(min <= max); + let mut x = self; + if x < min { + x = min; + } + if x > max { + x = max; + } + x + } + + /// Approximate number of [`bf16`] significant digits in base 10 + pub const DIGITS: u32 = 2; + /// [`bf16`] + /// [machine epsilon](https://en.wikipedia.org/wiki/Machine_epsilon) value + /// + /// This is the difference between 1.0 and the next largest representable number. + pub const EPSILON: bf16 = bf16(0x3C00u16); + /// [`bf16`] positive Infinity (+∞) + pub const INFINITY: bf16 = bf16(0x7F80u16); + /// Number of [`bf16`] significant digits in base 2 + pub const MANTISSA_DIGITS: u32 = 8; + /// Largest finite [`bf16`] value + pub const MAX: bf16 = bf16(0x7F7F); + /// Maximum possible [`bf16`] power of 10 exponent + pub const MAX_10_EXP: i32 = 38; + /// Maximum possible [`bf16`] power of 2 exponent + pub const MAX_EXP: i32 = 128; + /// Smallest finite [`bf16`] value + pub const MIN: bf16 = bf16(0xFF7F); + /// Minimum possible normal [`bf16`] power of 10 exponent + pub const MIN_10_EXP: i32 = -37; + /// One greater than the minimum possible normal [`bf16`] power of 2 exponent + pub const MIN_EXP: i32 = -125; + /// Smallest positive normal [`bf16`] value + pub const MIN_POSITIVE: bf16 = bf16(0x0080u16); + /// [`bf16`] Not a Number (NaN) + pub const NAN: bf16 = bf16(0x7FC0u16); + /// [`bf16`] negative infinity (-∞). + pub const NEG_INFINITY: bf16 = bf16(0xFF80u16); + /// The radix or base of the internal representation of [`bf16`] + pub const RADIX: u32 = 2; + + /// Minimum positive subnormal [`bf16`] value + pub const MIN_POSITIVE_SUBNORMAL: bf16 = bf16(0x0001u16); + /// Maximum subnormal [`bf16`] value + pub const MAX_SUBNORMAL: bf16 = bf16(0x007Fu16); + + /// [`bf16`] 1 + pub const ONE: bf16 = bf16(0x3F80u16); + /// [`bf16`] 0 + pub const ZERO: bf16 = bf16(0x0000u16); + /// [`bf16`] -0 + pub const NEG_ZERO: bf16 = bf16(0x8000u16); + /// [`bf16`] -1 + pub const NEG_ONE: bf16 = bf16(0xBF80u16); + + /// [`bf16`] Euler's number (ℯ) + pub const E: bf16 = bf16(0x402Eu16); + /// [`bf16`] Archimedes' constant (Ï€) + pub const PI: bf16 = bf16(0x4049u16); + /// [`bf16`] 1/Ï€ + pub const FRAC_1_PI: bf16 = bf16(0x3EA3u16); + /// [`bf16`] 1/√2 + pub const FRAC_1_SQRT_2: bf16 = bf16(0x3F35u16); + /// [`bf16`] 2/Ï€ + pub const FRAC_2_PI: bf16 = bf16(0x3F23u16); + /// [`bf16`] 2/√π + pub const FRAC_2_SQRT_PI: bf16 = bf16(0x3F90u16); + /// [`bf16`] Ï€/2 + pub const FRAC_PI_2: bf16 = bf16(0x3FC9u16); + /// [`bf16`] Ï€/3 + pub const FRAC_PI_3: bf16 = bf16(0x3F86u16); + /// [`bf16`] Ï€/4 + pub const FRAC_PI_4: bf16 = bf16(0x3F49u16); + /// [`bf16`] Ï€/6 + pub const FRAC_PI_6: bf16 = bf16(0x3F06u16); + /// [`bf16`] Ï€/8 + pub const FRAC_PI_8: bf16 = bf16(0x3EC9u16); + /// [`bf16`] ð—…ð—‡ 10 + pub const LN_10: bf16 = bf16(0x4013u16); + /// [`bf16`] ð—…ð—‡ 2 + pub const LN_2: bf16 = bf16(0x3F31u16); + /// [`bf16`] ð—…ð—ˆð—€â‚₀ℯ + pub const LOG10_E: bf16 = bf16(0x3EDEu16); + /// [`bf16`] ð—…ð—ˆð—€â‚â‚€2 + pub const LOG10_2: bf16 = bf16(0x3E9Au16); + /// [`bf16`] ð—…ð—ˆð—€â‚‚ℯ + pub const LOG2_E: bf16 = bf16(0x3FB9u16); + /// [`bf16`] ð—…ð—ˆð—€â‚‚10 + pub const LOG2_10: bf16 = bf16(0x4055u16); + /// [`bf16`] √2 + pub const SQRT_2: bf16 = bf16(0x3FB5u16); +} + +impl From for f32 { + #[inline] + fn from(x: bf16) -> f32 { + x.to_f32() + } +} + +impl From for f64 { + #[inline] + fn from(x: bf16) -> f64 { + x.to_f64() + } +} + +impl From for bf16 { + #[inline] + fn from(x: i8) -> bf16 { + // Convert to f32, then to bf16 + bf16::from_f32(f32::from(x)) + } +} + +impl From for bf16 { + #[inline] + fn from(x: u8) -> bf16 { + // Convert to f32, then to f16 + bf16::from_f32(f32::from(x)) + } +} + +impl PartialEq for bf16 { + fn eq(&self, other: &bf16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + (self.0 == other.0) || ((self.0 | other.0) & 0x7FFFu16 == 0) + } + } +} + +impl PartialOrd for bf16 { + fn partial_cmp(&self, other: &bf16) -> Option { + if self.is_nan() || other.is_nan() { + None + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => Some(self.0.cmp(&other.0)), + (false, true) => { + if (self.0 | other.0) & 0x7FFFu16 == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } + (true, false) => { + if (self.0 | other.0) & 0x7FFFu16 == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Less) + } + } + (true, true) => Some(other.0.cmp(&self.0)), + } + } + } + + fn lt(&self, other: &bf16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 < other.0, + (false, true) => false, + (true, false) => (self.0 | other.0) & 0x7FFFu16 != 0, + (true, true) => self.0 > other.0, + } + } + } + + fn le(&self, other: &bf16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 <= other.0, + (false, true) => (self.0 | other.0) & 0x7FFFu16 == 0, + (true, false) => true, + (true, true) => self.0 >= other.0, + } + } + } + + fn gt(&self, other: &bf16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 > other.0, + (false, true) => (self.0 | other.0) & 0x7FFFu16 != 0, + (true, false) => false, + (true, true) => self.0 < other.0, + } + } + } + + fn ge(&self, other: &bf16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 >= other.0, + (false, true) => true, + (true, false) => (self.0 | other.0) & 0x7FFFu16 == 0, + (true, true) => self.0 <= other.0, + } + } + } +} + +impl FromStr for bf16 { + type Err = ParseFloatError; + fn from_str(src: &str) -> Result { + f32::from_str(src).map(bf16::from_f32) + } +} + +impl Debug for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:?}", self.to_f32()) + } +} + +impl Display for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{}", self.to_f32()) + } +} + +impl LowerExp for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:e}", self.to_f32()) + } +} + +impl UpperExp for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:E}", self.to_f32()) + } +} + +impl Binary for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:b}", self.0) + } +} + +impl Octal for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:o}", self.0) + } +} + +impl LowerHex for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:x}", self.0) + } +} + +impl UpperHex for bf16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:X}", self.0) + } +} + +impl Neg for bf16 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0 ^ 0x8000) + } +} + +impl Add for bf16 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) + Self::to_f32(rhs)) + } +} + +impl Add<&bf16> for bf16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: &bf16) -> Self::Output { + self.add(*rhs) + } +} + +impl Add<&bf16> for &bf16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: &bf16) -> Self::Output { + (*self).add(*rhs) + } +} + +impl Add for &bf16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: bf16) -> Self::Output { + (*self).add(rhs) + } +} + +impl AddAssign for bf16 { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = (*self).add(rhs); + } +} + +impl AddAssign<&bf16> for bf16 { + #[inline] + fn add_assign(&mut self, rhs: &bf16) { + *self = (*self).add(rhs); + } +} + +impl Sub for bf16 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) - Self::to_f32(rhs)) + } +} + +impl Sub<&bf16> for bf16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: &bf16) -> Self::Output { + self.sub(*rhs) + } +} + +impl Sub<&bf16> for &bf16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: &bf16) -> Self::Output { + (*self).sub(*rhs) + } +} + +impl Sub for &bf16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: bf16) -> Self::Output { + (*self).sub(rhs) + } +} + +impl SubAssign for bf16 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = (*self).sub(rhs); + } +} + +impl SubAssign<&bf16> for bf16 { + #[inline] + fn sub_assign(&mut self, rhs: &bf16) { + *self = (*self).sub(rhs); + } +} + +impl Mul for bf16 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) * Self::to_f32(rhs)) + } +} + +impl Mul<&bf16> for bf16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: &bf16) -> Self::Output { + self.mul(*rhs) + } +} + +impl Mul<&bf16> for &bf16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: &bf16) -> Self::Output { + (*self).mul(*rhs) + } +} + +impl Mul for &bf16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: bf16) -> Self::Output { + (*self).mul(rhs) + } +} + +impl MulAssign for bf16 { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = (*self).mul(rhs); + } +} + +impl MulAssign<&bf16> for bf16 { + #[inline] + fn mul_assign(&mut self, rhs: &bf16) { + *self = (*self).mul(rhs); + } +} + +impl Div for bf16 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) / Self::to_f32(rhs)) + } +} + +impl Div<&bf16> for bf16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: &bf16) -> Self::Output { + self.div(*rhs) + } +} + +impl Div<&bf16> for &bf16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: &bf16) -> Self::Output { + (*self).div(*rhs) + } +} + +impl Div for &bf16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: bf16) -> Self::Output { + (*self).div(rhs) + } +} + +impl DivAssign for bf16 { + #[inline] + fn div_assign(&mut self, rhs: Self) { + *self = (*self).div(rhs); + } +} + +impl DivAssign<&bf16> for bf16 { + #[inline] + fn div_assign(&mut self, rhs: &bf16) { + *self = (*self).div(rhs); + } +} + +impl Rem for bf16 { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) % Self::to_f32(rhs)) + } +} + +impl Rem<&bf16> for bf16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: &bf16) -> Self::Output { + self.rem(*rhs) + } +} + +impl Rem<&bf16> for &bf16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: &bf16) -> Self::Output { + (*self).rem(*rhs) + } +} + +impl Rem for &bf16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: bf16) -> Self::Output { + (*self).rem(rhs) + } +} + +impl RemAssign for bf16 { + #[inline] + fn rem_assign(&mut self, rhs: Self) { + *self = (*self).rem(rhs); + } +} + +impl RemAssign<&bf16> for bf16 { + #[inline] + fn rem_assign(&mut self, rhs: &bf16) { + *self = (*self).rem(rhs); + } +} + +impl Product for bf16 { + #[inline] + fn product>(iter: I) -> Self { + bf16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +impl<'a> Product<&'a bf16> for bf16 { + #[inline] + fn product>(iter: I) -> Self { + bf16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +impl Sum for bf16 { + #[inline] + fn sum>(iter: I) -> Self { + bf16::from_f32(iter.map(|f| f.to_f32()).sum()) + } +} + +impl<'a> Sum<&'a bf16> for bf16 { + #[inline] + fn sum>(iter: I) -> Self { + bf16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +#[allow( + clippy::cognitive_complexity, + clippy::float_cmp, + clippy::neg_cmp_op_on_partial_ord +)] +#[cfg(test)] +mod test { + use super::*; + use core::cmp::Ordering; + #[cfg(feature = "num-traits")] + use num_traits::{AsPrimitive, FromPrimitive, ToPrimitive}; + use quickcheck_macros::quickcheck; + + #[cfg(feature = "num-traits")] + #[test] + fn as_primitive() { + let two = bf16::from_f32(2.0); + assert_eq!(>::as_(2), two); + assert_eq!(>::as_(two), 2); + + assert_eq!(>::as_(2.0), two); + assert_eq!(>::as_(two), 2.0); + + assert_eq!(>::as_(2.0), two); + assert_eq!(>::as_(two), 2.0); + } + + #[cfg(feature = "num-traits")] + #[test] + fn to_primitive() { + let two = bf16::from_f32(2.0); + assert_eq!(ToPrimitive::to_i32(&two).unwrap(), 2i32); + assert_eq!(ToPrimitive::to_f32(&two).unwrap(), 2.0f32); + assert_eq!(ToPrimitive::to_f64(&two).unwrap(), 2.0f64); + } + + #[cfg(feature = "num-traits")] + #[test] + fn from_primitive() { + let two = bf16::from_f32(2.0); + assert_eq!(::from_i32(2).unwrap(), two); + assert_eq!(::from_f32(2.0).unwrap(), two); + assert_eq!(::from_f64(2.0).unwrap(), two); + } + + #[test] + fn test_bf16_consts_from_f32() { + let one = bf16::from_f32(1.0); + let zero = bf16::from_f32(0.0); + let neg_zero = bf16::from_f32(-0.0); + let neg_one = bf16::from_f32(-1.0); + let inf = bf16::from_f32(core::f32::INFINITY); + let neg_inf = bf16::from_f32(core::f32::NEG_INFINITY); + let nan = bf16::from_f32(core::f32::NAN); + + assert_eq!(bf16::ONE, one); + assert_eq!(bf16::ZERO, zero); + assert!(zero.is_sign_positive()); + assert_eq!(bf16::NEG_ZERO, neg_zero); + assert!(neg_zero.is_sign_negative()); + assert_eq!(bf16::NEG_ONE, neg_one); + assert!(neg_one.is_sign_negative()); + assert_eq!(bf16::INFINITY, inf); + assert_eq!(bf16::NEG_INFINITY, neg_inf); + assert!(nan.is_nan()); + assert!(bf16::NAN.is_nan()); + + let e = bf16::from_f32(core::f32::consts::E); + let pi = bf16::from_f32(core::f32::consts::PI); + let frac_1_pi = bf16::from_f32(core::f32::consts::FRAC_1_PI); + let frac_1_sqrt_2 = bf16::from_f32(core::f32::consts::FRAC_1_SQRT_2); + let frac_2_pi = bf16::from_f32(core::f32::consts::FRAC_2_PI); + let frac_2_sqrt_pi = bf16::from_f32(core::f32::consts::FRAC_2_SQRT_PI); + let frac_pi_2 = bf16::from_f32(core::f32::consts::FRAC_PI_2); + let frac_pi_3 = bf16::from_f32(core::f32::consts::FRAC_PI_3); + let frac_pi_4 = bf16::from_f32(core::f32::consts::FRAC_PI_4); + let frac_pi_6 = bf16::from_f32(core::f32::consts::FRAC_PI_6); + let frac_pi_8 = bf16::from_f32(core::f32::consts::FRAC_PI_8); + let ln_10 = bf16::from_f32(core::f32::consts::LN_10); + let ln_2 = bf16::from_f32(core::f32::consts::LN_2); + let log10_e = bf16::from_f32(core::f32::consts::LOG10_E); + // core::f32::consts::LOG10_2 requires rustc 1.43.0 + let log10_2 = bf16::from_f32(2f32.log10()); + let log2_e = bf16::from_f32(core::f32::consts::LOG2_E); + // core::f32::consts::LOG2_10 requires rustc 1.43.0 + let log2_10 = bf16::from_f32(10f32.log2()); + let sqrt_2 = bf16::from_f32(core::f32::consts::SQRT_2); + + assert_eq!(bf16::E, e); + assert_eq!(bf16::PI, pi); + assert_eq!(bf16::FRAC_1_PI, frac_1_pi); + assert_eq!(bf16::FRAC_1_SQRT_2, frac_1_sqrt_2); + assert_eq!(bf16::FRAC_2_PI, frac_2_pi); + assert_eq!(bf16::FRAC_2_SQRT_PI, frac_2_sqrt_pi); + assert_eq!(bf16::FRAC_PI_2, frac_pi_2); + assert_eq!(bf16::FRAC_PI_3, frac_pi_3); + assert_eq!(bf16::FRAC_PI_4, frac_pi_4); + assert_eq!(bf16::FRAC_PI_6, frac_pi_6); + assert_eq!(bf16::FRAC_PI_8, frac_pi_8); + assert_eq!(bf16::LN_10, ln_10); + assert_eq!(bf16::LN_2, ln_2); + assert_eq!(bf16::LOG10_E, log10_e); + assert_eq!(bf16::LOG10_2, log10_2); + assert_eq!(bf16::LOG2_E, log2_e); + assert_eq!(bf16::LOG2_10, log2_10); + assert_eq!(bf16::SQRT_2, sqrt_2); + } + + #[test] + fn test_bf16_consts_from_f64() { + let one = bf16::from_f64(1.0); + let zero = bf16::from_f64(0.0); + let neg_zero = bf16::from_f64(-0.0); + let inf = bf16::from_f64(core::f64::INFINITY); + let neg_inf = bf16::from_f64(core::f64::NEG_INFINITY); + let nan = bf16::from_f64(core::f64::NAN); + + assert_eq!(bf16::ONE, one); + assert_eq!(bf16::ZERO, zero); + assert_eq!(bf16::NEG_ZERO, neg_zero); + assert_eq!(bf16::INFINITY, inf); + assert_eq!(bf16::NEG_INFINITY, neg_inf); + assert!(nan.is_nan()); + assert!(bf16::NAN.is_nan()); + + let e = bf16::from_f64(core::f64::consts::E); + let pi = bf16::from_f64(core::f64::consts::PI); + let frac_1_pi = bf16::from_f64(core::f64::consts::FRAC_1_PI); + let frac_1_sqrt_2 = bf16::from_f64(core::f64::consts::FRAC_1_SQRT_2); + let frac_2_pi = bf16::from_f64(core::f64::consts::FRAC_2_PI); + let frac_2_sqrt_pi = bf16::from_f64(core::f64::consts::FRAC_2_SQRT_PI); + let frac_pi_2 = bf16::from_f64(core::f64::consts::FRAC_PI_2); + let frac_pi_3 = bf16::from_f64(core::f64::consts::FRAC_PI_3); + let frac_pi_4 = bf16::from_f64(core::f64::consts::FRAC_PI_4); + let frac_pi_6 = bf16::from_f64(core::f64::consts::FRAC_PI_6); + let frac_pi_8 = bf16::from_f64(core::f64::consts::FRAC_PI_8); + let ln_10 = bf16::from_f64(core::f64::consts::LN_10); + let ln_2 = bf16::from_f64(core::f64::consts::LN_2); + let log10_e = bf16::from_f64(core::f64::consts::LOG10_E); + // core::f64::consts::LOG10_2 requires rustc 1.43.0 + let log10_2 = bf16::from_f64(2f64.log10()); + let log2_e = bf16::from_f64(core::f64::consts::LOG2_E); + // core::f64::consts::LOG2_10 requires rustc 1.43.0 + let log2_10 = bf16::from_f64(10f64.log2()); + let sqrt_2 = bf16::from_f64(core::f64::consts::SQRT_2); + + assert_eq!(bf16::E, e); + assert_eq!(bf16::PI, pi); + assert_eq!(bf16::FRAC_1_PI, frac_1_pi); + assert_eq!(bf16::FRAC_1_SQRT_2, frac_1_sqrt_2); + assert_eq!(bf16::FRAC_2_PI, frac_2_pi); + assert_eq!(bf16::FRAC_2_SQRT_PI, frac_2_sqrt_pi); + assert_eq!(bf16::FRAC_PI_2, frac_pi_2); + assert_eq!(bf16::FRAC_PI_3, frac_pi_3); + assert_eq!(bf16::FRAC_PI_4, frac_pi_4); + assert_eq!(bf16::FRAC_PI_6, frac_pi_6); + assert_eq!(bf16::FRAC_PI_8, frac_pi_8); + assert_eq!(bf16::LN_10, ln_10); + assert_eq!(bf16::LN_2, ln_2); + assert_eq!(bf16::LOG10_E, log10_e); + assert_eq!(bf16::LOG10_2, log10_2); + assert_eq!(bf16::LOG2_E, log2_e); + assert_eq!(bf16::LOG2_10, log2_10); + assert_eq!(bf16::SQRT_2, sqrt_2); + } + + #[test] + fn test_nan_conversion_to_smaller() { + let nan64 = f64::from_bits(0x7FF0_0000_0000_0001u64); + let neg_nan64 = f64::from_bits(0xFFF0_0000_0000_0001u64); + let nan32 = f32::from_bits(0x7F80_0001u32); + let neg_nan32 = f32::from_bits(0xFF80_0001u32); + let nan32_from_64 = nan64 as f32; + let neg_nan32_from_64 = neg_nan64 as f32; + let nan16_from_64 = bf16::from_f64(nan64); + let neg_nan16_from_64 = bf16::from_f64(neg_nan64); + let nan16_from_32 = bf16::from_f32(nan32); + let neg_nan16_from_32 = bf16::from_f32(neg_nan32); + + assert!(nan64.is_nan() && nan64.is_sign_positive()); + assert!(neg_nan64.is_nan() && neg_nan64.is_sign_negative()); + assert!(nan32.is_nan() && nan32.is_sign_positive()); + assert!(neg_nan32.is_nan() && neg_nan32.is_sign_negative()); + assert!(nan32_from_64.is_nan() && nan32_from_64.is_sign_positive()); + assert!(neg_nan32_from_64.is_nan() && neg_nan32_from_64.is_sign_negative()); + assert!(nan16_from_64.is_nan() && nan16_from_64.is_sign_positive()); + assert!(neg_nan16_from_64.is_nan() && neg_nan16_from_64.is_sign_negative()); + assert!(nan16_from_32.is_nan() && nan16_from_32.is_sign_positive()); + assert!(neg_nan16_from_32.is_nan() && neg_nan16_from_32.is_sign_negative()); + } + + #[test] + fn test_nan_conversion_to_larger() { + let nan16 = bf16::from_bits(0x7F81u16); + let neg_nan16 = bf16::from_bits(0xFF81u16); + let nan32 = f32::from_bits(0x7F80_0001u32); + let neg_nan32 = f32::from_bits(0xFF80_0001u32); + let nan32_from_16 = f32::from(nan16); + let neg_nan32_from_16 = f32::from(neg_nan16); + let nan64_from_16 = f64::from(nan16); + let neg_nan64_from_16 = f64::from(neg_nan16); + let nan64_from_32 = f64::from(nan32); + let neg_nan64_from_32 = f64::from(neg_nan32); + + assert!(nan16.is_nan() && nan16.is_sign_positive()); + assert!(neg_nan16.is_nan() && neg_nan16.is_sign_negative()); + assert!(nan32.is_nan() && nan32.is_sign_positive()); + assert!(neg_nan32.is_nan() && neg_nan32.is_sign_negative()); + assert!(nan32_from_16.is_nan() && nan32_from_16.is_sign_positive()); + assert!(neg_nan32_from_16.is_nan() && neg_nan32_from_16.is_sign_negative()); + assert!(nan64_from_16.is_nan() && nan64_from_16.is_sign_positive()); + assert!(neg_nan64_from_16.is_nan() && neg_nan64_from_16.is_sign_negative()); + assert!(nan64_from_32.is_nan() && nan64_from_32.is_sign_positive()); + assert!(neg_nan64_from_32.is_nan() && neg_nan64_from_32.is_sign_negative()); + } + + #[test] + fn test_bf16_to_f32() { + let f = bf16::from_f32(7.0); + assert_eq!(f.to_f32(), 7.0f32); + + // 7.1 is NOT exactly representable in 16-bit, it's rounded + let f = bf16::from_f32(7.1); + let diff = (f.to_f32() - 7.1f32).abs(); + // diff must be <= 4 * EPSILON, as 7 has two more significant bits than 1 + assert!(diff <= 4.0 * bf16::EPSILON.to_f32()); + + let tiny32 = f32::from_bits(0x0001_0000u32); + assert_eq!(bf16::from_bits(0x0001).to_f32(), tiny32); + assert_eq!(bf16::from_bits(0x0005).to_f32(), 5.0 * tiny32); + + assert_eq!(bf16::from_bits(0x0001), bf16::from_f32(tiny32)); + assert_eq!(bf16::from_bits(0x0005), bf16::from_f32(5.0 * tiny32)); + } + + #[test] + fn test_bf16_to_f64() { + let f = bf16::from_f64(7.0); + assert_eq!(f.to_f64(), 7.0f64); + + // 7.1 is NOT exactly representable in 16-bit, it's rounded + let f = bf16::from_f64(7.1); + let diff = (f.to_f64() - 7.1f64).abs(); + // diff must be <= 4 * EPSILON, as 7 has two more significant bits than 1 + assert!(diff <= 4.0 * bf16::EPSILON.to_f64()); + + let tiny64 = 2.0f64.powi(-133); + assert_eq!(bf16::from_bits(0x0001).to_f64(), tiny64); + assert_eq!(bf16::from_bits(0x0005).to_f64(), 5.0 * tiny64); + + assert_eq!(bf16::from_bits(0x0001), bf16::from_f64(tiny64)); + assert_eq!(bf16::from_bits(0x0005), bf16::from_f64(5.0 * tiny64)); + } + + #[test] + fn test_comparisons() { + let zero = bf16::from_f64(0.0); + let one = bf16::from_f64(1.0); + let neg_zero = bf16::from_f64(-0.0); + let neg_one = bf16::from_f64(-1.0); + + assert_eq!(zero.partial_cmp(&neg_zero), Some(Ordering::Equal)); + assert_eq!(neg_zero.partial_cmp(&zero), Some(Ordering::Equal)); + assert!(zero == neg_zero); + assert!(neg_zero == zero); + assert!(!(zero != neg_zero)); + assert!(!(neg_zero != zero)); + assert!(!(zero < neg_zero)); + assert!(!(neg_zero < zero)); + assert!(zero <= neg_zero); + assert!(neg_zero <= zero); + assert!(!(zero > neg_zero)); + assert!(!(neg_zero > zero)); + assert!(zero >= neg_zero); + assert!(neg_zero >= zero); + + assert_eq!(one.partial_cmp(&neg_zero), Some(Ordering::Greater)); + assert_eq!(neg_zero.partial_cmp(&one), Some(Ordering::Less)); + assert!(!(one == neg_zero)); + assert!(!(neg_zero == one)); + assert!(one != neg_zero); + assert!(neg_zero != one); + assert!(!(one < neg_zero)); + assert!(neg_zero < one); + assert!(!(one <= neg_zero)); + assert!(neg_zero <= one); + assert!(one > neg_zero); + assert!(!(neg_zero > one)); + assert!(one >= neg_zero); + assert!(!(neg_zero >= one)); + + assert_eq!(one.partial_cmp(&neg_one), Some(Ordering::Greater)); + assert_eq!(neg_one.partial_cmp(&one), Some(Ordering::Less)); + assert!(!(one == neg_one)); + assert!(!(neg_one == one)); + assert!(one != neg_one); + assert!(neg_one != one); + assert!(!(one < neg_one)); + assert!(neg_one < one); + assert!(!(one <= neg_one)); + assert!(neg_one <= one); + assert!(one > neg_one); + assert!(!(neg_one > one)); + assert!(one >= neg_one); + assert!(!(neg_one >= one)); + } + + #[test] + #[allow(clippy::erasing_op, clippy::identity_op)] + fn round_to_even_f32() { + // smallest positive subnormal = 0b0.0000_001 * 2^-126 = 2^-133 + let min_sub = bf16::from_bits(1); + let min_sub_f = (-133f32).exp2(); + assert_eq!(bf16::from_f32(min_sub_f).to_bits(), min_sub.to_bits()); + assert_eq!(f32::from(min_sub).to_bits(), min_sub_f.to_bits()); + + // 0.0000000_011111 rounded to 0.0000000 (< tie, no rounding) + // 0.0000000_100000 rounded to 0.0000000 (tie and even, remains at even) + // 0.0000000_100001 rounded to 0.0000001 (> tie, rounds up) + assert_eq!( + bf16::from_f32(min_sub_f * 0.49).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 0.50).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 0.51).to_bits(), + min_sub.to_bits() * 1 + ); + + // 0.0000001_011111 rounded to 0.0000001 (< tie, no rounding) + // 0.0000001_100000 rounded to 0.0000010 (tie and odd, rounds up to even) + // 0.0000001_100001 rounded to 0.0000010 (> tie, rounds up) + assert_eq!( + bf16::from_f32(min_sub_f * 1.49).to_bits(), + min_sub.to_bits() * 1 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 1.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 1.51).to_bits(), + min_sub.to_bits() * 2 + ); + + // 0.0000010_011111 rounded to 0.0000010 (< tie, no rounding) + // 0.0000010_100000 rounded to 0.0000010 (tie and even, remains at even) + // 0.0000010_100001 rounded to 0.0000011 (> tie, rounds up) + assert_eq!( + bf16::from_f32(min_sub_f * 2.49).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 2.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f32(min_sub_f * 2.51).to_bits(), + min_sub.to_bits() * 3 + ); + + assert_eq!( + bf16::from_f32(250.49f32).to_bits(), + bf16::from_f32(250.0).to_bits() + ); + assert_eq!( + bf16::from_f32(250.50f32).to_bits(), + bf16::from_f32(250.0).to_bits() + ); + assert_eq!( + bf16::from_f32(250.51f32).to_bits(), + bf16::from_f32(251.0).to_bits() + ); + assert_eq!( + bf16::from_f32(251.49f32).to_bits(), + bf16::from_f32(251.0).to_bits() + ); + assert_eq!( + bf16::from_f32(251.50f32).to_bits(), + bf16::from_f32(252.0).to_bits() + ); + assert_eq!( + bf16::from_f32(251.51f32).to_bits(), + bf16::from_f32(252.0).to_bits() + ); + assert_eq!( + bf16::from_f32(252.49f32).to_bits(), + bf16::from_f32(252.0).to_bits() + ); + assert_eq!( + bf16::from_f32(252.50f32).to_bits(), + bf16::from_f32(252.0).to_bits() + ); + assert_eq!( + bf16::from_f32(252.51f32).to_bits(), + bf16::from_f32(253.0).to_bits() + ); + } + + #[test] + #[allow(clippy::erasing_op, clippy::identity_op)] + fn round_to_even_f64() { + // smallest positive subnormal = 0b0.0000_001 * 2^-126 = 2^-133 + let min_sub = bf16::from_bits(1); + let min_sub_f = (-133f64).exp2(); + assert_eq!(bf16::from_f64(min_sub_f).to_bits(), min_sub.to_bits()); + assert_eq!(f64::from(min_sub).to_bits(), min_sub_f.to_bits()); + + // 0.0000000_011111 rounded to 0.0000000 (< tie, no rounding) + // 0.0000000_100000 rounded to 0.0000000 (tie and even, remains at even) + // 0.0000000_100001 rounded to 0.0000001 (> tie, rounds up) + assert_eq!( + bf16::from_f64(min_sub_f * 0.49).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 0.50).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 0.51).to_bits(), + min_sub.to_bits() * 1 + ); + + // 0.0000001_011111 rounded to 0.0000001 (< tie, no rounding) + // 0.0000001_100000 rounded to 0.0000010 (tie and odd, rounds up to even) + // 0.0000001_100001 rounded to 0.0000010 (> tie, rounds up) + assert_eq!( + bf16::from_f64(min_sub_f * 1.49).to_bits(), + min_sub.to_bits() * 1 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 1.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 1.51).to_bits(), + min_sub.to_bits() * 2 + ); + + // 0.0000010_011111 rounded to 0.0000010 (< tie, no rounding) + // 0.0000010_100000 rounded to 0.0000010 (tie and even, remains at even) + // 0.0000010_100001 rounded to 0.0000011 (> tie, rounds up) + assert_eq!( + bf16::from_f64(min_sub_f * 2.49).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 2.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + bf16::from_f64(min_sub_f * 2.51).to_bits(), + min_sub.to_bits() * 3 + ); + + assert_eq!( + bf16::from_f64(250.49f64).to_bits(), + bf16::from_f64(250.0).to_bits() + ); + assert_eq!( + bf16::from_f64(250.50f64).to_bits(), + bf16::from_f64(250.0).to_bits() + ); + assert_eq!( + bf16::from_f64(250.51f64).to_bits(), + bf16::from_f64(251.0).to_bits() + ); + assert_eq!( + bf16::from_f64(251.49f64).to_bits(), + bf16::from_f64(251.0).to_bits() + ); + assert_eq!( + bf16::from_f64(251.50f64).to_bits(), + bf16::from_f64(252.0).to_bits() + ); + assert_eq!( + bf16::from_f64(251.51f64).to_bits(), + bf16::from_f64(252.0).to_bits() + ); + assert_eq!( + bf16::from_f64(252.49f64).to_bits(), + bf16::from_f64(252.0).to_bits() + ); + assert_eq!( + bf16::from_f64(252.50f64).to_bits(), + bf16::from_f64(252.0).to_bits() + ); + assert_eq!( + bf16::from_f64(252.51f64).to_bits(), + bf16::from_f64(253.0).to_bits() + ); + } + + impl quickcheck::Arbitrary for bf16 { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + bf16(u16::arbitrary(g)) + } + } + + #[quickcheck] + fn qc_roundtrip_bf16_f32_is_identity(f: bf16) -> bool { + let roundtrip = bf16::from_f32(f.to_f32()); + if f.is_nan() { + roundtrip.is_nan() && f.is_sign_negative() == roundtrip.is_sign_negative() + } else { + f.0 == roundtrip.0 + } + } + + #[quickcheck] + fn qc_roundtrip_bf16_f64_is_identity(f: bf16) -> bool { + let roundtrip = bf16::from_f64(f.to_f64()); + if f.is_nan() { + roundtrip.is_nan() && f.is_sign_negative() == roundtrip.is_sign_negative() + } else { + f.0 == roundtrip.0 + } + } +} diff --git a/third_party/rust/half/src/bfloat/convert.rs b/third_party/rust/half/src/bfloat/convert.rs new file mode 100644 index 000000000000..4aa0aec751dc --- /dev/null +++ b/third_party/rust/half/src/bfloat/convert.rs @@ -0,0 +1,135 @@ +pub(crate) fn f32_to_bf16(value: f32) -> u16 { + // Convert to raw bytes + let x = value.to_bits(); + + // check for NaN + if x & 0x7FFF_FFFFu32 > 0x7F80_0000u32 { + // Keep high part of current mantissa but also set most significiant mantissa bit + return ((x >> 16) | 0x0040u32) as u16; + } + + // round and shift + let round_bit = 0x0000_8000u32; + if (x & round_bit) != 0 && (x & (3 * round_bit - 1)) != 0 { + (x >> 16) as u16 + 1 + } else { + (x >> 16) as u16 + } +} + +pub(crate) fn f64_to_bf16(value: f64) -> u16 { + // Convert to raw bytes, truncating the last 32-bits of mantissa; that precision will always + // be lost on half-precision. + let val = value.to_bits(); + let x = (val >> 32) as u32; + + // Extract IEEE754 components + let sign = x & 0x8000_0000u32; + let exp = x & 0x7FF0_0000u32; + let man = x & 0x000F_FFFFu32; + + // Check for all exponent bits being set, which is Infinity or NaN + if exp == 0x7FF0_0000u32 { + // Set mantissa MSB for NaN (and also keep shifted mantissa bits). + // We also have to check the last 32 bits. + let nan_bit = if man == 0 && (val as u32 == 0) { + 0 + } else { + 0x0040u32 + }; + return ((sign >> 16) | 0x7F80u32 | nan_bit | (man >> 13)) as u16; + } + + // The number is normalized, start assembling half precision version + let half_sign = sign >> 16; + // Unbias the exponent, then bias for bfloat16 precision + let unbiased_exp = ((exp >> 20) as i64) - 1023; + let half_exp = unbiased_exp + 127; + + // Check for exponent overflow, return +infinity + if half_exp >= 0xFF { + return (half_sign | 0x7F80u32) as u16; + } + + // Check for underflow + if half_exp <= 0 { + // Check mantissa for what we can do + if 7 - half_exp > 21 { + // No rounding possibility, so this is a full underflow, return signed zero + return half_sign as u16; + } + // Don't forget about hidden leading mantissa bit when assembling mantissa + let man = man | 0x0010_0000u32; + let mut half_man = man >> (14 - half_exp); + // Check for rounding + let round_bit = 1 << (13 - half_exp); + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + half_man += 1; + } + // No exponent for subnormals + return (half_sign | half_man) as u16; + } + + // Rebias the exponent + let half_exp = (half_exp as u32) << 7; + let half_man = man >> 13; + // Check for rounding + let round_bit = 0x0000_1000u32; + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + // Round it + ((half_sign | half_exp | half_man) + 1) as u16 + } else { + (half_sign | half_exp | half_man) as u16 + } +} + +pub(crate) fn bf16_to_f32(i: u16) -> f32 { + // If NaN, keep current mantissa but also set most significiant mantissa bit + if i & 0x7FFFu16 > 0x7F80u16 { + f32::from_bits((i as u32 | 0x0040u32) << 16) + } else { + f32::from_bits((i as u32) << 16) + } +} + +pub(crate) fn bf16_to_f64(i: u16) -> f64 { + // Check for signed zero + if i & 0x7FFFu16 == 0 { + return f64::from_bits((i as u64) << 48); + } + + let half_sign = (i & 0x8000u16) as u64; + let half_exp = (i & 0x7F80u16) as u64; + let half_man = (i & 0x007Fu16) as u64; + + // Check for an infinity or NaN when all exponent bits set + if half_exp == 0x7F80u64 { + // Check for signed infinity if mantissa is zero + if half_man == 0 { + return f64::from_bits((half_sign << 48) | 0x7FF0_0000_0000_0000u64); + } else { + // NaN, keep current mantissa but also set most significiant mantissa bit + return f64::from_bits((half_sign << 48) | 0x7FF8_0000_0000_0000u64 | (half_man << 45)); + } + } + + // Calculate double-precision components with adjusted exponent + let sign = half_sign << 48; + // Unbias exponent + let unbiased_exp = ((half_exp as i64) >> 7) - 127; + + // Check for subnormals, which will be normalized by adjusting exponent + if half_exp == 0 { + // Calculate how much to adjust the exponent by + let e = (half_man as u16).leading_zeros() - 9; + + // Rebias and adjust exponent + let exp = ((1023 - 127 - e) as u64) << 52; + let man = (half_man << (46 + e)) & 0xF_FFFF_FFFF_FFFFu64; + return f64::from_bits(sign | exp | man); + } + // Rebias exponent for a normalized normal + let exp = ((unbiased_exp + 1023) as u64) << 52; + let man = (half_man & 0x007Fu64) << 45; + f64::from_bits(sign | exp | man) +} diff --git a/third_party/rust/half/src/binary16.rs b/third_party/rust/half/src/binary16.rs new file mode 100644 index 000000000000..8d3fad75e7f3 --- /dev/null +++ b/third_party/rust/half/src/binary16.rs @@ -0,0 +1,1711 @@ +#[cfg(feature = "bytemuck")] +use bytemuck::{Pod, Zeroable}; +use core::{ + cmp::Ordering, + fmt::{ + Binary, Debug, Display, Error, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex, + }, + iter::{Product, Sum}, + num::{FpCategory, ParseFloatError}, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, + str::FromStr, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "zerocopy")] +use zerocopy::{AsBytes, FromBytes}; + +pub(crate) mod convert; + +/// A 16-bit floating point type implementing the IEEE 754-2008 standard [`binary16`] a.k.a `half` +/// format. +/// +/// This 16-bit floating point type is intended for efficient storage where the full range and +/// precision of a larger floating point value is not required. Because [`f16`] is primarily for +/// efficient storage, floating point operations such as addition, multiplication, etc. are not +/// implemented. Operations should be performed with [`f32`] or higher-precision types and converted +/// to/from [`f16`] as necessary. +/// +/// [`binary16`]: https://en.wikipedia.org/wiki/Half-precision_floating-point_format +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "bytemuck", derive(Zeroable, Pod))] +#[cfg_attr(feature = "zerocopy", derive(AsBytes, FromBytes))] +pub struct f16(u16); + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "all constants moved to associated constants of `f16`" +)] +pub mod consts { + use super::f16; + + #[deprecated(since = "1.4.0", note = "moved to `f16::DIGITS`")] + pub const DIGITS: u32 = f16::DIGITS; + #[deprecated(since = "1.4.0", note = "moved to `f16::EPSILON`")] + pub const EPSILON: f16 = f16::EPSILON; + #[deprecated(since = "1.4.0", note = "moved to `f16::INFINITY`")] + pub const INFINITY: f16 = f16::INFINITY; + #[deprecated(since = "1.4.0", note = "moved to `f16::MANTISSA_DIGITS`")] + pub const MANTISSA_DIGITS: u32 = f16::MANTISSA_DIGITS; + #[deprecated(since = "1.4.0", note = "moved to `f16::MAX`")] + pub const MAX: f16 = f16::MAX; + #[deprecated(since = "1.4.0", note = "moved to `f16::MAX_10_EXP`")] + pub const MAX_10_EXP: i32 = f16::MAX_10_EXP; + #[deprecated(since = "1.4.0", note = "moved to `f16::MAX_EXP`")] + pub const MAX_EXP: i32 = f16::MAX_EXP; + #[deprecated(since = "1.4.0", note = "moved to `f16::MIN`")] + pub const MIN: f16 = f16::MIN; + #[deprecated(since = "1.4.0", note = "moved to `f16::MIN_10_EXP`")] + pub const MIN_10_EXP: i32 = f16::MIN_10_EXP; + #[deprecated(since = "1.4.0", note = "moved to `f16::MIN_EXP`")] + pub const MIN_EXP: i32 = f16::MIN_EXP; + #[deprecated(since = "1.4.0", note = "moved to `f16::MIN_POSITIVE`")] + pub const MIN_POSITIVE: f16 = f16::MIN_POSITIVE; + #[deprecated(since = "1.4.0", note = "moved to `f16::NAN`")] + pub const NAN: f16 = f16::NAN; + #[deprecated(since = "1.4.0", note = "moved to `f16::NEG_INFINITY`")] + pub const NEG_INFINITY: f16 = f16::NEG_INFINITY; + #[deprecated(since = "1.4.0", note = "moved to `f16::RADIX`")] + pub const RADIX: u32 = f16::RADIX; + + #[deprecated(since = "1.4.0", note = "moved to `f16::MIN_POSITIVE_SUBNORMAL`")] + pub const MIN_POSITIVE_SUBNORMAL: f16 = f16::MIN_POSITIVE_SUBNORMAL; + #[deprecated(since = "1.4.0", note = "moved to `f16::MAX_SUBNORMAL`")] + pub const MAX_SUBNORMAL: f16 = f16::MAX_SUBNORMAL; + + #[deprecated(since = "1.4.0", note = "moved to `f16::ONE`")] + pub const ONE: f16 = f16::ONE; + #[deprecated(since = "1.4.0", note = "moved to `f16::ZERO`")] + pub const ZERO: f16 = f16::ZERO; + #[deprecated(since = "1.4.0", note = "moved to `f16::NEG_ZERO`")] + pub const NEG_ZERO: f16 = f16::NEG_ZERO; + + #[deprecated(since = "1.4.0", note = "moved to `f16::E`")] + pub const E: f16 = f16::E; + #[deprecated(since = "1.4.0", note = "moved to `f16::PI`")] + pub const PI: f16 = f16::PI; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_1_PI`")] + pub const FRAC_1_PI: f16 = f16::FRAC_1_PI; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_1_SQRT_2`")] + pub const FRAC_1_SQRT_2: f16 = f16::FRAC_1_SQRT_2; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_2_PI`")] + pub const FRAC_2_PI: f16 = f16::FRAC_2_PI; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_2_SQRT_PI`")] + pub const FRAC_2_SQRT_PI: f16 = f16::FRAC_2_SQRT_PI; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_PI_2`")] + pub const FRAC_PI_2: f16 = f16::FRAC_PI_2; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_PI_3`")] + pub const FRAC_PI_3: f16 = f16::FRAC_PI_3; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_PI_4`")] + pub const FRAC_PI_4: f16 = f16::FRAC_PI_4; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_PI_6`")] + pub const FRAC_PI_6: f16 = f16::FRAC_PI_6; + #[deprecated(since = "1.4.0", note = "moved to `f16::FRAC_PI_8`")] + pub const FRAC_PI_8: f16 = f16::FRAC_PI_8; + #[deprecated(since = "1.4.0", note = "moved to `f16::LN_10`")] + pub const LN_10: f16 = f16::LN_10; + #[deprecated(since = "1.4.0", note = "moved to `f16::LN_2`")] + pub const LN_2: f16 = f16::LN_2; + #[deprecated(since = "1.4.0", note = "moved to `f16::LOG10_E`")] + pub const LOG10_E: f16 = f16::LOG10_E; + #[deprecated(since = "1.4.0", note = "moved to `f16::LOG2_E`")] + pub const LOG2_E: f16 = f16::LOG2_E; + #[deprecated(since = "1.4.0", note = "moved to `f16::SQRT_2`")] + pub const SQRT_2: f16 = f16::SQRT_2; +} + +impl f16 { + /// Constructs a 16-bit floating point value from the raw bits. + #[inline] + pub const fn from_bits(bits: u16) -> f16 { + f16(bits) + } + + /// Constructs a 16-bit floating point value from a 32-bit floating point value. + /// + /// If the 32-bit value is to large to fit in 16-bits, ±∞ will result. NaN values are + /// preserved. 32-bit subnormal values are too tiny to be represented in 16-bits and result in + /// ±0. Exponents that underflow the minimum 16-bit exponent will result in 16-bit subnormals + /// or ±0. All other values are truncated and rounded to the nearest representable 16-bit + /// value. + #[inline] + pub fn from_f32(value: f32) -> f16 { + f16(convert::f32_to_f16(value)) + } + + /// Constructs a 16-bit floating point value from a 64-bit floating point value. + /// + /// If the 64-bit value is to large to fit in 16-bits, ±∞ will result. NaN values are + /// preserved. 64-bit subnormal values are too tiny to be represented in 16-bits and result in + /// ±0. Exponents that underflow the minimum 16-bit exponent will result in 16-bit subnormals + /// or ±0. All other values are truncated and rounded to the nearest representable 16-bit + /// value. + #[inline] + pub fn from_f64(value: f64) -> f16 { + f16(convert::f64_to_f16(value)) + } + + /// Converts a [`f16`] into the underlying bit representation. + #[inline] + pub const fn to_bits(self) -> u16 { + self.0 + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// little-endian byte order. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = f16::from_f32(12.5).to_le_bytes(); + /// assert_eq!(bytes, [0x40, 0x4A]); + /// ``` + #[inline] + pub const fn to_le_bytes(self) -> [u8; 2] { + self.0.to_le_bytes() + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// big-endian (network) byte order. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = f16::from_f32(12.5).to_be_bytes(); + /// assert_eq!(bytes, [0x4A, 0x40]); + /// ``` + #[inline] + pub const fn to_be_bytes(self) -> [u8; 2] { + self.0.to_be_bytes() + } + + /// Returns the memory representation of the underlying bit representation as a byte array in + /// native byte order. + /// + /// As the target platform's native endianness is used, portable code should use + /// [`to_be_bytes`][Self::to_be_bytes] or [`to_le_bytes`][Self::to_le_bytes], as appropriate, + /// instead. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let bytes = f16::from_f32(12.5).to_ne_bytes(); + /// assert_eq!(bytes, if cfg!(target_endian = "big") { + /// [0x4A, 0x40] + /// } else { + /// [0x40, 0x4A] + /// }); + /// ``` + #[inline] + pub const fn to_ne_bytes(self) -> [u8; 2] { + self.0.to_ne_bytes() + } + + /// Creates a floating point value from its representation as a byte array in little endian. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = f16::from_le_bytes([0x40, 0x4A]); + /// assert_eq!(value, f16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_le_bytes(bytes: [u8; 2]) -> f16 { + f16::from_bits(u16::from_le_bytes(bytes)) + } + + /// Creates a floating point value from its representation as a byte array in big endian. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = f16::from_be_bytes([0x4A, 0x40]); + /// assert_eq!(value, f16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_be_bytes(bytes: [u8; 2]) -> f16 { + f16::from_bits(u16::from_be_bytes(bytes)) + } + + /// Creates a floating point value from its representation as a byte array in native endian. + /// + /// As the target platform's native endianness is used, portable code likely wants to use + /// [`from_be_bytes`][Self::from_be_bytes] or [`from_le_bytes`][Self::from_le_bytes], as + /// appropriate instead. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let value = f16::from_ne_bytes(if cfg!(target_endian = "big") { + /// [0x4A, 0x40] + /// } else { + /// [0x40, 0x4A] + /// }); + /// assert_eq!(value, f16::from_f32(12.5)); + /// ``` + #[inline] + pub const fn from_ne_bytes(bytes: [u8; 2]) -> f16 { + f16::from_bits(u16::from_ne_bytes(bytes)) + } + + #[doc(hidden)] + #[deprecated(since = "1.2.0", note = "renamed to `to_bits`")] + #[inline] + pub fn as_bits(self) -> u16 { + self.to_bits() + } + + /// Converts a [`f16`] value into a `f32` value. + /// + /// This conversion is lossless as all 16-bit floating point values can be represented exactly + /// in 32-bit floating point. + #[inline] + pub fn to_f32(self) -> f32 { + convert::f16_to_f32(self.0) + } + + /// Converts a [`f16`] value into a `f64` value. + /// + /// This conversion is lossless as all 16-bit floating point values can be represented exactly + /// in 64-bit floating point. + #[inline] + pub fn to_f64(self) -> f64 { + convert::f16_to_f64(self.0) + } + + /// Returns `true` if this value is `NaN` and `false` otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = f16::NAN; + /// let f = f16::from_f32(7.0_f32); + /// + /// assert!(nan.is_nan()); + /// assert!(!f.is_nan()); + /// ``` + #[inline] + pub const fn is_nan(self) -> bool { + self.0 & 0x7FFFu16 > 0x7C00u16 + } + + /// Returns `true` if this value is ±∞ and `false`. + /// otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = f16::from_f32(7.0f32); + /// let inf = f16::INFINITY; + /// let neg_inf = f16::NEG_INFINITY; + /// let nan = f16::NAN; + /// + /// assert!(!f.is_infinite()); + /// assert!(!nan.is_infinite()); + /// + /// assert!(inf.is_infinite()); + /// assert!(neg_inf.is_infinite()); + /// ``` + #[inline] + pub const fn is_infinite(self) -> bool { + self.0 & 0x7FFFu16 == 0x7C00u16 + } + + /// Returns `true` if this number is neither infinite nor `NaN`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = f16::from_f32(7.0f32); + /// let inf = f16::INFINITY; + /// let neg_inf = f16::NEG_INFINITY; + /// let nan = f16::NAN; + /// + /// assert!(f.is_finite()); + /// + /// assert!(!nan.is_finite()); + /// assert!(!inf.is_finite()); + /// assert!(!neg_inf.is_finite()); + /// ``` + #[inline] + pub const fn is_finite(self) -> bool { + self.0 & 0x7C00u16 != 0x7C00u16 + } + + /// Returns `true` if the number is neither zero, infinite, subnormal, or `NaN`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let min = f16::MIN_POSITIVE; + /// let max = f16::MAX; + /// let lower_than_min = f16::from_f32(1.0e-10_f32); + /// let zero = f16::from_f32(0.0_f32); + /// + /// assert!(min.is_normal()); + /// assert!(max.is_normal()); + /// + /// assert!(!zero.is_normal()); + /// assert!(!f16::NAN.is_normal()); + /// assert!(!f16::INFINITY.is_normal()); + /// // Values between `0` and `min` are Subnormal. + /// assert!(!lower_than_min.is_normal()); + /// ``` + #[inline] + pub const fn is_normal(self) -> bool { + let exp = self.0 & 0x7C00u16; + exp != 0x7C00u16 && exp != 0 + } + + /// Returns the floating point category of the number. + /// + /// If only one property is going to be tested, it is generally faster to use the specific + /// predicate instead. + /// + /// # Examples + /// + /// ```rust + /// use std::num::FpCategory; + /// # use half::prelude::*; + /// + /// let num = f16::from_f32(12.4_f32); + /// let inf = f16::INFINITY; + /// + /// assert_eq!(num.classify(), FpCategory::Normal); + /// assert_eq!(inf.classify(), FpCategory::Infinite); + /// ``` + pub const fn classify(self) -> FpCategory { + let exp = self.0 & 0x7C00u16; + let man = self.0 & 0x03FFu16; + match (exp, man) { + (0, 0) => FpCategory::Zero, + (0, _) => FpCategory::Subnormal, + (0x7C00u16, 0) => FpCategory::Infinite, + (0x7C00u16, _) => FpCategory::Nan, + _ => FpCategory::Normal, + } + } + + /// Returns a number that represents the sign of `self`. + /// + /// * `1.0` if the number is positive, `+0.0` or [`INFINITY`][f16::INFINITY] + /// * `-1.0` if the number is negative, `-0.0` or [`NEG_INFINITY`][f16::NEG_INFINITY] + /// * [`NAN`][f16::NAN] if the number is `NaN` + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let f = f16::from_f32(3.5_f32); + /// + /// assert_eq!(f.signum(), f16::from_f32(1.0)); + /// assert_eq!(f16::NEG_INFINITY.signum(), f16::from_f32(-1.0)); + /// + /// assert!(f16::NAN.signum().is_nan()); + /// ``` + pub const fn signum(self) -> f16 { + if self.is_nan() { + self + } else if self.0 & 0x8000u16 != 0 { + Self::NEG_ONE + } else { + Self::ONE + } + } + + /// Returns `true` if and only if `self` has a positive sign, including `+0.0`, `NaNs` with a + /// positive sign bit and +∞. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = f16::NAN; + /// let f = f16::from_f32(7.0_f32); + /// let g = f16::from_f32(-7.0_f32); + /// + /// assert!(f.is_sign_positive()); + /// assert!(!g.is_sign_positive()); + /// // `NaN` can be either positive or negative + /// assert!(nan.is_sign_positive() != nan.is_sign_negative()); + /// ``` + #[inline] + pub const fn is_sign_positive(self) -> bool { + self.0 & 0x8000u16 == 0 + } + + /// Returns `true` if and only if `self` has a negative sign, including `-0.0`, `NaNs` with a + /// negative sign bit and −∞. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// + /// let nan = f16::NAN; + /// let f = f16::from_f32(7.0f32); + /// let g = f16::from_f32(-7.0f32); + /// + /// assert!(!f.is_sign_negative()); + /// assert!(g.is_sign_negative()); + /// // `NaN` can be either positive or negative + /// assert!(nan.is_sign_positive() != nan.is_sign_negative()); + /// ``` + #[inline] + pub const fn is_sign_negative(self) -> bool { + self.0 & 0x8000u16 != 0 + } + + /// Returns a number composed of the magnitude of `self` and the sign of `sign`. + /// + /// Equal to `self` if the sign of `self` and `sign` are the same, otherwise equal to `-self`. + /// If `self` is NaN, then NaN with the sign of `sign` is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let f = f16::from_f32(3.5); + /// + /// assert_eq!(f.copysign(f16::from_f32(0.42)), f16::from_f32(3.5)); + /// assert_eq!(f.copysign(f16::from_f32(-0.42)), f16::from_f32(-3.5)); + /// assert_eq!((-f).copysign(f16::from_f32(0.42)), f16::from_f32(3.5)); + /// assert_eq!((-f).copysign(f16::from_f32(-0.42)), f16::from_f32(-3.5)); + /// + /// assert!(f16::NAN.copysign(f16::from_f32(1.0)).is_nan()); + /// ``` + #[inline] + pub const fn copysign(self, sign: f16) -> f16 { + f16((sign.0 & 0x8000u16) | (self.0 & 0x7FFFu16)) + } + + /// Returns the maximum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let x = f16::from_f32(1.0); + /// let y = f16::from_f32(2.0); + /// + /// assert_eq!(x.max(y), y); + /// ``` + #[inline] + pub fn max(self, other: f16) -> f16 { + if other > self && !other.is_nan() { + other + } else { + self + } + } + + /// Returns the minimum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// let x = f16::from_f32(1.0); + /// let y = f16::from_f32(2.0); + /// + /// assert_eq!(x.min(y), x); + /// ``` + #[inline] + pub fn min(self, other: f16) -> f16 { + if other < self && !other.is_nan() { + other + } else { + self + } + } + + /// Restrict a value to a certain interval unless it is NaN. + /// + /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`. + /// Otherwise this returns `self`. + /// + /// Note that this function returns NaN if the initial value was NaN as well. + /// + /// # Panics + /// Panics if `min > max`, `min` is NaN, or `max` is NaN. + /// + /// # Examples + /// + /// ``` + /// # use half::prelude::*; + /// assert!(f16::from_f32(-3.0).clamp(f16::from_f32(-2.0), f16::from_f32(1.0)) == f16::from_f32(-2.0)); + /// assert!(f16::from_f32(0.0).clamp(f16::from_f32(-2.0), f16::from_f32(1.0)) == f16::from_f32(0.0)); + /// assert!(f16::from_f32(2.0).clamp(f16::from_f32(-2.0), f16::from_f32(1.0)) == f16::from_f32(1.0)); + /// assert!(f16::NAN.clamp(f16::from_f32(-2.0), f16::from_f32(1.0)).is_nan()); + /// ``` + #[inline] + pub fn clamp(self, min: f16, max: f16) -> f16 { + assert!(min <= max); + let mut x = self; + if x < min { + x = min; + } + if x > max { + x = max; + } + x + } + + /// Approximate number of [`f16`] significant digits in base 10 + pub const DIGITS: u32 = 3; + /// [`f16`] + /// [machine epsilon](https://en.wikipedia.org/wiki/Machine_epsilon) value + /// + /// This is the difference between 1.0 and the next largest representable number. + pub const EPSILON: f16 = f16(0x1400u16); + /// [`f16`] positive Infinity (+∞) + pub const INFINITY: f16 = f16(0x7C00u16); + /// Number of [`f16`] significant digits in base 2 + pub const MANTISSA_DIGITS: u32 = 11; + /// Largest finite [`f16`] value + pub const MAX: f16 = f16(0x7BFF); + /// Maximum possible [`f16`] power of 10 exponent + pub const MAX_10_EXP: i32 = 4; + /// Maximum possible [`f16`] power of 2 exponent + pub const MAX_EXP: i32 = 16; + /// Smallest finite [`f16`] value + pub const MIN: f16 = f16(0xFBFF); + /// Minimum possible normal [`f16`] power of 10 exponent + pub const MIN_10_EXP: i32 = -4; + /// One greater than the minimum possible normal [`f16`] power of 2 exponent + pub const MIN_EXP: i32 = -13; + /// Smallest positive normal [`f16`] value + pub const MIN_POSITIVE: f16 = f16(0x0400u16); + /// [`f16`] Not a Number (NaN) + pub const NAN: f16 = f16(0x7E00u16); + /// [`f16`] negative infinity (-∞) + pub const NEG_INFINITY: f16 = f16(0xFC00u16); + /// The radix or base of the internal representation of [`f16`] + pub const RADIX: u32 = 2; + + /// Minimum positive subnormal [`f16`] value + pub const MIN_POSITIVE_SUBNORMAL: f16 = f16(0x0001u16); + /// Maximum subnormal [`f16`] value + pub const MAX_SUBNORMAL: f16 = f16(0x03FFu16); + + /// [`f16`] 1 + pub const ONE: f16 = f16(0x3C00u16); + /// [`f16`] 0 + pub const ZERO: f16 = f16(0x0000u16); + /// [`f16`] -0 + pub const NEG_ZERO: f16 = f16(0x8000u16); + /// [`f16`] -1 + pub const NEG_ONE: f16 = f16(0xBC00u16); + + /// [`f16`] Euler's number (ℯ) + pub const E: f16 = f16(0x4170u16); + /// [`f16`] Archimedes' constant (Ï€) + pub const PI: f16 = f16(0x4248u16); + /// [`f16`] 1/Ï€ + pub const FRAC_1_PI: f16 = f16(0x3518u16); + /// [`f16`] 1/√2 + pub const FRAC_1_SQRT_2: f16 = f16(0x39A8u16); + /// [`f16`] 2/Ï€ + pub const FRAC_2_PI: f16 = f16(0x3918u16); + /// [`f16`] 2/√π + pub const FRAC_2_SQRT_PI: f16 = f16(0x3C83u16); + /// [`f16`] Ï€/2 + pub const FRAC_PI_2: f16 = f16(0x3E48u16); + /// [`f16`] Ï€/3 + pub const FRAC_PI_3: f16 = f16(0x3C30u16); + /// [`f16`] Ï€/4 + pub const FRAC_PI_4: f16 = f16(0x3A48u16); + /// [`f16`] Ï€/6 + pub const FRAC_PI_6: f16 = f16(0x3830u16); + /// [`f16`] Ï€/8 + pub const FRAC_PI_8: f16 = f16(0x3648u16); + /// [`f16`] ð—…ð—‡ 10 + pub const LN_10: f16 = f16(0x409Bu16); + /// [`f16`] ð—…ð—‡ 2 + pub const LN_2: f16 = f16(0x398Cu16); + /// [`f16`] ð—…ð—ˆð—€â‚₀ℯ + pub const LOG10_E: f16 = f16(0x36F3u16); + /// [`f16`] ð—…ð—ˆð—€â‚â‚€2 + pub const LOG10_2: f16 = f16(0x34D1u16); + /// [`f16`] ð—…ð—ˆð—€â‚‚ℯ + pub const LOG2_E: f16 = f16(0x3DC5u16); + /// [`f16`] ð—…ð—ˆð—€â‚‚10 + pub const LOG2_10: f16 = f16(0x42A5u16); + /// [`f16`] √2 + pub const SQRT_2: f16 = f16(0x3DA8u16); +} + +impl From for f32 { + #[inline] + fn from(x: f16) -> f32 { + x.to_f32() + } +} + +impl From for f64 { + #[inline] + fn from(x: f16) -> f64 { + x.to_f64() + } +} + +impl From for f16 { + #[inline] + fn from(x: i8) -> f16 { + // Convert to f32, then to f16 + f16::from_f32(f32::from(x)) + } +} + +impl From for f16 { + #[inline] + fn from(x: u8) -> f16 { + // Convert to f32, then to f16 + f16::from_f32(f32::from(x)) + } +} + +impl PartialEq for f16 { + fn eq(&self, other: &f16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + (self.0 == other.0) || ((self.0 | other.0) & 0x7FFFu16 == 0) + } + } +} + +impl PartialOrd for f16 { + fn partial_cmp(&self, other: &f16) -> Option { + if self.is_nan() || other.is_nan() { + None + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => Some(self.0.cmp(&other.0)), + (false, true) => { + if (self.0 | other.0) & 0x7FFFu16 == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } + (true, false) => { + if (self.0 | other.0) & 0x7FFFu16 == 0 { + Some(Ordering::Equal) + } else { + Some(Ordering::Less) + } + } + (true, true) => Some(other.0.cmp(&self.0)), + } + } + } + + fn lt(&self, other: &f16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 < other.0, + (false, true) => false, + (true, false) => (self.0 | other.0) & 0x7FFFu16 != 0, + (true, true) => self.0 > other.0, + } + } + } + + fn le(&self, other: &f16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 <= other.0, + (false, true) => (self.0 | other.0) & 0x7FFFu16 == 0, + (true, false) => true, + (true, true) => self.0 >= other.0, + } + } + } + + fn gt(&self, other: &f16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 > other.0, + (false, true) => (self.0 | other.0) & 0x7FFFu16 != 0, + (true, false) => false, + (true, true) => self.0 < other.0, + } + } + } + + fn ge(&self, other: &f16) -> bool { + if self.is_nan() || other.is_nan() { + false + } else { + let neg = self.0 & 0x8000u16 != 0; + let other_neg = other.0 & 0x8000u16 != 0; + match (neg, other_neg) { + (false, false) => self.0 >= other.0, + (false, true) => true, + (true, false) => (self.0 | other.0) & 0x7FFFu16 == 0, + (true, true) => self.0 <= other.0, + } + } + } +} + +impl FromStr for f16 { + type Err = ParseFloatError; + fn from_str(src: &str) -> Result { + f32::from_str(src).map(f16::from_f32) + } +} + +impl Debug for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:?}", self.to_f32()) + } +} + +impl Display for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{}", self.to_f32()) + } +} + +impl LowerExp for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:e}", self.to_f32()) + } +} + +impl UpperExp for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:E}", self.to_f32()) + } +} + +impl Binary for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:b}", self.0) + } +} + +impl Octal for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:o}", self.0) + } +} + +impl LowerHex for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:x}", self.0) + } +} + +impl UpperHex for f16 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{:X}", self.0) + } +} + +impl Neg for f16 { + type Output = Self; + + #[inline] + fn neg(self) -> Self::Output { + Self(self.0 ^ 0x8000) + } +} + +impl Add for f16 { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) + Self::to_f32(rhs)) + } +} + +impl Add<&f16> for f16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: &f16) -> Self::Output { + self.add(*rhs) + } +} + +impl Add<&f16> for &f16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: &f16) -> Self::Output { + (*self).add(*rhs) + } +} + +impl Add for &f16 { + type Output = >::Output; + + #[inline] + fn add(self, rhs: f16) -> Self::Output { + (*self).add(rhs) + } +} + +impl AddAssign for f16 { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = (*self).add(rhs); + } +} + +impl AddAssign<&f16> for f16 { + #[inline] + fn add_assign(&mut self, rhs: &f16) { + *self = (*self).add(rhs); + } +} + +impl Sub for f16 { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) - Self::to_f32(rhs)) + } +} + +impl Sub<&f16> for f16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: &f16) -> Self::Output { + self.sub(*rhs) + } +} + +impl Sub<&f16> for &f16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: &f16) -> Self::Output { + (*self).sub(*rhs) + } +} + +impl Sub for &f16 { + type Output = >::Output; + + #[inline] + fn sub(self, rhs: f16) -> Self::Output { + (*self).sub(rhs) + } +} + +impl SubAssign for f16 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = (*self).sub(rhs); + } +} + +impl SubAssign<&f16> for f16 { + #[inline] + fn sub_assign(&mut self, rhs: &f16) { + *self = (*self).sub(rhs); + } +} + +impl Mul for f16 { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) * Self::to_f32(rhs)) + } +} + +impl Mul<&f16> for f16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: &f16) -> Self::Output { + self.mul(*rhs) + } +} + +impl Mul<&f16> for &f16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: &f16) -> Self::Output { + (*self).mul(*rhs) + } +} + +impl Mul for &f16 { + type Output = >::Output; + + #[inline] + fn mul(self, rhs: f16) -> Self::Output { + (*self).mul(rhs) + } +} + +impl MulAssign for f16 { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = (*self).mul(rhs); + } +} + +impl MulAssign<&f16> for f16 { + #[inline] + fn mul_assign(&mut self, rhs: &f16) { + *self = (*self).mul(rhs); + } +} + +impl Div for f16 { + type Output = Self; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) / Self::to_f32(rhs)) + } +} + +impl Div<&f16> for f16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: &f16) -> Self::Output { + self.div(*rhs) + } +} + +impl Div<&f16> for &f16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: &f16) -> Self::Output { + (*self).div(*rhs) + } +} + +impl Div for &f16 { + type Output = >::Output; + + #[inline] + fn div(self, rhs: f16) -> Self::Output { + (*self).div(rhs) + } +} + +impl DivAssign for f16 { + #[inline] + fn div_assign(&mut self, rhs: Self) { + *self = (*self).div(rhs); + } +} + +impl DivAssign<&f16> for f16 { + #[inline] + fn div_assign(&mut self, rhs: &f16) { + *self = (*self).div(rhs); + } +} + +impl Rem for f16 { + type Output = Self; + + #[inline] + fn rem(self, rhs: Self) -> Self::Output { + Self::from_f32(Self::to_f32(self) % Self::to_f32(rhs)) + } +} + +impl Rem<&f16> for f16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: &f16) -> Self::Output { + self.rem(*rhs) + } +} + +impl Rem<&f16> for &f16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: &f16) -> Self::Output { + (*self).rem(*rhs) + } +} + +impl Rem for &f16 { + type Output = >::Output; + + #[inline] + fn rem(self, rhs: f16) -> Self::Output { + (*self).rem(rhs) + } +} + +impl RemAssign for f16 { + #[inline] + fn rem_assign(&mut self, rhs: Self) { + *self = (*self).rem(rhs); + } +} + +impl RemAssign<&f16> for f16 { + #[inline] + fn rem_assign(&mut self, rhs: &f16) { + *self = (*self).rem(rhs); + } +} + +impl Product for f16 { + #[inline] + fn product>(iter: I) -> Self { + f16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +impl<'a> Product<&'a f16> for f16 { + #[inline] + fn product>(iter: I) -> Self { + f16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +impl Sum for f16 { + #[inline] + fn sum>(iter: I) -> Self { + f16::from_f32(iter.map(|f| f.to_f32()).sum()) + } +} + +impl<'a> Sum<&'a f16> for f16 { + #[inline] + fn sum>(iter: I) -> Self { + f16::from_f32(iter.map(|f| f.to_f32()).product()) + } +} + +#[allow( + clippy::cognitive_complexity, + clippy::float_cmp, + clippy::neg_cmp_op_on_partial_ord +)] +#[cfg(test)] +mod test { + use super::*; + use core::cmp::Ordering; + #[cfg(feature = "num-traits")] + use num_traits::{AsPrimitive, FromPrimitive, ToPrimitive}; + use quickcheck_macros::quickcheck; + + #[cfg(feature = "num-traits")] + #[test] + fn as_primitive() { + let two = f16::from_f32(2.0); + assert_eq!(>::as_(2), two); + assert_eq!(>::as_(two), 2); + + assert_eq!(>::as_(2.0), two); + assert_eq!(>::as_(two), 2.0); + + assert_eq!(>::as_(2.0), two); + assert_eq!(>::as_(two), 2.0); + } + + #[cfg(feature = "num-traits")] + #[test] + fn to_primitive() { + let two = f16::from_f32(2.0); + assert_eq!(ToPrimitive::to_i32(&two).unwrap(), 2i32); + assert_eq!(ToPrimitive::to_f32(&two).unwrap(), 2.0f32); + assert_eq!(ToPrimitive::to_f64(&two).unwrap(), 2.0f64); + } + + #[cfg(feature = "num-traits")] + #[test] + fn from_primitive() { + let two = f16::from_f32(2.0); + assert_eq!(::from_i32(2).unwrap(), two); + assert_eq!(::from_f32(2.0).unwrap(), two); + assert_eq!(::from_f64(2.0).unwrap(), two); + } + + #[test] + fn test_f16_consts() { + // DIGITS + let digits = ((f16::MANTISSA_DIGITS as f32 - 1.0) * 2f32.log10()).floor() as u32; + assert_eq!(f16::DIGITS, digits); + // sanity check to show test is good + let digits32 = ((core::f32::MANTISSA_DIGITS as f32 - 1.0) * 2f32.log10()).floor() as u32; + assert_eq!(core::f32::DIGITS, digits32); + + // EPSILON + let one = f16::from_f32(1.0); + let one_plus_epsilon = f16::from_bits(one.to_bits() + 1); + let epsilon = f16::from_f32(one_plus_epsilon.to_f32() - 1.0); + assert_eq!(f16::EPSILON, epsilon); + // sanity check to show test is good + let one_plus_epsilon32 = f32::from_bits(1.0f32.to_bits() + 1); + let epsilon32 = one_plus_epsilon32 - 1f32; + assert_eq!(core::f32::EPSILON, epsilon32); + + // MAX, MIN and MIN_POSITIVE + let max = f16::from_bits(f16::INFINITY.to_bits() - 1); + let min = f16::from_bits(f16::NEG_INFINITY.to_bits() - 1); + let min_pos = f16::from_f32(2f32.powi(f16::MIN_EXP - 1)); + assert_eq!(f16::MAX, max); + assert_eq!(f16::MIN, min); + assert_eq!(f16::MIN_POSITIVE, min_pos); + // sanity check to show test is good + let max32 = f32::from_bits(core::f32::INFINITY.to_bits() - 1); + let min32 = f32::from_bits(core::f32::NEG_INFINITY.to_bits() - 1); + let min_pos32 = 2f32.powi(core::f32::MIN_EXP - 1); + assert_eq!(core::f32::MAX, max32); + assert_eq!(core::f32::MIN, min32); + assert_eq!(core::f32::MIN_POSITIVE, min_pos32); + + // MIN_10_EXP and MAX_10_EXP + let ten_to_min = 10f32.powi(f16::MIN_10_EXP); + assert!(ten_to_min / 10.0 < f16::MIN_POSITIVE.to_f32()); + assert!(ten_to_min > f16::MIN_POSITIVE.to_f32()); + let ten_to_max = 10f32.powi(f16::MAX_10_EXP); + assert!(ten_to_max < f16::MAX.to_f32()); + assert!(ten_to_max * 10.0 > f16::MAX.to_f32()); + // sanity check to show test is good + let ten_to_min32 = 10f64.powi(core::f32::MIN_10_EXP); + assert!(ten_to_min32 / 10.0 < f64::from(core::f32::MIN_POSITIVE)); + assert!(ten_to_min32 > f64::from(core::f32::MIN_POSITIVE)); + let ten_to_max32 = 10f64.powi(core::f32::MAX_10_EXP); + assert!(ten_to_max32 < f64::from(core::f32::MAX)); + assert!(ten_to_max32 * 10.0 > f64::from(core::f32::MAX)); + } + + #[test] + fn test_f16_consts_from_f32() { + let one = f16::from_f32(1.0); + let zero = f16::from_f32(0.0); + let neg_zero = f16::from_f32(-0.0); + let neg_one = f16::from_f32(-1.0); + let inf = f16::from_f32(core::f32::INFINITY); + let neg_inf = f16::from_f32(core::f32::NEG_INFINITY); + let nan = f16::from_f32(core::f32::NAN); + + assert_eq!(f16::ONE, one); + assert_eq!(f16::ZERO, zero); + assert!(zero.is_sign_positive()); + assert_eq!(f16::NEG_ZERO, neg_zero); + assert!(neg_zero.is_sign_negative()); + assert_eq!(f16::NEG_ONE, neg_one); + assert!(neg_one.is_sign_negative()); + assert_eq!(f16::INFINITY, inf); + assert_eq!(f16::NEG_INFINITY, neg_inf); + assert!(nan.is_nan()); + assert!(f16::NAN.is_nan()); + + let e = f16::from_f32(core::f32::consts::E); + let pi = f16::from_f32(core::f32::consts::PI); + let frac_1_pi = f16::from_f32(core::f32::consts::FRAC_1_PI); + let frac_1_sqrt_2 = f16::from_f32(core::f32::consts::FRAC_1_SQRT_2); + let frac_2_pi = f16::from_f32(core::f32::consts::FRAC_2_PI); + let frac_2_sqrt_pi = f16::from_f32(core::f32::consts::FRAC_2_SQRT_PI); + let frac_pi_2 = f16::from_f32(core::f32::consts::FRAC_PI_2); + let frac_pi_3 = f16::from_f32(core::f32::consts::FRAC_PI_3); + let frac_pi_4 = f16::from_f32(core::f32::consts::FRAC_PI_4); + let frac_pi_6 = f16::from_f32(core::f32::consts::FRAC_PI_6); + let frac_pi_8 = f16::from_f32(core::f32::consts::FRAC_PI_8); + let ln_10 = f16::from_f32(core::f32::consts::LN_10); + let ln_2 = f16::from_f32(core::f32::consts::LN_2); + let log10_e = f16::from_f32(core::f32::consts::LOG10_E); + // core::f32::consts::LOG10_2 requires rustc 1.43.0 + let log10_2 = f16::from_f32(2f32.log10()); + let log2_e = f16::from_f32(core::f32::consts::LOG2_E); + // core::f32::consts::LOG2_10 requires rustc 1.43.0 + let log2_10 = f16::from_f32(10f32.log2()); + let sqrt_2 = f16::from_f32(core::f32::consts::SQRT_2); + + assert_eq!(f16::E, e); + assert_eq!(f16::PI, pi); + assert_eq!(f16::FRAC_1_PI, frac_1_pi); + assert_eq!(f16::FRAC_1_SQRT_2, frac_1_sqrt_2); + assert_eq!(f16::FRAC_2_PI, frac_2_pi); + assert_eq!(f16::FRAC_2_SQRT_PI, frac_2_sqrt_pi); + assert_eq!(f16::FRAC_PI_2, frac_pi_2); + assert_eq!(f16::FRAC_PI_3, frac_pi_3); + assert_eq!(f16::FRAC_PI_4, frac_pi_4); + assert_eq!(f16::FRAC_PI_6, frac_pi_6); + assert_eq!(f16::FRAC_PI_8, frac_pi_8); + assert_eq!(f16::LN_10, ln_10); + assert_eq!(f16::LN_2, ln_2); + assert_eq!(f16::LOG10_E, log10_e); + assert_eq!(f16::LOG10_2, log10_2); + assert_eq!(f16::LOG2_E, log2_e); + assert_eq!(f16::LOG2_10, log2_10); + assert_eq!(f16::SQRT_2, sqrt_2); + } + + #[test] + fn test_f16_consts_from_f64() { + let one = f16::from_f64(1.0); + let zero = f16::from_f64(0.0); + let neg_zero = f16::from_f64(-0.0); + let inf = f16::from_f64(core::f64::INFINITY); + let neg_inf = f16::from_f64(core::f64::NEG_INFINITY); + let nan = f16::from_f64(core::f64::NAN); + + assert_eq!(f16::ONE, one); + assert_eq!(f16::ZERO, zero); + assert!(zero.is_sign_positive()); + assert_eq!(f16::NEG_ZERO, neg_zero); + assert!(neg_zero.is_sign_negative()); + assert_eq!(f16::INFINITY, inf); + assert_eq!(f16::NEG_INFINITY, neg_inf); + assert!(nan.is_nan()); + assert!(f16::NAN.is_nan()); + + let e = f16::from_f64(core::f64::consts::E); + let pi = f16::from_f64(core::f64::consts::PI); + let frac_1_pi = f16::from_f64(core::f64::consts::FRAC_1_PI); + let frac_1_sqrt_2 = f16::from_f64(core::f64::consts::FRAC_1_SQRT_2); + let frac_2_pi = f16::from_f64(core::f64::consts::FRAC_2_PI); + let frac_2_sqrt_pi = f16::from_f64(core::f64::consts::FRAC_2_SQRT_PI); + let frac_pi_2 = f16::from_f64(core::f64::consts::FRAC_PI_2); + let frac_pi_3 = f16::from_f64(core::f64::consts::FRAC_PI_3); + let frac_pi_4 = f16::from_f64(core::f64::consts::FRAC_PI_4); + let frac_pi_6 = f16::from_f64(core::f64::consts::FRAC_PI_6); + let frac_pi_8 = f16::from_f64(core::f64::consts::FRAC_PI_8); + let ln_10 = f16::from_f64(core::f64::consts::LN_10); + let ln_2 = f16::from_f64(core::f64::consts::LN_2); + let log10_e = f16::from_f64(core::f64::consts::LOG10_E); + // core::f64::consts::LOG10_2 requires rustc 1.43.0 + let log10_2 = f16::from_f64(2f64.log10()); + let log2_e = f16::from_f64(core::f64::consts::LOG2_E); + // core::f64::consts::LOG2_10 requires rustc 1.43.0 + let log2_10 = f16::from_f64(10f64.log2()); + let sqrt_2 = f16::from_f64(core::f64::consts::SQRT_2); + + assert_eq!(f16::E, e); + assert_eq!(f16::PI, pi); + assert_eq!(f16::FRAC_1_PI, frac_1_pi); + assert_eq!(f16::FRAC_1_SQRT_2, frac_1_sqrt_2); + assert_eq!(f16::FRAC_2_PI, frac_2_pi); + assert_eq!(f16::FRAC_2_SQRT_PI, frac_2_sqrt_pi); + assert_eq!(f16::FRAC_PI_2, frac_pi_2); + assert_eq!(f16::FRAC_PI_3, frac_pi_3); + assert_eq!(f16::FRAC_PI_4, frac_pi_4); + assert_eq!(f16::FRAC_PI_6, frac_pi_6); + assert_eq!(f16::FRAC_PI_8, frac_pi_8); + assert_eq!(f16::LN_10, ln_10); + assert_eq!(f16::LN_2, ln_2); + assert_eq!(f16::LOG10_E, log10_e); + assert_eq!(f16::LOG10_2, log10_2); + assert_eq!(f16::LOG2_E, log2_e); + assert_eq!(f16::LOG2_10, log2_10); + assert_eq!(f16::SQRT_2, sqrt_2); + } + + #[test] + fn test_nan_conversion_to_smaller() { + let nan64 = f64::from_bits(0x7FF0_0000_0000_0001u64); + let neg_nan64 = f64::from_bits(0xFFF0_0000_0000_0001u64); + let nan32 = f32::from_bits(0x7F80_0001u32); + let neg_nan32 = f32::from_bits(0xFF80_0001u32); + let nan32_from_64 = nan64 as f32; + let neg_nan32_from_64 = neg_nan64 as f32; + let nan16_from_64 = f16::from_f64(nan64); + let neg_nan16_from_64 = f16::from_f64(neg_nan64); + let nan16_from_32 = f16::from_f32(nan32); + let neg_nan16_from_32 = f16::from_f32(neg_nan32); + + assert!(nan64.is_nan() && nan64.is_sign_positive()); + assert!(neg_nan64.is_nan() && neg_nan64.is_sign_negative()); + assert!(nan32.is_nan() && nan32.is_sign_positive()); + assert!(neg_nan32.is_nan() && neg_nan32.is_sign_negative()); + assert!(nan32_from_64.is_nan() && nan32_from_64.is_sign_positive()); + assert!(neg_nan32_from_64.is_nan() && neg_nan32_from_64.is_sign_negative()); + assert!(nan16_from_64.is_nan() && nan16_from_64.is_sign_positive()); + assert!(neg_nan16_from_64.is_nan() && neg_nan16_from_64.is_sign_negative()); + assert!(nan16_from_32.is_nan() && nan16_from_32.is_sign_positive()); + assert!(neg_nan16_from_32.is_nan() && neg_nan16_from_32.is_sign_negative()); + } + + #[test] + fn test_nan_conversion_to_larger() { + let nan16 = f16::from_bits(0x7C01u16); + let neg_nan16 = f16::from_bits(0xFC01u16); + let nan32 = f32::from_bits(0x7F80_0001u32); + let neg_nan32 = f32::from_bits(0xFF80_0001u32); + let nan32_from_16 = f32::from(nan16); + let neg_nan32_from_16 = f32::from(neg_nan16); + let nan64_from_16 = f64::from(nan16); + let neg_nan64_from_16 = f64::from(neg_nan16); + let nan64_from_32 = f64::from(nan32); + let neg_nan64_from_32 = f64::from(neg_nan32); + + assert!(nan16.is_nan() && nan16.is_sign_positive()); + assert!(neg_nan16.is_nan() && neg_nan16.is_sign_negative()); + assert!(nan32.is_nan() && nan32.is_sign_positive()); + assert!(neg_nan32.is_nan() && neg_nan32.is_sign_negative()); + assert!(nan32_from_16.is_nan() && nan32_from_16.is_sign_positive()); + assert!(neg_nan32_from_16.is_nan() && neg_nan32_from_16.is_sign_negative()); + assert!(nan64_from_16.is_nan() && nan64_from_16.is_sign_positive()); + assert!(neg_nan64_from_16.is_nan() && neg_nan64_from_16.is_sign_negative()); + assert!(nan64_from_32.is_nan() && nan64_from_32.is_sign_positive()); + assert!(neg_nan64_from_32.is_nan() && neg_nan64_from_32.is_sign_negative()); + } + + #[test] + fn test_f16_to_f32() { + let f = f16::from_f32(7.0); + assert_eq!(f.to_f32(), 7.0f32); + + // 7.1 is NOT exactly representable in 16-bit, it's rounded + let f = f16::from_f32(7.1); + let diff = (f.to_f32() - 7.1f32).abs(); + // diff must be <= 4 * EPSILON, as 7 has two more significant bits than 1 + assert!(diff <= 4.0 * f16::EPSILON.to_f32()); + + assert_eq!(f16::from_bits(0x0000_0001).to_f32(), 2.0f32.powi(-24)); + assert_eq!(f16::from_bits(0x0000_0005).to_f32(), 5.0 * 2.0f32.powi(-24)); + + assert_eq!(f16::from_bits(0x0000_0001), f16::from_f32(2.0f32.powi(-24))); + assert_eq!( + f16::from_bits(0x0000_0005), + f16::from_f32(5.0 * 2.0f32.powi(-24)) + ); + } + + #[test] + fn test_f16_to_f64() { + let f = f16::from_f64(7.0); + assert_eq!(f.to_f64(), 7.0f64); + + // 7.1 is NOT exactly representable in 16-bit, it's rounded + let f = f16::from_f64(7.1); + let diff = (f.to_f64() - 7.1f64).abs(); + // diff must be <= 4 * EPSILON, as 7 has two more significant bits than 1 + assert!(diff <= 4.0 * f16::EPSILON.to_f64()); + + assert_eq!(f16::from_bits(0x0000_0001).to_f64(), 2.0f64.powi(-24)); + assert_eq!(f16::from_bits(0x0000_0005).to_f64(), 5.0 * 2.0f64.powi(-24)); + + assert_eq!(f16::from_bits(0x0000_0001), f16::from_f64(2.0f64.powi(-24))); + assert_eq!( + f16::from_bits(0x0000_0005), + f16::from_f64(5.0 * 2.0f64.powi(-24)) + ); + } + + #[test] + fn test_comparisons() { + let zero = f16::from_f64(0.0); + let one = f16::from_f64(1.0); + let neg_zero = f16::from_f64(-0.0); + let neg_one = f16::from_f64(-1.0); + + assert_eq!(zero.partial_cmp(&neg_zero), Some(Ordering::Equal)); + assert_eq!(neg_zero.partial_cmp(&zero), Some(Ordering::Equal)); + assert!(zero == neg_zero); + assert!(neg_zero == zero); + assert!(!(zero != neg_zero)); + assert!(!(neg_zero != zero)); + assert!(!(zero < neg_zero)); + assert!(!(neg_zero < zero)); + assert!(zero <= neg_zero); + assert!(neg_zero <= zero); + assert!(!(zero > neg_zero)); + assert!(!(neg_zero > zero)); + assert!(zero >= neg_zero); + assert!(neg_zero >= zero); + + assert_eq!(one.partial_cmp(&neg_zero), Some(Ordering::Greater)); + assert_eq!(neg_zero.partial_cmp(&one), Some(Ordering::Less)); + assert!(!(one == neg_zero)); + assert!(!(neg_zero == one)); + assert!(one != neg_zero); + assert!(neg_zero != one); + assert!(!(one < neg_zero)); + assert!(neg_zero < one); + assert!(!(one <= neg_zero)); + assert!(neg_zero <= one); + assert!(one > neg_zero); + assert!(!(neg_zero > one)); + assert!(one >= neg_zero); + assert!(!(neg_zero >= one)); + + assert_eq!(one.partial_cmp(&neg_one), Some(Ordering::Greater)); + assert_eq!(neg_one.partial_cmp(&one), Some(Ordering::Less)); + assert!(!(one == neg_one)); + assert!(!(neg_one == one)); + assert!(one != neg_one); + assert!(neg_one != one); + assert!(!(one < neg_one)); + assert!(neg_one < one); + assert!(!(one <= neg_one)); + assert!(neg_one <= one); + assert!(one > neg_one); + assert!(!(neg_one > one)); + assert!(one >= neg_one); + assert!(!(neg_one >= one)); + } + + #[test] + #[allow(clippy::erasing_op, clippy::identity_op)] + fn round_to_even_f32() { + // smallest positive subnormal = 0b0.0000_0000_01 * 2^-14 = 2^-24 + let min_sub = f16::from_bits(1); + let min_sub_f = (-24f32).exp2(); + assert_eq!(f16::from_f32(min_sub_f).to_bits(), min_sub.to_bits()); + assert_eq!(f32::from(min_sub).to_bits(), min_sub_f.to_bits()); + + // 0.0000000000_011111 rounded to 0.0000000000 (< tie, no rounding) + // 0.0000000000_100000 rounded to 0.0000000000 (tie and even, remains at even) + // 0.0000000000_100001 rounded to 0.0000000001 (> tie, rounds up) + assert_eq!( + f16::from_f32(min_sub_f * 0.49).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + f16::from_f32(min_sub_f * 0.50).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + f16::from_f32(min_sub_f * 0.51).to_bits(), + min_sub.to_bits() * 1 + ); + + // 0.0000000001_011111 rounded to 0.0000000001 (< tie, no rounding) + // 0.0000000001_100000 rounded to 0.0000000010 (tie and odd, rounds up to even) + // 0.0000000001_100001 rounded to 0.0000000010 (> tie, rounds up) + assert_eq!( + f16::from_f32(min_sub_f * 1.49).to_bits(), + min_sub.to_bits() * 1 + ); + assert_eq!( + f16::from_f32(min_sub_f * 1.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f32(min_sub_f * 1.51).to_bits(), + min_sub.to_bits() * 2 + ); + + // 0.0000000010_011111 rounded to 0.0000000010 (< tie, no rounding) + // 0.0000000010_100000 rounded to 0.0000000010 (tie and even, remains at even) + // 0.0000000010_100001 rounded to 0.0000000011 (> tie, rounds up) + assert_eq!( + f16::from_f32(min_sub_f * 2.49).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f32(min_sub_f * 2.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f32(min_sub_f * 2.51).to_bits(), + min_sub.to_bits() * 3 + ); + + assert_eq!( + f16::from_f32(2000.49f32).to_bits(), + f16::from_f32(2000.0).to_bits() + ); + assert_eq!( + f16::from_f32(2000.50f32).to_bits(), + f16::from_f32(2000.0).to_bits() + ); + assert_eq!( + f16::from_f32(2000.51f32).to_bits(), + f16::from_f32(2001.0).to_bits() + ); + assert_eq!( + f16::from_f32(2001.49f32).to_bits(), + f16::from_f32(2001.0).to_bits() + ); + assert_eq!( + f16::from_f32(2001.50f32).to_bits(), + f16::from_f32(2002.0).to_bits() + ); + assert_eq!( + f16::from_f32(2001.51f32).to_bits(), + f16::from_f32(2002.0).to_bits() + ); + assert_eq!( + f16::from_f32(2002.49f32).to_bits(), + f16::from_f32(2002.0).to_bits() + ); + assert_eq!( + f16::from_f32(2002.50f32).to_bits(), + f16::from_f32(2002.0).to_bits() + ); + assert_eq!( + f16::from_f32(2002.51f32).to_bits(), + f16::from_f32(2003.0).to_bits() + ); + } + + #[test] + #[allow(clippy::erasing_op, clippy::identity_op)] + fn round_to_even_f64() { + // smallest positive subnormal = 0b0.0000_0000_01 * 2^-14 = 2^-24 + let min_sub = f16::from_bits(1); + let min_sub_f = (-24f64).exp2(); + assert_eq!(f16::from_f64(min_sub_f).to_bits(), min_sub.to_bits()); + assert_eq!(f64::from(min_sub).to_bits(), min_sub_f.to_bits()); + + // 0.0000000000_011111 rounded to 0.0000000000 (< tie, no rounding) + // 0.0000000000_100000 rounded to 0.0000000000 (tie and even, remains at even) + // 0.0000000000_100001 rounded to 0.0000000001 (> tie, rounds up) + assert_eq!( + f16::from_f64(min_sub_f * 0.49).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + f16::from_f64(min_sub_f * 0.50).to_bits(), + min_sub.to_bits() * 0 + ); + assert_eq!( + f16::from_f64(min_sub_f * 0.51).to_bits(), + min_sub.to_bits() * 1 + ); + + // 0.0000000001_011111 rounded to 0.0000000001 (< tie, no rounding) + // 0.0000000001_100000 rounded to 0.0000000010 (tie and odd, rounds up to even) + // 0.0000000001_100001 rounded to 0.0000000010 (> tie, rounds up) + assert_eq!( + f16::from_f64(min_sub_f * 1.49).to_bits(), + min_sub.to_bits() * 1 + ); + assert_eq!( + f16::from_f64(min_sub_f * 1.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f64(min_sub_f * 1.51).to_bits(), + min_sub.to_bits() * 2 + ); + + // 0.0000000010_011111 rounded to 0.0000000010 (< tie, no rounding) + // 0.0000000010_100000 rounded to 0.0000000010 (tie and even, remains at even) + // 0.0000000010_100001 rounded to 0.0000000011 (> tie, rounds up) + assert_eq!( + f16::from_f64(min_sub_f * 2.49).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f64(min_sub_f * 2.50).to_bits(), + min_sub.to_bits() * 2 + ); + assert_eq!( + f16::from_f64(min_sub_f * 2.51).to_bits(), + min_sub.to_bits() * 3 + ); + + assert_eq!( + f16::from_f64(2000.49f64).to_bits(), + f16::from_f64(2000.0).to_bits() + ); + assert_eq!( + f16::from_f64(2000.50f64).to_bits(), + f16::from_f64(2000.0).to_bits() + ); + assert_eq!( + f16::from_f64(2000.51f64).to_bits(), + f16::from_f64(2001.0).to_bits() + ); + assert_eq!( + f16::from_f64(2001.49f64).to_bits(), + f16::from_f64(2001.0).to_bits() + ); + assert_eq!( + f16::from_f64(2001.50f64).to_bits(), + f16::from_f64(2002.0).to_bits() + ); + assert_eq!( + f16::from_f64(2001.51f64).to_bits(), + f16::from_f64(2002.0).to_bits() + ); + assert_eq!( + f16::from_f64(2002.49f64).to_bits(), + f16::from_f64(2002.0).to_bits() + ); + assert_eq!( + f16::from_f64(2002.50f64).to_bits(), + f16::from_f64(2002.0).to_bits() + ); + assert_eq!( + f16::from_f64(2002.51f64).to_bits(), + f16::from_f64(2003.0).to_bits() + ); + } + + impl quickcheck::Arbitrary for f16 { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + f16(u16::arbitrary(g)) + } + } + + #[quickcheck] + fn qc_roundtrip_f16_f32_is_identity(f: f16) -> bool { + let roundtrip = f16::from_f32(f.to_f32()); + if f.is_nan() { + roundtrip.is_nan() && f.is_sign_negative() == roundtrip.is_sign_negative() + } else { + f.0 == roundtrip.0 + } + } + + #[quickcheck] + fn qc_roundtrip_f16_f64_is_identity(f: f16) -> bool { + let roundtrip = f16::from_f64(f.to_f64()); + if f.is_nan() { + roundtrip.is_nan() && f.is_sign_negative() == roundtrip.is_sign_negative() + } else { + f.0 == roundtrip.0 + } + } +} diff --git a/third_party/rust/half/src/binary16/convert.rs b/third_party/rust/half/src/binary16/convert.rs new file mode 100644 index 000000000000..c2521d8d2608 --- /dev/null +++ b/third_party/rust/half/src/binary16/convert.rs @@ -0,0 +1,491 @@ +#![allow(dead_code, unused_imports)] + +macro_rules! convert_fn { + (fn $name:ident($var:ident : $vartype:ty) -> $restype:ty { + if feature("f16c") { $f16c:expr } + else { $fallback:expr }}) => { + #[inline] + pub(crate) fn $name($var: $vartype) -> $restype { + // Use CPU feature detection if using std + #[cfg(all( + feature = "use-intrinsics", + feature = "std", + any(target_arch = "x86", target_arch = "x86_64"), + not(target_feature = "f16c") + ))] + { + if is_x86_feature_detected!("f16c") { + $f16c + } else { + $fallback + } + } + // Use intrinsics directly when a compile target or using no_std + #[cfg(all( + feature = "use-intrinsics", + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "f16c" + ))] + { + $f16c + } + // Fallback to software + #[cfg(any( + not(feature = "use-intrinsics"), + not(any(target_arch = "x86", target_arch = "x86_64")), + all(not(feature = "std"), not(target_feature = "f16c")) + ))] + { + $fallback + } + } + }; +} + +convert_fn! { + fn f32_to_f16(f: f32) -> u16 { + if feature("f16c") { + unsafe { x86::f32_to_f16_x86_f16c(f) } + } else { + f32_to_f16_fallback(f) + } + } +} + +convert_fn! { + fn f64_to_f16(f: f64) -> u16 { + if feature("f16c") { + unsafe { x86::f32_to_f16_x86_f16c(f as f32) } + } else { + f64_to_f16_fallback(f) + } + } +} + +convert_fn! { + fn f16_to_f32(i: u16) -> f32 { + if feature("f16c") { + unsafe { x86::f16_to_f32_x86_f16c(i) } + } else { + f16_to_f32_fallback(i) + } + } +} + +convert_fn! { + fn f16_to_f64(i: u16) -> f64 { + if feature("f16c") { + unsafe { x86::f16_to_f32_x86_f16c(i) as f64 } + } else { + f16_to_f64_fallback(i) + } + } +} + +// TODO: While SIMD versions are faster, further improvements can be made by doing runtime feature +// detection once at beginning of convert slice method, rather than per chunk + +convert_fn! { + fn f32x4_to_f16x4(f: &[f32]) -> [u16; 4] { + if feature("f16c") { + unsafe { x86::f32x4_to_f16x4_x86_f16c(f) } + } else { + f32x4_to_f16x4_fallback(f) + } + } +} + +convert_fn! { + fn f16x4_to_f32x4(i: &[u16]) -> [f32; 4] { + if feature("f16c") { + unsafe { x86::f16x4_to_f32x4_x86_f16c(i) } + } else { + f16x4_to_f32x4_fallback(i) + } + } +} + +convert_fn! { + fn f64x4_to_f16x4(f: &[f64]) -> [u16; 4] { + if feature("f16c") { + unsafe { x86::f64x4_to_f16x4_x86_f16c(f) } + } else { + f64x4_to_f16x4_fallback(f) + } + } +} + +convert_fn! { + fn f16x4_to_f64x4(i: &[u16]) -> [f64; 4] { + if feature("f16c") { + unsafe { x86::f16x4_to_f64x4_x86_f16c(i) } + } else { + f16x4_to_f64x4_fallback(i) + } + } +} + +/////////////// Fallbacks //////////////// + +// In the below functions, round to nearest, with ties to even. +// Let us call the most significant bit that will be shifted out the round_bit. +// +// Round up if either +// a) Removed part > tie. +// (mantissa & round_bit) != 0 && (mantissa & (round_bit - 1)) != 0 +// b) Removed part == tie, and retained part is odd. +// (mantissa & round_bit) != 0 && (mantissa & (2 * round_bit)) != 0 +// (If removed part == tie and retained part is even, do not round up.) +// These two conditions can be combined into one: +// (mantissa & round_bit) != 0 && (mantissa & ((round_bit - 1) | (2 * round_bit))) != 0 +// which can be simplified into +// (mantissa & round_bit) != 0 && (mantissa & (3 * round_bit - 1)) != 0 + +fn f32_to_f16_fallback(value: f32) -> u16 { + // Convert to raw bytes + let x = value.to_bits(); + + // Extract IEEE754 components + let sign = x & 0x8000_0000u32; + let exp = x & 0x7F80_0000u32; + let man = x & 0x007F_FFFFu32; + + // Check for all exponent bits being set, which is Infinity or NaN + if exp == 0x7F80_0000u32 { + // Set mantissa MSB for NaN (and also keep shifted mantissa bits) + let nan_bit = if man == 0 { 0 } else { 0x0200u32 }; + return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 13)) as u16; + } + + // The number is normalized, start assembling half precision version + let half_sign = sign >> 16; + // Unbias the exponent, then bias for half precision + let unbiased_exp = ((exp >> 23) as i32) - 127; + let half_exp = unbiased_exp + 15; + + // Check for exponent overflow, return +infinity + if half_exp >= 0x1F { + return (half_sign | 0x7C00u32) as u16; + } + + // Check for underflow + if half_exp <= 0 { + // Check mantissa for what we can do + if 14 - half_exp > 24 { + // No rounding possibility, so this is a full underflow, return signed zero + return half_sign as u16; + } + // Don't forget about hidden leading mantissa bit when assembling mantissa + let man = man | 0x0080_0000u32; + let mut half_man = man >> (14 - half_exp); + // Check for rounding (see comment above functions) + let round_bit = 1 << (13 - half_exp); + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + half_man += 1; + } + // No exponent for subnormals + return (half_sign | half_man) as u16; + } + + // Rebias the exponent + let half_exp = (half_exp as u32) << 10; + let half_man = man >> 13; + // Check for rounding (see comment above functions) + let round_bit = 0x0000_1000u32; + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + // Round it + ((half_sign | half_exp | half_man) + 1) as u16 + } else { + (half_sign | half_exp | half_man) as u16 + } +} + +fn f64_to_f16_fallback(value: f64) -> u16 { + // Convert to raw bytes, truncating the last 32-bits of mantissa; that precision will always + // be lost on half-precision. + let val = value.to_bits(); + let x = (val >> 32) as u32; + + // Extract IEEE754 components + let sign = x & 0x8000_0000u32; + let exp = x & 0x7FF0_0000u32; + let man = x & 0x000F_FFFFu32; + + // Check for all exponent bits being set, which is Infinity or NaN + if exp == 0x7FF0_0000u32 { + // Set mantissa MSB for NaN (and also keep shifted mantissa bits). + // We also have to check the last 32 bits. + let nan_bit = if man == 0 && (val as u32 == 0) { + 0 + } else { + 0x0200u32 + }; + return ((sign >> 16) | 0x7C00u32 | nan_bit | (man >> 10)) as u16; + } + + // The number is normalized, start assembling half precision version + let half_sign = sign >> 16; + // Unbias the exponent, then bias for half precision + let unbiased_exp = ((exp >> 20) as i64) - 1023; + let half_exp = unbiased_exp + 15; + + // Check for exponent overflow, return +infinity + if half_exp >= 0x1F { + return (half_sign | 0x7C00u32) as u16; + } + + // Check for underflow + if half_exp <= 0 { + // Check mantissa for what we can do + if 10 - half_exp > 21 { + // No rounding possibility, so this is a full underflow, return signed zero + return half_sign as u16; + } + // Don't forget about hidden leading mantissa bit when assembling mantissa + let man = man | 0x0010_0000u32; + let mut half_man = man >> (11 - half_exp); + // Check for rounding (see comment above functions) + let round_bit = 1 << (10 - half_exp); + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + half_man += 1; + } + // No exponent for subnormals + return (half_sign | half_man) as u16; + } + + // Rebias the exponent + let half_exp = (half_exp as u32) << 10; + let half_man = man >> 10; + // Check for rounding (see comment above functions) + let round_bit = 0x0000_0200u32; + if (man & round_bit) != 0 && (man & (3 * round_bit - 1)) != 0 { + // Round it + ((half_sign | half_exp | half_man) + 1) as u16 + } else { + (half_sign | half_exp | half_man) as u16 + } +} + +fn f16_to_f32_fallback(i: u16) -> f32 { + // Check for signed zero + if i & 0x7FFFu16 == 0 { + return f32::from_bits((i as u32) << 16); + } + + let half_sign = (i & 0x8000u16) as u32; + let half_exp = (i & 0x7C00u16) as u32; + let half_man = (i & 0x03FFu16) as u32; + + // Check for an infinity or NaN when all exponent bits set + if half_exp == 0x7C00u32 { + // Check for signed infinity if mantissa is zero + if half_man == 0 { + return f32::from_bits((half_sign << 16) | 0x7F80_0000u32); + } else { + // NaN, keep current mantissa but also set most significiant mantissa bit + return f32::from_bits((half_sign << 16) | 0x7FC0_0000u32 | (half_man << 13)); + } + } + + // Calculate single-precision components with adjusted exponent + let sign = half_sign << 16; + // Unbias exponent + let unbiased_exp = ((half_exp as i32) >> 10) - 15; + + // Check for subnormals, which will be normalized by adjusting exponent + if half_exp == 0 { + // Calculate how much to adjust the exponent by + let e = (half_man as u16).leading_zeros() - 6; + + // Rebias and adjust exponent + let exp = (127 - 15 - e) << 23; + let man = (half_man << (14 + e)) & 0x7F_FF_FFu32; + return f32::from_bits(sign | exp | man); + } + + // Rebias exponent for a normalized normal + let exp = ((unbiased_exp + 127) as u32) << 23; + let man = (half_man & 0x03FFu32) << 13; + f32::from_bits(sign | exp | man) +} + +fn f16_to_f64_fallback(i: u16) -> f64 { + // Check for signed zero + if i & 0x7FFFu16 == 0 { + return f64::from_bits((i as u64) << 48); + } + + let half_sign = (i & 0x8000u16) as u64; + let half_exp = (i & 0x7C00u16) as u64; + let half_man = (i & 0x03FFu16) as u64; + + // Check for an infinity or NaN when all exponent bits set + if half_exp == 0x7C00u64 { + // Check for signed infinity if mantissa is zero + if half_man == 0 { + return f64::from_bits((half_sign << 48) | 0x7FF0_0000_0000_0000u64); + } else { + // NaN, keep current mantissa but also set most significiant mantissa bit + return f64::from_bits((half_sign << 48) | 0x7FF8_0000_0000_0000u64 | (half_man << 42)); + } + } + + // Calculate double-precision components with adjusted exponent + let sign = half_sign << 48; + // Unbias exponent + let unbiased_exp = ((half_exp as i64) >> 10) - 15; + + // Check for subnormals, which will be normalized by adjusting exponent + if half_exp == 0 { + // Calculate how much to adjust the exponent by + let e = (half_man as u16).leading_zeros() - 6; + + // Rebias and adjust exponent + let exp = ((1023 - 15 - e) as u64) << 52; + let man = (half_man << (43 + e)) & 0xF_FFFF_FFFF_FFFFu64; + return f64::from_bits(sign | exp | man); + } + + // Rebias exponent for a normalized normal + let exp = ((unbiased_exp + 1023) as u64) << 52; + let man = (half_man & 0x03FFu64) << 42; + f64::from_bits(sign | exp | man) +} + +#[inline] +fn f16x4_to_f32x4_fallback(v: &[u16]) -> [f32; 4] { + debug_assert!(v.len() >= 4); + + [ + f16_to_f32_fallback(v[0]), + f16_to_f32_fallback(v[1]), + f16_to_f32_fallback(v[2]), + f16_to_f32_fallback(v[3]), + ] +} + +#[inline] +fn f32x4_to_f16x4_fallback(v: &[f32]) -> [u16; 4] { + debug_assert!(v.len() >= 4); + + [ + f32_to_f16_fallback(v[0]), + f32_to_f16_fallback(v[1]), + f32_to_f16_fallback(v[2]), + f32_to_f16_fallback(v[3]), + ] +} + +#[inline] +fn f16x4_to_f64x4_fallback(v: &[u16]) -> [f64; 4] { + debug_assert!(v.len() >= 4); + + [ + f16_to_f64_fallback(v[0]), + f16_to_f64_fallback(v[1]), + f16_to_f64_fallback(v[2]), + f16_to_f64_fallback(v[3]), + ] +} + +#[inline] +fn f64x4_to_f16x4_fallback(v: &[f64]) -> [u16; 4] { + debug_assert!(v.len() >= 4); + + [ + f64_to_f16_fallback(v[0]), + f64_to_f16_fallback(v[1]), + f64_to_f16_fallback(v[2]), + f64_to_f16_fallback(v[3]), + ] +} + +/////////////// x86/x86_64 f16c //////////////// +#[cfg(all( + feature = "use-intrinsics", + any(target_arch = "x86", target_arch = "x86_64") +))] +mod x86 { + use core::{mem::MaybeUninit, ptr}; + + #[cfg(target_arch = "x86")] + use core::arch::x86::{__m128, __m128i, _mm_cvtph_ps, _mm_cvtps_ph, _MM_FROUND_TO_NEAREST_INT}; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::{ + __m128, __m128i, _mm_cvtph_ps, _mm_cvtps_ph, _MM_FROUND_TO_NEAREST_INT, + }; + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f16_to_f32_x86_f16c(i: u16) -> f32 { + let mut vec = MaybeUninit::<__m128i>::zeroed(); + vec.as_mut_ptr().cast::().write(i); + let retval = _mm_cvtph_ps(vec.assume_init()); + *(&retval as *const __m128).cast() + } + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f32_to_f16_x86_f16c(f: f32) -> u16 { + let mut vec = MaybeUninit::<__m128>::zeroed(); + vec.as_mut_ptr().cast::().write(f); + let retval = _mm_cvtps_ph(vec.assume_init(), _MM_FROUND_TO_NEAREST_INT); + *(&retval as *const __m128i).cast() + } + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f16x4_to_f32x4_x86_f16c(v: &[u16]) -> [f32; 4] { + debug_assert!(v.len() >= 4); + + let mut vec = MaybeUninit::<__m128i>::zeroed(); + ptr::copy_nonoverlapping(v.as_ptr(), vec.as_mut_ptr().cast(), 4); + let retval = _mm_cvtph_ps(vec.assume_init()); + *(&retval as *const __m128).cast() + } + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f32x4_to_f16x4_x86_f16c(v: &[f32]) -> [u16; 4] { + debug_assert!(v.len() >= 4); + + let mut vec = MaybeUninit::<__m128>::uninit(); + ptr::copy_nonoverlapping(v.as_ptr(), vec.as_mut_ptr().cast(), 4); + let retval = _mm_cvtps_ph(vec.assume_init(), _MM_FROUND_TO_NEAREST_INT); + *(&retval as *const __m128i).cast() + } + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f16x4_to_f64x4_x86_f16c(v: &[u16]) -> [f64; 4] { + debug_assert!(v.len() >= 4); + + let mut vec = MaybeUninit::<__m128i>::zeroed(); + ptr::copy_nonoverlapping(v.as_ptr(), vec.as_mut_ptr().cast(), 4); + let retval = _mm_cvtph_ps(vec.assume_init()); + let array = *(&retval as *const __m128).cast::<[f32; 4]>(); + // Let compiler vectorize this regular cast for now. + // TODO: investigate auto-detecting sse2/avx convert features + [ + array[0] as f64, + array[1] as f64, + array[2] as f64, + array[3] as f64, + ] + } + + #[target_feature(enable = "f16c")] + #[inline] + pub(super) unsafe fn f64x4_to_f16x4_x86_f16c(v: &[f64]) -> [u16; 4] { + debug_assert!(v.len() >= 4); + + // Let compiler vectorize this regular cast for now. + // TODO: investigate auto-detecting sse2/avx convert features + let v = [v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]; + + let mut vec = MaybeUninit::<__m128>::uninit(); + ptr::copy_nonoverlapping(v.as_ptr(), vec.as_mut_ptr().cast(), 4); + let retval = _mm_cvtps_ph(vec.assume_init(), _MM_FROUND_TO_NEAREST_INT); + *(&retval as *const __m128i).cast() + } +} diff --git a/third_party/rust/half/src/lib.rs b/third_party/rust/half/src/lib.rs new file mode 100644 index 000000000000..2c5568b035af --- /dev/null +++ b/third_party/rust/half/src/lib.rs @@ -0,0 +1,217 @@ +//! A crate that provides support for half-precision 16-bit floating point types. +//! +//! This crate provides the [`f16`] type, which is an implementation of the IEEE 754-2008 standard +//! [`binary16`] a.k.a `half` floating point type. This 16-bit floating point type is intended for +//! efficient storage where the full range and precision of a larger floating point value is not +//! required. This is especially useful for image storage formats. +//! +//! This crate also provides a [`bf16`] type, an alternative 16-bit floating point format. The +//! [`bfloat16`] format is a truncated IEEE 754 standard `binary32` float that preserves the +//! exponent to allow the same range as [`f32`] but with only 8 bits of precision (instead of 11 +//! bits for [`f16`]). See the [`bf16`] type for details. +//! +//! Because [`f16`] and [`bf16`] are primarily for efficient storage, floating point operations such +//! as addition, multiplication, etc. are not implemented by hardware. While this crate does provide +//! the appropriate trait implementations for basic operations, they each convert the value to +//! [`f32`] before performing the operation and then back afterward. When performing complex +//! arithmetic, manually convert to and from [`f32`] before and after to reduce repeated conversions +//! for each operation. +//! +//! This crate also provides a [`slice`][mod@slice] module for zero-copy in-place conversions of +//! [`u16`] slices to both [`f16`] and [`bf16`], as well as efficient vectorized conversions of +//! larger buffers of floating point values to and from these half formats. +//! +//! The crate uses `#[no_std]` by default, so can be used in embedded environments without using the +//! Rust [`std`] library. A `std` feature to enable support for the standard library is available, +//! see the [Cargo Features](#cargo-features) section below. +//! +//! A [`prelude`] module is provided for easy importing of available utility traits. +//! +//! # Cargo Features +//! +//! This crate supports a number of optional cargo features. None of these features are enabled by +//! default, even `std`. +//! +//! - **`use-intrinsics`** -- Use [`core::arch`] hardware intrinsics for `f16` and `bf16` conversions +//! if available on the compiler target. This feature currently only works on nightly Rust +//! until the corresponding intrinsics are stabilized. +//! +//! When this feature is enabled and the hardware supports it, the functions and traits in the +//! [`slice`][mod@slice] module will use vectorized SIMD intructions for increased efficiency. +//! +//! By default, without this feature, conversions are done only in software, which will also be +//! the fallback if the target does not have hardware support. Note that without the `std` +//! feature enabled, no runtime CPU feature detection is used, so the hardware support is only +//! compiled if the compiler target supports the CPU feature. +//! +//! - **`alloc`** -- Enable use of the [`alloc`] crate when not using the `std` library. +//! +//! Among other functions, this enables the [`vec`] module, which contains zero-copy +//! conversions for the [`Vec`] type. This allows fast conversion between raw `Vec` bits and +//! `Vec` or `Vec` arrays, and vice versa. +//! +//! - **`std`** -- Enable features that depend on the Rust [`std`] library. This also enables the +//! `alloc` feature automatically. +//! +//! Enabling the `std` feature also enables runtime CPU feature detection when the +//! `use-intrsincis` feature is also enabled. Without this feature detection, intrinsics are only +//! used when compiler target supports the target feature. +//! +//! - **`serde`** -- Adds support for the [`serde`] crate by implementing [`Serialize`] and +//! [`Deserialize`] traits for both [`f16`] and [`bf16`]. +//! +//! - **`num-traits`** -- Adds support for the [`num-traits`] crate by implementing [`ToPrimitive`], +//! [`FromPrimitive`], [`AsPrimitive`], [`Num`], [`Float`], [`FloatCore`], and [`Bounded`] traits +//! for both [`f16`] and [`bf16`]. +//! +//! - **`bytemuck`** -- Adds support for the [`bytemuck`] crate by implementing [`Zeroable`] and +//! [`Pod`] traits for both [`f16`] and [`bf16`]. +//! +//! - **`zerocopy`** -- Adds support for the [`zerocopy`] crate by implementing [`AsBytes`] and +//! [`FromBytes`] traits for both [`f16`] and [`bf16`]. +//! +//! [`alloc`]: https://doc.rust-lang.org/alloc/ +//! [`std`]: https://doc.rust-lang.org/std/ +//! [`binary16`]: https://en.wikipedia.org/wiki/Half-precision_floating-point_format +//! [`bfloat16`]: https://en.wikipedia.org/wiki/Bfloat16_floating-point_format +//! [`serde`]: https://crates.io/crates/serde +//! [`bytemuck`]: https://crates.io/crates/bytemuck +//! [`num-traits`]: https://crates.io/crates/num-traits +//! [`zerocopy`]: https://crates.io/crates/zerocopy +#![cfg_attr( + feature = "alloc", + doc = " +[`vec`]: mod@vec" +)] +#![cfg_attr( + not(feature = "alloc"), + doc = " +[`vec`]: # +[`Vec`]: https://docs.rust-lang.org/stable/alloc/vec/struct.Vec.html" +)] +#![cfg_attr( + feature = "serde", + doc = " +[`Serialize`]: serde::Serialize +[`Deserialize`]: serde::Deserialize" +)] +#![cfg_attr( + not(feature = "serde"), + doc = " +[`Serialize`]: https://docs.rs/serde/*/serde/trait.Serialize.html +[`Deserialize`]: https://docs.rs/serde/*/serde/trait.Deserialize.html" +)] +#![cfg_attr( + feature = "num-traits", + doc = " +[`ToPrimitive`]: ::num_traits::ToPrimitive +[`FromPrimitive`]: ::num_traits::FromPrimitive +[`AsPrimitive`]: ::num_traits::AsPrimitive +[`Num`]: ::num_traits::Num +[`Float`]: ::num_traits::Float +[`FloatCore`]: ::num_traits::float::FloatCore +[`Bounded`]: ::num_traits::Bounded" +)] +#![cfg_attr( + not(feature = "num-traits"), + doc = " +[`ToPrimitive`]: https://docs.rs/num-traits/*/num_traits/cast/trait.ToPrimitive.html +[`FromPrimitive`]: https://docs.rs/num-traits/*/num_traits/cast/trait.FromPrimitive.html +[`AsPrimitive`]: https://docs.rs/num-traits/*/num_traits/cast/trait.AsPrimitive.html +[`Num`]: https://docs.rs/num-traits/*/num_traits/trait.Num.html +[`Float`]: https://docs.rs/num-traits/*/num_traits/float/trait.Float.html +[`FloatCore`]: https://docs.rs/num-traits/*/num_traits/float/trait.FloatCore.html +[`Bounded`]: https://docs.rs/num-traits/*/num_traits/bounds/trait.Bounded.html" +)] +#![cfg_attr( + feature = "bytemuck", + doc = " +[`Zeroable`]: bytemuck::Zeroable +[`Pod`]: bytemuck::Pod" +)] +#![cfg_attr( + not(feature = "bytemuck"), + doc = " +[`Zeroable`]: https://docs.rs/bytemuck/*/bytemuck/trait.Zeroable.html +[`Pod`]: https://docs.rs/bytemuck/*bytemuck/trait.Pod.html" +)] +#![cfg_attr( + feature = "zerocopy", + doc = " +[`AsBytes`]: zerocopy::AsBytes +[`FromBytes`]: zerocopy::FromBytes" +)] +#![cfg_attr( + not(feature = "zerocopy"), + doc = " +[`AsBytes`]: https://docs.rs/zerocopy/*/zerocopy/trait.AsBytes.html +[`FromBytes`]: https://docs.rs/zerocopy/*/zerocopy/trait.FromBytes.html" +)] +#![warn( + missing_docs, + missing_copy_implementations, + missing_debug_implementations, + trivial_numeric_casts, + future_incompatible +)] +#![allow(clippy::verbose_bit_mask, clippy::cast_lossless)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + all( + feature = "use-intrinsics", + any(target_arch = "x86", target_arch = "x86_64") + ), + feature(stdsimd, f16c_target_feature) +)] +#![doc(html_root_url = "https://docs.rs/half/1.8.2")] +#![doc(test(attr(deny(warnings), allow(unused))))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod bfloat; +mod binary16; +#[cfg(feature = "num-traits")] +mod num_traits; + +pub mod slice; +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub mod vec; + +pub use bfloat::bf16; +#[doc(hidden)] +#[allow(deprecated)] +pub use binary16::consts; +pub use binary16::f16; + +/// A collection of the most used items and traits in this crate for easy importing. +/// +/// # Examples +/// +/// ```rust +/// use half::prelude::*; +/// ``` +pub mod prelude { + #[doc(no_inline)] + pub use crate::{ + bf16, f16, + slice::{HalfBitsSliceExt, HalfFloatSliceExt}, + }; + + #[cfg(feature = "alloc")] + #[doc(no_inline)] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + pub use crate::vec::{HalfBitsVecExt, HalfFloatVecExt}; +} + +// Keep this module private to crate +mod private { + use crate::{bf16, f16}; + + pub trait SealedHalf {} + + impl SealedHalf for f16 {} + impl SealedHalf for bf16 {} +} diff --git a/third_party/rust/half/src/num_traits.rs b/third_party/rust/half/src/num_traits.rs new file mode 100644 index 000000000000..e5ee490a8409 --- /dev/null +++ b/third_party/rust/half/src/num_traits.rs @@ -0,0 +1,1461 @@ +use crate::{bf16, f16}; +use core::cmp::Ordering; +use core::{num::FpCategory, ops::Div}; +use num_traits::{ + AsPrimitive, Bounded, FloatConst, FromPrimitive, Num, NumCast, One, ToPrimitive, Zero, +}; + +impl ToPrimitive for f16 { + #[inline] + fn to_i64(&self) -> Option { + Self::to_f32(*self).to_i64() + } + #[inline] + fn to_u64(&self) -> Option { + Self::to_f32(*self).to_u64() + } + #[inline] + fn to_i8(&self) -> Option { + Self::to_f32(*self).to_i8() + } + #[inline] + fn to_u8(&self) -> Option { + Self::to_f32(*self).to_u8() + } + #[inline] + fn to_i16(&self) -> Option { + Self::to_f32(*self).to_i16() + } + #[inline] + fn to_u16(&self) -> Option { + Self::to_f32(*self).to_u16() + } + #[inline] + fn to_i32(&self) -> Option { + Self::to_f32(*self).to_i32() + } + #[inline] + fn to_u32(&self) -> Option { + Self::to_f32(*self).to_u32() + } + #[inline] + fn to_f32(&self) -> Option { + Some(Self::to_f32(*self)) + } + #[inline] + fn to_f64(&self) -> Option { + Some(Self::to_f64(*self)) + } +} + +impl FromPrimitive for f16 { + #[inline] + fn from_i64(n: i64) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u64(n: u64) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i8(n: i8) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u8(n: u8) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i16(n: i16) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u16(n: u16) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i32(n: i32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u32(n: u32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_f32(n: f32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_f64(n: f64) -> Option { + n.to_f64().map(Self::from_f64) + } +} + +impl Num for f16 { + type FromStrRadixErr = ::FromStrRadixErr; + + #[inline] + fn from_str_radix(str: &str, radix: u32) -> Result { + Ok(Self::from_f32(f32::from_str_radix(str, radix)?)) + } +} + +impl One for f16 { + #[inline] + fn one() -> Self { + Self::ONE + } +} + +impl Zero for f16 { + #[inline] + fn zero() -> Self { + Self::ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +impl NumCast for f16 { + #[inline] + fn from(n: T) -> Option { + n.to_f32().map(Self::from_f32) + } +} + +impl num_traits::float::FloatCore for f16 { + #[inline] + fn infinity() -> Self { + Self::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + Self::NEG_INFINITY + } + + #[inline] + fn nan() -> Self { + Self::NAN + } + + #[inline] + fn neg_zero() -> Self { + Self::NEG_ZERO + } + + #[inline] + fn min_value() -> Self { + Self::MIN + } + + #[inline] + fn min_positive_value() -> Self { + Self::MIN_POSITIVE + } + + #[inline] + fn epsilon() -> Self { + Self::EPSILON + } + + #[inline] + fn max_value() -> Self { + Self::MAX + } + + #[inline] + fn is_nan(self) -> bool { + self.is_nan() + } + + #[inline] + fn is_infinite(self) -> bool { + self.is_infinite() + } + + #[inline] + fn is_finite(self) -> bool { + self.is_finite() + } + + #[inline] + fn is_normal(self) -> bool { + self.is_normal() + } + + #[inline] + fn classify(self) -> FpCategory { + self.classify() + } + + #[inline] + fn floor(self) -> Self { + Self::from_f32(self.to_f32().floor()) + } + + #[inline] + fn ceil(self) -> Self { + Self::from_f32(self.to_f32().ceil()) + } + + #[inline] + fn round(self) -> Self { + Self::from_f32(self.to_f32().round()) + } + + #[inline] + fn trunc(self) -> Self { + Self::from_f32(self.to_f32().trunc()) + } + + #[inline] + fn fract(self) -> Self { + Self::from_f32(self.to_f32().fract()) + } + + #[inline] + fn abs(self) -> Self { + Self::from_bits(self.to_bits() & 0x7FFF) + } + + #[inline] + fn signum(self) -> Self { + self.signum() + } + + #[inline] + fn is_sign_positive(self) -> bool { + self.is_sign_positive() + } + + #[inline] + fn is_sign_negative(self) -> bool { + self.is_sign_negative() + } + + fn min(self, other: Self) -> Self { + match self.partial_cmp(&other) { + None => { + if self.is_nan() { + other + } else { + self + } + } + Some(Ordering::Greater) | Some(Ordering::Equal) => other, + Some(Ordering::Less) => self, + } + } + + fn max(self, other: Self) -> Self { + match self.partial_cmp(&other) { + None => { + if self.is_nan() { + other + } else { + self + } + } + Some(Ordering::Greater) | Some(Ordering::Equal) => self, + Some(Ordering::Less) => other, + } + } + + #[inline] + fn recip(self) -> Self { + Self::from_f32(self.to_f32().recip()) + } + + #[inline] + fn powi(self, exp: i32) -> Self { + Self::from_f32(self.to_f32().powi(exp)) + } + + #[inline] + fn to_degrees(self) -> Self { + Self::from_f32(self.to_f32().to_degrees()) + } + + #[inline] + fn to_radians(self) -> Self { + Self::from_f32(self.to_f32().to_radians()) + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + num_traits::float::FloatCore::integer_decode(self.to_f32()) + } +} + +impl num_traits::float::Float for f16 { + #[inline] + fn nan() -> Self { + Self::NAN + } + + #[inline] + fn infinity() -> Self { + Self::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + Self::NEG_INFINITY + } + + #[inline] + fn neg_zero() -> Self { + Self::NEG_ZERO + } + + #[inline] + fn min_value() -> Self { + Self::MIN + } + + #[inline] + fn min_positive_value() -> Self { + Self::MIN_POSITIVE + } + + #[inline] + fn epsilon() -> Self { + Self::EPSILON + } + + #[inline] + fn max_value() -> Self { + Self::MAX + } + + #[inline] + fn is_nan(self) -> bool { + self.is_nan() + } + + #[inline] + fn is_infinite(self) -> bool { + self.is_infinite() + } + + #[inline] + fn is_finite(self) -> bool { + self.is_finite() + } + + #[inline] + fn is_normal(self) -> bool { + self.is_normal() + } + + #[inline] + fn classify(self) -> FpCategory { + self.classify() + } + + #[inline] + fn floor(self) -> Self { + Self::from_f32(self.to_f32().floor()) + } + + #[inline] + fn ceil(self) -> Self { + Self::from_f32(self.to_f32().ceil()) + } + + #[inline] + fn round(self) -> Self { + Self::from_f32(self.to_f32().round()) + } + + #[inline] + fn trunc(self) -> Self { + Self::from_f32(self.to_f32().trunc()) + } + + #[inline] + fn fract(self) -> Self { + Self::from_f32(self.to_f32().fract()) + } + + #[inline] + fn abs(self) -> Self { + Self::from_f32(self.to_f32().abs()) + } + + #[inline] + fn signum(self) -> Self { + Self::from_f32(self.to_f32().signum()) + } + + #[inline] + fn is_sign_positive(self) -> bool { + self.is_sign_positive() + } + + #[inline] + fn is_sign_negative(self) -> bool { + self.is_sign_negative() + } + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self { + Self::from_f32(self.to_f32().mul_add(a.to_f32(), b.to_f32())) + } + + #[inline] + fn recip(self) -> Self { + Self::from_f32(self.to_f32().recip()) + } + + #[inline] + fn powi(self, n: i32) -> Self { + Self::from_f32(self.to_f32().powi(n)) + } + + #[inline] + fn powf(self, n: Self) -> Self { + Self::from_f32(self.to_f32().powf(n.to_f32())) + } + + #[inline] + fn sqrt(self) -> Self { + Self::from_f32(self.to_f32().sqrt()) + } + + #[inline] + fn exp(self) -> Self { + Self::from_f32(self.to_f32().exp()) + } + + #[inline] + fn exp2(self) -> Self { + Self::from_f32(self.to_f32().exp2()) + } + + #[inline] + fn ln(self) -> Self { + Self::from_f32(self.to_f32().ln()) + } + + #[inline] + fn log(self, base: Self) -> Self { + Self::from_f32(self.to_f32().log(base.to_f32())) + } + + #[inline] + fn log2(self) -> Self { + Self::from_f32(self.to_f32().log2()) + } + + #[inline] + fn log10(self) -> Self { + Self::from_f32(self.to_f32().log10()) + } + + #[inline] + fn to_degrees(self) -> Self { + Self::from_f32(self.to_f32().to_degrees()) + } + + #[inline] + fn to_radians(self) -> Self { + Self::from_f32(self.to_f32().to_radians()) + } + + #[inline] + fn max(self, other: Self) -> Self { + self.max(other) + } + + #[inline] + fn min(self, other: Self) -> Self { + self.min(other) + } + + #[inline] + fn abs_sub(self, other: Self) -> Self { + Self::from_f32((self.to_f32() - other.to_f32()).max(0.0)) + } + + #[inline] + fn cbrt(self) -> Self { + Self::from_f32(self.to_f32().cbrt()) + } + + #[inline] + fn hypot(self, other: Self) -> Self { + Self::from_f32(self.to_f32().hypot(other.to_f32())) + } + + #[inline] + fn sin(self) -> Self { + Self::from_f32(self.to_f32().sin()) + } + + #[inline] + fn cos(self) -> Self { + Self::from_f32(self.to_f32().cos()) + } + + #[inline] + fn tan(self) -> Self { + Self::from_f32(self.to_f32().tan()) + } + + #[inline] + fn asin(self) -> Self { + Self::from_f32(self.to_f32().asin()) + } + + #[inline] + fn acos(self) -> Self { + Self::from_f32(self.to_f32().acos()) + } + + #[inline] + fn atan(self) -> Self { + Self::from_f32(self.to_f32().atan()) + } + + #[inline] + fn atan2(self, other: Self) -> Self { + Self::from_f32(self.to_f32().atan2(other.to_f32())) + } + + #[inline] + fn sin_cos(self) -> (Self, Self) { + let (sin, cos) = self.to_f32().sin_cos(); + (Self::from_f32(sin), Self::from_f32(cos)) + } + + #[inline] + fn exp_m1(self) -> Self { + Self::from_f32(self.to_f32().exp_m1()) + } + + #[inline] + fn ln_1p(self) -> Self { + Self::from_f32(self.to_f32().ln_1p()) + } + + #[inline] + fn sinh(self) -> Self { + Self::from_f32(self.to_f32().sinh()) + } + + #[inline] + fn cosh(self) -> Self { + Self::from_f32(self.to_f32().cosh()) + } + + #[inline] + fn tanh(self) -> Self { + Self::from_f32(self.to_f32().tanh()) + } + + #[inline] + fn asinh(self) -> Self { + Self::from_f32(self.to_f32().asinh()) + } + + #[inline] + fn acosh(self) -> Self { + Self::from_f32(self.to_f32().acosh()) + } + + #[inline] + fn atanh(self) -> Self { + Self::from_f32(self.to_f32().atanh()) + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + num_traits::float::Float::integer_decode(self.to_f32()) + } +} + +impl FloatConst for f16 { + #[inline] + fn E() -> Self { + Self::E + } + + #[inline] + fn FRAC_1_PI() -> Self { + Self::FRAC_1_PI + } + + #[inline] + fn FRAC_1_SQRT_2() -> Self { + Self::FRAC_1_SQRT_2 + } + + #[inline] + fn FRAC_2_PI() -> Self { + Self::FRAC_2_PI + } + + #[inline] + fn FRAC_2_SQRT_PI() -> Self { + Self::FRAC_2_SQRT_PI + } + + #[inline] + fn FRAC_PI_2() -> Self { + Self::FRAC_PI_2 + } + + #[inline] + fn FRAC_PI_3() -> Self { + Self::FRAC_PI_3 + } + + #[inline] + fn FRAC_PI_4() -> Self { + Self::FRAC_PI_4 + } + + #[inline] + fn FRAC_PI_6() -> Self { + Self::FRAC_PI_6 + } + + #[inline] + fn FRAC_PI_8() -> Self { + Self::FRAC_PI_8 + } + + #[inline] + fn LN_10() -> Self { + Self::LN_10 + } + + #[inline] + fn LN_2() -> Self { + Self::LN_2 + } + + #[inline] + fn LOG10_E() -> Self { + Self::LOG10_E + } + + #[inline] + fn LOG2_E() -> Self { + Self::LOG2_E + } + + #[inline] + fn PI() -> Self { + Self::PI + } + + fn SQRT_2() -> Self { + Self::SQRT_2 + } + + #[inline] + fn LOG10_2() -> Self + where + Self: Sized + Div, + { + Self::LOG10_2 + } + + #[inline] + fn LOG2_10() -> Self + where + Self: Sized + Div, + { + Self::LOG2_10 + } +} + +impl Bounded for f16 { + #[inline] + fn min_value() -> Self { + f16::MIN + } + + #[inline] + fn max_value() -> Self { + f16::MAX + } +} + +macro_rules! impl_as_primitive_to_f16 { + ($ty:ty, $meth:ident) => { + impl AsPrimitive<$ty> for f16 { + #[inline] + fn as_(self) -> $ty { + self.$meth().as_() + } + } + }; +} + +impl_as_primitive_to_f16!(i64, to_f32); +impl_as_primitive_to_f16!(u64, to_f32); +impl_as_primitive_to_f16!(i8, to_f32); +impl_as_primitive_to_f16!(u8, to_f32); +impl_as_primitive_to_f16!(i16, to_f32); +impl_as_primitive_to_f16!(u16, to_f32); +impl_as_primitive_to_f16!(i32, to_f32); +impl_as_primitive_to_f16!(u32, to_f32); +impl_as_primitive_to_f16!(f32, to_f32); +impl_as_primitive_to_f16!(f64, to_f64); + +macro_rules! impl_as_primitive_f16_from { + ($ty:ty, $meth:ident) => { + impl AsPrimitive for $ty { + #[inline] + fn as_(self) -> f16 { + f16::$meth(self.as_()) + } + } + }; +} + +impl_as_primitive_f16_from!(i64, from_f32); +impl_as_primitive_f16_from!(u64, from_f32); +impl_as_primitive_f16_from!(i8, from_f32); +impl_as_primitive_f16_from!(u8, from_f32); +impl_as_primitive_f16_from!(i16, from_f32); +impl_as_primitive_f16_from!(u16, from_f32); +impl_as_primitive_f16_from!(i32, from_f32); +impl_as_primitive_f16_from!(u32, from_f32); +impl_as_primitive_f16_from!(f32, from_f32); +impl_as_primitive_f16_from!(f64, from_f64); + +impl ToPrimitive for bf16 { + #[inline] + fn to_i64(&self) -> Option { + Self::to_f32(*self).to_i64() + } + #[inline] + fn to_u64(&self) -> Option { + Self::to_f32(*self).to_u64() + } + #[inline] + fn to_i8(&self) -> Option { + Self::to_f32(*self).to_i8() + } + #[inline] + fn to_u8(&self) -> Option { + Self::to_f32(*self).to_u8() + } + #[inline] + fn to_i16(&self) -> Option { + Self::to_f32(*self).to_i16() + } + #[inline] + fn to_u16(&self) -> Option { + Self::to_f32(*self).to_u16() + } + #[inline] + fn to_i32(&self) -> Option { + Self::to_f32(*self).to_i32() + } + #[inline] + fn to_u32(&self) -> Option { + Self::to_f32(*self).to_u32() + } + #[inline] + fn to_f32(&self) -> Option { + Some(Self::to_f32(*self)) + } + #[inline] + fn to_f64(&self) -> Option { + Some(Self::to_f64(*self)) + } +} + +impl FromPrimitive for bf16 { + #[inline] + fn from_i64(n: i64) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u64(n: u64) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i8(n: i8) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u8(n: u8) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i16(n: i16) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u16(n: u16) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_i32(n: i32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_u32(n: u32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_f32(n: f32) -> Option { + n.to_f32().map(Self::from_f32) + } + #[inline] + fn from_f64(n: f64) -> Option { + n.to_f64().map(Self::from_f64) + } +} + +impl Num for bf16 { + type FromStrRadixErr = ::FromStrRadixErr; + + #[inline] + fn from_str_radix(str: &str, radix: u32) -> Result { + Ok(Self::from_f32(f32::from_str_radix(str, radix)?)) + } +} + +impl One for bf16 { + #[inline] + fn one() -> Self { + Self::ONE + } +} + +impl Zero for bf16 { + #[inline] + fn zero() -> Self { + Self::ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +impl NumCast for bf16 { + #[inline] + fn from(n: T) -> Option { + n.to_f32().map(Self::from_f32) + } +} + +impl num_traits::float::FloatCore for bf16 { + #[inline] + fn infinity() -> Self { + Self::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + Self::NEG_INFINITY + } + + #[inline] + fn nan() -> Self { + Self::NAN + } + + #[inline] + fn neg_zero() -> Self { + Self::NEG_ZERO + } + + #[inline] + fn min_value() -> Self { + Self::MIN + } + + #[inline] + fn min_positive_value() -> Self { + Self::MIN_POSITIVE + } + + #[inline] + fn epsilon() -> Self { + Self::EPSILON + } + + #[inline] + fn max_value() -> Self { + Self::MAX + } + + #[inline] + fn is_nan(self) -> bool { + self.is_nan() + } + + #[inline] + fn is_infinite(self) -> bool { + self.is_infinite() + } + + #[inline] + fn is_finite(self) -> bool { + self.is_finite() + } + + #[inline] + fn is_normal(self) -> bool { + self.is_normal() + } + + #[inline] + fn classify(self) -> FpCategory { + self.classify() + } + + #[inline] + fn floor(self) -> Self { + Self::from_f32(self.to_f32().floor()) + } + + #[inline] + fn ceil(self) -> Self { + Self::from_f32(self.to_f32().ceil()) + } + + #[inline] + fn round(self) -> Self { + Self::from_f32(self.to_f32().round()) + } + + #[inline] + fn trunc(self) -> Self { + Self::from_f32(self.to_f32().trunc()) + } + + #[inline] + fn fract(self) -> Self { + Self::from_f32(self.to_f32().fract()) + } + + #[inline] + fn abs(self) -> Self { + Self::from_bits(self.to_bits() & 0x7FFF) + } + + #[inline] + fn signum(self) -> Self { + self.signum() + } + + #[inline] + fn is_sign_positive(self) -> bool { + self.is_sign_positive() + } + + #[inline] + fn is_sign_negative(self) -> bool { + self.is_sign_negative() + } + + fn min(self, other: Self) -> Self { + match self.partial_cmp(&other) { + None => { + if self.is_nan() { + other + } else { + self + } + } + Some(Ordering::Greater) | Some(Ordering::Equal) => other, + Some(Ordering::Less) => self, + } + } + + fn max(self, other: Self) -> Self { + match self.partial_cmp(&other) { + None => { + if self.is_nan() { + other + } else { + self + } + } + Some(Ordering::Greater) | Some(Ordering::Equal) => self, + Some(Ordering::Less) => other, + } + } + + #[inline] + fn recip(self) -> Self { + Self::from_f32(self.to_f32().recip()) + } + + #[inline] + fn powi(self, exp: i32) -> Self { + Self::from_f32(self.to_f32().powi(exp)) + } + + #[inline] + fn to_degrees(self) -> Self { + Self::from_f32(self.to_f32().to_degrees()) + } + + #[inline] + fn to_radians(self) -> Self { + Self::from_f32(self.to_f32().to_radians()) + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + num_traits::float::FloatCore::integer_decode(self.to_f32()) + } +} + +impl num_traits::float::Float for bf16 { + #[inline] + fn nan() -> Self { + Self::NAN + } + + #[inline] + fn infinity() -> Self { + Self::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + Self::NEG_INFINITY + } + + #[inline] + fn neg_zero() -> Self { + Self::NEG_ZERO + } + + #[inline] + fn min_value() -> Self { + Self::MIN + } + + #[inline] + fn min_positive_value() -> Self { + Self::MIN_POSITIVE + } + + #[inline] + fn epsilon() -> Self { + Self::EPSILON + } + + #[inline] + fn max_value() -> Self { + Self::MAX + } + + #[inline] + fn is_nan(self) -> bool { + self.is_nan() + } + + #[inline] + fn is_infinite(self) -> bool { + self.is_infinite() + } + + #[inline] + fn is_finite(self) -> bool { + self.is_finite() + } + + #[inline] + fn is_normal(self) -> bool { + self.is_normal() + } + + #[inline] + fn classify(self) -> FpCategory { + self.classify() + } + + #[inline] + fn floor(self) -> Self { + Self::from_f32(self.to_f32().floor()) + } + + #[inline] + fn ceil(self) -> Self { + Self::from_f32(self.to_f32().ceil()) + } + + #[inline] + fn round(self) -> Self { + Self::from_f32(self.to_f32().round()) + } + + #[inline] + fn trunc(self) -> Self { + Self::from_f32(self.to_f32().trunc()) + } + + #[inline] + fn fract(self) -> Self { + Self::from_f32(self.to_f32().fract()) + } + + #[inline] + fn abs(self) -> Self { + Self::from_f32(self.to_f32().abs()) + } + + #[inline] + fn signum(self) -> Self { + Self::from_f32(self.to_f32().signum()) + } + + #[inline] + fn is_sign_positive(self) -> bool { + self.is_sign_positive() + } + + #[inline] + fn is_sign_negative(self) -> bool { + self.is_sign_negative() + } + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self { + Self::from_f32(self.to_f32().mul_add(a.to_f32(), b.to_f32())) + } + + #[inline] + fn recip(self) -> Self { + Self::from_f32(self.to_f32().recip()) + } + + #[inline] + fn powi(self, n: i32) -> Self { + Self::from_f32(self.to_f32().powi(n)) + } + + #[inline] + fn powf(self, n: Self) -> Self { + Self::from_f32(self.to_f32().powf(n.to_f32())) + } + + #[inline] + fn sqrt(self) -> Self { + Self::from_f32(self.to_f32().sqrt()) + } + + #[inline] + fn exp(self) -> Self { + Self::from_f32(self.to_f32().exp()) + } + + #[inline] + fn exp2(self) -> Self { + Self::from_f32(self.to_f32().exp2()) + } + + #[inline] + fn ln(self) -> Self { + Self::from_f32(self.to_f32().ln()) + } + + #[inline] + fn log(self, base: Self) -> Self { + Self::from_f32(self.to_f32().log(base.to_f32())) + } + + #[inline] + fn log2(self) -> Self { + Self::from_f32(self.to_f32().log2()) + } + + #[inline] + fn log10(self) -> Self { + Self::from_f32(self.to_f32().log10()) + } + + #[inline] + fn to_degrees(self) -> Self { + Self::from_f32(self.to_f32().to_degrees()) + } + + #[inline] + fn to_radians(self) -> Self { + Self::from_f32(self.to_f32().to_radians()) + } + + #[inline] + fn max(self, other: Self) -> Self { + self.max(other) + } + + #[inline] + fn min(self, other: Self) -> Self { + self.min(other) + } + + #[inline] + fn abs_sub(self, other: Self) -> Self { + Self::from_f32((self.to_f32() - other.to_f32()).max(0.0)) + } + + #[inline] + fn cbrt(self) -> Self { + Self::from_f32(self.to_f32().cbrt()) + } + + #[inline] + fn hypot(self, other: Self) -> Self { + Self::from_f32(self.to_f32().hypot(other.to_f32())) + } + + #[inline] + fn sin(self) -> Self { + Self::from_f32(self.to_f32().sin()) + } + + #[inline] + fn cos(self) -> Self { + Self::from_f32(self.to_f32().cos()) + } + + #[inline] + fn tan(self) -> Self { + Self::from_f32(self.to_f32().tan()) + } + + #[inline] + fn asin(self) -> Self { + Self::from_f32(self.to_f32().asin()) + } + + #[inline] + fn acos(self) -> Self { + Self::from_f32(self.to_f32().acos()) + } + + #[inline] + fn atan(self) -> Self { + Self::from_f32(self.to_f32().atan()) + } + + #[inline] + fn atan2(self, other: Self) -> Self { + Self::from_f32(self.to_f32().atan2(other.to_f32())) + } + + #[inline] + fn sin_cos(self) -> (Self, Self) { + let (sin, cos) = self.to_f32().sin_cos(); + (Self::from_f32(sin), Self::from_f32(cos)) + } + + #[inline] + fn exp_m1(self) -> Self { + Self::from_f32(self.to_f32().exp_m1()) + } + + #[inline] + fn ln_1p(self) -> Self { + Self::from_f32(self.to_f32().ln_1p()) + } + + #[inline] + fn sinh(self) -> Self { + Self::from_f32(self.to_f32().sinh()) + } + + #[inline] + fn cosh(self) -> Self { + Self::from_f32(self.to_f32().cosh()) + } + + #[inline] + fn tanh(self) -> Self { + Self::from_f32(self.to_f32().tanh()) + } + + #[inline] + fn asinh(self) -> Self { + Self::from_f32(self.to_f32().asinh()) + } + + #[inline] + fn acosh(self) -> Self { + Self::from_f32(self.to_f32().acosh()) + } + + #[inline] + fn atanh(self) -> Self { + Self::from_f32(self.to_f32().atanh()) + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + num_traits::float::Float::integer_decode(self.to_f32()) + } +} + +impl FloatConst for bf16 { + #[inline] + fn E() -> Self { + Self::E + } + + #[inline] + fn FRAC_1_PI() -> Self { + Self::FRAC_1_PI + } + + #[inline] + fn FRAC_1_SQRT_2() -> Self { + Self::FRAC_1_SQRT_2 + } + + #[inline] + fn FRAC_2_PI() -> Self { + Self::FRAC_2_PI + } + + #[inline] + fn FRAC_2_SQRT_PI() -> Self { + Self::FRAC_2_SQRT_PI + } + + #[inline] + fn FRAC_PI_2() -> Self { + Self::FRAC_PI_2 + } + + #[inline] + fn FRAC_PI_3() -> Self { + Self::FRAC_PI_3 + } + + #[inline] + fn FRAC_PI_4() -> Self { + Self::FRAC_PI_4 + } + + #[inline] + fn FRAC_PI_6() -> Self { + Self::FRAC_PI_6 + } + + #[inline] + fn FRAC_PI_8() -> Self { + Self::FRAC_PI_8 + } + + #[inline] + fn LN_10() -> Self { + Self::LN_10 + } + + #[inline] + fn LN_2() -> Self { + Self::LN_2 + } + + #[inline] + fn LOG10_E() -> Self { + Self::LOG10_E + } + + #[inline] + fn LOG2_E() -> Self { + Self::LOG2_E + } + + #[inline] + fn PI() -> Self { + Self::PI + } + + #[inline] + fn SQRT_2() -> Self { + Self::SQRT_2 + } + + #[inline] + fn LOG10_2() -> Self + where + Self: Sized + Div, + { + Self::LOG10_2 + } + + #[inline] + fn LOG2_10() -> Self + where + Self: Sized + Div, + { + Self::LOG2_10 + } +} + +impl Bounded for bf16 { + #[inline] + fn min_value() -> Self { + bf16::MIN + } + + #[inline] + fn max_value() -> Self { + bf16::MAX + } +} + +macro_rules! impl_as_primitive_to_bf16 { + ($ty:ty, $meth:ident) => { + impl AsPrimitive<$ty> for bf16 { + #[inline] + fn as_(self) -> $ty { + self.$meth().as_() + } + } + }; +} + +impl_as_primitive_to_bf16!(i64, to_f32); +impl_as_primitive_to_bf16!(u64, to_f32); +impl_as_primitive_to_bf16!(i8, to_f32); +impl_as_primitive_to_bf16!(u8, to_f32); +impl_as_primitive_to_bf16!(i16, to_f32); +impl_as_primitive_to_bf16!(u16, to_f32); +impl_as_primitive_to_bf16!(i32, to_f32); +impl_as_primitive_to_bf16!(u32, to_f32); +impl_as_primitive_to_bf16!(f32, to_f32); +impl_as_primitive_to_bf16!(f64, to_f64); + +macro_rules! impl_as_primitive_bf16_from { + ($ty:ty, $meth:ident) => { + impl AsPrimitive for $ty { + #[inline] + fn as_(self) -> bf16 { + bf16::$meth(self.as_()) + } + } + }; +} + +impl_as_primitive_bf16_from!(i64, from_f32); +impl_as_primitive_bf16_from!(u64, from_f32); +impl_as_primitive_bf16_from!(i8, from_f32); +impl_as_primitive_bf16_from!(u8, from_f32); +impl_as_primitive_bf16_from!(i16, from_f32); +impl_as_primitive_bf16_from!(u16, from_f32); +impl_as_primitive_bf16_from!(i32, from_f32); +impl_as_primitive_bf16_from!(u32, from_f32); +impl_as_primitive_bf16_from!(f32, from_f32); +impl_as_primitive_bf16_from!(f64, from_f64); diff --git a/third_party/rust/half/src/slice.rs b/third_party/rust/half/src/slice.rs new file mode 100644 index 000000000000..3157e6a355ff --- /dev/null +++ b/third_party/rust/half/src/slice.rs @@ -0,0 +1,952 @@ +//! Contains utility functions and traits to convert between slices of [`u16`] bits and [`f16`] or +//! [`bf16`] numbers. +//! +//! The utility [`HalfBitsSliceExt`] sealed extension trait is implemented for `[u16]` slices, +//! while the utility [`HalfFloatSliceExt`] sealed extension trait is implemented for both `[f16]` +//! and `[bf16]` slices. These traits provide efficient conversions and reinterpret casting of +//! larger buffers of floating point values, and are automatically included in the +//! [`prelude`][crate::prelude] module. + +use crate::{bf16, binary16::convert, f16}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use core::slice; + +/// Extensions to `[f16]` and `[bf16]` slices to support conversion and reinterpret operations. +/// +/// This trait is sealed and cannot be implemented outside of this crate. +pub trait HalfFloatSliceExt: private::SealedHalfFloatSlice { + /// Reinterprets a slice of [`f16`] or [`bf16`] numbers as a slice of [`u16`] bits. + /// + /// This is a zero-copy operation. The reinterpreted slice has the same lifetime and memory + /// location as `self`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let float_buffer = [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]; + /// let int_buffer = float_buffer.reinterpret_cast(); + /// + /// assert_eq!(int_buffer, [float_buffer[0].to_bits(), float_buffer[1].to_bits(), float_buffer[2].to_bits()]); + /// ``` + fn reinterpret_cast(&self) -> &[u16]; + + /// Reinterprets a mutable slice of [`f16`] or [`bf16`] numbers as a mutable slice of [`u16`]. + /// bits + /// + /// This is a zero-copy operation. The transmuted slice has the same lifetime as the original, + /// which prevents mutating `self` as long as the returned `&mut [u16]` is borrowed. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let mut float_buffer = [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]; + /// + /// { + /// let int_buffer = float_buffer.reinterpret_cast_mut(); + /// + /// assert_eq!(int_buffer, [f16::from_f32(1.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]); + /// + /// // Mutating the u16 slice will mutating the original + /// int_buffer[0] = 0; + /// } + /// + /// // Note that we need to drop int_buffer before using float_buffer again or we will get a borrow error. + /// assert_eq!(float_buffer, [f16::from_f32(0.), f16::from_f32(2.), f16::from_f32(3.)]); + /// ``` + fn reinterpret_cast_mut(&mut self) -> &mut [u16]; + + /// Converts all of the elements of a `[f32]` slice into [`f16`] or [`bf16`] values in `self`. + /// + /// The length of `src` must be the same as `self`. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// // Initialize an empty buffer + /// let mut buffer = [0u16; 4]; + /// let buffer = buffer.reinterpret_cast_mut::(); + /// + /// let float_values = [1., 2., 3., 4.]; + /// + /// // Now convert + /// buffer.convert_from_f32_slice(&float_values); + /// + /// assert_eq!(buffer, [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.), f16::from_f32(4.)]); + /// ``` + fn convert_from_f32_slice(&mut self, src: &[f32]); + + /// Converts all of the elements of a `[f64]` slice into [`f16`] or [`bf16`] values in `self`. + /// + /// The length of `src` must be the same as `self`. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// // Initialize an empty buffer + /// let mut buffer = [0u16; 4]; + /// let buffer = buffer.reinterpret_cast_mut::(); + /// + /// let float_values = [1., 2., 3., 4.]; + /// + /// // Now convert + /// buffer.convert_from_f64_slice(&float_values); + /// + /// assert_eq!(buffer, [f16::from_f64(1.), f16::from_f64(2.), f16::from_f64(3.), f16::from_f64(4.)]); + /// ``` + fn convert_from_f64_slice(&mut self, src: &[f64]); + + /// Converts all of the [`f16`] or [`bf16`] elements of `self` into [`f32`] values in `dst`. + /// + /// The length of `src` must be the same as `self`. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// // Initialize an empty buffer + /// let mut buffer = [0f32; 4]; + /// + /// let half_values = [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.), f16::from_f32(4.)]; + /// + /// // Now convert + /// half_values.convert_to_f32_slice(&mut buffer); + /// + /// assert_eq!(buffer, [1., 2., 3., 4.]); + /// ``` + fn convert_to_f32_slice(&self, dst: &mut [f32]); + + /// Converts all of the [`f16`] or [`bf16`] elements of `self` into [`f64`] values in `dst`. + /// + /// The length of `src` must be the same as `self`. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// // Initialize an empty buffer + /// let mut buffer = [0f64; 4]; + /// + /// let half_values = [f16::from_f64(1.), f16::from_f64(2.), f16::from_f64(3.), f16::from_f64(4.)]; + /// + /// // Now convert + /// half_values.convert_to_f64_slice(&mut buffer); + /// + /// assert_eq!(buffer, [1., 2., 3., 4.]); + /// ``` + fn convert_to_f64_slice(&self, dst: &mut [f64]); + + // Because trait is sealed, we can get away with different interfaces between features. + + /// Converts all of the [`f16`] or [`bf16`] elements of `self` into [`f32`] values in a new + /// vector + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// This method is only available with the `std` or `alloc` feature. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// let half_values = [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.), f16::from_f32(4.)]; + /// let vec = half_values.to_f32_vec(); + /// + /// assert_eq!(vec, vec![1., 2., 3., 4.]); + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + fn to_f32_vec(&self) -> Vec; + + /// Converts all of the [`f16`] or [`bf16`] elements of `self` into [`f64`] values in a new + /// vector. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation](crate) for more information on hardware conversion + /// support. + /// + /// This method is only available with the `std` or `alloc` feature. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// let half_values = [f16::from_f64(1.), f16::from_f64(2.), f16::from_f64(3.), f16::from_f64(4.)]; + /// let vec = half_values.to_f64_vec(); + /// + /// assert_eq!(vec, vec![1., 2., 3., 4.]); + /// ``` + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + fn to_f64_vec(&self) -> Vec; +} + +/// Extensions to `[u16]` slices to support reinterpret operations. +/// +/// This trait is sealed and cannot be implemented outside of this crate. +pub trait HalfBitsSliceExt: private::SealedHalfBitsSlice { + /// Reinterprets a slice of [`u16`] bits as a slice of [`f16`] or [`bf16`] numbers. + /// + /// `H` is the type to cast to, and must be either the [`f16`] or [`bf16`] type. + /// + /// This is a zero-copy operation. The reinterpreted slice has the same lifetime and memory + /// location as `self`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let int_buffer = [f16::from_f32(1.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]; + /// let float_buffer: &[f16] = int_buffer.reinterpret_cast(); + /// + /// assert_eq!(float_buffer, [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]); + /// + /// // You may have to specify the cast type directly if the compiler can't infer the type. + /// // The following is also valid in Rust. + /// let typed_buffer = int_buffer.reinterpret_cast::(); + /// ``` + fn reinterpret_cast(&self) -> &[H] + where + H: crate::private::SealedHalf; + + /// Reinterprets a mutable slice of [`u16`] bits as a mutable slice of [`f16`] or [`bf16`] + /// numbers. + /// + /// `H` is the type to cast to, and must be either the [`f16`] or [`bf16`] type. + /// + /// This is a zero-copy operation. The transmuted slice has the same lifetime as the original, + /// which prevents mutating `self` as long as the returned `&mut [f16]` is borrowed. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let mut int_buffer = [f16::from_f32(1.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]; + /// + /// { + /// let float_buffer: &mut [f16] = int_buffer.reinterpret_cast_mut(); + /// + /// assert_eq!(float_buffer, [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]); + /// + /// // Mutating the f16 slice will mutating the original + /// float_buffer[0] = f16::from_f32(0.); + /// } + /// + /// // Note that we need to drop float_buffer before using int_buffer again or we will get a borrow error. + /// assert_eq!(int_buffer, [f16::from_f32(0.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]); + /// + /// // You may have to specify the cast type directly if the compiler can't infer the type. + /// // The following is also valid in Rust. + /// let typed_buffer = int_buffer.reinterpret_cast_mut::(); + /// ``` + fn reinterpret_cast_mut(&mut self) -> &mut [H] + where + H: crate::private::SealedHalf; +} + +mod private { + use crate::{bf16, f16}; + + pub trait SealedHalfFloatSlice {} + impl SealedHalfFloatSlice for [f16] {} + impl SealedHalfFloatSlice for [bf16] {} + + pub trait SealedHalfBitsSlice {} + impl SealedHalfBitsSlice for [u16] {} +} + +impl HalfFloatSliceExt for [f16] { + #[inline] + fn reinterpret_cast(&self) -> &[u16] { + let pointer = self.as_ptr() as *const u16; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts(pointer, length) } + } + + #[inline] + fn reinterpret_cast_mut(&mut self) -> &mut [u16] { + let pointer = self.as_ptr() as *mut u16; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts_mut(pointer, length) } + } + + fn convert_from_f32_slice(&mut self, src: &[f32]) { + assert_eq!( + self.len(), + src.len(), + "destination and source slices have different lengths" + ); + + let mut chunks = src.chunks_exact(4); + let mut chunk_count = 0usize; // Not using .enumerate() because we need this value for remainder + for chunk in &mut chunks { + let vec = convert::f32x4_to_f16x4(chunk); + let dst_idx = chunk_count * 4; + self[dst_idx..dst_idx + 4].copy_from_slice(vec.reinterpret_cast()); + chunk_count += 1; + } + + // Process remainder + if !chunks.remainder().is_empty() { + let mut buf = [0f32; 4]; + buf[..chunks.remainder().len()].copy_from_slice(chunks.remainder()); + let vec = convert::f32x4_to_f16x4(&buf); + let dst_idx = chunk_count * 4; + self[dst_idx..dst_idx + chunks.remainder().len()] + .copy_from_slice(vec[..chunks.remainder().len()].reinterpret_cast()); + } + } + + fn convert_from_f64_slice(&mut self, src: &[f64]) { + assert_eq!( + self.len(), + src.len(), + "destination and source slices have different lengths" + ); + + let mut chunks = src.chunks_exact(4); + let mut chunk_count = 0usize; // Not using .enumerate() because we need this value for remainder + for chunk in &mut chunks { + let vec = convert::f64x4_to_f16x4(chunk); + let dst_idx = chunk_count * 4; + self[dst_idx..dst_idx + 4].copy_from_slice(vec.reinterpret_cast()); + chunk_count += 1; + } + + // Process remainder + if !chunks.remainder().is_empty() { + let mut buf = [0f64; 4]; + buf[..chunks.remainder().len()].copy_from_slice(chunks.remainder()); + let vec = convert::f64x4_to_f16x4(&buf); + let dst_idx = chunk_count * 4; + self[dst_idx..dst_idx + chunks.remainder().len()] + .copy_from_slice(vec[..chunks.remainder().len()].reinterpret_cast()); + } + } + + fn convert_to_f32_slice(&self, dst: &mut [f32]) { + assert_eq!( + self.len(), + dst.len(), + "destination and source slices have different lengths" + ); + + let mut chunks = self.chunks_exact(4); + let mut chunk_count = 0usize; // Not using .enumerate() because we need this value for remainder + for chunk in &mut chunks { + let vec = convert::f16x4_to_f32x4(chunk.reinterpret_cast()); + let dst_idx = chunk_count * 4; + dst[dst_idx..dst_idx + 4].copy_from_slice(&vec); + chunk_count += 1; + } + + // Process remainder + if !chunks.remainder().is_empty() { + let mut buf = [0u16; 4]; + buf[..chunks.remainder().len()].copy_from_slice(chunks.remainder().reinterpret_cast()); + let vec = convert::f16x4_to_f32x4(&buf); + let dst_idx = chunk_count * 4; + dst[dst_idx..dst_idx + chunks.remainder().len()] + .copy_from_slice(&vec[..chunks.remainder().len()]); + } + } + + fn convert_to_f64_slice(&self, dst: &mut [f64]) { + assert_eq!( + self.len(), + dst.len(), + "destination and source slices have different lengths" + ); + + let mut chunks = self.chunks_exact(4); + let mut chunk_count = 0usize; // Not using .enumerate() because we need this value for remainder + for chunk in &mut chunks { + let vec = convert::f16x4_to_f64x4(chunk.reinterpret_cast()); + let dst_idx = chunk_count * 4; + dst[dst_idx..dst_idx + 4].copy_from_slice(&vec); + chunk_count += 1; + } + + // Process remainder + if !chunks.remainder().is_empty() { + let mut buf = [0u16; 4]; + buf[..chunks.remainder().len()].copy_from_slice(chunks.remainder().reinterpret_cast()); + let vec = convert::f16x4_to_f64x4(&buf); + let dst_idx = chunk_count * 4; + dst[dst_idx..dst_idx + chunks.remainder().len()] + .copy_from_slice(&vec[..chunks.remainder().len()]); + } + } + + #[cfg(any(feature = "alloc", feature = "std"))] + #[inline] + fn to_f32_vec(&self) -> Vec { + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(self.len()) }; + self.convert_to_f32_slice(&mut vec); + vec + } + + #[cfg(any(feature = "alloc", feature = "std"))] + #[inline] + fn to_f64_vec(&self) -> Vec { + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(self.len()) }; + self.convert_to_f64_slice(&mut vec); + vec + } +} + +impl HalfFloatSliceExt for [bf16] { + #[inline] + fn reinterpret_cast(&self) -> &[u16] { + let pointer = self.as_ptr() as *const u16; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts(pointer, length) } + } + + #[inline] + fn reinterpret_cast_mut(&mut self) -> &mut [u16] { + let pointer = self.as_ptr() as *mut u16; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts_mut(pointer, length) } + } + + fn convert_from_f32_slice(&mut self, src: &[f32]) { + assert_eq!( + self.len(), + src.len(), + "destination and source slices have different lengths" + ); + + // Just use regular loop here until there's any bf16 SIMD support. + for (i, f) in src.iter().enumerate() { + self[i] = bf16::from_f32(*f); + } + } + + fn convert_from_f64_slice(&mut self, src: &[f64]) { + assert_eq!( + self.len(), + src.len(), + "destination and source slices have different lengths" + ); + + // Just use regular loop here until there's any bf16 SIMD support. + for (i, f) in src.iter().enumerate() { + self[i] = bf16::from_f64(*f); + } + } + + fn convert_to_f32_slice(&self, dst: &mut [f32]) { + assert_eq!( + self.len(), + dst.len(), + "destination and source slices have different lengths" + ); + + // Just use regular loop here until there's any bf16 SIMD support. + for (i, f) in self.iter().enumerate() { + dst[i] = f.to_f32(); + } + } + + fn convert_to_f64_slice(&self, dst: &mut [f64]) { + assert_eq!( + self.len(), + dst.len(), + "destination and source slices have different lengths" + ); + + // Just use regular loop here until there's any bf16 SIMD support. + for (i, f) in self.iter().enumerate() { + dst[i] = f.to_f64(); + } + } + + #[cfg(any(feature = "alloc", feature = "std"))] + #[inline] + fn to_f32_vec(&self) -> Vec { + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(self.len()) }; + self.convert_to_f32_slice(&mut vec); + vec + } + + #[cfg(any(feature = "alloc", feature = "std"))] + #[inline] + fn to_f64_vec(&self) -> Vec { + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(self.len()) }; + self.convert_to_f64_slice(&mut vec); + vec + } +} + +impl HalfBitsSliceExt for [u16] { + // Since we sealed all the traits involved, these are safe. + #[inline] + fn reinterpret_cast(&self) -> &[H] + where + H: crate::private::SealedHalf, + { + let pointer = self.as_ptr() as *const H; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts(pointer, length) } + } + + #[inline] + fn reinterpret_cast_mut(&mut self) -> &mut [H] + where + H: crate::private::SealedHalf, + { + let pointer = self.as_mut_ptr() as *mut H; + let length = self.len(); + // SAFETY: We are reconstructing full length of original slice, using its same lifetime, + // and the size of elements are identical + unsafe { slice::from_raw_parts_mut(pointer, length) } + } +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfBitsSliceExt::reinterpret_cast_mut` instead" +)] +#[inline] +pub fn from_bits_mut(bits: &mut [u16]) -> &mut [f16] { + bits.reinterpret_cast_mut() +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfFloatSliceExt::reinterpret_cast_mut` instead" +)] +#[inline] +pub fn to_bits_mut(bits: &mut [f16]) -> &mut [u16] { + bits.reinterpret_cast_mut() +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfBitsSliceExt::reinterpret_cast` instead" +)] +#[inline] +pub fn from_bits(bits: &[u16]) -> &[f16] { + bits.reinterpret_cast() +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfFloatSliceExt::reinterpret_cast` instead" +)] +#[inline] +pub fn to_bits(bits: &[f16]) -> &[u16] { + bits.reinterpret_cast() +} + +#[allow(clippy::float_cmp)] +#[cfg(test)] +mod test { + use super::{HalfBitsSliceExt, HalfFloatSliceExt}; + use crate::{bf16, f16}; + + #[test] + fn test_slice_conversions_f16() { + let bits = &[ + f16::E.to_bits(), + f16::PI.to_bits(), + f16::EPSILON.to_bits(), + f16::FRAC_1_SQRT_2.to_bits(), + ]; + let numbers = &[f16::E, f16::PI, f16::EPSILON, f16::FRAC_1_SQRT_2]; + + // Convert from bits to numbers + let from_bits = bits.reinterpret_cast::(); + assert_eq!(from_bits, numbers); + + // Convert from numbers back to bits + let to_bits = from_bits.reinterpret_cast(); + assert_eq!(to_bits, bits); + } + + #[test] + fn test_mutablility_f16() { + let mut bits_array = [f16::PI.to_bits()]; + let bits = &mut bits_array[..]; + + { + // would not compile without these braces + let numbers = bits.reinterpret_cast_mut(); + numbers[0] = f16::E; + } + + assert_eq!(bits, &[f16::E.to_bits()]); + + bits[0] = f16::LN_2.to_bits(); + assert_eq!(bits, &[f16::LN_2.to_bits()]); + } + + #[test] + fn test_slice_conversions_bf16() { + let bits = &[ + bf16::E.to_bits(), + bf16::PI.to_bits(), + bf16::EPSILON.to_bits(), + bf16::FRAC_1_SQRT_2.to_bits(), + ]; + let numbers = &[bf16::E, bf16::PI, bf16::EPSILON, bf16::FRAC_1_SQRT_2]; + + // Convert from bits to numbers + let from_bits = bits.reinterpret_cast::(); + assert_eq!(from_bits, numbers); + + // Convert from numbers back to bits + let to_bits = from_bits.reinterpret_cast(); + assert_eq!(to_bits, bits); + } + + #[test] + fn test_mutablility_bf16() { + let mut bits_array = [bf16::PI.to_bits()]; + let bits = &mut bits_array[..]; + + { + // would not compile without these braces + let numbers = bits.reinterpret_cast_mut(); + numbers[0] = bf16::E; + } + + assert_eq!(bits, &[bf16::E.to_bits()]); + + bits[0] = bf16::LN_2.to_bits(); + assert_eq!(bits, &[bf16::LN_2.to_bits()]); + } + + #[test] + fn slice_convert_f16_f32() { + // Exact chunks + let vf32 = [1., 2., 3., 4., 5., 6., 7., 8.]; + let vf16 = [ + f16::from_f32(1.), + f16::from_f32(2.), + f16::from_f32(3.), + f16::from_f32(4.), + f16::from_f32(5.), + f16::from_f32(6.), + f16::from_f32(7.), + f16::from_f32(8.), + ]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf32 = [1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let vf16 = [ + f16::from_f32(1.), + f16::from_f32(2.), + f16::from_f32(3.), + f16::from_f32(4.), + f16::from_f32(5.), + f16::from_f32(6.), + f16::from_f32(7.), + f16::from_f32(8.), + f16::from_f32(9.), + ]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf32 = [1., 2.]; + let vf16 = [f16::from_f32(1.), f16::from_f32(2.)]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + } + + #[test] + fn slice_convert_bf16_f32() { + // Exact chunks + let vf32 = [1., 2., 3., 4., 5., 6., 7., 8.]; + let vf16 = [ + bf16::from_f32(1.), + bf16::from_f32(2.), + bf16::from_f32(3.), + bf16::from_f32(4.), + bf16::from_f32(5.), + bf16::from_f32(6.), + bf16::from_f32(7.), + bf16::from_f32(8.), + ]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf32 = [1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let vf16 = [ + bf16::from_f32(1.), + bf16::from_f32(2.), + bf16::from_f32(3.), + bf16::from_f32(4.), + bf16::from_f32(5.), + bf16::from_f32(6.), + bf16::from_f32(7.), + bf16::from_f32(8.), + bf16::from_f32(9.), + ]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf32 = [1., 2.]; + let vf16 = [bf16::from_f32(1.), bf16::from_f32(2.)]; + let mut buf32 = vf32; + let mut buf16 = vf16; + + vf16.convert_to_f32_slice(&mut buf32); + assert_eq!(&vf32, &buf32); + + buf16.convert_from_f32_slice(&vf32); + assert_eq!(&vf16, &buf16); + } + + #[test] + fn slice_convert_f16_f64() { + // Exact chunks + let vf64 = [1., 2., 3., 4., 5., 6., 7., 8.]; + let vf16 = [ + f16::from_f64(1.), + f16::from_f64(2.), + f16::from_f64(3.), + f16::from_f64(4.), + f16::from_f64(5.), + f16::from_f64(6.), + f16::from_f64(7.), + f16::from_f64(8.), + ]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf64 = [1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let vf16 = [ + f16::from_f64(1.), + f16::from_f64(2.), + f16::from_f64(3.), + f16::from_f64(4.), + f16::from_f64(5.), + f16::from_f64(6.), + f16::from_f64(7.), + f16::from_f64(8.), + f16::from_f64(9.), + ]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf64 = [1., 2.]; + let vf16 = [f16::from_f64(1.), f16::from_f64(2.)]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + } + + #[test] + fn slice_convert_bf16_f64() { + // Exact chunks + let vf64 = [1., 2., 3., 4., 5., 6., 7., 8.]; + let vf16 = [ + bf16::from_f64(1.), + bf16::from_f64(2.), + bf16::from_f64(3.), + bf16::from_f64(4.), + bf16::from_f64(5.), + bf16::from_f64(6.), + bf16::from_f64(7.), + bf16::from_f64(8.), + ]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf64 = [1., 2., 3., 4., 5., 6., 7., 8., 9.]; + let vf16 = [ + bf16::from_f64(1.), + bf16::from_f64(2.), + bf16::from_f64(3.), + bf16::from_f64(4.), + bf16::from_f64(5.), + bf16::from_f64(6.), + bf16::from_f64(7.), + bf16::from_f64(8.), + bf16::from_f64(9.), + ]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + + // Partial with chunks + let vf64 = [1., 2.]; + let vf16 = [bf16::from_f64(1.), bf16::from_f64(2.)]; + let mut buf64 = vf64; + let mut buf16 = vf16; + + vf16.convert_to_f64_slice(&mut buf64); + assert_eq!(&vf64, &buf64); + + buf16.convert_from_f64_slice(&vf64); + assert_eq!(&vf16, &buf16); + } + + #[test] + #[should_panic] + fn convert_from_f32_slice_len_mismatch_panics() { + let mut slice1 = [f16::ZERO; 3]; + let slice2 = [0f32; 4]; + slice1.convert_from_f32_slice(&slice2); + } + + #[test] + #[should_panic] + fn convert_from_f64_slice_len_mismatch_panics() { + let mut slice1 = [f16::ZERO; 3]; + let slice2 = [0f64; 4]; + slice1.convert_from_f64_slice(&slice2); + } + + #[test] + #[should_panic] + fn convert_to_f32_slice_len_mismatch_panics() { + let slice1 = [f16::ZERO; 3]; + let mut slice2 = [0f32; 4]; + slice1.convert_to_f32_slice(&mut slice2); + } + + #[test] + #[should_panic] + fn convert_to_f64_slice_len_mismatch_panics() { + let slice1 = [f16::ZERO; 3]; + let mut slice2 = [0f64; 4]; + slice1.convert_to_f64_slice(&mut slice2); + } +} diff --git a/third_party/rust/half/src/vec.rs b/third_party/rust/half/src/vec.rs new file mode 100644 index 000000000000..6967656e4de7 --- /dev/null +++ b/third_party/rust/half/src/vec.rs @@ -0,0 +1,286 @@ +//! Contains utility functions and traits to convert between vectors of [`u16`] bits and [`f16`] or +//! [`bf16`] vectors. +//! +//! The utility [`HalfBitsVecExt`] sealed extension trait is implemented for [`Vec`] vectors, +//! while the utility [`HalfFloatVecExt`] sealed extension trait is implemented for both +//! [`Vec`] and [`Vec`] vectors. These traits provide efficient conversions and +//! reinterpret casting of larger buffers of floating point values, and are automatically included +//! in the [`prelude`][crate::prelude] module. +//! +//! This module is only available with the `std` or `alloc` feature. + +use super::{bf16, f16, slice::HalfFloatSliceExt}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use core::mem; + +/// Extensions to [`Vec`] and [`Vec`] to support reinterpret operations. +/// +/// This trait is sealed and cannot be implemented outside of this crate. +pub trait HalfFloatVecExt: private::SealedHalfFloatVec { + /// Reinterprets a vector of [`f16`]or [`bf16`] numbers as a vector of [`u16`] bits. + /// + /// This is a zero-copy operation. The reinterpreted vector has the same memory location as + /// `self`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let float_buffer = vec![f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]; + /// let int_buffer = float_buffer.reinterpret_into(); + /// + /// assert_eq!(int_buffer, [f16::from_f32(1.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]); + /// ``` + fn reinterpret_into(self) -> Vec; + + /// Converts all of the elements of a `[f32]` slice into a new [`f16`] or [`bf16`] vector. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation][crate] for more information on hardware conversion + /// support. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// let float_values = [1., 2., 3., 4.]; + /// let vec: Vec = Vec::from_f32_slice(&float_values); + /// + /// assert_eq!(vec, vec![f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.), f16::from_f32(4.)]); + /// ``` + fn from_f32_slice(slice: &[f32]) -> Self; + + /// Converts all of the elements of a `[f64]` slice into a new [`f16`] or [`bf16`] vector. + /// + /// The conversion operation is vectorized over the slice, meaning the conversion may be more + /// efficient than converting individual elements on some hardware that supports SIMD + /// conversions. See [crate documentation][crate] for more information on hardware conversion + /// support. + /// + /// # Examples + /// ```rust + /// # use half::prelude::*; + /// let float_values = [1., 2., 3., 4.]; + /// let vec: Vec = Vec::from_f64_slice(&float_values); + /// + /// assert_eq!(vec, vec![f16::from_f64(1.), f16::from_f64(2.), f16::from_f64(3.), f16::from_f64(4.)]); + /// ``` + fn from_f64_slice(slice: &[f64]) -> Self; +} + +/// Extensions to [`Vec`] to support reinterpret operations. +/// +/// This trait is sealed and cannot be implemented outside of this crate. +pub trait HalfBitsVecExt: private::SealedHalfBitsVec { + /// Reinterprets a vector of [`u16`] bits as a vector of [`f16`] or [`bf16`] numbers. + /// + /// `H` is the type to cast to, and must be either the [`f16`] or [`bf16`] type. + /// + /// This is a zero-copy operation. The reinterpreted vector has the same memory location as + /// `self`. + /// + /// # Examples + /// + /// ```rust + /// # use half::prelude::*; + /// let int_buffer = vec![f16::from_f32(1.).to_bits(), f16::from_f32(2.).to_bits(), f16::from_f32(3.).to_bits()]; + /// let float_buffer = int_buffer.reinterpret_into::(); + /// + /// assert_eq!(float_buffer, [f16::from_f32(1.), f16::from_f32(2.), f16::from_f32(3.)]); + /// ``` + fn reinterpret_into(self) -> Vec + where + H: crate::private::SealedHalf; +} + +mod private { + use crate::{bf16, f16}; + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + + pub trait SealedHalfFloatVec {} + impl SealedHalfFloatVec for Vec {} + impl SealedHalfFloatVec for Vec {} + + pub trait SealedHalfBitsVec {} + impl SealedHalfBitsVec for Vec {} +} + +impl HalfFloatVecExt for Vec { + #[inline] + fn reinterpret_into(mut self) -> Vec { + // An f16 array has same length and capacity as u16 array + let length = self.len(); + let capacity = self.capacity(); + + // Actually reinterpret the contents of the Vec as u16, + // knowing that structs are represented as only their members in memory, + // which is the u16 part of `f16(u16)` + let pointer = self.as_mut_ptr() as *mut u16; + + // Prevent running a destructor on the old Vec, so the pointer won't be deleted + mem::forget(self); + + // Finally construct a new Vec from the raw pointer + // SAFETY: We are reconstructing full length and capacity of original vector, + // using its original pointer, and the size of elements are identical. + unsafe { Vec::from_raw_parts(pointer, length, capacity) } + } + + fn from_f32_slice(slice: &[f32]) -> Self { + let mut vec = Vec::with_capacity(slice.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(slice.len()) }; + vec.convert_from_f32_slice(slice); + vec + } + + fn from_f64_slice(slice: &[f64]) -> Self { + let mut vec = Vec::with_capacity(slice.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(slice.len()) }; + vec.convert_from_f64_slice(slice); + vec + } +} + +impl HalfFloatVecExt for Vec { + #[inline] + fn reinterpret_into(mut self) -> Vec { + // An f16 array has same length and capacity as u16 array + let length = self.len(); + let capacity = self.capacity(); + + // Actually reinterpret the contents of the Vec as u16, + // knowing that structs are represented as only their members in memory, + // which is the u16 part of `f16(u16)` + let pointer = self.as_mut_ptr() as *mut u16; + + // Prevent running a destructor on the old Vec, so the pointer won't be deleted + mem::forget(self); + + // Finally construct a new Vec from the raw pointer + // SAFETY: We are reconstructing full length and capacity of original vector, + // using its original pointer, and the size of elements are identical. + unsafe { Vec::from_raw_parts(pointer, length, capacity) } + } + + fn from_f32_slice(slice: &[f32]) -> Self { + let mut vec = Vec::with_capacity(slice.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(slice.len()) }; + vec.convert_from_f32_slice(slice); + vec + } + + fn from_f64_slice(slice: &[f64]) -> Self { + let mut vec = Vec::with_capacity(slice.len()); + // SAFETY: convert will initialize every value in the vector without reading them, + // so this is safe to do instead of double initialize from resize, and we're setting it to + // same value as capacity. + unsafe { vec.set_len(slice.len()) }; + vec.convert_from_f64_slice(slice); + vec + } +} + +impl HalfBitsVecExt for Vec { + // This is safe because all traits are sealed + #[inline] + fn reinterpret_into(mut self) -> Vec + where + H: crate::private::SealedHalf, + { + // An f16 array has same length and capacity as u16 array + let length = self.len(); + let capacity = self.capacity(); + + // Actually reinterpret the contents of the Vec as f16, + // knowing that structs are represented as only their members in memory, + // which is the u16 part of `f16(u16)` + let pointer = self.as_mut_ptr() as *mut H; + + // Prevent running a destructor on the old Vec, so the pointer won't be deleted + mem::forget(self); + + // Finally construct a new Vec from the raw pointer + // SAFETY: We are reconstructing full length and capacity of original vector, + // using its original pointer, and the size of elements are identical. + unsafe { Vec::from_raw_parts(pointer, length, capacity) } + } +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfBitsVecExt::reinterpret_into` instead" +)] +#[inline] +pub fn from_bits(bits: Vec) -> Vec { + bits.reinterpret_into() +} + +#[doc(hidden)] +#[deprecated( + since = "1.4.0", + note = "use `HalfFloatVecExt::reinterpret_into` instead" +)] +#[inline] +pub fn to_bits(numbers: Vec) -> Vec { + numbers.reinterpret_into() +} + +#[cfg(test)] +mod test { + use super::{HalfBitsVecExt, HalfFloatVecExt}; + use crate::{bf16, f16}; + #[cfg(all(feature = "alloc", not(feature = "std")))] + use alloc::vec; + + #[test] + fn test_vec_conversions_f16() { + let numbers = vec![f16::E, f16::PI, f16::EPSILON, f16::FRAC_1_SQRT_2]; + let bits = vec![ + f16::E.to_bits(), + f16::PI.to_bits(), + f16::EPSILON.to_bits(), + f16::FRAC_1_SQRT_2.to_bits(), + ]; + let bits_cloned = bits.clone(); + + // Convert from bits to numbers + let from_bits = bits.reinterpret_into::(); + assert_eq!(&from_bits[..], &numbers[..]); + + // Convert from numbers back to bits + let to_bits = from_bits.reinterpret_into(); + assert_eq!(&to_bits[..], &bits_cloned[..]); + } + + #[test] + fn test_vec_conversions_bf16() { + let numbers = vec![bf16::E, bf16::PI, bf16::EPSILON, bf16::FRAC_1_SQRT_2]; + let bits = vec![ + bf16::E.to_bits(), + bf16::PI.to_bits(), + bf16::EPSILON.to_bits(), + bf16::FRAC_1_SQRT_2.to_bits(), + ]; + let bits_cloned = bits.clone(); + + // Convert from bits to numbers + let from_bits = bits.reinterpret_into::(); + assert_eq!(&from_bits[..], &numbers[..]); + + // Convert from numbers back to bits + let to_bits = from_bits.reinterpret_into(); + assert_eq!(&to_bits[..], &bits_cloned[..]); + } +} diff --git a/third_party/rust/nss-gk-api/.cargo-checksum.json b/third_party/rust/nss-gk-api/.cargo-checksum.json new file mode 100644 index 000000000000..43178d2668b7 --- /dev/null +++ b/third_party/rust/nss-gk-api/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"484e9876017a5f297ed6921c5336e65235b1e4783753f4f3f31e0256e86ef266","README.md":"a76b467337dd5c5ac8f0e315b1e1f584f2d8c3b6fe57aba16306a3faf9f8fde7","bindings/bindings.toml":"4fbb1e31f56f068e040842408e0e9a5f0f1f35651298afa86836765049a2928b","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nspr_types.h":"6e1ca8e760c913e08507dcb8645748661c81b649ac148ee046d2e4e95f39cede","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_prelude.h":"3952a566aaa497b4c3094bc6c338bba987134a2d5f336c409d1bd6d31e14a8f8","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"3d8a2d22ca23653d6421a7bb2752c6c532e4cf823b2b3a6622730fd46586665d","src/err.rs":"6c7e785f9c52e7606bc4de3fa4e6805b73cf828430850fd6c4e87bff20f4530e","src/exp.rs":"01e999dc4be93a12e5890b2d8c4ce62d3f5afecf9f8373f150353633731445f9","src/lib.rs":"7b50ec7ddb61cdef461449e4380402569e4ffde7940341dbafc6b60922482132","src/p11.rs":"f79fbc3b639fe6ae7a2c68e2f89de0be9c40b44adcdaba1631fd980c24de77e6","src/prio.rs":"bdfef0e3898876ee223218aedc0b2d2f43575e4302ffdd0fa5a719d4f6e468e0","src/prtypes.rs":"270effec36a2d6836329e672e8043bf277f48fe136c8844f5eb503c0e32511f9","src/ssl.rs":"a36251e63484e382ff70d8c1008ec456746c7826e4c628fe42818880ecf1596b","src/time.rs":"1fce901a535d67baaef59c42f39c5d8eb5a4ac6cbb69026fbc7088dd822fa404","src/util.rs":"f3c163ff609b4211e1ebab08604f770e4bcf11a6946c9d0ecb4165594a4bb886"},"package":"1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"} \ No newline at end of file diff --git a/third_party/rust/nss-gk-api/Cargo.toml b/third_party/rust/nss-gk-api/Cargo.toml new file mode 100644 index 000000000000..099311745965 --- /dev/null +++ b/third_party/rust/nss-gk-api/Cargo.toml @@ -0,0 +1,54 @@ +# 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 are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.57.0" +name = "nss-gk-api" +version = "0.2.1" +authors = [ + "Martin Thomson ", + "Andy Leiserson ", + "John M. Schanck ", +] +description = "Gecko API for NSS" +readme = "README.md" +license = "MIT/Apache-2.0" +repository = "https://github.com/mozilla/nss-gk-api" + +[dependencies.once_cell] +version = "1" + +[dependencies.pkcs11-bindings] +version = ">= 0.1.3" + +[build-dependencies.bindgen] +version = ">= 0.59.2" +features = ["runtime"] +default-features = false + +[build-dependencies.mozbuild] +version = "0.1" +optional = true + +[build-dependencies.serde] +version = "1.0" + +[build-dependencies.serde_derive] +version = "1.0" + +[build-dependencies.toml] +version = "0.5" + +[features] +default = ["deny-warnings"] +deny-warnings = [] +gecko = ["mozbuild"] diff --git a/third_party/rust/nss-gk-api/README.md b/third_party/rust/nss-gk-api/README.md new file mode 100644 index 000000000000..f86711a93148 --- /dev/null +++ b/third_party/rust/nss-gk-api/README.md @@ -0,0 +1,6 @@ +# (UNSTABLE) Gecko API for NSS + +nss-gk-api is intended to provide a safe and idiomatic Rust interface to NSS. It is based on code from neqo-crypto, but has been factored out of mozilla-central so that it can be used in standalone applications and libraries such as authenticator-rs. That said, it is *primarily* for use in Gecko, and will not be extended to support arbitrary use cases. + +This is work in progress and major changes are expected. In particular, a new version of the `nss-sys` crate will be factored out of this crate. + diff --git a/third_party/rust/nss-gk-api/bindings/bindings.toml b/third_party/rust/nss-gk-api/bindings/bindings.toml new file mode 100644 index 000000000000..15c640765cb2 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/bindings.toml @@ -0,0 +1,280 @@ +# In this file, every section corresponds to a header file. +# A corresponding binding file will be created in $OUT_DIR. +# +# This configuration is processed by build.rs to populate `exclude` for each +# header with the types/variables/functions/enums declared for generation in +# other headers. + +[nspr_types] +types = [ + "PRBool", + "PRIntn", + "PRInt16", + "PRInt32", + "PRInt64", + "PROffset32", + "PROffset64", + "PRSize", + "PRStatus", + "PRUintn", + "PRUint8", + "PRUint16", + "PRUint32", + "PRUint64", + "PRUword", +] +variables = [ + "PR_FALSE", + "PR_TRUE", +] + +[nss_prelude] +types = [ + "SECItem", + "SECItemArray", + "SECItemStr", + "SECStatus", +] +functions = [ + "SECITEM_FreeItem", +] +enums = [ + "_SECStatus", + "SECItemType", +] + +[nss_ssl] +types = [ + "SSLAlertDescription", + "SSLExtensionType", + "SSLNamedGroup", + "SSLProtocolVariant", + "SSLSignatureScheme", +] +functions = [ + "SSL_AlertSentCallback", + "SSL_AuthCertificateComplete", + "SSL_AuthCertificateHook", + "SSL_CipherPrefSet", + "SSL_ConfigServerCert", + "SSL_ConfigServerSessionIDCache", + "SSL_DestroyResumptionTokenInfo", + "SSL_GetChannelInfo", + "SSL_GetImplementedCiphers", + "SSL_GetNextProto", + "SSL_GetNumImplementedCiphers", + "SSL_GetPreliminaryChannelInfo", + "SSL_GetResumptionTokenInfo", + "SSL_ForceHandshake", + "SSL_ImportFD", + "SSL_NamedGroupConfig", + "SSL_OptionSet", + "SSL_OptionGetDefault", + "SSL_PeerCertificate", + "SSL_PeerCertificateChain", + "SSL_PeerSignedCertTimestamps", + "SSL_PeerStapledOCSPResponses", + "SSL_ResetHandshake", + "SSL_SetNextProtoNego", + "SSL_SetURL", + "SSL_VersionRangeSet", +] +enums = [ + "SSLAuthType", + "SSLCipherAlgorithm", + "SSLCompressionMethod", + "SSLExtensionType", + "SSLKEAType", + "SSLMACAlgorithm", + "SSLNamedGroup", + "SSLNextProtoState", + "SSLProtocolVariant", + "SSLSignatureScheme", +] +variables = [ + "SSL_LIBRARY_VERSION_TLS_\\d_\\d", + "SSL_NumImplementedCiphers", + "ssl_preinfo_.*", +] +opaque = [ + "SSLExtraServerCertData", +] + +[nss_sslopt] +variables = [ + "SSL_REQUEST_CERTIFICATE", + "SSL_REQUIRE_CERTIFICATE", + "SSL_NO_LOCKS", + "SSL_ENABLE_SESSION_TICKETS", + "SSL_ENABLE_OCSP_STAPLING", + "SSL_ENABLE_ALPN", + "SSL_ENABLE_EXTENDED_MASTER_SECRET", + "SSL_ENABLE_SIGNED_CERT_TIMESTAMPS", + "SSL_ENABLE_0RTT_DATA", + "SSL_RECORD_SIZE_LIMIT", + "SSL_ENABLE_TLS13_COMPAT_MODE", + "SSL_ENABLE_HELLO_DOWNGRADE_CHECK", + "SSL_SUPPRESS_END_OF_EARLY_DATA", +] + +[nss_ciphers] +variables = ["TLS_.*"] +exclude = [ + ".*_(?:EXPORT(?:1024)?|anon|DES|RC4)_.*", + ".*_(?:MD5|NULL_SHA)", +] + +[nss_secerr] +types = ["SECErrorCodes"] +enums = ["SECErrorCodes"] + +[nss_sslerr] +types = ["SSLErrorCodes"] +enums = ["SSLErrorCodes"] + +[nss_init] +functions = [ + "NSS_Initialize", + "NSS_IsInitialized", + "NSS_NoDB_Init", + "NSS_SetDomesticPolicy", + "NSS_Shutdown", + "NSS_VersionCheck", +] +variables = [ + "NSS_INIT_READONLY", + "SECMOD_DB", +] + +[nss_p11] +types = [ + "CERTCertList", + "CERTCertListNode", + "HpkeAeadId", + "HpkeKdfId", + "HpkeKemId", +] +functions = [ + "CERT_DestroyCertificate", + "CERT_DestroyCertList", + "CERT_GetCertificateDer", + "PK11_CipherOp", + "PK11_CreateContextBySymKey", + "PK11_Decrypt", + "PK11_DestroyContext", + "PK11_DigestOp", + "PK11_DigestFinal", + "PK11_Encrypt", + "PK11_ExtractKeyValue", + "PK11_FindCertFromNickname", + "PK11_FindKeyByAnyCert", + "PK11_FreeSlot", + "PK11_FreeSymKey", + "PK11_GenerateKeyPairWithOpFlags", + "PK11_GenerateRandom", + "PK11_GetBlockSize", + "PK11_GetInternalSlot", + "PK11_GetKeyData", + "PK11_GetMechanism", + "PK11_HashBuf", + "PK11_HPKE_Serialize", + "PK11_ImportDataKey", + "PK11_ImportDERPrivateKeyInfoAndReturnKey", + "PK11_ImportSymKey", + "PK11_PubDeriveWithKDF", + "PK11_ReadRawAttribute", + "PK11_ReferenceSymKey", + "SECKEY_CopyPrivateKey", + "SECKEY_CopyPublicKey", + "SECKEY_DecodeDERSubjectPublicKeyInfo", + "SECKEY_DestroyPrivateKey", + "SECKEY_DestroyPublicKey", + "SECKEY_DestroySubjectPublicKeyInfo", + "SECKEY_ExtractPublicKey", + "SECKEY_ConvertToPublicKey", + "SECOID_FindOIDByTag", +] +enums = [ + "HpkeAeadId", + "HpkeKdfId", + "HpkeKemId", + "PK11ObjectType", + "PK11Origin", + "SECOidTag", +] +opaque = [ + "CERTCertificateStr", + "PK11ContextStr", + "PK11SlotInfoStr", + "PK11SymKeyStr", + "SECKEYPrivateKeyStr", + "SECKEYPublicKeyStr", +] +variables = [ + "AES_BLOCK_SIZE", + "PK11_ATTR_INSENSITIVE", + "PK11_ATTR_PRIVATE", + "PK11_ATTR_PUBLIC", + "PK11_ATTR_SENSITIVE", + "PK11_ATTR_SESSION", + "SEC_ASN1_OBJECT_ID", + "SHA256_LENGTH", +] + +[nspr_err] +variables = ["PR_.*_ERROR"] + +[nspr_error] +types = [ + "PRErrorCode", +] +functions = [ + "PR_ErrorToName", + "PR_ErrorToString", + "PR_GetError", + "PR_SetError", +] +variables = [ + "PR_LANGUAGE_I_DEFAULT", +] + +[nspr_io] +types = [ + "PRFileDesc", + "PRFileInfo", + "PRFileInfo64", + "PRFilePrivate", + "PRIOMethods", + "PRIOVec", + "PRSeekFN", + "PRSeek64FN", +] +functions = [ + "PR_Close", + "PR_CreateIOLayerStub", + "PR_GetUniqueIdentity", +] +variables = [ + "PR_AF_INET", +] +# opaque is for the stuff we don't plan to use, but we need for function signatures. +opaque = [ + "PRFileDesc", + "PRFileInfo", + "PRFileInfo64", + "PRFilePrivate", + "PRIOVec", +] +enums = [ + "PRDescType", + "PRSeekWhence", +] + +[nspr_time] +types = ["PRTime"] +functions = ["PR_Now"] + +[mozpkix] +cplusplus = true +types = ["mozilla::pkix::ErrorCode"] +enums = ["mozilla::pkix::ErrorCode"] diff --git a/third_party/rust/nss-gk-api/bindings/mozpkix.hpp b/third_party/rust/nss-gk-api/bindings/mozpkix.hpp new file mode 100644 index 000000000000..d0a6cb58619d --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/mozpkix.hpp @@ -0,0 +1 @@ +#include "mozpkix/pkixnss.h" \ No newline at end of file diff --git a/third_party/rust/nss-gk-api/bindings/nspr_err.h b/third_party/rust/nss-gk-api/bindings/nspr_err.h new file mode 100644 index 000000000000..204e771c49a3 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_err.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_error.h b/third_party/rust/nss-gk-api/bindings/nspr_error.h new file mode 100644 index 000000000000..8ff8ce202db0 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_error.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prerror.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_io.h b/third_party/rust/nss-gk-api/bindings/nspr_io.h new file mode 100644 index 000000000000..9997fb812e12 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_io.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prio.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_time.h b/third_party/rust/nss-gk-api/bindings/nspr_time.h new file mode 100644 index 000000000000..f5596577fad4 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_time.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prtime.h" diff --git a/third_party/rust/nss-gk-api/bindings/nspr_types.h b/third_party/rust/nss-gk-api/bindings/nspr_types.h new file mode 100644 index 000000000000..624587560bc3 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nspr_types.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "prtypes.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_ciphers.h b/third_party/rust/nss-gk-api/bindings/nss_ciphers.h new file mode 100644 index 000000000000..f064f39c5d34 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_ciphers.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#define SSL_DISABLE_DEPRECATED_CIPHER_SUITE_NAMES +#include "sslproto.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_init.h b/third_party/rust/nss-gk-api/bindings/nss_init.h new file mode 100644 index 000000000000..ae111bac2b31 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_init.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "nss.h" +#include "ssl.h" // For NSS_SetDomesticPolicy diff --git a/third_party/rust/nss-gk-api/bindings/nss_p11.h b/third_party/rust/nss-gk-api/bindings/nss_p11.h new file mode 100644 index 000000000000..7de50eebecd8 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_p11.h @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "cert.h" +#include "keyhi.h" +#include "pk11pub.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_prelude.h b/third_party/rust/nss-gk-api/bindings/nss_prelude.h new file mode 100644 index 000000000000..72af8f03c923 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_prelude.h @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "seccomon.h" +#include "secitem.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_secerr.h b/third_party/rust/nss-gk-api/bindings/nss_secerr.h new file mode 100644 index 000000000000..c2b2d4020c9a --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_secerr.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "secerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_ssl.h b/third_party/rust/nss-gk-api/bindings/nss_ssl.h new file mode 100644 index 000000000000..1cde112cf23e --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_ssl.h @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "sslproto.h" +#include "ssl.h" +#include "sslexp.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_sslerr.h b/third_party/rust/nss-gk-api/bindings/nss_sslerr.h new file mode 100644 index 000000000000..74a836f1e881 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_sslerr.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "sslerr.h" diff --git a/third_party/rust/nss-gk-api/bindings/nss_sslopt.h b/third_party/rust/nss-gk-api/bindings/nss_sslopt.h new file mode 100644 index 000000000000..a14e1e69d184 --- /dev/null +++ b/third_party/rust/nss-gk-api/bindings/nss_sslopt.h @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "ssl.h" diff --git a/third_party/rust/nss-gk-api/build.rs b/third_party/rust/nss-gk-api/build.rs new file mode 100644 index 000000000000..ab1892936bcd --- /dev/null +++ b/third_party/rust/nss-gk-api/build.rs @@ -0,0 +1,445 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] + +use bindgen::Builder; +use serde_derive::Deserialize; +use std::collections::HashMap; +use std::collections::HashSet; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const BINDINGS_DIR: &str = "bindings"; +const BINDINGS_CONFIG: &str = "bindings.toml"; + +// This is the format of a single section of the configuration file. +#[derive(Deserialize)] +struct Bindings { + /// types that are explicitly included + #[serde(default)] + types: Vec, + /// functions that are explicitly included + #[serde(default)] + functions: Vec, + /// variables (and `#define`s) that are explicitly included + #[serde(default)] + variables: Vec, + /// types that should be explicitly marked as opaque + #[serde(default)] + opaque: Vec, + /// enumerations that are turned into a module (without this, the enum is + /// mapped using the default, which means that the individual values are + /// formed with an underscore as _). + #[serde(default)] + enums: Vec, + + /// Any item that is specifically excluded; if none of the types, functions, + /// or variables fields are specified, everything defined will be mapped, + /// so this can be used to limit that. + #[serde(default)] + exclude: Vec, + + /// Whether the file is to be interpreted as C++ + #[serde(default)] + cplusplus: bool, +} + +fn is_debug() -> bool { + env::var("DEBUG") + .map(|d| d.parse::().unwrap_or(false)) + .unwrap_or(false) +} + +// bindgen needs access to libclang. +// On windows, this doesn't just work, you have to set LIBCLANG_PATH. +// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work. +fn setup_clang() { + if env::consts::OS != "windows" { + return; + } + println!("rerun-if-env-changed=LIBCLANG_PATH"); + println!("rerun-if-env-changed=MOZBUILD_STATE_PATH"); + if env::var("LIBCLANG_PATH").is_ok() { + return; + } + let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") { + PathBuf::from(dir.trim()) + } else { + eprintln!("warning: Building without a gecko setup is not likely to work."); + eprintln!(" A working libclang is needed to build nss-sys."); + eprintln!(" Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set."); + eprintln!(); + eprintln!(" We recommend checking out https://github.com/mozilla/gecko-dev"); + eprintln!(" Then run `./mach bootstrap` which will retrieve clang."); + eprintln!(" Make sure to export MOZBUILD_STATE_PATH when building."); + return; + }; + let libclang_dir = mozbuild_root.join("clang").join("lib"); + if libclang_dir.is_dir() { + env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap()); + println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap()); + } else { + println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko"); + } +} + +fn nss_dir() -> PathBuf { + let dir = if let Ok(dir) = env::var("NSS_DIR") { + let path = PathBuf::from(dir.trim()); + assert!( + !path.is_relative(), + "The NSS_DIR environment variable is expected to be an absolute path." + ); + path + } else { + let out_dir = env::var("OUT_DIR").unwrap(); + let dir = Path::new(&out_dir).join("nss"); + if !dir.exists() { + Command::new("hg") + .args(&[ + "clone", + "https://hg.mozilla.org/projects/nss", + dir.to_str().unwrap(), + ]) + .status() + .expect("can't clone nss"); + } + let nspr_dir = Path::new(&out_dir).join("nspr"); + if !nspr_dir.exists() { + Command::new("hg") + .args(&[ + "clone", + "https://hg.mozilla.org/projects/nspr", + nspr_dir.to_str().unwrap(), + ]) + .status() + .expect("can't clone nspr"); + } + dir + }; + assert!(dir.is_dir(), "NSS_DIR {:?} doesn't exist", dir); + // Note that this returns a relative path because UNC + // paths on windows cause certain tools to explode. + dir +} + +fn get_bash() -> PathBuf { + // When running under MOZILLABUILD, we need to make sure not to invoke + // another instance of bash that might be sitting around (like WSL). + match env::var("MOZILLABUILD") { + Ok(d) => PathBuf::from(d).join("msys").join("bin").join("bash.exe"), + Err(_) => PathBuf::from("bash"), + } +} + +fn build_nss(dir: PathBuf) { + let mut build_nss = vec![ + String::from("./build.sh"), + String::from("-Ddisable_tests=1"), + ]; + if is_debug() { + build_nss.push(String::from("--static")); + } else { + build_nss.push(String::from("-o")); + } + if let Ok(d) = env::var("NSS_JOBS") { + build_nss.push(String::from("-j")); + build_nss.push(d); + } + let status = Command::new(get_bash()) + .args(build_nss) + .current_dir(dir) + .status() + .expect("couldn't start NSS build"); + assert!(status.success(), "NSS build failed"); +} + +fn dynamic_link() { + let libs = if env::consts::OS == "windows" { + &["nssutil3.dll", "nss3.dll", "ssl3.dll"] + } else { + &["nssutil3", "nss3", "ssl3"] + }; + dynamic_link_both(libs); +} + +fn dynamic_link_both(extra_libs: &[&str]) { + let nspr_libs = if env::consts::OS == "windows" { + &["libplds4", "libplc4", "libnspr4"] + } else { + &["plds4", "plc4", "nspr4"] + }; + for lib in nspr_libs.iter().chain(extra_libs) { + println!("cargo:rustc-link-lib=dylib={}", lib); + } +} + +fn static_link() { + let mut static_libs = vec![ + "certdb", + "certhi", + "cryptohi", + "freebl", + "nss_static", + "nssb", + "nssdev", + "nsspki", + "nssutil", + "pk11wrap", + "pkcs12", + "pkcs7", + "smime", + "softokn_static", + "ssl", + ]; + if env::consts::OS != "macos" { + static_libs.push("sqlite"); + } + for lib in static_libs { + println!("cargo:rustc-link-lib=static={}", lib); + } + + // Dynamic libs that aren't transitively included by NSS libs. + let mut other_libs = Vec::new(); + if env::consts::OS != "windows" { + other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]); + } + if env::consts::OS == "macos" { + other_libs.push("sqlite3"); + } + dynamic_link_both(&other_libs); +} + +fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec { + let nsprinclude = nsstarget.join("include").join("nspr"); + let nssinclude = nssdist.join("public").join("nss"); + let includes = vec![nsprinclude, nssinclude]; + for i in &includes { + println!("cargo:include={}", i.to_str().unwrap()); + } + includes +} + +fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) { + let suffix = if bindings.cplusplus { ".hpp" } else { ".h" }; + let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix); + let header = header_path.to_str().unwrap(); + let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs"); + + println!("cargo:rerun-if-changed={}", header); + + let mut builder = Builder::default().header(header); + builder = builder.generate_comments(false); + builder = builder.size_t_is_usize(true); + + builder = builder.clang_arg("-v"); + + if !gecko { + builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT"); + if env::consts::OS == "windows" { + builder = builder.clang_arg("-DWIN"); + } else if env::consts::OS == "macos" { + builder = builder.clang_arg("-DDARWIN"); + } else if env::consts::OS == "linux" { + builder = builder.clang_arg("-DLINUX"); + } else if env::consts::OS == "android" { + builder = builder.clang_arg("-DLINUX"); + builder = builder.clang_arg("-DANDROID"); + } + if bindings.cplusplus { + builder = builder.clang_args(&["-x", "c++", "-std=c++11"]); + } + } + + builder = builder.clang_args(flags); + + // Apply the configuration. + for v in &bindings.types { + builder = builder.allowlist_type(v); + } + for v in &bindings.functions { + builder = builder.allowlist_function(v); + } + for v in &bindings.variables { + builder = builder.allowlist_var(v); + } + for v in &bindings.exclude { + builder = builder.blocklist_item(v); + } + for v in &bindings.opaque { + builder = builder.opaque_type(v); + } + for v in &bindings.enums { + builder = builder.constified_enum_module(v); + } + + let bindings = builder.generate().expect("unable to generate bindings"); + bindings + .write_to_file(out) + .expect("couldn't write bindings"); +} + +fn setup_standalone() -> Vec { + setup_clang(); + + let nss = nss_dir(); + println!("cargo:rerun-if-env-changed={}", nss.display()); + + build_nss(nss.clone()); + + // $NSS_DIR/../dist/ + let nssdist = nss.parent().unwrap().join("dist"); + println!("cargo:rerun-if-env-changed={}", nssdist.display()); + + let nsstarget = env::var("NSS_TARGET") + .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap()); + let nsstarget = nssdist.join(nsstarget.trim()); + + let includes = get_includes(&nsstarget, &nssdist); + + let nsslibdir = nsstarget.join("lib"); + println!( + "cargo:rustc-link-search=native={}", + nsslibdir.to_str().unwrap() + ); + if is_debug() { + static_link(); + } else { + dynamic_link(); + } + + let mut flags: Vec = Vec::new(); + for i in includes { + flags.push(String::from("-I") + i.to_str().unwrap()); + } + + flags +} + +#[cfg(feature = "gecko")] +fn setup_for_gecko() -> Vec { + use mozbuild::TOPOBJDIR; + + let fold_libs = mozbuild::config::MOZ_FOLD_LIBS; + let libs = if fold_libs { + vec!["nss3"] + } else { + vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"] + }; + + for lib in &libs { + println!("cargo:rustc-link-lib=dylib={}", lib); + } + + if fold_libs { + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR.join("security").to_str().unwrap() + ); + } else { + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR.join("dist").join("bin").to_str().unwrap() + ); + let nsslib_path = TOPOBJDIR.join("security").join("nss").join("lib"); + println!( + "cargo:rustc-link-search=native={}", + nsslib_path.join("nss").join("nss_nss3").to_str().unwrap() + ); + println!( + "cargo:rustc-link-search=native={}", + nsslib_path.join("ssl").join("ssl_ssl3").to_str().unwrap() + ); + println!( + "cargo:rustc-link-search=native={}", + TOPOBJDIR + .join("config") + .join("external") + .join("nspr") + .join("pr") + .to_str() + .unwrap() + ); + } + + let flags_path = TOPOBJDIR.join("netwerk/socket/neqo/extra-bindgen-flags"); + + println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap()); + let mut flags = fs::read_to_string(flags_path) + .expect("Failed to read extra-bindgen-flags file") + .split_whitespace() + .map(std::borrow::ToOwned::to_owned) + .collect::>(); + + flags.push(String::from("-include")); + flags.push( + TOPOBJDIR + .join("dist") + .join("include") + .join("mozilla-config.h") + .to_str() + .unwrap() + .to_string(), + ); + flags +} + +#[cfg(not(feature = "gecko"))] +fn setup_for_gecko() -> Vec { + unreachable!() +} + +fn process_config(config: &mut HashMap) { + let mut excludes = HashMap::new(); + for header in config.keys().cloned() { + // Collect the list of types, functions, and variables configured + // for generation in any other configured header, and add it to the list + // of items excluded from generation in this header. This ensures that + // each item only appears in one bindings module, which prevents some + // type conflicts. (However, it does mean that appropriate `use` + // declarations must be added for the generated modules.) + excludes.insert( + header.clone(), + config.iter().flat_map(|(h, b)| { + if *h != header { + vec![ + &b.types, + &b.functions, + &b.variables, + ] + } else { + vec![] + }.into_iter().flat_map(|v| v.iter()).cloned() + }).collect::>() + ); + } + + for (header, excludes) in excludes.into_iter() { + config.get_mut(&header).expect("key disappeared from config?").exclude.extend(excludes.into_iter()); + } +} + +fn main() { + let flags = if cfg!(feature = "gecko") { + setup_for_gecko() + } else { + setup_standalone() + }; + + let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG); + println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap()); + let config = fs::read_to_string(config_file).expect("unable to read binding configuration"); + let mut config: HashMap = ::toml::from_str(&config).unwrap(); + process_config(&mut config); + + for (k, v) in &config { + build_bindings(k, v, &flags[..], cfg!(feature = "gecko")); + } +} diff --git a/third_party/rust/nss-gk-api/src/err.rs b/third_party/rust/nss-gk-api/src/err.rs new file mode 100644 index 000000000000..cc52b51f639f --- /dev/null +++ b/third_party/rust/nss-gk-api/src/err.rs @@ -0,0 +1,257 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![allow(clippy::upper_case_acronyms)] + +use std::os::raw::c_char; +use std::str::Utf8Error; + +use crate::nss_prelude::*; +use crate::prtypes::*; + +include!(concat!(env!("OUT_DIR"), "/nspr_error.rs")); +mod codes { + #![allow(non_snake_case)] + include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs")); + include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs")); + include!(concat!(env!("OUT_DIR"), "/mozpkix.rs")); +} +pub use codes::mozilla_pkix_ErrorCode as mozpkix; +pub use codes::SECErrorCodes as sec; +pub use codes::SSLErrorCodes as ssl; +pub mod nspr { + include!(concat!(env!("OUT_DIR"), "/nspr_err.rs")); +} + +pub type Res = Result; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub enum Error { + AeadError, + CertificateLoading, + CipherInitFailure, + CreateSslSocket, + EchRetry(Vec), + HkdfError, + InternalError, + IntegerOverflow, + InvalidEpoch, + MixedHandshakeMethod, + NoDataAvailable, + NssError { + name: String, + code: PRErrorCode, + desc: String, + }, + OverrunError, + SelfEncryptFailure, + StringError, + TimeTravelError, + UnsupportedCipher, + UnsupportedVersion, +} + +impl Error { + pub(crate) fn last_nss_error() -> Self { + Self::from(unsafe { PR_GetError() }) + } +} + +impl std::error::Error for Error { + #[must_use] + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } + #[must_use] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Error: {:?}", self) + } +} + +impl From for Error { + #[must_use] + fn from(_: std::num::TryFromIntError) -> Self { + Self::IntegerOverflow + } +} +impl From for Error { + #[must_use] + fn from(_: std::ffi::NulError) -> Self { + Self::InternalError + } +} +impl From for Error { + fn from(_: Utf8Error) -> Self { + Self::StringError + } +} +impl From for Error { + fn from(code: PRErrorCode) -> Self { + let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR"); + let desc = wrap_str_fn( + || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) }, + "...", + ); + Self::NssError { name, code, desc } + } +} + +use std::ffi::CStr; + +fn wrap_str_fn(f: F, dflt: &str) -> String +where + F: FnOnce() -> *const c_char, +{ + unsafe { + let p = f(); + if p.is_null() { + return dflt.to_string(); + } + CStr::from_ptr(p).to_string_lossy().into_owned() + } +} + +pub fn is_blocked(result: &Res<()>) -> bool { + match result { + Err(Error::NssError { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR, + _ => false, + } +} + +pub trait IntoResult +{ + /// The `Ok` type for the result. + type Ok; + + /// Unsafe in our implementors because they take a pointer and have no way + /// to ensure that the pointer is valid. An invalid pointer could cause UB + /// in `impl Drop for Scoped`. + unsafe fn into_result(self) -> Result; +} + +pub unsafe fn into_result

(ptr: *mut P) -> Result<*mut P, Error> { + if ptr.is_null() { + Err(Error::last_nss_error()) + } else { + Ok(ptr) + } +} + +// This can be used to implement `IntoResult` for pointer types that do not make +// sense as smart pointers. For smart pointers use `scoped_ptr!`. +macro_rules! impl_into_result { + ($pointer:ty) => { + impl $crate::err::IntoResult for *mut $pointer { + type Ok = *mut $pointer; + + unsafe fn into_result(self) -> Result { + $crate::err::into_result(self) + } + } + } +} + +impl IntoResult for SECStatus { + type Ok = (); + + unsafe fn into_result(self) -> Result<(), Error> { + if self == SECSuccess { + Ok(()) + } else { + Err(Error::last_nss_error()) + } + } +} + +pub fn secstatus_to_res(code: SECStatus) -> Res<()> { + // Unsafe in the trait, but this impl should be safe. + unsafe { SECStatus::into_result(code) } +} + +#[cfg(test)] +mod tests { + use crate::err::{self, is_blocked, secstatus_to_res, Error, PRErrorCode, PR_SetError}; + use crate::ssl::{SECFailure, SECSuccess}; + use test_fixture::fixture_init; + + fn set_error_code(code: PRErrorCode) { + // This code doesn't work without initializing NSS first. + fixture_init(); + unsafe { + PR_SetError(code, 0); + } + } + + #[test] + fn error_code() { + fixture_init(); + assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ); + assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL); + assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR); + } + + #[test] + fn is_ok() { + assert!(secstatus_to_res(SECSuccess).is_ok()); + } + + #[test] + fn is_err() { + set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "SSL_ERROR_BAD_MAC_READ"); + assert_eq!(code, -12273); + assert_eq!( + desc, + "SSL received a record with an incorrect Message Authentication Code." + ); + } + _ => unreachable!(), + } + } + + #[test] + fn is_err_zero_code() { + set_error_code(0); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + match r.unwrap_err() { + Error::NssError { name, code, .. } => { + assert_eq!(name, "UNKNOWN_ERROR"); + assert_eq!(code, 0); + // Note that we don't test |desc| here because that comes from + // strerror(0), which is platform-dependent. + } + _ => unreachable!(), + } + } + + #[test] + fn blocked() { + set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR); + let r = secstatus_to_res(SECFailure); + assert!(r.is_err()); + assert!(is_blocked(&r)); + match r.unwrap_err() { + Error::NssError { name, code, desc } => { + assert_eq!(name, "PR_WOULD_BLOCK_ERROR"); + assert_eq!(code, -5998); + assert_eq!(desc, "The operation would have blocked"); + } + _ => panic!("bad error type"), + } + } +} diff --git a/third_party/rust/nss-gk-api/src/exp.rs b/third_party/rust/nss-gk-api/src/exp.rs new file mode 100644 index 000000000000..9f2255c1dcc1 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/exp.rs @@ -0,0 +1,25 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/* TODO +macro_rules! experimental_api { + ( $n:ident ( $( $a:ident : $t:ty ),* $(,)? ) ) => { + #[allow(non_snake_case)] + #[allow(clippy::too_many_arguments)] + pub(crate) unsafe fn $n ( $( $a : $t ),* ) -> Result<(), crate::err::Error> { + const EXP_FUNCTION: &str = stringify!($n); + let n = ::std::ffi::CString::new(EXP_FUNCTION)?; + let f = crate::ssl::SSL_GetExperimentalAPI(n.as_ptr()); + if f.is_null() { + return Err(crate::err::Error::InternalError); + } + let f: unsafe extern "C" fn( $( $t ),* ) -> crate::SECStatus = ::std::mem::transmute(f); + let rv = f( $( $a ),* ); + crate::err::secstatus_to_res(rv) + } + }; +} +*/ diff --git a/third_party/rust/nss-gk-api/src/lib.rs b/third_party/rust/nss-gk-api/src/lib.rs new file mode 100644 index 000000000000..1cd0f2a6e8ed --- /dev/null +++ b/third_party/rust/nss-gk-api/src/lib.rs @@ -0,0 +1,173 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] +// Bindgen auto generated code +// won't adhere to the clippy rules below +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::unseparated_literal_suffix)] +#![allow(clippy::used_underscore_binding)] + +#[macro_use] +pub mod err; +#[macro_use] +mod exp; +#[macro_use] +mod util; + +pub mod p11; +mod prio; +mod ssl; +pub mod time; + +pub use err::{Error, IntoResult, secstatus_to_res}; +pub use p11::{PrivateKey, PublicKey, SymKey}; +pub use util::*; + +use once_cell::sync::OnceCell; + +use std::ffi::CString; +use std::path::{Path, PathBuf}; +use std::ptr::null; + +const MINIMUM_NSS_VERSION: &str = "3.74"; + +#[allow(non_snake_case)] +#[allow(non_upper_case_globals)] +pub mod nss_prelude { + pub use crate::prtypes::*; + pub use _SECStatus::*; + include!(concat!(env!("OUT_DIR"), "/nss_prelude.rs")); +} +pub use nss_prelude::{SECItem, SECItemArray, SECItemType, SECStatus}; + +#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)] +#[allow(clippy::upper_case_acronyms)] +#[allow(unknown_lints, clippy::borrow_as_ptr)] +mod nss { + use crate::nss_prelude::*; + include!(concat!(env!("OUT_DIR"), "/nss_init.rs")); +} + +pub mod prtypes; +pub use prtypes::*; + +// Shadow these bindgen created values to correct their type. +pub const PR_FALSE: PRBool = prtypes::PR_FALSE as PRBool; +pub const PR_TRUE: PRBool = prtypes::PR_TRUE as PRBool; + +enum NssLoaded { + External, + NoDb, + Db(Box), +} + +impl Drop for NssLoaded { + fn drop(&mut self) { + if !matches!(self, Self::External) { + unsafe { + secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed"); + } + } + } +} + +static INITIALIZED: OnceCell = OnceCell::new(); + +fn already_initialized() -> bool { + unsafe { nss::NSS_IsInitialized() != 0 } +} + +fn version_check() { + let min_ver = CString::new(MINIMUM_NSS_VERSION).unwrap(); + assert_ne!( + unsafe { nss::NSS_VersionCheck(min_ver.as_ptr()) }, + 0, + "Minimum NSS version of {} not supported", + MINIMUM_NSS_VERSION, + ); +} + +/// Initialize NSS. This only executes the initialization routines once, so if there is any chance that +pub fn init() { + // Set time zero. + time::init(); + INITIALIZED.get_or_init(|| { + unsafe { + version_check(); + if already_initialized() { + return NssLoaded::External; + } + + secstatus_to_res(nss::NSS_NoDB_Init(null())).expect("NSS_NoDB_Init failed"); + secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); + + NssLoaded::NoDb + } + }); +} + +/// This enables SSLTRACE by calling a simple, harmless function to trigger its +/// side effects. SSLTRACE is not enabled in NSS until a socket is made or +/// global options are accessed. Reading an option is the least impact approach. +/// This allows us to use SSLTRACE in all of our unit tests and programs. +#[cfg(debug_assertions)] +fn enable_ssl_trace() { + let opt = ssl::Opt::Locking.as_int(); + let mut v: ::std::os::raw::c_int = 0; + secstatus_to_res(unsafe { ssl::SSL_OptionGetDefault(opt, &mut v) }) + .expect("SSL_OptionGetDefault failed"); +} + +/// Initialize with a database. +/// # Panics +/// If NSS cannot be initialized. +pub fn init_db>(dir: P) { + time::init(); + INITIALIZED.get_or_init(|| { + unsafe { + version_check(); + if already_initialized() { + return NssLoaded::External; + } + + let path = dir.into(); + assert!(path.is_dir()); + let pathstr = path.to_str().expect("path converts to string").to_string(); + let dircstr = CString::new(pathstr).unwrap(); + let empty = CString::new("").unwrap(); + secstatus_to_res(nss::NSS_Initialize( + dircstr.as_ptr(), + empty.as_ptr(), + empty.as_ptr(), + nss::SECMOD_DB.as_ptr().cast(), + nss::NSS_INIT_READONLY, + )) + .expect("NSS_Initialize failed"); + + secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed"); + secstatus_to_res(ssl::SSL_ConfigServerSessionIDCache( + 1024, + 0, + 0, + dircstr.as_ptr(), + )) + .expect("SSL_ConfigServerSessionIDCache failed"); + + #[cfg(debug_assertions)] + enable_ssl_trace(); + + NssLoaded::Db(path.into_boxed_path()) + } + }); +} + +/// # Panics +/// If NSS isn't initialized. +pub fn assert_initialized() { + INITIALIZED.get().expect("NSS not initialized with init or init_db"); +} diff --git a/third_party/rust/nss-gk-api/src/p11.rs b/third_party/rust/nss-gk-api/src/p11.rs new file mode 100644 index 000000000000..5eb6ea040c10 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/p11.rs @@ -0,0 +1,194 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use crate::err::{secstatus_to_res, Error, Res}; +use crate::util::SECItemMut; + +use pkcs11_bindings::CKA_VALUE; + +use std::convert::TryFrom; +use std::os::raw::{c_int, c_uint}; + +#[must_use] +pub fn hex_with_len(buf: impl AsRef<[u8]>) -> String { + use std::fmt::Write; + let buf = buf.as_ref(); + let mut ret = String::with_capacity(10 + buf.len() * 2); + write!(&mut ret, "[{}]: ", buf.len()).unwrap(); + for b in buf { + write!(&mut ret, "{:02x}", b).unwrap(); + } + ret +} + +#[allow(clippy::upper_case_acronyms)] +#[allow(clippy::unreadable_literal)] +#[allow(unknown_lints, clippy::borrow_as_ptr)] +mod nss_p11 { + use crate::prtypes::*; + use crate::nss_prelude::*; + include!(concat!(env!("OUT_DIR"), "/nss_p11.rs")); +} + +use crate::prtypes::*; +pub use nss_p11::*; + +// Shadow these bindgen created values to correct their type. +pub const SHA256_LENGTH: usize = nss_p11::SHA256_LENGTH as usize; +pub const AES_BLOCK_SIZE: usize = nss_p11::AES_BLOCK_SIZE as usize; + +scoped_ptr!(Certificate, CERTCertificate, CERT_DestroyCertificate); +scoped_ptr!(CertList, CERTCertList, CERT_DestroyCertList); + +scoped_ptr!(SubjectPublicKeyInfo, CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo); + +scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey); +impl_clone!(PublicKey, SECKEY_CopyPublicKey); + +impl PublicKey { + /// Get the HPKE serialization of the public key. + /// + /// # Errors + /// When the key cannot be exported, which can be because the type is not supported. + /// # Panics + /// When keys are too large to fit in `c_uint/usize`. So only on programming error. + pub fn key_data(&self) -> Res> { + let mut buf = vec![0; 100]; + let mut len: c_uint = 0; + secstatus_to_res(unsafe { + PK11_HPKE_Serialize( + **self, + buf.as_mut_ptr(), + &mut len, + c_uint::try_from(buf.len()).unwrap(), + ) + })?; + buf.truncate(usize::try_from(len).unwrap()); + Ok(buf) + } +} + +impl std::fmt::Debug for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.key_data() { + write!(f, "PublicKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque PublicKey") + } + } +} + +scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey); +impl_clone!(PrivateKey, SECKEY_CopyPrivateKey); + +impl PrivateKey { + /// Get the bits of the private key. + /// + /// # Errors + /// When the key cannot be exported, which can be because the type is not supported + /// or because the key data cannot be extracted from the PKCS#11 module. + /// # Panics + /// When the values are too large to fit. So never. + pub fn key_data(&self) -> Res> { + let mut key_item = SECItemMut::make_empty(); + secstatus_to_res(unsafe { + PK11_ReadRawAttribute( + PK11ObjectType::PK11_TypePrivKey, + (**self).cast(), + CKA_VALUE, + key_item.as_mut(), + ) + })?; + Ok(key_item.as_slice().to_owned()) + } +} + +impl std::fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.key_data() { + write!(f, "PrivateKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque PrivateKey") + } + } +} + +scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot); + +impl Slot { + pub fn internal() -> Res { + unsafe { Slot::from_ptr(PK11_GetInternalSlot()) } + } +} + +// Note: PK11SymKey is internally reference counted +scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey); +impl_clone!(SymKey, PK11_ReferenceSymKey); + +impl SymKey { + /// You really don't want to use this. + /// + /// # Errors + /// Internal errors in case of failures in NSS. + pub fn as_bytes(&self) -> Res<&[u8]> { + secstatus_to_res(unsafe { PK11_ExtractKeyValue(**self) })?; + + let key_item = unsafe { PK11_GetKeyData(**self) }; + // This is accessing a value attached to the key, so we can treat this as a borrow. + match unsafe { key_item.as_mut() } { + None => Err(Error::InternalError), + Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }), + } + } +} + +impl std::fmt::Debug for SymKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Ok(b) = self.as_bytes() { + write!(f, "SymKey {}", hex_with_len(b)) + } else { + write!(f, "Opaque SymKey") + } + } +} + +unsafe fn destroy_pk11_context(ctxt: *mut PK11Context) { + PK11_DestroyContext(ctxt, PRBool::from(true)); +} +scoped_ptr!(Context, PK11Context, destroy_pk11_context); + +/// Generate a randomized buffer. +/// # Panics +/// When `size` is too large or NSS fails. +#[must_use] +pub fn random(size: usize) -> Vec { + let mut buf = vec![0; size]; + secstatus_to_res(unsafe { + PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap()) + }) + .unwrap(); + buf +} + +impl_into_result!(SECOidData); + +#[cfg(test)] +mod test { + use super::random; + use test_fixture::fixture_init; + + #[test] + fn randomness() { + fixture_init(); + // If this ever fails, there is either a bug, or it's time to buy a lottery ticket. + assert_ne!(random(16), random(16)); + } +} diff --git a/third_party/rust/nss-gk-api/src/prio.rs b/third_party/rust/nss-gk-api/src/prio.rs new file mode 100644 index 000000000000..8468e08eb385 --- /dev/null +++ b/third_party/rust/nss-gk-api/src/prio.rs @@ -0,0 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] +#![allow( + dead_code, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::empty_enum, + clippy::too_many_lines, + unknown_lints, + clippy::borrow_as_ptr +)] + +use crate::prtypes::*; + +include!(concat!(env!("OUT_DIR"), "/nspr_io.rs")); diff --git a/third_party/rust/nss-gk-api/src/prtypes.rs b/third_party/rust/nss-gk-api/src/prtypes.rs new file mode 100644 index 000000000000..2416ec5ddc5f --- /dev/null +++ b/third_party/rust/nss-gk-api/src/prtypes.rs @@ -0,0 +1,22 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] +#![allow( + dead_code, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::empty_enum, + clippy::too_many_lines, + unknown_lints, + clippy::borrow_as_ptr +)] + +pub use PRStatus_PR_FAILURE as PR_FAILURE; +pub use PRStatus_PR_SUCCESS as PR_SUCCESS; + +include!(concat!(env!("OUT_DIR"), "/nspr_types.rs")); diff --git a/third_party/rust/nss-gk-api/src/ssl.rs b/third_party/rust/nss-gk-api/src/ssl.rs new file mode 100644 index 000000000000..1d74154bc70f --- /dev/null +++ b/third_party/rust/nss-gk-api/src/ssl.rs @@ -0,0 +1,157 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow( + dead_code, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + clippy::cognitive_complexity, + clippy::too_many_lines, + clippy::upper_case_acronyms, + unknown_lints, + clippy::borrow_as_ptr +)] + +use crate::err::{secstatus_to_res, Res}; +use crate::nss_prelude::*; +use crate::prio::PRFileDesc; + +mod nss_ssl { + use crate::err::PRErrorCode; + use crate::nss_prelude::*; + use crate::p11::CERTCertList; + use crate::prio::{ + PRFileDesc, + PRFileInfo, + PRFileInfo64, + PRIOVec, + }; + + include!(concat!(env!("OUT_DIR"), "/nss_ssl.rs")); +} +pub use nss_ssl::*; + +mod SSLOption { + include!(concat!(env!("OUT_DIR"), "/nss_sslopt.rs")); +} + +#[derive(Debug, Copy, Clone)] +pub enum Opt { + Locking, + Tickets, + OcspStapling, + Alpn, + ExtendedMasterSecret, + SignedCertificateTimestamps, + EarlyData, + RecordSizeLimit, + Tls13CompatMode, + HelloDowngradeCheck, + SuppressEndOfEarlyData, +} + +impl Opt { + // Cast is safe here because SSLOptions are within the i32 range + #[allow(clippy::cast_possible_wrap)] + pub(crate) fn as_int(self) -> PRInt32 { + let i = match self { + Self::Locking => SSLOption::SSL_NO_LOCKS, + Self::Tickets => SSLOption::SSL_ENABLE_SESSION_TICKETS, + Self::OcspStapling => SSLOption::SSL_ENABLE_OCSP_STAPLING, + Self::Alpn => SSLOption::SSL_ENABLE_ALPN, + Self::ExtendedMasterSecret => SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET, + Self::SignedCertificateTimestamps => SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, + Self::EarlyData => SSLOption::SSL_ENABLE_0RTT_DATA, + Self::RecordSizeLimit => SSLOption::SSL_RECORD_SIZE_LIMIT, + Self::Tls13CompatMode => SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE, + Self::HelloDowngradeCheck => SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK, + Self::SuppressEndOfEarlyData => SSLOption::SSL_SUPPRESS_END_OF_EARLY_DATA, + }; + i as PRInt32 + } + + // Some options are backwards, like SSL_NO_LOCKS, so use this to manage that. + fn map_enabled(self, enabled: bool) -> PRIntn { + let v = match self { + Self::Locking => !enabled, + _ => enabled, + }; + PRIntn::from(v) + } + + pub(crate) fn set(self, fd: *mut PRFileDesc, value: bool) -> Res<()> { + secstatus_to_res(unsafe { SSL_OptionSet(fd, self.as_int(), self.map_enabled(value)) }) + } +} + +/* + * TODO: these will be moved to a dedicated module + * +experimental_api!(SSL_GetCurrentEpoch( + fd: *mut PRFileDesc, + read_epoch: *mut u16, + write_epoch: *mut u16, +)); +experimental_api!(SSL_HelloRetryRequestCallback( + fd: *mut PRFileDesc, + cb: SSLHelloRetryRequestCallback, + arg: *mut c_void, +)); +experimental_api!(SSL_RecordLayerWriteCallback( + fd: *mut PRFileDesc, + cb: SSLRecordWriteCallback, + arg: *mut c_void, +)); +experimental_api!(SSL_RecordLayerData( + fd: *mut PRFileDesc, + epoch: Epoch, + ct: SSLContentType::Type, + data: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SendSessionTicket( + fd: *mut PRFileDesc, + extra: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SetMaxEarlyDataSize(fd: *mut PRFileDesc, size: u32)); +experimental_api!(SSL_SetResumptionToken( + fd: *mut PRFileDesc, + token: *const u8, + len: c_uint, +)); +experimental_api!(SSL_SetResumptionTokenCallback( + fd: *mut PRFileDesc, + cb: SSLResumptionTokenCallback, + arg: *mut c_void, +)); + +experimental_api!(SSL_GetResumptionTokenInfo( + token: *const u8, + token_len: c_uint, + info: *mut SSLResumptionTokenInfo, + len: c_uint, +)); + +experimental_api!(SSL_DestroyResumptionTokenInfo( + info: *mut SSLResumptionTokenInfo, +)); +*/ + +#[cfg(test)] +mod tests { + use super::{SSL_GetNumImplementedCiphers, SSL_NumImplementedCiphers}; + + #[test] + fn num_ciphers() { + assert!(unsafe { SSL_NumImplementedCiphers } > 0); + assert!(unsafe { SSL_GetNumImplementedCiphers() } > 0); + assert_eq!(unsafe { SSL_NumImplementedCiphers }, unsafe { + SSL_GetNumImplementedCiphers() + }); + } +} diff --git a/third_party/rust/nss-gk-api/src/time.rs b/third_party/rust/nss-gk-api/src/time.rs new file mode 100644 index 000000000000..1abc276dda7d --- /dev/null +++ b/third_party/rust/nss-gk-api/src/time.rs @@ -0,0 +1,255 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::upper_case_acronyms)] + +use crate::nss_prelude::PRInt64; +use crate::err::{Error, Res}; +//use crate::prio::PRFileDesc; +//use crate::ssl::SSLTimeFunc; + +use once_cell::sync::OnceCell; +//use std::boxed::Box; +use std::convert::{TryFrom, TryInto}; +use std::ops::Deref; +//use std::os::raw::c_void; +//use std::pin::Pin; +use std::time::{Duration, Instant}; + +include!(concat!(env!("OUT_DIR"), "/nspr_time.rs")); + +/* TODO move to exp module +experimental_api!(SSL_SetTimeFunc( + fd: *mut PRFileDesc, + cb: SSLTimeFunc, + arg: *mut c_void, +)); +*/ + +/// This struct holds the zero time used for converting between `Instant` and `PRTime`. +#[derive(Debug)] +struct TimeZero { + instant: Instant, + prtime: PRTime, +} + +impl TimeZero { + /// This function sets a baseline from an instance of `Instant`. + /// This allows for the possibility that code that uses these APIs will create + /// instances of `Instant` before any of this code is run. If `Instant`s older than + /// `BASE_TIME` are used with these conversion functions, they will fail. + /// To avoid that, we make sure that this sets the base time using the first value + /// it sees if it is in the past. If it is not, then use `Instant::now()` instead. + pub fn baseline(t: Instant) -> Self { + let now = Instant::now(); + let prnow = unsafe { PR_Now() }; + + if now <= t { + // `t` is in the future, just use `now`. + Self { + instant: now, + prtime: prnow, + } + } else { + let elapsed = Interval::from(now.duration_since(now)); + // An error from these unwrap functions would require + // ridiculously long application running time. + let prelapsed: PRTime = elapsed.try_into().unwrap(); + Self { + instant: t, + prtime: prnow.checked_sub(prelapsed).unwrap(), + } + } + } +} + +static BASE_TIME: OnceCell = OnceCell::new(); + +fn get_base() -> &'static TimeZero { + BASE_TIME.get_or_init(|| TimeZero { + instant: Instant::now(), + prtime: unsafe { PR_Now() }, + }) +} + +pub fn init() { + let _ = get_base(); +} + +/// Time wraps Instant and provides conversion functions into `PRTime`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Time { + t: Instant, +} + +impl Deref for Time { + type Target = Instant; + fn deref(&self) -> &Self::Target { + &self.t + } +} + +impl From for Time { + /// Convert from an Instant into a Time. + fn from(t: Instant) -> Self { + // Call `TimeZero::baseline(t)` so that time zero can be set. + BASE_TIME.get_or_init(|| TimeZero::baseline(t)); + Self { t } + } +} + +impl TryFrom for Time { + type Error = Error; + fn try_from(prtime: PRTime) -> Res { + let base = get_base(); + if let Some(delta) = prtime.checked_sub(base.prtime) { + let d = Duration::from_micros(delta.try_into()?); + base.instant + .checked_add(d) + .map_or(Err(Error::TimeTravelError), |t| Ok(Self { t })) + } else { + Err(Error::TimeTravelError) + } + } +} + +impl TryInto for Time { + type Error = Error; + fn try_into(self) -> Res { + let base = get_base(); + let delta = self + .t + .checked_duration_since(base.instant) + .ok_or(Error::TimeTravelError)?; + if let Ok(d) = PRTime::try_from(delta.as_micros()) { + d.checked_add(base.prtime).ok_or(Error::TimeTravelError) + } else { + Err(Error::TimeTravelError) + } + } +} + +impl From