// -*- coding: utf-8 -*- // // pwman-f2e // // Copyright 2023 Michael Büsch // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // use anyhow::{self as ah, Context as _}; use std::collections::HashMap; use crate::hid::MAX_MSG_PAYLOAD_SIZE; // CTAP commands: const CTAP_AUTHENTICATOR_MAKE_CREDENTIAL: u8 = 0x01; const CTAP_AUTHENTICATOR_GET_ASSERTION: u8 = 0x02; const CTAP_AUTHENTICATOR_GET_INFO: u8 = 0x04; const CTAP_AUTHENTICATOR_CLIENT_PIN: u8 = 0x06; const CTAP_AUTHENTICATOR_RESET: u8 = 0x07; const CTAP_AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08; const CTAP_AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40; const CTAP_AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF; // CTAP errors: const CTAP2_OK: u8 = 0x00; const CTAP1_ERR_INVALID_COMMAND: u8 = 0x01; const CTAP2_ERR_INVALID_CBOR: u8 = 0x12; const CTAP1_ERR_OTHER: u8 = 0x7F; #[allow(clippy::large_enum_variant)] pub enum CtapCmd { MakeCredential { client_data_hash: Vec, rp: HashMap, user: HashMap, pub_key_cred_param: Vec<(String, i32)>, exclude_list: Vec<(String, String, Vec)>, extensions: HashMap, options: HashMap, pin_auth: Vec, pin_protocol: u8, }, GetAssertion, //TODO NextAssertion, GetInfo, ClientPin, //TODO Reset, ParseError(u8), } impl CtapCmd { fn parse_cbor_make_credential(params: &[u8]) -> ah::Result { let mut cmd = CtapCmd::MakeCredential { client_data_hash: vec![], //TODO rp: HashMap::new(), //TODO user: HashMap::new(), //TODO pub_key_cred_param: vec![], //TODO exclude_list: vec![], //TODO extensions: HashMap::new(), //TODO options: HashMap::new(), //TODO pin_auth: vec![], //TODO pin_protocol: 1, //TODO }; let mut decoder = minicbor::Decoder::new(params); let Some(num_main) = decoder.map() .context("Main map not found.")? else { return Err(ah::format_err!("Indefinite map not supported.")); }; for _ in 0..num_main { let param_id = decoder.u8().context("Parameter ID is not u8.")?; #[rustfmt::skip] match param_id { 0x01 => { // clientDataHash let h = decoder.array(); //TODO } 0x02 => { // rp let rp = decoder.map(); //TODO } 0x03 => { // user let user = decoder.map(); //TODO } 0x04 => { // pubKeyCredParams } 0x05 => { // excludeList } 0x06 => { // extensions } 0x07 => { // options } 0x08 => { // pinAuth } 0x09 => { // pinProtocol } param_id => { return Err(ah::format_err!("Unknown parameter ID {param_id}.")); } }; } Ok(cmd) } pub fn parse_cbor(cmd_id: u8, params: &[u8]) -> ah::Result { #[rustfmt::skip] let cmd = match cmd_id { CTAP_AUTHENTICATOR_MAKE_CREDENTIAL => { match Self::parse_cbor_make_credential(params) { Ok(cmd) => cmd, Err(e) => { println!("Failed to parse CTAP_AUTHENTICATOR_MAKE_CREDENTIAL: {}", e); CtapCmd::ParseError(CTAP2_ERR_INVALID_CBOR) } } } CTAP_AUTHENTICATOR_GET_ASSERTION => { //TODO params CtapCmd::GetAssertion } CTAP_AUTHENTICATOR_GET_INFO => { CtapCmd::GetInfo } CTAP_AUTHENTICATOR_CLIENT_PIN => { //TODO params CtapCmd::ClientPin } CTAP_AUTHENTICATOR_RESET => { CtapCmd::Reset } CTAP_AUTHENTICATOR_GET_NEXT_ASSERTION => { CtapCmd::NextAssertion } CTAP_AUTHENTICATOR_VENDOR_FIRST..=CTAP_AUTHENTICATOR_VENDOR_LAST => { println!("CTAP_AUTHENTICATOR_VENDOR commands are not supported,"); CtapCmd::ParseError(CTAP1_ERR_INVALID_COMMAND) } _ => { println!("Unsupported CTAP command: {cmd_id}"); CtapCmd::ParseError(CTAP1_ERR_INVALID_COMMAND) } }; Ok(cmd) } } #[non_exhaustive] pub struct CtapResp { pub status: u8, pub cbor: Vec, } impl CtapResp { fn new() -> Self { Self { status: CTAP2_OK, cbor: vec![], } } } pub struct Ctap {} impl Ctap { pub fn new() -> Self { Self {} } pub fn handle_command(&mut self, cmd: CtapCmd) -> ah::Result { let mut resp = CtapResp::new(); match cmd { CtapCmd::MakeCredential { client_data_hash, rp, user, pub_key_cred_param, exclude_list, extensions, options, pin_auth, pin_protocol, } => { println!("TODO: CtapCmd::MakeCredential"); //TODO resp.status = CTAP1_ERR_OTHER; } CtapCmd::GetAssertion => { println!("TODO: CtapCmd::GetAssertion"); //TODO resp.status = CTAP1_ERR_OTHER; } CtapCmd::NextAssertion => { println!("TODO: CtapCmd::NextAssertion"); //TODO resp.status = CTAP1_ERR_OTHER; } CtapCmd::GetInfo => { let mut encoder = minicbor::Encoder::new(&mut resp.cbor); #[rustfmt::skip] encoder.map(5)? .u8(0x01)?.array(1)?.str("FIDO_2_0")? // versions //TODO .u8(0x02)?.array(1)?.str("")? // extensions .u8(0x03)?.bytes(&[42; 16])? // aaguid //TODO .u8(0x04)?.map(3)? // options .str("rk")?.bool(true)? .str("up")?.bool(true)? .str("plat")?.bool(true)? .u8(0x05)?.u16(MAX_MSG_PAYLOAD_SIZE as u16)? // maxMsgSize .u8(0x06)?.array(1)?.u8(1)?; // pinProtocols } CtapCmd::ClientPin => { println!("CTAP command 'authenticatorClientPIN' not supported."); resp.status = CTAP1_ERR_OTHER; } CtapCmd::Reset => { println!("CTAP command 'authenticatorReset' not supported."); resp.status = CTAP1_ERR_OTHER; } CtapCmd::ParseError(status) => { resp.status = status; } }; Ok(resp) } } // vim: ts=4 sw=4 expandtab