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

MSC4108 secure channel split encryption keys and monotonic nonces #146

Merged
merged 3 commits into from
Apr 20, 2024
Merged
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
100 changes: 72 additions & 28 deletions src/secure_channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,36 @@ 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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like constants to be either completely local, or if they aren't scoped to a impl block or even function, at the top of the file. They are now between a struct and its impl block which I find to be the last place I would expect them to be.

Though don't worry, I'll move them where I'll want them.


impl EstablishedSecureChannel {
fn create_check_code(
shared_secret: &SharedSecret,
our_public_key: Curve25519PublicKey,
their_public_key: Curve25519PublicKey,
initiator: bool,
) -> CheckCode {
const INFO_STRING: &str = "MATRIX_QR_CODE_LOGIN_CHECKCODE";

let mut bytes = [0u8; 2];
let kdf: Hkdf<Sha512> = 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())
Expand All @@ -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<Sha512> = 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())
Expand All @@ -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,
}
}

Expand All @@ -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()
Expand All @@ -245,7 +293,7 @@ impl EstablishedSecureChannel {
nonce: &Nonce,
ciphertext: &[u8],
) -> Result<Vec<u8>, SecureChannelError> {
let cipher = ChaCha20Poly1305::new(self.key());
let cipher = ChaCha20Poly1305::new(self.decryption_key());
let nonce = nonce.into();

let plaintext =
Expand All @@ -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(&current.to_le_bytes()[..12]);
Expand All @@ -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();
Expand Down