mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-03-26 22:23:14 +00:00
Key types (#56)
* Add generic SymmetricKey container * Rename SecretKey to SymmetricKey32 * Update SymmetricKey usage * Add PublicKey * Update PublicKey uses * Add PrivateKey * Replace StaticSecret with PrivateKey * Fix imports * Remove InstallKey from constructors * Final integration * lint fixes * Fixups
This commit is contained in:
parent
3b69f946fd
commit
95ddce9161
@ -6,11 +6,10 @@ use chat_proto::logoschat::{
|
||||
convos::private_v1::{PrivateV1Frame, private_v1_frame::FrameType},
|
||||
encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption},
|
||||
};
|
||||
use crypto::SecretKey;
|
||||
use crypto::{PrivateKey, PublicKey, SymmetricKey32};
|
||||
use double_ratchets::{Header, InstallationKeyPair, RatchetState};
|
||||
use prost::{Message, bytes::Bytes};
|
||||
use std::fmt::Debug;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::{
|
||||
conversation::{ChatError, ConversationId, Convo, Id},
|
||||
@ -38,8 +37,8 @@ impl Role {
|
||||
struct BaseConvoId([u8; 18]);
|
||||
|
||||
impl BaseConvoId {
|
||||
fn new(key: &SecretKey) -> Self {
|
||||
let base = Blake2bMac::<U18>::new_with_salt_and_personal(key.as_slice(), b"", b"L-PV1-CID")
|
||||
fn new(key: &SymmetricKey32) -> Self {
|
||||
let base = Blake2bMac::<U18>::new_with_salt_and_personal(key.as_bytes(), b"", b"L-PV1-CID")
|
||||
.expect("fixed inputs should never fail");
|
||||
Self(base.finalize_fixed().into())
|
||||
}
|
||||
@ -60,16 +59,16 @@ pub struct PrivateV1Convo {
|
||||
}
|
||||
|
||||
impl PrivateV1Convo {
|
||||
pub fn new_initiator(seed_key: SecretKey, remote: PublicKey) -> Self {
|
||||
pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey) -> Self {
|
||||
let base_convo_id = BaseConvoId::new(&seed_key);
|
||||
let local_convo_id = base_convo_id.id_for_participant(Role::Initiator);
|
||||
let remote_convo_id = base_convo_id.id_for_participant(Role::Responder);
|
||||
|
||||
// TODO: Danger - Fix double-ratchets types to Accept SecretKey
|
||||
// TODO: Danger - Fix double-ratchets types to Accept SymmetricKey32
|
||||
// perhaps update the DH to work with cryptocrate.
|
||||
// init_sender doesn't take ownership of the key so a reference can be used.
|
||||
let shared_secret: [u8; 32] = seed_key.as_bytes().to_vec().try_into().unwrap();
|
||||
let dr_state = RatchetState::init_sender(shared_secret, remote);
|
||||
let shared_secret: [u8; 32] = seed_key.DANGER_to_bytes();
|
||||
let dr_state = RatchetState::init_sender(shared_secret, *remote);
|
||||
|
||||
Self {
|
||||
local_convo_id,
|
||||
@ -78,16 +77,17 @@ impl PrivateV1Convo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_responder(
|
||||
seed_key: SecretKey,
|
||||
dh_self: InstallationKeyPair, // TODO: (P3) Rename; This accepts a Ephemeral key in most cases
|
||||
) -> Self {
|
||||
pub fn new_responder(seed_key: SymmetricKey32, dh_self: &PrivateKey) -> Self {
|
||||
let base_convo_id = BaseConvoId::new(&seed_key);
|
||||
let local_convo_id = base_convo_id.id_for_participant(Role::Responder);
|
||||
let remote_convo_id = base_convo_id.id_for_participant(Role::Initiator);
|
||||
|
||||
// TODO: Danger - Fix double-ratchets types to Accept SecretKey
|
||||
let dr_state = RatchetState::init_receiver(seed_key.as_bytes().to_owned(), dh_self);
|
||||
// TODO: (P3) Rename; This accepts a Ephemeral key in most cases
|
||||
let dh_self_installation_keypair =
|
||||
InstallationKeyPair::from_secret_bytes(dh_self.DANGER_to_bytes());
|
||||
// TODO: Danger - Fix double-ratchets types to Accept SymmetricKey32
|
||||
let dr_state =
|
||||
RatchetState::init_receiver(seed_key.DANGER_to_bytes(), dh_self_installation_keypair);
|
||||
|
||||
Self {
|
||||
local_convo_id,
|
||||
@ -135,7 +135,7 @@ impl PrivateV1Convo {
|
||||
|
||||
// Build the Header that DR impl expects
|
||||
let header = Header {
|
||||
dh_pub,
|
||||
dh_pub: *dh_pub,
|
||||
msg_num: dr_header.msg_num,
|
||||
prev_chain_len: dr_header.prev_chain_len,
|
||||
};
|
||||
@ -221,27 +221,23 @@ impl Debug for PrivateV1Convo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use x25519_dalek::StaticSecret;
|
||||
use crypto::PrivateKey;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_roundtrip() {
|
||||
let saro = StaticSecret::random();
|
||||
let raya = StaticSecret::random();
|
||||
let saro = PrivateKey::random();
|
||||
let raya = PrivateKey::random();
|
||||
|
||||
let pub_raya = PublicKey::from(&raya);
|
||||
|
||||
let seed_key = saro.diffie_hellman(&pub_raya);
|
||||
let seed_key = saro.diffie_hellman(&pub_raya).DANGER_to_bytes();
|
||||
let seed_key_saro = SymmetricKey32::from(seed_key);
|
||||
let seed_key_raya = SymmetricKey32::from(seed_key);
|
||||
let send_content_bytes = vec![0, 2, 4, 6, 8];
|
||||
let mut sr_convo =
|
||||
PrivateV1Convo::new_initiator(SecretKey::from(seed_key.to_bytes()), pub_raya);
|
||||
|
||||
let installation_key_pair = InstallationKeyPair::from(raya);
|
||||
let mut rs_convo = PrivateV1Convo::new_responder(
|
||||
SecretKey::from(seed_key.to_bytes()),
|
||||
installation_key_pair,
|
||||
);
|
||||
let mut sr_convo = PrivateV1Convo::new_initiator(seed_key_saro, pub_raya);
|
||||
let mut rs_convo = PrivateV1Convo::new_responder(seed_key_raya, &raya);
|
||||
|
||||
let send_frame = PrivateV1Frame {
|
||||
conversation_id: "_".into(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pub use crypto::{PrivateKey, PublicKey};
|
||||
use prost::bytes::Bytes;
|
||||
pub use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
pub trait CopyBytes {
|
||||
fn copy_to_bytes(&self) -> Bytes;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::crypto::{PublicKey, StaticSecret};
|
||||
use crate::crypto::{PrivateKey, PublicKey};
|
||||
|
||||
pub struct Identity {
|
||||
secret: StaticSecret,
|
||||
secret: PrivateKey,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Identity {
|
||||
@ -18,7 +18,7 @@ impl fmt::Debug for Identity {
|
||||
impl Identity {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
secret: StaticSecret::random(),
|
||||
secret: PrivateKey::random(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ impl Identity {
|
||||
PublicKey::from(&self.secret)
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &StaticSecret {
|
||||
pub fn secret(&self) -> &PrivateKey {
|
||||
&self.secret
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,11 @@ use rand_core::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crypto::{PrekeyBundle, SecretKey};
|
||||
use crypto::{PrekeyBundle, SymmetricKey32};
|
||||
|
||||
use crate::context::Introduction;
|
||||
use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo};
|
||||
use crate::crypto::{CopyBytes, PublicKey, StaticSecret};
|
||||
use crate::crypto::{CopyBytes, PrivateKey, PublicKey};
|
||||
use crate::identity::Identity;
|
||||
use crate::inbox::handshake::InboxHandshake;
|
||||
use crate::proto;
|
||||
@ -25,7 +25,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, PrivateKey>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Inbox {
|
||||
@ -47,12 +47,12 @@ impl Inbox {
|
||||
Self {
|
||||
ident,
|
||||
local_convo_id,
|
||||
ephemeral_keys: HashMap::<String, StaticSecret>::new(),
|
||||
ephemeral_keys: HashMap::<String, PrivateKey>::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_intro_bundle(&mut self) -> Introduction {
|
||||
let ephemeral = StaticSecret::random();
|
||||
let ephemeral = PrivateKey::random();
|
||||
|
||||
let ephemeral_key: PublicKey = (&ephemeral).into();
|
||||
self.ephemeral_keys
|
||||
@ -133,8 +133,7 @@ impl Inbox {
|
||||
|
||||
match frame.frame_type.unwrap() {
|
||||
proto::inbox_v1_frame::FrameType::InvitePrivateV1(_invite_private_v1) => {
|
||||
let mut convo =
|
||||
PrivateV1Convo::new_responder(seed_key, ephemeral_key.clone().into());
|
||||
let mut convo = PrivateV1Convo::new_responder(seed_key, ephemeral_key);
|
||||
|
||||
let Some(enc_payload) = _invite_private_v1.initial_message else {
|
||||
return Err(ChatError::Protocol("missing initial encpayload".into()));
|
||||
@ -169,10 +168,10 @@ impl Inbox {
|
||||
|
||||
fn perform_handshake(
|
||||
&self,
|
||||
ephemeral_key: &StaticSecret,
|
||||
ephemeral_key: &PrivateKey,
|
||||
header: proto::InboxHeaderV1,
|
||||
bytes: Bytes,
|
||||
) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> {
|
||||
) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> {
|
||||
// Get PublicKeys from protobuf
|
||||
let initator_static = PublicKey::from(
|
||||
<[u8; 32]>::try_from(header.initiator_static.as_ref())
|
||||
@ -215,7 +214,7 @@ impl Inbox {
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn lookup_ephemeral_key(&self, key: &str) -> Result<&StaticSecret, ChatError> {
|
||||
fn lookup_ephemeral_key(&self, key: &str) -> Result<&PrivateKey, ChatError> {
|
||||
self.ephemeral_keys
|
||||
.get(key)
|
||||
.ok_or(ChatError::UnknownEphemeralKey())
|
||||
|
||||
@ -2,10 +2,10 @@ use blake2::{
|
||||
Blake2bMac,
|
||||
digest::{FixedOutput, consts::U32},
|
||||
};
|
||||
use crypto::{DomainSeparator, PrekeyBundle, SecretKey, X3Handshake};
|
||||
use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::crypto::{PublicKey, StaticSecret};
|
||||
use crate::crypto::{PrivateKey, PublicKey};
|
||||
|
||||
type Blake2bMac256 = Blake2bMac<U32>;
|
||||
|
||||
@ -21,10 +21,10 @@ pub struct InboxHandshake {}
|
||||
impl InboxHandshake {
|
||||
/// Performs
|
||||
pub fn perform_as_initiator<R: RngCore + CryptoRng>(
|
||||
identity_keypair: &StaticSecret,
|
||||
identity_keypair: &PrivateKey,
|
||||
recipient_bundle: &PrekeyBundle,
|
||||
rng: &mut R,
|
||||
) -> (SecretKey, PublicKey) {
|
||||
) -> (SymmetricKey32, PublicKey) {
|
||||
// Perform X3DH handshake to get shared secret
|
||||
let (shared_secret, ephemeral_public) =
|
||||
InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng);
|
||||
@ -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>,
|
||||
identity_keypair: &PrivateKey,
|
||||
signed_prekey: &PrivateKey,
|
||||
onetime_prekey: Option<&PrivateKey>,
|
||||
initiator_identity: &PublicKey,
|
||||
initiator_ephemeral: &PublicKey,
|
||||
) -> SecretKey {
|
||||
) -> SymmetricKey32 {
|
||||
// Perform X3DH to get shared secret
|
||||
let shared_secret = InboxKeyExchange::responder(
|
||||
identity_keypair,
|
||||
@ -61,9 +61,9 @@ 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: SymmetricKey32) -> SymmetricKey32 {
|
||||
let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal(
|
||||
shared_secret.as_slice(),
|
||||
shared_secret.as_bytes(),
|
||||
&[], // No salt - input already has high entropy
|
||||
b"InboxV1-Seed",
|
||||
)
|
||||
@ -85,12 +85,12 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(rng);
|
||||
let alice_identity = PrivateKey::random_from_rng(rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(rng);
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(rng);
|
||||
let bob_identity = PrivateKey::random_from_rng(rng);
|
||||
let bob_signed_prekey = PrivateKey::random_from_rng(rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
|
||||
// Create Bob's prekey bundle
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use chat_proto::logoschat::intro::IntroBundle;
|
||||
use crypto::Ed25519Signature;
|
||||
use crypto::{Ed25519Signature, PrivateKey, PublicKey};
|
||||
use prost::Message;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
use crate::errors::ChatError;
|
||||
|
||||
@ -17,7 +16,7 @@ fn intro_binding_message(ephemeral: &PublicKey) -> Vec<u8> {
|
||||
}
|
||||
|
||||
pub(crate) fn sign_intro_binding<R: RngCore + CryptoRng>(
|
||||
secret: &StaticSecret,
|
||||
secret: &PrivateKey,
|
||||
ephemeral: &PublicKey,
|
||||
rng: R,
|
||||
) -> Ed25519Signature {
|
||||
@ -44,7 +43,7 @@ pub struct Introduction {
|
||||
impl Introduction {
|
||||
/// Create a new `Introduction` by signing the ephemeral key with the installation secret.
|
||||
pub(crate) fn new<R: RngCore + CryptoRng>(
|
||||
installation_secret: &StaticSecret,
|
||||
installation_secret: &PrivateKey,
|
||||
ephemeral_key: PublicKey,
|
||||
rng: R,
|
||||
) -> Self {
|
||||
@ -147,9 +146,9 @@ mod tests {
|
||||
use rand_core::OsRng;
|
||||
|
||||
fn create_test_introduction() -> Introduction {
|
||||
let install_secret = StaticSecret::random_from_rng(OsRng);
|
||||
let install_secret = PrivateKey::random_from_rng(OsRng);
|
||||
|
||||
let ephemeral_secret = StaticSecret::random_from_rng(OsRng);
|
||||
let ephemeral_secret = PrivateKey::random_from_rng(OsRng);
|
||||
let ephemeral_pub: PublicKey = (&ephemeral_secret).into();
|
||||
|
||||
Introduction::new(&install_secret, ephemeral_pub, OsRng)
|
||||
|
||||
@ -1,35 +1,137 @@
|
||||
use std::fmt::Debug;
|
||||
use generic_array::{GenericArray, typenum::U32};
|
||||
|
||||
pub use generic_array::{GenericArray, typenum::U32};
|
||||
use rand_core::{CryptoRng, OsRng, RngCore};
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
use x25519_dalek::{PublicKey as x25519_Pub, SharedSecret, StaticSecret as x25519_Priv};
|
||||
use xeddsa::xed25519::{self, PublicKey as xed25519_Pub};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)]
|
||||
pub struct SecretKey([u8; 32]);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Zeroize)] // TODO: (!) Zeroize only required by InstallationKeyPair
|
||||
pub struct PublicKey(x25519_Pub);
|
||||
|
||||
impl SecretKey {
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
impl From<x25519_Pub> for PublicKey {
|
||||
fn from(value: x25519_Pub) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
impl From<&x25519_Priv> for PublicKey {
|
||||
fn from(value: &x25519_Priv) -> Self {
|
||||
Self(x25519_Pub::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for PublicKey {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
Self(x25519_Pub::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PublicKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PublicKey> for xed25519_Pub {
|
||||
fn from(value: &PublicKey) -> Self {
|
||||
Self::from(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PublicKey {
|
||||
type Target = x25519_Pub;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for SecretKey {
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey(x25519_Priv);
|
||||
|
||||
impl PrivateKey {
|
||||
pub fn random_from_rng<T: RngCore + CryptoRng>(csprng: T) -> Self {
|
||||
Self(x25519_Priv::random_from_rng(csprng))
|
||||
}
|
||||
|
||||
//TODO: Remove. Force internal callers provide Rng to make deterministic testing possible
|
||||
pub fn random() -> PrivateKey {
|
||||
Self::random_from_rng(OsRng)
|
||||
}
|
||||
|
||||
pub fn diffie_hellman(&self, public_key: &PublicKey) -> SymmetricKey32 {
|
||||
(&self.0.diffie_hellman(&public_key.0)).into()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)] // All caps makes this standout more in reviews.
|
||||
pub fn DANGER_to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PrivateKey> for xed25519::PrivateKey {
|
||||
fn from(value: &PrivateKey) -> Self {
|
||||
Self::from(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PrivateKey> for PublicKey {
|
||||
fn from(value: &PrivateKey) -> Self {
|
||||
Self(x25519_Pub::from(&value.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for PrivateKey {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
SecretKey(value)
|
||||
Self(x25519_Priv::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GenericArray<u8, U32>> for SecretKey {
|
||||
fn from(value: GenericArray<u8, U32>) -> Self {
|
||||
SecretKey(value.into())
|
||||
/// A Generic secret key container for symmetric keys.
|
||||
/// SymmetricKey retains ownership of bytes to ensure they are Zeroized on drop.
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)]
|
||||
pub struct SymmetricKey<const N: usize>([u8; N]);
|
||||
|
||||
impl<const N: usize> SymmetricKey<N> {
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
|
||||
/// Returns internal [u8; N].
|
||||
/// This function bypasses zeroize_on_drop, and will be deprecated once all consumers have been migrated
|
||||
#[allow(nonstandard_style)]
|
||||
pub fn DANGER_to_bytes(self) -> [u8; N] {
|
||||
// TODO: (P3) Remove once DR ported to use safe keys.
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SecretKey {
|
||||
impl<const N: usize> From<[u8; N]> for SymmetricKey<N> {
|
||||
fn from(value: [u8; N]) -> Self {
|
||||
SymmetricKey(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Debug for SymmetricKey<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SecretKey").field(&"<32 bytes>").finish()
|
||||
write!(f, "SymmetricKey(...{N} Bytes Redacted...)")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (P5) look into typenum::generic_const_mappings to avoid having to implement From<U>
|
||||
pub type SymmetricKey32 = SymmetricKey<32>;
|
||||
|
||||
impl From<GenericArray<u8, U32>> for SymmetricKey32 {
|
||||
fn from(value: GenericArray<u8, U32>) -> Self {
|
||||
SymmetricKey(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SharedSecret> for SymmetricKey32 {
|
||||
// This relies on the feature 'zeroize' being set for x25519-dalek.
|
||||
// If not the SharedSecret will need to manually zeroized
|
||||
fn from(value: &SharedSecret) -> Self {
|
||||
value.to_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,6 @@ mod keys;
|
||||
mod x3dh;
|
||||
mod xeddsa_sign;
|
||||
|
||||
pub use keys::{GenericArray, SecretKey};
|
||||
pub use keys::{PrivateKey, PublicKey, SymmetricKey32};
|
||||
pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake};
|
||||
pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify};
|
||||
|
||||
@ -3,9 +3,8 @@ use std::marker::PhantomData;
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::Sha256;
|
||||
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
|
||||
|
||||
use crate::keys::SecretKey;
|
||||
use crate::keys::{PrivateKey, PublicKey, SymmetricKey32};
|
||||
use crate::xeddsa_sign::Ed25519Signature;
|
||||
|
||||
/// A prekey bundle containing the public keys needed to initiate an X3DH key exchange.
|
||||
@ -32,11 +31,11 @@ impl<D: DomainSeparator> X3Handshake<D> {
|
||||
|
||||
/// Derive the shared secret from DH outputs using HKDF-SHA256
|
||||
fn derive_shared_secret(
|
||||
dh1: &SharedSecret,
|
||||
dh2: &SharedSecret,
|
||||
dh3: &SharedSecret,
|
||||
dh4: Option<&SharedSecret>,
|
||||
) -> SecretKey {
|
||||
dh1: &SymmetricKey32,
|
||||
dh2: &SymmetricKey32,
|
||||
dh3: &SymmetricKey32,
|
||||
dh4: Option<&SymmetricKey32>,
|
||||
) -> SymmetricKey32 {
|
||||
// Concatenate all DH outputs
|
||||
let mut km = Vec::new();
|
||||
km.extend_from_slice(dh1.as_bytes());
|
||||
@ -53,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 SymmetricKey32 so it gets zeroized on drop.
|
||||
output.into()
|
||||
}
|
||||
|
||||
@ -67,12 +66,12 @@ 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: &PrivateKey,
|
||||
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);
|
||||
) -> (SymmetricKey32, PublicKey) {
|
||||
// Generate ephemeral key for this handshake (using PrivateKey for multiple DH operations)
|
||||
let ephemeral_secret = PrivateKey::random_from_rng(rng);
|
||||
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
||||
|
||||
// Perform the 4 Diffie-Hellman operations
|
||||
@ -102,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>,
|
||||
identity_keypair: &PrivateKey,
|
||||
signed_prekey: &PrivateKey,
|
||||
onetime_prekey: Option<&PrivateKey>,
|
||||
initiator_identity: &PublicKey,
|
||||
initiator_ephemeral: &PublicKey,
|
||||
) -> SecretKey {
|
||||
) -> SymmetricKey32 {
|
||||
let dh1 = signed_prekey.diffie_hellman(initiator_identity);
|
||||
let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral);
|
||||
let dh3 = signed_prekey.diffie_hellman(initiator_ephemeral);
|
||||
@ -135,17 +134,17 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(rng);
|
||||
let alice_identity = PrivateKey::random_from_rng(rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(rng);
|
||||
let bob_identity = PrivateKey::random_from_rng(rng);
|
||||
let bob_identity_pub = PublicKey::from(&bob_identity);
|
||||
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(rng);
|
||||
let bob_signed_prekey = PrivateKey::random_from_rng(rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
|
||||
let bob_onetime_prekey = StaticSecret::random_from_rng(rng);
|
||||
let bob_onetime_prekey = PrivateKey::random_from_rng(rng);
|
||||
let bob_onetime_prekey_pub = PublicKey::from(&bob_onetime_prekey);
|
||||
|
||||
// Create Bob's prekey bundle (with one-time prekey)
|
||||
@ -178,14 +177,14 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Alice (initiator) generates her identity key
|
||||
let alice_identity = StaticSecret::random_from_rng(rng);
|
||||
let alice_identity = PrivateKey::random_from_rng(rng);
|
||||
let alice_identity_pub = PublicKey::from(&alice_identity);
|
||||
|
||||
// Bob (responder) generates his keys
|
||||
let bob_identity = StaticSecret::random_from_rng(rng);
|
||||
let bob_identity = PrivateKey::random_from_rng(rng);
|
||||
let bob_identity_pub = PublicKey::from(&bob_identity);
|
||||
|
||||
let bob_signed_prekey = StaticSecret::random_from_rng(rng);
|
||||
let bob_signed_prekey = PrivateKey::random_from_rng(rng);
|
||||
let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey);
|
||||
|
||||
// Create Bob's prekey bundle (without one-time prekey)
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
//! that allow signing arbitrary messages with X25519 keys.
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use xeddsa::{Sign, Verify, xed25519};
|
||||
|
||||
use crate::{PrivateKey, PublicKey};
|
||||
/// A 64-byte XEdDSA signature over an Ed25519-compatible curve.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Ed25519Signature(pub [u8; 64]);
|
||||
@ -44,7 +44,7 @@ pub struct SignatureError;
|
||||
/// # Returns
|
||||
/// An `Ed25519Signature`
|
||||
pub fn xeddsa_sign<R: RngCore + CryptoRng>(
|
||||
secret: &StaticSecret,
|
||||
secret: &PrivateKey,
|
||||
message: &[u8],
|
||||
mut rng: R,
|
||||
) -> Ed25519Signature {
|
||||
@ -79,7 +79,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sign_and_verify_roundtrip() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let secret = PrivateKey::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
@ -90,12 +90,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_wrong_key_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let secret = PrivateKey::random_from_rng(OsRng);
|
||||
let message = b"test message";
|
||||
|
||||
let signature = xeddsa_sign(&secret, message, OsRng);
|
||||
|
||||
let wrong_secret = StaticSecret::random_from_rng(OsRng);
|
||||
let wrong_secret = PrivateKey::random_from_rng(OsRng);
|
||||
let wrong_public = PublicKey::from(&wrong_secret);
|
||||
|
||||
assert_eq!(
|
||||
@ -106,7 +106,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_wrong_message_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let secret = PrivateKey::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_corrupted_signature_fails() {
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let secret = PrivateKey::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let message = b"test message";
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user