Skip to content

Commit

Permalink
feat: Add interoperability with DR and X3DH based on Signal specs[1][2]
Browse files Browse the repository at this point in the history
  • Loading branch information
dkasak and poljar committed Feb 4, 2024
1 parent e477153 commit fa9776f
Show file tree
Hide file tree
Showing 25 changed files with 2,015 additions and 553 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["libolm-compat"]
default = ["libolm-compat", "interolm"]
js = ["getrandom/js"]
strict-signatures = []
libolm-compat = []
interolm = []

# The low-level-api feature exposes extra APIs that are only useful in advanced
# use cases and require extra care to use.
low-level-api = []
Expand All @@ -35,7 +37,7 @@ ed25519-dalek = { version = "2.0.0", default-features = false, features = ["rand
getrandom = "0.2.10"
hkdf = "0.12.3"
hmac = "0.12.1"
matrix-pickle = { version = "0.1.1" }
matrix-pickle = { git = "https://github.com/matrix-org/matrix-pickle.git", branch = "dkasak/decode-for-option-rebased" }
pkcs7 = "0.4.1"
prost = "0.12.1"
rand = "0.8.5"
Expand All @@ -46,6 +48,7 @@ sha2 = "0.10.8"
subtle = "2.5.0"
thiserror = "1.0.49"
x25519-dalek = { version = "2.0.0", features = ["serde", "reusable_secrets", "static_secrets"] }
xeddsa = "1.0.2"
zeroize = "1.6.0"

[dev-dependencies]
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ A Rust implementation of Olm and Megolm
vodozemac is a Rust reimplementation of
[libolm](https://gitlab.matrix.org/matrix-org/olm), a cryptographic library
used for end-to-end encryption in [Matrix](https://matrix.org). At its core, it
is an implementation of the [Olm][olm-docs] and [Megolm][megolm-docs] cryptographic ratchets,
along with a high-level API to easily establish cryptographic communication
channels employing those ratchets with other parties. It also implements some
other miscellaneous cryptographic functionality which is useful for building
Matrix clients, such as [SAS][sas].
is an implementation of the [Olm][olm-docs] and [Megolm][megolm-docs]
cryptographic ratchets, along with a high-level API to easily establish
cryptographic communication channels employing those ratchets with other
parties. It also implements some other miscellaneous cryptographic
functionality which is useful for building Matrix clients, such as [SAS][sas].

[olm-docs]:
<https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/olm.md>
Expand Down
14 changes: 14 additions & 0 deletions src/cipher/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@ struct ExpandedKeys(Box<[u8; 80]>);
impl ExpandedKeys {
const OLM_HKDF_INFO: &'static [u8] = b"OLM_KEYS";
const MEGOLM_HKDF_INFO: &'static [u8] = b"MEGOLM_KEYS";
#[cfg(feature = "interolm")]
const INTEROLM_HKDF_INFO: &'static [u8] = b"OLM_KEYS";

fn new(message_key: &[u8; 32]) -> Self {
Self::new_helper(message_key, Self::OLM_HKDF_INFO)
}

#[cfg(feature = "interolm")]
fn new_interolm(message_key: &[u8; 32]) -> Self {
Self::new_helper(message_key, Self::INTEROLM_HKDF_INFO)
}

fn new_megolm(message_key: &[u8; 128]) -> Self {
Self::new_helper(message_key, Self::MEGOLM_HKDF_INFO)
}
Expand Down Expand Up @@ -74,6 +81,13 @@ impl CipherKeys {
Self::from_expanded_keys(expanded_keys)
}

#[cfg(feature = "interolm")]
pub fn new_interolm(message_key: &[u8; 32]) -> Self {
let expanded_keys = ExpandedKeys::new_interolm(message_key);

Self::from_expanded_keys(expanded_keys)
}

pub fn new_megolm(message_key: &[u8; 128]) -> Self {
let expanded_keys = ExpandedKeys::new_megolm(message_key);

Expand Down
91 changes: 86 additions & 5 deletions src/cipher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ use aes::{
Aes256,
};
use hmac::{digest::MacError, Hmac, Mac as MacT};
use key::CipherKeys;
use sha2::Sha256;
use thiserror::Error;

use crate::{cipher::key::CipherKeys, Curve25519PublicKey};

type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;
type HmacSha256 = Hmac<Sha256>;
Expand Down Expand Up @@ -77,6 +78,38 @@ impl From<[u8; Mac::TRUNCATED_LEN]> for MessageMac {
}
}

#[cfg(feature = "interolm")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InterolmMessageMac(pub(crate) [u8; Mac::TRUNCATED_LEN]);

#[cfg(feature = "interolm")]
impl InterolmMessageMac {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}

#[cfg(feature = "interolm")]
impl From<Mac> for InterolmMessageMac {
fn from(m: Mac) -> Self {
Self(m.truncate())
}
}

#[cfg(feature = "interolm")]
impl From<[u8; Mac::TRUNCATED_LEN]> for InterolmMessageMac {
fn from(m: [u8; Mac::TRUNCATED_LEN]) -> Self {
Self(m)
}
}

#[cfg(feature = "interolm")]
impl From<InterolmMessageMac> for MessageMac {
fn from(value: InterolmMessageMac) -> Self {
Self::Truncated(value.0)
}
}

#[derive(Debug, Error)]
pub enum DecryptionError {
#[error("Failed decrypting, invalid padding")]
Expand All @@ -99,6 +132,13 @@ impl Cipher {
Self { keys }
}

#[cfg(feature = "interolm")]
pub fn new_interolm(key: &[u8; 32]) -> Self {
let keys = CipherKeys::new_interolm(key);

Self { keys }
}

pub fn new_megolm(&key: &[u8; 128]) -> Self {
let keys = CipherKeys::new_megolm(&key);

Expand All @@ -114,7 +154,7 @@ impl Cipher {

fn get_hmac(&self) -> HmacSha256 {
// We don't use HmacSha256::new() here because it expects a 64-byte
// large HMAC key while the Olm spec defines a 32-byte one instead.
// HMAC key while the Olm spec uses a 32-byte one instead.
//
// https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/olm.md#version-1
HmacSha256::new_from_slice(self.keys.mac_key()).expect("Invalid HMAC key size")
Expand All @@ -129,10 +169,24 @@ impl Cipher {
let mut hmac = self.get_hmac();
hmac.update(message);

let mac_bytes = hmac.finalize().into_bytes();
let mac = hmac.finalize().into_bytes().into();

Mac(mac)
}

pub fn mac_interolm(
&self,
sender_identity: Curve25519PublicKey,
receiver_identity: Curve25519PublicKey,
message: &[u8],
) -> Mac {
let mut hmac = self.get_hmac();

let mut mac = [0u8; 32];
mac.copy_from_slice(&mac_bytes);
hmac.update(&sender_identity.to_interolm_bytes());
hmac.update(&receiver_identity.to_interolm_bytes());
hmac.update(message);

let mac = hmac.finalize().into_bytes().into();

Mac(mac)
}
Expand Down Expand Up @@ -178,6 +232,22 @@ impl Cipher {
hmac.verify_truncated_left(tag)
}

#[cfg(not(fuzzing))]
pub fn verify_interolm_mac(
&self,
message: &[u8],
sender_identity: Curve25519PublicKey,
receiver_identity: Curve25519PublicKey,
tag: &[u8],
) -> Result<(), MacError> {
let mut hmac = self.get_hmac();

hmac.update(&sender_identity.to_interolm_bytes());
hmac.update(&receiver_identity.to_interolm_bytes());
hmac.update(message);
hmac.verify_truncated_left(tag)
}

/// A verify_mac method that always succeeds.
///
/// Useful if we're fuzzing vodozemac, since MAC verification discards a lot
Expand All @@ -191,4 +261,15 @@ impl Cipher {
pub fn verify_truncated_mac(&self, _: &[u8], _: &[u8]) -> Result<(), MacError> {
Ok(())
}

#[cfg(fuzzing)]
pub fn verify_interolm_mac(
&self,
_: &[u8],
_: Curve25519PublicKey,
_: Curve25519PublicKey,
_: &[u8],
) -> Result<(), MacError> {
Ok(())
}
}
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
#![deny(
clippy::mem_forget,
clippy::unwrap_used,
dead_code,
//dead_code,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
Expand All @@ -211,6 +211,7 @@
mod cipher;
mod types;
mod utilities;
mod xeddsa;

pub mod hazmat;
pub mod megolm;
Expand All @@ -225,6 +226,8 @@ pub use types::{
};
pub use utilities::{base64_decode, base64_encode};

pub use crate::xeddsa::{SignatureError as XEdDsaSignatureError, XEdDsaSignature};

/// Error type describing the various ways Vodozemac pickles can fail to be
/// decoded.
#[derive(Debug, thiserror::Error)]
Expand All @@ -235,7 +238,7 @@ pub enum PickleError {
/// The encrypted pickle could not have been decrypted.
#[error("The pickle couldn't be decrypted: {0}")]
Decryption(#[from] crate::cipher::DecryptionError),
/// The serialized Vodozemac object couldn't be deserialized.
/// The serialized Vodozemac object couldn't be deserialised.
#[error("The pickle couldn't be deserialized: {0}")]
Serialization(#[from] serde_json::Error),
}
Expand Down
8 changes: 4 additions & 4 deletions src/megolm/inbound_group_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub struct DecryptedMessage {
}

impl InboundGroupSession {
pub fn new(key: &SessionKey, session_config: SessionConfig) -> Self {
pub fn new(key: &SessionKey, config: SessionConfig) -> Self {
let initial_ratchet =
Ratchet::from_bytes(key.session_key.ratchet.clone(), key.session_key.ratchet_index);
let latest_ratchet = initial_ratchet.clone();
Expand All @@ -109,11 +109,11 @@ impl InboundGroupSession {
latest_ratchet,
signing_key: key.session_key.signing_key,
signing_key_verified: true,
config: session_config,
config,
}
}

pub fn import(session_key: &ExportedSessionKey, session_config: SessionConfig) -> Self {
pub fn import(session_key: &ExportedSessionKey, config: SessionConfig) -> Self {
let initial_ratchet =
Ratchet::from_bytes(session_key.ratchet.clone(), session_key.ratchet_index);
let latest_ratchet = initial_ratchet.clone();
Expand All @@ -123,7 +123,7 @@ impl InboundGroupSession {
latest_ratchet,
signing_key: session_key.signing_key,
signing_key_verified: false,
config: session_config,
config,
}
}

Expand Down
Loading

0 comments on commit fa9776f

Please sign in to comment.