Skip to content

Commit

Permalink
Merge pull request #142 from Johennes/johannes/megolm-mutation-tests
Browse files Browse the repository at this point in the history
test(megolm): Add mutation tests
  • Loading branch information
poljar authored Apr 29, 2024
2 parents 00a65cc + 7fde6e6 commit 420ce82
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 11 deletions.
11 changes: 9 additions & 2 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ exclude_re = [
"src/olm/messages/message\\.rs.*replace \\+ with \\* in <impl TryFrom for Message>::try_from",
# Due to the bit shifting | and ^ are equivalent here
"replace \\| with \\^ in SasBytes::bytes_to_decimal",
# Drop implementations perform zeroisation which cannot be tested in Rust
"impl Drop",
# Drop & Zeroize implementations perform zeroisation which cannot be tested in Rust
"impl (Drop|Zeroize)",
# These cause olm/account tests to hang
"RemoteChainKey::chain_index",
"RemoteChainKey::advance",
# Intentionally returns Ok(()) in all cases
"replace Cipher::verify_truncated_mac -> Result<\\(\\), MacError> with Ok\\(\\(\\)\\)",
# The constant value can't really be tested
"src/olm/account/one_time_keys\\.rs.*replace \\* with \\+$",
# Not testable because the latest ratchet is cloned from the initial one when it's past the requested index
"replace < with == in InboundGroupSession::find_ratchet",
# Causes an infinite loop
"replace -= with \\+= in Ratchet::advance_to",
# Causes an infinite loop
"replace -= with /= in Ratchet::advance_to",

]
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ jobs:
run: cargo clippy --all-targets -- -D warnings

test:
name: ${{ matrix.target.name }}
name: ${{ matrix.target.name }} ${{ matrix.channel }}
needs: [clippy]

runs-on: ${{ matrix.target.os }}
strategy:
matrix:
target: [
{ "os": "ubuntu-latest", "toolchain": "x86_64-unknown-linux-gnu", "name": "Linux GNU" },
{ "os": "macOS-latest", "toolchain": "x86_64-apple-darwin", "name": "macOS" },
{ "os": "macos-13", "toolchain": "x86_64-apple-darwin ", "name": "macOS x86" },
{ "os": "macos-latest", "toolchain": "aarch64-apple-darwin", "name": "macOS arm" },
{ "os": "windows-latest", "toolchain": "x86_64-pc-windows-msvc", "name": "Windows MSVC" },
]
channel: [stable, beta, nightly]
Expand Down
17 changes: 17 additions & 0 deletions src/megolm/group_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,20 @@ impl From<GroupSessionPickle> for GroupSession {
Self { ratchet: pickle.ratchet, signing_key: pickle.signing_key, config: pickle.config }
}
}

