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"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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 @@
|
|||
fn main() {
|
||||
let args = std::env::args();
|
||||
use config::try_load_preferences;
|
||||
use store::Store;
|
||||
|
||||
mod config;
|
||||
mod store;
|
||||
|
||||
fn main() {
|
||||
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