use crate::error::{Error, Result};
use soft_fido2_ctap::cbor::{MapParser, Value};
use soft_fido2_ctap::types::{PublicKeyCredentialDescriptor, User};
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CredentialsMetadata {
pub existing_resident_credentials_count: u32,
pub max_possible_remaining_resident_credentials_count: u32,
}
impl CredentialsMetadata {
pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
let parser = MapParser::from_bytes(bytes).map_err(|_| Error::Other)?;
let existing: i32 = parser.get(0x01).map_err(|_| Error::Other)?;
let remaining: i32 = parser.get(0x02).map_err(|_| Error::Other)?;
Ok(Self {
existing_resident_credentials_count: existing as u32,
max_possible_remaining_resident_credentials_count: remaining as u32,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RpInfo {
pub id: String,
pub name: Option<String>,
pub rp_id_hash: [u8; 32],
}
impl RpInfo {
pub fn from_cbor_value(rp_value: &Value, rp_id_hash: [u8; 32]) -> Result<Self> {
let Value::Map(map) = rp_value else {
return Err(Error::Other);
};
let mut id = None;
let mut name = None;
for (k, v) in map {
if let Value::Text(key) = k {
match key.as_str() {
"id" => {
if let Value::Text(val) = v {
id = Some(val.clone());
}
}
"name" => {
if let Value::Text(val) = v {
name = Some(val.clone());
}
}
_ => {} }
}
}
let id = id.ok_or(Error::Other)?;
Ok(Self {
id,
name,
rp_id_hash,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RpEnumerationBeginResponse {
pub rp: RpInfo,
pub total_rps: u32,
}
impl RpEnumerationBeginResponse {
pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
let parser = MapParser::from_bytes(bytes).map_err(|_| Error::Other)?;
let rp_value: Value = parser.get(0x03).map_err(|_| Error::Other)?;
let rp_id_hash_vec: Vec<u8> = parser.get_bytes(0x04).map_err(|_| Error::Other)?;
let total: i32 = parser.get(0x05).map_err(|_| Error::Other)?;
let mut rp_id_hash = [0u8; 32];
if rp_id_hash_vec.len() != 32 {
return Err(Error::Other);
}
rp_id_hash.copy_from_slice(&rp_id_hash_vec);
let rp = RpInfo::from_cbor_value(&rp_value, rp_id_hash)?;
Ok(Self {
rp,
total_rps: total as u32,
})
}
}
pub type RpEnumerationNextResponse = RpInfo;
impl RpEnumerationNextResponse {
pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
let parser = MapParser::from_bytes(bytes).map_err(|_| Error::Other)?;
let rp_value: Value = parser.get(0x03).map_err(|_| Error::Other)?;
let rp_id_hash_vec: Vec<u8> = parser.get_bytes(0x04).map_err(|_| Error::Other)?;
let mut rp_id_hash = [0u8; 32];
if rp_id_hash_vec.len() != 32 {
return Err(Error::Other);
}
rp_id_hash.copy_from_slice(&rp_id_hash_vec);
RpInfo::from_cbor_value(&rp_value, rp_id_hash)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CredentialInfo {
pub user: User,
pub credential_id: PublicKeyCredentialDescriptor,
pub public_key: Option<Vec<u8>>,
pub cred_protect: Option<u8>,
pub large_blob_key: Option<Vec<u8>>,
pub third_party_payment: Option<bool>,
}
impl CredentialInfo {
fn from_parser(parser: &MapParser) -> Result<Self> {
let user: User = parser.get(0x06).map_err(|_| Error::Other)?;
let cred_id_value: Value = parser.get(0x07).map_err(|_| Error::Other)?;
let credential_id = parse_credential_descriptor(&cred_id_value)?;
let public_key = parser.get_opt::<Vec<u8>>(0x08).ok().flatten();
let cred_protect = parser.get_opt::<u8>(0x0A).ok().flatten();
let large_blob_key = if parser.get_raw(0x0B).is_some() {
parser.get_bytes(0x0B).ok()
} else {
None
};
let third_party_payment = parser.get_opt::<bool>(0x0C).ok().flatten();
Ok(Self {
user,
credential_id,
public_key,
cred_protect,
large_blob_key,
third_party_payment,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CredentialEnumerationBeginResponse {
pub credential: CredentialInfo,
pub total_credentials: u32,
}
impl CredentialEnumerationBeginResponse {
pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
let parser = MapParser::from_bytes(bytes).map_err(|_| Error::Other)?;
let credential = CredentialInfo::from_parser(&parser)?;
let total: i32 = parser.get(0x09).map_err(|_| Error::Other)?;
Ok(Self {
credential,
total_credentials: total as u32,
})
}
}
pub type CredentialEnumerationNextResponse = CredentialInfo;
impl CredentialEnumerationNextResponse {
pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
let parser = MapParser::from_bytes(bytes).map_err(|_| Error::Other)?;
CredentialInfo::from_parser(&parser)
}
}
fn parse_credential_descriptor(value: &Value) -> Result<PublicKeyCredentialDescriptor> {
let Value::Map(map) = value else {
return Err(Error::Other);
};
let mut id = None;
let mut cred_type = None;
for (k, v) in map {
if let Value::Text(key) = k {
match key.as_str() {
"id" => {
if let Value::Bytes(bytes) = v {
id = Some(bytes.clone());
}
}
"type" => {
if let Value::Text(t) = v {
cred_type = Some(t.clone());
}
}
_ => {}
}
}
}
let id = id.ok_or(Error::Other)?;
let r#type = cred_type.ok_or(Error::Other)?;
Ok(PublicKeyCredentialDescriptor {
id,
r#type,
transports: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_credentials_metadata_parsing() {
let cbor = vec![0xa2, 0x01, 0x0a, 0x02, 0x18, 0x32];
let metadata = CredentialsMetadata::from_cbor(&cbor).unwrap();
assert_eq!(metadata.existing_resident_credentials_count, 10);
assert_eq!(
metadata.max_possible_remaining_resident_credentials_count,
50
);
}
#[test]
fn test_rp_info_parsing_with_name() {
let rp_value = Value::Map(vec![
(
Value::Text("id".to_string()),
Value::Text("example.com".to_string()),
),
(
Value::Text("name".to_string()),
Value::Text("Example".to_string()),
),
]);
let hash = [1u8; 32];
let rp = RpInfo::from_cbor_value(&rp_value, hash).unwrap();
assert_eq!(rp.id, "example.com");
assert_eq!(rp.name, Some("Example".to_string()));
assert_eq!(rp.rp_id_hash, hash);
}
#[test]
fn test_rp_info_parsing_without_name() {
let rp_value = Value::Map(vec![(
Value::Text("id".to_string()),
Value::Text("example.com".to_string()),
)]);
let hash = [2u8; 32];
let rp = RpInfo::from_cbor_value(&rp_value, hash).unwrap();
assert_eq!(rp.id, "example.com");
assert_eq!(rp.name, None);
}
#[test]
fn test_parse_credential_descriptor() {
let desc_value = Value::Map(vec![
(Value::Text("id".to_string()), Value::Bytes(vec![1, 2, 3])),
(
Value::Text("type".to_string()),
Value::Text("public-key".to_string()),
),
]);
let desc = parse_credential_descriptor(&desc_value).unwrap();
assert_eq!(desc.id, vec![1, 2, 3]);
assert_eq!(desc.r#type, "public-key");
}
}