use crate::wallet::Error;
use crate::wallet::Result;
use crate::MainSecretKey;
use bls::SecretKey;
use hex::encode;
use rand::Rng;
use ring::aead::{BoundKey, Nonce, NonceSequence};
use ring::error::Unspecified;
use serde::{Deserialize, Serialize};
use std::io::Read;
use std::num::NonZeroU32;
use std::path::Path;
const ITERATIONS: NonZeroU32 = match NonZeroU32::new(100_000) {
Some(v) => v,
None => panic!("`100_000` is not be zero"),
};
pub const ENCRYPTED_MAIN_SECRET_KEY_FILENAME: &str = "main_secret_key.encrypted";
#[derive(Serialize, Deserialize)]
pub(crate) struct EncryptedSecretKey {
encrypted_secret_key: String,
pub salt: String,
pub nonce: String,
}
impl EncryptedSecretKey {
pub fn save_to_file(&self, wallet_dir: &Path) -> Result<()> {
let serialized_data = serde_json::to_string(&self)
.map_err(|e| Error::FailedToSerializeEncryptedKey(e.to_string()))?;
let encrypted_secret_key_path = wallet_dir.join(ENCRYPTED_MAIN_SECRET_KEY_FILENAME);
std::fs::write(encrypted_secret_key_path, serialized_data)?;
Ok(())
}
pub fn from_file(wallet_dir: &Path) -> Result<EncryptedSecretKey> {
let path = wallet_dir.join(ENCRYPTED_MAIN_SECRET_KEY_FILENAME);
if !path.is_file() {
return Err(Error::EncryptedMainSecretKeyNotFound(path));
}
let mut file = std::fs::File::open(path).map_err(|_| {
Error::FailedToDeserializeEncryptedKey(String::from("File open failed."))
})?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).map_err(|_| {
Error::FailedToDeserializeEncryptedKey(String::from("File read failed."))
})?;
let encrypted_secret_key: EncryptedSecretKey =
serde_json::from_str(&buffer).map_err(|_| {
Error::FailedToDeserializeEncryptedKey(format!("Deserialization failed: {buffer}"))
})?;
Ok(encrypted_secret_key)
}
pub fn file_exists(wallet_dir: &Path) -> bool {
let path = wallet_dir.join(ENCRYPTED_MAIN_SECRET_KEY_FILENAME);
path.is_file()
}
pub fn decrypt(&self, password: &str) -> Result<MainSecretKey> {
let salt = hex::decode(&self.salt)
.map_err(|_| Error::FailedToDecryptKey(String::from("Invalid salt encoding.")))?;
let mut key = [0; 32];
ring::pbkdf2::derive(
ring::pbkdf2::PBKDF2_HMAC_SHA512,
ITERATIONS,
&salt,
password.as_bytes(),
&mut key,
);
let unbound_key = ring::aead::UnboundKey::new(&ring::aead::CHACHA20_POLY1305, &key)
.map_err(|_| {
Error::FailedToDecryptKey(String::from("Could not create unbound key."))
})?;
let nonce_vec = hex::decode(&self.nonce)
.map_err(|_| Error::FailedToDecryptKey(String::from("Invalid nonce encoding.")))?;
let mut nonce = [0u8; 12];
nonce.copy_from_slice(&nonce_vec[0..12]);
let mut opening_key = ring::aead::OpeningKey::new(unbound_key, NonceSeq(nonce));
let aad = ring::aead::Aad::from(&[]);
let mut encrypted_secret_key = hex::decode(&self.encrypted_secret_key).map_err(|_| {
Error::FailedToDecryptKey(String::from("Invalid encrypted secret key encoding."))
})?;
let decrypted_data = opening_key
.open_in_place(aad, &mut encrypted_secret_key)
.map_err(|_| Error::FailedToDecryptKey(String::from("Could not open encrypted key")))?;
let mut secret_key_bytes = [0u8; 32];
secret_key_bytes.copy_from_slice(&decrypted_data[0..32]);
let secret_key = SecretKey::from_bytes(secret_key_bytes)?;
Ok(MainSecretKey::new(secret_key))
}
}
struct NonceSeq([u8; 12]);
impl NonceSequence for NonceSeq {
fn advance(&mut self) -> std::result::Result<Nonce, Unspecified> {
Nonce::try_assume_unique_for_key(&self.0)
}
}
pub(crate) fn encrypt_secret_key(
secret_key: &MainSecretKey,
password: &str,
) -> Result<EncryptedSecretKey> {
let mut salt = [0u8; 8];
rand::thread_rng().fill(&mut salt);
let mut nonce = [0u8; 12];
rand::thread_rng().fill(&mut nonce);
let mut key = [0; 32];
ring::pbkdf2::derive(
ring::pbkdf2::PBKDF2_HMAC_SHA512,
ITERATIONS,
&salt,
password.as_bytes(),
&mut key,
);
let unbound_key = ring::aead::UnboundKey::new(&ring::aead::CHACHA20_POLY1305, &key)
.map_err(|_| Error::FailedToEncryptKey(String::from("Could not create unbound key.")))?;
let mut sealing_key = ring::aead::SealingKey::new(unbound_key, NonceSeq(nonce));
let aad = ring::aead::Aad::from(&[]);
let secret_key_bytes = secret_key.to_bytes();
let mut encrypted_secret_key = secret_key_bytes;
sealing_key
.seal_in_place_append_tag(aad, &mut encrypted_secret_key)
.map_err(|_| Error::FailedToEncryptKey(String::from("Could not seal sealing key.")))?;
Ok(EncryptedSecretKey {
encrypted_secret_key: encode(encrypted_secret_key),
salt: encode(salt),
nonce: encode(nonce),
})
}
#[cfg(test)]
mod tests {
use crate::wallet::encryption::{
encrypt_secret_key, EncryptedSecretKey, ENCRYPTED_MAIN_SECRET_KEY_FILENAME,
};
use crate::MainSecretKey;
use bls::SecretKey;
fn generate_main_secret_key() -> MainSecretKey {
let secret_key = SecretKey::random();
MainSecretKey::new(secret_key)
}
#[test]
fn test_encrypt_and_decrypt() {
let password = "safenetwork";
let main_secret_key = generate_main_secret_key();
let encrypted_secret_key =
encrypt_secret_key(&main_secret_key, password).expect("Failed to encrypt key");
let decrypted_secret_key = encrypted_secret_key
.decrypt(password)
.expect("Failed to decrypt key");
assert_eq!(main_secret_key.to_bytes(), decrypted_secret_key.to_bytes());
}
#[test]
fn test_decrypt_with_wrong_password() {
let password = "safenetwork";
let wrong_password = "unsafenetwork";
let main_secret_key = generate_main_secret_key();
let encrypted_secret_key =
encrypt_secret_key(&main_secret_key, password).expect("Failed to encrypt key");
assert!(encrypted_secret_key.decrypt(password).is_ok());
assert!(encrypted_secret_key.decrypt(wrong_password).is_err());
}
#[test]
fn test_save_to_file_and_read_from_file() {
let password = "safenetwork";
let main_secret_key = generate_main_secret_key();
let encrypted_secret_key =
encrypt_secret_key(&main_secret_key, password).expect("Failed to encrypt key");
let temp_dir = tempfile::tempdir().unwrap();
let wallet_dir = temp_dir.path();
encrypted_secret_key
.save_to_file(wallet_dir)
.expect("Failed to save encrypted key to file");
let encrypted_secret_key_path = wallet_dir.join(ENCRYPTED_MAIN_SECRET_KEY_FILENAME);
assert!(
encrypted_secret_key_path.is_file(),
"Encrypted key file does not exist"
);
let read_encrypted_secret_key = EncryptedSecretKey::from_file(wallet_dir)
.expect("Failed to read encrypted key from file.");
assert_eq!(
read_encrypted_secret_key.encrypted_secret_key,
encrypted_secret_key.encrypted_secret_key
);
assert_eq!(read_encrypted_secret_key.salt, encrypted_secret_key.salt);
assert_eq!(read_encrypted_secret_key.nonce, encrypted_secret_key.nonce);
}
#[test]
fn test_file_exists() {
}
}