diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index dd906dd5..462f8f46 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -12,7 +12,16 @@ // 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]. +//! +//! **Warning**: Please note the algorithm contains a critical flaw and does not +//! provide authentication of the ciphertext. +//! +//! [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 _, @@ -24,12 +33,58 @@ use crate::{ base64_decode, cipher::{ key::{CipherKeys, ExpandedKeys}, - Aes256CbcDec, Aes256CbcEnc, HmacSha256, + Aes256CbcDec, Aes256CbcEnc, HmacSha256, Mac, }, Curve25519PublicKey, Curve25519SecretKey, KeyError, }; -const MAC_LENGTH: usize = 8; +/// 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), +} + +#[derive(Debug, Error)] +pub enum MessageDecodeError { + #[error(transparent)] + Base64(#[from] crate::Base64DecodeError), + #[error(transparent)] + Key(#[from] KeyError), +} + +#[derive(Debug)] +pub struct Message { + /// The ciphertext of the message. + pub ciphertext: Vec, + /// The message authentication code of the message. Please note, that this + /// does not authenticate the message, for more info please take a look + /// at the module level documentation. + pub mac: Vec, + /// The ephemeral [`Curve25519PublicKey`] of the message which was used to + /// derive the individual message key. + pub ephemeral_key: Curve25519PublicKey, +} + +impl Message { + /// Try to decode a message from the ciphertext, MAC, and ephemeral key + /// triplet encoded as base64. + 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)?, + }) + } +} pub struct PkDecryption { key: Curve25519SecretKey, @@ -64,7 +119,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)?; @@ -116,10 +171,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) } } @@ -127,63 +182,10 @@ impl PkEncryption { impl From<&PkDecryption> for PkEncryption { fn from(value: &PkDecryption) -> Self { - Self::from(value.public_key()) - } -} - -impl From for PkEncryption { - fn from(public_key: Curve25519PublicKey) -> Self { - Self { public_key } - } -} - -#[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)?, - }) + Self::from_key(value.public_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}; @@ -191,8 +193,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; @@ -201,6 +203,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 { @@ -220,17 +224,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"; @@ -238,9 +250,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] @@ -252,9 +266,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]