Access the persistent keys through the store

This permits to set them using a vendor command and thus not embed their value
in the application.
This commit is contained in:
Julien Cretin
2020-05-30 20:15:59 +02:00
parent 2e419fe77b
commit 98a558a502
3 changed files with 126 additions and 11 deletions

View File

@@ -37,7 +37,6 @@ use self::data_formats::{
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm, PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
}; };
use self::hid::ChannelID; use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
use self::response::{ use self::response::{
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse, AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData, AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
@@ -509,7 +508,7 @@ where
}; };
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags); let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
auth_data.extend(AAGUID); auth_data.extend(self.persistent_store.aaguid()?);
// The length is fixed to 0x20 or 0x70 and fits one byte. // The length is fixed to 0x20 or 0x70 and fits one byte.
if credential_id.len() > 0xFF { if credential_id.len() > 0xFF {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG); return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
@@ -534,10 +533,11 @@ where
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 {
let attestation_key = let attestation_key =
crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap(); crypto::ecdsa::SecKey::from_bytes(self.persistent_store.attestation_private_key()?)
.unwrap();
( (
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data), attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
Some(vec![ATTESTATION_CERTIFICATE.to_vec()]), Some(vec![self.persistent_store.attestation_certificate()?]),
) )
} else { } else {
( (
@@ -769,7 +769,7 @@ where
String::from(FIDO2_VERSION_STRING), String::from(FIDO2_VERSION_STRING),
], ],
extensions: Some(vec![String::from("hmac-secret")]), extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID, aaguid: *self.persistent_store.aaguid()?,
options: Some(options_map), options: Some(options_map),
max_msg_size: Some(1024), max_msg_size: Some(1024),
pin_protocols: Some(vec![ pin_protocols: Some(vec![
@@ -1124,7 +1124,7 @@ mod test {
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0x03, 0x50, 0x03, 0x50,
]); ]);
expected_response.extend(AAGUID); expected_response.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(&[ expected_response.extend(&[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69, 0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
@@ -1197,7 +1197,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00, 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
]; ];
expected_auth_data.extend(AAGUID); expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]); expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!( assert_eq!(
auth_data[0..expected_auth_data.len()], auth_data[0..expected_auth_data.len()],
@@ -1234,7 +1234,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00, 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
]; ];
expected_auth_data.extend(AAGUID); expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]); expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
assert_eq!( assert_eq!(
auth_data[0..expected_auth_data.len()], auth_data[0..expected_auth_data.len()],
@@ -1330,7 +1330,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
]; ];
expected_auth_data.extend(AAGUID); expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]); expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!( assert_eq!(
auth_data[0..expected_auth_data.len()], auth_data[0..expected_auth_data.len()],

View File

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

View File

@@ -14,6 +14,7 @@
use crate::crypto::rng256::Rng256; use crate::crypto::rng256::Rng256;
use crate::ctap::data_formats::PublicKeyCredentialSource; use crate::ctap::data_formats::PublicKeyCredentialSource;
use crate::ctap::key_material;
use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::PIN_AUTH_LENGTH; use crate::ctap::PIN_AUTH_LENGTH;
use alloc::string::String; use alloc::string::String;
@@ -56,9 +57,14 @@ const GLOBAL_SIGNATURE_COUNTER: usize = 1;
const MASTER_KEYS: usize = 2; const MASTER_KEYS: usize = 2;
const PIN_HASH: usize = 3; const PIN_HASH: usize = 3;
const PIN_RETRIES: usize = 4; const PIN_RETRIES: usize = 4;
const NUM_TAGS: usize = 5; const ATTESTATION_PRIVATE_KEY: usize = 5;
const ATTESTATION_CERTIFICATE: usize = 6;
const AAGUID: usize = 7;
const NUM_TAGS: usize = 8;
const MAX_PIN_RETRIES: u8 = 6; const MAX_PIN_RETRIES: u8 = 6;
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
const AAGUID_LENGTH: usize = 16;
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Key { enum Key {
@@ -73,6 +79,9 @@ enum Key {
MasterKeys, MasterKeys,
PinHash, PinHash,
PinRetries, PinRetries,
AttestationPrivateKey,
AttestationCertificate,
Aaguid,
} }
pub struct MasterKeys<'a> { pub struct MasterKeys<'a> {
@@ -124,6 +133,9 @@ impl StoreConfig for Config {
MASTER_KEYS => add(Key::MasterKeys), MASTER_KEYS => add(Key::MasterKeys),
PIN_HASH => add(Key::PinHash), PIN_HASH => add(Key::PinHash),
PIN_RETRIES => add(Key::PinRetries), PIN_RETRIES => add(Key::PinRetries),
ATTESTATION_PRIVATE_KEY => add(Key::AttestationPrivateKey),
ATTESTATION_CERTIFICATE => add(Key::AttestationCertificate),
AAGUID => add(Key::Aaguid),
_ => debug_assert!(false), _ => debug_assert!(false),
} }
} }
@@ -211,6 +223,33 @@ impl PersistentStore {
}) })
.unwrap(); .unwrap();
} }
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
self.store
.insert(StoreEntry {
tag: ATTESTATION_PRIVATE_KEY,
data: key_material::ATTESTATION_PRIVATE_KEY,
sensitive: false,
})
.unwrap();
}
if self.store.find_one(&Key::AttestationCertificate).is_none() {
self.store
.insert(StoreEntry {
tag: ATTESTATION_CERTIFICATE,
data: key_material::ATTESTATION_CERTIFICATE,
sensitive: false,
})
.unwrap();
}
if self.store.find_one(&Key::Aaguid).is_none() {
self.store
.insert(StoreEntry {
tag: AAGUID,
data: key_material::AAGUID,
sensitive: false,
})
.unwrap();
}
} }
pub fn find_credential( pub fn find_credential(
@@ -394,10 +433,44 @@ impl PersistentStore {
.unwrap(); .unwrap();
} }
pub fn attestation_private_key(
&self,
) -> Result<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH], Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::AttestationPrivateKey)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let data = entry.data;
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH))
}
pub fn attestation_certificate(&self) -> Result<Vec<u8>, Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::AttestationCertificate)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
Ok(entry.data.to_vec())
}
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::Aaguid)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let data = entry.data;
if data.len() != AAGUID_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(array_ref!(data, 0, AAGUID_LENGTH))
}
pub fn reset(&mut self, rng: &mut impl Rng256) { pub fn reset(&mut self, rng: &mut impl Rng256) {
loop { loop {
let index = { let index = {
let mut iter = self.store.iter(); let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
match iter.next() { match iter.next() {
None => break, None => break,
Some((index, _)) => index, Some((index, _)) => index,
@@ -419,6 +492,13 @@ impl From<StoreError> for Ctap2StatusCode {
} }
} }
fn should_reset<'a>(entry: &StoreEntry<'a>) -> bool {
match entry.tag {
ATTESTATION_PRIVATE_KEY | ATTESTATION_CERTIFICATE | AAGUID => false,
_ => true,
}
}
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> { fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
let cbor = cbor::read(data).ok()?; let cbor = cbor::read(data).ok()?;
cbor.try_into().ok() cbor.try_into().ok()
@@ -696,4 +776,33 @@ mod test {
persistent_store.reset_pin_retries(); persistent_store.reset_pin_retries();
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES); assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
} }
#[test]
fn test_persistent_keys() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
// The persistent keys are initialized on a fresh store.
assert_eq!(
persistent_store.attestation_private_key().unwrap(),
key_material::ATTESTATION_PRIVATE_KEY
);
assert_eq!(
persistent_store.attestation_certificate().unwrap(),
key_material::ATTESTATION_CERTIFICATE
);
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
// The persistent keys stay initialized and preserve their value after a reset.
persistent_store.reset(&mut rng);
assert_eq!(
persistent_store.attestation_private_key().unwrap(),
key_material::ATTESTATION_PRIVATE_KEY
);
assert_eq!(
persistent_store.attestation_certificate().unwrap(),
key_material::ATTESTATION_CERTIFICATE
);
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
}
} }