Skip to content

Commit 3003422

Browse files
committed
Fix getRetries CTAP 2.0 / 2.1 behavior
1 parent 25de93f commit 3003422

File tree

4 files changed

+93
-12
lines changed

4 files changed

+93
-12
lines changed

libwebauthn/src/pin.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,9 +463,20 @@ where
463463
return Err(Error::Platform(PlatformError::PinTooLong));
464464
}
465465

466+
let uv_proto = select_uv_proto(&get_info_response).await?;
467+
466468
let current_pin = match get_info_response.options.as_ref().unwrap().get("clientPin") {
467469
// Obtaining the current PIN, if one is set
468-
Some(true) => Some(obtain_pin(self, pin_provider, timeout).await?),
470+
Some(true) => Some(
471+
obtain_pin(
472+
self,
473+
&get_info_response,
474+
uv_proto.version(),
475+
pin_provider,
476+
timeout,
477+
)
478+
.await?,
479+
),
469480

470481
// No PIN set yet
471482
Some(false) => None,
@@ -478,7 +489,6 @@ where
478489

479490
// In preparation for obtaining pinUvAuthToken, the platform:
480491
// * Obtains a shared secret.
481-
let uv_proto = select_uv_proto(&get_info_response).await?;
482492
let (public_key, shared_secret) = obtain_shared_secret(self, &uv_proto, timeout).await?;
483493

484494
// paddedPin is newPin padded on the right with 0x00 bytes to make it 64 bytes long. (Since the maximum length of newPin is 63 bytes, there is always at least one byte of padding.)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ impl Ctap2ClientPinRequest {
7979
}
8080
}
8181

