Skip to content
Snippets Groups Projects
data_formats.rs 44.6 KiB
Newer Older
Jean-Michel Picod's avatar
Jean-Michel Picod committed
// Copyright 2019 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.

use super::status_code::Ctap2StatusCode;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::convert::TryFrom;
use crypto::{ecdh, ecdsa};

// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialRpEntity {
    pub rp_id: String,
    pub rp_name: Option<String>,
    pub rp_icon: Option<String>,
}

impl TryFrom<&cbor::Value> for PublicKeyCredentialRpEntity {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let rp_map = read_map(cbor_value)?;
        let rp_id = read_text_string(ok_or_missing(rp_map.get(&cbor_text!("id")))?)?;
        let rp_name = rp_map
            .get(&cbor_text!("name"))
            .map(read_text_string)
            .transpose()?;
        let rp_icon = rp_map
            .get(&cbor_text!("icon"))
            .map(read_text_string)
            .transpose()?;
        Ok(Self {
            rp_id,
            rp_name,
            rp_icon,
        })
    }
}

// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialUserEntity {
    pub user_id: Vec<u8>,
    pub user_name: Option<String>,
    pub user_display_name: Option<String>,
    pub user_icon: Option<String>,
}

impl TryFrom<&cbor::Value> for PublicKeyCredentialUserEntity {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let user_map = read_map(cbor_value)?;
        let user_id = read_byte_string(ok_or_missing(user_map.get(&cbor_text!("id")))?)?;
        let user_name = user_map
            .get(&cbor_text!("name"))
            .map(read_text_string)
            .transpose()?;
        let user_display_name = user_map
            .get(&cbor_text!("displayName"))
            .map(read_text_string)
            .transpose()?;
        let user_icon = user_map
            .get(&cbor_text!("icon"))
            .map(read_text_string)
            .transpose()?;
        Ok(Self {
            user_id,
            user_name,
            user_display_name,
            user_icon,
        })
    }
}

impl From<PublicKeyCredentialUserEntity> for cbor::Value {
    fn from(entity: PublicKeyCredentialUserEntity) -> Self {
        cbor_map_options! {
            "id" => entity.user_id,
            "name" => entity.user_name,
            "displayName" => entity.user_display_name,
            "icon" => entity.user_icon,
        }
    }
}

// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[derive(Clone, PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum PublicKeyCredentialType {
    PublicKey,
    // This is the default for all strings not covered above.
    // Unknown types should be ignored, instead of returning errors.
    Unknown,
Jean-Michel Picod's avatar
Jean-Michel Picod committed
}

impl From<PublicKeyCredentialType> for cbor::Value {
    fn from(cred_type: PublicKeyCredentialType) -> Self {
        match cred_type {
            PublicKeyCredentialType::PublicKey => "public-key",
            // We should never create this credential type.
            PublicKeyCredentialType::Unknown => "unknown",
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        }
        .into()
    }
}

impl TryFrom<&cbor::Value> for PublicKeyCredentialType {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let cred_type_string = read_text_string(cbor_value)?;
        match &cred_type_string[..] {
            "public-key" => Ok(PublicKeyCredentialType::PublicKey),
            _ => Ok(PublicKeyCredentialType::Unknown),
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PublicKeyCredentialParameter {
    pub cred_type: PublicKeyCredentialType,
    pub alg: SignatureAlgorithm,
}

impl TryFrom<&cbor::Value> for PublicKeyCredentialParameter {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let cred_param_map = read_map(cbor_value)?;
        let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
            cred_param_map.get(&cbor_text!("type")),
        )?)?;
        let alg =
            SignatureAlgorithm::try_from(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
        Ok(Self { cred_type, alg })
    }
}

impl From<PublicKeyCredentialParameter> for cbor::Value {
    fn from(cred_param: PublicKeyCredentialParameter) -> Self {
        cbor_map_options! {
            "type" => cred_param.cred_type,
            "alg" => cred_param.alg as i64,
        }
    }
}

// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum AuthenticatorTransport {
    Usb,
    Nfc,
    Ble,
    Internal,
}

impl From<AuthenticatorTransport> for cbor::Value {
    fn from(transport: AuthenticatorTransport) -> Self {
        match transport {
            AuthenticatorTransport::Usb => "usb",
            AuthenticatorTransport::Nfc => "nfc",
            AuthenticatorTransport::Ble => "ble",
            AuthenticatorTransport::Internal => "internal",
        }
        .into()
    }
}

