Skip to content

Commit 5eaaec9

Browse files
authored
More extensions (#93)
1. Credential protection policy: a) Stop exposing `ctap_types` in our API and use our own implementation for the webauthn-layer (the CTAP layer still uses `ctap_types`) b) Also support `enforce_policy`, which we can only do from the inside, as we need `Ctap2GetInfoResponse` for that. Error out, if it can't be enforced. 2. Support `credProps` extension. a) Pretty straight forward, except that CTAP 2.0 devices are allowed to create discoverable credentials even if they are not requested. CTAP 2.1 devices are not allowed to do that anymore. So we need `Ctap2GetInfoResponse` once again to decide 3. Switch from LargeBlobKeys extension to LargeBlob extension. a) LargeBlob extension has "Preferred"-mode as well, so we need `Ctap2GetInfoResponse` again for deciding, if we can request it or not. b) LargeBlobKey-requests can easily be mapped into LargeBlob requests, by simply using LargeBlob `support = "required"`, so I'm not supporting both in the API. c) `GetAssertionLargeBlobExtension::Write()` is not yet supported, as we need the corresponding CTAP commands to store large blobs for that, which we don't have yet.
1 parent 24eb471 commit 5eaaec9

File tree

7 files changed

+236
-48
lines changed

7 files changed

+236
-48
lines changed

libwebauthn/examples/prf_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ async fn run_success_test(
154154
allow: vec![credential.clone()],
155155
user_verification: UserVerificationRequirement::Preferred,
156156
extensions: Some(GetAssertionRequestExtensions {
157-
cred_blob: None,
158157
hmac_or_prf,
158+
..Default::default()
159159
}),
160160
timeout: TIMEOUT,
161161
};

libwebauthn/examples/webauthn_extensions_hid.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ use std::error::Error;
33
use std::io::{self, Write};
44
use std::time::Duration;
55

6-
use ctap_types::ctap2::credential_management::CredentialProtectionPolicy;
76
use libwebauthn::UxUpdate;
87
use rand::{thread_rng, Rng};
98
use text_io::read;
109
use tokio::sync::mpsc::Receiver;
1110
use tracing_subscriber::{self, EnvFilter};
1211

1312
use libwebauthn::ops::webauthn::{
14-
GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions,
15-
HMACGetSecretInput, MakeCredentialHmacOrPrfInput, MakeCredentialRequest,
13+
CredentialProtectionExtension, CredentialProtectionPolicy, GetAssertionHmacOrPrfInput,
14+
GetAssertionRequest, GetAssertionRequestExtensions, HMACGetSecretInput,
15+
MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, MakeCredentialRequest,
1616
MakeCredentialsRequestExtensions, UserVerificationRequirement,
1717
};
1818
use libwebauthn::pin::PinRequestReason;
@@ -84,11 +84,15 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
8484
let challenge: [u8; 32] = thread_rng().gen();
8585

8686
let extensions = MakeCredentialsRequestExtensions {
87-
cred_protect: Some(CredentialProtectionPolicy::Required),
87+
cred_protect: Some(CredentialProtectionExtension {
88+
policy: CredentialProtectionPolicy::UserVerificationRequired,
89+
enforce_policy: true,
90+
}),
8891
cred_blob: Some(r"My own little blob".into()),
89-
large_blob_key: None,
92+
large_blob: MakeCredentialLargeBlobExtension::None,
9093
min_pin_length: Some(true),
9194
hmac_or_prf: MakeCredentialHmacOrPrfInput::HmacGetSecret,
95+
cred_props: Some(true),
9296
};
9397

9498
for mut device in devices {
@@ -148,6 +152,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
148152
salt1: [1; 32],
149153
salt2: None,
150154
}),
155+
..Default::default()
151156
}),
152157
timeout: TIMEOUT,
153158
};

libwebauthn/examples/webauthn_prf_hid.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,8 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
8585
let challenge: [u8; 32] = thread_rng().gen();
8686

8787
let extensions = MakeCredentialsRequestExtensions {
88-
cred_protect: None,
89-
cred_blob: None,
90-
large_blob_key: None,
91-
min_pin_length: None,
9288
hmac_or_prf: MakeCredentialHmacOrPrfInput::Prf,
89+
..Default::default()
9390
};
9491

