/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* 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://signal.org/docs/specifications/x3dh/x3dh.pdf
//! https://signal.org/docs/specifications/doubleratchet/doubleratchet.pdf
use std::collections::{HashMap, VecDeque};
use aes_gcm_siv::{AeadInPlace, Aes256GcmSiv, KeyInit};
use digest::Update;
use rand::rngs::OsRng;
use sha2::Sha256;
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret as X25519SecretKey};
mod hkdf;
use hkdf::Hkdf;
mod hmac;
use hmac::Hmac;
mod xeddsa;
use xeddsa::{XeddsaSigner, XeddsaVerifier};
const AEAD_TAG_SIZE: usize = 16;
const MESSAGE_KEY_CONSTANT: u8 = 0x01;
const CHAIN_KEY_CONSTANT: u8 = 0x02;
const X3DH_INIT_INFO: &[u8] = b"x3dh_double_ratchet_init";
const BLANK_NONCE: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// wat do?
const MAX_SKIP: u64 = 500;
/// The server contains published identity keys and prekeys.
#[derive(Default)]
struct Server(HashMap);
impl Server {
pub fn upload(&mut self, ik: X25519PublicKey, keyset: Keyset) {
self.0.insert(ik, keyset);
}
pub fn fetch(&mut self, ik: &X25519PublicKey) -> Option {
if let Some(keyset) = self.0.get_mut(ik) {
// The server should provide one one-time prekey if one exists,
// and then delete it. If all of the one-time prekeys have been
// deleted, the bundle will not contain a one-time prekey.
let onetime_prekey = keyset.onetime_prekeys.pop_front();
return Some(Bundle {
identity_key: *ik,
signed_prekey: keyset.signed_prekey,
prekey_signature: keyset.prekey_signature,
onetime_prekey,
})
}
None
}
}
/// The set of elliptic curve public keys sent uploaded to a server
struct Keyset {
pub signed_prekey: X25519PublicKey,
pub prekey_signature: [u8; 64],
pub onetime_prekeys: VecDeque,
}
/// The bundle is a structure returned by the server when requesting
/// it for a certain identity key
struct Bundle {
pub identity_key: X25519PublicKey,
pub signed_prekey: X25519PublicKey,
pub prekey_signature: [u8; 64],
pub onetime_prekey: Option,
}
/// Initial message sent from Alice to Bob (see below how it's used)
struct InitialMessage {
pub identity_key: X25519PublicKey,
pub ephemeral_key: X25519PublicKey,
pub prekey_used: Option,
pub ciphertext: Vec,
}
#[derive(Copy, Clone, Debug)]
struct MessageHeader {
/// Ratchet public key
dh: X25519PublicKey,
/// Previous chain length
pn: u64,
/// Message number
n: u64,
}
impl MessageHeader {
/// Creates a new message header containing the DH ratchet public key
/// `dh` the previous chain length `pn`, and the message number `n`.
pub fn new(dh: &X25519SecretKey, pn: u64, n: u64) -> Self {
Self { dh: X25519PublicKey::from(dh), pn, n }
}
pub fn to_bytes(self) -> [u8; 48] {
let mut ret = [0u8; 48];
ret[..32].copy_from_slice(&self.dh.to_bytes());
ret[32..40].copy_from_slice(&self.pn.to_le_bytes());
ret[40..].copy_from_slice(&self.n.to_le_bytes());
ret
}
pub fn from_bytes(arr: [u8; 48]) -> Self {
let pk_bytes: [u8; 32] = arr[..32].try_into().unwrap();
let dh = X25519PublicKey::from(pk_bytes);
let pn = u64::from_le_bytes(arr[32..40].try_into().unwrap());
let n = u64::from_le_bytes(arr[40..].try_into().unwrap());
Self { dh, pn, n }
}
/// Returns the AEAD encryption of the message header with header key `hk`.
/// Because the same `hk` will be used repeatedly, the AEAD nonce must
/// either be a stateful non-repeating value, or must be a random
/// non-repeating value chosen with at least 128 bits of entropy.
pub fn encrypt(&self, hk: [u8; 32], ad: &[u8]) -> Vec {
// FIXME: BUG: Don't reuse the nonce.
let nonce = [0u8; 12][..].into();
let mut ciphertext = vec![0u8; 48 + AEAD_TAG_SIZE];
ciphertext[..48].copy_from_slice(&self.to_bytes());
Aes256GcmSiv::new(&hk.into()).encrypt_in_place(nonce, ad, &mut ciphertext).unwrap();
ciphertext
}
/// Returns the authenticated decryption of `ciphertext` with header key `hk`.
pub fn decrypt(ciphertext: &[u8], hk: [u8; 32], ad: &[u8]) -> Option {
// FIXME: BUG: Don't reuse the nonce.
let nonce = [0u8; 12][..].into();
let mut plaintext = vec![0u8; ciphertext.len()];
plaintext.copy_from_slice(ciphertext);
match Aes256GcmSiv::new(&hk.into()).decrypt_in_place(nonce, ad, &mut plaintext) {
Ok(()) => {
plaintext.resize(plaintext.len() - AEAD_TAG_SIZE, 0);
let message_header = Self::from_bytes(plaintext.try_into().unwrap());
Some(message_header)
}
Err(_) => None,
}
}
}
/// Returns a pair (32-byte chain key, 32-byte message key) as the output of
/// applying a KDF keyed by a 32-byte chain key `ck` to some constant.
/// HMAC with SHA256 is recommended, using `ck` as the HMAC key and using
/// separate constants as input (e.g. a single byte 0x01 as input to produce
/// the message key, and a single byte 0x02 as input to produce the next chain
/// key.
fn kdf_ck(ck: [u8; 32]) -> ([u8; 32], [u8; 32]) {
let mut hmac = Hmac::::new_from_slice(&ck);
hmac.update(&[CHAIN_KEY_CONSTANT]);
let chain_key = hmac.finalize();
let mut hmac = Hmac::::new_from_slice(&ck);
hmac.update(&[MESSAGE_KEY_CONSTANT]);
let message_key = hmac.finalize();
(chain_key.into(), message_key.into())
}
/// Returns a new root key, chain key, and next header key as the output
/// of applying a KDF keyed by root key `rk` to a Diffie-Hellman output
/// `dh_out`.
/// This function is recommended to be implemented using HKDF with SHA256
/// using `rk` as HKDF salt, `dh_out` as HKDF input key material, and an
/// application-specific byte sequence as HKDF info. The info value should
/// be chosen to be distinct from other uses of HKDF in the application.
fn kdf_rk(rk: [u8; 32], dh_out: [u8; 32]) -> ([u8; 32], [u8; 32], [u8; 32]) {
const KDF_RK_INFO: &[u8] = b"x3dh_double_ratchet_kdf_rk";
const KDF_HE_INFO: &[u8] = b"x3dh_double_ratchet_kdf_rk_he";
let (_root_key, hkdf) = Hkdf::::extract(&rk, &dh_out);
let mut chain_key = [0u8; 32];
hkdf.expand(KDF_RK_INFO, &mut chain_key).unwrap();
let (root_key, hkdf) = Hkdf::::extract(&rk, &dh_out);
let mut next_header_key = [0u8; 32];
hkdf.expand(KDF_HE_INFO, &mut next_header_key).unwrap();
(root_key.into(), chain_key, next_header_key)
}
#[derive(Clone)]
struct DoubleRatchetSessionState {
/// DH ratchet key pair (the "sending" or "self" ratchet key) (DHRs)
pub dh_sending: X25519SecretKey,
/// DH ratchet public key (the "received" or "remote" key) (DHRr)
pub dh_remote: X25519PublicKey,
/// 32-byte root key (RK)
pub root_key: [u8; 32],
/// 32-byte Chain Key for sending (CKs)
pub chain_key_send: [u8; 32],
/// 32-byte Chain Key for receiving (CKr)
pub chain_key_recv: [u8; 32],
/// Message numbers for sending (Ns)
pub n_send: u64,
/// Message numbers for receiving (Nr)
pub n_recv: u64,
/// Number of messages in previous sending chain (PN)
pub n_prev: u64,
/// Dictionary of skipped-over message keys, indexed by header key
/// and message number. Raises an exception if too many elements
/// are stored.
pub mkskipped: HashMap<([u8; 32], u64), [u8; 32]>,
/// 32-byte Header Key for sending (HKs)
pub header_key_send: [u8; 32],
/// 32-byte Header Key for receiving (HKr)
pub header_key_recv: [u8; 32],
/// 32-byte Next Header Key for sending (NHKs)
pub next_header_key_send: [u8; 32],
/// 32-byte Next Header Key for receiving (NHKr)
pub next_header_key_recv: [u8; 32],
}
impl DoubleRatchetSessionState {
/// This function performs a symmetric-key ratchet step, then encrypts
/// the message with the resulting message key. In addition to the
/// message's _plaintext_ it takes an AD byte sequence which is
/// prepended to the header to form the associated data for the
// underlying AEAD encryption.
pub fn ratchet_encrypt(&mut self, plaintext: &[u8], ad: &[u8]) -> (Vec, Vec) {
let (chain_key, message_key) = kdf_ck(self.chain_key_send);
self.chain_key_send = chain_key;
println!("ENCRYPT(): new chain send: {:?}", &chain_key[..5]);
let header = MessageHeader::new(&self.dh_sending, self.n_prev, self.n_send);
let enc_header = header.encrypt(self.header_key_send, &[]);
let mut associated_data = Vec::with_capacity(ad.len() + enc_header.len());
associated_data.extend_from_slice(ad);
associated_data.extend_from_slice(&enc_header);
let mut ciphertext = vec![0u8; plaintext.len() + AEAD_TAG_SIZE];
ciphertext[..plaintext.len()].copy_from_slice(plaintext);
// Because each message key is only used once, the AEAD nonce may be
// handled in several ways:
// * Fixed to a constant
// * Derived from `mk` alongside an independent AEAD encryption key
// * Derived as an additional output from HMAC
// * Chosen randomly and transmitted
// ENCRYPT(message_key, plaintext, (AD || enc_header))
println!("ENCRYPT(): message key: {:?}", &message_key[..5]);
Aes256GcmSiv::new(&message_key.into())
.encrypt_in_place(BLANK_NONCE.into(), &associated_data, &mut ciphertext)
.unwrap();
self.n_send += 1;
(enc_header, ciphertext)
}
/// Decrypt messages. This function does the following:
/// * If the message corresponds to a skipped message key this function
/// decrypts the message, deletes the message key, and returns.
/// * Otherwise, if a new ratchet key has been received, this function
/// stores any skipped message keys from the receiving chain and
/// performs a DH ratchet step to replace the sending and receiving
/// chains.
/// * This function then stores any skipped message keys from the current
/// receiving chain, performs a symmetric-key ratchet step to derive
/// the relevant message key and next chain key, and decrypts the msg.
/// If an exception is raised (e.g. message authentication failure), then
/// the message is discarded and changes to the state object are discarded.
/// Otherwise, the decrypted plaintext is accepted and changes to the state
/// object are stored.
pub fn ratchet_decrypt(&mut self, enc_header: &[u8], ciphertext: &[u8], ad: &[u8]) -> Vec {
// We clone here so we don't have to worry about mutating the state before
// everything is correct.
let mut state = self.clone();
if let Some(plaintext) = state.try_skipped_message_keys(enc_header, ciphertext, ad) {
println!("found skipped");
*self = state;
return plaintext
}
if let Some((header, dh_ratchet)) = state.decrypt_header(enc_header) {
if dh_ratchet {
state.skip_message_keys(header.pn);
state.dh_ratchet(header);
}
state.skip_message_keys(header.n);
} else {
panic!("couldn't decrypt header")
}
let (chain_key, message_key) = kdf_ck(state.chain_key_recv);
state.chain_key_recv = chain_key;
println!("DECRYPT(): new chain recv: {:?}", &chain_key[..5]);
state.n_recv += 1;
let mut plaintext = vec![0u8; ciphertext.len()];
plaintext.copy_from_slice(ciphertext);
let mut associated_data = Vec::with_capacity(ad.len() + enc_header.len());
associated_data.extend_from_slice(ad);
associated_data.extend_from_slice(enc_header);
// DECRYPT(message_key, ciphertext, (AD || enc_header))
println!("DECRYPT(): message key: {:?}", &message_key[..5]);
Aes256GcmSiv::new(&message_key.into())
.decrypt_in_place(BLANK_NONCE.into(), &associated_data, &mut plaintext)
.unwrap();
// Apply the state change
*self = state;
plaintext.resize(plaintext.len() - AEAD_TAG_SIZE, 0);
plaintext
}
fn try_skipped_message_keys(
&mut self,
enc_header: &[u8],
ciphertext: &[u8],
ad: &[u8],
) -> Option> {
let mut plaintext = ciphertext.to_vec();
let mut rem = None;
for ((hk, n), mk) in self.mkskipped.iter_mut() {
if let Some(header) = MessageHeader::decrypt(enc_header, *hk, &[]) {
if header.n == *n {
rem = Some((*hk, *n));
let mut associated_data = Vec::with_capacity(ad.len() + enc_header.len());
associated_data.extend_from_slice(ad);
associated_data.extend_from_slice(enc_header);
let mk = *mk;
Aes256GcmSiv::new(&mk.into())
.decrypt_in_place(BLANK_NONCE.into(), &associated_data, &mut plaintext)
.unwrap();
plaintext.resize(plaintext.len() - AEAD_TAG_SIZE, 0);
break
}
panic!("Failed to decrypt message from skipped message keys");
}
}
if let Some(found) = rem {
self.mkskipped.remove(&found);
return Some(plaintext)
}
None
}
fn decrypt_header(&mut self, enc_header: &[u8]) -> Option<(MessageHeader, bool)> {
if let Some(header) = MessageHeader::decrypt(enc_header, self.header_key_recv, &[]) {
return Some((header, false))
}
if let Some(header) = MessageHeader::decrypt(enc_header, self.next_header_key_recv, &[]) {
return Some((header, true))
}
println!("Failed to decrypt header");
None
}
fn skip_message_keys(&mut self, until: u64) {
if self.n_recv + MAX_SKIP < until {
panic!("I can't hold all of these lemons");
}
if self.chain_key_recv != [0u8; 32] {
while self.n_recv < until {
let (chain_key_recv, message_key) = kdf_ck(self.chain_key_recv);
self.chain_key_recv = chain_key_recv;
println!("SKIP(): new chain recv: {:?}", &chain_key_recv[..5]);
self.mkskipped.insert((self.header_key_recv, self.n_recv), message_key);
self.n_recv += 1;
}
}
}
fn dh_ratchet(&mut self, header: MessageHeader) {
self.n_prev = self.n_send;
self.n_send = 0;
self.n_recv = 0;
self.header_key_send = self.next_header_key_send;
self.header_key_recv = self.next_header_key_recv;
self.dh_remote = header.dh;
let hkdf_ikm = self.dh_sending.diffie_hellman(&self.dh_remote);
(self.root_key, self.chain_key_recv, self.next_header_key_recv) =
kdf_rk(self.root_key, hkdf_ikm.to_bytes());
let dh_secret_new = X25519SecretKey::random_from_rng(OsRng);
self.dh_sending = dh_secret_new;
let hkdf_ikm = self.dh_sending.diffie_hellman(&self.dh_remote);
(self.root_key, self.chain_key_send, self.next_header_key_send) =
kdf_rk(self.root_key, hkdf_ikm.to_bytes());
}
}
fn main() {
// The "server" contains published identity keys and prekeys.
let mut server = Server::default();
// The X3DH protocol has three phases:
// 1. Bob publishes his identity key and prekeys to a server.
// 2. Alice fetches a "prekey bundle" from the server, and uses
// it to send an initial message to Bob.
// 3. Bob receives and processes Alice's initial message.
// Alice's identity key `IK_A`
let alice_ik_secret = X25519SecretKey::random_from_rng(OsRng);
let alice_ik_public = X25519PublicKey::from(&alice_ik_secret);
// Bob's identity key `IK_B`
let bob_ik_secret = X25519SecretKey::random_from_rng(OsRng);
let bob_ik_public = X25519PublicKey::from(&bob_ik_secret);
// Bob only needs to upload his identity key to the server once.
// However, Bob may upload new one-time prekeys at other times
// (e.g. when the server informs Bob that the server's store
// of one-time prekeys is getting low).
// Bob will also upload a new signed prekey and prekey signature
// at some interval (e.g. once a week/month). The new signed prekey
// and prekey signature will replace the previous values.
// Bob's signed prekey `SPK_B`
let bob_spk_secret = X25519SecretKey::random_from_rng(OsRng);
let bob_public_spk = X25519PublicKey::from(&bob_spk_secret);
// Bob's prekey signature `Sig(IK_b, Encode(SPK_B))`
let nonce = [0_u8; 64];
let bob_spk_signature = bob_ik_secret.xeddsa_sign(&bob_public_spk.to_bytes(), &nonce);
// A set of Bob's one-time prekeys `(OPK_B1, OPK_B2, OPK_B3, ...)`
let mut bob_opk_secrets = vec![
X25519SecretKey::random_from_rng(OsRng),
X25519SecretKey::random_from_rng(OsRng),
X25519SecretKey::random_from_rng(OsRng),
];
let mut bob_opk_publics = VecDeque::new();
bob_opk_publics.push_back(X25519PublicKey::from(&bob_opk_secrets[0]));
bob_opk_publics.push_back(X25519PublicKey::from(&bob_opk_secrets[1]));
bob_opk_publics.push_back(X25519PublicKey::from(&bob_opk_secrets[2]));
let bob_keyset = Keyset {
signed_prekey: bob_public_spk,
prekey_signature: bob_spk_signature,
onetime_prekeys: bob_opk_publics.clone(),
};
// Bob uploads his keyset to the server.
server.upload(bob_ik_public, bob_keyset);
// To perform an X3DH key agreement with Bob, Alice contacts the server
// and fetches a "prekey bundle" containing the following values:
// * Bob's identity key `IK_B`
// * Bob's signed prekey `SPK_B`
// * Bob's prekey signature `Sig(IK_B, Encode(SPK_B))`
// * (Optionally) Bob's one-time prekey `OPK_B`
let bob_keyset = server.fetch(&bob_ik_public).unwrap();
// Alice verifies the prekey signature and aborts the protocol if
// verification fails.
assert!(bob_keyset
.identity_key
.xeddsa_verify(&bob_keyset.signed_prekey.to_bytes(), &bob_keyset.prekey_signature));
// Alice then generates an ephemeral keypair with public key `EK_A`
let alice_ek_secret = X25519SecretKey::random_from_rng(OsRng);
let alice_ek_public = X25519PublicKey::from(&alice_ek_secret);
// If the bundle does _not_ contain a one-time prekey, she calculates:
// DH1 = DH(IK_A, SPK_B)
// DH2 = DH(EK_A, IK_B)
// DH3 = DH(EK_A, SPK_B)
// SK = KDF(DH1 || DH2 || DH3)
// If the bundle _does_ contain a one-time prekey, additionally she
// does another dh:
// DH4 = DH(EK_A, OPK_B)
// SK = KDF(DH1 || DH2 || DH3 || DH4)
let dh1 = alice_ik_secret.diffie_hellman(&bob_keyset.signed_prekey);
let dh2 = alice_ek_secret.diffie_hellman(&bob_keyset.identity_key);
let dh3 = alice_ek_secret.diffie_hellman(&bob_keyset.signed_prekey);
let mut dh4 = None;
if let Some(opk) = bob_keyset.onetime_prekey {
dh4 = Some(alice_ek_secret.diffie_hellman(&opk));
}
// KDF represents 32 bytes of output from the HKDF algorithm with inputs:
// - HKDF input key material = F || KM, where KM is an input byte sequence
// containing secret key material, and F is a byte sequence containing
// 32 0xFF bytes when the curve is X25519. F is used for cryptographic
// domain separation with XEdDSA.
// - HKDF salt = A zero-filled byte sequence equal to the hash output length.
// - HKDF info - The info parameter.
let salt = [0u8; 32];
let mut ikm = vec![0xFF; 32];
ikm.extend_from_slice(&dh1.to_bytes());
ikm.extend_from_slice(&dh2.to_bytes());
ikm.extend_from_slice(&dh3.to_bytes());
if let Some(ref opk_dh) = dh4 {
ikm.extend_from_slice(&opk_dh.to_bytes());
}
let hkdf = Hkdf::::new(&salt, &ikm);
let mut sk = [0u8; 32];
hkdf.expand(X3DH_INIT_INFO, &mut sk).unwrap();
// After calculating SK, Alice deletes her ephemeral private key and the
// DH outputs.
// TODO: Actually erase
drop(alice_ek_secret);
drop(dh1);
drop(dh2);
drop(dh3);
drop(dh4);
// Alice then calculates an "associated data" byte sequence AD that
// contains identity information for both parties:
// AD = Encode(IK_A) || Encode(IK_B)
// Alice may optionally append additional info to AD, such as Alice
// and Bob's usernames, certificates, or other identifying information.
let mut ad = Vec::with_capacity(64);
ad.extend_from_slice(&alice_ik_public.to_bytes());
ad.extend_from_slice(&bob_ik_public.to_bytes());
// Alice then sends Bob an initial message containing:
// - Alice's identity key IK_A
// - Alice's ephemeral key EK_A
// - Identifiers stating which of Bob's prekeys Alice used
// - An initial ciphertext with some AEAD encryption scheme using AD as
// associated data and using an encryption key which is either SK
// or the output of some cryptographic PRF keyed by SK.
let message = b"ohai bob";
let mut ciphertext = vec![0u8; message.len() + AEAD_TAG_SIZE];
ciphertext[..message.len()].copy_from_slice(message);
Aes256GcmSiv::new(&sk.into())
.encrypt_in_place(BLANK_NONCE.into(), &ad, &mut ciphertext)
.unwrap();
let initial_message = InitialMessage {
identity_key: alice_ik_public,
ephemeral_key: alice_ek_public,
prekey_used: bob_keyset.onetime_prekey,
ciphertext,
};
// Upon receiving Alice's initial message, Bob retrieves Alice's
// identity key and ephemeral key from the message. Bob also loads
// his identity private key, and the private key(s) corresponding
// to whichever signed prekey and one-time prekey (if any) Alice used.
// NOTE: In this example, we assume Bob already knows the latest prekey
// he signed and uploaded to the server.
// Using these keys, Bob repeats the DH and KDF calculations from the
// previous section to derive SK, and then deletes the DH values.
let mut onetime_prekey = None;
if let Some(opk_used) = initial_message.prekey_used {
for i in bob_opk_secrets.clone() {
if X25519PublicKey::from(&i.clone()) == opk_used {
onetime_prekey = Some(i);
}
}
}
let dh1 = bob_spk_secret.diffie_hellman(&initial_message.identity_key);
let dh2 = bob_ik_secret.diffie_hellman(&initial_message.ephemeral_key);
let dh3 = bob_spk_secret.diffie_hellman(&initial_message.ephemeral_key);
let mut dh4 = None;
if let Some(ref opk) = onetime_prekey {
dh4 = Some(opk.diffie_hellman(&initial_message.ephemeral_key));
}
let salt = [0u8; 32];
let mut ikm = vec![0xFF; 32];
ikm.extend_from_slice(&dh1.to_bytes());
ikm.extend_from_slice(&dh2.to_bytes());
ikm.extend_from_slice(&dh3.to_bytes());
if let Some(ref opk_dh) = dh4 {
ikm.extend_from_slice(&opk_dh.to_bytes());
}
// TODO: Erase ephemeral data
let hkdf = Hkdf::::new(&salt, &ikm);
let mut sk2 = [0u8; 32];
hkdf.expand(X3DH_INIT_INFO, &mut sk2).unwrap();
assert_eq!(sk, sk2); // Just to confirm everything's correct
// Bob then constructs the AD byte sequence using IK_A and IK_B
// as Alice did above.
let mut ad = Vec::with_capacity(64);
ad.extend_from_slice(&initial_message.identity_key.to_bytes());
ad.extend_from_slice(&bob_ik_public.to_bytes());
// Finally, Bob attempts to decrypt the initial ciphertext using SK and AD.
// If the initial ciphertext fails to decrypt, Bob aborts the protocol and
// deletes SK.
let mut plaintext = vec![0_u8; initial_message.ciphertext.len()];
plaintext.copy_from_slice(&initial_message.ciphertext);
Aes256GcmSiv::new(&sk2.into())
.decrypt_in_place(BLANK_NONCE.into(), &ad, &mut plaintext)
.unwrap();
plaintext.resize(plaintext.len() - AEAD_TAG_SIZE, 0);
assert_eq!(plaintext, message); // Just to confirm everything's correct
// If the initial ciphertext decrypts successfully, the protocol is complete
// for Bob. Bob deletes any one-time prekey secret key that was used, for
// forward secrecy. Bob may then continue using SK or keys derived from SK
// within the post-X3DH protocol for communication with Alice.
if let Some(opk) = onetime_prekey {
bob_opk_secrets.retain(|x| x.to_bytes() != opk.to_bytes());
}
// =======================+
// Double Ratchet with X3DH
// ========================
// * The SK output from X3DH becomes the SK input to Double Ratchet initialization.
// * The AD output from X3DH becomes the AD input to Double Ratchet {en,de}cryption.
// * Bob's signed prekey SPK_B becomes Bob's initial ratchet public key (and
// corresponding keypair) for Double Ratchet initialization.
// Any Double Ratchet message encrypted using Alice's initial sending chain can
// serve as an "initial ciphertext" for X3DH. To deal with the possibility of
// lost or out-of-order messages, a recommended pattern is for Alice to repeatedly
// send the same X3DH initial message prepended to all of her Double Ratchet
// messages until she receives Bob's first Double Ratchet response message.
// Once Alice and Bob have agreed on SK and Bob's ratchet public key, Alice
// and Bob initialize their states:
// Alice:
let alice_dh_secret = X25519SecretKey::random_from_rng(OsRng);
// The X3DH secret becomes the HKDF salt, and the ikm is the DH output
// of Alice's DH secret and Bob's SPK_B.
let hkdf_ikm = alice_dh_secret.diffie_hellman(&bob_keyset.signed_prekey);
let (root_key, chain_key_send, next_header_key_send) = kdf_rk(sk, hkdf_ikm.to_bytes());
// TODO: We're using SK here as the initial header encryption keys. Perhaps it's not safe?
let mut ars = DoubleRatchetSessionState {
dh_sending: alice_dh_secret,
dh_remote: bob_keyset.signed_prekey,
root_key,
chain_key_send,
chain_key_recv: [0u8; 32],
n_send: 0,
n_recv: 0,
n_prev: 0,
mkskipped: HashMap::default(),
header_key_send: sk,
header_key_recv: [0u8; 32],
next_header_key_send,
next_header_key_recv: sk,
};
// Bob:
let mut brs = DoubleRatchetSessionState {
dh_sending: bob_spk_secret,
dh_remote: X25519PublicKey::from([0u8; 32]),
root_key: sk,
chain_key_send: [0u8; 32],
chain_key_recv: [0u8; 32],
n_send: 0,
n_recv: 0,
n_prev: 0,
mkskipped: HashMap::default(),
header_key_send: [0u8; 32],
header_key_recv: [0u8; 32],
next_header_key_send: sk,
next_header_key_recv: sk,
};
// TODO: What kind of AD should be used?
// Alice sends it to Bob, and Bob decrypts.
let message_to_bob = b"hai bobz";
println!("Alice: n_recv={}, n_send={}, n_prev={}", ars.n_recv, ars.n_send, ars.n_prev);
let (enc_header, ciphertext) = ars.ratchet_encrypt(message_to_bob, &[]);
println!("Bob: n_recv={}, n_send={}, n_prev={}", brs.n_recv, brs.n_send, brs.n_prev);
let plaintext = brs.ratchet_decrypt(&enc_header, &ciphertext, &[]);
assert_eq!(plaintext, message_to_bob);
println!("Bob decrypted message: {}", String::from_utf8_lossy(&plaintext));
// Bob replies to Alice.
let message_to_alice = b"hai alice, what's up?";
println!("Bob: n_recv={}, n_send={}, n_prev={}", brs.n_recv, brs.n_send, brs.n_prev);
let (enc_header, ciphertext) = brs.ratchet_encrypt(message_to_alice, &[]);
println!("Alice: n_recv={}, n_send={}, n_prev={}", ars.n_recv, ars.n_send, ars.n_prev);
let plaintext = ars.ratchet_decrypt(&enc_header, &ciphertext, &[]);
assert_eq!(plaintext, message_to_alice);
println!("Alice decrypted message: {}", String::from_utf8_lossy(&plaintext));
// Alice loves Bob.
let message_to_bob = b"you schizo";
println!("Alice: n_recv={}, n_send={}, n_prev={}", ars.n_recv, ars.n_send, ars.n_prev);
let (enc_header, ciphertext) = ars.ratchet_encrypt(message_to_bob, &[]);
println!("Bob: n_recv={}, n_send={}, n_prev={}", brs.n_recv, brs.n_send, brs.n_prev);
let plaintext = brs.ratchet_decrypt(&enc_header, &ciphertext, &[]);
assert_eq!(plaintext, message_to_bob);
println!("Bob decrypted message: {}", String::from_utf8_lossy(&plaintext));
// Let's try out of order
let message_to_bob1 = b"hello";
let message_to_bob2 = b"jello";
let (enc_header1, ciphertext1) = ars.ratchet_encrypt(message_to_bob1, &[]);
let (enc_header2, ciphertext2) = ars.ratchet_encrypt(message_to_bob2, &[]);
// Slow Bob
let plaintext = brs.ratchet_decrypt(&enc_header2, &ciphertext2, &[]);
assert_eq!(plaintext, message_to_bob2);
let plaintext = brs.ratchet_decrypt(&enc_header1, &ciphertext1, &[]);
assert_eq!(plaintext, message_to_bob1);
let message_to_alice = b"weaponised autism";
let (enc_header, ciphertext) = brs.ratchet_encrypt(message_to_alice, &[]);
let plaintext = ars.ratchet_decrypt(&enc_header, &ciphertext, &[]);
assert_eq!(plaintext, message_to_alice);
}