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(&param_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(&param_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