diff --git a/src/config.rs b/src/config.rs index 4375694..1484e64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use std::{fs::File, io::Read, path::Path}; -use crate::store::HashType; +use crate::crypto::HashType; pub struct Preferences { _store_type: String, @@ -11,7 +11,7 @@ pub struct Preferences { pub fn try_load_preferences() -> Option { //Alert: develop a better home dir acquisition method - let mut file = Path::new("/home/nya/.nyanpass.conf"); + let mut file = Path::new("~/.nyanpass.conf"); if !file.exists() { file = Path::new("/etc/nyanpass.conf"); } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..6e66d1c --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,274 @@ +use std::{ + fs::File, + io::{BufReader, Read}, +}; + +use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce}; +use argon2::PasswordHasher; +use password_hash::SaltString; +use rand::{rngs::StdRng, RngCore, SeedableRng}; + +use crate::data::DataPage; + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub enum EncryptionType { + Invalid = 0, + AesGcm = 1, + Chacha20Poly1305 = 2, +} + +impl From for EncryptionType { + fn from(n: u8) -> Self { + match n { + 0 => EncryptionType::Invalid, + 1 => EncryptionType::AesGcm, + 2 => EncryptionType::Chacha20Poly1305, + _ => panic!("Invalid value '{}'.", n), + } + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub enum HashType { + Invalid = 0, + Argon2 = 1, + Bcrypt = 2, + Pbkdf2 = 4, +} + +impl From for HashType { + fn from(n: u8) -> Self { + match n { + 0 => HashType::Invalid, + 1 => HashType::Argon2, + 2 => HashType::Bcrypt, + 4 => HashType::Pbkdf2, + _ => panic!("Invalid value '{}'.", n), + } + } +} + +pub trait EncryptionManager {} + +pub struct EncryptionContext { + enc_type: EncryptionType, + hash_type: HashType, + key: Vec, + salt: SaltString, + iv_root: Vec, +} + +impl EncryptionContext { + pub fn decrypt_block(&self, ciphertext: &Vec, offset: usize) -> Result { + let plaintext = match self.enc_type { + EncryptionType::Invalid => unreachable!(), + EncryptionType::AesGcm => { + let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key); + let aes = Aes256Gcm::new(aes_key); + + let nonce = nonce_offset(&self.iv_root, offset); + let nonce = Nonce::from_slice(&nonce); + + aes.decrypt(nonce, ciphertext.as_ref()) + } + EncryptionType::Chacha20Poly1305 => todo!(), + }; + + if plaintext.is_err() { + return Err(format!("Decryption error: {}", plaintext.unwrap_err())); + } + + Ok(DataPage::load(offset, plaintext.unwrap())) + } + + pub fn decrypt_block_from_file( + &self, + len: usize, + offset: usize, + file: &mut BufReader, + ) -> Result { + let mut buf: Vec = vec![0; len + 16]; + match file.read_exact(&mut buf) { + Err(err) => Err(format!("Read error: {}", err.to_string())), + Ok(_) => self.decrypt_block(&buf, offset), + } + } + + pub fn default(passphrase: &Vec) -> Self { + let mut rng = StdRng::from_entropy(); + + let salt = SaltString::generate(&mut rng); + + let (default_hash_type, default_enc_type) = (HashType::Argon2, EncryptionType::AesGcm); + + let key = derive_key(&passphrase, &salt, default_hash_type).unwrap(); + + let mut iv: [u8; 12] = [0; 12]; + rng.fill_bytes(&mut iv); + + EncryptionContext { + enc_type: default_enc_type, + hash_type: default_hash_type, + key, + salt, + iv_root: iv.to_vec(), + } + } + + pub fn encrypt_block( + &self, + plaintext: &Vec, + offset: usize, + ) -> Result { + let ciphertext = match self.enc_type { + EncryptionType::Invalid => unreachable!(), + EncryptionType::AesGcm => { + let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key); + let aes = Aes256Gcm::new(aes_key); + + let nonce = nonce_offset(&self.iv_root, offset); + let nonce = Nonce::from_slice(&nonce); + + aes.encrypt(nonce, plaintext.as_ref()) + } + EncryptionType::Chacha20Poly1305 => todo!(), + }; + + if ciphertext.is_err() { + return Err("Encryption error"); + } + + Ok(DataPage::load(offset, ciphertext.unwrap())) + } + + pub fn encryption_type(&self) -> EncryptionType { + self.enc_type + } + + pub fn from_settings( + passphrase: &Vec, + enc_type: EncryptionType, + hash_type: HashType, + salt: SaltString, + iv_root: Vec, + ) -> Self { + let key = derive_key(&passphrase, &salt, hash_type).unwrap(); + + EncryptionContext { + enc_type, + hash_type, + key, + salt, + iv_root, + } + } + + pub unsafe fn get_key(&self) -> &Vec { + &self.key + } + + pub fn hash_type(&self) -> HashType { + self.hash_type + } + + pub fn offset_iv_by(&self, offset: usize) -> Vec { + nonce_offset(&self.iv_root, offset) + } + + pub fn root_iv(&self) -> &Vec { + &self.iv_root + } + + pub fn salt(&self) -> &SaltString { + &self.salt + } +} + +pub fn nonce_offset(nonce_root: &Vec, offset: usize) -> Vec { + let len = nonce_root.len(); + if len < (usize::BITS / 8) as usize { + panic!("Nonce length less than {}", usize::BITS / 8); + } + let start = len - (usize::BITS / 8) as usize; + + let mut nonce_copy = vec![0; len]; + nonce_copy.clone_from_slice(&nonce_root); + + let mut num = u64::from_be_bytes(nonce_copy[start..len].try_into().unwrap()); + num += offset as u64; + nonce_copy[start..len].copy_from_slice(&num.to_be_bytes()); + + nonce_copy +} + +pub fn derive_key( + password: &Vec, + salt: &SaltString, + hash_type: HashType, +) -> Result, &'static str> { + let hash = match hash_type { + HashType::Invalid => unreachable!(), + HashType::Argon2 => { + let argon = argon2::Argon2::default(); + argon.hash_password(password, salt) + } + HashType::Bcrypt => { + todo!() + } + HashType::Pbkdf2 => { + todo!() + } + }; + + if hash.is_err() { + return Err("Hashing error"); + } + + let hash = hash.unwrap(); + + let hash: Vec = hash.hash.unwrap().as_bytes().to_owned(); + Ok(hash) +} + +#[cfg(test)] +mod tests { + use super::nonce_offset; + + #[test] + fn nonce_offset_increment() { + let nonce: Vec = vec![0; 12]; + + let nonce_off = nonce_offset(&nonce, 0xfeeddeadbeef); + + assert!( + nonce_off[11] == 0xef + && nonce_off[10] == 0xbe + && nonce_off[9] == 0xad + && nonce_off[8] == 0xde + && nonce_off[7] == 0xed + && nonce_off[6] == 0xfe, + "\ninitial = {:x?}\nnew = {:x?}", + nonce, + nonce_off + ); + } + + #[test] + fn nonce_offset_increment_carry() { + let nonce: Vec = vec![0xfe; 12]; + + let nonce_off = nonce_offset(&nonce, 0x2); + + assert!( + nonce_off[11] == 0x00 + && nonce_off[10] == 0xff + && nonce_off[9] == 0xfe + && nonce_off[8] == 0xfe + && nonce_off[7] == 0xfe + && nonce_off[6] == 0xfe, + "\ninitial = {:x?}\nnew = {:x?}", + nonce, + nonce_off + ); + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..1cea798 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,176 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +pub trait DataPager { + fn get_page(&mut self, offset: usize) -> Result<&mut DataPage, String>; + + fn update_page(&mut self, page: WritableDataPage) -> Result; +} + +#[derive(PartialEq, Eq, Hash)] +pub struct DataPage { + offset: usize, + pos: usize, + data: Vec, +} + +impl Read for DataPage { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let range = self.pos..(self.pos + buf.len()).clamp(0, self.data.len() - buf.len()); + let size = range.len(); + buf.copy_from_slice(&self.data[range]); + self.pos += size; + Ok(size) + } +} + +impl Seek for DataPage { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + let cur = self.pos; + + let newpos = match pos { + SeekFrom::Start(x) => { + if x > self.data.len() as u64 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek position out of buffer bounds.", + )); + } + x + } + SeekFrom::End(x) => { + if x > self.data.len() as i64 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek position out of buffer bounds.", + )); + } + + (self.data.len() as i64 - x) as u64 + } + SeekFrom::Current(x) => { + let new = self.pos as i64 + x; + + if new < 0 || new > self.data.len() as i64 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek position out of buffer bounds.", + )); + } + + new as u64 + } + }; + + self.pos = newpos as usize; + + Ok(cur as u64) + } +} + +impl DataPage { + pub(crate) fn load(offset: usize, data: Vec) -> Self { + DataPage { + offset, + pos: 0, + data, + } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn buffer(&self) -> &[u8] { + &self.data + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn mutate(&mut self) -> WritableDataPage { + WritableDataPage { page: self } + } +} + +pub struct WritableDataPage<'a> { + page: &'a mut DataPage, +} + +impl Write for WritableDataPage<'_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let pos = self.page.pos; + + let len = buf.len().clamp(0, self.page.data.len() - pos); + + //Is this neccessary? + if len == 0 || buf.len() == 0 { + return Ok(0); + } + + self.page.data[pos..pos + len].copy_from_slice(&buf); + + Ok(len) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl WritableDataPage<'_> { + pub fn data(&self) -> &[u8] { + self.page.buffer() + } + + pub fn offset(&self) -> usize { + self.page.offset() + } +} + +#[cfg(test)] +mod tests { + use super::DataPage; + use std::io::{Read, Seek, SeekFrom}; + + fn make_page() -> DataPage { + DataPage { + offset: 0, + data: vec![0; 128], + pos: 0, + } + } + + #[test] + fn datapage_seek() { + let mut stream = make_page(); + + { + let pos = stream.seek(SeekFrom::Start(64)); + assert!(pos.is_ok()); + assert_eq!(pos.unwrap(), 0); + } + + { + let pos = stream.seek(SeekFrom::Start(128)); + assert!(pos.is_ok()); + assert_eq!(pos.unwrap(), 64); + } + + { + let pos = stream.seek(SeekFrom::Start(256)); + assert!(pos.is_err()); + } + } + + #[test] + fn datapage_read() { + let mut stream = make_page(); + let mut buf: [u8; 8] = [1; 8]; + + assert!(stream.read_exact(&mut buf).is_ok()); + + assert_eq!(&buf, &[0; 8]); + assert_eq!(stream.stream_position().unwrap(), 8); + } +} diff --git a/src/main.rs b/src/main.rs index 2de4642..f9760fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ use config::try_load_preferences; use store::Store; mod config; +mod crypto; +mod data; mod store; fn main() { @@ -13,7 +15,7 @@ fn main() { let _preferences = try_load_preferences(); - let store = match store::local::load( + let mut store = match store::local::load( "/tmp/store.db".to_string(), "meowmeow".as_bytes().to_owned(), ) { diff --git a/src/store/local.rs b/src/store/local.rs deleted file mode 100644 index ff8bb7a..0000000 --- a/src/store/local.rs +++ /dev/null @@ -1,905 +0,0 @@ -use std::{ - arch::x86_64::_mm_crc32_u32, - collections::{hash_map::DefaultHasher, HashMap}, - fs::File, - hash::{Hash, Hasher}, - io::{BufReader, Read, Seek, SeekFrom, Write}, - path::Path, -}; - -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce}; -use argon2::PasswordHasher; -use password_hash::SaltString; -use rand::{rngs::StdRng, RngCore, SeedableRng}; - -use super::{EncryptionStream, EncryptionType, HashType, RecordType, Store, StoreRecord}; - -pub struct LocalStore { - path: String, - header: LocalStoreHeader, - meta: LocalStoreMeta, - enc_ctx: EncryptionContext, - cache: HashMap, -} - -/* - Store binary format: - Header: 0x00 (first 32 bytes): - 0x00 - magic number - 0x04 - last access timestamp - 0x08 - last write timestamp - 0x0c - encryption type byte - 0x0d - hash type byte - 0x0e - superblock size - 0x10 - cipher key size - 0x12 - encrypted block offset - 0x16 - header CRC-32 checksum - 0x1a - padding - Enc details: 16 bytes - 0x20 - salt - 0x30 - master IV - 0x3c - 4 byte padding with 0 ----Encrypted section starts here--- ---Block 0 - Superblock: - Hash: - 0x40 - master password hash - Metadata: - 0x60 - record count - 0x64 - specifier count - 0x68 - padding with 0 - Auth tag: 0x80 ---Block 1 - Index: - Root node: - 0x90 - root node or invalid node if empty db -*/ - -//Proposal: Initialize IV to 0? - -const MAGIC_NUM: u32 = 0x6d656f77; -const HEADER_SIZE: usize = 32; - -struct LocalStoreHeader { - magic_h: u32, - last_access: u32, - last_write: u32, - enc_type: EncryptionType, - hash_type: HashType, - enc_sup_block_size: u16, - enc_key_size: u16, - encrypted_block_offset: u32, - chksum_crc: u32, -} - -impl LocalStoreHeader { - fn get_checksum(&self) -> u32 { - let mut crc: u32 = 0xffffffff; - unsafe { - crc = _mm_crc32_u32(crc, self.last_access); - crc = _mm_crc32_u32(crc, self.last_write); - crc = _mm_crc32_u32(crc, self.enc_type as u32); - crc = _mm_crc32_u32(crc, self.hash_type as u32); - crc = _mm_crc32_u32(crc, self.enc_sup_block_size as u32); - crc = _mm_crc32_u32(crc, self.enc_key_size as u32); - crc = _mm_crc32_u32(crc, self.encrypted_block_offset); - } - crc - } - - fn is_checksum_valid(&self) -> bool { - let chk = self.get_checksum(); - chk == self.chksum_crc - } -} - -struct LocalStoreMeta { - record_count: u32, - specifier_count: u32, - index_node_arity: u16, - data_block_size: u32, - data_offset: u64, -} - -struct EncryptionContext { - enc_type: EncryptionType, - hash_type: HashType, - key: Vec, - salt: SaltString, - iv_root: Vec, -} - -fn try_deserialize_header(file: &mut BufReader) -> Result { - let mut header: LocalStoreHeader = LocalStoreHeader { - magic_h: 0, - last_access: 0, - last_write: 0, - enc_type: EncryptionType::Invalid, - hash_type: HashType::Invalid, - enc_sup_block_size: 0, - enc_key_size: 0, - chksum_crc: 0, - encrypted_block_offset: 32, - }; - - let mut buf: [u8; HEADER_SIZE] = [0; HEADER_SIZE]; - - if file.read_exact(&mut buf).is_err() { - return Err("Read error."); - } - - header.magic_h = u32::from_be_bytes(buf[0..4].try_into().unwrap()); - if header.magic_h != MAGIC_NUM { - return Err("Invalid header magic number."); - } - - header.last_access = u32::from_be_bytes(buf[4..8].try_into().unwrap()); - header.last_write = u32::from_be_bytes(buf[8..12].try_into().unwrap()); - header.enc_type = EncryptionType::from(buf[12]); - header.hash_type = HashType::from(buf[13]); - header.enc_sup_block_size = u16::from_be_bytes(buf[14..16].try_into().unwrap()); - header.enc_key_size = u16::from_be_bytes(buf[16..18].try_into().unwrap()); - header.encrypted_block_offset = u32::from_be_bytes(buf[18..22].try_into().unwrap()); - header.chksum_crc = u32::from_be_bytes(buf[22..26].try_into().unwrap()); - - if !header.is_checksum_valid() { - return Err("Invalid checksum."); - } - - Ok(header) -} - -fn try_deserialize_superblock( - file: &mut BufReader, - enc: &EncryptionContext, - header: &LocalStoreHeader, -) -> Result { - let data = enc - .decrypt_block_from_file(header.enc_sup_block_size as usize, 0, file) - .unwrap(); - - if data.data[0..enc.key.len()].ne(&enc.key) { - return Err("Key mismatch."); - } - - let meta = deserialize_store_meta(&&data.data[32..]); - - Ok(meta) -} - -fn serialize_header(header: &LocalStoreHeader) -> Vec { - let mut buf = Vec::::new(); - buf.append(u32::to_be_bytes(MAGIC_NUM).to_vec().as_mut()); - buf.append(u32::to_be_bytes(header.last_access).to_vec().as_mut()); - buf.append(u32::to_be_bytes(header.last_write).to_vec().as_mut()); - buf.push(header.enc_type as u8); - buf.push(header.hash_type as u8); - buf.append( - u16::to_be_bytes(header.enc_sup_block_size) - .to_vec() - .as_mut(), - ); - buf.append(u16::to_be_bytes(header.enc_key_size).to_vec().as_mut()); - buf.append( - u32::to_be_bytes(header.encrypted_block_offset) - .to_vec() - .as_mut(), - ); - buf.append(u32::to_be_bytes(header.chksum_crc).to_vec().as_mut()); - - let padding_len = 32 - buf.len(); - let mut pad: Vec = vec![0; padding_len]; - buf.append(&mut pad); - - return buf; -} - -fn try_create_store(path: &String, passphrase: &Vec) -> bool { - let timestamp: u32 = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as u32; - - let mut rng = StdRng::from_entropy(); - - let salt = SaltString::generate(&mut rng); - - let (default_hash_type, default_enc_type) = (HashType::Argon2, EncryptionType::AesGcm); - - let key = derive_key(&passphrase, &salt, default_hash_type).unwrap(); - - let mut iv: [u8; 12] = [0; 12]; - rng.fill_bytes(&mut iv); - - let mut salt_buf: [u8; 16] = [0; 16]; - salt.b64_decode(&mut salt_buf).unwrap(); - - let enc = EncryptionContext { - enc_type: default_enc_type, - hash_type: default_hash_type, - key, - salt, - iv_root: iv.to_vec(), - }; - - let mut superblock_buf: Vec = Vec::new(); - - superblock_buf.extend_from_slice(&enc.key); - if superblock_buf.len() != 32 { - superblock_buf.append([b'\0'].repeat(32 - superblock_buf.len()).as_mut()); - } - - let meta = LocalStoreMeta { - record_count: 0, - specifier_count: 0, - index_node_arity: 16, - data_block_size: 8092, - data_offset: 0, - }; - - superblock_buf.append(&mut serialize_store_meta(&meta)); - - let enc_buf = enc.encrypt_block(&superblock_buf, 0).unwrap(); - - let mut header: LocalStoreHeader = LocalStoreHeader { - magic_h: MAGIC_NUM, - last_access: timestamp, - last_write: timestamp, - enc_type: enc.enc_type, - enc_sup_block_size: (enc_buf.len() - 16) as u16, - enc_key_size: 256, - hash_type: enc.hash_type, - encrypted_block_offset: 32, - chksum_crc: 0, - }; - - header.chksum_crc = header.get_checksum(); - - let mut index_block: Vec = vec![0; meta.data_block_size as usize]; - let root_node_buf = IndexNode { - node_type: IndexNodeType::Leaf, - children: Vec::new(), - } - .serialize(&meta); - - index_block[0..root_node_buf.len()].copy_from_slice(&root_node_buf); - - let mut buf = serialize_header(&header); - - buf.extend_from_slice(&salt_buf); - buf.extend_from_slice(&iv); - buf.append([b'\0'].repeat(16 - iv.len()).as_mut()); - buf.extend_from_slice(&enc_buf.data); - buf.extend_from_slice(&enc.encrypt_block(&index_block, 0x90).unwrap().data); - - match std::fs::File::create(path).unwrap().write(&buf) { - Ok(x) => return x == buf.len(), - Err(_) => return false, - } -} - -fn get_enc_salt_iv(file: &mut BufReader) -> Result<(SaltString, Vec), &'static str> { - let mut buf: [u8; 32] = [0; 32]; - - file.read_exact(&mut buf).unwrap(); - - let salt = match SaltString::b64_encode(&buf[..16]) { - Err(_) => return Err("Invalid salt."), - Ok(x) => x, - }; - - let iv = buf[16..28].to_owned(); - Ok((salt, iv)) -} - -fn derive_key( - password: &Vec, - salt: &SaltString, - hash_type: HashType, -) -> Result, &'static str> { - let hash = match hash_type { - HashType::Invalid => unreachable!(), - HashType::Argon2 => { - let argon = argon2::Argon2::default(); - argon.hash_password(password, salt) - } - HashType::Bcrypt => { - todo!() - } - HashType::Pbkdf2 => { - todo!() - } - }; - - if hash.is_err() { - return Err("Hashing error"); - } - - let hash = hash.unwrap(); - - let hash: Vec = hash.hash.unwrap().as_bytes().to_owned(); - Ok(hash) -} - -fn deserialize_store_meta(data: &[u8]) -> LocalStoreMeta { - let records = u32::from_be_bytes(data[0..4].try_into().unwrap()); - let specifiers = u32::from_be_bytes(data[4..8].try_into().unwrap()); - let index_node_arity = u16::from_be_bytes(data[8..10].try_into().unwrap()); - let data_block_size = u32::from_be_bytes(data[10..14].try_into().unwrap()); - let data_offset = u64::from_be_bytes(data[14..22].try_into().unwrap()); - - LocalStoreMeta { - record_count: records, - specifier_count: specifiers, - index_node_arity, - data_block_size, - data_offset, - } -} - -fn serialize_store_meta(meta: &LocalStoreMeta) -> Vec { - let mut buf: Vec = Vec::with_capacity(32); - - buf.append(u32::to_be_bytes(meta.record_count).to_vec().as_mut()); - buf.append(u32::to_be_bytes(meta.specifier_count).to_vec().as_mut()); - buf.append(u16::to_be_bytes(meta.index_node_arity).to_vec().as_mut()); - buf.append(u32::to_be_bytes(meta.data_block_size).to_vec().as_mut()); - buf.append(u64::to_be_bytes(meta.data_offset).to_vec().as_mut()); - buf.append([b'\0'].repeat(32 - buf.len()).as_mut()); - - buf -} - -pub fn load(path: String, passphrase: Vec) -> Result { - let path_p = Path::new(&path); - - if !path_p.exists() { - if !try_create_store(&path, &passphrase) { - return Err("Cannot create store."); - } - } else if !path_p.is_file() { - return Err("Invalid path."); - } - - let file = match File::open(&path_p) { - Ok(x) => x, - Err(_) => return Err("Cannot open store."), - }; - - let mut reader = BufReader::new(file); - - let header = match try_deserialize_header(&mut reader) { - Err(x) => return Err(x), - Ok(meta) => meta, - }; - - let (salt, iv) = match get_enc_salt_iv(&mut reader) { - Ok(s) => s, - Err(x) => return Err(x), - }; - - let key = match derive_key(&passphrase, &salt, header.hash_type) { - Ok(x) => x, - Err(x) => return Err(x), - }; - - let enc_ctx = EncryptionContext { - enc_type: header.enc_type, - hash_type: header.hash_type, - key, - salt, - iv_root: iv, - }; - - let meta = match try_deserialize_superblock(&mut reader, &enc_ctx, &header) { - Ok(d) => d, - Err(x) => return Err(x), - }; - - let store = LocalStore { - path, - header, - enc_ctx, - meta, - cache: HashMap::new(), - }; - - return Ok(store); -} - -struct LocalRecord { - record: StoreRecord, - position: usize, - size: usize, -} - -const CRED_FLAG_METADATA: u8 = 0b0000100; - -impl LocalRecord { - fn serialize(&self) -> Vec { - let mut buf = Vec::::new(); - - buf.push(self.record.r#type as u8); - - let mut flags: u8 = 0; - - if self.record.meta.is_some() { - flags |= CRED_FLAG_METADATA; - } - buf.push(flags); - - let spec_bytes = self.record.specifier.bytes(); - let key_bytes = self.record.key.bytes(); - let value_bytes = &self.record.value; - let meta_len = if self.record.meta.is_none() { - 0 - } else { - self.record.meta.as_ref().unwrap().len() - }; - - buf.append(u32::to_be_bytes(spec_bytes.len() as u32).to_vec().as_mut()); - buf.append(u32::to_be_bytes(key_bytes.len() as u32).to_vec().as_mut()); - buf.append(u32::to_be_bytes(value_bytes.len() as u32).to_vec().as_mut()); - buf.append(u32::to_be_bytes(meta_len as u32).to_vec().as_mut()); - - buf.extend(spec_bytes); - buf.extend(key_bytes); - buf.extend(value_bytes); - - if meta_len > 0 { - buf.extend(self.record.meta.as_ref().unwrap().bytes()); - } - - buf - } - - fn from_block(bytes: &[u8]) -> Result { - let r#type = bytes[0]; - let flags = bytes[1]; - let spec_len = u32::from_be_bytes(bytes[2..6].try_into().unwrap()) as usize; - let key_len = u32::from_be_bytes(bytes[6..10].try_into().unwrap()) as usize; - let val_len = u32::from_be_bytes(bytes[10..14].try_into().unwrap()) as usize; - let meta_len = u32::from_be_bytes(bytes[14..18].try_into().unwrap()) as usize; - - if key_len < 1 { - return Err("Invalid structure: key length is 0."); - } - - if spec_len < 1 { - return Err("Invalid structure: specifier length is 0."); - } - - if val_len < 1 { - return Err("Invalid structure: value length is 0."); - } - - let key_offset: usize = 18 + spec_len; - let val_offset: usize = key_offset + key_len; - - let specifier = std::str::from_utf8(&bytes[18..(18 + spec_len)]) - .expect("Cannot deserialize the specifier."); - let key = std::str::from_utf8(&bytes[key_offset..key_offset + key_len]) - .expect("Cannot deserialize the key."); - let value = &bytes[val_offset..val_offset + val_len]; - let meta = if meta_len == 0 { - None - } else { - let meta_offset = val_offset + val_len; - Some( - std::str::from_utf8(&bytes[meta_offset..meta_offset + meta_len]) - .expect("Cannot deserialize metadata.") - .to_owned(), - ) - }; - - let record = LocalRecord { - record: StoreRecord { - specifier: specifier.to_owned(), - key: key.to_owned(), - r#type: RecordType::from(r#type), - value: value.to_vec(), - meta, - }, - position: 0, - size: val_offset + val_len + meta_len, - }; - - Ok(record) - } -} - -impl Store for LocalStore { - fn get_creds(&self, specifier: &String, key: &String) -> Option { - match self.btree_find_key(&key) { - (Some(x), _) => { - println!("Found: {}", x); - todo!() - } - (None, _) => return None, - } - } - - fn get_creds_by_specifier(&self, specifier: &String) -> Vec { - todo!() - } - - fn store_creds( - &self, - specifier: &String, - key: &String, - value: &Vec, - r#type: RecordType, - meta: Option, - ) { - todo!() - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -enum IndexNodeType { - Internal = 1, - Leaf = 2, - Invalid = 0, -} - -impl From for IndexNodeType { - fn from(value: u8) -> Self { - match value { - 0 => IndexNodeType::Invalid, - 1 => IndexNodeType::Internal, - 2 => IndexNodeType::Leaf, - _ => panic!("Invalid value {}", value), - } - } -} - -struct IndexNodeEntry { - key: u64, - pointer: u64, -} - -struct IndexNode { - node_type: IndexNodeType, - children: Vec, -} - -impl IndexNode { - fn children(&self) -> &Vec { - &self.children - } - - fn len(&self) -> usize { - self.children.len() - } - - fn serialize(&self, meta: &LocalStoreMeta) -> Vec { - let mut buf: Vec = vec![0; 16 * meta.index_node_arity as usize + 2]; - - let mut cnt = 2; - for child in self.children() { - buf[cnt..(cnt + 8)].copy_from_slice(&child.key.to_be_bytes()); - buf[(cnt + 8)..(cnt + 16)].copy_from_slice(&child.pointer.to_be_bytes()); - cnt += 16; - } - - buf[0] = self.node_type as u8; - buf - } -} - -impl LocalStore { - fn read_node(&self, data: &mut EncryptionStream) -> IndexNode { - let node_type = { - let mut buf: [u8; 2] = [0; 2]; - data.read_exact(&mut buf).expect("Cannot read node type."); - IndexNodeType::from(buf[0]) - }; - - let mut children = Vec::with_capacity(self.meta.index_node_arity as usize); - for _ in 0..self.meta.index_node_arity { - let mut buf: [u8; 16] = [0; 16]; - - data.read_exact(&mut buf) - .expect("Cannot read child node content."); - - let key = u64::from_be_bytes(buf[0..8].try_into().unwrap()); - - if key == 0 { - break; - } - - let pointer = u64::from_be_bytes(buf[0..8].try_into().unwrap()); - - let entry = IndexNodeEntry { key, pointer }; - - children.push(entry); - } - - let node = IndexNode { - node_type, - children, - }; - - node - } - - fn btree_find_key(&self, key: &String) -> (Option, Vec) { - let mut file: BufReader = - BufReader::new(File::open(&self.path).expect("Cannot open file")); - - let hash = { - let mut hasher = DefaultHasher::new(); - key.hash(&mut hasher); - hasher.finish() - }; - - let mut traversal_history = Vec::new(); - - let pos = file.seek(SeekFrom::Start(0x90)).unwrap(); - let mut block = self - .enc_ctx - .decrypt_block_from_file(self.meta.data_block_size as usize, 0x90, &mut file) - .unwrap(); - - loop { - traversal_history.push(block.stream_position().unwrap()); - let node = self.read_node(&mut block); - - if node.node_type == IndexNodeType::Invalid { - panic!( - "Invalid node detected: {}", - block.stream_position().unwrap() - ); - } - - if node.node_type == IndexNodeType::Leaf { - for child in node.children() { - if child.key == hash { - return (Some(child.pointer), traversal_history); - } - } - return (None, traversal_history); - } - - for child in node.children { - if child.key < hash { - continue; - } - - block.seek(SeekFrom::Start(child.pointer)).unwrap(); - } - } - } -} - -fn nonce_offset(nonce_root: &Vec, offset: usize) -> Vec { - let len = nonce_root.len(); - if len < (usize::BITS / 8) as usize { - panic!("Nonce length less than {}", usize::BITS / 8); - } - let start = len - (usize::BITS / 8) as usize; - - let mut nonce_copy = vec![0; len]; - nonce_copy.clone_from_slice(&nonce_root); - - let mut num = u64::from_be_bytes(nonce_copy[start..len].try_into().unwrap()); - num += offset as u64; - nonce_copy[start..len].copy_from_slice(&num.to_be_bytes()); - - nonce_copy -} - -impl EncryptionContext { - fn decrypt_block( - &self, - ciphertext: &Vec, - offset: usize, - ) -> Result { - let plaintext = match self.enc_type { - EncryptionType::Invalid => unreachable!(), - EncryptionType::AesGcm => { - let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key); - let aes = Aes256Gcm::new(aes_key); - - let nonce = nonce_offset(&self.iv_root, offset); - let nonce = Nonce::from_slice(&nonce); - - aes.decrypt(nonce, ciphertext.as_ref()) - } - EncryptionType::Chacha20Poly1305 => todo!(), - }; - - if plaintext.is_err() { - return Err(format!("Decryption error: {}", plaintext.unwrap_err())); - } - - Ok(EncryptionStream { - data: plaintext.unwrap(), - pos: 0, - }) - } - - fn encrypt_block( - &self, - plaintext: &Vec, - offset: usize, - ) -> Result { - let ciphertext = match self.enc_type { - EncryptionType::Invalid => unreachable!(), - EncryptionType::AesGcm => { - let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key); - let aes = Aes256Gcm::new(aes_key); - - let nonce = nonce_offset(&self.iv_root, offset); - let nonce = Nonce::from_slice(&nonce); - - aes.encrypt(nonce, plaintext.as_ref()) - } - EncryptionType::Chacha20Poly1305 => todo!(), - }; - - if ciphertext.is_err() { - return Err("Encryption error"); - } - - Ok(EncryptionStream { - data: ciphertext.unwrap(), - pos: 0, - }) - } - - fn decrypt_block_from_file( - &self, - len: usize, - offset: usize, - file: &mut BufReader, - ) -> Result { - let mut buf: Vec = vec![0; len + 16]; - match file.read_exact(&mut buf) { - Err(err) => Err(format!("Read error: {}", err.to_string())), - Ok(_) => self.decrypt_block(&buf, offset), - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::size_of; - - use crate::store::{ - local::{nonce_offset, LocalStoreHeader, HEADER_SIZE}, - StoreRecord, - }; - - use super::{IndexNode, IndexNodeEntry, IndexNodeType, LocalRecord}; - - #[test] - fn header_size_const_greater_than_realsize() { - let c = HEADER_SIZE; - let r = size_of::(); - - assert!(c > r, "const = {}, real = {}", c, r); - } - - #[test] - fn nonce_offset_increment() { - { - let nonce: Vec = vec![0; 12]; - - let nonce_off = nonce_offset(&nonce, 0xfeeddeadbeef); - - assert!( - nonce_off[11] == 0xef - && nonce_off[10] == 0xbe - && nonce_off[9] == 0xad - && nonce_off[8] == 0xde - && nonce_off[7] == 0xed - && nonce_off[6] == 0xfe, - "\ninitial = {:x?}\nnew = {:x?}", - nonce, - nonce_off - ); - } - - //Carry test - { - let nonce: Vec = vec![0xfe; 12]; - - let nonce_off = nonce_offset(&nonce, 0x2); - - assert!( - nonce_off[11] == 0x00 - && nonce_off[10] == 0xff - && nonce_off[9] == 0xfe - && nonce_off[8] == 0xfe - && nonce_off[7] == 0xfe - && nonce_off[6] == 0xfe, - "\ninitial = {:x?}\nnew = {:x?}", - nonce, - nonce_off - ); - } - } - - #[test] - fn localstore_record_serialize() { - let record = LocalRecord { - position: 0, - size: 0, - record: StoreRecord { - key: "meow".to_owned(), - meta: None, - specifier: "test".to_owned(), - r#type: crate::store::RecordType::LoginPassword, - value: vec![1, 2, 3, 4], - }, - }; - - let serialized = record.serialize(); - - println!("D: {:x?}", serialized); - - let deserialized = LocalRecord::from_block(&serialized).unwrap(); - - assert_eq!(deserialized.record, record.record); - - let reserialized = deserialized.serialize(); - - assert_eq!(serialized, reserialized); - } - - #[test] - fn index_node_serialize() { - let root = IndexNode { - node_type: IndexNodeType::Internal, - children: vec![IndexNodeEntry { - key: 1, - pointer: 258, - }], - }; - - let leaf = IndexNode { - node_type: IndexNodeType::Leaf, - children: vec![ - IndexNodeEntry { - key: 1, - pointer: 516, - }, - IndexNodeEntry { - key: 4, - pointer: 520, - }, - IndexNodeEntry { - key: 16, - pointer: 524, - }, - ], - }; - - let meta = super::LocalStoreMeta { - record_count: 3, - specifier_count: 0, - index_node_arity: 16, - data_block_size: 8192, - data_offset: 0, - }; - - let root_bytes = root.serialize(&meta); - let leaf_bytes = leaf.serialize(&meta); - - assert_eq!(IndexNodeType::from(root_bytes[0]), root.node_type); - assert_eq!(IndexNodeType::from(leaf_bytes[0]), leaf.node_type); - - assert_eq!(root_bytes[2..10], root.children[0].key.to_be_bytes()); - assert_eq!(root_bytes[10..18], root.children[0].pointer.to_be_bytes()); - assert!(root_bytes.iter().skip(18).all(|x| *x == 0u8)); - - let mut cnt = 2; - for child in leaf.children { - assert_eq!(leaf_bytes[cnt..(cnt + 8)], child.key.to_be_bytes()); - assert_eq!( - leaf_bytes[(cnt + 8)..(cnt + 16)], - child.pointer.to_be_bytes() - ); - cnt += 16; - } - assert!(root_bytes.iter().skip(cnt).all(|x| *x == 0u8)); - } -} diff --git a/src/store/local/data.rs b/src/store/local/data.rs new file mode 100644 index 0000000..06d7acf --- /dev/null +++ b/src/store/local/data.rs @@ -0,0 +1,135 @@ +use crate::store::{RecordType, StoreRecord}; + +pub(super) struct LocalRecord { + record: StoreRecord, + position: usize, + size: usize, +} + +const CRED_FLAG_METADATA: u8 = 0b0000100; + +impl LocalRecord { + fn serialize(&self) -> Vec { + let mut buf = Vec::::new(); + + buf.push(self.record.r#type as u8); + + let mut flags: u8 = 0; + + if self.record.meta.is_some() { + flags |= CRED_FLAG_METADATA; + } + buf.push(flags); + + let spec_bytes = self.record.specifier.bytes(); + let key_bytes = self.record.key.bytes(); + let value_bytes = &self.record.value; + let meta_len = if self.record.meta.is_none() { + 0 + } else { + self.record.meta.as_ref().unwrap().len() + }; + + buf.append(u32::to_be_bytes(spec_bytes.len() as u32).to_vec().as_mut()); + buf.append(u32::to_be_bytes(key_bytes.len() as u32).to_vec().as_mut()); + buf.append(u32::to_be_bytes(value_bytes.len() as u32).to_vec().as_mut()); + buf.append(u32::to_be_bytes(meta_len as u32).to_vec().as_mut()); + + buf.extend(spec_bytes); + buf.extend(key_bytes); + buf.extend(value_bytes); + + if meta_len > 0 { + buf.extend(self.record.meta.as_ref().unwrap().bytes()); + } + + buf + } + + fn from_block(bytes: &[u8]) -> Result { + let r#type = bytes[0]; + let flags = bytes[1]; + let spec_len = u32::from_be_bytes(bytes[2..6].try_into().unwrap()) as usize; + let key_len = u32::from_be_bytes(bytes[6..10].try_into().unwrap()) as usize; + let val_len = u32::from_be_bytes(bytes[10..14].try_into().unwrap()) as usize; + let meta_len = u32::from_be_bytes(bytes[14..18].try_into().unwrap()) as usize; + + if key_len < 1 { + return Err("Invalid structure: key length is 0."); + } + + if spec_len < 1 { + return Err("Invalid structure: specifier length is 0."); + } + + if val_len < 1 { + return Err("Invalid structure: value length is 0."); + } + + let key_offset: usize = 18 + spec_len; + let val_offset: usize = key_offset + key_len; + + let specifier = std::str::from_utf8(&bytes[18..(18 + spec_len)]) + .expect("Cannot deserialize the specifier."); + let key = std::str::from_utf8(&bytes[key_offset..key_offset + key_len]) + .expect("Cannot deserialize the key."); + let value = &bytes[val_offset..val_offset + val_len]; + let meta = if meta_len == 0 { + None + } else { + let meta_offset = val_offset + val_len; + Some( + std::str::from_utf8(&bytes[meta_offset..meta_offset + meta_len]) + .expect("Cannot deserialize metadata.") + .to_owned(), + ) + }; + + let record = LocalRecord { + record: StoreRecord { + specifier: specifier.to_owned(), + key: key.to_owned(), + r#type: RecordType::from(r#type), + value: value.to_vec(), + meta, + }, + position: 0, + size: val_offset + val_len + meta_len, + }; + + Ok(record) + } +} + +#[cfg(test)] +mod tests { + use super::LocalRecord; + use crate::store::StoreRecord; + + #[test] + fn localstore_record_serialize() { + let record = LocalRecord { + position: 0, + size: 0, + record: StoreRecord { + key: "meow".to_owned(), + meta: None, + specifier: "test".to_owned(), + r#type: crate::store::RecordType::LoginPassword, + value: vec![1, 2, 3, 4], + }, + }; + + let serialized = record.serialize(); + + println!("D: {:x?}", serialized); + + let deserialized = LocalRecord::from_block(&serialized).unwrap(); + + assert_eq!(deserialized.record, record.record); + + let reserialized = deserialized.serialize(); + + assert_eq!(serialized, reserialized); + } +} diff --git a/src/store/local/header.rs b/src/store/local/header.rs new file mode 100644 index 0000000..75cc115 --- /dev/null +++ b/src/store/local/header.rs @@ -0,0 +1,85 @@ +use std::{ + arch::x86_64::_mm_crc32_u32, + fs::File, + io::{BufReader, Read}, +}; + +use crate::crypto::{EncryptionType, HashType}; + +pub const MAGIC_NUM: u32 = 0x6d656f77; +pub const HEADER_SIZE: usize = 32; + +pub(super) struct LocalStoreHeader { + pub magic_h: u32, + pub last_access: u32, + pub last_write: u32, + pub enc_type: EncryptionType, + pub hash_type: HashType, + pub enc_sup_block_size: u16, + pub enc_key_size: u16, + pub encrypted_block_offset: u32, + pub chksum_crc: u32, +} + +impl LocalStoreHeader { + pub fn get_checksum(&self) -> u32 { + let mut crc: u32 = 0xffffffff; + unsafe { + crc = _mm_crc32_u32(crc, self.last_access); + crc = _mm_crc32_u32(crc, self.last_write); + crc = _mm_crc32_u32(crc, self.enc_type as u32); + crc = _mm_crc32_u32(crc, self.hash_type as u32); + crc = _mm_crc32_u32(crc, self.enc_sup_block_size as u32); + crc = _mm_crc32_u32(crc, self.enc_key_size as u32); + crc = _mm_crc32_u32(crc, self.encrypted_block_offset); + } + crc + } + + pub fn is_checksum_valid(&self) -> bool { + let chk = self.get_checksum(); + chk == self.chksum_crc + } +} + +pub(super) fn try_deserialize_header( + file: &mut BufReader, +) -> Result { + let mut header: LocalStoreHeader = LocalStoreHeader { + magic_h: 0, + last_access: 0, + last_write: 0, + enc_type: EncryptionType::Invalid, + hash_type: HashType::Invalid, + enc_sup_block_size: 0, + enc_key_size: 0, + chksum_crc: 0, + encrypted_block_offset: 32, + }; + + let mut buf: [u8; HEADER_SIZE] = [0; HEADER_SIZE]; + + if file.read_exact(&mut buf).is_err() { + return Err("Read error."); + } + + header.magic_h = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + if header.magic_h != MAGIC_NUM { + return Err("Invalid header magic number."); + } + + header.last_access = u32::from_be_bytes(buf[4..8].try_into().unwrap()); + header.last_write = u32::from_be_bytes(buf[8..12].try_into().unwrap()); + header.enc_type = EncryptionType::from(buf[12]); + header.hash_type = HashType::from(buf[13]); + header.enc_sup_block_size = u16::from_be_bytes(buf[14..16].try_into().unwrap()); + header.enc_key_size = u16::from_be_bytes(buf[16..18].try_into().unwrap()); + header.encrypted_block_offset = u32::from_be_bytes(buf[18..22].try_into().unwrap()); + header.chksum_crc = u32::from_be_bytes(buf[22..26].try_into().unwrap()); + + if !header.is_checksum_valid() { + return Err("Invalid checksum."); + } + + Ok(header) +} diff --git a/src/store/local/index.rs b/src/store/local/index.rs new file mode 100644 index 0000000..afb7162 --- /dev/null +++ b/src/store/local/index.rs @@ -0,0 +1,258 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + io::{Read, Seek, SeekFrom}, +}; + +use crate::data::{DataPage, DataPager}; + +use super::{LocalStore, LocalStoreMeta}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub(super) enum IndexNodeType { + Internal = 1, + Leaf = 2, + Invalid = 0, +} + +impl From for IndexNodeType { + fn from(value: u8) -> Self { + match value { + 0 => IndexNodeType::Invalid, + 1 => IndexNodeType::Internal, + 2 => IndexNodeType::Leaf, + _ => panic!("Invalid value {}", value), + } + } +} + +pub(super) struct IndexNodeEntry { + key: u64, + pointer: u64, +} + +pub(super) struct IndexNode { + node_type: IndexNodeType, + children: Vec, +} + +impl IndexNode { + pub fn new() -> Self { + IndexNode { + node_type: IndexNodeType::Leaf, + children: Vec::new(), + } + } + + pub fn children(&self) -> &Vec { + &self.children + } + + pub fn len(&self) -> usize { + self.children.len() + } + + pub fn serialize(&self, meta: &LocalStoreMeta) -> Vec { + let mut buf: Vec = vec![0; 16 * meta.index_node_arity as usize + 2]; + + let mut cnt = 2; + for child in self.children() { + buf[cnt..(cnt + 8)].copy_from_slice(&child.key.to_be_bytes()); + buf[(cnt + 8)..(cnt + 16)].copy_from_slice(&child.pointer.to_be_bytes()); + cnt += 16; + } + + buf[0] = self.node_type as u8; + buf + } +} + +pub fn hash_key(key: &String) -> u64 { + let mut hasher = DefaultHasher::new(); + key.hash(&mut hasher); + hasher.finish() +} + +pub(super) fn read_node(meta: &LocalStoreMeta, data: &mut DataPage) -> IndexNode { + let node_type = { + let mut buf: [u8; 2] = [0; 2]; + data.read_exact(&mut buf).expect("Cannot read node type."); + IndexNodeType::from(buf[0]) + }; + + let mut children = Vec::with_capacity(meta.index_node_arity as usize); + for _ in 0..meta.index_node_arity { + let mut buf: [u8; 16] = [0; 16]; + + data.read_exact(&mut buf) + .expect("Cannot read child node content."); + + let key = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + + if key == 0 { + break; + } + + let pointer = u64::from_be_bytes(buf[0..8].try_into().unwrap()); + + let entry = IndexNodeEntry { key, pointer }; + + children.push(entry); + } + + let node = IndexNode { + node_type, + children, + }; + + node +} + +impl LocalStore { + pub(super) fn btree_find_key(&mut self, key: &String) -> (Option, Vec) { + let hash = hash_key(key); + + let mut traversal_history = Vec::new(); + + let block = self.io.get_page(0xa0).expect("IO Error."); + + loop { + traversal_history.push(block.stream_position().unwrap()); + let node = read_node(&self.meta, block); + + if node.node_type == IndexNodeType::Invalid { + panic!( + "Invalid node detected: {}", + block.stream_position().unwrap() + ); + } + + if node.node_type == IndexNodeType::Leaf { + for child in node.children() { + if child.key == hash { + return (Some(child.pointer), traversal_history); + } + } + return (None, traversal_history); + } + + for child in node.children { + if child.key < hash { + continue; + } + + block.seek(SeekFrom::Start(child.pointer)).unwrap(); + } + } + } + + pub(super) fn btree_insert_key( + &mut self, + key: &String, + pointer: u64, + ) -> Result, String> { + let (result, traversal_history) = self.btree_find_key(key); + + if result.is_some() { + return Err("Value already exists".to_string()); + } + + let hash = hash_key(key); + + let mut block = &mut self.io.get_page(0xa0).expect("IO Error."); + + if traversal_history.is_empty() { + return Err("No nodes found...".to_string()); + } + + let position = *traversal_history.last().unwrap(); + + block.seek(SeekFrom::Start(position)).unwrap(); + + let mut node = read_node(&self.meta, &mut block); + if node.len() == self.meta.index_node_arity as usize { + //TODO Reorganize nodes + todo!() + } + + let entry = IndexNodeEntry { key: hash, pointer }; + + node.children.push(entry); + + block.seek(SeekFrom::Start(position)).unwrap(); + node.serialize(&self.meta); + + //todo serialize + + todo!() + } + + pub(super) fn btree_delete_key(&self, key: &String) { + let _hash = hash_key(key); + + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::{IndexNode, IndexNodeEntry, IndexNodeType}; + + #[test] + fn index_node_serialize() { + let root = IndexNode { + node_type: IndexNodeType::Internal, + children: vec![IndexNodeEntry { + key: 1, + pointer: 258, + }], + }; + + let leaf = IndexNode { + node_type: IndexNodeType::Leaf, + children: vec![ + IndexNodeEntry { + key: 1, + pointer: 516, + }, + IndexNodeEntry { + key: 4, + pointer: 520, + }, + IndexNodeEntry { + key: 16, + pointer: 524, + }, + ], + }; + + let meta = super::LocalStoreMeta { + record_count: 3, + specifier_count: 0, + index_node_arity: 16, + data_block_size: 8192, + data_offset: 0, + }; + + let root_bytes = root.serialize(&meta); + let leaf_bytes = leaf.serialize(&meta); + + assert_eq!(IndexNodeType::from(root_bytes[0]), root.node_type); + assert_eq!(IndexNodeType::from(leaf_bytes[0]), leaf.node_type); + + assert_eq!(root_bytes[2..10], root.children[0].key.to_be_bytes()); + assert_eq!(root_bytes[10..18], root.children[0].pointer.to_be_bytes()); + assert!(root_bytes.iter().skip(18).all(|x| *x == 0u8)); + + let mut cnt = 2; + for child in leaf.children { + assert_eq!(leaf_bytes[cnt..(cnt + 8)], child.key.to_be_bytes()); + assert_eq!( + leaf_bytes[(cnt + 8)..(cnt + 16)], + child.pointer.to_be_bytes() + ); + cnt += 16; + } + assert!(root_bytes.iter().skip(cnt).all(|x| *x == 0u8)); + } +} diff --git a/src/store/local/io.rs b/src/store/local/io.rs new file mode 100644 index 0000000..740992a --- /dev/null +++ b/src/store/local/io.rs @@ -0,0 +1,62 @@ +use std::{ + collections::HashMap, + fs::File, + io::{BufReader, Read, Seek, SeekFrom}, +}; + +use crate::{ + crypto::EncryptionContext, + data::{DataPage, DataPager}, +}; + +use super::LocalStoreMeta; + +pub struct DataAccessor { + path: String, + cache: HashMap, + dirty_pages: Vec, + enc: EncryptionContext, + block_size: usize, +} + +impl DataPager for DataAccessor { + fn get_page(&mut self, offset: usize) -> Result<&mut DataPage, String> { + if !self.cache.contains_key(&offset) { + let data = self.read_page_from_file(offset); + match data { + Ok(x) => self.cache.insert(offset, x), + Err(x) => return Err(x), + }; + }; + + Ok(self.cache.get_mut(&offset).unwrap()) + } + + fn update_page(&mut self, page: crate::data::WritableDataPage) -> Result { + self.dirty_pages + .push(DataPage::load(page.offset(), page.data().to_vec())); + return Ok(1); + } +} + +impl DataAccessor { + fn read_page_from_file(&mut self, offset: usize) -> Result { + let mut file = BufReader::new(File::open(&self.path).unwrap()); + file.seek(SeekFrom::Start(offset as u64)).unwrap(); + + let mut buf = vec![0; self.block_size + 16]; + file.read_exact(&mut buf).unwrap(); + + self.enc.decrypt_block(&buf, offset) + } + + pub(super) fn create(path: &String, enc: EncryptionContext, meta: &LocalStoreMeta) -> Self { + DataAccessor { + enc, + path: path.clone(), + cache: HashMap::new(), + dirty_pages: Vec::new(), + block_size: meta.data_block_size as usize, + } + } +} diff --git a/src/store/local/mod.rs b/src/store/local/mod.rs new file mode 100644 index 0000000..eb7826c --- /dev/null +++ b/src/store/local/mod.rs @@ -0,0 +1,333 @@ +mod data; +mod header; +mod index; +mod io; + +use std::{ + collections::HashMap, + fs::File, + io::{BufReader, Read, Write}, + path::Path, +}; + +use password_hash::SaltString; + +use crate::crypto::EncryptionContext; + +use self::{ + data::LocalRecord, + header::{try_deserialize_header, LocalStoreHeader, MAGIC_NUM}, + index::IndexNode, + io::DataAccessor, +}; + +use super::{RecordType, Store, StoreRecord}; + +pub struct LocalStore { + path: String, + header: LocalStoreHeader, + meta: LocalStoreMeta, + cache: HashMap, + io: DataAccessor, +} + +/* + Store binary format: + Header: 0x00 (first 96 bytes): + 0x00 - magic number + 0x04 - last access timestamp + 0x08 - last write timestamp + 0x0c - encryption type byte + 0x0d - hash type byte + 0x0e - superblock size + 0x10 - cipher key size + 0x12 - encrypted block offset + 0x16 - header CRC-32 checksum + 0x1a - padding (6 bytes) + Enc details: 48 bytes + 0x20 - salt + 0x40 - master IV + 0x4c - 3 byte padding with 0 + 0x4f - salt size +---Encrypted section starts here--- +--Block 0 + Superblock: + Hash: + 0x50 - master password hash + Metadata: + 0x70 - record count + 0x74 - specifier count + 0x88 - padding with 0 + Auth tag: 0x90 +--Block 1 + Index: + Root node: + 0xa0 - root node or invalid node if empty db +*/ + +//Proposal: Initialize IV to 0? + +struct LocalStoreMeta { + record_count: u32, + specifier_count: u32, + index_node_arity: u16, + data_block_size: u32, + data_offset: u64, +} + +fn try_deserialize_superblock( + file: &mut BufReader, + enc: &EncryptionContext, + header: &LocalStoreHeader, +) -> Result { + unsafe { + let data = match enc.decrypt_block_from_file(header.enc_sup_block_size as usize, 0, file) { + Ok(x) => x, + Err(_) => return Err("Possibly corrupted store superblock."), + }; + + if data.buffer()[0..enc.get_key().len()].ne(enc.get_key()) { + return Err("Key mismatch."); + } + + let meta = deserialize_store_meta(&&data.buffer()[32..]); + + Ok(meta) + } +} + +fn serialize_header(header: &LocalStoreHeader) -> Vec { + let mut buf = Vec::::new(); + buf.append(u32::to_be_bytes(MAGIC_NUM).to_vec().as_mut()); + buf.append(u32::to_be_bytes(header.last_access).to_vec().as_mut()); + buf.append(u32::to_be_bytes(header.last_write).to_vec().as_mut()); + buf.push(header.enc_type as u8); + buf.push(header.hash_type as u8); + buf.append( + u16::to_be_bytes(header.enc_sup_block_size) + .to_vec() + .as_mut(), + ); + buf.append(u16::to_be_bytes(header.enc_key_size).to_vec().as_mut()); + buf.append( + u32::to_be_bytes(header.encrypted_block_offset) + .to_vec() + .as_mut(), + ); + buf.append(u32::to_be_bytes(header.chksum_crc).to_vec().as_mut()); + + let padding_len = 32 - buf.len(); + let mut pad: Vec = vec![0; padding_len]; + buf.append(&mut pad); + + return buf; +} + +fn try_create_store(path: &String, passphrase: &Vec) -> bool { + let timestamp: u32 = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as u32; + + let enc = EncryptionContext::default(passphrase); + + let mut superblock_buf: Vec = Vec::new(); + + unsafe { + superblock_buf.extend_from_slice(&enc.get_key()); + } + + if superblock_buf.len() != 32 { + superblock_buf.append([b'\0'].repeat(32 - superblock_buf.len()).as_mut()); + } + + let meta = LocalStoreMeta { + record_count: 0, + specifier_count: 0, + index_node_arity: 16, + data_block_size: 8092, + data_offset: 0, + }; + + superblock_buf.append(&mut serialize_store_meta(&meta)); + superblock_buf.extend_from_slice(&[b'\0'].repeat(64 - superblock_buf.len())); + + let enc_buf = enc.encrypt_block(&superblock_buf, 0).unwrap(); + + let mut header: LocalStoreHeader = LocalStoreHeader { + magic_h: MAGIC_NUM, + last_access: timestamp, + last_write: timestamp, + enc_type: enc.encryption_type(), + enc_sup_block_size: (enc_buf.len() - 16) as u16, + enc_key_size: 256, + hash_type: enc.hash_type(), + encrypted_block_offset: 32, + chksum_crc: 0, + }; + + header.chksum_crc = header.get_checksum(); + + let mut index_block: Vec = vec![0; meta.data_block_size as usize]; + let root_node_buf = IndexNode::new().serialize(&meta); + + index_block[0..root_node_buf.len()].copy_from_slice(&root_node_buf); + + let mut buf = serialize_header(&header); + + let mut salt_buf: [u8; 32] = [0; 32]; + enc.salt().b64_decode(&mut salt_buf).unwrap(); + if salt_buf.len() > 32 { + return false; + } + buf.extend_from_slice(&salt_buf); + + let iv = &enc.root_iv(); + if iv.len() != 12 { + return false; + } + buf.extend_from_slice(iv); + buf.extend_from_slice(&[b'\0'].repeat(4)); + + buf.extend_from_slice(&[b'\0'].repeat(0x50 - buf.len())); + buf[0x4f] = 16; + + buf.extend_from_slice(&enc_buf.buffer()); + buf.extend_from_slice(&enc.encrypt_block(&index_block, 0xa0).unwrap().buffer()); + + match std::fs::File::create(path).unwrap().write(&buf) { + Ok(x) => return x == buf.len(), + Err(_) => return false, + } +} + +fn get_enc_salt_iv(file: &mut BufReader) -> Result<(SaltString, Vec), &'static str> { + let mut buf: [u8; 48] = [0; 48]; + + file.read_exact(&mut buf).unwrap(); + + let salt_len = buf[47] as usize; + + let salt = match SaltString::b64_encode(&buf[0..salt_len]) { + Err(_) => return Err("Invalid salt."), + Ok(x) => x, + }; + + let iv = buf[32..44].to_owned(); + Ok((salt, iv)) +} + +fn deserialize_store_meta(data: &[u8]) -> LocalStoreMeta { + let records = u32::from_be_bytes(data[0..4].try_into().unwrap()); + let specifiers = u32::from_be_bytes(data[4..8].try_into().unwrap()); + let index_node_arity = u16::from_be_bytes(data[8..10].try_into().unwrap()); + let data_block_size = u32::from_be_bytes(data[10..14].try_into().unwrap()); + let data_offset = u64::from_be_bytes(data[14..22].try_into().unwrap()); + + LocalStoreMeta { + record_count: records, + specifier_count: specifiers, + index_node_arity, + data_block_size, + data_offset, + } +} + +fn serialize_store_meta(meta: &LocalStoreMeta) -> Vec { + let mut buf: Vec = Vec::with_capacity(32); + + buf.append(u32::to_be_bytes(meta.record_count).to_vec().as_mut()); + buf.append(u32::to_be_bytes(meta.specifier_count).to_vec().as_mut()); + buf.append(u16::to_be_bytes(meta.index_node_arity).to_vec().as_mut()); + buf.append(u32::to_be_bytes(meta.data_block_size).to_vec().as_mut()); + buf.append(u64::to_be_bytes(meta.data_offset).to_vec().as_mut()); + buf.append([b'\0'].repeat(32 - buf.len()).as_mut()); + + buf +} + +pub fn load(path: String, passphrase: Vec) -> Result { + let path_p = Path::new(&path); + + if !path_p.exists() { + if !try_create_store(&path, &passphrase) { + return Err("Cannot create store."); + } + } else if !path_p.is_file() { + return Err("Invalid path."); + } + + let file = match File::open(&path_p) { + Ok(x) => x, + Err(_) => return Err("Cannot open store."), + }; + + let mut reader = BufReader::new(file); + + let header = match try_deserialize_header(&mut reader) { + Err(x) => return Err(x), + Ok(meta) => meta, + }; + + let (salt, iv) = match get_enc_salt_iv(&mut reader) { + Ok(s) => s, + Err(x) => return Err(x), + }; + + let enc_ctx = + EncryptionContext::from_settings(&passphrase, header.enc_type, header.hash_type, salt, iv); + + let meta = match try_deserialize_superblock(&mut reader, &enc_ctx, &header) { + Ok(d) => d, + Err(x) => return Err(x), + }; + + let store = LocalStore { + io: DataAccessor::create(&path, enc_ctx, &meta), + path, + header, + meta, + cache: HashMap::new(), + }; + + return Ok(store); +} + +impl Store for LocalStore { + fn get_creds(&mut self, specifier: &String, key: &String) -> Option { + match self.btree_find_key(&key) { + (Some(x), _) => { + println!("Found: {}", x); + todo!() + } + (None, _) => return None, + } + } + + fn get_creds_by_specifier(&mut self, specifier: &String) -> Vec { + todo!() + } + + fn store_creds( + &mut self, + specifier: &String, + key: &String, + value: &Vec, + r#type: RecordType, + meta: Option, + ) { + todo!() + } +} + +#[cfg(test)] +mod tests { + use std::mem::size_of; + + use crate::store::local::{header::HEADER_SIZE, LocalStoreHeader}; + + #[test] + fn header_size_const_greater_than_realsize() { + let c = HEADER_SIZE; + let r = size_of::(); + + assert!(c > r, "const = {}, real = {}", c, r); + } +} diff --git a/src/store/mod.rs b/src/store/mod.rs index 110a838..a01ed79 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -1,5 +1,3 @@ -use std::io::{Read, Seek, SeekFrom}; - pub(crate) mod local; pub(crate) mod server; @@ -39,10 +37,10 @@ impl From for RecordType { } pub trait Store { - fn get_creds(&self, specifier: &String, key: &String) -> Option; - fn get_creds_by_specifier(&self, specifier: &String) -> Vec; + fn get_creds(&mut self, specifier: &String, key: &String) -> Option; + fn get_creds_by_specifier(&mut self, specifier: &String) -> Vec; fn store_creds( - &self, + &mut self, specifier: &String, key: &String, value: &Vec, @@ -50,155 +48,3 @@ pub trait Store { meta: Option, ); } - -#[derive(Clone, Copy, PartialEq, PartialOrd)] -pub enum EncryptionType { - Invalid = 0, - AesGcm = 1, - Chacha20Poly1305 = 2, -} - -impl From for EncryptionType { - fn from(n: u8) -> Self { - match n { - 0 => EncryptionType::Invalid, - 1 => EncryptionType::AesGcm, - 2 => EncryptionType::Chacha20Poly1305, - _ => panic!("Invalid value '{}'.", n), - } - } -} - -#[derive(Clone, Copy, PartialEq, PartialOrd)] -pub enum HashType { - Invalid = 0, - Argon2 = 1, - Bcrypt = 2, - Pbkdf2 = 4, -} - -impl From for HashType { - fn from(n: u8) -> Self { - match n { - 0 => HashType::Invalid, - 1 => HashType::Argon2, - 2 => HashType::Bcrypt, - 4 => HashType::Pbkdf2, - _ => panic!("Invalid value '{}'.", n), - } - } -} - -struct EncryptionStream { - data: Vec, - pos: usize, -} - -impl Read for EncryptionStream { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let range = self.pos..(self.pos + buf.len()).clamp(0, self.data.len() - buf.len()); - let size = range.len(); - buf.copy_from_slice(&self.data[range]); - self.pos += size; - Ok(size) - } -} - -impl Seek for EncryptionStream { - fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { - let cur = self.pos; - - let newpos = match pos { - SeekFrom::Start(x) => { - if x > self.data.len() as u64 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Seek position out of buffer bounds.", - )); - } - x - } - SeekFrom::End(x) => { - if x > self.data.len() as i64 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Seek position out of buffer bounds.", - )); - } - - (self.data.len() as i64 - x) as u64 - } - SeekFrom::Current(x) => { - let new = self.pos as i64 + x; - - if new < 0 || new > self.data.len() as i64 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Seek position out of buffer bounds.", - )); - } - - new as u64 - } - }; - - self.pos = newpos as usize; - - Ok(cur as u64) - } -} - -impl EncryptionStream { - pub fn len(&self) -> usize { - self.data.len() - } -} - -#[cfg(test)] -mod tests { - use std::io::{Read, Seek, SeekFrom}; - - use super::EncryptionStream; - - fn make_enc_stream() -> EncryptionStream { - EncryptionStream { - data: vec![0; 128], - pos: 0, - } - } - - #[test] - fn encryption_stream_seek() { - let mut stream = make_enc_stream(); - - { - { - let pos = stream.seek(SeekFrom::Start(64)); - assert!(pos.is_ok()); - assert_eq!(pos.unwrap(), 0); - } - - { - let pos = stream.seek(SeekFrom::Start(128)); - assert!(pos.is_ok()); - assert_eq!(pos.unwrap(), 64); - } - - { - let pos = stream.seek(SeekFrom::Start(256)); - assert!(pos.is_err()); - } - } - } - - #[test] - fn encryption_stream_read() { - let mut stream = make_enc_stream(); - let mut buf: [u8; 8] = [1; 8]; - - assert!(stream.read_exact(&mut buf).is_ok()); - - assert_eq!(&buf, &[0; 8]); - assert_eq!(stream.stream_position().unwrap(), 8); - } -} diff --git a/src/store/server.rs b/src/store/server.rs index 228f446..486dc6b 100644 --- a/src/store/server.rs +++ b/src/store/server.rs @@ -9,7 +9,7 @@ pub struct ServerStore { pub fn load(address: String) -> Result { let url = match reqwest::Url::parse(&address) { Ok(x) => x, - Err(x) => return Err("Invalid address."), + Err(_) => return Err("Invalid address."), }; let mut headers = reqwest::header::HeaderMap::new(); @@ -41,16 +41,16 @@ impl ServerStore { } impl Store for ServerStore { - fn get_creds(&self, specifier: &String, key: &String) -> Option { + fn get_creds(&mut self, specifier: &String, key: &String) -> Option { todo!() } - fn get_creds_by_specifier(&self, specifier: &String) -> Vec { + fn get_creds_by_specifier(&mut self, specifier: &String) -> Vec { todo!() } fn store_creds( - &self, + &mut self, specifier: &String, key: &String, value: &Vec,