Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

40 implement a secure key storage functionality for the pop server #176

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
96e069d
Implement ChaCha20Poly1305 encryption for keystore files.
Tekum-Emmanuella Sep 18, 2024
b78ba29
Implemented ChaCha20Poly1305 decryption for keystore files.
Tekum-Emmanuella Sep 18, 2024
3ca8d7e
Implemented/added memmory management by clearing and forgetting the t…
Tekum-Emmanuella Sep 19, 2024
0a3aa61
Added logging, redaction, and path handling improvements.
Tekum-Emmanuella Sep 20, 2024
acd5d64
Improved keystore security and error handling with custom error types…
Tekum-Emmanuella Sep 24, 2024
6fd582b
fix: update error handling in KeyStore.
Tekum-Emmanuella Sep 26, 2024
e169c08
Fix: import errors for KeystoreError in mediator-coordination module.
Tekum-Emmanuella Sep 26, 2024
f069f43
fix: dependencies.
Tekum-Emmanuella Sep 30, 2024
bcb84be
Implement From<std::io::Error> for KeystoreError (propagate file errors)
Tekum-Emmanuella Sep 30, 2024
8fe00a8
fix: Use map_err for explicit conversion of std::io::Error in FileSys…
Tekum-Emmanuella Sep 30, 2024
2bb07fc
adjusting errors to be more descriptive
Blindspot22 Oct 10, 2024
f5ee699
enhance error handling in encryption method with specific error mapping
Blindspot22 Oct 10, 2024
df7abb8
Merge remote-tracking branch 'origin' into 40-implement-a-secure-key-…
Blindspot22 Oct 22, 2024
e8e879d
removing (dead code) the decrypt function which is never used
Blindspot22 Oct 23, 2024
aef2636
Implement ChaCha20Poly1305 decryption for keystore files
Blindspot22 Oct 23, 2024
4a274aa
Enhance KeyStore security: Implement secure key management, nonce han…
Blindspot22 Oct 29, 2024
f5a599e
implement a secure key storage functionality for the pop server
Blindspot22 Nov 1, 2024
86af767
solving unresolved import
Blindspot22 Nov 1, 2024
194ce12
feat: enhancement
Blindspot22 Nov 12, 2024
1384383
Merge branch 'main' of github.com:adorsys/didcomm-mediator-rs into 40…
Blindspot22 Nov 12, 2024
7240052
Merge remote-tracking branch 'origin/main' into 40-implement-a-secure…
Christiantyemele Nov 20, 2024
4463b8c
added a key wrapper using cocoon
Christiantyemele Nov 20, 2024
0bfe0d9
feat: unused imports
Blindspot22 Nov 21, 2024
eeeb1f9
feat(): init test impl
Christiantyemele Nov 21, 2024
9af1f96
fix(): init test impl
Christiantyemele Nov 21, 2024
261fd50
fix(): init added secure repos trait impl
Christiantyemele Nov 26, 2024
ad33cab
fix(): fixing test on secure key storage
Christiantyemele Nov 27, 2024
7bbe8d2
feat(): fixing failing tests from change
Christiantyemele Nov 27, 2024
b39c212
fix(): polish secure key testing
Christiantyemele Nov 28, 2024
86207cb
resolving conflicts
Blindspot22 Nov 29, 2024
6791b31
fix(): merged main into secure key storage
Christiantyemele Nov 29, 2024
f468865
fix: fixing conflits in plugin.rs
Blindspot22 Dec 2, 2024
1587ae9
feat: resolving warnings and edit Cargo.toml
Blindspot22 Dec 3, 2024
3f34292
feat: fixing conflicts
Blindspot22 Dec 3, 2024
3d39d5e
Merge branch 'main' of https://github.com/adorsys/didcomm-mediator-rs…
Christiantyemele Dec 3, 2024
8ef9cf7
feat: resolving conflits stateful.rs
Blindspot22 Dec 4, 2024
1c7dfd0
feat: resolving conflits stateful.rs
Blindspot22 Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/keystore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ chrono.workspace = true
serde_json.workspace = true
thiserror.workspace = true
nix.workspace = true
chacha20poly1305 = "0.10.1"
log = "0.4.22"
openssl = "0.10.66"
jsonwebtoken = "9.3.0"
zeroize.workspace = true
secrecy = "0.10.2"
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved

23 changes: 23 additions & 0 deletions crates/keystore/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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),
}
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
122 changes: 88 additions & 34 deletions crates/keystore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -34,6 +18,76 @@
keys: Vec<Jwk>,
}

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

use log::{debug, info, error}; // Import logging macros

Check warning on line 26 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

unused import: `error`

Check warning on line 26 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

unused import: `error`
use secrecy::{ExposeSecret, SecretString};
use zeroize::Zeroize;

struct FileSystemKeystore {

Check warning on line 30 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

struct `FileSystemKeystore` is never constructed

Check warning on line 30 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

struct `FileSystemKeystore` is never constructed
key: SecretString, // Store key securely using secrecy crate
nonce: Vec<u8>,
}
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved

impl FileSystemKeystore {
fn encrypt(mut self, secret: KeyStore) -> Result<(), Box<dyn Error>> {

Check warning on line 36 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

methods `encrypt` and `decrypt` are never used

Check warning on line 36 in crates/keystore/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build and Test

methods `encrypt` and `decrypt` are never used
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
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(&self.nonce), buffer.as_slice())
.map_err(|err| err).unwrap();
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
// Overwrite the file with encrypted keys
keystorefile.write_all(&encrypted_key)?; // Use Result for error handling

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(())
}
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved

fn decrypt(self, secret: KeyStore) -> Result<Vec<u8>, std::io::Error> {
Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
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| err).unwrap();

// 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)
Tekum-Emmanuella marked this conversation as resolved.
Show resolved Hide resolved
}
}

Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
impl<'a> KeyStore<'a> {
/// Constructs file-based key-value store.
pub fn new(fs: &'a mut dyn FileSystem, storage_dirpath: &str) -> Self {
Expand All @@ -49,13 +103,13 @@
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::IoError)?;
.map_err(KeystoreError::FileError)?;

// Collect paths and associated timestamps of files inside `dir`
let mut collected: Vec<(String, i32)> = vec![];
Expand All @@ -65,7 +119,7 @@
.trim_start_matches(&format!("{}/", &dirpath))
.trim_end_matches(".json")
.parse()
.map_err(|_| KeyStoreError::NonCompliant)?;
.map_err(|_| KeystoreError::NonCompliant)?;

collected.push((path, stamp));
}
Expand All @@ -77,9 +131,9 @@
.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::<Vec<Jwk>>(&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::<Vec<Jwk>>(&content).map_err(KeystoreError::ParseError)?;

let filename = path
.trim_start_matches(&format!("{}/", &dirpath))
Expand All @@ -99,16 +153,16 @@
}

/// 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
Expand All @@ -119,10 +173,10 @@
/// 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 @@ -133,11 +187,11 @@

/// 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
5 changes: 3 additions & 2 deletions crates/plugins/mediator-coordination/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -43,7 +44,7 @@ pub fn read_diddoc(fs: &dyn FileSystem, storage_dirpath: &str) -> Result<Documen
pub fn read_keystore<'a>(
fs: &'a mut dyn FileSystem,
storage_dirpath: &str,
) -> Result<KeyStore<'a>, KeyStoreError> {
) -> Result<KeyStore<'a>, KeystoreError> {
KeyStore::latest(fs, storage_dirpath)
}

Expand Down
Loading