impl TryFrom<&cbor::Value> for AuthenticatorTransport {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let transport_string = read_text_string(cbor_value)?;
        match &transport_string[..] {
            "usb" => Ok(AuthenticatorTransport::Usb),
            "nfc" => Ok(AuthenticatorTransport::Nfc),
            "ble" => Ok(AuthenticatorTransport::Ble),
            "internal" => Ok(AuthenticatorTransport::Internal),
            _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
        }
    }
}

// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialDescriptor {
    pub key_type: PublicKeyCredentialType,
    pub key_id: Vec<u8>,
    pub transports: Option<Vec<AuthenticatorTransport>>,
}

impl TryFrom<&cbor::Value> for PublicKeyCredentialDescriptor {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let cred_desc_map = read_map(cbor_value)?;
        let key_type = PublicKeyCredentialType::try_from(ok_or_missing(
            cred_desc_map.get(&cbor_text!("type")),
        )?)?;
        let key_id = read_byte_string(ok_or_missing(cred_desc_map.get(&cbor_text!("id")))?)?;
        let transports = match cred_desc_map.get(&cbor_text!("transports")) {
            Some(exclude_entry) => {
                let transport_vec = read_array(exclude_entry)?;
                let transports = transport_vec
                    .iter()
                    .map(AuthenticatorTransport::try_from)
                    .collect::<Result<Vec<AuthenticatorTransport>, Ctap2StatusCode>>(
                )?;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
                Some(transports)
            }
            None => None,
        };
        Ok(Self {
            key_type,
            key_id,
            transports,
        })
    }
}

impl From<PublicKeyCredentialDescriptor> for cbor::Value {
    fn from(desc: PublicKeyCredentialDescriptor) -> Self {
        cbor_map_options! {
            "type" => desc.key_type,
            "id" => desc.key_id,
            "transports" => desc.transports.map(|vec| cbor_array_vec!(vec)),
        }
    }
}

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct Extensions(BTreeMap<String, cbor::Value>);

impl TryFrom<&cbor::Value> for Extensions {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let mut extensions = BTreeMap::new();
        for (extension_key, extension_value) in read_map(cbor_value)? {
            if let cbor::KeyType::TextString(extension_key_string) = extension_key {
                extensions.insert(extension_key_string.to_string(), extension_value.clone());
            } else {
                return Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
            }
        }
        Ok(Extensions(extensions))
    }
}

impl From<Extensions> for cbor::Value {
    fn from(extensions: Extensions) -> Self {
        cbor_map_btree!(extensions
            .0
            .into_iter()
            .map(|(key, value)| (cbor_text!(key), value))
            .collect())
    }
}

impl Extensions {
    #[cfg(test)]
    pub fn new(extension_map: BTreeMap<String, cbor::Value>) -> Self {
        Extensions(extension_map)
    }

    pub fn has_make_credential_hmac_secret(&self) -> Result<bool, Ctap2StatusCode> {
        self.0
            .get("hmac-secret")
            .map(read_bool)
            .unwrap_or(Ok(false))
    }

    pub fn get_assertion_hmac_secret(
        &self,
    ) -> Option<Result<GetAssertionHmacSecretInput, Ctap2StatusCode>> {
        self.0
            .get("hmac-secret")
            .map(GetAssertionHmacSecretInput::try_from)
    }
}

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct GetAssertionHmacSecretInput {
    pub key_agreement: CoseKey,
    pub salt_enc: Vec<u8>,
    pub salt_auth: Vec<u8>,
}

impl TryFrom<&cbor::Value> for GetAssertionHmacSecretInput {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let input_map = read_map(cbor_value)?;
        let cose_key = read_map(ok_or_missing(input_map.get(&cbor_unsigned!(1)))?)?;
        let salt_enc = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(2)))?)?;
        let salt_auth = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(3)))?)?;
        Ok(Self {
            key_agreement: CoseKey(cose_key.clone()),
            salt_enc,
            salt_auth,
        })
    }
}

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct GetAssertionHmacSecretOutput(Vec<u8>);

impl From<GetAssertionHmacSecretOutput> for cbor::Value {
    fn from(message: GetAssertionHmacSecretOutput) -> cbor::Value {
        cbor_bytes!(message.0)
    }
}

impl TryFrom<&cbor::Value> for GetAssertionHmacSecretOutput {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        Ok(GetAssertionHmacSecretOutput(read_byte_string(cbor_value)?))
    }
}

