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