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

test(megolm): Add mutation tests #142

Merged
merged 7 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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);
}
}
Loading