Skip to content

Commit e73c76b

Browse files
authored
Implement discoverable credential 'preferred' (#125)
Switched away from a context-less `From<MakeCredentialRequest> for Ctap2MakeCredentialRequest` implementation to a dedicated function that can additionally receive the device-info to make that transformation. Now we also don't need to copy the original request and modify it (which was always a bit icky), but we can do the direct translation to the Ctap2-request.
1 parent a91e6a5 commit e73c76b

File tree

8 files changed

+143
-91
lines changed

8 files changed

+143
-91
lines changed

libwebauthn/examples/webauthn_cable.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use tokio::time::sleep;
1818
use tracing_subscriber::{self, EnvFilter};
1919

2020
use libwebauthn::ops::webauthn::{
21-
GetAssertionRequest, MakeCredentialRequest, UserVerificationRequirement,
21+
GetAssertionRequest, MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement,
2222
};
2323
use libwebauthn::proto::ctap2::{
2424
Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
@@ -112,7 +112,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
112112
hash: Vec::from(challenge),
113113
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
114114
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
115-
require_resident_key: false,
115+
resident_key: Some(ResidentKeyRequirement::Discouraged),
116116
user_verification: UserVerificationRequirement::Preferred,
117117
algorithms: vec![Ctap2CredentialType::default()],
118118
exclude: None,

libwebauthn/examples/webauthn_extensions_hid.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use libwebauthn::ops::webauthn::{
1313
CredentialProtectionExtension, CredentialProtectionPolicy, GetAssertionHmacOrPrfInput,
1414
GetAssertionRequest, GetAssertionRequestExtensions, HMACGetSecretInput,
1515
MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, MakeCredentialRequest,
16-
MakeCredentialsRequestExtensions, UserVerificationRequirement,
16+
MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement,
1717
};
1818
use libwebauthn::pin::PinRequestReason;
1919
use libwebauthn::proto::ctap2::{
@@ -108,7 +108,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
108108
hash: Vec::from(challenge),
109109
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
110110
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
111-
require_resident_key: true,
111+
resident_key: Some(ResidentKeyRequirement::Required),
112112
user_verification: UserVerificationRequirement::Preferred,
113113
algorithms: vec![Ctap2CredentialType::default()],
114114
exclude: None,

libwebauthn/examples/webauthn_hid.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use tokio::sync::mpsc::Receiver;
1010
use tracing_subscriber::{self, EnvFilter};
1111

1212
use libwebauthn::ops::webauthn::{
13-
GetAssertionRequest, MakeCredentialRequest, UserVerificationRequirement,
13+
GetAssertionRequest, MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement,
1414
};
1515
use libwebauthn::pin::PinRequestReason;
1616
use libwebauthn::proto::ctap2::{
@@ -91,7 +91,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
9191
hash: Vec::from(challenge),
9292
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
9393
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
94-
require_resident_key: false,
94+
resident_key: Some(ResidentKeyRequirement::Discouraged),
9595
user_verification: UserVerificationRequirement::Preferred,
9696
algorithms: vec![Ctap2CredentialType::default()],
9797
exclude: None,

libwebauthn/examples/webauthn_preflight_hid.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use tokio::sync::mpsc::Receiver;
1212
use tracing_subscriber::{self, EnvFilter};
1313

1414
use libwebauthn::ops::webauthn::{
15-
GetAssertionRequest, GetAssertionResponse, MakeCredentialRequest, UserVerificationRequirement,
15+
GetAssertionRequest, GetAssertionResponse, MakeCredentialRequest, ResidentKeyRequirement,
16+
UserVerificationRequirement,
1617
};
1718
use libwebauthn::pin::PinRequestReason;
1819
use libwebauthn::proto::ctap2::{
@@ -164,7 +165,7 @@ async fn make_credential_call(
164165
hash: Vec::from(challenge),
165166
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
166167
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
167-
require_resident_key: false,
168+
resident_key: Some(ResidentKeyRequirement::Discouraged),
168169
user_verification: UserVerificationRequirement::Preferred,
169170
algorithms: vec![Ctap2CredentialType::default()],
170171
exclude: exclude_list,

libwebauthn/examples/webauthn_prf_hid.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tracing_subscriber::{self, EnvFilter};
1414
use libwebauthn::ops::webauthn::{
1515
GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions,
1616
MakeCredentialHmacOrPrfInput, MakeCredentialRequest, MakeCredentialsRequestExtensions,
17-
PRFValue, UserVerificationRequirement,
17+
PRFValue, ResidentKeyRequirement, UserVerificationRequirement,
1818
};
1919
use libwebauthn::pin::PinRequestReason;
2020
use libwebauthn::proto::ctap2::{
@@ -102,7 +102,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
102102
hash: Vec::from(challenge),
103103
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
104104
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
105-
require_resident_key: true,
105+
resident_key: Some(ResidentKeyRequirement::Required),
106106
user_verification: UserVerificationRequirement::Preferred,
107107
algorithms: vec![Ctap2CredentialType::default()],
108108
exclude: None,

libwebauthn/src/ops/webauthn.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use make_credential::{
1515
MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension,
1616
MakeCredentialLargeBlobExtensionOutput, MakeCredentialPrfOutput, MakeCredentialRequest,
1717
MakeCredentialResponse, MakeCredentialsRequestExtensions, MakeCredentialsResponseExtensions,
18-
MakeCredentialsResponseUnsignedExtensions,
18+
MakeCredentialsResponseUnsignedExtensions, ResidentKeyRequirement,
1919
};
2020

2121
#[derive(Debug, Clone, Copy)]
@@ -50,6 +50,7 @@ pub trait DowngradableRequest<T> {
5050

5151
#[cfg(test)]
5252
mod tests {
53+
use crate::ops::webauthn::make_credential::ResidentKeyRequirement;
5354
use crate::ops::webauthn::{
5455
DowngradableRequest, MakeCredentialRequest, UserVerificationRequirement,
5556
};
@@ -61,15 +62,15 @@ mod tests {
6162
fn ctap2_make_credential_downgradable() {
6263
let mut request = MakeCredentialRequest::dummy();
6364
request.algorithms = vec![Ctap2CredentialType::default()];
64-
request.require_resident_key = false;
65+
request.resident_key = Some(ResidentKeyRequirement::Discouraged);
6566
assert!(request.is_downgradable());
6667
}
6768

6869
#[test]
6970
fn ctap2_make_credential_downgradable_unsupported_rk() {
7071
let mut request = MakeCredentialRequest::dummy();
7172
request.algorithms = vec![Ctap2CredentialType::default()];
72-
request.require_resident_key = true;
73+
request.resident_key = Some(ResidentKeyRequirement::Required);
7374
assert!(!request.is_downgradable());
7475
}
7576

libwebauthn/src/ops/webauthn/make_credential.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,23 @@ impl MakeCredentialsResponseUnsignedExtensions {
9898
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#op-makecred-step-rk
9999
// if the "rk" option is false: the authenticator MUST create a non-discoverable credential.
100100
// Note: This step is a change from CTAP2.0 where if the "rk" option is false the authenticator could optionally create a discoverable credential.
101-
Some(CredentialPropsExtension {
102-
rk: Some(request.require_resident_key),
103-
})
101+
match request.resident_key {
102+
Some(ResidentKeyRequirement::Discouraged) | None => {
103+
Some(CredentialPropsExtension { rk: Some(false) })
104+
}
105+
Some(ResidentKeyRequirement::Preferred) => {
106+
if info.map(|i| i.option_enabled("rk")).unwrap_or_default() {
107+
Some(CredentialPropsExtension { rk: Some(true) })
108+
} else {
109+
// Default value in case "rk" is missing (which it is in this constellation) is "false"
110+
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#makecred-rk
111+
Some(CredentialPropsExtension { rk: Some(false) })
112+
}
113+
}
114+
Some(ResidentKeyRequirement::Required) => {
115+
Some(CredentialPropsExtension { rk: Some(true) })
116+
}
117+
}
104118
} else {
105119
Some(CredentialPropsExtension {
106120
// For CTAP 2.0, we can't say if "rk" is true or not.
@@ -135,6 +149,13 @@ impl MakeCredentialsResponseUnsignedExtensions {
135149
}
136150
}
137151

152+
#[derive(Debug, Clone, Copy)]
153+
pub enum ResidentKeyRequirement {
154+
Required,
155+
Preferred,
156+
Discouraged,
157+
}
158+
138159
#[derive(Debug, Clone)]
139160
pub struct MakeCredentialRequest {
140161
pub hash: Vec<u8>,
@@ -143,7 +164,7 @@ pub struct MakeCredentialRequest {
143164
pub relying_party: Ctap2PublicKeyCredentialRpEntity,
144165
/// userEntity
145166
pub user: Ctap2PublicKeyCredentialUserEntity,
146-
pub require_resident_key: bool,
167+
pub resident_key: Option<ResidentKeyRequirement>,
147168
pub user_verification: UserVerificationRequirement,
148169
/// credTypesAndPubKeyAlgs
149170
pub algorithms: Vec<Ctap2CredentialType>,
@@ -270,7 +291,7 @@ impl MakeCredentialRequest {
270291
exclude: None,
271292
extensions: None,
272293
origin: "example.org".to_owned(),
273-
require_resident_key: false,
294+
resident_key: None,
274295
user_verification: UserVerificationRequirement::Discouraged,
275296
timeout: Duration::from_secs(10),
276297
}
@@ -294,7 +315,10 @@ impl DowngradableRequest<RegisterRequest> for MakeCredentialRequest {
294315
}
295316

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

0 commit comments

Comments
 (0)