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:
@@ -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()],
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user