diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f4c6d574d..20684d801 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -12,3 +12,16 @@ pub mod verified; pub mod x25519; pub const ZEROES: [u8; 64] = [0_u8; 64]; + +/// Constant time byte slice equality. +pub fn secure_eq(a: &[u8], b: &[u8]) -> bool { + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} diff --git a/crypto/src/salsa.rs b/crypto/src/salsa.rs index b59f4bbf9..0711ba07c 100644 --- a/crypto/src/salsa.rs +++ b/crypto/src/salsa.rs @@ -49,6 +49,7 @@ impl Salsa { } } + #[inline] pub fn crypt(&mut self, mut plaintext: &[u8], mut ciphertext: &mut [u8]) { let (j0, j1, j2, j3, j4, j5, j6, j7, mut j8, mut j9, j10, j11, j12, j13, j14, j15) = ( self.state[0], diff --git a/crypto/src/x25519.rs b/crypto/src/x25519.rs index d81399454..e0a2d8b7d 100644 --- a/crypto/src/x25519.rs +++ b/crypto/src/x25519.rs @@ -19,7 +19,6 @@ pub const ED25519_SIGNATURE_SIZE: usize = 64; pub struct X25519KeyPair(x25519_dalek::StaticSecret, Secret<32>, x25519_dalek::PublicKey); impl X25519KeyPair { - #[inline(always)] pub fn generate() -> X25519KeyPair { let sk = x25519_dalek::StaticSecret::new(SecureRandom::get()); let sk2 = Secret(sk.to_bytes()); @@ -96,7 +95,6 @@ impl Clone for X25519KeyPair { pub struct Ed25519KeyPair(ed25519_dalek::Keypair, Secret<32>); impl Ed25519KeyPair { - #[inline(always)] pub fn generate() -> Ed25519KeyPair { let mut rng = SecureRandom::get(); let kp = ed25519_dalek::Keypair::generate(&mut rng); diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index 2a96bf826..5e7a797f9 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -10,6 +10,7 @@ use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, SHA384}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE}; use zerotier_crypto::random; use zerotier_crypto::secret::Secret; +use zerotier_crypto::secure_eq; use zerotier_utils::gatherarray::GatherArray; use zerotier_utils::memory; @@ -736,13 +737,14 @@ impl ReceiveContext { let hmac1_end = kex_packet_len - HMAC_SIZE; // Check the secondary HMAC first, which proves that the sender knows the recipient's full static identity. - if !hmac_sha384_2( - app.get_local_s_public_blob_hash(), - canonical_header_bytes, - &kex_packet[HEADER_SIZE..hmac1_end], - ) - .eq(&kex_packet[hmac1_end..kex_packet_len]) - { + if !secure_eq( + &hmac_sha384_2( + app.get_local_s_public_blob_hash(), + canonical_header_bytes, + &kex_packet[HEADER_SIZE..hmac1_end], + ), + &kex_packet[hmac1_end..kex_packet_len], + ) { return Err(Error::FailedAuthentication); } @@ -814,13 +816,14 @@ impl ReceiveContext { // Authenticate entire packet with HMAC-SHA384, verifying alice's identity via 'ss' secret that was // just mixed into the key. - if !hmac_sha384_2( - kbkdf512(noise_ik_incomplete_es_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - canonical_header_bytes, - &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], - ) - .eq(&kex_packet[aes_gcm_tag_end..hmac1_end]) - { + if !secure_eq( + &hmac_sha384_2( + kbkdf512(noise_ik_incomplete_es_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + canonical_header_bytes, + &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], + ), + &kex_packet[aes_gcm_tag_end..hmac1_end], + ) { return Err(Error::FailedAuthentication); } @@ -830,7 +833,7 @@ impl ReceiveContext { // then create new sessions. let (new_session, ratchet_key, last_ratchet_count) = if let Some(session) = session.as_ref() { // Existing session identity must match the one in this offer. - if !session.remote_s_public_blob_hash.eq(&SHA384::hash(&alice_s_public_blob)) { + if !secure_eq(&session.remote_s_public_blob_hash, &SHA384::hash(&alice_s_public_blob)) { return Err(Error::FailedAuthentication); } @@ -1118,13 +1121,14 @@ impl ReceiveContext { } // Check main packet HMAC for full validation of session key. - if !hmac_sha384_2( - kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), - canonical_header_bytes, - &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], - ) - .eq(&kex_packet[aes_gcm_tag_end..kex_packet_len]) - { + if !secure_eq( + &hmac_sha384_2( + kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + canonical_header_bytes, + &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], + ), + &kex_packet[aes_gcm_tag_end..kex_packet_len], + ) { return Err(Error::FailedAuthentication); }