Skip to content

Commit

Permalink
implement a secure key storage functionality for the pop server
Browse files Browse the repository at this point in the history
  • Loading branch information
Blindspot22 committed Nov 1, 2024
1 parent 4a274aa commit f5a599e
Showing 1 changed file with 40 additions and 101 deletions.
141 changes: 40 additions & 101 deletions crates/web-plugins/didcomm-messaging/keystore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
pub mod filesystem;
pub mod errors;

use chrono::Utc;
use did_utils::{
crypto::{Ed25519KeyPair, Generate, ToPublic, X25519KeyPair},
jwk::Jwk,
};
use std::{error::Error, fs::File, io::{Read, Write}};
use std::error::Error;

use crate::filesystem::FileSystem;
use crate::errors::KeystoreError;

#[derive(Debug, thiserror::Error)]
pub enum KeyStoreError {
#[error("failure to convert to JWK format")]
JwkConversionError,
#[error("failure to generate key pair")]
KeyPairGenerationError,
#[error("ioerror: {0}")]
IoError(std::io::Error),
#[error("non compliant")]
NonCompliant,
#[error("not found")]
NotFound,
#[error("parse error")]
ParseError(serde_json::Error),
#[error("serde error")]
SerdeError(serde_json::Error),
}

pub struct KeyStore<'a> {
fs: &'a mut dyn FileSystem,
Expand All @@ -18,86 +34,6 @@ pub struct KeyStore<'a> {
keys: Vec<Jwk>,
}

use chacha20poly1305::{
aead::{generic_array::GenericArray, Aead, AeadCore, OsRng},
ChaCha20Poly1305, KeyInit,
};

use log::{debug, info};
use secrecy::{ExposeSecret, SecretString};
use zeroize::Zeroize;

struct FileSystemKeystore {
key: SecretString, // Store key securely using secrecy crate
nonce: Vec<u8>,
}

impl FileSystemKeystore {
fn encrypt(&mut self, secret: &KeyStore) -> Result<(), KeystoreError> {
let key = self.key.expose_secret(); // Access key securely
let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key.as_bytes()));

let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let path = secret.path();
let mut keystorefile = File::open(path.clone())?; // Use Result for error handling

let mut buffer = Vec::new();
keystorefile.read_to_end(&mut buffer)?; // Use Result for error handling

let encrypted_key = cipher
.encrypt(GenericArray::from_slice(&nonce), buffer.as_slice())
.map_err(KeystoreError::EncryptionError)?;

// Overwrite the file with encrypted keys and nonce
let mut keystorefile = File::create(path.clone())?; // Create or truncate the file for overwriting
keystorefile.write_all(&nonce)?; // Write nonce at the beginning
keystorefile.write_all(&encrypted_key)?; // Write encrypted data

// Store the nonce for decryption
self.nonce = nonce.to_vec();

// Overwrite the buffer with zeros to prevent data leakage
buffer.clear();
buffer.zeroize();

// Conditional logging
debug!("Encryption successful for keystore file: {}", path);

Ok(())
}

fn decrypt(&mut self, secret: &KeyStore) -> Result<Vec<u8>, KeystoreError> {
let key = self.key.expose_secret(); // Access key securely
let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key.as_bytes()));

let path = secret.path();
let mut keystorefile = File::open(path.clone())?; // Use Result for error handling

// Read nonce first
let mut nonce = [0u8; 12]; // Nonce size for ChaCha20Poly1305
keystorefile.read_exact(&mut nonce)?;

let mut buffer = Vec::new();
keystorefile.read_to_end(&mut buffer)?; // Use Result for error handling

let decrypted_key = cipher
.decrypt(GenericArray::from_slice(&nonce), buffer.as_slice())
.map_err(|err| KeystoreError::DecryptionError(err))?; // Wrap decryption error

// Enhanced redaction: Replace all sensitive characters with asterisks
let redacted_key = decrypted_key.iter().map(|b| if b.is_ascii_graphic() && !b.is_ascii_whitespace() { '*' as u8 } else { *b }).collect::<Vec<u8>>();

// Conditional logging with redacted key
info!("Decryption successful for keystore file: {}, redacted key: {:?}", &path, redacted_key);

buffer.clear();
buffer.zeroize();

Ok(decrypted_key)
}
}


