Refactore local store. Create data accessor with shared buffers.

This commit is contained in:
femsci 2023-03-08 12:37:36 +01:00
parent ab056bfe73
commit 53e5bd81af
Signed by: femsci
GPG key ID: 08F7911F0E650C67
12 changed files with 1335 additions and 1069 deletions

View file

@ -1,6 +1,6 @@
use std::{fs::File, io::Read, path::Path}; use std::{fs::File, io::Read, path::Path};
use crate::store::HashType; use crate::crypto::HashType;
pub struct Preferences { pub struct Preferences {
_store_type: String, _store_type: String,
@ -11,7 +11,7 @@ pub struct Preferences {
pub fn try_load_preferences() -> Option<Preferences> { pub fn try_load_preferences() -> Option<Preferences> {
//Alert: develop a better home dir acquisition method //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() { if !file.exists() {
file = Path::new("/etc/nyanpass.conf"); file = Path::new("/etc/nyanpass.conf");
} }

274
src/crypto/mod.rs Normal file
View file

@ -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<u8> 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<u8> 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<u8>,
salt: SaltString,
iv_root: Vec<u8>,
}
impl EncryptionContext {
pub fn decrypt_block(&self, ciphertext: &Vec<u8>, offset: usize) -> Result<DataPage, String> {
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<File>,
) -> Result<DataPage, String> {
let mut buf: Vec<u8> = 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<u8>) -> 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<u8>,
offset: usize,
) -> Result<DataPage, &'static str> {
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<u8>,
enc_type: EncryptionType,
hash_type: HashType,
salt: SaltString,
iv_root: Vec<u8>,
) -> 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<u8> {
&self.key
}
pub fn hash_type(&self) -> HashType {
self.hash_type
}
pub fn offset_iv_by(&self, offset: usize) -> Vec<u8> {
nonce_offset(&self.iv_root, offset)
}
pub fn root_iv(&self) -> &Vec<u8> {
&self.iv_root
}
pub fn salt(&self) -> &SaltString {
&self.salt
}
}
pub fn nonce_offset(nonce_root: &Vec<u8>, offset: usize) -> Vec<u8> {
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<u8>,
salt: &SaltString,
hash_type: HashType,
) -> Result<Vec<u8>, &'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<u8> = 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<u8> = 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<u8> = 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
);
}
}

176
src/data/mod.rs Normal file
View file

@ -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<u64, ()>;
}
#[derive(PartialEq, Eq, Hash)]
pub struct DataPage {
offset: usize,
pos: usize,
data: Vec<u8>,
}
impl Read for DataPage {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
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<u64> {
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<u8>) -> 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<usize> {
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);
}
}

View file

