Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
to a new `PasskeyAccessor` trait instead of the `TryInto<Passkey>`, making it possible to use a
custom passkey representation type that goes throughout the entire flow without losing any
additional information through a conversion. (#87)
- ⚠ BREAKING: The `Ctap2Api::get_info` method now returns a boxed response due to the size of
the response. (#88)


### passkey-client v0.5.0
Expand All @@ -35,6 +37,9 @@
- Support stringified booleans in webauthn requests (#67)
- Be more tolerant to failed deserialization of optional vectors (#67)
- ⚠ BREAKING: Add `username` and `user_display_name` to the `Passkey` type and its mock builder. (#87)
- Update CTAP2 types to ignore unknown values during deserialization,
just like their WebAuthn equivalents. (#88)
- ⚠ BREAKING: Update `ctap2::get_info::Response` to have all the fields from ctap 2.2 (#88)

## Passkey v0.4.0
### passkey-authenticator v0.4.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::ops::Not;
use passkey_types::{
crypto::hmac_sha256,
ctap2::{
Ctap2Error, StatusCode, U2FError,
Ctap2Error, StatusCode,
extensions::{
AuthenticatorPrfGetOutputs, AuthenticatorPrfInputs, AuthenticatorPrfMakeOutputs,
AuthenticatorPrfValues, HmacSecretSaltOrOutput,
Expand Down Expand Up @@ -150,7 +150,9 @@ impl<S, U> Authenticator<S, U> {
return Ok(None);
};

let hmac_creds = passkey_ext.ok_or(U2FError::InvalidParameter)?;
let Some(hmac_creds) = passkey_ext else {
return Ok(None);
};

let Some(request) = select_salts(credential_id, salts) else {
return Ok(None);
Expand Down
21 changes: 16 additions & 5 deletions passkey-authenticator/src/authenticator/get_info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use passkey_types::ctap2::get_info::{Options, Response, Version};
use passkey_types::{
ctap2::get_info::{Options, Response, Version},
webauthn::PublicKeyCredentialParameters,
};

use crate::{
Authenticator, CredentialStore, UserValidationMethod, credential_store::DiscoverabilitySupport,
Expand All @@ -7,9 +10,9 @@ use crate::{
impl<S: CredentialStore, U: UserValidationMethod> Authenticator<S, U> {
/// Using this method, the host can request that the authenticator report a list of all
/// supported protocol versions, supported extensions, AAGUID of the device, and its capabilities.
pub async fn get_info(&self) -> Response {
Response {
versions: vec![Version::FIDO_2_0, Version::U2F_V2],
pub async fn get_info(&self) -> Box<Response> {
Box::new(Response {
versions: vec![Version::FIDO_2_0, Version::FIDO_2_1],
extensions: self.extensions.list_extensions(),
aaguid: *self.aaguid(),
options: Some(Options {
Expand All @@ -22,6 +25,14 @@ impl<S: CredentialStore, U: UserValidationMethod> Authenticator<S, U> {
max_msg_size: None,
pin_protocols: None,
transports: Some(self.transports.clone()),
}
algorithms: Some(
self.algs
.iter()
.copied()
.map(PublicKeyCredentialParameters::from)
.collect(),
),
..Default::default()
})
}
}
2 changes: 1 addition & 1 deletion passkey-authenticator/src/authenticator/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ where
.set_make_credential_extensions(extensions.signed)?;

let response = Response {
fmt: "None".into(),
fmt: "none".into(),
auth_data,
att_stmt: coset::cbor::value::Value::Map(vec![]),
ep_att: None,
Expand Down
4 changes: 2 additions & 2 deletions passkey-authenticator/src/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ mod sealed {
#[async_trait::async_trait]
pub trait Ctap2Api: sealed::Sealed {
/// Request to get the information of the authenticator and see what it supports.
async fn get_info(&self) -> get_info::Response;
async fn get_info(&self) -> Box<get_info::Response>;

/// Request to create and save a new credential in the authenticator.
async fn make_credential(
Expand All @@ -51,7 +51,7 @@ where
S: CredentialStore + Sync + Send,
U: UserValidationMethod<PasskeyItem = <S as CredentialStore>::PasskeyItem> + Sync + Send,
{
async fn get_info(&self) -> get_info::Response {
async fn get_info(&self) -> Box<get_info::Response> {
Authenticator::get_info(self).await
}

Expand Down
2 changes: 2 additions & 0 deletions passkey-client/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,12 @@ fn map_rk_maps_criteria_to_rk_bool() {
up: true,
plat: true,
client_pin: None,
..Default::default()
}),
max_msg_size: None,
pin_protocols: None,
transports: None,
..Default::default()
};
let client = Client::new(Authenticator::new(
ctap2::Aaguid::new_empty(),
Expand Down
4 changes: 2 additions & 2 deletions passkey-types/src/ctap2/extensions/hmac_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};

serde_workaround! {
/// Object holding the initial salts for creating the secret.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct HmacGetSecretInput {
/// Should be of form [`coset::CoseKey`] but that doesn't implement [`Serialize`] or [`Deserialize`].
#[serde(rename=0x01)]
Expand All @@ -20,7 +20,7 @@ serde_workaround! {
pub salt_auth: Bytes,

/// The Pin Authentication protocol used in the derivation of the shared secret.
#[serde(rename=0x04, default, skip_serializing_if= Option::is_none)]
#[serde(rename=0x04; default, skip_serializing_if= Option::is_none)]
pub pin_uv_auth_protocol: Option<u8>,
}
}
Expand Down
4 changes: 2 additions & 2 deletions passkey-types/src/ctap2/extensions/prf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{Bytes, webauthn};
use crate::ctap2::{get_assertion, make_credential};

/// This struct is a more opiniated mirror of [`webauthn::AuthenticationExtensionsPrfInputs`].
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct AuthenticatorPrfInputs {
/// See [`webauthn::AuthenticationExtensionsPrfInputs::eval`].
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand All @@ -24,7 +24,7 @@ pub struct AuthenticatorPrfInputs {
}

/// This struct is a more opiniated mirror of [`webauthn::AuthenticationExtensionsPrfValues`].
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct AuthenticatorPrfValues {
/// This is the already hashed values of [`webauthn::AuthenticationExtensionsPrfValues::first`].
pub first: [u8; 32],
Expand Down
42 changes: 29 additions & 13 deletions passkey-types/src/ctap2/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::{
Bytes,
ctap2::AuthenticatorData,
utils::serde::{ignore_unknown, ignore_unknown_opt_vec},
webauthn::{PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity},
};

Expand Down Expand Up @@ -34,25 +35,35 @@ serde_workaround! {
/// A sequence of PublicKeyCredentialDescriptor structures, each denoting a credential. If
/// this parameter is present and has 1 or more entries, the authenticator MUST only
/// generate an assertion using one of the denoted credentials.
#[serde(rename = 0x03, default, skip_serializing_if = Option::is_none)]
#[serde(
rename = 0x03;
default,
skip_serializing_if = Option::is_none,
deserialize_with = ignore_unknown_opt_vec
)]
pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>,

/// Parameters to influence authenticator operation. These parameters might be authenticator
/// specific.
#[serde(rename = 0x04, default, skip_serializing_if = Option::is_none)]
#[serde(
rename = 0x04;
default,
skip_serializing_if = Option::is_none,
deserialize_with = ignore_unknown
)]
pub extensions: Option<ExtensionInputs>,

/// Parameters to influence authenticator operation, see [`Options`] for more details.
#[serde(rename = 0x05, default)]
#[serde(rename = 0x05; default)]
pub options: Options,

/// First 16 bytes of HMAC-SHA-256 of clientDataHash using pinToken which platform got from
/// the authenticator: HMAC-SHA-256(pinToken, clientDataHash). (NOT YET SUPPORTED)
#[serde(rename = 0x06, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x06; default, skip_serializing_if = Option::is_none)]
pub pin_auth: Option<Bytes>,

/// PIN protocol version chosen by the client
#[serde(rename = 0x07, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x07; default, skip_serializing_if = Option::is_none)]
pub pin_protocol: Option<u8>,
}
}
Expand All @@ -66,7 +77,8 @@ pub struct ExtensionInputs {
#[serde(
rename = "hmac-secret",
default,
skip_serializing_if = "Option::is_none"
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown"
)]
pub hmac_secret: Option<HmacGetSecretInput>,

Expand All @@ -75,7 +87,11 @@ pub struct ExtensionInputs {
/// The output from a request using the `prf` extension will not be signed
/// and will be un-encrypted.
/// This input should already be hashed by the client.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown"
)]
pub prf: Option<AuthenticatorPrfInputs>,
}

Expand All @@ -99,7 +115,7 @@ serde_workaround! {
/// PublicKeyCredentialDescriptor structure containing the credential identifier whose
/// private key was used to generate the assertion. May be omitted if the allowList has
/// exactly one Credential.
#[serde(rename = 0x01, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x01; default, skip_serializing_if = Option::is_none)]
pub credential: Option<PublicKeyCredentialDescriptor>,

/// The signed-over contextual bindings made by the authenticator
Expand Down Expand Up @@ -134,7 +150,7 @@ serde_workaround! {
/// authenticator returns "id" as well as other fields to the platform. Platform will use
/// this information to show the account selection UX to the user and for the user selected
/// account, it will ONLY return "id" back to the WebAuthn layer and discard other user details.
#[serde(rename = 0x04, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x04; default, skip_serializing_if = Option::is_none)]
pub user: Option<PublicKeyCredentialUserEntity>,

/// Total number of account credentials for the RP. This member is required when more than
Expand All @@ -143,7 +159,7 @@ serde_workaround! {
///
/// It seems unlikely that more than 256 credentials would be needed for any given RP. Please
/// file an enhancement request if this limit impacts your application.
#[serde(rename = 0x05, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x05; default, skip_serializing_if = Option::is_none)]
pub number_of_credentials: Option<u8>,

/// Indicates that a credential was selected by the user via interaction directly with the authenticator,
Expand All @@ -152,20 +168,20 @@ serde_workaround! {
/// MUST NOT be present in response to a request where an [`Request::allow_list`] was given,
/// where [`Self::number_of_credentials`] is greater than one,
/// nor in response to an `authenticatorGetNextAssertion` request.
#[serde(rename = 0x06, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x06; default, skip_serializing_if = Option::is_none)]
pub user_selected: Option<bool>,

/// The contents of the associated `largeBlobKey` if present for the asserted credential,
/// and if [largeBlobKey[] was true in the extensions input.
///
/// This extension is currently un-supported by this library.
#[serde(rename = 0x07, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x07; default, skip_serializing_if = Option::is_none)]
pub large_blob_key: Option<Bytes>,

/// A map, keyed by extension identifiers, to unsigned outputs of extensions, if any.
/// Authenticators SHOULD omit this field if no processed extensions define unsigned outputs.
/// Clients MUST treat an empty map the same as an omitted field.
#[serde(rename = 0x08, default, skip_serializing_if = Option::is_none)]
#[serde(rename = 0x08; default, skip_serializing_if = Option::is_none)]
pub unsigned_extension_outputs: Option<UnsignedExtensionOutputs>,
}
}
Expand Down
Loading
Loading