Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interoperability with DR and X3DH based on Signal specs #124

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading