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
107 changes: 99 additions & 8 deletions libwebauthn/src/ops/webauthn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use crate::{
ctap1::{Ctap1RegisteredKey, Ctap1Version},
ctap2::{
Ctap2AttestationStatement, Ctap2COSEAlgorithmIdentifier, Ctap2CredentialType,
Ctap2GetAssertionResponseExtensions, Ctap2MakeCredentialsResponseExtensions,
Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
Ctap2PublicKeyCredentialUserEntity,
Ctap2GetAssertionResponseExtensions, Ctap2GetInfoResponse,
Ctap2MakeCredentialsResponseExtensions, Ctap2PublicKeyCredentialDescriptor,
Ctap2PublicKeyCredentialRpEntity, Ctap2PublicKeyCredentialUserEntity,
},
},
webauthn::CtapError,
Expand Down Expand Up @@ -54,7 +54,7 @@ pub struct MakeCredentialResponse {
pub attestation_statement: Ctap2AttestationStatement,
pub enterprise_attestation: Option<bool>,
pub large_blob_key: Option<Vec<u8>>,
pub unsigned_extensions_output: Option<MakeCredentialsResponseUnsignedExtensions>,
pub unsigned_extensions_output: MakeCredentialsResponseUnsignedExtensions,
}

#[derive(Debug, Default, Clone, Serialize)]
Expand All @@ -73,6 +73,95 @@ pub struct MakeCredentialsResponseUnsignedExtensions {
pub prf: Option<MakeCredentialPrfOutput>,
}

impl MakeCredentialsResponseUnsignedExtensions {
pub fn has_some(&self) -> bool {
self.cred_props.is_some()
|| self.hmac_create_secret.is_some()
|| self.large_blob.is_some()
|| self.prf.is_some()
}

pub fn from_signed_extensions(
signed_extensions: &Option<Ctap2MakeCredentialsResponseExtensions>,
request: &MakeCredentialRequest,
info: Option<&Ctap2GetInfoResponse>,
) -> MakeCredentialsResponseUnsignedExtensions {
let mut hmac_create_secret = None;
let mut prf = None;
if let Some(signed_extensions) = signed_extensions {
(hmac_create_secret, prf) = if let Some(incoming_ext) = &request.extensions {
match &incoming_ext.hmac_or_prf {
MakeCredentialHmacOrPrfInput::None => (None, None),
MakeCredentialHmacOrPrfInput::HmacGetSecret => {
(signed_extensions.hmac_secret, None)
}
MakeCredentialHmacOrPrfInput::Prf => (
None,
Some(MakeCredentialPrfOutput {
enabled: signed_extensions.hmac_secret,
}),
),
}
} else {
(None, None)
};
}

// credProps extension
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
let cred_props = match &request
.extensions
.as_ref()
.and_then(|x| x.cred_props.as_ref())
{
None | Some(false) => None, // Not requested, so we don't give an answer
Some(true) => {
// https://w3c.github.io/webauthn/#dom-credentialpropertiesoutput-rk
// Some authenticators create discoverable credentials even when not
// requested by the client platform. Because of this, client platforms may be
// forced to omit the rk property because they lack the assurance to be able
// to set it to false.
if info.map(|x| x.supports_fido_2_1()) == Some(true) {
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#op-makecred-step-rk
// if the "rk" option is false: the authenticator MUST create a non-discoverable credential.
// Note: This step is a change from CTAP2.0 where if the "rk" option is false the authenticator could optionally create a discoverable credential.
Some(CredentialPropsExtension {
rk: Some(request.require_resident_key),
})
} else {
Some(CredentialPropsExtension {
// For CTAP 2.0, we can't say if "rk" is true or not.
rk: None,
})
}
}
};

// largeBlob extension
// https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension
let large_blob = match &request.extensions.as_ref().map(|x| &x.large_blob) {
None | Some(MakeCredentialLargeBlobExtension::None) => None, // Not requested, so we don't give an answer
Some(MakeCredentialLargeBlobExtension::Preferred)
| Some(MakeCredentialLargeBlobExtension::Required) => {
if info.map(|x| x.option_enabled("largeBlobs")) == Some(true) {
Some(MakeCredentialLargeBlobExtensionOutput {
supported: Some(true),
})
} else {
None
}
}
};

MakeCredentialsResponseUnsignedExtensions {
cred_props,
hmac_create_secret,
large_blob,
prf,
}
}
}