82-
pub fn new_get_pin_retries() -> Self {
82+
pub fn new_get_pin_retries(pin_proto: Option<Ctap2PinUvAuthProtocol>) -> Self {
8383
Self {
84-
protocol: None,
84+
protocol: pin_proto,
8585
command: Ctap2PinUvAuthProtocolCommand::GetPinRetries,
8686
key_agreement: None,
8787
uv_auth_param: None,

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,41 +65,90 @@ pub struct Ctap2GetInfoResponse {
6565
#[serde(skip_serializing_if = "Option::is_none")]
6666
pub force_pin_change: Option<bool>,
6767

68+
/// minPINLength (0x0D)
6869
#[serde(default)]
6970
#[serde(skip_serializing_if = "Option::is_none")]
7071
pub min_pin_length: Option<u32>,
7172

73+
/// firmwareVersion (0x0E)
7274
#[serde(default)]
7375
#[serde(skip_serializing_if = "Option::is_none")]
7476
pub firmware_version: Option<u32>,
7577

78+
/// maxCredBlobLength (0x0F)
7679
#[serde(default)]
7780
#[serde(skip_serializing_if = "Option::is_none")]
7881
pub max_cred_blob_length: Option<u32>,
7982

83+
/// maxRPIDsForSetMinPINLength (0x10)
8084
#[serde(default)]
8185
#[serde(skip_serializing_if = "Option::is_none")]
8286
pub max_rpids_for_setminpinlength: Option<u32>,
8387

88+
/// preferredPlatformUvAttempts (0x11)
8489
#[serde(default)]
8590
#[serde(skip_serializing_if = "Option::is_none")]
8691
pub preferred_platform_uv_attempts: Option<u32>,
8792

93+
/// uvModality (0x12)
8894
#[serde(default)]
8995
#[serde(skip_serializing_if = "Option::is_none")]
9096
pub uv_modality: Option<u32>,
9197

98+
/// certifications (0x13)
9299
#[serde(default)]
93100
#[serde(skip_serializing_if = "Option::is_none")]
94101
pub certifications: Option<HashMap<String, u32>>,
95102

103+
/// remainingDiscoverableCredentials (0x14)
96104
#[serde(default)]
97105
#[serde(skip_serializing_if = "Option::is_none")]
98106
pub remaining_discoverable_creds: Option<u32>,
99107

108+
/// vendorPrototypeConfigCommands (0x15)
100109
#[serde(default)]
101110
#[serde(skip_serializing_if = "Option::is_none")]
102111
pub vendor_proto_config_cmds: Option<Vec<u32>>,
112+
113+
/// attestationFormats (0x16)
114+
#[serde(default)]
115+
#[serde(skip_serializing_if = "Option::is_none")]
116+
pub attestation_formats: Option<Vec<String>>,
117+
118+
/// uvCountSinceLastPinEntry (0x17)
119+
#[serde(default)]
120+
#[serde(skip_serializing_if = "Option::is_none")]
121+
pub uv_count_since_last_pin_entry: Option<u32>,
122+
123+
/// longTouchForReset (0x18)
124+
#[serde(default)]
125+
#[serde(skip_serializing_if = "Option::is_none")]
126+
pub long_touch_for_reset: Option<bool>,
127+
128+
/// encIdentifier (0x19)
129+
#[serde(default)]
130+
#[serde(skip_serializing_if = "Option::is_none")]
131+
pub enc_identifier: Option<ByteBuf>,
132+
133+
/// transportsForReset (0x1A)
134+
#[serde(default)]
135+
#[serde(skip_serializing_if = "Option::is_none")]
136+
pub transports_for_reset: Option<Vec<String>>,
137+
138+
/// pinComplexityPolicy (0x1B)
139+
#[serde(default)]
140+
#[serde(skip_serializing_if = "Option::is_none")]
141+
pub pin_complexity_policy: Option<bool>,
142+
143+
/// pinComplexityPolicyURL (0x1C)
144+
#[serde(default)]
145+
#[serde(skip_serializing_if = "Option::is_none")]
146+
pub pin_complexity_policy_url: Option<ByteBuf>,
147+
148+
/// maxPINLength (0x1D)
149+
#[serde(default)]
150+
#[serde(skip_serializing_if = "Option::is_none")]
151+
pub max_pin_length: Option<u32>,
103152
}
104153

105154
impl Ctap2GetInfoResponse {

libwebauthn/src/webauthn.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use crate::pin::{
1717
use crate::proto::ctap1::Ctap1;
1818
use crate::proto::ctap2::{
1919
Ctap2, Ctap2ClientPinRequest, Ctap2GetAssertionRequest, Ctap2GetInfoResponse,
20-
Ctap2MakeCredentialRequest, Ctap2UserVerifiableRequest, Ctap2UserVerificationOperation,
20+
Ctap2MakeCredentialRequest, Ctap2PinUvAuthProtocol, Ctap2UserVerifiableRequest,
21+
Ctap2UserVerificationOperation,
2122
};
2223
pub use crate::transport::error::{CtapError, Error, PlatformError, TransportError};
2324
use crate::transport::{Channel, Ctap2AuthTokenPermission};
@@ -339,22 +340,29 @@ where
339340
return Ok(UsedPinUvAuthToken::LegacyUV);
340341
}
341342

343+
let uv_proto = select_uv_proto(&get_info_response).await?;
342344
// For operations that include a PIN, we want to fetch one before obtaining a shared secret.
343345
// This prevents the shared secret from expiring whilst we wait for the user to enter a PIN.
344346
let pin = match uv_operation {
345347
Ctap2UserVerificationOperation::None => unreachable!(),
346348
Ctap2UserVerificationOperation::GetPinToken
347-
| Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingPinWithPermissions => {
348-
Some(obtain_pin(channel, pin_provider, timeout).await?)
349-
}
349+
| Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingPinWithPermissions => Some(
350+
obtain_pin(
351+
channel,
352+
&get_info_response,
353+
uv_proto.version(),
354+
pin_provider,
355+
timeout,
356+
)
357+
.await?,
358+
),
350359
Ctap2UserVerificationOperation::GetPinUvAuthTokenUsingUvWithPermissions => {
351360
None // TODO probably?
352361
}
353362
};
354363

355364
// In preparation for obtaining pinUvAuthToken, the platform:
356365
// * Obtains a shared secret.
357-
let uv_proto = select_uv_proto(&get_info_response).await?;
358366
let (public_key, shared_secret) = obtain_shared_secret(channel, &uv_proto, timeout).await?;
359367

360368
// Then the platform obtains a pinUvAuthToken from the authenticator, with the mc (and likely also with the ga)
@@ -446,16 +454,30 @@ where
446454

447455
pub(crate) async fn obtain_pin<C>(
448456
channel: &mut C,
457+
info: &Ctap2GetInfoResponse,
458+
pin_proto: Ctap2PinUvAuthProtocol,
449459
pin_provider: &Box<dyn PinProvider>,
450460
timeout: Duration,
451461
) -> Result<Vec<u8>, Error>
452462
where
453463
C: Channel,
454464
{
465+
// FIDO 2.0 requires PIN protocol, 2.1 does not anymore
466+
let pin_protocol = if info.supports_fido_2_1() {
467+
None
468+
} else {
469+
Some(pin_proto)
470+
};
471+
455472
let attempts_left = channel
456-
.ctap2_client_pin(&Ctap2ClientPinRequest::new_get_pin_retries(), timeout)
457-
.await?
458-
.pin_retries;
473+
.ctap2_client_pin(
474+
&Ctap2ClientPinRequest::new_get_pin_retries(pin_protocol),
475+
timeout,
476+
)
477+
.await
478+
.map(|x| x.pin_retries)
479+
.ok() // It's optional, so soft-error here
480+
.flatten();
459481
let Some(raw_pin) = pin_provider.provide_pin(attempts_left).await else {
460482
info!("User cancelled operation: no PIN provided");
461483
return Err(Error::Ctap(CtapError::PINRequired));

0 commit comments

Comments
 (0)