#[cfg(test)]
mod test {
use crate::megolm::{GroupSession, SessionConfig};

#[test]
fn create_with_session_config() {
assert_eq!(
GroupSession::new(SessionConfig::version_1()).session_config(),
SessionConfig::version_1()
);
assert_eq!(
GroupSession::new(SessionConfig::version_2()).session_config(),
SessionConfig::version_2()
);
}
}
33 changes: 32 additions & 1 deletion src/megolm/inbound_group_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,13 @@ impl From<&GroupSession> for InboundGroupSession {

#[cfg(test)]
mod test {
use olm_rs::outbound_group_session::OlmOutboundGroupSession;

use super::InboundGroupSession;
use crate::megolm::{GroupSession, SessionConfig, SessionOrdering};
use crate::{
cipher::Cipher,
megolm::{GroupSession, SessionConfig, SessionKey, SessionOrdering},
};

#[test]
fn advance_inbound_session() {
Expand Down Expand Up @@ -579,6 +584,32 @@ mod test {
assert_eq!(merged.compare(&mut first_session), SessionOrdering::Equal);
}

#[test]
fn verify_mac() {
let olm_session = OlmOutboundGroupSession::new();
let session_key = SessionKey::from_base64(&olm_session.session_key()).unwrap();
let message = olm_session.encrypt("Hello").as_str().try_into().unwrap();

let mut session = InboundGroupSession::new(&session_key, SessionConfig::version_1());
let ratchet = session.find_ratchet(0).unwrap();
let cipher = Cipher::new_megolm(ratchet.as_bytes());

session
.verify_mac(&cipher, &message)
.expect("Should verify MAC from matching outbound session");

let olm_session = OlmOutboundGroupSession::new();
let session_key = SessionKey::from_base64(&olm_session.session_key()).unwrap();

let mut session = InboundGroupSession::new(&session_key, SessionConfig::version_1());
let ratchet = session.find_ratchet(0).unwrap();
let cipher = Cipher::new_megolm(ratchet.as_bytes());

session
.verify_mac(&cipher, &message)
.expect_err("Should not verify MAC from different outbound session");
}

/// Test that [`InboundGroupSession::get_cipher_at`] correctly handles the
/// correct range of message indices.`
#[cfg(feature = "low-level-api")]
Expand Down
96 changes: 91 additions & 5 deletions src/megolm/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ pub struct MegolmMessage {
pub(super) signature: Ed25519Signature,
}

impl MegolmMessage {
const MESSAGE_TRUNCATED_SUFFIX_LENGTH: usize = Mac::TRUNCATED_LEN + Ed25519Signature::LENGTH;
const MESSAGE_SUFFIX_LENGTH: usize = Mac::LENGTH + Ed25519Signature::LENGTH;
const MESSAGE_TRUNCATED_SUFFIX_LENGTH: usize = Mac::TRUNCATED_LEN + Ed25519Signature::LENGTH;
const MESSAGE_SUFFIX_LENGTH: usize = Mac::LENGTH + Ed25519Signature::LENGTH;

impl MegolmMessage {
/// The actual ciphertext of the message.
pub fn ciphertext(&self) -> &[u8] {
&self.ciphertext
Expand Down Expand Up @@ -274,8 +274,8 @@ impl TryFrom<&[u8]> for MegolmMessage {
let version = *message.first().ok_or(DecodeError::MissingVersion)?;

let suffix_length = match version {
VERSION => Self::MESSAGE_SUFFIX_LENGTH,
MAC_TRUNCATED_VERSION => Self::MESSAGE_TRUNCATED_SUFFIX_LENGTH,
VERSION => MESSAGE_SUFFIX_LENGTH,
MAC_TRUNCATED_VERSION => MESSAGE_TRUNCATED_SUFFIX_LENGTH,
_ => return Err(DecodeError::InvalidVersion(VERSION, version)),
};

Expand Down Expand Up @@ -346,3 +346,89 @@ impl ProtobufMegolmMessage {
.concat()
}
}

#[cfg(test)]
mod test {
#[cfg(feature = "low-level-api")]
use std::vec;

use crate::{
cipher::Mac,
megolm::{
message::{
MAC_TRUNCATED_VERSION, MESSAGE_SUFFIX_LENGTH, MESSAGE_TRUNCATED_SUFFIX_LENGTH,
VERSION,
},
MegolmMessage,
},
DecodeError, Ed25519Signature,
};
#[cfg(feature = "low-level-api")]
use crate::{Ed25519Keypair, Ed25519PublicKey};

#[test]
fn suffix_lengths() {
assert_eq!(MESSAGE_TRUNCATED_SUFFIX_LENGTH, Mac::TRUNCATED_LEN + Ed25519Signature::LENGTH);
assert_eq!(MESSAGE_SUFFIX_LENGTH, Mac::LENGTH + Ed25519Signature::LENGTH);
}

#[test]
fn message_to_short() {
let mut bytes = [1u8; 97];
bytes[0] = VERSION;
assert!(matches!(
MegolmMessage::try_from(bytes.as_ref()),
Err(DecodeError::MessageTooShort(_))
));
}

#[test]
fn truncated_message_to_short() {
let mut bytes = [1u8; 73];
bytes[0] = MAC_TRUNCATED_VERSION;
assert!(matches!(
MegolmMessage::try_from(bytes.as_ref()),
Err(DecodeError::MessageTooShort(_))
));
}

#[cfg(feature = "low-level-api")]
#[test]
fn add_valid_signature_succeeds() {
let mut message = MegolmMessage {
version: VERSION,
ciphertext: vec![],
message_index: 0,
mac: Mac([0u8; Mac::LENGTH]).into(),
signature: Ed25519Signature::from_slice(&[0; Ed25519Signature::LENGTH]).unwrap(),
};

let signing_key = Ed25519Keypair::new();
let signature = signing_key.sign(&message.to_signature_bytes());

message
.add_signature(signature, signing_key.public_key())
.expect("Should be able to add valid signature");
assert_eq!(message.signature, signature);
}

#[cfg(feature = "low-level-api")]
#[test]
fn add_invalid_signature_fails() {
let mut message = MegolmMessage {
version: VERSION,
ciphertext: vec![],
message_index: 0,
mac: Mac([0u8; Mac::LENGTH]).into(),
signature: Ed25519Signature::from_slice(&[0; Ed25519Signature::LENGTH]).unwrap(),
};

let public_key = Ed25519PublicKey::from_slice(&[0; 32]).unwrap();
let signature = Ed25519Signature::from_slice(&[1; Ed25519Signature::LENGTH]).unwrap();

message
.add_signature(signature, public_key)
.expect_err("Should not be able to add invalid signature");
assert_ne!(message.signature, signature);
}
}
26 changes: 25 additions & 1 deletion src/megolm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,21 @@ mod test {

use super::{ExportedSessionKey, GroupSession, InboundGroupSession, MegolmMessage};
use crate::{
megolm::{GroupSessionPickle, InboundGroupSessionPickle, SessionConfig, SessionKey},
megolm::{
default_config, GroupSessionPickle, InboundGroupSessionPickle, SessionConfig,
SessionKey,
},
run_corpus,
};

const PICKLE_KEY: [u8; 32] = [0u8; 32];

#[test]
fn default_config_is_v1() {
assert_eq!(default_config(), SessionConfig::version_1());
assert_ne!(default_config(), SessionConfig::default());
}

#[test]
fn encrypting() -> Result<()> {
let mut session = GroupSession::new(SessionConfig::version_1());
Expand Down Expand Up @@ -292,6 +301,21 @@ mod test {
Ok(())
}

#[test]
fn message_getters() {
let mut session = GroupSession::new(SessionConfig::version_1());

// Get message with index 2
session.encrypt("foo bar 1");
session.encrypt("foo bar 2");
let message = session.encrypt("foo bar 3");

assert_eq!(message.ciphertext(), message.ciphertext);
assert_eq!(message.message_index(), message.message_index);
assert_eq!(message.mac(), message.mac.as_bytes());
assert_eq!(message.signature(), &message.signature);
}

#[test]
fn fuzz_corpus_decoding() {
run_corpus("megolm-decoding", |data| {
Expand Down
12 changes: 12 additions & 0 deletions src/megolm/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,16 @@ mod tests {
ratchet.counter = (1 << 24) - 1;
ratchet.advance_to(1 << 24);
}

#[test]
fn advance_forward_and_back() {
let mut ratchet = Ratchet::new();
assert_eq!(ratchet.counter, 0);
ratchet.advance();
assert_eq!(ratchet.counter, 1);
ratchet.advance();
assert_eq!(ratchet.counter, 2);
ratchet.advance_to(1);
assert_eq!(ratchet.counter, 1);
}
}
11 changes: 11 additions & 0 deletions src/megolm/session_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,14 @@ impl Default for SessionConfig {
Self::version_2()
}
}

#[cfg(test)]
mod test {
use crate::megolm::{session_config::Version, SessionConfig};

#[test]
fn version() {
assert_eq!(SessionConfig::version_1().version(), Version::V1 as u8);
assert_eq!(SessionConfig::version_2().version(), Version::V2 as u8);
}
}

0 comments on commit 420ce82

Please sign in to comment.