dup-crypto 0.58.0

Manage cryptographic operations for DUniter Protocols and the Duniter eco-system most broadly.
Documentation
//  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::{KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey}
//! };
//! use dup_crypto::private_message::{ChaChaRounds, 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 = KeyPairFromSeed32Generator::generate(Seed32::new([42u8; 32]));
//!
//! // Choose number of chacha rounds.
//! let chacha_rounds = ChaChaRounds::ChaCha20;
//!
//! // 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 = 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,
//!     authentication_policy,
//!     chacha_rounds,
//!     &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::{ChaChaRounds, DecryptedMessage};
//! use dup_crypto::seeds::Seed32;
//!
//! let receiver_key_pair = KeyPairFromSeed32Generator::generate(
//!     Seed32::from_base58("7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ").expect("invalid seed")
//! );
//!
//! let mut encrypted_message = vec![3, 81, 192, 79, 234, // ... several bytes hidden
//! # 127, 151, 145, 237, 209, 209, 213, 219, 29, 249, 21, 217, 231, 216, 147, 105, 39, 180, 181, 92, 97, 215, 153, 65, 104, 221, 236, 96, 151, 136, 3, 100, 109, 170, 117, 137, 66, 225, 189, 200, 38, 151, 219, 60, 78, 17, 146, 69, 35, 92, 186, 192, 69, 187, 44, 201, 163, 53, 16, 151, 212, 172, 120, 151, 241, 42, 79, 11, 77, 54, 21, 30, 206, 105, 94, 195, 177, 80, 58, 96, 28, 27, 99, 164, 39, 87, 49, 143, 185, 7, 137, 138, 189, 60, 98, 208, 169, 168, 236, 13, 86, 74, 177, 60, 197, 45, 222, 135, 193, 130, 161, 192, 56, 168, 169, 97, 8, 33, 101, 202, 180, 239, 178, 42, 139, 226, 59, 22, 228, 43, 245, 236, 204, 106, 86, 218, 88, 238, 215, 219, 4, 38, 88, 90, 42, 250, 27, 236, 204, 73, 53, 179, 39, 7, 124, 187, 126, 81, 4, 117, 244, 114, 88, 52, 214, 86, 168, 213, 201, 114, 248, 145, 212, 164, 189, 78, 8, 201, 178, 85, 12, 25, 248, 193, 247, 13, 103, 15, 50, 197, 17, 41, 93, 164, 36, 87, 97, 215, 216, 207, 183, 21, 236, 114, 227, 88, 235, 86, 72, 183, 49, 69, 176];
//!
//! let DecryptedMessage { message, sender_public_key, signature_opt } =
//!     dup_crypto::private_message::decrypt_private_message(
//!         b"service name - currency name",
//!         ChaChaRounds::ChaCha20,
//!         &mut encrypted_message,
//!         &receiver_key_pair,
//! )?;
//!
//! assert_eq!(
//!     message,
//!     b"Hello, this is a secret message, which can only be read by the recipient.",
//! );
//! assert_eq!{
//!     "5pFCsihCTDbFaysD6jDhvv7wUcZsSKoGWQ3Lm1QU5Z9t",
//!     &sender_public_key.to_string(),
//! }
//! assert_eq!(
//!     signature_opt,
//!     None
//! );
//!
//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(())
//! ```
//!

mod authentication;

pub use self::authentication::AuthenticationPolicy;

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 chacha20poly1305::aead::{AeadInPlace as _, NewAead};
use chacha20poly1305::{ChaCha12Poly1305, ChaCha20Poly1305, ChaCha8Poly1305};
use std::num::NonZeroU32;
use std::{convert::TryFrom, hint::unreachable_unchecked};
use zeroize::Zeroize;

type Key = chacha20poly1305::aead::Key<ChaCha20Poly1305>;
type Nonce =
    chacha20poly1305::aead::Nonce<chacha20poly1305::aead::generic_array::typenum::consts::U12>;
type Tag = chacha20poly1305::aead::Tag<chacha20poly1305::aead::generic_array::typenum::consts::U16>;

/// Metadata length
pub const METADATA_LEN: usize = CLEAR_FOOTER_LEN + AUTHENTICATION_DATAS_LEN;

const AUTHENTICATION_DATAS_LEN: usize = 97;
const CLEAR_FOOTER_LEN: usize = EPHEMERAL_PUBLIC_KEY_LEN + TAG_LEN;
const EPHEMERAL_PUBLIC_KEY_LEN: usize = 32;
const PBKDF2_ITERATIONS: u32 = 3;
const SENDER_PUBLIC_KEY_LEN: usize = 32;
const TAG_LEN: usize = 16;

