Repository: google/OpenSK
File: src/ctap/data_formats.rs (Rust)

Code before:
}

// https://www.w3.org/TR/webauthn/#public-key-credential-source
#[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 simulate protocol buffers in CBOR with maps. Each field of a message 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()
    }
}

impl From<PublicKeyCredentialSource> for cbor::Value {
    fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
        use PublicKeyCredentialSourceField::*;
        let mut private_key = [0; 32];
        credential.private_key.to_bytes(&mut private_key);
        cbor_extend_map_options! {

Review comment (security):
There's nothing protocol-buffer-specific in this. FIDO2 already encodes many structs as maps, with string or integer keys, for example:
https://github.com/google/OpenSK/blob/8bbf42623f57f09c2c17ed5acd26f77eb49d6545/src/ctap/data_formats.rs#L1127-L1131

Protocol buffers refers to a [specific encoding of tag-value pairs](https://developers.google.com/protocol-buffers/docs/encoding), which this change doesn't implement at all (the CBOR encoding is used instead). So referring to "protocol buffers" here is misleading/confusing.

I'd simply mention "Encode credentials as a map, for versioning" or something like that.

Code after:
}

// https://www.w3.org/TR/webauthn/#public-key-credential-source
//
// Note that we only use the WebAuthn definition as an example. This data-structure is not specified
// by FIDO. In particular we may choose how we serialize and deserialize it.
#[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>>,
}

// 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.
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()
    }
}

impl From<PublicKeyCredentialSource> for cbor::Value {
    fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
        use PublicKeyCredentialSourceField::*;
        let mut private_key = [0u8; 32];
        credential.private_key.to_bytes(&mut private_key);
        cbor_map_options! {
            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,