diff --git a/Cargo.toml b/Cargo.toml index 423a239..6df2642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,10 @@ members = [ "reference", ] resolver = "2" + +[profile.bench] +opt-level = 3 +strip = true +debug = true +codegen-units = 1 +lto = true diff --git a/performance/Cargo.toml b/performance/Cargo.toml index b36ed91..bb8cb89 100644 --- a/performance/Cargo.toml +++ b/performance/Cargo.toml @@ -10,9 +10,6 @@ name = "zssp" path = "src/lib.rs" doc = true -[profile.bench] -debug = true - [dependencies] rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } diff --git a/performance/src/crypto/p384.rs b/performance/src/crypto/p384.rs index 67f780a..24be426 100644 --- a/performance/src/crypto/p384.rs +++ b/performance/src/crypto/p384.rs @@ -10,7 +10,7 @@ pub trait P384PublicKey: Sized + Send + Sync { /// Create a P-384 public key from raw bytes. /// /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384 - /// curve, or if it breaks the P-384 spec in any other way. + /// curve, or if it breaks the P-384 spec in any other way. `P384KeyPair::agree` must never fail. fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; /// Get the raw bytes that uniquely define the public key. @@ -25,7 +25,7 @@ pub trait P384PublicKey: Sized + Send + Sync { pub trait P384KeyPair { /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. type PublicKey: P384PublicKey; - /// Randomly generate a new P-384 keypair. + /// Randomly generate a new P-384 keypair. This keypair must be fully valid. /// /// This function may use the provided RNG or its own, so long as the output is cryptographically random. fn generate(rng: &mut Rng) -> Self; @@ -37,10 +37,6 @@ pub trait P384KeyPair { /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `ecdh_out`. /// - /// **CRITICAL**: This function must return `false` if key agreement between this private key and - /// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret. - /// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed - /// library that has already implemented this correctly. - #[must_use] - fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; + /// If there is any possibility of this function failing, panic instead of returning. + fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]); } diff --git a/performance/src/crypto_impl/p384_impl.rs b/performance/src/crypto_impl/p384_impl.rs index 17bfb1b..1ad96f4 100644 --- a/performance/src/crypto_impl/p384_impl.rs +++ b/performance/src/crypto_impl/p384_impl.rs @@ -27,13 +27,12 @@ impl P384KeyPair for CrateP384KeyPair { CompressedPoint::from(self.public_key()).as_slice().try_into().unwrap() } - fn agree(&self, public_key: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool { + fn agree(&self, public_key: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) { *output = self .diffie_hellman(public_key) .raw_secret_bytes() .as_slice() .try_into() .unwrap(); - true } } diff --git a/performance/src/result.rs b/performance/src/result.rs index 11dcf70..2013d49 100644 --- a/performance/src/result.rs +++ b/performance/src/result.rs @@ -9,9 +9,6 @@ use crate::zeta::Session; /// Depending on the error type trying again may not work. #[derive(Debug)] pub enum OpenError { - /// An invalid parameter was supplied to the function. - InvalidPublicKey, - /// The given identity string was larger than `IDENTITY_MAX_SIZE`, a.k.a. 4096 bytes. IdentityTooLarge, @@ -243,7 +240,6 @@ pub enum SessionEvent { impl Display for OpenError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - OpenError::InvalidPublicKey => f.write_str("invalid public key"), OpenError::IdentityTooLarge => f.write_str("identity too large"), OpenError::StorageError(e) => e.fmt(f), } diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 82083ad..c0c4921 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -180,30 +180,15 @@ impl SymmetricState { *i = j; Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - #[must_use] - fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { + fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); - if secret.agree(&remote, &mut ecdh_secret) { - self.mix_key(hmac, ecdh_secret.as_ref()); - Some(()) - } else { - None - } + secret.agree(&remote, &mut ecdh_secret); + self.mix_key(hmac, ecdh_secret.as_ref()); } - #[must_use] - fn mix_dh_no_init( - &mut self, - hmac: &mut Crypto::Hmac, - secret: &Crypto::KeyPair, - remote: &Crypto::PublicKey, - ) -> Option<()> { + fn mix_dh_no_init(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); - if secret.agree(&remote, &mut ecdh_secret) { - self.mix_key_no_init(hmac, ecdh_secret.as_ref()); - Some(()) - } else { - None - } + secret.agree(&remote, &mut ecdh_secret); + self.mix_key_no_init(hmac, ecdh_secret.as_ref()); } } @@ -299,7 +284,7 @@ fn create_a1_state( ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: &[u8], -) -> Option>> { +) -> Box> { // <- s // ... // -> e, es, e1 @@ -314,7 +299,7 @@ fn create_a1_state( // Process message pattern 1 e token. let e_secret = noise.write_e_no_init(hash, hmac, rng, &mut x1); // Process message pattern 1 es token. - noise.mix_dh(hmac, &e_secret, s_remote)?; + noise.mix_dh(hmac, &e_secret, s_remote); // Process message pattern 1 e1 token. let i = x1.len(); let (e1_secret, e1_public) = Crypto::Kem::generate(rng.lock().unwrap().deref_mut()); @@ -340,7 +325,7 @@ fn create_a1_state( set_header(&mut x1, 0, &to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c)); let identity = identity.try_into().unwrap(); - Some(Box::new(StateA1 { noise, e_secret, e1_secret, identity, x1 })) + Box::new(StateA1 { noise, e_secret, e1_secret, identity, x1 }) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1>( @@ -371,13 +356,10 @@ pub(crate) fn trans_to_a1>( &state1, state2.as_ref(), identity, - ) - .ok_or(OpenError::InvalidPublicKey)?; + ); let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); - if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { - return Err(OpenError::InvalidPublicKey); - } + ctx.s_secret.agree(&s_remote, &mut noise_kk_ss); let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); @@ -478,9 +460,7 @@ pub(crate) fn received_x1_trans>( let hash = &mut Crypto::Hash::new(); let hmac = &mut Crypto::Hmac::new(); - if let Some(a1) = create_a1_state( + let a1 = create_a1_state( hash, hmac, &ctx.rng, @@ -1203,32 +1173,29 @@ fn timeout_trans>( &state.ratchet_state1, state.ratchet_state2.as_ref(), identity, - ) { - let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); - let mut hk_send = Zeroizing::new([0u8; HASHLEN]); - a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); - let mut x1 = a1.x1.clone(); + ); + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); - drop(state); - let resend_timer = { - let mut state = session.state.write().unwrap(); - state.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); - *state.key_mut(true) = DuplexKey::default(); - state.key_mut(true).recv.kid = Some(new_kid_recv); - let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; - state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; - state.beta = ZetaAutomata::A1(a1); - resend_timer - }; - drop(kex_lock); + drop(state); + let resend_timer = { + let mut state = session.state.write().unwrap(); + state.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + *state.key_mut(true) = DuplexKey::default(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); + state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A1(a1); + resend_timer + }; + drop(kex_lock); - send(&mut x1, None); - Some(resend_timer) - } else { - None - } + send(&mut x1, None); + Some(resend_timer) } ZetaAutomata::S2 => { // Corresponds to Transition Algorithm 6 found in Section 4.3. @@ -1251,9 +1218,7 @@ fn timeout_trans>( // Process message pattern 1 e token. let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut k1); // Process message pattern 1 es token. - if noise.mix_dh_no_init(hmac, &e_secret, &session.s_remote).is_none() { - return None; - } + noise.mix_dh_no_init(hmac, &e_secret, &session.s_remote); // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. @@ -1410,9 +1375,7 @@ pub(crate) fn received_k1_trans { /// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret. /// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. - fn agree(&self, public_key: &Self::PublicKey) -> Option<[u8; P384_ECDH_SHARED_SECRET_SIZE]>; + fn agree(&self, public_key: &Self::PublicKey) -> [u8; P384_ECDH_SHARED_SECRET_SIZE]; } diff --git a/reference/src/crypto_impl/p384_impl.rs b/reference/src/crypto_impl/p384_impl.rs index 66cd470..bcce08f 100644 --- a/reference/src/crypto_impl/p384_impl.rs +++ b/reference/src/crypto_impl/p384_impl.rs @@ -29,13 +29,11 @@ impl P384KeyPair for P384CrateKeyPair { CompressedPoint::from(self.public_key()).as_slice().try_into().unwrap() } - fn agree(&self, public_key: &Self::PublicKey) -> Option<[u8; P384_ECDH_SHARED_SECRET_SIZE]> { - Some( - self.diffie_hellman(public_key) - .raw_secret_bytes() - .as_slice() - .try_into() - .unwrap(), - ) + fn agree(&self, public_key: &Self::PublicKey) -> [u8; P384_ECDH_SHARED_SECRET_SIZE] { + self.diffie_hellman(public_key) + .raw_secret_bytes() + .as_slice() + .try_into() + .unwrap() } } diff --git a/reference/src/result.rs b/reference/src/result.rs index c2575ac..5108d69 100644 --- a/reference/src/result.rs +++ b/reference/src/result.rs @@ -9,9 +9,6 @@ use crate::zeta::Session; /// Depending on the error type trying again may not work. #[derive(Debug)] pub enum OpenError { - /// An invalid parameter was supplied to the function. - InvalidPublicKey, - /// The given identity string was larger than `IDENTITY_MAX_SIZE`, a.k.a. 4096 bytes. IdentityTooLarge, @@ -243,7 +240,6 @@ pub enum SessionEvent { impl Display for OpenError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - OpenError::InvalidPublicKey => f.write_str("invalid public key"), OpenError::IdentityTooLarge => f.write_str("identity too large"), OpenError::StorageError(e) => e.fmt(f), } diff --git a/reference/src/zeta.rs b/reference/src/zeta.rs index 990d612..ccfde48 100644 --- a/reference/src/zeta.rs +++ b/reference/src/zeta.rs @@ -123,13 +123,9 @@ impl SymmetricState { *i = j; Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - fn mix_dh(&mut self, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { - if let Some(ecdh) = secret.agree(&remote).map(Zeroizing::new) { - self.mix_key(ecdh.as_ref()); - Some(()) - } else { - None - } + fn mix_dh(&mut self, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) { + let ecdh = Zeroizing::new(secret.agree(&remote)); + self.mix_key(ecdh.as_ref()); } } @@ -247,7 +243,7 @@ fn create_a1_state( ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: Vec, -) -> Option> { +) -> StateA1 { // <- s // ... // -> e, es, e1 @@ -261,7 +257,7 @@ fn create_a1_state( // Process message pattern 1 e token. let e_secret = noise.write_e(rng, &mut x1); // Process message pattern 1 es token. - noise.mix_dh(&e_secret, s_remote)?; + noise.mix_dh(&e_secret, s_remote); // Process message pattern 1 e1 token. let i = x1.len(); let (e1_secret, e1_public) = ::Kem::generate(rng.borrow_mut().deref_mut()); @@ -280,13 +276,13 @@ fn create_a1_state( let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); x1.extend(&gen_null_response(rng.borrow_mut().deref_mut())); - Some(StateA1 { + StateA1 { noise, e_secret, e1_secret, identity, packet: Packet(0, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), x1), - }) + } } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( @@ -305,8 +301,7 @@ pub(crate) fn trans_to_a1( let mut session_map = ctx.session_map.borrow_mut(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.borrow_mut().deref_mut()); - let a1 = create_a1_state::(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity) - .ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state::(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity); let packet = a1.packet.clone(); let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); @@ -388,9 +383,7 @@ pub(crate) fn received_x1_trans( // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise - .mix_dh(&ctx.s_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&ctx.s_secret, &e_remote); // Process message pattern 1 e1 token. let j = i + KYBER_PUBLIC_KEY_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -436,9 +429,7 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 e token. let e_secret = noise.write_e(&ctx.rng, &mut x2); // Process message pattern 2 ee token. - noise - .mix_dh(&e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&e_secret, &e_remote); // Process message pattern 2 ekem1 token. let i = x2.len(); let (ekem1, ekem1_secret) = ::Kem::encapsulate( @@ -519,9 +510,7 @@ pub(crate) fn received_x2_trans( // Process message pattern 2 e token. let e_remote = noise.read_e(&mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise - .mix_dh(e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(e_secret, &e_remote); // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -587,9 +576,7 @@ pub(crate) fn received_x2_trans( x3.extend(&ctx.s_secret.public_key_bytes()); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), i, &mut x3); // Process message pattern 3 se token. - noise - .mix_dh(&ctx.s_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&ctx.s_secret, &e_remote); // Process message pattern 3 payload. let i = x3.len(); x3.extend(identity); @@ -704,9 +691,7 @@ pub(crate) fn received_x3_trans( .ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. - noise - .mix_dh(&zeta.e_secret, &s_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&zeta.e_secret, &s_remote); // Process message pattern 3 payload. let k = x3.len(); let j = k - AES_GCM_TAG_SIZE; @@ -1048,29 +1033,26 @@ fn timeout_trans( } let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - if let Some(a1) = create_a1_state::( + let a1 = create_a1_state::( &ctx.rng, &zeta.s_remote, new_kid_recv, &zeta.ratchet_state1, zeta.ratchet_state2.as_ref(), identity.clone(), - ) { - let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); - let packet = a1.packet.clone(); + ); + let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); + let packet = a1.packet.clone(); - zeta.hk_send = hk_send; - *zeta.key_mut(true) = DuplexKey::default(); - zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; - zeta.timeout_timer = current_time + ::SETTINGS.initial_offer_timeout as i64; - zeta.beta = ZetaAutomata::A1(a1); - zeta.defrag = DefragBuffer::new(Some(hk_recv)); + zeta.hk_send = hk_send; + *zeta.key_mut(true) = DuplexKey::default(); + zeta.key_mut(true).recv.kid = Some(new_kid_recv); + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.initial_offer_timeout as i64; + zeta.beta = ZetaAutomata::A1(a1); + zeta.defrag = DefragBuffer::new(Some(hk_recv)); - send(&packet, None); - } else { - zeta.expire(); - } + send(&packet, None); } ZetaAutomata::S2 => { // Corresponds to Transition Algorithm 6 found in Section 4.3. @@ -1090,15 +1072,9 @@ fn timeout_trans( // Process message pattern 1 e token. let e_secret = noise.write_e(&ctx.rng, &mut k1); // Process message pattern 1 es token. - if noise.mix_dh(&e_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } + noise.mix_dh(&e_secret, &zeta.s_remote); // Process message pattern 1 ss token. - if noise.mix_dh(&ctx.s_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } + noise.mix_dh(&ctx.s_secret, &zeta.s_remote); // Process message pattern 1 payload. let i = k1.len(); k1.extend(&new_kid_recv.get().to_be_bytes()); @@ -1189,13 +1165,9 @@ pub(crate) fn received_k1_trans( // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise - .mix_dh(s_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(s_secret, &e_remote); // Process message pattern 1 ss token. - noise - .mix_dh(s_secret, &zeta.s_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(s_secret, &zeta.s_remote); // Process message pattern 1 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -1210,13 +1182,9 @@ pub(crate) fn received_k1_trans( // Process message pattern 2 e token. let e_secret = noise.write_e(rng, &mut k2); // Process message pattern 2 ee token. - noise - .mix_dh(&e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&e_secret, &e_remote); // Process message pattern 2 se token. - noise - .mix_dh(&s_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(&s_secret, &e_remote); // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(session, &zeta, rng, session_map); @@ -1310,13 +1278,9 @@ pub(crate) fn received_k2_trans( // Process message pattern 2 e token. let e_remote = noise.read_e(&mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise - .mix_dh(e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(e_secret, &e_remote); // Process message pattern 2 se token. - noise - .mix_dh(e_secret, &zeta.s_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(e_secret, &zeta.s_remote); // Process message pattern 2 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE;