Skip to content

Commit 6cdb6d2

Browse files
committed
Allow for shared secret to be created even if no PIN is set on the device to support HMAC/PRF
1 parent 94ee46d commit 6cdb6d2

File tree

10 files changed

+213
-74
lines changed

10 files changed

+213
-74
lines changed

libwebauthn/src/management/authenticator_config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,8 @@ impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest {
199199
fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) {
200200
// No-op
201201
}
202+
203+
fn needs_shared_secret(&self, _get_info_response: &Ctap2GetInfoResponse) -> bool {
204+
false
205+
}
202206
}

libwebauthn/src/management/bio_enrollment.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,4 +335,8 @@ impl Ctap2UserVerifiableRequest for Ctap2BioEnrollmentRequest {
335335
}
336336
}
337337
}
338+
339+
fn needs_shared_secret(&self, _get_info_response: &Ctap2GetInfoResponse) -> bool {
340+
false
341+
}
338342
}

libwebauthn/src/management/credential_management.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,4 +318,8 @@ impl Ctap2UserVerifiableRequest for Ctap2CredentialManagementRequest {
318318
}
319319
}
320320
}
321+
322+
fn needs_shared_secret(&self, _get_info_response: &Ctap2GetInfoResponse) -> bool {
323+
false
324+
}
321325
}

libwebauthn/src/ops/webauthn/make_credential.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ pub enum MakeCredentialHmacOrPrfInput {
193193
// },
194194
}
195195

