diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f429fa11..507b0c94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy --all-targets --all-features -- -D warnings test: name: ${{ matrix.target.name }} ${{ matrix.channel }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1741b334..a50106e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,118 @@ -# Contributing to Vodozemac +# Contributing to vodozemac Thank you for taking the time to contribute to Matrix! This is the repository for Vodozemac, a Rust implementation of Olm and Megolm. -## Sign off +# Writing changelog entries + +We aim to maintain clear and informative changelogs that accurately reflect the +changes in our project. This guide will help you write useful changelog entries +using git-cliff, which fetches changelog entries from commit messages. + +## Commit Message Format + +Commit messages should be formatted as Conventional Commits. In addition, some +git trailers are supported and have special meaning (see below). + +### Conventional Commits + +Conventional Commits are structured as follows: + +``` +(): +``` + +The type of changes which will be included in changelogs is one of the following: + + feat: A new feature + fix: A bug fix + doc: Documentation changes + refactor: Code refactoring + perf: Performance improvements + ci: Changes to CI configuration files and scripts + +The scope is optional and can specify the area of the codebase affected (e.g., +olm, cipher). + +### Changelog Trailer + +In addition to the Conventional Commit format, you can use the `Changelog` git +trailer to specify the changelog message explicitly. When that trailer is +present, its value will be used as the changelog entry instead of the commit's +leading line. + + +#### Example Commit Message +``` +feat: Add a method to encode Ed25519 public keys to Base64 -We ask that everybody who contributes to this project signs off their contributions, as explained below. +This patch adds the Ed25519PublicKey::to_base64() method, which allows us to +stringify Ed25519 and thus present them to users. It's also commonly used when +Ed25519 keys need to be inserted into JSON. -We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is Apache Software License v2 (see [LICENSE](./LICENSE)). +Changelog: Added the Ed25519PublicKey::to_base64() method which can be used to +stringify the Ed25519 public key. +``` + +In this commit message, the content specified in the `Changelog` trailer will be +used for the changelog entry. + +### Security fixes + +Commits addressing security vulnerabilities must include specific trailers for +vulnerability metadata. These commits are required to include at least the +`Security-Impact` trailer to indicate that the commit is a security fix. + +Security issues have some additional git-trailers: + + Security-Impact: The magnitude of harm that can be expected, i.e. low/moderate/high/critical. + CVE: The CVE that was assigned to this issue. + GitHub-Advisory: The GitHub advisory identifier. + +Example: + +``` +fix: Use a constant-time Base64 encoder for secret key material + +This patch fixes a security issue around a side-channel vulnerability[1] +when decoding secret key material using Base64. + +In some circumstances an attacker can obtain information about secret +secret key material via a controlled-channel and side-channel attack. + +This patch avoids the side-channel by switching to the base64ct crate +for the encoding, and more importantly, the decoding of secret key +material. + +Security-Impact: Low +CVE: CVE-2024-40640 +GitHub-Advisory: GHSA-j8cm-g7r6-hfpq + +Changelog: Use a constant-time Base64 encoder for secret key material +to mitigate side-channel attacks leaking secret key material. +``` + +## Sign off -In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: +We ask that everybody who contributes to this project signs off their +contributions, as explained below. + +We follow a simple 'inbound=outbound' model for contributions: the act of +submitting an 'inbound' contribution means that the contributor agrees to +license their contribution under the same terms as the project's overall +'outbound' license - in our case, this is Apache Software License v2 (see +[LICENSE](./LICENSE)). + +In order to have a concrete record that your contribution is intentional and you +agree to license it under the same terms as the project's license, we've adopted +the same lightweight approach used by the [Linux +Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), +[Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many +other projects: the [Developer Certificate of +Origin](https://developercertificate.org/) (DCO). This is a simple declaration +that you wrote the contribution or otherwise have the right to contribute it to +Matrix: ``` Developer Certificate of Origin @@ -50,10 +152,13 @@ By making a contribution to this project, I certify that: this project or the open source license(s) involved. ``` -If you agree to this for your contribution, then all that's needed is to include the line in your commit or pull request comment: +If you agree to this for your contribution, then all that's needed is to include +the line in your commit or pull request comment: ``` Signed-off-by: Your Name ``` -Git allows you to add this signoff automatically when using the `-s` flag to `git commit`, which uses the name and email set in your `user.name` and `user.email` git configs. \ No newline at end of file +Git allows you to add this signoff automatically when using the `-s` flag to +`git commit`, which uses the name and email set in your `user.name` and +`user.email` git configs. diff --git a/Cargo.toml b/Cargo.toml index 951c6579..ad07a1ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ default = ["libolm-compat"] js = ["getrandom/js"] strict-signatures = [] libolm-compat = [] +insecure-pk-encryption = [] # 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 = [] diff --git a/README.md b/README.md index 825d240f..e44a64cb 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,68 @@ -![Build Status](https://img.shields.io/github/actions/workflow/status/matrix-org/vodozemac/ci.yml?style=flat-square) -[![codecov](https://img.shields.io/codecov/c/github/matrix-org/vodozemac/main.svg?style=flat-square)](https://codecov.io/gh/matrix-org/vodozemac) -[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0) -[![Docs - Main](https://img.shields.io/badge/docs-main-blue.svg?style=flat-square)](https://matrix-org.github.io/vodozemac/vodozemac/index.html) -[![Docs - Stable](https://img.shields.io/crates/v/vodozemac?color=blue&label=docs&style=flat-square)](https://docs.rs/vodozemac) +

vodozemac

+
+ vodozemac is an implementation of Olm (Double Ratchet) and Megolm +

+ + +
+
+ + + + + + + + + +
+ + + + + + +
+
+
-A Rust implementation of Olm and Megolm +[vodozemac] is a pure Rust implementation of the [Olm] and [Megolm] +cryptographic ratchets, offering a high-level API for straightforward creation +of secure communication channels using these ratchets. -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]. +Designed as a modern alternative to the [libolm] cryptographic library, which is +used for end-to-end encryption in [Matrix], vodozemac provides not only the +[Olm] and [Megolm] ratchets but also additional cryptographic features useful +for developing Matrix clients, such as [SAS] and the integrated encryption +scheme outlined in [MSC4108]. -[olm-docs]: - +[vodozemac]: https://hjp.znanje.hr/index.php?show=search_by_id&id=f19vXxZ%2F +[Olm]: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/olm.md +[Megolm]: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/megolm.md +[libolm]: https://gitlab.matrix.org/matrix-org/olm +[SAS]: https://spec.matrix.org/v1.2/client-server-api/#short-authentication-string-sas-verification +[Matrix]: https://matrix.org +[MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108 -[megolm-docs]: - +# Documentation -[sas]: - +Explore how to implement end-to-end encryption in our [documentation]. -# Features +[documentation]: https://docs.rs/vodozemac/latest/vodozemac/ -## Supported +# Installation -- [Olm](https://matrix-org.github.io/vodozemac/vodozemac/olm/index.html) -- [Megolm](https://matrix-org.github.io/vodozemac/vodozemac/megolm/index.html) -- libolm pickle format (read-only) -- Modern pickle format -- [SAS (Short Authentication Strings)](https://matrix-org.github.io/vodozemac/vodozemac/sas/index.html) +To install add the following to your project's `Cargo.toml`: -## Unsupported +```toml +[dependencies] +vodozemac = "0.7.0" +``` -- Creating asymmetric [server-side message key - backups][legacy-message-key-backup], since these have been implemented in - [matrix-sdk-crypto]. +# Security Notes -[legacy-message-key-backup]: - +This crate has received one security [audit] by [Least Authority], with no +significant findings. -[matrix-sdk-crypto]: - - -## Planned - -- Primitives for the asymmetric authenticated [server-side message key backups][authenticated-message-key-backup]. - -[authenticated-message-key-backup]: - +[audit]: https://matrix.org/media/Least%20Authority%20-%20Matrix%20vodozemac%20Final%20Audit%20Report.pdf +[Least Authority]: https://leastauthority.com/ diff --git a/cliff.toml b/cliff.toml index e3a2463b..f6da1814 100644 --- a/cliff.toml +++ b/cliff.toml @@ -17,7 +17,27 @@ body = """ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} {% for commit in commits %} - - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% set_global commit_message = commit.message -%} + {% set_global breaking = commit.breaking -%} + {% for footer in commit.footers -%} + {% if footer.token | lower == "changelog" -%} + {% set_global commit_message = footer.value -%} + {% elif footer.token | lower == "security-impact" -%} + {% set_global security_impact = footer.value -%} + {% elif footer.token | lower == "cve" -%} + {% set_global cve = footer.value -%} + {% elif footer.token | lower == "github-advisory" -%} + {% set_global github_advisory = footer.value -%} + {% endif -%} + {% endfor -%} + - {% if breaking %}[**breaking**] {% endif %}{{ commit_message | upper_first }} + {% if security_impact -%} + (\ + *{{ security_impact | upper_first }}*\ + {% if cve -%}, [{{ cve | upper }}](https://www.cve.org/CVERecord?id={{ cve }}){% endif -%}\ + {% if github_advisory -%}, [{{ github_advisory | upper }}](https://github.com/matrix-org/vodozemac/security/advisories/{{ github_advisory }}){% endif -%} + ) + {% endif -%} {% endfor %} {% endfor %}\n """ @@ -39,16 +59,18 @@ commit_preprocessors = [ ] # regex for parsing and grouping commits commit_parsers = [ - { message = ".*[sS]ecurity", group = "Security"}, + { footer = "Security-Impact:", group = "Security" }, + { footer = "CVE:", group = "Security" }, + { footer = "GitHub-Advisory:", group = "Security" }, { message = "^feat", group = "Features"}, { message = "^fix", group = "Bug Fixes"}, { message = "^doc", group = "Documentation"}, { message = "^perf", group = "Performance"}, { message = "^refactor", group = "Refactor"}, - { message = "^style", group = "Styling"}, - { message = "^test", group = "Testing"}, { message = "^chore\\(release\\): prepare for", skip = true}, { message = "^chore", skip = true}, + { message = "^style", group = "Styling", skip = true}, + { message = "^test", skip = true}, { message = "^ci", skip = true}, ] # filter out the commits that are not matched by commit parsers diff --git a/contrib/zemi.png b/contrib/zemi.png new file mode 100644 index 00000000..8a2dec5d Binary files /dev/null and b/contrib/zemi.png differ diff --git a/src/ecies/mod.rs b/src/ecies/mod.rs index 45d7d560..e8d3703a 100644 --- a/src/ecies/mod.rs +++ b/src/ecies/mod.rs @@ -14,6 +14,8 @@ #![deny(missing_docs)] +//! Implementation of an integrated encryption scheme. +//! //! This module implements //! [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme), the //! elliptic curve variant of the Integrated Encryption Scheme. This is a hybrid diff --git a/src/lib.rs b/src/lib.rs index c3d82619..8736f467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,6 +217,7 @@ pub mod ecies; pub mod hazmat; pub mod megolm; pub mod olm; +#[cfg(feature = "insecure-pk-encryption")] pub mod pk_encryption; pub mod sas; diff --git a/src/olm/messages/pre_key.rs b/src/olm/messages/pre_key.rs index ce659b30..5a89ebba 100644 --- a/src/olm/messages/pre_key.rs +++ b/src/olm/messages/pre_key.rs @@ -153,7 +153,7 @@ impl PreKeyMessage { /// Create a new pre-key message from the session keys and standard message. #[cfg(feature = "low-level-api")] - pub fn wrap(session_keys: SessionKeys, message: Message) -> Self { + pub const fn wrap(session_keys: SessionKeys, message: Message) -> Self { PreKeyMessage::new(session_keys, message) } diff --git a/src/olm/session/message_key.rs b/src/olm/session/message_key.rs index 602610a3..1c690e50 100644 --- a/src/olm/session/message_key.rs +++ b/src/olm/session/message_key.rs @@ -95,13 +95,13 @@ impl MessageKey { /// Get the message key's ratchet key. #[cfg(feature = "low-level-api")] - pub fn ratchet_key(&self) -> RatchetPublicKey { + pub const fn ratchet_key(&self) -> RatchetPublicKey { self.ratchet_key } /// Get the message key's index. #[cfg(feature = "low-level-api")] - pub fn index(&self) -> u64 { + pub const fn index(&self) -> u64 { self.index } } diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 77624290..3a519fce 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -12,7 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! ☣️ Compat support for Olm's PkEncryption and PkDecryption +//! ☣️ Compat support for libolm's PkEncryption and PkDecryption +//! +//! This implements the `m.megolm_backup.v1.curve25519-aes-sha2` described in +//! the Matrix [spec]. This is a asymmetric encrytpion scheme based on +//! Curve25519. +//! +//! **Warning**: Please note the algorithm contains a critical flaw and does not +//! provide authentication of the ciphertext. +//! +//! # Examples +//! +//! ``` +//! use anyhow::Result; +//! use vodozemac::pk_encryption::{PkDecryption, PkEncryption}; +//! +//! fn main() -> Result<()> { +//! let plaintext = b"It's a secret to everybody"; +//! +//! let decryption = PkDecryption::new(); +//! let encryption = PkEncryption::from_key(decryption.public_key()); +//! +//! let message = encryption.encrypt(plaintext); +//! let decrypted = decryption.decrypt(&message)?; +//! +//! assert_eq!(decrypted.as_slice(), plaintext); +//! +//! Ok(()) +//! } +//! ``` +//! +//! [spec]: https://spec.matrix.org/v1.11/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2 + use aes::cipher::{ block_padding::{Pkcs7, UnpadError}, BlockDecryptMut as _, BlockEncryptMut as _, KeyIvInit as _, @@ -26,14 +57,71 @@ use crate::{ base64_decode, cipher::{ key::{CipherKeys, ExpandedKeys}, - Aes256CbcDec, Aes256CbcEnc, HmacSha256, + Aes256CbcDec, Aes256CbcEnc, HmacSha256, Mac, }, Curve25519PublicKey, Curve25519SecretKey, KeyError, }; -const MAC_LENGTH: usize = 8; const PICKLE_VERSION: u32 = 1; +/// Error type describing the failure cases the Pk decryption step can have. +#[derive(Debug, Error)] +pub enum Error { + /// The message has invalid [Pkcs7] padding. + #[error("failed to decrypt, invalid padding: {0}")] + InvalidPadding(#[from] UnpadError), + /// The message failed to be authenticated. + #[error("the MAC of the ciphertext didn't pass validation: {0}")] + Mac(#[from] MacError), +} + +/// Error describing failures that might happen during the decoding of a +/// encrypted [`Message`]. +#[derive(Debug, Error)] +pub enum MessageDecodeError { + /// One of the message parts wasn't valid Base64. + #[error(transparent)] + Base64(#[from] crate::Base64DecodeError), + /// The ephemeral Curve25519 key isn't valid. + #[error(transparent)] + Key(#[from] KeyError), +} + +/// A message that was encrypted using a [`PkEncryption`] object. +#[derive(Debug)] +pub struct Message { + /// The ciphertext of the message. + pub ciphertext: Vec, + /// The message authentication code of the message. + /// + /// *Warning*: As stated in the module description, this does not + /// authenticate the message. + pub mac: Vec, + /// The ephemeral [`Curve25519PublicKey`] of the message which was used to + /// derive the individual message key. + pub ephemeral_key: Curve25519PublicKey, +} + +impl Message { + /// Attempt to decode a PkEncryption [`Message`] from a Base64 encoded + /// triplet of ciphertext, MAC, and ephemeral key. + pub fn from_base64( + ciphertext: &str, + mac: &str, + ephemeral_key: &str, + ) -> Result { + Ok(Self { + ciphertext: base64_decode(ciphertext)?, + mac: base64_decode(mac)?, + ephemeral_key: Curve25519PublicKey::from_base64(ephemeral_key)?, + }) + } +} + +/// The decryption component of the PkEncryption support. +/// +/// This struct allows you to share a public key, enabling others to encrypt +/// messages that can be decrypted using the corresponding private key. pub struct PkDecryption { key: Curve25519SecretKey, public_key: Curve25519PublicKey, @@ -67,7 +155,7 @@ impl PkDecryption { let hmac = HmacSha256::new_from_slice(cipher_keys.mac_key()) .expect("We should be able to create a Hmac object from a 32 byte key"); - // This is a know issue, we check the MAC of an empty message instead of + // BUG: This is a know issue, we check the MAC of an empty message instead of // updating the `hmac` object with the ciphertext bytes. hmac.verify_truncated_left(&message.mac)?; @@ -175,15 +263,24 @@ impl From<&PkDecryption> for PkDecryptionPickle { } } +/// The encryption component of PkEncryption support. +/// +/// This struct can be created using a [`Curve25519PublicKey`] corresponding to +/// a [`PkDecryption`] object, allowing messages to be encrypted for the +/// associated decryption object. pub struct PkEncryption { public_key: Curve25519PublicKey, } impl PkEncryption { + /// Create a new [`PkEncryption`] object from a [`Curve25519PublicKey`]. + /// + /// The public key should come from an existing [`PkDecryption`] object. pub const fn from_key(public_key: Curve25519PublicKey) -> Self { Self { public_key } } + /// Encrypt a message using this [`PkEncryption`] object. pub fn encrypt(&self, message: &[u8]) -> Message { let ephemeral_key = Curve25519SecretKey::new(); let shared_secret = ephemeral_key.diffie_hellman(&self.public_key); @@ -197,10 +294,10 @@ impl PkEncryption { let hmac = HmacSha256::new_from_slice(cipher_keys.mac_key()) .expect("We should be able to create a Hmac object from a 32 byte key"); - // This is a know issue, we create a MAC of an empty message instead of + // BUG: This is a know issue, we create a MAC of an empty message instead of // updating the `hmac` object with the ciphertext bytes. let mut mac = hmac.finalize().into_bytes().to_vec(); - mac.truncate(MAC_LENGTH); + mac.truncate(Mac::TRUNCATED_LEN); Message { ciphertext, mac, ephemeral_key: Curve25519PublicKey::from(&ephemeral_key) } } @@ -208,7 +305,7 @@ impl PkEncryption { impl From<&PkDecryption> for PkEncryption { fn from(value: &PkDecryption) -> Self { - Self::from(value.public_key()) + Self::from_key(value.public_key()) } } @@ -218,53 +315,6 @@ impl From for PkEncryption { } } -#[derive(Debug, Error)] -pub enum MessageDecodeError { - #[error(transparent)] - Base64(#[from] crate::Base64DecodeError), - #[error(transparent)] - Key(#[from] KeyError), -} - -#[derive(Debug)] -pub struct Message { - pub ciphertext: Vec, - pub mac: Vec, - pub ephemeral_key: Curve25519PublicKey, -} - -impl Message { - pub fn from_base64( - ciphertext: &str, - mac: &str, - ephemeral_key: &str, - ) -> Result { - Ok(Self { - ciphertext: base64_decode(ciphertext)?, - mac: base64_decode(mac)?, - ephemeral_key: Curve25519PublicKey::from_base64(ephemeral_key)?, - }) - } -} - -/// Error type describing the failure cases the Pk decryption step can have. -#[derive(Debug, Error)] -pub enum Error { - /// The message has invalid PKCS7 padding. - #[error("Failed decrypting, invalid padding: {0}")] - InvalidPadding(#[from] UnpadError), - /// The message failed to be authenticated. - #[error("The MAC of the ciphertext didn't pass validation {0}")] - Mac(#[from] MacError), - /// The message's Curve25519 key failed to be decoded. - #[error("The message's ephemeral Curve25519 key could not been decoded: {0}")] - InvalidCurveKey(#[from] KeyError), - /// The decrypted message should contain a backed up room key, but the - /// plaintext isn't valid JSON. - #[error("The decrypted message isn't valid JSON: {0}")] - Json(#[from] serde_json::error::Error), -} - #[cfg(test)] mod tests { use olm_rs::pk::{OlmPkDecryption, OlmPkEncryption, PkMessage}; @@ -272,8 +322,8 @@ mod tests { use super::{Message, MessageDecodeError, PkDecryption, PkEncryption}; use crate::{base64_encode, Curve25519PublicKey}; - // Conversion from the libolm type to the vodozemac type. To make some tests - // easier on the eyes. + /// Conversion from the libolm type to the vodozemac type. To make some + /// tests easier on the eyes. impl TryFrom for Message { type Error = MessageDecodeError; @@ -282,6 +332,8 @@ mod tests { } } + /// Conversion from the vodozemac type to the libolm type, in a similar + /// manner to the above [TryFrom] implementation. impl From for PkMessage { fn from(val: Message) -> Self { PkMessage { @@ -301,17 +353,25 @@ mod tests { let message = "It's a secret to everybody"; let encrypted = encryptor.encrypt(message); - let encrypted = encrypted.try_into().unwrap(); + let encrypted = + encrypted.try_into().expect("We should be able to decode a message libolm created"); - let decrypted = decryptor.decrypt(&encrypted).unwrap(); + let decrypted = decryptor + .decrypt(&encrypted) + .expect("We should be able to decrypt a message libolm encrypted"); - assert_eq!(message.as_bytes(), decrypted); + assert_eq!( + message.as_bytes(), + decrypted, + "The plaintext should match the decrypted message" + ); } #[test] fn encrypt_for_libolm_pk_decryption() { let decryptor = OlmPkDecryption::new(); - let public_key = Curve25519PublicKey::from_base64(decryptor.public_key()).unwrap(); + let public_key = Curve25519PublicKey::from_base64(decryptor.public_key()) + .expect("libolm should provide us with a valid Curve25519 public key"); let encryptor = PkEncryption::from_key(public_key); let message = "It's a secret to everybody"; @@ -319,9 +379,11 @@ mod tests { let encrypted = encryptor.encrypt(message.as_ref()); let encrypted = encrypted.into(); - let decrypted = decryptor.decrypt(encrypted).unwrap(); + let decrypted = decryptor + .decrypt(encrypted) + .expect("We should be able to decrypt a message vodozemac encrypted using libolm"); - assert_eq!(message, decrypted); + assert_eq!(message, decrypted, "The plaintext should match the decrypted message"); } #[test] @@ -333,9 +395,11 @@ mod tests { let message = "It's a secret to everybody"; let encrypted = encryptor.encrypt(message.as_ref()); - let decrypted = decryptor.decrypt(&encrypted).unwrap(); + let decrypted = decryptor + .decrypt(&encrypted) + .expect("We should be able to decrypt a message we encrypted"); - assert_eq!(message.as_ref(), decrypted); + assert_eq!(message.as_ref(), decrypted, "The plaintext should match the decrypted message"); } #[test]