Skip to content

Commit

Permalink
Add vendor command to load certificate and priv key
Browse files Browse the repository at this point in the history
  • Loading branch information
jmichelp committed Dec 16, 2020
1 parent 218188a commit efb6378
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 7 deletions.
78 changes: 73 additions & 5 deletions src/ctap/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
// limitations under the License.

use super::data_formats::{
extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned,
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions, GetAssertionOptions,
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
extract_unsigned, ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions,
GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use super::key_material;
use super::status_code::Ctap2StatusCode;
use alloc::string::String;
use alloc::vec::Vec;
use arrayref::array_ref;
use cbor::destructure_cbor_map;
use core::convert::TryFrom;

Expand All @@ -41,6 +44,8 @@ pub enum Command {
#[cfg(feature = "with_ctap2_1")]
AuthenticatorSelection,
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
// Vendor specific commands
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
}

impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
Expand All @@ -63,7 +68,8 @@ impl Command {
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
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;

pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
Expand Down Expand Up @@ -109,6 +115,12 @@ impl Command {
// Parameters are ignored.
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),
}
}
Expand Down Expand Up @@ -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)]
mod test {
use super::super::data_formats::{
Expand Down
190 changes: 188 additions & 2 deletions src/ctap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod timed_permission;
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
use self::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command,
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
};
#[cfg(feature = "with_ctap2_1")]
use self::data_formats::AuthenticatorTransport;
Expand All @@ -44,7 +44,7 @@ use self::pin_protocol_v1::PinPermission;
use self::pin_protocol_v1::PinProtocolV1;
use self::response::{
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
AuthenticatorMakeCredentialResponse, ResponseData,
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
};
use self::status_code::Ctap2StatusCode;
use self::storage::PersistentStore;
Expand Down Expand Up @@ -358,6 +358,10 @@ where
#[cfg(feature = "with_ctap2_1")]
Command::AuthenticatorSelection => self.process_selection(cid),
// TODO(kaczmarczyck) implement FIDO 2.1 commands
// Vendor specific commands
Command::AuthenticatorVendorConfigure(params) => {
self.process_vendor_configure(params, cid)
}
};
#[cfg(feature = "debug_ctap")]
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
Expand Down Expand Up @@ -919,6 +923,63 @@ where
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(
&self,
rp_id_hash: &[u8],
Expand All @@ -941,6 +1002,7 @@ where

#[cfg(test)]
mod test {
use super::command::AuthenticatorAttestationMaterial;
use super::data_formats::{
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
Expand Down Expand Up @@ -2052,4 +2114,128 @@ mod test {
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
}
))
);
}
}
26 changes: 26 additions & 0 deletions src/ctap/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub enum ResponseData {
AuthenticatorReset,
#[cfg(feature = "with_ctap2_1")]
AuthenticatorSelection,
AuthenticatorVendor(AuthenticatorVendorResponse),
}

impl From<ResponseData> for Option<cbor::Value> {
Expand All @@ -48,6 +49,7 @@ impl From<ResponseData> for Option<cbor::Value> {
ResponseData::AuthenticatorReset => None,
#[cfg(feature = "with_ctap2_1")]
ResponseData::AuthenticatorSelection => None,
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
}
}
}
Expand Down Expand Up @@ -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)]
mod test {
use super::super::data_formats::PackedAttestationStatement;
Expand Down

0 comments on commit efb6378

Please sign in to comment.