Merge branch 'develop' into always-uv
This commit is contained in:
@@ -125,6 +125,9 @@ a few things you can personalize:
|
|||||||
length.
|
length.
|
||||||
1. Increase the `MAX_CRED_BLOB_LENGTH` in `ctap/mod.rs`, if you expect blobs
|
1. Increase the `MAX_CRED_BLOB_LENGTH` in `ctap/mod.rs`, if you expect blobs
|
||||||
bigger than the default value.
|
bigger than the default value.
|
||||||
|
1. Implement enterprise attestation. This can be as easy as setting
|
||||||
|
ENTERPRISE_ATTESTATION_MODE in `ctap/mod.rs`. If you want to use a different
|
||||||
|
attestation type than batch attestation, you have to implement it first.
|
||||||
1. If a certification (additional to FIDO's) requires that all requests are
|
1. If a certification (additional to FIDO's) requires that all requests are
|
||||||
protected with user verification, set `ENFORCE_ALWAYS_UV` in
|
protected with user verification, set `ENFORCE_ALWAYS_UV` in
|
||||||
`ctap/config_mod.rs` to `true`.
|
`ctap/config_mod.rs` to `true`.
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ pub struct AuthenticatorMakeCredentialParameters {
|
|||||||
pub options: MakeCredentialOptions,
|
pub options: MakeCredentialOptions,
|
||||||
pub pin_uv_auth_param: Option<Vec<u8>>,
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
pub pin_uv_auth_protocol: Option<u64>,
|
pub pin_uv_auth_protocol: Option<u64>,
|
||||||
pub enterprise_attestation: Option<bool>,
|
pub enterprise_attestation: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
||||||
@@ -219,7 +219,7 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
|
|
||||||
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).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()?;
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
let enterprise_attestation = enterprise_attestation.map(extract_bool).transpose()?;
|
let enterprise_attestation = enterprise_attestation.map(extract_unsigned).transpose()?;
|
||||||
|
|
||||||
Ok(AuthenticatorMakeCredentialParameters {
|
Ok(AuthenticatorMakeCredentialParameters {
|
||||||
client_data_hash,
|
client_data_hash,
|
||||||
@@ -601,7 +601,7 @@ mod test {
|
|||||||
0x05 => cbor_array![],
|
0x05 => cbor_array![],
|
||||||
0x08 => vec![0x12, 0x34],
|
0x08 => vec![0x12, 0x34],
|
||||||
0x09 => 1,
|
0x09 => 1,
|
||||||
0x0A => true,
|
0x0A => 2,
|
||||||
};
|
};
|
||||||
let returned_make_credential_parameters =
|
let returned_make_credential_parameters =
|
||||||
AuthenticatorMakeCredentialParameters::try_from(cbor_value).unwrap();
|
AuthenticatorMakeCredentialParameters::try_from(cbor_value).unwrap();
|
||||||
@@ -635,7 +635,7 @@ mod test {
|
|||||||
options,
|
options,
|
||||||
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
||||||
pin_uv_auth_protocol: Some(1),
|
pin_uv_auth_protocol: Some(1),
|
||||||
enterprise_attestation: Some(true),
|
enterprise_attestation: Some(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -18,9 +18,21 @@ use super::pin_protocol_v1::PinProtocolV1;
|
|||||||
use super::response::ResponseData;
|
use super::response::ResponseData;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
use super::storage::PersistentStore;
|
||||||
use super::{check_pin_uv_auth_protocol, ENFORCE_ALWAYS_UV};
|
use super::{check_pin_uv_auth_protocol, ENFORCE_ALWAYS_UV, ENTERPRISE_ATTESTATION_MODE};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
|
||||||
|
/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig.
|
||||||
|
fn process_enable_enterprise_attestation(
|
||||||
|
persistent_store: &mut PersistentStore,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
||||||
|
persistent_store.enable_enterprise_attestation()?;
|
||||||
|
Ok(ResponseData::AuthenticatorConfig)
|
||||||
|
} else {
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes the subcommand toggleAlwaysUv for AuthenticatorConfig.
|
/// Processes the subcommand toggleAlwaysUv for AuthenticatorConfig.
|
||||||
fn process_toggle_always_uv(
|
fn process_toggle_always_uv(
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
@@ -100,6 +112,9 @@ pub fn process_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match sub_command {
|
match sub_command {
|
||||||
|
ConfigSubCommand::EnableEnterpriseAttestation => {
|
||||||
|
process_enable_enterprise_attestation(persistent_store)
|
||||||
|
}
|
||||||
ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(persistent_store),
|
ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(persistent_store),
|
||||||
ConfigSubCommand::SetMinPinLength => {
|
ConfigSubCommand::SetMinPinLength => {
|
||||||
if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params {
|
if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params {
|
||||||
@@ -117,6 +132,34 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_enable_enterprise_attestation() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
|
||||||
|
let config_params = AuthenticatorConfigParameters {
|
||||||
|
sub_command: ConfigSubCommand::EnableEnterpriseAttestation,
|
||||||
|
sub_command_params: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let config_response =
|
||||||
|
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
|
||||||
|
|
||||||
|
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
||||||
|
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
|
||||||
|
assert_eq!(persistent_store.enterprise_attestation(), Ok(true));
|
||||||
|
} else {
|
||||||
|
assert_eq!(
|
||||||
|
config_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_toggle_always_uv() {
|
fn test_process_toggle_always_uv() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
|
|||||||
@@ -105,6 +105,22 @@ fn enumerate_credentials_response(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the token permissions have the correct associated RP ID.
|
||||||
|
///
|
||||||
|
/// Either no RP ID is associated, or the RP ID matches the stored credential.
|
||||||
|
fn check_rp_id_permissions(
|
||||||
|
persistent_store: &mut PersistentStore,
|
||||||
|
pin_protocol_v1: &mut PinProtocolV1,
|
||||||
|
credential_id: &[u8],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
// Pre-check a sufficient condition before calling the store.
|
||||||
|
if pin_protocol_v1.has_no_rp_id_permission().is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let (_, credential) = persistent_store.find_credential_item(credential_id)?;
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_permission(&credential.rp_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes the subcommand getCredsMetadata for CredentialManagement.
|
/// Processes the subcommand getCredsMetadata for CredentialManagement.
|
||||||
fn process_get_creds_metadata(
|
fn process_get_creds_metadata(
|
||||||
persistent_store: &PersistentStore,
|
persistent_store: &PersistentStore,
|
||||||
@@ -157,12 +173,14 @@ fn process_enumerate_rps_get_next_rp(
|
|||||||
fn process_enumerate_credentials_begin(
|
fn process_enumerate_credentials_begin(
|
||||||
persistent_store: &PersistentStore,
|
persistent_store: &PersistentStore,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
|
pin_protocol_v1: &mut PinProtocolV1,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
let rp_id_hash = sub_command_params
|
let rp_id_hash = sub_command_params
|
||||||
.rp_id_hash
|
.rp_id_hash
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?;
|
||||||
let mut iter_result = Ok(());
|
let mut iter_result = Ok(());
|
||||||
let iter = persistent_store.iter_credentials(&mut iter_result)?;
|
let iter = persistent_store.iter_credentials(&mut iter_result)?;
|
||||||
let mut rp_credentials: Vec<usize> = iter
|
let mut rp_credentials: Vec<usize> = iter
|
||||||
@@ -201,18 +219,21 @@ fn process_enumerate_credentials_get_next_credential(
|
|||||||
/// Processes the subcommand deleteCredential for CredentialManagement.
|
/// Processes the subcommand deleteCredential for CredentialManagement.
|
||||||
fn process_delete_credential(
|
fn process_delete_credential(
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
|
pin_protocol_v1: &mut PinProtocolV1,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let credential_id = sub_command_params
|
let credential_id = sub_command_params
|
||||||
.credential_id
|
.credential_id
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?
|
||||||
.key_id;
|
.key_id;
|
||||||
|
check_rp_id_permissions(persistent_store, pin_protocol_v1, &credential_id)?;
|
||||||
persistent_store.delete_credential(&credential_id)
|
persistent_store.delete_credential(&credential_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand updateUserInformation for CredentialManagement.
|
/// Processes the subcommand updateUserInformation for CredentialManagement.
|
||||||
fn process_update_user_information(
|
fn process_update_user_information(
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
|
pin_protocol_v1: &mut PinProtocolV1,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let credential_id = sub_command_params
|
let credential_id = sub_command_params
|
||||||
@@ -222,6 +243,7 @@ fn process_update_user_information(
|
|||||||
let user = sub_command_params
|
let user = sub_command_params
|
||||||
.user
|
.user
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
||||||
|
check_rp_id_permissions(persistent_store, pin_protocol_v1, &credential_id)?;
|
||||||
persistent_store.update_credential(&credential_id, user)
|
persistent_store.update_credential(&credential_id, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,13 +279,10 @@ pub fn process_credential_management(
|
|||||||
match sub_command {
|
match sub_command {
|
||||||
CredentialManagementSubCommand::GetCredsMetadata
|
CredentialManagementSubCommand::GetCredsMetadata
|
||||||
| CredentialManagementSubCommand::EnumerateRpsBegin
|
| CredentialManagementSubCommand::EnumerateRpsBegin
|
||||||
| CredentialManagementSubCommand::DeleteCredential
|
|
||||||
| CredentialManagementSubCommand::EnumerateCredentialsBegin
|
| CredentialManagementSubCommand::EnumerateCredentialsBegin
|
||||||
|
| CredentialManagementSubCommand::DeleteCredential
|
||||||
| CredentialManagementSubCommand::UpdateUserInformation => {
|
| CredentialManagementSubCommand::UpdateUserInformation => {
|
||||||
check_pin_uv_auth_protocol(pin_protocol)?;
|
check_pin_uv_auth_protocol(pin_protocol)?;
|
||||||
persistent_store
|
|
||||||
.pin_hash()?
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
|
||||||
let pin_auth = pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
let pin_auth = pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
||||||
let mut management_data = vec![sub_command as u8];
|
let mut management_data = vec![sub_command as u8];
|
||||||
if let Some(sub_command_params) = sub_command_params.clone() {
|
if let Some(sub_command_params) = sub_command_params.clone() {
|
||||||
@@ -274,9 +293,8 @@ pub fn process_credential_management(
|
|||||||
if !pin_protocol_v1.verify_pin_auth_token(&management_data, &pin_auth) {
|
if !pin_protocol_v1.verify_pin_auth_token(&management_data, &pin_auth) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
|
// The RP ID permission is handled differently per subcommand below.
|
||||||
pin_protocol_v1.has_permission(PinPermission::CredentialManagement)?;
|
pin_protocol_v1.has_permission(PinPermission::CredentialManagement)?;
|
||||||
pin_protocol_v1.has_no_permission_rp_id()?;
|
|
||||||
// TODO(kaczmarczyck) sometimes allow a RP ID
|
|
||||||
}
|
}
|
||||||
CredentialManagementSubCommand::EnumerateRpsGetNextRp
|
CredentialManagementSubCommand::EnumerateRpsGetNextRp
|
||||||
| CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => {}
|
| CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => {}
|
||||||
@@ -284,13 +302,17 @@ pub fn process_credential_management(
|
|||||||
|
|
||||||
let response = match sub_command {
|
let response = match sub_command {
|
||||||
CredentialManagementSubCommand::GetCredsMetadata => {
|
CredentialManagementSubCommand::GetCredsMetadata => {
|
||||||
|
pin_protocol_v1.has_no_rp_id_permission()?;
|
||||||
Some(process_get_creds_metadata(persistent_store)?)
|
Some(process_get_creds_metadata(persistent_store)?)
|
||||||
}
|
}
|
||||||
CredentialManagementSubCommand::EnumerateRpsBegin => Some(process_enumerate_rps_begin(
|
CredentialManagementSubCommand::EnumerateRpsBegin => {
|
||||||
persistent_store,
|
pin_protocol_v1.has_no_rp_id_permission()?;
|
||||||
stateful_command_permission,
|
Some(process_enumerate_rps_begin(
|
||||||
now,
|
persistent_store,
|
||||||
)?),
|
stateful_command_permission,
|
||||||
|
now,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
CredentialManagementSubCommand::EnumerateRpsGetNextRp => Some(
|
CredentialManagementSubCommand::EnumerateRpsGetNextRp => Some(
|
||||||
process_enumerate_rps_get_next_rp(persistent_store, stateful_command_permission)?,
|
process_enumerate_rps_get_next_rp(persistent_store, stateful_command_permission)?,
|
||||||
),
|
),
|
||||||
@@ -298,6 +320,7 @@ pub fn process_credential_management(
|
|||||||
Some(process_enumerate_credentials_begin(
|
Some(process_enumerate_credentials_begin(
|
||||||
persistent_store,
|
persistent_store,
|
||||||
stateful_command_permission,
|
stateful_command_permission,
|
||||||
|
pin_protocol_v1,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||||
now,
|
now,
|
||||||
)?)
|
)?)
|
||||||
@@ -311,6 +334,7 @@ pub fn process_credential_management(
|
|||||||
CredentialManagementSubCommand::DeleteCredential => {
|
CredentialManagementSubCommand::DeleteCredential => {
|
||||||
process_delete_credential(
|
process_delete_credential(
|
||||||
persistent_store,
|
persistent_store,
|
||||||
|
pin_protocol_v1,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||||
)?;
|
)?;
|
||||||
None
|
None
|
||||||
@@ -318,6 +342,7 @@ pub fn process_credential_management(
|
|||||||
CredentialManagementSubCommand::UpdateUserInformation => {
|
CredentialManagementSubCommand::UpdateUserInformation => {
|
||||||
process_update_user_information(
|
process_update_user_information(
|
||||||
persistent_store,
|
persistent_store,
|
||||||
|
pin_protocol_v1,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||||
)?;
|
)?;
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -939,6 +939,24 @@ impl From<SetMinPinLengthParams> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum EnterpriseAttestationMode {
|
||||||
|
VendorFacilitated = 0x01,
|
||||||
|
PlatformManaged = 0x02,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u64> for EnterpriseAttestationMode {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(value: u64) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
match value {
|
||||||
|
1 => Ok(EnterpriseAttestationMode::VendorFacilitated),
|
||||||
|
2 => Ok(EnterpriseAttestationMode::PlatformManaged),
|
||||||
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
#[cfg_attr(test, derive(IntoEnumIterator))]
|
#[cfg_attr(test, derive(IntoEnumIterator))]
|
||||||
pub enum CredentialManagementSubCommand {
|
pub enum CredentialManagementSubCommand {
|
||||||
@@ -1795,6 +1813,26 @@ mod test {
|
|||||||
assert_eq!(cbor::Value::from(config_sub_command_params), cbor_params);
|
assert_eq!(cbor::Value::from(config_sub_command_params), cbor_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_enterprise_attestation_mode() {
|
||||||
|
assert_eq!(
|
||||||
|
EnterpriseAttestationMode::try_from(0),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
EnterpriseAttestationMode::try_from(1),
|
||||||
|
Ok(EnterpriseAttestationMode::VendorFacilitated),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
EnterpriseAttestationMode::try_from(2),
|
||||||
|
Ok(EnterpriseAttestationMode::PlatformManaged),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
EnterpriseAttestationMode::try_from(3),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_into_cred_management_sub_command() {
|
fn test_from_into_cred_management_sub_command() {
|
||||||
let cbor_sub_command: cbor::Value = cbor_int!(0x01);
|
let cbor_sub_command: cbor::Value = cbor_int!(0x01);
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ use self::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;
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionExtensions,
|
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
||||||
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
GetAssertionExtensions, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
||||||
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
PublicKeyCredentialParameter, PublicKeyCredentialSource, PublicKeyCredentialType,
|
||||||
SignatureAlgorithm,
|
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
use self::large_blobs::{LargeBlobs, MAX_MSG_SIZE};
|
use self::large_blobs::{LargeBlobs, MAX_MSG_SIZE};
|
||||||
@@ -61,6 +61,7 @@ use alloc::vec::Vec;
|
|||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use cbor::cbor_map_options;
|
use cbor::cbor_map_options;
|
||||||
|
use core::convert::TryFrom;
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
|
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
|
||||||
@@ -74,9 +75,11 @@ use libtock_drivers::crp;
|
|||||||
use libtock_drivers::timer::{ClockValue, Duration};
|
use libtock_drivers::timer::{ClockValue, Duration};
|
||||||
|
|
||||||
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
||||||
// this setting. The basic attestation uses the signing key from key_material.rs
|
// this setting. The basic attestation uses the signing key configured with a
|
||||||
// as a batch key. Turn it on if you want attestation. In this case, be aware that
|
// vendor command as a batch key. If you turn batch attestation on, be aware that
|
||||||
// it is your responsibility to generate your own key material and keep it secret.
|
// it is your responsibility to safely generate and store the key material. Also,
|
||||||
|
// the batches must have size of at least 100k authenticators before using new
|
||||||
|
// key material.
|
||||||
const USE_BATCH_ATTESTATION: bool = false;
|
const USE_BATCH_ATTESTATION: bool = false;
|
||||||
// The signature counter is currently implemented as a global counter, if you set
|
// The signature counter is currently implemented as a global counter, if you set
|
||||||
// this flag to true. The spec strongly suggests to have per-credential-counters,
|
// this flag to true. The spec strongly suggests to have per-credential-counters,
|
||||||
@@ -86,6 +89,21 @@ const USE_BATCH_ATTESTATION: bool = false;
|
|||||||
// solution is a compromise to be compatible with U2F and not wasting storage.
|
// solution is a compromise to be compatible with U2F and not wasting storage.
|
||||||
const USE_SIGNATURE_COUNTER: bool = true;
|
const USE_SIGNATURE_COUNTER: bool = true;
|
||||||
pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
|
pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
|
||||||
|
// This flag allows usage of enterprise attestation. For privacy reasons, it is
|
||||||
|
// disabled by default. You can choose between
|
||||||
|
// - EnterpriseAttestationMode::VendorFacilitated,
|
||||||
|
// - EnterpriseAttestationMode::PlatformManaged.
|
||||||
|
// For VendorFacilitated, choose an appriopriate ENTERPRISE_RP_ID_LIST.
|
||||||
|
// To enable the feature, send the subcommand enableEnterpriseAttestation in
|
||||||
|
// AuthenticatorConfig. An enterprise might want to customize the type of
|
||||||
|
// attestation that is used. OpenSK defaults to batch attestation. Configuring
|
||||||
|
// individual certificates then makes authenticators identifiable. Do NOT set
|
||||||
|
// USE_BATCH_ATTESTATION to true at the same time in this case! The code asserts
|
||||||
|
// that you don't use the same key material for batch and enterprise attestation.
|
||||||
|
// If you implement your own enterprise attestation mechanism, and you want batch
|
||||||
|
// attestation at the same time, proceed carefully and remove the assertion.
|
||||||
|
pub const ENTERPRISE_ATTESTATION_MODE: Option<EnterpriseAttestationMode> = None;
|
||||||
|
const ENTERPRISE_RP_ID_LIST: &[&str] = &[];
|
||||||
// Our credential ID consists of
|
// Our credential ID consists of
|
||||||
// - 16 byte initialization vector for AES-256,
|
// - 16 byte initialization vector for AES-256,
|
||||||
// - 32 byte ECDSA private key for the credential,
|
// - 32 byte ECDSA private key for the credential,
|
||||||
@@ -311,6 +329,11 @@ where
|
|||||||
check_user_presence: CheckUserPresence,
|
check_user_presence: CheckUserPresence,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> CtapState<'a, R, CheckUserPresence> {
|
) -> CtapState<'a, R, CheckUserPresence> {
|
||||||
|
#[allow(clippy::assertions_on_constants)]
|
||||||
|
{
|
||||||
|
assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
let persistent_store = PersistentStore::new(rng);
|
let persistent_store = PersistentStore::new(rng);
|
||||||
let pin_protocol_v1 = PinProtocolV1::new(rng);
|
let pin_protocol_v1 = PinProtocolV1::new(rng);
|
||||||
CtapState {
|
CtapState {
|
||||||
@@ -573,7 +596,7 @@ where
|
|||||||
options,
|
options,
|
||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
enterprise_attestation: _,
|
enterprise_attestation,
|
||||||
} = make_credential_params;
|
} = make_credential_params;
|
||||||
|
|
||||||
self.pin_uv_auth_precheck(&pin_uv_auth_param, pin_uv_auth_protocol, cid)?;
|
self.pin_uv_auth_precheck(&pin_uv_auth_param, pin_uv_auth_protocol, cid)?;
|
||||||
@@ -583,6 +606,26 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rp_id = rp.rp_id;
|
let rp_id = rp.rp_id;
|
||||||
|
let ep_att = if let Some(enterprise_attestation) = enterprise_attestation {
|
||||||
|
let authenticator_mode =
|
||||||
|
ENTERPRISE_ATTESTATION_MODE.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
||||||
|
if !self.persistent_store.enterprise_attestation()? {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
match (
|
||||||
|
EnterpriseAttestationMode::try_from(enterprise_attestation)?,
|
||||||
|
authenticator_mode,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
EnterpriseAttestationMode::PlatformManaged,
|
||||||
|
EnterpriseAttestationMode::PlatformManaged,
|
||||||
|
) => ENTERPRISE_RP_ID_LIST.contains(&rp_id.as_str()),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let mut cred_protect_policy = extensions.cred_protect;
|
let mut cred_protect_policy = extensions.cred_protect;
|
||||||
if cred_protect_policy.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
if cred_protect_policy.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
< DEFAULT_CRED_PROTECT.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
< DEFAULT_CRED_PROTECT.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
@@ -649,7 +692,7 @@ where
|
|||||||
}
|
}
|
||||||
self.pin_protocol_v1
|
self.pin_protocol_v1
|
||||||
.has_permission(PinPermission::MakeCredential)?;
|
.has_permission(PinPermission::MakeCredential)?;
|
||||||
self.pin_protocol_v1.has_permission_for_rp_id(&rp_id)?;
|
self.pin_protocol_v1.ensure_rp_id_permission(&rp_id)?;
|
||||||
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -738,7 +781,7 @@ where
|
|||||||
let mut signature_data = auth_data.clone();
|
let mut signature_data = auth_data.clone();
|
||||||
signature_data.extend(client_data_hash);
|
signature_data.extend(client_data_hash);
|
||||||
|
|
||||||
let (signature, x5c) = if USE_BATCH_ATTESTATION {
|
let (signature, x5c) = if USE_BATCH_ATTESTATION || ep_att {
|
||||||
let attestation_private_key = self
|
let attestation_private_key = self
|
||||||
.persistent_store
|
.persistent_store
|
||||||
.attestation_private_key()?
|
.attestation_private_key()?
|
||||||
@@ -765,11 +808,13 @@ where
|
|||||||
x5c,
|
x5c,
|
||||||
ecdaa_key_id: None,
|
ecdaa_key_id: None,
|
||||||
};
|
};
|
||||||
|
let ep_att = if ep_att { Some(true) } else { None };
|
||||||
Ok(ResponseData::AuthenticatorMakeCredential(
|
Ok(ResponseData::AuthenticatorMakeCredential(
|
||||||
AuthenticatorMakeCredentialResponse {
|
AuthenticatorMakeCredentialResponse {
|
||||||
fmt: String::from("packed"),
|
fmt: String::from("packed"),
|
||||||
auth_data,
|
auth_data,
|
||||||
att_stmt: attestation_statement,
|
att_stmt: attestation_statement,
|
||||||
|
ep_att,
|
||||||
large_blob_key,
|
large_blob_key,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -938,7 +983,7 @@ where
|
|||||||
}
|
}
|
||||||
self.pin_protocol_v1
|
self.pin_protocol_v1
|
||||||
.has_permission(PinPermission::GetAssertion)?;
|
.has_permission(PinPermission::GetAssertion)?;
|
||||||
self.pin_protocol_v1.has_permission_for_rp_id(&rp_id)?;
|
self.pin_protocol_v1.ensure_rp_id_permission(&rp_id)?;
|
||||||
UV_FLAG
|
UV_FLAG
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -1055,6 +1100,12 @@ where
|
|||||||
options_map.insert(String::from("up"), true);
|
options_map.insert(String::from("up"), true);
|
||||||
options_map.insert(String::from("pinUvAuthToken"), true);
|
options_map.insert(String::from("pinUvAuthToken"), true);
|
||||||
options_map.insert(String::from("largeBlobs"), true);
|
options_map.insert(String::from("largeBlobs"), true);
|
||||||
|
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
||||||
|
options_map.insert(
|
||||||
|
String::from("ep"),
|
||||||
|
self.persistent_store.enterprise_attestation()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
options_map.insert(String::from("authnrCfg"), true);
|
options_map.insert(String::from("authnrCfg"), true);
|
||||||
options_map.insert(String::from("credMgmt"), true);
|
options_map.insert(String::from("credMgmt"), true);
|
||||||
options_map.insert(String::from("setMinPINLength"), true);
|
options_map.insert(String::from("setMinPINLength"), true);
|
||||||
@@ -1252,6 +1303,7 @@ mod test {
|
|||||||
fmt,
|
fmt,
|
||||||
auth_data,
|
auth_data,
|
||||||
att_stmt,
|
att_stmt,
|
||||||
|
ep_att,
|
||||||
large_blob_key,
|
large_blob_key,
|
||||||
} = make_credential_response;
|
} = make_credential_response;
|
||||||
// The expected response is split to only assert the non-random parts.
|
// The expected response is split to only assert the non-random parts.
|
||||||
@@ -1272,6 +1324,7 @@ mod test {
|
|||||||
&auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()],
|
&auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()],
|
||||||
expected_extension_cbor
|
expected_extension_cbor
|
||||||
);
|
);
|
||||||
|
assert!(ep_att.is_none());
|
||||||
assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64);
|
assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64);
|
||||||
assert_eq!(large_blob_key, None);
|
assert_eq!(large_blob_key, None);
|
||||||
}
|
}
|
||||||
@@ -1301,12 +1354,13 @@ mod test {
|
|||||||
String::from("largeBlobKey"),
|
String::from("largeBlobKey"),
|
||||||
]],
|
]],
|
||||||
0x03 => ctap_state.persistent_store.aaguid().unwrap(),
|
0x03 => ctap_state.persistent_store.aaguid().unwrap(),
|
||||||
0x04 => cbor_map! {
|
0x04 => cbor_map_options! {
|
||||||
"rk" => true,
|
"rk" => true,
|
||||||
"clientPin" => false,
|
"clientPin" => false,
|
||||||
"up" => true,
|
"up" => true,
|
||||||
"pinUvAuthToken" => true,
|
"pinUvAuthToken" => true,
|
||||||
"largeBlobs" => true,
|
"largeBlobs" => true,
|
||||||
|
"ep" => ENTERPRISE_ATTESTATION_MODE.map(|_| false),
|
||||||
"authnrCfg" => true,
|
"authnrCfg" => true,
|
||||||
"credMgmt" => true,
|
"credMgmt" => true,
|
||||||
"setMinPINLength" => true,
|
"setMinPINLength" => true,
|
||||||
|
|||||||
@@ -501,6 +501,7 @@ impl PinProtocolV1 {
|
|||||||
encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cred_random)
|
encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cred_random)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the required command's token permission is granted.
|
||||||
pub fn has_permission(&self, permission: PinPermission) -> Result<(), Ctap2StatusCode> {
|
pub fn has_permission(&self, permission: PinPermission) -> Result<(), Ctap2StatusCode> {
|
||||||
// Relies on the fact that all permissions are represented by powers of two.
|
// Relies on the fact that all permissions are represented by powers of two.
|
||||||
if permission as u8 & self.permissions != 0 {
|
if permission as u8 & self.permissions != 0 {
|
||||||
@@ -510,22 +511,47 @@ impl PinProtocolV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_no_permission_rp_id(&self) -> Result<(), Ctap2StatusCode> {
|
/// Check if no RP ID is associated with the token permission.
|
||||||
|
pub fn has_no_rp_id_permission(&self) -> Result<(), Ctap2StatusCode> {
|
||||||
if self.permissions_rp_id.is_some() {
|
if self.permissions_rp_id.is_some() {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_permission_for_rp_id(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
/// Check if no or the passed RP ID is associated with the token permission.
|
||||||
if let Some(permissions_rp_id) = &self.permissions_rp_id {
|
pub fn has_no_or_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
||||||
if rp_id != permissions_rp_id {
|
match &self.permissions_rp_id {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
Some(p) if rp_id != p => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
||||||
}
|
_ => Ok(()),
|
||||||
} else {
|
}
|
||||||
self.permissions_rp_id = Some(String::from(rp_id));
|
}
|
||||||
|
|
||||||
|
/// Check if no RP ID is associated with the token permission, or it matches the hash.
|
||||||
|
pub fn has_no_or_rp_id_hash_permission(
|
||||||
|
&self,
|
||||||
|
rp_id_hash: &[u8],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
match &self.permissions_rp_id {
|
||||||
|
Some(p) if rp_id_hash != Sha256::hash(p.as_bytes()) => {
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the passed RP ID is associated with the token permission.
|
||||||
|
///
|
||||||
|
/// If no RP ID is associated, associate the passed RP ID as a side effect.
|
||||||
|
pub fn ensure_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
||||||
|
match &self.permissions_rp_id {
|
||||||
|
Some(p) if rp_id != p => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
||||||
|
None => {
|
||||||
|
self.permissions_rp_id = Some(String::from(rp_id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1150,24 +1176,65 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_has_no_permission_rp_id() {
|
fn test_has_no_rp_id_permission() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
||||||
assert_eq!(pin_protocol_v1.has_no_permission_rp_id(), Ok(()));
|
assert_eq!(pin_protocol_v1.has_no_rp_id_permission(), Ok(()));
|
||||||
assert_eq!(pin_protocol_v1.permissions_rp_id, None,);
|
assert_eq!(pin_protocol_v1.permissions_rp_id, None);
|
||||||
pin_protocol_v1.permissions_rp_id = Some("example.com".to_string());
|
pin_protocol_v1.permissions_rp_id = Some("example.com".to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.has_no_permission_rp_id(),
|
pin_protocol_v1.has_no_rp_id_permission(),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_has_permission_for_rp_id() {
|
fn test_has_no_or_rp_id_permission() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.has_permission_for_rp_id("example.com"),
|
pin_protocol_v1.has_no_or_rp_id_permission("example.com"),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(pin_protocol_v1.permissions_rp_id, None);
|
||||||
|
pin_protocol_v1.permissions_rp_id = Some("example.com".to_string());
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_permission("example.com"),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_permission("another.example.com"),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_has_no_or_rp_id_hash_permission() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
||||||
|
let rp_id_hash = Sha256::hash(b"example.com");
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(pin_protocol_v1.permissions_rp_id, None);
|
||||||
|
pin_protocol_v1.permissions_rp_id = Some("example.com".to_string());
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.has_no_or_rp_id_hash_permission(&[0x4A; 32]),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ensure_rp_id_permission() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
|
||||||
|
assert_eq!(
|
||||||
|
pin_protocol_v1.ensure_rp_id_permission("example.com"),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1175,11 +1242,11 @@ mod test {
|
|||||||
Some(String::from("example.com"))
|
Some(String::from("example.com"))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.has_permission_for_rp_id("example.com"),
|
pin_protocol_v1.ensure_rp_id_permission("example.com"),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.has_permission_for_rp_id("counter-example.com"),
|
pin_protocol_v1.ensure_rp_id_permission("counter-example.com"),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ pub struct AuthenticatorMakeCredentialResponse {
|
|||||||
pub fmt: String,
|
pub fmt: String,
|
||||||
pub auth_data: Vec<u8>,
|
pub auth_data: Vec<u8>,
|
||||||
pub att_stmt: PackedAttestationStatement,
|
pub att_stmt: PackedAttestationStatement,
|
||||||
|
pub ep_att: Option<bool>,
|
||||||
pub large_blob_key: Option<Vec<u8>>,
|
pub large_blob_key: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ impl From<AuthenticatorMakeCredentialResponse> for cbor::Value {
|
|||||||
fmt,
|
fmt,
|
||||||
auth_data,
|
auth_data,
|
||||||
att_stmt,
|
att_stmt,
|
||||||
|
ep_att,
|
||||||
large_blob_key,
|
large_blob_key,
|
||||||
} = make_credential_response;
|
} = make_credential_response;
|
||||||
|
|
||||||
@@ -77,6 +79,7 @@ impl From<AuthenticatorMakeCredentialResponse> for cbor::Value {
|
|||||||
0x01 => fmt,
|
0x01 => fmt,
|
||||||
0x02 => auth_data,
|
0x02 => auth_data,
|
||||||
0x03 => att_stmt,
|
0x03 => att_stmt,
|
||||||
|
0x04 => ep_att,
|
||||||
0x05 => large_blob_key,
|
0x05 => large_blob_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,6 +323,7 @@ mod test {
|
|||||||
fmt: "packed".to_string(),
|
fmt: "packed".to_string(),
|
||||||
auth_data: vec![0xAD],
|
auth_data: vec![0xAD],
|
||||||
att_stmt,
|
att_stmt,
|
||||||
|
ep_att: Some(true),
|
||||||
large_blob_key: Some(vec![0x1B]),
|
large_blob_key: Some(vec![0x1B]),
|
||||||
};
|
};
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
@@ -328,6 +332,7 @@ mod test {
|
|||||||
0x01 => "packed",
|
0x01 => "packed",
|
||||||
0x02 => vec![0xAD],
|
0x02 => vec![0xAD],
|
||||||
0x03 => cbor_packed_attestation_statement,
|
0x03 => cbor_packed_attestation_statement,
|
||||||
|
0x04 => true,
|
||||||
0x05 => vec![0x1B],
|
0x05 => vec![0x1B],
|
||||||
};
|
};
|
||||||
assert_eq!(response_cbor, Some(expected_cbor));
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150;
|
|||||||
|
|
||||||
const MAX_PIN_RETRIES: u8 = 8;
|
const MAX_PIN_RETRIES: u8 = 8;
|
||||||
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
||||||
const DEFAULT_MIN_PIN_LENGTH_RP_IDS: Vec<String> = Vec::new();
|
const DEFAULT_MIN_PIN_LENGTH_RP_IDS: &[&str] = &[];
|
||||||
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
||||||
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
||||||
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
||||||
@@ -151,7 +151,7 @@ impl PersistentStore {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found.
|
/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found.
|
||||||
fn find_credential_item(
|
pub fn find_credential_item(
|
||||||
&self,
|
&self,
|
||||||
credential_id: &[u8],
|
credential_id: &[u8],
|
||||||
) -> Result<(usize, PublicKeyCredentialSource), Ctap2StatusCode> {
|
) -> Result<(usize, PublicKeyCredentialSource), Ctap2StatusCode> {
|
||||||
@@ -439,12 +439,17 @@ impl PersistentStore {
|
|||||||
/// Returns the list of RP IDs that are used to check if reading the minimum PIN length is
|
/// Returns the list of RP IDs that are used to check if reading the minimum PIN length is
|
||||||
/// allowed.
|
/// allowed.
|
||||||
pub fn min_pin_length_rp_ids(&self) -> Result<Vec<String>, Ctap2StatusCode> {
|
pub fn min_pin_length_rp_ids(&self) -> Result<Vec<String>, Ctap2StatusCode> {
|
||||||
let rp_ids = self
|
let rp_ids = self.store.find(key::MIN_PIN_LENGTH_RP_IDS)?.map_or_else(
|
||||||
.store
|
|| {
|
||||||
.find(key::MIN_PIN_LENGTH_RP_IDS)?
|
Some(
|
||||||
.map_or(Some(DEFAULT_MIN_PIN_LENGTH_RP_IDS), |value| {
|
DEFAULT_MIN_PIN_LENGTH_RP_IDS
|
||||||
deserialize_min_pin_length_rp_ids(&value)
|
.iter()
|
||||||
});
|
.map(|&s| String::from(s))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|value| deserialize_min_pin_length_rp_ids(&value),
|
||||||
|
);
|
||||||
debug_assert!(rp_ids.is_some());
|
debug_assert!(rp_ids.is_some());
|
||||||
Ok(rp_ids.unwrap_or_default())
|
Ok(rp_ids.unwrap_or_default())
|
||||||
}
|
}
|
||||||
@@ -455,7 +460,8 @@ impl PersistentStore {
|
|||||||
min_pin_length_rp_ids: Vec<String>,
|
min_pin_length_rp_ids: Vec<String>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let mut min_pin_length_rp_ids = min_pin_length_rp_ids;
|
let mut min_pin_length_rp_ids = min_pin_length_rp_ids;
|
||||||
for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS {
|
for &rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() {
|
||||||
|
let rp_id = String::from(rp_id);
|
||||||
if !min_pin_length_rp_ids.contains(&rp_id) {
|
if !min_pin_length_rp_ids.contains(&rp_id) {
|
||||||
min_pin_length_rp_ids.push(rp_id);
|
min_pin_length_rp_ids.push(rp_id);
|
||||||
}
|
}
|
||||||
@@ -611,6 +617,23 @@ impl PersistentStore {
|
|||||||
Ok(self.store.insert(key::FORCE_PIN_CHANGE, &[])?)
|
Ok(self.store.insert(key::FORCE_PIN_CHANGE, &[])?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether enterprise attestation is enabled.
|
||||||
|
pub fn enterprise_attestation(&self) -> Result<bool, Ctap2StatusCode> {
|
||||||
|
match self.store.find(key::ENTERPRISE_ATTESTATION)? {
|
||||||
|
None => Ok(false),
|
||||||
|
Some(value) if value.is_empty() => Ok(true),
|
||||||
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks enterprise attestation as enabled.
|
||||||
|
pub fn enable_enterprise_attestation(&mut self) -> Result<(), Ctap2StatusCode> {
|
||||||
|
if !self.enterprise_attestation()? {
|
||||||
|
self.store.insert(key::ENTERPRISE_ATTESTATION, &[])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether alwaysUv is enabled.
|
/// Returns whether alwaysUv is enabled.
|
||||||
pub fn has_always_uv(&self) -> Result<bool, Ctap2StatusCode> {
|
pub fn has_always_uv(&self) -> Result<bool, Ctap2StatusCode> {
|
||||||
if ENFORCE_ALWAYS_UV {
|
if ENFORCE_ALWAYS_UV {
|
||||||
@@ -1210,7 +1233,8 @@ mod test {
|
|||||||
persistent_store.set_min_pin_length_rp_ids(rp_ids.clone()),
|
persistent_store.set_min_pin_length_rp_ids(rp_ids.clone()),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS {
|
for &rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() {
|
||||||
|
let rp_id = String::from(rp_id);
|
||||||
if !rp_ids.contains(&rp_id) {
|
if !rp_ids.contains(&rp_id) {
|
||||||
rp_ids.push(rp_id);
|
rp_ids.push(rp_id);
|
||||||
}
|
}
|
||||||
@@ -1332,6 +1356,18 @@ mod test {
|
|||||||
assert!(!persistent_store.has_force_pin_change().unwrap());
|
assert!(!persistent_store.has_force_pin_change().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enterprise_attestation() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
assert!(!persistent_store.enterprise_attestation().unwrap());
|
||||||
|
assert_eq!(persistent_store.enable_enterprise_attestation(), Ok(()));
|
||||||
|
assert!(persistent_store.enterprise_attestation().unwrap());
|
||||||
|
persistent_store.reset(&mut rng).unwrap();
|
||||||
|
assert!(!persistent_store.enterprise_attestation().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_always_uv() {
|
fn test_always_uv() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ make_partition! {
|
|||||||
/// If this entry exists and is empty, alwaysUv is enabled.
|
/// If this entry exists and is empty, alwaysUv is enabled.
|
||||||
ALWAYS_UV = 2038;
|
ALWAYS_UV = 2038;
|
||||||
|
|
||||||
/// If this entry exists and equals 1, the PIN needs to be changed.
|
/// If this entry exists and is empty, enterprise attestation is enabled.
|
||||||
|
ENTERPRISE_ATTESTATION = 2039;
|
||||||
|
|
||||||
|
/// If this entry exists and is empty, the PIN needs to be changed.
|
||||||
FORCE_PIN_CHANGE = 2040;
|
FORCE_PIN_CHANGE = 2040;
|
||||||
|
|
||||||
/// The secret of the CredRandom feature.
|
/// The secret of the CredRandom feature.
|
||||||
|
|||||||
Reference in New Issue
Block a user