1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 use serde::{Deserialize, Serialize};
6 use std::net::SocketAddr;
7 use std::string;
8 use std::sync::{Arc, Mutex};
9 use warp::Filter;
10
11 use crate::virtualdevices::webdriver::{testtoken, virtualmanager::VirtualManagerState};
12
default_as_false() -> bool13 fn default_as_false() -> bool {
14 false
15 }
default_as_true() -> bool16 fn default_as_true() -> bool {
17 false
18 }
19
20 #[derive(Serialize, Deserialize, Clone, PartialEq)]
21 pub struct AuthenticatorConfiguration {
22 protocol: string::String,
23 transport: string::String,
24 #[serde(rename = "hasResidentKey")]
25 #[serde(default = "default_as_false")]
26 has_resident_key: bool,
27 #[serde(rename = "hasUserVerification")]
28 #[serde(default = "default_as_false")]
29 has_user_verification: bool,
30 #[serde(rename = "isUserConsenting")]
31 #[serde(default = "default_as_true")]
32 is_user_consenting: bool,
33 #[serde(rename = "isUserVerified")]
34 #[serde(default = "default_as_false")]
35 is_user_verified: bool,
36 }
37
38 #[derive(Serialize, Deserialize, Clone, PartialEq)]
39 pub struct CredentialParameters {
40 #[serde(rename = "credentialId")]
41 credential_id: String,
42 #[serde(rename = "isResidentCredential")]
43 is_resident_credential: bool,
44 #[serde(rename = "rpId")]
45 rp_id: String,
46 #[serde(rename = "privateKey")]
47 private_key: String,
48 #[serde(rename = "userHandle")]
49 #[serde(default)]
50 user_handle: String,
51 #[serde(rename = "signCount")]
52 sign_count: u64,
53 }
54
55 #[derive(Serialize, Deserialize, Clone, PartialEq)]
56 pub struct UserVerificationParameters {
57 #[serde(rename = "isUserVerified")]
58 is_user_verified: bool,
59 }
60
61 impl CredentialParameters {
new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters62 fn new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters {
63 let credential_id = base64::encode_config(&tc.credential, base64::URL_SAFE);
64
65 let private_key = base64::encode_config(&tc.privkey, base64::URL_SAFE);
66
67 let user_handle = base64::encode_config(&tc.user_handle, base64::URL_SAFE);
68
69 CredentialParameters {
70 credential_id,
71 is_resident_credential: tc.is_resident_credential,
72 rp_id: tc.rp_id.clone(),
73 private_key,
74 user_handle,
75 sign_count: tc.sign_count,
76 }
77 }
78 }
79
with_state( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone80 fn with_state(
81 state: Arc<Mutex<VirtualManagerState>>,
82 ) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone
83 {
84 warp::any().map(move || state.clone())
85 }
86
authenticator_add( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone87 fn authenticator_add(
88 state: Arc<Mutex<VirtualManagerState>>,
89 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
90 warp::path!("webauthn" / "authenticator")
91 .and(warp::post())
92 .and(warp::body::json())
93 .and(with_state(state))
94 .and_then(handlers::authenticator_add)
95 }
96
authenticator_delete( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone97 fn authenticator_delete(
98 state: Arc<Mutex<VirtualManagerState>>,
99 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
100 warp::path!("webauthn" / "authenticator" / u64)
101 .and(warp::delete())
102 .and(with_state(state))
103 .and_then(handlers::authenticator_delete)
104 }
105
authenticator_set_uv( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone106 fn authenticator_set_uv(
107 state: Arc<Mutex<VirtualManagerState>>,
108 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
109 warp::path!("webauthn" / "authenticator" / u64 / "uv")
110 .and(warp::post())
111 .and(warp::body::json())
112 .and(with_state(state))
113 .and_then(handlers::authenticator_set_uv)
114 }
115
116 // This is not part of the specification, but it's useful for debugging
authenticator_get( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone117 fn authenticator_get(
118 state: Arc<Mutex<VirtualManagerState>>,
119 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
120 warp::path!("webauthn" / "authenticator" / u64)
121 .and(warp::get())
122 .and(with_state(state))
123 .and_then(handlers::authenticator_get)
124 }
125
authenticator_credential_add( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone126 fn authenticator_credential_add(
127 state: Arc<Mutex<VirtualManagerState>>,
128 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
129 warp::path!("webauthn" / "authenticator" / u64 / "credential")
130 .and(warp::post())
131 .and(warp::body::json())
132 .and(with_state(state))
133 .and_then(handlers::authenticator_credential_add)
134 }
135
authenticator_credential_delete( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone136 fn authenticator_credential_delete(
137 state: Arc<Mutex<VirtualManagerState>>,
138 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
139 warp::path!("webauthn" / "authenticator" / u64 / "credentials" / String)
140 .and(warp::delete())
141 .and(with_state(state))
142 .and_then(handlers::authenticator_credential_delete)
143 }
144
authenticator_credentials_get( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone145 fn authenticator_credentials_get(
146 state: Arc<Mutex<VirtualManagerState>>,
147 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
148 warp::path!("webauthn" / "authenticator" / u64 / "credentials")
149 .and(warp::get())
150 .and(with_state(state))
151 .and_then(handlers::authenticator_credentials_get)
152 }
153
authenticator_credentials_clear( state: Arc<Mutex<VirtualManagerState>>, ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone154 fn authenticator_credentials_clear(
155 state: Arc<Mutex<VirtualManagerState>>,
156 ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
157 warp::path!("webauthn" / "authenticator" / u64 / "credentials")
158 .and(warp::delete())
159 .and(with_state(state))
160 .and_then(handlers::authenticator_credentials_clear)
161 }
162
163 mod handlers {
164 use super::{CredentialParameters, UserVerificationParameters};
165 use crate::virtualdevices::webdriver::{
166 testtoken, virtualmanager::VirtualManagerState, web_api::AuthenticatorConfiguration,
167 };
168 use serde::Serialize;
169 use std::convert::Infallible;
170 use std::ops::DerefMut;
171 use std::sync::{Arc, Mutex};
172 use std::vec;
173 use warp::http::{uri, StatusCode};
174
175 #[derive(Serialize)]
176 struct JsonSuccess {}
177
178 impl JsonSuccess {
blank() -> JsonSuccess179 pub fn blank() -> JsonSuccess {
180 JsonSuccess {}
181 }
182 }
183
184 #[derive(Serialize)]
185 struct JsonError {
186 #[serde(skip_serializing_if = "Option::is_none")]
187 line: Option<u32>,
188 error: String,
189 details: String,
190 }
191
192 impl JsonError {
new(error: &str, line: u32, details: &str) -> JsonError193 pub fn new(error: &str, line: u32, details: &str) -> JsonError {
194 JsonError {
195 details: details.to_string(),
196 error: error.to_string(),
197 line: Some(line),
198 }
199 }
from_status_code(code: StatusCode) -> JsonError200 pub fn from_status_code(code: StatusCode) -> JsonError {
201 JsonError {
202 details: code.canonical_reason().unwrap().to_string(),
203 line: None,
204 error: "".to_string(),
205 }
206 }
from_error(error: &str) -> JsonError207 pub fn from_error(error: &str) -> JsonError {
208 JsonError {
209 details: "".to_string(),
210 error: error.to_string(),
211 line: None,
212 }
213 }
214 }
215
216 macro_rules! reply_error {
217 ($status:expr) => {
218 warp::reply::with_status(
219 warp::reply::json(&JsonError::from_status_code($status)),
220 $status,
221 )
222 };
223 }
224
225 macro_rules! try_json {
226 ($val:expr, $status:expr) => {
227 match $val {
228 Ok(v) => v,
229 Err(e) => {
230 return Ok(warp::reply::with_status(
231 warp::reply::json(&JsonError::new(
232 $status.canonical_reason().unwrap(),
233 line!(),
234 &e.to_string(),
235 )),
236 $status,
237 ));
238 }
239 }
240 };
241 }
242
validate_rp_id(rp_id: &str) -> crate::Result<()>243 pub fn validate_rp_id(rp_id: &str) -> crate::Result<()> {
244 if let Ok(uri) = rp_id.parse::<uri::Uri>().map_err(|_| {
245 crate::errors::AuthenticatorError::U2FToken(crate::errors::U2FTokenError::Unknown)
246 }) {
247 if uri.scheme().is_none()
248 && uri.path_and_query().is_none()
249 && uri.port().is_none()
250 && uri.host().is_some()
251 && uri.authority().unwrap() == uri.host().unwrap()
252 // Don't try too hard to ensure it's a valid domain, just
253 // ensure there's a label delim in there somewhere
254 && uri.host().unwrap().find('.').is_some()
255 {
256 return Ok(());
257 }
258 }
259 Err(crate::errors::AuthenticatorError::U2FToken(
260 crate::errors::U2FTokenError::Unknown,
261 ))
262 }
263
authenticator_add( auth: AuthenticatorConfiguration, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>264 pub async fn authenticator_add(
265 auth: AuthenticatorConfiguration,
266 state: Arc<Mutex<VirtualManagerState>>,
267 ) -> Result<impl warp::Reply, Infallible> {
268 let protocol = match auth.protocol.as_str() {
269 "ctap1/u2f" => testtoken::TestWireProtocol::CTAP1,
270 "ctap2" => testtoken::TestWireProtocol::CTAP2,
271 _ => {
272 return Ok(warp::reply::with_status(
273 warp::reply::json(&JsonError::from_error(
274 format!("unknown protocol: {}", auth.protocol).as_str(),
275 )),
276 StatusCode::BAD_REQUEST,
277 ))
278 }
279 };
280
281 let mut state_lock = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
282 let mut state_obj = state_lock.deref_mut();
283 state_obj.authenticator_counter += 1;
284
285 let tt = testtoken::TestToken::new(
286 state_obj.authenticator_counter,
287 protocol,
288 auth.transport,
289 auth.is_user_consenting,
290 auth.has_user_verification,
291 auth.is_user_verified,
292 auth.has_resident_key,
293 );
294
295 match state_obj
296 .tokens
297 .binary_search_by_key(&state_obj.authenticator_counter, |probe| probe.id)
298 {
299 Ok(_) => panic!("unexpected repeat of authenticator_id"),
300 Err(idx) => state_obj.tokens.insert(idx, tt),
301 }
302
303 #[derive(Serialize)]
304 struct AddResult {
305 #[serde(rename = "authenticatorId")]
306 authenticator_id: u64,
307 }
308
309 Ok(warp::reply::with_status(
310 warp::reply::json(&AddResult {
311 authenticator_id: state_obj.authenticator_counter,
312 }),
313 StatusCode::CREATED,
314 ))
315 }
316
authenticator_delete( id: u64, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>317 pub async fn authenticator_delete(
318 id: u64,
319 state: Arc<Mutex<VirtualManagerState>>,
320 ) -> Result<impl warp::Reply, Infallible> {
321 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
322 match state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
323 Ok(idx) => state_obj.tokens.remove(idx),
324 Err(_) => {
325 return Ok(reply_error!(StatusCode::NOT_FOUND));
326 }
327 };
328
329 Ok(warp::reply::with_status(
330 warp::reply::json(&JsonSuccess::blank()),
331 StatusCode::OK,
332 ))
333 }
334
authenticator_get( id: u64, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>335 pub async fn authenticator_get(
336 id: u64,
337 state: Arc<Mutex<VirtualManagerState>>,
338 ) -> Result<impl warp::Reply, Infallible> {
339 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
340 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
341 let tt = &mut state_obj.tokens[idx];
342
343 let data = AuthenticatorConfiguration {
344 protocol: tt.protocol.to_webdriver_string(),
345 transport: tt.transport.clone(),
346 has_resident_key: tt.has_resident_key,
347 has_user_verification: tt.has_user_verification,
348 is_user_consenting: tt.is_user_consenting,
349 is_user_verified: tt.is_user_verified,
350 };
351
352 return Ok(warp::reply::with_status(
353 warp::reply::json(&data),
354 StatusCode::OK,
355 ));
356 }
357
358 Ok(reply_error!(StatusCode::NOT_FOUND))
359 }
360
authenticator_set_uv( id: u64, uv: UserVerificationParameters, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>361 pub async fn authenticator_set_uv(
362 id: u64,
363 uv: UserVerificationParameters,
364 state: Arc<Mutex<VirtualManagerState>>,
365 ) -> Result<impl warp::Reply, Infallible> {
366 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
367
368 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
369 let tt = &mut state_obj.tokens[idx];
370 tt.is_user_verified = uv.is_user_verified;
371 return Ok(warp::reply::with_status(
372 warp::reply::json(&JsonSuccess::blank()),
373 StatusCode::OK,
374 ));
375 }
376
377 Ok(reply_error!(StatusCode::NOT_FOUND))
378 }
379
authenticator_credential_add( id: u64, auth: CredentialParameters, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>380 pub async fn authenticator_credential_add(
381 id: u64,
382 auth: CredentialParameters,
383 state: Arc<Mutex<VirtualManagerState>>,
384 ) -> Result<impl warp::Reply, Infallible> {
385 let credential = try_json!(
386 base64::decode_config(&auth.credential_id, base64::URL_SAFE),
387 StatusCode::BAD_REQUEST
388 );
389
390 let privkey = try_json!(
391 base64::decode_config(&auth.private_key, base64::URL_SAFE),
392 StatusCode::BAD_REQUEST
393 );
394
395 let userhandle = try_json!(
396 base64::decode_config(&auth.user_handle, base64::URL_SAFE),
397 StatusCode::BAD_REQUEST
398 );
399
400 try_json!(validate_rp_id(&auth.rp_id), StatusCode::BAD_REQUEST);
401
402 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
403 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
404 let tt = &mut state_obj.tokens[idx];
405
406 tt.insert_credential(
407 &credential,
408 &privkey,
409 auth.rp_id,
410 auth.is_resident_credential,
411 &userhandle,
412 auth.sign_count,
413 );
414
415 return Ok(warp::reply::with_status(
416 warp::reply::json(&JsonSuccess::blank()),
417 StatusCode::CREATED,
418 ));
419 }
420
421 Ok(reply_error!(StatusCode::NOT_FOUND))
422 }
423
authenticator_credential_delete( id: u64, credential_id: String, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>424 pub async fn authenticator_credential_delete(
425 id: u64,
426 credential_id: String,
427 state: Arc<Mutex<VirtualManagerState>>,
428 ) -> Result<impl warp::Reply, Infallible> {
429 let credential = try_json!(
430 base64::decode_config(&credential_id, base64::URL_SAFE),
431 StatusCode::BAD_REQUEST
432 );
433
434 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
435
436 debug!("Asking to delete {}", &credential_id);
437
438 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
439 let tt = &mut state_obj.tokens[idx];
440 debug!("Asking to delete from token {}", tt.id);
441 if tt.delete_credential(&credential) {
442 return Ok(warp::reply::with_status(
443 warp::reply::json(&JsonSuccess::blank()),
444 StatusCode::OK,
445 ));
446 }
447 }
448
449 Ok(reply_error!(StatusCode::NOT_FOUND))
450 }
451
authenticator_credentials_get( id: u64, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>452 pub async fn authenticator_credentials_get(
453 id: u64,
454 state: Arc<Mutex<VirtualManagerState>>,
455 ) -> Result<impl warp::Reply, Infallible> {
456 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
457
458 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
459 let tt = &mut state_obj.tokens[idx];
460 let mut creds: vec::Vec<CredentialParameters> = vec![];
461 for ttc in &tt.credentials {
462 creds.push(CredentialParameters::new_from_test_token_credential(ttc));
463 }
464
465 return Ok(warp::reply::with_status(
466 warp::reply::json(&creds),
467 StatusCode::OK,
468 ));
469 }
470
471 Ok(reply_error!(StatusCode::NOT_FOUND))
472 }
473
authenticator_credentials_clear( id: u64, state: Arc<Mutex<VirtualManagerState>>, ) -> Result<impl warp::Reply, Infallible>474 pub async fn authenticator_credentials_clear(
475 id: u64,
476 state: Arc<Mutex<VirtualManagerState>>,
477 ) -> Result<impl warp::Reply, Infallible> {
478 let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
479 if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
480 let tt = &mut state_obj.tokens[idx];
481
482 tt.credentials.clear();
483
484 return Ok(warp::reply::with_status(
485 warp::reply::json(&JsonSuccess::blank()),
486 StatusCode::OK,
487 ));
488 }
489
490 Ok(reply_error!(StatusCode::NOT_FOUND))
491 }
492 }
493
494 #[tokio::main]
serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr)495 pub async fn serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr) {
496 let routes = authenticator_add(state.clone())
497 .or(authenticator_delete(state.clone()))
498 .or(authenticator_get(state.clone()))
499 .or(authenticator_set_uv(state.clone()))
500 .or(authenticator_credential_add(state.clone()))
501 .or(authenticator_credential_delete(state.clone()))
502 .or(authenticator_credentials_get(state.clone()))
503 .or(authenticator_credentials_clear(state.clone()));
504
505 warp::serve(routes).run(addr).await;
506 }
507
508 #[cfg(test)]
509 mod tests {
510 use super::handlers::validate_rp_id;
511 use super::testtoken::*;
512 use super::*;
513 use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState;
514 use bytes::Buf;
515 use std::sync::{Arc, Mutex};
516 use warp::http::StatusCode;
517
init()518 fn init() {
519 let _ = env_logger::builder().is_test(true).try_init();
520 }
521
522 #[test]
test_validate_rp_id()523 fn test_validate_rp_id() {
524 init();
525
526 assert_matches!(
527 validate_rp_id(&String::from("http://example.com")),
528 Err(crate::errors::AuthenticatorError::U2FToken(
529 crate::errors::U2FTokenError::Unknown,
530 ))
531 );
532 assert_matches!(
533 validate_rp_id(&String::from("https://example.com")),
534 Err(crate::errors::AuthenticatorError::U2FToken(
535 crate::errors::U2FTokenError::Unknown,
536 ))
537 );
538 assert_matches!(
539 validate_rp_id(&String::from("example.com:443")),
540 Err(crate::errors::AuthenticatorError::U2FToken(
541 crate::errors::U2FTokenError::Unknown,
542 ))
543 );
544 assert_matches!(
545 validate_rp_id(&String::from("example.com/path")),
546 Err(crate::errors::AuthenticatorError::U2FToken(
547 crate::errors::U2FTokenError::Unknown,
548 ))
549 );
550 assert_matches!(
551 validate_rp_id(&String::from("example.com:443/path")),
552 Err(crate::errors::AuthenticatorError::U2FToken(
553 crate::errors::U2FTokenError::Unknown,
554 ))
555 );
556 assert_matches!(
557 validate_rp_id(&String::from("user:pass@example.com")),
558 Err(crate::errors::AuthenticatorError::U2FToken(
559 crate::errors::U2FTokenError::Unknown,
560 ))
561 );
562 assert_matches!(
563 validate_rp_id(&String::from("com")),
564 Err(crate::errors::AuthenticatorError::U2FToken(
565 crate::errors::U2FTokenError::Unknown,
566 ))
567 );
568 assert_matches!(validate_rp_id(&String::from("example.com")), Ok(()));
569 }
570
mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>>571 fn mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>> {
572 let state = VirtualManagerState::new();
573
574 {
575 let mut state_obj = state.lock().unwrap();
576 for id in ids {
577 state_obj.tokens.push(TestToken::new(
578 *id,
579 TestWireProtocol::CTAP1,
580 "internal".to_string(),
581 true,
582 true,
583 true,
584 true,
585 ));
586 }
587
588 state_obj.tokens.sort_by_key(|probe| probe.id)
589 }
590
591 state
592 }
593
assert_success_rsp_blank(body: &bytes::Bytes)594 fn assert_success_rsp_blank(body: &bytes::Bytes) {
595 assert_eq!(String::from_utf8_lossy(body.bytes()), r#"{}"#)
596 }
597
assert_creds_equals_test_token_params( a: &[CredentialParameters], b: &[TestTokenCredential], )598 fn assert_creds_equals_test_token_params(
599 a: &[CredentialParameters],
600 b: &[TestTokenCredential],
601 ) {
602 assert_eq!(a.len(), b.len());
603
604 for (i, j) in a.iter().zip(b.iter()) {
605 assert_eq!(
606 i.credential_id,
607 base64::encode_config(&j.credential, base64::URL_SAFE)
608 );
609 assert_eq!(
610 i.user_handle,
611 base64::encode_config(&j.user_handle, base64::URL_SAFE)
612 );
613 assert_eq!(
614 i.private_key,
615 base64::encode_config(&j.privkey, base64::URL_SAFE)
616 );
617 assert_eq!(i.rp_id, j.rp_id);
618 assert_eq!(i.sign_count, j.sign_count);
619 assert_eq!(i.is_resident_credential, j.is_resident_credential);
620 }
621 }
622
623 #[tokio::test]
test_authenticator_add()624 async fn test_authenticator_add() {
625 init();
626 let filter = authenticator_add(mk_state_with_token_list(&[]));
627
628 {
629 let res = warp::test::request()
630 .method("POST")
631 .path("/webauthn/authenticator")
632 .reply(&filter)
633 .await;
634 assert!(res.status().is_client_error());
635 }
636
637 let valid_add = AuthenticatorConfiguration {
638 protocol: "ctap1/u2f".to_string(),
639 transport: "usb".to_string(),
640 has_resident_key: false,
641 has_user_verification: false,
642 is_user_consenting: false,
643 is_user_verified: false,
644 };
645
646 {
647 let mut invalid = valid_add.clone();
648 invalid.protocol = "unknown".to_string();
649 let res = warp::test::request()
650 .method("POST")
651 .path("/webauthn/authenticator")
652 .json(&invalid)
653 .reply(&filter)
654 .await;
655 assert!(res.status().is_client_error());
656 assert!(String::from_utf8_lossy(res.body().bytes())
657 .contains(&String::from("unknown protocol: unknown")));
658 }
659
660 {
661 let mut unknown = valid_add.clone();
662 unknown.transport = "unknown".to_string();
663 let res = warp::test::request()
664 .method("POST")
665 .path("/webauthn/authenticator")
666 .json(&unknown)
667 .reply(&filter)
668 .await;
669 assert!(res.status().is_success());
670 assert_eq!(
671 String::from_utf8_lossy(res.body().bytes()),
672 r#"{"authenticatorId":1}"#
673 )
674 }
675
676 {
677 let res = warp::test::request()
678 .method("POST")
679 .path("/webauthn/authenticator")
680 .json(&valid_add)
681 .reply(&filter)
682 .await;
683 assert!(res.status().is_success());
684 assert_eq!(
685 String::from_utf8_lossy(res.body().bytes()),
686 r#"{"authenticatorId":2}"#
687 )
688 }
689 }
690
691 #[tokio::test]
test_authenticator_delete()692 async fn test_authenticator_delete() {
693 init();
694 let filter = authenticator_delete(mk_state_with_token_list(&[32]));
695
696 {
697 let res = warp::test::request()
698 .method("DELETE")
699 .path("/webauthn/authenticator/3")
700 .reply(&filter)
701 .await;
702 assert!(res.status().is_client_error());
703 }
704
705 {
706 let res = warp::test::request()
707 .method("DELETE")
708 .path("/webauthn/authenticator/32")
709 .reply(&filter)
710 .await;
711 assert!(res.status().is_success());
712 assert_success_rsp_blank(res.body());
713 }
714
715 {
716 let res = warp::test::request()
717 .method("DELETE")
718 .path("/webauthn/authenticator/42")
719 .reply(&filter)
720 .await;
721 assert!(res.status().is_client_error());
722 }
723 }
724
725 #[tokio::test]
test_authenticator_change_uv()726 async fn test_authenticator_change_uv() {
727 init();
728 let state = mk_state_with_token_list(&[1]);
729 let filter = authenticator_set_uv(state.clone());
730
731 {
732 let state_obj = state.lock().unwrap();
733 assert_eq!(true, state_obj.tokens[0].is_user_verified);
734 }
735
736 {
737 // Empty POST is bad
738 let res = warp::test::request()
739 .method("POST")
740 .path("/webauthn/authenticator/1/uv")
741 .reply(&filter)
742 .await;
743 assert!(res.status().is_client_error());
744 }
745
746 {
747 // Unexpected POST structure is bad
748 #[derive(Serialize)]
749 struct Unexpected {
750 id: u64,
751 }
752 let unexpected = Unexpected { id: 4 };
753
754 let res = warp::test::request()
755 .method("POST")
756 .path("/webauthn/authenticator/1/uv")
757 .json(&unexpected)
758 .reply(&filter)
759 .await;
760 assert!(res.status().is_client_error());
761 }
762
763 {
764 let param_false = UserVerificationParameters {
765 is_user_verified: false,
766 };
767
768 let res = warp::test::request()
769 .method("POST")
770 .path("/webauthn/authenticator/1/uv")
771 .json(¶m_false)
772 .reply(&filter)
773 .await;
774 assert_eq!(res.status(), 200);
775 assert_success_rsp_blank(res.body());
776
777 let state_obj = state.lock().unwrap();
778 assert_eq!(false, state_obj.tokens[0].is_user_verified);
779 }
780
781 {
782 let param_false = UserVerificationParameters {
783 is_user_verified: true,
784 };
785
786 let res = warp::test::request()
787 .method("POST")
788 .path("/webauthn/authenticator/1/uv")
789 .json(¶m_false)
790 .reply(&filter)
791 .await;
792 assert_eq!(res.status(), 200);
793 assert_success_rsp_blank(res.body());
794
795 let state_obj = state.lock().unwrap();
796 assert_eq!(true, state_obj.tokens[0].is_user_verified);
797 }
798 }
799
800 #[tokio::test]
test_authenticator_credentials()801 async fn test_authenticator_credentials() {
802 init();
803 let state = mk_state_with_token_list(&[1]);
804 let filter = authenticator_credential_add(state.clone())
805 .or(authenticator_credential_delete(state.clone()))
806 .or(authenticator_credentials_get(state.clone()))
807 .or(authenticator_credentials_clear(state.clone()));
808
809 let valid_add_credential = CredentialParameters {
810 credential_id: r"c3VwZXIgcmVhZGVy".to_string(),
811 is_resident_credential: true,
812 rp_id: "valid.rpid".to_string(),
813 private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE),
814 user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE),
815 sign_count: 0,
816 };
817
818 {
819 let res = warp::test::request()
820 .method("POST")
821 .path("/webauthn/authenticator/1/credential")
822 .reply(&filter)
823 .await;
824 assert!(res.status().is_client_error());
825 }
826
827 {
828 let mut invalid = valid_add_credential.clone();
829 invalid.credential_id = "!@#$ invalid base64".to_string();
830 let res = warp::test::request()
831 .method("POST")
832 .path("/webauthn/authenticator/1/credential")
833 .json(&invalid)
834 .reply(&filter)
835 .await;
836 assert!(res.status().is_client_error());
837 }
838
839 {
840 let mut invalid = valid_add_credential.clone();
841 invalid.rp_id = "example".to_string();
842 let res = warp::test::request()
843 .method("POST")
844 .path("/webauthn/authenticator/1/credential")
845 .json(&invalid)
846 .reply(&filter)
847 .await;
848 assert!(res.status().is_client_error());
849 }
850
851 {
852 let mut invalid = valid_add_credential.clone();
853 invalid.rp_id = "https://example.com".to_string();
854 let res = warp::test::request()
855 .method("POST")
856 .path("/webauthn/authenticator/1/credential")
857 .json(&invalid)
858 .reply(&filter)
859 .await;
860 assert!(res.status().is_client_error());
861
862 let state_obj = state.lock().unwrap();
863 assert_eq!(0, state_obj.tokens[0].credentials.len());
864 }
865
866 {
867 let mut no_user_handle = valid_add_credential.clone();
868 no_user_handle.user_handle = "".to_string();
869 no_user_handle.credential_id = "YQo=".to_string();
870 let res = warp::test::request()
871 .method("POST")
872 .path("/webauthn/authenticator/1/credential")
873 .json(&no_user_handle)
874 .reply(&filter)
875 .await;
876 assert!(res.status().is_success());
877 assert_success_rsp_blank(res.body());
878
879 let state_obj = state.lock().unwrap();
880 assert_eq!(1, state_obj.tokens[0].credentials.len());
881 let c = &state_obj.tokens[0].credentials[0];
882 assert!(c.user_handle.is_empty());
883 }
884
885 {
886 let res = warp::test::request()
887 .method("POST")
888 .path("/webauthn/authenticator/1/credential")
889 .json(&valid_add_credential)
890 .reply(&filter)
891 .await;
892 assert_eq!(res.status(), StatusCode::CREATED);
893 assert_success_rsp_blank(res.body());
894
895 let state_obj = state.lock().unwrap();
896 assert_eq!(2, state_obj.tokens[0].credentials.len());
897 let c = &state_obj.tokens[0].credentials[1];
898 assert!(!c.user_handle.is_empty());
899 }
900
901 {
902 // Duplicate, should still be two credentials
903 let res = warp::test::request()
904 .method("POST")
905 .path("/webauthn/authenticator/1/credential")
906 .json(&valid_add_credential)
907 .reply(&filter)
908 .await;
909 assert_eq!(res.status(), StatusCode::CREATED);
910 assert_success_rsp_blank(res.body());
911
912 let state_obj = state.lock().unwrap();
913 assert_eq!(2, state_obj.tokens[0].credentials.len());
914 }
915
916 {
917 let res = warp::test::request()
918 .method("GET")
919 .path("/webauthn/authenticator/1/credentials")
920 .reply(&filter)
921 .await;
922 assert_eq!(res.status(), 200);
923 let (_, body) = res.into_parts();
924 let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap();
925
926 let state_obj = state.lock().unwrap();
927 assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials);
928 }
929
930 {
931 let res = warp::test::request()
932 .method("DELETE")
933 .path("/webauthn/authenticator/1/credentials/YmxhbmsK")
934 .reply(&filter)
935 .await;
936 assert_eq!(res.status(), 404);
937 }
938
939 {
940 let res = warp::test::request()
941 .method("DELETE")
942 .path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy")
943 .reply(&filter)
944 .await;
945 assert_eq!(res.status(), 200);
946 assert_success_rsp_blank(res.body());
947
948 let state_obj = state.lock().unwrap();
949 assert_eq!(1, state_obj.tokens[0].credentials.len());
950 }
951
952 {
953 let res = warp::test::request()
954 .method("DELETE")
955 .path("/webauthn/authenticator/1/credentials")
956 .reply(&filter)
957 .await;
958 assert!(res.status().is_success());
959 assert_success_rsp_blank(res.body());
960
961 let state_obj = state.lock().unwrap();
962 assert_eq!(0, state_obj.tokens[0].credentials.len());
963 }
964 }
965 }
966