Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion libwebauthn/src/ops/webauthn/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ impl FromIdlModel<PublicKeyCredentialRequestOptionsJSON> for GetAssertionRequest
}
parsed.0
} else {
effective_rp_id.to_string()
RelyingPartyId::try_from(effective_rp_id)
.map_err(|err| GetAssertionPrepareError::InvalidRelyingPartyId(err.to_string()))?
.0
};

let prf = match inner.extensions.as_ref() {
Expand Down Expand Up @@ -906,6 +908,42 @@ mod tests {
));
}

#[tokio::test]
async fn test_request_from_json_rejects_ip_effective_domain() {
let request_origin: RequestOrigin = "https://127.0.0.1:8443".parse().unwrap();
let req_json = json_field_rm(REQUEST_BASE_JSON, "rpId");

let result = from_json(
&request_origin,
&MockPublicSuffixList,
RelatedOrigins::Disabled,
&req_json,
)
.await;
assert!(matches!(
result,
Err(GetAssertionPrepareError::InvalidRelyingPartyId(_))
));
}

#[tokio::test]
async fn test_request_from_json_rejects_ipv6_effective_domain() {
let request_origin: RequestOrigin = "https://[::1]:8443".parse().unwrap();
let req_json = json_field_rm(REQUEST_BASE_JSON, "rpId");

let result = from_json(
&request_origin,
&MockPublicSuffixList,
RelatedOrigins::Disabled,
&req_json,
)
.await;
assert!(matches!(
result,
Err(GetAssertionPrepareError::InvalidRelyingPartyId(_))
));
}

#[tokio::test]
async fn test_request_from_json_mismatching_rp_id() {
let request_origin: RequestOrigin = "https://example.org".parse().unwrap();
Expand Down
11 changes: 9 additions & 2 deletions libwebauthn/src/ops/webauthn/idl/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
ops::webauthn::{
MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement,
},
proto::ctap2::{Ctap2CredentialType, Ctap2PublicKeyCredentialRpEntity},
proto::ctap2::Ctap2CredentialType,
};

use serde::Deserialize;
Expand All @@ -28,6 +28,13 @@ fn default_user_verification() -> UserVerificationRequirement {
UserVerificationRequirement::Preferred
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialRpEntityJSON {
pub id: Option<String>,
pub name: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialUserEntity {
Expand All @@ -39,7 +46,7 @@ pub struct PublicKeyCredentialUserEntity {
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialCreationOptionsJSON {
pub rp: Ctap2PublicKeyCredentialRpEntity,
pub rp: PublicKeyCredentialRpEntityJSON,
pub user: PublicKeyCredentialUserEntity,
pub challenge: Base64UrlString,
#[serde(rename = "pubKeyCredParams")]
Expand Down
60 changes: 57 additions & 3 deletions libwebauthn/src/ops/webauthn/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,16 +409,18 @@ impl FromIdlModel<PublicKeyCredentialCreationOptionsJSON> for MakeCredentialRequ
inner: PublicKeyCredentialCreationOptionsJSON,
) -> Result<Self, MakeCredentialPrepareError> {
let effective_rp_id = request_origin.origin.host.as_str();
let rp_id = RelyingPartyId::try_from(inner.rp.id.as_str())
let rp_id = RelyingPartyId::try_from(inner.rp.id.as_deref().unwrap_or(effective_rp_id))
.map_err(|err| MakeCredentialPrepareError::InvalidRelyingPartyId(err.to_string()))?;
if !rp_id_authorised(request_origin, &rp_id, settings).await {
return Err(MakeCredentialPrepareError::MismatchingRelyingPartyId(
rp_id.0,
effective_rp_id.to_string(),
));
}
let mut relying_party = inner.rp;
relying_party.id = rp_id.0;
let relying_party = Ctap2PublicKeyCredentialRpEntity {
id: rp_id.0,
name: inner.rp.name,
};
let resident_key = if inner
.authenticator_selection
.as_ref()
Expand Down Expand Up @@ -1138,6 +1140,58 @@ mod tests {
));
}

#[tokio::test]
async fn test_request_from_json_rp_id_defaults_to_effective_domain() {
let request_origin: RequestOrigin = "https://example.org".parse().unwrap();
let req_json = json_field_add(REQUEST_BASE_JSON, "rp", r#"{"name": "example.org"}"#);

let req = from_json(
&request_origin,
&MockPublicSuffixList,
RelatedOrigins::Disabled,
&req_json,
)
.await
.unwrap();
assert_eq!(req.relying_party.id, "example.org");
}

#[tokio::test]
async fn test_request_from_json_rejects_ipv4_effective_domain() {
let request_origin: RequestOrigin = "https://127.0.0.1:8443".parse().unwrap();
let req_json = json_field_add(REQUEST_BASE_JSON, "rp", r#"{"name": "example.org"}"#);

let result = from_json(
&request_origin,
&MockPublicSuffixList,
RelatedOrigins::Disabled,
&req_json,
)
.await;
assert!(matches!(
result,
Err(MakeCredentialPrepareError::InvalidRelyingPartyId(_))
));
}

#[tokio::test]
async fn test_request_from_json_rejects_ipv6_effective_domain() {
let request_origin: RequestOrigin = "https://[::1]:8443".parse().unwrap();
let req_json = json_field_add(REQUEST_BASE_JSON, "rp", r#"{"name": "example.org"}"#);

let result = from_json(
&request_origin,
&MockPublicSuffixList,
RelatedOrigins::Disabled,
&req_json,
)
.await;
assert!(matches!(
result,
Err(MakeCredentialPrepareError::InvalidRelyingPartyId(_))
));
}

#[tokio::test]
async fn origin_trust_accepts_mismatching_rp_id() {
let request_origin: RequestOrigin = "https://app.example.org".parse().unwrap();
Expand Down
Loading