diff --git a/CHANGELOG.md b/CHANGELOG.md index b59fa8cef9579dcc515777811221f7932ca43eda..66db7a96212ffa37ee47460d15a6ba7ee40a21fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added + +- Private message encryption/decryption with authentification + ## [0.12.1] - 2020-03-03 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index dc2ddc1d85049a3195c2f5604624cb267eaad4f5..8cb891672cf117f91e666f9568a06091804b47a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ "generic-array", - "subtle", + "subtle 1.0.0", ] [[package]] @@ -310,6 +310,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "curve25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" +dependencies = [ + "byteorder", + "digest", + "rand_core", + "subtle 2.2.2", + "zeroize", +] + [[package]] name = "digest" version = "0.8.1" @@ -330,6 +343,7 @@ dependencies = [ "bs58", "byteorder", "criterion", + "curve25519-dalek", "ring", "scrypt", "serde", @@ -372,6 +386,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.3.1" @@ -579,6 +604,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.3.0" @@ -784,6 +818,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +[[package]] +name = "subtle" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" + [[package]] name = "syn" version = "1.0.14" @@ -923,6 +963,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasm-bindgen" version = "0.2.58" diff --git a/Cargo.toml b/Cargo.toml index b145e0e983c343d35c92ff5c2a62cde23bdd4003..826600e11d6f4b59cd8ef3f8f41ba84abc8fe5de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,10 +39,11 @@ harness = false required-features = ["criterion"] [features] -default = ["dewif", "rand", "ser"] +default = ["dewif", "private_message", "rand", "ser"] aes256 = ["aes"] dewif = ["aes256", "arrayvec"] x25519 = [] +private_message = ["arrayvec", "x25519", "rand"] rand = [] ser = ["serde"] diff --git a/src/keys/ed25519.rs b/src/keys/ed25519.rs index e7d178b23ed8d90a9386c38bec4a7ab7923136d4..f13ee32519c6f01f70dbdfafd9ef382fb3394377 100644 --- a/src/keys/ed25519.rs +++ b/src/keys/ed25519.rs @@ -719,6 +719,7 @@ Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 pubkey_bytes.copy_from_slice(&bytes[32..64]); let keypair = KeyPairFromSeed32Generator::generate(Seed32::new(seed)); + println!("seed={}", keypair.seed()); assert_eq!( "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7", &keypair.public_key().to_base58() diff --git a/src/lib.rs b/src/lib.rs index c1f0f3bff4abbb3b8a5cf645214bcf64a7e3d6a4..06d8472f9d4e22bf1edea46b5a3592123fca87ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ //! * [Compute Sha256 hash](./hashs/index.html#compute-sha256-hash) //! * [Ed25519](./keys/index.html) //! * [generate and use ed25519 key-pair](./keys/index.html#generate-and-use-ed25519-key-pair) +//! * [Private message encryption with authentification](./private_message/index.html) +//! * [Encrypt a private message (sender side)](./private_message/index.html#encrypt-a-private-message-sender-side) +//! * [Decrypt a private message (receiver side)](./private_message/index.html#decrypt-a-private-message-receiver-side) //! #![deny( @@ -48,6 +51,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 0000000000000000000000000000000000000000..1655c7aaa07fc95f06938cc1be0fb11ca0e05829 --- /dev/null +++ b/src/private_message.rs @@ -0,0 +1,520 @@ +// 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/>. + +//! Private message encryption/decryption +//! +//! ## Encrypt a private message (sender side) +//! +//! **Warning**: Take the time to study which is the authentication policy adapted to **your specific use case**. +//! Choosing an unsuitable authentication policy can be **dramatic for your end users**. +//! Read the documentation of [AuthenticationPolicy](./enum.AuthenticationPolicy.html). +//! +//! ```; +//! use dup_crypto::keys::{ +//! KeyPair, PublicKey, +//! ed25519::{KeyPairFromSaltedPasswordGenerator, PublicKey as Ed25519PublicKey, SaltedPassword} +//! }; +//! use dup_crypto::private_message::{Aad, Algorithm, AuthenticationPolicy, METADATA_LEN}; +//! use dup_crypto::seeds::Seed32; +//! +//! // Take the time to study which is the authentication policy adapted +//! // to your specific use case. +//! // Read `dup_crypto::private_message::AuthenticationPolicy` documentation. +//! let authentication_policy = AuthenticationPolicy::PrivateAuthentication; +//! +//! // Regardless of the authentication policy chosen, the sender's key-pair is required. +//! let sender_key_pair = KeyPairFromSaltedPasswordGenerator::with_default_parameters() +//! .generate(SaltedPassword::new("sender salt".to_owned(), "sender password".to_owned())); +//! +//! // Choose an encryption algorithm adapted to your specific use case. +//! // Read `dup_crypto::private_message::Algorithm` documentation. +//! let encryption_algo = Algorithm::Chacha20Poly1305; +//! +//! // Aad value must be known by the software that will decipher the message, it can be the +//! // name of the service followed by the name of the network (name of the currency for example). +//! // This field is only used to ensure that there is no interference between different services +//! // and/or networks. +//! let aad = Aad::from(b"service name - currency name"); +//! +//! // Define receiver and message content +//! // The message must be mutable because for performance reasons the encryption is applied +//! // directly to the bytes of the message (the message is never copied). +//! let receiver_public_key = Ed25519PublicKey::from_base58( +//! "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7" +//! ).expect("invalid public key"); +//! let message = b"This is a secret message, which can only be read by the recipient."; +//! +//! // It is up to you to create the buffer that will contain the encrypted message. +//! // This gives you the freedom to choose how to allocate the memory space and in +//! // which type of "container" to store the bytes of the encrypted message. +//! // Metadata needed for decryption and authentication will be added to your message, +//! // you must make sure that your buffer has enough capacity to hold this metadata. +//! let mut buffer: Vec<u8> = Vec::with_capacity(message.len() + METADATA_LEN); +//! buffer.extend(&message[..]); +//! +//! // Finally, authenticate and encrypt the message. +//! dup_crypto::private_message::encrypt_private_message( +//! aad, +//! encryption_algo, +//! authentication_policy, +//! &mut buffer, +//! &receiver_public_key, +//! &sender_key_pair, +//! )?; +//! +//! // Send message to the recipient by any way.. +//! +//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(()) +//! ``` +//! +//! ## Decrypt a private message (receiver side) +//! +//! ``` +//! use dup_crypto::keys::{KeyPair, ed25519::KeyPairFromSeed32Generator}; +//! use dup_crypto::private_message::{Aad, Algorithm}; +//! use dup_crypto::seeds::Seed32; +//! +//! let receiver_key_pair = KeyPairFromSeed32Generator::generate( +//! Seed32::from_base58("7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ").expect("invalid seed") +//! ); +//! +//! let mut encrypted_message = vec![221u8, 252, 176, 127, 197, // ... several bytes hidden +//! # 20, 191, 154, 245, 206, 154, 71, 71, +//! # 169, 240, 50, 142, 231, 143, 239, 55, 31, 117, 197, 66, 90, 232, 14, 108, 203, 188, 70, 123, 75, +//! # 216, 55, 5, 57, 60, 35, 185, 99, 147, 23, 51, 57, 93, 213, 149, 101, 24, 195, 18, 168, 37, 71, 182, +//! # 220, 198, 250, 72, 199, 21, 66, 15, 57, 144, 247, 54, 19, 30, 134, 210, 227, 205, 113, 142, 15, 77, +//! # 76, 223, 132, 38, 237, 100, 139, 227, 115, 49, 216, 102, 120, 124, 84, 208, 85, 242, 141, 216, 145, +//! # 10, 17, 168, 219, 129, 199, 149, 188, 210, 123, 79, 128, 76, 159, 133, 251, 95, 29, 238, 43, 225, +//! # 211, 43, 197, 237, 93, 79, 243, 120, 227, 153, 79, 57, 1, 23, 233, 167, 110, 210, 16, 52, 16, 73, 13, +//! # 214, 16, 223, 17, 175, 228, 211, 151, 79, 227, 14, 56, 135, 77, 73, 36, 22, 115, 77, 201, 114, 38, +//! # 206, 240, 212, 129, 247, 111, 165, 182, 98, 176, 247, 69, 198, 34, 71, 26, 176, 147, 205, 173, 50, +//! # 247, 151, 148, 197, 162, 88, 254, 185, 149, 108, 2, 137, 139, 66, 82, 168, 213, 118, 218, 188, 238, +//! # 147, 89, 156]; +//! +//! let (message, signature_opt) = dup_crypto::private_message::decrypt_private_message( +//! Aad::from(b"service name - currency name"), +//! Algorithm::Chacha20Poly1305, +//! &mut encrypted_message, +//! &receiver_key_pair, +//! )?; +//! +//! assert_eq!( +//! message, +//! &b"This is a secret message, which can only be read by the recipient."[..], +//! ); +//! assert_eq!( +//! signature_opt, +//! None +//! ); +//! +//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(()) +//! ``` +//! + +mod authentication; + +pub use self::authentication::AuthenticationPolicy; +pub use ring::aead::Aad; + +use self::authentication::{ + generate_authentication_proof, verify_authentication_proof, write_anthentication_datas, +}; +use crate::keys::ed25519::{ + Ed25519KeyPair, KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey, Signature, +}; +use crate::keys::x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey}; +use crate::keys::{KeyPair, PubkeyFromBytesError}; +use crate::rand::UnspecifiedRandError; +use crate::seeds::Seed32; +use ring::aead::{LessSafeKey, Nonce, Tag, UnboundKey}; +use ring::pbkdf2; +use std::convert::TryFrom; +use std::num::NonZeroU32; +use zeroize::Zeroize; + +/// Metadata length +pub const METADATA_LEN: usize = 129; // EPHEMERAL_PUBLIC_KEY_LEN + AUTHENTICATION_DATAS_LEN + +const ITERATIONS: u32 = 3; +const SENDER_PUBLIC_KEY_LEN: usize = 32; +const EPHEMERAL_PUBLIC_KEY_LEN: usize = 32; +const AUTHENTICATION_DATAS_LEN: usize = 97; + +/// Private message encryption algorithm +/// If your program is susceptible to running on machines that do not provide hardware +/// acceleration for AES (some phones, embedded devices, old computers, etc) then you +/// should choose `Chacha20Poly1305`. Even on devices with hardware acceleration for AES, +/// the performance of `Chacha20Poly1305` is often equivalent to `Aes256Gcm`, so only choose +/// `Aes256Gcm` if you have strong reasons to do so. +#[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), + /// Invalid ephemeral pubkey + InvalidEphemeralPubkey(PubkeyFromBytesError), + /// Invalid sender pubkey + InvalidSenderPubkey(PubkeyFromBytesError), + /// Invalid authentication proof : invalid signature + InvalidAuthenticationProof, + /// 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 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, M>( + additionally_authenticated_data: Aad<A>, + algorithm: Algorithm, + authentication_policy: AuthenticationPolicy, + message: &mut M, + receiver_public_key: &Ed25519PublicKey, + sender_keypair: &Ed25519KeyPair, +) -> Result<(), PrivateMessageError> +where + A: AsRef<[u8]>, + M: AsRef<[u8]> + AsMut<[u8]> + Extend<u8>, +{ + // Generate ephemeral ed25519 keypair + let ephemeral_keypair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + // 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_keypair.public_key().as_ref(), + ephemeral_keypair.seed(), + &receiver_public_key, + )?; + + // Write encrypted footer (=authentication datas) + let encrypted_footer = write_anthentication_datas( + &sender_keypair.public_key(), + generate_authentication_proof( + authentication_policy, + sender_keypair, + receiver_public_key, + message.as_ref(), + ), + authentication_policy, + ); + message.extend(encrypted_footer); + + // Encrypt message + let tag = encrypt::<A>( + symmetric_key, + nonce, + additionally_authenticated_data, + message.as_mut(), + )?; + + // write clear footer (tag and ephemeral_public_key) + let mut clear_footer = arrayvec::ArrayVec::<[u8; 64]>::new(); + clear_footer + .try_extend_from_slice(tag.as_ref()) + .expect("too long tag"); + clear_footer + .try_extend_from_slice(ephemeral_keypair.public_key().as_ref()) + .expect("too long ephemeral_public_key"); + message.extend(clear_footer.into_iter()); + + Ok(()) +} + +/// Decrypt private message. +/// Return a reference to decrypted bytes and an optional signature. +/// If the authentication method chosen by the sender is `Signature`, +/// then the signature is necessarily returned. The signature is returned +/// to allow subsequent publication of proof that this particular message was sent by the sender. +pub fn decrypt_private_message<'m, A: AsRef<[u8]>>( + additionally_authenticated_data: Aad<A>, + algorithm: Algorithm, + encrypted_message: &'m mut [u8], + receiver_key_pair: &Ed25519KeyPair, +) -> Result<(&'m [u8], Option<Signature>), PrivateMessageError> { + // Get ephemeral public key + let len = encrypted_message.len(); + let ephemeral_public_key = &encrypted_message[(len - EPHEMERAL_PUBLIC_KEY_LEN)..]; + + // 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, + &receiver_key_pair.seed(), + &Ed25519PublicKey::try_from(ephemeral_public_key) + .map_err(PrivateMessageError::InvalidEphemeralPubkey)?, + )?; + + // Decrypt message + decrypt::<A>( + symmetric_key, + nonce, + additionally_authenticated_data, + &mut encrypted_message[..(len - EPHEMERAL_PUBLIC_KEY_LEN)], + )?; + + // Verify authentication proof + let tag_len = algorithm.to_ring_algo().tag_len(); + let authent_end = len - EPHEMERAL_PUBLIC_KEY_LEN - tag_len; + let authent_begin = authent_end - AUTHENTICATION_DATAS_LEN; + let sig_opt = verify_authentication_proof( + receiver_key_pair, + &encrypted_message[..authent_begin], + &encrypted_message[authent_begin..authent_end], + )?; + + Ok((&encrypted_message[..authent_begin], sig_opt)) +} + +fn generate_symmetric_key_and_nonce( + algorithm: Algorithm, + ephemeral_public_key: &[u8], + exchange_secret_key: &Seed32, + exchange_public_key: &Ed25519PublicKey, +) -> Result<(UnboundKey, Nonce), PrivateMessageError> { + let shared_secret = diffie_hellman( + X25519SecretKey::from(exchange_secret_key), + X25519PublicKey::from(exchange_public_key), + |key_material| derive(key_material, 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<A: AsRef<[u8]>>( + key: UnboundKey, + nonce: Nonce, + aad: Aad<A>, + message: &mut [u8], +) -> Result<Tag, PrivateMessageError> { + let key = LessSafeKey::new(key); + Ok(key.seal_in_place_separate_tag(nonce, aad, message.as_mut())?) +} + +fn decrypt<A: AsRef<[u8]>>( + key: UnboundKey, + nonce: Nonce, + aad: Aad<A>, + encrypted_message: &mut [u8], +) -> Result<(), PrivateMessageError> { + let key = LessSafeKey::new(key); + key.open_in_place(nonce, aad, encrypted_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 sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let encrypted_message1 = + test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?; + + let encrypted_message2 = + test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?; + + 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 sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = + test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?; + + println!("encrypted message={:?}", encrypted_message); + + match decrypt_private_message( + Aad::from(b"invalid aad"), + Algorithm::Chacha20Poly1305, + &mut encrypted_message, + &receiver_key_pair, + ) { + 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 sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = + test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?; + + println!("encrypted message={:?}", encrypted_message); + + match decrypt_private_message( + Aad::from(AAD), + Algorithm::Aes256Gcm, + &mut encrypted_message, + &receiver_key_pair, + ) { + 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 sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let message = MESSAGE; + + let mut encrypted_message = + test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?; + + println!("encrypted message={:?}", encrypted_message); + + let (decrypted_message, sig_opt) = decrypt_private_message( + Aad::from(AAD), + Algorithm::Chacha20Poly1305, + &mut encrypted_message, + &receiver_key_pair, + )?; + + println!("decrypted message={:?}", decrypted_message); + + assert_eq!(decrypted_message, message); + assert_eq!(sig_opt, None); + + Ok(()) + } + + fn test_encrypt( + message: &[u8], + receiver_public_key: &Ed25519PublicKey, + sender_keypair: &Ed25519KeyPair, + ) -> Result<Vec<u8>, PrivateMessageError> { + let mut encrypted_message = Vec::new(); + encrypted_message.extend(message); + + encrypt_private_message( + Aad::from(AAD), + Algorithm::Chacha20Poly1305, + AuthenticationPolicy::PrivateAuthentication, + &mut encrypted_message, + receiver_public_key, + sender_keypair, + )?; + + Ok(encrypted_message) + } +} diff --git a/src/private_message/authentication.rs b/src/private_message/authentication.rs new file mode 100644 index 0000000000000000000000000000000000000000..52f626f3554a71adc4e3e3787b4e8582c2e15b24 --- /dev/null +++ b/src/private_message/authentication.rs @@ -0,0 +1,311 @@ +// 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/>. + +//! Handle private message authentication policy + +use super::{PrivateMessageError, AUTHENTICATION_DATAS_LEN, SENDER_PUBLIC_KEY_LEN}; +use crate::keys::ed25519::{Ed25519KeyPair, PublicKey as Ed25519PublicKey, Signature}; +use crate::keys::x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey}; +use crate::keys::{KeyPair, PublicKey, Signator}; +use ring::digest; +use std::convert::TryFrom; + +#[derive(Clone, Copy, Debug)] +/// Authentication policy. +/// +/// **Warning**: Take the time to study which is the authentication policy adapted to **your specific use case**. +/// Choosing an unsuitable authentication policy can be **dramatic for your end users**. +pub enum AuthenticationPolicy { + /// Only the sender and the recipient have proof that the message was written by one of them. + /// The recipient knows that he is not the author of the message so he has proof that the message was necessarily written by the sender. + /// If your use case is the encrypted correspondence between machines in a decentralized network, + /// and you sometimes need to prove that a machine has sent this or that message (to prove for example that it has not honored a commitment), + /// then choose policy `Signature` instead. + PrivateAuthentication, + /// The sender proves that he is the author of the message. + /// If the message is publicly disclosed, everyone will have proof that the sender is indeed the one who wrote the message. + /// In certain uses this can be harmful to the sender: in case of conflict with the recipient, + /// the latter may threaten to disclose their private correspondence to blackmail the sender. + /// If your use case is private messaging between humans, choose method `PrivateAuthentication` instead. + Signature, +} + +impl Into<u8> for AuthenticationPolicy { + fn into(self) -> u8 { + match self { + Self::PrivateAuthentication => 0, + Self::Signature => 1, + } + } +} + +impl From<u8> for AuthenticationPolicy { + fn from(source: u8) -> Self { + match source { + 0 => Self::PrivateAuthentication, + _ => Self::Signature, + } + } +} + +pub(crate) struct AuthenticationProof([u8; 64]); + +impl AsRef<[u8]> for AuthenticationProof { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +pub(crate) fn write_anthentication_datas( + sender_public_key: &Ed25519PublicKey, + authent_proof: AuthenticationProof, + authent_policy: AuthenticationPolicy, +) -> impl AsRef<[u8]> + IntoIterator<Item = u8> { + let mut authent_datas = arrayvec::ArrayVec::<[u8; 128]>::new(); + authent_datas + .try_extend_from_slice(sender_public_key.as_ref()) + .expect("too long sender public key"); + authent_datas + .try_extend_from_slice(authent_proof.as_ref()) + .expect("too long authent_proof"); + authent_datas.push(authent_policy.into()); + authent_datas +} + +pub(crate) fn generate_authentication_proof( + authentication_policy: AuthenticationPolicy, + sender_keypair: &Ed25519KeyPair, + receiver_public_key: &Ed25519PublicKey, + message: &[u8], +) -> AuthenticationProof { + AuthenticationProof(match authentication_policy { + AuthenticationPolicy::PrivateAuthentication => diffie_hellman( + X25519SecretKey::from(sender_keypair.seed()), + X25519PublicKey::from(receiver_public_key), + |key_material| { + let mut hash = [0u8; 64]; + let mut ctx = digest::Context::new(&digest::SHA512); + ctx.update(message.as_ref()); + ctx.update(key_material); + let digest = ctx.finish(); + hash.copy_from_slice(digest.as_ref()); + hash + }, + ), + AuthenticationPolicy::Signature => { + sender_keypair.generate_signator().sign(message.as_ref()).0 + } + }) +} + +pub(crate) fn verify_authentication_proof( + receiver_key_pair: &Ed25519KeyPair, + message: &[u8], + authentication_datas: &[u8], +) -> Result<Option<Signature>, PrivateMessageError> { + let sender_public_key = + Ed25519PublicKey::try_from(&authentication_datas[..SENDER_PUBLIC_KEY_LEN]) + .map_err(PrivateMessageError::InvalidSenderPubkey)?; + let mut authent_proof = AuthenticationProof([0u8; 64]); + authent_proof.0.copy_from_slice( + &authentication_datas[SENDER_PUBLIC_KEY_LEN..(AUTHENTICATION_DATAS_LEN - 1)], + ); + let mut signature_opt = None; + match AuthenticationPolicy::from(authentication_datas[AUTHENTICATION_DATAS_LEN - 1]) { + AuthenticationPolicy::PrivateAuthentication => { + let expected_proof = AuthenticationProof(diffie_hellman( + X25519SecretKey::from(receiver_key_pair.seed()), + X25519PublicKey::from(&sender_public_key), + |key_material| { + let mut hash = [0u8; 64]; + let mut ctx = digest::Context::new(&digest::SHA512); + ctx.update(message.as_ref()); + ctx.update(key_material); + let digest = ctx.finish(); + hash.copy_from_slice(digest.as_ref()); + hash + }, + )); + for i in 0..64 { + if expected_proof.0[i] != authent_proof.0[i] { + return Err(PrivateMessageError::InvalidAuthenticationProof); + } + } + } + AuthenticationPolicy::Signature => { + signature_opt = Some(Signature(authent_proof.0)); + sender_public_key + .verify(message, &Signature(authent_proof.0)) + .map_err(|_| PrivateMessageError::InvalidAuthenticationProof)?; + } + } + Ok(signature_opt) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::keys::ed25519::KeyPairFromSeed32Generator; + use crate::seeds::Seed32; + + const MESSAGE: &[u8] = b"message"; + + #[test] + fn private_authent_ok() -> Result<(), PrivateMessageError> { + let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let authent_policy = AuthenticationPolicy::PrivateAuthentication; + + let authent_proof = generate_authentication_proof( + authent_policy, + &sender_key_pair, + &receiver_key_pair.public_key(), + MESSAGE, + ); + + let authent_datas = write_anthentication_datas( + &sender_key_pair.public_key(), + authent_proof, + authent_policy, + ); + + verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())?; + + Ok(()) + } + + #[test] + fn invalid_sender_pubkey() -> Result<(), PrivateMessageError> { + let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let authent_policy = AuthenticationPolicy::PrivateAuthentication; + + let authent_proof = generate_authentication_proof( + authent_policy, + &sender_key_pair, + &receiver_key_pair.public_key(), + MESSAGE, + ); + + let mut authent_datas: Vec<u8> = write_anthentication_datas( + &sender_key_pair.public_key(), + authent_proof, + authent_policy, + ) + .as_ref() + .to_vec(); + + let invalid_pubkey_bytes = [ + 206u8, 58, 67, 221, 20, 133, 0, 225, 86, 115, 26, 104, 142, 116, 140, 132, 119, 51, + 175, 45, 82, 225, 14, 195, 7, 107, 43, 212, 8, 37, 234, 23, + ]; + + authent_datas[..32].copy_from_slice(&invalid_pubkey_bytes); + + if let Err(PrivateMessageError::InvalidSenderPubkey(_)) = + verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref()) + { + Ok(()) + } else { + panic!("Expected PrivateMessageError::InvalidSenderPubkey.") + } + } + + #[test] + fn invalid_private_authent_proof() -> Result<(), PrivateMessageError> { + let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let authent_policy = AuthenticationPolicy::PrivateAuthentication; + + let authent_proof = generate_authentication_proof( + authent_policy, + &receiver_key_pair, // invalid key pair + &receiver_key_pair.public_key(), + MESSAGE, + ); + + let authent_datas = write_anthentication_datas( + &sender_key_pair.public_key(), + authent_proof, + authent_policy, + ); + + if let Err(PrivateMessageError::InvalidAuthenticationProof) = + verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref()) + { + Ok(()) + } else { + panic!("Expected PrivateMessageError::InvalidSenderPubkey.") + } + } + + #[test] + fn invalid_sig_authent_proof() -> Result<(), PrivateMessageError> { + let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let authent_policy = AuthenticationPolicy::Signature; + + let authent_proof = generate_authentication_proof( + authent_policy, + &receiver_key_pair, // invalid key pair + &receiver_key_pair.public_key(), + MESSAGE, + ); + + let authent_datas = write_anthentication_datas( + &sender_key_pair.public_key(), + authent_proof, + authent_policy, + ); + + if let Err(PrivateMessageError::InvalidAuthenticationProof) = + verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref()) + { + Ok(()) + } else { + panic!("Expected PrivateMessageError::InvalidSenderPubkey.") + } + } + + #[test] + fn sig_authent_ok() -> Result<(), PrivateMessageError> { + let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?); + + let authent_policy = AuthenticationPolicy::Signature; + + let authent_proof = generate_authentication_proof( + authent_policy, + &sender_key_pair, + &receiver_key_pair.public_key(), + MESSAGE, + ); + + let authent_datas = write_anthentication_datas( + &sender_key_pair.public_key(), + authent_proof, + authent_policy, + ); + + verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())?; + + Ok(()) + } +}