From 042e20f14c57783d52d3dbc5877b0a8bb72561a7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Apr 2024 15:17:52 +0100 Subject: [PATCH 1/3] Implement split encryption keys and monotonic nonces From https://github.com/matrix-org/matrix-spec-proposals/commit/aa37af9b38ed9426655de07795a74b459b25ba4a --- src/secure_channel/mod.rs | 100 +++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/src/secure_channel/mod.rs b/src/secure_channel/mod.rs index bdb2936c..d268287d 100644 --- a/src/secure_channel/mod.rs +++ b/src/secure_channel/mod.rs @@ -130,11 +130,20 @@ pub struct EstablishedSecureChannel { encryption_nonce: SecureChannelNonce, #[zeroize(skip)] decryption_nonce: SecureChannelNonce, - key: Box<[u8; 32]>, + /// The key that we will use to encrypt messages + encryption_key: Box<[u8; 32]>, + /// The key that the other party will use to encrypt messages + decryption_key: Box<[u8; 32]>, #[zeroize(skip)] check_code: CheckCode, + /// Whether we initiated the secure channel or not. i.e. are we Device G? + initiator: bool, } +const CHECKCODE_INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN_CHECKCODE"; +const ENCKEY_S_INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN_ENCKEY_S"; +const ENCKEY_G_INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN_ENCKEY_G"; + impl EstablishedSecureChannel { fn create_check_code( shared_secret: &SharedSecret, @@ -142,15 +151,15 @@ impl EstablishedSecureChannel { their_public_key: Curve25519PublicKey, initiator: bool, ) -> CheckCode { - const INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN_CHECKCODE"; - let mut bytes = [0u8; 2]; let kdf: Hkdf = Hkdf::new(None, shared_secret.as_bytes()); let info = if initiator { - format!("{INFO_STRING}|{}|{}", our_public_key.to_base64(), their_public_key.to_base64(),) + // we are Device G. Gp = our_public_key, Sp = their_public_key + format!("{CHECKCODE_INFO_STRING}|{}|{}", our_public_key.to_base64(), their_public_key.to_base64(),) } else { - format!("{INFO_STRING}|{}|{}", their_public_key.to_base64(), our_public_key.to_base64(),) + // we are Device S. Gp = their_public_key, Sp = our_public_key + format!("{CHECKCODE_INFO_STRING}|{}|{}", their_public_key.to_base64(), our_public_key.to_base64(),) }; kdf.expand(info.as_bytes(), bytes.as_mut_slice()) @@ -160,20 +169,21 @@ impl EstablishedSecureChannel { } fn create_key( + info: &str, shared_secret: &SharedSecret, our_public_key: Curve25519PublicKey, their_public_key: Curve25519PublicKey, initiator: bool, ) -> Box<[u8; 32]> { - const INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN"; - let mut key = Box::new([0u8; 32]); let kdf: Hkdf = Hkdf::new(None, shared_secret.as_bytes()); let info = if initiator { - format!("{INFO_STRING}|{}|{}", our_public_key.to_base64(), their_public_key.to_base64(),) + // we are Device G. Gp = our_public_key, Sp = their_public_key + format!("{info}|{}|{}", our_public_key.to_base64(), their_public_key.to_base64(),) } else { - format!("{INFO_STRING}|{}|{}", their_public_key.to_base64(), our_public_key.to_base64(),) + // we are Device S. Gp = their_public_key, Sp = our_public_key + format!("{info}|{}|{}", their_public_key.to_base64(), our_public_key.to_base64(),) }; kdf.expand(info.as_bytes(), key.as_mut_slice()) @@ -182,29 +192,62 @@ impl EstablishedSecureChannel { key } - fn new( + fn create_encryption_key( shared_secret: &SharedSecret, our_public_key: Curve25519PublicKey, their_public_key: Curve25519PublicKey, initiator: bool, - ) -> Self { - let (encryption_nonce, decryption_nonce) = if initiator { - (SecureChannelNonce::even(), SecureChannelNonce::odd()) + ) -> Box<[u8; 32]> { + let info: &str = if initiator { + // we are Device G + ENCKEY_G_INFO_STRING } else { - (SecureChannelNonce::odd(), SecureChannelNonce::even()) + // we are Device S + ENCKEY_S_INFO_STRING }; - let key = Self::create_key(shared_secret, our_public_key, their_public_key, initiator); + Self::create_key(info, shared_secret, our_public_key, their_public_key, initiator) + } + + fn create_decryption_key( + shared_secret: &SharedSecret, + our_public_key: Curve25519PublicKey, + their_public_key: Curve25519PublicKey, + initiator: bool, + ) -> Box<[u8; 32]> { + let info: &str = if initiator { + // we are Device G, they are Device S + ENCKEY_S_INFO_STRING + } else { + // we are Device S, they are Device G + ENCKEY_G_INFO_STRING + }; + + Self::create_key(info, shared_secret, our_public_key, their_public_key, initiator) + } + + fn new( + shared_secret: &SharedSecret, + our_public_key: Curve25519PublicKey, + their_public_key: Curve25519PublicKey, + initiator: bool, + ) -> Self { + let (encryption_nonce, decryption_nonce) = (SecureChannelNonce::zero(), SecureChannelNonce::zero()); + + let encryption_key = Self::create_encryption_key(shared_secret, our_public_key, their_public_key, initiator); + let decryption_key = Self::create_decryption_key(shared_secret, our_public_key, their_public_key, initiator); let check_code = Self::create_check_code(shared_secret, our_public_key, their_public_key, initiator); Self { - key, + encryption_key, + decryption_key, encryption_nonce, decryption_nonce, our_public_key, their_public_key, check_code, + initiator, } } @@ -216,19 +259,24 @@ impl EstablishedSecureChannel { &self.check_code } - fn key(&self) -> &Chacha20Key { - Chacha20Key::from_slice(self.key.as_slice()) + fn encryption_key(&self) -> &Chacha20Key { + Chacha20Key::from_slice(self.encryption_key.as_slice()) + } + + fn decryption_key(&self) -> &Chacha20Key { + Chacha20Key::from_slice(self.decryption_key.as_slice()) } pub fn encrypt(&mut self, plaintext: &[u8]) -> SecureChannelMessage { let nonce = self.encryption_nonce.get(); - let cipher = ChaCha20Poly1305::new(self.key()); + let cipher = ChaCha20Poly1305::new(self.encryption_key()); let ciphertext = cipher.encrypt(&nonce, plaintext).expect( "We should always be able to encrypt a message since we provide the correct nonce", ); - if nonce.as_slice() == &[0u8; 12] { + if self.initiator && nonce.as_slice() == &[0u8; 12] { + // we are Device G, we send the LoginInitiateMessage InitialMessage { ciphertext, public_key: self.our_public_key }.into() } else { Message { ciphertext }.into() @@ -245,7 +293,7 @@ impl EstablishedSecureChannel { nonce: &Nonce, ciphertext: &[u8], ) -> Result, SecureChannelError> { - let cipher = ChaCha20Poly1305::new(self.key()); + let cipher = ChaCha20Poly1305::new(self.decryption_key()); let nonce = nonce.into(); let plaintext = @@ -260,17 +308,13 @@ struct SecureChannelNonce { } impl SecureChannelNonce { - fn even() -> Self { + fn zero() -> Self { Self { inner: 0 } } - fn odd() -> Self { - Self { inner: 1 } - } - fn get(&mut self) -> Nonce { let current = self.inner; - self.inner += 2; + self.inner += 1; let mut nonce = [0u8; 12]; nonce.copy_from_slice(¤t.to_le_bytes()[..12]); @@ -288,7 +332,7 @@ mod test { #[test] fn channel_creation() { - let plaintext = b"I'ts a secret to everybody"; + let plaintext = b"It's a secret to everybody"; let alice = SecureChannel::new(); let bob = SecureChannel::new(); From 31b91b2c70030ce6cb584c446bf3e0d250e76866 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Apr 2024 15:22:59 +0100 Subject: [PATCH 2/3] Rename InitialMessage => LoginInitiateMessage to match with description in MSC4108 --- src/secure_channel/messages.rs | 16 ++++++++-------- src/secure_channel/mod.rs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/secure_channel/messages.rs b/src/secure_channel/messages.rs index 95d07637..5693a5f2 100644 --- a/src/secure_channel/messages.rs +++ b/src/secure_channel/messages.rs @@ -28,12 +28,12 @@ pub enum MessageDecodeError { } #[derive(Debug)] -pub struct InitialMessage { +pub struct LoginInitiateMessage { pub public_key: Curve25519PublicKey, pub ciphertext: Vec, } -impl InitialMessage { +impl LoginInitiateMessage { pub fn encode(&self) -> String { let ciphertext = base64_encode(&self.ciphertext); let key = self.public_key.to_base64(); @@ -72,7 +72,7 @@ impl Message { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum SecureChannelMessage { - Initial(InitialMessage), + Initial(LoginInitiateMessage), Normal(Message), } @@ -85,7 +85,7 @@ impl SecureChannelMessage { } pub fn decode(foo: &str) -> Result { - if let Ok(message) = InitialMessage::decode(foo) { + if let Ok(message) = LoginInitiateMessage::decode(foo) { Ok(message.into()) } else { Message::decode(foo).map(Into::into) @@ -99,13 +99,13 @@ impl From for SecureChannelMessage { } } -impl From for SecureChannelMessage { - fn from(value: InitialMessage) -> Self { +impl From for SecureChannelMessage { + fn from(value: LoginInitiateMessage) -> Self { Self::Initial(value) } } -impl Serialize for InitialMessage { +impl Serialize for LoginInitiateMessage { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -116,7 +116,7 @@ impl Serialize for InitialMessage { } } -impl<'de> Deserialize<'de> for InitialMessage { +impl<'de> Deserialize<'de> for LoginInitiateMessage { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/src/secure_channel/mod.rs b/src/secure_channel/mod.rs index d268287d..e12a5ad1 100644 --- a/src/secure_channel/mod.rs +++ b/src/secure_channel/mod.rs @@ -20,7 +20,7 @@ use thiserror::Error; use x25519_dalek::{EphemeralSecret, SharedSecret}; use zeroize::{Zeroize, ZeroizeOnDrop}; -pub use self::messages::{InitialMessage, Message, SecureChannelMessage}; +pub use self::messages::{LoginInitiateMessage, Message, SecureChannelMessage}; use crate::Curve25519PublicKey; mod messages; @@ -74,7 +74,7 @@ impl SecureChannel { pub fn create_inbound_channel( self, - message: &InitialMessage, + message: &LoginInitiateMessage, ) -> Result { let our_public_key = self.public_key(); @@ -277,7 +277,7 @@ impl EstablishedSecureChannel { if self.initiator && nonce.as_slice() == &[0u8; 12] { // we are Device G, we send the LoginInitiateMessage - InitialMessage { ciphertext, public_key: self.our_public_key }.into() + LoginInitiateMessage { ciphertext, public_key: self.our_public_key }.into() } else { Message { ciphertext }.into() } From e70d80579dcb4bfe90386b2ed8b4b04d1f5029a3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 19 Apr 2024 16:05:38 +0100 Subject: [PATCH 3/3] Revert "Rename InitialMessage => LoginInitiateMessage to match with description in MSC4108" This reverts commit 31b91b2c70030ce6cb584c446bf3e0d250e76866. --- src/secure_channel/messages.rs | 16 ++++++++-------- src/secure_channel/mod.rs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/secure_channel/messages.rs b/src/secure_channel/messages.rs index 5693a5f2..95d07637 100644 --- a/src/secure_channel/messages.rs +++ b/src/secure_channel/messages.rs @@ -28,12 +28,12 @@ pub enum MessageDecodeError { } #[derive(Debug)] -pub struct LoginInitiateMessage { +pub struct InitialMessage { pub public_key: Curve25519PublicKey, pub ciphertext: Vec, } -impl LoginInitiateMessage { +impl InitialMessage { pub fn encode(&self) -> String { let ciphertext = base64_encode(&self.ciphertext); let key = self.public_key.to_base64(); @@ -72,7 +72,7 @@ impl Message { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum SecureChannelMessage { - Initial(LoginInitiateMessage), + Initial(InitialMessage), Normal(Message), } @@ -85,7 +85,7 @@ impl SecureChannelMessage { } pub fn decode(foo: &str) -> Result { - if let Ok(message) = LoginInitiateMessage::decode(foo) { + if let Ok(message) = InitialMessage::decode(foo) { Ok(message.into()) } else { Message::decode(foo).map(Into::into) @@ -99,13 +99,13 @@ impl From for SecureChannelMessage { } } -impl From for SecureChannelMessage { - fn from(value: LoginInitiateMessage) -> Self { +impl From for SecureChannelMessage { + fn from(value: InitialMessage) -> Self { Self::Initial(value) } } -impl Serialize for LoginInitiateMessage { +impl Serialize for InitialMessage { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -116,7 +116,7 @@ impl Serialize for LoginInitiateMessage { } } -impl<'de> Deserialize<'de> for LoginInitiateMessage { +impl<'de> Deserialize<'de> for InitialMessage { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/src/secure_channel/mod.rs b/src/secure_channel/mod.rs index e12a5ad1..d268287d 100644 --- a/src/secure_channel/mod.rs +++ b/src/secure_channel/mod.rs @@ -20,7 +20,7 @@ use thiserror::Error; use x25519_dalek::{EphemeralSecret, SharedSecret}; use zeroize::{Zeroize, ZeroizeOnDrop}; -pub use self::messages::{LoginInitiateMessage, Message, SecureChannelMessage}; +pub use self::messages::{InitialMessage, Message, SecureChannelMessage}; use crate::Curve25519PublicKey; mod messages; @@ -74,7 +74,7 @@ impl SecureChannel { pub fn create_inbound_channel( self, - message: &LoginInitiateMessage, + message: &InitialMessage, ) -> Result { let our_public_key = self.public_key(); @@ -277,7 +277,7 @@ impl EstablishedSecureChannel { if self.initiator && nonce.as_slice() == &[0u8; 12] { // we are Device G, we send the LoginInitiateMessage - LoginInitiateMessage { ciphertext, public_key: self.our_public_key }.into() + InitialMessage { ciphertext, public_key: self.our_public_key }.into() } else { Message { ciphertext }.into() }