#[derive(Debug, Clone)]
pub struct MakeCredentialRequest {
pub hash: Vec<u8>,
Expand Down Expand Up @@ -131,10 +220,12 @@ pub struct CredentialProtectionExtension {
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum CredentialProtectionPolicy {
#[serde(rename = "userVerificationOptional")]
UserVerificationOptional = 1,
UserVerificationOptionalWithCredentialIdList = 2,
#[serde(rename = "userVerificationOptionalWithCredentialIDList")]
UserVerificationOptionalWithCredentialIDList = 2,
#[serde(rename = "userVerificationRequired")]
UserVerificationRequired = 3,
}

Expand All @@ -144,7 +235,7 @@ impl From<CredentialProtectionPolicy> for Ctap2CredentialProtectionPolicy {
CredentialProtectionPolicy::UserVerificationOptional => {
Ctap2CredentialProtectionPolicy::Optional
}
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList => {
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList
}
CredentialProtectionPolicy::UserVerificationRequired => {
Expand All @@ -161,7 +252,7 @@ impl From<Ctap2CredentialProtectionPolicy> for CredentialProtectionPolicy {
CredentialProtectionPolicy::UserVerificationOptional
}
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList => {
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList
}
Ctap2CredentialProtectionPolicy::Required => {
CredentialProtectionPolicy::UserVerificationRequired
Expand Down
98 changes: 9 additions & 89 deletions libwebauthn/src/proto/ctap2/model/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ use super::{
use crate::{
fido::AuthenticatorData,
ops::webauthn::{
CredentialPropsExtension, CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput,
MakeCredentialLargeBlobExtension, MakeCredentialLargeBlobExtensionOutput,
MakeCredentialPrfOutput, MakeCredentialRequest, MakeCredentialResponse,
MakeCredentialsRequestExtensions, MakeCredentialsResponseUnsignedExtensions,
CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension,
MakeCredentialRequest, MakeCredentialResponse, MakeCredentialsRequestExtensions,
MakeCredentialsResponseUnsignedExtensions,
},
pin::PinUvAuthProtocol,
proto::CtapError,
Expand Down Expand Up @@ -255,11 +254,12 @@ impl Ctap2MakeCredentialResponse {
request: &MakeCredentialRequest,
info: Option<&Ctap2GetInfoResponse>,
) -> MakeCredentialResponse {
let unsigned_extensions_output = self
.authenticator_data
.extensions
.as_ref()
.map(|x| x.to_unsigned_extensions(request, info));
let unsigned_extensions_output =
MakeCredentialsResponseUnsignedExtensions::from_signed_extensions(
&self.authenticator_data.extensions,
request,
info,
);
MakeCredentialResponse {
format: self.format,
authenticator_data: self.authenticator_data,
Expand Down Expand Up @@ -330,83 +330,3 @@ pub struct Ctap2MakeCredentialsResponseExtensions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_pin_length: Option<u32>,
}

impl Ctap2MakeCredentialsResponseExtensions {
pub fn to_unsigned_extensions(
&self,
request: &MakeCredentialRequest,
info: Option<&Ctap2GetInfoResponse>,
) -> MakeCredentialsResponseUnsignedExtensions {
let (hmac_create_secret, prf) = if let Some(incoming_ext) = &request.extensions {
match &incoming_ext.hmac_or_prf {
MakeCredentialHmacOrPrfInput::None => (None, None),
MakeCredentialHmacOrPrfInput::HmacGetSecret => (self.hmac_secret, None),
MakeCredentialHmacOrPrfInput::Prf => (
None,
Some(MakeCredentialPrfOutput {
enabled: self.hmac_secret,
}),
),
}
} else {
(None, None)
};

// credProps extension
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
let cred_props = match &request
.extensions
.as_ref()
.and_then(|x| x.cred_props.as_ref())
{
None | Some(false) => None, // Not requested, so we don't give an answer
Some(true) => {
// https://w3c.github.io/webauthn/#dom-credentialpropertiesoutput-rk
// Some authenticators create discoverable credentials even when not
// requested by the client platform. Because of this, client platforms may be
// forced to omit the rk property because they lack the assurance to be able
// to set it to false.
if info.map(|x| x.supports_fido_2_1()) == Some(true) {
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#op-makecred-step-rk
// if the "rk" option is false: the authenticator MUST create a non-discoverable credential.
// Note: This step is a change from CTAP2.0 where if the "rk" option is false the authenticator could optionally create a discoverable credential.
Some(CredentialPropsExtension {
rk: Some(request.require_resident_key),
})
} else {
Some(CredentialPropsExtension {
// For CTAP 2.0, we can't say if "rk" is true or not.
rk: None,
})
}
}
};

// largeBlob extension
// https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension
let large_blob = match &request
.extensions
.as_ref()
.and_then(|x| Some(&x.large_blob))
{
None | Some(MakeCredentialLargeBlobExtension::None) => None, // Not requested, so we don't give an answer
Some(MakeCredentialLargeBlobExtension::Preferred)
| Some(MakeCredentialLargeBlobExtension::Required) => {
if info.map(|x| x.option_enabled("largeBlobs")) == Some(true) {
Some(MakeCredentialLargeBlobExtensionOutput {
supported: Some(true),
})
} else {
None
}
}
};

MakeCredentialsResponseUnsignedExtensions {
cred_props,
hmac_create_secret,
large_blob,
prf,
}
}
}
Loading