use std::marker::PhantomData;
use std::sync::Arc;
use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroizing;
use crate::backend::{BackendKey, KeychainBackend};
use crate::cipher;
use crate::error::{KeystoreError, Result};
use crate::format::{
decode_file, encode_file, CipherId, KdfParams, KeystoreHeader, FORMAT_VERSION_V1,
};
use crate::kdf;
use crate::password::Password;
use crate::scheme::KeyScheme;
use crate::signer::SignerHandle;
pub struct Keystore<K: KeyScheme> {
backend: Arc<dyn KeychainBackend>,
path: BackendKey,
header: KeystoreHeader,
cached_public: parking_lot::Mutex<Option<K::PublicKey>>,
_marker: PhantomData<fn() -> K>,
}
impl<K: KeyScheme> Keystore<K> {
pub fn create(
backend: Arc<dyn KeychainBackend>,
path: BackendKey,
password: Password,
plaintext: Option<Zeroizing<Vec<u8>>>,
kdf_params: KdfParams,
) -> Result<Self> {
Self::create_with_rng(
backend,
path,
password,
plaintext,
kdf_params,
&mut rand_core::OsRng,
)
}
pub fn create_with_rng<R: RngCore + CryptoRng>(
backend: Arc<dyn KeychainBackend>,
path: BackendKey,
password: Password,
plaintext: Option<Zeroizing<Vec<u8>>>,
kdf_params: KdfParams,
rng: &mut R,
) -> Result<Self> {
if backend.exists(&path)? {
return Err(KeystoreError::AlreadyExists(path.as_str().to_string()));
}
let secret: Zeroizing<Vec<u8>> = match plaintext {
Some(p) => {
if p.len() != K::SECRET_LEN {
return Err(KeystoreError::InvalidPlaintext {
expected: K::SECRET_LEN,
got: p.len(),
});
}
p
}
None => K::generate(rng),
};
let public = K::public_key(&secret)?;
let mut salt = [0u8; 16];
let mut nonce = [0u8; 12];
rng.fill_bytes(&mut salt);
rng.fill_bytes(&mut nonce);
let mut header = KeystoreHeader {
magic: K::MAGIC,
format_version: FORMAT_VERSION_V1,
scheme_id: K::SCHEME_ID,
kdf: kdf_params,
cipher: CipherId::Aes256Gcm,
salt,
nonce,
payload_len: 0, };
header.payload_len = (secret.len() + cipher::TAG_SIZE) as u32;
let enc_key = kdf::derive_key(password.as_bytes(), &header.salt, &header.kdf)?;
let header_bytes = header.encode();
let ciphertext_and_tag = cipher::encrypt(&enc_key, &header.nonce, &secret, &header_bytes)?;
debug_assert_eq!(
ciphertext_and_tag.len() as u32,
header.payload_len,
"ciphertext length invariant violated"
);
let file_bytes = encode_file(&header, &ciphertext_and_tag);
backend.write(&path, &file_bytes)?;
Ok(Self {
backend,
path,
header,
cached_public: parking_lot::Mutex::new(Some(public)),
_marker: PhantomData,
})
}
pub fn load(backend: Arc<dyn KeychainBackend>, path: BackendKey) -> Result<Self> {
let bytes = backend.read(&path)?;
let (header, _ciphertext_and_tag, _header_bytes) = decode_file(&bytes)?;
if header.magic != K::MAGIC {
return Err(KeystoreError::SchemeMismatch {
expected: K::SCHEME_ID,
expected_name: K::NAME,
found: header.scheme_id,
});
}
if header.scheme_id != K::SCHEME_ID {
return Err(KeystoreError::SchemeMismatch {
expected: K::SCHEME_ID,
expected_name: K::NAME,
found: header.scheme_id,
});
}
Ok(Self {
backend,
path,
header,
cached_public: parking_lot::Mutex::new(None),
_marker: PhantomData,
})
}
pub fn header(&self) -> KeystoreHeader {
self.header
}
pub fn path(&self) -> &BackendKey {
&self.path
}
pub fn cached_public_key(&self) -> Option<K::PublicKey> {
self.cached_public.lock().clone()
}
pub fn unlock(&self, password: Password) -> Result<SignerHandle<K>> {
let bytes = self.backend.read(&self.path)?;
let (header, ciphertext_and_tag, header_bytes) = decode_file(&bytes)?;
if header.magic != K::MAGIC || header.scheme_id != K::SCHEME_ID {
return Err(KeystoreError::SchemeMismatch {
expected: K::SCHEME_ID,
expected_name: K::NAME,
found: header.scheme_id,
});
}
let enc_key = kdf::derive_key(password.as_bytes(), &header.salt, &header.kdf)?;
let plaintext =
cipher::decrypt(&enc_key, &header.nonce, &ciphertext_and_tag, &header_bytes)?;
if plaintext.len() != K::SECRET_LEN {
return Err(KeystoreError::InvalidPlaintext {
expected: K::SECRET_LEN,
got: plaintext.len(),
});
}
let public = K::public_key(&plaintext)?;
*self.cached_public.lock() = Some(public.clone());
Ok(SignerHandle::from_parts(plaintext, public))
}
pub fn change_password(&mut self, old: Password, new: Password) -> Result<()> {
self.change_password_with_rng(old, new, &mut rand_core::OsRng)
}
pub fn change_password_with_rng<R: RngCore + CryptoRng>(
&mut self,
old: Password,
new: Password,
rng: &mut R,
) -> Result<()> {
let bytes = self.backend.read(&self.path)?;
let (_header, ciphertext_and_tag, header_bytes) = decode_file(&bytes)?;
let old_key = kdf::derive_key(old.as_bytes(), &self.header.salt, &self.header.kdf)?;
let plaintext = cipher::decrypt(
&old_key,
&self.header.nonce,
&ciphertext_and_tag,
&header_bytes,
)?;
let mut salt = [0u8; 16];
let mut nonce = [0u8; 12];
rng.fill_bytes(&mut salt);
rng.fill_bytes(&mut nonce);
let mut new_header = self.header;
new_header.salt = salt;
new_header.nonce = nonce;
new_header.payload_len = (plaintext.len() + cipher::TAG_SIZE) as u32;
let new_key = kdf::derive_key(new.as_bytes(), &salt, &new_header.kdf)?;
let new_header_bytes = new_header.encode();
let new_ct = cipher::encrypt(&new_key, &nonce, &plaintext, &new_header_bytes)?;
let new_file = encode_file(&new_header, &new_ct);
self.backend.write(&self.path, &new_file)?;
self.header = new_header;
Ok(())
}
pub fn rotate_kdf(&mut self, password: Password, new_params: KdfParams) -> Result<()> {
self.rotate_kdf_with_rng(password, new_params, &mut rand_core::OsRng)
}
pub fn rotate_kdf_with_rng<R: RngCore + CryptoRng>(
&mut self,
password: Password,
new_params: KdfParams,
rng: &mut R,
) -> Result<()> {
let bytes = self.backend.read(&self.path)?;
let (_header, ciphertext_and_tag, header_bytes) = decode_file(&bytes)?;
let old_key = kdf::derive_key(password.as_bytes(), &self.header.salt, &self.header.kdf)?;
let plaintext = cipher::decrypt(
&old_key,
&self.header.nonce,
&ciphertext_and_tag,
&header_bytes,
)?;
let mut salt = [0u8; 16];
let mut nonce = [0u8; 12];
rng.fill_bytes(&mut salt);
rng.fill_bytes(&mut nonce);
let mut new_header = self.header;
new_header.kdf = new_params;
new_header.salt = salt;
new_header.nonce = nonce;
new_header.payload_len = (plaintext.len() + cipher::TAG_SIZE) as u32;
let new_key = kdf::derive_key(password.as_bytes(), &salt, &new_params)?;
let new_header_bytes = new_header.encode();
let new_ct = cipher::encrypt(&new_key, &nonce, &plaintext, &new_header_bytes)?;
let new_file = encode_file(&new_header, &new_ct);
self.backend.write(&self.path, &new_file)?;
self.header = new_header;
Ok(())
}
pub fn delete(self) -> Result<()> {
self.backend.delete(&self.path)
}
}
impl<K: KeyScheme> std::fmt::Debug for Keystore<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keystore")
.field("scheme", &K::NAME)
.field("path", &self.path)
.field("kdf", &self.header.kdf)
.finish()
}
}