diff --git a/Cargo.toml b/Cargo.toml index 2eee1fcb..72c717f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,11 @@ json-canon = "0.1.3" qrcode = "0.12.0" image = "0.23" reqwest = "0.11" +chacha20poly1305 = "0.10.1" +log = "0.4.22" +openssl = "0.10.66" +jsonwebtoken = "9.3.0" +secrecy = "0.10.2" tempdir = "0.3.7" headers = "0.3" thiserror = "1.0.48" diff --git a/crates/keystore/Cargo.toml b/crates/keystore/Cargo.toml index cb5fe10a..1e9770ec 100644 --- a/crates/keystore/Cargo.toml +++ b/crates/keystore/Cargo.toml @@ -10,3 +10,10 @@ chrono.workspace = true serde_json.workspace = true thiserror.workspace = true nix.workspace = true +chacha20poly1305.workspace =true +log.workspace = true +openssl.workspace = true +jsonwebtoken.workspace = true +zeroize.workspace = true +secrecy.workspace = true + diff --git a/crates/keystore/src/errors.rs b/crates/keystore/src/errors.rs new file mode 100644 index 00000000..e84f5f49 --- /dev/null +++ b/crates/keystore/src/errors.rs @@ -0,0 +1,28 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum KeystoreError { + #[error("File error: {0}")] + FileError(std::io::Error), + #[error("JwkConversionError")] + JwkConversionError, + #[error("KeyPairGenerationError")] + KeyPairGenerationError, + #[error("non compliant")] + NonCompliant, + #[error("not found")] + NotFound, + #[error("parse error")] + ParseError(serde_json::Error), + #[error("serde error")] + SerdeError(serde_json::Error), + #[error("Encryption error: {0}")] + EncryptionError(chacha20poly1305::Error), + #[error("Decryption error: {0}")] + DecryptionError(chacha20poly1305::Error), +} +impl From for KeystoreError { + fn from(err: std::io::Error) -> Self { + KeystoreError::FileError(err) + } +} \ No newline at end of file diff --git a/crates/keystore/src/lib.rs b/crates/keystore/src/lib.rs index cc68778d..9745f51b 100644 --- a/crates/keystore/src/lib.rs +++ b/crates/keystore/src/lib.rs @@ -1,31 +1,15 @@ pub mod filesystem; +pub mod errors; use chrono::Utc; use did_utils::{ crypto::{Ed25519KeyPair, Generate, ToPublic, X25519KeyPair}, jwk::Jwk, }; -use std::error::Error; +use std::{error::Error, fs::File, io::{Read, Write}}; use crate::filesystem::FileSystem; - -#[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), -} +use crate::errors::KeystoreError; pub struct KeyStore<'a> { fs: &'a mut dyn FileSystem, @@ -34,6 +18,80 @@ pub struct KeyStore<'a> { keys: Vec, } +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, +} + +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(|err| KeystoreError::EncryptionError(err))?; // Wrap encryption error + + // Overwrite the file with encrypted keys + keystorefile.write_all(&encrypted_key)?; // Use Result for error handling + + // 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(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 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 decrypted_key = cipher + .decrypt(GenericArray::from_slice(&self.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::>(); + + // 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 { @@ -49,13 +107,13 @@ impl<'a> KeyStore<'a> { pub fn latest( fs: &'a mut dyn FileSystem, storage_dirpath: &str, - ) -> Result { + ) -> Result { let dirpath = format!("{storage_dirpath}/keystore"); // Read directory let paths = fs .read_dir_files(&dirpath) - .map_err(KeyStoreError::IoError)?; + .map_err(KeystoreError::FileError)?; // Collect paths and associated timestamps of files inside `dir` let mut collected: Vec<(String, i32)> = vec![]; @@ -65,7 +123,7 @@ impl<'a> KeyStore<'a> { .trim_start_matches(&format!("{}/", &dirpath)) .trim_end_matches(".json") .parse() - .map_err(|_| KeyStoreError::NonCompliant)?; + .map_err(|_| KeystoreError::NonCompliant)?; collected.push((path, stamp)); } @@ -77,9 +135,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::IoError)?; - let keys = serde_json::from_str::>(&content).map_err(KeyStoreError::ParseError)?; + let path = file.ok_or(KeystoreError::NotFound)?; + let content = fs.read_to_string(path).map_err(KeystoreError::FileError)?; + let keys = serde_json::from_str::>(&content).map_err(KeystoreError::ParseError)?; let filename = path .trim_start_matches(&format!("{}/", &dirpath)) @@ -99,16 +157,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::IoError)?; + .map_err(KeystoreError::FileError)?; self.fs .write( &self.path(), - &serde_json::to_string_pretty(&self.keys).map_err(KeyStoreError::SerdeError)?, + &serde_json::to_string_pretty(&self.keys).map_err(KeystoreError::SerdeError)?, ) - .map_err(KeyStoreError::IoError) + .map_err(KeystoreError::FileError) } /// Searches keypair given public key @@ -119,10 +177,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> { - 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); @@ -133,11 +191,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 { - let keypair = X25519KeyPair::new().map_err(|_| KeyStoreError::KeyPairGenerationError)?; + pub fn gen_x25519_jwk(&mut self) -> Result { + 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); diff --git a/crates/plugins/mediator-coordination/src/util/mod.rs b/crates/plugins/mediator-coordination/src/util/mod.rs index fed03ee1..f21a8d65 100644 --- a/crates/plugins/mediator-coordination/src/util/mod.rs +++ b/crates/plugins/mediator-coordination/src/util/mod.rs @@ -4,7 +4,8 @@ use did_utils::{ didcore::{AssertionMethod, Document, KeyAgreement, KeyFormat, VerificationMethod}, jwk::Jwk, }; -use keystore::{filesystem::FileSystem, KeyStore, KeyStoreError}; +use keystore::{filesystem::FileSystem, KeyStore}; +use keystore::errors::KeystoreError; use serde_json::Error as SerdeError; use std::io; @@ -43,7 +44,7 @@ pub fn read_diddoc(fs: &dyn FileSystem, storage_dirpath: &str) -> Result( fs: &'a mut dyn FileSystem, storage_dirpath: &str, -) -> Result, KeyStoreError> { +) -> Result, KeystoreError> { KeyStore::latest(fs, storage_dirpath) }