9592
for mut device in devices {
@@ -429,8 +426,8 @@ async fn run_success_test(
429426
allow: vec![credential.clone()],
430427
user_verification: UserVerificationRequirement::Discouraged,
431428
extensions: Some(GetAssertionRequestExtensions {
432-
cred_blob: None,
433429
hmac_or_prf,
430+
..Default::default()
434431
}),
435432
timeout: TIMEOUT,
436433
};
@@ -471,8 +468,8 @@ async fn run_failed_test(
471468
allow: credential.map(|x| vec![x.clone()]).unwrap_or_default(),
472469
user_verification: UserVerificationRequirement::Discouraged,
473470
extensions: Some(GetAssertionRequestExtensions {
474-
cred_blob: None,
475471
hmac_or_prf,
472+
..Default::default()
476473
}),
477474
timeout: TIMEOUT,
478475
};

libwebauthn/src/ops/webauthn.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::{
33
time::Duration,
44
};
55

6-
use ctap_types::ctap2::credential_management::CredentialProtectionPolicy;
7-
use serde::Deserialize;
6+
use ctap_types::ctap2::credential_management::CredentialProtectionPolicy as Ctap2CredentialProtectionPolicy;
7+
use serde::{Deserialize, Serialize};
88
use serde_cbor::Value;
99
use sha2::{Digest, Sha256};
1010
use tracing::{debug, error, instrument, trace};
@@ -113,11 +113,67 @@ pub enum MakeCredentialHmacOrPrfOutput {
113113
},
114114
}
115115

116+
#[derive(Debug, Clone)]
117+
pub struct CredentialProtectionExtension {
118+
pub policy: CredentialProtectionPolicy,
119+
pub enforce_policy: bool,
120+
}
121+
122+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
123+
#[serde(rename_all = "camelCase")]
124+
pub enum CredentialProtectionPolicy {
125+
UserVerificationOptional = 1,
126+
UserVerificationOptionalWithCredentialIdList = 2,
127+
UserVerificationRequired = 3,
128+
}
129+
130+
impl From<CredentialProtectionPolicy> for Ctap2CredentialProtectionPolicy {
131+
fn from(value: CredentialProtectionPolicy) -> Self {
132+
match value {
133+
CredentialProtectionPolicy::UserVerificationOptional => {
134+
Ctap2CredentialProtectionPolicy::Optional
135+
}
136+
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList => {
137+
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList
138+
}
139+
CredentialProtectionPolicy::UserVerificationRequired => {
140+
Ctap2CredentialProtectionPolicy::Required
141+
}
142+
}
143+
}
144+
}
145+
146+
impl From<Ctap2CredentialProtectionPolicy> for CredentialProtectionPolicy {
147+
fn from(value: Ctap2CredentialProtectionPolicy) -> Self {
148+
match value {
149+
Ctap2CredentialProtectionPolicy::Optional => {
150+
CredentialProtectionPolicy::UserVerificationOptional
151+
}
152+
Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList => {
153+
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
154+
}
155+
Ctap2CredentialProtectionPolicy::Required => {
156+
CredentialProtectionPolicy::UserVerificationRequired
157+
}
158+
}
159+
}
160+
}
161+
162+
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
163+
#[serde(rename_all = "camelCase")]
164+
pub enum MakeCredentialLargeBlobExtension {
165+
#[default]
166+
None,
167+
Preferred,
168+
Required,
169+
}
170+
116171
#[derive(Debug, Default, Clone)]
117172
pub struct MakeCredentialsRequestExtensions {
118-
pub cred_protect: Option<CredentialProtectionPolicy>,
173+
pub cred_props: Option<bool>,
174+
pub cred_protect: Option<CredentialProtectionExtension>,
119175
pub cred_blob: Option<Vec<u8>>,
120-
pub large_blob_key: Option<bool>,
176+
pub large_blob: MakeCredentialLargeBlobExtension,
121177
pub min_pin_length: Option<bool>,
122178
pub hmac_or_prf: MakeCredentialHmacOrPrfInput,
123179
}
@@ -130,6 +186,9 @@ pub struct MakeCredentialsResponseExtensions {
130186
/// Current min PIN lenght
131187
pub min_pin_length: Option<u32>,
132188
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>,
133192
}
134193

