use crate::{Authenticator, CoseKeyPair, CredentialStore, UserValidationMethod};
use coset::iana;
use p256::{
ecdsa::{signature::Signer, SigningKey},
SecretKey,
};
use passkey_types::{
ctap2::{Flags, U2FError},
u2f::{
AuthenticationRequest, AuthenticationResponse, PublicKey, RegisterRequest, RegisterResponse,
},
Bytes, Passkey,
};
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_types::Passkey::wrap_u2f_registration_request(
&request, &response, handle, &private,
);
let result = self.store_mut().save_credential(passkey, user, rp).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())
.await
.map_err(|_| U2FError::Other);
let credential: Passkey = maybe_credential?
.into_iter()
.next()
.ok_or(U2FError::Other)?
.try_into()
.map_err(|_| 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 {
use super::{AuthenticationRequest, Authenticator, RegisterRequest};
use crate::{u2f::U2fApi, user_validation::MockUserValidationMethod};
use generic_array::GenericArray;
use p256::{
ecdsa::{signature::Verifier, Signature, VerifyingKey},
EncodedPoint,
};
use passkey_types::{ctap2::Aaguid, *};
#[tokio::test]
async fn test_save_u2f_passkey() {
let credstore: Option<Passkey> = None;
let mut authenticator = Authenticator::new(
Aaguid::new_empty(),
credstore,
MockUserValidationMethod::verified_user(0),
);
let challenge: [u8; 32] = ::rand::random();
let application: [u8; 32] = ::rand::random();
let reg_request = RegisterRequest {
challenge,
application,
};
let handle: [u8; 16] = ::rand::random();
let store_result = authenticator.register(reg_request, &handle[..]).await;
assert!(store_result.is_ok());
let response = store_result.unwrap();
let public_key = response.public_key;
let challenge: [u8; 32] = ::rand::random();
let auth_req = AuthenticationRequest {
parameter: u2f::AuthenticationParameter::CheckOnly,
application,
challenge,
key_handle: handle.to_vec(),
};
let counter = 181;
let auth_result = authenticator
.authenticate(auth_req, counter, ctap2::Flags::UV)
.await;
assert!(auth_result.is_ok());
let auth_result = auth_result.unwrap();
assert_eq!(auth_result.counter, counter);
assert_eq!(auth_result.user_presence, ctap2::Flags::UV);
let ep = EncodedPoint::from_affine_coordinates(
&GenericArray::clone_from_slice(&public_key.x),
&GenericArray::clone_from_slice(&public_key.y),
false,
);
let verifying_key = VerifyingKey::from_encoded_point(&ep).unwrap();
let sig = Signature::from_der(&auth_result.signature).unwrap();
let signature_target = application
.into_iter()
.chain(std::iter::once(auth_result.user_presence.into()))
.chain(auth_result.counter.to_be_bytes())
.chain(challenge)
.collect::<Vec<u8>>();
assert!(verifying_key.verify(&signature_target, &sig).is_ok());
}
}