CVE-2024-40640
Description
vodozemac is an open source implementation of Olm and Megolm in pure Rust. Versions before 0.7.0 of vodozemac use a non-constant time base64 implementation for importing key material for Megolm group sessions and PkDecryption Ed25519 secret keys. This flaw might allow an attacker to infer some information about the secret key material through a side-channel attack. The use of a non-constant time base64 implementation might allow an attacker to observe timing variations in the encoding and decoding operations of the secret key material. This could potentially provide insights into the underlying secret key material. The impact of this vulnerability is considered low because exploiting the attacker is required to have access to high precision timing measurements, as well as repeated access to the base64 encoding or decoding processes. Additionally, the estimated leakage amount is bounded and low according to the referenced paper. This has been patched in commit 734b6c6948d4b2bdee3dd8b4efa591d93a61d272 which has been included in release version 0.7.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
vodozemaccrates.io | < 0.7.0 | 0.7.0 |
Patches
392d6acca73b6734b6c6948d4fix: Use a constant-time Base64 encoder for secret key material
4 files changed · +21 −12
Cargo.toml+1 −0 modified@@ -29,6 +29,7 @@ low-level-api = [] aes = "0.8.4" arrayvec = { version = "0.7.4", features = ["serde"] } base64 = "0.22.1" +base64ct = { version = "1.6.0", features = ["std", "alloc"] } cbc = { version = "0.1.2", features = ["std"] } chacha20poly1305 = "0.10.1" curve25519-dalek = { version = "4.1.2", default-features = false, features = ["zeroize"] }
src/megolm/session_keys.rs+7 −9 modified@@ -14,15 +14,13 @@ use std::io::{Cursor, Read}; +use base64ct::Encoding; use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroize; use super::ratchet::Ratchet; -use crate::{ - utilities::{base64_decode, base64_encode}, - Ed25519PublicKey, Ed25519Signature, SignatureError, -}; +use crate::{Ed25519PublicKey, Ed25519Signature, SignatureError}; /// Error type describing failure modes for the `SessionKey` and /// `ExportedSessionKey` decoding. @@ -36,7 +34,7 @@ pub enum SessionKeyDecodeError { Read(#[from] std::io::Error), /// The encoded session key wasn't valid base64. #[error("The session key wasn't valid base64: {0}")] - Base64(#[from] base64::DecodeError), + Base64(#[from] base64ct::Error), /// The signature on the session key was invalid. #[error("The signature on the session key was invalid: {0}")] Signature(#[from] SignatureError), @@ -93,7 +91,7 @@ impl ExportedSessionKey { pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(&bytes); + let ret = base64ct::Base64Unpadded::encode_string(&bytes); bytes.zeroize(); @@ -102,7 +100,7 @@ impl ExportedSessionKey { /// Deserialize the `ExportedSessionKey` from base64 encoded string. pub fn from_base64(key: &str) -> Result<Self, SessionKeyDecodeError> { - let mut bytes = base64_decode(key)?; + let mut bytes = base64ct::Base64Unpadded::decode_vec(key)?; let ret = Self::from_bytes(&bytes); bytes.zeroize(); @@ -268,7 +266,7 @@ impl SessionKey { /// to a string using unpadded base64 as the encoding. pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(&bytes); + let ret = base64ct::Base64Unpadded::encode_string(&bytes); bytes.zeroize(); @@ -277,7 +275,7 @@ impl SessionKey { /// Deserialize the `SessionKey` from base64 encoded string. pub fn from_base64(key: &str) -> Result<Self, SessionKeyDecodeError> { - let mut bytes = base64_decode(key)?; + let mut bytes = base64ct::Base64Unpadded::decode_vec(key)?; let ret = Self::from_bytes(&bytes); bytes.zeroize();
src/types/ed25519.rs+11 −3 modified@@ -15,6 +15,7 @@ use std::fmt::Display; use base64::decoded_len_estimate; +use base64ct::Encoding; use curve25519_dalek::EdwardsPoint; #[cfg(not(fuzzing))] use ed25519_dalek::Verifier; @@ -226,7 +227,7 @@ impl Ed25519SecretKey { /// otherwise an unintentional copy of the key might exist in memory. pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(bytes.as_ref()); + let ret = base64ct::Base64Unpadded::encode_string(bytes.as_ref()); bytes.zeroize(); @@ -242,9 +243,16 @@ impl Ed25519SecretKey { length: decoded_len_estimate(input.len()), }) } else { - let mut bytes = base64_decode(input)?; - let mut key_bytes = [0u8; 32]; + // Ed25519 secret keys can sometimes be encoded with padding, don't ask me why. + // This means that if the unpadded decoding fails, we have to attempt the padded + // one. + let mut bytes = if let Ok(bytes) = base64ct::Base64Unpadded::decode_vec(input) { + bytes + } else { + base64ct::Base64::decode_vec(input)? + }; + let mut key_bytes = [0u8; 32]; key_bytes.copy_from_slice(&bytes); let key = Self::from_slice(&key_bytes);
src/types/mod.rs+2 −0 modified@@ -46,6 +46,8 @@ impl KeyId { pub enum KeyError { #[error("Failed decoding a public key from base64: {}", .0)] Base64Error(#[from] base64::DecodeError), + #[error("Failed to decode a private key from base64: {}", .0)] + Base64PrivateKey(#[from] base64ct::Error), #[error( "Failed decoding {key_type} key from base64: \ Invalid number of bytes for {key_type}, expected {expected_length}, got {length}."
77765dace112fix: Use a constant-time Base64 encoder for secret key material
4 files changed · +21 −12
Cargo.toml+1 −0 modified@@ -29,6 +29,7 @@ low-level-api = [] aes = "0.8.4" arrayvec = { version = "0.7.4", features = ["serde"] } base64 = "0.22.1" +base64ct = { version = "1.6.0", features = ["std", "alloc"] } cbc = { version = "0.1.2", features = ["std"] } chacha20poly1305 = "0.10.1" curve25519-dalek = { version = "4.1.2", default-features = false, features = ["zeroize"] }
src/megolm/session_keys.rs+7 −9 modified@@ -14,15 +14,13 @@ use std::io::{Cursor, Read}; +use base64ct::Encoding; use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroize; use super::ratchet::Ratchet; -use crate::{ - utilities::{base64_decode, base64_encode}, - Ed25519PublicKey, Ed25519Signature, SignatureError, -}; +use crate::{Ed25519PublicKey, Ed25519Signature, SignatureError}; /// Error type describing failure modes for the `SessionKey` and /// `ExportedSessionKey` decoding. @@ -36,7 +34,7 @@ pub enum SessionKeyDecodeError { Read(#[from] std::io::Error), /// The encoded session key wasn't valid base64. #[error("The session key wasn't valid base64: {0}")] - Base64(#[from] base64::DecodeError), + Base64(#[from] base64ct::Error), /// The signature on the session key was invalid. #[error("The signature on the session key was invalid: {0}")] Signature(#[from] SignatureError), @@ -93,7 +91,7 @@ impl ExportedSessionKey { pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(&bytes); + let ret = base64ct::Base64Unpadded::encode_string(&bytes); bytes.zeroize(); @@ -102,7 +100,7 @@ impl ExportedSessionKey { /// Deserialize the `ExportedSessionKey` from base64 encoded string. pub fn from_base64(key: &str) -> Result<Self, SessionKeyDecodeError> { - let mut bytes = base64_decode(key)?; + let mut bytes = base64ct::Base64Unpadded::decode_vec(key)?; let ret = Self::from_bytes(&bytes); bytes.zeroize(); @@ -268,7 +266,7 @@ impl SessionKey { /// to a string using unpadded base64 as the encoding. pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(&bytes); + let ret = base64ct::Base64Unpadded::encode_string(&bytes); bytes.zeroize(); @@ -277,7 +275,7 @@ impl SessionKey { /// Deserialize the `SessionKey` from base64 encoded string. pub fn from_base64(key: &str) -> Result<Self, SessionKeyDecodeError> { - let mut bytes = base64_decode(key)?; + let mut bytes = base64ct::Base64Unpadded::decode_vec(key)?; let ret = Self::from_bytes(&bytes); bytes.zeroize();
src/types/ed25519.rs+11 −3 modified@@ -15,6 +15,7 @@ use std::fmt::Display; use base64::decoded_len_estimate; +use base64ct::Encoding; use curve25519_dalek::EdwardsPoint; #[cfg(not(fuzzing))] use ed25519_dalek::Verifier; @@ -226,7 +227,7 @@ impl Ed25519SecretKey { /// otherwise an unintentional copy of the key might exist in memory. pub fn to_base64(&self) -> String { let mut bytes = self.to_bytes(); - let ret = base64_encode(bytes.as_ref()); + let ret = base64ct::Base64Unpadded::encode_string(bytes.as_ref()); bytes.zeroize(); @@ -242,9 +243,16 @@ impl Ed25519SecretKey { length: decoded_len_estimate(input.len()), }) } else { - let mut bytes = base64_decode(input)?; - let mut key_bytes = [0u8; 32]; + // Ed25519 secret keys can sometimes be encoded with padding, don't ask me why. + // This means that if the unpadded decoding fails, we have to attempt the padded + // one. + let mut bytes = if let Ok(bytes) = base64ct::Base64Unpadded::decode_vec(input) { + bytes + } else { + base64ct::Base64::decode_vec(input)? + }; + let mut key_bytes = [0u8; 32]; key_bytes.copy_from_slice(&bytes); let key = Self::from_slice(&key_bytes);
src/types/mod.rs+2 −0 modified@@ -46,6 +46,8 @@ impl KeyId { pub enum KeyError { #[error("Failed decoding a public key from base64: {}", .0)] Base64Error(#[from] base64::DecodeError), + #[error("Failed to decode a private key from base64: {}", .0)] + Base64PrivateKey(#[from] base64ct::Error), #[error( "Failed decoding {key_type} key from base64: \ Invalid number of bytes for {key_type}, expected {expected_length}, got {length}."
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-j8cm-g7r6-hfpqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-40640ghsaADVISORY
- arxiv.org/abs/2108.04600nvdWEB
- github.com/matrix-org/vodozemac/commit/734b6c6948d4b2bdee3dd8b4efa591d93a61d272nvdWEB
- github.com/matrix-org/vodozemac/commit/77765dace11266ef9523301624a01265c6e0f790ghsaWEB
- github.com/matrix-org/vodozemac/security/advisories/GHSA-j8cm-g7r6-hfpqnvdWEB
- rustsec.org/advisories/RUSTSEC-2024-0354.htmlghsaWEB
News mentions
0No linked articles in our index yet.