diff --git a/Cargo.toml b/Cargo.toml index 13a951d..4562fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "double-ratchet-rs" authors = ["satvrn", "Hannes Furmans"] -description = "A pure Rust implementation of the Double Ratchet Algorithm as specified by Signal." +description = "A pure Rust implementation of the Double Ratchet algorithm as described by Signal." homepage = "https://github.com/notsatvrn/double-ratchet-rs" repository = "https://github.com/notsatvrn/double-ratchet-rs" readme = "README.md" keywords = ["double-ratchet", "crypto", "cryptography", "signal"] -version = "0.4.5" +version = "0.4.6" edition = "2021" license = "MIT" @@ -19,7 +19,7 @@ aes-gcm-siv = "0.11" sha2 = {version = "0.10", default-features = false} serde = {version = "1.0", default-features = false, features = ["derive"]} postcard = {version = "1.0", default-features = false, features = ["alloc"]} -hashbrown = {version = "0.14", features = ["serde"]} +hashbrown = {version = "0.14", features = ["serde"], optional = true} zeroize = {version = "1.6", default-features = false, features = ["zeroize_derive"]} [dev-dependencies] @@ -31,3 +31,7 @@ harness = false [profile.release] lto = true + +[features] +default = ["hashbrown"] +std = ["sha2/std", "serde/std", "postcard/use-std", "zeroize/std"] diff --git a/README.md b/README.md index a39adfa..150dc01 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ # double-ratchet-rs -A pure Rust implementation of the Double Ratchet Algorithm as specified by [Signal][1]. +A pure Rust implementation of the Double Ratchet algorithm as described by [Signal][1]. This implementation follows the cryptographic recommendations provided by [Signal][2]. -The AEAD Algorithm uses a constant Nonce. This might be changed in the future. +The AEAD algorithm uses a constant Nonce. This might be changed in the future. Fork of [double-ratchet-2](https://github.com/Dione-Software/double-ratchet-2). @@ -163,9 +163,14 @@ let im_ratchet = RatchetEncHeader::import(&ex_ratchet).unwrap(); assert_eq!(im_ratchet, bob_ratchet) ``` +## Features + +- `hashbrown`: Use `hashbrown` for `HashMap`. Enabled by default for `no_std` support. +- `std`: Use `std` instead of `alloc`. Can be used with `hashbrown`, but it isn't required. + ## **M**inimum **S**upported **R**ust **V**ersion (MSRV) -The current MSRV is 1.61.0. +The current MSRV is 1.60.0 without `hashbrown` and 1.64.0 with `hashbrown`. ## License @@ -174,4 +179,3 @@ This project is licensed under the [MIT license](https://github.com/notsatvrn/do [1]: https://signal.org/docs/specifications/doubleratchet/ [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption - diff --git a/src/aead.rs b/src/aead.rs index 3414de8..5c6e897 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -1,13 +1,15 @@ use aes_gcm_siv::aead::AeadInPlace; use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce}; -use alloc::vec::Vec; use rand_core::{OsRng, RngCore}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + pub fn encrypt(mk: &[u8; 32], data: &[u8], associated_data: &[u8]) -> (Vec, [u8; 12]) { let cipher = Aes256GcmSiv::new_from_slice(mk).expect("Encryption failure {}"); let mut nonce_data = [0u8; 12]; - OsRng::fill_bytes(&mut OsRng, &mut nonce_data); + OsRng.fill_bytes(&mut nonce_data); let nonce = Nonce::from_slice(&nonce_data); let mut buffer = Vec::new(); diff --git a/src/dh.rs b/src/dh.rs index ac6722b..871ebc9 100644 --- a/src/dh.rs +++ b/src/dh.rs @@ -24,7 +24,7 @@ impl PartialEq for DhKeyPair { impl Debug for DhKeyPair { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("DhKeyPair") - .field("private_key", &self.private_key.to_bytes()) + .field("private_key", self.private_key.as_bytes()) .field("public_key", self.public_key.as_bytes()) .finish() } @@ -38,7 +38,7 @@ impl Default for DhKeyPair { impl DhKeyPair { pub fn new() -> Self { - let secret = StaticSecret::random_from_rng(&mut OsRng); + let secret = StaticSecret::random_from_rng(OsRng); let public = PublicKey::from(&secret); DhKeyPair { private_key: secret, @@ -58,11 +58,6 @@ pub fn gen_shared_secret() -> SharedSecret { alice_pair.key_agreement(&bob_pair.public_key) } -#[cfg(test)] -pub fn gen_key_pair() -> DhKeyPair { - DhKeyPair::new() -} - #[cfg(test)] mod tests { use crate::dh::DhKeyPair; diff --git a/src/header.rs b/src/header.rs index 2981077..45bec21 100644 --- a/src/header.rs +++ b/src/header.rs @@ -4,14 +4,13 @@ use crate::aead::encrypt; use crate::dh::DhKeyPair; use aes_gcm_siv::aead::AeadInPlace; use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce}; -use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use x25519_dalek::PublicKey; - -#[cfg(test)] -use crate::dh::gen_key_pair; use zeroize::Zeroize; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[derive(Serialize, Deserialize, Debug, Zeroize, Clone, PartialEq, Eq)] #[zeroize(drop)] pub struct Header { @@ -93,7 +92,7 @@ impl EncryptedHeader { #[cfg(test)] pub fn gen_header() -> Header { - let dh_pair = gen_key_pair(); + let dh_pair = DhKeyPair::new(); let pn = 10; let n = 50; Header::new(&dh_pair, pn, n) diff --git a/src/lib.rs b/src/lib.rs index a477478..99f5960 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ -//! A pure Rust implementation of the Double Ratchet Algorithm as specified by [Signal][1]. +//! A pure Rust implementation of the Double Ratchet algorithm as described by [Signal][1]. //! //! This implementation follows the cryptographic recommendations provided by [Signal][2]. -//! The AEAD Algorithm uses a constant Nonce. This might be changed in the future. +//! The AEAD algorithm uses a constant Nonce. This might be changed in the future. //! //! Fork of [double-ratchet-2](https://github.com/Dione-Software/double-ratchet-2). //! @@ -156,9 +156,14 @@ //! assert_eq!(im_ratchet, bob_ratchet) //! ``` //! +//! ## Features +//! +//! - `hashbrown`: Use `hashbrown` for `HashMap`. Enabled by default for `no_std` support. +//! - `std`: Use `std` instead of `alloc`. Can be used with `hashbrown`, but it isn't required. +//! //! ## **M**inimum **S**upported **R**ust **V**ersion (MSRV) //! -//! The current MSRV is 1.61.0. +//! The current MSRV is 1.60.0 without `hashbrown` and 1.64.0 with `hashbrown`. //! //! ## License //! @@ -168,19 +173,24 @@ //! [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms //! [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] #![allow(stable_features)] +#[cfg(not(feature = "std"))] extern crate alloc; +#[cfg(feature = "std")] +extern crate std as alloc; + pub use x25519_dalek::PublicKey; mod aead; mod dh; +mod header; mod kdf_chain; mod kdf_root; mod ratchet; -mod header; -pub use ratchet::*; +pub use dh::*; pub use header::*; +pub use ratchet::*; diff --git a/src/ratchet.rs b/src/ratchet.rs index 4fa5eab..b6a60e7 100644 --- a/src/ratchet.rs +++ b/src/ratchet.rs @@ -2,15 +2,20 @@ use crate::aead::{decrypt, encrypt}; use crate::dh::DhKeyPair; -use crate::header::{Header, EncryptedHeader}; +use crate::header::{EncryptedHeader, Header}; use crate::kdf_chain::kdf_ck; use crate::kdf_root::{kdf_rk, kdf_rk_he}; -use alloc::vec::Vec; -use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use x25519_dalek::PublicKey; use zeroize::Zeroize; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +#[cfg(any(not(feature = "std"), feature = "hashbrown"))] +use hashbrown::HashMap; +#[cfg(all(feature = "std", not(feature = "hashbrown")))] +use std::collections::HashMap; + const MAX_SKIP: usize = 100; /// A standard ratchet. @@ -115,7 +120,12 @@ impl Ratchet { self.mkskipped .remove(&(header.public_key.to_bytes(), header.n)) .unwrap(); - Some(decrypt(&mk, enc_data, &header.concat(associated_data), nonce)) + Some(decrypt( + &mk, + enc_data, + &header.concat(associated_data), + nonce, + )) } else { None } @@ -156,7 +166,7 @@ impl Ratchet { Some(d) => d, None => { if Some(header.public_key) != self.dhr { - if self.ckr != None { + if self.ckr.is_some() { self.skip_message_keys(header.pn).unwrap(); } self.dhratchet(header); @@ -298,7 +308,11 @@ impl RatchetEncHeader { /// Encrypt bytes with a [RatchetEncHeader]. /// Requires bytes and associated bytes. /// Returns an [EncryptedHeader], encrypted bytes, and a nonce. - pub fn encrypt(&mut self, data: &[u8], associated_data: &[u8]) -> (EncryptedHeader, Vec, [u8; 12]) { + pub fn encrypt( + &mut self, + data: &[u8], + associated_data: &[u8], + ) -> (EncryptedHeader, Vec, [u8; 12]) { let (cks, mk) = kdf_ck(&self.cks.unwrap()); self.cks = Some(cks); let header = Header::new(&self.dhs, self.pn, self.ns); @@ -316,7 +330,7 @@ impl RatchetEncHeader { associated_data: &[u8], ) -> (Option>, Option
) { let ret_data = self.mkskipped.clone().into_iter().find(|e| { - let header = enc_header.decrypt(&e.0.0); + let header = enc_header.decrypt(&e.0 .0); match header { None => false, Some(h) => h.n == e.0 .1, @@ -325,7 +339,7 @@ impl RatchetEncHeader { match ret_data { None => (None, None), Some(data) => { - let header = enc_header.decrypt(&data.0.0); + let header = enc_header.decrypt(&data.0 .0); let mk = data.1; self.mkskipped.remove(&(data.0 .0, data.0 .1)); ( @@ -425,7 +439,8 @@ impl RatchetEncHeader { nonce: &[u8; 12], associated_data: &[u8], ) -> (Vec, Header) { - let (data, header) = self.try_skipped_message_keys(enc_header, enc_data, nonce, associated_data); + let (data, header) = + self.try_skipped_message_keys(enc_header, enc_data, nonce, associated_data); if let Some(d) = data { return (d, header.unwrap()); };