mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-02-10 08:53:08 +00:00
chore: merge main
This commit is contained in:
commit
a5943ae9ff
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -506,6 +506,7 @@ dependencies = [
|
|||||||
"blake2",
|
"blake2",
|
||||||
"chat-proto",
|
"chat-proto",
|
||||||
"crypto",
|
"crypto",
|
||||||
|
"double-ratchets",
|
||||||
"hex",
|
"hex",
|
||||||
"prost",
|
"prost",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ crate-type = ["staticlib","dylib"]
|
|||||||
blake2.workspace = true
|
blake2.workspace = true
|
||||||
chat-proto = { git = "https://github.com/logos-messaging/chat_proto" }
|
chat-proto = { git = "https://github.com/logos-messaging/chat_proto" }
|
||||||
crypto = { path = "../crypto" }
|
crypto = { path = "../crypto" }
|
||||||
|
double-ratchets = { path = "../double-ratchets" }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
prost = "0.14.1"
|
prost = "0.14.1"
|
||||||
rand_core = { version = "0.6" }
|
rand_core = { version = "0.6" }
|
||||||
|
|||||||
@ -111,22 +111,3 @@ impl Context {
|
|||||||
.ok_or_else(|| ChatError::NoConvo(handle))
|
.ok_or_else(|| ChatError::NoConvo(handle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use crate::dm::privatev1::PrivateV1Convo;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn convo_store_get() {
|
|
||||||
let mut store: ChatStore = ChatStore::new();
|
|
||||||
|
|
||||||
let new_convo = PrivateV1Convo::new([0; 32].into());
|
|
||||||
let convo_id = store.insert_chat(new_convo);
|
|
||||||
|
|
||||||
let convo = store.get_mut_chat(&convo_id).ok_or_else(|| 0);
|
|
||||||
convo.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,36 +3,92 @@ use chat_proto::logoschat::{
|
|||||||
encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption},
|
encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption},
|
||||||
};
|
};
|
||||||
use crypto::SecretKey;
|
use crypto::SecretKey;
|
||||||
|
use double_ratchets::{Header, InstallationKeyPair, RatchetState};
|
||||||
use prost::{Message, bytes::Bytes};
|
use prost::{Message, bytes::Bytes};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use x25519_dalek::PublicKey;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{ChatId, HasChatId, Chat},
|
common::{Chat, ChatId, HasChatId},
|
||||||
errors::ChatError,
|
errors::{ChatError, EncryptionError},
|
||||||
|
proto,
|
||||||
types::AddressedEncryptedPayload,
|
types::AddressedEncryptedPayload,
|
||||||
utils::timestamp_millis,
|
utils::timestamp_millis,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub struct PrivateV1Convo {
|
||||||
pub struct PrivateV1Convo {}
|
dr_state: RatchetState,
|
||||||
|
}
|
||||||
|
|
||||||
impl PrivateV1Convo {
|
impl PrivateV1Convo {
|
||||||
pub fn new(_seed_key: SecretKey) -> Self {
|
pub fn new_initiator(seed_key: SecretKey, remote: PublicKey) -> Self {
|
||||||
Self {}
|
// TODO: Danger - Fix double-ratchets types to Accept SecretKey
|
||||||
|
// 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();
|
||||||
|
Self {
|
||||||
|
dr_state: RatchetState::init_sender(shared_secret, remote),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt(&self, frame: PrivateV1Frame) -> EncryptedPayload {
|
pub fn new_responder(seed_key: SecretKey, dh_self: InstallationKeyPair) -> Self {
|
||||||
// TODO: Integrate DR
|
Self {
|
||||||
|
// TODO: Danger - Fix double-ratchets types to Accept SecretKey
|
||||||
|
dr_state: RatchetState::init_receiver(seed_key.as_bytes().to_owned(), dh_self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&mut self, frame: PrivateV1Frame) -> EncryptedPayload {
|
||||||
|
let encoded_bytes = frame.encode_to_vec();
|
||||||
|
let (cipher_text, header) = self.dr_state.encrypt_message(&encoded_bytes);
|
||||||
|
|
||||||
EncryptedPayload {
|
EncryptedPayload {
|
||||||
encryption: Some(Encryption::Doubleratchet(Doubleratchet {
|
encryption: Some(Encryption::Doubleratchet(Doubleratchet {
|
||||||
dh: Bytes::from(vec![]),
|
dh: Bytes::from(Vec::from(header.dh_pub.to_bytes())),
|
||||||
msg_num: 0,
|
msg_num: header.msg_num,
|
||||||
prev_chain_len: 1,
|
prev_chain_len: header.prev_chain_len,
|
||||||
ciphertext: Bytes::from(frame.encode_to_vec()),
|
ciphertext: Bytes::from(cipher_text),
|
||||||
aux: "".into(),
|
aux: "".into(),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrypt(&mut self, payload: EncryptedPayload) -> Result<PrivateV1Frame, EncryptionError> {
|
||||||
|
// Validate and extract the encryption header or return errors
|
||||||
|
let dr_header = if let Some(enc) = payload.encryption {
|
||||||
|
if let proto::Encryption::Doubleratchet(dr) = enc {
|
||||||
|
dr
|
||||||
|
} else {
|
||||||
|
return Err(EncryptionError::Decryption(
|
||||||
|
"incorrect encryption type".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(EncryptionError::Decryption("missing payload".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Turn the bytes into a PublicKey
|
||||||
|
let byte_arr: [u8; 32] = dr_header
|
||||||
|
.dh
|
||||||
|
.to_vec()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| EncryptionError::Decryption("invalid public key length".into()))?;
|
||||||
|
let dh_pub = PublicKey::from(byte_arr);
|
||||||
|
|
||||||
|
// Build the Header that DR impl expects
|
||||||
|
let header = Header {
|
||||||
|
dh_pub,
|
||||||
|
msg_num: dr_header.msg_num,
|
||||||
|
prev_chain_len: dr_header.prev_chain_len,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decrypt into Frame
|
||||||
|
let content_bytes = self
|
||||||
|
.dr_state
|
||||||
|
.decrypt_message(&dr_header.ciphertext, header)
|
||||||
|
.map_err(|e| EncryptionError::Decryption(e.to_string()))?;
|
||||||
|
Ok(PrivateV1Frame::decode(content_bytes.as_slice()).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasChatId for PrivateV1Convo {
|
impl HasChatId for PrivateV1Convo {
|
||||||
@ -67,3 +123,53 @@ impl Chat for PrivateV1Convo {
|
|||||||
self.id().into()
|
self.id().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for PrivateV1Convo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("PrivateV1Convo")
|
||||||
|
.field("dr_state", &"******")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use x25519_dalek::StaticSecret;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_roundtrip() {
|
||||||
|
let saro = StaticSecret::random();
|
||||||
|
let raya = StaticSecret::random();
|
||||||
|
|
||||||
|
let pub_raya = PublicKey::from(&raya);
|
||||||
|
|
||||||
|
let seed_key = saro.diffie_hellman(&pub_raya);
|
||||||
|
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 send_frame = PrivateV1Frame {
|
||||||
|
conversation_id: "_".into(),
|
||||||
|
sender: Bytes::new(),
|
||||||
|
timestamp: timestamp_millis(),
|
||||||
|
frame_type: Some(FrameType::Content(Bytes::from(send_content_bytes.clone()))),
|
||||||
|
};
|
||||||
|
let payload = sr_convo.encrypt(send_frame.clone());
|
||||||
|
let recv_frame = rs_convo.decrypt(payload).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
recv_frame == send_frame,
|
||||||
|
"{:?}. {:?}",
|
||||||
|
recv_frame,
|
||||||
|
send_content_bytes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,3 +21,11 @@ pub enum ChatError {
|
|||||||
#[error("convo with handle: {0} was not found")]
|
#[error("convo with handle: {0} was not found")]
|
||||||
NoConvo(u32),
|
NoConvo(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum EncryptionError {
|
||||||
|
#[error("encryption: {0}")]
|
||||||
|
Encryption(String),
|
||||||
|
#[error("decryption: {0}")]
|
||||||
|
Decryption(String),
|
||||||
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ impl InboxHandshake {
|
|||||||
/// Derive keys from X3DH shared secret
|
/// Derive keys from X3DH shared secret
|
||||||
fn derive_keys_from_shared_secret(shared_secret: SecretKey) -> SecretKey {
|
fn derive_keys_from_shared_secret(shared_secret: SecretKey) -> SecretKey {
|
||||||
let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal(
|
let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal(
|
||||||
shared_secret.as_bytes(),
|
shared_secret.as_slice(),
|
||||||
&[], // No salt - input already has high entropy
|
&[], // No salt - input already has high entropy
|
||||||
b"InboxV1-Seed",
|
b"InboxV1-Seed",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -85,7 +85,7 @@ impl Inbox {
|
|||||||
let (seed_key, ephemeral_pub) =
|
let (seed_key, ephemeral_pub) =
|
||||||
InboxHandshake::perform_as_initiator(&self.ident.secret(), &pkb, &mut rng);
|
InboxHandshake::perform_as_initiator(&self.ident.secret(), &pkb, &mut rng);
|
||||||
|
|
||||||
let mut convo = PrivateV1Convo::new(seed_key);
|
let mut convo = PrivateV1Convo::new_initiator(seed_key, remote_bundle.ephemeral_key);
|
||||||
|
|
||||||
let mut payloads = convo.send_message(initial_message.as_bytes())?;
|
let mut payloads = convo.send_message(initial_message.as_bytes())?;
|
||||||
|
|
||||||
@ -136,16 +136,11 @@ impl Inbox {
|
|||||||
|
|
||||||
fn perform_handshake(
|
fn perform_handshake(
|
||||||
&self,
|
&self,
|
||||||
payload: proto::EncryptedPayload,
|
ephemeral_key: &StaticSecret,
|
||||||
|
header: proto::InboxHeaderV1,
|
||||||
|
bytes: Bytes,
|
||||||
) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> {
|
) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> {
|
||||||
let handshake = Self::extract_payload(payload)?;
|
// Get PublicKeys from protobuf
|
||||||
let header = handshake
|
|
||||||
.header
|
|
||||||
.ok_or(ChatError::UnexpectedPayload("InboxV1Header".into()))?;
|
|
||||||
let pubkey_hex = hex::encode(header.responder_ephemeral.as_ref());
|
|
||||||
|
|
||||||
let ephemeral_key = self.lookup_ephemeral_key(&pubkey_hex)?;
|
|
||||||
|
|
||||||
let initator_static = PublicKey::from(
|
let initator_static = PublicKey::from(
|
||||||
<[u8; 32]>::try_from(header.initiator_static.as_ref())
|
<[u8; 32]>::try_from(header.initiator_static.as_ref())
|
||||||
.map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?,
|
.map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?,
|
||||||
@ -165,7 +160,7 @@ impl Inbox {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Decrypt Content
|
// TODO: Decrypt Content
|
||||||
let frame = proto::InboxV1Frame::decode(handshake.payload)?;
|
let frame = proto::InboxV1Frame::decode(bytes)?;
|
||||||
Ok((seed_key, frame))
|
Ok((seed_key, frame))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,14 +208,23 @@ impl InboundMessageHandler for Inbox {
|
|||||||
return Err(ChatError::Protocol("Example error".into()));
|
return Err(ChatError::Protocol("Example error".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ep = proto::EncryptedPayload::decode(message)?;
|
let handshake = Self::extract_payload(proto::EncryptedPayload::decode(message)?)?;
|
||||||
let (seed_key, frame) = self.perform_handshake(ep)?;
|
|
||||||
|
let header = handshake
|
||||||
|
.header
|
||||||
|
.ok_or(ChatError::UnexpectedPayload("InboxV1Header".into()))?;
|
||||||
|
|
||||||
|
// Get Ephemeral key used by the initator
|
||||||
|
let key_index = hex::encode(header.responder_ephemeral.as_ref());
|
||||||
|
let ephemeral_key = self.lookup_ephemeral_key(&key_index)?;
|
||||||
|
|
||||||
|
// Perform handshake and decrypt frame
|
||||||
|
let (seed_key, frame) = self.perform_handshake(ephemeral_key, header, handshake.payload)?;
|
||||||
|
|
||||||
match frame.frame_type.unwrap() {
|
match frame.frame_type.unwrap() {
|
||||||
chat_proto::logoschat::inbox::inbox_v1_frame::FrameType::InvitePrivateV1(
|
proto::inbox_v1_frame::FrameType::InvitePrivateV1(_invite_private_v1) => {
|
||||||
_invite_private_v1,
|
let convo = PrivateV1Convo::new_responder(seed_key, ephemeral_key.clone().into());
|
||||||
) => {
|
|
||||||
let convo = PrivateV1Convo::new(seed_key);
|
|
||||||
// TODO: Update PrivateV1 Constructor with DR, initial_message
|
// TODO: Update PrivateV1 Constructor with DR, initial_message
|
||||||
Ok((Box::new(convo), vec![]))
|
Ok((Box::new(convo), vec![]))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ pub use chat_proto::logoschat::encryption::encrypted_payload::Encryption;
|
|||||||
pub use chat_proto::logoschat::encryption::inbox_handshake_v1::InboxHeaderV1;
|
pub use chat_proto::logoschat::encryption::inbox_handshake_v1::InboxHeaderV1;
|
||||||
pub use chat_proto::logoschat::encryption::{EncryptedPayload, InboxHandshakeV1};
|
pub use chat_proto::logoschat::encryption::{EncryptedPayload, InboxHandshakeV1};
|
||||||
pub use chat_proto::logoschat::envelope::EnvelopeV1;
|
pub use chat_proto::logoschat::envelope::EnvelopeV1;
|
||||||
pub use chat_proto::logoschat::inbox::InboxV1Frame;
|
pub use chat_proto::logoschat::inbox::{InboxV1Frame, inbox_v1_frame};
|
||||||
pub use chat_proto::logoschat::invite::InvitePrivateV1;
|
pub use chat_proto::logoschat::invite::InvitePrivateV1;
|
||||||
|
|
||||||
pub use prost::Message;
|
pub use prost::Message;
|
||||||
|
|||||||
@ -7,9 +7,13 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
|||||||
pub struct SecretKey([u8; 32]);
|
pub struct SecretKey([u8; 32]);
|
||||||
|
|
||||||
impl SecretKey {
|
impl SecretKey {
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
self.0.as_slice()
|
self.0.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[u8; 32]> for SecretKey {
|
impl From<[u8; 32]> for SecretKey {
|
||||||
|
|||||||
@ -37,3 +37,13 @@ impl InstallationKeyPair {
|
|||||||
Self { secret, public }
|
Self { secret, public }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<StaticSecret> for InstallationKeyPair {
|
||||||
|
fn from(value: StaticSecret) -> Self {
|
||||||
|
let public = PublicKey::from(&value);
|
||||||
|
Self {
|
||||||
|
secret: value,
|
||||||
|
public,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user