Skip to content

Commit 6823563

Browse files
Fixes and test for MC
1 parent a3a58d8 commit 6823563

File tree

8 files changed

+212
-56
lines changed

8 files changed

+212
-56
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::ops::webauthn::Operation;
2+
3+
use serde::Deserialize;
4+
use sha2::{Digest, Sha256};
5+
6+
#[derive(Debug, Clone, PartialEq, Deserialize)]
7+
pub struct ClientData {
8+
pub operation: Operation,
9+
pub challenge: Vec<u8>,
10+
pub origin: String,
11+
#[serde(rename = "crossOrigin")]
12+
pub cross_origin: Option<bool>,
13+
}
14+
15+
impl ClientData {
16+
pub fn hash(&self) -> Vec<u8> {
17+
let op_str = match &self.operation {
18+
Operation::MakeCredential => "webauthn.create",
19+
Operation::GetAssertion => "webauthn.get",
20+
};
21+
let challenge_str = base64_url::encode(&self.challenge);
22+
let origin_str = &self.origin;
23+
let cross_origin_str = if self.cross_origin.unwrap_or(false) {
24+
"true"
25+
} else {
26+
"false"
27+
};
28+
let json =
29+
format!("{{\"type\":\"{op_str}\",\"challenge\":\"{challenge_str}\",\"origin\":\"{origin_str}\",\"crossOrigin\":{cross_origin_str}}}");
30+
31+
let mut hasher = Sha256::new();
32+
hasher.update(json.as_bytes());
33+
hasher.finalize().to_vec()
34+
}
35+
}
36+

libwebauthn/src/ops/webauthn/idl.rs renamed to libwebauthn/src/ops/webauthn/idl/base64url.rs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,8 @@
11
use std::ops::Deref;
22

33
use base64_url;
4-
use serde::{de::DeserializeOwned, Deserialize, Serialize};
5-
use serde_json;
4+
use serde::{Deserialize, Serialize};
65

7-
use super::rpid::RelyingPartyId;
8-
9-
pub type JsonError = serde_json::Error;
10-
11-
pub trait WebAuthnIDL<E>: Sized
12-
where
13-
E: std::error::Error, // Validation error type.
14-
Self: FromInnerModel<Self::InnerModel, E>,
15-
{
16-
/// An error type that can be returned when deserializing from JSON, including
17-
/// JSON parsing errors and any additional validation errors.
18-
type Error: std::error::Error + From<JsonError> + From<E>;
19-
20-
/// The JSON model that this IDL can deserialize from.
21-
type InnerModel: DeserializeOwned;
22-
23-
fn from_json(rpid: &RelyingPartyId, json: &str) -> Result<Self, Self::Error> {
24-
let inner_model: Self::InnerModel = serde_json::from_str(json)?;
25-
Self::from_inner_model(rpid, inner_model).map_err(From::from)
26-
}
27-
}
28-
29-
pub trait FromInnerModel<T, E>: Sized
30-
where
31-
T: DeserializeOwned,
32-
E: std::error::Error,
33-
{
34-
fn from_inner_model(rpid: &RelyingPartyId, inner: T) -> Result<Self, E>;
35-
}
36-
37-
// TODO(afresta): Move to ctap2 module.
386
#[derive(Debug, Clone, PartialEq)]
397
pub struct Base64UrlString(pub Vec<u8>);
408

libwebauthn/src/ops/webauthn/create.rs renamed to libwebauthn/src/ops/webauthn/idl/create.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use super::idl::Base64UrlString;
1+
use super::Base64UrlString;
22
use crate::{
33
ops::webauthn::{
44
MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement,
55
},
66
proto::ctap2::{
77
Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
8-
Ctap2PublicKeyCredentialUserEntity,
98
},
109
};
1110

@@ -33,10 +32,18 @@ fn default_user_verification() -> UserVerificationRequirement {
3332
UserVerificationRequirement::Preferred
3433
}
3534