196-
#[derive(Debug, Default, Clone, Serialize)]
196+
#[derive(Debug, Default, Clone, Serialize, PartialEq)]
197197
pub struct MakeCredentialPrfOutput {
198198
#[serde(skip_serializing_if = "Option::is_none")]
199199
pub enabled: Option<bool>,
@@ -315,10 +315,7 @@ impl DowngradableRequest<RegisterRequest> for MakeCredentialRequest {
315315
}
316316

317317
// Options must not include "rk" set to true.
318-
if matches!(
319-
self.resident_key,
320-
Some(ResidentKeyRequirement::Required)
321-
) {
318+
if matches!(self.resident_key, Some(ResidentKeyRequirement::Required)) {
322319
debug!("Not downgradable: request requires resident key");
323320
return false;
324321
}

libwebauthn/src/proto/ctap2/model.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,17 @@ pub trait Ctap2UserVerifiableRequest {
209209
fn permissions_rpid(&self) -> Option<&str>;
210210
fn can_use_uv(&self, info: &Ctap2GetInfoResponse) -> bool;
211211
fn handle_legacy_preview(&mut self, info: &Ctap2GetInfoResponse);
212+
/// We need to establish a shared secret, even if no PIN or UV is set on the device
213+
fn needs_shared_secret(&self, info: &Ctap2GetInfoResponse) -> bool;
212214
}
213215

214216
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215217
pub enum Ctap2UserVerificationOperation {
216218
GetPinUvAuthTokenUsingUvWithPermissions,
217219
GetPinUvAuthTokenUsingPinWithPermissions,
218220
GetPinToken,
219-
None,
221+
ClientPinOnlyForSharedSecret,
222+
LegacyUv,
220223
}
221224

222225
#[cfg(test)]

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,20 @@ impl Ctap2UserVerifiableRequest for Ctap2GetAssertionRequest {
441441
fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) {
442442
// No-op
443443
}
444+
445+
fn needs_shared_secret(&self, get_info_response: &Ctap2GetInfoResponse) -> bool {
446+
let hmac_supported = get_info_response
447+
.extensions
448+
.as_ref()
449+
.map(|e| e.contains(&String::from("hmac-secret")))
450+
.unwrap_or_default();
451+
let hmac_requested = self
452+
.extensions
453+
.as_ref()
454+
.map(|e| !matches!(e.hmac_or_prf, GetAssertionHmacOrPrfInput::None))
455+
.unwrap_or_default();
456+
hmac_requested && hmac_supported
457+
}
444458
}
445459

446460
impl Ctap2GetAssertionResponse {

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,21 @@ pub struct Ctap2GetInfoResponse {
153153
}
154154

155155
impl Ctap2GetInfoResponse {
156+
/// Only checks if the option exists, i.e. is not None
157+
/// but does not check if the option is enabled (true)
158+
/// or disabled (false)
159+
pub fn option_exists(&self, name: &str) -> bool {
160+
let Some(options) = self.options.as_ref() else {
161+
return false;
162+
};
163+
options.get(name).is_some()
164+
}
165+
166+
/// Checks if the option exists and is set to true
156167
pub fn option_enabled(&self, name: &str) -> bool {
157-
if self.options.is_none() {
168+
let Some(options) = self.options.as_ref() else {
158169
return false;
159-
}
160-
let options = self.options.as_ref().unwrap();
170+
};
161171
options.get(name) == Some(&true)
162172
}
163173

@@ -195,32 +205,43 @@ impl Ctap2GetInfoResponse {
195205
(self.option_enabled("pinUvAuthToken") && self.option_enabled("uv"))
196206
}
197207

208+
pub fn can_establish_shared_secret(&self) -> bool {
209+
// clientPin exists: clientPin command is supported, so we can establish a shared secret
210+
// uv exists and pinUvAuthToken is enabled: clientPin command partially supported. Enough to establish shared secret
211+
self.option_exists("clientPin")
212+
|| (self.option_exists("uv") && self.option_enabled("pinUvAuthToken"))
213+
}
214+
198215
pub fn uv_operation(&self, uv_blocked: bool) -> Option<Ctap2UserVerificationOperation> {
199216
if self.option_enabled("uv") && !uv_blocked {
200217
if self.option_enabled("pinUvAuthToken") {
201218
debug!("getPinUvAuthTokenUsingUvWithPermissions");
202-
return Some(
203-
Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingUvWithPermissions,
204-
);
219+
Some(Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingUvWithPermissions)
205220
} else {
206221
debug!("Deprecated FIDO 2.0 behaviour: populating 'uv' flag");
207-
return Some(Ctap2UserVerificationOperation::None);
222+
Some(Ctap2UserVerificationOperation::LegacyUv)
208223
}
209224
} else {
210225
// !uv
226+
227+
// clientPIN exists, but is not enabled, aka PIN is not yet set on the device
228+
// We can use it for establishing a shared secret, but not for creating a pinUvAuthToken
229+
if self.option_exists("clientPin") && !self.option_enabled("clientPin") {
230+
return Some(Ctap2UserVerificationOperation::ClientPinOnlyForSharedSecret);
231+
}
232+
233+
// If we do have a PIN, check if we need to use legacy getPinToken or new getPinUvAuthToken..-command
211234
if self.option_enabled("pinUvAuthToken") {
212235
assert!(self.option_enabled("clientPin"));
213236
debug!("getPinUvAuthTokenUsingPinWithPermissions");
214-
return Some(
215-
Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingPinWithPermissions,
216-
);
237+
Some(Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingPinWithPermissions)
217238
} else if self.option_enabled("clientPin") {
218239
// !pinUvAuthToken
219240
debug!("getPinToken");
220-
return Some(Ctap2UserVerificationOperation::GetPinToken);
241+
Some(Ctap2UserVerificationOperation::GetPinToken)
221242
} else {
222243
debug!("No UV and no PIN (e.g. maybe UV was blocked and no PIN available)");
223-
return None;
244+
None
224245
}
225246
}
226247
}

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

Lines changed: 7 additions & 4 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-
CredentialProtectionPolicy, ResidentKeyRequirement,
11-
MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, MakeCredentialRequest,
12-
MakeCredentialResponse, MakeCredentialsRequestExtensions,
13-
MakeCredentialsResponseUnsignedExtensions,
10+
CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension,
11+
MakeCredentialRequest, MakeCredentialResponse, MakeCredentialsRequestExtensions,
12+
MakeCredentialsResponseUnsignedExtensions, ResidentKeyRequirement,
1413
},
1514
pin::PinUvAuthProtocol,
1615
proto::CtapError,
@@ -350,6 +349,10 @@ impl Ctap2UserVerifiableRequest for Ctap2MakeCredentialRequest {
350349
fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) {
351350
// No-op
352351
}
352+
353+
fn needs_shared_secret(&self, _get_info_response: &Ctap2GetInfoResponse) -> bool {
354+
false
355+
}
353356
}
354357

355358
#[derive(Debug, Default, Clone, Serialize, Deserialize)]

libwebauthn/src/transport/channel.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,39 @@ impl Ctap2AuthTokenPermission {
9696

9797
#[derive(Debug, Clone)]
9898
pub struct AuthTokenData {
99-
pub shared_secret: Vec<u8>,
100-
pub permission: Ctap2AuthTokenPermission,
101-
pub pin_uv_auth_token: Vec<u8>,
10299
pub protocol_version: Ctap2PinUvAuthProtocol,
103100
pub key_agreement: PublicKey,
101+
pub shared_secret: Vec<u8>,
104102
pub uv_operation: Ctap2UserVerificationOperation,
103+
pub permission: Option<Ctap2AuthTokenPermission>,
104+
pub pin_uv_auth_token: Option<Vec<u8>>,
105+
}
106+
107+
impl AuthTokenData {
108+
pub fn new(
109+
shared_secret: Vec<u8>,
110+
protocol_version: Ctap2PinUvAuthProtocol,
111+
key_agreement: PublicKey,
112+
uv_operation: Ctap2UserVerificationOperation,
113+
) -> Self {
114+
Self {
115+
protocol_version,
116+
key_agreement,
117+
shared_secret,
118+
uv_operation,
119+
permission: None,
120+
pin_uv_auth_token: None,
121+
}
122+
}
123+
124+
pub fn store_auth_token(
125+
&mut self,
126+
permission: Ctap2AuthTokenPermission,
127+
pin_uv_auth_token: Vec<u8>,
128+
) {
129+
self.permission = Some(permission);
130+
self.pin_uv_auth_token = Some(pin_uv_auth_token);
131+
}
105132
}
106133

107134
#[async_trait]
@@ -111,8 +138,10 @@ pub trait Ctap2AuthTokenStore {
111138
fn clear_uv_auth_token_store(&mut self);
112139
fn get_uv_auth_token(&self, requested_permission: &Ctap2AuthTokenPermission) -> Option<&[u8]> {
113140
if let Some(stored_data) = self.get_auth_data() {
114-
if stored_data.permission.contains(requested_permission) {
115-
return Some(&stored_data.pin_uv_auth_token);
141+
if let Some(permission) = &stored_data.permission {
142+
if permission.contains(requested_permission) {
143+
return stored_data.pin_uv_auth_token.as_deref();
144+
}
116145
}
117146
}
118147
None

0 commit comments

Comments
 (0)