Add vendor command to load certificate and priv key
This commit is contained in:
@@ -13,14 +13,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions, GetAssertionOptions,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions,
|
||||||
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialDescriptor,
|
GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
|
||||||
PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
|
||||||
|
PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
|
use super::key_material;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use arrayref::array_ref;
|
||||||
use cbor::destructure_cbor_map;
|
use cbor::destructure_cbor_map;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
@@ -41,6 +44,8 @@ pub enum Command {
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
|
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
|
||||||
|
// Vendor specific commands
|
||||||
|
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
|
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
|
||||||
@@ -63,7 +68,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
||||||
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
||||||
const AUTHENTICATOR_CONFIG: u8 = 0xC0;
|
const AUTHENTICATOR_CONFIG: u8 = 0xC0;
|
||||||
const AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
|
const AUTHENTICATOR_VENDOR_CONFIGURE: u8 = 0x40;
|
||||||
|
const AUTHENTICATOR_VENDOR_FIRST_UNUSED: u8 = 0x41;
|
||||||
const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
||||||
|
|
||||||
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
||||||
@@ -109,6 +115,12 @@ impl Command {
|
|||||||
// Parameters are ignored.
|
// Parameters are ignored.
|
||||||
Ok(Command::AuthenticatorSelection)
|
Ok(Command::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
Command::AUTHENTICATOR_VENDOR_CONFIGURE => {
|
||||||
|
let decoded_cbor = cbor::read(&bytes[1..])?;
|
||||||
|
Ok(Command::AuthenticatorVendorConfigure(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,6 +384,62 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
|
pub struct AuthenticatorAttestationMaterial {
|
||||||
|
pub certificate: Vec<u8>,
|
||||||
|
pub private_key: [u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorAttestationMaterial {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => certificate,
|
||||||
|
2 => private_key,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
let certificate = certificate.map(extract_byte_string).transpose()?.unwrap();
|
||||||
|
let private_key = private_key.map(extract_byte_string).transpose()?.unwrap();
|
||||||
|
if private_key.len() != key_material::ATTESTATION_PRIVATE_KEY_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
||||||
|
}
|
||||||
|
let private_key = array_ref!(private_key, 0, key_material::ATTESTATION_PRIVATE_KEY_LENGTH);
|
||||||
|
Ok(AuthenticatorAttestationMaterial {
|
||||||
|
certificate,
|
||||||
|
private_key: *private_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
|
pub struct AuthenticatorVendorConfigureParameters {
|
||||||
|
pub lockdown: bool,
|
||||||
|
pub attestation_material: Option<AuthenticatorAttestationMaterial>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorVendorConfigureParameters {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => lockdown,
|
||||||
|
2 => attestation_material,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
let lockdown = lockdown.map_or(Ok(false), extract_bool)?;
|
||||||
|
let attestation_material = attestation_material
|
||||||
|
.map(AuthenticatorAttestationMaterial::try_from)
|
||||||
|
.transpose()?;
|
||||||
|
Ok(AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown,
|
||||||
|
attestation_material,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::{
|
use super::super::data_formats::{
|
||||||
|
|||||||
190
src/ctap/mod.rs
190
src/ctap/mod.rs
@@ -29,7 +29,7 @@ mod timed_permission;
|
|||||||
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
|
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
|
||||||
use self::command::{
|
use self::command::{
|
||||||
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
||||||
AuthenticatorMakeCredentialParameters, Command,
|
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use self::data_formats::AuthenticatorTransport;
|
use self::data_formats::AuthenticatorTransport;
|
||||||
@@ -44,7 +44,7 @@ use self::pin_protocol_v1::PinPermission;
|
|||||||
use self::pin_protocol_v1::PinProtocolV1;
|
use self::pin_protocol_v1::PinProtocolV1;
|
||||||
use self::response::{
|
use self::response::{
|
||||||
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
||||||
AuthenticatorMakeCredentialResponse, ResponseData,
|
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
||||||
};
|
};
|
||||||
use self::status_code::Ctap2StatusCode;
|
use self::status_code::Ctap2StatusCode;
|
||||||
use self::storage::PersistentStore;
|
use self::storage::PersistentStore;
|
||||||
@@ -358,6 +358,10 @@ where
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
Command::AuthenticatorSelection => self.process_selection(cid),
|
Command::AuthenticatorSelection => self.process_selection(cid),
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
||||||
|
// Vendor specific commands
|
||||||
|
Command::AuthenticatorVendorConfigure(params) => {
|
||||||
|
self.process_vendor_configure(params, cid)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
||||||
@@ -919,6 +923,63 @@ where
|
|||||||
Ok(ResponseData::AuthenticatorSelection)
|
Ok(ResponseData::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_vendor_configure(
|
||||||
|
&mut self,
|
||||||
|
params: AuthenticatorVendorConfigureParameters,
|
||||||
|
cid: ChannelID,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
(self.check_user_presence)(cid)?;
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
let has_priv_key = self.persistent_store.attestation_private_key()?.is_some();
|
||||||
|
let has_cert = self.persistent_store.attestation_certificate()?.is_some();
|
||||||
|
|
||||||
|
if params.attestation_material.is_some() {
|
||||||
|
let data = params.attestation_material.unwrap();
|
||||||
|
if !has_cert {
|
||||||
|
self.persistent_store
|
||||||
|
.set_attestation_certificate(&data.certificate)?;
|
||||||
|
}
|
||||||
|
if !has_priv_key {
|
||||||
|
self.persistent_store
|
||||||
|
.set_attestation_private_key(&data.private_key)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let has_priv_key = self.persistent_store.attestation_private_key()?.is_some();
|
||||||
|
let has_cert = self.persistent_store.attestation_certificate()?.is_some();
|
||||||
|
if params.lockdown {
|
||||||
|
// To avoid bricking the authenticator, we only allow lockdown
|
||||||
|
// to happen if both values are programmed or if both U2F/CTAP1 and
|
||||||
|
// batch attestation are disabled.
|
||||||
|
#[cfg(feature = "with_ctap1")]
|
||||||
|
let need_certificate = true;
|
||||||
|
#[cfg(not(feature = "with_ctap1"))]
|
||||||
|
let need_certificate = USE_BATCH_ATTESTATION;
|
||||||
|
|
||||||
|
if (need_certificate && !(has_priv_key && has_cert))
|
||||||
|
|| libtock_drivers::crp::protect().is_err()
|
||||||
|
{
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||||
|
} else {
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: has_cert,
|
||||||
|
pkey_programmed: has_priv_key,
|
||||||
|
lockdown_enabled: true,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: has_cert,
|
||||||
|
pkey_programmed: has_priv_key,
|
||||||
|
lockdown_enabled: false,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_auth_data(
|
pub fn generate_auth_data(
|
||||||
&self,
|
&self,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
@@ -941,6 +1002,7 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::command::AuthenticatorAttestationMaterial;
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
||||||
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
@@ -2052,4 +2114,128 @@ mod test {
|
|||||||
last_counter = next_counter;
|
last_counter = next_counter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_configure() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
|
// Nothing should be configured at the beginning
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: None,
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: false,
|
||||||
|
pkey_programmed: false,
|
||||||
|
lockdown_enabled: false
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inject dummy values
|
||||||
|
let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
let dummy_cert = [0xddu8; 20];
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
|
certificate: dummy_cert.to_vec(),
|
||||||
|
private_key: dummy_key,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
lockdown_enabled: false
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_cert
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to inject other dummy values and check that intial values are retained.
|
||||||
|
let other_dummy_key = [0x44u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
|
certificate: dummy_cert.to_vec(),
|
||||||
|
private_key: other_dummy_key,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
lockdown_enabled: false
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_cert
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now try to lock the device
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: true,
|
||||||
|
attestation_material: None,
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
lockdown_enabled: true
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub enum ResponseData {
|
|||||||
AuthenticatorReset,
|
AuthenticatorReset,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
|
AuthenticatorVendor(AuthenticatorVendorResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseData> for Option<cbor::Value> {
|
impl From<ResponseData> for Option<cbor::Value> {
|
||||||
@@ -48,6 +49,7 @@ impl From<ResponseData> for Option<cbor::Value> {
|
|||||||
ResponseData::AuthenticatorReset => None,
|
ResponseData::AuthenticatorReset => None,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
ResponseData::AuthenticatorSelection => None,
|
ResponseData::AuthenticatorSelection => None,
|
||||||
|
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +233,30 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
|
||||||
|
pub struct AuthenticatorVendorResponse {
|
||||||
|
pub cert_programmed: bool,
|
||||||
|
pub pkey_programmed: bool,
|
||||||
|
pub lockdown_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthenticatorVendorResponse> for cbor::Value {
|
||||||
|
fn from(vendor_response: AuthenticatorVendorResponse) -> Self {
|
||||||
|
let AuthenticatorVendorResponse {
|
||||||
|
cert_programmed,
|
||||||
|
pkey_programmed,
|
||||||
|
lockdown_enabled,
|
||||||
|
} = vendor_response;
|
||||||
|
|
||||||
|
cbor_map_options! {
|
||||||
|
1 => cert_programmed,
|
||||||
|
2 => pkey_programmed,
|
||||||
|
3 => lockdown_enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::PackedAttestationStatement;
|
use super::super::data_formats::PackedAttestationStatement;
|
||||||
|
|||||||
Reference in New Issue
Block a user