Header info. Basic AES-GCM. Stored hash in superblock.
This commit is contained in:
parent
fab222b19a
commit
821af01c8f
8 changed files with 2082 additions and 4 deletions
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Cargo launch",
|
||||||
|
"cargo": {
|
||||||
|
"args": ["build"]
|
||||||
|
},
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1406
Cargo.lock
generated
1406
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -3,6 +3,16 @@ name = "nyanpass"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aes-gcm = "0.10.1"
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
|
sha2 = "0.10.6"
|
||||||
|
sha3 = "0.10.6"
|
||||||
|
argon2 = "0.4.1"
|
||||||
|
bcrypt = "0.13.0"
|
||||||
|
pbkdf2 = "0.10"
|
||||||
|
password-hash = "0.4.2"
|
||||||
|
rand_core = { version = "0.6", features = ["std"] }
|
||||||
|
passwords = "3.1.12"
|
||||||
|
reqwest = "0.11.13"
|
||||||
|
|
||||||
|
|
32
src/config.rs
Normal file
32
src/config.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use std::{fs::File, io::Read, path::Path};
|
||||||
|
|
||||||
|
pub struct Preferences {
|
||||||
|
_store_path: String,
|
||||||
|
_pref_hash_algo: String,
|
||||||
|
_pref_enc_algo: String,
|
||||||
|
_pref_sign_algo: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_load_preferences() -> Option<Preferences> {
|
||||||
|
//Alert: develop a better home dir acquisition method
|
||||||
|
let mut file = Path::new("/home/nya/.nyanpass.conf");
|
||||||
|
if !file.exists() {
|
||||||
|
file = Path::new("/etc/nyanpass.conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = File::open(file);
|
||||||
|
|
||||||
|
if file.is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = file.unwrap();
|
||||||
|
|
||||||
|
let mut buf: String = String::new();
|
||||||
|
if file.read_to_string(&mut buf).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", buf);
|
||||||
|
return None;
|
||||||
|
}
|
28
src/main.rs
28
src/main.rs
|
@ -1,4 +1,28 @@
|
||||||
|
use config::try_load_preferences;
|
||||||
|
use store::Store;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod store;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = std::env::args();
|
let args = std::env::args().collect::<Vec<String>>();
|
||||||
|
|
||||||
|
for _arg in args {
|
||||||
|
//options
|
||||||
|
}
|
||||||
|
|
||||||
|
try_load_preferences();
|
||||||
|
|
||||||
|
let store = match store::local::load(
|
||||||
|
"/tmp/store.db".to_string(),
|
||||||
|
"meowmeow".as_bytes().to_owned(),
|
||||||
|
) {
|
||||||
|
Err(x) => {
|
||||||
|
println!("Error: {}", x);
|
||||||
|
std::process::exit(-1);
|
||||||
|
}
|
||||||
|
Ok(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
store.get_creds_by_key("meow".to_string());
|
||||||
}
|
}
|
||||||
|
|
522
src/store/local.rs
Normal file
522
src/store/local.rs
Normal file
|
@ -0,0 +1,522 @@
|
||||||
|
use std::{
|
||||||
|
arch::x86_64::_mm_crc32_u32,
|
||||||
|
fs::File,
|
||||||
|
io::{BufReader, Read, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
|
||||||
|
use argon2::PasswordHasher;
|
||||||
|
use password_hash::SaltString;
|
||||||
|
|
||||||
|
use super::{Store, StoreRecord};
|
||||||
|
|
||||||
|
pub struct LocalStore {
|
||||||
|
path: String,
|
||||||
|
header: LocalStoreHeader,
|
||||||
|
enc_ctx: EncryptionContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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:
|
||||||
|
0x00 - master password hash
|
||||||
|
Metadata:
|
||||||
|
0x20 - record count
|
||||||
|
0x24 - specifier count
|
||||||
|
0x08 -
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
/*println!(
|
||||||
|
"Checksum: {:x}, Expected: {:x}, Size:: {}",
|
||||||
|
self.chksum_crc, chk, HEADER_SIZE
|
||||||
|
);*/
|
||||||
|
chk == self.chksum_crc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EncryptionContext {
|
||||||
|
enc_type: EncryptionType,
|
||||||
|
hash_type: HashType,
|
||||||
|
key: Vec<u8>,
|
||||||
|
salt: SaltString,
|
||||||
|
iv_root: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
enum EncryptionType {
|
||||||
|
Invalid = 0,
|
||||||
|
AesGcm = 1,
|
||||||
|
Chacha20Poly1305 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncryptionType {
|
||||||
|
fn from(n: u8) -> EncryptionType {
|
||||||
|
match n {
|
||||||
|
0 => EncryptionType::Invalid,
|
||||||
|
1 => EncryptionType::AesGcm,
|
||||||
|
2 => EncryptionType::Chacha20Poly1305,
|
||||||
|
_ => panic!("Invalid value '{}'.", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
enum HashType {
|
||||||
|
Invalid = 0,
|
||||||
|
Argon2 = 1,
|
||||||
|
Bcrypt = 2,
|
||||||
|
Pbkdf2 = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HashType {
|
||||||
|
fn from(n: u8) -> HashType {
|
||||||
|
match n {
|
||||||
|
0 => HashType::Invalid,
|
||||||
|
1 => HashType::Argon2,
|
||||||
|
2 => HashType::Bcrypt,
|
||||||
|
4 => HashType::Pbkdf2,
|
||||||
|
_ => panic!("Invalid value '{}'.", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String, &'static str> {
|
||||||
|
let mut buf: Vec<u8> = vec![0; header.enc_sup_block_size as usize];
|
||||||
|
|
||||||
|
if file.read_exact(&mut buf).is_err() {
|
||||||
|
return Err("Read error.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let dat = enc.decrypt_block(&buf).unwrap();
|
||||||
|
println!("Superblocc: {:?}", &dat);
|
||||||
|
Ok(String::from_utf8_lossy(&dat).try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 padlen = 32 - buf.len();
|
||||||
|
let mut pad: Vec<u8> = vec![0; padlen];
|
||||||
|
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 salt = SaltString::generate(&mut rand_core::OsRng);
|
||||||
|
|
||||||
|
let (default_hash_type, default_enc_type) = (HashType::Argon2, EncryptionType::AesGcm);
|
||||||
|
|
||||||
|
let key = derive_key(&passphrase, &salt, default_hash_type).unwrap();
|
||||||
|
|
||||||
|
let iv: &[u8; 12] = b"unique nonce"; //TODO: Nonce generation
|
||||||
|
|
||||||
|
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 enc_buf: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
enc_buf.extend_from_slice(&enc.key);
|
||||||
|
if enc_buf.len() != 32 {
|
||||||
|
enc_buf.append([b'\0'].repeat(32 - enc_buf.len()).as_mut());
|
||||||
|
}
|
||||||
|
|
||||||
|
let enc_buf = enc.encrypt_block(&enc_buf).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() 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 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);
|
||||||
|
|
||||||
|
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 get_specifier_map(file: &mut BufReader<File>, header: &LocalStoreHeader) {}
|
||||||
|
|
||||||
|
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 sup_data = match try_deserialize_superblock(&mut reader, &enc_ctx, &header) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(x) => return Err(x),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Data: {}", &sup_data);
|
||||||
|
|
||||||
|
let spec_map = get_specifier_map(&mut reader, &header);
|
||||||
|
|
||||||
|
let store = LocalStore {
|
||||||
|
path: path,
|
||||||
|
header,
|
||||||
|
enc_ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CredentialRecord {
|
||||||
|
id: u64,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
specifier: Option<String>,
|
||||||
|
meta: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CRED_FLAG_SPECIFIER: u8 = 0b0000010;
|
||||||
|
const CRED_FLAG_METADATA: u8 = 0b0000100;
|
||||||
|
|
||||||
|
impl CredentialRecord {
|
||||||
|
fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::<u8>::new();
|
||||||
|
|
||||||
|
let mut flags: u8 = 0;
|
||||||
|
|
||||||
|
if self.specifier.is_some() {
|
||||||
|
flags |= CRED_FLAG_SPECIFIER;
|
||||||
|
//put specifier here for better indexing
|
||||||
|
//use hashtable in superblock
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.meta.is_some() {
|
||||||
|
flags |= CRED_FLAG_METADATA;
|
||||||
|
}
|
||||||
|
bytes.push(flags);
|
||||||
|
|
||||||
|
bytes.append(u64::to_be_bytes(self.id).to_vec().as_mut());
|
||||||
|
|
||||||
|
let key_bytes = self.key.bytes();
|
||||||
|
let value_bytes = self.value.bytes();
|
||||||
|
|
||||||
|
bytes.append(u32::to_be_bytes(key_bytes.len() as u32).to_vec().as_mut());
|
||||||
|
bytes.append(u32::to_be_bytes(value_bytes.len() as u32).to_vec().as_mut());
|
||||||
|
|
||||||
|
bytes.extend(key_bytes);
|
||||||
|
bytes.extend(value_bytes);
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_block(bytes: &[u8]) {
|
||||||
|
let flags = bytes[0];
|
||||||
|
|
||||||
|
let id = u64::from_be_bytes(bytes[1..5].try_into().unwrap());
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, typ: EncryptionType) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store for LocalStore {
|
||||||
|
fn get_creds(&self, id: u64) -> Option<StoreRecord> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_creds_by_key(&self, key: String) -> Vec<StoreRecord> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_creds(
|
||||||
|
&self,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
specifier: Option<String>,
|
||||||
|
meta: Option<String>,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncryptionContext {
|
||||||
|
fn decrypt_block(&self, ciphertext: &Vec<u8>) -> Result<Vec<u8>, &'static str> {
|
||||||
|
let plaintext = match self.enc_type {
|
||||||
|
EncryptionType::Invalid => unreachable!(),
|
||||||
|
EncryptionType::AesGcm => {
|
||||||
|
println!(
|
||||||
|
"Aes dec: len: {} key: {:?}: iv: {:?}",
|
||||||
|
ciphertext.len(),
|
||||||
|
&self.key,
|
||||||
|
&self.iv_root
|
||||||
|
);
|
||||||
|
|
||||||
|
let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key);
|
||||||
|
let aes = Aes256Gcm::new(aes_key);
|
||||||
|
|
||||||
|
let nonce = Nonce::from_slice(&self.iv_root);
|
||||||
|
|
||||||
|
aes.decrypt(nonce, ciphertext.as_ref())
|
||||||
|
}
|
||||||
|
EncryptionType::Chacha20Poly1305 => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if plaintext.is_err() {
|
||||||
|
return Err("Decryption error");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(plaintext.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt_block(&self, plaintext: &Vec<u8>) -> Result<Vec<u8>, &'static str> {
|
||||||
|
let ciphertext = match self.enc_type {
|
||||||
|
EncryptionType::Invalid => unreachable!(),
|
||||||
|
EncryptionType::AesGcm => {
|
||||||
|
println!(
|
||||||
|
"Aes enc: len: {} key: {:?}: iv: {:?}",
|
||||||
|
plaintext.len(),
|
||||||
|
&self.key,
|
||||||
|
&self.iv_root
|
||||||
|
);
|
||||||
|
|
||||||
|
let aes_key = aes_gcm::aead::generic_array::GenericArray::from_slice(&self.key);
|
||||||
|
let aes = Aes256Gcm::new(aes_key);
|
||||||
|
|
||||||
|
let nonce = Nonce::from_slice(&self.iv_root);
|
||||||
|
|
||||||
|
aes.encrypt(nonce, plaintext.as_ref())
|
||||||
|
}
|
||||||
|
EncryptionType::Chacha20Poly1305 => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if ciphertext.is_err() {
|
||||||
|
return Err("Encryption error");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ciphertext.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::store::local::{LocalStoreHeader, HEADER_SIZE};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn header_size_const_greater_than_realsize() {
|
||||||
|
let c = HEADER_SIZE;
|
||||||
|
let r = size_of::<LocalStoreHeader>();
|
||||||
|
|
||||||
|
assert!(c > r, "const = {}, real = {}", c, r);
|
||||||
|
}
|
||||||
|
}
|
22
src/store/mod.rs
Normal file
22
src/store/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
pub(crate) mod local;
|
||||||
|
pub(crate) mod server;
|
||||||
|
|
||||||
|
pub struct StoreRecord {
|
||||||
|
id: u64,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
specifier: Option<String>,
|
||||||
|
meta: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Store {
|
||||||
|
fn get_creds(&self, id: u64) -> Option<StoreRecord>;
|
||||||
|
fn get_creds_by_key(&self, key: String) -> Vec<StoreRecord>;
|
||||||
|
fn store_creds(
|
||||||
|
&self,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
specifier: Option<String>,
|
||||||
|
meta: Option<String>,
|
||||||
|
);
|
||||||
|
}
|
48
src/store/server.rs
Normal file
48
src/store/server.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use reqwest::header::HeaderValue;
|
||||||
|
|
||||||
|
use super::{Store, StoreRecord};
|
||||||
|
|
||||||
|
pub struct ServerStore {
|
||||||
|
address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(address: String) -> Result<ServerStore, &'static str> {
|
||||||
|
let url = match reqwest::Url::parse(&address) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => return Err("Invalid address."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
headers.append(
|
||||||
|
"Authorization",
|
||||||
|
HeaderValue::from_static("$meowmeowplaceholder"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.user_agent(format!("nyanpass v{}", env!("CARGO_PKG_VERSION")))
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return Ok(ServerStore { address: address });
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store for ServerStore {
|
||||||
|
fn get_creds(&self, id: u64) -> Option<StoreRecord> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_creds_by_key(&self, key: String) -> Vec<StoreRecord> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_creds(
|
||||||
|
&self,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
specifier: Option<String>,
|
||||||
|
meta: Option<String>,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue