Upgrade commands (#381)
* upgrade commands * precise metadata length * const METADATA_LEN for test
This commit is contained in:
@@ -17,10 +17,11 @@ use super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SI
|
|||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
||||||
CoseKey, CredentialManagementSubCommand, CredentialManagementSubCommandParameters,
|
CoseKey, CoseSignature, CredentialManagementSubCommand,
|
||||||
GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
|
CredentialManagementSubCommandParameters, GetAssertionExtensions, GetAssertionOptions,
|
||||||
PinUvAuthProtocol, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
|
||||||
PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, SetMinPinLengthParams,
|
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
|
||||||
|
PublicKeyCredentialUserEntity, SetMinPinLengthParams,
|
||||||
};
|
};
|
||||||
use super::key_material;
|
use super::key_material;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
@@ -49,6 +50,8 @@ pub enum Command {
|
|||||||
AuthenticatorConfig(AuthenticatorConfigParameters),
|
AuthenticatorConfig(AuthenticatorConfigParameters),
|
||||||
// Vendor specific commands
|
// Vendor specific commands
|
||||||
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
||||||
|
AuthenticatorVendorUpgrade(AuthenticatorVendorUpgradeParameters),
|
||||||
|
AuthenticatorVendorUpgradeInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
@@ -66,6 +69,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_CONFIG: u8 = 0x0D;
|
const AUTHENTICATOR_CONFIG: u8 = 0x0D;
|
||||||
const _AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
|
const _AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
|
||||||
const AUTHENTICATOR_VENDOR_CONFIGURE: u8 = 0x40;
|
const AUTHENTICATOR_VENDOR_CONFIGURE: u8 = 0x40;
|
||||||
|
const AUTHENTICATOR_VENDOR_UPGRADE: u8 = 0x41;
|
||||||
|
const AUTHENTICATOR_VENDOR_UPGRADE_INFO: u8 = 0x42;
|
||||||
const _AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
const _AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
||||||
|
|
||||||
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
||||||
@@ -134,6 +139,16 @@ impl Command {
|
|||||||
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
|
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Command::AUTHENTICATOR_VENDOR_UPGRADE => {
|
||||||
|
let decoded_cbor = cbor_read(&bytes[1..])?;
|
||||||
|
Ok(Command::AuthenticatorVendorUpgrade(
|
||||||
|
AuthenticatorVendorUpgradeParameters::try_from(decoded_cbor)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Command::AUTHENTICATOR_VENDOR_UPGRADE_INFO => {
|
||||||
|
// Parameters are ignored.
|
||||||
|
Ok(Command::AuthenticatorVendorUpgradeInfo)
|
||||||
|
}
|
||||||
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,11 +588,47 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorConfigureParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct AuthenticatorVendorUpgradeParameters {
|
||||||
|
pub address: Option<usize>,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
pub hash: Vec<u8>,
|
||||||
|
pub signature: Option<CoseSignature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
0x01 => address,
|
||||||
|
0x02 => data,
|
||||||
|
0x03 => hash,
|
||||||
|
0x04 => signature,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
let address = address
|
||||||
|
.map(extract_unsigned)
|
||||||
|
.transpose()?
|
||||||
|
.map(|u| u as usize);
|
||||||
|
let data = extract_byte_string(ok_or_missing(data)?)?;
|
||||||
|
let hash = extract_byte_string(ok_or_missing(hash)?)?;
|
||||||
|
let signature = signature.map(CoseSignature::try_from).transpose()?;
|
||||||
|
Ok(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address,
|
||||||
|
data,
|
||||||
|
hash,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::{
|
use super::super::data_formats::{
|
||||||
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
|
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
|
||||||
PublicKeyCredentialUserEntity,
|
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use super::super::ES256_CRED_PARAM;
|
use super::super::ES256_CRED_PARAM;
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -997,7 +1048,7 @@ mod test {
|
|||||||
0x02 => cbor_map! {
|
0x02 => cbor_map! {
|
||||||
0x01 => dummy_cert,
|
0x01 => dummy_cert,
|
||||||
0x02 => dummy_pkey
|
0x02 => dummy_pkey
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
||||||
@@ -1006,8 +1057,81 @@ mod test {
|
|||||||
attestation_material: Some(AuthenticatorAttestationMaterial {
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
certificate: dummy_cert.to_vec(),
|
certificate: dummy_cert.to_vec(),
|
||||||
private_key: dummy_pkey
|
private_key: dummy_pkey
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade() {
|
||||||
|
// Incomplete command
|
||||||
|
let cbor_bytes = vec![Command::AUTHENTICATOR_VENDOR_UPGRADE];
|
||||||
|
let command = Command::deserialize(&cbor_bytes);
|
||||||
|
assert_eq!(command, Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR));
|
||||||
|
|
||||||
|
// Missing data
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
0x01 => 0x1000,
|
||||||
|
0x03 => [0x44; 32],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Missing hash
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
0x01 => 0x1000,
|
||||||
|
0x02 => [0xFF; 0x100],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Valid without address
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
0x02 => [0xFF; 0x100],
|
||||||
|
0x03 => [0x44; 32],
|
||||||
|
0x04 => cbor_map! {
|
||||||
|
"alg" => -7,
|
||||||
|
"signature" => [0x55; 64],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
||||||
|
Ok(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: None,
|
||||||
|
data: vec![0xFF; 0x100],
|
||||||
|
hash: vec![0x44; 32],
|
||||||
|
signature: Some(CoseSignature {
|
||||||
|
algorithm: SignatureAlgorithm::ES256,
|
||||||
|
bytes: [0x55; 64],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Valid without signature
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
0x01 => 0x1000,
|
||||||
|
0x02 => [0xFF; 0x100],
|
||||||
|
0x03 => [0x44; 32],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
|
||||||
|
Ok(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: Some(0x1000),
|
||||||
|
data: vec![0xFF; 0x100],
|
||||||
|
hash: vec![0x44; 32],
|
||||||
|
signature: None,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_vendor_upgrade_info() {
|
||||||
|
let cbor_bytes = [Command::AUTHENTICATOR_VENDOR_UPGRADE_INFO];
|
||||||
|
let command = Command::deserialize(&cbor_bytes);
|
||||||
|
assert_eq!(command, Ok(Command::AuthenticatorVendorUpgradeInfo));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
||||||
pub const AAGUID_LENGTH: usize = 16;
|
pub const AAGUID_LENGTH: usize = 16;
|
||||||
pub const _UPGRADE_PUBLIC_KEY_LENGTH: usize = 77;
|
pub const UPGRADE_PUBLIC_KEY_LENGTH: usize = 77;
|
||||||
|
|
||||||
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
||||||
pub const _UPGRADE_PUBLIC_KEY: &[u8; _UPGRADE_PUBLIC_KEY_LENGTH] =
|
pub const UPGRADE_PUBLIC_KEY: &[u8; UPGRADE_PUBLIC_KEY_LENGTH] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey_cbor.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey_cbor.bin"));
|
||||||
|
|||||||
365
src/ctap/mod.rs
365
src/ctap/mod.rs
@@ -35,7 +35,7 @@ mod token_state;
|
|||||||
use self::client_pin::{ClientPin, PinPermission};
|
use self::client_pin::{ClientPin, PinPermission};
|
||||||
use self::command::{
|
use self::command::{
|
||||||
AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters,
|
AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters,
|
||||||
AuthenticatorVendorConfigureParameters, Command,
|
AuthenticatorVendorConfigureParameters, AuthenticatorVendorUpgradeParameters, Command,
|
||||||
};
|
};
|
||||||
use self::config_command::process_config;
|
use self::config_command::process_config;
|
||||||
use self::credential_management::process_credential_management;
|
use self::credential_management::process_credential_management;
|
||||||
@@ -46,22 +46,25 @@ use self::customization::{
|
|||||||
MAX_RP_IDS_LENGTH, USE_BATCH_ATTESTATION, USE_SIGNATURE_COUNTER,
|
MAX_RP_IDS_LENGTH, USE_BATCH_ATTESTATION, USE_SIGNATURE_COUNTER,
|
||||||
};
|
};
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
|
||||||
GetAssertionExtensions, PackedAttestationStatement, PinUvAuthProtocol,
|
EnterpriseAttestationMode, GetAssertionExtensions, PackedAttestationStatement,
|
||||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
|
PinUvAuthProtocol, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
||||||
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
||||||
|
SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
use self::large_blobs::LargeBlobs;
|
use self::large_blobs::LargeBlobs;
|
||||||
use self::response::{
|
use self::response::{
|
||||||
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
||||||
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
AuthenticatorMakeCredentialResponse, AuthenticatorVendorConfigureResponse,
|
||||||
|
AuthenticatorVendorUpgradeInfoResponse, ResponseData,
|
||||||
};
|
};
|
||||||
use self::status_code::Ctap2StatusCode;
|
use self::status_code::Ctap2StatusCode;
|
||||||
use self::storage::PersistentStore;
|
use self::storage::PersistentStore;
|
||||||
use self::timed_permission::TimedPermission;
|
use self::timed_permission::TimedPermission;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
use self::timed_permission::U2fUserPresenceState;
|
use self::timed_permission::U2fUserPresenceState;
|
||||||
|
use crate::embedded_flash::{UpgradeLocations, UpgradeStorage};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
@@ -71,6 +74,7 @@ use byteorder::{BigEndian, ByteOrder};
|
|||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
use crypto::ecdsa;
|
||||||
use crypto::hmac::{hmac_256, verify_hmac_256};
|
use crypto::hmac::{hmac_256, verify_hmac_256};
|
||||||
use crypto::rng256::Rng256;
|
use crypto::rng256::Rng256;
|
||||||
use crypto::sha256::Sha256;
|
use crypto::sha256::Sha256;
|
||||||
@@ -146,6 +150,56 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the metadata of an upgrade, and checks its correctness.
|
||||||
|
///
|
||||||
|
/// Returns the hash over the upgrade, including partition and some metadata.
|
||||||
|
/// The metadata consists of:
|
||||||
|
/// - 32B upgrade hash (SHA256)
|
||||||
|
/// - 4B timestamp (little endian encoding)
|
||||||
|
/// - 4B partition address (little endian encoding)
|
||||||
|
/// The upgrade hash is computed over the firmware image and all metadata,
|
||||||
|
/// except the hash itself.
|
||||||
|
fn parse_metadata(
|
||||||
|
upgrade_locations: &UpgradeLocations,
|
||||||
|
metadata: &[u8],
|
||||||
|
) -> Result<[u8; 32], Ctap2StatusCode> {
|
||||||
|
const METADATA_LEN: usize = 40;
|
||||||
|
if metadata.len() != METADATA_LEN {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
// The hash implementation handles this in chunks, so no memory issues.
|
||||||
|
let partition_slice = upgrade_locations
|
||||||
|
.read_partition(0, upgrade_locations.partition_length())
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(partition_slice);
|
||||||
|
hasher.update(&metadata[32..METADATA_LEN]);
|
||||||
|
let computed_hash = hasher.finalize();
|
||||||
|
if &computed_hash != array_ref!(metadata, 0, 32) {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
|
}
|
||||||
|
Ok(computed_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies the signature over the given hash.
|
||||||
|
///
|
||||||
|
/// The public key is COSE encoded, and the hash is a SHA256.
|
||||||
|
fn verify_signature(
|
||||||
|
signature: Option<CoseSignature>,
|
||||||
|
public_key_bytes: &[u8],
|
||||||
|
signed_hash: &[u8; 32],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
let signature =
|
||||||
|
ecdsa::Signature::try_from(signature.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?)?;
|
||||||
|
let cbor_public_key = cbor_read(public_key_bytes)?;
|
||||||
|
let cose_key = CoseKey::try_from(cbor_public_key)?;
|
||||||
|
let public_key = ecdsa::PubKey::try_from(cose_key)?;
|
||||||
|
if !public_key.verify_hash_vartime(signed_hash, &signature) {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds data necessary to sign an assertion for a credential.
|
/// Holds data necessary to sign an assertion for a credential.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AssertionInput {
|
pub struct AssertionInput {
|
||||||
@@ -288,6 +342,7 @@ pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(
|
|||||||
// The state initializes to Reset and its timeout, and never goes back to Reset.
|
// The state initializes to Reset and its timeout, and never goes back to Reset.
|
||||||
stateful_command_permission: StatefulPermission,
|
stateful_command_permission: StatefulPermission,
|
||||||
large_blobs: LargeBlobs,
|
large_blobs: LargeBlobs,
|
||||||
|
upgrade_locations: Option<UpgradeLocations>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
||||||
@@ -314,6 +369,7 @@ where
|
|||||||
),
|
),
|
||||||
stateful_command_permission: StatefulPermission::new_reset(now),
|
stateful_command_permission: StatefulPermission::new_reset(now),
|
||||||
large_blobs: LargeBlobs::new(),
|
large_blobs: LargeBlobs::new(),
|
||||||
|
upgrade_locations: UpgradeLocations::new().ok(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +537,10 @@ where
|
|||||||
Command::AuthenticatorVendorConfigure(params) => {
|
Command::AuthenticatorVendorConfigure(params) => {
|
||||||
self.process_vendor_configure(params, cid)
|
self.process_vendor_configure(params, cid)
|
||||||
}
|
}
|
||||||
|
Command::AuthenticatorVendorUpgrade(params) => {
|
||||||
|
self.process_vendor_upgrade(params)
|
||||||
|
}
|
||||||
|
Command::AuthenticatorVendorUpgradeInfo => self.process_vendor_upgrade_info(),
|
||||||
};
|
};
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
||||||
@@ -1140,13 +1200,13 @@ where
|
|||||||
|
|
||||||
let response = match params.attestation_material {
|
let response = match params.attestation_material {
|
||||||
// Only reading values.
|
// Only reading values.
|
||||||
None => AuthenticatorVendorResponse {
|
None => AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: current_cert.is_some(),
|
cert_programmed: current_cert.is_some(),
|
||||||
pkey_programmed: current_priv_key.is_some(),
|
pkey_programmed: current_priv_key.is_some(),
|
||||||
},
|
},
|
||||||
// Device is already fully programmed. We don't leak information.
|
// Device is already fully programmed. We don't leak information.
|
||||||
Some(_) if current_cert.is_some() && current_priv_key.is_some() => {
|
Some(_) if current_cert.is_some() && current_priv_key.is_some() => {
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
}
|
}
|
||||||
@@ -1171,7 +1231,7 @@ where
|
|||||||
self.persistent_store
|
self.persistent_store
|
||||||
.set_attestation_private_key(&data.private_key)?;
|
.set_attestation_private_key(&data.private_key)?;
|
||||||
}
|
}
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
}
|
}
|
||||||
@@ -1192,7 +1252,60 @@ where
|
|||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ResponseData::AuthenticatorVendor(response))
|
Ok(ResponseData::AuthenticatorVendorConfigure(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_vendor_upgrade(
|
||||||
|
&mut self,
|
||||||
|
params: AuthenticatorVendorUpgradeParameters,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
let AuthenticatorVendorUpgradeParameters {
|
||||||
|
address,
|
||||||
|
data,
|
||||||
|
hash,
|
||||||
|
signature,
|
||||||
|
} = params;
|
||||||
|
let upgrade_locations = self
|
||||||
|
.upgrade_locations
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)?;
|
||||||
|
let written_slice = if let Some(address) = address {
|
||||||
|
upgrade_locations
|
||||||
|
.write_partition(address, &data)
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
||||||
|
upgrade_locations
|
||||||
|
.read_partition(address, data.len())
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?
|
||||||
|
} else {
|
||||||
|
// Compares the hash inside the metadata to the actual hash.
|
||||||
|
let upgrade_hash = parse_metadata(upgrade_locations, &data)?;
|
||||||
|
// Only signed firmware images may be fully written.
|
||||||
|
verify_signature(signature, key_material::UPGRADE_PUBLIC_KEY, &upgrade_hash)?;
|
||||||
|
// Write the metadata page after verifying that its hash is signed.
|
||||||
|
upgrade_locations
|
||||||
|
.write_metadata(&data)
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
||||||
|
&upgrade_locations
|
||||||
|
.read_metadata()
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?[..data.len()]
|
||||||
|
};
|
||||||
|
let written_hash = Sha256::hash(written_slice);
|
||||||
|
if hash != written_hash {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
|
}
|
||||||
|
Ok(ResponseData::AuthenticatorVendorUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_vendor_upgrade_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
let upgrade_locations = self
|
||||||
|
.upgrade_locations
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)?;
|
||||||
|
Ok(ResponseData::AuthenticatorVendorUpgradeInfo(
|
||||||
|
AuthenticatorVendorUpgradeInfoResponse {
|
||||||
|
info: upgrade_locations.partition_address() as u32,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_auth_data(
|
pub fn generate_auth_data(
|
||||||
@@ -2833,8 +2946,8 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
Ok(ResponseData::AuthenticatorVendor(
|
Ok(ResponseData::AuthenticatorVendorConfigure(
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: false,
|
cert_programmed: false,
|
||||||
pkey_programmed: false,
|
pkey_programmed: false,
|
||||||
}
|
}
|
||||||
@@ -2856,8 +2969,8 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
Ok(ResponseData::AuthenticatorVendor(
|
Ok(ResponseData::AuthenticatorVendorConfigure(
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
}
|
}
|
||||||
@@ -2894,8 +3007,8 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
Ok(ResponseData::AuthenticatorVendor(
|
Ok(ResponseData::AuthenticatorVendorConfigure(
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
}
|
}
|
||||||
@@ -2928,12 +3041,228 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
Ok(ResponseData::AuthenticatorVendor(
|
Ok(ResponseData::AuthenticatorVendorConfigure(
|
||||||
AuthenticatorVendorResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_metadata() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
// The test buffer starts fully erased with 0xFF bytes.
|
||||||
|
// The compiler issues an incorrect warning.
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut upgrade_locations = ctap_state.upgrade_locations.as_mut().unwrap();
|
||||||
|
|
||||||
|
// Partition of 0x40000 bytes and 8 bytes metadata are hashed.
|
||||||
|
let hashed_data = vec![0xFF; 0x40000 + 8];
|
||||||
|
let expected_hash = Sha256::hash(&hashed_data);
|
||||||
|
let mut metadata = vec![0xFF; 40];
|
||||||
|
metadata[..32].copy_from_slice(&expected_hash);
|
||||||
|
assert_eq!(
|
||||||
|
parse_metadata(upgrade_locations, &metadata),
|
||||||
|
Ok(expected_hash)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Any manipulation of data fails.
|
||||||
|
metadata[32] = 0x88;
|
||||||
|
assert_eq!(
|
||||||
|
parse_metadata(upgrade_locations, &metadata),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
metadata[32] = 0xFF;
|
||||||
|
metadata[0] ^= 0x01;
|
||||||
|
assert_eq!(
|
||||||
|
parse_metadata(upgrade_locations, &metadata),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
metadata[0] ^= 0x01;
|
||||||
|
upgrade_locations.write_partition(0, &[0x88; 1]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_metadata(upgrade_locations, &metadata),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_signature() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
|
let message = [0x44; 64];
|
||||||
|
let signed_hash = Sha256::hash(&message);
|
||||||
|
let signature = private_key.sign_rfc6979::<Sha256>(&message);
|
||||||
|
|
||||||
|
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||||
|
signature.to_bytes(&mut signature_bytes);
|
||||||
|
let cose_signature = CoseSignature {
|
||||||
|
algorithm: SignatureAlgorithm::ES256,
|
||||||
|
bytes: signature_bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_key = private_key.genpk();
|
||||||
|
let mut public_key_bytes = vec![];
|
||||||
|
cbor_write(
|
||||||
|
cbor::Value::from(CoseKey::from(public_key)),
|
||||||
|
&mut public_key_bytes,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
verify_signature(
|
||||||
|
Some(cose_signature.clone()),
|
||||||
|
&public_key_bytes,
|
||||||
|
&signed_hash
|
||||||
|
),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
verify_signature(Some(cose_signature.clone()), &public_key_bytes, &[0x55; 32]),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
public_key_bytes[0] ^= 0x01;
|
||||||
|
assert_eq!(
|
||||||
|
verify_signature(Some(cose_signature), &public_key_bytes, &signed_hash),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
|
||||||
|
);
|
||||||
|
public_key_bytes[0] ^= 0x01;
|
||||||
|
assert_eq!(
|
||||||
|
verify_signature(None, &public_key_bytes, &signed_hash),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
|
);
|
||||||
|
signature_bytes[0] ^= 0x01;
|
||||||
|
let cose_signature = CoseSignature {
|
||||||
|
algorithm: SignatureAlgorithm::ES256,
|
||||||
|
bytes: signature_bytes,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
verify_signature(Some(cose_signature), &public_key_bytes, &signed_hash),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade() {
|
||||||
|
// The test partition storage has size 0x40000.
|
||||||
|
// The test metadata storage has size 0x1000.
|
||||||
|
// The test identifier matches partition B.
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
const METADATA_LEN: usize = 40;
|
||||||
|
|
||||||
|
let data = vec![0xFF; 0x1000];
|
||||||
|
let hash = Sha256::hash(&data).to_vec();
|
||||||
|
let upgrade_locations = ctap_state.upgrade_locations.as_ref().unwrap();
|
||||||
|
let partition_length = upgrade_locations.partition_length();
|
||||||
|
let mut signed_over_data = upgrade_locations
|
||||||
|
.read_partition(0, partition_length)
|
||||||
|
.unwrap()
|
||||||
|
.to_vec();
|
||||||
|
signed_over_data.extend(&[0xFF; METADATA_LEN - 32]);
|
||||||
|
let signed_hash = Sha256::hash(&signed_over_data);
|
||||||
|
let mut metadata = vec![0xFF; METADATA_LEN];
|
||||||
|
metadata[..32].copy_from_slice(&signed_hash);
|
||||||
|
let metadata_hash = Sha256::hash(&metadata).to_vec();
|
||||||
|
|
||||||
|
let signature = private_key.sign_rfc6979::<Sha256>(&signed_over_data);
|
||||||
|
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
|
||||||
|
signature.to_bytes(&mut signature_bytes);
|
||||||
|
let cose_signature = CoseSignature {
|
||||||
|
algorithm: SignatureAlgorithm::ES256,
|
||||||
|
bytes: signature_bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write to partition and metadata.
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: Some(0x20000),
|
||||||
|
data: data.clone(),
|
||||||
|
hash: hash.clone(),
|
||||||
|
signature: None,
|
||||||
|
});
|
||||||
|
assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade));
|
||||||
|
|
||||||
|
// We can't inject a public key for our known private key, so the last upgrade step fails.
|
||||||
|
// verify_signature is separately tested for that reason.
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: None,
|
||||||
|
data: metadata.clone(),
|
||||||
|
hash: metadata_hash.clone(),
|
||||||
|
signature: Some(cose_signature.clone()),
|
||||||
|
});
|
||||||
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
||||||
|
|
||||||
|
// Write metadata of a wrong size.
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: None,
|
||||||
|
data: metadata[..METADATA_LEN - 1].to_vec(),
|
||||||
|
hash: metadata_hash,
|
||||||
|
signature: Some(cose_signature),
|
||||||
|
});
|
||||||
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
||||||
|
|
||||||
|
// Write outside of the partition.
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: Some(0x40000),
|
||||||
|
data: data.clone(),
|
||||||
|
hash,
|
||||||
|
signature: None,
|
||||||
|
});
|
||||||
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER));
|
||||||
|
|
||||||
|
// Write a bad hash.
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: Some(0x20000),
|
||||||
|
data,
|
||||||
|
hash: [0xEE; 32].to_vec(),
|
||||||
|
signature: None,
|
||||||
|
});
|
||||||
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade_no_second_partition() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
ctap_state.upgrade_locations = None;
|
||||||
|
|
||||||
|
let data = vec![0xFF; 0x1000];
|
||||||
|
let hash = Sha256::hash(&data).to_vec();
|
||||||
|
let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters {
|
||||||
|
address: Some(0),
|
||||||
|
data,
|
||||||
|
hash,
|
||||||
|
signature: None,
|
||||||
|
});
|
||||||
|
assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade_info() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
let partition_address = ctap_state
|
||||||
|
.upgrade_locations
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.partition_address();
|
||||||
|
|
||||||
|
let upgrade_info_reponse = ctap_state.process_vendor_upgrade_info();
|
||||||
|
assert_eq!(
|
||||||
|
upgrade_info_reponse,
|
||||||
|
Ok(ResponseData::AuthenticatorVendorUpgradeInfo(
|
||||||
|
AuthenticatorVendorUpgradeInfoResponse {
|
||||||
|
info: partition_address as u32,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ pub enum ResponseData {
|
|||||||
AuthenticatorCredentialManagement(Option<AuthenticatorCredentialManagementResponse>),
|
AuthenticatorCredentialManagement(Option<AuthenticatorCredentialManagementResponse>),
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
AuthenticatorLargeBlobs(Option<AuthenticatorLargeBlobsResponse>),
|
AuthenticatorLargeBlobs(Option<AuthenticatorLargeBlobsResponse>),
|
||||||
// TODO(kaczmarczyck) dummy, extend
|
|
||||||
AuthenticatorConfig,
|
AuthenticatorConfig,
|
||||||
AuthenticatorVendor(AuthenticatorVendorResponse),
|
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureResponse),
|
||||||
|
AuthenticatorVendorUpgrade,
|
||||||
|
AuthenticatorVendorUpgradeInfo(AuthenticatorVendorUpgradeInfoResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseData> for Option<cbor::Value> {
|
impl From<ResponseData> for Option<cbor::Value> {
|
||||||
@@ -53,7 +54,9 @@ impl From<ResponseData> for Option<cbor::Value> {
|
|||||||
ResponseData::AuthenticatorSelection => None,
|
ResponseData::AuthenticatorSelection => None,
|
||||||
ResponseData::AuthenticatorLargeBlobs(data) => data.map(|d| d.into()),
|
ResponseData::AuthenticatorLargeBlobs(data) => data.map(|d| d.into()),
|
||||||
ResponseData::AuthenticatorConfig => None,
|
ResponseData::AuthenticatorConfig => None,
|
||||||
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
|
ResponseData::AuthenticatorVendorConfigure(data) => Some(data.into()),
|
||||||
|
ResponseData::AuthenticatorVendorUpgrade => None,
|
||||||
|
ResponseData::AuthenticatorVendorUpgradeInfo(data) => Some(data.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,14 +303,14 @@ impl From<AuthenticatorCredentialManagementResponse> for cbor::Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct AuthenticatorVendorResponse {
|
pub struct AuthenticatorVendorConfigureResponse {
|
||||||
pub cert_programmed: bool,
|
pub cert_programmed: bool,
|
||||||
pub pkey_programmed: bool,
|
pub pkey_programmed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AuthenticatorVendorResponse> for cbor::Value {
|
impl From<AuthenticatorVendorConfigureResponse> for cbor::Value {
|
||||||
fn from(vendor_response: AuthenticatorVendorResponse) -> Self {
|
fn from(vendor_response: AuthenticatorVendorConfigureResponse) -> Self {
|
||||||
let AuthenticatorVendorResponse {
|
let AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed,
|
cert_programmed,
|
||||||
pkey_programmed,
|
pkey_programmed,
|
||||||
} = vendor_response;
|
} = vendor_response;
|
||||||
@@ -319,6 +322,21 @@ impl From<AuthenticatorVendorResponse> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct AuthenticatorVendorUpgradeInfoResponse {
|
||||||
|
pub info: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthenticatorVendorUpgradeInfoResponse> for cbor::Value {
|
||||||
|
fn from(vendor_upgrade_info_response: AuthenticatorVendorUpgradeInfoResponse) -> Self {
|
||||||
|
let AuthenticatorVendorUpgradeInfoResponse { info } = vendor_upgrade_info_response;
|
||||||
|
|
||||||
|
cbor_map_options! {
|
||||||
|
0x01 => info as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::{PackedAttestationStatement, PublicKeyCredentialType};
|
use super::super::data_formats::{PackedAttestationStatement, PublicKeyCredentialType};
|
||||||
@@ -622,7 +640,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_vendor_response_into_cbor() {
|
fn test_vendor_response_into_cbor() {
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
ResponseData::AuthenticatorVendor(AuthenticatorVendorResponse {
|
ResponseData::AuthenticatorVendorConfigure(AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
pkey_programmed: false,
|
pkey_programmed: false,
|
||||||
})
|
})
|
||||||
@@ -635,7 +653,7 @@ mod test {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
ResponseData::AuthenticatorVendor(AuthenticatorVendorResponse {
|
ResponseData::AuthenticatorVendorConfigure(AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: false,
|
cert_programmed: false,
|
||||||
pkey_programmed: true,
|
pkey_programmed: true,
|
||||||
})
|
})
|
||||||
@@ -648,4 +666,22 @@ mod test {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade_into_cbor() {
|
||||||
|
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorVendorUpgrade.into();
|
||||||
|
assert_eq!(response_cbor, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_upgrade_info_into_cbor() {
|
||||||
|
let vendor_upgrade_info_response =
|
||||||
|
AuthenticatorVendorUpgradeInfoResponse { info: 0x00060000 };
|
||||||
|
let response_cbor: Option<cbor::Value> =
|
||||||
|
ResponseData::AuthenticatorVendorUpgradeInfo(vendor_upgrade_info_response).into();
|
||||||
|
let expected_cbor = cbor_map! {
|
||||||
|
0x01 => 0x00060000,
|
||||||
|
};
|
||||||
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user