Bug 1666701 - Upgrade Authenticator-rs to v0.3.1 r=kjacobs,keeler

This patch fixes a failure to compile on OpenBSD, and also includes the new
(and not yet used by Gecko) WebDriver implementation, and its associated
error-code upgrades.

This has a lot of new packages added into the cargo-checksum, but they were
already used by Gecko, and thus don't change the gecko-wide Cargo.{lock,toml}
files.

Differential Revision: https://phabricator.services.mozilla.com/D92784
This commit is contained in:
J.C. Jones 2020-10-07 19:14:34 +00:00
Родитель 0563d3a60a
Коммит 429b38902b
23 изменённых файлов: 2623 добавлений и 19 удалений

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

@ -189,9 +189,9 @@ dependencies = [
[[package]]
name = "authenticator"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1705a61db5ea860a40b54f649ee82ece942aef6bb91c6e86994e3b7d96597ab"
checksum = "08cee7a0952628fde958e149507c2bb321ab4fccfafd225da0b20adc956ef88a"
dependencies = [
"bitflags",
"core-foundation",

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

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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -13,16 +13,25 @@
[package]
edition = "2018"
name = "authenticator"
version = "0.3.0"
version = "0.3.1"
authors = ["J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>", "Kyle Machulis <kyle@nonpolynomial.com>"]
description = "Library for interacting with CTAP1/2 security keys for Web Authentication. Used by Firefox."
keywords = ["ctap2", "u2f", "fido", "webauthn"]
categories = ["cryptography", "hardware-support", "os"]
license = "MPL-2.0"
repository = "https://github.com/mozilla/authenticator-rs/"
[dependencies.base64]
version = "^0.10"
optional = true
[dependencies.bitflags]
version = "1.0"
[dependencies.bytes]
version = "0.5"
features = ["serde"]
optional = true
[dependencies.libc]
version = "0.2"
@ -34,6 +43,24 @@ version = "0.7"
[dependencies.runloop]
version = "0.1.0"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.serde_json]
version = "1.0"
optional = true
[dependencies.tokio]
version = "0.2"
features = ["macros"]
optional = true
[dependencies.warp]
version = "0.2.4"
optional = true
[dev-dependencies.assert_matches]
version = "1.2"
@ -54,6 +81,7 @@ optional = true
[features]
binding-recompile = ["bindgen"]
webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"]
[target."cfg(target_os = \"freebsd\")".dependencies.devd-rs]
version = "0.3"
[target."cfg(target_os = \"linux\")".dependencies.libudev]

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

@ -40,6 +40,15 @@ fn main() {
let mut opts = Options::new();
opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms");
#[cfg(feature = "webdriver")]
opts.optflag("w", "webdriver", "enable WebDriver virtual bus");
opts.optflag("h", "help", "print this help menu").optopt(
"t",
"timeout",
"timeout in seconds",
"SEC",
);
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
@ -58,6 +67,25 @@ fn main() {
manager.add_u2f_usb_hid_platform_transports();
}
#[cfg(feature = "webdriver")]
{
if matches.opt_present("webdriver") {
manager.add_webdriver_virtual_bus();
}
}
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 15) {
Ok(timeout_s) => {
println!("Using {}s as the timeout", &timeout_s);
timeout_s * 1_000
}
Err(e) => {
println!("{}", e);
print_usage(&program, opts);
return;
}
};
println!("Asking a security key to register now...");
let challenge_str = format!(
"{}{}",
@ -101,7 +129,7 @@ fn main() {
manager
.register(
flags,
60_000 * 5,
timeout_ms,
chall_bytes.clone(),
app_bytes.clone(),
vec![],
@ -133,7 +161,7 @@ fn main() {
if let Err(e) = manager.sign(
flags,
15_000,
timeout_ms,
chall_bytes,
vec![app_bytes],
vec![key_handle],

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

@ -1,2 +1,3 @@
comment_width = 200
wrap_comments = true
edition = "2018"

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

@ -84,6 +84,17 @@ 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,
flags: crate::RegisterFlags,
@ -125,7 +136,7 @@ impl AuthenticatorService {
);
transport_mutex.lock().unwrap().register(
flags.clone(),
flags,
timeout,
challenge.clone(),
application.clone(),
@ -178,7 +189,7 @@ impl AuthenticatorService {
transports_to_cancel.remove(idx);
transport_mutex.lock().unwrap().sign(
flags.clone(),
flags,
timeout,
challenge.clone(),
app_ids.clone(),
@ -533,7 +544,7 @@ mod tests {
mk_challenge(),
mk_appid(),
vec![],
status_tx.clone(),
status_tx,
callback.clone(),
)
.is_ok());
@ -605,7 +616,7 @@ mod tests {
mk_challenge(),
mk_appid(),
vec![],
status_tx.clone(),
status_tx,
callback.clone(),
)
.is_ok());

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

@ -75,6 +75,7 @@ pub use crate::capi::*;
pub mod errors;
pub mod statecallback;
mod virtualdevices;
// Keep this in sync with the constants in u2fhid-capi.h.
bitflags! {

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

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use libc::{c_int, c_short, c_ulong};
use libudev;
use libudev::EventType;
use runloop::RunLoop;
use std::collections::HashMap;

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

@ -140,9 +140,7 @@ impl AuthenticatorTransport for U2FManager {
status,
callback,
};
self.tx
.send(action)
.map_err(|e| AuthenticatorError::from(e))
Ok(self.tx.send(action)?)
}
fn sign(

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

@ -5,6 +5,7 @@
extern crate libc;
use std::ffi::OsString;
use std::io;
use std::io::{Read, Result, Write};
use std::mem;

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

@ -11,6 +11,8 @@ pub struct StateCallback<T> {
}
impl<T> StateCallback<T> {
// This is used for the Condvar, which requires this kind of construction
#[allow(clippy::mutex_atomic)]
pub fn new(cb: Box<dyn Fn(T) + Send>) -> Self {
Self {
callback: Arc::new(Mutex::new(Some(cb))),
@ -134,11 +136,13 @@ mod tests {
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_statecallback_observer_unclonable() {
let mut sc = StateCallback::<()>::new(Box::new(move |_| {}));
sc.add_uncloneable_observer(Box::new(move || {}));
assert!(sc.observer.lock().unwrap().is_some());
// This is deliberate, to force an extra clone
assert!(sc.clone().observer.lock().unwrap().is_none());
}

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

@ -52,7 +52,6 @@ fn send_status(status_mutex: &Mutex<Sender<crate::StatusUpdate>>, msg: crate::St
},
Err(e) => {
error!("Couldn't obtain status mutex: {:?}", e);
return;
}
};
}

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

@ -7,8 +7,6 @@ use std::{cmp, fmt, io, str};
use crate::consts::*;
use crate::util::io_err;
use log;
pub fn to_hex(data: &[u8], joiner: &str) -> String {
let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect();
parts.join(joiner)

8
third_party/rust/authenticator/src/virtualdevices/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#[cfg(feature = "webdriver")]
pub mod webdriver;
pub mod software_u2f;

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

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub struct SoftwareU2FToken {}
// This is simply for platforms that aren't using the U2F Token, usually for builds
// without --feature webdriver
#[allow(dead_code)]
impl SoftwareU2FToken {
pub fn new() -> SoftwareU2FToken {
Self {}
}
pub fn register(
&self,
_flags: crate::RegisterFlags,
_timeout: u64,
_challenge: Vec<u8>,
_application: crate::AppId,
_key_handles: Vec<crate::KeyHandle>,
) -> crate::Result<crate::RegisterResult> {
Ok((vec![0u8; 16], self.dev_info()))
}
/// The implementation of this method must return quickly and should
/// report its status via the status and callback methods
pub fn sign(
&self,
_flags: crate::SignFlags,
_timeout: u64,
_challenge: Vec<u8>,
_app_ids: Vec<crate::AppId>,
_key_handles: Vec<crate::KeyHandle>,
) -> crate::Result<crate::SignResult> {
Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info()))
}
pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo {
crate::u2ftypes::U2FDeviceInfo {
vendor_name: b"Mozilla".to_vec(),
device_name: b"Authenticator Webdriver Token".to_vec(),
version_interface: 0,
version_major: 1,
version_minor: 2,
version_build: 3,
cap_flags: 0,
}
}
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {}

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

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
mod testtoken;
mod virtualmanager;
mod web_api;
pub use virtualmanager::VirtualManager;

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

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors;
use crate::virtualdevices::software_u2f::SoftwareU2FToken;
use crate::{RegisterFlags, RegisterResult, SignFlags, SignResult};
pub enum TestWireProtocol {
CTAP1,
CTAP2,
}
impl TestWireProtocol {
pub fn to_webdriver_string(&self) -> String {
match self {
TestWireProtocol::CTAP1 => "ctap1/u2f".to_string(),
TestWireProtocol::CTAP2 => "ctap2".to_string(),
}
}
}
pub struct TestTokenCredential {
pub credential: Vec<u8>,
pub privkey: Vec<u8>,
pub user_handle: Vec<u8>,
pub sign_count: u64,
pub is_resident_credential: bool,
pub rp_id: String,
}
pub struct TestToken {
pub id: u64,
pub protocol: TestWireProtocol,
pub transport: String,
pub is_user_consenting: bool,
pub has_user_verification: bool,
pub is_user_verified: bool,
pub has_resident_key: bool,
pub u2f_impl: Option<SoftwareU2FToken>,
pub credentials: Vec<TestTokenCredential>,
}
impl TestToken {
pub fn new(
id: u64,
protocol: TestWireProtocol,
transport: String,
is_user_consenting: bool,
has_user_verification: bool,
is_user_verified: bool,
has_resident_key: bool,
) -> TestToken {
match protocol {
TestWireProtocol::CTAP1 => Self {
id,
protocol,
transport,
is_user_consenting,
has_user_verification,
is_user_verified,
has_resident_key,
u2f_impl: Some(SoftwareU2FToken::new()),
credentials: Vec::new(),
},
_ => unreachable!(),
}
}
pub fn insert_credential(
&mut self,
credential: &[u8],
privkey: &[u8],
rp_id: String,
is_resident_credential: bool,
user_handle: &[u8],
sign_count: u64,
) {
let c = TestTokenCredential {
credential: credential.to_vec(),
privkey: privkey.to_vec(),
rp_id,
is_resident_credential,
user_handle: user_handle.to_vec(),
sign_count,
};
match self
.credentials
.binary_search_by_key(&credential, |probe| &probe.credential)
{
Ok(_) => {}
Err(idx) => self.credentials.insert(idx, c),
}
}
pub fn delete_credential(&mut self, credential: &[u8]) -> bool {
debug!("Asking to delete credential",);
if let Ok(idx) = self
.credentials
.binary_search_by_key(&credential, |probe| &probe.credential)
{
debug!("Asking to delete credential from idx {}", idx);
self.credentials.remove(idx);
return true;
}
false
}
pub fn register(&self) -> crate::Result<RegisterResult> {
if self.u2f_impl.is_some() {
return self.u2f_impl.as_ref().unwrap().register(
RegisterFlags::empty(),
10_000,
vec![0; 32],
vec![0; 32],
vec![],
);
}
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
))
}
pub fn sign(&self) -> crate::Result<SignResult> {
if self.u2f_impl.is_some() {
return self.u2f_impl.as_ref().unwrap().sign(
SignFlags::empty(),
10_000,
vec![0; 32],
vec![vec![0; 32]],
vec![],
);
}
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
))
}
}

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

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use runloop::RunLoop;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::ops::Deref;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::vec;
use std::{io, string, thread};
use crate::authenticatorservice::AuthenticatorTransport;
use crate::errors;
use crate::statecallback::StateCallback;
use crate::virtualdevices::webdriver::{testtoken, web_api};
pub struct VirtualManagerState {
pub authenticator_counter: u64,
pub tokens: vec::Vec<testtoken::TestToken>,
}
impl VirtualManagerState {
pub fn new() -> Arc<Mutex<VirtualManagerState>> {
Arc::new(Mutex::new(VirtualManagerState {
authenticator_counter: 0,
tokens: vec![],
}))
}
}
pub struct VirtualManager {
addr: SocketAddr,
state: Arc<Mutex<VirtualManagerState>>,
rloop: Option<RunLoop>,
}
impl VirtualManager {
pub fn new() -> io::Result<Self> {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080);
let state = VirtualManagerState::new();
let stateclone = state.clone();
let builder = thread::Builder::new().name("WebDriver Command Server".into());
builder.spawn(move || {
web_api::serve(stateclone, addr);
})?;
Ok(Self {
addr,
state,
rloop: None,
})
}
pub fn url(&self) -> string::String {
format!("http://{}/webauthn/authenticator", &self.addr)
}
}
impl AuthenticatorTransport for VirtualManager {
fn register(
&mut self,
_flags: crate::RegisterFlags,
timeout: u64,
_challenge: Vec<u8>,
_application: crate::AppId,
_key_handles: Vec<crate::KeyHandle>,
_status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
if self.rloop.is_some() {
error!("WebDriver state error, prior operation never cancelled.");
return Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
));
}
let state = self.state.clone();
let rloop = try_or!(
RunLoop::new_with_timeout(
move |alive| {
while alive() {
let state_obj = state.lock().unwrap();
for token in state_obj.tokens.deref() {
if token.is_user_consenting {
let register_result = token.register();
thread::spawn(move || {
callback.call(register_result);
});
return;
}
}
}
},
timeout
),
|_| Err(errors::AuthenticatorError::Platform)
);
self.rloop = Some(rloop);
Ok(())
}
fn sign(
&mut self,
_flags: crate::SignFlags,
timeout: u64,
_challenge: Vec<u8>,
_app_ids: Vec<crate::AppId>,
_key_handles: Vec<crate::KeyHandle>,
_status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
if self.rloop.is_some() {
error!("WebDriver state error, prior operation never cancelled.");
return Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
));
}
let state = self.state.clone();
let rloop = try_or!(
RunLoop::new_with_timeout(
move |alive| {
while alive() {
let state_obj = state.lock().unwrap();
for token in state_obj.tokens.deref() {
if token.is_user_consenting {
let sign_result = token.sign();
thread::spawn(move || {
callback.call(sign_result);
});
return;
}
}
}
},
timeout
),
|_| Err(errors::AuthenticatorError::Platform)
);
self.rloop = Some(rloop);
Ok(())
}
fn cancel(&mut self) -> crate::Result<()> {
if let Some(r) = self.rloop.take() {
debug!("WebDriver operation cancelled.");
r.cancel();
}
Ok(())
}
}

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

@ -0,0 +1,965 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::string;
use std::sync::{Arc, Mutex};
use warp::Filter;
use crate::virtualdevices::webdriver::{testtoken, virtualmanager::VirtualManagerState};
fn default_as_false() -> bool {
false
}
fn default_as_true() -> bool {
false
}
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct AuthenticatorConfiguration {
protocol: string::String,
transport: string::String,
#[serde(rename = "hasResidentKey")]
#[serde(default = "default_as_false")]
has_resident_key: bool,
#[serde(rename = "hasUserVerification")]
#[serde(default = "default_as_false")]
has_user_verification: bool,
#[serde(rename = "isUserConsenting")]
#[serde(default = "default_as_true")]
is_user_consenting: bool,
#[serde(rename = "isUserVerified")]
#[serde(default = "default_as_false")]
is_user_verified: bool,
}
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct CredentialParameters {
#[serde(rename = "credentialId")]
credential_id: String,
#[serde(rename = "isResidentCredential")]
is_resident_credential: bool,
#[serde(rename = "rpId")]
rp_id: String,
#[serde(rename = "privateKey")]
private_key: String,
#[serde(rename = "userHandle")]
#[serde(default)]
user_handle: String,
#[serde(rename = "signCount")]
sign_count: u64,
}
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct UserVerificationParameters {
#[serde(rename = "isUserVerified")]
is_user_verified: bool,
}
impl CredentialParameters {
fn new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters {
let credential_id = base64::encode_config(&tc.credential, base64::URL_SAFE);
let private_key = base64::encode_config(&tc.privkey, base64::URL_SAFE);
let user_handle = base64::encode_config(&tc.user_handle, base64::URL_SAFE);
CredentialParameters {
credential_id,
is_resident_credential: tc.is_resident_credential,
rp_id: tc.rp_id.clone(),
private_key,
user_handle,
sign_count: tc.sign_count,
}
}
}
fn with_state(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone
{
warp::any().map(move || state.clone())
}
fn authenticator_add(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator")
.and(warp::post())
.and(warp::body::json())
.and(with_state(state))
.and_then(handlers::authenticator_add)
}
fn authenticator_delete(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64)
.and(warp::delete())
.and(with_state(state))
.and_then(handlers::authenticator_delete)
}
fn authenticator_set_uv(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64 / "uv")
.and(warp::post())
.and(warp::body::json())
.and(with_state(state))
.and_then(handlers::authenticator_set_uv)
}
// This is not part of the specification, but it's useful for debugging
fn authenticator_get(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64)
.and(warp::get())
.and(with_state(state))
.and_then(handlers::authenticator_get)
}
fn authenticator_credential_add(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64 / "credential")
.and(warp::post())
.and(warp::body::json())
.and(with_state(state))
.and_then(handlers::authenticator_credential_add)
}
fn authenticator_credential_delete(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64 / "credentials" / String)
.and(warp::delete())
.and(with_state(state))
.and_then(handlers::authenticator_credential_delete)
}
fn authenticator_credentials_get(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64 / "credentials")
.and(warp::get())
.and(with_state(state))
.and_then(handlers::authenticator_credentials_get)
}
fn authenticator_credentials_clear(
state: Arc<Mutex<VirtualManagerState>>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("webauthn" / "authenticator" / u64 / "credentials")
.and(warp::delete())
.and(with_state(state))
.and_then(handlers::authenticator_credentials_clear)
}
mod handlers {
use super::{CredentialParameters, UserVerificationParameters};
use crate::virtualdevices::webdriver::{
testtoken, virtualmanager::VirtualManagerState, web_api::AuthenticatorConfiguration,
};
use serde::Serialize;
use std::convert::Infallible;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex};
use std::vec;
use warp::http::{uri, StatusCode};
#[derive(Serialize)]
struct JsonSuccess {}
impl JsonSuccess {
pub fn blank() -> JsonSuccess {
JsonSuccess {}
}
}
#[derive(Serialize)]
struct JsonError {
#[serde(skip_serializing_if = "Option::is_none")]
line: Option<u32>,
error: String,
details: String,
}
impl JsonError {
pub fn new(error: &str, line: u32, details: &str) -> JsonError {
JsonError {
details: details.to_string(),
error: error.to_string(),
line: Some(line),
}
}
pub fn from_status_code(code: StatusCode) -> JsonError {
JsonError {
details: code.canonical_reason().unwrap().to_string(),
line: None,
error: "".to_string(),
}
}
pub fn from_error(error: &str) -> JsonError {
JsonError {
details: "".to_string(),
error: error.to_string(),
line: None,
}
}
}
macro_rules! reply_error {
($status:expr) => {
warp::reply::with_status(
warp::reply::json(&JsonError::from_status_code($status)),
$status,
)
};
}
macro_rules! try_json {
($val:expr, $status:expr) => {
match $val {
Ok(v) => v,
Err(e) => {
return Ok(warp::reply::with_status(
warp::reply::json(&JsonError::new(
$status.canonical_reason().unwrap(),
line!(),
&e.to_string(),
)),
$status,
));
}
}
};
}
pub fn validate_rp_id(rp_id: &str) -> crate::Result<()> {
if let Ok(uri) = rp_id.parse::<uri::Uri>().map_err(|_| {
crate::errors::AuthenticatorError::U2FToken(crate::errors::U2FTokenError::Unknown)
}) {
if uri.scheme().is_none()
&& uri.path_and_query().is_none()
&& uri.port().is_none()
&& uri.host().is_some()
&& uri.authority().unwrap() == uri.host().unwrap()
// Don't try too hard to ensure it's a valid domain, just
// ensure there's a label delim in there somewhere
&& uri.host().unwrap().find('.').is_some()
{
return Ok(());
}
}
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
}
pub async fn authenticator_add(
auth: AuthenticatorConfiguration,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let protocol = match auth.protocol.as_str() {
"ctap1/u2f" => testtoken::TestWireProtocol::CTAP1,
"ctap2" => testtoken::TestWireProtocol::CTAP2,
_ => {
return Ok(warp::reply::with_status(
warp::reply::json(&JsonError::from_error(
format!("unknown protocol: {}", auth.protocol).as_str(),
)),
StatusCode::BAD_REQUEST,
))
}
};
let mut state_lock = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
let mut state_obj = state_lock.deref_mut();
state_obj.authenticator_counter += 1;
let tt = testtoken::TestToken::new(
state_obj.authenticator_counter,
protocol,
auth.transport,
auth.is_user_consenting,
auth.has_user_verification,
auth.is_user_verified,
auth.has_resident_key,
);
match state_obj
.tokens
.binary_search_by_key(&state_obj.authenticator_counter, |probe| probe.id)
{
Ok(_) => panic!("unexpected repeat of authenticator_id"),
Err(idx) => state_obj.tokens.insert(idx, tt),
}
#[derive(Serialize)]
struct AddResult {
#[serde(rename = "authenticatorId")]
authenticator_id: u64,
}
Ok(warp::reply::with_status(
warp::reply::json(&AddResult {
authenticator_id: state_obj.authenticator_counter,
}),
StatusCode::CREATED,
))
}
pub async fn authenticator_delete(
id: u64,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
match state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
Ok(idx) => state_obj.tokens.remove(idx),
Err(_) => {
return Ok(reply_error!(StatusCode::NOT_FOUND));
}
};
Ok(warp::reply::with_status(
warp::reply::json(&JsonSuccess::blank()),
StatusCode::OK,
))
}
pub async fn authenticator_get(
id: u64,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
let data = AuthenticatorConfiguration {
protocol: tt.protocol.to_webdriver_string(),
transport: tt.transport.clone(),
has_resident_key: tt.has_resident_key,
has_user_verification: tt.has_user_verification,
is_user_consenting: tt.is_user_consenting,
is_user_verified: tt.is_user_verified,
};
return Ok(warp::reply::with_status(
warp::reply::json(&data),
StatusCode::OK,
));
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
pub async fn authenticator_set_uv(
id: u64,
uv: UserVerificationParameters,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
tt.is_user_verified = uv.is_user_verified;
return Ok(warp::reply::with_status(
warp::reply::json(&JsonSuccess::blank()),
StatusCode::OK,
));
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
pub async fn authenticator_credential_add(
id: u64,
auth: CredentialParameters,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let credential = try_json!(
base64::decode_config(&auth.credential_id, base64::URL_SAFE),
StatusCode::BAD_REQUEST
);
let privkey = try_json!(
base64::decode_config(&auth.private_key, base64::URL_SAFE),
StatusCode::BAD_REQUEST
);
let userhandle = try_json!(
base64::decode_config(&auth.user_handle, base64::URL_SAFE),
StatusCode::BAD_REQUEST
);
try_json!(validate_rp_id(&auth.rp_id), StatusCode::BAD_REQUEST);
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
tt.insert_credential(
&credential,
&privkey,
auth.rp_id,
auth.is_resident_credential,
&userhandle,
auth.sign_count,
);
return Ok(warp::reply::with_status(
warp::reply::json(&JsonSuccess::blank()),
StatusCode::CREATED,
));
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
pub async fn authenticator_credential_delete(
id: u64,
credential_id: String,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let credential = try_json!(
base64::decode_config(&credential_id, base64::URL_SAFE),
StatusCode::BAD_REQUEST
);
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
debug!("Asking to delete {}", &credential_id);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
debug!("Asking to delete from token {}", tt.id);
if tt.delete_credential(&credential) {
return Ok(warp::reply::with_status(
warp::reply::json(&JsonSuccess::blank()),
StatusCode::OK,
));
}
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
pub async fn authenticator_credentials_get(
id: u64,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
let mut creds: vec::Vec<CredentialParameters> = vec![];
for ttc in &tt.credentials {
creds.push(CredentialParameters::new_from_test_token_credential(ttc));
}
return Ok(warp::reply::with_status(
warp::reply::json(&creds),
StatusCode::OK,
));
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
pub async fn authenticator_credentials_clear(
id: u64,
state: Arc<Mutex<VirtualManagerState>>,
) -> Result<impl warp::Reply, Infallible> {
let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
let tt = &mut state_obj.tokens[idx];
tt.credentials.clear();
return Ok(warp::reply::with_status(
warp::reply::json(&JsonSuccess::blank()),
StatusCode::OK,
));
}
Ok(reply_error!(StatusCode::NOT_FOUND))
}
}
#[tokio::main]
pub async fn serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr) {
let routes = authenticator_add(state.clone())
.or(authenticator_delete(state.clone()))
.or(authenticator_get(state.clone()))
.or(authenticator_set_uv(state.clone()))
.or(authenticator_credential_add(state.clone()))
.or(authenticator_credential_delete(state.clone()))
.or(authenticator_credentials_get(state.clone()))
.or(authenticator_credentials_clear(state.clone()));
warp::serve(routes).run(addr).await;
}
#[cfg(test)]
mod tests {
use super::handlers::validate_rp_id;
use super::testtoken::*;
use super::*;
use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState;
use bytes::Buf;
use std::sync::{Arc, Mutex};
use warp::http::StatusCode;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn test_validate_rp_id() {
init();
assert_matches!(
validate_rp_id(&String::from("http://example.com")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("https://example.com")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("example.com:443")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("example.com/path")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("example.com:443/path")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("user:pass@example.com")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(
validate_rp_id(&String::from("com")),
Err(crate::errors::AuthenticatorError::U2FToken(
crate::errors::U2FTokenError::Unknown,
))
);
assert_matches!(validate_rp_id(&String::from("example.com")), Ok(()));
}
fn mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>> {
let state = VirtualManagerState::new();
{
let mut state_obj = state.lock().unwrap();
for id in ids {
state_obj.tokens.push(TestToken::new(
*id,
TestWireProtocol::CTAP1,
"internal".to_string(),
true,
true,
true,
true,
));
}
state_obj.tokens.sort_by_key(|probe| probe.id)
}
state
}
fn assert_success_rsp_blank(body: &bytes::Bytes) {
assert_eq!(String::from_utf8_lossy(body.bytes()), r#"{}"#)
}
fn assert_creds_equals_test_token_params(
a: &[CredentialParameters],
b: &[TestTokenCredential],
) {
assert_eq!(a.len(), b.len());
for (i, j) in a.iter().zip(b.iter()) {
assert_eq!(
i.credential_id,
base64::encode_config(&j.credential, base64::URL_SAFE)
);
assert_eq!(
i.user_handle,
base64::encode_config(&j.user_handle, base64::URL_SAFE)
);
assert_eq!(
i.private_key,
base64::encode_config(&j.privkey, base64::URL_SAFE)
);
assert_eq!(i.rp_id, j.rp_id);
assert_eq!(i.sign_count, j.sign_count);
assert_eq!(i.is_resident_credential, j.is_resident_credential);
}
}
#[tokio::test]
async fn test_authenticator_add() {
init();
let filter = authenticator_add(mk_state_with_token_list(&[]));
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
let valid_add = AuthenticatorConfiguration {
protocol: "ctap1/u2f".to_string(),
transport: "usb".to_string(),
has_resident_key: false,
has_user_verification: false,
is_user_consenting: false,
is_user_verified: false,
};
{
let mut invalid = valid_add.clone();
invalid.protocol = "unknown".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
assert!(String::from_utf8_lossy(res.body().bytes())
.contains(&String::from("unknown protocol: unknown")));
}
{
let mut unknown = valid_add.clone();
unknown.transport = "unknown".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator")
.json(&unknown)
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_eq!(
String::from_utf8_lossy(res.body().bytes()),
r#"{"authenticatorId":1}"#
)
}
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator")
.json(&valid_add)
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_eq!(
String::from_utf8_lossy(res.body().bytes()),
r#"{"authenticatorId":2}"#
)
}
}
#[tokio::test]
async fn test_authenticator_delete() {
init();
let filter = authenticator_delete(mk_state_with_token_list(&[32]));
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/3")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/32")
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_success_rsp_blank(res.body());
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/42")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
}
#[tokio::test]
async fn test_authenticator_change_uv() {
init();
let state = mk_state_with_token_list(&[1]);
let filter = authenticator_set_uv(state.clone());
{
let state_obj = state.lock().unwrap();
assert_eq!(true, state_obj.tokens[0].is_user_verified);
}
{
// Empty POST is bad
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/uv")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
// Unexpected POST structure is bad
#[derive(Serialize)]
struct Unexpected {
id: u64,
}
let unexpected = Unexpected { id: 4 };
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/uv")
.json(&unexpected)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let param_false = UserVerificationParameters {
is_user_verified: false,
};
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/uv")
.json(&param_false)
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(false, state_obj.tokens[0].is_user_verified);
}
{
let param_false = UserVerificationParameters {
is_user_verified: true,
};
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/uv")
.json(&param_false)
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(true, state_obj.tokens[0].is_user_verified);
}
}
#[tokio::test]
async fn test_authenticator_credentials() {
init();
let state = mk_state_with_token_list(&[1]);
let filter = authenticator_credential_add(state.clone())
.or(authenticator_credential_delete(state.clone()))
.or(authenticator_credentials_get(state.clone()))
.or(authenticator_credentials_clear(state.clone()));
let valid_add_credential = CredentialParameters {
credential_id: r"c3VwZXIgcmVhZGVy".to_string(),
is_resident_credential: true,
rp_id: "valid.rpid".to_string(),
private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE),
user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE),
sign_count: 0,
};
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.credential_id = "!@#$ invalid base64".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.rp_id = "example".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
}
{
let mut invalid = valid_add_credential.clone();
invalid.rp_id = "https://example.com".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&invalid)
.reply(&filter)
.await;
assert!(res.status().is_client_error());
let state_obj = state.lock().unwrap();
assert_eq!(0, state_obj.tokens[0].credentials.len());
}
{
let mut no_user_handle = valid_add_credential.clone();
no_user_handle.user_handle = "".to_string();
no_user_handle.credential_id = "YQo=".to_string();
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&no_user_handle)
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(1, state_obj.tokens[0].credentials.len());
let c = &state_obj.tokens[0].credentials[0];
assert!(c.user_handle.is_empty());
}
{
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&valid_add_credential)
.reply(&filter)
.await;
assert_eq!(res.status(), StatusCode::CREATED);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(2, state_obj.tokens[0].credentials.len());
let c = &state_obj.tokens[0].credentials[1];
assert!(!c.user_handle.is_empty());
}
{
// Duplicate, should still be two credentials
let res = warp::test::request()
.method("POST")
.path("/webauthn/authenticator/1/credential")
.json(&valid_add_credential)
.reply(&filter)
.await;
assert_eq!(res.status(), StatusCode::CREATED);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(2, state_obj.tokens[0].credentials.len());
}
{
let res = warp::test::request()
.method("GET")
.path("/webauthn/authenticator/1/credentials")
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
let (_, body) = res.into_parts();
let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap();
let state_obj = state.lock().unwrap();
assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials);
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials/YmxhbmsK")
.reply(&filter)
.await;
assert_eq!(res.status(), 404);
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy")
.reply(&filter)
.await;
assert_eq!(res.status(), 200);
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(1, state_obj.tokens[0].credentials.len());
}
{
let res = warp::test::request()
.method("DELETE")
.path("/webauthn/authenticator/1/credentials")
.reply(&filter)
.await;
assert!(res.status().is_success());
assert_success_rsp_blank(res.body());
let state_obj = state.lock().unwrap();
assert_eq!(0, state_obj.tokens[0].credentials.len());
}
}
}

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

