use std::fmt::Debug;
use super::u2f::{AuthenticationRequest, RegisterRequest, RegisterResponse};
use crate::{Bytes, ctap2::make_credential as ctap2, webauthn};
use coset::CoseKey;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "testable")]
mod mock;
#[cfg(feature = "testable")]
pub use self::mock::PasskeyBuilder;
#[derive(Clone)]
#[cfg_attr(any(test, feature = "testable"), derive(PartialEq))]
pub struct Passkey {
pub key: CoseKey,
pub credential_id: Bytes,
pub rp_id: String,
pub user_handle: Option<Bytes>,
pub username: Option<String>,
pub user_display_name: Option<String>,
pub counter: Option<u32>,
pub extensions: CredentialExtensions,
}
impl Passkey {
pub fn from_u2f_register_response(
request: &RegisterRequest,
response: &RegisterResponse,
private_key: &CoseKey,
) -> Self {
let app_id: Bytes = request.application.to_vec().into();
Self {
key: private_key.clone(),
credential_id: response.key_handle.clone().to_vec().into(),
rp_id: app_id.into(),
user_handle: None,
username: None,
user_display_name: None,
counter: Some(0),
extensions: Default::default(),
}
}
pub fn from_u2f_auth_request(
request: &AuthenticationRequest,
counter: u32,
private_key: &CoseKey,
) -> Self {
let app_id: Bytes = request.application.to_vec().into();
Self {
key: private_key.clone(),
credential_id: request.key_handle.clone().to_vec().into(),
rp_id: app_id.into(),
user_handle: None,
username: None,
user_display_name: None,
counter: Some(counter),
extensions: Default::default(),
}
}
pub fn wrap_u2f_registration_request(
request: &RegisterRequest,
response: &RegisterResponse,
key_handle: &[u8],
private_key: &CoseKey,
) -> (
Passkey,
ctap2::PublicKeyCredentialUserEntity,
ctap2::PublicKeyCredentialRpEntity,
) {
let passkey = Passkey::from_u2f_register_response(request, response, private_key);
let user_entity = ctap2::PublicKeyCredentialUserEntity {
id: key_handle.to_vec().into(),
display_name: None,
name: None,
icon_url: None,
};
let app_id: Bytes = request.application.to_vec().into();
let rp = ctap2::PublicKeyCredentialRpEntity {
id: app_id.into(),
name: None,
};
(passkey, user_entity, rp)
}
#[cfg(feature = "testable")]
pub fn mock(rp_id: String) -> PasskeyBuilder {
PasskeyBuilder::new(rp_id)
}
}
impl From<Passkey> for webauthn::PublicKeyCredentialDescriptor {
fn from(value: Passkey) -> Self {
Self {
ty: webauthn::PublicKeyCredentialType::PublicKey,
id: value.credential_id,
transports: None,
}
}
}
impl From<&Passkey> for webauthn::PublicKeyCredentialDescriptor {
fn from(value: &Passkey) -> Self {
Self {
ty: webauthn::PublicKeyCredentialType::PublicKey,
id: value.credential_id.clone(),
transports: None,
}
}
}
impl Debug for Passkey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Passkey")
.field("key_type", &self.key.kty)
.field("counter", &self.counter)
.finish()
}
}
#[derive(Default, Clone, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(any(test, feature = "testable"), derive(PartialEq))]
pub struct CredentialExtensions {
pub hmac_secret: Option<StoredHmacSecret>,
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(any(test, feature = "testable"), derive(PartialEq))]
pub struct StoredHmacSecret {
pub cred_with_uv: Vec<u8>,
pub cred_without_uv: Option<Vec<u8>>,
}
impl Debug for StoredHmacSecret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StoredHmacSecret")
.field("cred_with_uv", &"<Redacted>")
.field(
"cred_without_uv",
if self.cred_without_uv.is_some() {
&"<Redacted>"
} else {
&"None"
},
)
.finish()
}
}