blob: e5127555535afb1d8178779e29427f8fe79838ad [file] [log] [blame]
// 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<Parameters> for Vec<u8> {
fn from(message: Parameters) -> Vec<u8> {
// TODO: serialize parameters correctly
match message {
Parameters::AuthenticatorMakeCredential => vec![],
Parameters::AuthenticatorGetAssertion => vec![],
Parameters::AuthenticatorClientPIN => vec![],
}
}
}
pub struct Command {
pub value: Value,
pub parameters: Option<Parameters>,
}
impl From<Command> for Vec<u8> {
/// Converts the given CTAP command into its binary representation.
fn from(message: Command) -> Vec<u8> {
let mut result = vec![message.value as u8];
if let Some(p) = message.parameters {
result.append(&mut p.into());
}
result
}
}
impl TryFrom<Vec<u8>> for Command {
type Error = anyhow::Error;
/// Convert a binary message to its Rust representation.
fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> {
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<u8> = 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<Command> = 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<Command> = bytes.try_into();
match res {
Err(e) => {
assert_eq!(e.to_string(), "Unknown message type.");
Ok(())
}
_ => bail!("Should not parse unknown message."),
}
}
}