Jean-Michel Picod's avatar
Jean-Michel Picod committed
// Even though options are optional, we can use the default if not present.
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct MakeCredentialOptions {
    pub rk: bool,
    pub uv: bool,
}

impl TryFrom<&cbor::Value> for MakeCredentialOptions {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let options_map = read_map(cbor_value)?;
        let rk = match options_map.get(&cbor_text!("rk")) {
            Some(options_entry) => read_bool(options_entry)?,
            None => false,
        };
        if let Some(options_entry) = options_map.get(&cbor_text!("up")) {
            if !read_bool(options_entry)? {
                return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
            }
        }
        let uv = match options_map.get(&cbor_text!("uv")) {
            Some(options_entry) => read_bool(options_entry)?,
            None => false,
        };
        Ok(Self { rk, uv })
    }
}

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct GetAssertionOptions {
    pub up: bool,
    pub uv: bool,
}

impl TryFrom<&cbor::Value> for GetAssertionOptions {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let options_map = read_map(cbor_value)?;
        if let Some(options_entry) = options_map.get(&cbor_text!("rk")) {
            // This is only for returning the correct status code.
            read_bool(options_entry)?;
            return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
        }
        let up = match options_map.get(&cbor_text!("up")) {
            Some(options_entry) => read_bool(options_entry)?,
            None => true,
        };
        let uv = match options_map.get(&cbor_text!("uv")) {
            Some(options_entry) => read_bool(options_entry)?,
            None => false,
        };
        Ok(Self { up, uv })
    }
}

// https://www.w3.org/TR/webauthn/#packed-attestation
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PackedAttestationStatement {
    pub alg: i64,
    pub sig: Vec<u8>,
    pub x5c: Option<Vec<Vec<u8>>>,
    pub ecdaa_key_id: Option<Vec<u8>>,
}

impl From<PackedAttestationStatement> for cbor::Value {
    fn from(att_stmt: PackedAttestationStatement) -> Self {
        cbor_map_options! {
            "alg" => att_stmt.alg,
            "sig" => att_stmt.sig,
            "x5c" => att_stmt.x5c.map(|x| cbor_array_vec!(x)),
            "ecdaaKeyId" => att_stmt.ecdaa_key_id,
        }
    }
}

