#[cfg(doc)]
use crate::stubs::*;
use serde::{Deserialize, Serialize};
use serde_cbor_2::{
ser::to_vec_packed,
value::{from_value, to_value},
Value,
};
use std::fmt::Debug;
use webauthn_rs_core::proto::COSEKey;
use webauthn_rs_proto::CredentialProtectionPolicy;
use crate::crypto::{compute_sha256, SHA256Hash};
use super::*;
macro_rules! cred_struct {
(
$(#[$outer:meta])*
$vis:vis struct $name:ident = $cmd:tt
) => {
$(#[$outer])*
#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
#[serde(into = "BTreeMap<u32, Value>")]
pub struct $name {
sub_command: u8,
sub_command_params: Option<BTreeMap<Value, Value>>,
pin_uv_protocol: Option<u32>,
pin_uv_auth_param: Option<Vec<u8>>,
}
impl CBORCommand for $name {
const CMD: u8 = $cmd;
type Response = CredentialManagementResponse;
}
impl CredentialManagementRequestTrait for $name {
const ENUMERATE_RPS_GET_NEXT: Self = Self {
sub_command: 0x03,
sub_command_params: None,
pin_uv_protocol: None,
pin_uv_auth_param: None,
};
const ENUMERATE_CREDENTIALS_GET_NEXT: Self = Self {
sub_command: 0x05,
sub_command_params: None,
pin_uv_protocol: None,
pin_uv_auth_param: None,
};
fn new(
s: CredSubCommand,
pin_uv_protocol: Option<u32>,
pin_uv_auth_param: Option<Vec<u8>>,
) -> Self {
let sub_command = (&s).into();
let sub_command_params = s.into();
Self {
sub_command,
sub_command_params,
pin_uv_protocol,
pin_uv_auth_param,
}
}
}
impl From<$name> for BTreeMap<u32, Value> {
fn from(value: $name) -> Self {
let $name {
sub_command,
sub_command_params,
pin_uv_protocol,
pin_uv_auth_param,
} = value;
let mut keys = BTreeMap::new();
keys.insert(0x01, Value::Integer(sub_command.into()));
if let Some(v) = sub_command_params {
keys.insert(0x02, Value::Map(v));
}
if let Some(v) = pin_uv_protocol {
keys.insert(0x03, Value::Integer(v.into()));
}
if let Some(v) = pin_uv_auth_param {
keys.insert(0x04, Value::Bytes(v));
}
keys
}
}
};
}
pub trait CredentialManagementRequestTrait:
CBORCommand<Response = CredentialManagementResponse>
{
fn new(
s: CredSubCommand,
pin_uv_protocol: Option<u32>,
pin_uv_auth_param: Option<Vec<u8>>,
) -> Self;
const ENUMERATE_RPS_GET_NEXT: Self;
const ENUMERATE_CREDENTIALS_GET_NEXT: Self;
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum CredSubCommand {
#[default]
Unknown,
GetCredsMetadata,
EnumerateRPsBegin,
EnumerateCredentialsBegin(SHA256Hash),
DeleteCredential(PublicKeyCredentialDescriptorCM),
UpdateUserInformation(PublicKeyCredentialDescriptorCM, UserCM),
}
impl From<&CredSubCommand> for u8 {
fn from(c: &CredSubCommand) -> Self {
use CredSubCommand::*;
match c {
Unknown => 0x00,
GetCredsMetadata => 0x01,
EnumerateRPsBegin => 0x02,
EnumerateCredentialsBegin(_) => 0x04,
DeleteCredential(_) => 0x06,
UpdateUserInformation(_, _) => 0x07,
}
}
}
impl From<CredSubCommand> for Option<BTreeMap<Value, Value>> {
fn from(c: CredSubCommand) -> Self {
use CredSubCommand::*;
match c {
Unknown | GetCredsMetadata | EnumerateRPsBegin => None,
EnumerateCredentialsBegin(rp_id_hash) => Some(BTreeMap::from([(
Value::Integer(0x01),
Value::Bytes(rp_id_hash.to_vec()),
)])),
DeleteCredential(credential_id) => Some(BTreeMap::from([(
Value::Integer(0x02),
to_value(credential_id).ok()?,
)])),
UpdateUserInformation(credential_id, user) => Some(BTreeMap::from([
(Value::Integer(0x02), to_value(credential_id).ok()?),
(Value::Integer(0x03), to_value(user).ok()?),
])),
}
}
}
impl CredSubCommand {
pub fn prf(&self) -> Vec<u8> {
let subcommand = self.into();
let sub_command_params: Option<BTreeMap<Value, Value>> = self.to_owned().into();
let mut o = Vec::new();
o.push(subcommand);
if let Some(p) = sub_command_params
.as_ref()
.and_then(|p| to_vec_packed(p).ok())
{
o.extend_from_slice(p.as_slice())
}
o
}
#[inline]
pub fn enumerate_credentials_by_rpid(rp_id: &str) -> Self {
Self::EnumerateCredentialsBegin(compute_sha256(rp_id.as_bytes()))
}
}
#[derive(Debug, Default, Serialize, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RelyingPartyCM {
pub name: Option<String>,
pub id: Option<String>,
#[serde(skip)]
pub hash: Option<SHA256Hash>,
}
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UserCM {
#[serde(with = "serde_bytes")]
pub id: Vec<u8>,
pub name: Option<String>,
pub display_name: Option<String>,
}
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialDescriptorCM {
#[serde(rename = "type")]
pub type_: String,
#[serde(with = "serde_bytes")]
pub id: Vec<u8>,
}
impl From<Vec<u8>> for PublicKeyCredentialDescriptorCM {
fn from(id: Vec<u8>) -> Self {
Self {
type_: "public-key".to_string(),
id,
}
}
}
#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(try_from = "BTreeMap<u32, Value>")]
pub struct CredentialManagementResponse {
pub storage_metadata: Option<CredentialStorageMetadata>,
pub rp: Option<RelyingPartyCM>,
pub total_rps: Option<u32>,
pub discoverable_credential: DiscoverableCredential,
pub total_credentials: Option<u32>,
}
impl TryFrom<BTreeMap<u32, Value>> for CredentialManagementResponse {
type Error = &'static str;
fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
trace!(?raw);
let mut rp: Option<RelyingPartyCM> = if let Some(v) = raw.remove(&0x03) {
Some(from_value(v).map_err(|e| {
error!("parsing rp: {e:?}");
"parsing rp"
})?)
} else {
None
};
if let Some(rp_id_hash) = raw
.remove(&0x04)
.and_then(|v| value_to_vec_u8(v, "0x04"))
.and_then(|v| v.try_into().ok())
{
if let Some(rp) = &mut rp {
rp.hash = Some(rp_id_hash);
} else {
rp = Some(RelyingPartyCM {
hash: Some(rp_id_hash),
..Default::default()
});
}
}
Ok(Self {
storage_metadata: CredentialStorageMetadata::try_from(&mut raw).ok(),
rp,
total_rps: raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05")),
total_credentials: raw.remove(&0x09).and_then(|v| value_to_u32(&v, "0x09")),
discoverable_credential: DiscoverableCredential::try_from(&mut raw)?,
})
}
}
crate::deserialize_cbor!(CredentialManagementResponse);
#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
pub struct CredentialStorageMetadata {
pub existing_resident_credentials_count: u32,
pub max_possible_remaining_resident_credentials_count: u32,
}
impl TryFrom<&mut BTreeMap<u32, Value>> for CredentialStorageMetadata {
type Error = WebauthnCError;
fn try_from(raw: &mut BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
Ok(Self {
existing_resident_credentials_count: raw
.remove(&0x01)
.and_then(|v| value_to_u32(&v, "0x01"))
.ok_or(WebauthnCError::MissingRequiredField)?,
max_possible_remaining_resident_credentials_count: raw
.remove(&0x02)
.and_then(|v| value_to_u32(&v, "0x02"))
.ok_or(WebauthnCError::MissingRequiredField)?,
})
}
}
#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
pub struct DiscoverableCredential {
pub user: Option<UserCM>,
pub credential_id: Option<PublicKeyCredentialDescriptorCM>,
pub public_key: Option<COSEKey>,
pub cred_protect: Option<CredentialProtectionPolicy>,
pub large_blob_key: Option<Vec<u8>>,
}
impl TryFrom<&mut BTreeMap<u32, Value>> for DiscoverableCredential {
type Error = &'static str;
fn try_from(raw: &mut BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
Ok(Self {
user: if let Some(v) = raw.remove(&0x06) {
Some(from_value(v).map_err(|e| {
error!("parsing user: {e:?}");
"parsing user"
})?)
} else {
None
},
credential_id: if let Some(v) = raw.remove(&0x07) {
Some(from_value(v).map_err(|e| {
error!("parsing credentialID: {e:?}");
"parsing credentialID"
})?)
} else {
None
},
public_key: if let Some(v) = raw.remove(&0x08) {
Some(COSEKey::try_from(&v).map_err(|e| {
error!("parsing publicKey: {e:?}");
"parsing publicKey"
})?)
} else {
None
},
cred_protect: raw
.remove(&0x0A)
.and_then(|v| value_to_u8(&v, "0x0A"))
.and_then(|v| CredentialProtectionPolicy::try_from(v).ok()),
large_blob_key: raw.remove(&0x0B).and_then(|v| value_to_vec_u8(v, "0x0B")),
})
}
}
cred_struct! {
pub struct CredentialManagementRequest = 0x0a
}
cred_struct! {
pub struct PrototypeCredentialManagementRequest = 0x41
}
#[cfg(test)]
mod test {
use webauthn_rs_core::proto::{COSEEC2Key, COSEKeyType, ECDSACurve};
use webauthn_rs_proto::COSEAlgorithm;
use super::*;
const PIN_UV_AUTH_PARAM: [u8; 32] = [
0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c,
0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e,
0xfa, 0x5d,
];
#[test]
fn get_cred_metadata() {
let _ = tracing_subscriber::fmt::try_init();
const SUBCOMMAND: CredSubCommand = CredSubCommand::GetCredsMetadata;
assert_eq!(vec![0x01], SUBCOMMAND.prf());
let c =
CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
assert_eq!(
vec![
0x0a, 0xa3, 0x01, 0x01, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let c = PrototypeCredentialManagementRequest::new(
SUBCOMMAND,
Some(1),
Some(PIN_UV_AUTH_PARAM.to_vec()),
);
assert_eq!(
vec![
0x41, 0xa3, 0x01, 0x01, 0x03, 0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let r = [0xa2, 0x01, 0x03, 0x02, 0x16];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
storage_metadata: Some(CredentialStorageMetadata {
existing_resident_credentials_count: 3,
max_possible_remaining_resident_credentials_count: 22,
}),
..Default::default()
},
a
)
}
#[test]
fn enumerate_rps_begin() {
let _ = tracing_subscriber::fmt::try_init();
const SUBCOMMAND: CredSubCommand = CredSubCommand::EnumerateRPsBegin;
assert_eq!(vec![0x02], SUBCOMMAND.prf());
let c =
CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
assert_eq!(
vec![
0x0a, 0xa3, 0x01, 0x02, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let c = PrototypeCredentialManagementRequest::new(
SUBCOMMAND,
Some(1),
Some(PIN_UV_AUTH_PARAM.to_vec()),
);
assert_eq!(
vec![
0x41, 0xa3, 0x01, 0x02, 0x03, 0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let r = [
0xa3, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x78, 0x18, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
0x68, 0x6e, 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, 0x79, 0x65, 0x61, 0x72, 0x2e, 0x69,
0x64, 0x2e, 0x61, 0x75, 0x04, 0x58, 0x20, 0x6a, 0xb9, 0xbb, 0xf0, 0xdf, 0x9a, 0x16,
0xf9, 0x1d, 0xbb, 0x33, 0xbb, 0xb1, 0x32, 0xfa, 0xf9, 0xd1, 0x7c, 0x78, 0x2c, 0x48,
0x26, 0xc6, 0xec, 0x70, 0xec, 0xee, 0x58, 0xd9, 0x7e, 0xf5, 0x2a, 0x05, 0x02,
];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
rp: Some(RelyingPartyCM {
name: None,
id: Some("webauthn.firstyear.id.au".to_string()),
hash: Some([
0x6a, 0xb9, 0xbb, 0xf0, 0xdf, 0x9a, 0x16, 0xf9, 0x1d, 0xbb, 0x33, 0xbb,
0xb1, 0x32, 0xfa, 0xf9, 0xd1, 0x7c, 0x78, 0x2c, 0x48, 0x26, 0xc6, 0xec,
0x70, 0xec, 0xee, 0x58, 0xd9, 0x7e, 0xf5, 0x2a
]),
}),
total_rps: Some(2),
..Default::default()
},
a
);
let r = [
0xa3, 0x03, 0xa3, 0x62, 0x69, 0x64, 0x78, 0x1e, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
0x68, 0x6e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x77, 0x65,
0x62, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x64, 0x69, 0x63, 0x6f,
0x6e, 0x78, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x49, 0x63, 0x6f,
0x6e, 0x2e, 0x70, 0x6e, 0x67, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x74, 0x57, 0x65, 0x62,
0x41, 0x75, 0x74, 0x68, 0x6e, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x04, 0x58, 0x20, 0xe4, 0x53, 0x29, 0xd0, 0x3a, 0x20, 0x68, 0xd1,
0xca, 0xf7, 0xf7, 0xbb, 0x0a, 0xe9, 0x54, 0xe6, 0xb0, 0xe6, 0x25, 0x97, 0x45, 0xf3,
0x2f, 0x48, 0x29, 0xf7, 0x50, 0xf0, 0x50, 0x11, 0xf9, 0xc2, 0x05, 0x03,
];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
rp: Some(RelyingPartyCM {
name: Some("WebAuthn Test Server".to_string()),
id: Some("webauthntest.azurewebsites.net".to_string()),
hash: Some([
0xe4, 0x53, 0x29, 0xd0, 0x3a, 0x20, 0x68, 0xd1, 0xca, 0xf7, 0xf7, 0xbb,
0x0a, 0xe9, 0x54, 0xe6, 0xb0, 0xe6, 0x25, 0x97, 0x45, 0xf3, 0x2f, 0x48,
0x29, 0xf7, 0x50, 0xf0, 0x50, 0x11, 0xf9, 0xc2
]),
}),
total_rps: Some(3),
..Default::default()
},
a
);
let r = [];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
total_rps: None,
..Default::default()
},
a
);
let r = [0xa1, 0x05, 0x00];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
total_rps: Some(0),
..Default::default()
},
a
);
}
#[test]
fn enumerate_rps_next() {
let _ = tracing_subscriber::fmt::try_init();
assert_eq!(
vec![0x0a, 0xa1, 0x01, 0x03],
CredentialManagementRequest::ENUMERATE_RPS_GET_NEXT
.cbor()
.expect("encode error")
);
assert_eq!(
vec![0x41, 0xa1, 0x01, 0x03],
PrototypeCredentialManagementRequest::ENUMERATE_RPS_GET_NEXT
.cbor()
.expect("encode error")
);
let r = [
0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x78, 0x21, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
0x68, 0x6e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x73, 0x2e, 0x69, 0x6f, 0x04,
0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8,
0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2,
0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c,
];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
rp: Some(RelyingPartyCM {
name: None,
id: Some("webauthntest.identitystandards.io".to_string()),
hash: Some([
0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8,
0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32,
0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c
]),
}),
..Default::default()
},
a
);
}
#[test]
fn enumerate_credentials_begin() {
let _ = tracing_subscriber::fmt::try_init();
const SUBCOMMAND: CredSubCommand = CredSubCommand::EnumerateCredentialsBegin([
0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1,
0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c,
0xeb, 0xc4, 0xad, 0x5c,
]);
assert_eq!(
vec![
0x04, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5,
0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f,
0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c
],
SUBCOMMAND.prf()
);
assert_eq!(
CredSubCommand::enumerate_credentials_by_rpid("webauthntest.identitystandards.io"),
SUBCOMMAND
);
let c =
CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
assert_eq!(
vec![
0x0a, 0xa4, 0x01, 0x04, 0x02, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb,
0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4,
0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c, 0x03,
0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3,
0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74,
0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let c = PrototypeCredentialManagementRequest::new(
SUBCOMMAND,
Some(1),
Some(PIN_UV_AUTH_PARAM.to_vec()),
);
assert_eq!(
vec![
0x41, 0xa4, 0x01, 0x04, 0x02, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb,
0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4,
0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c, 0x03,
0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3,
0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74,
0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
],
c.cbor().expect("encode error")
);
let r = [
0xa6, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20,
0x44, 0x6f, 0x65, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba,
0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79,
0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08,
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x6f, 0xd0, 0x2c, 0xd1,
0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a, 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45,
0x4d, 0x71, 0x47, 0x7a, 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf,
0x22, 0x58, 0x20, 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9, 0x1d, 0x8d, 0xd0,
0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5, 0x09, 0x02, 0x0a, 0x01, 0x0b, 0x58, 0x20,
0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21, 0x2f, 0xf8,
0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34, 0x71, 0x76, 0x5c, 0x85,
0x3a, 0xa8, 0x0a, 0xe6,
];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
discoverable_credential: DiscoverableCredential {
user: Some(UserCM {
id: b"alice@example.com".to_vec(),
name: Some("allison@example.com".to_string()),
display_name: Some("Allison Doe".to_string()),
}),
credential_id: Some(
vec![
0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10,
0x15, 0x89, 0x76, 0xf1
]
.into()
),
public_key: Some(COSEKey {
type_: COSEAlgorithm::ES256,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x: vec![
0x6f, 0xd0, 0x2c, 0xd1, 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a,
0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45, 0x4d, 0x71, 0x47, 0x7a,
0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf
]
.into(),
y: vec![
0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9,
0x1d, 0x8d, 0xd0, 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5
]
.into()
})
}),
cred_protect: Some(CredentialProtectionPolicy::UserVerificationOptional),
large_blob_key: Some(vec![
0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21,
0x2f, 0xf8, 0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34,
0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8, 0x0a, 0xe6
]),
},
total_credentials: Some(2),
..Default::default()
},
a
);
}
#[test]
fn enumerate_credentials_next() {
let _ = tracing_subscriber::fmt::try_init();
assert_eq!(
vec![0x0a, 0xa1, 0x01, 0x05],
CredentialManagementRequest::ENUMERATE_CREDENTIALS_GET_NEXT
.cbor()
.expect("encode error")
);
assert_eq!(
vec![0x41, 0xa1, 0x01, 0x05],
PrototypeCredentialManagementRequest::ENUMERATE_CREDENTIALS_GET_NEXT
.cbor()
.expect("encode error")
);
let r = [
0xa5, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20,
0x44, 0x6f, 0x65, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba,
0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79,
0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08,
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x6f, 0xd0, 0x2c, 0xd1,
0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a, 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45,
0x4d, 0x71, 0x47, 0x7a, 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf,
0x22, 0x58, 0x20, 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9, 0x1d, 0x8d, 0xd0,
0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5, 0x0a, 0x01, 0x0b, 0x58, 0x20, 0xa3, 0x81,
0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21, 0x2f, 0xf8, 0x2a, 0x30,
0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34, 0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8,
0x0a, 0xe6,
];
let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
.expect("Failed to decode message");
assert_eq!(
CredentialManagementResponse {
discoverable_credential: DiscoverableCredential {
user: Some(UserCM {
id: b"alice@example.com".to_vec(),
name: Some("allison@example.com".to_string()),
display_name: Some("Allison Doe".to_string()),
}),
credential_id: Some(
vec![
0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10,
0x15, 0x89, 0x76, 0xf1
]
.into()
),
public_key: Some(COSEKey {
type_: COSEAlgorithm::ES256,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x: vec![
0x6f, 0xd0, 0x2c, 0xd1, 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a,
0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45, 0x4d, 0x71, 0x47, 0x7a,
0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf
]
.into(),
y: vec![
0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9,
0x1d, 0x8d, 0xd0, 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5
]
.into()
})
}),
cred_protect: Some(CredentialProtectionPolicy::UserVerificationOptional),
large_blob_key: Some(vec![
0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21,
0x2f, 0xf8, 0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34,
0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8, 0x0a, 0xe6
]),
},
..Default::default()
},
a
);
}
#[test]
fn update_user_information() {
let _ = tracing_subscriber::fmt::try_init();
let s = CredSubCommand::UpdateUserInformation(
vec![
0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89,
0x76, 0xf1,
]
.into(),
UserCM {
id: b"alice@example.com".to_vec(),
name: Some("allison@example.com".to_string()),
display_name: Some("Allison Doe".to_string()),
},
);
assert_eq!(
vec![
0x07, 0xa2, 0x02, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d,
0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79, 0x70,
0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x03, 0xa3,
0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x61,
0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61,
0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20, 0x44, 0x6f, 0x65
],
s.prf()
);
let c = CredentialManagementRequest::new(s, Some(2), Some(PIN_UV_AUTH_PARAM.into()));
assert_eq!(
vec![
0x0a, 0xa4, 0x01, 0x07, 0x02, 0xa2, 0x02, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24,
0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1,
0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b,
0x65, 0x79, 0x03, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40,
0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c,
0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e,
0x20, 0x44, 0x6f, 0x65, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d,
],
c.cbor().expect("encode error")
);
}
}