From a37e566ca1ee9332e7a3457ef825905d9631bf23 Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Wed, 25 Oct 2023 15:55:27 +0300 Subject: [PATCH] [refactor] #3422: transplant X25519Sha256 and ChaCha20Poly1305 code from ursa, migrate iroha_p2p to use it Signed-off-by: Nikita Strygin --- Cargo.lock | 3 + crypto/Cargo.toml | 50 +++-- crypto/src/encryption/chacha20poly1305.rs | 150 +++++++++++++++ crypto/src/encryption/mod.rs | 216 ++++++++++++++++++++++ crypto/src/kex/mod.rs | 34 ++++ crypto/src/kex/x25519.rs | 123 ++++++++++++ crypto/src/lib.rs | 37 +++- p2p/src/lib.rs | 10 +- p2p/src/peer.rs | 40 ++-- p2p/tests/integration/p2p.rs | 2 +- 10 files changed, 622 insertions(+), 43 deletions(-) create mode 100644 crypto/src/encryption/chacha20poly1305.rs create mode 100644 crypto/src/encryption/mod.rs create mode 100644 crypto/src/kex/mod.rs create mode 100644 crypto/src/kex/x25519.rs diff --git a/Cargo.lock b/Cargo.lock index 8334f6d56f5..a7ea5ea3251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2983,9 +2983,11 @@ dependencies = [ name = "iroha_crypto" version = "2.0.0-pre-rc.20" dependencies = [ + "aead", "amcl_wrapper", "arrayref", "blake2 0.10.6", + "chacha20poly1305", "curve25519-dalek", "derive_more", "ed25519-dalek", @@ -2997,6 +2999,7 @@ dependencies = [ "iroha_macro", "iroha_primitives", "iroha_schema", + "k256", "libsodium-sys-stable", "openssl-sys", "parity-scale-codec", diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 013239f8d44..15c6a943d31 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -14,7 +14,24 @@ workspace = true default = ["std"] # Enable static linkage of the rust standard library. # Please refer to https://docs.rust-embedded.org/book/intro/no-std.html -std = ["ursa"] +std = [ + "ursa", + "blake2", + "sha2", + "hkdf", + "amcl_wrapper", + "ed25519-dalek", + "curve25519-dalek", + "x25519-dalek", + "rand", + "rand_chacha", + "secp256k1", + "zeroize", + "arrayref", + "aead", + "chacha20poly1305", + "k256", +] # Force static linking vendored = ["openssl-sys"] # Replace structures and methods with FFI equivalents to facilitate dynamic linkage (mainly used in smartcontracts) @@ -38,28 +55,35 @@ openssl-sys = { version = "0.9.93", features = ["vendored"], optional = true } getset = { workspace = true } ursa = { workspace = true, optional = true } -blake2 = "0.10.6" -sha2 = "0.10.8" -hkdf = "0.12.3" -amcl_wrapper = "0.4.0" +blake2 = { version = "0.10.6", optional = true } +sha2 = { version = "0.10.8", optional = true } +hkdf = { version = "0.12.3", optional = true } +amcl_wrapper = { version = "0.4.0", optional = true } # TODO: bump these -ed25519-dalek = "1.0.1" -curve25519-dalek = "3.2.1" -x25519-dalek = "1.2.0" +ed25519-dalek = { version = "1.0.1", optional = true } +curve25519-dalek = { version = "3.2.1", optional = true } +x25519-dalek = { version = "1.2.0", optional = true } # TODO: bump me -rand = "0.7" +rand = { version = "0.7", optional = true } # TODO: bump me -rand_chacha = "0.2" +rand_chacha = { version = "0.2", optional = true } # TODO: bump me -secp256k1 = { version = "0.19", features = ["rand", "serde"] } +secp256k1 = { version = "0.19", features = ["rand", "serde"], optional = true } # TODO: bump me -zeroize = "1.1" -arrayref = "0.3.7" +zeroize = { version = "1.1", optional = true } +arrayref = { version = "0.3.7", optional = true } +# TODO: bump me +aead = { version = "0.3", optional = true } +# TODO: bump me +chacha20poly1305 = { version = "0.7", optional = true } + +# TODO: bump me +k256 = { version = "0.9.6", optional = true, features = ["ecdh", "ecdsa", "sha256"]} [dev-dependencies] hex-literal = { workspace = true } diff --git a/crypto/src/encryption/chacha20poly1305.rs b/crypto/src/encryption/chacha20poly1305.rs new file mode 100644 index 00000000000..5dfdf513186 --- /dev/null +++ b/crypto/src/encryption/chacha20poly1305.rs @@ -0,0 +1,150 @@ +use aead::{ + generic_array::{ + typenum::{U0, U12, U16, U32, U36}, + GenericArray, + }, + Aead, Error, NewAead, Payload, +}; +use chacha20poly1305::ChaCha20Poly1305 as SysChaCha20Poly1305; + +// use zeroize::Zeroize; +use super::Encryptor; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct ChaCha20Poly1305 { + key: GenericArray, +} + +impl Encryptor for ChaCha20Poly1305 { + type MinSize = U36; +} + +impl NewAead for ChaCha20Poly1305 { + type KeySize = U32; + + fn new(key: &GenericArray) -> Self { + Self { key: *key } + } +} + +impl Aead for ChaCha20Poly1305 { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; + + fn encrypt<'msg, 'aad>( + &self, + nonce: &GenericArray, + plaintext: impl Into>, + ) -> Result, Error> { + let aead = SysChaCha20Poly1305::new(&self.key); + let ciphertext = aead.encrypt(nonce, plaintext)?; + Ok(ciphertext) + } + + fn decrypt<'msg, 'aad>( + &self, + nonce: &GenericArray, + ciphertext: impl Into>, + ) -> Result, Error> { + let aead = SysChaCha20Poly1305::new(&self.key); + let plaintext = aead.decrypt(nonce, ciphertext)?; + Ok(plaintext) + } +} + +// default_impl!(ChaCha20Poly1305); +// drop_impl!(ChaCha20Poly1305); + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[test] + fn encrypt_easy_works() { + let aes = ChaCha20Poly1305::new(&ChaCha20Poly1305::key_gen().unwrap()); + let aad = Vec::new(); + let message = b"Hello and Goodbye!".to_vec(); + let res = aes.encrypt_easy(&aad, &message); + assert!(res.is_ok()); + let ciphertext = res.unwrap(); + let res = aes.decrypt_easy(&aad, &ciphertext); + assert!(res.is_ok()); + assert_eq!(message, res.unwrap()); + } + + #[test] + fn encrypt_works() { + let aes = ChaCha20Poly1305::new(&ChaCha20Poly1305::key_gen().unwrap()); + let nonce = ChaCha20Poly1305::nonce_gen().unwrap(); + let aad = b"encrypt test".to_vec(); + let message = b"Hello and Goodbye!".to_vec(); + let payload = Payload { + msg: message.as_slice(), + aad: aad.as_slice(), + }; + let res = aes.encrypt(&nonce, payload); + assert!(res.is_ok()); + let ciphertext = res.unwrap(); + let payload = Payload { + msg: ciphertext.as_slice(), + aad: aad.as_slice(), + }; + let res = aes.decrypt(&nonce, payload); + assert!(res.is_ok()); + assert_eq!(message, res.unwrap()); + } + + #[test] + fn decrypt_should_fail() { + let aes = ChaCha20Poly1305::new(&ChaCha20Poly1305::key_gen().unwrap()); + let aad = b"decrypt should fail".to_vec(); + let message = b"Hello and Goodbye!".to_vec(); + let res = aes.encrypt_easy(&aad, &message); + assert!(res.is_ok()); + let mut ciphertext = res.unwrap(); + + let aad = b"decrypt should succeed".to_vec(); + let res = aes.decrypt_easy(&aad, &ciphertext); + assert!(res.is_err()); + + let aad = b"decrypt should fail".to_vec(); + ciphertext[0] ^= ciphertext[1]; + let res = aes.decrypt_easy(&aad, &ciphertext); + assert!(res.is_err()); + } + + #[test] + fn buffer_works() { + let aes = ChaCha20Poly1305::new(&ChaCha20Poly1305::key_gen().unwrap()); + let aad = b"buffer works".to_vec(); + let dummytext = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + let mut ciphertext = Vec::new(); + let res = aes.encrypt_buffer(&aad, &mut Cursor::new(dummytext), &mut ciphertext); + assert!(res.is_ok()); + let mut plaintext = Vec::new(); + let res = aes.decrypt_buffer( + &aad, + &mut Cursor::new(ciphertext.as_slice()), + &mut plaintext, + ); + assert!(res.is_ok()); + assert_eq!(dummytext.to_vec(), plaintext); + } + + // #[test] + // fn zeroed_on_drop() { + // let mut aes = ChaCha20Poly1305::new(&ChaCha20Poly1305::key_gen().unwrap()); + // aes.zeroize(); + // + // fn as_bytes(x: &T) -> &[u8] { + // use std::{mem, slice}; + // + // unsafe { slice::from_raw_parts(x as *const T as *const u8, mem::size_of_val(x)) } + // } + // + // assert!(as_bytes(&aes.key).iter().all(|b| *b == 0u8)); + // } +} diff --git a/crypto/src/encryption/mod.rs b/crypto/src/encryption/mod.rs new file mode 100644 index 00000000000..1c1d80c53b8 --- /dev/null +++ b/crypto/src/encryption/mod.rs @@ -0,0 +1,216 @@ +// TODO: clean up & remove +#![allow(unused, missing_docs)] + +mod chacha20poly1305; + +use std::io::{Read, Write}; + +use aead::{ + generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, + Aead, Error, NewAead, Payload, +}; +use rand::{rngs::OsRng, RngCore}; + +pub use self::chacha20poly1305::ChaCha20Poly1305; +use crate::SessionKey; + +// Helpful for generating bytes using the operating system random number generator +pub fn random_vec(bytes: usize) -> Result, Error> { + let mut value = vec![0u8; bytes]; + OsRng.fill_bytes(value.as_mut_slice()); + Ok(value) +} + +pub fn random_bytes>() -> Result, Error> { + Ok(GenericArray::clone_from_slice( + random_vec(T::to_usize())?.as_slice(), + )) +} + +fn read_buffer(buffer: &mut I) -> Result, Error> { + let mut v = Vec::new(); + let bytes_read = buffer.read_to_end(&mut v).map_err(|_| Error)?; + v.truncate(bytes_read); + Ok(v) +} + +/// A generic symmetric encryption wrapper +/// +/// # Usage +/// +/// ``` +/// extern crate ursa; +/// use ursa::encryption::symm::prelude::*; +/// +/// let encryptor = SymmetricEncryptor::::default(); +/// let aad = b"Using Aes128Gcm to encrypt data"; +/// let message = b"Hidden message"; +/// let res = encryptor.encrypt_easy(aad.as_ref(), message.as_ref()); +/// assert!(res.is_ok()); +/// +/// let ciphertext = res.unwrap(); +/// let res = encryptor.decrypt_easy(aad.as_ref(), ciphertext.as_slice()); +/// assert!(res.is_ok()); +/// assert_eq!(res.unwrap().as_slice(), message); +/// ``` +#[derive(Debug, Clone)] +pub struct SymmetricEncryptor { + encryptor: E, +} + +impl SymmetricEncryptor { + pub fn new(encryptor: E) -> Self { + Self { encryptor } + } + + pub fn new_from_session_key(key: SessionKey) -> Self { + Self::new(::new(GenericArray::from_slice(&key.0))) + } + + pub fn new_with_key>(key: A) -> Result { + Ok(Self { + encryptor: ::new(GenericArray::from_slice(key.as_ref())), + }) + } + + // Encrypt `plaintext` and integrity protect `aad`. The result is the ciphertext. + // This method handles safely generating a `nonce` and prepends it to the ciphertext + pub fn encrypt_easy>(&self, aad: A, plaintext: A) -> Result, Error> { + self.encryptor.encrypt_easy(aad, plaintext) + } + + // Encrypt `plaintext` and integrity protect `aad`. The result is the ciphertext. + pub fn encrypt>( + &self, + nonce: A, + aad: A, + plaintext: A, + ) -> Result, Error> { + let nonce = GenericArray::from_slice(nonce.as_ref()); + let payload = Payload { + msg: plaintext.as_ref(), + aad: aad.as_ref(), + }; + self.encryptor.encrypt(nonce, payload) + } + + // Decrypt `ciphertext` using integrity protected `aad`. The result is the plaintext if successful + // or an error if the `ciphetext` cannot be decrypted due to tampering, an incorrect `aad` value, + // or incorrect key. + // `aad` must be the same value used in `encrypt_easy`. Expects the nonce to be prepended to + // the `ciphertext` + pub fn decrypt_easy>(&self, aad: A, ciphertext: A) -> Result, Error> { + self.encryptor.decrypt_easy(aad, ciphertext) + } + + // Decrypt `ciphertext` using integrity protected `aad`. The result is the plaintext if successful + // or an error if the `ciphetext` cannot be decrypted due to tampering, an incorrect `aad` value, + // or incorrect key. + // `aad` must be the same value used in `encrypt_easy`. + pub fn decrypt>( + &self, + nonce: A, + aad: A, + ciphertext: A, + ) -> Result, Error> { + let nonce = GenericArray::from_slice(nonce.as_ref()); + let payload = Payload { + msg: ciphertext.as_ref(), + aad: aad.as_ref(), + }; + self.encryptor.decrypt(nonce, payload) + } + + // Similar to `encrypt_easy` but reads from a stream instead of a slice + pub fn encrypt_buffer, I: Read, O: Write>( + &self, + aad: A, + plaintext: &mut I, + ciphertext: &mut O, + ) -> Result<(), Error> { + self.encryptor.encrypt_buffer(aad, plaintext, ciphertext) + } + + // Similar to `decrypt_easy` but reads from a stream instead of a slice + pub fn decrypt_buffer, I: Read, O: Write>( + &self, + aad: A, + ciphertext: &mut I, + plaintext: &mut O, + ) -> Result<(), Error> { + self.encryptor.decrypt_buffer(aad, ciphertext, plaintext) + } +} + +impl Default for SymmetricEncryptor { + fn default() -> Self { + SymmetricEncryptor { + encryptor: E::default(), + } + } +} + +/// Generic encryptor trait that all ciphers should extend. +pub trait Encryptor: Aead + NewAead { + /// The minimum size that the ciphertext will yield from plaintext + type MinSize: ArrayLength; + + fn encrypt_easy>(&self, aad: M, plaintext: M) -> Result, Error> { + let nonce = Self::nonce_gen()?; + let payload = Payload { + msg: plaintext.as_ref(), + aad: aad.as_ref(), + }; + let ciphertext = self.encrypt(&nonce, payload)?; + let mut result = nonce.to_vec(); + result.extend_from_slice(ciphertext.as_slice()); + Ok(result) + } + + fn decrypt_easy>(&self, aad: M, ciphertext: M) -> Result, Error> { + let ciphertext = ciphertext.as_ref(); + if ciphertext.len() < Self::MinSize::to_usize() { + return Err(Error); + } + + let nonce = GenericArray::from_slice(&ciphertext[..Self::NonceSize::to_usize()]); + let payload = Payload { + msg: &ciphertext[Self::NonceSize::to_usize()..], + aad: aad.as_ref(), + }; + let plaintext = self.decrypt(&nonce, payload)?; + Ok(plaintext) + } + + fn encrypt_buffer, I: Read, O: Write>( + &self, + aad: M, + plaintext: &mut I, + ciphertext: &mut O, + ) -> Result<(), Error> { + let p = read_buffer(plaintext)?; + let c = self.encrypt_easy(aad.as_ref(), p.as_slice())?; + ciphertext.write_all(c.as_slice()).map_err(|_| Error)?; + Ok(()) + } + + fn decrypt_buffer, I: Read, O: Write>( + &self, + aad: M, + ciphertext: &mut I, + plaintext: &mut O, + ) -> Result<(), Error> { + let c = read_buffer(ciphertext)?; + let p = self.decrypt_easy(aad.as_ref(), c.as_slice())?; + plaintext.write_all(p.as_slice()).map_err(|_| Error)?; + Ok(()) + } + + fn key_gen() -> Result, Error> { + random_bytes() + } + + fn nonce_gen() -> Result, Error> { + random_bytes() + } +} diff --git a/crypto/src/kex/mod.rs b/crypto/src/kex/mod.rs new file mode 100644 index 00000000000..23de4536689 --- /dev/null +++ b/crypto/src/kex/mod.rs @@ -0,0 +1,34 @@ +// TODO: clean up & remove +#![allow(unused, missing_docs)] + +mod x25519; + +pub use x25519::X25519Sha256; + +use crate::{Error, KeyGenOption, PrivateKey, PublicKey, SessionKey}; + +/// A Generic trait for key exchange schemes. Each scheme provides a way to generate keys and +/// do a diffie-hellman computation +pub trait KeyExchangeScheme { + /// Generate a new instance of the scheme + fn new() -> Self; + /// Create new keypairs. If + /// `options` is None, the keys are generated ephemerally from the `OsRng` + /// `options` is UseSeed, the keys are generated ephemerally from the sha256 hash of the seed which is + /// then used to seed the ChaChaRng + /// `options` is FromPrivateKey, the corresponding public key is returned. This should be used for + /// static Diffie-Hellman and loading a long-term key. + fn keypair(&self, options: Option) -> Result<(PublicKey, PrivateKey), Error>; + /// Compute the diffie-hellman shared secret. + /// `local_private_key` is the key generated from calling `keypair` while + /// `remote_public_key` is the key received from a different call to `keypair` from another party. + fn compute_shared_secret( + &self, + local_private_key: &PrivateKey, + remote_public_key: &PublicKey, + ) -> Result; + + fn shared_secret_size() -> usize; + fn public_key_size() -> usize; + fn private_key_size() -> usize; +} diff --git a/crypto/src/kex/x25519.rs b/crypto/src/kex/x25519.rs new file mode 100644 index 00000000000..a8f875dbac2 --- /dev/null +++ b/crypto/src/kex/x25519.rs @@ -0,0 +1,123 @@ +use arrayref::array_ref; +use iroha_primitives::const_vec::ConstVec; +use rand::{rngs::OsRng, SeedableRng}; +use rand_chacha::ChaChaRng; +use sha2::Digest; +use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret}; +use zeroize::Zeroize; + +const ALGORITHM: Algorithm = Algorithm::Ed25519; + +use super::KeyExchangeScheme; +use crate::{Algorithm, Error, KeyGenOption, PrivateKey, PublicKey, SessionKey}; + +#[derive(Copy, Clone)] +pub struct X25519Sha256; + +impl KeyExchangeScheme for X25519Sha256 { + fn new() -> Self { + Self + } + + fn keypair(&self, option: Option) -> Result<(PublicKey, PrivateKey), Error> { + let (pk, sk) = match option { + Some(mut o) => match o { + KeyGenOption::UseSeed(ref mut s) => { + let hash = sha2::Sha256::digest(s.as_slice()); + s.zeroize(); + let mut rng = ChaChaRng::from_seed(*array_ref!(hash.as_slice(), 0, 32)); + let sk = StaticSecret::new(&mut rng); + let pk = X25519PublicKey::from(&sk); + (pk, sk) + } + KeyGenOption::FromPrivateKey(ref s) => { + assert_eq!(s.digest_function, ALGORITHM); + let sk = StaticSecret::from(*array_ref!(&s.payload, 0, 32)); + let pk = X25519PublicKey::from(&sk); + (pk, sk) + } + }, + None => { + let mut rng = OsRng::default(); + let sk = StaticSecret::new(&mut rng); + let pk = X25519PublicKey::from(&sk); + (pk, sk) + } + }; + Ok(( + PublicKey { + digest_function: ALGORITHM, + payload: ConstVec::new(pk.as_bytes().to_vec()), + }, + PrivateKey { + digest_function: ALGORITHM, + payload: ConstVec::new(sk.to_bytes().to_vec()), + }, + )) + } + + fn compute_shared_secret( + &self, + local_private_key: &PrivateKey, + remote_public_key: &PublicKey, + ) -> Result { + assert_eq!(local_private_key.digest_function, ALGORITHM); + assert_eq!(remote_public_key.digest_function, ALGORITHM); + let sk = StaticSecret::from(*array_ref!(&local_private_key.payload, 0, 32)); + let pk = X25519PublicKey::from(*array_ref!(&remote_public_key.payload, 0, 32)); + let shared_secret = sk.diffie_hellman(&pk); + let hash = sha2::Sha256::digest(shared_secret.as_bytes()); + Ok(SessionKey(ConstVec::new(hash.as_slice().to_vec()))) + } + + fn public_key_size() -> usize { + 32 + } + fn private_key_size() -> usize { + 32 + } + fn shared_secret_size() -> usize { + 32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // #[test] + // fn convert_from_sig_keys() { + // use crate::{Ed25519Sha512, SignatureScheme}; + // let sig_scheme = Ed25519Sha512::new(); + // let (pk, sk) = sig_scheme.keypair(None).unwrap(); + // let res = Ed25519Sha512::ver_key_to_key_exchange(&pk); + // assert!(res.is_ok()); + // let pk1 = res.unwrap(); + // let kex_scheme = X25519Sha256::new(); + // let res = kex_scheme.compute_shared_secret(&sk, &pk1); + // assert!(res.is_ok()); + // } + + #[test] + fn key_exchange() { + let scheme = X25519Sha256::new(); + let res = scheme.keypair(None); + assert!(res.is_ok()); + let (pk, sk) = res.unwrap(); + let res = scheme.compute_shared_secret(&sk, &pk); + assert!(res.is_ok()); + let res = scheme.keypair(None); + assert!(res.is_ok()); + let (pk1, sk1) = res.unwrap(); + let res = scheme.compute_shared_secret(&sk1, &pk); + assert!(res.is_ok()); + let res = scheme.compute_shared_secret(&sk, &pk1); + assert!(res.is_ok()); + + let res = scheme.keypair(Some(KeyGenOption::FromPrivateKey(sk.clone()))); + assert!(res.is_ok()); + let (pk1, sk1) = res.unwrap(); + assert_eq!(pk1, pk); + assert_eq!(sk1, sk); + } +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 3865ab96e22..a7ecc11a4ac 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -4,7 +4,13 @@ #[cfg(not(feature = "std"))] extern crate alloc; +#[cfg(feature = "std")] +#[cfg(not(feature = "ffi_import"))] +pub mod encryption; mod hash; +#[cfg(feature = "std")] +#[cfg(not(feature = "ffi_import"))] +pub mod kex; mod merkle; #[cfg(not(feature = "ffi_import"))] mod multihash; @@ -28,6 +34,7 @@ pub use blake2; use derive_more::{DebugCustom, Display}; use error::{Error, NoSuchAlgorithm}; use getset::{CopyGetters, Getters}; +// TODO: do not spill the hashes to the crate root pub use hash::*; use iroha_macro::ffi_impl_opaque; use iroha_primitives::const_vec::ConstVec; @@ -39,12 +46,10 @@ use parity_scale_codec::{Decode, Encode}; use serde::Deserialize; use serde::Serialize; use serde_with::{DeserializeFromStr, SerializeDisplay}; +// TODO: do not spill the signatures to the crate root pub use signature::*; #[cfg(feature = "std")] #[cfg(not(feature = "ffi_import"))] -pub use ursa; -#[cfg(feature = "std")] -#[cfg(not(feature = "ffi_import"))] use ursa::{ keys::{KeyGenOption as UrsaKeyGenOption, PrivateKey as UrsaPrivateKey}, signatures::{ @@ -125,7 +130,7 @@ impl FromStr for Algorithm { allow(unused_tuple_struct_fields) )] #[derive(Debug, Clone)] -enum KeyGenOption { +pub enum KeyGenOption { /// Use seed UseSeed(Vec), /// Derive from private key @@ -328,6 +333,19 @@ ffi::ffi_item! { #[ffi_impl_opaque] impl PublicKey { + /// Creates a new public key from raw bytes received from elsewhere + pub fn from_raw(algorithm: Algorithm, payload: ConstVec) -> Self { + Self { + digest_function: algorithm, + payload, + } + } + + /// Extracts the raw bytes from public key + pub fn into_raw(self) -> (Algorithm, ConstVec) { + (self.digest_function, self.payload) + } + /// Key payload // TODO: Derive with getset once FFI impl is fixed pub fn payload(&self) -> &[u8] { @@ -480,6 +498,17 @@ impl<'de> Deserialize<'de> for PrivateKey { } } +/// A session key derived from a key exchange. Will usually be used for a symmetric encryption afterwards +#[allow(unused_tuple_struct_fields)] +pub struct SessionKey(ConstVec); + +impl SessionKey { + /// Expose the raw bytes of the session key + pub fn payload(&self) -> &[u8] { + self.0.as_ref() + } +} + /// Shim for decoding hexadecimal strings pub(crate) fn hex_decode + ?Sized>(payload: &T) -> Result, Error> { hex::decode(payload).map_err(|err| Error::Parse(err.to_string())) diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 8a5232ab45f..fb34a8049b4 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -8,7 +8,9 @@ use iroha_crypto::{ digest::{Update, VariableOutput}, Blake2bVar, }, - ursa::{encryption::symm::prelude::ChaCha20Poly1305, kex::x25519::X25519Sha256, CryptoError}, + encryption::ChaCha20Poly1305, + error::Error as CryptoError, + kex::X25519Sha256, }; pub use network::message::*; use parity_scale_codec::{Decode, Encode}; @@ -24,7 +26,7 @@ pub mod boilerplate { //! Module containing trait shorthands. Remove when trait aliases //! are stable - use iroha_crypto::ursa::{encryption::symm::Encryptor, kex::KeyExchangeScheme}; + use iroha_crypto::{encryption::Encryptor, kex::KeyExchangeScheme}; use super::*; @@ -37,8 +39,8 @@ pub mod boilerplate { impl Kex for T where T: KeyExchangeScheme + Send + 'static {} /// Shorthand for traits required for encryptor - pub trait Enc: Encryptor + Send + 'static {} - impl Enc for T where T: Encryptor + Send + 'static {} + pub trait Enc: Encryptor + Clone + Send + 'static {} + impl Enc for T where T: Encryptor + Clone + Send + 'static {} } /// Errors used in [`crate`]. diff --git a/p2p/src/peer.rs b/p2p/src/peer.rs index 22d78171984..56162ab0ce2 100644 --- a/p2p/src/peer.rs +++ b/p2p/src/peer.rs @@ -93,7 +93,7 @@ mod run { state::{ConnectedFrom, Connecting, Ready}, *, }; - use crate::{blake2b_hash, unbounded_with_len}; + use crate::unbounded_with_len; /// Peer task. #[allow(clippy::too_many_lines)] @@ -132,7 +132,7 @@ mod run { } = peer; peer_id = new_peer_id; - let disambiguator = blake2b_hash(&cryptographer.shared_key); + let disambiguator = cryptographer.disambiguator; tracing::Span::current().record("peer", &peer_id.to_string()); tracing::Span::current().record("disambiguator", disambiguator); @@ -374,7 +374,8 @@ mod run { mod state { //! Module for peer stages. - use iroha_crypto::ursa::keys::PublicKey; + use iroha_crypto::PublicKey; + use iroha_primitives::const_vec::ConstVec; use super::{cryptographer::Cryptographer, *}; @@ -416,9 +417,10 @@ mod state { ) -> Result, crate::Error> { let key_exchange = K::new(); let (local_public_key, local_private_key) = key_exchange.keypair(None)?; + let (algorithm, local_public_key_raw) = local_public_key.into_raw(); let write_half = &mut connection.write; garbage::write(write_half).await?; - write_half.write_all(local_public_key.as_ref()).await?; + write_half.write_all(&local_public_key_raw).await?; // Read server hello with node's public key let read_half = &mut connection.read; let remote_public_key = { @@ -426,7 +428,7 @@ mod state { // Then we have servers public key let mut key = vec![0_u8; 32]; let _ = read_half.read_exact(&mut key).await?; - PublicKey(key) + PublicKey::from_raw(algorithm, ConstVec::new(key)) }; let shared_key = key_exchange.compute_shared_secret(&local_private_key, &remote_public_key)?; @@ -455,17 +457,18 @@ mod state { ) -> Result, crate::Error> { let key_exchange = K::new(); let (local_public_key, local_private_key) = key_exchange.keypair(None)?; + let (algorithm, local_public_key_raw) = local_public_key.into_raw(); let read_half = &mut connection.read; let remote_public_key = { garbage::read(read_half).await?; // And then we have clients public key let mut key = vec![0_u8; 32]; let _ = read_half.read_exact(&mut key).await?; - PublicKey(key) + PublicKey::from_raw(algorithm, ConstVec::new(key)) }; let write_half = &mut connection.write; garbage::write(write_half).await?; - write_half.write_all(local_public_key.as_ref()).await?; + write_half.write_all(&local_public_key_raw).await?; let shared_key = key_exchange.compute_shared_secret(&local_private_key, &remote_public_key)?; let cryptographer = Cryptographer::new(shared_key); @@ -675,15 +678,16 @@ pub mod message { } mod cryptographer { - use aead::{generic_array::GenericArray, NewAead}; - use iroha_crypto::ursa::{encryption::symm::SymmetricEncryptor, keys::SessionKey}; + use iroha_crypto::{encryption::SymmetricEncryptor, SessionKey}; use super::*; + use crate::blake2b_hash; /// Peer's cryptographic primitives + #[derive(Clone)] pub struct Cryptographer { - /// Shared key - pub shared_key: SessionKey, + /// Blake2b hash of the session key, used for ¿disambiguation? + pub disambiguator: u64, /// Encryptor created from session key, that we got by Diffie-Hellman scheme pub encryptor: SymmetricEncryptor, } @@ -713,21 +717,15 @@ mod cryptographer { /// Derives shared key from local private key and remote public key. pub fn new(shared_key: SessionKey) -> Self { - let encryptor = SymmetricEncryptor::::new(::new( - GenericArray::from_slice(shared_key.as_ref()), - )); + let disambiguator = blake2b_hash(shared_key.payload()); + + let encryptor = SymmetricEncryptor::::new_from_session_key(shared_key); Self { - shared_key, + disambiguator, encryptor, } } } - - impl Clone for Cryptographer { - fn clone(&self) -> Self { - Self::new(self.shared_key.clone()) - } - } } /// An identification for [`Peer`] connections. diff --git a/p2p/tests/integration/p2p.rs b/p2p/tests/integration/p2p.rs index fc9a958401e..4660d480660 100644 --- a/p2p/tests/integration/p2p.rs +++ b/p2p/tests/integration/p2p.rs @@ -354,7 +354,7 @@ async fn start_network( #[test] fn test_encryption() { - use iroha_crypto::ursa::encryption::symm::prelude::*; + use iroha_crypto::encryption::{ChaCha20Poly1305, SymmetricEncryptor}; const TEST_KEY: [u8; 32] = [ 5, 87, 82, 183, 220, 57, 107, 49, 227, 4, 96, 231, 198, 88, 153, 11, 22, 65, 56, 45, 237,