@ -0,0 +1,2 @@
requests>=2.23.0
rich>=3.0

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

@ -0,0 +1,207 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from rich.console import Console
from rich.logging import RichHandler
import argparse
import logging
import requests
console = Console()
log = logging.getLogger("webdriver-driver")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help="sub-command help")
parser.add_argument(
"--verbose", "-v", help="Be more verbose", action="count", default=0
)
parser.add_argument(
"--url",
default="http://localhost:8080/webauthn/authenticator",
help="webdriver url",
)
def device_add(args):
data = {
"protocol": args.protocol,
"transport": args.transport,
"hasResidentKey": args.residentkey in ["true", "yes"],
"isUserConsenting": args.consent in ["true", "yes"],
"hasUserVerification": args.uv in ["available", "verified"],
"isUserVerified": args.uv in ["verified"],
}
console.print("Adding new device: ", data)
rsp = requests.post(args.url, json=data)
console.print(rsp)
console.print(rsp.json())
parser_add = subparsers.add_parser("add", help="Add a device")
parser_add.set_defaults(func=device_add)
parser_add.add_argument(
"--consent",
choices=["yes", "no", "true", "false"],
default="true",
help="consent automatically",
)
parser_add.add_argument(
"--residentkey",
choices=["yes", "no", "true", "false"],
default="no",
help="indicate a resident key",
)
parser_add.add_argument(
"--uv",
choices=["no", "available", "verified"],
default="no",
help="indicate user verification",
)
parser_add.add_argument(
"--protocol", choices=["ctap1/u2f", "ctap2"], default="ctap1/u2f", help="protocol"
)
parser_add.add_argument("--transport", default="usb", help="transport type(s)")
def device_delete(args):
rsp = requests.delete(f"{args.url}/{args.id}")
console.print(rsp)
console.print(rsp.json())
parser_delete = subparsers.add_parser("delete", help="Delete a device")
parser_delete.set_defaults(func=device_delete)
parser_delete.add_argument("id", type=int, help="device ID to delete")
def device_view(args):
rsp = requests.get(f"{args.url}/{args.id}")
console.print(rsp)
console.print(rsp.json())
parser_view = subparsers.add_parser("view", help="View data about a device")
parser_view.set_defaults(func=device_view)
parser_view.add_argument("id", type=int, help="device ID to view")
def device_update_uv(args):
data = {"isUserVerified": args.uv in ["verified", "yes"]}
rsp = requests.post(f"{args.url}/{args.id}/uv", json=data)
console.print(rsp)
console.print(rsp.json())
parser_update_uv = subparsers.add_parser(
"update-uv", help="Update the User Verified setting"
)
parser_update_uv.set_defaults(func=device_update_uv)
parser_update_uv.add_argument("id", type=int, help="device ID to update")
parser_update_uv.add_argument(
"uv",
choices=["no", "yes", "verified"],
default="no",
help="indicate user verification",
)
def credential_add(args):
data = {
"credentialId": args.credentialId,
"isResidentCredential": args.isResidentCredential in ["true", "yes"],
"rpId": args.rpId,
"privateKey": args.privateKey,
"signCount": args.signCount,
}
if args.userHandle:
data["userHandle"] = (args.userHandle,)
console.print(f"Adding new credential to device {args.id}: ", data)
rsp = requests.post(f"{args.url}/{args.id}/credential", json=data)
console.print(rsp)
console.print(rsp.json())
parser_credential_add = subparsers.add_parser("addcred", help="Add a credential")
parser_credential_add.set_defaults(func=credential_add)
parser_credential_add.add_argument(
"--id", required=True, type=int, help="device ID to use"
)
parser_credential_add.add_argument(
"--credentialId", required=True, help="base64url-encoded credential ID"
)
parser_credential_add.add_argument(
"--isResidentCredential",
choices=["yes", "no", "true", "false"],
default="no",
help="indicate a resident key",
)
parser_credential_add.add_argument("--rpId", required=True, help="RP id (hostname)")
parser_credential_add.add_argument(
"--privateKey", required=True, help="base64url-encoded private key per RFC 5958"
)
parser_credential_add.add_argument("--userHandle", help="base64url-encoded user handle")
parser_credential_add.add_argument(
"--signCount", default=0, type=int, help="initial signature counter"
)
def credentials_get(args):
rsp = requests.get(f"{args.url}/{args.id}/credentials")
console.print(rsp)
console.print(rsp.json())
parser_credentials_get = subparsers.add_parser("getcreds", help="Get credentials")
parser_credentials_get.set_defaults(func=credentials_get)
parser_credentials_get.add_argument("id", type=int, help="device ID to query")
def credential_delete(args):
rsp = requests.delete(f"{args.url}/{args.id}/credentials/{args.credentialId}")
console.print(rsp)
console.print(rsp.json())
parser_credentials_get = subparsers.add_parser("delcred", help="Delete a credential")
parser_credentials_get.set_defaults(func=credential_delete)
parser_credentials_get.add_argument("id", type=int, help="device ID to affect")
parser_credentials_get.add_argument(
"credentialId", help="base64url-encoded credential ID"
)
def credentials_clear(args):
rsp = requests.delete(f"{args.url}/{args.id}/credentials")
console.print(rsp)
console.print(rsp.json())
parser_credentials_get = subparsers.add_parser(
"clearcreds", help="Clear all credentials for a device"
)
parser_credentials_get.set_defaults(func=credentials_clear)
parser_credentials_get.add_argument("id", type=int, help="device ID to affect")
def main():
args = parser.parse_args()
loglevel = logging.INFO
if args.verbose > 0:
loglevel = logging.DEBUG
logging.basicConfig(
level=loglevel, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
)
try:
args.func(args)
except requests.exceptions.ConnectionError as ce:
log.error(f"Connection refused to {args.url}: {ce}")
if __name__ == "__main__":
main()

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

@ -25,7 +25,7 @@ cubeb-sys = { version = "0.7", optional = true, features=["gecko-in-tree"] }
encoding_glue = { path = "../../../../intl/encoding_glue" }
audioipc-client = { path = "../../../../media/audioipc/client", optional = true }
audioipc-server = { path = "../../../../media/audioipc/server", optional = true }
authenticator = "0.3.0"
authenticator = "0.3.1"
gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
gecko_logger = { path = "../../../../xpcom/rust/gecko_logger" }
rsdparsa_capi = { path = "../../../../dom/media/webrtc/sdp/rsdparsa_capi" }