зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1850025 - vendor authenticator-rs v0.4.0-alpha20. r=keeler,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D186806
This commit is contained in:
Родитель
8eede6849d
Коммит
fba2249b4b
|
@ -296,9 +296,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.19"
|
||||
version = "0.4.0-alpha.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c9595e202b7acb03f6b85630bbd04bb85f220d7fdf406050c5d979df52eb96"
|
||||
checksum = "6552b35e6a39b2e059b8107e45abf2adfa7cd3d20d294f5a315996da38559cbe"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bitflags 1.3.2",
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
authors = ["Martin Sirringhaus", "John Schanck"]
|
||||
|
||||
[dependencies]
|
||||
authenticator = { version = "0.4.0-alpha.19", features = ["gecko"] }
|
||||
authenticator = { version = "0.4.0-alpha.20", features = ["gecko"] }
|
||||
base64 = "^0.21"
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
|
|
|
@ -542,12 +542,10 @@ impl AuthrsTransport {
|
|||
relying_party: RelyingParty {
|
||||
id: relying_party_id.to_string(),
|
||||
name: None,
|
||||
icon: None,
|
||||
},
|
||||
origin: origin.to_string(),
|
||||
user: User {
|
||||
id: user_id.to_vec(),
|
||||
icon: None,
|
||||
name: Some(user_name.to_string()),
|
||||
display_name: None,
|
||||
},
|
||||
|
|
|
@ -673,7 +673,6 @@ impl TestTokenManager {
|
|||
let rp = RelyingParty {
|
||||
id: rp_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
};
|
||||
token.insert_credential(
|
||||
id,
|
||||
|
|
|
@ -57,6 +57,13 @@ user-id = 175410
|
|||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.authenticator]]
|
||||
version = "0.4.0-alpha.20"
|
||||
when = "2023-08-24"
|
||||
user-id = 175410
|
||||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.bhttp]]
|
||||
version = "0.3.1"
|
||||
when = "2023-02-23"
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -39,13 +39,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.19"
|
||||
version = "0.4.0-alpha.20"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64 0.21.2",
|
||||
"base64",
|
||||
"bindgen 0.58.1",
|
||||
"bitflags",
|
||||
"bytes 0.5.6",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"core-foundation",
|
||||
"devd-rs",
|
||||
|
@ -67,8 +67,6 @@ dependencies = [
|
|||
"serde_cbor",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tokio",
|
||||
"warp",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -78,12 +76,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
|
@ -148,22 +140,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "buf_redux"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.5.6"
|
||||
|
@ -173,17 +149,11 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
|
||||
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
|
@ -316,21 +286,6 @@ dependencies = [
|
|||
"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 = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
@ -346,57 +301,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
|
@ -433,62 +337,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"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.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -498,40 +352,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
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"
|
||||
|
@ -547,59 +367,6 @@ 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 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.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
|
@ -620,15 +387,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.136"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
|
@ -678,64 +445,18 @@ 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.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||
dependencies = [
|
||||
"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.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mozbuild"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616"
|
||||
|
||||
[[package]]
|
||||
name = "multipart"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
|
||||
dependencies = [
|
||||
"buf_redux",
|
||||
"httparse",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"quick-error",
|
||||
"rand",
|
||||
"safemem",
|
||||
"tempfile",
|
||||
"twoway",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
|
@ -771,21 +492,11 @@ dependencies = [
|
|||
"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.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
|
@ -832,44 +543,6 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
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"
|
||||
|
@ -881,15 +554,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
|
@ -908,9 +581,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -945,20 +618,11 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -967,18 +631,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
version = "0.6.28"
|
||||
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",
|
||||
]
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
|
@ -1002,33 +657,12 @@ 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 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
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.147"
|
||||
|
@ -1079,40 +713,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
|
@ -1130,25 +730,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -1166,20 +747,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
|
@ -1198,107 +765,6 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
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 = "1.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes 1.2.1",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"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.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
|
@ -1308,126 +774,24 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"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.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
source = "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.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
@ -1446,47 +810,6 @@ 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",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d"
|
||||
dependencies = [
|
||||
"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.11.0+wasi-snapshot-preview1"
|
||||
|
@ -1532,60 +855,3 @@ 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 = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.19"
|
||||
version = "0.4.0-alpha.20"
|
||||
authors = [
|
||||
"J.C. Jones <jc@mozilla.com>",
|
||||
"Tim Taubert <ttaubert@mozilla.com>",
|
||||
|
@ -92,18 +92,6 @@ version = "1.0"
|
|||
[dependencies.sha2]
|
||||
version = "^0.10.0"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.17"
|
||||
features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
]
|
||||
optional = true
|
||||
|
||||
[dependencies.warp]
|
||||
version = "0.3.2"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.assert_matches]
|
||||
version = "1.2"
|
||||
|
||||
|
@ -133,11 +121,6 @@ crypto_openssl = [
|
|||
]
|
||||
default = ["crypto_nss"]
|
||||
gecko = ["nss-gk-api/gecko"]
|
||||
webdriver = [
|
||||
"bytes",
|
||||
"warp",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[target."cfg(target_os = \"freebsd\")".dependencies.devd-rs]
|
||||
version = "0.3"
|
||||
|
|
|
@ -10,13 +10,13 @@ use authenticator::{
|
|||
crypto::COSEAlgorithm,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
RelyingPartyWrapper, ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
statecallback::StateCallback,
|
||||
Pin, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use sha2::{Digest, Sha256};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::sync::mpsc::{channel, RecvError};
|
||||
use std::{env, thread};
|
||||
|
||||
|
@ -31,14 +31,21 @@ fn main() {
|
|||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
|
||||
let rp_id = "example.com".to_string();
|
||||
let app_id = "https://fido.example.com/myApp".to_string();
|
||||
|
||||
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(
|
||||
"a",
|
||||
"app_id",
|
||||
&format!("Using App ID {app_id} from origin 'https://{rp_id}'"),
|
||||
);
|
||||
opts.optflag("s", "hmac_secret", "With hmac-secret");
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
opts.optflag("f", "fallback", "Use CTAP1 fallback implementation");
|
||||
|
@ -53,10 +60,7 @@ fn main() {
|
|||
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
||||
let fallback = matches.opt_present("fallback");
|
||||
|
||||
|
@ -73,14 +77,8 @@ fn main() {
|
|||
};
|
||||
|
||||
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: [u8; 32] = challenge.finalize().into();
|
||||
let mut chall_bytes = [0u8; 32];
|
||||
thread_rng().fill_bytes(&mut chall_bytes);
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
|
@ -143,19 +141,22 @@ fn main() {
|
|||
|
||||
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();
|
||||
// If we're testing AppID support, then register with an RP ID that isn't valid for the origin.
|
||||
let relying_party = RelyingParty {
|
||||
id: if matches.opt_present("app_id") {
|
||||
app_id.clone()
|
||||
} else {
|
||||
rp_id.clone()
|
||||
},
|
||||
name: None,
|
||||
};
|
||||
let ctap_args = RegisterArgs {
|
||||
client_data_hash: chall_bytes,
|
||||
relying_party: RelyingParty {
|
||||
id: "example.com".to_string(),
|
||||
name: None,
|
||||
icon: None,
|
||||
},
|
||||
origin: origin.clone(),
|
||||
relying_party,
|
||||
origin: format!("https://{rp_id}"),
|
||||
user,
|
||||
pub_cred_params: vec![
|
||||
PublicKeyCredentialParameters {
|
||||
|
@ -228,10 +229,11 @@ fn main() {
|
|||
allow_list = Vec::new();
|
||||
}
|
||||
|
||||
let alternate_rp_id = matches.opt_present("app_id").then(|| app_id.clone());
|
||||
let ctap_args = SignArgs {
|
||||
client_data_hash: chall_bytes,
|
||||
origin,
|
||||
relying_party_id: "example.com".to_string(),
|
||||
origin: format!("https://{rp_id}"),
|
||||
relying_party_id: rp_id,
|
||||
allow_list,
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
user_presence_req: true,
|
||||
|
@ -250,7 +252,7 @@ fn main() {
|
|||
},
|
||||
},
|
||||
pin: None,
|
||||
alternate_rp_id: None,
|
||||
alternate_rp_id: alternate_rp_id.clone(),
|
||||
use_ctap1_fallback: fallback,
|
||||
};
|
||||
|
||||
|
@ -272,6 +274,13 @@ fn main() {
|
|||
match sign_result {
|
||||
Ok(assertion_object) => {
|
||||
println!("Assertion Object: {assertion_object:?}");
|
||||
if let Some(alt_rp_id) = alternate_rp_id {
|
||||
assert_eq!(
|
||||
assertion_object.0[0].auth_data.rp_id_hash,
|
||||
RelyingPartyWrapper::from(alt_rp_id.as_str()).hash()
|
||||
);
|
||||
println!("Used AppID");
|
||||
}
|
||||
println!("Done.");
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,6 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
|||
|
||||
let user = User {
|
||||
id: username.as_bytes().to_vec(),
|
||||
icon: None,
|
||||
name: Some(username.to_string()),
|
||||
display_name: None,
|
||||
};
|
||||
|
@ -116,7 +115,6 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
|||
relying_party: RelyingParty {
|
||||
id: "example.com".to_string(),
|
||||
name: None,
|
||||
icon: None,
|
||||
},
|
||||
origin,
|
||||
user,
|
||||
|
@ -173,7 +171,6 @@ fn main() {
|
|||
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",
|
||||
|
@ -193,10 +190,7 @@ fn main() {
|
|||
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 15) {
|
||||
Ok(timeout_s) => {
|
||||
|
|
|
@ -472,7 +472,7 @@ fn ask_user_bio_options(
|
|||
.expect("error: unable to read user input");
|
||||
let name = input.trim().to_string();
|
||||
tx.send(InteractiveRequest::BioEnrollment(
|
||||
BioEnrollmentCmd::ChangeName(chosen_id, name.clone()),
|
||||
BioEnrollmentCmd::ChangeName(chosen_id, name),
|
||||
puat,
|
||||
))
|
||||
.expect("Failed to send GetEnrollments request.");
|
||||
|
@ -742,7 +742,6 @@ fn main() {
|
|||
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",
|
||||
|
@ -761,10 +760,7 @@ fn main() {
|
|||
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 120) {
|
||||
Ok(timeout_s) => {
|
||||
|
|
|
@ -25,7 +25,6 @@ fn main() {
|
|||
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",
|
||||
|
@ -44,10 +43,7 @@ fn main() {
|
|||
|
||||
let mut manager = AuthenticatorService::new()
|
||||
.expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 25) {
|
||||
Ok(timeout_s) => {
|
||||
|
|
|
@ -23,7 +23,6 @@ fn main() {
|
|||
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",
|
||||
|
@ -42,10 +41,7 @@ fn main() {
|
|||
|
||||
let mut manager = AuthenticatorService::new()
|
||||
.expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 25) {
|
||||
Ok(timeout_s) => {
|
||||
|
|
|
@ -136,7 +136,6 @@ fn main() {
|
|||
|
||||
let user = User {
|
||||
id: "user_id".as_bytes().to_vec(),
|
||||
icon: None,
|
||||
name: Some("A. User".to_string()),
|
||||
display_name: None,
|
||||
};
|
||||
|
@ -146,7 +145,6 @@ fn main() {
|
|||
relying_party: RelyingParty {
|
||||
id: "example.com".to_string(),
|
||||
name: None,
|
||||
icon: None,
|
||||
},
|
||||
origin: origin.clone(),
|
||||
user,
|
||||
|
|
|
@ -138,17 +138,6 @@ impl AuthenticatorService {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "webdriver")]
|
||||
pub fn add_webdriver_virtual_bus(&mut self) {
|
||||
match crate::virtualdevices::webdriver::VirtualManager::new() {
|
||||
Ok(token) => {
|
||||
println!("WebDriver ready, listening at {}", &token.url());
|
||||
self.add_transport(Box::new(token));
|
||||
}
|
||||
Err(e) => error!("Could not add WebDriver virtual bus: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
|
@ -456,12 +445,10 @@ mod tests {
|
|||
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,
|
||||
},
|
||||
|
@ -535,12 +522,10 @@ mod tests {
|
|||
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,
|
||||
},
|
||||
|
@ -633,12 +618,10 @@ mod tests {
|
|||
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,
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ use serde::{
|
|||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use serde_bytes::ByteBuf;
|
||||
use serde_cbor::Value;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -404,7 +403,7 @@ impl Serialize for PinUvAuthParam {
|
|||
|
||||
/// A Curve 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)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Curve {
|
||||
// +---------+-------+----------+------------------------------------+
|
||||
// | Name | Value | Key Type | Description |
|
||||
|
@ -433,17 +432,27 @@ pub enum Curve {
|
|||
Ed448 = 7,
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for Curve {
|
||||
impl Serialize for Curve {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TryFrom<i64> for Curve {
|
||||
type Error = CryptoError;
|
||||
fn try_from(i: u64) -> Result<Self, Self::Error> {
|
||||
fn try_from(i: i64) -> Result<Self, Self::Error> {
|
||||
match i {
|
||||
1 => Ok(Curve::SECP256R1),
|
||||
2 => Ok(Curve::SECP384R1),
|
||||
3 => Ok(Curve::SECP521R1),
|
||||
4 => Ok(Curve::X25519),
|
||||
5 => Ok(Curve::X448),
|
||||
6 => Ok(Curve::Ed25519),
|
||||
7 => Ok(Curve::Ed448),
|
||||
i if i == Curve::SECP256R1 as i64 => Ok(Curve::SECP256R1),
|
||||
i if i == Curve::SECP384R1 as i64 => Ok(Curve::SECP384R1),
|
||||
i if i == Curve::SECP521R1 as i64 => Ok(Curve::SECP521R1),
|
||||
i if i == Curve::X25519 as i64 => Ok(Curve::X25519),
|
||||
i if i == Curve::X448 as i64 => Ok(Curve::X448),
|
||||
i if i == Curve::Ed25519 as i64 => Ok(Curve::Ed25519),
|
||||
i if i == Curve::Ed448 as i64 => Ok(Curve::Ed448),
|
||||
_ => Err(CryptoError::UnknownKeyType),
|
||||
}
|
||||
}
|
||||
|
@ -551,71 +560,7 @@ impl Serialize for COSEAlgorithm {
|
|||
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),
|
||||
}
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -651,69 +596,69 @@ impl TryFrom<i64> for COSEAlgorithm {
|
|||
type Error = CryptoError;
|
||||
fn try_from(i: i64) -> Result<Self, Self::Error> {
|
||||
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),
|
||||
i if i == COSEAlgorithm::RS512 as i64 => Ok(COSEAlgorithm::RS512),
|
||||
i if i == COSEAlgorithm::RS384 as i64 => Ok(COSEAlgorithm::RS384),
|
||||
i if i == COSEAlgorithm::RS256 as i64 => Ok(COSEAlgorithm::RS256),
|
||||
i if i == COSEAlgorithm::ES256K as i64 => Ok(COSEAlgorithm::ES256K),
|
||||
i if i == COSEAlgorithm::HSS_LMS as i64 => Ok(COSEAlgorithm::HSS_LMS),
|
||||
i if i == COSEAlgorithm::SHAKE256 as i64 => Ok(COSEAlgorithm::SHAKE256),
|
||||
i if i == COSEAlgorithm::SHA512 as i64 => Ok(COSEAlgorithm::SHA512),
|
||||
i if i == COSEAlgorithm::SHA384 as i64 => Ok(COSEAlgorithm::SHA384),
|
||||
i if i == COSEAlgorithm::RSAES_OAEP_SHA_512 as i64 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_512),
|
||||
i if i == COSEAlgorithm::RSAES_OAEP_SHA_256 as i64 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_256),
|
||||
i if i == COSEAlgorithm::RSAES_OAEP_RFC_8017_default as i64 => Ok(COSEAlgorithm::RSAES_OAEP_RFC_8017_default),
|
||||
i if i == COSEAlgorithm::PS512 as i64 => Ok(COSEAlgorithm::PS512),
|
||||
i if i == COSEAlgorithm::PS384 as i64 => Ok(COSEAlgorithm::PS384),
|
||||
i if i == COSEAlgorithm::PS256 as i64 => Ok(COSEAlgorithm::PS256),
|
||||
i if i == COSEAlgorithm::ES512 as i64 => Ok(COSEAlgorithm::ES512),
|
||||
i if i == COSEAlgorithm::ES384 as i64 => Ok(COSEAlgorithm::ES384),
|
||||
i if i == COSEAlgorithm::ECDH_SS_A256KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A256KW),
|
||||
i if i == COSEAlgorithm::ECDH_SS_A192KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A192KW),
|
||||
i if i == COSEAlgorithm::ECDH_SS_A128KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A128KW),
|
||||
i if i == COSEAlgorithm::ECDH_ES_A256KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A256KW),
|
||||
i if i == COSEAlgorithm::ECDH_ES_A192KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A192KW),
|
||||
i if i == COSEAlgorithm::ECDH_ES_A128KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A128KW),
|
||||
i if i == COSEAlgorithm::ECDH_SS_HKDF512 as i64 => Ok(COSEAlgorithm::ECDH_SS_HKDF512),
|
||||
i if i == COSEAlgorithm::ECDH_SS_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_SS_HKDF256),
|
||||
i if i == COSEAlgorithm::ECDH_ES_HKDF512 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF512),
|
||||
i if i == COSEAlgorithm::ECDH_ES_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF256),
|
||||
i if i == COSEAlgorithm::SHAKE128 as i64 => Ok(COSEAlgorithm::SHAKE128),
|
||||
i if i == COSEAlgorithm::SHA512_256 as i64 => Ok(COSEAlgorithm::SHA512_256),
|
||||
i if i == COSEAlgorithm::SHA256 as i64 => Ok(COSEAlgorithm::SHA256),
|
||||
i if i == COSEAlgorithm::SHA256_64 as i64 => Ok(COSEAlgorithm::SHA256_64),
|
||||
i if i == COSEAlgorithm::SHA1 as i64 => Ok(COSEAlgorithm::SHA1),
|
||||
i if i == COSEAlgorithm::Direct_HKDF_AES256 as i64 => Ok(COSEAlgorithm::Direct_HKDF_AES256),
|
||||
i if i == COSEAlgorithm::Direct_HKDF_AES128 as i64 => Ok(COSEAlgorithm::Direct_HKDF_AES128),
|
||||
i if i == COSEAlgorithm::Direct_HKDF_SHA512 as i64 => Ok(COSEAlgorithm::Direct_HKDF_SHA512),
|
||||
i if i == COSEAlgorithm::Direct_HKDF_SHA256 as i64 => Ok(COSEAlgorithm::Direct_HKDF_SHA256),
|
||||
i if i == COSEAlgorithm::EDDSA as i64 => Ok(COSEAlgorithm::EDDSA),
|
||||
i if i == COSEAlgorithm::ES256 as i64 => Ok(COSEAlgorithm::ES256),
|
||||
i if i == COSEAlgorithm::Direct as i64 => Ok(COSEAlgorithm::Direct),
|
||||
i if i == COSEAlgorithm::A256KW as i64 => Ok(COSEAlgorithm::A256KW),
|
||||
i if i == COSEAlgorithm::A192KW as i64 => Ok(COSEAlgorithm::A192KW),
|
||||
i if i == COSEAlgorithm::A128KW as i64 => Ok(COSEAlgorithm::A128KW),
|
||||
i if i == COSEAlgorithm::A128GCM as i64 => Ok(COSEAlgorithm::A128GCM),
|
||||
i if i == COSEAlgorithm::A192GCM as i64 => Ok(COSEAlgorithm::A192GCM),
|
||||
i if i == COSEAlgorithm::A256GCM as i64 => Ok(COSEAlgorithm::A256GCM),
|
||||
i if i == COSEAlgorithm::HMAC256_64 as i64 => Ok(COSEAlgorithm::HMAC256_64),
|
||||
i if i == COSEAlgorithm::HMAC256_256 as i64 => Ok(COSEAlgorithm::HMAC256_256),
|
||||
i if i == COSEAlgorithm::HMAC384_384 as i64 => Ok(COSEAlgorithm::HMAC384_384),
|
||||
i if i == COSEAlgorithm::HMAC512_512 as i64 => Ok(COSEAlgorithm::HMAC512_512),
|
||||
i if i == COSEAlgorithm::AES_CCM_16_64_128 as i64 => Ok(COSEAlgorithm::AES_CCM_16_64_128),
|
||||
i if i == COSEAlgorithm::AES_CCM_16_64_256 as i64 => Ok(COSEAlgorithm::AES_CCM_16_64_256),
|
||||
i if i == COSEAlgorithm::AES_CCM_64_64_128 as i64 => Ok(COSEAlgorithm::AES_CCM_64_64_128),
|
||||
i if i == COSEAlgorithm::AES_CCM_64_64_256 as i64 => Ok(COSEAlgorithm::AES_CCM_64_64_256),
|
||||
i if i == COSEAlgorithm::AES_MAC_128_64 as i64 => Ok(COSEAlgorithm::AES_MAC_128_64),
|
||||
i if i == COSEAlgorithm::AES_MAC_256_64 as i64 => Ok(COSEAlgorithm::AES_MAC_256_64),
|
||||
i if i == COSEAlgorithm::ChaCha20_Poly1305 as i64 => Ok(COSEAlgorithm::ChaCha20_Poly1305),
|
||||
i if i == COSEAlgorithm::AES_MAC_128_128 as i64 => Ok(COSEAlgorithm::AES_MAC_128_128),
|
||||
i if i == COSEAlgorithm::AES_MAC_256_128 as i64 => Ok(COSEAlgorithm::AES_MAC_256_128),
|
||||
i if i == COSEAlgorithm::AES_CCM_16_128_128 as i64 => Ok(COSEAlgorithm::AES_CCM_16_128_128),
|
||||
i if i == COSEAlgorithm::AES_CCM_16_128_256 as i64 => Ok(COSEAlgorithm::AES_CCM_16_128_256),
|
||||
i if i == COSEAlgorithm::AES_CCM_64_128_128 as i64 => Ok(COSEAlgorithm::AES_CCM_64_128_128),
|
||||
i if i == COSEAlgorithm::AES_CCM_64_128_256 as i64 => Ok(COSEAlgorithm::AES_CCM_64_128_256),
|
||||
i if i == COSEAlgorithm::IV_GENERATION as i64 => Ok(COSEAlgorithm::IV_GENERATION),
|
||||
i if i == COSEAlgorithm::INSECURE_RS1 as i64 => Ok(COSEAlgorithm::INSECURE_RS1),
|
||||
_ => Err(CryptoError::UnknownAlgorithm),
|
||||
}
|
||||
}
|
||||
|
@ -750,7 +695,7 @@ impl COSEEC2Key {
|
|||
})
|
||||
}
|
||||
|
||||
fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
|
||||
pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
|
||||
let (curve_oid, seq_len, alg_len, spk_len) = match self.curve {
|
||||
Curve::SECP256R1 => (
|
||||
DER_OID_P256_BYTES,
|
||||
|
@ -796,10 +741,7 @@ pub struct COSEOKPKey {
|
|||
pub x: Vec<u8>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// A COSE RSA PublicKey. This is a provided credential from a registered authenticator.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct COSERSAKey {
|
||||
/// An RSA modulus
|
||||
|
@ -808,19 +750,9 @@ pub struct COSERSAKey {
|
|||
pub e: Vec<u8>,
|
||||
}
|
||||
|
||||
/// 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<u8>,
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc8152#section-13
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[repr(i64)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum COSEKeyTypeId {
|
||||
// Reserved is invalid
|
||||
// Reserved = 0,
|
||||
|
@ -830,18 +762,24 @@ pub enum COSEKeyTypeId {
|
|||
EC2 = 2,
|
||||
/// RSA
|
||||
RSA = 3,
|
||||
/// Symmetric
|
||||
Symmetric = 4,
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for COSEKeyTypeId {
|
||||
impl Serialize for COSEKeyTypeId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i64> for COSEKeyTypeId {
|
||||
type Error = CryptoError;
|
||||
fn try_from(i: u64) -> Result<Self, Self::Error> {
|
||||
fn try_from(i: i64) -> Result<Self, Self::Error> {
|
||||
match i {
|
||||
1 => Ok(COSEKeyTypeId::OKP),
|
||||
2 => Ok(COSEKeyTypeId::EC2),
|
||||
3 => Ok(COSEKeyTypeId::RSA),
|
||||
4 => Ok(COSEKeyTypeId::Symmetric),
|
||||
i if i == COSEKeyTypeId::OKP as i64 => Ok(COSEKeyTypeId::OKP),
|
||||
i if i == COSEKeyTypeId::EC2 as i64 => Ok(COSEKeyTypeId::EC2),
|
||||
i if i == COSEKeyTypeId::RSA as i64 => Ok(COSEKeyTypeId::RSA),
|
||||
_ => Err(CryptoError::UnknownKeyType),
|
||||
}
|
||||
}
|
||||
|
@ -852,24 +790,12 @@ impl TryFrom<u64> for COSEKeyTypeId {
|
|||
#[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
|
||||
RSA(COSERSAKey),
|
||||
}
|
||||
|
||||
/// A COSE Key as provided by the Authenticator. You should never need
|
||||
|
@ -918,60 +844,29 @@ impl<'de> Deserialize<'de> for COSEKey {
|
|||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut curve: Option<Curve> = None;
|
||||
let mut key_type: Option<COSEKeyTypeId> = None;
|
||||
let mut alg: Option<COSEAlgorithm> = None;
|
||||
// OKP / EC2
|
||||
let mut curve: Option<Curve> = None;
|
||||
let mut x: Option<Vec<u8>> = None;
|
||||
let mut y: Option<Vec<u8>> = None;
|
||||
|
||||
// RSA specific
|
||||
let mut n: Option<Vec<u8>> = None;
|
||||
let mut e: Option<Vec<u8>> = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
trace!("cose key {:?}", key);
|
||||
// See https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters
|
||||
match key {
|
||||
1 => {
|
||||
if key_type.is_some() {
|
||||
return Err(SerdeError::duplicate_field("key_type"));
|
||||
}
|
||||
let value: u64 = map.next_value()?;
|
||||
let value: i64 = 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_else(|| 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 = Curve::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() {
|
||||
|
@ -982,38 +877,77 @@ impl<'de> Deserialize<'de> for COSEKey {
|
|||
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);
|
||||
-1 => match key_type {
|
||||
None => return Err(SerdeError::missing_field("key_type")),
|
||||
Some(COSEKeyTypeId::OKP) | Some(COSEKeyTypeId::EC2) => {
|
||||
if curve.is_some() {
|
||||
return Err(SerdeError::duplicate_field("curve"));
|
||||
}
|
||||
let value: i64 = map.next_value()?;
|
||||
let val = Curve::try_from(value).map_err(|_| {
|
||||
SerdeError::custom(format!("unsupported curve {value}"))
|
||||
})?;
|
||||
curve = Some(val);
|
||||
}
|
||||
Some(COSEKeyTypeId::RSA) => {
|
||||
if n.is_some() {
|
||||
return Err(SerdeError::duplicate_field("n"));
|
||||
}
|
||||
let value: ByteBuf = map.next_value()?;
|
||||
n = Some(value.to_vec());
|
||||
}
|
||||
},
|
||||
-2 => match key_type {
|
||||
None => return Err(SerdeError::missing_field("key_type")),
|
||||
Some(COSEKeyTypeId::OKP) | Some(COSEKeyTypeId::EC2) => {
|
||||
if x.is_some() {
|
||||
return Err(SerdeError::duplicate_field("x"));
|
||||
}
|
||||
let value: ByteBuf = map.next_value()?;
|
||||
x = Some(value.to_vec());
|
||||
}
|
||||
Some(COSEKeyTypeId::RSA) => {
|
||||
if e.is_some() {
|
||||
return Err(SerdeError::duplicate_field("e"));
|
||||
}
|
||||
let value: ByteBuf = map.next_value()?;
|
||||
e = Some(value.to_vec());
|
||||
}
|
||||
},
|
||||
-3 if key_type == Some(COSEKeyTypeId::EC2) => {
|
||||
if y.is_some() {
|
||||
return Err(SerdeError::duplicate_field("y"));
|
||||
}
|
||||
let value: ByteBuf = map.next_value()?;
|
||||
y = Some(value.to_vec());
|
||||
}
|
||||
other => {
|
||||
return Err(SerdeError::custom(format!("unexpected field: {other}")));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let key_type = key_type.ok_or_else(|| SerdeError::missing_field("key_type"))?;
|
||||
let x = x.ok_or_else(|| SerdeError::missing_field("x"))?;
|
||||
let alg = alg.ok_or_else(|| SerdeError::missing_field("alg"))?;
|
||||
let key_type = key_type.ok_or_else(|| SerdeError::missing_field("key_type (1)"))?;
|
||||
let alg = alg.ok_or_else(|| SerdeError::missing_field("alg (3)"))?;
|
||||
|
||||
let res = match key_type {
|
||||
COSEKeyTypeId::OKP => {
|
||||
let curve = curve.ok_or_else(|| SerdeError::missing_field("curve"))?;
|
||||
let curve = curve.ok_or_else(|| SerdeError::missing_field("curve (-1)"))?;
|
||||
let x = x.ok_or_else(|| SerdeError::missing_field("x (-2)"))?;
|
||||
COSEKeyType::OKP(COSEOKPKey { curve, x })
|
||||
}
|
||||
COSEKeyTypeId::EC2 => {
|
||||
let curve = curve.ok_or_else(|| SerdeError::missing_field("curve"))?;
|
||||
let y = y.ok_or_else(|| SerdeError::missing_field("y"))?;
|
||||
let curve = curve.ok_or_else(|| SerdeError::missing_field("curve (-1)"))?;
|
||||
let x = x.ok_or_else(|| SerdeError::missing_field("x (-2)"))?;
|
||||
let y = y.ok_or_else(|| SerdeError::missing_field("y (-3)"))?;
|
||||
COSEKeyType::EC2(COSEEC2Key { curve, x, y })
|
||||
}
|
||||
COSEKeyTypeId::RSA => {
|
||||
let e = y.ok_or_else(|| SerdeError::missing_field("y"))?;
|
||||
COSEKeyType::RSA(COSERSAKey { e, n: x })
|
||||
let n = n.ok_or_else(|| SerdeError::missing_field("n (-1)"))?;
|
||||
let e = e.ok_or_else(|| SerdeError::missing_field("e (-2)"))?;
|
||||
COSEKeyType::RSA(COSERSAKey { e, n })
|
||||
}
|
||||
COSEKeyTypeId::Symmetric => COSEKeyType::Symmetric(COSESymmetricKey { key: x }),
|
||||
};
|
||||
Ok(COSEKey { alg, key: res })
|
||||
}
|
||||
|
@ -1032,7 +966,6 @@ impl Serialize for COSEKey {
|
|||
COSEKeyType::OKP(_) => 3,
|
||||
COSEKeyType::EC2(_) => 5,
|
||||
COSEKeyType::RSA(_) => 4,
|
||||
COSEKeyType::Symmetric(_) => 3,
|
||||
};
|
||||
let mut map = serializer.serialize_map(Some(map_len))?;
|
||||
match &self.key {
|
||||
|
@ -1040,25 +973,20 @@ impl Serialize for COSEKey {
|
|||
map.serialize_entry(&1, &COSEKeyTypeId::OKP)?;
|
||||
map.serialize_entry(&3, &self.alg)?;
|
||||
map.serialize_entry(&-1, &key.curve)?;
|
||||
map.serialize_entry(&-2, &key.x)?;
|
||||
map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.x))?;
|
||||
}
|
||||
COSEKeyType::EC2(key) => {
|
||||
map.serialize_entry(&1, &(COSEKeyTypeId::EC2 as u8))?;
|
||||
map.serialize_entry(&1, &COSEKeyTypeId::EC2)?;
|
||||
map.serialize_entry(&3, &self.alg)?;
|
||||
map.serialize_entry(&-1, &(key.curve as u8))?;
|
||||
map.serialize_entry(&-1, &key.curve)?;
|
||||
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.serialize_entry(&-1, &serde_bytes::Bytes::new(&key.n))?;
|
||||
map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.e))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1169,7 +1097,7 @@ mod test {
|
|||
Curve, PinProtocolImpl, PinUvAuth1, PinUvAuth2, PinUvAuthProtocol, PublicInputs,
|
||||
SharedSecret,
|
||||
};
|
||||
use crate::crypto::{COSEEC2Key, COSEKeyType};
|
||||
use crate::crypto::{COSEEC2Key, COSERSAKey, COSEKeyType};
|
||||
use crate::ctap2::attestation::AAGuid;
|
||||
use crate::ctap2::commands::client_pin::Pin;
|
||||
use crate::ctap2::commands::get_info::{
|
||||
|
@ -1180,7 +1108,64 @@ mod test {
|
|||
use serde_cbor::de::from_slice;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_key() {
|
||||
fn test_serialize_rsa_key() {
|
||||
let data: [u8; 272] = [
|
||||
0xa4, 0x01, 0x03, 0x03, 0x39, 0x01, 0x00, 0x20, 0x59, 0x01, 0x00, 0xd4, 0xd2, 0x53,
|
||||
0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1, 0x8f, 0x89,
|
||||
0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27, 0xa7,
|
||||
0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f,
|
||||
0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc,
|
||||
0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d, 0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77,
|
||||
0xe5, 0xd1, 0x84, 0xa1, 0x40, 0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30,
|
||||
0x44, 0xdc, 0x68, 0xbd, 0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e,
|
||||
0x3c, 0xa5, 0x3a, 0x1d, 0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d,
|
||||
0x8a, 0x5e, 0xbe, 0x12, 0xbd, 0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71,
|
||||
0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1, 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30,
|
||||
0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60,
|
||||
0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda,
|
||||
0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4, 0xcb, 0xac, 0xd5, 0xee, 0x84,
|
||||
0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49, 0xea, 0x45, 0xd4, 0xa1,
|
||||
0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a, 0x99, 0x41, 0x20, 0x5e, 0x1a,
|
||||
0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc,
|
||||
0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89,
|
||||
0x9b, 0x89, 0x32, 0x81, 0x89, 0xed, 0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a,
|
||||
0xa5, 0x21, 0x43, 0x01, 0x00, 0x01
|
||||
];
|
||||
let expected: COSEKey = COSEKey {
|
||||
alg: COSEAlgorithm::RS256,
|
||||
key: COSEKeyType::RSA(COSERSAKey {
|
||||
e: vec![1, 0, 1],
|
||||
n: vec![
|
||||
0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c,
|
||||
0x51, 0xb1, 0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7,
|
||||
0xb0, 0x4f, 0xe7, 0x27, 0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40,
|
||||
0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f, 0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc,
|
||||
0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d,
|
||||
0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1, 0x84, 0xa1, 0x40,
|
||||
0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68, 0xbd,
|
||||
0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a,
|
||||
0x1d, 0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e,
|
||||
0xbe, 0x12, 0xbd, 0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72,
|
||||
0x6d, 0xd2, 0xcb, 0x37, 0xb1, 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30,
|
||||
0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25,
|
||||
0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2,
|
||||
0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4, 0xcb, 0xac,
|
||||
0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49,
|
||||
0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a,
|
||||
0x99, 0x41, 0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98,
|
||||
0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37,
|
||||
0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed,
|
||||
0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a, 0xa5
|
||||
],
|
||||
}),
|
||||
};
|
||||
let actual: COSEKey = from_slice(&data).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(&data[..], &serde_cbor::to_vec(&actual).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_ec2_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,
|
||||
|
|
|
@ -205,6 +205,15 @@ pub struct AuthenticatorData {
|
|||
pub extensions: Extension,
|
||||
}
|
||||
|
||||
impl AuthenticatorData {
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
match serde_cbor::value::to_value(self) {
|
||||
Ok(serde_cbor::value::Value::Bytes(out)) => out,
|
||||
_ => unreachable!(), // Serialize is guaranteed to produce bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AuthenticatorData {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
@ -353,6 +362,15 @@ pub enum AttestationStatement {
|
|||
Packed(AttestationStatementPacked),
|
||||
#[serde(rename = "fido-u2f")]
|
||||
FidoU2F(AttestationStatementFidoU2F),
|
||||
// The remaining attestation statement formats are deserialized as serde_cbor::Values---we do
|
||||
// not perform any validation of their contents. These are expected to be used primarily when
|
||||
// anonymizing attestation objects that contain attestation statements in these formats.
|
||||
#[serde(rename = "android-key")]
|
||||
AndroidKey(serde_cbor::Value),
|
||||
#[serde(rename = "android-safetynet")]
|
||||
AndroidSafetyNet(serde_cbor::Value),
|
||||
Apple(serde_cbor::Value),
|
||||
Tpm(serde_cbor::Value),
|
||||
}
|
||||
|
||||
// AttestationStatement::None is serialized as the empty map. We need to enforce
|
||||
|
@ -497,6 +515,22 @@ impl Serialize for AttestationObject {
|
|||
map.serialize_entry(&"fmt", &"fido-u2f")?; // (1) "fmt"
|
||||
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
|
||||
}
|
||||
AttestationStatement::AndroidKey(ref v) => {
|
||||
map.serialize_entry(&"fmt", &"android-key")?; // (1) "fmt"
|
||||
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
|
||||
}
|
||||
AttestationStatement::AndroidSafetyNet(ref v) => {
|
||||
map.serialize_entry(&"fmt", &"android-safetynet")?; // (1) "fmt"
|
||||
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
|
||||
}
|
||||
AttestationStatement::Apple(ref v) => {
|
||||
map.serialize_entry(&"fmt", &"apple")?; // (1) "fmt"
|
||||
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
|
||||
}
|
||||
AttestationStatement::Tpm(ref v) => {
|
||||
map.serialize_entry(&"fmt", &"tpm")?; // (1) "fmt"
|
||||
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
|
||||
}
|
||||
}
|
||||
map.serialize_entry(&"authData", &self.auth_data)?; // (3) "authData"
|
||||
map.end()
|
||||
|
|
|
@ -168,9 +168,6 @@ pub struct GetAssertion {
|
|||
pub extensions: GetAssertionExtensions,
|
||||
pub options: GetAssertionOptions,
|
||||
pub pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
|
||||
// This is used to implement the FIDO AppID extension.
|
||||
pub alternate_rp_id: Option<String>,
|
||||
}
|
||||
|
||||
impl GetAssertion {
|
||||
|
@ -180,7 +177,6 @@ impl GetAssertion {
|
|||
allow_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
options: GetAssertionOptions,
|
||||
extensions: GetAssertionExtensions,
|
||||
alternate_rp_id: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
client_data_hash,
|
||||
|
@ -189,7 +185,6 @@ impl GetAssertion {
|
|||
extensions,
|
||||
options,
|
||||
pin_uv_auth_param: None,
|
||||
alternate_rp_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -620,7 +615,6 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
vec![PublicKeyCredentialDescriptor {
|
||||
id: vec![
|
||||
|
@ -637,7 +631,6 @@ pub mod test {
|
|||
user_verification: None,
|
||||
},
|
||||
Default::default(),
|
||||
None,
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
|
@ -691,7 +684,7 @@ pub mod test {
|
|||
|
||||
// fido response
|
||||
let mut msg = cid.to_vec();
|
||||
msg.extend([HIDCmd::Cbor.into(), 0x1, 0x5c]); // cmd + bcnt
|
||||
msg.extend([HIDCmd::Cbor.into(), 0x1, 0x2a]); // cmd + bcnt
|
||||
msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[..57]);
|
||||
device.add_read(&msg, 0);
|
||||
|
||||
|
@ -756,7 +749,6 @@ pub mod test {
|
|||
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()),
|
||||
}),
|
||||
|
@ -830,7 +822,6 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
vec![allowed_key.clone()],
|
||||
GetAssertionOptions {
|
||||
|
@ -838,7 +829,6 @@ pub mod test {
|
|||
user_verification: None,
|
||||
},
|
||||
Default::default(),
|
||||
None,
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
// channel id
|
||||
|
@ -921,7 +911,6 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
vec![too_long_key_handle.clone()],
|
||||
GetAssertionOptions {
|
||||
|
@ -929,7 +918,6 @@ pub mod test {
|
|||
user_verification: None,
|
||||
},
|
||||
Default::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
|
@ -1060,7 +1048,6 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
vec![
|
||||
// This should never be tested, because it gets pre-filtered, since it is too long
|
||||
|
@ -1111,7 +1098,6 @@ pub mod test {
|
|||
user_verification: None,
|
||||
},
|
||||
Default::default(),
|
||||
None,
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
|
@ -1160,7 +1146,7 @@ pub mod test {
|
|||
|
||||
// Sending first GetAssertion with first allow_list-entry, that will return an error
|
||||
let mut msg = cid.to_vec();
|
||||
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x94]);
|
||||
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x90]);
|
||||
msg.extend(vec![0x2]); // u2f command
|
||||
msg.extend(vec![
|
||||
0xa4, // map(4)
|
||||
|
@ -1196,10 +1182,7 @@ pub mod test {
|
|||
0x6a, // text(10)
|
||||
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // public-key
|
||||
0x5, // options
|
||||
0xa2, // map(2)
|
||||
0x62, // text(2)
|
||||
0x75, 0x76, // uv
|
||||
0xf4, // false
|
||||
0xa1, // map(1)
|
||||
0x62, // text(2)
|
||||
0x75, 0x70, // up
|
||||
0xf4, // false
|
||||
|
@ -1215,7 +1198,7 @@ pub mod test {
|
|||
|
||||
// Sending second GetAssertion with first allow_list-entry, that will return a success
|
||||
let mut msg = cid.to_vec();
|
||||
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x94]);
|
||||
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x90]);
|
||||
msg.extend(vec![0x2]); // u2f command
|
||||
msg.extend(vec![
|
||||
0xa4, // map(4)
|
||||
|
@ -1251,10 +1234,7 @@ pub mod test {
|
|||
0x6a, // text(10)
|
||||
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // public-key
|
||||
0x5, // options
|
||||
0xa2, // map(2)
|
||||
0x62, // text(2)
|
||||
0x75, 0x76, // uv
|
||||
0xf4, // false
|
||||
0xa1, // map(1)
|
||||
0x62, // text(2)
|
||||
0x75, 0x70, // up
|
||||
0xf4, // false
|
||||
|
@ -1262,7 +1242,7 @@ pub mod test {
|
|||
device.add_write(&msg, 0);
|
||||
|
||||
let mut msg = cid.to_vec();
|
||||
msg.extend([HIDCmd::Cbor.into(), 0x1, 0x5c]); // cmd + bcnt
|
||||
msg.extend([HIDCmd::Cbor.into(), 0x1, 0x2a]); // cmd + bcnt
|
||||
msg.extend(&GET_ASSERTION_SAMPLE_RESPONSE_CTAP2[..57]);
|
||||
device.add_read(&msg, 0);
|
||||
|
||||
|
@ -1432,7 +1412,7 @@ pub mod test {
|
|||
0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E,
|
||||
];
|
||||
|
||||
const GET_ASSERTION_SAMPLE_RESPONSE_CTAP2: [u8; 348] = [
|
||||
const GET_ASSERTION_SAMPLE_RESPONSE_CTAP2: [u8; 298] = [
|
||||
0x00, // status == success
|
||||
0xA5, // map(5)
|
||||
0x01, // unsigned(1)
|
||||
|
@ -1462,7 +1442,7 @@ pub mod test {
|
|||
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, // end: signature
|
||||
0x04, // unsigned(4)
|
||||
0xA4, // map(4)
|
||||
0xA3, // map(3)
|
||||
0x62, // text(2)
|
||||
0x69, 0x64, // "id"
|
||||
0x58, 0x20, // bytes(0x32, ) user_id
|
||||
|
@ -1470,13 +1450,6 @@ pub mod test {
|
|||
0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93,
|
||||
0x30, 0x82, // end: user_id
|
||||
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,
|
||||
|
|
|
@ -563,13 +563,11 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
Some(User {
|
||||
id: base64::engine::general_purpose::URL_SAFE
|
||||
.decode("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=")
|
||||
.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")),
|
||||
}),
|
||||
|
@ -621,13 +619,11 @@ pub mod test {
|
|||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("example.com"),
|
||||
name: Some(String::from("Acme")),
|
||||
icon: None,
|
||||
}),
|
||||
Some(User {
|
||||
id: base64::engine::general_purpose::URL_SAFE
|
||||
.decode("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=")
|
||||
.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")),
|
||||
}),
|
||||
|
@ -837,7 +833,7 @@ pub mod test {
|
|||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP2: [u8; 260] = [
|
||||
pub const MAKE_CREDENTIALS_SAMPLE_REQUEST_CTAP2: [u8; 210] = [
|
||||
// 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))
|
||||
|
@ -858,20 +854,13 @@ pub mod test {
|
|||
0x64, // text(4)
|
||||
0x41, 0x63, 0x6d, 0x65, // "Acme"
|
||||
0x03, // unsigned(3) - user
|
||||
0xa4, // map(4)
|
||||
0xa3, // map(3)
|
||||
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)
|
||||
|
|
|
@ -32,9 +32,10 @@ use crate::ctap2::commands::{
|
|||
};
|
||||
use crate::ctap2::preflight::{
|
||||
do_credential_list_filtering_ctap1, do_credential_list_filtering_ctap2,
|
||||
silently_discover_credentials,
|
||||
};
|
||||
use crate::ctap2::server::{
|
||||
RelyingParty, RelyingPartyWrapper, ResidentKeyRequirement, UserVerificationRequirement,
|
||||
RelyingPartyWrapper, ResidentKeyRequirement, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::{AuthenticatorError, UnsupportedOption};
|
||||
use crate::statecallback::StateCallback;
|
||||
|
@ -138,12 +139,11 @@ macro_rules! handle_errors {
|
|||
};
|
||||
}
|
||||
|
||||
fn ask_user_for_pin<U>(
|
||||
fn ask_user_for_pin(
|
||||
was_invalid: bool,
|
||||
retries: Option<u8>,
|
||||
status: &Sender<StatusUpdate>,
|
||||
callback: &StateCallback<crate::Result<U>>,
|
||||
) -> Result<Pin, ()> {
|
||||
) -> Result<Pin, AuthenticatorError> {
|
||||
info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply");
|
||||
let (tx, rx) = channel();
|
||||
if was_invalid {
|
||||
|
@ -162,8 +162,7 @@ fn ask_user_for_pin<U>(
|
|||
Err(RecvError) => {
|
||||
// recv() can only fail, if the other side is dropping the Sender.
|
||||
info!("Callback dropped the channel. Aborting.");
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
Err(())
|
||||
Err(AuthenticatorError::CancelledByUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,17 +290,16 @@ fn get_pin_uv_auth_param<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
|
|||
/// Handles asking the user for a PIN, if needed and sending StatusUpdates
|
||||
/// regarding PIN and UV usage.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2, U>(
|
||||
fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
|
||||
cmd: &mut T,
|
||||
dev: &mut Dev,
|
||||
mut skip_uv: bool,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
uv_req: UserVerificationRequirement,
|
||||
status: &Sender<StatusUpdate>,
|
||||
callback: &StateCallback<crate::Result<U>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
pin: &mut Option<Pin>,
|
||||
) -> Result<PinUvAuthResult, ()> {
|
||||
) -> Result<PinUvAuthResult, AuthenticatorError> {
|
||||
while alive() {
|
||||
debug!("-----------------------------------------------------------------");
|
||||
debug!("Getting pinUvAuthParam");
|
||||
|
@ -311,21 +309,15 @@ fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2,
|
|||
}
|
||||
|
||||
Err(AuthenticatorError::PinError(PinError::PinRequired)) => {
|
||||
if let Ok(new_pin) = ask_user_for_pin(false, None, status, callback) {
|
||||
*pin = Some(new_pin);
|
||||
skip_uv = true;
|
||||
continue;
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
let new_pin = ask_user_for_pin(false, None, status)?;
|
||||
*pin = Some(new_pin);
|
||||
skip_uv = true;
|
||||
continue;
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::InvalidPin(retries))) => {
|
||||
if let Ok(new_pin) = ask_user_for_pin(true, retries, status, callback) {
|
||||
*pin = Some(new_pin);
|
||||
continue;
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
let new_pin = ask_user_for_pin(true, retries, status)?;
|
||||
*pin = Some(new_pin);
|
||||
continue;
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::InvalidUv(retries))) => {
|
||||
if retries == Some(0) {
|
||||
|
@ -342,20 +334,17 @@ fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2,
|
|||
StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked),
|
||||
);
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
return Err(e);
|
||||
}
|
||||
Err(e @ AuthenticatorError::PinError(PinError::PinBlocked)) => {
|
||||
send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinBlocked));
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
return Err(e);
|
||||
}
|
||||
Err(e @ AuthenticatorError::PinError(PinError::PinNotSet)) => {
|
||||
send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinNotSet));
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
return Err(e);
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::UvBlocked)) => {
|
||||
skip_uv = true;
|
||||
|
@ -371,12 +360,11 @@ fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2,
|
|||
}
|
||||
Err(e) => {
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
Err(AuthenticatorError::CancelledByUser)
|
||||
}
|
||||
|
||||
pub fn register<Dev: FidoDevice>(
|
||||
|
@ -453,23 +441,19 @@ pub fn register<Dev: FidoDevice>(
|
|||
let permissions =
|
||||
PinUvAuthTokenPermission::MakeCredential | PinUvAuthTokenPermission::GetAssertion;
|
||||
|
||||
let pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut makecred,
|
||||
dev,
|
||||
skip_uv,
|
||||
permissions,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
&mut pin,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let pin_uv_auth_result = unwrap_result!(
|
||||
determine_puap_if_needed(
|
||||
&mut makecred,
|
||||
dev,
|
||||
skip_uv,
|
||||
permissions,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
alive,
|
||||
&mut pin,
|
||||
),
|
||||
callback
|
||||
);
|
||||
// Do "pre-flight": Filter the exclude-list
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
makecred.exclude_list = unwrap_result!(
|
||||
|
@ -548,42 +532,53 @@ pub fn sign<Dev: FidoDevice>(
|
|||
}
|
||||
}
|
||||
|
||||
let mut allow_list = args.allow_list;
|
||||
let mut rp_id = args.relying_party_id;
|
||||
let client_data_hash = ClientDataHash(args.client_data_hash);
|
||||
if let Some(alt_rp_id) = args.alternate_rp_id {
|
||||
if !allow_list.is_empty() {
|
||||
// Try to silently discover U2F credentials that require the FIDO App ID extension. If
|
||||
// any are found, we should use the alternate RP ID instead of the provided RP ID.
|
||||
let silent_creds = silently_discover_credentials(
|
||||
dev,
|
||||
&allow_list,
|
||||
&RelyingPartyWrapper::from(alt_rp_id.as_str()),
|
||||
&client_data_hash,
|
||||
);
|
||||
if !silent_creds.is_empty() {
|
||||
allow_list = silent_creds;
|
||||
rp_id = alt_rp_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut get_assertion = GetAssertion::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: args.relying_party_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
}),
|
||||
args.allow_list,
|
||||
client_data_hash,
|
||||
RelyingPartyWrapper::from(rp_id.as_str()),
|
||||
allow_list,
|
||||
GetAssertionOptions {
|
||||
user_presence: Some(args.user_presence_req),
|
||||
user_verification: None,
|
||||
},
|
||||
args.extensions,
|
||||
args.alternate_rp_id,
|
||||
);
|
||||
|
||||
let mut skip_uv = false;
|
||||
let mut pin = args.pin;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut get_assertion,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::GetAssertion,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
&mut pin,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let pin_uv_auth_result = unwrap_result!(
|
||||
determine_puap_if_needed(
|
||||
&mut get_assertion,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::GetAssertion,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
alive,
|
||||
&mut pin,
|
||||
),
|
||||
callback
|
||||
);
|
||||
// Third, use the shared secret in the extensions, if requested
|
||||
if let Some(extension) = get_assertion.extensions.hmac_secret.as_mut() {
|
||||
if let Some(secret) = dev.get_shared_secret() {
|
||||
|
@ -644,19 +639,7 @@ pub fn sign<Dev: FidoDevice>(
|
|||
debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let mut resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
if resp.is_err() {
|
||||
// Retry with a different RP ID if one was supplied. This is intended to be
|
||||
// used with the AppID provided in the WebAuthn FIDO AppID extension.
|
||||
if let Some(alternate_rp_id) = get_assertion.alternate_rp_id {
|
||||
get_assertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: alternate_rp_id,
|
||||
..Default::default()
|
||||
});
|
||||
get_assertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
}
|
||||
}
|
||||
let resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
match resp {
|
||||
Ok(result) => {
|
||||
callback.call(Ok(result));
|
||||
|
@ -753,9 +736,10 @@ pub(crate) fn set_or_change_pin_helper<T: From<()>>(
|
|||
// that wrong PIN all the time. So we `take()` it, and only test it once.
|
||||
// If that PIN is wrong, we fall back to the "ask_user_for_pin"-method.
|
||||
let curr_pin = match current_pin.take() {
|
||||
None => match ask_user_for_pin(was_invalid, retries, &status, &callback) {
|
||||
None => match ask_user_for_pin(was_invalid, retries, &status) {
|
||||
Ok(pin) => pin,
|
||||
_ => {
|
||||
Err(e) => {
|
||||
callback.call(Err(e));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
@ -867,22 +851,19 @@ pub(crate) fn bio_enrollment(
|
|||
let mut pin = None;
|
||||
while alive() {
|
||||
if !skip_puap {
|
||||
pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut bio_cmd,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::BioEnrollment,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
&mut pin,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
pin_uv_auth_result = unwrap_result!(
|
||||
determine_puap_if_needed(
|
||||
&mut bio_cmd,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::BioEnrollment,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
alive,
|
||||
&mut pin,
|
||||
),
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
@ -1116,22 +1097,19 @@ pub(crate) fn credential_management(
|
|||
let mut pin = None;
|
||||
while alive() {
|
||||
if !skip_puap {
|
||||
pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut cred_management,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::CredentialManagement,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
&mut pin,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
pin_uv_auth_result = unwrap_result!(
|
||||
determine_puap_if_needed(
|
||||
&mut cred_management,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::CredentialManagement,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
alive,
|
||||
&mut pin,
|
||||
),
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
@ -1390,22 +1368,19 @@ pub(crate) fn configure_authenticator(
|
|||
// the token, before handing them out.
|
||||
// If authinfo.options.uv_acfg is not supported, this will return UnauthorizedPermission
|
||||
if !skip_puap {
|
||||
pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut authcfg,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::AuthenticatorConfiguration,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
&mut pin,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
pin_uv_auth_result = unwrap_result!(
|
||||
determine_puap_if_needed(
|
||||
&mut authcfg,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::AuthenticatorConfiguration,
|
||||
UserVerificationRequirement::Preferred,
|
||||
&status,
|
||||
alive,
|
||||
&mut pin,
|
||||
),
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use super::client_data::ClientDataHash;
|
||||
use super::commands::get_assertion::{GetAssertion, GetAssertionOptions};
|
||||
use super::commands::{CommandError, PinUvAuthCommand, RequestCtap1, Retryable, StatusCode};
|
||||
use super::commands::{PinUvAuthCommand, RequestCtap1, Retryable};
|
||||
use crate::authenticatorservice::GetAssertionExtensions;
|
||||
use crate::consts::{PARAMETER_SIZE, U2F_AUTHENTICATE, U2F_CHECK_IS_REGISTERED};
|
||||
use crate::crypto::PinUvAuthToken;
|
||||
use crate::ctap2::server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::transport::{FidoDevice, FidoProtocol, VirtualFidoDevice};
|
||||
use crate::u2ftypes::CTAP1RequestAPDU;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
|
@ -18,9 +18,9 @@ use sha2::{Digest, Sha256};
|
|||
/// if this token is already registered or not.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckKeyHandle<'assertion> {
|
||||
pub(crate) key_handle: &'assertion [u8],
|
||||
pub(crate) client_data_hash: &'assertion [u8],
|
||||
pub(crate) rp: &'assertion RelyingPartyWrapper,
|
||||
pub key_handle: &'assertion [u8],
|
||||
pub client_data_hash: &'assertion [u8],
|
||||
pub rp: &'assertion RelyingPartyWrapper,
|
||||
}
|
||||
|
||||
impl<'assertion> RequestCtap1 for CheckKeyHandle<'assertion> {
|
||||
|
@ -144,11 +144,6 @@ pub(crate) fn do_credential_list_filtering_ctap2<Dev: FidoDevice>(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 1.2: Return early, if we only have one chunk anyways
|
||||
if cred_list.len() <= chunk_size {
|
||||
return Ok(cred_list);
|
||||
}
|
||||
|
||||
let chunked_list = cred_list.chunks(chunk_size);
|
||||
|
||||
// Step 2: If we have more than one chunk: Loop over all, doing GetAssertion
|
||||
|
@ -160,19 +155,13 @@ pub(crate) fn do_credential_list_filtering_ctap2<Dev: FidoDevice>(
|
|||
rp.clone(),
|
||||
chunk.to_vec(),
|
||||
GetAssertionOptions {
|
||||
user_verification: if pin_uv_auth_token.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(false)
|
||||
},
|
||||
user_verification: None, // defaults to Some(false) if puap is absent
|
||||
user_presence: Some(false),
|
||||
},
|
||||
GetAssertionExtensions::default(),
|
||||
None,
|
||||
);
|
||||
silent_assert.set_pin_uv_auth_param(pin_uv_auth_token.clone())?;
|
||||
let res = dev.send_msg(&silent_assert);
|
||||
match res {
|
||||
match dev.send_msg(&silent_assert) {
|
||||
Ok(response) => {
|
||||
// This chunk contains a key_handle that is already known to the device.
|
||||
// Filter out all credentials the device returned. Those are valid.
|
||||
|
@ -185,12 +174,11 @@ pub(crate) fn do_credential_list_filtering_ctap2<Dev: FidoDevice>(
|
|||
final_list = credential_ids;
|
||||
break;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(StatusCode::NoCredentials, _))) => {
|
||||
Err(_) => {
|
||||
// No-op: Go to next chunk.
|
||||
}
|
||||
Err(e) => {
|
||||
// Some unexpected error
|
||||
return Err(e.into());
|
||||
// NOTE: while we expect a StatusCode::NoCredentials error here, some tokens return
|
||||
// other values.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,3 +188,21 @@ pub(crate) fn do_credential_list_filtering_ctap2<Dev: FidoDevice>(
|
|||
// MakeCredential or a Success in case of GetAssertion
|
||||
Ok(final_list)
|
||||
}
|
||||
|
||||
pub(crate) fn silently_discover_credentials<Dev: FidoDevice>(
|
||||
dev: &mut Dev,
|
||||
cred_list: &[PublicKeyCredentialDescriptor],
|
||||
rp: &RelyingPartyWrapper,
|
||||
client_data_hash: &ClientDataHash,
|
||||
) -> Vec<PublicKeyCredentialDescriptor> {
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
if let Ok(cred_list) = do_credential_list_filtering_ctap2(dev, cred_list, rp, None) {
|
||||
return cred_list;
|
||||
}
|
||||
} else if let Some(key_handle) =
|
||||
do_credential_list_filtering_ctap1(dev, cred_list, rp, client_data_hash)
|
||||
{
|
||||
return vec![key_handle];
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
|
|
|
@ -49,8 +49,6 @@ pub struct RelyingParty {
|
|||
pub id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
// Note: This enum is provided to make old CTAP1/U2F API work. This should be deprecated at some point
|
||||
|
@ -62,6 +60,15 @@ pub enum RelyingPartyWrapper {
|
|||
Hash(RpIdHash),
|
||||
}
|
||||
|
||||
impl From<&str> for RelyingPartyWrapper {
|
||||
fn from(rp_id: &str) -> Self {
|
||||
Self::Data(RelyingParty {
|
||||
id: rp_id.to_string(),
|
||||
name: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RelyingPartyWrapper {
|
||||
pub fn hash(&self) -> RpIdHash {
|
||||
match *self {
|
||||
|
@ -92,8 +99,6 @@ impl RelyingPartyWrapper {
|
|||
pub struct User {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: Vec<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub icon: Option<String>, // This has been removed from Webauthn-2
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "displayName")]
|
||||
pub display_name: Option<String>,
|
||||
|
@ -330,13 +335,24 @@ mod test {
|
|||
COSEAlgorithm, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
Transport, User,
|
||||
};
|
||||
use serde_cbor::from_slice;
|
||||
|
||||
fn create_user() -> 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,
|
||||
],
|
||||
name: Some(String::from("johnpsmith@example.com")),
|
||||
display_name: Some(String::from("John P. Smith")),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn serialize_rp() {
|
||||
let rp = RelyingParty {
|
||||
id: String::from("Acme"),
|
||||
name: None,
|
||||
icon: None,
|
||||
};
|
||||
|
||||
let payload = ser::to_vec(&rp).unwrap();
|
||||
|
@ -352,25 +368,57 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_user() {
|
||||
// This includes an obsolete "icon" field to test that deserialization
|
||||
// ignores it.
|
||||
let input = 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, // ...
|
||||
];
|
||||
let expected = create_user();
|
||||
let actual: User = from_slice(&input).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[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 user = create_user();
|
||||
|
||||
let payload = ser::to_vec(&user).unwrap();
|
||||
println!("payload = {payload:?}");
|
||||
assert_eq!(
|
||||
payload,
|
||||
vec![
|
||||
0xa4, // map(4)
|
||||
0xa3, // map(3)
|
||||
0x62, // text(2)
|
||||
0x69, 0x64, // "id"
|
||||
0x58, 0x20, // bytes(32)
|
||||
|
@ -379,15 +427,6 @@ mod test {
|
|||
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,
|
||||
|
@ -405,14 +444,13 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_user_noicon_nodisplayname() {
|
||||
fn serialize_user_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,
|
||||
};
|
||||
|
|
|
@ -46,8 +46,8 @@ pub use ctap2::commands::client_pin::{Pin, PinError};
|
|||
pub use ctap2::commands::credential_management::CredentialManagementResult;
|
||||
pub use ctap2::commands::get_assertion::{Assertion, GetAssertionResult};
|
||||
pub use ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use serde::Serialize;
|
||||
pub use ctap2::commands::make_credentials::MakeCredentialsResult;
|
||||
use serde::Serialize;
|
||||
pub use statemachine::StateMachine;
|
||||
pub use status_update::{
|
||||
BioEnrollmentCmd, CredManagementCmd, InteractiveRequest, InteractiveUpdate, StatusPinUv,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use crate::authenticatorservice::{RegisterArgs, SignArgs};
|
||||
use crate::consts::PARAMETER_SIZE;
|
||||
|
||||
use crate::ctap2;
|
||||
use crate::ctap2::client_data::ClientDataHash;
|
||||
use crate::ctap2::commands::client_pin::Pin;
|
||||
use crate::ctap2::commands::get_assertion::GetAssertionResult;
|
||||
|
@ -22,7 +23,6 @@ use crate::transport::device_selector::{
|
|||
use crate::transport::platform::transaction::Transaction;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoProtocol};
|
||||
use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
|
||||
use crate::ctap2;
|
||||
use crate::{
|
||||
AuthenticatorTransports, InteractiveRequest, KeyHandle, ManageResult, RegisterFlags, SignFlags,
|
||||
};
|
||||
|
@ -69,19 +69,16 @@ impl StateMachine {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn init_and_select(
|
||||
fn init_device(
|
||||
info: DeviceBuildParameters,
|
||||
selector: &Sender<DeviceSelectorEvent>,
|
||||
status: &Sender<crate::StatusUpdate>,
|
||||
ctap2_only: bool,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Option<Device> {
|
||||
// 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()?;
|
||||
let _ = selector.send(DeviceSelectorEvent::NotAToken(id));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
@ -89,20 +86,28 @@ impl StateMachine {
|
|||
// Try initializing it.
|
||||
if let Err(e) = dev.init() {
|
||||
warn!("error while initializing device: {}", e);
|
||||
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
|
||||
let _ = selector.send(DeviceSelectorEvent::NotAToken(dev.id()));
|
||||
return None;
|
||||
}
|
||||
|
||||
if ctap2_only && dev.get_protocol() != FidoProtocol::CTAP2 {
|
||||
info!("Device does not support CTAP2");
|
||||
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
|
||||
return None;
|
||||
}
|
||||
Some(dev)
|
||||
}
|
||||
|
||||
fn wait_for_device_selector(
|
||||
dev: &mut Device,
|
||||
selector: &Sender<DeviceSelectorEvent>,
|
||||
status: &Sender<crate::StatusUpdate>,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> bool {
|
||||
let (tx, rx) = channel();
|
||||
selector
|
||||
if selector
|
||||
.send(DeviceSelectorEvent::ImAToken((dev.id(), tx)))
|
||||
.ok()?;
|
||||
.is_err()
|
||||
{
|
||||
// If we fail to register with the device selector, then we're not going
|
||||
// to be selected.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can be cancelled from the user (through keep_alive()) or from the device selector
|
||||
// (through a DeviceCommand::Cancel on rx). We'll combine those signals into a single
|
||||
|
@ -112,41 +117,40 @@ impl StateMachine {
|
|||
// Blocking recv. DeviceSelector will tell us what to do
|
||||
match rx.recv() {
|
||||
Ok(DeviceCommand::Blink) => {
|
||||
// Inform the user that there are multiple devices available.
|
||||
// NOTE: We'll send this once per device, so the recipient should be prepared
|
||||
// to receive this message multiple times.
|
||||
// The caller wants the user to choose a device. Send a status update and blink
|
||||
// this device. NOTE: We send one status update per device, so the recipient should be
|
||||
// prepared to receive the message multiple times.
|
||||
send_status(status, crate::StatusUpdate::SelectDeviceNotice);
|
||||
match dev.block_and_blink(&keep_blinking) {
|
||||
BlinkResult::DeviceSelected => {
|
||||
// User selected us. Let DeviceSelector know, so it can cancel all other
|
||||
// outstanding open blink-requests.
|
||||
// outstanding open blink-requests. If we fail to send the SelectedToken
|
||||
// message to the device selector, then don't consider this token as having
|
||||
// been selected.
|
||||
selector
|
||||
.send(DeviceSelectorEvent::SelectedToken(dev.id()))
|
||||
.ok()?;
|
||||
.is_ok()
|
||||
}
|
||||
BlinkResult::Cancelled => {
|
||||
info!("Device {:?} was not selected", dev.id());
|
||||
return None;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(DeviceCommand::Cancel) => {
|
||||
info!("Device {:?} was not selected", dev.id());
|
||||
return None;
|
||||
false
|
||||
}
|
||||
Ok(DeviceCommand::Removed) => {
|
||||
info!("Device {:?} was removed", dev.id());
|
||||
return None;
|
||||
}
|
||||
Ok(DeviceCommand::Continue) => {
|
||||
// Just continue
|
||||
false
|
||||
}
|
||||
Ok(DeviceCommand::Continue) => true,
|
||||
Err(_) => {
|
||||
warn!("Error when trying to receive messages from DeviceSelector! Exiting.");
|
||||
return None;
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(dev)
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
|
@ -198,11 +202,12 @@ impl StateMachine {
|
|||
cbc.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
let mut dev = match Self::init_device(info, &selector) {
|
||||
Some(dev) => dev,
|
||||
None => return,
|
||||
};
|
||||
if !Self::wait_for_device_selector(&mut dev, &selector, &status, alive) {
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Device {:?} continues with the register process", dev.id());
|
||||
|
@ -275,11 +280,12 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
let mut dev = match Self::init_device(info, &selector) {
|
||||
Some(dev) => dev,
|
||||
None => return,
|
||||
};
|
||||
if !Self::wait_for_device_selector(&mut dev, &selector, &status, alive) {
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Device {:?} continues with the signing process", dev.id());
|
||||
|
@ -319,11 +325,19 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
let mut dev = match Self::init_device(info, &selector) {
|
||||
Some(dev) => dev,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if dev.get_protocol() != FidoProtocol::CTAP2 {
|
||||
info!("Device does not support CTAP2");
|
||||
let _ = selector.send(DeviceSelectorEvent::NotAToken(dev.id()));
|
||||
return;
|
||||
}
|
||||
|
||||
if !Self::wait_for_device_selector(&mut dev, &selector, &status, alive) {
|
||||
return;
|
||||
};
|
||||
ctap2::reset_helper(&mut dev, selector, status, callback.clone(), alive);
|
||||
},
|
||||
|
@ -349,11 +363,19 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
let mut dev = match Self::init_device(info, &selector) {
|
||||
Some(dev) => dev,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if dev.get_protocol() != FidoProtocol::CTAP2 {
|
||||
info!("Device does not support CTAP2");
|
||||
let _ = selector.send(DeviceSelectorEvent::NotAToken(dev.id()));
|
||||
return;
|
||||
}
|
||||
|
||||
if !Self::wait_for_device_selector(&mut dev, &selector, &status, alive) {
|
||||
return;
|
||||
};
|
||||
|
||||
ctap2::set_or_change_pin_helper(
|
||||
|
@ -607,11 +629,19 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
let mut dev = match Self::init_device(info, &selector) {
|
||||
Some(dev) => dev,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if dev.get_protocol() != FidoProtocol::CTAP2 {
|
||||
info!("Device does not support CTAP2");
|
||||
let _ = selector.send(DeviceSelectorEvent::NotAToken(dev.id()));
|
||||
return;
|
||||
}
|
||||
|
||||
if !Self::wait_for_device_selector(&mut dev, &selector, &status, alive) {
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Device {:?} selected for interactive management.", dev.id());
|
||||
|
|
|
@ -1,8 +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/. */
|
||||
|
||||
#[cfg(feature = "webdriver")]
|
||||
pub mod webdriver;
|
||||
|
||||
pub mod software_u2f;
|
|
@ -1,65 +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::consts::Capability;
|
||||
use crate::{RegisterResult, SignResult};
|
||||
|
||||
pub struct SoftwareU2FToken {}
|
||||
|
||||
// This is simply for platforms that aren't using the U2F Token, usually for builds
|
||||
// without --feature webdriver
|
||||
#[allow(dead_code)]
|
||||
|
||||
impl SoftwareU2FToken {
|
||||
pub fn new() -> SoftwareU2FToken {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&self,
|
||||
_flags: crate::RegisterFlags,
|
||||
_timeout: u64,
|
||||
_challenge: Vec<u8>,
|
||||
_application: crate::AppId,
|
||||
_key_handles: Vec<crate::KeyHandle>,
|
||||
) -> crate::Result<crate::RegisterResult> {
|
||||
Ok(RegisterResult::CTAP1(vec![0u8; 16], self.dev_info()))
|
||||
}
|
||||
|
||||
/// The implementation of this method must return quickly and should
|
||||
/// report its status via the status and callback methods
|
||||
pub fn sign(
|
||||
&self,
|
||||
_flags: crate::SignFlags,
|
||||
_timeout: u64,
|
||||
_challenge: Vec<u8>,
|
||||
_app_ids: Vec<crate::AppId>,
|
||||
_key_handles: Vec<crate::KeyHandle>,
|
||||
) -> crate::Result<crate::SignResult> {
|
||||
Ok(SignResult::CTAP1(
|
||||
vec![0u8; 0],
|
||||
vec![0u8; 0],
|
||||
vec![0u8; 0],
|
||||
self.dev_info(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo {
|
||||
crate::u2ftypes::U2FDeviceInfo {
|
||||
vendor_name: b"Mozilla".to_vec(),
|
||||
device_name: b"Authenticator Webdriver Token".to_vec(),
|
||||
version_interface: 0,
|
||||
version_major: 1,
|
||||
version_minor: 2,
|
||||
version_build: 3,
|
||||
cap_flags: Capability::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
|
@ -1,9 +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/. */
|
||||
|
||||
mod testtoken;
|
||||
mod virtualmanager;
|
||||
mod web_api;
|
||||
|
||||
pub use virtualmanager::VirtualManager;
|
|
@ -1,140 +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::virtualdevices::software_u2f::SoftwareU2FToken;
|
||||
use crate::{RegisterFlags, RegisterResult, SignFlags, SignResult};
|
||||
|
||||
pub enum TestWireProtocol {
|
||||
CTAP1,
|
||||
CTAP2,
|
||||
}
|
||||
|
||||
impl TestWireProtocol {
|
||||
pub fn to_webdriver_string(&self) -> String {
|
||||
match self {
|
||||
TestWireProtocol::CTAP1 => "ctap1/u2f".to_string(),
|
||||
TestWireProtocol::CTAP2 => "ctap2".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestTokenCredential {
|
||||
pub credential: Vec<u8>,
|
||||
pub privkey: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub sign_count: u64,
|
||||
pub is_resident_credential: bool,
|
||||
pub rp_id: String,
|
||||
}
|
||||
|
||||
pub struct TestToken {
|
||||
pub id: u64,
|
||||
pub protocol: TestWireProtocol,
|
||||
pub transport: String,
|
||||
pub is_user_consenting: bool,
|
||||
pub has_user_verification: bool,
|
||||
pub is_user_verified: bool,
|
||||
pub has_resident_key: bool,
|
||||
pub u2f_impl: Option<SoftwareU2FToken>,
|
||||
pub credentials: Vec<TestTokenCredential>,
|
||||
}
|
||||
|
||||
impl TestToken {
|
||||
pub fn new(
|
||||
id: u64,
|
||||
protocol: TestWireProtocol,
|
||||
transport: String,
|
||||
is_user_consenting: bool,
|
||||
has_user_verification: bool,
|
||||
is_user_verified: bool,
|
||||
has_resident_key: bool,
|
||||
) -> TestToken {
|
||||
match protocol {
|
||||
TestWireProtocol::CTAP1 => Self {
|
||||
id,
|
||||
protocol,
|
||||
transport,
|
||||
is_user_consenting,
|
||||
has_user_verification,
|
||||
is_user_verified,
|
||||
has_resident_key,
|
||||
u2f_impl: Some(SoftwareU2FToken::new()),
|
||||
credentials: Vec::new(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_credential(
|
||||
&mut self,
|
||||
credential: &[u8],
|
||||
privkey: &[u8],
|
||||
rp_id: String,
|
||||
is_resident_credential: bool,
|
||||
user_handle: &[u8],
|
||||
sign_count: u64,
|
||||
) {
|
||||
let c = TestTokenCredential {
|
||||
credential: credential.to_vec(),
|
||||
privkey: privkey.to_vec(),
|
||||
rp_id,
|
||||
is_resident_credential,
|
||||
user_handle: user_handle.to_vec(),
|
||||
sign_count,
|
||||
};
|
||||
|
||||
match self
|
||||
.credentials
|
||||
.binary_search_by_key(&credential, |probe| &probe.credential)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(idx) => self.credentials.insert(idx, c),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_credential(&mut self, credential: &[u8]) -> bool {
|
||||
debug!("Asking to delete credential",);
|
||||
if let Ok(idx) = self
|
||||
.credentials
|
||||
.binary_search_by_key(&credential, |probe| &probe.credential)
|
||||
{
|
||||
debug!("Asking to delete credential from idx {}", idx);
|
||||
self.credentials.remove(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn register(&self) -> crate::Result<RegisterResult> {
|
||||
if self.u2f_impl.is_some() {
|
||||
return self.u2f_impl.as_ref().unwrap().register(
|
||||
RegisterFlags::empty(),
|
||||
10_000,
|
||||
vec![0; 32],
|
||||
vec![0; 32],
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
Err(errors::AuthenticatorError::U2FToken(
|
||||
errors::U2FTokenError::Unknown,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn sign(&self) -> crate::Result<SignResult> {
|
||||
if self.u2f_impl.is_some() {
|
||||
return self.u2f_impl.as_ref().unwrap().sign(
|
||||
SignFlags::empty(),
|
||||
10_000,
|
||||
vec![0; 32],
|
||||
vec![vec![0; 32]],
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
Err(errors::AuthenticatorError::U2FToken(
|
||||
errors::U2FTokenError::Unknown,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,151 +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 runloop::RunLoop;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::vec;
|
||||
use std::{io, string, thread};
|
||||
|
||||
use crate::authenticatorservice::{AuthenticatorTransport, RegisterArgs, SignArgs};
|
||||
use crate::errors;
|
||||
use crate::statecallback::StateCallback;
|
||||
use crate::virtualdevices::webdriver::{testtoken, web_api};
|
||||
|
||||
pub struct VirtualManagerState {
|
||||
pub authenticator_counter: u64,
|
||||
pub tokens: vec::Vec<testtoken::TestToken>,
|
||||
}
|
||||
|
||||
impl VirtualManagerState {
|
||||
pub fn new() -> Arc<Mutex<VirtualManagerState>> {
|
||||
Arc::new(Mutex::new(VirtualManagerState {
|
||||
authenticator_counter: 0,
|
||||
tokens: vec![],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtualManager {
|
||||
addr: SocketAddr,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
rloop: Option<RunLoop>,
|
||||
}
|
||||
|
||||
impl VirtualManager {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080);
|
||||
let state = VirtualManagerState::new();
|
||||
let stateclone = state.clone();
|
||||
|
||||
let builder = thread::Builder::new().name("WebDriver Command Server".into());
|
||||
builder.spawn(move || {
|
||||
web_api::serve(stateclone, addr);
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
addr,
|
||||
state,
|
||||
rloop: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn url(&self) -> string::String {
|
||||
format!("http://{}/webauthn/authenticator", &self.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticatorTransport for VirtualManager {
|
||||
fn register(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
_ctap_args: RegisterArgs,
|
||||
_status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::RegisterResult>>,
|
||||
) -> crate::Result<()> {
|
||||
if self.rloop.is_some() {
|
||||
error!("WebDriver state error, prior operation never cancelled.");
|
||||
return Err(errors::AuthenticatorError::U2FToken(
|
||||
errors::U2FTokenError::Unknown,
|
||||
));
|
||||
}
|
||||
|
||||
let state = self.state.clone();
|
||||
let rloop = try_or!(
|
||||
RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
while alive() {
|
||||
let state_obj = state.lock().unwrap();
|
||||
|
||||
for token in state_obj.tokens.deref() {
|
||||
if token.is_user_consenting {
|
||||
let register_result = token.register();
|
||||
thread::spawn(move || {
|
||||
callback.call(register_result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
timeout
|
||||
),
|
||||
|_| Err(errors::AuthenticatorError::Platform)
|
||||
);
|
||||
|
||||
self.rloop = Some(rloop);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
_ctap_args: SignArgs,
|
||||
_status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::SignResult>>,
|
||||
) -> crate::Result<()> {
|
||||
if self.rloop.is_some() {
|
||||
error!("WebDriver state error, prior operation never cancelled.");
|
||||
return Err(errors::AuthenticatorError::U2FToken(
|
||||
errors::U2FTokenError::Unknown,
|
||||
));
|
||||
}
|
||||
|
||||
let state = self.state.clone();
|
||||
let rloop = try_or!(
|
||||
RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
while alive() {
|
||||
let state_obj = state.lock().unwrap();
|
||||
|
||||
for token in state_obj.tokens.deref() {
|
||||
if token.is_user_consenting {
|
||||
let sign_result = token.sign();
|
||||
thread::spawn(move || {
|
||||
callback.call(sign_result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
timeout
|
||||
),
|
||||
|_| Err(errors::AuthenticatorError::Platform)
|
||||
);
|
||||
|
||||
self.rloop = Some(rloop);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cancel(&mut self) -> crate::Result<()> {
|
||||
if let Some(r) = self.rloop.take() {
|
||||
debug!("WebDriver operation cancelled.");
|
||||
r.cancel();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,964 +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 serde::{Deserialize, Serialize};
|
||||
use std::net::SocketAddr;
|
||||
use std::string;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use warp::Filter;
|
||||
|
||||
use crate::virtualdevices::webdriver::{testtoken, virtualmanager::VirtualManagerState};
|
||||
|
||||
fn default_as_false() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_as_true() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct AuthenticatorConfiguration {
|
||||
protocol: string::String,
|
||||
transport: string::String,
|
||||
#[serde(rename = "hasResidentKey")]
|
||||
#[serde(default = "default_as_false")]
|
||||
has_resident_key: bool,
|
||||
#[serde(rename = "hasUserVerification")]
|
||||
#[serde(default = "default_as_false")]
|
||||
has_user_verification: bool,
|
||||
#[serde(rename = "isUserConsenting")]
|
||||
#[serde(default = "default_as_true")]
|
||||
is_user_consenting: bool,
|
||||
#[serde(rename = "isUserVerified")]
|
||||
#[serde(default = "default_as_false")]
|
||||
is_user_verified: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct CredentialParameters {
|
||||
#[serde(rename = "credentialId")]
|
||||
credential_id: String,
|
||||
#[serde(rename = "isResidentCredential")]
|
||||
is_resident_credential: bool,
|
||||
#[serde(rename = "rpId")]
|
||||
rp_id: String,
|
||||
#[serde(rename = "privateKey")]
|
||||
private_key: String,
|
||||
#[serde(rename = "userHandle")]
|
||||
#[serde(default)]
|
||||
user_handle: String,
|
||||
#[serde(rename = "signCount")]
|
||||
sign_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct UserVerificationParameters {
|
||||
#[serde(rename = "isUserVerified")]
|
||||
is_user_verified: bool,
|
||||
}
|
||||
|
||||
impl CredentialParameters {
|
||||
fn new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters {
|
||||
let credential_id = base64::encode_config(&tc.credential, base64::URL_SAFE);
|
||||
|
||||
let private_key = base64::encode_config(&tc.privkey, base64::URL_SAFE);
|
||||
|
||||
let user_handle = base64::encode_config(&tc.user_handle, base64::URL_SAFE);
|
||||
|
||||
CredentialParameters {
|
||||
credential_id,
|
||||
is_resident_credential: tc.is_resident_credential,
|
||||
rp_id: tc.rp_id.clone(),
|
||||
private_key,
|
||||
user_handle,
|
||||
sign_count: tc.sign_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_state(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone
|
||||
{
|
||||
warp::any().map(move || state.clone())
|
||||
}
|
||||
|
||||
fn authenticator_add(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_add)
|
||||
}
|
||||
|
||||
fn authenticator_delete(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64)
|
||||
.and(warp::delete())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_delete)
|
||||
}
|
||||
|
||||
fn authenticator_set_uv(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64 / "uv")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_set_uv)
|
||||
}
|
||||
|
||||
// This is not part of the specification, but it's useful for debugging
|
||||
fn authenticator_get(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64)
|
||||
.and(warp::get())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_get)
|
||||
}
|
||||
|
||||
fn authenticator_credential_add(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64 / "credential")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_credential_add)
|
||||
}
|
||||
|
||||
fn authenticator_credential_delete(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64 / "credentials" / String)
|
||||
.and(warp::delete())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_credential_delete)
|
||||
}
|
||||
|
||||
fn authenticator_credentials_get(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64 / "credentials")
|
||||
.and(warp::get())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_credentials_get)
|
||||
}
|
||||
|
||||
fn authenticator_credentials_clear(
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("webauthn" / "authenticator" / u64 / "credentials")
|
||||
.and(warp::delete())
|
||||
.and(with_state(state))
|
||||
.and_then(handlers::authenticator_credentials_clear)
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
use super::{CredentialParameters, UserVerificationParameters};
|
||||
use crate::virtualdevices::webdriver::{
|
||||
testtoken, virtualmanager::VirtualManagerState, web_api::AuthenticatorConfiguration,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::convert::Infallible;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::vec;
|
||||
use warp::http::{uri, StatusCode};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonSuccess {}
|
||||
|
||||
impl JsonSuccess {
|
||||
pub fn blank() -> JsonSuccess {
|
||||
JsonSuccess {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonError {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
line: Option<u32>,
|
||||
error: String,
|
||||
details: String,
|
||||
}
|
||||
|
||||
impl JsonError {
|
||||
pub fn new(error: &str, line: u32, details: &str) -> JsonError {
|
||||
JsonError {
|
||||
details: details.to_string(),
|
||||
error: error.to_string(),
|
||||
line: Some(line),
|
||||
}
|
||||
}
|
||||
pub fn from_status_code(code: StatusCode) -> JsonError {
|
||||
JsonError {
|
||||
details: code.canonical_reason().unwrap().to_string(),
|
||||
line: None,
|
||||
error: "".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn from_error(error: &str) -> JsonError {
|
||||
JsonError {
|
||||
details: "".to_string(),
|
||||
error: error.to_string(),
|
||||
line: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! reply_error {
|
||||
($status:expr) => {
|
||||
warp::reply::with_status(
|
||||
warp::reply::json(&JsonError::from_status_code($status)),
|
||||
$status,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! try_json {
|
||||
($val:expr, $status:expr) => {
|
||||
match $val {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonError::new(
|
||||
$status.canonical_reason().unwrap(),
|
||||
line!(),
|
||||
&e.to_string(),
|
||||
)),
|
||||
$status,
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn validate_rp_id(rp_id: &str) -> crate::Result<()> {
|
||||
if let Ok(uri) = rp_id.parse::<uri::Uri>().map_err(|_| {
|
||||
crate::errors::AuthenticatorError::U2FToken(crate::errors::U2FTokenError::Unknown)
|
||||
}) {
|
||||
if uri.scheme().is_none()
|
||||
&& uri.path_and_query().is_none()
|
||||
&& uri.port().is_none()
|
||||
&& uri.host().is_some()
|
||||
&& uri.authority().unwrap() == uri.host().unwrap()
|
||||
// Don't try too hard to ensure it's a valid domain, just
|
||||
// ensure there's a label delim in there somewhere
|
||||
&& uri.host().unwrap().find('.').is_some()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn authenticator_add(
|
||||
auth: AuthenticatorConfiguration,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let protocol = match auth.protocol.as_str() {
|
||||
"ctap1/u2f" => testtoken::TestWireProtocol::CTAP1,
|
||||
"ctap2" => testtoken::TestWireProtocol::CTAP2,
|
||||
_ => {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonError::from_error(
|
||||
format!("unknown protocol: {}", auth.protocol).as_str(),
|
||||
)),
|
||||
StatusCode::BAD_REQUEST,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let mut state_lock = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
let mut state_obj = state_lock.deref_mut();
|
||||
state_obj.authenticator_counter += 1;
|
||||
|
||||
let tt = testtoken::TestToken::new(
|
||||
state_obj.authenticator_counter,
|
||||
protocol,
|
||||
auth.transport,
|
||||
auth.is_user_consenting,
|
||||
auth.has_user_verification,
|
||||
auth.is_user_verified,
|
||||
auth.has_resident_key,
|
||||
);
|
||||
|
||||
match state_obj
|
||||
.tokens
|
||||
.binary_search_by_key(&state_obj.authenticator_counter, |probe| probe.id)
|
||||
{
|
||||
Ok(_) => panic!("unexpected repeat of authenticator_id"),
|
||||
Err(idx) => state_obj.tokens.insert(idx, tt),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AddResult {
|
||||
#[serde(rename = "authenticatorId")]
|
||||
authenticator_id: u64,
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&AddResult {
|
||||
authenticator_id: state_obj.authenticator_counter,
|
||||
}),
|
||||
StatusCode::CREATED,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn authenticator_delete(
|
||||
id: u64,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
match state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
Ok(idx) => state_obj.tokens.remove(idx),
|
||||
Err(_) => {
|
||||
return Ok(reply_error!(StatusCode::NOT_FOUND));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonSuccess::blank()),
|
||||
StatusCode::OK,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn authenticator_get(
|
||||
id: u64,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
|
||||
let data = AuthenticatorConfiguration {
|
||||
protocol: tt.protocol.to_webdriver_string(),
|
||||
transport: tt.transport.clone(),
|
||||
has_resident_key: tt.has_resident_key,
|
||||
has_user_verification: tt.has_user_verification,
|
||||
is_user_consenting: tt.is_user_consenting,
|
||||
is_user_verified: tt.is_user_verified,
|
||||
};
|
||||
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&data),
|
||||
StatusCode::OK,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
pub async fn authenticator_set_uv(
|
||||
id: u64,
|
||||
uv: UserVerificationParameters,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
tt.is_user_verified = uv.is_user_verified;
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonSuccess::blank()),
|
||||
StatusCode::OK,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
pub async fn authenticator_credential_add(
|
||||
id: u64,
|
||||
auth: CredentialParameters,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let credential = try_json!(
|
||||
base64::decode_config(&auth.credential_id, base64::URL_SAFE),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
|
||||
let privkey = try_json!(
|
||||
base64::decode_config(&auth.private_key, base64::URL_SAFE),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
|
||||
let userhandle = try_json!(
|
||||
base64::decode_config(&auth.user_handle, base64::URL_SAFE),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
|
||||
try_json!(validate_rp_id(&auth.rp_id), StatusCode::BAD_REQUEST);
|
||||
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
|
||||
tt.insert_credential(
|
||||
&credential,
|
||||
&privkey,
|
||||
auth.rp_id,
|
||||
auth.is_resident_credential,
|
||||
&userhandle,
|
||||
auth.sign_count,
|
||||
);
|
||||
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonSuccess::blank()),
|
||||
StatusCode::CREATED,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
pub async fn authenticator_credential_delete(
|
||||
id: u64,
|
||||
credential_id: String,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let credential = try_json!(
|
||||
base64::decode_config(&credential_id, base64::URL_SAFE),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
debug!("Asking to delete {}", &credential_id);
|
||||
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
debug!("Asking to delete from token {}", tt.id);
|
||||
if tt.delete_credential(&credential) {
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonSuccess::blank()),
|
||||
StatusCode::OK,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
pub async fn authenticator_credentials_get(
|
||||
id: u64,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
let mut creds: vec::Vec<CredentialParameters> = vec![];
|
||||
for ttc in &tt.credentials {
|
||||
creds.push(CredentialParameters::new_from_test_token_credential(ttc));
|
||||
}
|
||||
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&creds),
|
||||
StatusCode::OK,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
|
||||
pub async fn authenticator_credentials_clear(
|
||||
id: u64,
|
||||
state: Arc<Mutex<VirtualManagerState>>,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
|
||||
let tt = &mut state_obj.tokens[idx];
|
||||
|
||||
tt.credentials.clear();
|
||||
|
||||
return Ok(warp::reply::with_status(
|
||||
warp::reply::json(&JsonSuccess::blank()),
|
||||
StatusCode::OK,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(reply_error!(StatusCode::NOT_FOUND))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr) {
|
||||
let routes = authenticator_add(state.clone())
|
||||
.or(authenticator_delete(state.clone()))
|
||||
.or(authenticator_get(state.clone()))
|
||||
.or(authenticator_set_uv(state.clone()))
|
||||
.or(authenticator_credential_add(state.clone()))
|
||||
.or(authenticator_credential_delete(state.clone()))
|
||||
.or(authenticator_credentials_get(state.clone()))
|
||||
.or(authenticator_credentials_clear(state.clone()));
|
||||
|
||||
warp::serve(routes).run(addr).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::handlers::validate_rp_id;
|
||||
use super::testtoken::*;
|
||||
use super::*;
|
||||
use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use warp::http::StatusCode;
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_rp_id() {
|
||||
init();
|
||||
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("http://example.com")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("https://example.com")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("example.com:443")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("example.com/path")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("example.com:443/path")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("user:pass@example.com")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(
|
||||
validate_rp_id(&String::from("com")),
|
||||
Err(crate::errors::AuthenticatorError::U2FToken(
|
||||
crate::errors::U2FTokenError::Unknown,
|
||||
))
|
||||
);
|
||||
assert_matches!(validate_rp_id(&String::from("example.com")), Ok(()));
|
||||
}
|
||||
|
||||
fn mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>> {
|
||||
let state = VirtualManagerState::new();
|
||||
|
||||
{
|
||||
let mut state_obj = state.lock().unwrap();
|
||||
for id in ids {
|
||||
state_obj.tokens.push(TestToken::new(
|
||||
*id,
|
||||
TestWireProtocol::CTAP1,
|
||||
"internal".to_string(),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
state_obj.tokens.sort_by_key(|probe| probe.id)
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn assert_success_rsp_blank(body: &warp::hyper::body::Bytes) {
|
||||
assert_eq!(String::from_utf8_lossy(&body), r#"{}"#)
|
||||
}
|
||||
|
||||
fn assert_creds_equals_test_token_params(
|
||||
a: &[CredentialParameters],
|
||||
b: &[TestTokenCredential],
|
||||
) {
|
||||
assert_eq!(a.len(), b.len());
|
||||
|
||||
for (i, j) in a.iter().zip(b.iter()) {
|
||||
assert_eq!(
|
||||
i.credential_id,
|
||||
base64::encode_config(&j.credential, base64::URL_SAFE)
|
||||
);
|
||||
assert_eq!(
|
||||
i.user_handle,
|
||||
base64::encode_config(&j.user_handle, base64::URL_SAFE)
|
||||
);
|
||||
assert_eq!(
|
||||
i.private_key,
|
||||
base64::encode_config(&j.privkey, base64::URL_SAFE)
|
||||
);
|
||||
assert_eq!(i.rp_id, j.rp_id);
|
||||
assert_eq!(i.sign_count, j.sign_count);
|
||||
assert_eq!(i.is_resident_credential, j.is_resident_credential);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_authenticator_add() {
|
||||
init();
|
||||
let filter = authenticator_add(mk_state_with_token_list(&[]));
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
let valid_add = AuthenticatorConfiguration {
|
||||
protocol: "ctap1/u2f".to_string(),
|
||||
transport: "usb".to_string(),
|
||||
has_resident_key: false,
|
||||
has_user_verification: false,
|
||||
is_user_consenting: false,
|
||||
is_user_verified: false,
|
||||
};
|
||||
|
||||
{
|
||||
let mut invalid = valid_add.clone();
|
||||
invalid.protocol = "unknown".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator")
|
||||
.json(&invalid)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
assert!(String::from_utf8_lossy(&res.body())
|
||||
.contains(&String::from("unknown protocol: unknown")));
|
||||
}
|
||||
|
||||
{
|
||||
let mut unknown = valid_add.clone();
|
||||
unknown.transport = "unknown".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator")
|
||||
.json(&unknown)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&res.body()),
|
||||
r#"{"authenticatorId":1}"#
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator")
|
||||
.json(&valid_add)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&res.body()),
|
||||
r#"{"authenticatorId":2}"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_authenticator_delete() {
|
||||
init();
|
||||
let filter = authenticator_delete(mk_state_with_token_list(&[32]));
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/3")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/32")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
assert_success_rsp_blank(res.body());
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/42")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_authenticator_change_uv() {
|
||||
init();
|
||||
let state = mk_state_with_token_list(&[1]);
|
||||
let filter = authenticator_set_uv(state.clone());
|
||||
|
||||
{
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(true, state_obj.tokens[0].is_user_verified);
|
||||
}
|
||||
|
||||
{
|
||||
// Empty POST is bad
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/uv")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
// Unexpected POST structure is bad
|
||||
#[derive(Serialize)]
|
||||
struct Unexpected {
|
||||
id: u64,
|
||||
}
|
||||
let unexpected = Unexpected { id: 4 };
|
||||
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/uv")
|
||||
.json(&unexpected)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
let param_false = UserVerificationParameters {
|
||||
is_user_verified: false,
|
||||
};
|
||||
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/uv")
|
||||
.json(¶m_false)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), 200);
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(false, state_obj.tokens[0].is_user_verified);
|
||||
}
|
||||
|
||||
{
|
||||
let param_false = UserVerificationParameters {
|
||||
is_user_verified: true,
|
||||
};
|
||||
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/uv")
|
||||
.json(¶m_false)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), 200);
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(true, state_obj.tokens[0].is_user_verified);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_authenticator_credentials() {
|
||||
init();
|
||||
let state = mk_state_with_token_list(&[1]);
|
||||
let filter = authenticator_credential_add(state.clone())
|
||||
.or(authenticator_credential_delete(state.clone()))
|
||||
.or(authenticator_credentials_get(state.clone()))
|
||||
.or(authenticator_credentials_clear(state.clone()));
|
||||
|
||||
let valid_add_credential = CredentialParameters {
|
||||
credential_id: r"c3VwZXIgcmVhZGVy".to_string(),
|
||||
is_resident_credential: true,
|
||||
rp_id: "valid.rpid".to_string(),
|
||||
private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE),
|
||||
user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE),
|
||||
sign_count: 0,
|
||||
};
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
let mut invalid = valid_add_credential.clone();
|
||||
invalid.credential_id = "!@#$ invalid base64".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&invalid)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
let mut invalid = valid_add_credential.clone();
|
||||
invalid.rp_id = "example".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&invalid)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
}
|
||||
|
||||
{
|
||||
let mut invalid = valid_add_credential.clone();
|
||||
invalid.rp_id = "https://example.com".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&invalid)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(0, state_obj.tokens[0].credentials.len());
|
||||
}
|
||||
|
||||
{
|
||||
let mut no_user_handle = valid_add_credential.clone();
|
||||
no_user_handle.user_handle = "".to_string();
|
||||
no_user_handle.credential_id = "YQo=".to_string();
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&no_user_handle)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(1, state_obj.tokens[0].credentials.len());
|
||||
let c = &state_obj.tokens[0].credentials[0];
|
||||
assert!(c.user_handle.is_empty());
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&valid_add_credential)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::CREATED);
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(2, state_obj.tokens[0].credentials.len());
|
||||
let c = &state_obj.tokens[0].credentials[1];
|
||||
assert!(!c.user_handle.is_empty());
|
||||
}
|
||||
|
||||
{
|
||||
// Duplicate, should still be two credentials
|
||||
let res = warp::test::request()
|
||||
.method("POST")
|
||||
.path("/webauthn/authenticator/1/credential")
|
||||
.json(&valid_add_credential)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::CREATED);
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(2, state_obj.tokens[0].credentials.len());
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("GET")
|
||||
.path("/webauthn/authenticator/1/credentials")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), 200);
|
||||
let (_, body) = res.into_parts();
|
||||
let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap();
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials);
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/1/credentials/YmxhbmsK")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), 404);
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert_eq!(res.status(), 200);
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(1, state_obj.tokens[0].credentials.len());
|
||||
}
|
||||
|
||||
{
|
||||
let res = warp::test::request()
|
||||
.method("DELETE")
|
||||
.path("/webauthn/authenticator/1/credentials")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
assert_success_rsp_blank(res.body());
|
||||
|
||||
let state_obj = state.lock().unwrap();
|
||||
assert_eq!(0, state_obj.tokens[0].credentials.len());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
requests>=2.23.0
|
||||
rich>=3.0
|
|
@ -1,207 +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/.
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import requests
|
||||
|
||||
console = Console()
|
||||
log = logging.getLogger("webdriver-driver")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(help="sub-command help")
|
||||
|
||||
parser.add_argument(
|
||||
"--verbose", "-v", help="Be more verbose", action="count", default=0
|
||||
)
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
default="http://localhost:8080/webauthn/authenticator",
|
||||
help="webdriver url",
|
||||
)
|
||||
|
||||
|
||||
def device_add(args):
|
||||
data = {
|
||||
"protocol": args.protocol,
|
||||
"transport": args.transport,
|
||||
"hasResidentKey": args.residentkey in ["true", "yes"],
|
||||
"isUserConsenting": args.consent in ["true", "yes"],
|
||||
"hasUserVerification": args.uv in ["available", "verified"],
|
||||
"isUserVerified": args.uv in ["verified"],
|
||||
}
|
||||
console.print("Adding new device: ", data)
|
||||
rsp = requests.post(args.url, json=data)
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_add = subparsers.add_parser("add", help="Add a device")
|
||||
parser_add.set_defaults(func=device_add)
|
||||
parser_add.add_argument(
|
||||
"--consent",
|
||||
choices=["yes", "no", "true", "false"],
|
||||
default="true",
|
||||
help="consent automatically",
|
||||
)
|
||||
parser_add.add_argument(
|
||||
"--residentkey",
|
||||
choices=["yes", "no", "true", "false"],
|
||||
default="no",
|
||||
help="indicate a resident key",
|
||||
)
|
||||
parser_add.add_argument(
|
||||
"--uv",
|
||||
choices=["no", "available", "verified"],
|
||||
default="no",
|
||||
help="indicate user verification",
|
||||
)
|
||||
parser_add.add_argument(
|
||||
"--protocol", choices=["ctap1/u2f", "ctap2"], default="ctap1/u2f", help="protocol"
|
||||
)
|
||||
parser_add.add_argument("--transport", default="usb", help="transport type(s)")
|
||||
|
||||
|
||||
def device_delete(args):
|
||||
rsp = requests.delete(f"{args.url}/{args.id}")
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_delete = subparsers.add_parser("delete", help="Delete a device")
|
||||
parser_delete.set_defaults(func=device_delete)
|
||||
parser_delete.add_argument("id", type=int, help="device ID to delete")
|
||||
|
||||
|
||||
def device_view(args):
|
||||
rsp = requests.get(f"{args.url}/{args.id}")
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_view = subparsers.add_parser("view", help="View data about a device")
|
||||
parser_view.set_defaults(func=device_view)
|
||||
parser_view.add_argument("id", type=int, help="device ID to view")
|
||||
|
||||
|
||||
def device_update_uv(args):
|
||||
data = {"isUserVerified": args.uv in ["verified", "yes"]}
|
||||
rsp = requests.post(f"{args.url}/{args.id}/uv", json=data)
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_update_uv = subparsers.add_parser(
|
||||
"update-uv", help="Update the User Verified setting"
|
||||
)
|
||||
parser_update_uv.set_defaults(func=device_update_uv)
|
||||
parser_update_uv.add_argument("id", type=int, help="device ID to update")
|
||||
parser_update_uv.add_argument(
|
||||
"uv",
|
||||
choices=["no", "yes", "verified"],
|
||||
default="no",
|
||||
help="indicate user verification",
|
||||
)
|
||||
|
||||
|
||||
def credential_add(args):
|
||||
data = {
|
||||
"credentialId": args.credentialId,
|
||||
"isResidentCredential": args.isResidentCredential in ["true", "yes"],
|
||||
"rpId": args.rpId,
|
||||
"privateKey": args.privateKey,
|
||||
"signCount": args.signCount,
|
||||
}
|
||||
if args.userHandle:
|
||||
data["userHandle"] = (args.userHandle,)
|
||||
|
||||
console.print(f"Adding new credential to device {args.id}: ", data)
|
||||
rsp = requests.post(f"{args.url}/{args.id}/credential", json=data)
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_credential_add = subparsers.add_parser("addcred", help="Add a credential")
|
||||
parser_credential_add.set_defaults(func=credential_add)
|
||||
parser_credential_add.add_argument(
|
||||
"--id", required=True, type=int, help="device ID to use"
|
||||
)
|
||||
parser_credential_add.add_argument(
|
||||
"--credentialId", required=True, help="base64url-encoded credential ID"
|
||||
)
|
||||
parser_credential_add.add_argument(
|
||||
"--isResidentCredential",
|
||||
choices=["yes", "no", "true", "false"],
|
||||
default="no",
|
||||
help="indicate a resident key",
|
||||
)
|
||||
parser_credential_add.add_argument("--rpId", required=True, help="RP id (hostname)")
|
||||
parser_credential_add.add_argument(
|
||||
"--privateKey", required=True, help="base64url-encoded private key per RFC 5958"
|
||||
)
|
||||
parser_credential_add.add_argument("--userHandle", help="base64url-encoded user handle")
|
||||
parser_credential_add.add_argument(
|
||||
"--signCount", default=0, type=int, help="initial signature counter"
|
||||
)
|
||||
|
||||
|
||||
def credentials_get(args):
|
||||
rsp = requests.get(f"{args.url}/{args.id}/credentials")
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_credentials_get = subparsers.add_parser("getcreds", help="Get credentials")
|
||||
parser_credentials_get.set_defaults(func=credentials_get)
|
||||
parser_credentials_get.add_argument("id", type=int, help="device ID to query")
|
||||
|
||||
|
||||
def credential_delete(args):
|
||||
rsp = requests.delete(f"{args.url}/{args.id}/credentials/{args.credentialId}")
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_credentials_get = subparsers.add_parser("delcred", help="Delete a credential")
|
||||
parser_credentials_get.set_defaults(func=credential_delete)
|
||||
parser_credentials_get.add_argument("id", type=int, help="device ID to affect")
|
||||
parser_credentials_get.add_argument(
|
||||
"credentialId", help="base64url-encoded credential ID"
|
||||
)
|
||||
|
||||
|
||||
def credentials_clear(args):
|
||||
rsp = requests.delete(f"{args.url}/{args.id}/credentials")
|
||||
console.print(rsp)
|
||||
console.print(rsp.json())
|
||||
|
||||
|
||||
parser_credentials_get = subparsers.add_parser(
|
||||
"clearcreds", help="Clear all credentials for a device"
|
||||
)
|
||||
parser_credentials_get.set_defaults(func=credentials_clear)
|
||||
parser_credentials_get.add_argument("id", type=int, help="device ID to affect")
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
loglevel = logging.INFO
|
||||
if args.verbose > 0:
|
||||
loglevel = logging.DEBUG
|
||||
logging.basicConfig(
|
||||
level=loglevel, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
|
||||
)
|
||||
|
||||
try:
|
||||
args.func(args)
|
||||
except requests.exceptions.ConnectionError as ce:
|
||||
log.error(f"Connection refused to {args.url}: {ce}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Загрузка…
Ссылка в новой задаче