35+
#[derive(Debug, Clone, PartialEq, Deserialize)]
36+
pub struct PublicKeyCredentialUserEntity {
37+
pub id: Base64UrlString,
38+
pub name: String,
39+
#[serde(rename = "displayName")]
40+
pub display_name: String,
41+
}
42+
3643
#[derive(Debug, Clone, Deserialize)]
3744
pub struct PublicKeyCredentialCreationOptionsJSON {
3845
pub rp: Ctap2PublicKeyCredentialRpEntity,
39-
pub user: Ctap2PublicKeyCredentialUserEntity,
46+
pub user: PublicKeyCredentialUserEntity,
4047
pub challenge: Base64UrlString,
4148
#[serde(rename = "pubKeyCredParams")]
4249
pub params: Vec<Ctap2CredentialType>,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
mod base64url;
2+
pub mod create;
3+
pub mod rpid;
4+
5+
pub use base64url::Base64UrlString;
6+
7+
use rpid::RelyingPartyId;
8+
9+
use serde::de::DeserializeOwned;
10+
use serde_json;
11+
12+
pub type JsonError = serde_json::Error;
13+
14+
pub trait WebAuthnIDL<E>: Sized
15+
where
16+
E: std::error::Error, // Validation error type.
17+
Self: FromInnerModel<Self::InnerModel, E>,
18+
{
19+
/// An error type that can be returned when deserializing from JSON, including
20+
/// JSON parsing errors and any additional validation errors.
21+
type Error: std::error::Error + From<JsonError> + From<E>;
22+
23+
/// The JSON model that this IDL can deserialize from.
24+
type InnerModel: DeserializeOwned;
25+
26+
fn from_json(rpid: &RelyingPartyId, json: &str) -> Result<Self, Self::Error> {
27+
let inner_model: Self::InnerModel = serde_json::from_str(json)?;
28+
Self::from_inner_model(rpid, inner_model).map_err(From::from)
29+
}
30+
}
31+
32+
pub trait FromInnerModel<T, E>: Sized
33+
where
34+
T: DeserializeOwned,
35+
E: std::error::Error,
36+
{
37+
fn from_inner_model(rpid: &RelyingPartyId, inner: T) -> Result<Self, E>;
38+
}
File renamed without changes.

libwebauthn/src/ops/webauthn/make_credential.rs

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ use tracing::{debug, instrument, trace};
99
use crate::{
1010
fido::AuthenticatorData,
1111
ops::webauthn::{
12-
create::PublicKeyCredentialCreationOptionsJSON,
13-
idl::{Base64UrlString, FromInnerModel, JsonError, WebAuthnIDL},
14-
rpid::RelyingPartyId,
12+
client_data::ClientData,
13+
idl::{
14+
create::PublicKeyCredentialCreationOptionsJSON, Base64UrlString, FromInnerModel,
15+
JsonError, WebAuthnIDL,
16+
},
17+
Operation, RelyingPartyId,
1518
},
1619
proto::{
1720
ctap1::{Ctap1RegisteredKey, Ctap1Version},
@@ -162,7 +165,7 @@ impl MakeCredentialsResponseUnsignedExtensions {
162165
}
163166
}
164167

165-
#[derive(Debug, Clone, Copy, Deserialize)]
168+
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
166169
pub enum ResidentKeyRequirement {
167170
#[serde(rename = "required")]
168171
Required,
@@ -172,7 +175,7 @@ pub enum ResidentKeyRequirement {
172175
Discouraged,
173176
}
174177

175-
#[derive(Debug, Clone)]
178+
#[derive(Debug, Clone, PartialEq)]
176179
pub struct MakeCredentialRequest {
177180
pub hash: Vec<u8>,
178181
pub origin: String,
@@ -221,14 +224,21 @@ impl FromInnerModel<PublicKeyCredentialCreationOptionsJSON, MakeCredentialReques
221224

222225
let timeout: Duration = inner
223226
.timeout
224-
.map(|s| Duration::from_secs(s.into()))
227+
.map(|s| Duration::from_millis(s.into()))
225228
.unwrap_or(DEFAULT_TIMEOUT);
226229

230+
let client_data_json = ClientData {
231+
operation: Operation::MakeCredential,
232+
challenge: inner.challenge.to_vec(),
233+
origin: rpid.to_string(),
234+
cross_origin: None,
235+
};
236+
227237
Ok(Self {
228-
hash: inner.challenge.into(),
238+
hash: client_data_json.hash(),
229239
origin: rpid.to_owned().into(),
230240
relying_party: inner.rp,
231-
user: inner.user,
241+
user: inner.user.into(),
232242
resident_key,
233243
user_verification,
234244
algorithms: inner.params,
@@ -255,7 +265,7 @@ impl WebAuthnIDL<MakeCredentialRequestParsingError> for MakeCredentialRequest {
255265
type InnerModel = PublicKeyCredentialCreationOptionsJSON;
256266
}
257267

258-
#[derive(Debug, Clone, Deserialize)]
268+
#[derive(Debug, Clone, Deserialize, PartialEq)]
259269
pub struct MakeCredentialPrfInput {
260270
#[serde(rename = "eval")]
261271
pub _eval: Option<JsonValue>,
@@ -267,7 +277,7 @@ pub struct MakeCredentialPrfOutput {
267277
pub enabled: Option<bool>,
268278
}
269279

270-
#[derive(Debug, Clone, Deserialize)]
280+
#[derive(Debug, Clone, Deserialize, PartialEq)]
271281
pub struct CredentialProtectionExtension {
272282
#[serde(rename = "credentialProtectionPolicy")]
273283
pub policy: CredentialProtectionPolicy,
@@ -324,7 +334,7 @@ pub struct CredentialPropsExtension {
324334
pub rk: Option<bool>,
325335
}
326336

327-
#[derive(Debug, Default, Clone, Deserialize)]
337+
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
328338
pub struct MakeCredentialLargeBlobExtensionInput {
329339
pub support: MakeCredentialLargeBlobExtension,
330340
}
@@ -346,7 +356,7 @@ pub struct MakeCredentialLargeBlobExtensionOutput {
346356
pub supported: Option<bool>,
347357
}
348358

349-
#[derive(Debug, Default, Clone, Deserialize)]
359+
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
350360
pub struct MakeCredentialsRequestExtensions {
351361
#[serde(rename = "credProps")]
352362
pub cred_props: Option<bool>,
@@ -452,3 +462,86 @@ impl DowngradableRequest<RegisterRequest> for MakeCredentialRequest {
452462
Ok(downgraded)
453463
}
454464
}
465+
466+
#[cfg(test)]
467+
mod tests {
468+
use std::time::Duration;
469+
470+
use crate::ops::webauthn::MakeCredentialRequest;
471+
use crate::ops::webauthn::RelyingPartyId;
472+
473+
use super::*;
474+
475+
pub const REQUEST_BASE_JSON: &str = r#"
476+
{
477+
"rp": {
478+
"id": "example.org",
479+
"name": "example.org"
480+
},
481+
"user": {
482+
"id": "dXNlcmlk",
483+
"name": "mario.rossi",
484+
"displayName": "Mario Rossi"
485+
},
486+
"challenge": "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu",
487+
"pubKeyCredParams": [
488+
{
489+
"type": "public-key",
490+
"alg": -7
491+
}
492+
],
493+
"timeout": 30000,
494+
"excludeCredentials": [],
495+
"authenticatorSelection": {
496+
"residentKey": "discouraged",
497+
"userVerification": "preferred"
498+
},
499+
"attestation": "none",
500+
"attestationFormats": ["packed", "fido-u2f"]
501+
}
502+
"#;
503+
504+
fn request_base() -> MakeCredentialRequest {
505+
MakeCredentialRequest {
506+
origin: "example.org".to_string(),
507+
hash: ClientData {
508+
operation: Operation::MakeCredential,
509+
challenge: base64_url::decode("Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu")
510+
.unwrap(),
511+
origin: "example.org".to_string(),
512+
cross_origin: None,
513+
}
514+
.hash(),
515+
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
516+
user: Ctap2PublicKeyCredentialUserEntity::new(b"userid", "mario.rossi", "Mario Rossi"),
517+
resident_key: Some(ResidentKeyRequirement::Discouraged),
518+
user_verification: UserVerificationRequirement::Preferred,
519+
algorithms: vec![Ctap2CredentialType::default()],
520+
exclude: None,
521+
extensions: None,
522+
timeout: Duration::from_secs(30),
523+
}
524+
}
525+
526+
fn json_field_add(str: &str, field: &str, value: &str) -> String {
527+
let mut v: serde_json::Value = serde_json::from_str(str).unwrap();
528+
v.as_object_mut()
529+
.unwrap()
530+
.insert(field.to_owned(), serde_json::from_str(value).unwrap());
531+
serde_json::to_string(&v).unwrap()
532+
}
533+
534+
fn json_field_rm(str: &str, field: &str) -> String {
535+
let mut v: serde_json::Value = serde_json::from_str(str).unwrap();
536+
v.as_object_mut().unwrap().remove(field);
537+
serde_json::to_string(&v).unwrap()
538+
}
539+
540+
#[test]
541+
fn test_request_from_json_base() {
542+
let rpid = RelyingPartyId::try_from("example.org").unwrap();
543+
let req: MakeCredentialRequest =
544+
MakeCredentialRequest::from_json(&rpid, REQUEST_BASE_JSON).unwrap();
545+
assert_eq!(req, request_base());
546+
}
547+
}

libwebauthn/src/ops/webauthn/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
mod create;
1+
mod client_data;
22
mod get_assertion;
3-
pub(crate) mod idl;
3+
pub mod idl;
44
mod make_credential;
5-
mod rpid;
65

76
use super::u2f::{RegisterRequest, SignRequest};
87
use crate::webauthn::CtapError;
@@ -13,18 +12,23 @@ pub use get_assertion::{
1312
GetAssertionResponseExtensions, GetAssertionResponseUnsignedExtensions, HMACGetSecretInput,
1413
HMACGetSecretOutput, PRFValue, PrfInput,
1514
};
16-
pub use idl::{Base64UrlString, WebAuthnIDL};
15+
pub use idl::{rpid::RelyingPartyId, Base64UrlString, WebAuthnIDL};
1716
pub use make_credential::{
1817
CredentialPropsExtension, CredentialProtectionExtension, CredentialProtectionPolicy,
1918
MakeCredentialLargeBlobExtension, MakeCredentialLargeBlobExtensionOutput,
2019
MakeCredentialPrfInput, MakeCredentialPrfOutput, MakeCredentialRequest, MakeCredentialResponse,
2120
MakeCredentialsRequestExtensions, MakeCredentialsResponseExtensions,
2221
MakeCredentialsResponseUnsignedExtensions, ResidentKeyRequirement,
2322
};
24-
pub use rpid::RelyingPartyId;
2523
use serde::Deserialize;
2624

27-
#[derive(Debug, Clone, Copy, Deserialize)]
25+
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
26+
pub enum Operation {
27+
MakeCredential,
28+
GetAssertion,
29+
}
30+
31+
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
2832
pub enum UserVerificationRequirement {
2933
#[serde(rename = "required")]
3034
Required,

0 commit comments

Comments
 (0)