#[derive(PartialEq)]
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum SignatureAlgorithm {
    ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize,
    // This is the default for all numbers not covered above.
    // Unknown types should be ignored, instead of returning errors.
    Unknown = 0,
impl TryFrom<&cbor::Value> for SignatureAlgorithm {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        match read_integer(cbor_value)? {
            ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
            _ => Ok(SignatureAlgorithm::Unknown),
// https://www.w3.org/TR/webauthn/#public-key-credential-source
Jean-Michel Picod's avatar
Jean-Michel Picod committed
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PublicKeyCredentialSource {
    // TODO function to convert to / from Vec<u8>
    pub key_type: PublicKeyCredentialType,
    pub credential_id: Vec<u8>,
    pub private_key: ecdsa::SecKey, // TODO(kaczmarczyck) open for other algorithms
    pub rp_id: String,
    pub user_handle: Vec<u8>, // not optional, but nullable
    pub other_ui: Option<String>,
    pub cred_random: Option<Vec<u8>>,

    /// Contains the unknown fields when parsing a CBOR value.
    ///
    /// Those fields could either be deleted fields from older versions (they should have reserved
    /// tags) or fields from newer versions (the tags should not be reserved). If this is empty,
    /// then the parsed credential is probably from the same version (but not necessarily).
    pub unknown_fields: BTreeMap<cbor::KeyType, cbor::Value>,
}

// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
// is associated with a unique tag, implemented with a CBOR unsigned key.
#[repr(u64)]
enum PublicKeyCredentialSourceField {
    CredentialId = 0,
    PrivateKey = 1,
    RpId = 2,
    UserHandle = 3,
    OtherUi = 4,
    CredRandom = 5,
    // When a field is removed, its tag should be reserved and not used for new fields. We document
    // those reserved tags below.
    // Reserved tags: none.
}

impl From<PublicKeyCredentialSourceField> for cbor::KeyType {
    fn from(field: PublicKeyCredentialSourceField) -> cbor::KeyType {
        (field as u64).into()
    }
Jean-Michel Picod's avatar
Jean-Michel Picod committed
}

impl From<PublicKeyCredentialSource> for cbor::Value {
    fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
        use PublicKeyCredentialSourceField::*;
        let mut private_key = [0; 32];
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        credential.private_key.to_bytes(&mut private_key);
        cbor_extend_map_options! {
            credential.unknown_fields,
            CredentialId => Some(credential.credential_id),
            PrivateKey => Some(private_key.to_vec()),
            RpId => Some(credential.rp_id),
            UserHandle => Some(credential.user_handle),
            OtherUi => credential.other_ui,
            CredRandom => credential.cred_random
impl PublicKeyCredentialSource {
    pub fn parse_cbor(cbor_value: cbor::Value) -> Option<PublicKeyCredentialSource> {
        use PublicKeyCredentialSourceField::*;
        let mut map = match cbor_value {
            cbor::Value::Map(x) => x,
            _ => return None,
        };
Jean-Michel Picod's avatar
Jean-Michel Picod committed

        let credential_id = read_byte_string(&map.remove(&CredentialId.into())?).ok()?;
        let private_key = read_byte_string(&map.remove(&PrivateKey.into())?).ok()?;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        if private_key.len() != 32 {
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        }
        let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32))?;
        let rp_id = read_text_string(&map.remove(&RpId.into())?).ok()?;
        let user_handle = read_byte_string(&map.remove(&UserHandle.into())?).ok()?;
        let other_ui = map
            .remove(&OtherUi.into())
            .as_ref()
            .map(read_text_string)
            .transpose()
            .ok()?;
        let cred_random = map
            .remove(&CredRandom.into())
            .as_ref()
            .map(read_byte_string)
            .transpose()
            .ok()?;
        let unknown_fields = map;
        Some(PublicKeyCredentialSource {
Jean-Michel Picod's avatar
Jean-Michel Picod committed
            key_type: PublicKeyCredentialType::PublicKey,
            credential_id,
            private_key,
            rp_id,
            user_handle,
            other_ui,
            cred_random,
Jean-Michel Picod's avatar
Jean-Michel Picod committed
        })
    }
}

// TODO(kaczmarczyck) we could decide to split this data type up
// It depends on the algorithm though, I think.
// So before creating a mess, this is my workaround.
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);

// This is the algorithm specifier that is supposed to be used in a COSE key
// map. The CTAP specification says -25 which represents ECDH-ES + HKDF-256
// here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
// In fact, this is just used for compatibility with older specification versions.
const ECDH_ALGORITHM: i64 = -25;
// This is the identifier used by OpenSSH. To be compatible, we accept both.
const ES256_ALGORITHM: i64 = -7;
Jean-Michel Picod's avatar
Jean-Michel Picod committed
const EC2_KEY_TYPE: i64 = 2;
const P_256_CURVE: i64 = 1;

impl From<ecdh::PubKey> for CoseKey {
    fn from(pk: ecdh::PubKey) -> Self {
        let mut x_bytes = [0; ecdh::NBYTES];
        let mut y_bytes = [0; ecdh::NBYTES];
        pk.to_coordinates(&mut x_bytes, &mut y_bytes);
        let x_byte_cbor: cbor::Value = cbor_bytes_lit!(&x_bytes);
        let y_byte_cbor: cbor::Value = cbor_bytes_lit!(&y_bytes);
        // TODO(kaczmarczyck) do not write optional parameters, spec is unclear
        let cose_cbor_value = cbor_map_options! {
            1 => EC2_KEY_TYPE,
            3 => ECDH_ALGORITHM,
            -1 => P_256_CURVE,
            -2 => x_byte_cbor,
            -3 => y_byte_cbor,
        };
        if let cbor::Value::Map(cose_map) = cose_cbor_value {
            CoseKey(cose_map)
        } else {
            unreachable!();
        }
    }
}

impl TryFrom<CoseKey> for ecdh::PubKey {
    type Error = Ctap2StatusCode;

    fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> {
        let key_type = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(1)))?)?;
        if key_type != EC2_KEY_TYPE {
            return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
        }
        let algorithm = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(3)))?)?;
        if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
Jean-Michel Picod's avatar
Jean-Michel Picod committed
            return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
        }
        let curve = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(-1)))?)?;
        if curve != P_256_CURVE {
            return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
        }
        let x_bytes = read_byte_string(ok_or_missing(cose_key.0.get(&cbor_int!(-2)))?)?;
        if x_bytes.len() != ecdh::NBYTES {
            return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
        }
        let y_bytes = read_byte_string(ok_or_missing(cose_key.0.get(&cbor_int!(-3)))?)?;
        if y_bytes.len() != ecdh::NBYTES {
            return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
        }
        let x_array_ref = array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES];
        let y_array_ref = array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES];
        ecdh::PubKey::from_coordinates(x_array_ref, y_array_ref)
            .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
    }
}

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum ClientPinSubCommand {
    GetPinRetries,
    GetKeyAgreement,
    SetPin,
    ChangePin,
    GetPinUvAuthTokenUsingPin,
    GetPinUvAuthTokenUsingUv,
    GetUvRetries,
}

