Skip to content

Commit 006b8f9

Browse files
Parse unsigned extensions, even if no signed extensions are present (#104)
Some unsigned extensions have to be present, even if no signed extensions are present. Thus we have to decouple them. And rename to camelCase seemed to make `Id` out of `ID`, which is needed for JSON-parsing. So we manually rename the enums in `CredentialProtectionPolicy` now, just to be sure. Co-authored-by: Alfie Fresta <alfie.fresta@gmail.com>
1 parent e2789ad commit 006b8f9

File tree

2 files changed

+108
-97
lines changed

2 files changed

+108
-97
lines changed

libwebauthn/src/ops/webauthn.rs

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use crate::{
1212
ctap1::{Ctap1RegisteredKey, Ctap1Version},
1313
ctap2::{
1414
Ctap2AttestationStatement, Ctap2COSEAlgorithmIdentifier, Ctap2CredentialType,
15-
Ctap2GetAssertionResponseExtensions, Ctap2MakeCredentialsResponseExtensions,
16-
Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
17-
Ctap2PublicKeyCredentialUserEntity,
15+
Ctap2GetAssertionResponseExtensions, Ctap2GetInfoResponse,
16+
Ctap2MakeCredentialsResponseExtensions, Ctap2PublicKeyCredentialDescriptor,
17+
Ctap2PublicKeyCredentialRpEntity, Ctap2PublicKeyCredentialUserEntity,
1818
},
1919
},
2020
webauthn::CtapError,
@@ -54,7 +54,7 @@ pub struct MakeCredentialResponse {
5454
pub attestation_statement: Ctap2AttestationStatement,
5555
pub enterprise_attestation: Option<bool>,
5656
pub large_blob_key: Option<Vec<u8>>,
57-
pub unsigned_extensions_output: Option<MakeCredentialsResponseUnsignedExtensions>,
57+
pub unsigned_extensions_output: MakeCredentialsResponseUnsignedExtensions,
5858
}
5959

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

76+
impl MakeCredentialsResponseUnsignedExtensions {
77+
pub fn has_some(&self) -> bool {
78+
self.cred_props.is_some()
79+
|| self.hmac_create_secret.is_some()
80+
|| self.large_blob.is_some()
81+
|| self.prf.is_some()
82+
}
83+
84+
pub fn from_signed_extensions(
85+
signed_extensions: &Option<Ctap2MakeCredentialsResponseExtensions>,
86+
request: &MakeCredentialRequest,
87+
info: Option<&Ctap2GetInfoResponse>,
88+
) -> MakeCredentialsResponseUnsignedExtensions {
89+
let mut hmac_create_secret = None;
90+
let mut prf = None;
91+
if let Some(signed_extensions) = signed_extensions {
92+
(hmac_create_secret, prf) = if let Some(incoming_ext) = &request.extensions {
93+
match &incoming_ext.hmac_or_prf {
94+
MakeCredentialHmacOrPrfInput::None => (None, None),
95+
MakeCredentialHmacOrPrfInput::HmacGetSecret => {
96+
(signed_extensions.hmac_secret, None)
97+
}
98+
MakeCredentialHmacOrPrfInput::Prf => (
99+
None,
100+
Some(MakeCredentialPrfOutput {
101+
enabled: signed_extensions.hmac_secret,
102+
}),
103+
),
104+
}
105+
} else {
106+
(None, None)
107+
};
108+
}
109+
110+
// credProps extension
111+
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
112+
let cred_props = match &request
113+
.extensions
114+
.as_ref()
115+
.and_then(|x| x.cred_props.as_ref())
116+
{
117+
None | Some(false) => None, // Not requested, so we don't give an answer
118+
Some(true) => {
119+
// https://w3c.github.io/webauthn/#dom-credentialpropertiesoutput-rk
120+
// Some authenticators create discoverable credentials even when not
121+
// requested by the client platform. Because of this, client platforms may be
122+
// forced to omit the rk property because they lack the assurance to be able
123+
// to set it to false.
124+
if info.map(|x| x.supports_fido_2_1()) == Some(true) {
125+
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#op-makecred-step-rk
126+
// if the "rk" option is false: the authenticator MUST create a non-discoverable credential.
127+
// Note: This step is a change from CTAP2.0 where if the "rk" option is false the authenticator could optionally create a discoverable credential.
128+
Some(CredentialPropsExtension {
129+
rk: Some(request.require_resident_key),
130+
})
131+
} else {
132+
Some(CredentialPropsExtension {
133+
// For CTAP 2.0, we can't say if "rk" is true or not.
134+
rk: None,
135+
})
136+
}
137+
}
138+
};
139+
140+
// largeBlob extension
141+
// https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension
142+
let large_blob = match &request.extensions.as_ref().map(|x| &x.large_blob) {
143+
None | Some(MakeCredentialLargeBlobExtension::None) => None, // Not requested, so we don't give an answer
144+
Some(MakeCredentialLargeBlobExtension::Preferred)
145+
| Some(MakeCredentialLargeBlobExtension::Required) => {
146+
if info.map(|x| x.option_enabled("largeBlobs")) == Some(true) {
147+
Some(MakeCredentialLargeBlobExtensionOutput {
148+
supported: Some(true),
149+
})
150+
} else {
151+
None
152+
}
153+
}
154+
};
155+
156+
MakeCredentialsResponseUnsignedExtensions {
157+
cred_props,
158+
hmac_create_secret,
159+
large_blob,
160+
prf,
161+
}
162+
}
163+
}
164+
76165
#[derive(Debug, Clone)]
77166
pub struct MakeCredentialRequest {
78167
pub hash: Vec<u8>,
@@ -131,10 +220,12 @@ pub struct CredentialProtectionExtension {
131220
}
132221

133222
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
134-
#[serde(rename_all = "camelCase")]
135223
pub enum CredentialProtectionPolicy {
224+
#[serde(rename = "userVerificationOptional")]
136225
UserVerificationOptional = 1,
137-
UserVerificationOptionalWithCredentialIdList = 2,
226+
#[serde(rename = "userVerificationOptionalWithCredentialIDList")]
227+
UserVerificationOptionalWithCredentialIDList = 2,
228+
#[serde(rename = "userVerificationRequired")]
138229
UserVerificationRequired = 3,
139230
}
140231

@@ -144,7 +235,7 @@ impl From<CredentialProtectionPolicy> for Ctap2CredentialProtectionPolicy {
144235
CredentialProtectionPolicy::UserVerificationOptional => {
145236
Ctap2CredentialProtectionPolicy::Optional
146237
}
147-
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
238+
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList => {
148239
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList
149240
}
150241
CredentialProtectionPolicy::UserVerificationRequired => {
@@ -161,7 +252,7 @@ impl From<Ctap2CredentialProtectionPolicy> for CredentialProtectionPolicy {
161252
CredentialProtectionPolicy::UserVerificationOptional
162253
}
163254
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList => {
164-
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
255+
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList
165256
}
166257
Ctap2CredentialProtectionPolicy::Required => {
167258
CredentialProtectionPolicy::UserVerificationRequired

libwebauthn/src/proto/ctap2/model/make_credential.rs

Lines changed: 9 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ use super::{
77
use crate::{
88
fido::AuthenticatorData,
99
ops::webauthn::{
10-
CredentialPropsExtension, CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput,
11-
MakeCredentialLargeBlobExtension, MakeCredentialLargeBlobExtensionOutput,
12-
MakeCredentialPrfOutput, MakeCredentialRequest, MakeCredentialResponse,
13-
MakeCredentialsRequestExtensions, MakeCredentialsResponseUnsignedExtensions,
10+
CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension,
11+
MakeCredentialRequest, MakeCredentialResponse, MakeCredentialsRequestExtensions,
12+
MakeCredentialsResponseUnsignedExtensions,
1413
},
1514
pin::PinUvAuthProtocol,
1615
proto::CtapError,
@@ -255,11 +254,12 @@ impl Ctap2MakeCredentialResponse {
255254
request: &MakeCredentialRequest,
256255
info: Option<&Ctap2GetInfoResponse>,
257256
) -> MakeCredentialResponse {
258-
let unsigned_extensions_output = self
259-
.authenticator_data
260-
.extensions
261-
.as_ref()
262-
.map(|x| x.to_unsigned_extensions(request, info));
257+
let unsigned_extensions_output =
258+
MakeCredentialsResponseUnsignedExtensions::from_signed_extensions(
259+
&self.authenticator_data.extensions,
260+
request,
261+
info,
262+
);
263263
MakeCredentialResponse {
264264
format: self.format,
265265
authenticator_data: self.authenticator_data,
@@ -330,83 +330,3 @@ pub struct Ctap2MakeCredentialsResponseExtensions {
330330
#[serde(default, skip_serializing_if = "Option::is_none")]
331331
pub min_pin_length: Option<u32>,
332332
}
333-
334-
impl Ctap2MakeCredentialsResponseExtensions {
335-
pub fn to_unsigned_extensions(
336-
&self,
337-
request: &MakeCredentialRequest,
338-
info: Option<&Ctap2GetInfoResponse>,
339-
) -> MakeCredentialsResponseUnsignedExtensions {
340-
let (hmac_create_secret, prf) = if let Some(incoming_ext) = &request.extensions {
341-
match &incoming_ext.hmac_or_prf {
342-
MakeCredentialHmacOrPrfInput::None => (None, None),
343-
MakeCredentialHmacOrPrfInput::HmacGetSecret => (self.hmac_secret, None),
344-
MakeCredentialHmacOrPrfInput::Prf => (
345-
None,
346-
Some(MakeCredentialPrfOutput {
347-
enabled: self.hmac_secret,
348-
}),
349-
),
350-
}
351-
} else {
352-
(None, None)
353-
};
354-
355-
// credProps extension
356-
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
357-
let cred_props = match &request
358-
.extensions
359-
.as_ref()
360-
.and_then(|x| x.cred_props.as_ref())
361-
{
362-
None | Some(false) => None, // Not requested, so we don't give an answer
363-
Some(true) => {
364-
// https://w3c.github.io/webauthn/#dom-credentialpropertiesoutput-rk
365-
// Some authenticators create discoverable credentials even when not
366-
// requested by the client platform. Because of this, client platforms may be
367-
// forced to omit the rk property because they lack the assurance to be able
368-
// to set it to false.
369-
if info.map(|x| x.supports_fido_2_1()) == Some(true) {
370-
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#op-makecred-step-rk
371-
// if the "rk" option is false: the authenticator MUST create a non-discoverable credential.
372-
// Note: This step is a change from CTAP2.0 where if the "rk" option is false the authenticator could optionally create a discoverable credential.
373-
Some(CredentialPropsExtension {
374-
rk: Some(request.require_resident_key),
375-
})
376-
} else {
377-
Some(CredentialPropsExtension {
378-
// For CTAP 2.0, we can't say if "rk" is true or not.
379-
rk: None,
380-
})
381-
}
382-
}
383-
};
384-
385-
// largeBlob extension
386-
// https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension
387-
let large_blob = match &request
388-
.extensions
389-
.as_ref()
390-
.and_then(|x| Some(&x.large_blob))
391-
{
392-
None | Some(MakeCredentialLargeBlobExtension::None) => None, // Not requested, so we don't give an answer
393-
Some(MakeCredentialLargeBlobExtension::Preferred)
394-
| Some(MakeCredentialLargeBlobExtension::Required) => {
395-
if info.map(|x| x.option_enabled("largeBlobs")) == Some(true) {
396-
Some(MakeCredentialLargeBlobExtensionOutput {
397-
supported: Some(true),
398-
})
399-
} else {
400-
None
401-
}
402-
}
403-
};
404-
405-
MakeCredentialsResponseUnsignedExtensions {
406-
cred_props,
407-
hmac_create_secret,
408-
large_blob,
409-
prf,
410-
}
411-
}
412-
}

0 commit comments

Comments
 (0)