/// Number of ChaCha rounds
#[derive(Clone, Copy, Debug)]
pub enum ChaChaRounds {
    /// 8 rounds
    ChaCha8,
    /// 12 rounds
    ChaCha12,
    /// 20 rounds
    ChaCha20,
}

/// 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 aead error
    UnspecifiedAeadError,
    /// Unspecified rand error
    UnspecifiedRandError,
}

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::UnspecifiedRandError
    }
}

#[derive(Zeroize)]
#[zeroize(drop)]
struct SharedSecret([u8; 44]);

impl Default for SharedSecret {
    fn default() -> Self {
        SharedSecret([0u8; 44])
    }
}

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<M>(
    additionally_authenticated_data: &[u8],
    authentication_policy: AuthenticationPolicy,
    chacha_rounds: ChaChaRounds,
    message: &mut M,
    receiver_public_key: &Ed25519PublicKey,
    sender_keypair: &Ed25519KeyPair,
) -> Result<(), PrivateMessageError>
where
    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 shared_secret = generate_shared_secret(
        ephemeral_keypair.public_key().datas.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(
        additionally_authenticated_data,
        chacha_rounds,
        message.as_mut(),
        shared_secret,
    )?;

    // write clear footer (tag and ephemeral_public_key)
    let mut clear_footer = arrayvec::ArrayVec::<u8, 48>::new();
    clear_footer
        .try_extend_from_slice(tag.as_ref())
        .unwrap_or_else(|_| unsafe { unreachable_unchecked() }); // It's safe because the tag is 16 bytes long.
    clear_footer
        .try_extend_from_slice(ephemeral_keypair.public_key().datas.as_ref())
        .unwrap_or_else(|_| unsafe { unreachable_unchecked() }); // It's safe because the public key is 32 bytes long.
    message.extend(clear_footer.into_iter());

    Ok(())
}

/// Decrypted message
pub struct DecryptedMessage<'m> {
    /// decrypted message content
    pub message: &'m [u8],
    /// Sender public key
    pub sender_public_key: Ed25519PublicKey,
    /// Optional signature
    pub signature_opt: Option<Signature>,
}

/// 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>(
    additionally_authenticated_data: &[u8],
    chacha_rounds: ChaChaRounds,
    encrypted_message: &'m mut [u8],
    receiver_key_pair: &Ed25519KeyPair,
) -> Result<DecryptedMessage<'m>, PrivateMessageError> {
    // Read clear footer (tag and ephemeral public key)
    let len = encrypted_message.len();
    let clear_footer_begin = len - EPHEMERAL_PUBLIC_KEY_LEN - TAG_LEN;
    let tag_end = len - EPHEMERAL_PUBLIC_KEY_LEN;
    let tag = Tag::from_slice(&encrypted_message[clear_footer_begin..tag_end]).to_owned();
    let sender_ephemeral_public_key =
        Ed25519PublicKey::try_from(&encrypted_message[(len - EPHEMERAL_PUBLIC_KEY_LEN)..])
            .map_err(PrivateMessageError::InvalidEphemeralPubKey)?;

    // Compute DH exchange (receiver_secret_key, ephemeral_public_key)
    // and derive symmetric_key and nonce from shared secret
    let shared_secret = generate_shared_secret(
        &sender_ephemeral_public_key.datas.as_ref(),
        &receiver_key_pair.seed(),
        &sender_ephemeral_public_key,
    );

    // Decrypt message
    decrypt(
        additionally_authenticated_data,
        chacha_rounds,
        &mut encrypted_message[..(len - CLEAR_FOOTER_LEN)],
        shared_secret,
        &tag,
    )?;

    // Verify authentication proof
    let authent_end = clear_footer_begin;
    let authent_begin = authent_end - AUTHENTICATION_DATAS_LEN;
    let (sender_public_key, sig_opt) = verify_authentication_proof(
        receiver_key_pair,
        &encrypted_message[..authent_begin],
        &encrypted_message[authent_begin..authent_end],
    )?;

    Ok(DecryptedMessage {
        message: &encrypted_message[..authent_begin],
        sender_public_key,
        signature_opt: sig_opt,
    })
}

fn generate_shared_secret(
    ephemeral_public_key: &[u8],
    exchange_secret_key: &Seed32,
    exchange_public_key: &Ed25519PublicKey,
) -> SharedSecret {
    diffie_hellman(
        X25519SecretKey::from(exchange_secret_key),
        X25519PublicKey::from(exchange_public_key),
        |key_material| derive(key_material, ephemeral_public_key),
    )
}

