use crate::{
Authenticator, CoseKeyPair, CredentialStore, UserValidationMethod, passkey::PasskeyAccessor,
};
use coset::iana;
use p256::{
SecretKey,
ecdsa::{SigningKey, signature::Signer},
};
use passkey_types::{
Bytes, Passkey,
ctap2::{Flags, U2FError},
u2f::{
AuthenticationRequest, AuthenticationResponse, PublicKey, RegisterRequest, RegisterResponse,
},
};
mod sealed {
use crate::{Authenticator, CredentialStore, UserValidationMethod};
pub trait Sealed {}
impl<S: CredentialStore, U: UserValidationMethod> Sealed for Authenticator<S, U> {}
}
#[async_trait::async_trait]
pub trait U2fApi: sealed::Sealed {
async fn register(
&mut self,
request: RegisterRequest,
handle: &[u8],
) -> Result<RegisterResponse, U2FError>;
async fn authenticate(
&self,
request: AuthenticationRequest,
counter: u32,
user_presence: Flags,
) -> Result<AuthenticationResponse, U2FError>;
}
#[async_trait::async_trait]
impl<S: CredentialStore + Sync + Send, U: UserValidationMethod + Sync + Send> U2fApi
for Authenticator<S, U>
{
async fn register(
&mut self,
request: RegisterRequest,
handle: &[u8],
) -> Result<RegisterResponse, U2FError> {
let private_key = {
let mut rng = rand::thread_rng();
SecretKey::random(&mut rng)
};
let CoseKeyPair { public: _, private } =
CoseKeyPair::from_secret_key(&private_key, iana::Algorithm::ES256);
let signing_key = SigningKey::from(private_key);
let public_key = signing_key.verifying_key();
let pub_key_encoded = public_key.to_encoded_point(false);
let public_key = PublicKey {
x: pub_key_encoded.x().unwrap().as_slice().try_into().unwrap(),
y: pub_key_encoded.y().unwrap().as_slice().try_into().unwrap(),
};
let signature_target = [0x00] .into_iter()
.chain(request.application) .chain(request.challenge) .chain(handle.iter().copied()) .chain(public_key.encode()) .collect::<Vec<u8>>();
let signature_singleton: p256::ecdsa::Signature = signing_key.sign(&signature_target);
let signature = signature_singleton.to_vec();
let attestation_certificate = Vec::new();
let response = RegisterResponse {
public_key,
key_handle: handle.into(),
attestation_certificate,
signature,
};
let (passkey, user, rp) =
Passkey::wrap_u2f_registration_request(&request, &response, handle, &private);
let options = passkey_types::ctap2::get_assertion::Options {
rk: false,
uv: false,
up: false,
};
let result = self
.store_mut()
.save_credential(passkey, user, rp, options)
.await;
match result {
Ok(_) => Ok(response),
_ => Err(U2FError::Other),
}
}
async fn authenticate(
&self,
request: AuthenticationRequest,
counter: u32,
user_presence: Flags,
) -> Result<AuthenticationResponse, U2FError> {
let pk_descriptor = passkey_types::webauthn::PublicKeyCredentialDescriptor {
ty: passkey_types::webauthn::PublicKeyCredentialType::PublicKey,
id: request.key_handle.into(),
transports: None,
};
let id_bytes: Bytes = request.application.to_vec().into();
let maybe_credential = self
.store()
.find_credentials(
Some(&[pk_descriptor]),
String::from(id_bytes).as_str(),
None,
)
.await
.map_err(|_| U2FError::Other);
let credential = maybe_credential?
.into_iter()
.next()
.ok_or(U2FError::Other)?;
let secret_key =
super::private_key_from_cose_key(&credential.key()).map_err(|_| U2FError::Other)?;
let signing_key = SigningKey::from(secret_key);
let signature_target = request
.application .into_iter()
.chain(std::iter::once(user_presence.into())) .chain(counter.to_be_bytes()) .chain(request.challenge) .collect::<Vec<u8>>();
let signature: p256::ecdsa::Signature = signing_key.sign(&signature_target);
let signature_bytes = signature.to_der().as_bytes().to_vec();
Ok(AuthenticationResponse {
user_presence,
counter,
signature: signature_bytes,
})
}
}
#[cfg(test)]
mod tests;