Reorganize a little

This commit is contained in:
Nicolas Stalder
2022-03-05 20:07:02 +01:00
parent b6bfca001c
commit fa55a3dc9f
19 changed files with 548 additions and 288 deletions
+17
View File
@@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.2.0] - 2022-03-05
- use 2021 edition
- make CTAP1 and CTAP2 more homogeneous
- add Authenticator traits
- lower `MAX_CREDENTIAL_ID_LENGTH` to 255 bytes, which seems to be the
limit used in practice (coming from U2F's size bytes)
- replace `MESSAGE_SIZE` with a theoretical and a realistic constant
+2 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "ctap-types"
version = "0.1.0"
version = "0.2.0"
authors = ["Nicolas Stalder <n@stalder.io>"]
edition = "2021"
@@ -14,13 +14,11 @@ delog = "0.1"
heapless = { version = "0.7", default-features = false, features = ["serde"] }
heapless-bytes = "0.3"
interchange = "0.2.1"
iso7816 = "0.1"
serde = { version = "1", default-features = false, features = ["derive"] }
serde-indexed = "0.1"
serde_repr = "0.1"
# iso7816 = { git = "https://github.com/ycrypto/iso7816" }
iso7816 = "0.1.0-alpha.1"
[features]
log-all = ["cbor-smol/log-all"]
log-none = []
+16 -108
View File
@@ -1,6 +1,10 @@
//! The FIDO CTAP Authenticator API is a completely irregular RPC protocol.
//! Anytime there is some consistency in one place, another choice is made
//! in another place. Sorry!
//! The FIDO CTAP Authenticator API in terms of RPC with our types.
use crate::ctap1;
use crate::ctap2;
pub use ctap1::Authenticator as Ctap1Authenticator;
pub use ctap2::Authenticator as Ctap2Authenticator;
// pub trait Authenticator {
// fn process(&mut self, request: &mut Request) -> Result<Response, Error>;
@@ -26,113 +30,17 @@ pub enum Response {
Ctap2(ctap2::Response),
}
pub mod ctap1 {
pub use crate::ctap1;
#[derive(Clone, Debug, PartialEq)]
pub enum Request {
Register(ctap1::Register),
Authenticate(ctap1::Register),
Version,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Response {
// Compiler not letting this enum be empty.
#[allow(non_camel_case_types)]
_unused,
}
/// Authenticator which supports both CTAP1 and CTAP2.
pub trait Authenticator: ctap1::Authenticator + ctap2::Authenticator {
// fn call(&mut self, request: &Request) -> Result<Response> {
// Ok(match request {
// Request::Ctap1(request) => Response::Ctap1(self.call_ctap1(request)?),
// Request::Ctap2(request) => Response::Ctap2(self.call_ctap2(request)?),
// })
// }
}
pub mod ctap2 {
pub use crate::ctap2::*;
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
// clippy says...large size difference
pub enum Request {
// 0x1
MakeCredential(make_credential::Parameters),
// 0x2
GetAssertion(get_assertion::Parameters),
// 0x8
GetNextAssertion,
// 0x4
GetInfo,
// 0x6
ClientPin(client_pin::Parameters),
// 0x7
Reset,
// 0xA
CredentialManagement(credential_management::Parameters),
// vendor, to be embellished
// Q: how to handle the associated CBOR structures
Vendor(crate::operation::VendorOperation),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Response {
MakeCredential(make_credential::Response),
GetAssertion(get_assertion::Response),
GetNextAssertion(get_assertion::Response),
GetInfo(get_info::Response),
ClientPin(client_pin::Response),
Reset,
CredentialManagement(credential_management::Response),
// Q: how to handle the associated CBOR structures
Vendor,
}
}
impl<A: ctap1::Authenticator + ctap2::Authenticator> Authenticator for A {}
// pub type Result<T> = core::result::Result<T, Error>;
#[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,
}
View File
View File
+1 -1
View File
@@ -1,4 +1,4 @@
//! # cosey
//! Because why wouldn't pile JOSE on top of CBOR...
//!
//! Data types and serde for public COSE_Keys
//!
+155 -89
View File
@@ -1,11 +1,98 @@
use iso7816::{Command as ApduCommand, Instruction};
//! Types for CTAP1.
//!
//! Note that all ctap1::Authenticators automatically implement RPC with [`Request`] and
//! [`Response`].
use crate::Bytes;
pub const NO_ERROR: u16 = 0x9000;
/// Re-export of the iso7816::Status.
pub use iso7816::Status as Error;
pub mod authenticate {
use super::{Bytes, ControlByte};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Request {
pub control_byte: ControlByte,
pub challenge: Bytes<32>,
pub app_id: Bytes<32>,
pub key_handle: Bytes<255>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Response {
pub user_presence: u8,
pub count: u32,
pub signature: Bytes<72>,
}
// impl AuthenticateResponse {
// pub fn new(user_presence: u8, count: u32, signature: Bytes<72>) -> Self {
// Self {
// user_presence,
// count,
// signature,
// }
// }
// }
}
pub mod register {
use super::Bytes;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Request {
pub challenge: Bytes<32>,
pub app_id: Bytes<32>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Response {
pub header_byte: u8,
pub public_key: Bytes<65>,
pub key_handle: Bytes<255>,
pub attestation_certificate: Bytes<1024>,
pub signature: Bytes<72>,
}
impl Response {
pub fn new(
header_byte: u8,
public_key: &crate::cose::EcdhEsHkdf256PublicKey,
key_handle: &[u8],
signature: Bytes<72>,
attestation_certificate: &[u8],
) -> Self {
debug_assert!(key_handle.len() <= 255);
debug_assert!(attestation_certificate.len() <= 1024);
debug_assert!(signature.len() <= 72);
let mut public_key_bytes = Bytes::new();
let mut key_handle_bytes = Bytes::new();
let mut cert_bytes = Bytes::new();
public_key_bytes.push(0x04).unwrap();
public_key_bytes.extend_from_slice(&public_key.x).unwrap();
public_key_bytes.extend_from_slice(&public_key.y).unwrap();
key_handle_bytes.extend_from_slice(key_handle).unwrap();
cert_bytes
.extend_from_slice(attestation_certificate)
.unwrap();
Self {
header_byte,
public_key: public_key_bytes,
key_handle: key_handle_bytes,
attestation_certificate: cert_bytes,
signature,
}
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ControlByte {
@@ -19,7 +106,7 @@ pub enum ControlByte {
DontEnforceUserPresenceAndSign = 0x08,
}
impl core::convert::TryFrom<u8> for ControlByte {
impl TryFrom<u8> for ControlByte {
type Error = Error;
fn try_from(byte: u8) -> Result<ControlByte> {
@@ -34,100 +121,37 @@ impl core::convert::TryFrom<u8> for ControlByte {
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Register {
pub challenge: Bytes<32>,
pub app_id: Bytes<32>,
}
/// Type alias for convenience.
pub type Register = register::Request;
/// Type alias for convenience.
pub type Authenticate = authenticate::Request;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RegisterResponse {
pub header_byte: u8,
pub public_key: Bytes<65>,
pub key_handle: Bytes<255>,
pub attestation_certificate: Bytes<1024>,
pub signature: Bytes<72>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Authenticate {
pub control_byte: ControlByte,
pub challenge: Bytes<32>,
pub app_id: Bytes<32>,
pub key_handle: Bytes<255>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthenticateResponse {
user_presence: u8,
count: u32,
signature: Bytes<72>,
}
/// Type alias for convenience.
pub type RegisterResponse = register::Response;
/// Type alias for convenience.
pub type AuthenticateResponse = authenticate::Response;
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Command {
Register(Register),
Authenticate(Authenticate),
/// Enum of all CTAP1 requests.
pub enum Request {
Register(register::Request),
Authenticate(authenticate::Request),
Version,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
/// Enum of all CTAP1 responses.
pub enum Response {
Register(RegisterResponse),
Authenticate(AuthenticateResponse),
Register(register::Response),
Authenticate(authenticate::Response),
Version([u8; 6]),
}
impl RegisterResponse {
pub fn new(
header_byte: u8,
public_key: &crate::cose::EcdhEsHkdf256PublicKey,
key_handle: &[u8],
signature: Bytes<72>,
attestation_certificate: &[u8],
) -> Self {
debug_assert!(key_handle.len() <= 255);
debug_assert!(attestation_certificate.len() <= 1024);
debug_assert!(signature.len() <= 72);
let mut public_key_bytes = Bytes::new();
let mut key_handle_bytes = Bytes::new();
let mut cert_bytes = Bytes::new();
public_key_bytes.push(0x04).unwrap();
public_key_bytes.extend_from_slice(&public_key.x).unwrap();
public_key_bytes.extend_from_slice(&public_key.y).unwrap();
key_handle_bytes.extend_from_slice(key_handle).unwrap();
cert_bytes
.extend_from_slice(attestation_certificate)
.unwrap();
Self {
header_byte,
public_key: public_key_bytes,
key_handle: key_handle_bytes,
attestation_certificate: cert_bytes,
signature,
}
}
}
impl AuthenticateResponse {
pub fn new(user_presence: u8, count: u32, signature: Bytes<72>) -> Self {
Self {
user_presence,
count,
signature,
}
}
}
impl Response {
#[allow(clippy::result_unit_err)]
#[inline(never)]
pub fn serialize<const S: usize>(
&self,
buf: &mut iso7816::Data<S>,
@@ -150,12 +174,14 @@ impl Response {
}
}
}
impl<const S: usize> core::convert::TryFrom<&ApduCommand<S>> for Command {
impl<const S: usize> TryFrom<&iso7816::Command<S>> for Request {
type Error = Error;
fn try_from(apdu: &ApduCommand<S>) -> Result<Command> {
#[inline(never)]
fn try_from(apdu: &iso7816::Command<S>) -> Result<Request> {
let cla = apdu.class().into_inner();
let ins = match apdu.instruction() {
Instruction::Unknown(ins) => ins,
iso7816::Instruction::Unknown(ins) => ins,
_ins => 0,
};
let p1 = apdu.p1;
@@ -168,7 +194,7 @@ impl<const S: usize> core::convert::TryFrom<&ApduCommand<S>> for Command {
if ins == 0x3 {
// for some weird historical reason, [0, 3, 0, 0, 0, 0, 0, 0, 0]
// is valid to send here.
return Ok(Command::Version);
return Ok(Request::Version);
};
let request = apdu.data();
@@ -179,7 +205,7 @@ impl<const S: usize> core::convert::TryFrom<&ApduCommand<S>> for Command {
if request.len() != 64 {
return Err(Error::IncorrectDataParameter);
}
Ok(Command::Register(Register {
Ok(Request::Register(Register {
challenge: Bytes::from_slice(&request[..32]).unwrap(),
app_id: Bytes::from_slice(&request[32..]).unwrap(),
}))
@@ -195,7 +221,7 @@ impl<const S: usize> core::convert::TryFrom<&ApduCommand<S>> for Command {
if request.len() != 65 + key_handle_length {
return Err(Error::IncorrectDataParameter);
}
Ok(Command::Authenticate(Authenticate {
Ok(Request::Authenticate(Authenticate {
control_byte,
challenge: Bytes::from_slice(&request[..32]).unwrap(),
app_id: Bytes::from_slice(&request[32..64]).unwrap(),
@@ -204,9 +230,49 @@ impl<const S: usize> core::convert::TryFrom<&ApduCommand<S>> for Command {
}
// version
0x3 => Ok(Command::Version),
0x3 => Ok(Request::Version),
_ => Err(Error::InstructionNotSupportedOrInvalid),
}
}
}
/// CTAP1 (U2F) authenticator API
///
/// Note that all Authenticators automatically implement RPC with [`Request`] and
/// [`Response`].
pub trait Authenticator {
/// Register a U2F credential.
fn register(&mut self, request: &register::Request) -> Result<register::Response>;
/// Authenticate with a U2F credential.
fn authenticate(
&mut self,
request: &authenticate::Request,
) -> Result<authenticate::Response>;
/// Supported U2F version.
fn version() -> [u8; 6] {
*b"U2F_V2"
}
#[inline(never)]
fn call_ctap1(&mut self, request: &Request) -> Result<Response> {
match request {
Request::Register(reg) => {
debug_now!("CTAP1.REG");
Ok(Response::Register(self.register(reg)?))
}
Request::Authenticate(auth) => {
debug_now!("CTAP1.AUTH");
Ok(Response::Authenticate(self.authenticate(auth)?))
}
Request::Version => Ok(Response::Version(Self::version())),
}
}
}
impl<A: Authenticator> crate::Rpc<Error, Request, Response> for A {
/// Dispatches the enum of possible requests into the appropriate trait method.
fn call(&mut self, request: &Request) -> Result<Response> {
self.call_ctap1(request)
}
}
+326 -3
View File
@@ -1,16 +1,167 @@
//! Types for CTAP2.
//!
//! Note that all ctap2::Authenticators automatically implement RPC with [`Request`] and
//! [`Response`].
use bitflags::bitflags;
use cbor_smol::cbor_deserialize;
use serde::{Deserialize, Serialize};
use crate::sizes::*;
use crate::Bytes;
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 get_next_assertion;
pub mod make_credential;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
// clippy says...large size difference
/// Enum of all CTAP2 requests.
pub enum Request {
// 0x1
MakeCredential(make_credential::Request),
// 0x2
GetAssertion(get_assertion::Request),
// 0x8
GetNextAssertion,
// 0x4
GetInfo,
// 0x6
ClientPin(client_pin::Request),
// 0x7
Reset,
// 0xA
CredentialManagement(credential_management::Request),
// vendor, to be embellished
// Q: how to handle the associated CBOR structures
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 {
/// Deserialize from CBOR where the first byte denotes the operation.
#[inline(never)]
pub fn deserialize(data: &[u8]) -> Result<Self> {
if data.len() < 1 {
return Err(CtapMappingError::ParsingError(cbor_smol::Error::DeserializeUnexpectedEnd))?;
}
let (&op, data) = data.split_first()
.ok_or_else(|| 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::GetInfo => Request::GetInfo,
Operation::ClientPin =>
Request::ClientPin(cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?),
// NB: FIDO Alliance "stole" 0x40 and 0x41, so these are not available
Operation::Vendor(vendor_operation) => Request::Vendor(vendor_operation),
Operation::BioEnrollment |
Operation::PreviewBioEnrollment |
Operation::Config |
Operation::LargeBlobs |
Operation::Selection => {
debug_now!("unhandled CBOR operation {:?}", operation);
return Err(CtapMappingError::InvalidCommand(op))?;
}
})
}
}
#[derive(Clone, Debug, PartialEq)]
/// Enum of all CTAP2 responses.
pub enum Response {
MakeCredential(make_credential::Response),
GetAssertion(get_assertion::Response),
GetNextAssertion(get_assertion::Response),
GetInfo(get_info::Response),
ClientPin(client_pin::Response),
Reset,
CredentialManagement(credential_management::Response),
// Q: how to handle the associated CBOR structures
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 Response::*;
use cbor_smol::cbor_serialize;
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 | 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();
}
}
}
// TODO: this is a bit weird to model...
// Need to be able to "skip unknown keys" in deserialization
//
@@ -148,6 +299,7 @@ pub type SerializedAuthenticatorData = Bytes<AUTHENTICATOR_DATA_LENGTH>;
// The reason for this non-use of CBOR is for compatibility with
// FIDO U2F authentication signatures.
impl<A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<A, E> {
#[inline(never)]
pub fn serialize(&self) -> SerializedAuthenticatorData {
// let mut bytes = Vec::<u8, AUTHENTICATOR_DATA_LENGTH>::new();
let mut bytes = SerializedAuthenticatorData::new();
@@ -210,3 +362,174 @@ impl<A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<
// ES256,
// EdDSA,
// }
#[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,
}
/// CTAP2 authenticator API
///
/// Note that all Authenticators automatically implement [`crate::Rpc`] with [`Request`] and
/// [`Response`].
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 vendor(&mut self, op: VendorOperation) -> Result<()>;
/// Dispatches the enum of possible requests into the appropriate trait method.
#[inline(never)]
fn call_ctap2(&mut self, request: &Request) -> Result<Response> {
match request {
// 0x4
Request::GetInfo => {
debug_now!("CTAP2.GI");
Ok(Response::GetInfo(self.get_info()))
}
// 0x2
Request::MakeCredential(request) => {
debug_now!("CTAP2.MC");
Ok(Response::MakeCredential(
self.make_credential(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
// 0x1
Request::GetAssertion(request) => {
debug_now!("CTAP2.GA");
Ok(Response::GetAssertion(
self.get_assertion(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
// 0x8
Request::GetNextAssertion => {
debug_now!("CTAP2.GNA");
Ok(Response::GetNextAssertion(
self.get_next_assertion().map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
// 0x7
Request::Reset => {
debug_now!("CTAP2.RST");
self.reset().map_err(|e| {
debug!("error: {:?}", e);
e
})?;
Ok(Response::Reset)
}
// 0x6
Request::ClientPin(request) => {
debug_now!("CTAP2.PIN");
Ok(Response::ClientPin(self.client_pin(request).map_err(
|e| {
debug!("error: {:?}", e);
e
},
)?))
}
// 0xA
Request::CredentialManagement(request) => {
debug_now!("CTAP2.CM");
Ok(Response::CredentialManagement(
self.credential_management(request).map_err(|e| {
debug!("error: {:?}", e);
e
})?,
))
}
// Not stable
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 {
/// Dispatches the enum of possible requests into the appropriate trait method.
#[inline(never)]
fn call(&mut self, request: &Request) -> Result<Response> {
self.call_ctap2(request)
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ pub enum PinV1Subcommand {
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
#[serde_indexed(offset = 1)]
pub struct Parameters {
pub struct Request {
// 0x01
// PIN protocol version chosen by the client.
// For this version of the spec, this SHALL be the number 1.
+5 -2
View File
@@ -1,14 +1,17 @@
use crate::{Bytes16, Bytes32};
use serde_indexed::{DeserializeIndexed, SerializeIndexed};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{
Bytes,
cose::PublicKey,
webauthn::{
PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
},
};
type Bytes16 = Bytes<16>;
type Bytes32 = Bytes<32>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
// #[derive(Clone,Debug,Eq,PartialEq,Serialize, Deserialize)]
// #[serde(tag = "credProtect")]
@@ -52,7 +55,7 @@ pub struct SubcommandParameters {
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
#[serde_indexed(offset = 1)]
pub struct Parameters {
pub struct Request {
// 0x01
pub sub_command: Subcommand,
// 0x02
+1 -1
View File
@@ -55,7 +55,7 @@ pub type AllowList = Vec<PublicKeyCredentialDescriptor, MAX_CREDENTIAL_COUNT_IN_
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
// #[serde(rename_all = "camelCase")]
#[serde_indexed(offset = 1)]
pub struct Parameters {
pub struct Request {
pub rp_id: String<64>,
pub client_data_hash: Bytes<32>,
#[serde(skip_serializing_if = "Option::is_none")]
-1
View File
@@ -1 +0,0 @@
pub use super::get_assertion::Response;
+4 -4
View File
@@ -11,7 +11,7 @@ use crate::webauthn::*;
// // Approach 1:
// pub type AuthenticatorExtensions = heapless::LinearMap<String<11>, bool, 2>;
// impl core::convert::TryFrom<&String<44>> for CredentialProtectionPolicy {
// impl TryFrom<&String<44>> for CredentialProtectionPolicy {
// type Error = crate::authenticator::Error;
// fn try_from(value: &String<44>) -> Result<Self, Self::Error> {
@@ -24,8 +24,8 @@ use crate::webauthn::*;
// }
// }
impl core::convert::TryFrom<u8> for CredentialProtectionPolicy {
type Error = crate::authenticator::Error;
impl TryFrom<u8> for CredentialProtectionPolicy {
type Error = super::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
@@ -69,7 +69,7 @@ pub struct Extensions {
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
// #[serde(rename_all = "camelCase")]
#[serde_indexed(offset = 1)]
pub struct Parameters {
pub struct Request {
pub client_data_hash: Bytes<32>,
pub rp: PublicKeyCredentialRpEntity,
pub user: PublicKeyCredentialUserEntity,
+10 -6
View File
@@ -19,22 +19,26 @@
extern crate delog;
generate_macros!();
pub use heapless::spsc::{Consumer, Producer, Queue};
pub use heapless;
pub use heapless_bytes;
pub use heapless::{String, Vec};
pub use heapless_bytes::Bytes;
pub type Bytes16 = Bytes<16>;
pub type Bytes32 = Bytes<32>;
pub mod authenticator;
pub mod cose;
pub mod ctap1;
pub mod ctap2;
pub mod operation;
pub mod rpc;
// pub mod serde;
pub(crate) mod operation;
pub use cbor_smol as serde;
pub mod sizes;
pub mod webauthn;
pub use ctap2::{Error, Result};
#[cfg(test)]
mod tests {}
/// Call a remote procedure with a request, receive a response, maybe.
pub trait Rpc<Error, Request, Response> {
fn call(&mut self, request: &Request) -> core::result::Result<Response, Error>;
}
-2
View File
@@ -1,5 +1,3 @@
use core::convert::TryFrom;
/// the authenticator API, consisting of "operations"
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Operation {
-10
View File
@@ -1,10 +0,0 @@
#![allow(clippy::declare_interior_mutable_const)]
use crate::authenticator::{Error, Request, Response};
// PRIOR ART:
// https://xenomai.org/documentation/xenomai-2.4/html/api/group__native__queue.html
// https://doc.micrium.com/display/osiiidoc/Using+Message+Queues
interchange::interchange! {
CtapInterchange: (Request, Result<Response, Error>)
}
-53
View File
@@ -1,53 +0,0 @@
pub mod de;
pub mod ser;
pub mod error;
pub use error::{Error, Result};
// pub use de::from_bytes;
// pub use de::take_from_bytes;
// kudos to postcard, this is much nicer than returning size
pub fn cbor_serialize<'a, 'b, T: serde::Serialize>(
object: &'a T,
buffer: &'b mut [u8],
) -> Result<&'b [u8]> {
let writer = ser::SliceWriter::new(buffer);
let mut ser = ser::Serializer::new(writer);
object.serialize(&mut ser)?;
let writer = ser.into_inner();
let size = writer.bytes_written();
Ok(&buffer[..size])
}
pub fn cbor_serialize_bytes<'a, 'b, N: heapless::ArrayLength<u8>, T: serde::Serialize>(
object: &'a T,
bytes: &'b mut heapless_bytes::Bytes<N>,
) -> Result<usize> {
let len_before = bytes.len();
let mut ser = ser::Serializer::new(bytes);
object.serialize(&mut ser)?;
Ok(ser.into_inner().len() - len_before)
}
pub fn cbor_serialize_bytes<N: heapless::ArrayLength<u8>, T: serde::Serialize>(object: &T) -> Result<heapless_bytes::Bytes<N>> {
let mut data = heapless_bytes::Bytes::<N>::new();
cbor_serialize_bytes(object, &mut data)?;
Ok(data)
}
pub fn cbor_deserialize<'de, T: serde::Deserialize<'de>>(
buffer: &'de [u8],
) -> Result<T> {
// cortex_m_semihosting::hprintln!("deserializing {:?}", buffer).ok();
de::from_bytes(buffer)
}
+8 -3
View File
@@ -11,11 +11,16 @@ pub const ASN1_SIGNATURE_LENGTH: usize = 77;
pub const COSE_KEY_LENGTH: usize = 256;
// pub const COSE_KEY_LENGTH_BYTES: usize = 256;
pub const MAX_CREDENTIAL_ID_LENGTH: usize = 512;
pub const MAX_CREDENTIAL_ID_LENGTH_PLUS_256: usize = 768;
pub const MAX_CREDENTIAL_ID_LENGTH: usize = 255;
pub const MAX_CREDENTIAL_ID_LENGTH_PLUS_256: usize = 767;
pub const MAX_CREDENTIAL_COUNT_IN_LIST: usize = 10;
pub const PACKET_SIZE: usize = 64;
// 7609 bytes
pub const MESSAGE_SIZE: usize = PACKET_SIZE - 7 + 128 * (PACKET_SIZE - 5);
/// The theoretical maximal message size, which however is far
/// too large for most platforms.
pub const THEORETICAL_MAX_MESSAGE_SIZE: usize = PACKET_SIZE - 7 + 128 * (PACKET_SIZE - 5);
/// The size used by Yubico, which means that no platforms will
/// realistically expect a larger size.
pub const REALISTIC_MAX_MESSAGE_SIZE: usize = 1200;
+2
View File
@@ -1,3 +1,5 @@
//! Subset of WebAuthn types that crept into CTAP.
use crate::sizes::*;
use crate::{Bytes, String};
use serde::{Deserialize, Serialize};