Skip to content
Snippets Groups Projects
Select Git revision
  • df90ee24c632e3c16e9d951d12fcf4b54cf2dd2d
  • master default protected
  • upgrade-polkadot-v1.19.1
  • 311_gtest_fixes
  • set_UniversalDividendApi_in_RuntimeApiCollection
  • tuxmain/fix-change-owner-key
  • network/gtest-1000 protected
  • upgradable-multisig
  • runtime/gtest-1000
  • network/gdev-800 protected
  • cgeek/issue-297-cpu
  • gdev-800-tests
  • update-docker-compose-rpc-squid-names
  • fix-252
  • 1000i100-test
  • hugo/tmp-0.9.1
  • network/gdev-803 protected
  • hugo/endpoint-gossip
  • network/gdev-802 protected
  • hugo/distance-precompute
  • network/gdev-900 protected
  • gtest-1000-0.11.1 protected
  • gtest-1000-0.11.0 protected
  • gtest-1000 protected
  • gdev-900-0.10.1 protected
  • gdev-900-0.10.0 protected
  • gdev-900-0.9.2 protected
  • gdev-800-0.8.0 protected
  • gdev-900-0.9.1 protected
  • gdev-900-0.9.0 protected
  • gdev-803 protected
  • gdev-802 protected
  • runtime-801 protected
  • gdev-800 protected
  • runtime-800-bis protected
  • runtime-800 protected
  • runtime-800-backup protected
  • runtime-701 protected
  • runtime-700 protected
  • runtime-600 protected
  • runtime-500 protected
41 results

git-conventions.md

Blame
    • Hugo Trentesaux's avatar
      ef73a0d0
      improve documentation (!101) · ef73a0d0
      Hugo Trentesaux authored and Éloïs's avatar Éloïs committed
      * fix
      
      * doc(end2end): detail test users
      
      * doc(all): update docker tag
      
      update docker image name from 0.2.0 to 0.3.0
      use "docker compose" everywhere instead of "docker-compose"
      improve table of content
      fix layout
      
      * doc(all): improve docs
      
      add logo to readme
      add table of content
      rewording
      complete
      
      * doc(all): fix typos
      ef73a0d0
      History
      improve documentation (!101)
      Hugo Trentesaux authored and Éloïs's avatar Éloïs committed
      * fix
      
      * doc(end2end): detail test users
      
      * doc(all): update docker tag
      
      update docker image name from 0.2.0 to 0.3.0
      use "docker compose" everywhere instead of "docker-compose"
      improve table of content
      fix layout
      
      * doc(all): improve docs
      
      add logo to readme
      add table of content
      rewording
      complete
      
      * doc(all): fix typos
    private_message.rs 19.08 KiB
    //  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, DecryptedMessage};
    //! 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 DecryptedMessage { message, sender_public_key, 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!{
    //!     "4HbjoXtWu9C2Q5LMu1RcWHS66k4dnvHspBxKWagFG5rJ",
    //!     &sender_public_key.to_string(),
    //! }
    //! 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().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::<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().datas.as_ref())
            .expect("too long ephemeral_public_key");
        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, A: AsRef<[u8]>>(
        additionally_authenticated_data: Aad<A>,
        algorithm: Algorithm,
        encrypted_message: &'m mut [u8],
        receiver_key_pair: &Ed25519KeyPair,
    ) -> Result<DecryptedMessage<'m>, 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 (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_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 DecryptedMessage {
                message: decrypted_message,
                sender_public_key,
                signature_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!(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::from(AAD),
                Algorithm::Chacha20Poly1305,
                AuthenticationPolicy::PrivateAuthentication,
                &mut encrypted_message,
                receiver_public_key,
                sender_keypair,
            )?;
    
            Ok(encrypted_message)
        }
    }