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:
John Schanck 2023-08-28 17:48:55 +00:00
Родитель 8eede6849d
Коммит fba2249b4b
32 изменённых файлов: 630 добавлений и 2921 удалений

4
Cargo.lock сгенерированный
Просмотреть файл

@ -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"

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

776
third_party/rust/authenticator/Cargo.lock сгенерированный поставляемый
Просмотреть файл

@ -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"

19
third_party/rust/authenticator/Cargo.toml поставляемый
Просмотреть файл

@ -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,
};

2
third_party/rust/authenticator/src/lib.rs поставляемый
Просмотреть файл

@ -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(&param_false)
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(false, state_obj.tokens[0].is_user_verified);
}
{
let param_false = UserVerificationParameters {
is_user_verified: true,
};
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/uv")
.json(&param_false)
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(true, state_obj.tokens[0].is_user_verified);
}
}
#[tokio::test]
async fn test_authenticator_credentials() {
init();
let state = mk_state_with_token_list(&[1]);
let filter = authenticator_credential_add(state.clone())
.or(authenticator_credential_delete(state.clone()))
.or(authenticator_credentials_get(state.clone()))
.or(authenticator_credentials_clear(state.clone()));
let valid_add_credential = CredentialParameters {
credential_id: r"c3VwZXIgcmVhZGVy".to_string(),
is_resident_credential: true,
rp_id: "valid.rpid".to_string(),
private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE),
user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE),
sign_count: 0,
};
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.credential_id = "!@#$ invalid base64".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.rp_id = "example".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.rp_id = "https://example.com".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
let state_obj = state.lock().unwrap();
assert_eq!(0, state_obj.tokens[0].credentials.len());
}
{
let mut no_user_handle = valid_add_credential.clone();
no_user_handle.user_handle = "".to_string();
no_user_handle.credential_id = "YQo=".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&no_user_handle)
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(1, state_obj.tokens[0].credentials.len());
let c = &state_obj.tokens[0].credentials[0];
assert!(c.user_handle.is_empty());
}
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&valid_add_credential)
.reply(&filter)
.await;
assert_eq!(res.status(), StatusCode::CREATED);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(2, state_obj.tokens[0].credentials.len());
let c = &state_obj.tokens[0].credentials[1];
assert!(!c.user_handle.is_empty());
}
{
// Duplicate, should still be two credentials
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&valid_add_credential)
.reply(&filter)
.await;
assert_eq!(res.status(), StatusCode::CREATED);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(2, state_obj.tokens[0].credentials.len());
}
{
let res = warp::test::request()
.method("GET")
.path("/webauthn/authenticator/1/credentials")
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
let (_, body) = res.into_parts();
let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap();
let state_obj = state.lock().unwrap();
assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials);
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials/YmxhbmsK")
.reply(&filter)
.await;
assert_eq!(res.status(), 404);
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy")
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(1, state_obj.tokens[0].credentials.len());
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials")
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(0, state_obj.tokens[0].credentials.len());
}
}
}

Просмотреть файл

@ -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()