add std support, wording changes, code improvements

This commit is contained in:
satvrn 2023-07-19 21:50:47 +00:00
parent 45916848ad
commit 2b559b79f9
7 changed files with 65 additions and 36 deletions

View file

@ -1,12 +1,12 @@
[package] [package]
name = "double-ratchet-rs" name = "double-ratchet-rs"
authors = ["satvrn", "Hannes Furmans"] 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" homepage = "https://github.com/notsatvrn/double-ratchet-rs"
repository = "https://github.com/notsatvrn/double-ratchet-rs" repository = "https://github.com/notsatvrn/double-ratchet-rs"
readme = "README.md" readme = "README.md"
keywords = ["double-ratchet", "crypto", "cryptography", "signal"] keywords = ["double-ratchet", "crypto", "cryptography", "signal"]
version = "0.4.5" version = "0.4.6"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -19,7 +19,7 @@ aes-gcm-siv = "0.11"
sha2 = {version = "0.10", default-features = false} sha2 = {version = "0.10", default-features = false}
serde = {version = "1.0", default-features = false, features = ["derive"]} serde = {version = "1.0", default-features = false, features = ["derive"]}
postcard = {version = "1.0", default-features = false, features = ["alloc"]} 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"]} zeroize = {version = "1.6", default-features = false, features = ["zeroize_derive"]}
[dev-dependencies] [dev-dependencies]
@ -31,3 +31,7 @@ harness = false
[profile.release] [profile.release]
lto = true lto = true
[features]
default = ["hashbrown"]
std = ["sha2/std", "serde/std", "postcard/use-std", "zeroize/std"]

View file

