dummy implementation for enterprise attestation

This commit is contained in:
Fabian Kaczmarczyck
2021-02-04 21:26:00 +01:00
parent b32d92d9e2
commit 44b7c3cdc1
7 changed files with 171 additions and 13 deletions

View File

@@ -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!(

View File

@@ -12,15 +12,27 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use super::check_pin_uv_auth_protocol;
use super::command::AuthenticatorConfigParameters; use super::command::AuthenticatorConfigParameters;
use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams}; use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams};
use super::pin_protocol_v1::PinProtocolV1; 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, 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 setMinPINLength for AuthenticatorConfig. /// Processes the subcommand setMinPINLength for AuthenticatorConfig.
fn process_set_min_pin_length( fn process_set_min_pin_length(
persistent_store: &mut PersistentStore, persistent_store: &mut PersistentStore,
@@ -85,6 +97,9 @@ pub fn process_config(
} }
match sub_command { match sub_command {
ConfigSubCommand::EnableEnterpriseAttestation => {
process_enable_enterprise_attestation(persistent_store)
}
ConfigSubCommand::SetMinPinLength => { ConfigSubCommand::SetMinPinLength => {
if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params { if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params {
process_set_min_pin_length(persistent_store, params) process_set_min_pin_length(persistent_store, params)
@@ -101,6 +116,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)
);
}
}
fn create_min_pin_config_params( fn create_min_pin_config_params(
min_pin_length: u8, min_pin_length: u8,
min_pin_length_rp_ids: Option<Vec<String>>, min_pin_length_rp_ids: Option<Vec<String>>,

View File

@@ -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,22 @@ 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(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);

View File

@@ -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};
@@ -86,6 +87,18 @@ 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!
pub const ENTERPRISE_ATTESTATION_MODE: Option<EnterpriseAttestationMode> = None;
const ENTERPRISE_RP_ID_LIST: Vec<String> = Vec::new();
// 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,
@@ -562,7 +575,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)?;
@@ -572,6 +585,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),
_ => 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)
@@ -723,7 +756,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()?
@@ -750,11 +783,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,
}, },
)) ))
@@ -1026,6 +1061,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);
@@ -1227,6 +1268,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.
@@ -1247,6 +1289,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);
} }
@@ -1276,12 +1319,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,

View File

@@ -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));

View File

@@ -610,6 +610,23 @@ impl PersistentStore {
pub fn force_pin_change(&mut self) -> Result<(), Ctap2StatusCode> { pub fn force_pin_change(&mut self) -> Result<(), Ctap2StatusCode> {
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(())
}
} }
impl From<persistent_store::StoreError> for Ctap2StatusCode { impl From<persistent_store::StoreError> for Ctap2StatusCode {
@@ -1308,6 +1325,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_serialize_deserialize_credential() { fn test_serialize_deserialize_credential() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};

View File

@@ -93,7 +93,10 @@ make_partition! {
/// The stored large blob can be too big for one key, so it has to be sharded. /// The stored large blob can be too big for one key, so it has to be sharded.
LARGE_BLOB_SHARDS = 2000..2004; LARGE_BLOB_SHARDS = 2000..2004;
/// 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.