impl From<ClientPinSubCommand> for cbor::Value {
    fn from(subcommand: ClientPinSubCommand) -> Self {
        match subcommand {
            ClientPinSubCommand::GetPinRetries => 0x01,
            ClientPinSubCommand::GetKeyAgreement => 0x02,
            ClientPinSubCommand::SetPin => 0x03,
            ClientPinSubCommand::ChangePin => 0x04,
            ClientPinSubCommand::GetPinUvAuthTokenUsingPin => 0x05,
            ClientPinSubCommand::GetPinUvAuthTokenUsingUv => 0x06,
            ClientPinSubCommand::GetUvRetries => 0x07,
        }
        .into()
    }
}

impl TryFrom<&cbor::Value> for ClientPinSubCommand {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let subcommand_int = read_unsigned(cbor_value)?;
        match subcommand_int {
            0x01 => Ok(ClientPinSubCommand::GetPinRetries),
            0x02 => Ok(ClientPinSubCommand::GetKeyAgreement),
            0x03 => Ok(ClientPinSubCommand::SetPin),
            0x04 => Ok(ClientPinSubCommand::ChangePin),
            0x05 => Ok(ClientPinSubCommand::GetPinUvAuthTokenUsingPin),
            0x06 => Ok(ClientPinSubCommand::GetPinUvAuthTokenUsingUv),
            0x07 => Ok(ClientPinSubCommand::GetUvRetries),
            // TODO(kaczmarczyck) what is the correct status code for this error?
            _ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
        }
    }
}

pub(super) fn read_unsigned(cbor_value: &cbor::Value) -> Result<u64, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::KeyValue(cbor::KeyType::Unsigned(unsigned)) => Ok(*unsigned),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn read_integer(cbor_value: &cbor::Value) -> Result<i64, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::KeyValue(cbor::KeyType::Unsigned(unsigned)) => {
            if *unsigned <= core::i64::MAX as u64 {
                Ok(*unsigned as i64)
            } else {
                Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
            }
        }
        cbor::Value::KeyValue(cbor::KeyType::Negative(signed)) => Ok(*signed),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub fn read_byte_string(cbor_value: &cbor::Value) -> Result<Vec<u8>, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::KeyValue(cbor::KeyType::ByteString(byte_string)) => Ok(byte_string.to_vec()),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn read_text_string(cbor_value: &cbor::Value) -> Result<String, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::KeyValue(cbor::KeyType::TextString(text_string)) => {
            Ok(text_string.to_string())
        }
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn read_array(cbor_value: &cbor::Value) -> Result<&Vec<cbor::Value>, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::Array(array) => Ok(array),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn read_map(
    cbor_value: &cbor::Value,
) -> Result<&BTreeMap<cbor::KeyType, cbor::Value>, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::Map(map) => Ok(map),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn read_bool(cbor_value: &cbor::Value) -> Result<bool, Ctap2StatusCode> {
    match cbor_value {
        cbor::Value::Simple(cbor::SimpleValue::FalseValue) => Ok(false),
        cbor::Value::Simple(cbor::SimpleValue::TrueValue) => Ok(true),
        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
    }
}

pub(super) fn ok_or_missing(
    value_option: Option<&cbor::Value>,
) -> Result<&cbor::Value, Ctap2StatusCode> {
    value_option.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
}

#[cfg(test)]
mod test {
    use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
    use super::*;
    use alloc::collections::BTreeMap;
    use crypto::rng256::{Rng256, ThreadRng256};
Jean-Michel Picod's avatar
Jean-Michel Picod committed