impl<'a> KeyStore<'a> {
/// Constructs file-based key-value store.
pub fn new(fs: &'a mut dyn FileSystem, storage_dirpath: &str) -> Self {
Expand All @@ -113,23 +49,23 @@ impl<'a> KeyStore<'a> {
pub fn latest(
fs: &'a mut dyn FileSystem,
storage_dirpath: &str,
) -> Result<Self, KeystoreError> {
) -> Result<Self, KeyStoreError> {
let dirpath = format!("{storage_dirpath}/keystore");

// Read directory
let paths = fs
.read_dir_files(&dirpath)
.map_err(KeystoreError::FileError)?;
.map_err(KeyStoreError::IoError)?;

// Collect paths and associated timestamps of files inside dir
// Collect paths and associated timestamps of files inside `dir`
let mut collected: Vec<(String, i32)> = vec![];
for path in paths {
if path.ends_with(".json") {
let stamp: i32 = path
.trim_start_matches(&format!("{}/", &dirpath))
.trim_end_matches(".json")
.parse()
.map_err(|_| KeystoreError::NonCompliant)?;
.map_err(|_| KeyStoreError::NonCompliant)?;

collected.push((path, stamp));
}
Expand All @@ -141,9 +77,9 @@ impl<'a> KeyStore<'a> {
.max_by_key(|(_, stamp)| stamp)
.map(|(path, _)| path);

let path = file.ok_or(KeystoreError::NotFound)?;
let content = fs.read_to_string(path).map_err(KeystoreError::FileError)?;
let keys = serde_json::from_str::<Vec<Jwk>>(&content).map_err(KeystoreError::ParseError)?;
let path = file.ok_or(KeyStoreError::NotFound)?;
let content = fs.read_to_string(path).map_err(KeyStoreError::IoError)?;
let keys = serde_json::from_str::<Vec<Jwk>>(&content).map_err(KeyStoreError::ParseError)?;

let filename = path
.trim_start_matches(&format!("{}/", &dirpath))
Expand All @@ -163,16 +99,16 @@ impl<'a> KeyStore<'a> {
}

/// Persists store on disk
fn persist(&mut self) -> Result<(), KeystoreError> {
fn persist(&mut self) -> Result<(), KeyStoreError> {
self.fs
.create_dir_all(&self.dirpath)
.map_err(KeystoreError::FileError)?;
.map_err(KeyStoreError::IoError)?;
self.fs
.write(
&self.path(),
&serde_json::to_string_pretty(&self.keys).map_err(KeystoreError::ParseError)?,
&serde_json::to_string_pretty(&self.keys).map_err(KeyStoreError::SerdeError)?,
)
.map_err(KeystoreError::FileError)
.map_err(KeyStoreError::IoError)
}

/// Searches keypair given public key
Expand All @@ -183,10 +119,10 @@ impl<'a> KeyStore<'a> {
/// Generates and persists an ed25519 keypair for digital signatures.
/// Returns public Jwk for convenience.
pub fn gen_ed25519_jwk(&mut self) -> Result<Jwk, Box<dyn Error>> {
let keypair = Ed25519KeyPair::new().map_err(|_| KeystoreError::KeyPairGenerationError)?;
let keypair = Ed25519KeyPair::new().map_err(|_| KeyStoreError::KeyPairGenerationError)?;
let jwk: Jwk = keypair
.try_into()
.map_err(|_| KeystoreError::JwkConversionError)?;
.map_err(|_| KeyStoreError::JwkConversionError)?;
let pub_jwk = jwk.to_public();

self.keys.push(jwk);
Expand All @@ -197,11 +133,11 @@ impl<'a> KeyStore<'a> {

/// Generates and persists an x25519 keypair for digital signatures.
/// Returns public Jwk for convenience.
pub fn gen_x25519_jwk(&mut self) -> Result<Jwk, KeystoreError> {
let keypair = X25519KeyPair::new().map_err(|_| KeystoreError::KeyPairGenerationError)?;
pub fn gen_x25519_jwk(&mut self) -> Result<Jwk, KeyStoreError> {
let keypair = X25519KeyPair::new().map_err(|_| KeyStoreError::KeyPairGenerationError)?;
let jwk: Jwk = keypair
.try_into()
.map_err(|_| KeystoreError::JwkConversionError)?;
.map_err(|_| KeyStoreError::JwkConversionError)?;
let pub_jwk = jwk.to_public();

self.keys.push(jwk);
Expand Down Expand Up @@ -253,7 +189,10 @@ mod tests {
let jwk = store.gen_ed25519_jwk().unwrap();
assert!(store.find_keypair(&jwk).is_some());

let latest_store = KeyStore::latest(&mut mock_fs, "").unwrap();
assert_eq!(latest_store.keys.len(), 1);
let jwk = store.gen_x25519_jwk().unwrap();
assert!(store.find_keypair(&jwk).is_some());

let latest = KeyStore::latest(&mut mock_fs, "");
assert!(latest.is_ok());
}
}

0 comments on commit f5a599e

Please sign in to comment.