From bcd382e5e930cb16650bf64ecffe7579f0d55e9f Mon Sep 17 00:00:00 2001 From: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:32:58 +0200 Subject: [PATCH] Moves CTAP secrets to the key store (#617) The PIN hash can be encrypted and decrypted, and CredRandom is part of the master secrets. --- libraries/opensk/src/api/key_store.rs | 60 +++++++++++++++++++++++- libraries/opensk/src/ctap/client_pin.rs | 5 ++ libraries/opensk/src/ctap/mod.rs | 5 +- libraries/opensk/src/ctap/storage.rs | 47 +------------------ libraries/opensk/src/ctap/storage/key.rs | 2 +- 5 files changed, 69 insertions(+), 50 deletions(-) diff --git a/libraries/opensk/src/api/key_store.rs b/libraries/opensk/src/api/key_store.rs index 3c19f2c..90d6d0b 100644 --- a/libraries/opensk/src/api/key_store.rs +++ b/libraries/opensk/src/api/key_store.rs @@ -23,12 +23,26 @@ use rand_core::RngCore; /// /// Implementations may use the environment store: [`STORAGE_KEY`] is reserved for this usage. 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. fn key_handle_encryption(&mut self) -> Result, Error>; /// Returns the key for key handles authentication. fn key_handle_authentication(&mut self) -> Result, Error>; + /// Returns the key for the CredRandom feature. + fn cred_random(&mut self, has_uv: bool) -> Result, 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, Error>; + /// Derives an ECDSA private key from a seed. /// /// The result is big-endian. @@ -54,6 +68,10 @@ pub const STORAGE_KEY: usize = 2046; pub trait Helper: Env {} impl KeyStore for T { + fn init(&mut self) -> Result<(), Error> { + Ok(()) + } + fn key_handle_encryption(&mut self) -> Result, Error> { Ok(get_master_keys(self)?.encryption.clone()) } @@ -62,6 +80,18 @@ impl KeyStore for T { Ok(get_master_keys(self)?.authentication.clone()) } + fn cred_random(&mut self, has_uv: bool) -> Result, 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, Error> { + Ok(Secret::from_exposed_secret(*cipher)) + } + fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result, Error> { match EcdsaSk::::from_slice(seed) { None => Err(Error), @@ -91,14 +121,17 @@ struct MasterKeys { /// Master authentication key. 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 { 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), None => { - let mut master_keys = vec![0; 64]; + let mut master_keys = vec![0; 128]; env.rng().fill_bytes(&mut master_keys); env.store().insert(STORAGE_KEY, &master_keys)?; master_keys @@ -108,9 +141,14 @@ fn get_master_keys(env: &mut impl Env) -> Result { encryption.copy_from_slice(array_ref![master_keys, 0, 32]); let mut authentication: Secret<[u8; 32]> = Secret::default(); 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 { encryption, authentication, + cred_random: [cred_random_no_uv, cred_random_with_uv], }) } @@ -133,11 +171,15 @@ mod test { // Master keys are well-defined and stable. let encryption_key = key_store.key_handle_encryption().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_authentication().unwrap(), &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. let ecdsa_seed = key_store.generate_ecdsa_seed().unwrap(); @@ -152,5 +194,19 @@ mod test { key_store.key_handle_authentication().unwrap(), 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); } } diff --git a/libraries/opensk/src/ctap/client_pin.rs b/libraries/opensk/src/ctap/client_pin.rs index db425a5..f3ccff9 100644 --- a/libraries/opensk/src/ctap/client_pin.rs +++ b/libraries/opensk/src/ctap/client_pin.rs @@ -26,6 +26,7 @@ use crate::api::crypto::ecdh::SecretKey as _; use crate::api::crypto::hmac256::Hmac256; use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; +use crate::api::key_store::KeyStore; use crate::ctap::storage; #[cfg(test)] use crate::env::EcdhSk; @@ -98,6 +99,9 @@ fn check_and_store_new_pin( } let mut pin_hash = Secret::default(); Sha::::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. storage::set_pin( env, @@ -182,6 +186,7 @@ impl ClientPin { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); } storage::decr_pin_retries(env)?; + let pin_hash = env.key_store().decrypt_pin_hash(&pin_hash)?; let pin_hash_dec = shared_secret .decrypt(&pin_hash_enc) .map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?; diff --git a/libraries/opensk/src/ctap/mod.rs b/libraries/opensk/src/ctap/mod.rs index 8e8f61e..aad8bbb 100644 --- a/libraries/opensk/src/ctap/mod.rs +++ b/libraries/opensk/src/ctap/mod.rs @@ -70,6 +70,7 @@ use crate::api::crypto::hkdf256::Hkdf256; use crate::api::crypto::sha256::Sha256; use crate::api::crypto::HASH_SIZE; use crate::api::customization::Customization; +use crate::api::key_store::KeyStore; use crate::api::rng::Rng; use crate::api::user_presence::{UserPresence, UserPresenceError}; use crate::env::{EcdsaSk, Env, Hkdf, Sha}; @@ -956,9 +957,9 @@ impl CtapState { ) -> Result, Ctap2StatusCode> { let private_key_bytes = private_key.to_bytes(); 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(); - Hkdf::::hkdf_256(&key, salt, b"credRandom", &mut output); + Hkdf::::hkdf_256(&*key, salt, b"credRandom", &mut output); Ok(output) } diff --git a/libraries/opensk/src/ctap/storage.rs b/libraries/opensk/src/ctap/storage.rs index 9e991eb..7f3df85 100644 --- a/libraries/opensk/src/ctap/storage.rs +++ b/libraries/opensk/src/ctap/storage.rs @@ -17,7 +17,6 @@ mod key; use crate::api::attestation_store::{self, AttestationStore}; use crate::api::customization::Customization; use crate::api::key_store::KeyStore; -use crate::api::rng::Rng; use crate::ctap::client_pin::PIN_AUTH_LENGTH; use crate::ctap::data_formats::{ extract_array, extract_text_string, PublicKeyCredentialSource, PublicKeyCredentialUserEntity, @@ -45,15 +44,7 @@ struct PinProperties { /// Initializes the store by creating missing objects. pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { - // Generate and store the CredRandom secrets if they are missing. - 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)?; - } + env.key_store().init()?; Ok(()) } @@ -255,19 +246,6 @@ pub fn incr_global_signature_counter( 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. fn pin_properties(env: &mut impl Env) -> Result, Ctap2StatusCode> { let pin_properties = match env.store().find(key::PIN_PROPERTIES)? { @@ -620,6 +598,7 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec) -> Result, Ctap2 mod test { use super::*; use crate::api::attestation_store::{self, Attestation, AttestationStore}; + use crate::api::rng::Rng; use crate::ctap::crypto_wrapper::PrivateKey; use crate::ctap::data_formats::{ CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType, @@ -846,28 +825,6 @@ mod test { 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] fn test_pin_hash_and_length() { let mut env = TestEnv::default(); diff --git a/libraries/opensk/src/ctap/storage/key.rs b/libraries/opensk/src/ctap/storage/key.rs index 71ae3a5..3736b37 100644 --- a/libraries/opensk/src/ctap/storage/key.rs +++ b/libraries/opensk/src/ctap/storage/key.rs @@ -101,7 +101,7 @@ make_partition! { FORCE_PIN_CHANGE = 2040; /// 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. MIN_PIN_LENGTH_RP_IDS = 2042;