use bitflags::bitflags;
use cbor_smol::cbor_deserialize;
use serde::{Deserialize, Serialize};
use crate::{sizes::*, Bytes, Vec};
pub use crate::operation::{Operation, VendorOperation};
pub mod client_pin;
pub mod credential_management;
pub mod get_assertion;
pub mod get_info;
pub mod make_credential;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Request {
MakeCredential(make_credential::Request),
GetAssertion(get_assertion::Request),
GetNextAssertion,
GetInfo,
ClientPin(client_pin::Request),
Reset,
CredentialManagement(credential_management::Request),
Selection,
Vendor(crate::operation::VendorOperation),
}
pub enum CtapMappingError {
InvalidCommand(u8),
ParsingError(cbor_smol::Error),
}
impl From<CtapMappingError> for Error {
fn from(mapping_error: CtapMappingError) -> Error {
match mapping_error {
CtapMappingError::InvalidCommand(_cmd) => Error::InvalidCommand,
CtapMappingError::ParsingError(cbor_error) => match cbor_error {
cbor_smol::Error::SerdeMissingField => Error::MissingParameter,
_ => Error::InvalidCbor,
},
}
}
}
impl Request {
#[inline(never)]
pub fn deserialize(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(
CtapMappingError::ParsingError(cbor_smol::Error::DeserializeUnexpectedEnd).into(),
);
}
let (&op, data) = data.split_first().ok_or(CtapMappingError::ParsingError(
cbor_smol::Error::DeserializeUnexpectedEnd,
))?;
let operation = Operation::try_from(op).map_err(|_| {
debug_now!("invalid operation {}", op);
CtapMappingError::InvalidCommand(op)
})?;
info!("deser {:?}", operation);
Ok(match operation {
Operation::MakeCredential => Request::MakeCredential(
cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
),
Operation::GetAssertion => Request::GetAssertion(
cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
),
Operation::GetNextAssertion => Request::GetNextAssertion,
Operation::CredentialManagement | Operation::PreviewCredentialManagement => {
Request::CredentialManagement(
cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
)
}
Operation::Reset => Request::Reset,
Operation::Selection => Request::Selection,
Operation::GetInfo => Request::GetInfo,
Operation::ClientPin => {
Request::ClientPin(cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?)
}
Operation::Vendor(vendor_operation) => Request::Vendor(vendor_operation),
Operation::BioEnrollment
| Operation::PreviewBioEnrollment
| Operation::Config
| Operation::LargeBlobs => {
debug_now!("unhandled CBOR operation {:?}", operation);
return Err(CtapMappingError::InvalidCommand(op).into());
}
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Response {
MakeCredential(make_credential::Response),
GetAssertion(get_assertion::Response),
GetNextAssertion(get_assertion::Response),
GetInfo(get_info::Response),
ClientPin(client_pin::Response),
Reset,
Selection,
CredentialManagement(credential_management::Response),
Vendor,
}
impl Response {
#[inline(never)]
pub fn serialize<const N: usize>(&self, buffer: &mut Vec<u8, N>) {
buffer.resize_default(buffer.capacity()).ok();
let (status, data) = buffer.split_first_mut().unwrap();
use cbor_smol::cbor_serialize;
use Response::*;
let outcome = match self {
GetInfo(response) => cbor_serialize(response, data),
MakeCredential(response) => cbor_serialize(response, data),
ClientPin(response) => cbor_serialize(response, data),
GetAssertion(response) | GetNextAssertion(response) => cbor_serialize(response, data),
CredentialManagement(response) => cbor_serialize(response, data),
Reset | Selection | Vendor => Ok([].as_slice()),
};
if let Ok(slice) = outcome {
*status = 0;
let l = slice.len();
buffer.resize_default(l + 1).ok();
} else {
*status = Error::Other as u8;
buffer.resize_default(1).ok();
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct AuthenticatorOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub rk: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub up: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uv: Option<bool>,
}
pub type PinAuth = Bytes<16>;
bitflags! {
pub struct AuthenticatorDataFlags: u8 {
const EMPTY = 0;
const USER_PRESENCE = 1 << 0;
const USER_VERIFIED = 1 << 2;
const ATTESTED_CREDENTIAL_DATA = 1 << 6;
const EXTENSION_DATA = 1 << 7;
}
}
pub trait SerializeAttestedCredentialData {
fn serialize(&self) -> Bytes<ATTESTED_CREDENTIAL_DATA_LENGTH>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthenticatorData<A, E> {
pub rp_id_hash: Bytes<32>,
pub flags: AuthenticatorDataFlags,
pub sign_count: u32,
pub attested_credential_data: Option<A>,
pub extensions: Option<E>,
}
pub type SerializedAuthenticatorData = Bytes<AUTHENTICATOR_DATA_LENGTH>;
impl<A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<A, E> {
#[inline(never)]
pub fn serialize(&self) -> SerializedAuthenticatorData {
let mut bytes = SerializedAuthenticatorData::new();
bytes.extend_from_slice(&self.rp_id_hash).unwrap();
bytes.push(self.flags.bits()).unwrap();
bytes
.extend_from_slice(&self.sign_count.to_be_bytes())
.unwrap();
if let Some(ref attested_credential_data) = &self.attested_credential_data {
bytes
.extend_from_slice(&attested_credential_data.serialize())
.unwrap();
}
if let Some(extensions) = self.extensions.as_ref() {
let mut extensions_buf = [0u8; 128];
let ser = crate::serde::cbor_serialize(extensions, &mut extensions_buf).unwrap();
bytes.extend_from_slice(ser).unwrap();
}
bytes
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
Success = 0x00,
InvalidCommand = 0x01,
InvalidParameter = 0x02,
InvalidLength = 0x03,
InvalidSeq = 0x04,
Timeout = 0x05,
ChannelBusy = 0x06,
LockRequired = 0x0A,
InvalidChannel = 0x0B,
CborUnexpectedType = 0x11,
InvalidCbor = 0x12,
MissingParameter = 0x14,
LimitExceeded = 0x15,
UnsupportedExtension = 0x16,
CredentialExcluded = 0x19,
Processing = 0x21,
InvalidCredential = 0x22,
UserActionPending = 0x23,
OperationPending = 0x24,
NoOperations = 0x25,
UnsupportedAlgorithm = 0x26,
OperationDenied = 0x27,
KeyStoreFull = 0x28,
NotBusy = 0x29,
NoOperationPending = 0x2A,
UnsupportedOption = 0x2B,
InvalidOption = 0x2C,
KeepaliveCancel = 0x2D,
NoCredentials = 0x2E,
UserActionTimeout = 0x2F,
NotAllowed = 0x30,
PinInvalid = 0x31,
PinBlocked = 0x32,
PinAuthInvalid = 0x33,
PinAuthBlocked = 0x34,
PinNotSet = 0x35,
PinRequired = 0x36,
PinPolicyViolation = 0x37,
PinTokenExpired = 0x38,
RequestTooLarge = 0x39,
ActionTimeout = 0x3A,
UpRequired = 0x3B,
Other = 0x7F,
SpecLast = 0xDF,
ExtensionFirst = 0xE0,
ExtensionLast = 0xEF,
VendorFirst = 0xF0,
VendorLast = 0xFF,
}
pub trait Authenticator {
fn get_info(&mut self) -> get_info::Response;
fn make_credential(
&mut self,
request: &make_credential::Request,
) -> Result<make_credential::Response>;
fn get_assertion(
&mut self,
request: &get_assertion::Request,
) -> Result<get_assertion::Response>;
fn get_next_assertion(&mut self) -> Result<get_assertion::Response>;
fn reset(&mut self) -> Result<()>;
fn client_pin(&mut self, request: &client_pin::Request) -> Result<client_pin::Response>;
fn credential_management(
&mut self,
request: &credential_management::Request,
) -> Result<credential_management::Response>;
fn selection(&mut self) -> Result<()>;
fn vendor(&mut self, op: VendorOperation) -> Result<()>;
#[inline(never)]
fn call_ctap2(&mut self, request: &Request) -> Result<Response> {
match request {
Request::GetInfo => {
debug_now!("CTAP2.GI");
Ok(Response::GetInfo(self.get_info()))
}
Request::MakeCredential(request) => {
debug_now!("CTAP2.MC");
Ok(Response::MakeCredential(
self.make_credential(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
Request::GetAssertion(request) => {
debug_now!("CTAP2.GA");
Ok(Response::GetAssertion(
self.get_assertion(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
Request::GetNextAssertion => {
debug_now!("CTAP2.GNA");
Ok(Response::GetNextAssertion(
self.get_next_assertion().map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
Request::Reset => {
debug_now!("CTAP2.RST");
self.reset().map_err(|e| {
debug!("error: {:?}", e);
e
})?;
Ok(Response::Reset)
}
Request::ClientPin(request) => {
debug_now!("CTAP2.PIN");
Ok(Response::ClientPin(self.client_pin(request).map_err(
|e| {
debug!("error: {:?}", e);
e
},
)?))
}
Request::CredentialManagement(request) => {
debug_now!("CTAP2.CM");
Ok(Response::CredentialManagement(
self.credential_management(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
Request::Selection => {
debug_now!("CTAP2.SEL");
self.selection().map_err(|e| {
debug!("error: {:?}", e);
e
})?;
Ok(Response::Selection)
}
Request::Vendor(op) => {
debug_now!("CTAP2.V");
self.vendor(*op).map_err(|e| {
debug!("error: {:?}", e);
e
})?;
Ok(Response::Vendor)
}
}
}
}
impl<A: Authenticator> crate::Rpc<Error, Request, Response> for A {
#[inline(never)]
fn call(&mut self, request: &Request) -> Result<Response> {
self.call_ctap2(request)
}
}