@ -5,10 +5,10 @@
# double-ratchet-rs # 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]. 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). 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) 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) ## **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 ## 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/ [1]: https://signal.org/docs/specifications/doubleratchet/
[2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms
[3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption

View file

@ -1,13 +1,15 @@
use aes_gcm_siv::aead::AeadInPlace; use aes_gcm_siv::aead::AeadInPlace;
use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce}; use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce};
use alloc::vec::Vec;
use rand_core::{OsRng, RngCore}; 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>, [u8; 12]) { pub fn encrypt(mk: &[u8; 32], data: &[u8], associated_data: &[u8]) -> (Vec<u8>, [u8; 12]) {
let cipher = Aes256GcmSiv::new_from_slice(mk).expect("Encryption failure {}"); let cipher = Aes256GcmSiv::new_from_slice(mk).expect("Encryption failure {}");
let mut nonce_data = [0u8; 12]; 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 nonce = Nonce::from_slice(&nonce_data);
let mut buffer = Vec::new(); let mut buffer = Vec::new();

View file

@ -24,7 +24,7 @@ impl PartialEq for DhKeyPair {
impl Debug for DhKeyPair { impl Debug for DhKeyPair {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("DhKeyPair") 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()) .field("public_key", self.public_key.as_bytes())
.finish() .finish()
} }
@ -38,7 +38,7 @@ impl Default for DhKeyPair {
impl DhKeyPair { impl DhKeyPair {
pub fn new() -> Self { pub fn new() -> Self {
let secret = StaticSecret::random_from_rng(&mut OsRng); let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret); let public = PublicKey::from(&secret);
DhKeyPair { DhKeyPair {
private_key: secret, private_key: secret,
@ -58,11 +58,6 @@ pub fn gen_shared_secret() -> SharedSecret {
alice_pair.key_agreement(&bob_pair.public_key) alice_pair.key_agreement(&bob_pair.public_key)
} }
#[cfg(test)]
pub fn gen_key_pair() -> DhKeyPair {
DhKeyPair::new()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::dh::DhKeyPair; use crate::dh::DhKeyPair;

View file

@ -4,14 +4,13 @@ use crate::aead::encrypt;
use crate::dh::DhKeyPair; use crate::dh::DhKeyPair;
use aes_gcm_siv::aead::AeadInPlace; use aes_gcm_siv::aead::AeadInPlace;
use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce}; use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce};
use alloc::vec::Vec;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use x25519_dalek::PublicKey; use x25519_dalek::PublicKey;
#[cfg(test)]
use crate::dh::gen_key_pair;
use zeroize::Zeroize; use zeroize::Zeroize;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Serialize, Deserialize, Debug, Zeroize, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Zeroize, Clone, PartialEq, Eq)]
#[zeroize(drop)] #[zeroize(drop)]
pub struct Header { pub struct Header {
@ -93,7 +92,7 @@ impl EncryptedHeader {
#[cfg(test)] #[cfg(test)]
pub fn gen_header() -> Header { pub fn gen_header() -> Header {
let dh_pair = gen_key_pair(); let dh_pair = DhKeyPair::new();
let pn = 10; let pn = 10;
let n = 50; let n = 50;
Header::new(&dh_pair, pn, n) Header::new(&dh_pair, pn, n)

View file

@ -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]. //! 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). //! Fork of [double-ratchet-2](https://github.com/Dione-Software/double-ratchet-2).
//! //!
@ -156,9 +156,14 @@
//! assert_eq!(im_ratchet, bob_ratchet) //! 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) //! ## **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 //! ## License
//! //!
@ -168,19 +173,24 @@
//! [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms //! [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms
//! [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption //! [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption
#![no_std] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(stable_features)] #![allow(stable_features)]
#[cfg(not(feature = "std"))]
extern crate alloc; extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
pub use x25519_dalek::PublicKey; pub use x25519_dalek::PublicKey;
mod aead; mod aead;
mod dh; mod dh;
mod header;
mod kdf_chain; mod kdf_chain;
mod kdf_root; mod kdf_root;
mod ratchet; mod ratchet;
mod header;
pub use ratchet::*; pub use dh::*;
pub use header::*; pub use header::*;
pub use ratchet::*;

View file

@ -2,15 +2,20 @@
use crate::aead::{decrypt, encrypt}; use crate::aead::{decrypt, encrypt};
use crate::dh::DhKeyPair; use crate::dh::DhKeyPair;
use crate::header::{Header, EncryptedHeader}; use crate::header::{EncryptedHeader, Header};
use crate::kdf_chain::kdf_ck; use crate::kdf_chain::kdf_ck;
use crate::kdf_root::{kdf_rk, kdf_rk_he}; use crate::kdf_root::{kdf_rk, kdf_rk_he};
use alloc::vec::Vec;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use x25519_dalek::PublicKey; use x25519_dalek::PublicKey;
use zeroize::Zeroize; 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; const MAX_SKIP: usize = 100;
/// A standard ratchet. /// A standard ratchet.
@ -115,7 +120,12 @@ impl Ratchet {
self.mkskipped self.mkskipped
.remove(&(header.public_key.to_bytes(), header.n)) .remove(&(header.public_key.to_bytes(), header.n))
.unwrap(); .unwrap();
Some(decrypt(&mk, enc_data, &header.concat(associated_data), nonce)) Some(decrypt(
&mk,
enc_data,
&header.concat(associated_data),
nonce,
))
} else { } else {
None None
} }
@ -156,7 +166,7 @@ impl Ratchet {
Some(d) => d, Some(d) => d,
None => { None => {
if Some(header.public_key) != self.dhr { if Some(header.public_key) != self.dhr {
if self.ckr != None { if self.ckr.is_some() {
self.skip_message_keys(header.pn).unwrap(); self.skip_message_keys(header.pn).unwrap();
} }
self.dhratchet(header); self.dhratchet(header);
@ -298,7 +308,11 @@ impl RatchetEncHeader {
/// Encrypt bytes with a [RatchetEncHeader]. /// Encrypt bytes with a [RatchetEncHeader].
/// Requires bytes and associated bytes. /// Requires bytes and associated bytes.
/// Returns an [EncryptedHeader], encrypted bytes, and a nonce. /// Returns an [EncryptedHeader], encrypted bytes, and a nonce.
pub fn encrypt(&mut self, data: &[u8], associated_data: &[u8]) -> (EncryptedHeader, Vec<u8>, [u8; 12]) { pub fn encrypt(
&mut self,
data: &[u8],
associated_data: &[u8],
) -> (EncryptedHeader, Vec<u8>, [u8; 12]) {
let (cks, mk) = kdf_ck(&self.cks.unwrap()); let (cks, mk) = kdf_ck(&self.cks.unwrap());
self.cks = Some(cks); self.cks = Some(cks);
let header = Header::new(&self.dhs, self.pn, self.ns); let header = Header::new(&self.dhs, self.pn, self.ns);
@ -316,7 +330,7 @@ impl RatchetEncHeader {
associated_data: &[u8], associated_data: &[u8],
) -> (Option<Vec<u8>>, Option<Header>) { ) -> (Option<Vec<u8>>, Option<Header>) {
let ret_data = self.mkskipped.clone().into_iter().find(|e| { 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 { match header {
None => false, None => false,
Some(h) => h.n == e.0 .1, Some(h) => h.n == e.0 .1,
@ -325,7 +339,7 @@ impl RatchetEncHeader {
match ret_data { match ret_data {
None => (None, None), None => (None, None),
Some(data) => { Some(data) => {
let header = enc_header.decrypt(&data.0.0); let header = enc_header.decrypt(&data.0 .0);
let mk = data.1; let mk = data.1;
self.mkskipped.remove(&(data.0 .0, data.0 .1)); self.mkskipped.remove(&(data.0 .0, data.0 .1));
( (
@ -425,7 +439,8 @@ impl RatchetEncHeader {
nonce: &[u8; 12], nonce: &[u8; 12],
associated_data: &[u8], associated_data: &[u8],
) -> (Vec<u8>, Header) { ) -> (Vec<u8>, 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 { if let Some(d) = data {
return (d, header.unwrap()); return (d, header.unwrap());
}; };