135194
impl MakeCredentialRequest {
@@ -190,10 +249,20 @@ pub struct HMACGetSecretInput {
190249
pub salt2: Option<[u8; 32]>,
191250
}
192251

252+
#[derive(Debug, Default, Clone, PartialEq, Eq)]
253+
pub enum GetAssertionLargeBlobExtension {
254+
#[default]
255+
None,
256+
Read,
257+
// Not yet supported
258+
// Write(Vec<u8>),
259+
}
260+
193261
#[derive(Debug, Default, Clone)]
194262
pub struct GetAssertionRequestExtensions {
195263
pub cred_blob: Option<bool>,
196264
pub hmac_or_prf: GetAssertionHmacOrPrfInput,
265+
pub large_blob: GetAssertionLargeBlobExtension,
197266
}
198267

199268
#[derive(Clone, Debug, Default)]

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

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use crate::{
22
fido::AuthenticatorData,
33
ops::webauthn::{
44
Assertion, Ctap2HMACGetSecretOutput, GetAssertionHmacOrPrfInput,
5-
GetAssertionHmacOrPrfOutput, GetAssertionRequest, GetAssertionRequestExtensions,
6-
GetAssertionResponseExtensions, HMACGetSecretInput, PRFValue,
5+
GetAssertionHmacOrPrfOutput, GetAssertionLargeBlobExtension, GetAssertionRequest,
6+
GetAssertionRequestExtensions, GetAssertionResponseExtensions, HMACGetSecretInput,
7+
PRFValue,
78
},
89
pin::PinUvAuthProtocol,
910
transport::AuthTokenData,
@@ -141,15 +142,36 @@ impl Ctap2GetAssertionRequest {
141142
.as_ref()
142143
.map_or(true, |extensions| extensions.skip_serializing())
143144
}
145+
146+
pub(crate) fn from_webauthn_request(
147+
req: &GetAssertionRequest,
148+
info: &Ctap2GetInfoResponse,
149+
) -> Result<Self, Error> {
150+
// Cloning it, so we can modify it
151+
let mut req = req.clone();
152+
if let Some(ext) = req.extensions.as_mut() {
153+
// LargeBlob (NOTE: Not to be confused with LargeBlobKey)
154+
// https://w3c.github.io/webauthn/#sctn-large-blob-extension
155+
// If read is present and has the value true:
156+
// [..]
157+
// 3. If successful, set blob to the result.
158+
//
159+
// So we silently drop the extension if the device does not support it.
160+
if !info.option_enabled("largeBlobs") {
161+
ext.large_blob = GetAssertionLargeBlobExtension::None;
162+
}
163+
}
164+
Ok(Ctap2GetAssertionRequest::from(req))
165+
}
144166
}
145167

146-
impl From<&GetAssertionRequest> for Ctap2GetAssertionRequest {
147-
fn from(op: &GetAssertionRequest) -> Self {
168+
impl From<GetAssertionRequest> for Ctap2GetAssertionRequest {
169+
fn from(op: GetAssertionRequest) -> Self {
148170
Self {
149-
relying_party_id: op.relying_party_id.clone(),
150-
client_data_hash: ByteBuf::from(op.hash.clone()),
151-
allow: op.allow.clone(),
152-
extensions: op.extensions.as_ref().map(|x| x.clone().into()),
171+
relying_party_id: op.relying_party_id,
172+
client_data_hash: ByteBuf::from(op.hash),
173+
allow: op.allow,
174+
extensions: op.extensions.map(|x| x.into()),
153175
options: Some(Ctap2GetAssertionOptions {
154176
require_user_presence: true,
155177
require_user_verification: op.user_verification.is_required(),
@@ -169,6 +191,8 @@ pub struct Ctap2GetAssertionRequestExtensions {
169191
#[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")]
170192
pub hmac_secret: Option<CalculatedHMACGetSecretInput>,
171193
// From which we calculate hmac_secret
194+
#[serde(skip_serializing_if = "Option::is_none")]
195+
pub large_blob_key: Option<bool>,
172196
#[serde(skip)]
173197
pub hmac_or_prf: GetAssertionHmacOrPrfInput,
174198
}
@@ -179,6 +203,11 @@ impl From<GetAssertionRequestExtensions> for Ctap2GetAssertionRequestExtensions
179203
cred_blob: other.cred_blob,
180204
hmac_secret: None, // Get's calculated later
181205
hmac_or_prf: other.hmac_or_prf,
206+
large_blob_key: if other.large_blob == GetAssertionLargeBlobExtension::Read {
207+
Some(true)
208+
} else {
209+
None
210+
},
182211
}
183212
}
184213
}

0 commit comments

Comments
 (0)