From 0ada2f3372a9472f8e281858c3a47d0cc58e9142 Mon Sep 17 00:00:00 2001 From: Hannes <55623006+umgefahren@users.noreply.github.com> Date: Mon, 23 Aug 2021 14:05:05 +0200 Subject: [PATCH] Added Ratchet Import and Export --- Cargo.toml | 6 ++-- README.md | 12 ++++++- src/lib.rs | 14 ++++++++ src/ratchet.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++- tests/mod.rs | 19 ++++++++++ 5 files changed, 141 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb6580f..f4cdb7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ homepage = "https://github.com/Dione-Software/double-ratchet-2" repository = "https://github.com/Dione-Software/double-ratchet-2" readme = "README.md" keywords = ["double-ratchet", "crypto", "cryptography", "signal"] -version = "0.3.3" +version = "0.3.4" edition = "2018" license = "MIT" @@ -17,7 +17,7 @@ maintenance = { status = "actively-developed" } [dependencies] -p256 = {version = "0.9", features = ["zeroize", "ecdh", "arithmetic", "pem"]} +p256 = {version = "0.9", features = ["zeroize", "ecdh", "arithmetic", "pem", "jwk"]} rand_core = {version = "0.6", features = ["getrandom"]} hkdf = "0.11.0" hmac = "0.11.0" @@ -27,7 +27,7 @@ sha2 = {version = "0.9.5", optional = true} serde = {version = "1.0.125", default-features = false, features = ["derive"]} serde_bytes = "0.11.5" bincode = "1.3.3" -hashbrown = "0.11.2" +hashbrown = {version = "0.11.2", features = ["serde"]} zeroize = {version = "1.3.0", features = ["zeroize_derive"]} [dev-dependencies] diff --git a/README.md b/README.md index ffba8a2..42a571c 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,16 @@ let decrypted = bob_ratchet.ratchet_decrypt(&header, &encrypted, &nonce, ad); assert_eq!(data, decrypted) ``` +## Export / Import Ratchet with encrypted headers +This ratchet implements import and export functionality. This works over a bincode backend and +maybe useful for saving Ratchets to and loading from a file. +```rust +let (bob_ratchet, public_key) = RatchetEncHeader::init_bob(sk, shared_hka, shared_nhkb); +let ex_ratchet = bob_ratchet.export(); +let im_ratchet = RatchetEncHeader::import(&ex_ratchet); +assert_eq!(im_ratchet, bob_ratchet) +``` + ## Features Currently the crate only supports one feature: ring. If feature is enabled the crate switches @@ -120,6 +130,6 @@ TODO: [2]: https://signal.org/docs/specifications/doubleratchet/#recommended-cryptographic-algorithms [3]: https://signal.org/docs/specifications/doubleratchet/#double-ratchet-with-header-encryption -Current version: 0.3.1 +Current version: 0.3.4 License: MIT diff --git a/src/lib.rs b/src/lib.rs index 403b419..fcd6390 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,20 @@ //! assert_eq!(data, decrypted) //! ``` //! +//! # Export / Import Ratchet with encrypted headers +//! This ratchet implements import and export functionality. This works over a bincode backend and +//! maybe useful for saving Ratchets to and loading from a file. +//! ``` +//! # use double_ratchet_2::ratchet::RatchetEncHeader; +//! # let sk = [0; 32]; +//! # let shared_hka = [1; 32]; +//! # let shared_nhkb = [2; 32]; +//! let (bob_ratchet, public_key) = RatchetEncHeader::init_bob(sk, shared_hka, shared_nhkb); +//! let ex_ratchet = bob_ratchet.export(); +//! let im_ratchet = RatchetEncHeader::import(&ex_ratchet); +//! assert_eq!(im_ratchet, bob_ratchet) +//! ``` +//! //! # Features //! //! Currently the crate only supports one feature: ring. If feature is enabled the crate switches diff --git a/src/ratchet.rs b/src/ratchet.rs index 296b9e2..0351fe2 100644 --- a/src/ratchet.rs +++ b/src/ratchet.rs @@ -9,9 +9,10 @@ use crate::header::Header; use alloc::vec::Vec; use crate::kdf_chain::kdf_ck; use crate::aead::{encrypt, decrypt}; -use alloc::string::ToString; +use alloc::string::{ToString, String}; use zeroize::Zeroize; use rand_core::OsRng; +use serde::{Deserialize, Serialize}; const MAX_SKIP: usize = 100; @@ -161,6 +162,7 @@ impl Ratchet { } } +#[derive(PartialEq, Debug)] pub struct RatchetEncHeader { dhs: DhKeyPair, dhr: Option, @@ -201,6 +203,85 @@ impl Drop for RatchetEncHeader { } } +#[derive(Serialize, Deserialize)] +struct ExRatchetEncHeader { + dhs: (String, String), + dhr: Option, + rk: [u8; 32], + cks: Option<[u8; 32]>, + ckr: Option<[u8; 32]>, + ns: usize, + nr: usize, + pn: usize, + hks: Option<[u8; 32]>, + hkr: Option<[u8; 32]>, + nhks: Option<[u8; 32]>, + nhkr: Option<[u8; 32]>, + mkskipped: HashMap<(Option<[u8; 32]>, usize), [u8; 32]> +} + +impl From<&RatchetEncHeader> for ExRatchetEncHeader { + fn from(reh: &RatchetEncHeader) -> Self { + let private_dhs = reh.dhs.private_key.to_jwk_string(); + let public_dhs = reh.dhs.public_key.to_jwk_string(); + let dhs = (private_dhs, public_dhs); + let dhr = reh.dhr.map(|e| e.to_jwk_string()); + let rk = reh.rk; + let cks = reh.cks; + let ckr = reh.ckr; + let ns = reh.ns; + let nr = reh.nr; + let pn = reh.pn; + let hks = reh.hks; + let hkr = reh.hkr; + let nhks = reh.nhks; + let nhkr = reh.nhkr; + let mkskipped = reh.mkskipped.clone(); + Self { + dhs, + dhr, + rk, + cks, + ckr, + ns, + nr, + pn, + hks, + hkr, + nhks, + nhkr, + mkskipped + } + } +} + +impl From<&ExRatchetEncHeader> for RatchetEncHeader { + fn from(ex_reh: &ExRatchetEncHeader) -> Self { + let private_dhs = SecretKey::from_jwk_str(&ex_reh.dhs.0).unwrap(); + let public_dhs = PublicKey::from_jwk_str(&ex_reh.dhs.1).unwrap(); + let dhs = DhKeyPair { + private_key: private_dhs, + public_key: public_dhs + }; + let dhr = ex_reh.dhr.as_ref().map(|e| PublicKey::from_jwk_str(&e).unwrap()); + Self { + dhs, + dhr, + rk: ex_reh.rk, + cks: ex_reh.cks, + ckr: ex_reh.ckr, + ns: ex_reh.ns, + nr: ex_reh.nr, + pn: ex_reh.pn, + hks: ex_reh.hks, + hkr: ex_reh.hkr, + nhks: ex_reh.nhks, + nhkr: ex_reh.nhkr, + mkskipped: ex_reh.mkskipped.clone() + } + } +} + impl RatchetEncHeader { pub fn init_alice(sk: [u8; 32], bob_dh_public_key: PublicKey, @@ -351,4 +432,16 @@ impl RatchetEncHeader { self.nr += 1; (decrypt(&mk, ciphertext, &header.concat(ad), nonce), header) } + + /// Export the ratchet to Binary data + pub fn export(&self) -> Vec { + let ex: ExRatchetEncHeader = self.into(); + bincode::serialize(&ex).unwrap() + } + + /// Import the ratchet from Binary data. Panics when binary data is invalid. + pub fn import(inp: &[u8]) -> Self { + let ex: ExRatchetEncHeader = bincode::deserialize(inp).unwrap(); + RatchetEncHeader::from(&ex) + } } \ No newline at end of file diff --git a/tests/mod.rs b/tests/mod.rs index 0b2e8ec..1284a1b 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -173,3 +173,22 @@ fn ratchet_ench_enc_skip_panic() { decrypteds.push(decrypted); } } + +#[test] +fn import_export() { + let sk = [1; 32]; + let shared_hka = [2; 32]; + let shared_nhkb = [3; 32]; + let (bob_ratchet, public_key) = RatchetEncHeader::init_bob(sk, + shared_hka, + shared_nhkb); + let alice_ratchet = RatchetEncHeader::init_alice(sk, public_key, shared_hka, shared_nhkb); + + let ex_bob_ratchet = bob_ratchet.export(); + let in_bob_ratchet = RatchetEncHeader::import(&ex_bob_ratchet); + assert_eq!(in_bob_ratchet, bob_ratchet); + + let ex_alice_ratchet = alice_ratchet.export(); + let in_alice_ratchet = RatchetEncHeader::import(&ex_alice_ratchet); + assert_eq!(in_alice_ratchet, alice_ratchet); +} \ No newline at end of file