CBOR maps use Vec instead of BTreeMap (#303)

* CBOR uses Vec for map internally

* remove BTreeMap from get_info

* rename cbor_map_btree and clean up cbor_array_vec

* destructure now takes Vec, not BTreeMap

* adds dedup in CBOR writer

* fail to write CBOR maps with duplicates

* CBOR interface refinements

* macro documentation for CBOR map and array
This commit is contained in:
kaczmarczyck
2021-04-13 14:46:28 +02:00
committed by GitHub
parent 054e303d11
commit 78b7767682
9 changed files with 258 additions and 163 deletions

View File

@@ -594,14 +594,14 @@ mod test {
0x01 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F],
0x02 => cbor_map! {
"id" => "example.com",
"name" => "Example",
"icon" => "example.com/icon.png",
"name" => "Example",
},
0x03 => cbor_map! {
"id" => vec![0x1D, 0x1D, 0x1D, 0x1D],
"icon" => "example.com/foo/icon.png",
"name" => "foo",
"displayName" => "bar",
"icon" => "example.com/foo/icon.png",
},
0x04 => cbor_array![ES256_CRED_PARAM],
0x05 => cbor_array![],
@@ -656,8 +656,8 @@ mod test {
0x01 => "example.com",
0x02 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F],
0x03 => cbor_array![ cbor_map! {
"type" => "public-key",
"id" => vec![0x2D, 0x2D, 0x2D, 0x2D],
"type" => "public-key",
"transports" => cbor_array!["usb"],
} ],
0x06 => vec![0x12, 0x34],

View File

@@ -13,7 +13,6 @@
// limitations under the License.
use super::status_code::Ctap2StatusCode;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use arrayref::array_ref;
@@ -62,8 +61,8 @@ impl From<PublicKeyCredentialRpEntity> for cbor::Value {
fn from(entity: PublicKeyCredentialRpEntity) -> Self {
cbor_map_options! {
"id" => entity.rp_id,
"name" => entity.rp_name,
"icon" => entity.rp_icon,
"name" => entity.rp_name,
}
}
}
@@ -108,9 +107,9 @@ impl From<PublicKeyCredentialUserEntity> for cbor::Value {
fn from(entity: PublicKeyCredentialUserEntity) -> Self {
cbor_map_options! {
"id" => entity.user_id,
"icon" => entity.user_icon,
"name" => entity.user_name,
"displayName" => entity.user_display_name,
"icon" => entity.user_icon,
}
}
}
@@ -174,8 +173,8 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialParameter {
impl From<PublicKeyCredentialParameter> for cbor::Value {
fn from(cred_param: PublicKeyCredentialParameter) -> Self {
cbor_map_options! {
"type" => cred_param.cred_type,
"alg" => cred_param.alg,
"type" => cred_param.cred_type,
}
}
}
@@ -262,8 +261,8 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialDescriptor {
impl From<PublicKeyCredentialDescriptor> for cbor::Value {
fn from(desc: PublicKeyCredentialDescriptor) -> Self {
cbor_map_options! {
"type" => desc.key_type,
"id" => desc.key_id,
"type" => desc.key_type,
"transports" => desc.transports.map(|vec| cbor_array_vec!(vec)),
}
}
@@ -1119,7 +1118,7 @@ pub(super) fn extract_array(cbor_value: cbor::Value) -> Result<Vec<cbor::Value>,
pub(super) fn extract_map(
cbor_value: cbor::Value,
) -> Result<BTreeMap<cbor::KeyType, cbor::Value>, Ctap2StatusCode> {
) -> Result<Vec<(cbor::KeyType, cbor::Value)>, Ctap2StatusCode> {
match cbor_value {
cbor::Value::Map(map) => Ok(map),
_ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
@@ -1142,7 +1141,6 @@ pub(super) fn ok_or_missing<T>(value_option: Option<T>) -> Result<T, Ctap2Status
mod test {
use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
use super::*;
use alloc::collections::BTreeMap;
use cbor::{
cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_false, cbor_int, cbor_null,
cbor_text, cbor_unsigned,
@@ -1371,21 +1369,18 @@ mod test {
extract_map(cbor_array![]),
Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE)
);
assert_eq!(extract_map(cbor_map! {}), Ok(BTreeMap::new()));
assert_eq!(extract_map(cbor_map! {}), Ok(Vec::new()));
assert_eq!(
extract_map(cbor_map! {
1 => cbor_false!(),
"foo" => b"bar",
b"bin" => -42,
"foo" => b"bar",
}),
Ok([
Ok(vec![
(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<_, _>>())
(cbor_text!("foo"), cbor_bytes_lit!(b"bar")),
])
);
}
@@ -1419,8 +1414,8 @@ mod 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",
"name" => "Example",
};
let rp_entity = PublicKeyCredentialRpEntity::try_from(cbor_rp_entity);
let expected_rp_entity = PublicKeyCredentialRpEntity {
@@ -1435,9 +1430,9 @@ mod test {
fn test_from_into_public_key_credential_user_entity() {
let cbor_user_entity = cbor_map! {
"id" => vec![0x1D, 0x1D, 0x1D, 0x1D],
"icon" => "example.com/foo/icon.png",
"name" => "foo",
"displayName" => "bar",
"icon" => "example.com/foo/icon.png",
};
let user_entity = PublicKeyCredentialUserEntity::try_from(cbor_user_entity.clone());
let expected_user_entity = PublicKeyCredentialUserEntity {
@@ -1541,8 +1536,8 @@ mod test {
#[test]
fn test_from_into_public_key_credential_parameter() {
let cbor_credential_parameter = cbor_map! {
"type" => "public-key",
"alg" => ES256_ALGORITHM,
"type" => "public-key",
};
let credential_parameter =
PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone());
@@ -1558,8 +1553,8 @@ mod test {
#[test]
fn test_from_into_public_key_credential_descriptor() {
let cbor_credential_descriptor = cbor_map! {
"type" => "public-key",
"id" => vec![0x2D, 0x2D, 0x2D, 0x2D],
"type" => "public-key",
"transports" => cbor_array!["usb"],
};
let credential_descriptor =
@@ -1577,11 +1572,11 @@ mod test {
#[test]
fn test_from_make_credential_extensions() {
let cbor_extensions = cbor_map! {
"hmac-secret" => true,
"credProtect" => CredentialProtectionPolicy::UserVerificationRequired,
"minPinLength" => true,
"credBlob" => vec![0xCB],
"credProtect" => CredentialProtectionPolicy::UserVerificationRequired,
"hmac-secret" => true,
"largeBlobKey" => true,
"minPinLength" => true,
};
let extensions = MakeCredentialExtensions::try_from(cbor_extensions);
let expected_extensions = MakeCredentialExtensions {
@@ -1601,12 +1596,12 @@ mod test {
let pk = sk.genpk();
let cose_key = CoseKey::from(pk);
let cbor_extensions = cbor_map! {
"credBlob" => true,
"hmac-secret" => cbor_map! {
1 => cbor::Value::from(cose_key.clone()),
2 => vec![0x02; 32],
3 => vec![0x03; 16],
},
"credBlob" => true,
"largeBlobKey" => true,
};
let extensions = GetAssertionExtensions::try_from(cbor_extensions);
@@ -1631,13 +1626,13 @@ mod test {
let pk = sk.genpk();
let cose_key = CoseKey::from(pk);
let cbor_extensions = cbor_map! {
"credBlob" => true,
"hmac-secret" => cbor_map! {
1 => cbor::Value::from(cose_key.clone()),
2 => vec![0x02; 32],
3 => vec![0x03; 16],
4 => 2,
},
"credBlob" => true,
"largeBlobKey" => true,
};
let extensions = GetAssertionExtensions::try_from(cbor_extensions);
@@ -1690,7 +1685,7 @@ mod test {
let cbor_packed_attestation_statement = cbor_map! {
"alg" => 1,
"sig" => vec![0x55, 0x55, 0x55, 0x55],
"x5c" => cbor_array_vec![vec![certificate]],
"x5c" => cbor_array![certificate],
"ecdaaKeyId" => vec![0xEC, 0xDA, 0x1D],
};
let packed_attestation_statement = PackedAttestationStatement {
@@ -1878,7 +1873,7 @@ mod test {
};
let cbor_params = cbor_map! {
0x01 => 6,
0x02 => cbor_array_vec!(vec!["example.com".to_string()]),
0x02 => cbor_array!("example.com".to_string()),
0x03 => true,
};
assert_eq!(cbor::Value::from(params.clone()), cbor_params);
@@ -1897,7 +1892,7 @@ mod test {
ConfigSubCommandParams::SetMinPinLength(set_min_pin_length_params);
let cbor_params = cbor_map! {
0x01 => 6,
0x02 => cbor_array_vec!(vec!["example.com".to_string()]),
0x02 => cbor_array!("example.com".to_string()),
0x03 => true,
};
assert_eq!(cbor::Value::from(config_sub_command_params), cbor_params);

View File

@@ -63,7 +63,6 @@ use self::timed_permission::TimedPermission;
#[cfg(feature = "with_ctap1")]
use self::timed_permission::U2fUserPresenceState;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
@@ -706,10 +705,10 @@ where
};
let cred_protect_output = extensions.cred_protect.and(cred_protect_policy);
let extensions_output = cbor_map_options! {
"hmac-secret" => hmac_secret_output,
"credProtect" => cred_protect_output,
"minPinLength" => min_pin_length_output,
"credBlob" => cred_blob_output,
"credProtect" => cred_protect_output,
"hmac-secret" => hmac_secret_output,
"minPinLength" => min_pin_length_output,
};
if !cbor::write(extensions_output, &mut auth_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
@@ -805,8 +804,8 @@ where
None
};
let extensions_output = cbor_map_options! {
"hmac-secret" => encrypted_output,
"credBlob" => cred_blob,
"hmac-secret" => encrypted_output,
};
if !cbor::write(extensions_output, &mut auth_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
@@ -1033,26 +1032,29 @@ where
versions.insert(0, String::from(U2F_VERSION_STRING))
}
}
let mut options_map = BTreeMap::new();
options_map.insert(String::from("rk"), true);
options_map.insert(
String::from("clientPin"),
self.persistent_store.pin_hash()?.is_some(),
);
options_map.insert(String::from("up"), true);
options_map.insert(String::from("pinUvAuthToken"), true);
options_map.insert(String::from("largeBlobs"), true);
let mut options = vec![];
if ENTERPRISE_ATTESTATION_MODE.is_some() {
options_map.insert(
options.push((
String::from("ep"),
self.persistent_store.enterprise_attestation()?,
);
));
}
options_map.insert(String::from("authnrCfg"), true);
options_map.insert(String::from("credMgmt"), true);
options_map.insert(String::from("setMinPINLength"), true);
options_map.insert(String::from("makeCredUvNotRqd"), !has_always_uv);
options_map.insert(String::from("alwaysUv"), has_always_uv);
options.append(&mut vec![
(String::from("rk"), true),
(String::from("up"), true),
(String::from("alwaysUv"), has_always_uv),
(String::from("credMgmt"), true),
(String::from("authnrCfg"), true),
(
String::from("clientPin"),
self.persistent_store.pin_hash()?.is_some(),
),
(String::from("largeBlobs"), true),
(String::from("pinUvAuthToken"), true),
(String::from("setMinPINLength"), true),
(String::from("makeCredUvNotRqd"), !has_always_uv),
]);
Ok(ResponseData::AuthenticatorGetInfo(
AuthenticatorGetInfoResponse {
versions,
@@ -1064,7 +1066,7 @@ where
String::from("largeBlobKey"),
]),
aaguid: self.persistent_store.aaguid()?,
options: Some(options_map),
options: Some(options),
max_msg_size: Some(MAX_MSG_SIZE as u64),
// The order implies preference. We favor the new V2.
pin_protocols: Some(vec![
@@ -1285,33 +1287,33 @@ mod test {
String::from(FIDO2_VERSION_STRING),
String::from(FIDO2_1_VERSION_STRING),
]],
0x02 => cbor_array_vec![vec![
0x02 => cbor_array![
String::from("hmac-secret"),
String::from("credProtect"),
String::from("minPinLength"),
String::from("credBlob"),
String::from("largeBlobKey"),
]],
],
0x03 => ctap_state.persistent_store.aaguid().unwrap(),
0x04 => cbor_map_options! {
"rk" => true,
"clientPin" => false,
"up" => true,
"pinUvAuthToken" => true,
"largeBlobs" => true,
"ep" => ENTERPRISE_ATTESTATION_MODE.map(|_| false),
"authnrCfg" => true,
"rk" => true,
"up" => true,
"alwaysUv" => false,
"credMgmt" => true,
"authnrCfg" => true,
"clientPin" => false,
"largeBlobs" => true,
"pinUvAuthToken" => true,
"setMinPINLength" => true,
"makeCredUvNotRqd" => true,
"alwaysUv" => false,
},
0x05 => MAX_MSG_SIZE as u64,
0x06 => cbor_array_vec![vec![2, 1]],
0x06 => cbor_array![2, 1],
0x07 => MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
0x08 => CREDENTIAL_ID_SIZE as u64,
0x09 => cbor_array_vec![vec!["usb"]],
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
0x09 => cbor_array!["usb"],
0x0A => cbor_array![ES256_CRED_PARAM],
0x0B => MAX_LARGE_BLOB_ARRAY_SIZE as u64,
0x0C => false,
0x0D => ctap_state.persistent_store.min_pin_length().unwrap() as u64,

View File

@@ -17,10 +17,9 @@ use super::data_formats::{
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use cbor::{cbor_array_vec, cbor_bool, cbor_int, cbor_map_btree, cbor_map_options, cbor_text};
use cbor::{cbor_array_vec, cbor_bool, cbor_int, cbor_map_collection, cbor_map_options, cbor_text};
#[derive(Debug, PartialEq)]
pub enum ResponseData {
@@ -123,7 +122,7 @@ pub struct AuthenticatorGetInfoResponse {
pub versions: Vec<String>,
pub extensions: Option<Vec<String>>,
pub aaguid: [u8; 16],
pub options: Option<BTreeMap<String, bool>>,
pub options: Option<Vec<(String, bool)>>,
pub max_msg_size: Option<u64>,
pub pin_protocols: Option<Vec<u64>>,
pub max_credential_count_in_list: Option<u64>,
@@ -141,7 +140,7 @@ pub struct AuthenticatorGetInfoResponse {
// - 0x12: uvModality
// Add them when your hardware supports any kind of user verification within
// the boundary of the device, e.g. fingerprint or built-in keyboard.
pub certifications: Option<BTreeMap<String, i64>>,
pub certifications: Option<Vec<(String, i64)>>,
pub remaining_discoverable_credentials: Option<u64>,
// - 0x15: vendorPrototypeConfigCommands missing as we don't support it.
}
@@ -170,19 +169,19 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
} = get_info_response;
let options_cbor: Option<cbor::Value> = options.map(|options| {
let options_map: BTreeMap<_, _> = options
let options_map: Vec<(_, _)> = options
.into_iter()
.map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
.collect();
cbor_map_btree!(options_map)
cbor_map_collection!(options_map)
});
let certifications_cbor: Option<cbor::Value> = certifications.map(|certifications| {
let certifications_map: BTreeMap<_, _> = certifications
let certifications_map: Vec<(_, _)> = certifications
.into_iter()
.map(|(key, value)| (cbor_text!(key), cbor_int!(value)))
.collect();
cbor_map_btree!(certifications_map)
cbor_map_collection!(certifications_map)
});
cbor_map_options! {
@@ -337,7 +336,7 @@ mod test {
let cbor_packed_attestation_statement = cbor_map! {
"alg" => 1,
"sig" => vec![0x55, 0x55, 0x55, 0x55],
"x5c" => cbor_array_vec![vec![certificate]],
"x5c" => cbor_array![certificate],
"ecdaaKeyId" => vec![0xEC, 0xDA, 0x1D],
};
@@ -385,17 +384,17 @@ mod test {
ResponseData::AuthenticatorGetAssertion(get_assertion_response).into();
let expected_cbor = cbor_map_options! {
0x01 => cbor_map! {
"type" => "public-key",
"id" => vec![0x2D, 0x2D, 0x2D, 0x2D],
"type" => "public-key",
"transports" => cbor_array!["usb"],
},
0x02 => vec![0xAD],
0x03 => vec![0x51],
0x04 => cbor_map! {
"id" => vec![0x1D, 0x1D, 0x1D, 0x1D],
"icon" => "example.com/foo/icon.png".to_string(),
"name" => "foo".to_string(),
"displayName" => "bar".to_string(),
"icon" => "example.com/foo/icon.png".to_string(),
},
0x05 => 2,
0x07 => vec![0x1B],
@@ -438,15 +437,11 @@ mod test {
#[test]
fn test_get_info_optionals_into_cbor() {
let mut options_map = BTreeMap::new();
options_map.insert(String::from("rk"), true);
let mut certifications_map = BTreeMap::new();
certifications_map.insert(String::from("example-cert"), 1);
let get_info_response = AuthenticatorGetInfoResponse {
versions: vec!["FIDO_2_0".to_string()],
extensions: Some(vec!["extension".to_string()]),
aaguid: [0x00; 16],
options: Some(options_map),
options: Some(vec![(String::from("rk"), true)]),
max_msg_size: Some(1024),
pin_protocols: Some(vec![1]),
max_credential_count_in_list: Some(20),
@@ -459,22 +454,22 @@ mod test {
firmware_version: Some(0),
max_cred_blob_length: Some(1024),
max_rp_ids_for_set_min_pin_length: Some(8),
certifications: Some(certifications_map),
certifications: Some(vec![(String::from("example-cert"), 1)]),
remaining_discoverable_credentials: Some(150),
};
let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into();
let expected_cbor = cbor_map_options! {
0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
0x02 => cbor_array_vec![vec!["extension"]],
0x01 => cbor_array!["FIDO_2_0"],
0x02 => cbor_array!["extension"],
0x03 => vec![0x00; 16],
0x04 => cbor_map! {"rk" => true},
0x05 => 1024,
0x06 => cbor_array_vec![vec![1]],
0x06 => cbor_array![1],
0x07 => 20,
0x08 => 256,
0x09 => cbor_array_vec![vec!["usb"]],
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
0x09 => cbor_array!["usb"],
0x0A => cbor_array![ES256_CRED_PARAM],
0x0B => 1024,
0x0C => false,
0x0D => 4,

View File

@@ -1370,13 +1370,13 @@ mod test {
private_key,
rp_id: String::from("example.com"),
user_handle: vec![0x00],
user_display_name: None,
cred_protect_policy: None,
user_display_name: Some(String::from("Display Name")),
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
creation_order: 0,
user_name: None,
user_icon: None,
cred_blob: None,
large_blob_key: None,
user_name: Some(String::from("name")),
user_icon: Some(String::from("icon")),
cred_blob: Some(vec![0xCB]),
large_blob_key: Some(vec![0x1B]),
};
let serialized = serialize_credential(credential.clone()).unwrap();
let reconstructed = deserialize_credential(&serialized).unwrap();