use super::{Command, CommandError, CtapResponse, PinUvAuthCommand, RequestCtap2, StatusCode};
use crate::{
crypto::{COSEKey, PinUvAuthParam, PinUvAuthToken},
ctap2::server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash,
UserVerificationRequirement,
},
errors::AuthenticatorError,
transport::errors::HIDError,
FidoDevice,
};
use serde::{
de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use serde_bytes::ByteBuf;
use serde_cbor::{de::from_slice, to_vec, Value};
use std::fmt;
#[derive(Debug, Clone, Deserialize, Default)]
struct CredManagementParams {
rp_id_hash: Option<RpIdHash>, credential_id: Option<PublicKeyCredentialDescriptor>, user: Option<PublicKeyCredentialUserEntity>, }
impl CredManagementParams {
fn has_some(&self) -> bool {
self.rp_id_hash.is_some() || self.credential_id.is_some() || self.user.is_some()
}
}
impl Serialize for CredManagementParams {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_map_optional!(
serializer,
&0x01 => self.rp_id_hash.as_ref().map(|r| ByteBuf::from(r.as_ref())),
&0x02 => &self.credential_id,
&0x03 => &self.user,
)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) enum CredManagementCommand {
GetCredsMetadata,
EnumerateRPsBegin,
EnumerateRPsGetNextRP,
EnumerateCredentialsBegin(RpIdHash),
EnumerateCredentialsGetNextCredential,
DeleteCredential(PublicKeyCredentialDescriptor),
UpdateUserInformation((PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity)),
}
impl CredManagementCommand {
fn to_id_and_param(&self) -> (u8, CredManagementParams) {
let mut params = CredManagementParams::default();
match &self {
CredManagementCommand::GetCredsMetadata => (0x01, params),
CredManagementCommand::EnumerateRPsBegin => (0x02, params),
CredManagementCommand::EnumerateRPsGetNextRP => (0x03, params),
CredManagementCommand::EnumerateCredentialsBegin(rp_id_hash) => {
params.rp_id_hash = Some(rp_id_hash.clone());
(0x04, params)
}
CredManagementCommand::EnumerateCredentialsGetNextCredential => (0x05, params),
CredManagementCommand::DeleteCredential(cred_id) => {
params.credential_id = Some(cred_id.clone());
(0x06, params)
}
CredManagementCommand::UpdateUserInformation((cred_id, user)) => {
params.credential_id = Some(cred_id.clone());
params.user = Some(user.clone());
(0x07, params)
}
}
}
}
#[derive(Debug)]
pub struct CredentialManagement {
pub(crate) subcommand: CredManagementCommand, pin_uv_auth_param: Option<PinUvAuthParam>, use_legacy_preview: bool,
}
impl CredentialManagement {
pub(crate) fn new(subcommand: CredManagementCommand, use_legacy_preview: bool) -> Self {
Self {
subcommand,
pin_uv_auth_param: None,
use_legacy_preview,
}
}
}
impl Serialize for CredentialManagement {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (id, params) = self.subcommand.to_id_and_param();
serialize_map_optional!(
serializer,
&0x01 => Some(&id),
&0x02 => params.has_some().then_some(¶ms),
&0x03 => self.pin_uv_auth_param.as_ref().map(|p| p.pin_protocol.id()),
&0x04 => &self.pin_uv_auth_param,
)
}
}
#[derive(Debug, Default)]
pub struct CredentialManagementResponse {
pub existing_resident_credentials_count: Option<u64>,
pub max_possible_remaining_resident_credentials_count: Option<u64>,
pub rp: Option<RelyingParty>,
pub rp_id_hash: Option<RpIdHash>,
pub total_rps: Option<u64>,
pub user: Option<PublicKeyCredentialUserEntity>,
pub credential_id: Option<PublicKeyCredentialDescriptor>,
pub public_key: Option<COSEKey>,
pub total_credentials: Option<u64>,
pub cred_protect: Option<u64>,
pub large_blob_key: Option<Vec<u8>>,
}
impl CtapResponse for CredentialManagementResponse {}
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct CredentialRpListEntry {
pub rp: RelyingParty,
pub rp_id_hash: RpIdHash,
pub credentials: Vec<CredentialListEntry>,
}
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct CredentialListEntry {
pub user: PublicKeyCredentialUserEntity,
pub credential_id: PublicKeyCredentialDescriptor,
pub public_key: COSEKey,
pub cred_protect: u64,
pub large_blob_key: Option<Vec<u8>>,
}
#[derive(Debug, Serialize)]
pub enum CredentialManagementResult {
CredentialList(CredentialList),
DeleteSucess,
UpdateSuccess,
}
#[derive(Debug, Default, Serialize)]
pub struct CredentialList {
pub existing_resident_credentials_count: u64,
pub max_possible_remaining_resident_credentials_count: u64,
pub credential_list: Vec<CredentialRpListEntry>,
}
impl CredentialList {
pub fn new() -> Self {
Default::default()
}
}
impl<'de> Deserialize<'de> for CredentialManagementResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CredentialManagementResponseVisitor;
impl<'de> Visitor<'de> for CredentialManagementResponseVisitor {
type Value = CredentialManagementResponse;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut existing_resident_credentials_count = None; let mut max_possible_remaining_resident_credentials_count = None; let mut rp = None; let mut rp_id_hash = None; let mut total_rps = None; let mut user = None; let mut credential_id = None; let mut public_key = None; let mut total_credentials = None; let mut cred_protect = None; let mut large_blob_key = None;
while let Some(key) = map.next_key()? {
match key {
0x01 => {
if existing_resident_credentials_count.is_some() {
return Err(SerdeError::duplicate_field(
"existing_resident_credentials_count",
));
}
existing_resident_credentials_count = Some(map.next_value()?);
}
0x02 => {
if max_possible_remaining_resident_credentials_count.is_some() {
return Err(SerdeError::duplicate_field(
"max_possible_remaining_resident_credentials_count",
));
}
max_possible_remaining_resident_credentials_count =
Some(map.next_value()?);
}
0x03 => {
if rp.is_some() {
return Err(SerdeError::duplicate_field("rp"));
}
rp = Some(map.next_value()?);
}
0x04 => {
if rp_id_hash.is_some() {
return Err(SerdeError::duplicate_field("rp_id_hash"));
}
let rp_raw = map.next_value::<ByteBuf>()?;
rp_id_hash =
Some(RpIdHash::from(rp_raw.as_slice()).map_err(|_| {
SerdeError::invalid_length(rp_raw.len(), &"32")
})?);
}
0x05 => {
if total_rps.is_some() {
return Err(SerdeError::duplicate_field("total_rps"));
}
total_rps = Some(map.next_value()?);
}
0x06 => {
if user.is_some() {
return Err(SerdeError::duplicate_field("user"));
}
user = Some(map.next_value()?);
}
0x07 => {
if credential_id.is_some() {
return Err(SerdeError::duplicate_field("credential_id"));
}
credential_id = Some(map.next_value()?);
}
0x08 => {
if public_key.is_some() {
return Err(SerdeError::duplicate_field("public_key"));
}
public_key = Some(map.next_value()?);
}
0x09 => {
if total_credentials.is_some() {
return Err(SerdeError::duplicate_field("total_credentials"));
}
total_credentials = Some(map.next_value()?);
}
0x0A => {
if cred_protect.is_some() {
return Err(SerdeError::duplicate_field("cred_protect"));
}
cred_protect = Some(map.next_value()?);
}
0x0B => {
if large_blob_key.is_some() {
return Err(SerdeError::duplicate_field("large_blob_key"));
}
large_blob_key = Some(map.next_value::<ByteBuf>()?.into_vec());
}
k => {
warn!("ClientPinResponse: unexpected key: {:?}", k);
let _ = map.next_value::<IgnoredAny>()?;
continue;
}
}
}
Ok(CredentialManagementResponse {
existing_resident_credentials_count,
max_possible_remaining_resident_credentials_count,
rp,
rp_id_hash,
total_rps,
user,
credential_id,
public_key,
total_credentials,
cred_protect,
large_blob_key,
})
}
}
deserializer.deserialize_bytes(CredentialManagementResponseVisitor)
}
}
impl RequestCtap2 for CredentialManagement {
type Output = CredentialManagementResponse;
fn command(&self) -> Command {
if self.use_legacy_preview {
Command::CredentialManagementPreview
} else {
Command::CredentialManagement
}
}
fn wire_format(&self) -> Result<Vec<u8>, HIDError> {
let output = to_vec(&self).map_err(CommandError::Serializing)?;
trace!("client subcommmand: {:04X?}", &output);
Ok(output)
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: FidoDevice,
{
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
if status.is_ok() {
if input.len() > 1 {
trace!("parsing credential management data: {:#04X?}", &input);
let credential_management =
from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Ok(credential_management)
} else {
Ok(CredentialManagementResponse::default())
}
} else {
let data: Option<Value> = if input.len() > 1 {
Some(from_slice(&input[1..]).map_err(CommandError::Deserializing)?)
} else {
None
};
Err(CommandError::StatusCode(status, data).into())
}
}
fn send_to_virtual_device<Dev: crate::VirtualFidoDevice>(
&self,
_dev: &mut Dev,
) -> Result<Self::Output, HIDError> {
unimplemented!()
}
}
impl PinUvAuthCommand for CredentialManagement {
fn get_rp_id(&self) -> Option<&String> {
None
}
fn set_pin_uv_auth_param(
&mut self,
pin_uv_auth_token: Option<PinUvAuthToken>,
) -> Result<(), AuthenticatorError> {
let mut param = None;
if let Some(token) = pin_uv_auth_token {
let (id, params) = self.subcommand.to_id_and_param();
let mut data = vec![id];
if params.has_some() {
data.extend(to_vec(¶ms).map_err(CommandError::Serializing)?);
}
param = Some(token.derive(&data).map_err(CommandError::Crypto)?);
}
self.pin_uv_auth_param = param;
Ok(())
}
fn can_skip_user_verification(
&mut self,
_info: &crate::AuthenticatorInfo,
_uv: UserVerificationRequirement,
) -> bool {
false
}
fn set_uv_option(&mut self, _uv: Option<bool>) {
}
fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam> {
self.pin_uv_auth_param.as_ref()
}
fn hmac_requested(&self) -> bool {
false
}
}
#[cfg(test)]
mod test {
use crate::ctap2::server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, Transport,
};
use super::CredManagementParams;
#[test]
fn test_serialize_cred_management_params() {
let cred_management_params = CredManagementParams {
rp_id_hash: Some(RelyingParty::from("example.org").hash()),
credential_id: Some(PublicKeyCredentialDescriptor {
id: vec![1, 2, 3, 4],
transports: vec![Transport::USB, Transport::NFC],
}),
user: Some(PublicKeyCredentialUserEntity {
id: vec![5, 6, 7, 8],
name: Some("testuser".to_string()),
display_name: Some("Test User".to_string()),
}),
};
let serialized =
serde_cbor::ser::to_vec(&cred_management_params).expect("Failed to serialize to CBOR");
assert_eq!(
serialized,
[
163, 1, 88, 32, 191, 171, 195, 116, 50, 149, 139, 6, 51, 96, 211, 173, 100, 97, 201,
196, 115, 90, 231, 248, 237, 212, 101, 146, 165, 224, 240, 20, 82, 178, 228, 181,
2, 162, 98, 105, 100, 68, 1, 2, 3, 4, 100, 116, 121, 112, 101, 106, 112, 117, 98,
108, 105, 99, 45, 107, 101, 121, 3, 163, 98, 105, 100, 68, 5, 6, 7, 8, 100, 110,
97, 109, 101, 104, 116, 101, 115, 116, 117, 115, 101, 114, 107, 100, 105, 115, 112,
108, 97, 121, 78, 97, 109, 101, 105, 84, 101, 115, 116, 32, 85, 115, 101, 114
]
);
}
}