mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-02-10 00:43:09 +00:00
Merge 3797eca0bfd87ac5960488507aedc2c48f05e954 into 7c580b5896196735ec09bd0e0d41377a98a9b29c
This commit is contained in:
commit
aa805529dd
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -218,6 +218,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"chacha20poly1305",
|
||||
"crypto",
|
||||
"hkdf",
|
||||
"rand",
|
||||
"rand_core",
|
||||
@ -473,6 +474,7 @@ dependencies = [
|
||||
"crypto",
|
||||
"hex",
|
||||
"prost",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"safer-ffi",
|
||||
"thiserror",
|
||||
|
||||
@ -13,6 +13,7 @@ crypto = { path = "../crypto" }
|
||||
hex = "0.4.3"
|
||||
prost = "0.14.1"
|
||||
rand_core = { version = "0.6" }
|
||||
rand = "0.8.5"
|
||||
safer-ffi = "0.1.13"
|
||||
thiserror = "2.0.17"
|
||||
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "reusable_secrets", "getrandom"] }
|
||||
|
||||
@ -2,7 +2,7 @@ use chat_proto::logoschat::{
|
||||
convos::private_v1::{PrivateV1Frame, private_v1_frame::FrameType},
|
||||
encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption},
|
||||
};
|
||||
use crypto::SecretKey;
|
||||
use crypto::SecretKey32;
|
||||
use prost::{Message, bytes::Bytes};
|
||||
|
||||
use crate::{
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
pub struct PrivateV1Convo {}
|
||||
|
||||
impl PrivateV1Convo {
|
||||
pub fn new(_seed_key: SecretKey) -> Self {
|
||||
pub fn new(_seed_key: SecretKey32) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
pub use blake2::Digest;
|
||||
use blake2::{Blake2b, digest};
|
||||
use prost::bytes::Bytes;
|
||||
pub use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
pub use crypto::{PrivateKey32, PublicKey32};
|
||||
|
||||
// TODO: (P4) Make handing of Keys in Prost easier
|
||||
pub trait CopyBytes {
|
||||
fn copy_to_bytes(&self) -> Bytes;
|
||||
}
|
||||
|
||||
impl CopyBytes for PublicKey {
|
||||
impl CopyBytes for PublicKey32 {
|
||||
fn copy_to_bytes(&self) -> Bytes {
|
||||
Bytes::copy_from_slice(self.as_bytes())
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use blake2::{Blake2b512, Digest};
|
||||
use std::fmt;
|
||||
|
||||
use crate::crypto::{PublicKey, StaticSecret};
|
||||
use crate::crypto::{PrivateKey32, PublicKey32};
|
||||
|
||||
pub struct Identity {
|
||||
secret: StaticSecret,
|
||||
secret: PrivateKey32,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Identity {
|
||||
@ -19,7 +19,7 @@ impl fmt::Debug for Identity {
|
||||
impl Identity {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
secret: StaticSecret::random(),
|
||||
secret: PrivateKey32::random(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,11 +27,11 @@ impl Identity {
|
||||
hex::encode(Blake2b512::digest(self.public_key()))
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
PublicKey::from(&self.secret)
|
||||
pub fn public_key(&self) -> PublicKey32 {
|
||||
PublicKey32::from(&self.secret)
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &StaticSecret {
|
||||
pub fn secret(&self) -> &PrivateKey32 {
|
||||
&self.secret
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@ use blake2::{
|
||||
Blake2bMac,
|
||||
digest::{FixedOutput, consts::U32},
|
||||
};
|
||||
use crypto::{DomainSeparator, PrekeyBundle, SecretKey, X3Handshake};
|
||||
use crypto::{DomainSeparator, PrekeyBundle, SecretKey32, X3Handshake};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::crypto::{PublicKey, StaticSecret};
|
||||
use crate::crypto::{PrivateKey32, PublicKey32};
|
||||
|
||||
type Blake2bMac256 = Blake2bMac<U32>;
|
||||
|
||||
@ -21,16 +21,16 @@ pub struct InboxHandshake {}
|
||||
impl InboxHandshake {
|
||||
/// Performs
|
||||
pub fn perform_as_initiator<R: RngCore + CryptoRng>(
|
||||
identity_keypair: &StaticSecret,
|
||||
identity_keypair: &PrivateKey32,
|
||||
recipient_bundle: &PrekeyBundle,
|
||||
rng: &mut R,
|
||||
) -> (SecretKey, PublicKey) {
|
||||
) -> (SecretKey32, PublicKey32) {
|
||||
// Perform X3DH handshake to get shared secret
|
||||
let (shared_secret, ephemeral_public) =
|
||||
InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng);
|
||||
|
||||
let seed_key = Self::derive_keys_from_shared_secret(shared_secret);
|
||||
(seed_key, ephemeral_public)
|
||||
(seed_key, ephemeral_public.into())
|
||||
}
|
||||
|
||||
/// Perform the Inbox Handshake after receiving a keyBundle
|
||||
@ -42,12 +42,12 @@ impl InboxHandshake {
|
||||
/// * `initiator_identity` - Initiator's identity public key
|
||||
/// * `initiator_ephemeral` - Initiator's ephemeral public key
|
||||
pub fn perform_as_responder(
|
||||
identity_keypair: &StaticSecret,
|
||||
signed_prekey: &StaticSecret,
|
||||
onetime_prekey: Option<&StaticSecret>,
|
||||
initiator_identity: &PublicKey,
|
||||
initiator_ephemeral: &PublicKey,
|
||||
) -> SecretKey {
|
||||
identity_keypair: &PrivateKey32,
|
||||
signed_prekey: &PrivateKey32,
|
||||
onetime_prekey: Option<&PrivateKey32>,
|
||||
initiator_identity: &PublicKey32,
|
||||
initiator_ephemeral: &PublicKey32,
|
||||
) -> SecretKey32 {
|
||||
// Perform X3DH to get shared secret
|
||||
let shared_secret = InboxKeyExchange::responder(
|
||||
identity_keypair,
|
||||
@ -61,7 +61,7 @@ impl InboxHandshake {
|
||||
}
|
||||
|
||||
/// Derive keys from X3DH shared secret
|
||||
fn derive_keys_from_shared_secret(shared_secret: SecretKey) -> SecretKey {
|
||||
fn derive_keys_from_shared_secret(shared_secret: SecretKey32) -> SecretKey32 {
|
||||
let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal(
|
||||
shared_secret.as_bytes(),
|
||||
&[], // No salt - input already has high entropy
|
||||
@ -85,17 +85,17 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
let alice_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey32::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
let bob_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey);
|
||||
|
||||
// Create Bob's prekey bundle
|
||||
let bob_bundle = PrekeyBundle {
|
||||
identity_key: PublicKey::from(&bob_identity),
|
||||
identity_key: PublicKey32::from(&bob_identity),
|
||||
signed_prekey: bob_signed_prekey_pub,
|
||||
signature: [0u8; 64],
|
||||
onetime_prekey: None,
|
||||
|
||||
@ -5,18 +5,18 @@ use rand_core::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crypto::{PrekeyBundle, SecretKey};
|
||||
use crypto::{PrekeyBundle, SecretKey32};
|
||||
|
||||
use crate::context::Introduction;
|
||||
use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo};
|
||||
use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey, StaticSecret};
|
||||
use crate::crypto::{Blake2b128, CopyBytes, Digest, PrivateKey32, PublicKey32};
|
||||
use crate::identity::Identity;
|
||||
use crate::inbox::handshake::InboxHandshake;
|
||||
use crate::proto;
|
||||
use crate::types::{AddressedEncryptedPayload, ContentData};
|
||||
|
||||
/// Compute the deterministic Delivery_address for an installation
|
||||
fn delivery_address_for_installation(_: PublicKey) -> String {
|
||||
fn delivery_address_for_installation(_: PublicKey32) -> String {
|
||||
// TODO: Implement Delivery Address
|
||||
"delivery_address".into()
|
||||
}
|
||||
@ -24,7 +24,7 @@ fn delivery_address_for_installation(_: PublicKey) -> String {
|
||||
pub struct Inbox {
|
||||
ident: Rc<Identity>,
|
||||
local_convo_id: String,
|
||||
ephemeral_keys: HashMap<String, StaticSecret>,
|
||||
ephemeral_keys: HashMap<String, PrivateKey32>,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for Inbox {
|
||||
@ -46,7 +46,7 @@ impl Inbox {
|
||||
Self {
|
||||
ident,
|
||||
local_convo_id,
|
||||
ephemeral_keys: HashMap::<String, StaticSecret>::new(),
|
||||
ephemeral_keys: HashMap::<String, PrivateKey32>::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +56,9 @@ impl Inbox {
|
||||
}
|
||||
|
||||
pub fn create_bundle(&mut self) -> PrekeyBundle {
|
||||
let ephemeral = StaticSecret::random();
|
||||
let ephemeral = PrivateKey32::random_from_rng(&mut OsRng);
|
||||
|
||||
let signed_prekey = PublicKey::from(&ephemeral);
|
||||
let signed_prekey = PublicKey32::from(&ephemeral);
|
||||
self.ephemeral_keys
|
||||
.insert(hex::encode(signed_prekey.as_bytes()), ephemeral);
|
||||
|
||||
@ -140,7 +140,7 @@ impl Inbox {
|
||||
fn perform_handshake(
|
||||
&self,
|
||||
payload: proto::EncryptedPayload,
|
||||
) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> {
|
||||
) -> Result<(SecretKey32, proto::InboxV1Frame), ChatError> {
|
||||
let handshake = Self::extract_payload(payload)?;
|
||||
let header = handshake
|
||||
.header
|
||||
@ -149,12 +149,12 @@ impl Inbox {
|
||||
|
||||
let ephemeral_key = self.lookup_ephemeral_key(&pubkey_hex)?;
|
||||
|
||||
let initator_static = PublicKey::from(
|
||||
let initator_static = PublicKey32::from(
|
||||
<[u8; 32]>::try_from(header.initiator_static.as_ref())
|
||||
.map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?,
|
||||
);
|
||||
|
||||
let initator_ephemeral = PublicKey::from(
|
||||
let initator_ephemeral = PublicKey32::from(
|
||||
<[u8; 32]>::try_from(header.initiator_ephemeral.as_ref())
|
||||
.map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?,
|
||||
);
|
||||
@ -193,7 +193,7 @@ impl Inbox {
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn lookup_ephemeral_key(&self, key: &str) -> Result<&StaticSecret, ChatError> {
|
||||
fn lookup_ephemeral_key(&self, key: &str) -> Result<&PrivateKey32, ChatError> {
|
||||
self.ephemeral_keys
|
||||
.get(key)
|
||||
.ok_or_else(|| return ChatError::UnknownEphemeralKey())
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use crypto::PrekeyBundle;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::crypto::PublicKey32;
|
||||
use crate::errors::ChatError;
|
||||
|
||||
/// Supplies remote participants with the required keys to use Inbox protocol
|
||||
pub struct Introduction {
|
||||
pub installation_key: PublicKey,
|
||||
pub ephemeral_key: PublicKey,
|
||||
pub installation_key: PublicKey32,
|
||||
pub ephemeral_key: PublicKey32,
|
||||
}
|
||||
|
||||
impl From<PrekeyBundle> for Introduction {
|
||||
@ -48,13 +48,13 @@ impl TryFrom<&[u8]> for Introduction {
|
||||
.map_err(|_| ChatError::BadParsing("installation_key"))?
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::InvalidKeyLength)?;
|
||||
let installation_key = PublicKey::from(installation_bytes);
|
||||
let installation_key = PublicKey32::from(installation_bytes);
|
||||
|
||||
let ephemeral_bytes: [u8; 32] = hex::decode(parts[1])
|
||||
.map_err(|_| ChatError::BadParsing("ephemeral_key"))?
|
||||
.try_into()
|
||||
.map_err(|_| ChatError::InvalidKeyLength)?;
|
||||
let ephemeral_key = PublicKey::from(ephemeral_bytes);
|
||||
let ephemeral_key = PublicKey32::from(ephemeral_bytes);
|
||||
|
||||
Ok(Introduction {
|
||||
installation_key,
|
||||
|
||||
@ -1,30 +1,100 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use generic_array::{GenericArray, typenum::U32};
|
||||
use generic_array::{GenericArray, typenum::U32};
|
||||
use rand_core::{CryptoRng, OsRng, RngCore};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use x25519_dalek;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)]
|
||||
pub struct SecretKey([u8; 32]);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PublicKey32(x25519_dalek::PublicKey);
|
||||
|
||||
impl SecretKey {
|
||||
impl From<&PrivateKey32> for PublicKey32 {
|
||||
fn from(value: &PrivateKey32) -> Self {
|
||||
Self(x25519_dalek::PublicKey::from(&value.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for PublicKey32 {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
Self(x25519_dalek::PublicKey::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PublicKey32 {
|
||||
type Target = x25519_dalek::PublicKey;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for PublicKey32 {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PublicKey32 {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey32(x25519_dalek::StaticSecret);
|
||||
|
||||
impl PrivateKey32 {
|
||||
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
|
||||
Self(x25519_dalek::StaticSecret::random_from_rng(csprng))
|
||||
}
|
||||
|
||||
//TODO: Remove. Force internal callers provide Rng to make deterministic testing possible
|
||||
pub fn random() -> PrivateKey32 {
|
||||
Self::random_from_rng(&mut OsRng)
|
||||
}
|
||||
|
||||
// Convenience function to generate a PublicKey32
|
||||
pub fn public_key(&self) -> PublicKey32 {
|
||||
PublicKey32::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for PrivateKey32 {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
Self(x25519_dalek::StaticSecret::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PrivateKey32 {
|
||||
type Target = x25519_dalek::StaticSecret;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)]
|
||||
pub struct SecretKey32([u8; 32]);
|
||||
|
||||
impl SecretKey32 {
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for SecretKey {
|
||||
impl From<[u8; 32]> for SecretKey32 {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
SecretKey(value)
|
||||
SecretKey32(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GenericArray<u8, U32>> for SecretKey {
|
||||
impl From<GenericArray<u8, U32>> for SecretKey32 {
|
||||
fn from(value: GenericArray<u8, U32>) -> Self {
|
||||
SecretKey(value.into())
|
||||
SecretKey32(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SecretKey {
|
||||
impl Debug for SecretKey32 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SecretKey").field(&"<32 bytes>").finish()
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod keys;
|
||||
mod x3dh;
|
||||
|
||||
pub use keys::{GenericArray, SecretKey};
|
||||
pub use keys::{PrivateKey32, PublicKey32, SecretKey32};
|
||||
pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake};
|
||||
|
||||
@ -3,17 +3,17 @@ use std::marker::PhantomData;
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::Sha256;
|
||||
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
|
||||
use x25519_dalek::SharedSecret;
|
||||
|
||||
use crate::keys::SecretKey;
|
||||
use crate::keys::{PrivateKey32, PublicKey32, SecretKey32};
|
||||
|
||||
/// A prekey bundle containing the public keys needed to initiate an X3DH key exchange.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrekeyBundle {
|
||||
pub identity_key: PublicKey,
|
||||
pub signed_prekey: PublicKey,
|
||||
pub identity_key: PublicKey32,
|
||||
pub signed_prekey: PublicKey32,
|
||||
pub signature: [u8; 64],
|
||||
pub onetime_prekey: Option<PublicKey>,
|
||||
pub onetime_prekey: Option<PublicKey32>,
|
||||
}
|
||||
|
||||
pub trait DomainSeparator {
|
||||
@ -35,7 +35,7 @@ impl<D: DomainSeparator> X3Handshake<D> {
|
||||
dh2: &SharedSecret,
|
||||
dh3: &SharedSecret,
|
||||
dh4: Option<&SharedSecret>,
|
||||
) -> SecretKey {
|
||||
) -> SecretKey32 {
|
||||
// Concatenate all DH outputs
|
||||
let mut km = Vec::new();
|
||||
km.extend_from_slice(dh1.as_bytes());
|
||||
@ -52,7 +52,7 @@ impl<D: DomainSeparator> X3Handshake<D> {
|
||||
hk.expand(Self::domain_separator(), &mut output)
|
||||
.expect("32 bytes is valid HKDF output length");
|
||||
|
||||
// Move into SecretKey so it gets zeroized on drop.
|
||||
// Move into SecretKey32 so it gets zeroized on drop.
|
||||
output.into()
|
||||
}
|
||||
|
||||
@ -66,13 +66,13 @@ impl<D: DomainSeparator> X3Handshake<D> {
|
||||
/// # Returns
|
||||
/// A tuple of (shared secret bytes, ephemeral public key)
|
||||
pub fn initator<R: RngCore + CryptoRng>(
|
||||
identity_keypair: &StaticSecret,
|
||||
identity_keypair: &PrivateKey32,
|
||||
recipient_bundle: &PrekeyBundle,
|
||||
rng: &mut R,
|
||||
) -> (SecretKey, PublicKey) {
|
||||
// Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations)
|
||||
let ephemeral_secret = StaticSecret::random_from_rng(rng);
|
||||
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
||||
) -> (SecretKey32, PublicKey32) {
|
||||
// Generate ephemeral key for this handshake
|
||||
let ephemeral_secret = PrivateKey32::random_from_rng(rng);
|
||||
let ephemeral_public = PublicKey32::from(&ephemeral_secret);
|
||||
|
||||
// Perform the 4 Diffie-Hellman operations
|
||||
let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey);
|
||||
@ -101,12 +101,12 @@ impl<D: DomainSeparator> X3Handshake<D> {
|
||||
/// # Returns
|
||||
/// The derived shared secret bytes
|
||||
pub fn responder(
|
||||
identity_keypair: &StaticSecret,
|
||||
signed_prekey: &StaticSecret,
|
||||
onetime_prekey: Option<&StaticSecret>,
|
||||
initiator_identity: &PublicKey,
|
||||
initiator_ephemeral: &PublicKey,
|
||||
) -> SecretKey {
|
||||
identity_keypair: &PrivateKey32,
|
||||
signed_prekey: &PrivateKey32,
|
||||
onetime_prekey: Option<&PrivateKey32>,
|
||||
initiator_identity: &PublicKey32,
|
||||
initiator_ephemeral: &PublicKey32,
|
||||
) -> SecretKey32 {
|
||||
let dh1 = signed_prekey.diffie_hellman(initiator_identity);
|
||||
let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral);
|
||||
let dh3 = signed_prekey.diffie_hellman(initiator_ephemeral);
|
||||
@ -134,18 +134,18 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
let alice_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey32::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_identity_pub = PublicKey::from(&bob_identity);
|
||||
let bob_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_identity_pub = PublicKey32::from(&bob_identity);
|
||||
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey);
|
||||
|
||||
let bob_onetime_prekey = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_onetime_prekey_pub = PublicKey::from(&bob_onetime_prekey);
|
||||
let bob_onetime_prekey = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_onetime_prekey_pub = PublicKey32::from(&bob_onetime_prekey);
|
||||
|
||||
// Create Bob's prekey bundle (with one-time prekey)
|
||||
let bob_bundle = PrekeyBundle {
|
||||
@ -177,15 +177,15 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
let alice_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let alice_identity_pub = PublicKey32::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_identity_pub = PublicKey::from(&bob_identity);
|
||||
let bob_identity = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_identity_pub = PublicKey32::from(&bob_identity);
|
||||
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng);
|
||||
let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey);
|
||||
|
||||
// Create Bob's prekey bundle (without one-time prekey)
|
||||
let bob_bundle = PrekeyBundle {
|
||||
|
||||
@ -11,8 +11,8 @@ name = "generate-headers"
|
||||
required-features = ["headers"]
|
||||
|
||||
[dependencies]
|
||||
x25519-dalek = { version="2.0.1", features=["static_secrets"] }
|
||||
chacha20poly1305 = "0.10.1"
|
||||
crypto = { path = "../crypto" }
|
||||
rand_core = "0.6.4"
|
||||
rand = "0.8.5"
|
||||
hkdf = "0.12.4"
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain};
|
||||
use crypto::PrivateKey32;
|
||||
use double_ratchets::{RatchetState, hkdf::PrivateV1Domain};
|
||||
|
||||
fn main() {
|
||||
// === Initial shared secret (X3DH / prekey result in real systems) ===
|
||||
let shared_secret = [42u8; 32];
|
||||
|
||||
let bob_dh = InstallationKeyPair::generate();
|
||||
let bob_dh = PrivateKey32::random();
|
||||
|
||||
let mut alice: RatchetState<PrivateV1Domain> =
|
||||
RatchetState::init_sender(shared_secret, bob_dh.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_dh.public_key());
|
||||
let mut bob: RatchetState<PrivateV1Domain> = RatchetState::init_receiver(shared_secret, bob_dh);
|
||||
|
||||
let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!");
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
//! Demonstrates out-of-order message handling with skipped keys persistence.
|
||||
//!
|
||||
//! Run with: cargo run --example out_of_order_demo --features storage
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crypto::PrivateKey32;
|
||||
#[cfg(feature = "storage")]
|
||||
use double_ratchets::{
|
||||
InstallationKeyPair, RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain,
|
||||
state::Header,
|
||||
RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain, state::Header,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@ -22,10 +22,10 @@ fn run_demo() {
|
||||
|
||||
// Setup
|
||||
let shared_secret = [0x42u8; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = PrivateKey32::random();
|
||||
|
||||
let alice_state: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
let bob_state: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_receiver(shared_secret, bob_keypair);
|
||||
|
||||
@ -81,9 +81,9 @@ fn run_demo() {
|
||||
.expect("Failed to create storage");
|
||||
|
||||
// Re-setup
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = PrivateKey32::random();
|
||||
let alice_state: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
let bob_state: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_receiver(shared_secret, bob_keypair);
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain};
|
||||
use double_ratchets::{RatchetState, hkdf::PrivateV1Domain, types::DhPrivateKey};
|
||||
|
||||
fn main() {
|
||||
// === Initial shared secret (X3DH / prekey result in real systems) ===
|
||||
let shared_secret = [42u8; 32];
|
||||
|
||||
let bob_dh = InstallationKeyPair::generate();
|
||||
let bob_dh = DhPrivateKey::random();
|
||||
|
||||
let mut alice: RatchetState<PrivateV1Domain> =
|
||||
RatchetState::init_sender(shared_secret, bob_dh.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_dh.public_key().clone());
|
||||
let mut bob: RatchetState<PrivateV1Domain> = RatchetState::init_receiver(shared_secret, bob_dh);
|
||||
|
||||
let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!");
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
//! Run with: cargo run --example storage_demo --features storage
|
||||
//! For SQLCipher: cargo run --example storage_demo --features sqlcipher
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crypto::PrivateKey32;
|
||||
#[cfg(feature = "storage")]
|
||||
use double_ratchets::{
|
||||
InstallationKeyPair, RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain,
|
||||
};
|
||||
use double_ratchets::{RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain};
|
||||
|
||||
fn main() {
|
||||
println!("=== Double Ratchet Storage Demo ===\n");
|
||||
@ -140,7 +140,7 @@ fn ensure_tmp_directory() {
|
||||
fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteStorage) {
|
||||
// === Setup: Simulate X3DH key exchange ===
|
||||
let shared_secret = [0x42u8; 32]; // In reality, this comes from X3DH
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = PrivateKey32::random();
|
||||
|
||||
let conv_id = "conv1";
|
||||
|
||||
@ -148,7 +148,7 @@ fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteS
|
||||
alice_storage,
|
||||
conv_id,
|
||||
shared_secret,
|
||||
bob_keypair.public().clone(),
|
||||
bob_keypair.public_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crypto::PublicKey32;
|
||||
use safer_ffi::prelude::*;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::{
|
||||
Header, RatchetState,
|
||||
@ -22,7 +22,7 @@ fn double_ratchet_init_sender(
|
||||
shared_secret: [u8; 32],
|
||||
remote_pub: [u8; 32],
|
||||
) -> repr_c::Box<FFIRatchetState> {
|
||||
let state = RatchetState::init_sender(shared_secret, PublicKey::from(remote_pub));
|
||||
let state = RatchetState::init_sender(shared_secret, PublicKey32::from(remote_pub));
|
||||
Box::new(FFIRatchetState(state)).into()
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
use crypto::PrivateKey32;
|
||||
use safer_ffi::prelude::*;
|
||||
|
||||
use crate::InstallationKeyPair;
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(opaque)]
|
||||
pub struct FFIInstallationKeyPair(pub(crate) InstallationKeyPair);
|
||||
pub struct FFIInstallationKeyPair(pub(crate) PrivateKey32);
|
||||
|
||||
#[ffi_export]
|
||||
fn installation_key_pair_generate() -> repr_c::Box<FFIInstallationKeyPair> {
|
||||
Box::new(FFIInstallationKeyPair(InstallationKeyPair::generate())).into()
|
||||
Box::new(FFIInstallationKeyPair(PrivateKey32::random())).into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn installation_key_pair_public(keypair: &FFIInstallationKeyPair) -> [u8; 32] {
|
||||
keypair.0.public().clone().to_bytes()
|
||||
keypair.0.public_key().to_bytes()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
use rand_core::OsRng;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::types::SharedSecret;
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct InstallationKeyPair {
|
||||
secret: StaticSecret,
|
||||
public: PublicKey,
|
||||
}
|
||||
|
||||
impl InstallationKeyPair {
|
||||
pub fn generate() -> Self {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
Self { secret, public }
|
||||
}
|
||||
|
||||
pub fn dh(&self, their_public: &PublicKey) -> SharedSecret {
|
||||
self.secret.diffie_hellman(their_public).to_bytes()
|
||||
}
|
||||
|
||||
pub fn public(&self) -> &PublicKey {
|
||||
&self.public
|
||||
}
|
||||
|
||||
/// Export the secret key as raw bytes for serialization/storage.
|
||||
pub fn secret_bytes(&self) -> &[u8; 32] {
|
||||
self.secret.as_bytes()
|
||||
}
|
||||
|
||||
/// Import the secret key from raw bytes.
|
||||
pub fn from_secret_bytes(bytes: [u8; 32]) -> Self {
|
||||
let secret = StaticSecret::from(bytes);
|
||||
let public = PublicKey::from(&secret);
|
||||
Self { secret, public }
|
||||
}
|
||||
}
|
||||
@ -2,14 +2,12 @@ pub mod aead;
|
||||
pub mod errors;
|
||||
pub mod ffi;
|
||||
pub mod hkdf;
|
||||
pub mod keypair;
|
||||
pub mod reader;
|
||||
pub mod state;
|
||||
#[cfg(feature = "storage")]
|
||||
pub mod storage;
|
||||
pub mod types;
|
||||
|
||||
pub use keypair::InstallationKeyPair;
|
||||
pub use state::{Header, RatchetState};
|
||||
#[cfg(feature = "storage")]
|
||||
pub use storage::{RatchetSession, SessionError, SqliteStorage, StorageConfig, StorageError};
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
use std::{collections::HashMap, marker::PhantomData};
|
||||
|
||||
use crypto::PublicKey32;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
|
||||
use x25519_dalek::PublicKey;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use crate::{
|
||||
aead::{decrypt, encrypt},
|
||||
errors::RatchetError,
|
||||
hkdf::{DefaultDomain, HkdfInfo, kdf_chain, kdf_root},
|
||||
keypair::InstallationKeyPair,
|
||||
reader::Reader,
|
||||
types::{ChainKey, MessageKey, Nonce, RootKey, SharedSecret},
|
||||
types::{ChainKey, DhPrivateKey, MessageKey, Nonce, RootKey, SharedSecret},
|
||||
};
|
||||
|
||||
/// Current binary format version.
|
||||
@ -28,14 +27,14 @@ pub struct RatchetState<D: HkdfInfo = DefaultDomain> {
|
||||
pub sending_chain: Option<ChainKey>,
|
||||
pub receiving_chain: Option<ChainKey>,
|
||||
|
||||
pub dh_self: InstallationKeyPair,
|
||||
pub dh_remote: Option<PublicKey>,
|
||||
pub dh_self: DhPrivateKey,
|
||||
pub dh_remote: Option<PublicKey32>,
|
||||
|
||||
pub msg_send: u32,
|
||||
pub msg_recv: u32,
|
||||
pub prev_chain_len: u32,
|
||||
|
||||
pub skipped_keys: HashMap<(PublicKey, u32), MessageKey>,
|
||||
pub skipped_keys: HashMap<(PublicKey32, u32), MessageKey>,
|
||||
|
||||
pub(crate) _domain: PhantomData<D>,
|
||||
}
|
||||
@ -104,8 +103,7 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
write_option(&mut buf, self.sending_chain);
|
||||
write_option(&mut buf, self.receiving_chain);
|
||||
|
||||
let dh_secret = self.dh_self.secret_bytes();
|
||||
buf.extend_from_slice(dh_secret);
|
||||
buf.extend_from_slice(self.dh_self.as_bytes());
|
||||
|
||||
write_option(&mut buf, dh_remote);
|
||||
|
||||
@ -141,10 +139,10 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
let receiving_chain = reader.read_option()?;
|
||||
|
||||
let mut dh_self_bytes: [u8; 32] = reader.read_array()?;
|
||||
let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes);
|
||||
let dh_self = DhPrivateKey::from(dh_self_bytes);
|
||||
dh_self_bytes.zeroize();
|
||||
|
||||
let dh_remote = reader.read_option()?.map(PublicKey::from);
|
||||
let dh_remote = reader.read_option()?.map(PublicKey32::from);
|
||||
|
||||
let msg_send = reader.read_u32()?;
|
||||
let msg_recv = reader.read_u32()?;
|
||||
@ -153,7 +151,7 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
let skipped_count = reader.read_u32()? as usize;
|
||||
let mut skipped_keys = HashMap::with_capacity(skipped_count);
|
||||
for _ in 0..skipped_count {
|
||||
let pk = PublicKey::from(reader.read_array::<32>()?);
|
||||
let pk = PublicKey32::from(reader.read_array::<32>()?);
|
||||
let msg_num = reader.read_u32()?;
|
||||
let mk: MessageKey = reader.read_array()?;
|
||||
skipped_keys.insert((pk, msg_num), mk);
|
||||
@ -198,7 +196,7 @@ impl<'de, D: HkdfInfo> Deserialize<'de> for RatchetState<D> {
|
||||
/// Public header attached to every encrypted message (unencrypted but authenticated).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Header {
|
||||
pub dh_pub: PublicKey,
|
||||
pub dh_pub: PublicKey32,
|
||||
pub msg_num: u32,
|
||||
pub prev_chain_len: u32,
|
||||
}
|
||||
@ -233,12 +231,12 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `RatchetState` ready to send the first message.
|
||||
pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey) -> Self {
|
||||
let dh_self = InstallationKeyPair::generate();
|
||||
pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey32) -> Self {
|
||||
let dh_self = DhPrivateKey::random();
|
||||
|
||||
// Initial DH
|
||||
let dh_out = dh_self.dh(&remote_pub);
|
||||
let (root_key, sending_chain) = kdf_root::<D>(&shared_secret, &dh_out);
|
||||
let dh_out = dh_self.diffie_hellman(&remote_pub);
|
||||
let (root_key, sending_chain) = kdf_root::<D>(&shared_secret, dh_out.as_bytes());
|
||||
|
||||
Self {
|
||||
root_key,
|
||||
@ -271,7 +269,7 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `RatchetState` ready to receive the first message.
|
||||
pub fn init_receiver(shared_secret: SharedSecret, dh_self: InstallationKeyPair) -> Self {
|
||||
pub fn init_receiver(shared_secret: SharedSecret, dh_self: DhPrivateKey) -> Self {
|
||||
Self {
|
||||
root_key: shared_secret,
|
||||
|
||||
@ -296,9 +294,9 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `remote_pub` - The new DH public key from the sender.
|
||||
pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey) {
|
||||
let dh_out = self.dh_self.dh(&remote_pub);
|
||||
let (new_root, recv_chain) = kdf_root::<D>(&self.root_key, &dh_out);
|
||||
pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey32) {
|
||||
let dh_out = self.dh_self.diffie_hellman(&remote_pub);
|
||||
let (new_root, recv_chain) = kdf_root::<D>(&self.root_key, dh_out.as_bytes());
|
||||
|
||||
self.root_key = new_root;
|
||||
self.receiving_chain = Some(recv_chain);
|
||||
@ -312,9 +310,9 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
pub fn dh_ratchet_send(&mut self) {
|
||||
let remote = self.dh_remote.expect("no remote DH key");
|
||||
|
||||
self.dh_self = InstallationKeyPair::generate();
|
||||
let dh_out = self.dh_self.dh(&remote);
|
||||
let (new_root, send_chain) = kdf_root::<D>(&self.root_key, &dh_out);
|
||||
self.dh_self = DhPrivateKey::random();
|
||||
let dh_out = self.dh_self.diffie_hellman(&remote);
|
||||
let (new_root, send_chain) = kdf_root::<D>(&self.root_key, dh_out.as_bytes());
|
||||
|
||||
self.root_key = new_root;
|
||||
self.sending_chain = Some(send_chain);
|
||||
@ -345,7 +343,7 @@ impl<D: HkdfInfo> RatchetState<D> {
|
||||
*chain = next_chain;
|
||||
|
||||
let header = Header {
|
||||
dh_pub: self.dh_self.public().clone(),
|
||||
dh_pub: PublicKey32::from(&self.dh_self),
|
||||
msg_num: self.msg_send,
|
||||
prev_chain_len: self.prev_chain_len,
|
||||
};
|
||||
@ -478,10 +476,10 @@ mod tests {
|
||||
let shared_secret = [0x42; 32];
|
||||
|
||||
// Bob generates his long-term keypair
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
|
||||
// Alice initializes as sender, knowing Bob's public key
|
||||
let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
|
||||
// Bob initializes as receiver with his private key
|
||||
let bob = RatchetState::init_receiver(shared_secret, bob_keypair);
|
||||
@ -554,7 +552,7 @@ mod tests {
|
||||
bob.decrypt_message(&ct, header).unwrap();
|
||||
|
||||
// Bob performs DH ratchet by trying to send
|
||||
let old_bob_pub = bob.dh_self.public().clone();
|
||||
let old_bob_pub = bob.dh_self.public_key();
|
||||
let (bob_ct, bob_header) = {
|
||||
let mut b = bob.clone();
|
||||
b.encrypt_message(b"reply")
|
||||
@ -562,7 +560,7 @@ mod tests {
|
||||
assert_ne!(bob_header.dh_pub, old_bob_pub);
|
||||
|
||||
// Alice receives Bob's message with new DH pub → ratchets
|
||||
let old_alice_pub = alice.dh_self.public().clone();
|
||||
let old_alice_pub = alice.dh_self.public_key();
|
||||
let old_root = alice.root_key;
|
||||
|
||||
// Even if decrypt fails (wrong key), ratchet should happen
|
||||
@ -605,7 +603,7 @@ mod tests {
|
||||
// Tamper with header (change DH pub byte)
|
||||
let mut tampered_pub_bytes = header.dh_pub.to_bytes();
|
||||
tampered_pub_bytes[0] ^= 0xff;
|
||||
header.dh_pub = PublicKey::from(tampered_pub_bytes);
|
||||
header.dh_pub = PublicKey32::from(tampered_pub_bytes);
|
||||
|
||||
let result = bob.decrypt_message(&ct, header);
|
||||
assert!(result.is_err());
|
||||
@ -688,8 +686,8 @@ mod tests {
|
||||
restored.dh_remote.map(|pk| pk.to_bytes())
|
||||
);
|
||||
assert_eq!(
|
||||
alice.dh_self.public().to_bytes(),
|
||||
restored.dh_self.public().to_bytes()
|
||||
alice.dh_self.public_key().to_bytes(),
|
||||
restored.dh_self.public_key().to_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
use x25519_dalek::PublicKey;
|
||||
use crypto::PublicKey32;
|
||||
|
||||
use crate::{
|
||||
InstallationKeyPair,
|
||||
errors::RatchetError,
|
||||
hkdf::HkdfInfo,
|
||||
state::{Header, RatchetState},
|
||||
types::SharedSecret,
|
||||
types::{DhPrivateKey, SharedSecret},
|
||||
};
|
||||
|
||||
use super::{SqliteStorage, StorageError};
|
||||
@ -82,7 +81,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> {
|
||||
storage: &'a mut SqliteStorage,
|
||||
conversation_id: impl Into<String>,
|
||||
shared_secret: SharedSecret,
|
||||
remote_pub: PublicKey,
|
||||
remote_pub: PublicKey32,
|
||||
) -> Result<Self, StorageError> {
|
||||
let state = RatchetState::<D>::init_sender(shared_secret, remote_pub);
|
||||
Self::create(storage, conversation_id, state)
|
||||
@ -93,7 +92,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> {
|
||||
storage: &'a mut SqliteStorage,
|
||||
conversation_id: impl Into<String>,
|
||||
shared_secret: SharedSecret,
|
||||
dh_self: InstallationKeyPair,
|
||||
dh_self: DhPrivateKey,
|
||||
) -> Result<Self, StorageError> {
|
||||
let conversation_id = conversation_id.into();
|
||||
if storage.exists(&conversation_id)? {
|
||||
@ -180,7 +179,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair, storage::StorageConfig};
|
||||
use crate::{hkdf::DefaultDomain, storage::StorageConfig, types::DhPrivateKey};
|
||||
|
||||
fn create_test_storage() -> SqliteStorage {
|
||||
SqliteStorage::new(StorageConfig::InMemory).unwrap()
|
||||
@ -191,9 +190,9 @@ mod tests {
|
||||
let mut storage = create_test_storage();
|
||||
|
||||
let shared_secret = [0x42; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
let alice: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
|
||||
// Create session
|
||||
{
|
||||
@ -214,9 +213,9 @@ mod tests {
|
||||
let mut storage = create_test_storage();
|
||||
|
||||
let shared_secret = [0x42; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
let alice: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
|
||||
// Create and encrypt
|
||||
{
|
||||
@ -238,9 +237,9 @@ mod tests {
|
||||
let mut storage = create_test_storage();
|
||||
|
||||
let shared_secret = [0x42; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
let alice: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
let bob: RatchetState<DefaultDomain> =
|
||||
RatchetState::init_receiver(shared_secret, bob_keypair);
|
||||
|
||||
@ -278,8 +277,8 @@ mod tests {
|
||||
let mut storage = create_test_storage();
|
||||
|
||||
let shared_secret = [0x42; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let bob_pub = bob_keypair.public().clone();
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
let bob_pub = bob_keypair.public_key();
|
||||
|
||||
// First call creates
|
||||
{
|
||||
|
||||
@ -280,7 +280,7 @@ fn blob_to_array<const N: usize>(blob: Vec<u8>) -> [u8; N] {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair};
|
||||
use crate::hkdf::DefaultDomain;
|
||||
|
||||
fn create_test_storage() -> SqliteStorage {
|
||||
SqliteStorage::new(StorageConfig::InMemory).unwrap()
|
||||
@ -288,8 +288,8 @@ mod tests {
|
||||
|
||||
fn create_test_state() -> (RatchetState<DefaultDomain>, RatchetState<DefaultDomain>) {
|
||||
let shared_secret = [0x42; 32];
|
||||
let bob_keypair = InstallationKeyPair::generate();
|
||||
let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
|
||||
let bob_keypair = DhPrivateKey::random();
|
||||
let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key());
|
||||
let bob = RatchetState::init_receiver(shared_secret, bob_keypair);
|
||||
(alice, bob)
|
||||
}
|
||||
@ -309,8 +309,8 @@ mod tests {
|
||||
assert_eq!(alice.msg_recv, loaded.msg_recv);
|
||||
assert_eq!(alice.prev_chain_len, loaded.prev_chain_len);
|
||||
assert_eq!(
|
||||
alice.dh_self.public().to_bytes(),
|
||||
loaded.dh_self.public().to_bytes()
|
||||
alice.dh_self.public_key().as_bytes(),
|
||||
loaded.dh_self.public_key().as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ use crate::{
|
||||
state::{RatchetState, SkippedKey},
|
||||
types::MessageKey,
|
||||
};
|
||||
use crypto::PublicKey32;
|
||||
use thiserror::Error;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StorageError {
|
||||
@ -53,16 +53,20 @@ impl<D: HkdfInfo> From<&RatchetState<D>> for RatchetStateRecord {
|
||||
|
||||
impl RatchetStateRecord {
|
||||
pub fn into_ratchet_state<D: HkdfInfo>(self, skipped_keys: Vec<SkippedKey>) -> RatchetState<D> {
|
||||
use crate::keypair::InstallationKeyPair;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret);
|
||||
let dh_remote = self.dh_remote.map(PublicKey::from);
|
||||
let dh_self = DhPrivateKey::from(self.dh_self_secret);
|
||||
let dh_remote = self.dh_remote.map(PublicKey32::from);
|
||||
|
||||
let skipped: HashMap<(PublicKey, u32), MessageKey> = skipped_keys
|
||||
let skipped: HashMap<(PublicKey32, u32), MessageKey> = skipped_keys
|
||||
.into_iter()
|
||||
.map(|sk| ((PublicKey::from(sk.public_key), sk.msg_num), sk.message_key))
|
||||
.map(|sk| {
|
||||
(
|
||||
(PublicKey32::from(sk.public_key), sk.msg_num),
|
||||
sk.message_key,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
RatchetState {
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
use crypto::PrivateKey32;
|
||||
|
||||
/// Type alias for diffie-hellman private keys.
|
||||
pub type DhPrivateKey = PrivateKey32;
|
||||
|
||||
/// Type alias for root keys (32 bytes).
|
||||
pub type RootKey = [u8; 32];
|
||||
/// Type alias for chain keys (sending/receiving, 32 bytes).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user