Skip to content

Commit c782d8b

Browse files
committed
Separate signed/unsigned extensions for MakeCredential
1 parent 5eaaec9 commit c782d8b

6 files changed

Lines changed: 154 additions & 119 deletions

File tree

libwebauthn/src/fido.rs

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
22
use cosey::PublicKey;
33
use serde::{
44
de::{DeserializeOwned, Error as DesError, Visitor},
5-
ser::Error as SerError,
6-
Deserialize, Deserializer, Serialize, Serializer,
5+
Deserialize, Deserializer, Serialize,
76
};
87
use serde_bytes::ByteBuf;
98
use std::{
109
fmt,
1110
io::{Cursor, Read},
1211
marker::PhantomData,
1312
};
14-
use tracing::warn;
15-
16-
use crate::proto::{
17-
ctap2::{Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType},
18-
CtapError,
13+
use tracing::{error, warn};
14+
15+
use crate::{
16+
proto::{
17+
ctap2::{Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType},
18+
CtapError,
19+
},
20+
webauthn::{Error, PlatformError},
1921
};
2022

2123
#[derive(Debug, PartialEq, Eq)]
@@ -62,28 +64,6 @@ pub struct AttestedCredentialData {
6264
pub credential_public_key: PublicKey,
6365
}
6466

65-
impl Serialize for AttestedCredentialData {
66-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67-
where
68-
S: Serializer,
69-
{
70-
// Name | Length
71-
// --------------------------------
72-
// aaguid | 16
73-
// credentialIdLenght | 2
74-
// credentialId | L
75-
// credentialPublicKey | variable
76-
let mut res = self.aaguid.to_vec();
77-
res.write_u16::<BigEndian>(self.credential_id.len() as u16)
78-
.map_err(SerError::custom)?;
79-
res.extend(&self.credential_id);
80-
let cose_encoded_public_key =
81-
serde_cbor::to_vec(&self.credential_public_key).map_err(SerError::custom)?;
82-
res.extend(cose_encoded_public_key);
83-
serializer.serialize_bytes(&res)
84-
}
85-
}
86-
8767
impl From<&AttestedCredentialData> for Ctap2PublicKeyCredentialDescriptor {
8868
fn from(data: &AttestedCredentialData) -> Self {
8969
Self {
@@ -103,14 +83,11 @@ pub struct AuthenticatorData<T> {
10383
pub extensions: Option<T>,
10484
}
10585

106-
impl<T> Serialize for AuthenticatorData<T>
86+
impl<T> AuthenticatorData<T>
10787
where
10888
T: Clone + Serialize,
10989
{
110-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111-
where
112-
S: Serializer,
113-
{
90+
pub fn to_output(&self) -> Result<Vec<u8>, Error> {
11491
// Name | Length
11592
// -----------------------------------
11693
// rpIdHash | 32
@@ -121,14 +98,46 @@ where
12198
let mut res = self.rp_id_hash.to_vec();
12299
res.push(self.flags.bits());
123100
res.write_u32::<BigEndian>(self.signature_count)
124-
.map_err(SerError::custom)?;
101+
.map_err(|e| {
102+
error!("Failed to create AuthenticatorData output vec at signature_count: {e:?}");
103+
Error::Platform(PlatformError::InvalidDeviceResponse)
104+
})?;
105+
125106
if let Some(att_data) = &self.attested_credential {
126-
res.extend(serde_cbor::to_vec(att_data).map_err(SerError::custom)?);
107+
// Name | Length
108+
// --------------------------------
109+
// aaguid | 16
110+
// credentialIdLenght | 2
111+
// credentialId | L
112+
// credentialPublicKey | variable
113+
res.extend(att_data.aaguid);
114+
res.write_u16::<BigEndian>(att_data.credential_id.len() as u16)
115+
.map_err(|e| {
116+
error!(
117+
"Failed to create AuthenticatorData output vec at attested_credential.credential_id: {e:?}"
118+
);
119+
Error::Platform(PlatformError::InvalidDeviceResponse)
120+
})?;
121+
res.extend(&att_data.credential_id);
122+
let cose_encoded_public_key =
123+
serde_cbor::to_vec(&att_data.credential_public_key)
124+
.map_err(|e| {
125+
error!(
126+
"Failed to create AuthenticatorData output vec at attested_credential.credential_public_key: {e:?}"
127+
);
128+
Error::Platform(PlatformError::InvalidDeviceResponse)
129+
})?;
130+
res.extend(cose_encoded_public_key);
127131
}
128-
if let Some(extensions) = &self.extensions {
129-
res.extend(serde_cbor::to_vec(extensions).map_err(SerError::custom)?);
132+
133+
if self.extensions.is_some() || self.flags.contains(AuthenticatorDataFlags::EXTENSION_DATA)
134+
{
135+
res.extend(serde_cbor::to_vec(&self.extensions).map_err(|e| {
136+
error!("Failed to create AuthenticatorData output vec at extensions: {e:?}");
137+
Error::Platform(PlatformError::InvalidDeviceResponse)
138+
})?);
130139
}
131-
serializer.serialize_bytes(&res)
140+
Ok(res)
132141
}
133142
}
134143

libwebauthn/src/ops/u2f.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ impl UpgradableResponse<MakeCredentialResponse, MakeCredentialRequest> for Regis
148148
attestation_statement,
149149
enterprise_attestation: None,
150150
large_blob_key: None,
151-
unsigned_extension_output: None,
152151
};
153152
Ok(resp.into_make_credential_output(request, None))
154153
}

libwebauthn/src/ops/webauthn.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use crate::{
1616
ctap1::{Ctap1RegisteredKey, Ctap1Version},
1717
ctap2::{
1818
Ctap2AttestationStatement, Ctap2COSEAlgorithmIdentifier, Ctap2CredentialType,
19-
Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
20-
Ctap2PublicKeyCredentialUserEntity,
19+
Ctap2MakeCredentialsResponseExtensions, Ctap2PublicKeyCredentialDescriptor,
20+
Ctap2PublicKeyCredentialRpEntity, Ctap2PublicKeyCredentialUserEntity,
2121
},
2222
},
2323
webauthn::CtapError,
@@ -57,7 +57,23 @@ pub struct MakeCredentialResponse {
5757
pub attestation_statement: Ctap2AttestationStatement,
5858
pub enterprise_attestation: Option<bool>,
5959
pub large_blob_key: Option<Vec<u8>>,
60-
pub unsigned_extension_output: Option<BTreeMap<Value, Value>>,
60+
pub unsigned_extensions_output: Option<MakeCredentialsResponseUnsignedExtensions>,
61+
}
62+
63+
#[derive(Debug, Default, Clone, Serialize)]
64+
#[serde(rename_all = "camelCase")]
65+
pub struct MakeCredentialsResponseUnsignedExtensions {
66+
// pub app_id: Option<bool>,
67+
#[serde(skip_serializing_if = "Option::is_none")]
68+
pub cred_props: Option<CredentialPropsExtension>,
69+
// #[serde(skip_serializing_if = "Option::is_none")]
70+
// pub cred_blob: Option<bool>,
71+
#[serde(skip_serializing_if = "Option::is_none")]
72+
pub hmac_create_secret: Option<bool>,
73+
#[serde(skip_serializing_if = "Option::is_none")]
74+
pub large_blob: Option<MakeCredentialLargeBlobExtensionOutput>,
75+
#[serde(skip_serializing_if = "Option::is_none")]
76+
pub prf: Option<MakeCredentialPrfOutput>,
6177
}
6278

6379
#[derive(Debug, Clone)]
@@ -79,7 +95,7 @@ pub struct MakeCredentialRequest {
7995
pub timeout: Duration,
8096
}
8197

82-
#[derive(Debug, Default, Clone)]
98+
#[derive(Debug, Default, Clone, Serialize)]
8399
pub struct PRFValue {
84100
pub first: [u8; 32],
85101
pub second: Option<[u8; 32]>,
@@ -103,14 +119,10 @@ pub enum MakeCredentialHmacOrPrfInput {
103119
// },
104120
}
105121

106-
#[derive(Debug, Default, Clone)]
107-
pub enum MakeCredentialHmacOrPrfOutput {
108-
#[default]
109-
None,
110-
HmacGetSecret(bool),
111-
Prf {
112-
enabled: bool,
113-
},
122+
#[derive(Debug, Default, Clone, Serialize)]
123+
pub struct MakeCredentialPrfOutput {
124+
#[serde(skip_serializing_if = "Option::is_none")]
125+
pub enabled: Option<bool>,
114126
}
115127

116128
#[derive(Debug, Clone)]
@@ -159,6 +171,13 @@ impl From<Ctap2CredentialProtectionPolicy> for CredentialProtectionPolicy {
159171
}
160172
}
161173

174+
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
175+
#[serde(rename_all = "camelCase")]
176+
pub struct CredentialPropsExtension {
177+
#[serde(skip_serializing_if = "Option::is_none")]
178+
pub rk: Option<bool>,
179+
}
180+
162181
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
163182
#[serde(rename_all = "camelCase")]
164183
pub enum MakeCredentialLargeBlobExtension {
@@ -168,6 +187,12 @@ pub enum MakeCredentialLargeBlobExtension {
168187
Required,
169188
}
170189

190+
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
191+
pub struct MakeCredentialLargeBlobExtensionOutput {
192+
#[serde(skip_serializing_if = "Option::is_none")]
193+
pub supported: Option<bool>,
194+
}
195+
171196
#[derive(Debug, Default, Clone)]
172197
pub struct MakeCredentialsRequestExtensions {
173198
pub cred_props: Option<bool>,
@@ -178,21 +203,9 @@ pub struct MakeCredentialsRequestExtensions {
178203
pub hmac_or_prf: MakeCredentialHmacOrPrfInput,
179204
}
180205

181-
#[derive(Debug, Default, Clone)]
182-
pub struct MakeCredentialsResponseExtensions {
183-
pub cred_protect: Option<CredentialProtectionPolicy>,
184-
/// If storing credBlob was successful
185-
pub cred_blob: Option<bool>,
186-
/// Current min PIN lenght
187-
pub min_pin_length: Option<u32>,
188-
pub hmac_or_prf: MakeCredentialHmacOrPrfOutput,
189-
// Currently, credProps only returns one value: rk = bool
190-
// If these get more in the future, we can use a struct here.
191-
pub cred_props_rk: Option<bool>,
192-
}
206+
pub type MakeCredentialsResponseExtensions = Ctap2MakeCredentialsResponseExtensions;
193207

194208
impl MakeCredentialRequest {
195-
#[cfg(test)]
196209
pub fn dummy() -> Self {
197210
Self {
198211
hash: vec![0; 32],
@@ -203,7 +216,7 @@ impl MakeCredentialRequest {
203216
extensions: None,
204217
origin: "example.org".to_owned(),
205218
require_resident_key: false,
206-
user_verification: UserVerificationRequirement::Preferred,
219+
user_verification: UserVerificationRequirement::Discouraged,
207220
timeout: Duration::from_secs(10),
208221
}
209222
}
@@ -265,9 +278,11 @@ pub struct GetAssertionRequestExtensions {
265278
pub large_blob: GetAssertionLargeBlobExtension,
266279
}
267280

268-
#[derive(Clone, Debug, Default)]
281+
#[derive(Clone, Debug, Default, Serialize)]
282+
#[serde(rename_all = "camelCase")]
269283
pub struct HMACGetSecretOutput {
270284
pub output1: [u8; 32],
285+
#[serde(skip_serializing_if = "Option::is_none")]
271286
pub output2: Option<[u8; 32]>,
272287
}
273288

libwebauthn/src/proto/ctap2/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub use model::{
2626
Ctap2CredentialManagementResponse, Ctap2RPData,
2727
};
2828
pub use model::{Ctap2GetAssertionRequest, Ctap2GetAssertionResponse};
29-
pub use model::{Ctap2MakeCredentialRequest, Ctap2MakeCredentialResponse};
29+
pub use model::{
30+
Ctap2MakeCredentialRequest, Ctap2MakeCredentialResponse, Ctap2MakeCredentialsResponseExtensions,
31+
};
3032
pub mod preflight;
3133
pub use protocol::Ctap2;

libwebauthn/src/proto/ctap2/model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub use client_pin::{
2525
mod make_credential;
2626
pub use make_credential::{
2727
Ctap2MakeCredentialOptions, Ctap2MakeCredentialRequest, Ctap2MakeCredentialResponse,
28+
Ctap2MakeCredentialsResponseExtensions,
2829
};
2930
mod get_assertion;
3031
pub use get_assertion::{

0 commit comments

Comments
 (0)