use crate::error::WebauthnCError;
use crate::transport::iso7816::ISO7816ResponseAPDU;
use crate::usb::framing::U2FHIDFrame;
use crate::usb::*;
#[derive(Debug, PartialEq, Eq)]
pub struct InitResponse {
pub nonce: Vec<u8>,
pub cid: u32,
protocol_version: u8,
device_version_major: u8,
device_version_minor: u8,
device_version_build: u8,
capabilities: u8,
}
impl InitResponse {
pub fn supports_ctap1(&self) -> bool {
self.capabilities & CAPABILITY_NMSG == 0
}
pub fn supports_ctap2(&self) -> bool {
self.capabilities & CAPABILITY_CBOR > 0
}
}
impl TryFrom<&[u8]> for InitResponse {
type Error = WebauthnCError;
fn try_from(d: &[u8]) -> Result<Self, Self::Error> {
if d.len() < 17 {
return Err(WebauthnCError::MessageTooShort);
}
let (nonce, d) = d.split_at(8);
let nonce = nonce.to_vec();
let (cid, d) = d.split_at(4);
let cid = u32::from_be_bytes(
cid.try_into()
.map_err(|_| WebauthnCError::MessageTooShort)?,
);
Ok(InitResponse {
nonce,
cid,
protocol_version: d[0],
device_version_major: d[1],
device_version_minor: d[2],
device_version_build: d[3],
capabilities: d[4],
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CBORResponse {
pub status: u8,
pub data: Vec<u8>,
}
impl TryFrom<&[u8]> for CBORResponse {
type Error = WebauthnCError;
fn try_from(d: &[u8]) -> Result<Self, Self::Error> {
if d.is_empty() {
return Err(WebauthnCError::MessageTooShort);
}
Ok(Self {
status: d[0],
data: d[1..].to_vec(),
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum U2FError {
None,
InvalidCommand,
InvalidParameter,
InvalidMessageLength,
InvalidMessageSequencing,
MessageTimeout,
ChannelBusy,
ChannelRequiresLock,
SyncCommandFailed,
Unspecified,
Unknown,
}
impl From<u8> for U2FError {
fn from(v: u8) -> Self {
match v {
0x00 => U2FError::None,
0x01 => U2FError::InvalidCommand,
0x02 => U2FError::InvalidParameter,
0x03 => U2FError::InvalidMessageLength,
0x04 => U2FError::InvalidMessageSequencing,
0x05 => U2FError::MessageTimeout,
0x06 => U2FError::ChannelBusy,
0x0a => U2FError::ChannelRequiresLock,
0x0b => U2FError::SyncCommandFailed,
0x7f => U2FError::Unspecified,
_ => U2FError::Unknown,
}
}
}
impl From<&[u8]> for U2FError {
fn from(d: &[u8]) -> Self {
if !d.is_empty() {
U2FError::from(d[0])
} else {
U2FError::Unknown
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Response {
Init(InitResponse),
Msg(ISO7816ResponseAPDU),
Cbor(CBORResponse),
Error(U2FError),
Unknown,
}
impl TryFrom<&U2FHIDFrame> for Response {
type Error = WebauthnCError;
fn try_from(f: &U2FHIDFrame) -> Result<Response, WebauthnCError> {
if !f.complete() {
error!("cannot parse incomplete frame");
return Err(WebauthnCError::Internal);
}
let b = &f.data[..];
Ok(match f.cmd {
U2FHID_INIT => InitResponse::try_from(b).map(Response::Init)?,
U2FHID_MSG => ISO7816ResponseAPDU::try_from(b).map(Response::Msg)?,
U2FHID_CBOR => CBORResponse::try_from(b).map(Response::Cbor)?,
U2FHID_ERROR => Response::Error(U2FError::from(b)),
_ => Response::Unknown,
})
}
}
#[allow(clippy::panic)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init() {
let c = U2FHIDFrame {
cid: 0xffffffff,
cmd: 0x86,
len: 0x08,
data: vec![0x7f, 0x4d, 0x02, 0x24, 0x8e, 0xb5, 0xcb, 0x48],
};
let expected: HidSendReportBytes = [
0x00, 0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x08, 0x7f, 0x4d, 0x02, 0x24, 0x8e, 0xb5, 0xcb, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
assert_eq!(HidSendReportBytes::from(&c), expected);
let d: HidReportBytes = [
0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, 0x7f, 0x4d, 0x02, 0x24, 0x8e, 0xb5, 0xcb, 0x48, 0x00, 0x27, 0x00, 0x01, 0x02, 0x05, 0x02, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let expected = Response::Init(InitResponse {
nonce: vec![0x7f, 0x4d, 0x02, 0x24, 0x8e, 0xb5, 0xcb, 0x48],
cid: 2555905,
protocol_version: 2,
device_version_major: 5,
device_version_minor: 2,
device_version_build: 4,
capabilities: 5,
});
let frame: U2FHIDFrame = <U2FHIDFrame>::try_from(&d).expect("init frame");
assert_eq!(frame.cid, 0xffffffff);
assert_eq!(frame.cmd, 0x86);
assert_eq!(frame.len, 0x11);
assert_eq!(frame.data.len(), 0x11);
let r: Response = Response::try_from(&frame).expect("init response");
assert_eq!(r, expected);
if let Response::Init(r) = r {
assert!(r.supports_ctap1());
assert!(r.supports_ctap2());
} else {
panic!("bad response");
}
assert_eq!(HidSendReportBytes::from(&frame)[1..], d)
}
#[test]
fn init_capabilities() {
let d: [u8; 64] = [
0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, 0xb2, 0xe7, 0xc4, 0xd6, 0x1c, 0x9e, 0x17, 0x0a, 0x0a, 0xa4, 0xcb, 0x08, 0x02, 0x05, 0x04, 0x03, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let frame: U2FHIDFrame = <U2FHIDFrame>::try_from(&d).expect("init frame");
let r: Response = Response::try_from(&frame).expect("init response");
if let Response::Init(r) = r {
assert!(!r.supports_ctap1());
assert!(r.supports_ctap2());
} else {
panic!("bad response");
}
let d: [u8; 64] = [
0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, 0x52, 0x49, 0x4a, 0x8a, 0x40, 0x46, 0xb9, 0xe4, 0xbe, 0x31, 0x2d, 0x40, 0x02, 0x01, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let frame: U2FHIDFrame = <U2FHIDFrame>::try_from(&d).expect("init frame");
let r: Response = Response::try_from(&frame).expect("init response");
if let Response::Init(r) = r {
assert!(r.supports_ctap1());
assert!(!r.supports_ctap2());
} else {
panic!("bad response");
}
}
#[test]
fn error() {
let d: [u8; 64] = [
0x6f, 0xdf, 0x43, 0x22, 0xbf, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let frame: U2FHIDFrame = <U2FHIDFrame>::try_from(&d).expect("error frame");
assert_eq!(frame.cid, 0x6fdf4322);
assert_eq!(frame.len, 1);
let r: Response = Response::try_from(&frame).expect("error response");
assert_eq!(r, Response::Error(U2FError::InvalidCommand));
}
#[test]
fn get_info() {
let _ = tracing_subscriber::fmt().try_init();
let d: [[u8; 64]; 4] = [
[
0x6f, 0x1c, 0xc9, 0xca, 0x90, 0x00, 0xc5, 0x00, 0xac, 0x01, 0x82, 0x68, 0x46, 0x49,
0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x6c, 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f,
0x31, 0x5f, 0x50, 0x52, 0x45, 0x02, 0x82, 0x6b, 0x63, 0x72, 0x65, 0x64, 0x50, 0x72,
0x6f, 0x74, 0x65, 0x63, 0x74, 0x6b, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63,
0x72, 0x65, 0x74, 0x03, 0x50, 0x14, 0x9a, 0x20,
],
[
0x6f, 0x1c, 0xc9, 0xca, 0x00, 0x21, 0x8e, 0xf6, 0x41, 0x33, 0x96, 0xb8, 0x81, 0xf8,
0xd5, 0xb7, 0xf1, 0xf5, 0x04, 0xa5, 0x62, 0x72, 0x6b, 0xf5, 0x62, 0x75, 0x70, 0xf5,
0x64, 0x70, 0x6c, 0x61, 0x74, 0xf4, 0x69, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50,
0x69, 0x6e, 0xf5, 0x75, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x4d, 0x67, 0x6d, 0x74, 0x50, 0x72, 0x65, 0x76,
],
[
0x6f, 0x1c, 0xc9, 0xca, 0x01, 0x69, 0x65, 0x77, 0xf5, 0x05, 0x19, 0x04, 0xb0, 0x06,
0x82, 0x02, 0x01, 0x07, 0x08, 0x08, 0x18, 0x80, 0x09, 0x82, 0x63, 0x6e, 0x66, 0x63,
0x63, 0x75, 0x73, 0x62, 0x0a, 0x82, 0xa2, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x64, 0x74,
0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79,
0xa2, 0x63, 0x61, 0x6c, 0x67, 0x27, 0x64, 0x74,
],
[
0x6f, 0x1c, 0xc9, 0xca, 0x02, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69,
0x63, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x04, 0x0e, 0x1a, 0x00, 0x05, 0x04, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
];
let frames: Vec<U2FHIDFrame> = d
.iter()
.map(|f| <U2FHIDFrame>::try_from(f))
.collect::<Result<Vec<U2FHIDFrame>, WebauthnCError>>()
.unwrap();
let frame: U2FHIDFrame = frames.iter().sum();
let r: Response = Response::try_from(&frame).expect("init response");
let r = if let Response::Cbor(r) = r {
r
} else {
panic!("expected Response::Cbor");
};
let a: GetInfoResponse = CBORResponse::try_from(&r.data).expect("failed to decode message");
assert!(a.versions.len() == 2);
assert!(a.versions.contains("FIDO_2_0"));
assert!(a.versions.contains("FIDO_2_1_PRE"));
assert!(a.extensions == Some(vec!["credProtect".to_string(), "hmac-secret".to_string()]));
assert!(
a.aaguid
== vec![20, 154, 32, 33, 142, 246, 65, 51, 150, 184, 129, 248, 213, 183, 241, 245]
);
let m = a.options.as_ref().unwrap();
assert!(m.len() == 5);
assert!(m.get("clientPin") == Some(&true));
assert!(m.get("credentialMgmtPreview") == Some(&true));
assert!(m.get("plat") == Some(&false));
assert!(m.get("rk") == Some(&true));
assert!(m.get("up") == Some(&true));
assert!(a.max_msg_size == Some(1200));
assert!(a.max_cred_count_in_list == Some(8));
assert!(a.max_cred_id_len == Some(128));
assert!(a.transports == Some(vec!["nfc".to_string(), "usb".to_string()]));
}
}