diff --git a/.github/codespell.sh b/.github/codespell.sh index d5c9db53..7d9e1425 100755 --- a/.github/codespell.sh +++ b/.github/codespell.sh @@ -1,6 +1,6 @@ #!/bin/bash -CODESPELL="codespell --ignore-words-list=gost,sorce,clen,ot,crate" +CODESPELL="codespell --ignore-words-list=gost,sorce,clen,ot,crate,aci" result=0 echo "Running codespell on source code..." diff --git a/Cargo.toml b/Cargo.toml index 370b6296..619f5f5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ pkg-config = "0.3" [dependencies] asn1 = "0.16.2" +bimap = "0.6.3" bitflags = "2.4.1" cfg-if = "1.0.0" constant_time_eq = "0.3.0" @@ -37,7 +38,7 @@ num-integer = "0.1.45" num-traits = "0.2.17" once_cell = "1.18.0" paste = "1.0.15" -rusqlite = "0.31.0" +rusqlite = { version = "0.31.0", optional = true } serde = { version = "1.0.180", features = ["derive"] } serde_json = "1.0.104" serial_test = "3.1.1" @@ -58,10 +59,15 @@ sp800_108 = [] sshkdf = [] tlskdf = [] -# tese are always required, so easier to specify this way -basic = [ "aes", "hmac", "pbkdf2" ] +# Databases +jsondb = ["memorydb"] +memorydb = [] +sqlitedb = ["dep:rusqlite"] -#select everythign by default +# these are always required, so easier to specify this way +basic = [ "aes", "hmac", "pbkdf2", "sqlitedb" ] + +#select everything by default # Use --no-default-features --features basic, xxx for custom selections default = [ "basic", "ecc", "eddsa", "hash", "hkdf", "rsa", "sp800_108", "sshkdf", "tlskdf"] diff --git a/src/config.rs b/src/config.rs index c29b8a8c..5a345a1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,7 +37,7 @@ pub struct Slot { impl Slot { pub fn new() -> Slot { Slot { - slot: 0, + slot: u32::MAX, description: None, manufacturer: None, dbtype: None, @@ -48,7 +48,7 @@ impl Slot { #[cfg(test)] pub fn with_db(dbtype: &str, dbpath: Option) -> Slot { Slot { - slot: 0, + slot: u32::MAX, description: None, manufacturer: None, dbtype: Some(dbtype.to_string()), @@ -71,9 +71,10 @@ impl Config { Config { slots: Vec::new() } } + #[cfg(test)] pub fn add_slot(&mut self, slot: Slot) -> Result<()> { for s in &self.slots { - if slot.slot == s.slot { + if slot.slot == u32::MAX || slot.slot == s.slot { return Err(interface::KRR_SLOT_CONFIG)?; } } @@ -81,7 +82,7 @@ impl Config { Ok(()) } - pub fn find_conf() -> Result { + fn find_conf() -> Result { /* First check for our own env var, * this has the highest precedence */ match env::var("KRYOPTIC_CONF") { @@ -110,82 +111,124 @@ impl Config { } } - pub fn from_file(filename: &str) -> Result { + fn from_file(filename: &str) -> Result { let config_str = fs::read_to_string(filename)?; let conf: Config = toml::from_str(&config_str).map_err(config_error)?; Ok(conf) } - pub fn from_legacy_conf_string(name: &str) -> Result { + fn from_legacy_conf_string(name: &str) -> Result { let mut conf = Config { slots: Vec::new() }; /* backwards compatibility where we used to only specify * a file, this does not support all older options, just * the more common one of specifying a .sql file with no * slot specification. */ if name.ends_with(".sql") { - match storage::name_to_type(name) { + match storage::suffix_to_type(name) { Ok(typ_) => { let mut slot = Slot::new(); slot.dbtype = Some(typ_.to_string()); slot.dbpath = Some(name.to_string()); /* if this fails there will be no slots defined */ - let _ = conf.add_slot(slot); + let _ = conf.slots.push(slot); } Err(_) => (), } } - return Ok(conf); + Ok(conf) + } + + fn fix_slot_numbers(&mut self) { + let mut slotnum: u32 = 0; + /* if there are any slot missing a valid slot number + * we are going to allocate slots numbers after the highest + * properly configured one. Note that the config file format + * requires slot numbers, so this generally happens for legacy + * or init args configurations only, ie a single slot */ + let mut missing = false; + for slot in &self.slots { + if slot.slot != u32::MAX { + if slotnum <= slot.slot { + slotnum = slot.slot + 1; + } + } else { + missing = true; + } + } + if missing { + for slot in &mut self.slots { + if slot.slot == u32::MAX { + slot.slot = slotnum; + slotnum += 1; + } + } + } + } + + pub fn default_config() -> Result { + let filename = Self::find_conf()?; + + match Self::from_file(&filename) { + Ok(conf) => return Ok(conf), + Err(e) => { + /* attempt fallback, return original error on fail */ + match Self::from_legacy_conf_string(&filename) { + Ok(mut conf) => { + conf.fix_slot_numbers(); + return Ok(conf); + } + Err(_) => return Err(e), + } + } + } } pub fn from_init_args(&mut self, args: &str) -> Result<()> { - let assign_slot: bool; - let mut conf = if args.starts_with("kryoptic_conf=") { - assign_slot = false; + let conf = if args.starts_with("kryoptic_conf=") { let comps: Vec<&str> = args.splitn(2, '=').collect(); Self::from_file(comps[1])? } else { - assign_slot = true; Self::from_legacy_conf_string(args)? }; - if assign_slot { - /* check if it has already been loaded */ - for s in &self.slots { - if s.dbtype.as_deref() == conf.slots[0].dbtype.as_deref() - && s.dbpath.as_deref() == conf.slots[0].dbpath.as_deref() - { - conf.slots[0].slot = s.slot; - } - } - } - /* check and add slots */ for mut slot in conf.slots { - let mut slotnum: u32 = 0; let mut found = false; + /* check if it has already been loaded */ for s in &self.slots { - if assign_slot { - if slotnum <= s.slot { - slotnum += s.slot + 1; - } - } else if s.slot == slot.slot { - if s.dbtype.as_deref() != slot.dbtype.as_deref() { - return Err(interface::CKR_ARGUMENTS_BAD)?; + if slot.slot == u32::MAX { + if s.dbtype.as_deref() == slot.dbtype.as_deref() + && s.dbpath.as_deref() == slot.dbpath.as_deref() + { + /* already loaded so we just match the slot number */ + found = true; + slot.slot = s.slot; } - if s.dbpath.as_deref() != slot.dbpath.as_deref() { - return Err(interface::CKR_ARGUMENTS_BAD)?; + } else { + if slot.slot != s.slot { + if s.dbtype.as_deref() == slot.dbtype.as_deref() + && s.dbpath.as_deref() == slot.dbpath.as_deref() + { + /* already loaded in a different slot, fail! */ + return Err(interface::CKR_ARGUMENTS_BAD)?; + } + } else { + if s.dbtype.as_deref() != slot.dbtype.as_deref() { + return Err(interface::CKR_ARGUMENTS_BAD)?; + } + if s.dbpath.as_deref() != slot.dbpath.as_deref() { + return Err(interface::CKR_ARGUMENTS_BAD)?; + } + /* already present skip adding */ + found = true; } - /* already present skip adding */ - found = true; } } - if assign_slot { - slot.slot = slotnum; - } if !found { - self.add_slot(slot)?; + self.slots.push(slot); } } + self.fix_slot_numbers(); Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 5abfc06c..2821fc33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -390,35 +390,18 @@ struct GlobalConfig { conf: Config, } -impl GlobalConfig { - fn empty_config() -> GlobalConfig { - GlobalConfig { - conf: Config::new(), - } - } - - fn default_config() -> GlobalConfig { - /* if there is no config file we get an empty config */ - let filename = match Config::find_conf() { - Ok(f) => f, - Err(_) => return Self::empty_config(), - }; - /* if the file is not accessible or malformed we set an empty config, - * an error will be returned later at fn_initialize() time */ - let conf = if let Ok(c) = Config::from_file(&filename) { - c - } else if let Ok(c) = Config::from_legacy_conf_string(&filename) { - c - } else { - return Self::empty_config(); - }; - - GlobalConfig { conf: conf } - } -} - -static CONFIG: Lazy> = - Lazy::new(|| RwLock::new(GlobalConfig::default_config())); +static CONFIG: Lazy> = Lazy::new(|| { + /* if there is no config file or the configuration is malformed, + * set an empty config, an error will be returned later at + * fn_initialize() time */ + let global_conf = GlobalConfig { + conf: match Config::default_config() { + Ok(conf) => conf, + Err(_) => Config::new(), + }, + }; + RwLock::new(global_conf) +}); extern "C" fn fn_initialize(_init_args: CK_VOID_PTR) -> CK_RV { let mut gconf = global_wlock!(noinitcheck CONFIG); @@ -463,7 +446,12 @@ extern "C" fn fn_initialize(_init_args: CK_VOID_PTR) -> CK_RV { #[cfg(test)] fn force_load_config() -> CK_RV { - let testconf = GlobalConfig::default_config(); + let testconf = GlobalConfig { + conf: match Config::default_config() { + Ok(conf) => conf, + Err(e) => return e.rv(), + }, + }; if testconf.conf.slots.len() == 0 { return CKR_GENERAL_ERROR; } @@ -537,7 +525,7 @@ extern "C" fn fn_init_token( if res_or_ret!(rstate.has_sessions(slot_id)) { return CKR_SESSION_EXISTS; } - let vpin: Vec = bytes_to_vec!(pin, pin_len); + let vpin = bytes_to_slice!(pin, pin_len, u8); let vlabel: Vec = if label.is_null() { vec![0x20u8; 32] } else { @@ -565,7 +553,7 @@ extern "C" fn fn_init_pin( return CKR_USER_NOT_LOGGED_IN; } - let vpin: Vec = bytes_to_vec!(pin, pin_len); + let vpin = bytes_to_slice!(pin, pin_len, u8); ret_to_rv!(token.set_pin(CKU_USER, &vpin, &vec![0u8; 0])) } @@ -581,12 +569,30 @@ extern "C" fn fn_set_pin( if !session.is_writable() { return CKR_SESSION_READ_ONLY; } - let vpin: Vec = bytes_to_vec!(new_pin, new_len); - let vold: Vec = bytes_to_vec!(old_pin, old_len); + let vpin = bytes_to_slice!(new_pin, new_len, u8); + let vold = bytes_to_slice!(old_pin, old_len, u8); + + if vpin.len() == 0 || vold.len() == 0 { + return CKR_PIN_INVALID; + } let slot_id = session.get_slot_id(); let mut token = res_or_ret!(rstate.get_token_from_slot_mut(slot_id)); - ret_to_rv!(token.set_pin(CK_UNAVAILABLE_INFORMATION, &vpin, &vold)) + let do_logout = if token.is_logged_in(KRY_UNSPEC) { + false + } else { + ok_or_ret!(token.login(CKU_USER, &vold)); + true + }; + + let ret = + ret_to_rv!(token.set_pin(CK_UNAVAILABLE_INFORMATION, &vpin, &vold)); + + if do_logout { + let _ = token.logout(); + } + + ret } extern "C" fn fn_open_session( slot_id: CK_SLOT_ID, @@ -673,7 +679,7 @@ extern "C" fn fn_login( return CKR_SESSION_READ_ONLY_EXISTS; } } - let vpin: Vec = bytes_to_vec!(pin, pin_len); + let vpin = bytes_to_slice!(pin, pin_len, u8); let mut token = res_or_ret!(rstate.get_token_from_slot_mut(slot_id)); if user_type == CKU_CONTEXT_SPECIFIC { let session = res_or_ret!(rstate.get_session_mut(s_handle)); diff --git a/src/pkcs11/interface.rs b/src/pkcs11/interface.rs index 6656a1d0..40997961 100644 --- a/src/pkcs11/interface.rs +++ b/src/pkcs11/interface.rs @@ -32,6 +32,7 @@ pub const KRA_SERIAL_NUMBER: CK_ATTRIBUTE_TYPE = KRY_VENDOR_OFFSET + 6; /* Errors */ pub const KRR_TOKEN_NOT_INITIALIZED: CK_ULONG = KRY_VENDOR_OFFSET + 1; pub const KRR_SLOT_CONFIG: CK_ULONG = KRY_VENDOR_OFFSET + 2; +pub const KRR_CONFIG_ERROR: CK_ULONG = KRY_VENDOR_OFFSET + 3; pub const KRY_UNSPEC: CK_ULONG = CK_UNAVAILABLE_INFORMATION; diff --git a/src/storage.rs b/src/storage.rs deleted file mode 100644 index a58f62c6..00000000 --- a/src/storage.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2024 Simo Sorce -// See LICENSE.txt file for terms - -use crate::error::Result; -use crate::interface::{CKR_TOKEN_NOT_RECOGNIZED, CK_ATTRIBUTE}; -use crate::object::Object; - -use std::fmt::Debug; - -pub const SQLITEDB: &str = "sqlite"; -pub const JSONDB: &str = "json"; -pub const MEMORYDB: &str = "memory"; - -pub trait Storage: Debug + Send + Sync { - fn open(&mut self, filename: &String) -> Result<()>; - fn reinit(&mut self) -> Result<()>; - fn flush(&mut self) -> Result<()>; - fn fetch_by_uid(&self, uid: &String) -> Result; - fn store(&mut self, uid: &String, obj: Object) -> Result<()>; - fn search(&self, template: &[CK_ATTRIBUTE]) -> Result>; - fn remove_by_uid(&mut self, uid: &String) -> Result<()>; -} - -pub mod json; -pub mod memory; -pub mod sqlite; - -pub fn name_to_type(name: &str) -> Result<&'static str> { - if name.ends_with(".sql") { - Ok(SQLITEDB) - } else if name.ends_with(".json") { - Ok(JSONDB) - } else { - Err(CKR_TOKEN_NOT_RECOGNIZED)? - } -} diff --git a/src/storage/aci.rs b/src/storage/aci.rs new file mode 100644 index 00000000..a01adbaf --- /dev/null +++ b/src/storage/aci.rs @@ -0,0 +1,430 @@ +// Copyright 2024 Simo Sorce +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::aes; +use crate::attribute::{Attribute, CkAttrs}; +use crate::error::Result; +use crate::interface::*; +use crate::object::Object; +use crate::token::TokenFacilities; +use crate::{byte_ptr, get_random_data, sizeof, void_ptr}; + +const DEFPIN_SALT: &str = "DEFAULT SALT DATA"; /* at least 16 bytes for FIPS */ +const DEFPIN_ITER: CK_ULONG = 1000; +const DEFAULT_IV_SIZE: usize = 12; /* 96 bits as required by FIPS for AES GCM */ + +const USER_PIN_IV: &str = "UPIN"; + +const MAX_LOGIN_ATTEMPTS: CK_ULONG = 10; + +pub struct StorageAuthInfo { + pub max_attempts: CK_ULONG, + pub cur_attempts: CK_ULONG, + pub locked: bool, + pub update_obj: bool, +} + +/* Storage abstract Authentication, Confidentialiy, Integrity + * functionality */ +#[derive(Debug)] +pub struct StorageACI { + kek: Option, + encrypt: bool, +} + +impl StorageACI { + pub fn new(encrypt: bool) -> StorageACI { + StorageACI { + kek: None, + encrypt: encrypt, + } + } + + pub fn encrypts(&self) -> bool { + self.encrypt + } + + pub fn reset(&mut self, facilities: &TokenFacilities) -> Result<()> { + if !self.encrypt { + return Ok(()); + } + let class = CKO_SECRET_KEY; + let keytyp = CKK_AES; + let keylen = CK_ULONG::try_from(aes::MAX_AES_SIZE_BYTES)?; + let truebool: CK_BBOOL = CK_TRUE; + let mut template = CkAttrs::with_capacity(3); + template.add_ulong(CKA_CLASS, &class); + template.add_ulong(CKA_KEY_TYPE, &keytyp); + template.add_ulong(CKA_VALUE_LEN, &keylen); + template.add_bool(CKA_EXTRACTABLE, &truebool); + let aes = facilities.mechanisms.get(CKM_AES_KEY_GEN)?; + self.kek = Some(aes.generate_key( + &CK_MECHANISM { + mechanism: CKM_AES_KEY_GEN, + pParameter: std::ptr::null_mut(), + ulParameterLen: 0, + }, + template.as_slice(), + &facilities.mechanisms, + &facilities.factories, + )?); + Ok(()) + } + + pub fn unauth(&mut self) { + self.kek = None; + } + + fn wrap_kek( + &self, + facilities: &TokenFacilities, + wrapper: &Object, + ) -> Result> { + let kek = match self.kek { + Some(ref k) => k, + None => return Err(CKR_USER_NOT_LOGGED_IN)?, + }; + let vlen = kek.get_attr_as_ulong(CKA_VALUE_LEN)?; + let bs = aes::AES_BLOCK_SIZE; + let mut buf = vec![0u8; (((vlen as usize + bs) / bs) + 1) * bs]; + let aes = facilities.mechanisms.get(CKM_AES_KEY_WRAP_KWP)?; + let factory = facilities.factories.get_object_factory(&kek)?; + let outlen = aes.wrap_key( + &CK_MECHANISM { + mechanism: CKM_AES_KEY_WRAP_KWP, + pParameter: void_ptr!(USER_PIN_IV.as_ptr()), + ulParameterLen: USER_PIN_IV.len() as CK_ULONG, + }, + wrapper, + &kek, + buf.as_mut_slice(), + factory, + )?; + buf.resize(outlen, 0); + Ok(buf) + } + + fn unwrap_kek( + &mut self, + facilities: &TokenFacilities, + wrapper: &Object, + wrapped: &Object, + ) -> Result { + let data = wrapped.get_attr_as_bytes(CKA_VALUE)?.as_slice(); + let class = CKO_SECRET_KEY; + let keytyp = CKK_AES; + let keylen = aes::MAX_AES_SIZE_BYTES as CK_ULONG; + let truebool: CK_BBOOL = CK_TRUE; + let mut template = CkAttrs::with_capacity(5); + template.add_ulong(CKA_CLASS, &class); + template.add_ulong(CKA_KEY_TYPE, &keytyp); + template.add_ulong(CKA_VALUE_LEN, &keylen); + template.add_bool(CKA_ENCRYPT, &truebool); + template.add_bool(CKA_DECRYPT, &truebool); + template.add_bool(CKA_EXTRACTABLE, &truebool); + let aes = facilities.mechanisms.get(CKM_AES_KEY_WRAP_KWP)?; + aes.unwrap_key( + &CK_MECHANISM { + mechanism: CKM_AES_KEY_WRAP_KWP, + pParameter: void_ptr!(USER_PIN_IV.as_ptr()), + ulParameterLen: USER_PIN_IV.len() as CK_ULONG, + }, + wrapper, + data, + template.as_slice(), + facilities + .factories + .get_obj_factory_from_key_template(template.as_slice())?, + ) + } + + /* We should probably have lifetimes to ensure iv and aad are around for + * the lifetime of the returned structure, but this will require substantial + * reworking of the bindings, so for now we just get this comment. + * ENSURE the arguments stay in scope until CK_GCM_PARAMS is needed + * */ + fn encryption_params(iv: &[u8], aad: &[u8]) -> CK_GCM_PARAMS { + CK_GCM_PARAMS { + pIv: iv.as_ptr() as *mut CK_BYTE, + ulIvLen: iv.len() as CK_ULONG, + ulIvBits: (iv.len() * 8) as CK_ULONG, + pAAD: aad.as_ptr() as *mut CK_BYTE, + ulAADLen: aad.len() as CK_ULONG, + ulTagBits: 64 as CK_ULONG, + } + } + + pub fn encrypt_value( + &self, + facilities: &TokenFacilities, + uid: &String, + val: &Vec, + ) -> Result> { + if let Some(ref kek) = self.kek { + let mut iv = [0u8; DEFAULT_IV_SIZE]; + get_random_data(&mut iv)?; + let mut params = Self::encryption_params(&iv, uid.as_bytes()); + let mech: CK_MECHANISM = CK_MECHANISM { + mechanism: CKM_AES_GCM, + pParameter: &mut params as *mut CK_GCM_PARAMS as *mut _, + ulParameterLen: sizeof!(CK_GCM_PARAMS), + }; + let aes = facilities.mechanisms.get(CKM_AES_GCM)?; + let mut op = aes.encryption_new(&mech, &kek)?; + let clen = op.encryption_len(val.len(), false)?; + let mut encval = vec![0u8; iv.len() + clen]; + encval[..iv.len()].copy_from_slice(&iv); + let outlen = op.encrypt( + val.as_slice(), + &mut encval.as_mut_slice()[iv.len()..], + )?; + encval.resize(iv.len() + outlen, 0); + return Ok(encval); + } else { + return Err(CKR_GENERAL_ERROR)?; + } + } + + pub fn decrypt_value( + &self, + facilities: &TokenFacilities, + uid: &String, + val: &Vec, + ) -> Result> { + if let Some(ref kek) = self.kek { + let mut params = Self::encryption_params( + &val.as_slice()[..DEFAULT_IV_SIZE], + uid.as_bytes(), + ); + let mech: CK_MECHANISM = CK_MECHANISM { + mechanism: CKM_AES_GCM, + pParameter: &mut params as *mut CK_GCM_PARAMS as *mut _, + ulParameterLen: sizeof!(CK_GCM_PARAMS), + }; + let aes = facilities.mechanisms.get(CKM_AES_GCM)?; + let mut op = aes.decryption_new(&mech, &kek)?; + let mut plain = + vec![ + 0u8; + op.decryption_len(val.len() - DEFAULT_IV_SIZE, false)? + ]; + let outlen = op.decrypt( + &val.as_slice()[DEFAULT_IV_SIZE..], + plain.as_mut_slice(), + )?; + plain.resize(outlen, 0); + return Ok(plain); + } else { + return Err(CKR_GENERAL_ERROR)?; + } + } + + fn random_salt(&self) -> Result { + let mut data = [0u8; 8]; + get_random_data(&mut data)?; + Ok(hex::encode(data)) + } + + fn parse_auth_object_label( + &self, + obj: &Object, + ) -> Result<(String, CK_ULONG)> { + let label = obj.get_attr_as_string(CKA_LABEL)?; + let parts: Vec<_> = label.as_str().split(":").collect(); + if parts.len() != 2 { + return Err(CKR_GENERAL_ERROR)?; + } + Ok(( + parts[0].to_string(), + match parts[1].parse::() { + Ok(u) => u, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }, + )) + } + + fn pin_to_unlock_key( + &mut self, + facilities: &TokenFacilities, + pin: &[u8], + salt: Option, + iter: Option, + ) -> Result { + let insalt: String; + let psalt: &str = match salt { + Some(s) => { + insalt = s; + insalt.as_str() + } + None => DEFPIN_SALT, + }; + let iterations = match iter { + Some(i) => i, + None => DEFPIN_ITER, + }; + let params = CK_PKCS5_PBKD2_PARAMS2 { + saltSource: CKZ_DATA_SPECIFIED, + pSaltSourceData: void_ptr!(psalt.as_ptr()), + ulSaltSourceDataLen: CK_ULONG::try_from(psalt.len())?, + iterations: iterations, + prf: CKP_PKCS5_PBKD2_HMAC_SHA512, + pPrfData: std::ptr::null_mut(), + ulPrfDataLen: 0, + pPassword: byte_ptr!(pin.as_ptr()), + ulPasswordLen: pin.len() as CK_ULONG, + }; + let class = CKO_SECRET_KEY; + let keytyp = CKK_AES; + let keylen = aes::MAX_AES_SIZE_BYTES as CK_ULONG; + let label = format!("{}:{}", psalt, iterations); + let truebool: CK_BBOOL = CK_TRUE; + let mut template = CkAttrs::with_capacity(6); + template.add_slice(CKA_LABEL, label.as_bytes())?; + template.add_ulong(CKA_CLASS, &class); + template.add_ulong(CKA_KEY_TYPE, &keytyp); + template.add_ulong(CKA_VALUE_LEN, &keylen); + template.add_bool(CKA_WRAP, &truebool); + template.add_bool(CKA_UNWRAP, &truebool); + let pbkdf2 = facilities.mechanisms.get(CKM_PKCS5_PBKD2)?; + pbkdf2.generate_key( + &CK_MECHANISM { + mechanism: CKM_PKCS5_PBKD2, + pParameter: ¶ms as *const _ as *mut _, + ulParameterLen: sizeof!(CK_PKCS5_PBKD2_PARAMS2), + }, + template.as_slice(), + &facilities.mechanisms, + &facilities.factories, + ) + } + + fn kek_to_wrapped_object( + &mut self, + facilities: &TokenFacilities, + uid: &String, + key: Object, + ) -> Result { + /* label contains the salt and iterations information + * to recover the wrapping key from a pin */ + let label = key.get_attr_as_string(CKA_LABEL)?; + let wrapped = self.wrap_kek(facilities, &key)?; + 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_string(CKA_LABEL, label))?; + obj.set_attr(Attribute::from_bytes(CKA_VALUE, wrapped))?; + 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) + } + + pub fn auth_object_is_default(&self, obj: &Object) -> Result { + Ok(self.parse_auth_object_label(obj)?.0 == DEFPIN_SALT) + } + + /// 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 from the pin. + /// + /// If encryption is enabled, then the kek is wrapped with this key + /// and returned as the auth token. + /// + /// Otherwise the derived key is returned as the token. + /// + /// If the pin is not empty, it uses a random salt, otherwise uses + /// the DEFAULT SALT (this is also used as indication that the PIN + /// needs to be reset). + pub fn make_auth_object( + &mut self, + facilities: &TokenFacilities, + uid: &String, + pin: &[u8], + ) -> Result { + let salt = if pin.len() == 0 { + None + } else { + Some(self.random_salt()?) + }; + let mut key = self.pin_to_unlock_key(facilities, pin, salt, None)?; + if self.encrypt { + self.kek_to_wrapped_object(facilities, uid, key) + } else { + key.set_attr(Attribute::from_string(CKA_UNIQUE_ID, uid.clone()))?; + Ok(key) + } + } + + pub fn authenticate( + &mut self, + facilities: &TokenFacilities, + auth_obj: &mut Object, + pin: &[u8], + set_kek: bool, + ) -> Result { + let mut info = self.user_attempts(&auth_obj)?; + if info.locked { + return Ok(info); + } + + let (salt, iterations) = self.parse_auth_object_label(auth_obj)?; + let key = self.pin_to_unlock_key( + facilities, + pin, + Some(salt), + Some(iterations), + )?; + + let stored = info.cur_attempts; + if self.encrypt { + match self.unwrap_kek(facilities, &key, auth_obj) { + Ok(k) => { + if set_kek { + self.kek = Some(k); + } + info.cur_attempts = 0; + } + Err(_) => info.cur_attempts += 1, + } + } else { + let stored_value = auth_obj.get_attr_as_bytes(CKA_VALUE)?; + let value = key.get_attr_as_bytes(CKA_VALUE)?; + + if value == stored_value { + info.cur_attempts = 0; + } else { + 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) + } +} diff --git a/src/storage/format.rs b/src/storage/format.rs new file mode 100644 index 00000000..7be93995 --- /dev/null +++ b/src/storage/format.rs @@ -0,0 +1,492 @@ +// Copyright 2024 Simo Sorce +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::attribute::Attribute; +use crate::error::Result; +use crate::interface::*; +use crate::misc::copy_sized_string; +use crate::object::Object; +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 */ +]; + +#[cfg(feature = "fips")] +const TOKEN_LABEL: &str = "Kryoptic FIPS Token"; +#[cfg(not(feature = "fips"))] +const TOKEN_LABEL: &str = "Kryoptic Soft Token"; + +const MANUFACTURER_ID: &str = "Kryoptic Project"; + +#[cfg(feature = "fips")] +const TOKEN_MODEL: &str = "FIPS-140-3 v1"; +#[cfg(not(feature = "fips"))] +const TOKEN_MODEL: &str = "v1"; + +const SO_OBJ_UID: &str = "0"; +const USER_OBJ_UID: &str = "1"; +const TOKEN_INFO_UID: &str = "2"; + +pub fn so_obj_uid() -> String { + SO_OBJ_UID.to_string() +} + +pub fn user_obj_uid() -> String { + USER_OBJ_UID.to_string() +} + +pub fn token_info_uid() -> String { + TOKEN_INFO_UID.to_string() +} + +pub fn user_flags( + user_type: CK_USER_TYPE, + info: &StorageAuthInfo, + flag: &mut CK_FLAGS, +) { + 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]; + } else if user_type == CKU_USER { + *flag = USER_PIN_FLAGS[remaining as usize]; + } +} + +#[cfg(feature = "fips")] +fn checked_pin(pin: &[u8]) -> &[u8] { + const DEFAULT_PIN_FIPS: &str = "DEFAULT PIN FIPS"; + if pin.len() == 0 { + DEFAULT_PIN_FIPS.as_bytes() + } else { + pin + } +} +#[cfg(not(feature = "fips"))] +fn checked_pin(pin: &[u8]) -> &[u8] { + pin +} + +fn get_pin_uid(user_type: CK_USER_TYPE) -> Result { + match user_type { + CKU_SO => Ok(so_obj_uid()), + CKU_USER => Ok(user_obj_uid()), + _ => return Err(CKR_GENERAL_ERROR)?, + } +} + +pub fn object_to_token_info(obj: &Object) -> Result { + if obj.get_attr_as_ulong(CKA_CLASS)? != KRO_TOKEN_DATA { + return Err(CKR_TOKEN_NOT_RECOGNIZED)?; + } + let label = obj + .get_attr_as_string(CKA_LABEL) + .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; + let manufacturer = obj + .get_attr_as_string(KRA_MANUFACTURER_ID) + .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; + let model = obj + .get_attr_as_string(KRA_MODEL) + .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; + let serial = obj + .get_attr_as_string(KRA_SERIAL_NUMBER) + .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; + let mut info = StorageTokenInfo { + label: [0; 32], + manufacturer: [0; 32], + model: [0; 16], + serial: [0; 16], + flags: obj + .get_attr_as_ulong(KRA_FLAGS) + .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?, + }; + copy_sized_string(label.as_bytes(), &mut info.label); + copy_sized_string(manufacturer.as_bytes(), &mut info.manufacturer); + copy_sized_string(model.as_bytes(), &mut info.model); + copy_sized_string(serial.as_bytes(), &mut info.serial); + Ok(info) +} + +pub fn token_info_to_object( + info: &StorageTokenInfo, + obj: &mut Object, +) -> Result<()> { + obj.set_attr(Attribute::string_from_sized(CKA_LABEL, &info.label))?; + obj.set_attr(Attribute::string_from_sized( + KRA_MANUFACTURER_ID, + &info.manufacturer, + ))?; + obj.set_attr(Attribute::string_from_sized(KRA_MODEL, &info.model))?; + obj.set_attr(Attribute::string_from_sized( + KRA_SERIAL_NUMBER, + &info.serial, + ))?; + + /* filter out runtime flags */ + let flags = info.flags + & (CKF_LOGIN_REQUIRED | CKF_TOKEN_INITIALIZED | CKF_WRITE_PROTECTED); + obj.set_attr(Attribute::from_ulong(KRA_FLAGS, flags))?; + Ok(()) +} + +pub trait StorageRaw: Debug + Send + Sync { + fn is_initialized(&self) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } + fn db_reset(&mut self) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } + fn open(&mut self) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } + fn flush(&mut self) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } + fn fetch_by_uid(&self, _uid: &String) -> Result { + Err(CKR_GENERAL_ERROR)? + } + fn search(&self, _template: &[CK_ATTRIBUTE]) -> Result> { + Err(CKR_GENERAL_ERROR)? + } + fn store_obj(&mut self, _obj: Object) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } + fn remove_by_uid(&mut self, _uid: &String) -> Result<()> { + Err(CKR_GENERAL_ERROR)? + } +} + +#[derive(Debug)] +pub struct StdStorageFormat { + store: Box, + aci: StorageACI, +} + +impl StdStorageFormat { + pub fn new( + store: Box, + aci: StorageACI, + ) -> StdStorageFormat { + StdStorageFormat { + store: store, + aci: aci, + } + } + + 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)?; + 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)?; + 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() { + return Err(e); + } + } + }; + Ok(so_flags | usr_flags) + } + + 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) + } + + fn default_token_info( + &mut self, + encrypted: bool, + ) -> Result { + /* TOKEN INFO */ + let mut info = StorageTokenInfo { + label: [0u8; 32], + manufacturer: [0u8; 32], + model: [0u8; 16], + serial: [0u8; 16], + flags: CKF_TOKEN_INITIALIZED, + }; + if encrypted { + info.flags |= CKF_LOGIN_REQUIRED; + } + + /* default strings */ + copy_sized_string(TOKEN_LABEL.as_bytes(), &mut info.label); + copy_sized_string(MANUFACTURER_ID.as_bytes(), &mut info.manufacturer); + copy_sized_string(TOKEN_MODEL.as_bytes(), &mut info.model); + + let mut obj = Object::new(); + obj.set_attr(Attribute::from_string(CKA_UNIQUE_ID, token_info_uid()))?; + obj.set_attr(Attribute::from_bool(CKA_TOKEN, true))?; + obj.set_attr(Attribute::from_ulong(CKA_CLASS, KRO_TOKEN_DATA))?; + token_info_to_object(&info, &mut obj)?; + let info = object_to_token_info(&obj)?; + self.store.store_obj(obj)?; + Ok(info) + } +} + +impl Storage for StdStorageFormat { + fn open(&mut self) -> Result { + self.store.open()?; + self.store.is_initialized()?; + let mut info = self.load_token_info()?; + info.flags |= self.init_pin_flags()?; + Ok(info) + } + + fn reinit( + &mut self, + facilities: &TokenFacilities, + ) -> Result { + self.store.db_reset()?; + /* Create new KEK so default auth objects can be generated */ + self.aci.reset(facilities)?; + self.default_so_pin(facilities)?; + let mut info = self.default_token_info(self.aci.encrypts())?; + info.flags |= self.init_pin_flags()?; + Ok(info) + } + + fn flush(&mut self) -> Result<()> { + self.store.flush() + } + + fn fetch( + &self, + facilities: &TokenFacilities, + handle: CK_OBJECT_HANDLE, + get_sensitive: bool, + ) -> Result { + let uid = match facilities.handles.get(handle) { + Some(u) => u, + None => return Err(CKR_OBJECT_HANDLE_INVALID)?, + }; + let mut obj = self.store.fetch_by_uid(&uid)?; + let ats = facilities.factories.get_sensitive_attrs(&obj)?; + if !get_sensitive { + for typ in ats { + /* remove the sensitive attribute completely */ + obj.del_attr(typ); + } + } else if self.aci.encrypts() { + for typ in ats { + /* replace the encrypted val with the clear text one */ + let encval = obj.get_attr_as_bytes(typ)?; + let plain = self.aci.decrypt_value(facilities, uid, encval)?; + obj.set_attr(Attribute::from_bytes(typ, plain))?; + } + } + + obj.set_handle(handle); + Ok(obj) + } + + fn store( + &mut self, + facilities: &mut TokenFacilities, + mut obj: Object, + ) -> Result { + let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; + if self.aci.encrypts() { + let ats = facilities.factories.get_sensitive_attrs(&obj)?; + for typ in ats { + /* replace the clear text val with the encrypted one */ + let plain = obj.get_attr_as_bytes(typ)?; + let encval = self.aci.encrypt_value(facilities, &uid, plain)?; + obj.set_attr(Attribute::from_bytes(typ, encval))?; + } + } + let mut handle = obj.get_handle(); + if handle == CK_INVALID_HANDLE { + handle = facilities.handles.next(); + facilities.handles.insert(handle, uid)?; + } + self.store.store_obj(obj)?; + Ok(handle) + } + + fn search( + &self, + facilities: &mut TokenFacilities, + template: &[CK_ATTRIBUTE], + ) -> Result> { + let mut objects = self.store.search(template)?; + let mut result = Vec::::with_capacity(objects.len()); + for mut obj in objects.drain(..) { + if obj.is_sensitive() { + obj.set_zeroize(); + match facilities.factories.check_sensitive(&obj, template) { + Err(_) => continue, + Ok(()) => (), + } + } + if let Ok(uid) = obj.get_attr_as_string(CKA_UNIQUE_ID) { + /* do not return internal objects */ + if let Ok(numuid) = uid.parse::() { + if numuid < 10 { + continue; + } + } + let handle = match facilities.handles.get_by_uid(&uid) { + Some(h) => *h, + None => { + let h = facilities.handles.next(); + facilities.handles.insert(h, uid)?; + h + } + }; + result.push(handle); + } + } + Ok(result) + } + + fn remove( + &mut self, + facilities: &TokenFacilities, + handle: CK_OBJECT_HANDLE, + ) -> Result<()> { + let uid = match facilities.handles.get(handle) { + Some(u) => u, + None => return Err(CKR_OBJECT_HANDLE_INVALID)?, + }; + self.store.remove_by_uid(&uid) + } + + fn load_token_info(&self) -> Result { + let obj = self.store.fetch_by_uid(&token_info_uid())?; + object_to_token_info(&obj) + } + + fn store_token_info(&mut self, info: &StorageTokenInfo) -> Result<()> { + let uid = token_info_uid(); + let mut obj = self.store.fetch_by_uid(&uid)?; + token_info_to_object(info, &mut obj)?; + self.store.store_obj(obj) + } + + fn auth_user( + &mut self, + facilities: &TokenFacilities, + user_type: CK_USER_TYPE, + pin: &[u8], + flag: &mut CK_FLAGS, + 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( + facilities, + &mut auth_obj, + checked_pin(pin), + !check_only, + )?; + + if info.update_obj { + let _ = self.store.store_obj(auth_obj); + } + + if info.cur_attempts == 0 { + *flag = 0; + return Ok(()); + } + user_flags(user_type, &info, flag); + if info.locked { + Err(CKR_PIN_LOCKED)? + } else { + Err(CKR_PIN_INCORRECT)? + } + } + + 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); + } + } + }; + self.aci.unauth(); + Ok(()) + } + + fn set_user_pin( + &mut self, + facilities: &TokenFacilities, + 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) + } +} diff --git a/src/storage/json.rs b/src/storage/json.rs index 3d5a19f4..61ff99c9 100644 --- a/src/storage/json.rs +++ b/src/storage/json.rs @@ -1,210 +1,87 @@ // Copyright 2024 Simo Sorce // See LICENSE.txt file for terms -use crate::attribute::{string_to_ck_date, AttrType, Attribute}; -use crate::error::{Error, Result}; +use crate::error::Result; use crate::interface::*; use crate::object::Object; -use crate::storage::{memory, Storage}; +use crate::storage::aci::StorageACI; +use crate::storage::format::{StdStorageFormat, StorageRaw}; +use crate::storage::{memory, Storage, StorageDBInfo}; -use data_encoding::BASE64; -use serde::{Deserialize, Serialize}; -use serde_json::{from_reader, to_string_pretty, Map, Number, Value}; - -fn to_json_value(a: &Attribute) -> Value { - match a.get_attrtype() { - AttrType::BoolType => match a.to_bool() { - Ok(b) => Value::Bool(b), - Err(_) => Value::Null, - }, - AttrType::NumType => match a.to_ulong() { - Ok(l) => Value::Number(Number::from(l)), - Err(_) => Value::Null, - }, - AttrType::StringType => match a.to_string() { - Ok(s) => Value::String(s), - Err(_) => Value::String(BASE64.encode(a.get_value())), - }, - AttrType::BytesType => Value::String(BASE64.encode(a.get_value())), - AttrType::DateType => match a.to_date_string() { - Ok(d) => Value::String(d), - Err(_) => Value::String(String::new()), - }, - AttrType::IgnoreType => Value::Null, - AttrType::DenyType => Value::Null, - } -} - -fn uninit(e: std::io::Error) -> Error { - if e.kind() == std::io::ErrorKind::NotFound { - Error::ck_rv_from_error(CKR_CRYPTOKI_NOT_INITIALIZED, e) - } else { - Error::other_error(e) - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JsonObject { - attributes: Map, -} - -impl JsonObject { - pub fn from_object(o: &Object) -> JsonObject { - let mut jo = JsonObject { - attributes: Map::new(), - }; - for a in o.get_attributes() { - jo.attributes.insert(a.name(), to_json_value(a)); - } - jo - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JsonToken { - objects: Vec, -} - -impl JsonToken { - pub fn load(filename: &str) -> Result { - match std::fs::File::open(filename) { - Ok(f) => Ok(from_reader::(f)?), - Err(e) => Err(uninit(e)), - } - } - - pub fn prime_cache(&self, cache: &mut Box) -> Result<()> { - for jo in &self.objects { - let mut obj = Object::new(); - let mut uid: Option = None; - for (key, val) in &jo.attributes { - let (id, atype) = AttrType::attr_name_to_id_type(key)?; - let attr = match atype { - AttrType::BoolType => match val.as_bool() { - Some(b) => Attribute::from_bool(id, b), - None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, - }, - AttrType::NumType => match val.as_u64() { - Some(n) => Attribute::from_u64(id, n), - None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, - }, - AttrType::StringType => match val.as_str() { - Some(s) => Attribute::from_string(id, s.to_string()), - None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, - }, - AttrType::BytesType => match val.as_str() { - Some(s) => { - let len = match BASE64.decode_len(s.len()) { - Ok(l) => l, - Err(_) => return Err(CKR_GENERAL_ERROR)?, - }; - let mut v = vec![0; len]; - match BASE64.decode_mut(s.as_bytes(), &mut v) { - Ok(l) => { - Attribute::from_bytes(id, v[0..l].to_vec()) - } - Err(_) => return Err(CKR_GENERAL_ERROR)?, - } - } - None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, - }, - AttrType::DateType => match val.as_str() { - Some(s) => { - if s.len() == 0 { - /* special case for default empty value */ - Attribute::from_date_bytes(id, Vec::new()) - } else { - Attribute::from_date(id, string_to_ck_date(&s)?) - } - } - None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, - }, - AttrType::DenyType => continue, - AttrType::IgnoreType => continue, - }; - - obj.set_attr(attr)?; - if key == "CKA_UNIQUE_ID" { - uid = match val.as_str() { - Some(s) => Some(s.to_string()), - None => return Err(CKR_DEVICE_ERROR)?, - } - } - } - match uid { - Some(u) => cache.store(&u, obj)?, - None => return Err(CKR_DEVICE_ERROR)?, - } - } - Ok(()) - } - - pub fn from_cache(cache: &mut Box) -> JsonToken { - let objs = cache.search(&[]).unwrap(); - let mut jt = JsonToken { - objects: Vec::with_capacity(objs.len()), - }; - for o in objs { - if !o.is_token() { - continue; - } - jt.objects.push(JsonObject::from_object(&o)); - } - - jt - } - - pub fn save(&self, filename: &str) -> Result<()> { - let jstr = match to_string_pretty(&self) { - Ok(j) => j, - Err(e) => return Err(Error::other_error(e)), - }; - match std::fs::write(filename, jstr) { - Ok(_) => Ok(()), - Err(e) => Err(Error::other_error(e)), - } - } +mod objects { + include!("json_objects.rs"); } +use objects::*; #[derive(Debug)] pub struct JsonStorage { filename: String, - cache: Box, + cache: Box, } -impl Storage for JsonStorage { - fn open(&mut self, filename: &String) -> Result<()> { - self.filename = filename.clone(); - let token = JsonToken::load(&self.filename)?; - token.prime_cache(&mut self.cache) +impl StorageRaw for JsonStorage { + fn is_initialized(&self) -> Result<()> { + self.cache.is_initialized() } - fn reinit(&mut self) -> Result<()> { - // TODO + fn db_reset(&mut self) -> Result<()> { + // TODO: reset not implemented yet Ok(()) } + fn open(&mut self) -> Result<()> { + let token = JsonObjects::load(&self.filename)?; + token.prime_store(&mut self.cache) + } fn flush(&mut self) -> Result<()> { - let token = JsonToken::from_cache(&mut self.cache); + let token = JsonObjects::from_store(&mut self.cache); token.save(&self.filename) } fn fetch_by_uid(&self, uid: &String) -> Result { self.cache.fetch_by_uid(uid) } - fn store(&mut self, uid: &String, obj: Object) -> Result<()> { - self.cache.store(uid, obj)?; - self.flush() - } fn search(&self, template: &[CK_ATTRIBUTE]) -> Result> { self.cache.search(template) } + fn store_obj(&mut self, obj: Object) -> Result<()> { + self.cache.store_obj(obj)?; + self.flush() + } fn remove_by_uid(&mut self, uid: &String) -> Result<()> { self.cache.remove_by_uid(uid)?; self.flush() } } -pub fn json() -> Box { - Box::new(JsonStorage { - filename: String::from(""), - cache: memory::memory(), - }) +#[derive(Debug)] +pub struct JsonDBInfo { + db_type: &'static str, + db_suffix: &'static str, } + +impl StorageDBInfo for JsonDBInfo { + fn new(&self, conf: &Option) -> Result> { + let raw_store = Box::new(JsonStorage { + filename: match conf { + Some(s) => s.clone(), + None => String::from(""), + }, + cache: memory::raw_store(), + }); + Ok(Box::new(StdStorageFormat::new( + raw_store, + StorageACI::new(true), + ))) + } + + fn dbtype(&self) -> &str { + self.db_type + } + + fn dbsuffix(&self) -> &str { + self.db_suffix + } +} + +pub static DBINFO: JsonDBInfo = JsonDBInfo { + db_type: "json", + db_suffix: ".json", +}; diff --git a/src/storage/json_objects.rs b/src/storage/json_objects.rs new file mode 100644 index 00000000..ebb8b53f --- /dev/null +++ b/src/storage/json_objects.rs @@ -0,0 +1,155 @@ +use crate::attribute::string_to_ck_date; +use crate::attribute::{AttrType, Attribute}; +use crate::error::{Error, Result}; +use crate::interface::*; +use crate::object::Object; +use crate::storage::format::StorageRaw; + +use data_encoding::BASE64; +use serde::{Deserialize, Serialize}; +use serde_json::{from_reader, to_string_pretty, Map, Number, Value}; + +fn uninit(e: std::io::Error) -> Error { + if e.kind() == std::io::ErrorKind::NotFound { + Error::ck_rv_from_error(CKR_CRYPTOKI_NOT_INITIALIZED, e) + } else { + Error::other_error(e) + } +} + +fn to_json_value(a: &Attribute) -> Value { + match a.get_attrtype() { + AttrType::BoolType => match a.to_bool() { + Ok(b) => Value::Bool(b), + Err(_) => Value::Null, + }, + AttrType::NumType => match a.to_ulong() { + Ok(l) => Value::Number(Number::from(l)), + Err(_) => Value::Null, + }, + AttrType::StringType => match a.to_string() { + Ok(s) => Value::String(s), + Err(_) => Value::String(BASE64.encode(a.get_value())), + }, + AttrType::BytesType => Value::String(BASE64.encode(a.get_value())), + AttrType::DateType => match a.to_date_string() { + Ok(d) => Value::String(d), + Err(_) => Value::String(String::new()), + }, + AttrType::IgnoreType => Value::Null, + AttrType::DenyType => Value::Null, + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JsonObject { + attributes: Map, +} + +impl JsonObject { + pub fn from_object(o: &Object) -> JsonObject { + let mut jo = JsonObject { + attributes: Map::new(), + }; + for a in o.get_attributes() { + jo.attributes.insert(a.name(), to_json_value(a)); + } + jo + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JsonObjects { + objects: Vec, +} + +impl JsonObjects { + pub fn load(filename: &str) -> Result { + match std::fs::File::open(filename) { + Ok(f) => Ok(from_reader::(f)?), + Err(e) => Err(uninit(e)), + } + } + + pub fn prime_store(&self, store: &mut Box) -> Result<()> { + for jo in &self.objects { + let mut obj = Object::new(); + for (key, val) in &jo.attributes { + let (id, atype) = AttrType::attr_name_to_id_type(key)?; + let attr = match atype { + AttrType::BoolType => match val.as_bool() { + Some(b) => Attribute::from_bool(id, b), + None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, + }, + AttrType::NumType => match val.as_u64() { + Some(n) => Attribute::from_u64(id, n), + None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, + }, + AttrType::StringType => match val.as_str() { + Some(s) => Attribute::from_string(id, s.to_string()), + None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, + }, + AttrType::BytesType => match val.as_str() { + Some(s) => { + let len = match BASE64.decode_len(s.len()) { + Ok(l) => l, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }; + let mut v = vec![0; len]; + match BASE64.decode_mut(s.as_bytes(), &mut v) { + Ok(l) => { + Attribute::from_bytes(id, v[0..l].to_vec()) + } + Err(_) => return Err(CKR_GENERAL_ERROR)?, + } + } + None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, + }, + AttrType::DateType => match val.as_str() { + Some(s) => { + if s.len() == 0 { + /* special case for default empty value */ + Attribute::from_date_bytes(id, Vec::new()) + } else { + Attribute::from_date(id, string_to_ck_date(&s)?) + } + } + None => return Err(CKR_ATTRIBUTE_VALUE_INVALID)?, + }, + AttrType::DenyType => continue, + AttrType::IgnoreType => continue, + }; + + obj.set_attr(attr)?; + } + store.store_obj(obj)?; + } + Ok(()) + } + + pub fn from_store(store: &mut Box) -> JsonObjects { + let objs = store.search(&[]).unwrap(); + let mut jt = JsonObjects { + objects: Vec::with_capacity(objs.len()), + }; + for o in objs { + if !o.is_token() { + continue; + } + jt.objects.push(JsonObject::from_object(&o)); + } + + jt + } + + pub fn save(&self, filename: &str) -> Result<()> { + let jstr = match to_string_pretty(&self) { + Ok(j) => j, + Err(e) => return Err(Error::other_error(e)), + }; + match std::fs::write(filename, jstr) { + Ok(_) => Ok(()), + Err(e) => Err(Error::other_error(e)), + } + } +} diff --git a/src/storage/memory.rs b/src/storage/memory.rs index 80a21133..c669c941 100644 --- a/src/storage/memory.rs +++ b/src/storage/memory.rs @@ -7,21 +7,30 @@ use std::fmt::Debug; use crate::error::{Error, Result}; use crate::interface::*; use crate::object::Object; -use crate::storage::Storage; +use crate::storage::aci::StorageACI; +use crate::storage::format::{StdStorageFormat, StorageRaw}; +use crate::storage::{Storage, StorageDBInfo}; #[derive(Debug)] struct MemoryStorage { objects: HashMap, } -impl Storage for MemoryStorage { - fn open(&mut self, _filename: &String) -> Result<()> { - return Err(CKR_GENERAL_ERROR)?; +impl StorageRaw for MemoryStorage { + fn is_initialized(&self) -> Result<()> { + if self.objects.len() != 0 { + Ok(()) + } else { + Err(CKR_CRYPTOKI_NOT_INITIALIZED)? + } } - fn reinit(&mut self) -> Result<()> { + fn db_reset(&mut self) -> Result<()> { self.objects.clear(); Ok(()) } + fn open(&mut self) -> Result<()> { + Ok(()) + } fn flush(&mut self) -> Result<()> { Ok(()) } @@ -31,10 +40,6 @@ impl Storage for MemoryStorage { None => Err(Error::not_found(uid.clone())), } } - fn store(&mut self, uid: &String, obj: Object) -> Result<()> { - self.objects.insert(uid.clone(), obj); - Ok(()) - } fn search(&self, template: &[CK_ATTRIBUTE]) -> Result> { let mut ret = Vec::::new(); for (_, o) in self.objects.iter() { @@ -44,14 +49,55 @@ impl Storage for MemoryStorage { } Ok(ret) } + fn store_obj(&mut self, obj: Object) -> Result<()> { + let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; + self.objects.insert(uid, obj); + Ok(()) + } fn remove_by_uid(&mut self, uid: &String) -> Result<()> { self.objects.remove(uid); Ok(()) } } -pub fn memory() -> Box { +pub fn raw_store() -> Box { Box::new(MemoryStorage { objects: HashMap::new(), }) } + +#[derive(Debug)] +pub struct MemoryDBInfo { + db_type: &'static str, + db_suffix: &'static str, +} + +impl StorageDBInfo for MemoryDBInfo { + fn new(&self, conf: &Option) -> Result> { + let encrypt = match conf { + Some(s) => match s.as_str() { + "flags=encrypt" => true, + _ => return Err(CKR_ARGUMENTS_BAD)?, + }, + None => false, + }; + let raw_store = raw_store(); + Ok(Box::new(StdStorageFormat::new( + raw_store, + StorageACI::new(encrypt), + ))) + } + + fn dbtype(&self) -> &str { + self.db_type + } + + fn dbsuffix(&self) -> &str { + self.db_suffix + } +} + +pub static DBINFO: MemoryDBInfo = MemoryDBInfo { + db_type: "memory", + db_suffix: "", +}; diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 00000000..7b693936 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,124 @@ +// Copyright 2024 Simo Sorce +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::error::Result; +use crate::interface::*; +use crate::object::Object; +use crate::token::TokenFacilities; + +use once_cell::sync::Lazy; + +pub struct StorageTokenInfo { + pub label: [CK_UTF8CHAR; 32usize], + pub manufacturer: [CK_UTF8CHAR; 32usize], + pub model: [CK_UTF8CHAR; 16usize], + pub serial: [CK_CHAR; 16usize], + pub flags: CK_FLAGS, +} + +pub trait StorageDBInfo: Debug + Send + Sync { + fn new(&self, conf: &Option) -> Result>; + fn dbtype(&self) -> &str; + fn dbsuffix(&self) -> &str; +} + +pub trait Storage: Debug + Send + Sync { + fn open(&mut self) -> Result; + fn reinit( + &mut self, + facilities: &TokenFacilities, + ) -> Result; + fn flush(&mut self) -> Result<()>; + fn fetch( + &self, + faclities: &TokenFacilities, + handle: CK_OBJECT_HANDLE, + get_sensitive: bool, + ) -> Result; + fn store( + &mut self, + faclities: &mut TokenFacilities, + obj: Object, + ) -> Result; + fn search( + &self, + faclities: &mut TokenFacilities, + template: &[CK_ATTRIBUTE], + ) -> Result>; + fn remove( + &mut self, + facilities: &TokenFacilities, + handle: CK_OBJECT_HANDLE, + ) -> Result<()>; + fn load_token_info(&self) -> Result; + fn store_token_info(&mut self, info: &StorageTokenInfo) -> Result<()>; + fn auth_user( + &mut self, + faclities: &TokenFacilities, + user_type: CK_USER_TYPE, + pin: &[u8], + flag: &mut CK_FLAGS, + check_only: bool, + ) -> Result<()>; + fn unauth_user(&mut self, user_type: CK_USER_TYPE) -> Result<()>; + fn set_user_pin( + &mut self, + facilities: &TokenFacilities, + user_type: CK_USER_TYPE, + pin: &[u8], + ) -> Result<()>; +} + +mod aci; +pub mod format; + +#[cfg(feature = "jsondb")] +pub mod json; + +#[cfg(feature = "memorydb")] +pub mod memory; + +#[cfg(feature = "sqlitedb")] +mod sqlite; + +static STORAGE_DBS: Lazy> = Lazy::new(|| { + let mut v = Vec::<&'static dyn StorageDBInfo>::with_capacity(4); + + #[cfg(feature = "jsondb")] + v.push(&json::DBINFO); + + #[cfg(feature = "memorydb")] + v.push(&memory::DBINFO); + + #[cfg(feature = "sqlitedb")] + v.push(&sqlite::DBINFO); + + v +}); + +pub fn suffix_to_type(name: &str) -> Result<&'static str> { + for i in 0..STORAGE_DBS.len() { + let suffix = STORAGE_DBS[i].dbsuffix(); + if suffix == "" { + continue; + } + if name.ends_with(suffix) { + return Ok(STORAGE_DBS[i].dbtype()); + } + } + Err(KRR_CONFIG_ERROR)? +} + +pub fn new_storage( + name: &str, + conf: &Option, +) -> Result> { + for i in 0..STORAGE_DBS.len() { + if name == STORAGE_DBS[i].dbtype() { + return STORAGE_DBS[i].new(conf); + } + } + Err(CKR_TOKEN_NOT_RECOGNIZED)? +} diff --git a/src/storage/sqlite.rs b/src/storage/sqlite.rs index 54ce45eb..7fee9d83 100644 --- a/src/storage/sqlite.rs +++ b/src/storage/sqlite.rs @@ -1,15 +1,19 @@ // Copyright 2024 Simo Sorce // See LICENSE.txt file for terms -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use crate::attribute::{string_to_ck_date, AttrType, Attribute}; use crate::error::{Error, Result}; use crate::interface::*; use crate::object::Object; -use crate::storage::Storage; +use crate::storage::aci::StorageACI; +use crate::storage::format::{StdStorageFormat, StorageRaw}; +use crate::storage::{Storage, StorageDBInfo}; -use rusqlite::{params, types::Value, Connection, Rows, Transaction}; +use rusqlite::types::Value; +use rusqlite::{params, Connection, Rows, Transaction}; +use rusqlite::{Error as rlError, ErrorCode}; fn bad_code(error: E) -> Error { Error::ck_rv_from_error(CKR_GENERAL_ERROR, error) @@ -19,10 +23,60 @@ fn bad_storage(error: E) -> Error { Error::ck_rv_from_error(CKR_DEVICE_MEMORY, error) } -const IS_DB_INITIALIZED: &str = - "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='objects'"; +impl From for Error { + fn from(error: rlError) -> Error { + match error { + rlError::SqliteFailure(_, _) => match error.sqlite_error_code() { + Some(e) => match e { + ErrorCode::ConstraintViolation + | ErrorCode::TypeMismatch + | ErrorCode::ApiMisuse + | ErrorCode::ParameterOutOfRange => { + Error::ck_rv_from_error(CKR_GENERAL_ERROR, error) + } + ErrorCode::DatabaseBusy + | ErrorCode::DatabaseLocked + | ErrorCode::FileLockingProtocolFailed => { + Error::ck_rv_from_error( + CKR_TOKEN_RESOURCE_EXCEEDED, + error, + ) + } + ErrorCode::OutOfMemory => { + Error::ck_rv_from_error(CKR_DEVICE_MEMORY, error) + } + ErrorCode::CannotOpen + | ErrorCode::NotFound + | ErrorCode::PermissionDenied => { + Error::ck_rv_from_error(CKR_TOKEN_NOT_RECOGNIZED, error) + } + ErrorCode::ReadOnly => Error::ck_rv_from_error( + CKR_TOKEN_WRITE_PROTECTED, + error, + ), + ErrorCode::TooBig => { + Error::ck_rv_from_error(CKR_DATA_LEN_RANGE, error) + } + _ => Error::ck_rv_from_error(CKR_DEVICE_ERROR, error), + }, + None => Error::ck_rv_from_error(CKR_GENERAL_ERROR, error), + }, + _ => Error::ck_rv_from_error(CKR_GENERAL_ERROR, error), + } + } +} + +impl From>> for Error { + fn from(_: std::sync::PoisonError>) -> Error { + Error::ck_rv(CKR_CANT_LOCK) + } +} + +const OBJECTS_TABLE: &str = "objects"; const DROP_DB_TABLE: &str = "DROP TABLE objects"; const CREATE_DB_TABLE: &str = "CREATE TABLE objects (id int NOT NULL, attr int NOT NULL, val blob, UNIQUE (id, attr))"; +const CHECK_DB_TABLE: &str = + "SELECT count(*) FROM sqlite_master WHERE type='table' AND name = ?"; /* search by filter constants */ const SEARCH_ALL: &str = "SELECT * FROM objects"; @@ -37,6 +91,28 @@ const UPDATE_ATTR: &str = "INSERT OR REPLACE INTO objects VALUES (?, ?, ?)"; const DELETE_OBJ: &str = "DELETE FROM objects WHERE id = ?"; const MAX_ID: &str = "SELECT IFNULL(MAX(id), 0) FROM objects"; +pub fn check_table( + conn: MutexGuard<'_, rusqlite::Connection>, + tablename: &str, +) -> Result<()> { + let mut stmt = conn.prepare(CHECK_DB_TABLE)?; + let mut rows = stmt.query(rusqlite::params![tablename])?; + if let Some(row) = rows.next()? { + match row.get(0)? { + 1 => (), + 0 => return Err(CKR_CRYPTOKI_NOT_INITIALIZED)?, + _ => return Err(CKR_DEVICE_ERROR)?, + } + } else { + return Err(CKR_CRYPTOKI_NOT_INITIALIZED)?; + } + match rows.next() { + Ok(None) => Ok(()), + Ok(_) => Err(CKR_DEVICE_ERROR)?, + Err(e) => Err(e)?, + } +} + #[derive(Debug)] pub struct SqliteStorage { filename: String, @@ -44,29 +120,6 @@ pub struct SqliteStorage { } impl SqliteStorage { - fn is_initialized(&self) -> Result<()> { - let conn = self.conn.lock().unwrap(); - let result = conn - .query_row_and_then(IS_DB_INITIALIZED, [], |row| row.get(0)) - .map_err(bad_storage)?; - match result { - 1 => Ok(()), - 0 => Err(CKR_CRYPTOKI_NOT_INITIALIZED)?, - _ => Err(CKR_DEVICE_MEMORY)?, - } - } - - fn db_reset(&mut self) -> Result<()> { - let mut conn = self.conn.lock().unwrap(); - let mut tx = conn.transaction().map_err(bad_storage)?; - tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); - /* the drop can fail when files are empty (new) */ - let _ = tx.execute(DROP_DB_TABLE, params![]); - tx.execute(CREATE_DB_TABLE, params![]) - .map_err(bad_storage)?; - tx.commit().map_err(bad_storage) - } - fn rows_to_objects(mut rows: Rows) -> Result> { let mut objid = 0; let mut objects = Vec::::new(); @@ -130,66 +183,10 @@ impl SqliteStorage { Ok(objects) } - fn search_by_unique_id(conn: &Connection, uid: &String) -> Result { - let mut stmt = conn.prepare(SEARCH_BY_SINGLE_ATTR).map_err(bad_code)?; - let rows = stmt.query(params![CKA_UNIQUE_ID, uid]).map_err(bad_code)?; - let mut objects = Self::rows_to_objects(rows)?; - match objects.len() { - 0 => Err(Error::not_found(uid.clone())), - 1 => Ok(objects.pop().unwrap()), - _ => Err(CKR_GENERAL_ERROR)?, - } - } - - fn search_with_filter( - conn: &Connection, - template: &[CK_ATTRIBUTE], - ) -> Result> { - let mut search_query = String::from(SEARCH_ALL); - let mut subqcount = 0; - let mut search_params = Vec::::with_capacity(template.len() * 2); - for a in template { - /* add subqueries */ - if subqcount == 0 { - search_query.push_str(SEARCH_NEST); - } else { - search_query.push_str(SEARCH_CONCAT); - } - search_query.push_str(SEARCH_OBJ_ID); - /* add parameters */ - search_params.push(Value::from(u32::try_from(a.type_)?)); - search_params.push(match AttrType::attr_id_to_attrtype(a.type_)? { - AttrType::BoolType => Value::from(a.to_bool()?), - AttrType::NumType => Self::num_to_val(a.to_ulong()?)?, - AttrType::StringType => Value::from(a.to_string()?), - AttrType::BytesType => Value::from(a.to_buf()?), - AttrType::DateType => { - Value::from(a.to_attribute()?.to_date_string()?) - } - AttrType::DenyType | AttrType::IgnoreType => { - return Err(CKR_ATTRIBUTE_TYPE_INVALID)? - } - }); - subqcount += 1; - } - if subqcount > 0 { - search_query.push_str(SEARCH_CLOSE); - } - /* finally make sure results return ordered by id, - * this simplifies conversion to actual Objects */ - search_query.push_str(SEARCH_ORDER); - - let mut stmt = conn.prepare(&search_query).map_err(bad_code)?; - let rows = stmt - .query(rusqlite::params_from_iter(search_params)) - .map_err(bad_code)?; - Ok(Self::rows_to_objects(rows)?) - } - fn store_object( tx: &mut Transaction, uid: &String, - obj: &Object, + obj: Object, ) -> Result<()> { let objid = match Self::delete_object(tx, uid)? { 0 => { @@ -271,46 +268,101 @@ impl SqliteStorage { } } -impl Storage for SqliteStorage { - fn open(&mut self, filename: &String) -> Result<()> { - self.filename = filename.clone(); +impl StorageRaw for SqliteStorage { + fn is_initialized(&self) -> Result<()> { + let conn = self.conn.lock()?; + check_table(conn, OBJECTS_TABLE) + } + + fn db_reset(&mut self) -> Result<()> { + let mut conn = self.conn.lock()?; + let mut tx = conn.transaction().map_err(bad_storage)?; + tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); + /* the drop can fail when files are empty (new) */ + let _ = tx.execute(DROP_DB_TABLE, params![]); + tx.execute(CREATE_DB_TABLE, params![]) + .map_err(bad_storage)?; + tx.commit().map_err(bad_storage) + } + + fn open(&mut self) -> Result<()> { self.conn = match Connection::open(&self.filename) { Ok(c) => Arc::new(Mutex::from(c)), Err(_) => return Err(CKR_TOKEN_NOT_PRESENT)?, }; - self.is_initialized() - } - fn reinit(&mut self) -> Result<()> { - self.db_reset() + Ok(()) } + fn flush(&mut self) -> Result<()> { Ok(()) } + fn fetch_by_uid(&self, uid: &String) -> Result { - let conn = self.conn.lock().unwrap(); - Self::search_by_unique_id(&conn, uid) - } - fn store(&mut self, uid: &String, obj: Object) -> Result<()> { - let mut conn = self.conn.lock().unwrap(); - let mut tx = conn.transaction().map_err(bad_storage)?; - tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); - Self::store_object(&mut tx, uid, &obj)?; - tx.commit().map_err(bad_storage) + let conn = self.conn.lock()?; + let mut stmt = conn.prepare(SEARCH_BY_SINGLE_ATTR).map_err(bad_code)?; + let rows = stmt.query(params![CKA_UNIQUE_ID, uid]).map_err(bad_code)?; + let mut objects = Self::rows_to_objects(rows)?; + match objects.len() { + 0 => Err(Error::not_found(uid.clone())), + 1 => Ok(objects.pop().unwrap()), + _ => Err(CKR_GENERAL_ERROR)?, + } } + fn search(&self, template: &[CK_ATTRIBUTE]) -> Result> { - let conn = self.conn.lock().unwrap(); - let mut objects = Self::search_with_filter(&conn, template)?; - drop(conn); - let mut result = Vec::::new(); - for obj in objects.drain(..) { - if obj.match_template(template) { - result.push(obj); + let conn = self.conn.lock()?; + let mut search_query = String::from(SEARCH_ALL); + let mut subqcount = 0; + let mut search_params = Vec::::with_capacity(template.len() * 2); + for a in template { + /* add subqueries */ + if subqcount == 0 { + search_query.push_str(SEARCH_NEST); + } else { + search_query.push_str(SEARCH_CONCAT); } + search_query.push_str(SEARCH_OBJ_ID); + /* add parameters */ + search_params.push(Value::from(u32::try_from(a.type_)?)); + search_params.push(match AttrType::attr_id_to_attrtype(a.type_)? { + AttrType::BoolType => Value::from(a.to_bool()?), + AttrType::NumType => Self::num_to_val(a.to_ulong()?)?, + AttrType::StringType => Value::from(a.to_string()?), + AttrType::BytesType => Value::from(a.to_buf()?), + AttrType::DateType => { + Value::from(a.to_attribute()?.to_date_string()?) + } + AttrType::DenyType | AttrType::IgnoreType => { + return Err(CKR_ATTRIBUTE_TYPE_INVALID)? + } + }); + subqcount += 1; + } + if subqcount > 0 { + search_query.push_str(SEARCH_CLOSE); } - Ok(result) + /* finally make sure results return ordered by id, + * this simplifies conversion to actual Objects */ + search_query.push_str(SEARCH_ORDER); + + let mut stmt = conn.prepare(&search_query).map_err(bad_code)?; + let rows = stmt + .query(rusqlite::params_from_iter(search_params)) + .map_err(bad_code)?; + Ok(Self::rows_to_objects(rows)?) + } + + fn store_obj(&mut self, obj: Object) -> Result<()> { + let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; + let mut conn = self.conn.lock()?; + let mut tx = conn.transaction().map_err(bad_storage)?; + tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); + Self::store_object(&mut tx, &uid, obj)?; + tx.commit().map_err(bad_storage) } + fn remove_by_uid(&mut self, uid: &String) -> Result<()> { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock()?; let mut tx = conn.transaction().map_err(bad_storage)?; tx.set_drop_behavior(rusqlite::DropBehavior::Rollback); Self::delete_object(&mut tx, &uid)?; @@ -318,9 +370,37 @@ impl Storage for SqliteStorage { } } -pub fn sqlite() -> Box { - Box::new(SqliteStorage { - filename: String::from(""), - conn: Arc::new(Mutex::from(Connection::open_in_memory().unwrap())), - }) +#[derive(Debug)] +pub struct SqliteDBInfo { + db_type: &'static str, + db_suffix: &'static str, +} + +impl StorageDBInfo for SqliteDBInfo { + fn new(&self, conf: &Option) -> Result> { + let raw_store = Box::new(SqliteStorage { + filename: match conf { + Some(s) => s.clone(), + None => String::from(""), + }, + conn: Arc::new(Mutex::from(Connection::open_in_memory()?)), + }); + Ok(Box::new(StdStorageFormat::new( + raw_store, + StorageACI::new(true), + ))) + } + + fn dbtype(&self) -> &str { + self.db_type + } + + fn dbsuffix(&self) -> &str { + self.db_suffix + } } + +pub static DBINFO: SqliteDBInfo = SqliteDBInfo { + db_type: "sqlite", + db_suffix: ".sql", +}; diff --git a/src/tests/init.rs b/src/tests/init.rs index c5c3827d..fd027443 100644 --- a/src/tests/init.rs +++ b/src/tests/init.rs @@ -11,7 +11,7 @@ use serial_test::{parallel, serial}; #[parallel] fn test_init_token() { let dbpath = format!("{}/{}", TESTDIR, "test_init_token.sql"); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); let mut args = TestToken::make_init_args(Some(testtokn.make_init_string())); let args_ptr = &mut args as *mut CK_C_INITIALIZE_ARGS; @@ -249,7 +249,7 @@ fn test_init_token() { } fn test_re_init_token_common(db: String) { - let mut testtokn = TestToken::new(db, true); + let mut testtokn = TestToken::new(db); let mut args = TestToken::make_init_args(Some(testtokn.make_init_string())); let args_ptr = &mut args as *mut CK_C_INITIALIZE_ARGS; @@ -275,6 +275,7 @@ fn test_re_init_token_common(db: String) { testtokn.finalize(); } +#[cfg(feature = "jsondb")] #[test] #[serial] fn test_re_init_token_json() { diff --git a/src/tests/login.rs b/src/tests/login.rs index f00b8b48..7f42e2ff 100644 --- a/src/tests/login.rs +++ b/src/tests/login.rs @@ -134,6 +134,7 @@ fn test_login(name: &str) { testtokn.finalize(); } +#[cfg(feature = "jsondb")] #[test] #[parallel] fn test_login_json() { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index b4484985..6c13e945 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -15,6 +15,7 @@ mod util; use util::*; mod token; +mod ts; const TESTDIR: &str = "test/kryoptic"; const SO_PIN: &str = "12345678"; @@ -52,11 +53,10 @@ struct TestToken<'a> { sync: Option>, session: CK_SESSION_HANDLE, session_rw: bool, - encrypted: bool, } impl TestToken<'_> { - fn new<'a>(filename: String, encrypted: bool) -> TestToken<'a> { + fn new<'a>(filename: String) -> TestToken<'a> { let mut slots = SLOTS.write().unwrap(); slots.id += 1; while check_test_slot_busy(slots.id) { @@ -72,7 +72,6 @@ impl TestToken<'_> { sync: Some(SYNC.read().unwrap()), session: CK_INVALID_HANDLE, session_rw: false, - encrypted: encrypted, } } @@ -89,20 +88,21 @@ impl TestToken<'_> { label.resize(32, 0x20); /* Init a brand new token */ let mut token = Token::new( - storage::name_to_type(&self.filename).unwrap(), + storage::suffix_to_type(&self.filename).unwrap(), Some(self.filename.clone()), ) .unwrap(); - token.use_encryption(self.encrypted); token.initialize(&so_pin, &label).unwrap(); + token.login(CKU_SO, &so_pin); token.set_pin(CKU_USER, &user_pin, &vec![0u8; 0]).unwrap(); + token.logout(); token.login(CKU_USER, &user_pin); - let test_data = storage::json::JsonToken::load(filename).unwrap(); - let mut cache = storage::memory::memory(); - test_data.prime_cache(&mut cache).unwrap(); + let test_data = ts::json::JsonObjects::load(filename).unwrap(); + let mut tstore = ts::TransferStorage::new(); + test_data.prime_store(&mut tstore).unwrap(); - let objects = cache.search(&[]).unwrap(); + let objects = tstore.search(&[]).unwrap(); for obj in objects { token.insert_object(CK_INVALID_HANDLE, obj.clone()).unwrap(); } @@ -114,7 +114,7 @@ impl TestToken<'_> { fn make_config_file(&self, confname: &str) { let dbpath = self.filename.clone(); - let dbtype = storage::name_to_type(&dbpath).unwrap(); + let dbtype = storage::suffix_to_type(&dbpath).unwrap(); let mut conf = config::Config::new(); let mut slot = config::Slot::with_db(dbtype, Some(dbpath)); slot.slot = u32::try_from(self.get_slot()).unwrap(); @@ -172,7 +172,7 @@ impl TestToken<'_> { db: Option<&'a str>, ) -> TestToken<'a> { let dbpath = format!("{}/{}", TESTDIR, filename); - let mut td = Self::new(dbpath, false); + let mut td = Self::new(dbpath); td.setup_db(db); let mut args = Self::make_init_args(Some(td.make_init_string())); diff --git a/src/tests/random.rs b/src/tests/random.rs index eef697f7..e9eebcd5 100644 --- a/src/tests/random.rs +++ b/src/tests/random.rs @@ -8,7 +8,7 @@ use serial_test::parallel; #[test] #[parallel] fn test_random() { - let mut testtokn = TestToken::initialized("test_random.json", None); + let mut testtokn = TestToken::initialized("test_random.sql", None); let session = testtokn.get_session(false); let data: &[u8] = &mut [0, 0, 0, 0]; diff --git a/src/tests/token.rs b/src/tests/token.rs index 44cbc3d4..6204cf4e 100644 --- a/src/tests/token.rs +++ b/src/tests/token.rs @@ -11,7 +11,7 @@ use serial_test::{parallel, serial}; fn test_token_setup(name: &str) -> TestToken { let dbpath = format!("{}/{}", TESTDIR, name); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.setup_db(None); testtokn } @@ -83,7 +83,7 @@ fn test_token_datadir() { let dbpath = format!("{}/token.sql", confdir); std::fs::create_dir_all(confdir).unwrap(); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.make_config_file(&confname); testtokn.setup_db(None); @@ -112,6 +112,7 @@ fn test_token_datadir() { testtokn.finalize(); } +#[cfg(feature = "jsondb")] #[test] #[serial] fn test_token_json() { @@ -130,7 +131,7 @@ fn test_token_sql() { #[parallel] fn test_interface_null() { let dbpath = format!("{}/{}", TESTDIR, "test_interface_null.sql"); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.setup_db(None); /* NULL interface name and NULL version -- the module should return default one */ @@ -167,7 +168,7 @@ fn test_interface_null() { #[parallel] fn test_interface_pkcs11() { let dbpath = format!("{}/{}", TESTDIR, "test_interface_pkcs11.sql"); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.setup_db(None); /* NULL version -- the module should return default one */ @@ -205,7 +206,7 @@ fn test_interface_pkcs11() { fn test_interface_pkcs11_version3() { let dbpath = format!("{}/{}", TESTDIR, "test_interface_pkcs11_version3.sql"); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.setup_db(None); /* Get the specific version 3.0 */ @@ -244,7 +245,7 @@ fn test_interface_pkcs11_version3() { fn test_interface_pkcs11_version240() { let dbpath = format!("{}/{}", TESTDIR, "test_interface_pkcs11_version240.sql"); - let mut testtokn = TestToken::new(dbpath, true); + let mut testtokn = TestToken::new(dbpath); testtokn.setup_db(None); /* Get the specific version 2.40 */ @@ -329,16 +330,23 @@ fn test_config_multiple_tokens() { format!("{}/{}", TESTDIR, "test_config_multiple.sql"), "TOKEN 1", ), + #[cfg(feature = "jsondb")] ( "json", format!("{}/{}", TESTDIR, "test_config_multiple.json"), "TOKEN 2", ), + #[cfg(not(feature = "jsondb"))] + ( + "sqlite", + format!("{}/{}", TESTDIR, "test_config_multiple_2.sql"), + "TOKEN 2", + ), ]; let mut config = String::new(); let mut tokens = Vec::::new(); for db in &dbs { - let mut token = TestToken::new(db.1.clone(), false); + let mut token = TestToken::new(db.1.clone()); token.setup_db(None); /* here we hand code a config file. * to ensure changes in the toml crate do not break the format */ diff --git a/src/tests/ts.rs b/src/tests/ts.rs new file mode 100644 index 00000000..88c696d8 --- /dev/null +++ b/src/tests/ts.rs @@ -0,0 +1,47 @@ +// Copyright 2024 Simo Sorce +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::error::Result; +use crate::interface::*; +use crate::object::Object; +use crate::storage::format::StorageRaw; + +use std::collections::HashMap; + +pub mod json { + #![allow(dead_code)] + #![allow(unused_attributes)] + include!("../storage/json_objects.rs"); +} + +#[derive(Debug)] +pub struct TransferStorage { + objects: HashMap, +} + +impl TransferStorage { + pub fn new() -> Box { + Box::new(TransferStorage { + objects: HashMap::new(), + }) + } +} + +impl StorageRaw for TransferStorage { + fn search(&self, template: &[CK_ATTRIBUTE]) -> Result> { + let mut ret = Vec::::new(); + for (_, o) in self.objects.iter() { + if o.match_template(template) { + ret.push(o.clone()); + } + } + Ok(ret) + } + fn store_obj(&mut self, obj: Object) -> Result<()> { + let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; + self.objects.insert(uid, obj); + Ok(()) + } +} diff --git a/src/token.rs b/src/token.rs index c23b5c50..28954cc8 100644 --- a/src/token.rs +++ b/src/token.rs @@ -5,8 +5,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::vec::Vec; -use crate::aes; -use crate::attribute::{Attribute, CkAttrs}; +use crate::attribute::CkAttrs; use crate::error::Result; #[cfg(feature = "fips")] use crate::fips; @@ -14,127 +13,80 @@ use crate::interface::*; use crate::mechanism::Mechanisms; use crate::misc::copy_sized_string; use crate::object::{Object, ObjectFactories}; +use crate::register_all; use crate::storage::*; -use crate::{get_random_data, register_all, sizeof, void_ptr}; -use hex; +use bimap; -#[cfg(feature = "fips")] -const TOKEN_LABEL: &str = "Kryoptic FIPS Token"; -#[cfg(not(feature = "fips"))] -const TOKEN_LABEL: &str = "Kryoptic Soft Token"; - -const MANUFACTURER_ID: &str = "Kryoptic Project"; - -#[cfg(feature = "fips")] -const TOKEN_MODEL: &str = "FIPS-140-3 v1"; -#[cfg(not(feature = "fips"))] -const TOKEN_MODEL: &str = "v1"; - -const SO_PIN_UID: &str = "0"; -const USER_PIN_UID: &str = "1"; -const TOKEN_INFO_UID: &str = "2"; - -const MAX_LOGIN_ATTEMPTS: CK_ULONG = 10; - -const USER_PIN_IV: &str = "UPIN"; -const DEFPIN_SALT: &str = "DEFAULT SALT DATA"; /* at least 16 bytes for FIPS */ -const DEFPIN_ITER: usize = 1000; -const DEFAULT_IV_SIZE: usize = 12; /* 96 bits as required by FIPS for AES GCM */ - -#[cfg(feature = "fips")] -fn default_password() -> Vec { - const DEFPIN_PASS: &str = "DEFAULT PASSWORD"; - DEFPIN_PASS.as_bytes().to_vec() -} -#[cfg(not(feature = "fips"))] -fn default_password() -> Vec { - vec![0u8; 0] -} - -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Handles { - map: HashMap, - rev: HashMap, + map: bimap::hash::BiHashMap, next: CK_OBJECT_HANDLE, } impl Handles { pub fn new() -> Handles { Handles { - map: HashMap::new(), - rev: HashMap::new(), + map: bimap::hash::BiHashMap::new(), next: 1, } } - pub fn insert(&mut self, handle: CK_OBJECT_HANDLE, value: String) { - if let Some(val) = self.rev.insert(value.clone(), handle) { - /* this uid was already mapped */ - if val != handle { - let _ = self.map.remove(&val); - } - } - if let Some(uid) = self.map.insert(handle, value) { - /* this handle was already mapped */ - if &uid != self.map.get(&handle).unwrap() { - let _ = self.rev.remove(&uid); - } + pub fn insert( + &mut self, + handle: CK_OBJECT_HANDLE, + value: String, + ) -> Result<()> { + match self.map.insert_no_overwrite(handle, value) { + Ok(()) => Ok(()), + Err(_) => Err(CKR_GENERAL_ERROR)?, } } pub fn get(&self, handle: CK_OBJECT_HANDLE) -> Option<&String> { - self.map.get(&handle) + self.map.get_by_left(&handle) } pub fn get_by_uid(&self, uid: &String) -> Option<&CK_OBJECT_HANDLE> { - self.rev.get(uid) + self.map.get_by_right(uid) } pub fn remove(&mut self, handle: CK_OBJECT_HANDLE) { - if let Some(uid) = self.map.remove(&handle) { - let _ = self.rev.remove(&uid); - } + let _ = self.map.remove_by_left(&handle); } - fn next(&mut self) -> CK_OBJECT_HANDLE { + pub fn next(&mut self) -> CK_OBJECT_HANDLE { let next = self.next; self.next += 1; next } } +#[derive(Debug)] +pub struct TokenFacilities { + pub mechanisms: Mechanisms, + pub factories: ObjectFactories, + pub handles: Handles, +} + #[derive(Debug)] pub struct Token { info: CK_TOKEN_INFO, - filename: Option, - object_factories: ObjectFactories, - mechanisms: Mechanisms, + facilities: TokenFacilities, storage: Box, session_objects: HashMap, - handles: Handles, - kek: Option, - so_logged_in: bool, + logged: CK_USER_TYPE, } impl Token { pub fn new(dbtype: &str, dbpath: Option) -> Result { - /* when no filename is provided we assume a memory only - * token that has no backing store */ - let store = match dbtype { - SQLITEDB => sqlite::sqlite(), - JSONDB => json::json(), - MEMORYDB => memory::memory(), - _ => return Err(CKR_GENERAL_ERROR)?, - }; - let mut token: Token = Token { info: CK_TOKEN_INFO { label: [0u8; 32], manufacturerID: [0u8; 32], model: [0u8; 16], serialNumber: [0u8; 16], - flags: CKF_RNG | CKF_LOGIN_REQUIRED, + flags: CKF_RNG, ulMaxSessionCount: CK_EFFECTIVELY_INFINITE, ulSessionCount: 0, ulMaxRwSessionCount: CK_EFFECTIVELY_INFINITE, @@ -149,55 +101,30 @@ impl Token { firmwareVersion: CK_VERSION { major: 0, minor: 0 }, utcTime: *b"0000000000000000", }, - filename: match dbpath { - Some(s) => Some(s.clone()), - None => None, + facilities: TokenFacilities { + mechanisms: Mechanisms::new(), + factories: ObjectFactories::new(), + handles: Handles::new(), }, - object_factories: ObjectFactories::new(), - mechanisms: Mechanisms::new(), - storage: store, + storage: new_storage(dbtype, &dbpath)?, session_objects: HashMap::new(), - handles: Handles::new(), - kek: None, - so_logged_in: false, + logged: KRY_UNSPEC, }; - /* default strings */ - copy_sized_string(TOKEN_LABEL.as_bytes(), &mut token.info.label); - copy_sized_string( - MANUFACTURER_ID.as_bytes(), - &mut token.info.manufacturerID, - ); - copy_sized_string(TOKEN_MODEL.as_bytes(), &mut token.info.model); - /* register mechanisms and factories */ - register_all(&mut token.mechanisms, &mut token.object_factories); + register_all( + &mut token.facilities.mechanisms, + &mut token.facilities.factories, + ); - if let Some(ref filename) = token.filename { - match token.storage.open(filename) { - Ok(()) => { - token.load_token_info()?; - token.info.flags |= CKF_TOKEN_INITIALIZED; - #[cfg(not(test))] - { - token.info.flags &= !CKF_RESTORE_KEY_NOT_NEEDED; - } + match token.storage.open() { + Ok(info) => token.fill_token_info(&info), + Err(err) => match err.rv() { + CKR_CRYPTOKI_NOT_INITIALIZED => { + token.info.flags &= !CKF_TOKEN_INITIALIZED } - Err(err) => match err.rv() { - CKR_CRYPTOKI_NOT_INITIALIZED => { - token.info.flags &= !CKF_TOKEN_INITIALIZED - } - _ => return Err(err), - }, - } - } else { - token.info.flags &= !CKF_LOGIN_REQUIRED; - token.info.flags |= CKF_TOKEN_INITIALIZED; - token.info.flags |= CKF_RESTORE_KEY_NOT_NEEDED; - } - - if token.info.flags & CKF_TOKEN_INITIALIZED != 0 { - token.init_pin_flags()?; + _ => return Err(err), + }, } #[cfg(feature = "fips")] @@ -206,648 +133,178 @@ impl Token { Ok(token) } - #[cfg(test)] - pub fn use_encryption(&mut self, enc: bool) { - if enc { - self.info.flags |= CKF_RESTORE_KEY_NOT_NEEDED; - } else { - self.info.flags &= !CKF_RESTORE_KEY_NOT_NEEDED; - } + fn fill_token_info(&mut self, info: &StorageTokenInfo) { + self.info.label = info.label; + self.info.manufacturerID = info.manufacturer; + self.info.model = info.model; + self.info.serialNumber = info.serial; + self.info.flags = info.flags | CKF_RNG; } - pub fn is_initialized(&self) -> bool { - self.info.flags & CKF_TOKEN_INITIALIZED == CKF_TOKEN_INITIALIZED - } - - fn is_login_required(&self) -> bool { - self.info.flags & CKF_LOGIN_REQUIRED == CKF_LOGIN_REQUIRED + pub fn get_token_info(&self) -> &CK_TOKEN_INFO { + &self.info } - fn load_token_info(&mut self) -> Result<()> { - let uid = TOKEN_INFO_UID.to_string(); - let obj = match self.storage.fetch_by_uid(&uid) { - Ok(o) => o, - Err(e) => { - if e.attr_not_found() { - /* it is ok if no token data is stored yet, - * we'll use defaults */ - return Ok(()); - } else { - return Err(e); - } - } - }; - if obj.get_attr_as_ulong(CKA_CLASS)? != KRO_TOKEN_DATA { - return Err(CKR_TOKEN_NOT_RECOGNIZED)?; - } - let label = obj - .get_attr_as_string(CKA_LABEL) - .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; - copy_sized_string(label.as_bytes(), &mut self.info.label); - let issuer = obj - .get_attr_as_string(KRA_MANUFACTURER_ID) - .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; - copy_sized_string(issuer.as_bytes(), &mut self.info.manufacturerID); - let model = obj - .get_attr_as_string(KRA_MODEL) - .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; - copy_sized_string(model.as_bytes(), &mut self.info.model); - let serial = obj - .get_attr_as_string(KRA_SERIAL_NUMBER) - .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; - copy_sized_string(serial.as_bytes(), &mut self.info.serialNumber); - self.info.flags = obj - .get_attr_as_ulong(KRA_FLAGS) - .map_err(|_| CKR_TOKEN_NOT_RECOGNIZED)?; - - Ok(()) + pub fn is_initialized(&self) -> bool { + self.info.flags & CKF_TOKEN_INITIALIZED == CKF_TOKEN_INITIALIZED } - fn store_token_info(&mut self) -> Result<()> { - let uid = TOKEN_INFO_UID.to_string(); - let mut obj = match self.storage.fetch_by_uid(&uid) { - Ok(o) => o.clone(), - Err(_) => { - let mut o = Object::new(); - o.set_attr(Attribute::from_string(CKA_UNIQUE_ID, uid.clone()))?; - o.set_attr(Attribute::from_bool(CKA_TOKEN, true))?; - o.set_attr(Attribute::from_ulong(CKA_CLASS, KRO_TOKEN_DATA))?; - o - } + pub fn initialize(&mut self, pin: &[u8], label: &[u8]) -> Result<()> { + if self.is_initialized() { + self.auth_user(CKU_SO, pin, true)?; }; - obj.set_attr(Attribute::string_from_sized( - CKA_LABEL, - &self.info.label, - ))?; - obj.set_attr(Attribute::string_from_sized( - KRA_MANUFACTURER_ID, - &self.info.manufacturerID, - ))?; - obj.set_attr(Attribute::string_from_sized( - KRA_MODEL, - &self.info.model, - ))?; - obj.set_attr(Attribute::string_from_sized( - KRA_SERIAL_NUMBER, - &self.info.serialNumber, - ))?; - obj.set_attr(Attribute::from_ulong(KRA_FLAGS, self.info.flags))?; - - self.storage.store(&uid, obj)?; - return Ok(()); - } - fn fetch_pin_object(&mut self, uid: &str) -> Result { - let obj = match self.storage.fetch_by_uid(&uid.to_string()) { - Ok(o) => o, - Err(e) => { - if e.attr_not_found() { - return Err(CKR_USER_PIN_NOT_INITIALIZED)?; - } else { - return Err(e); - } - } - }; - if obj.get_attr_as_ulong(CKA_CLASS)? != CKO_SECRET_KEY { - return Err(CKR_GENERAL_ERROR)?; - } - if obj.get_attr_as_ulong(CKA_KEY_TYPE)? != CKK_GENERIC_SECRET { - return Err(CKR_GENERAL_ERROR)?; - } - Ok(obj) - } + self.facilities.handles = Handles::new(); + self.session_objects.clear(); + self.logged = KRY_UNSPEC; - fn store_pin_object( - &mut self, - uid: String, - label: String, - wrapped: Vec, - ) -> Result<()> { - match self.storage.fetch_by_uid(&uid) { - Ok(o) => { - let mut obj = o.clone(); - obj.set_attr(Attribute::from_string(CKA_LABEL, label))?; - obj.set_attr(Attribute::from_bytes(CKA_VALUE, wrapped))?; - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, 0))?; - self.storage.store(&uid, obj)?; - } - Err(_) => { - let mut obj = Object::new(); - 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_string(CKA_LABEL, label))?; - obj.set_attr(Attribute::from_bytes(CKA_VALUE, wrapped))?; - obj.set_attr(Attribute::from_ulong( - KRA_MAX_LOGIN_ATTEMPTS, - MAX_LOGIN_ATTEMPTS, - ))?; - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, 0))?; + /* this inits from scratch or deletes and reinits an existing db */ + let mut info = self.storage.reinit(&self.facilities)?; - self.storage.store(&uid, obj)?; - } - } - return Ok(()); - } + /* Add SO PIN */ + self.set_pin(CKU_SO, pin, &[])?; - fn parse_pin_label(&self, label: &str) -> Result<(String, usize)> { - let parts: Vec<_> = label.split(":").collect(); - if parts.len() != 2 { - return Err(CKR_GENERAL_ERROR)?; - } - Ok(( - parts[0].to_string(), - match parts[1].parse() { - Ok(u) => u, - Err(_) => return Err(CKR_GENERAL_ERROR)?, - }, - )) - } + /* copy Label */ + copy_sized_string(label, &mut info.label); - fn pin_to_key( - &mut self, - pin: &Vec, - salt: &str, - iterations: usize, - ) -> Result { - let params = CK_PKCS5_PBKD2_PARAMS2 { - saltSource: CKZ_DATA_SPECIFIED, - pSaltSourceData: salt.as_ptr() as *const _ as *mut _, - ulSaltSourceDataLen: salt.len() as CK_ULONG, - iterations: iterations as CK_ULONG, - prf: CKP_PKCS5_PBKD2_HMAC_SHA512, - pPrfData: std::ptr::null_mut(), - ulPrfDataLen: 0, - pPassword: pin.as_ptr() as *const _ as *mut _, - ulPasswordLen: pin.len() as CK_ULONG, - }; - let class = CKO_SECRET_KEY; - let keytyp = CKK_AES; - let keylen = aes::MAX_AES_SIZE_BYTES as CK_ULONG; - let truebool: CK_BBOOL = CK_TRUE; - let mut template = CkAttrs::with_capacity(5); - template.add_ulong(CKA_CLASS, &class); - template.add_ulong(CKA_KEY_TYPE, &keytyp); - template.add_ulong(CKA_VALUE_LEN, &keylen); - template.add_bool(CKA_WRAP, &truebool); - template.add_bool(CKA_UNWRAP, &truebool); - let pbkdf2 = self.mechanisms.get(CKM_PKCS5_PBKD2)?; - pbkdf2.generate_key( - &CK_MECHANISM { - mechanism: CKM_PKCS5_PBKD2, - pParameter: ¶ms as *const _ as *mut _, - ulParameterLen: sizeof!(CK_PKCS5_PBKD2_PARAMS2), - }, - template.as_slice(), - &self.mechanisms, - &self.object_factories, - ) - } + /* save token info with provided label */ + self.storage.store_token_info(&info)?; - fn wrap_kek( - &mut self, - wrapper: &Object, - mut kek: Object, - ) -> Result> { - let vlen = kek.get_attr_as_ulong(CKA_VALUE_LEN)?; - let bs = aes::AES_BLOCK_SIZE; - let mut buf = vec![0u8; (((vlen as usize + bs) / bs) + 1) * bs]; - let aes = self.mechanisms.get(CKM_AES_GCM)?; - let factory = self.object_factories.get_object_factory(&kek)?; - /* need to do this or wrap_key will fail */ - kek.set_attr(Attribute::from_bool(CKA_EXTRACTABLE, true))?; - let outlen = aes.wrap_key( - &CK_MECHANISM { - mechanism: CKM_AES_KEY_WRAP_KWP, - pParameter: void_ptr!(USER_PIN_IV.as_ptr()), - ulParameterLen: USER_PIN_IV.len() as CK_ULONG, - }, - wrapper, - &kek, - buf.as_mut_slice(), - factory, - )?; - buf.resize(outlen, 0); - Ok(buf) - } + /* copy info on Token object */ + self.fill_token_info(&info); - fn unwrap_kek(&self, wrapper: &Object, wrapped: &[u8]) -> Result { - let class = CKO_SECRET_KEY; - let keytyp = CKK_AES; - let keylen = aes::MAX_AES_SIZE_BYTES as CK_ULONG; - let truebool: CK_BBOOL = CK_TRUE; - let mut template = CkAttrs::with_capacity(5); - template.add_ulong(CKA_CLASS, &class); - template.add_ulong(CKA_KEY_TYPE, &keytyp); - template.add_ulong(CKA_VALUE_LEN, &keylen); - template.add_bool(CKA_ENCRYPT, &truebool); - template.add_bool(CKA_DECRYPT, &truebool); - let aes = self.mechanisms.get(CKM_AES_GCM)?; - Ok(aes.unwrap_key( - &CK_MECHANISM { - mechanism: CKM_AES_KEY_WRAP_KWP, - pParameter: void_ptr!(USER_PIN_IV.as_ptr()), - ulParameterLen: USER_PIN_IV.len() as CK_ULONG, - }, - wrapper, - wrapped, - template.as_slice(), - self.object_factories - .get_obj_factory_from_key_template(template.as_slice())?, - )?) - } + /* IMPORTANT: we always forcibly unauth here (A reinit + * creates the token as if CKU_SO was logged in in oreder + * to properly store data and set PINs). + * The caller must ensure authentication after a reset + * to be able to correctly access the database. */ + self.storage.unauth_user(CKU_SO)?; - fn update_pin_flags(&mut self, obj: &Object) -> Result<()> { - let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; - let is_so = match uid.as_str() { - SO_PIN_UID => true, - USER_PIN_UID => false, - _ => return Err(CKR_GENERAL_ERROR)?, - }; - let max = obj.get_attr_as_ulong(KRA_MAX_LOGIN_ATTEMPTS)?; - let attempts = obj.get_attr_as_ulong(KRA_LOGIN_ATTEMPTS)?; - match max - attempts { - 0 => { - if is_so { - self.info.flags |= CKF_SO_PIN_LOCKED; - } else { - self.info.flags |= CKF_USER_PIN_LOCKED; - } - } - 1 => { - if is_so { - self.info.flags |= CKF_SO_PIN_FINAL_TRY; - } else { - self.info.flags |= CKF_USER_PIN_FINAL_TRY; - } - } - 2 | 3 => { - if is_so { - self.info.flags |= CKF_SO_PIN_COUNT_LOW; - } else { - self.info.flags |= CKF_USER_PIN_COUNT_LOW; - } - } - _ => { - if attempts == 0 { - self.info.flags &= if is_so { - !(CKF_SO_PIN_FINAL_TRY | CKF_SO_PIN_COUNT_LOW) - } else { - !(CKF_USER_PIN_FINAL_TRY | CKF_USER_PIN_COUNT_LOW) - } - } - } + #[cfg(feature = "fips")] + if fips::token_init(self).is_err() { + return Err(CKR_GENERAL_ERROR)?; } - Ok(()) - } - fn init_pin_flags(&mut self) -> Result<()> { - let so = self.fetch_pin_object(SO_PIN_UID)?; - self.update_pin_flags(&so)?; - let so_label = so.get_attr_as_string(CKA_LABEL)?; - if self.parse_pin_label(so_label.as_str())?.0 == DEFPIN_SALT { - self.info.flags |= CKF_SO_PIN_TO_BE_CHANGED; - } - let user = self.fetch_pin_object(USER_PIN_UID)?; - self.update_pin_flags(&user)?; - let user_label = user.get_attr_as_string(CKA_LABEL)?; - if self.parse_pin_label(user_label.as_str())?.0 == DEFPIN_SALT { - self.info.flags |= CKF_USER_PIN_TO_BE_CHANGED; - } else { - self.info.flags |= CKF_USER_PIN_INITIALIZED; - } Ok(()) } - fn reset_user_pin(&mut self) -> Result<()> { - let class = CKO_SECRET_KEY; - let keytyp = CKK_AES; - let keylen = aes::MAX_AES_SIZE_BYTES as CK_ULONG; - let mut template = CkAttrs::with_capacity(3); - template.add_ulong(CKA_CLASS, &class); - template.add_ulong(CKA_KEY_TYPE, &keytyp); - template.add_ulong(CKA_VALUE_LEN, &keylen); - let aes = self.mechanisms.get(CKM_AES_KEY_GEN)?; - let kek = aes.generate_key( - &CK_MECHANISM { - mechanism: CKM_AES_KEY_GEN, - pParameter: std::ptr::null_mut(), - ulParameterLen: 0, - }, - template.as_slice(), - &self.mechanisms, - &self.object_factories, - )?; - /* the default pin is the null pin - * Except in FIPS mode where OpenSSL refuses empty passwords */ - let key = - self.pin_to_key(&default_password(), DEFPIN_SALT, DEFPIN_ITER)?; - let wrapped = self.wrap_kek(&key, kek)?; - self.store_pin_object( - USER_PIN_UID.to_string(), - format!("{}:{}", DEFPIN_SALT, DEFPIN_ITER), - wrapped, - ) - } - - fn random_pin_salt(&self) -> Result { - let mut data = [0u8; 8]; - get_random_data(&mut data)?; - Ok(hex::encode(data)) - } - pub fn set_pin( &mut self, user_type: CK_USER_TYPE, - pin: &Vec, - old: &Vec, + pin: &[u8], + old: &[u8], ) -> Result<()> { let utype = match user_type { - CK_UNAVAILABLE_INFORMATION => { - if self.so_logged_in { - CKU_SO - } else { - CKU_USER - } - } + CK_UNAVAILABLE_INFORMATION => self.logged, CKU_USER => CKU_USER, CKU_SO => CKU_SO, _ => return Err(CKR_GENERAL_ERROR)?, }; - match utype { - CKU_USER => { - if self.so_logged_in && old.len() == 0 { - /* this is a forced change, - * which will make all existing secrets unreadable */ - self.reset_user_pin()?; - } - let kek = if old.len() == 0 { - /* In FIPS mode OpenSSL's PBKDF2 does not accept empty - * passwords, so we replace it for a default password - * during initialization */ - self.check_user_login(&default_password())? - } else { - self.check_user_login(old)? - }; - let salt = self.random_pin_salt()?; - let key = self.pin_to_key(pin, salt.as_str(), DEFPIN_ITER)?; - let wrapped = self.wrap_kek(&key, kek)?; - self.store_pin_object( - USER_PIN_UID.to_string(), - format!("{}:{}", salt, DEFPIN_ITER), - wrapped, - )?; - - if old.len() != 0 { - self.info.flags |= CKF_USER_PIN_INITIALIZED; - } - } - CKU_SO => { - if self.is_initialized() { - /* When the token is not yet initialized, the set_pin - * operation is used to set the initial SO PIN, so we - * can't check the old one in that case as we'd fail. - */ - self.check_so_login(old)?; - } - let salt = if pin.len() != 0 { - self.random_pin_salt()? - } else { - DEFPIN_SALT.to_string() - }; - let derived = - self.pin_to_key(pin, salt.as_str(), DEFPIN_ITER)?; - let value = derived.get_attr_as_bytes(CKA_VALUE)?; - /* TODO: should we store a copy of the kek with - * the so token for recovery reasons ? */ - self.store_pin_object( - SO_PIN_UID.to_string(), - format!("{}:{}", salt, DEFPIN_ITER), - value.clone(), - )?; - } - _ => return Err(CKR_GENERAL_ERROR)?, + if old.len() != 0 { + self.auth_user(utype, old, true)?; } - Ok(()) - } - - pub fn initialize(&mut self, pin: &Vec, label: &Vec) -> Result<()> { - if self.is_initialized() { - self.check_so_login(pin)?; - }; - - /* this inits from scratch or deletes and reinits an existing db */ - self.storage.reinit()?; - - self.handles = Handles::new(); - self.session_objects.clear(); - self.so_logged_in = false; - self.kek = None; - - /* mark uninitialized otherwise set_pin() will fail trying to verify - * the SO PIN from storage (which has just been obliterated) */ - self.info.flags &= !CKF_TOKEN_INITIALIZED; - - /* Add SO PIN */ - self.set_pin(CKU_SO, pin, &vec![0u8; 0])?; - /* Generate KEK and store with empty User PIN */ - self.reset_user_pin()?; - copy_sized_string(label.as_slice(), &mut self.info.label); - self.store_token_info()?; + self.storage.set_user_pin(&self.facilities, utype, pin)?; - self.init_pin_flags()?; - - #[cfg(feature = "fips")] - if fips::token_init(self).is_err() { - return Err(CKR_GENERAL_ERROR)?; + if utype == CKU_USER { + self.info.flags |= CKF_USER_PIN_INITIALIZED; } - - self.info.flags |= CKF_TOKEN_INITIALIZED; - Ok(()) } - fn update_pin_attempts( - &mut self, - uid: String, - attempts: CK_ULONG, - ) -> Result<()> { - let mut obj = self.storage.fetch_by_uid(&uid)?.clone(); - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, attempts))?; - self.storage.store(&uid, obj) - } - - fn check_so_login(&mut self, pin: &Vec) -> Result<()> { - let mut obj = self.fetch_pin_object(SO_PIN_UID)?; - - let stored_attempts = obj.get_attr_as_ulong(KRA_LOGIN_ATTEMPTS)?; - let max = obj.get_attr_as_ulong(KRA_MAX_LOGIN_ATTEMPTS)?; - if stored_attempts >= max { - return Err(CKR_PIN_LOCKED)?; - } - - let label = obj.get_attr_as_string(CKA_LABEL)?; - let (salt, iterations) = self.parse_pin_label(label.as_str())?; - let key = self.pin_to_key(pin, salt.as_str(), iterations)?; - - let stored_value = obj.get_attr_as_bytes(CKA_VALUE)?; - let value = key.get_attr_as_bytes(CKA_VALUE)?; - - let mut attempts = stored_attempts; - if value == stored_value { - attempts = 0; - } else { - attempts += 1; - } - - /* Store attempts back to token */ - if stored_attempts != attempts { - let _ = self.update_pin_attempts(SO_PIN_UID.to_string(), attempts); - - /* set token info */ - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, attempts))?; - self.update_pin_flags(&obj)?; + pub fn is_logged_in(&self, user_type: CK_USER_TYPE) -> bool { + if user_type != CKU_SO && self.info.flags & CKF_LOGIN_REQUIRED == 0 { + return true; } - if attempts == 0 { - return Ok(()); - } - if self.info.flags & CKF_SO_PIN_LOCKED != 0 { - return Err(CKR_PIN_LOCKED)?; + match user_type { + KRY_UNSPEC => self.logged == CKU_SO || self.logged == CKU_USER, + CKU_SO => self.logged == CKU_SO, + CKU_USER => self.logged == CKU_USER, + _ => false, } - return Err(CKR_PIN_INCORRECT)?; } - fn check_user_login(&mut self, pin: &Vec) -> Result { - let mut obj = self.fetch_pin_object(USER_PIN_UID)?; - - let stored_attempts = obj.get_attr_as_ulong(KRA_LOGIN_ATTEMPTS)?; - let max = obj.get_attr_as_ulong(KRA_MAX_LOGIN_ATTEMPTS)?; - if stored_attempts >= max { - return Err(CKR_PIN_LOCKED)?; - } - - let label = obj.get_attr_as_string(CKA_LABEL)?; - let (salt, iterations) = self.parse_pin_label(label.as_str())?; - let key = self.pin_to_key(pin, salt.as_str(), iterations)?; - - let mut attempts = stored_attempts; - let kek = match self - .unwrap_kek(&key, obj.get_attr_as_bytes(CKA_VALUE)?.as_slice()) - { - Ok(k) => { - attempts = 0; - Some(k) + fn update_auth_flags(&mut self, user_type: CK_USER_TYPE, flags: CK_FLAGS) { + match user_type { + CKU_USER => { + self.info.flags &= !(CKF_USER_PIN_LOCKED + | CKF_USER_PIN_FINAL_TRY + | CKF_USER_PIN_COUNT_LOW); + self.info.flags |= flags; } - Err(_) => { - attempts += 1; - None + CKU_SO => { + self.info.flags &= !(CKF_SO_PIN_LOCKED + | CKF_SO_PIN_FINAL_TRY + | CKF_SO_PIN_COUNT_LOW); + self.info.flags |= flags; } - }; - - /* Store attempts back to token */ - if stored_attempts != attempts { - let _ = - self.update_pin_attempts(USER_PIN_UID.to_string(), attempts); - - /* set token info */ - obj.set_attr(Attribute::from_ulong(KRA_LOGIN_ATTEMPTS, attempts))?; - self.update_pin_flags(&obj)?; - } - - if attempts == 0 { - return Ok(kek.unwrap()); - } - if self.info.flags & CKF_USER_PIN_LOCKED != 0 { - return Err(CKR_PIN_LOCKED)?; + _ => (), } - return Err(CKR_PIN_INCORRECT)?; } - pub fn is_logged_in(&self, user_type: CK_USER_TYPE) -> bool { - if user_type != CKU_SO && !self.is_login_required() { - return true; + fn auth_user( + &mut self, + user_type: CK_USER_TYPE, + pin: &[u8], + check_only: bool, + ) -> Result<()> { + if user_type != CKU_SO && user_type != CKU_USER { + return Err(CKR_USER_TYPE_INVALID)?; + } + let mut flags: CK_FLAGS = 0; + let ret = self.storage.auth_user( + &self.facilities, + user_type, + pin, + &mut flags, + check_only, + ); + self.update_auth_flags(user_type, flags); + if ret.is_err() { + return ret; } - match user_type { - KRY_UNSPEC => self.so_logged_in || self.kek.is_some(), - CKU_SO => self.so_logged_in, - CKU_USER => self.kek.is_some(), - _ => false, + if !check_only { + self.logged = user_type; } + Ok(()) } - pub fn login(&mut self, user_type: CK_USER_TYPE, pin: &Vec) -> CK_RV { - if !self.is_login_required() { - return CKR_OK; - } - match user_type { - CKU_SO => { - if self.so_logged_in { + pub fn login(&mut self, user_type: CK_USER_TYPE, pin: &[u8]) -> CK_RV { + let result = match user_type { + CKU_SO | CKU_USER => { + if user_type == self.logged { return CKR_USER_ALREADY_LOGGED_IN; } - if self.kek.is_some() { + if self.logged != KRY_UNSPEC { return CKR_USER_ANOTHER_ALREADY_LOGGED_IN; } - match self.check_so_login(pin) { - Ok(()) => { - self.so_logged_in = true; - CKR_OK - } - Err(e) => e.rv(), - } + self.auth_user(user_type, pin, false) } - CKU_USER => { - if self.kek.is_some() { - return CKR_USER_ALREADY_LOGGED_IN; - } - if self.so_logged_in { - return CKR_USER_ANOTHER_ALREADY_LOGGED_IN; - } - match self.check_user_login(pin) { - Ok(kek) => { - self.kek = Some(kek); - CKR_OK - } - Err(e) => e.rv(), - } - } - CKU_CONTEXT_SPECIFIC => match self.check_user_login(pin) { - Ok(_) => CKR_OK, - Err(e) => e.rv(), - }, - _ => CKR_USER_TYPE_INVALID, + CKU_CONTEXT_SPECIFIC => self.auth_user(self.logged, pin, true), + _ => return CKR_USER_TYPE_INVALID, + }; + match result { + Ok(()) => CKR_OK, + Err(e) => e.rv(), } } pub fn logout(&mut self) -> CK_RV { - let mut ret = CKR_USER_NOT_LOGGED_IN; - if !self.is_login_required() { - ret = CKR_OK; - } - if self.kek.is_some() { - self.kek = None; - ret = CKR_OK; - } - if self.so_logged_in { - self.so_logged_in = false; - ret = CKR_OK; - } - if ret != CKR_OK { - return ret; + match self.logged { + KRY_UNSPEC => CKR_USER_NOT_LOGGED_IN, + CKU_SO | CKU_USER => { + self.clear_private_session_objects(); + let user_type = self.logged; + self.logged = KRY_UNSPEC; + if self.storage.unauth_user(user_type).is_err() { + return CKR_GENERAL_ERROR; + } + CKR_OK + } + _ => CKR_GENERAL_ERROR, } - - self.clear_private_session_objects(); - - CKR_OK } pub fn save(&mut self) -> Result<()> { @@ -865,7 +322,7 @@ impl Token { /* remove all private session objects */ for handle in priv_handles { let _ = self.session_objects.remove(&handle); - self.handles.remove(handle); + self.facilities.handles.remove(handle); } } @@ -882,135 +339,50 @@ impl Token { for h in handles { let _ = self.session_objects.remove(&h); - self.handles.remove(h); + self.facilities.handles.remove(h); } } - /* We should probably have lifetimes to ensure iv and aad are around for - * the lifetime of the returned structure, but this will require substantial - * reworking of the bindings, so for now we just get this comment. - * ENSURE the arguments stay in scope until CK_GCM_PARAMS is needed - * */ - fn encryption_params(&self, iv: &[u8], aad: &[u8]) -> CK_GCM_PARAMS { - CK_GCM_PARAMS { - pIv: iv.as_ptr() as *mut CK_BYTE, - ulIvLen: iv.len() as CK_ULONG, - ulIvBits: (iv.len() * 8) as CK_ULONG, - pAAD: aad.as_ptr() as *mut CK_BYTE, - ulAADLen: aad.len() as CK_ULONG, - ulTagBits: 64 as CK_ULONG, - } + pub fn drop_session_objects(&mut self, handle: CK_SESSION_HANDLE) { + self.clear_session_objects(handle); } - fn encrypt_value(&self, uid: &String, val: &Vec) -> Result> { - if let Some(ref kek) = self.kek { - let mut iv = [0u8; DEFAULT_IV_SIZE]; - get_random_data(&mut iv)?; - let mut params = self.encryption_params(&iv, uid.as_bytes()); - let mech: CK_MECHANISM = CK_MECHANISM { - mechanism: CKM_AES_GCM, - pParameter: &mut params as *mut CK_GCM_PARAMS as *mut _, - ulParameterLen: sizeof!(CK_GCM_PARAMS), - }; - let aes = self.mechanisms.get(CKM_AES_GCM)?; - let mut op = aes.encryption_new(&mech, &kek)?; - let clen = op.encryption_len(val.len(), false)?; - let mut encval = vec![0u8; iv.len() + clen]; - encval[..iv.len()].copy_from_slice(&iv); - let outlen = op.encrypt( - val.as_slice(), - &mut encval.as_mut_slice()[iv.len()..], - )?; - encval.resize(iv.len() + outlen, 0); - return Ok(encval); - } else { - return Err(CKR_GENERAL_ERROR)?; - } + pub fn get_mechs_num(&self) -> usize { + self.facilities.mechanisms.len() } - fn object_to_storage( - &mut self, - mut obj: Object, - encrypt: bool, - ) -> Result<()> { - let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; - if encrypt && self.info.flags & CKF_RESTORE_KEY_NOT_NEEDED != 0 { - let ats = self.object_factories.get_sensitive_attrs(&obj)?; - for typ in ats { - let plain = obj.get_attr_as_bytes(typ)?; - let encval = self.encrypt_value(&uid, plain)?; - - /* now replace the clear text val with the encrypted one */ - obj.set_attr(Attribute::from_bytes(typ, encval))?; - } - } - self.storage.store(&uid, obj) + pub fn get_mechs_list(&self) -> Vec { + self.facilities.mechanisms.list() } - fn decrypt_value(&self, uid: &String, val: &Vec) -> Result> { - if let Some(ref kek) = self.kek { - let mut params = self.encryption_params( - &val.as_slice()[..DEFAULT_IV_SIZE], - uid.as_bytes(), - ); - let mech: CK_MECHANISM = CK_MECHANISM { - mechanism: CKM_AES_GCM, - pParameter: &mut params as *mut CK_GCM_PARAMS as *mut _, - ulParameterLen: sizeof!(CK_GCM_PARAMS), - }; - let aes = self.mechanisms.get(CKM_AES_GCM)?; - let mut op = aes.decryption_new(&mech, &kek)?; - let mut plain = - vec![ - 0u8; - op.decryption_len(val.len() - DEFAULT_IV_SIZE, false)? - ]; - let outlen = op.decrypt( - &val.as_slice()[DEFAULT_IV_SIZE..], - plain.as_mut_slice(), - )?; - plain.resize(outlen, 0); - return Ok(plain); - } else { - return Err(CKR_GENERAL_ERROR)?; + pub fn get_mech_info( + &self, + typ: CK_MECHANISM_TYPE, + ) -> Result<&CK_MECHANISM_INFO> { + match self.facilities.mechanisms.info(typ) { + Some(m) => Ok(m), + None => Err(CKR_MECHANISM_INVALID)?, } } - fn object_from_storage( - &self, - uid: &String, - decrypt: bool, - ) -> Result { - let mut obj = self.storage.fetch_by_uid(uid)?; - if decrypt && self.info.flags & CKF_RESTORE_KEY_NOT_NEEDED != 0 { - let ats = self.object_factories.get_sensitive_attrs(&obj)?; - for typ in ats { - let encval = obj.get_attr_as_bytes(typ)?; - let plain = self.decrypt_value(uid, encval)?; + pub fn get_mechanisms(&self) -> &Mechanisms { + &self.facilities.mechanisms + } - /* now replace the encrypted val with the clear text one */ - obj.set_attr(Attribute::from_bytes(typ, plain))?; - } - } - Ok(obj) + pub fn get_object_factories(&self) -> &ObjectFactories { + &self.facilities.factories } pub fn get_object_by_handle( &mut self, o_handle: CK_OBJECT_HANDLE, ) -> Result { - let is_logged_in = self.is_logged_in(KRY_UNSPEC); - let mut obj = match self.handles.get(o_handle) { - Some(s) => { - if let Some(o) = self.session_objects.get(&o_handle) { - o.clone() - } else { - self.object_from_storage(s, true)? - } - } - None => return Err(CKR_OBJECT_HANDLE_INVALID)?, + let mut obj = match self.session_objects.get(&o_handle) { + Some(o) => o.clone(), + None => self.storage.fetch(&self.facilities, o_handle, true)?, }; - if !is_logged_in && obj.is_token() && obj.is_private() { + if !self.is_logged_in(KRY_UNSPEC) && obj.is_token() && obj.is_private() + { return Err(CKR_USER_NOT_LOGGED_IN)?; } if obj.is_sensitive() { @@ -1024,21 +396,18 @@ impl Token { s_handle: CK_SESSION_HANDLE, mut obj: Object, ) -> Result { - let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; - let is_token = obj.is_token(); - if is_token { + let handle: CK_OBJECT_HANDLE; + if obj.is_token() { if !self.is_logged_in(KRY_UNSPEC) { return Err(CKR_USER_NOT_LOGGED_IN)?; } + handle = self.storage.store(&mut self.facilities, obj)?; } else { + handle = self.facilities.handles.next(); + obj.set_handle(handle); obj.set_session(s_handle); - } - let handle = self.handles.next(); - obj.set_handle(handle); - self.handles.insert(handle, uid.clone()); - if obj.is_token() { - self.object_to_storage(obj, true)?; - } else { + let uid = obj.get_attr_as_string(CKA_UNIQUE_ID)?; + self.facilities.handles.insert(handle, uid)?; self.session_objects.insert(handle, obj); } Ok(handle) @@ -1049,7 +418,7 @@ impl Token { s_handle: CK_SESSION_HANDLE, template: &[CK_ATTRIBUTE], ) -> Result { - let object = self.object_factories.create(template)?; + let object = self.facilities.factories.create(template)?; self.insert_object(s_handle, object) } @@ -1062,46 +431,39 @@ impl Token { let _ = self.session_objects.remove(&o_handle); } None => { - let uid = match self.handles.get(o_handle) { - Some(u) => u, - None => return Err(CKR_OBJECT_HANDLE_INVALID)?, - }; - let obj = self.object_from_storage(uid, false)?; + let obj = + self.storage.fetch(&self.facilities, o_handle, false)?; if !obj.is_destroyable() { return Err(CKR_ACTION_PROHIBITED)?; } - let _ = self.storage.remove_by_uid(&uid); + let _ = self.storage.remove(&self.facilities, o_handle); } } - self.handles.remove(o_handle); + self.facilities.handles.remove(o_handle); Ok(()) } - pub fn get_token_info(&self) -> &CK_TOKEN_INFO { - &self.info - } - pub fn get_object_attrs( &mut self, o_handle: CK_OBJECT_HANDLE, template: &mut [CK_ATTRIBUTE], ) -> Result<()> { let is_logged = self.is_logged_in(KRY_UNSPEC); - let obj = match self.handles.get(o_handle) { - Some(uid) => { - if let Some(o) = self.session_objects.get(&o_handle) { - Cow::Borrowed(o) - } else { - Cow::Owned(self.object_from_storage(uid, false)?) - } - } - None => return Err(CKR_OBJECT_HANDLE_INVALID)?, + let obj = match self.session_objects.get(&o_handle) { + Some(o) => Cow::Borrowed(o), + None => Cow::Owned(self.storage.fetch( + &self.facilities, + o_handle, + false, + )?), }; if !is_logged && obj.is_token() && obj.is_private() { /* do not reveal if the object exists or not */ return Err(CKR_OBJECT_HANDLE_INVALID)?; } - self.object_factories.get_object_attributes(&obj, template) + self.facilities + .factories + .get_object_attributes(&obj, template) } pub fn set_object_attrs( @@ -1109,57 +471,33 @@ impl Token { o_handle: CK_OBJECT_HANDLE, template: &mut [CK_ATTRIBUTE], ) -> Result<()> { - let uid = match self.handles.get(o_handle) { - Some(u) => u, - None => return Err(CKR_OBJECT_HANDLE_INVALID)?, - }; - if let Some(mut obj) = self.session_objects.get_mut(&o_handle) { - return self - .object_factories - .set_object_attributes(&mut obj, template); - } else { - /* no need to decrypt because Sensitive attributes - * cannot be changed via this function */ - let mut obj = self.object_from_storage(uid, false)?; - self.object_factories - .set_object_attributes(&mut obj, template)?; - self.object_to_storage(obj, false) - } - } - - pub fn drop_session_objects(&mut self, handle: CK_SESSION_HANDLE) { - self.clear_session_objects(handle); - } - - pub fn get_mechs_num(&self) -> usize { - self.mechanisms.len() - } - - pub fn get_mechs_list(&self) -> Vec { - self.mechanisms.list() - } - - pub fn get_mech_info( - &self, - typ: CK_MECHANISM_TYPE, - ) -> Result<&CK_MECHANISM_INFO> { - match self.mechanisms.info(typ) { - Some(m) => Ok(m), - None => Err(CKR_MECHANISM_INVALID)?, + match self.session_objects.get_mut(&o_handle) { + Some(mut obj) => self + .facilities + .factories + .set_object_attributes(&mut obj, template), + None => { + if !self.is_logged_in(KRY_UNSPEC) { + return Err(CKR_USER_NOT_LOGGED_IN)?; + } + let mut obj = + self.storage.fetch(&self.facilities, o_handle, true)?; + self.facilities + .factories + .set_object_attributes(&mut obj, template)?; + let _ = self.storage.store(&mut self.facilities, obj)?; + Ok(()) + } } } pub fn get_object_size(&self, o_handle: CK_OBJECT_HANDLE) -> Result { - match self.handles.get(o_handle) { - Some(s) => { - if let Some(o) = self.session_objects.get(&o_handle) { - o.rough_size() - } else { - self.object_from_storage(s, false)?.rough_size() - } - } - None => Err(CKR_OBJECT_HANDLE_INVALID)?, - } + let obj = if let Some(o) = self.session_objects.get(&o_handle) { + Cow::Borrowed(o) + } else { + Cow::Owned(self.storage.fetch(&self.facilities, o_handle, false)?) + }; + obj.rough_size() } pub fn copy_object( @@ -1168,21 +506,16 @@ impl Token { o_handle: CK_OBJECT_HANDLE, template: &[CK_ATTRIBUTE], ) -> Result { - let is_logged_in = self.is_logged_in(KRY_UNSPEC); - let obj: Cow<'_, Object> = match self.handles.get(o_handle) { - Some(uid) => { - if let Some(o) = self.session_objects.get_mut(&o_handle) { - Cow::Borrowed(o) - } else { - Cow::Owned(self.object_from_storage(uid, true)?) - } + let obj = if let Some(o) = self.session_objects.get_mut(&o_handle) { + Cow::Borrowed(o) + } else { + let o = self.storage.fetch(&mut self.facilities, o_handle, true)?; + if !self.is_logged_in(KRY_UNSPEC) && o.is_private() { + return Err(CKR_USER_NOT_LOGGED_IN)?; } - None => return Err(CKR_OBJECT_HANDLE_INVALID)?, + Cow::Owned(o) }; - if !is_logged_in && obj.is_token() && obj.is_private() { - return Err(CKR_USER_NOT_LOGGED_IN)?; - } - let newobj = self.object_factories.copy(&obj, template)?; + let newobj = self.facilities.factories.copy(&obj, template)?; self.insert_object(s_handle, newobj) } @@ -1190,13 +523,13 @@ impl Token { &mut self, template: &[CK_ATTRIBUTE], ) -> Result> { + let mut tmpl = CkAttrs::from(template); let mut handles = Vec::::new(); - let is_logged_in = self.is_logged_in(KRY_UNSPEC); /* First add internal session objects */ for (_, o) in &self.session_objects { if o.is_sensitive() { - match self.object_factories.check_sensitive(o, template) { + match self.facilities.factories.check_sensitive(o, template) { Err(_) => continue, Ok(()) => (), } @@ -1206,49 +539,14 @@ impl Token { } } - /* Then search storage */ - let ret = self.storage.search(template)?; - for o in ret { - if !is_logged_in && o.is_private() { - continue; - } - - if o.is_sensitive() { - match self.object_factories.check_sensitive(&o, template) { - Err(_) => continue, - Ok(()) => (), - } - } - - let uid = match o.get_attr_as_string(CKA_UNIQUE_ID) { - Ok(s) => s, - Err(_) => return Err(CKR_GENERAL_ERROR)?, - }; - let handle = match self.handles.get_by_uid(&uid) { - Some(h) => *h, - None => { - let h = self.handles.next(); - self.handles.insert(h, uid.clone()); - h - } - }; - - /* do not return internal objects */ - if let Ok(numuid) = uid.parse::() { - if numuid < 10 { - continue; - } - } - handles.push(handle); + if !self.is_logged_in(KRY_UNSPEC) { + tmpl.add_owned_bool(CKA_PRIVATE, CK_FALSE)?; } - Ok(handles) - } - pub fn get_mechanisms(&self) -> &Mechanisms { - &self.mechanisms - } - - pub fn get_object_factories(&self) -> &ObjectFactories { - &self.object_factories + /* Then search storage */ + let mut storage_handles = + self.storage.search(&mut self.facilities, tmpl.as_slice())?; + handles.append(&mut storage_handles); + Ok(handles) } }