    #[test]
    fn test_read_unsigned() {
        assert_eq!(read_unsigned(&cbor_int!(123)), Ok(123));
        assert_eq!(
            read_unsigned(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_unsigned_limits() {
        assert_eq!(
            read_unsigned(&cbor_unsigned!(std::u64::MAX)),
            Ok(std::u64::MAX)
        );
        assert_eq!(
            read_unsigned(&cbor_unsigned!((std::i64::MAX as u64) + 1)),
            Ok((std::i64::MAX as u64) + 1)
        );
        assert_eq!(
            read_unsigned(&cbor_int!(std::i64::MAX)),
            Ok(std::i64::MAX as u64)
        );
        assert_eq!(read_unsigned(&cbor_int!(123)), Ok(123));
        assert_eq!(read_unsigned(&cbor_int!(1)), Ok(1));
        assert_eq!(read_unsigned(&cbor_int!(0)), Ok(0));
        assert_eq!(
            read_unsigned(&cbor_int!(-1)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_int!(-123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_unsigned(&cbor_int!(std::i64::MIN)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_integer() {
        assert_eq!(read_integer(&cbor_int!(123)), Ok(123));
        assert_eq!(read_integer(&cbor_int!(-123)), Ok(-123));
        assert_eq!(
            read_integer(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_integer(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_integer(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_integer(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_integer(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_integer_limits() {
        assert_eq!(
            read_integer(&cbor_unsigned!(std::u64::MAX)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_integer(&cbor_unsigned!((std::i64::MAX as u64) + 1)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_integer(&cbor_int!(std::i64::MAX)), Ok(std::i64::MAX));
        assert_eq!(read_integer(&cbor_int!(123)), Ok(123));
        assert_eq!(read_integer(&cbor_int!(1)), Ok(1));
        assert_eq!(read_integer(&cbor_int!(0)), Ok(0));
        assert_eq!(read_integer(&cbor_int!(-1)), Ok(-1));
        assert_eq!(read_integer(&cbor_int!(-123)), Ok(-123));
        assert_eq!(read_integer(&cbor_int!(std::i64::MIN)), Ok(std::i64::MIN));
    }

    #[test]
    fn test_read_byte_string() {
        assert_eq!(
            read_byte_string(&cbor_int!(123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_byte_string(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_byte_string(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_byte_string(&cbor_bytes_lit!(b"")), Ok(Vec::new()));
        assert_eq!(
            read_byte_string(&cbor_bytes_lit!(b"bar")),
            Ok(b"bar".to_vec())
        );
        assert_eq!(
            read_byte_string(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_byte_string(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_text_string() {
        assert_eq!(
            read_text_string(&cbor_int!(123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_text_string(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_text_string(&cbor_text!("")), Ok(String::new()));
        assert_eq!(
            read_text_string(&cbor_text!("foo")),
            Ok(String::from("foo"))
        );
        assert_eq!(
            read_text_string(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_text_string(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_text_string(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_array() {
        assert_eq!(
            read_array(&cbor_int!(123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_array(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_array(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_array(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_array(&cbor_array![]), Ok(&Vec::new()));
        assert_eq!(
            read_array(&cbor_array![
                123,
                cbor_null!(),
                "foo",
                cbor_array![],
                cbor_map! {},
            ]),
            Ok(&vec![
                cbor_int!(123),
                cbor_null!(),
                cbor_text!("foo"),
                cbor_array![],
                cbor_map! {},
            ])
        );
        assert_eq!(
            read_array(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_read_map() {
        assert_eq!(
            read_map(&cbor_int!(123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_map(&cbor_bool!(true)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_map(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_map(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_map(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_map(&cbor_map! {}), Ok(&BTreeMap::new()));
        assert_eq!(
            read_map(&cbor_map! {
                1 => cbor_false!(),
                "foo" => b"bar",
                b"bin" => -42,
            }),
            Ok(&[
                (cbor_unsigned!(1), cbor_false!()),
                (cbor_text!("foo"), cbor_bytes_lit!(b"bar")),
                (cbor_bytes_lit!(b"bin"), cbor_int!(-42)),
            ]
            .iter()
            .cloned()
            .collect::<BTreeMap<_, _>>())
        );
    }

    #[test]
    fn test_read_bool() {
        assert_eq!(
            read_bool(&cbor_int!(123)),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(read_bool(&cbor_bool!(true)), Ok(true));
        assert_eq!(read_bool(&cbor_bool!(false)), Ok(false));
        assert_eq!(
            read_bool(&cbor_text!("foo")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_bool(&cbor_bytes_lit!(b"bar")),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_bool(&cbor_array![]),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
        assert_eq!(
            read_bool(&cbor_map! {}),
            Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
        );
    }

    #[test]
    fn test_from_public_key_credential_rp_entity() {
        let cbor_rp_entity = cbor_map! {
            "id" => "example.com",
            "name" => "Example",
            "icon" => "example.com/icon.png",
        };
        let rp_entity = PublicKeyCredentialRpEntity::try_from(&cbor_rp_entity);
        let expected_rp_entity = PublicKeyCredentialRpEntity {