@@ -7,8 +7,15 @@ use tracing::{debug, error, trace};
77use crate :: {
88 fido:: AuthenticatorData ,
99 ops:: webauthn:: {
10- idl:: { FromInnerModel , JsonError } ,
11- Base64UrlString , WebAuthnIDL ,
10+ client_data:: ClientData ,
11+ idl:: {
12+ get:: {
13+ HmacGetSecretInputJson , LargeBlobInputJson , PrfInputJson ,
14+ PublicKeyCredentialRequestOptionsJSON ,
15+ } ,
16+ FromInnerModel , JsonError ,
17+ } ,
18+ Operation , WebAuthnIDL ,
1219 } ,
1320 pin:: PinUvAuthProtocol ,
1421 proto:: ctap2:: {
@@ -22,15 +29,15 @@ use super::{DowngradableRequest, RelyingPartyId, SignRequest, UserVerificationRe
2229
2330pub const DEFAULT_TIMEOUT : Duration = Duration :: from_secs ( 60 ) ;
2431
25- #[ derive( Debug , Default , Clone , Serialize ) ]
32+ #[ derive( Debug , Default , Clone , Serialize , PartialEq ) ]
2633pub struct PRFValue {
2734 #[ serde( with = "serde_bytes" ) ]
2835 pub first : [ u8 ; 32 ] ,
2936 #[ serde( skip_serializing_if = "Option::is_none" , with = "serde_bytes" ) ]
3037 pub second : Option < [ u8 ; 32 ] > ,
3138}
3239
33- #[ derive( Debug , Clone ) ]
40+ #[ derive( Debug , Clone , PartialEq ) ]
3441pub struct GetAssertionRequest {
3542 pub relying_party_id : String ,
3643 pub hash : Vec < u8 > ,
@@ -68,22 +75,6 @@ impl WebAuthnIDL<GetAssertionRequestParsingError> for GetAssertionRequest {
6875 AuthenticationExtensionsClientInputsJSON extensions;
6976}; */
7077
71- #[ derive( Deserialize , Debug , Clone ) ]
72- pub struct PublicKeyCredentialRequestOptionsJSON {
73- pub challenge : Base64UrlString ,
74- pub timeout : Option < u32 > ,
75- #[ serde( rename = "rpId" ) ]
76- pub relying_party_id : Option < String > ,
77- #[ serde( rename = "allowCredentials" ) ]
78- #[ serde( default ) ]
79- pub allow_credentials : Vec < Ctap2PublicKeyCredentialDescriptor > ,
80- #[ serde( rename = "userVerification" ) ]
81- pub uv_requirement : UserVerificationRequirement ,
82- #[ serde( default ) ]
83- pub hints : Vec < String > ,
84- pub extensions : Option < GetAssertionRequestExtensionsJSON > ,
85- }
86-
8778impl FromInnerModel < PublicKeyCredentialRequestOptionsJSON , GetAssertionRequestParsingError >
8879 for GetAssertionRequest
8980{
@@ -123,89 +114,43 @@ impl FromInnerModel<PublicKeyCredentialRequestOptionsJSON, GetAssertionRequestPa
123114
124115 let timeout: Duration = inner
125116 . timeout
126- . map ( |s| Duration :: from_secs ( s. into ( ) ) )
117+ . map ( |s| Duration :: from_millis ( s. into ( ) ) )
127118 . unwrap_or ( DEFAULT_TIMEOUT ) ;
128119
120+ let client_data_json = ClientData {
121+ operation : Operation :: GetAssertion ,
122+ challenge : inner. challenge . to_vec ( ) ,
123+ origin : rpid. to_string ( ) ,
124+ cross_origin : None ,
125+ } ;
126+
129127 Ok ( GetAssertionRequest {
130128 relying_party_id : rpid. to_string ( ) ,
131- hash : inner. challenge . into ( ) ,
132- allow : inner. allow_credentials ,
129+ hash : client_data_json. hash ( ) ,
130+ allow : inner
131+ . allow_credentials
132+ . into_iter ( )
133+ . map ( |c| c. into ( ) )
134+ . collect ( ) ,
133135 extensions,
134136 user_verification : inner. uv_requirement ,
135137 timeout,
136138 } )
137139 }
138140}
139141
140- #[ derive( Debug , Clone ) ]
142+ #[ derive( Debug , Clone , PartialEq ) ]
141143pub enum GetAssertionHmacOrPrfInput {
142144 HmacGetSecret ( HMACGetSecretInput ) ,
143145 Prf ( PrfInput ) ,
144146}
145147
146- #[ derive( Debug , Clone ) ]
148+ #[ derive( Debug , Clone , PartialEq ) ]
147149pub struct PrfInput {
148150 pub eval : Option < PRFValue > ,
149151 pub eval_by_credential : HashMap < String , PRFValue > ,
150152}
151153
152- #[ derive( Debug , Default , Clone , Serialize ) ]
153- pub struct GetAssertionPrfOutput {
154- #[ serde( skip_serializing_if = "Option::is_none" ) ]
155- pub results : Option < PRFValue > ,
156- }
157-
158- #[ derive( Clone , Debug , Default , Eq , PartialEq ) ]
159- pub struct HMACGetSecretInput {
160- pub salt1 : [ u8 ; 32 ] ,
161- pub salt2 : Option < [ u8 ; 32 ] > ,
162- }
163-
164- #[ derive( Debug , Clone , Deserialize ) ]
165- pub struct HmacGetSecretInputJson {
166- pub salt1 : Base64UrlString ,
167- pub salt2 : Option < Base64UrlString > ,
168- }
169-
170- impl TryFrom < HmacGetSecretInputJson > for HMACGetSecretInput {
171- type Error = GetAssertionRequestParsingError ;
172-
173- fn try_from ( value : HmacGetSecretInputJson ) -> Result < Self , Self :: Error > {
174- let salt1 = value. salt1 . as_slice ( ) . try_into ( ) . map_err ( |_| {
175- GetAssertionRequestParsingError :: UnexpectedLengthError (
176- "extensions.hmacCreateSecret.salt1" . to_string ( ) ,
177- value. salt1 . as_slice ( ) . len ( ) ,
178- )
179- } ) ?;
180- let salt2 = match value. salt2 {
181- Some ( s) => Some ( s. as_slice ( ) . try_into ( ) . map_err ( |_| {
182- GetAssertionRequestParsingError :: UnexpectedLengthError (
183- "extensions.hmacCreateSecret.salt2" . to_string ( ) ,
184- s. as_slice ( ) . len ( ) ,
185- )
186- } ) ?) ,
187- None => None ,
188- } ;
189- Ok ( HMACGetSecretInput { salt1, salt2 } )
190- }
191- }
192- #[ derive( Debug , Clone , Deserialize ) ]
193- pub struct LargeBlobInputJson {
194- pub read : Option < bool > ,
195- }
196-
197- #[ derive( Debug , Clone , Deserialize ) ]
198- pub struct PrfInputJson {
199- pub eval : Option < PrfValuesJson > ,
200- pub eval_by_credential : Option < HashMap < String , PrfValuesJson > > ,
201- }
202-
203- #[ derive( Debug , Clone , Deserialize ) ]
204- pub struct PrfValuesJson {
205- pub first : Base64UrlString ,
206- pub second : Option < Base64UrlString > ,
207- }
208-
209154impl TryFrom < PrfInputJson > for PrfInput {
210155 type Error = GetAssertionRequestParsingError ;
211156
@@ -266,6 +211,41 @@ impl TryFrom<PrfInputJson> for PrfInput {
266211 }
267212}
268213
214+ #[ derive( Debug , Default , Clone , Serialize ) ]
215+ pub struct GetAssertionPrfOutput {
216+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
217+ pub results : Option < PRFValue > ,
218+ }
219+
220+ #[ derive( Clone , Debug , Default , Eq , PartialEq ) ]
221+ pub struct HMACGetSecretInput {
222+ pub salt1 : [ u8 ; 32 ] ,
223+ pub salt2 : Option < [ u8 ; 32 ] > ,
224+ }
225+
226+ impl TryFrom < HmacGetSecretInputJson > for HMACGetSecretInput {
227+ type Error = GetAssertionRequestParsingError ;
228+
229+ fn try_from ( value : HmacGetSecretInputJson ) -> Result < Self , Self :: Error > {
230+ let salt1 = value. salt1 . as_slice ( ) . try_into ( ) . map_err ( |_| {
231+ GetAssertionRequestParsingError :: UnexpectedLengthError (
232+ "extensions.hmacCreateSecret.salt1" . to_string ( ) ,
233+ value. salt1 . as_slice ( ) . len ( ) ,
234+ )
235+ } ) ?;
236+ let salt2 = match value. salt2 {
237+ Some ( s) => Some ( s. as_slice ( ) . try_into ( ) . map_err ( |_| {
238+ GetAssertionRequestParsingError :: UnexpectedLengthError (
239+ "extensions.hmacCreateSecret.salt2" . to_string ( ) ,
240+ s. as_slice ( ) . len ( ) ,
241+ )
242+ } ) ?) ,
243+ None => None ,
244+ } ;
245+ Ok ( HMACGetSecretInput { salt1, salt2 } )
246+ }
247+ }
248+
269249#[ derive( Debug , Clone , PartialEq , Eq ) ]
270250pub enum GetAssertionLargeBlobExtension {
271251 Read ,
@@ -296,25 +276,13 @@ pub struct GetAssertionLargeBlobExtensionOutput {
296276 // pub written: Option<bool>,
297277}
298278
299- #[ derive( Debug , Default , Clone ) ]
279+ #[ derive( Debug , Default , Clone , PartialEq ) ]
300280pub struct GetAssertionRequestExtensions {
301281 pub cred_blob : bool ,
302282 pub hmac_or_prf : Option < GetAssertionHmacOrPrfInput > ,
303283 pub large_blob : Option < GetAssertionLargeBlobExtension > ,
304284}
305285
306- #[ derive( Debug , Clone , Default , Deserialize ) ]
307- pub struct GetAssertionRequestExtensionsJSON {
308- #[ serde( rename = "getCredBlob" ) ]
309- pub cred_blob : Option < bool > ,
310- #[ serde( rename = "largeBlobKey" ) ]
311- pub large_blob : Option < LargeBlobInputJson > ,
312- #[ serde( rename = "hmacCreateSecret" ) ]
313- pub hamc_get_secret : Option < HmacGetSecretInputJson > ,
314- #[ serde( rename = "prf" ) ]
315- pub prf : Option < PrfInputJson > ,
316- }
317-
318286#[ derive( Clone , Debug , Default , Serialize ) ]
319287#[ serde( rename_all = "camelCase" ) ]
320288pub struct HMACGetSecretOutput {
@@ -460,3 +428,147 @@ impl DowngradableRequest<Vec<SignRequest>> for GetAssertionRequest {
460428 Ok ( downgraded_requests)
461429 }
462430}
431+
432+ #[ cfg( test) ]
433+ mod tests {
434+ use std:: time:: Duration ;
435+
436+ use serde_bytes:: ByteBuf ;
437+
438+ use crate :: ops:: webauthn:: GetAssertionRequest ;
439+ use crate :: ops:: webauthn:: RelyingPartyId ;
440+ use crate :: proto:: ctap2:: Ctap2PublicKeyCredentialType ;
441+
442+ use super :: * ;
443+
444+ pub const REQUEST_BASE_JSON : & str = r#"
445+ {
446+ "challenge": "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu",
447+ "timeout": 30000,
448+ "rpId": "example.org",
449+ "allowCredentials": [
450+ {
451+ "type": "public-key",
452+ "id": "bXktY3JlZGVudGlhbC1pZA"
453+ }
454+ ],
455+ "userVerification": "preferred"
456+ }
457+ "# ;
458+
459+ fn request_base ( ) -> GetAssertionRequest {
460+ let client_data_json = ClientData {
461+ operation : Operation :: GetAssertion ,
462+ challenge : base64_url:: decode ( "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu" ) . unwrap ( ) ,
463+ origin : "example.org" . to_string ( ) ,
464+ cross_origin : None ,
465+ } ;
466+ GetAssertionRequest {
467+ relying_party_id : "example.org" . to_owned ( ) ,
468+ hash : client_data_json. hash ( ) ,
469+ allow : vec ! [ Ctap2PublicKeyCredentialDescriptor {
470+ r#type: Ctap2PublicKeyCredentialType :: PublicKey ,
471+ id: ByteBuf :: from( base64_url:: decode( "bXktY3JlZGVudGlhbC1pZA" ) . unwrap( ) ) ,
472+ transports: None ,
473+ } ] ,
474+ extensions : GetAssertionRequestExtensions :: default ( ) ,
475+ user_verification : UserVerificationRequirement :: Preferred ,
476+ timeout : Duration :: from_secs ( 30 ) ,
477+ }
478+ }
479+
480+ fn json_field_add ( str : & str , field : & str , value : & str ) -> String {
481+ let mut v: serde_json:: Value = serde_json:: from_str ( str) . unwrap ( ) ;
482+ v. as_object_mut ( )
483+ . unwrap ( )
484+ . insert ( field. to_owned ( ) , serde_json:: from_str ( value) . unwrap ( ) ) ;
485+ serde_json:: to_string ( & v) . unwrap ( )
486+ }
487+
488+ fn json_field_rm ( str : & str , field : & str ) -> String {
489+ let mut v: serde_json:: Value = serde_json:: from_str ( str) . unwrap ( ) ;
490+ v. as_object_mut ( ) . unwrap ( ) . remove ( field) ;
491+ serde_json:: to_string ( & v) . unwrap ( )
492+ }
493+
494+ #[ test]
495+ fn test_request_from_json_base ( ) {
496+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
497+ let req: GetAssertionRequest =
498+ GetAssertionRequest :: from_json ( & rpid, REQUEST_BASE_JSON ) . unwrap ( ) ;
499+ assert_eq ! ( req, request_base( ) ) ;
500+ }
501+
502+ #[ test]
503+ fn test_request_from_json_ignore_missing_rp_id ( ) {
504+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
505+ let req_json = json_field_rm ( REQUEST_BASE_JSON , "rpId" ) ;
506+
507+ let req: GetAssertionRequest = GetAssertionRequest :: from_json ( & rpid, & req_json) . unwrap ( ) ;
508+ assert_eq ! ( req, request_base( ) ) ;
509+ }
510+
511+ #[ test]
512+ fn test_request_from_json_ignore_request_rp_id ( ) {
513+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
514+ let req_json = json_field_rm ( REQUEST_BASE_JSON , "rpId" ) ;
515+ let req_json = json_field_add ( & req_json, "rpId" , r#""another-example.org""# ) ;
516+
517+ let req: GetAssertionRequest = GetAssertionRequest :: from_json ( & rpid, & req_json) . unwrap ( ) ;
518+ assert_eq ! ( req, request_base( ) ) ;
519+ }
520+
521+ #[ test]
522+ fn test_request_from_json_ignore_missing_allow_credentials ( ) {
523+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
524+ let req_json = json_field_rm ( REQUEST_BASE_JSON , "allowCredentials" ) ;
525+
526+ let req: GetAssertionRequest = GetAssertionRequest :: from_json ( & rpid, & req_json) . unwrap ( ) ;
527+ assert_eq ! (
528+ req,
529+ GetAssertionRequest {
530+ allow: vec![ ] ,
531+ ..request_base( )
532+ }
533+ ) ;
534+ }
535+
536+ #[ test]
537+ fn test_request_from_json_default_timeout ( ) {
538+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
539+ let req_json = json_field_rm ( REQUEST_BASE_JSON , "timeout" ) ;
540+
541+ let req: GetAssertionRequest = GetAssertionRequest :: from_json ( & rpid, & req_json) . unwrap ( ) ;
542+ assert_eq ! ( req. timeout, DEFAULT_TIMEOUT ) ;
543+ }
544+
545+ #[ test]
546+ #[ ignore] // FIXME(#134) allow arbitrary size input
547+ fn test_request_from_json_prf_extension ( ) {
548+ let rpid = RelyingPartyId :: try_from ( "example.org" ) . unwrap ( ) ;
549+ let req_json = json_field_add (
550+ REQUEST_BASE_JSON ,
551+ "extensions" ,
552+ r#"{"prf":{"eval":{"first": "second"}}}"# ,
553+ ) ;
554+
555+ let req: GetAssertionRequest = GetAssertionRequest :: from_json ( & rpid, & req_json) . unwrap ( ) ;
556+ if let GetAssertionRequestExtensions {
557+ hmac_or_prf :
558+ Some ( GetAssertionHmacOrPrfInput :: Prf ( PrfInput {
559+ eval : Some ( ref prf_value) ,
560+ ..
561+ } ) ) ,
562+ ..
563+ } = & req. extensions
564+ {
565+ assert_eq ! ( & prf_value. first[ ..] , b"first" ) ;
566+ assert_eq ! (
567+ prf_value. second. as_ref( ) . map( |s| & s[ ..] ) ,
568+ Some ( & b"second" [ ..] )
569+ ) ;
570+ } else {
571+ panic ! ( "Expected PRF extension with correct values" ) ;
572+ }
573+ }
574+ }
0 commit comments