#[cfg(target_arch = "wasm32")]
#[cfg(not(tarpaulin_include))]
fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret {
    let mut shared_secret = SharedSecret::default();
    let mut hmac = cryptoxide::hmac::Hmac::new(cryptoxide::sha2::Sha512::new(), seed);
    cryptoxide::pbkdf2::pbkdf2(&mut hmac, salt, PBKDF2_ITERATIONS, shared_secret.as_mut());
    shared_secret
}
#[cfg(not(target_arch = "wasm32"))]
fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret {
    let mut shared_secret = SharedSecret::default();
    ring::pbkdf2::derive(
        ring::pbkdf2::PBKDF2_HMAC_SHA512,
        unsafe { NonZeroU32::new_unchecked(PBKDF2_ITERATIONS) },
        salt,
        seed,
        shared_secret.as_mut(),
    );
    shared_secret
}

fn encrypt(
    associated_data: &[u8],
    chacha_rounds: ChaChaRounds,
    message: &mut [u8],
    shared_secret: SharedSecret,
) -> Result<Tag, PrivateMessageError> {
    let symmetric_key = Key::from_slice(&shared_secret.as_ref()[..32]);
    let nonce = Nonce::from_slice(&shared_secret.as_ref()[32..44]);
    match chacha_rounds {
        ChaChaRounds::ChaCha8 => ChaCha8Poly1305::new(symmetric_key)
            .encrypt_in_place_detached(&nonce, associated_data, message)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
        ChaChaRounds::ChaCha12 => ChaCha12Poly1305::new(symmetric_key)
            .encrypt_in_place_detached(&nonce, associated_data, message)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
        ChaChaRounds::ChaCha20 => ChaCha20Poly1305::new(symmetric_key)
            .encrypt_in_place_detached(&nonce, associated_data, message)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
    }
}

fn decrypt(
    associated_data: &[u8],
    chacha_rounds: ChaChaRounds,
    encrypted_message: &mut [u8],
    shared_secret: SharedSecret,
    tag: &Tag,
) -> Result<(), PrivateMessageError> {
    let symmetric_key = Key::from_slice(&shared_secret.as_ref()[..32]);
    let nonce = Nonce::from_slice(&shared_secret.as_ref()[32..44]);

    match chacha_rounds {
        ChaChaRounds::ChaCha8 => ChaCha8Poly1305::new(symmetric_key)
            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
        ChaChaRounds::ChaCha12 => ChaCha12Poly1305::new(symmetric_key)
            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
        ChaChaRounds::ChaCha20 => ChaCha20Poly1305::new(symmetric_key)
            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use crate::keys::ed25519::KeyPairFromSeed32Generator;
    use crate::keys::KeyPair;
    use unwrap::unwrap;

    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(
            b"invalid aad",
            ChaChaRounds::ChaCha20,
            &mut encrypted_message,
            &receiver_key_pair,
        ) {
            Ok(_) => {
                panic!("Expected error PrivateMessageError::UnspecifiedAeadError, found: Ok(()).")
            }
            Err(PrivateMessageError::UnspecifiedAeadError) => Ok(()),
            Err(e) => panic!(
                "Expected error PrivateMessageError::UnspecifiedAeadError, 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,
            ChaChaRounds::ChaCha12,
            &mut encrypted_message,
            &receiver_key_pair,
        ) {
            Ok(_) => {
                panic!("Expected error PrivateMessageError::UnspecifiedAeadError, found: Ok(()).")
            }
            Err(PrivateMessageError::UnspecifiedAeadError) => Ok(()),
            Err(e) => panic!(
                "Expected error PrivateMessageError::UnspecifiedAeadError, found: {:?}.",
                e
            ),
        }
    }

    #[test]
    fn encrypt_and_decrypt_ok() -> Result<(), PrivateMessageError> {
        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
        let receiver_key_pair = KeyPairFromSeed32Generator::generate(unwrap!(Seed32::from_base58(
            "7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ"
        )));

        let message = MESSAGE;

        let mut encrypted_message =
            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;

        println!("encrypted message={:?}", encrypted_message);

        let DecryptedMessage {
            message: decrypted_message,
            sender_public_key,
            signature_opt,
        } = decrypt_private_message(
            AAD,
            ChaChaRounds::ChaCha20,
            &mut encrypted_message,
            &receiver_key_pair,
        )?;

        println!("decrypted message={:?}", decrypted_message);

        assert_eq!(decrypted_message, message);
        assert_eq!(sender_public_key, sender_key_pair.public_key());
        assert_eq!(signature_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,
            AuthenticationPolicy::PrivateAuthentication,
            ChaChaRounds::ChaCha20,
            &mut encrypted_message,
            receiver_public_key,
            sender_keypair,
        )?;

        Ok(encrypted_message)
    }
}