use hkdf::Hkdf;
use rand::rngs::OsRng;
use rand::RngCore;
use secrecy::{ExposeSecret, Secret};
use sha2::Sha256;
use x25519_dalek::{EphemeralSecret, PublicKey};
use crate::{
aead, error::CoreError, message::recipient::MessageRecipientHeader,
x25519::private::X25519PrivateKey, x25519::public::X25519PublicKey,
};
pub const CURVE_NAME_X25519: &str = "X25519";
pub(crate) const ENCRYPTED_FILE_KEY_BYTES: usize = 32;
const X25519_RECIPIENT_KEY_LABEL: &[u8] = b"ntge-encryption-X25519/v1";
#[derive(Debug)]
pub struct FileKey(pub(crate) Secret<[u8; 16]>);
#[allow(dead_code)]
impl FileKey {
pub(crate) fn new() -> Self {
let mut file_key = [0; 16];
OsRng.fill_bytes(&mut file_key);
FileKey(Secret::new(file_key))
}
}
#[allow(dead_code)]
impl FileKey {
pub(crate) fn wrap(&self, public_key: &X25519PublicKey) -> MessageRecipientHeader {
let mut csprng = OsRng {};
let ephemeral_private_key = EphemeralSecret::new(&mut csprng);
let ephemeral_public_key = PublicKey::from(&ephemeral_private_key);
let shared_secret = ephemeral_private_key.diffie_hellman(&(public_key.raw));
let mut salt = vec![];
salt.extend_from_slice(ephemeral_public_key.as_bytes());
salt.extend_from_slice(public_key.raw.as_bytes());
let mut encryption_key = [0; 32];
Hkdf::<Sha256>::new(Some(&salt), shared_secret.as_bytes())
.expand(X25519_RECIPIENT_KEY_LABEL, &mut encryption_key)
.expect("encryption_key is the correct length");
let encrypted_file_key = {
let mut key = [0; ENCRYPTED_FILE_KEY_BYTES];
key.copy_from_slice(&aead::aead_encrypt(&encryption_key, self.0.expose_secret()));
key
};
let ephemeral_public_key = ephemeral_public_key.as_bytes().to_vec();
let encrypted_file_key = encrypted_file_key.to_vec();
MessageRecipientHeader {
key_type: String::from(CURVE_NAME_X25519),
ephemeral_public_key,
encrypted_file_key,
}
}
pub(crate) fn unwrap(
message_recipient: &MessageRecipientHeader,
secret_key: &X25519PrivateKey,
) -> Result<Self, CoreError> {
let shared_secret = secret_key
.raw
.diffie_hellman(&message_recipient.get_ephemeral_public_key());
let public_key = PublicKey::from(&secret_key.raw);
let mut salt = vec![];
salt.extend_from_slice(message_recipient.get_ephemeral_public_key().as_bytes());
salt.extend_from_slice(public_key.as_bytes());
let mut encryption_key = [0; 32];
Hkdf::<Sha256>::new(Some(&salt), shared_secret.as_bytes())
.expand(X25519_RECIPIENT_KEY_LABEL, &mut encryption_key)
.expect("encryption_key is the correct length");
match aead::aead_decrypt(&encryption_key, &message_recipient.encrypted_file_key) {
Ok(key) => {
let mut file_key = [0; 16];
file_key.copy_from_slice(&key);
Ok(FileKey(Secret::new(file_key)))
}
Err(_) => {
let e = CoreError::MessageDecryptionError {
name: "Message",
reason: "cannot decrypt file key from message recipient",
};
Err(e)
}
}
}
}
impl Drop for FileKey {
fn drop(&mut self) {
if cfg!(feature = "drop-log-enable") {
println!("{:?} is being deallocated", self);
}
}
}
#[no_mangle]
#[cfg(target_os = "ios")]
pub unsafe extern "C" fn c_x25519_file_key_destroy(file_key: *mut FileKey) {
let _ = Box::from_raw(file_key);
}
#[cfg(test)]
mod test {
use crate::{
aead, x25519::filekey::FileKey, x25519::private::X25519PrivateKey,
x25519::public::X25519PublicKey,
};
use rand::rngs::OsRng;
use rand::RngCore;
use secrecy::ExposeSecret;
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
#[test]
fn it_smoke() {
let mut alice_csprng = OsRng {};
let alice_private = EphemeralSecret::new(&mut alice_csprng);
let _alice_public = PublicKey::from(&alice_private);
let mut bob_csprng = OsRng {};
let bob_secret = EphemeralSecret::new(&mut bob_csprng);
let _bob_public = PublicKey::from(&bob_secret);
}
#[test]
fn it_use_aead_encrypt_and_decrypt() {
let mut csprng = OsRng {};
let mut key = [0; 32];
csprng.fill_bytes(&mut key);
let plaintext = "Plaintext";
let ciphertext = aead::aead_encrypt(&key, &plaintext.as_bytes());
let decrypted_vec = aead::aead_decrypt(&key, &ciphertext).unwrap();
let plaintext_vec = plaintext.as_bytes().to_vec();
assert_eq!(plaintext_vec, decrypted_vec);
}
#[test]
fn it_wrap_then_unwrap_a_file_key() {
let file_key = FileKey::new();
let mut alice_csprng = OsRng {};
let alice_secret_key = X25519PrivateKey {
raw: StaticSecret::new(&mut alice_csprng),
};
let alice_public_key = X25519PublicKey {
raw: PublicKey::from(&alice_secret_key.raw),
};
let message_recipient_for_alice = file_key.wrap(&alice_public_key);
let decrypted_file_key =
FileKey::unwrap(&message_recipient_for_alice, &alice_secret_key).unwrap();
assert_eq!(
file_key.0.expose_secret(),
decrypted_file_key.0.expose_secret()
);
}
}