Moves CTAP secrets to the key store (#617)
The PIN hash can be encrypted and decrypted, and CredRandom is part of the master secrets.
This commit is contained in:
@@ -23,12 +23,26 @@ use rand_core::RngCore;
|
|||||||
///
|
///
|
||||||
/// Implementations may use the environment store: [`STORAGE_KEY`] is reserved for this usage.
|
/// Implementations may use the environment store: [`STORAGE_KEY`] is reserved for this usage.
|
||||||
pub trait KeyStore {
|
pub trait KeyStore {
|
||||||
|
/// Initializes the key store (if needed).
|
||||||
|
///
|
||||||
|
/// This function should be a no-op if the key store is already initialized.
|
||||||
|
fn init(&mut self) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Returns the AES key for key handles encryption.
|
/// Returns the AES key for key handles encryption.
|
||||||
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
||||||
|
|
||||||
/// Returns the key for key handles authentication.
|
/// Returns the key for key handles authentication.
|
||||||
fn key_handle_authentication(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
fn key_handle_authentication(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
||||||
|
|
||||||
|
/// Returns the key for the CredRandom feature.
|
||||||
|
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error>;
|
||||||
|
|
||||||
|
/// Encrypts a PIN hash.
|
||||||
|
fn encrypt_pin_hash(&mut self, plain: &[u8; 16]) -> Result<[u8; 16], Error>;
|
||||||
|
|
||||||
|
/// Decrypts a PIN hash.
|
||||||
|
fn decrypt_pin_hash(&mut self, cipher: &[u8; 16]) -> Result<Secret<[u8; 16]>, Error>;
|
||||||
|
|
||||||
/// Derives an ECDSA private key from a seed.
|
/// Derives an ECDSA private key from a seed.
|
||||||
///
|
///
|
||||||
/// The result is big-endian.
|
/// The result is big-endian.
|
||||||
@@ -54,6 +68,10 @@ pub const STORAGE_KEY: usize = 2046;
|
|||||||
pub trait Helper: Env {}
|
pub trait Helper: Env {}
|
||||||
|
|
||||||
impl<T: Helper> KeyStore for T {
|
impl<T: Helper> KeyStore for T {
|
||||||
|
fn init(&mut self) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error> {
|
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error> {
|
||||||
Ok(get_master_keys(self)?.encryption.clone())
|
Ok(get_master_keys(self)?.encryption.clone())
|
||||||
}
|
}
|
||||||
@@ -62,6 +80,18 @@ impl<T: Helper> KeyStore for T {
|
|||||||
Ok(get_master_keys(self)?.authentication.clone())
|
Ok(get_master_keys(self)?.authentication.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error> {
|
||||||
|
Ok(get_master_keys(self)?.cred_random[has_uv as usize].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt_pin_hash(&mut self, plain: &[u8; 16]) -> Result<[u8; 16], Error> {
|
||||||
|
Ok(*plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt_pin_hash(&mut self, cipher: &[u8; 16]) -> Result<Secret<[u8; 16]>, Error> {
|
||||||
|
Ok(Secret::from_exposed_secret(*cipher))
|
||||||
|
}
|
||||||
|
|
||||||
fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result<Secret<[u8; 32]>, Error> {
|
fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result<Secret<[u8; 32]>, Error> {
|
||||||
match EcdsaSk::<T>::from_slice(seed) {
|
match EcdsaSk::<T>::from_slice(seed) {
|
||||||
None => Err(Error),
|
None => Err(Error),
|
||||||
@@ -91,14 +121,17 @@ struct MasterKeys {
|
|||||||
|
|
||||||
/// Master authentication key.
|
/// Master authentication key.
|
||||||
authentication: Secret<[u8; 32]>,
|
authentication: Secret<[u8; 32]>,
|
||||||
|
|
||||||
|
/// Cred random keys (without and with UV in that order).
|
||||||
|
cred_random: [Secret<[u8; 32]>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_master_keys(env: &mut impl Env) -> Result<MasterKeys, Error> {
|
fn get_master_keys(env: &mut impl Env) -> Result<MasterKeys, Error> {
|
||||||
let master_keys = match env.store().find(STORAGE_KEY)? {
|
let master_keys = match env.store().find(STORAGE_KEY)? {
|
||||||
Some(x) if x.len() == 64 => x,
|
Some(x) if x.len() == 128 => x,
|
||||||
Some(_) => return Err(Error),
|
Some(_) => return Err(Error),
|
||||||
None => {
|
None => {
|
||||||
let mut master_keys = vec![0; 64];
|
let mut master_keys = vec![0; 128];
|
||||||
env.rng().fill_bytes(&mut master_keys);
|
env.rng().fill_bytes(&mut master_keys);
|
||||||
env.store().insert(STORAGE_KEY, &master_keys)?;
|
env.store().insert(STORAGE_KEY, &master_keys)?;
|
||||||
master_keys
|
master_keys
|
||||||
@@ -108,9 +141,14 @@ fn get_master_keys(env: &mut impl Env) -> Result<MasterKeys, Error> {
|
|||||||
encryption.copy_from_slice(array_ref![master_keys, 0, 32]);
|
encryption.copy_from_slice(array_ref![master_keys, 0, 32]);
|
||||||
let mut authentication: Secret<[u8; 32]> = Secret::default();
|
let mut authentication: Secret<[u8; 32]> = Secret::default();
|
||||||
authentication.copy_from_slice(array_ref![master_keys, 32, 32]);
|
authentication.copy_from_slice(array_ref![master_keys, 32, 32]);
|
||||||
|
let mut cred_random_no_uv: Secret<[u8; 32]> = Secret::default();
|
||||||
|
cred_random_no_uv.copy_from_slice(array_ref![master_keys, 64, 32]);
|
||||||
|
let mut cred_random_with_uv: Secret<[u8; 32]> = Secret::default();
|
||||||
|
cred_random_with_uv.copy_from_slice(array_ref![master_keys, 96, 32]);
|
||||||
Ok(MasterKeys {
|
Ok(MasterKeys {
|
||||||
encryption,
|
encryption,
|
||||||
authentication,
|
authentication,
|
||||||
|
cred_random: [cred_random_no_uv, cred_random_with_uv],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +171,15 @@ mod test {
|
|||||||
// Master keys are well-defined and stable.
|
// Master keys are well-defined and stable.
|
||||||
let encryption_key = key_store.key_handle_encryption().unwrap();
|
let encryption_key = key_store.key_handle_encryption().unwrap();
|
||||||
let authentication_key = key_store.key_handle_authentication().unwrap();
|
let authentication_key = key_store.key_handle_authentication().unwrap();
|
||||||
|
let cred_random_no_uv = key_store.cred_random(false).unwrap();
|
||||||
|
let cred_random_with_uv = key_store.cred_random(true).unwrap();
|
||||||
assert_eq!(&key_store.key_handle_encryption().unwrap(), &encryption_key);
|
assert_eq!(&key_store.key_handle_encryption().unwrap(), &encryption_key);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&key_store.key_handle_authentication().unwrap(),
|
&key_store.key_handle_authentication().unwrap(),
|
||||||
&authentication_key
|
&authentication_key
|
||||||
);
|
);
|
||||||
|
assert_eq!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
||||||
|
assert_eq!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
||||||
|
|
||||||
// ECDSA seeds are well-defined and stable.
|
// ECDSA seeds are well-defined and stable.
|
||||||
let ecdsa_seed = key_store.generate_ecdsa_seed().unwrap();
|
let ecdsa_seed = key_store.generate_ecdsa_seed().unwrap();
|
||||||
@@ -152,5 +194,19 @@ mod test {
|
|||||||
key_store.key_handle_authentication().unwrap(),
|
key_store.key_handle_authentication().unwrap(),
|
||||||
authentication_key
|
authentication_key
|
||||||
);
|
);
|
||||||
|
assert_ne!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
||||||
|
assert_ne!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pin_hash_encrypt_decrypt() {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let key_store = env.key_store();
|
||||||
|
assert_eq!(key_store.init(), Ok(()));
|
||||||
|
|
||||||
|
let pin_hash = [0x55; 16];
|
||||||
|
let encrypted = key_store.encrypt_pin_hash(&pin_hash).unwrap();
|
||||||
|
let decrypted = key_store.decrypt_pin_hash(&encrypted).unwrap();
|
||||||
|
assert_eq!(pin_hash, *decrypted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use crate::api::crypto::ecdh::SecretKey as _;
|
|||||||
use crate::api::crypto::hmac256::Hmac256;
|
use crate::api::crypto::hmac256::Hmac256;
|
||||||
use crate::api::crypto::sha256::Sha256;
|
use crate::api::crypto::sha256::Sha256;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
|
use crate::api::key_store::KeyStore;
|
||||||
use crate::ctap::storage;
|
use crate::ctap::storage;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::env::EcdhSk;
|
use crate::env::EcdhSk;
|
||||||
@@ -98,6 +99,9 @@ fn check_and_store_new_pin<E: Env>(
|
|||||||
}
|
}
|
||||||
let mut pin_hash = Secret::default();
|
let mut pin_hash = Secret::default();
|
||||||
Sha::<E>::digest_mut(&pin, &mut pin_hash);
|
Sha::<E>::digest_mut(&pin, &mut pin_hash);
|
||||||
|
let pin_hash = env
|
||||||
|
.key_store()
|
||||||
|
.encrypt_pin_hash(array_ref![pin_hash, 0, PIN_AUTH_LENGTH])?;
|
||||||
// The PIN length is always < PIN_PADDED_LENGTH < 256.
|
// The PIN length is always < PIN_PADDED_LENGTH < 256.
|
||||||
storage::set_pin(
|
storage::set_pin(
|
||||||
env,
|
env,
|
||||||
@@ -182,6 +186,7 @@ impl<E: Env> ClientPin<E> {
|
|||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||||
}
|
}
|
||||||
storage::decr_pin_retries(env)?;
|
storage::decr_pin_retries(env)?;
|
||||||
|
let pin_hash = env.key_store().decrypt_pin_hash(&pin_hash)?;
|
||||||
let pin_hash_dec = shared_secret
|
let pin_hash_dec = shared_secret
|
||||||
.decrypt(&pin_hash_enc)
|
.decrypt(&pin_hash_enc)
|
||||||
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
|
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ use crate::api::crypto::hkdf256::Hkdf256;
|
|||||||
use crate::api::crypto::sha256::Sha256;
|
use crate::api::crypto::sha256::Sha256;
|
||||||
use crate::api::crypto::HASH_SIZE;
|
use crate::api::crypto::HASH_SIZE;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
|
use crate::api::key_store::KeyStore;
|
||||||
use crate::api::rng::Rng;
|
use crate::api::rng::Rng;
|
||||||
use crate::api::user_presence::{UserPresence, UserPresenceError};
|
use crate::api::user_presence::{UserPresence, UserPresenceError};
|
||||||
use crate::env::{EcdsaSk, Env, Hkdf, Sha};
|
use crate::env::{EcdsaSk, Env, Hkdf, Sha};
|
||||||
@@ -956,9 +957,9 @@ impl<E: Env> CtapState<E> {
|
|||||||
) -> Result<Secret<[u8; HASH_SIZE]>, Ctap2StatusCode> {
|
) -> Result<Secret<[u8; HASH_SIZE]>, Ctap2StatusCode> {
|
||||||
let private_key_bytes = private_key.to_bytes();
|
let private_key_bytes = private_key.to_bytes();
|
||||||
let salt = array_ref!(private_key_bytes, 0, 32);
|
let salt = array_ref!(private_key_bytes, 0, 32);
|
||||||
let key = storage::cred_random_secret(env, has_uv)?;
|
let key = env.key_store().cred_random(has_uv)?;
|
||||||
let mut output = Secret::default();
|
let mut output = Secret::default();
|
||||||
Hkdf::<E>::hkdf_256(&key, salt, b"credRandom", &mut output);
|
Hkdf::<E>::hkdf_256(&*key, salt, b"credRandom", &mut output);
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ mod key;
|
|||||||
use crate::api::attestation_store::{self, AttestationStore};
|
use crate::api::attestation_store::{self, AttestationStore};
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::api::key_store::KeyStore;
|
use crate::api::key_store::KeyStore;
|
||||||
use crate::api::rng::Rng;
|
|
||||||
use crate::ctap::client_pin::PIN_AUTH_LENGTH;
|
use crate::ctap::client_pin::PIN_AUTH_LENGTH;
|
||||||
use crate::ctap::data_formats::{
|
use crate::ctap::data_formats::{
|
||||||
extract_array, extract_text_string, PublicKeyCredentialSource, PublicKeyCredentialUserEntity,
|
extract_array, extract_text_string, PublicKeyCredentialSource, PublicKeyCredentialUserEntity,
|
||||||
@@ -45,15 +44,7 @@ struct PinProperties {
|
|||||||
|
|
||||||
/// Initializes the store by creating missing objects.
|
/// Initializes the store by creating missing objects.
|
||||||
pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
|
pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
|
||||||
// Generate and store the CredRandom secrets if they are missing.
|
env.key_store().init()?;
|
||||||
if env.store().find_handle(key::CRED_RANDOM_SECRET)?.is_none() {
|
|
||||||
let cred_random_with_uv = env.rng().gen_uniform_u8x32();
|
|
||||||
let cred_random_without_uv = env.rng().gen_uniform_u8x32();
|
|
||||||
let mut cred_random = Vec::with_capacity(64);
|
|
||||||
cred_random.extend_from_slice(&cred_random_without_uv);
|
|
||||||
cred_random.extend_from_slice(&cred_random_with_uv);
|
|
||||||
env.store().insert(key::CRED_RANDOM_SECRET, &cred_random)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,19 +246,6 @@ pub fn incr_global_signature_counter(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the CredRandom secret.
|
|
||||||
pub fn cred_random_secret(env: &mut impl Env, has_uv: bool) -> Result<[u8; 32], Ctap2StatusCode> {
|
|
||||||
let cred_random_secret = env
|
|
||||||
.store()
|
|
||||||
.find(key::CRED_RANDOM_SECRET)?
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
|
||||||
if cred_random_secret.len() != 64 {
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
let offset = if has_uv { 32 } else { 0 };
|
|
||||||
Ok(*array_ref![cred_random_secret, offset, 32])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the PIN properties and wraps them into PinProperties.
|
/// Reads the PIN properties and wraps them into PinProperties.
|
||||||
fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2StatusCode> {
|
fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2StatusCode> {
|
||||||
let pin_properties = match env.store().find(key::PIN_PROPERTIES)? {
|
let pin_properties = match env.store().find(key::PIN_PROPERTIES)? {
|
||||||
@@ -620,6 +598,7 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec<String>) -> Result<Vec<u8>, Ctap2
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
|
use crate::api::rng::Rng;
|
||||||
use crate::ctap::crypto_wrapper::PrivateKey;
|
use crate::ctap::crypto_wrapper::PrivateKey;
|
||||||
use crate::ctap::data_formats::{
|
use crate::ctap::data_formats::{
|
||||||
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
|
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
|
||||||
@@ -846,28 +825,6 @@ mod test {
|
|||||||
assert_eq!(found_credential, Some(expected_credential));
|
assert_eq!(found_credential, Some(expected_credential));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cred_random_secret() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
init(&mut env).unwrap();
|
|
||||||
|
|
||||||
// CredRandom secrets stay the same within the same CTAP reset cycle.
|
|
||||||
let cred_random_with_uv_1 = cred_random_secret(&mut env, true).unwrap();
|
|
||||||
let cred_random_without_uv_1 = cred_random_secret(&mut env, false).unwrap();
|
|
||||||
let cred_random_with_uv_2 = cred_random_secret(&mut env, true).unwrap();
|
|
||||||
let cred_random_without_uv_2 = cred_random_secret(&mut env, false).unwrap();
|
|
||||||
assert_eq!(cred_random_with_uv_1, cred_random_with_uv_2);
|
|
||||||
assert_eq!(cred_random_without_uv_1, cred_random_without_uv_2);
|
|
||||||
|
|
||||||
// CredRandom secrets change after reset. This test may fail if the random generator produces the
|
|
||||||
// same keys.
|
|
||||||
reset(&mut env).unwrap();
|
|
||||||
let cred_random_with_uv_3 = cred_random_secret(&mut env, true).unwrap();
|
|
||||||
let cred_random_without_uv_3 = cred_random_secret(&mut env, false).unwrap();
|
|
||||||
assert!(cred_random_with_uv_1 != cred_random_with_uv_3);
|
|
||||||
assert!(cred_random_without_uv_1 != cred_random_without_uv_3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pin_hash_and_length() {
|
fn test_pin_hash_and_length() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ make_partition! {
|
|||||||
FORCE_PIN_CHANGE = 2040;
|
FORCE_PIN_CHANGE = 2040;
|
||||||
|
|
||||||
/// The secret of the CredRandom feature.
|
/// The secret of the CredRandom feature.
|
||||||
CRED_RANDOM_SECRET = 2041;
|
_CRED_RANDOM_SECRET = 2041;
|
||||||
|
|
||||||
/// List of RP IDs allowed to read the minimum PIN length.
|
/// List of RP IDs allowed to read the minimum PIN length.
|
||||||
MIN_PIN_LENGTH_RP_IDS = 2042;
|
MIN_PIN_LENGTH_RP_IDS = 2042;
|
||||||
|
|||||||
Reference in New Issue
Block a user