Merge branch 'master' into authenticator-selection

This commit is contained in:
kaczmarczyck
2020-06-22 12:44:36 +02:00
committed by GitHub
10 changed files with 626 additions and 192 deletions

View File

@@ -131,26 +131,31 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut param_map = extract_map(cbor_value)?;
destructure_cbor_map! {
let {
1 => client_data_hash,
2 => rp,
3 => user,
4 => cred_param_vec,
5 => exclude_list,
6 => extensions,
7 => options,
8 => pin_uv_auth_param,
9 => pin_uv_auth_protocol,
} = extract_map(cbor_value)?;
}
let client_data_hash =
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?;
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(rp)?)?;
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(user)?)?;
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(
param_map.remove(&cbor_unsigned!(2)),
)?)?;
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(
param_map.remove(&cbor_unsigned!(3)),
)?)?;
let cred_param_vec = extract_array(ok_or_missing(param_map.remove(&cbor_unsigned!(4)))?)?;
let cred_param_vec = extract_array(ok_or_missing(cred_param_vec)?)?;
let pub_key_cred_params = cred_param_vec
.into_iter()
.map(PublicKeyCredentialParameter::try_from)
.collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
let exclude_list = match param_map.remove(&cbor_unsigned!(5)) {
let exclude_list = match exclude_list {
Some(entry) => {
let exclude_list_vec = extract_array(entry)?;
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len());
@@ -164,12 +169,11 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
None => None,
};
let extensions = param_map
.remove(&cbor_unsigned!(6))
let extensions = extensions
.map(MakeCredentialExtensions::try_from)
.transpose()?;
let options = match param_map.remove(&cbor_unsigned!(7)) {
let options = match options {
Some(entry) => MakeCredentialOptions::try_from(entry)?,
None => MakeCredentialOptions {
rk: false,
@@ -177,15 +181,8 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
},
};
let pin_uv_auth_param = param_map
.remove(&cbor_unsigned!(8))
.map(extract_byte_string)
.transpose()?;
let pin_uv_auth_protocol = param_map
.remove(&cbor_unsigned!(9))
.map(extract_unsigned)
.transpose()?;
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
Ok(AuthenticatorMakeCredentialParameters {
client_data_hash,
@@ -217,14 +214,22 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut param_map = extract_map(cbor_value)?;
destructure_cbor_map! {
let {
1 => rp_id,
2 => client_data_hash,
3 => allow_list,
4 => extensions,
5 => options,
6 => pin_uv_auth_param,
7 => pin_uv_auth_protocol,
} = extract_map(cbor_value)?;
}
let rp_id = extract_text_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?;
let client_data_hash =
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
let allow_list = match param_map.remove(&cbor_unsigned!(3)) {
let allow_list = match allow_list {
Some(entry) => {
let allow_list_vec = extract_array(entry)?;
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len());
@@ -238,12 +243,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
None => None,
};
let extensions = param_map
.remove(&cbor_unsigned!(4))
let extensions = extensions
.map(GetAssertionExtensions::try_from)
.transpose()?;
let options = match param_map.remove(&cbor_unsigned!(5)) {
let options = match options {
Some(entry) => GetAssertionOptions::try_from(entry)?,
None => GetAssertionOptions {
up: true,
@@ -251,15 +255,8 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
},
};
let pin_uv_auth_param = param_map
.remove(&cbor_unsigned!(6))
.map(extract_byte_string)
.transpose()?;
let pin_uv_auth_protocol = param_map
.remove(&cbor_unsigned!(7))
.map(extract_unsigned)
.transpose()?;
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
Ok(AuthenticatorGetAssertionParameters {
rp_id,
@@ -287,33 +284,26 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut param_map = extract_map(cbor_value)?;
destructure_cbor_map! {
let {
1 => pin_protocol,
2 => sub_command,
3 => key_agreement,
4 => pin_auth,
5 => new_pin_enc,
6 => pin_hash_enc,
} = extract_map(cbor_value)?;
}
let pin_protocol = extract_unsigned(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
let sub_command =
ClientPinSubCommand::try_from(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
let key_agreement = param_map
.remove(&cbor_unsigned!(3))
let pin_protocol = extract_unsigned(ok_or_missing(pin_protocol)?)?;
let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?;
let key_agreement = key_agreement
.map(extract_map)
.transpose()?
.map(|x| CoseKey(x));
let pin_auth = param_map
.remove(&cbor_unsigned!(4))
.map(extract_byte_string)
.transpose()?;
let new_pin_enc = param_map
.remove(&cbor_unsigned!(5))
.map(extract_byte_string)
.transpose()?;
let pin_hash_enc = param_map
.remove(&cbor_unsigned!(6))
.map(extract_byte_string)
.transpose()?;
let pin_auth = pin_auth.map(extract_byte_string).transpose()?;
let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?;
let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?;
Ok(AuthenticatorClientPinParameters {
pin_protocol,

View File

@@ -31,16 +31,18 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialRpEntity {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut rp_map = extract_map(cbor_value)?;
let rp_id = extract_text_string(ok_or_missing(rp_map.remove(&cbor_text!("id")))?)?;
let rp_name = rp_map
.remove(&cbor_text!("name"))
.map(extract_text_string)
.transpose()?;
let rp_icon = rp_map
.remove(&cbor_text!("icon"))
.map(extract_text_string)
.transpose()?;
destructure_cbor_map! {
let {
"id" => rp_id,
"icon" => rp_icon,
"name" => rp_name,
} = extract_map(cbor_value)?;
}
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
let rp_name = rp_name.map(extract_text_string).transpose()?;
let rp_icon = rp_icon.map(extract_text_string).transpose()?;
Ok(Self {
rp_id,
rp_name,
@@ -62,20 +64,20 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialUserEntity {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut user_map = extract_map(cbor_value)?;
let user_id = extract_byte_string(ok_or_missing(user_map.remove(&cbor_text!("id")))?)?;
let user_name = user_map
.remove(&cbor_text!("name"))
.map(extract_text_string)
.transpose()?;
let user_display_name = user_map
.remove(&cbor_text!("displayName"))
.map(extract_text_string)
.transpose()?;
let user_icon = user_map
.remove(&cbor_text!("icon"))
.map(extract_text_string)
.transpose()?;
destructure_cbor_map! {
let {
"id" => user_id,
"icon" => user_icon,
"name" => user_name,
"displayName" => user_display_name,
} = extract_map(cbor_value)?;
}
let user_id = extract_byte_string(ok_or_missing(user_id)?)?;
let user_name = user_name.map(extract_text_string).transpose()?;
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
let user_icon = user_icon.map(extract_text_string).transpose()?;
Ok(Self {
user_id,
user_name,
@@ -141,13 +143,15 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialParameter {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut cred_param_map = extract_map(cbor_value)?;
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_param_map.remove(&cbor_text!("type")),
)?)?;
let alg = SignatureAlgorithm::try_from(ok_or_missing(
cred_param_map.remove(&cbor_text!("alg")),
)?)?;
destructure_cbor_map! {
let {
"alg" => alg,
"type" => cred_type,
} = extract_map(cbor_value)?;
}
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(cred_type)?)?;
let alg = SignatureAlgorithm::try_from(ok_or_missing(alg)?)?;
Ok(Self { cred_type, alg })
}
}
@@ -209,12 +213,17 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialDescriptor {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut cred_desc_map = extract_map(cbor_value)?;
let key_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_desc_map.remove(&cbor_text!("type")),
)?)?;
let key_id = extract_byte_string(ok_or_missing(cred_desc_map.remove(&cbor_text!("id")))?)?;
let transports = match cred_desc_map.remove(&cbor_text!("transports")) {
destructure_cbor_map! {
let {
"id" => key_id,
"type" => key_type,
"transports" => transports,
} = extract_map(cbor_value)?;
}
let key_type = PublicKeyCredentialType::try_from(ok_or_missing(key_type)?)?;
let key_id = extract_byte_string(ok_or_missing(key_id)?)?;
let transports = match transports {
Some(exclude_entry) => {
let transport_vec = extract_array(exclude_entry)?;
let transports = transport_vec
@@ -225,6 +234,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialDescriptor {
}
None => None,
};
Ok(Self {
key_type,
key_id,
@@ -253,12 +263,15 @@ impl TryFrom<cbor::Value> for MakeCredentialExtensions {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut extensions_map = extract_map(cbor_value)?;
let hmac_secret = extensions_map
.remove(&cbor_text!("hmac-secret"))
.map_or(Ok(false), extract_bool)?;
let cred_protect = extensions_map
.remove(&cbor_text!("credProtect"))
destructure_cbor_map! {
let {
"credProtect" => cred_protect,
"hmac-secret" => hmac_secret,
} = extract_map(cbor_value)?;
}
let hmac_secret = hmac_secret.map_or(Ok(false), extract_bool)?;
let cred_protect = cred_protect
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
Ok(Self {
@@ -277,9 +290,13 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut extensions_map = extract_map(cbor_value)?;
let hmac_secret = extensions_map
.remove(&cbor_text!("hmac-secret"))
destructure_cbor_map! {
let {
"hmac-secret" => hmac_secret,
} = extract_map(cbor_value)?;
}
let hmac_secret = hmac_secret
.map(GetAssertionHmacSecretInput::try_from)
.transpose()?;
Ok(Self { hmac_secret })
@@ -297,10 +314,17 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut input_map = extract_map(cbor_value)?;
let cose_key = extract_map(ok_or_missing(input_map.remove(&cbor_unsigned!(1)))?)?;
let salt_enc = extract_byte_string(ok_or_missing(input_map.remove(&cbor_unsigned!(2)))?)?;
let salt_auth = extract_byte_string(ok_or_missing(input_map.remove(&cbor_unsigned!(3)))?)?;
destructure_cbor_map! {
let {
1 => cose_key,
2 => salt_enc,
3 => salt_auth,
} = extract_map(cbor_value)?;
}
let cose_key = extract_map(ok_or_missing(cose_key)?)?;
let salt_enc = extract_byte_string(ok_or_missing(salt_enc)?)?;
let salt_auth = extract_byte_string(ok_or_missing(salt_auth)?)?;
Ok(Self {
key_agreement: CoseKey(cose_key),
salt_enc,
@@ -320,17 +344,24 @@ impl TryFrom<cbor::Value> for MakeCredentialOptions {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut options_map = extract_map(cbor_value)?;
let rk = match options_map.remove(&cbor_text!("rk")) {
destructure_cbor_map! {
let {
"rk" => rk,
"up" => up,
"uv" => uv,
} = extract_map(cbor_value)?;
}
let rk = match rk {
Some(options_entry) => extract_bool(options_entry)?,
None => false,
};
if let Some(options_entry) = options_map.remove(&cbor_text!("up")) {
if let Some(options_entry) = up {
if !extract_bool(options_entry)? {
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
}
}
let uv = match options_map.remove(&cbor_text!("uv")) {
let uv = match uv {
Some(options_entry) => extract_bool(options_entry)?,
None => false,
};
@@ -348,17 +379,24 @@ impl TryFrom<cbor::Value> for GetAssertionOptions {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
let mut options_map = extract_map(cbor_value)?;
if let Some(options_entry) = options_map.remove(&cbor_text!("rk")) {
destructure_cbor_map! {
let {
"rk" => rk,
"up" => up,
"uv" => uv,
} = extract_map(cbor_value)?;
}
if let Some(options_entry) = rk {
// This is only for returning the correct status code.
extract_bool(options_entry)?;
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
}
let up = match options_map.remove(&cbor_text!("up")) {
let up = match up {
Some(options_entry) => extract_bool(options_entry)?,
None => true,
};
let uv = match options_map.remove(&cbor_text!("uv")) {
let uv = match uv {
Some(options_entry) => extract_bool(options_entry)?,
None => false,
};
@@ -501,27 +539,33 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
use PublicKeyCredentialSourceField::*;
let mut map = extract_map(cbor_value)?;
let credential_id = extract_byte_string(ok_or_missing(map.remove(&CredentialId.into()))?)?;
let private_key = extract_byte_string(ok_or_missing(map.remove(&PrivateKey.into()))?)?;
use PublicKeyCredentialSourceField::{
CredProtectPolicy, CredRandom, CredentialId, OtherUi, PrivateKey, RpId, UserHandle,
};
destructure_cbor_map! {
let {
CredentialId => credential_id,
PrivateKey => private_key,
RpId => rp_id,
UserHandle => user_handle,
OtherUi => other_ui,
CredRandom => cred_random,
CredProtectPolicy => cred_protect_policy,
} = extract_map(cbor_value)?;
}
let credential_id = extract_byte_string(ok_or_missing(credential_id)?)?;
let private_key = extract_byte_string(ok_or_missing(private_key)?)?;
if private_key.len() != 32 {
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
}
let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32))
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
let rp_id = extract_text_string(ok_or_missing(map.remove(&RpId.into()))?)?;
let user_handle = extract_byte_string(ok_or_missing(map.remove(&UserHandle.into()))?)?;
let other_ui = map
.remove(&OtherUi.into())
.map(extract_text_string)
.transpose()?;
let cred_random = map
.remove(&CredRandom.into())
.map(extract_byte_string)
.transpose()?;
let cred_protect_policy = map
.remove(&CredProtectPolicy.into())
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
let other_ui = other_ui.map(extract_text_string).transpose()?;
let cred_random = cred_random.map(extract_byte_string).transpose()?;
let cred_protect_policy = cred_protect_policy
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
// We don't return whether there were unknown fields in the CBOR value. This means that
@@ -599,27 +643,37 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
type Error = Ctap2StatusCode;
fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> {
let mut cose_map = cose_key.0;
let key_type = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(1)))?)?;
destructure_cbor_map! {
let {
1 => key_type,
3 => algorithm,
-1 => curve,
-2 => x_bytes,
-3 => y_bytes,
} = cose_key.0;
}
let key_type = extract_integer(ok_or_missing(key_type)?)?;
if key_type != EC2_KEY_TYPE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let algorithm = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(3)))?)?;
let algorithm = extract_integer(ok_or_missing(algorithm)?)?;
if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let curve = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(-1)))?)?;
let curve = extract_integer(ok_or_missing(curve)?)?;
if curve != P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let x_bytes = extract_byte_string(ok_or_missing(cose_map.remove(&cbor_int!(-2)))?)?;
let x_bytes = extract_byte_string(ok_or_missing(x_bytes)?)?;
if x_bytes.len() != ecdh::NBYTES {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
let y_bytes = extract_byte_string(ok_or_missing(cose_map.remove(&cbor_int!(-3)))?)?;
let y_bytes = extract_byte_string(ok_or_missing(y_bytes)?)?;
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)

View File

@@ -38,7 +38,6 @@ use self::data_formats::{
SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
use self::response::{
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
@@ -531,7 +530,7 @@ where
};
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
auth_data.extend(AAGUID);
auth_data.extend(self.persistent_store.aaguid()?);
// The length is fixed to 0x20 or 0x70 and fits one byte.
if credential_id.len() > 0xFF {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
@@ -556,18 +555,25 @@ where
let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash);
let (signature, x5c) = if USE_BATCH_ATTESTATION {
let attestation_key =
crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap();
(
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
Some(vec![ATTESTATION_CERTIFICATE.to_vec()]),
)
} else {
(
// We currently use the presence of the attestation private key in the persistent storage to
// decide whether batch attestation is needed.
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
Some(attestation_private_key) => {
let attestation_key =
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
let attestation_certificate = self
.persistent_store
.attestation_certificate()?
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
(
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
Some(vec![attestation_certificate]),
)
}
None => (
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
None,
)
),
};
let attestation_statement = PackedAttestationStatement {
alg: SignatureAlgorithm::ES256 as i64,
@@ -794,7 +800,7 @@ where
String::from(FIDO2_VERSION_STRING),
],
extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID,
aaguid: *self.persistent_store.aaguid()?,
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![
@@ -1160,7 +1166,7 @@ mod test {
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0x03, 0x50,
]);
expected_response.extend(AAGUID);
expected_response.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(&[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
@@ -1259,7 +1265,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1296,7 +1302,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1439,7 +1445,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],

View File

@@ -67,5 +67,11 @@ pub enum Ctap2StatusCode {
// CTAP2_ERR_VENDOR_FIRST = 0xF0,
CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG = 0xF0,
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR = 0xF1,
/// An internal invariant is broken.
///
/// This type of error is unexpected and the current state is undefined.
CTAP2_ERR_VENDOR_INTERNAL_ERROR = 0xF2,
CTAP2_ERR_VENDOR_LAST = 0xFF,
}

View File

@@ -15,7 +15,7 @@
use crate::crypto::rng256::Rng256;
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::PIN_AUTH_LENGTH;
use crate::ctap::{key_material, PIN_AUTH_LENGTH, USE_BATCH_ATTESTATION};
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryInto;
@@ -56,9 +56,14 @@ const GLOBAL_SIGNATURE_COUNTER: usize = 1;
const MASTER_KEYS: usize = 2;
const PIN_HASH: usize = 3;
const PIN_RETRIES: usize = 4;
const NUM_TAGS: usize = 5;
const ATTESTATION_PRIVATE_KEY: usize = 5;
const ATTESTATION_CERTIFICATE: usize = 6;
const AAGUID: usize = 7;
const NUM_TAGS: usize = 8;
const MAX_PIN_RETRIES: u8 = 6;
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
const AAGUID_LENGTH: usize = 16;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Key {
@@ -73,6 +78,9 @@ enum Key {
MasterKeys,
PinHash,
PinRetries,
AttestationPrivateKey,
AttestationCertificate,
Aaguid,
}
pub struct MasterKeys<'a> {
@@ -124,6 +132,9 @@ impl StoreConfig for Config {
MASTER_KEYS => add(Key::MasterKeys),
PIN_HASH => add(Key::PinHash),
PIN_RETRIES => add(Key::PinRetries),
ATTESTATION_PRIVATE_KEY => add(Key::AttestationPrivateKey),
ATTESTATION_CERTIFICATE => add(Key::AttestationCertificate),
AAGUID => add(Key::Aaguid),
_ => debug_assert!(false),
}
}
@@ -197,6 +208,20 @@ impl PersistentStore {
})
.unwrap();
}
// The following 3 entries are meant to be written by vendor-specific commands.
if USE_BATCH_ATTESTATION {
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
.unwrap();
}
if self.store.find_one(&Key::AttestationCertificate).is_none() {
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
}
}
if self.store.find_one(&Key::Aaguid).is_none() {
self.set_aaguid(key_material::AAGUID).unwrap();
}
}
pub fn find_credential(
@@ -395,10 +420,88 @@ impl PersistentStore {
.unwrap();
}
pub fn attestation_private_key(
&self,
) -> Result<Option<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::AttestationPrivateKey) {
None => return Ok(None),
Some((_, entry)) => entry.data,
};
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(Some(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH)))
}
pub fn set_attestation_private_key(
&mut self,
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: ATTESTATION_PRIVATE_KEY,
data: attestation_private_key,
sensitive: false,
};
match self.store.find_one(&Key::AttestationPrivateKey) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn attestation_certificate(&self) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::AttestationCertificate) {
None => return Ok(None),
Some((_, entry)) => entry.data,
};
Ok(Some(data.to_vec()))
}
pub fn set_attestation_certificate(
&mut self,
attestation_certificate: &[u8],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: ATTESTATION_CERTIFICATE,
data: attestation_certificate,
sensitive: false,
};
match self.store.find_one(&Key::AttestationCertificate) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::Aaguid)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let data = entry.data;
if data.len() != AAGUID_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(array_ref!(data, 0, AAGUID_LENGTH))
}
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: AAGUID,
data: aaguid,
sensitive: false,
};
match self.store.find_one(&Key::Aaguid) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn reset(&mut self, rng: &mut impl Rng256) {
loop {
let index = {
let mut iter = self.store.iter();
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
match iter.next() {
None => break,
Some((index, _)) => index,
@@ -420,6 +523,13 @@ impl From<StoreError> for Ctap2StatusCode {
}
}
fn should_reset(entry: &StoreEntry<'_>) -> bool {
match entry.tag {
ATTESTATION_PRIVATE_KEY | ATTESTATION_CERTIFICATE | AAGUID => false,
_ => true,
}
}
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
let cbor = cbor::read(data).ok()?;
cbor.try_into().ok()
@@ -745,4 +855,41 @@ mod test {
persistent_store.reset_pin_retries();
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
}
#[test]
fn test_persistent_keys() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
// Make sure the attestation are absent. There is no batch attestation in tests.
assert!(persistent_store
.attestation_private_key()
.unwrap()
.is_none());
assert!(persistent_store
.attestation_certificate()
.unwrap()
.is_none());
// Make sure the persistent keys are initialized.
persistent_store
.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
.unwrap();
persistent_store
.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
// The persistent keys stay initialized and preserve their value after a reset.
persistent_store.reset(&mut rng);
assert_eq!(
persistent_store.attestation_private_key().unwrap().unwrap(),
key_material::ATTESTATION_PRIVATE_KEY
);
assert_eq!(
persistent_store.attestation_certificate().unwrap().unwrap(),
key_material::ATTESTATION_CERTIFICATE
);
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
}
}