Skip to content
Snippets Groups Projects
Commit fceb201b authored by Éloïs's avatar Éloïs
Browse files

[feat] add module private message encryption

Closes #3
parent b312dc44
No related branches found
No related tags found
No related merge requests found
Pipeline #8305 failed
...@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] - ReleaseDate ## [Unreleased] - ReleaseDate
### Added
- Private message encryption/decryption with authentification
## [0.12.1] - 2020-03-03 ## [0.12.1] - 2020-03-03
### Fixed ### Fixed
......
...@@ -285,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" ...@@ -285,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"subtle", "subtle 1.0.0",
] ]
[[package]] [[package]]
...@@ -310,6 +310,19 @@ dependencies = [ ...@@ -310,6 +310,19 @@ dependencies = [
"memchr", "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]] [[package]]
name = "digest" name = "digest"
version = "0.8.1" version = "0.8.1"
...@@ -330,6 +343,7 @@ dependencies = [ ...@@ -330,6 +343,7 @@ dependencies = [
"bs58", "bs58",
"byteorder", "byteorder",
"criterion", "criterion",
"curve25519-dalek",
"ring", "ring",
"scrypt", "scrypt",
"serde", "serde",
...@@ -372,6 +386,17 @@ dependencies = [ ...@@ -372,6 +386,17 @@ dependencies = [
"typenum", "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]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
...@@ -579,6 +604,15 @@ dependencies = [ ...@@ -579,6 +604,15 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rayon" name = "rayon"
version = "1.3.0" version = "1.3.0"
...@@ -784,6 +818,12 @@ version = "1.0.0" ...@@ -784,6 +818,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "subtle"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.14" version = "1.0.14"
...@@ -923,6 +963,12 @@ dependencies = [ ...@@ -923,6 +963,12 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.58" version = "0.2.58"
......
...@@ -39,10 +39,11 @@ harness = false ...@@ -39,10 +39,11 @@ harness = false
required-features = ["criterion"] required-features = ["criterion"]
[features] [features]
default = ["dewif", "rand", "ser"] default = ["dewif", "private_message", "rand", "ser"]
aes256 = ["aes"] aes256 = ["aes"]
dewif = ["aes256", "arrayvec"] dewif = ["aes256", "arrayvec"]
x25519 = [] x25519 = []
private_message = ["arrayvec", "x25519", "rand"]
rand = [] rand = []
ser = ["serde"] ser = ["serde"]
...@@ -719,6 +719,7 @@ Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 ...@@ -719,6 +719,7 @@ Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
pubkey_bytes.copy_from_slice(&bytes[32..64]); pubkey_bytes.copy_from_slice(&bytes[32..64]);
let keypair = KeyPairFromSeed32Generator::generate(Seed32::new(seed)); let keypair = KeyPairFromSeed32Generator::generate(Seed32::new(seed));
println!("seed={}", keypair.seed());
assert_eq!( assert_eq!(
"8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7", "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7",
&keypair.public_key().to_base58() &keypair.public_key().to_base58()
......
...@@ -48,6 +48,8 @@ pub mod bases; ...@@ -48,6 +48,8 @@ pub mod bases;
pub mod dewif; pub mod dewif;
pub mod hashs; pub mod hashs;
pub mod keys; pub mod keys;
#[cfg(feature = "private_message")]
pub mod private_message;
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
pub mod rand; pub mod rand;
pub mod seeds; pub mod seeds;
// 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 = 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."[..],
//! );
//!
//! # 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,
};
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
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 mut [u8], 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;
verify_authentication_proof(
receiver_key_pair,
&encrypted_message[..authent_begin],
&encrypted_message[authent_begin..authent_end],
)?;
Ok(&mut encrypted_message[..authent_begin])
}
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 = decrypt_private_message(
Aad::from(AAD),
Algorithm::Chacha20Poly1305,
&mut encrypted_message,
&receiver_key_pair,
)?;
println!("decrypted message={:?}", decrypted_message);
assert_eq!(decrypted_message, message);
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)
}
}
// 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<(), 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)],
);
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 => {
sender_public_key
.verify(message, &Signature(authent_proof.0))
.map_err(|_| PrivateMessageError::InvalidAuthenticationProof)?;
}
}
Ok(())
}
#[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(())
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment