diff --git a/src/storage/aci.rs b/src/storage/aci.rs index f235520..33f6c0d 100644 --- a/src/storage/aci.rs +++ b/src/storage/aci.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use crate::aes; -use crate::attribute::{Attribute, CkAttrs}; +use crate::attribute::CkAttrs; use crate::error::Result; use crate::interface::*; use crate::kasn1::*; @@ -15,6 +15,7 @@ use crate::CSPRNG; use crate::{byte_ptr, sizeof, void_ptr}; use asn1; +use zeroize::Zeroize; pub fn pbkdf2_derive( facilities: &TokenFacilities, @@ -177,7 +178,6 @@ fn aes_gcm_decrypt( } const SHA256_LEN: usize = 32; -const KEK_AAD: &str = "ENCRYPTION KEY"; const GEN_KEYTYP: CK_ULONG = CKK_GENERIC_SECRET; const AES_KEYTYP: CK_ULONG = CKK_AES; const AES_KEYLEN: CK_ULONG = aes::MAX_AES_SIZE_BYTES as CK_ULONG; @@ -203,6 +203,7 @@ fn secret_key_template<'a>( fn encrypt_key( facilities: &TokenFacilities, + id: &str, pin: &[u8], iterations: usize, key_version: u64, @@ -226,8 +227,7 @@ fn encrypt_key( pin, key_template.as_slice(), )?; - let (gcm, data) = - aes_gcm_encrypt(facilities, &kek, KEK_AAD.as_bytes(), key)?; + let (gcm, data) = aes_gcm_encrypt(facilities, &kek, id.as_bytes(), key)?; let enc_params = KAlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), @@ -258,6 +258,7 @@ fn encrypt_key( fn decrypt_key( facilities: &TokenFacilities, + id: &str, pin: &[u8], data: &[u8], ) -> Result<(u64, Vec)> { @@ -292,13 +293,7 @@ fn decrypt_key( }; Ok(( kkbps1.key_version_number, - aes_gcm_decrypt( - facilities, - &key, - params, - KEK_AAD.as_bytes(), - pdata.data, - )?, + aes_gcm_decrypt(facilities, &key, params, id.as_bytes(), pdata.data)?, )) } @@ -393,22 +388,46 @@ fn decrypt_data( aes_gcm_decrypt(facilities, &dek, params, data_id.as_bytes(), pdata.data) } -const DEFAULT_PIN: &str = "DEFAULT PIN"; -const DEFPIN_ITER: usize = 1000; - const MAX_LOGIN_ATTEMPTS: CK_ULONG = 10; pub struct StorageAuthInfo { + pub default_pin: bool, + pub user_data: Option>, pub max_attempts: CK_ULONG, pub cur_attempts: CK_ULONG, - pub locked: bool, - pub update_obj: bool, +} + +impl Default for StorageAuthInfo { + fn default() -> StorageAuthInfo { + StorageAuthInfo { + default_pin: false, + user_data: None, + max_attempts: MAX_LOGIN_ATTEMPTS, + cur_attempts: 0, + } + } +} + +impl Drop for StorageAuthInfo { + fn drop(&mut self) { + if let Some(ref mut data) = self.user_data { + data.zeroize(); + } + } +} + +impl StorageAuthInfo { + pub fn locked(&self) -> bool { + self.cur_attempts >= self.max_attempts + } } /* Storage abstract Authentication, Confidentialiy, Integrity * functionality */ #[derive(Debug)] pub struct StorageACI { + def_iterations: usize, + key_version: u64, key: Option, encrypt: bool, } @@ -416,6 +435,8 @@ pub struct StorageACI { impl StorageACI { pub fn new(encrypt: bool) -> StorageACI { StorageACI { + def_iterations: 1000, + key_version: 0, key: None, encrypt: encrypt, } @@ -434,6 +455,7 @@ impl StorageACI { * key and HKDF allows only CKK_HKDf or CKK_GENERIC_SECRET */ let template = secret_key_template(&GEN_KEYTYP, &AES_KEYLEN); let mech = facilities.mechanisms.get(CKM_GENERIC_SECRET_KEY_GEN)?; + self.key_version += 1; self.key = Some(mech.generate_key( &CK_MECHANISM { mechanism: CKM_GENERIC_SECRET_KEY_GEN, @@ -458,10 +480,9 @@ impl StorageACI { val: &Vec, ) -> Result> { if let Some(ref key) = self.key { - let key_version = 1; /* FIXME */ encrypt_data( facilities, - key_version, + self.key_version, key, uid.as_str(), val.as_slice(), @@ -478,10 +499,9 @@ impl StorageACI { val: &Vec, ) -> Result> { if let Some(ref key) = self.key { - let key_version = 1; /* FIXME */ decrypt_data( facilities, - key_version, + self.key_version, key, uid.as_str(), val.as_slice(), @@ -491,66 +511,6 @@ impl StorageACI { } } - fn new_storage_object(uid: &String) -> Result { - let mut obj = Object::new(); - obj.set_zeroize(); - obj.set_attr(Attribute::from_string(CKA_UNIQUE_ID, uid.clone()))?; - obj.set_attr(Attribute::from_bool(CKA_TOKEN, true))?; - obj.set_attr(Attribute::from_ulong(CKA_CLASS, CKO_SECRET_KEY))?; - obj.set_attr(Attribute::from_ulong(CKA_KEY_TYPE, CKK_GENERIC_SECRET))?; - obj.set_attr(Attribute::from_ulong( - KRA_MAX_LOGIN_ATTEMPTS, - MAX_LOGIN_ATTEMPTS, - ))?; - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, 0))?; - Ok(obj) - } - - fn key_to_storage_object( - &mut self, - facilities: &TokenFacilities, - pin: &[u8], - uid: &String, - ) -> Result { - let ek = if self.encrypt { - match self.key { - Some(ref k) => k.get_attr_as_bytes(CKA_VALUE)?.as_slice(), - None => return Err(CKR_USER_NOT_LOGGED_IN)?, - } - } else { - "NO ENCRYPTION".as_bytes() - }; - let iterations = DEFPIN_ITER; - let key_version = 1; /* FIXME */ - let wrapped = - encrypt_key(facilities, pin, iterations, key_version, ek)?; - let mut obj = Self::new_storage_object(uid)?; - obj.set_attr(Attribute::from_bytes(CKA_VALUE, wrapped))?; - - Ok(obj) - } - - pub fn auth_object_is_default(&self, obj: &Object) -> Result { - match obj.get_attr_as_string(CKA_LABEL) { - Ok(label) => Ok(label.as_str() == DEFAULT_PIN), - Err(_) => Ok(false), - } - } - - /// Returns, the max available and the current attempts at - /// authentication that resulted in a failure. A successful - /// authentication causes current to return to 0 - pub fn user_attempts(&self, obj: &Object) -> Result { - let max = obj.get_attr_as_ulong(KRA_MAX_LOGIN_ATTEMPTS)?; - let cur = obj.get_attr_as_ulong(KRA_LOGIN_ATTEMPTS)?; - Ok(StorageAuthInfo { - max_attempts: max, - cur_attempts: cur, - locked: cur >= max, - update_obj: false, - }) - } - /// Creates a user authentication token by deriving a key encryption /// key (kek) from the pin. /// @@ -558,73 +518,74 @@ impl StorageACI { /// with this key and returned as the auth token. /// /// Otherwise the derived key is returned as the token. - /// - /// If the pin is empty, sets a label that indicated the PIN needs to - /// be reset. - pub fn make_auth_object( + pub fn key_to_user_data( &mut self, facilities: &TokenFacilities, - uid: &String, + uid: &str, pin: &[u8], - ) -> Result { - let mut key = self.key_to_storage_object(facilities, pin, uid)?; - if pin.len() == 0 { - key.set_attr(Attribute::from_string( - CKA_LABEL, - DEFAULT_PIN.to_string(), - ))?; + ) -> Result { + let mut info = StorageAuthInfo::default(); + let ek = if self.encrypt { + match self.key { + Some(ref k) => k.get_attr_as_bytes(CKA_VALUE)?.as_slice(), + None => return Err(CKR_USER_NOT_LOGGED_IN)?, + } + } else { + "NO ENCRYPTION".as_bytes() }; - Ok(key) + info.user_data = Some(encrypt_key( + facilities, + uid, + pin, + self.def_iterations, + self.key_version, + ek, + )?); + Ok(info) } pub fn authenticate( &mut self, facilities: &TokenFacilities, - auth_obj: &mut Object, + uid: &str, + info: &mut StorageAuthInfo, pin: &[u8], set_key: bool, - ) -> Result { - let mut info = self.user_attempts(&auth_obj)?; - if info.locked { - return Ok(info); + ) -> Result { + if info.locked() { + return Ok(false); } let stored = info.cur_attempts; - let wrapped = auth_obj.get_attr_as_bytes(CKA_VALUE)?.as_slice(); - match decrypt_key(facilities, pin, wrapped) { + let wrapped = match &info.user_data { + Some(data) => data.as_slice(), + None => return Err(CKR_GENERAL_ERROR)?, + }; + match decrypt_key(facilities, uid, pin, wrapped) { Ok((key_version, key)) => { - if key_version != 1 { - /* FIXME */ - info.cur_attempts += 1; - } else if !self.encrypt { - if key.as_slice() == "NO ENCRYPTION".as_bytes() { - info.cur_attempts = 0; - } else { - info.cur_attempts += 1; - } - } else if self.encrypt { + if self.encrypt { if set_key { let mut template = secret_key_template(&GEN_KEYTYP, &AES_KEYLEN); template.add_vec(CKA_VALUE, key)?; + self.key_version = key_version; self.key = Some( facilities.factories.create(template.as_slice())?, ); } info.cur_attempts = 0; + } else { + if key.as_slice() == "NO ENCRYPTION".as_bytes() { + info.cur_attempts = 0; + } else { + info.cur_attempts += 1; + } } } Err(_) => info.cur_attempts += 1, } - /* Store attempts back to token */ - if info.cur_attempts != stored { - info.update_obj = true; - auth_obj.set_attr(Attribute::from_ulong( - KRA_LOGIN_ATTEMPTS, - info.cur_attempts, - ))?; - } - Ok(info) + /* Indicate if data has changed */ + Ok(info.cur_attempts != stored) } } diff --git a/src/storage/format.rs b/src/storage/format.rs index 10196e3..750834c 100644 --- a/src/storage/format.rs +++ b/src/storage/format.rs @@ -11,48 +11,38 @@ use crate::storage::aci::{StorageACI, StorageAuthInfo}; use crate::storage::{Storage, StorageTokenInfo}; use crate::token::TokenFacilities; -pub static SO_PIN_FLAGS: [CK_FLAGS; 4] = [ - CKF_SO_PIN_LOCKED, /* 0 attempts left */ - CKF_SO_PIN_FINAL_TRY, /* 1 attempt left */ - CKF_SO_PIN_COUNT_LOW, /* 2 or 3 .. */ - CKF_SO_PIN_COUNT_LOW, /* attempts left */ -]; - -pub static USER_PIN_FLAGS: [CK_FLAGS; 4] = [ - CKF_USER_PIN_LOCKED, /* 0 attempts left */ - CKF_USER_PIN_FINAL_TRY, /* 1 attempt left */ - CKF_USER_PIN_COUNT_LOW, /* 2 or 3 .. */ - CKF_USER_PIN_COUNT_LOW, /* attempts left */ -]; - -const SO_OBJ_UID: &str = "0"; -const USER_OBJ_UID: &str = "1"; - -pub fn so_obj_uid() -> String { - SO_OBJ_UID.to_string() -} - -pub fn user_obj_uid() -> String { - USER_OBJ_UID.to_string() -} - pub fn user_flags( user_type: CK_USER_TYPE, info: &StorageAuthInfo, flag: &mut CK_FLAGS, ) { - let remaining = if info.locked { + let remaining = if info.locked() { 0 } else { info.max_attempts - info.cur_attempts }; - if remaining > 3 { - *flag = 0; - } else if user_type == CKU_SO { - /* casting here is fine because remaining is guaranteed to fit */ - *flag = SO_PIN_FLAGS[remaining as usize]; + if user_type == CKU_SO { + *flag |= match remaining { + 0 => CKF_SO_PIN_LOCKED, + 1 => CKF_SO_PIN_FINAL_TRY, + 2 | 3 => CKF_SO_PIN_COUNT_LOW, + _ => 0, + }; + if info.default_pin { + *flag |= CKF_SO_PIN_TO_BE_CHANGED; + } } else if user_type == CKU_USER { - *flag = USER_PIN_FLAGS[remaining as usize]; + *flag |= match remaining { + 0 => CKF_USER_PIN_LOCKED, + 1 => CKF_USER_PIN_FINAL_TRY, + 2 | 3 => CKF_USER_PIN_COUNT_LOW, + _ => 0, + }; + if info.default_pin { + *flag |= CKF_USER_PIN_TO_BE_CHANGED; + } else { + *flag |= CKF_USER_PIN_INITIALIZED; + } } } @@ -70,10 +60,13 @@ fn checked_pin(pin: &[u8]) -> &[u8] { pin } -fn get_pin_uid(user_type: CK_USER_TYPE) -> Result { +const SO_ID: &str = "SO"; +const USER_ID: &str = "USER"; + +fn get_pin_uid(user_type: CK_USER_TYPE) -> Result<&'static str> { match user_type { - CKU_SO => Ok(so_obj_uid()), - CKU_USER => Ok(user_obj_uid()), + CKU_SO => Ok(SO_ID), + CKU_USER => Ok(USER_ID), _ => return Err(CKR_GENERAL_ERROR)?, } } @@ -113,6 +106,16 @@ pub trait StorageRaw: Debug + Send + Sync { fn store_token_info(&mut self, _info: &StorageTokenInfo) -> Result<()> { Err(CKR_GENERAL_ERROR)? } + fn fetch_user(&self, _uid: &str) -> Result { + Err(CKR_GENERAL_ERROR)? + } + fn store_user( + &mut self, + _uid: &str, + _data: &StorageAuthInfo, + ) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } } #[derive(Debug)] @@ -134,40 +137,19 @@ impl StdStorageFormat { fn init_pin_flags(&mut self) -> Result { let mut so_flags: CK_FLAGS = 0; - let uid = get_pin_uid(CKU_SO)?; - let obj = match self.store.fetch_by_uid(&uid, &[]) { - Ok(o) => o, - Err(e) => { - if e.attr_not_found() { - return Err(CKR_USER_PIN_NOT_INITIALIZED)?; - } else { - return Err(e); - } - } - }; - let info = self.aci.user_attempts(&obj)?; + let info = self.store.fetch_user(SO_ID)?; user_flags(CKU_SO, &info, &mut so_flags); - if self.aci.auth_object_is_default(&obj)? { - so_flags |= CKF_SO_PIN_TO_BE_CHANGED; - } let mut usr_flags: CK_FLAGS = 0; - let uid = get_pin_uid(CKU_USER)?; - match self.store.fetch_by_uid(&uid, &[]) { - Ok(obj) => { - let info = self.aci.user_attempts(&obj)?; + match self.store.fetch_user(USER_ID) { + Ok(info) => { user_flags(CKU_USER, &info, &mut usr_flags); - if self.aci.auth_object_is_default(&obj)? { - usr_flags |= CKF_USER_PIN_TO_BE_CHANGED; - } else { - usr_flags |= CKF_USER_PIN_INITIALIZED; - } } Err(e) => { /* if the user object is not available we just ignore it. * This happen on DB resets, and initialization, until a pin * is set */ - if !e.attr_not_found() { + if e.rv() != CKR_USER_PIN_NOT_INITIALIZED { return Err(e); } } @@ -176,12 +158,10 @@ impl StdStorageFormat { } fn default_so_pin(&mut self, facilities: &TokenFacilities) -> Result<()> { - let auth_obj = self.aci.make_auth_object( - facilities, - &get_pin_uid(CKU_SO)?, - checked_pin(&[]), - )?; - self.store.store_obj(auth_obj) + let data = + self.aci + .key_to_user_data(facilities, SO_ID, checked_pin(&[]))?; + self.store.store_user(SO_ID, &data) } fn default_token_info( @@ -394,33 +374,25 @@ impl Storage for StdStorageFormat { check_only: bool, ) -> Result<()> { let uid = get_pin_uid(user_type)?; - let mut auth_obj = match self.store.fetch_by_uid(&uid, &[]) { - Ok(o) => o, - Err(e) => { - if e.attr_not_found() { - return Err(CKR_USER_PIN_NOT_INITIALIZED)?; - } else { - return Err(e); - } - } - }; - let info = self.aci.authenticate( + let mut user_data = self.store.fetch_user(uid)?; + let update = self.aci.authenticate( facilities, - &mut auth_obj, + &uid, + &mut user_data, checked_pin(pin), !check_only, )?; - if info.update_obj { - let _ = self.store.store_obj(auth_obj); + if update { + let _ = self.store.store_user(uid, &user_data); } - if info.cur_attempts == 0 { + if user_data.cur_attempts == 0 { *flag = 0; return Ok(()); } - user_flags(user_type, &info, flag); - if info.locked { + user_flags(user_type, &user_data, flag); + if user_data.locked() { Err(CKR_PIN_LOCKED)? } else { Err(CKR_PIN_INCORRECT)? @@ -428,17 +400,8 @@ impl Storage for StdStorageFormat { } fn unauth_user(&mut self, user_type: CK_USER_TYPE) -> Result<()> { - let uid = get_pin_uid(user_type)?; - let _ = match self.store.fetch_by_uid(&uid, &[]) { - Ok(o) => o, - Err(e) => { - if e.attr_not_found() { - return Err(CKR_USER_PIN_NOT_INITIALIZED)?; - } else { - return Err(e); - } - } - }; + /* check it exists so we return the correct error */ + let _ = self.store.fetch_user(get_pin_uid(user_type)?)?; self.aci.unauth(); Ok(()) } @@ -449,11 +412,8 @@ impl Storage for StdStorageFormat { user_type: CK_USER_TYPE, pin: &[u8], ) -> Result<()> { - let obj = self.aci.make_auth_object( - facilities, - &get_pin_uid(user_type)?, - pin, - )?; - self.store.store_obj(obj) + let uid = get_pin_uid(user_type)?; + let data = self.aci.key_to_user_data(facilities, uid, pin)?; + self.store.store_user(uid, &data) } } diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index 57dab49..38a41b1 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -8,7 +8,7 @@ use crate::error::{Error, Result}; use crate::interface::*; use crate::misc::copy_sized_string; use crate::object::Object; -use crate::storage::aci::StorageACI; +use crate::storage::aci::{StorageACI, StorageAuthInfo}; use crate::storage::format::{StdStorageFormat, StorageRaw}; use crate::storage::sqlite_common::check_table; use crate::storage::{Storage, StorageDBInfo, StorageTokenInfo}; @@ -242,6 +242,11 @@ impl SqliteStorage { } } +const USER_FLAGS: &str = "USER FLAGS"; +const USER_COUNTER: &str = "USER COUNTER"; +const USER_DATA: &str = "USER DATA"; +const USER_FLAGS_DEFAULT_PIN: u32 = 1; + impl StorageRaw for SqliteStorage { fn is_initialized(&self) -> Result<()> { let conn = self.conn.lock()?; @@ -453,6 +458,80 @@ impl StorageRaw for SqliteStorage { tx.commit().map_err(bad_storage) } + + fn fetch_user(&self, uid: &str) -> Result { + let conn = self.conn.lock()?; + let mut stmt = conn.prepare( + "SELECT name, data from meta WHERE (name in (?, ?, ?) AND value=?)", + )?; + let mut rows = stmt.query(params![ + Value::from(ValueRef::from(USER_FLAGS)), + Value::from(ValueRef::from(USER_COUNTER)), + Value::from(ValueRef::from(USER_DATA)), + Value::from(ValueRef::from(uid)) + ])?; + let mut info = StorageAuthInfo::default(); + while let Some(row) = rows.next()? { + let name: String = row.get(0)?; + let data: Vec = row.get(1)?; + match name.as_str() { + USER_FLAGS => { + let flags32 = u32::from_le_bytes(data.try_into()?); + if flags32 & USER_FLAGS_DEFAULT_PIN != 0 { + info.default_pin = true; + } + } + USER_COUNTER => { + info.cur_attempts = CK_ULONG::try_from(u32::from_le_bytes( + data.try_into()?, + ))? + } + USER_DATA => info.user_data = Some(data), + _ => (), /* ignore unknown values for forward compatibility */ + } + } + if info.user_data.is_none() { + return Err(CKR_USER_PIN_NOT_INITIALIZED)?; + } + Ok(info) + } + + fn store_user(&mut self, uid: &str, data: &StorageAuthInfo) -> Result<()> { + let mut conn = self.conn.lock()?; + let mut tx = conn.transaction().map_err(bad_storage)?; + tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); + + if data.default_pin { + let flags32 = USER_FLAGS_DEFAULT_PIN; + Self::store_meta( + &mut tx, + USER_FLAGS, + None, + Some(uid), + Some(&flags32.to_le_bytes() as &[u8]), + )?; + } + + let counter32 = u32::try_from(data.cur_attempts)?; + Self::store_meta( + &mut tx, + USER_COUNTER, + None, + Some(uid), + Some(&counter32.to_le_bytes() as &[u8]), + )?; + + if let Some(blob) = &data.user_data { + Self::store_meta( + &mut tx, + USER_DATA, + None, + Some(uid), + Some(blob.as_slice()), + )?; + } + tx.commit().map_err(bad_storage) + } } #[derive(Debug)] diff --git a/src/tests/login.rs b/src/tests/login.rs index bc43280..0bbc2f4 100644 --- a/src/tests/login.rs +++ b/src/tests/login.rs @@ -66,7 +66,7 @@ fn test_login() { assert_eq!(ret, CKR_OK); assert_eq!(token_info.flags & pin_flags_mask, 0); - /* fail a few more time to brig the count to low */ + /* fail a few more times to bring the count to low */ for _ in 1..7 { let pin = "87654321"; let ret = fn_login(