// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! # CTAP Protocol //! //! This crate represents CTAP messages and turns them into a binary representation to be sent to a //! remote device. use anyhow::anyhow; /// The Rust representation of CTAP values (or identifiers). #[derive(Debug, PartialEq)] pub enum Value { AuthenticatorMakeCredential = 0x01, AuthenticatorGetAssertion = 0x02, AuthenticatorGetInfo = 0x04, AuthenticatorClientPIN = 0x06, AuthenticatorReset = 0x07, AuthenticatorGetNextAssertion = 0x08, } /// The Rust representation of CTAP parameters. #[derive(Debug, PartialEq)] pub enum Parameters { AuthenticatorMakeCredential, AuthenticatorGetAssertion, AuthenticatorClientPIN, } impl From for Vec { fn from(message: Parameters) -> Vec { // TODO: serialize parameters correctly match message { Parameters::AuthenticatorMakeCredential => vec![], Parameters::AuthenticatorGetAssertion => vec![], Parameters::AuthenticatorClientPIN => vec![], } } } pub struct Command { pub value: Value, pub parameters: Option, } impl From for Vec { /// Converts the given CTAP command into its binary representation. fn from(message: Command) -> Vec { let mut result = vec![message.value as u8]; if let Some(p) = message.parameters { result.append(&mut p.into()); } result } } impl TryFrom> for Command { type Error = anyhow::Error; /// Convert a binary message to its Rust representation. fn try_from(bytes: Vec) -> anyhow::Result { if bytes.is_empty() { Err(anyhow!("Binary message was empty.")) } else { match bytes[0] { _x if _x == Value::AuthenticatorMakeCredential as u8 => Ok(Self { value: Value::AuthenticatorMakeCredential, parameters: Some(Parameters::AuthenticatorMakeCredential), }), _x if _x == Value::AuthenticatorGetAssertion as u8 => Ok(Self { value: Value::AuthenticatorGetAssertion, parameters: Some(Parameters::AuthenticatorGetAssertion), }), _x if _x == Value::AuthenticatorGetInfo as u8 => Ok(Self { value: Value::AuthenticatorGetInfo, parameters: None, }), _x if _x == Value::AuthenticatorClientPIN as u8 => Ok(Self { value: Value::AuthenticatorClientPIN, parameters: Some(Parameters::AuthenticatorClientPIN), }), _x if _x == Value::AuthenticatorReset as u8 => Ok(Self { value: Value::AuthenticatorReset, parameters: None, }), _x if _x == Value::AuthenticatorGetNextAssertion as u8 => Ok(Self { value: Value::AuthenticatorGetNextAssertion, parameters: None, }), _ => Err(anyhow!("Unknown message type.")), } } } } #[cfg(test)] mod tests { use super::*; use anyhow::bail; #[test] fn translate_message_to_bytes() { let mut message = Command { value: Value::AuthenticatorReset, parameters: None, }; let mut bytes: Vec = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorReset as u8]); message = Command { value: Value::AuthenticatorMakeCredential, parameters: None, }; bytes = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorMakeCredential as u8]); message = Command { value: Value::AuthenticatorGetAssertion, parameters: None, }; bytes = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorGetAssertion as u8]); message = Command { value: Value::AuthenticatorGetInfo, parameters: None, }; bytes = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorGetInfo as u8]); message = Command { value: Value::AuthenticatorClientPIN, parameters: None, }; bytes = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorClientPIN as u8]); message = Command { value: Value::AuthenticatorGetNextAssertion, parameters: None, }; bytes = message.into(); assert_eq!(bytes, vec![Value::AuthenticatorGetNextAssertion as u8]); } #[test] fn translate_bytes_to_message() -> anyhow::Result<()> { let mut bytes = vec![Value::AuthenticatorReset as u8]; let mut message: Command = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorReset); assert_eq!(message.parameters, None); bytes = vec![Value::AuthenticatorMakeCredential as u8]; message = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorMakeCredential); assert_eq!( message.parameters, Some(Parameters::AuthenticatorMakeCredential) ); bytes = vec![Value::AuthenticatorGetAssertion as u8]; message = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorGetAssertion); assert_eq!( message.parameters, Some(Parameters::AuthenticatorGetAssertion) ); bytes = vec![Value::AuthenticatorGetInfo as u8]; message = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorGetInfo); assert_eq!(message.parameters, None); bytes = vec![Value::AuthenticatorClientPIN as u8]; message = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorClientPIN); assert_eq!(message.parameters, Some(Parameters::AuthenticatorClientPIN)); bytes = vec![Value::AuthenticatorGetNextAssertion as u8]; message = bytes.try_into()?; assert_eq!(message.value, Value::AuthenticatorGetNextAssertion); assert_eq!(message.parameters, None); Ok(()) } #[test] fn translate_empty_bytes() -> anyhow::Result<()> { let bytes = vec![]; let res: anyhow::Result = bytes.try_into(); match res { Err(e) => { assert_eq!(e.to_string(), "Binary message was empty."); Ok(()) } _ => bail!("Should not parse empty bytes"), } } #[test] fn translate_unknown_message() -> anyhow::Result<()> { let bytes = vec![0x10]; let res: anyhow::Result = bytes.try_into(); match res { Err(e) => { assert_eq!(e.to_string(), "Unknown message type."); Ok(()) } _ => bail!("Should not parse unknown message."), } } }