@ -2,6 +2,8 @@ use config::try_load_preferences;
use store::Store; use store::Store;
mod config; mod config;
mod crypto;
mod data;
mod store; mod store;
fn main() { fn main() {
@ -13,7 +15,7 @@ fn main() {
let _preferences = try_load_preferences(); let _preferences = try_load_preferences();
let store = match store::local::load( let mut store = match store::local::load(
"/tmp/store.db".to_string(), "/tmp/store.db".to_string(),
"meowmeow".as_bytes().to_owned(), "meowmeow".as_bytes().to_owned(),
) { ) {

View file

@ -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<String, LocalRecord>,
}
/*
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<u8>,
salt: SaltString,
iv_root: Vec<u8>,
}
fn try_deserialize_header(file: &mut BufReader<File>) -> Result<LocalStoreHeader, &'static str> {
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<File>,
enc: &EncryptionContext,
header: &LocalStoreHeader,
) -> Result<LocalStoreMeta, &'static str> {
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<u8> {
let mut buf = Vec::<u8>::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<u8> = vec![0; padding_len];
buf.append(&mut pad);
return buf;
}
fn try_create_store(path: &String, passphrase: &Vec<u8>) -> 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<u8> = 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<u8> = 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<File>) -> Result<(SaltString, Vec<u8>), &'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<u8>,
salt: &SaltString,
hash_type: HashType,
) -> Result<Vec<u8>, &'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<u8> = 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<u8> {
let mut buf: Vec<u8> = 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<u8>) -> Result<LocalStore, &'static str> {
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<u8> {
let mut buf = Vec::<u8>::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<LocalRecord, &'static str> {
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<StoreRecord> {
match self.btree_find_key(&key) {
(Some(x), _) => {
println!("Found: {}", x);
todo!()
}
(None, _) => return None,
}
}
fn get_creds_by_specifier(&self, specifier: &String) -> Vec<StoreRecord> {
todo!()
}
fn store_creds(
&self,
specifier: &String,
key: &String,
value: &Vec<u8>,
r#type: RecordType,
meta: Option<String>,
) {
todo!()
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
enum IndexNodeType {
Internal = 1,
Leaf = 2,
Invalid = 0,
}
impl From<u8> 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<IndexNodeEntry>,
}
impl IndexNode {
fn children(&self) -> &Vec<IndexNodeEntry> {
&self.children
}
fn len(&self) -> usize {
self.children.len()
}
fn serialize(&self, meta: &LocalStoreMeta) -> Vec<u8> {
let mut buf: Vec<u8> = 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<u64>, Vec<u64>) {
let mut file: BufReader<File> =
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<u8>, offset: usize) -> Vec<u8> {
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<u8>,
offset: usize,
) -> Result<EncryptionStream, String> {
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<u8>,
offset: usize,
) -> Result<EncryptionStream, &'static str> {
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<File>,
) -> Result<EncryptionStream, String> {
let mut buf: Vec<u8> = 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::<LocalStoreHeader>();
assert!(c > r, "const = {}, real = {}", c, r);
}
#[test]
fn nonce_offset_increment() {
{
let nonce: Vec<u8> = 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<u8> = 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));
}
}

135
src/store/local/data.rs Normal file
View file

@ -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<u8> {
let mut buf = Vec::<u8>::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<LocalRecord, &'static str> {
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);
}
}

85
src/store/local/header.rs Normal file
View file

@ -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<File>,
) -> Result<LocalStoreHeader, &'static str> {
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)
}

258
src/store/local/index.rs Normal file
View file

@ -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<u8> 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<IndexNodeEntry>,
}
impl IndexNode {
pub fn new() -> Self {
IndexNode {
node_type: IndexNodeType::Leaf,
children: Vec::new(),
}
}
pub fn children(&self) -> &Vec<IndexNodeEntry> {
&self.children
}
pub fn len(&self) -> usize {
self.children.len()
}
pub fn serialize(&self, meta: &LocalStoreMeta) -> Vec<u8> {
let mut buf: Vec<u8> = 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<u64>, Vec<u64>) {
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<Vec<u64>, 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));
}
}

62
src/store/local/io.rs Normal file
View file

@ -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<usize, DataPage>,
dirty_pages: Vec<DataPage>,
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<u64, ()> {
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<DataPage, String> {
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,
}
}
}

333
src/store/local/mod.rs Normal file
View file

@ -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<String, LocalRecord>,
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<File>,
enc: &EncryptionContext,
header: &LocalStoreHeader,
) -> Result<LocalStoreMeta, &'static str> {
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<u8> {
let mut buf = Vec::<u8>::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<u8> = vec![0; padding_len];
buf.append(&mut pad);
return buf;
}
fn try_create_store(path: &String, passphrase: &Vec<u8>) -> bool {
let timestamp: u32 = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as u32;
let enc = EncryptionContext::default(passphrase);
let mut superblock_buf: Vec<u8> = 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<u8> = 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<File>) -> Result<(SaltString, Vec<u8>), &'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<u8> {
let mut buf: Vec<u8> = 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<u8>) -> Result<LocalStore, &'static str> {
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<StoreRecord> {
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<StoreRecord> {
todo!()
}
fn store_creds(
&mut self,
specifier: &String,
key: &String,
value: &Vec<u8>,
r#type: RecordType,
meta: Option<String>,
) {
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::<LocalStoreHeader>();
assert!(c > r, "const = {}, real = {}", c, r);
}
}

View file

@ -1,5 +1,3 @@
use std::io::{Read, Seek, SeekFrom};
pub(crate) mod local; pub(crate) mod local;
pub(crate) mod server; pub(crate) mod server;
@ -39,10 +37,10 @@ impl From<u8> for RecordType {
} }
pub trait Store { pub trait Store {
fn get_creds(&self, specifier: &String, key: &String) -> Option<StoreRecord>; fn get_creds(&mut self, specifier: &String, key: &String) -> Option<StoreRecord>;
fn get_creds_by_specifier(&self, specifier: &String) -> Vec<StoreRecord>; fn get_creds_by_specifier(&mut self, specifier: &String) -> Vec<StoreRecord>;
fn store_creds( fn store_creds(
&self, &mut self,
specifier: &String, specifier: &String,
key: &String, key: &String,
value: &Vec<u8>, value: &Vec<u8>,
@ -50,155 +48,3 @@ pub trait Store {
meta: Option<String>, meta: Option<String>,
); );
} }
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub enum EncryptionType {
Invalid = 0,
AesGcm = 1,
Chacha20Poly1305 = 2,
}
impl From<u8> 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<u8> 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<u8>,
pos: usize,
}
impl Read for EncryptionStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
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<u64> {
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);
}
}

View file

@ -9,7 +9,7 @@ pub struct ServerStore {
pub fn load(address: String) -> Result<ServerStore, &'static str> { pub fn load(address: String) -> Result<ServerStore, &'static str> {
let url = match reqwest::Url::parse(&address) { let url = match reqwest::Url::parse(&address) {
Ok(x) => x, Ok(x) => x,
Err(x) => return Err("Invalid address."), Err(_) => return Err("Invalid address."),
}; };
let mut headers = reqwest::header::HeaderMap::new(); let mut headers = reqwest::header::HeaderMap::new();
@ -41,16 +41,16 @@ impl ServerStore {
} }
impl Store for ServerStore { impl Store for ServerStore {
fn get_creds(&self, specifier: &String, key: &String) -> Option<StoreRecord> { fn get_creds(&mut self, specifier: &String, key: &String) -> Option<StoreRecord> {
todo!() todo!()
} }
fn get_creds_by_specifier(&self, specifier: &String) -> Vec<StoreRecord> { fn get_creds_by_specifier(&mut self, specifier: &String) -> Vec<StoreRecord> {
todo!() todo!()
} }
fn store_creds( fn store_creds(
&self, &mut self,
specifier: &String, specifier: &String,
key: &String, key: &String,
value: &Vec<u8>, value: &Vec<u8>,