From c16a6415494c948bf83326ff8a08eba5e60b5f5e Mon Sep 17 00:00:00 2001 From: librelois <elois@ifee.fr> Date: Tue, 3 Mar 2020 00:07:47 +0100 Subject: [PATCH] [feat] add module private message encryption Closes #3 --- Cargo.lock | 7 + Cargo.toml | 2 + src/lib.rs | 2 + src/private_message.rs | 372 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 383 insertions(+) create mode 100644 src/private_message.rs diff --git a/Cargo.lock b/Cargo.lock index bc8cfd2..695c6a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "cryptoxide" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35f15e1a0699dd988fed910dd78fdc6407f44654cd12589c91fa44ea67d9159" + [[package]] name = "csv" version = "1.1.3" @@ -330,6 +336,7 @@ dependencies = [ "bs58", "byteorder", "criterion", + "cryptoxide", "ring", "scrypt", "serde", diff --git a/Cargo.toml b/Cargo.toml index e9aa6d2..edd85e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ arrayvec = { version = "0.5.1", features = ["array-sizes-33-128", "array-sizes-1 base64 = "0.11.0" bs58 = "0.3.0" byteorder = "1.3.2" +cryptoxide = { version = "0.1.3", optional = true } ring = "0.16.9" scrypt = { version = "0.2", default-features = false } serde = { version = "1.0.*", features = ["derive"], optional = true } @@ -39,5 +40,6 @@ default = ["dewif", "rand", "ser"] aes256 = ["aes"] dewif = ["aes256", "arrayvec"] +private_message = ["cryptoxide", "rand"] rand = [] ser = ["serde"] diff --git a/src/lib.rs b/src/lib.rs index c1f0f3b..9d640ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,8 @@ pub mod bases; pub mod dewif; pub mod hashs; pub mod keys; +#[cfg(feature = "private_message")] +pub mod private_message; #[cfg(feature = "rand")] pub mod rand; pub mod seeds; diff --git a/src/private_message.rs b/src/private_message.rs new file mode 100644 index 0000000..8bbe4ed --- /dev/null +++ b/src/private_message.rs @@ -0,0 +1,372 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage private message encryption/decryption +//! + +pub use ring::aead::Aad; + +use crate::keys::ed25519::{Ed25519KeyPair, PublicKey}; +use crate::rand::UnspecifiedRandError; +use crate::seeds::Seed32; +use cryptoxide::ed25519::{exchange, keypair}; +use ring::aead::{LessSafeKey, Nonce, UnboundKey}; +use ring::pbkdf2; +use std::io::Write; +use std::num::NonZeroU32; +use zeroize::Zeroize; + +const ITERATIONS: u32 = 3; +const EPHEMERAL_PUBLIC_KEY_LEN: usize = 32; + +/// Private message encryption algorithm +#[derive(Clone, Copy, Debug)] +pub enum Algorithm { + /// AES-256 in GCM mode with 128-bit tags and 96 bit nonces. + Aes256Gcm, + /// ChaCha20-Poly1305 as described in [RFC 7539](https://tools.ietf.org/html/rfc7539). + Chacha20Poly1305, +} + +impl Algorithm { + fn to_ring_algo(self) -> &'static ring::aead::Algorithm { + match self { + Self::Aes256Gcm => &ring::aead::AES_256_GCM, + Self::Chacha20Poly1305 => &ring::aead::CHACHA20_POLY1305, + } + } +} + +/// Error at encryption/decryption of a private message +#[derive(Debug)] +pub enum PrivateMessageError { + /// I/O error + IoError(std::io::Error), + /// Unspecified errror + Unspecified, +} + +impl From<std::io::Error> for PrivateMessageError { + fn from(e: std::io::Error) -> Self { + PrivateMessageError::IoError(e) + } +} + +impl From<UnspecifiedRandError> for PrivateMessageError { + fn from(_: UnspecifiedRandError) -> Self { + PrivateMessageError::Unspecified + } +} + +impl From<ring::error::Unspecified> for PrivateMessageError { + fn from(_: ring::error::Unspecified) -> Self { + PrivateMessageError::Unspecified + } +} + +#[derive(Zeroize)] +#[zeroize(drop)] +struct SecretKey([u8; 64]); + +impl AsRef<[u8]> for SecretKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(Zeroize)] +#[zeroize(drop)] +struct SharedSecret([u8; 48]); + +impl Default for SharedSecret { + fn default() -> Self { + SharedSecret([0u8; 48]) + } +} + +impl AsRef<[u8]> for SharedSecret { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for SharedSecret { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +/// Encrypt private message +pub fn encrypt_private_message<A: AsRef<[u8]>, W: Write>( + additionally_authenticated_data: Aad<A>, + algorithm: Algorithm, + message: &mut [u8], + receiver_public_key: &PublicKey, + writer: &mut W, +) -> Result<(), PrivateMessageError> { + // Generate ephemeral ed25519 keypair + let (ephemeral_secret_key, ephemeral_public_key) = keypair(Seed32::random()?.as_ref()); + + // Compute DH exchange (ephemeral_secret_key, receiver_public_key) + // and derive symmetric_key and nonce from shared secret + let (symmetric_key, nonce) = generate_symmetric_key_and_nonce( + algorithm, + &ephemeral_public_key, + SecretKey(ephemeral_secret_key).as_ref(), + &receiver_public_key.as_ref(), + )?; + + // write ephemeral_public_key + let _ = writer.write(&ephemeral_public_key)?; + + // Encrypt and write message + encrypt_and_write::<A, W>( + symmetric_key, + nonce, + additionally_authenticated_data, + message, + writer, + )?; + + Ok(()) +} + +/// Decrypt private message +/// Return message tag +pub fn decrypt_private_message<A: AsRef<[u8]>, W: Write>( + additionally_authenticated_data: Aad<A>, + algorithm: Algorithm, + encrypted_message: &mut [u8], + receiver_key_pair: &Ed25519KeyPair, + writer: &mut W, +) -> Result<(), PrivateMessageError> { + // Get ephemeral public key + let ephemeral_public_key = &encrypted_message[..EPHEMERAL_PUBLIC_KEY_LEN]; + + // Get receiver secret key + let (receiver_secret_key, _receiver_public_key) = keypair(receiver_key_pair.seed().as_ref()); + + // Compute DH exchange (receiver_secret_key, ephemeral_public_key) + // and derive symmetric_key and nonce from shared secret + let (symmetric_key, nonce) = generate_symmetric_key_and_nonce( + algorithm, + &ephemeral_public_key, + SecretKey(receiver_secret_key).as_ref(), + ephemeral_public_key, + )?; + + // Decrypt and write decrypted message + decrypt_and_write::<A, W>( + symmetric_key, + nonce, + additionally_authenticated_data, + encrypted_message, + writer, + )?; + + Ok(()) +} + +fn generate_symmetric_key_and_nonce( + algorithm: Algorithm, + ephemeral_public_key: &[u8], + exchange_secret_key: &[u8], + exchange_public_key: &[u8], +) -> Result<(UnboundKey, Nonce), PrivateMessageError> { + let shared_secret = derive( + &exchange(exchange_public_key, exchange_secret_key), + ephemeral_public_key, + ); + + let symmetric_key = UnboundKey::new(algorithm.to_ring_algo(), &shared_secret.as_ref()[..32])?; + let nonce = Nonce::try_assume_unique_for_key(&shared_secret.as_ref()[32..44])?; + + Ok((symmetric_key, nonce)) +} + +fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret { + let mut shared_secret = SharedSecret::default(); + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA384, + NonZeroU32::new(ITERATIONS).expect("ITERATIONS must be > 0"), + salt, + seed, + shared_secret.as_mut(), + ); + shared_secret +} + +fn encrypt_and_write<A: AsRef<[u8]>, W: Write>( + key: UnboundKey, + nonce: Nonce, + aad: Aad<A>, + message: &mut [u8], + writer: &mut W, +) -> Result<(), PrivateMessageError> { + let key = LessSafeKey::new(key); + let tag = key.seal_in_place_separate_tag(nonce, aad, message)?; + let _ = writer.write(&message)?; + let _ = writer.write(tag.as_ref())?; + + Ok(()) +} + +fn decrypt_and_write<A: AsRef<[u8]>, W: Write>( + key: UnboundKey, + nonce: Nonce, + aad: Aad<A>, + encrypted_message: &mut [u8], + writer: &mut W, +) -> Result<(), PrivateMessageError> { + let key = LessSafeKey::new(key); + //let aad_len = aad.as_ref().len(); + let decrypted_message = key.open_in_place( + nonce, + aad, + &mut encrypted_message[EPHEMERAL_PUBLIC_KEY_LEN..], + )?; + //let plain_len = decrypted_message.len() - aad_len; + let _ = writer.write(&decrypted_message[..])?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::keys::ed25519::KeyPairFromSeed32Generator; + use crate::keys::KeyPair; + + const AAD: &[u8] = b"service name - currency name"; + const MESSAGE: &[u8] = + b"Hello, this is a secret message, which can only be read by the recipient."; + + #[test] + fn encrypt_same_message_must_be_different() -> Result<(), PrivateMessageError> { + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let encrypted_message1 = test_encrypt(message, &receiver_key_pair.public_key())?; + + let encrypted_message2 = test_encrypt(message, &receiver_key_pair.public_key())?; + + assert_ne!(encrypted_message1, encrypted_message2); + assert_ne!(encrypted_message1[32..37], encrypted_message2[32..37]); + + Ok(()) + } + + #[test] + fn encrypt_then_decrypt_with_invalid_aad() -> Result<(), PrivateMessageError> { + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = test_encrypt(message, &receiver_key_pair.public_key())?; + + println!("encrypted message={:?}", encrypted_message); + + let mut decrypted_message = Vec::new(); + + match decrypt_private_message( + Aad::from(b"invalid aad"), + Algorithm::Chacha20Poly1305, + &mut encrypted_message, + &receiver_key_pair, + &mut decrypted_message, + ) { + Ok(()) => panic!("Expected error rivateMessageError::Unspecified, found: Ok(())."), + Err(PrivateMessageError::Unspecified) => Ok(()), + Err(e) => panic!( + "Expected error rivateMessageError::Unspecified, found: {:?}.", + e + ), + } + } + + #[test] + fn encrypt_then_decrypt_with_invalid_algorithm() -> Result<(), PrivateMessageError> { + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = test_encrypt(message, &receiver_key_pair.public_key())?; + + println!("encrypted message={:?}", encrypted_message); + + let mut decrypted_message = Vec::new(); + + match decrypt_private_message( + Aad::from(AAD), + Algorithm::Aes256Gcm, + &mut encrypted_message, + &receiver_key_pair, + &mut decrypted_message, + ) { + Ok(()) => panic!("Expected error rivateMessageError::Unspecified, found: Ok(())."), + Err(PrivateMessageError::Unspecified) => Ok(()), + Err(e) => panic!( + "Expected error rivateMessageError::Unspecified, found: {:?}.", + e + ), + } + } + + #[test] + fn encrypt_and_decrypt_ok() -> Result<(), PrivateMessageError> { + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = test_encrypt(message, &receiver_key_pair.public_key())?; + + println!("encrypted message={:?}", encrypted_message); + + let mut decrypted_message = Vec::new(); + + decrypt_private_message( + Aad::from(AAD), + Algorithm::Chacha20Poly1305, + &mut encrypted_message, + &receiver_key_pair, + &mut decrypted_message, + )?; + + println!("decrypted message={:?}", decrypted_message); + + assert_eq!(decrypted_message, message); + + Ok(()) + } + + fn test_encrypt( + message: &[u8], + receiver_public_key: &PublicKey, + ) -> Result<Vec<u8>, PrivateMessageError> { + let mut encrypted_message = Vec::new(); + + encrypt_private_message( + Aad::from(AAD), + Algorithm::Chacha20Poly1305, + &mut message.to_owned(), + receiver_public_key, + &mut encrypted_message, + )?; + + Ok(encrypted_message) + } +} -- GitLab