diff --git a/zerotier-network-hypervisor/src/lib.rs b/zerotier-network-hypervisor/src/lib.rs index 2f82ebab9..0933e2aaf 100644 --- a/zerotier-network-hypervisor/src/lib.rs +++ b/zerotier-network-hypervisor/src/lib.rs @@ -9,6 +9,10 @@ #[macro_use] extern crate lazy_static; +pub const VERSION_MAJOR: u8 = 1; +pub const VERSION_MINOR: u8 = 99; +pub const VERSION_REVISION: u8 = 1; + pub mod util; pub mod error; pub mod vl1; @@ -26,11 +30,6 @@ pub type PacketBufferFactory = crate::util::buffer::PooledBufferFactory<{ crate: /// Source for instances of PacketBuffer pub type PacketBufferPool = crate::util::pool::Pool, crate::PacketBufferFactory>; -pub const VERSION_MAJOR: u8 = 1; -pub const VERSION_MINOR: u8 = 99; -pub const VERSION_REVISION: u8 = 1; -pub const VERSION_STR: &'static str = "1.99.1"; - /* * Protocol versions * @@ -60,13 +59,13 @@ pub const VERSION_STR: &'static str = "1.99.1"; * 10 - 1.4.0 ... 1.4.6 * + Contained early pre-alpha versions of multipath, which are deprecated * 11 - 1.6.0 ... 2.0.0 - * + Supports AES-GMAC-SIV symmetric crypto, backported from v2 tree. + * + Supports and prefers AES-GMAC-SIV symmetric crypto, backported. + * * 20 - 2.0.0 ... CURRENT + * + Forward secrecy with cryptographic ratchet! Finally!!! + * + New identity format including both x25519 and NIST P-521 keys. + * + AES-GMAC-SIV, a FIPS-compliant SIV construction using AES. * + HELLO and OK(HELLO) include an extra HMAC to harden authentication - * + HELLO and OK(HELLO) carry meta-data in a dictionary that's encrypted - * + Forward secrecy, key lifetime management - * + Old planet/moon stuff is DEAD! Independent roots are easier. - * + AES encryption with the SIV construction AES-GMAC-SIV - * + New combined Curve25519/NIST P-384 identity + * + HELLO and OK(HELLO) use a dictionary for better extensibilit. */ pub const VERSION_PROTO: u8 = 20; diff --git a/zerotier-network-hypervisor/src/vl1/endpoint.rs b/zerotier-network-hypervisor/src/vl1/endpoint.rs index 5b3dbce90..47b040b6a 100644 --- a/zerotier-network-hypervisor/src/vl1/endpoint.rs +++ b/zerotier-network-hypervisor/src/vl1/endpoint.rs @@ -27,7 +27,7 @@ pub const TYPE_HTTP: u8 = 8; pub const TYPE_WEBRTC: u8 = 9; pub const TYPE_ZEROTIER_ENCAP: u8 = 10; -/// A communication endpoint on the network where some ZeroTier node can be reached. +/// A communication endpoint on the network where a ZeroTier node can be reached. /// /// Currently only a few of these are supported. The rest are reserved for future use. #[derive(Clone, PartialEq, Eq)] diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs index 9ddaa606f..d07432ce5 100644 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ b/zerotier-network-hypervisor/src/vl1/ephemeral.rs @@ -6,6 +6,8 @@ * https://www.zerotier.com/ */ +use std::fmt::{Debug, Display}; +use std::error::Error; use std::sync::atomic::{AtomicU32, Ordering}; use std::io::Write; use std::convert::TryInto; @@ -23,14 +25,44 @@ use crate::vl1::Address; use crate::vl1::protocol::*; use crate::vl1::symmetricsecret::SymmetricSecret; -const EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE: u8 = 0x01; +const EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE_HMAC: u8 = 0x01; + +pub const ALGORITHM_C25519: u8 = 0x01; +pub const ALGORITHM_NISTP521ECDH: u8 = 0x02; +pub const ALGORITHM_SIDHP751: u8 = 0x04; + +pub enum EphemeralKeyAgreementError { + OldPublic, + StateMismatch, + InvalidData, + NoCompatibleAlgorithms +} + +impl Display for EphemeralKeyAgreementError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EphemeralKeyAgreementError::OldPublic => f.write_str("old (replayed?) public key data from remote"), + EphemeralKeyAgreementError::StateMismatch => f.write_str("ratchet state mismatch"), + EphemeralKeyAgreementError::InvalidData => f.write_str("invalid public key data"), + EphemeralKeyAgreementError::NoCompatibleAlgorithms => f.write_str("no compatible algorithms in public key data") + } + } +} + +impl Debug for EphemeralKeyAgreementError { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(self, f) } +} + +impl Error for EphemeralKeyAgreementError {} /// A set of ephemeral secret key pairs. Multiple algorithms are used. pub(crate) struct EphemeralKeyPairSet { - previous_ratchet_state: Option<[u8; 16]>, // Previous state of ratchet on which this agreement should build - c25519: C25519KeyPair, // Hipster DJB cryptography - p521: P521KeyPair, // US Federal Government cryptography - sidhp751: Option, // Post-quantum moon math cryptography (not used in every ratchet tick) + previous_ratchet_count: u64, // Previous ratchet count, next ratchet should be this + 1 + state_hmac: Option<[u8; 48]>, // HMAC of previous ratchet count, if there was a previous state + c25519: C25519KeyPair, // Hipster DJB cryptography + p521: P521KeyPair, // US Federal Government cryptography + sidhp751: Option, // Post-quantum moon math cryptography (not used in every ratchet tick) } impl EphemeralKeyPairSet { @@ -39,15 +71,16 @@ impl EphemeralKeyPairSet { /// This contains key pairs for the asymmetric key agreement algorithms used and a /// timestamp used to enforce TTL. pub fn new(local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { - let (sidhp751, previous_ratchet_state) = previous_ephemeral_secret.map_or_else(|| { + let (sidhp751, previous_ratchet_count, state_hmac) = previous_ephemeral_secret.map_or_else(|| { ( Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)), + 0, None ) }, |previous_ephemeral_secret| { ( - if previous_ephemeral_secret.ratchet_state[0] == 0 { - // We include SIDH with a probability of 1/256, which for a 5 minute re-key interval + if (previous_ephemeral_secret.ratchet_count & 0xff) == 0 { + // We include SIDH every 256 ratchets, which for a 5 minute re-key interval // means SIDH will be included about every 24 hours. SIDH is slower and is intended // to guard against long term warehousing for eventual cracking with a QC, so this // should be good enough for that threat model. @@ -55,11 +88,13 @@ impl EphemeralKeyPairSet { } else { None }, - Some(previous_ephemeral_secret.ratchet_state.clone()) + previous_ephemeral_secret.ratchet_count, + Some(SHA384::hmac(previous_ephemeral_secret.secret.ephemeral_ratchet_state_key.as_bytes(), &previous_ephemeral_secret.ratchet_count.to_be_bytes())) ) }); EphemeralKeyPairSet { - previous_ratchet_state, + previous_ratchet_count, + state_hmac, c25519: C25519KeyPair::generate(true), p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"), sidhp751, @@ -73,19 +108,21 @@ impl EphemeralKeyPairSet { pub fn public_bytes(&self) -> Vec { let mut b: Vec = Vec::with_capacity(SHA384_HASH_SIZE + 8 + C25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + SIDH_P751_PUBLIC_KEY_SIZE); - if self.previous_ratchet_state.is_some() { - b.push(EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE); - let _ = b.write_all(self.previous_ratchet_state.as_ref().unwrap()); + let _ = varint::write(&mut b, self.previous_ratchet_count); + + if self.state_hmac.is_some() { + b.push(EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE_HMAC); + let _ = b.write_all(self.state_hmac.as_ref().unwrap()); } else { b.push(0); } - b.push(EphemeralKeyAgreementAlgorithm::C25519 as u8); + b.push(ALGORITHM_C25519); let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64); let _ = b.write_all(&self.c25519.public_bytes()); let _ = self.sidhp751.as_ref().map(|sidhp751| { - b.push(EphemeralKeyAgreementAlgorithm::SIDHP751 as u8); + b.push(ALGORITHM_SIDHP751); let _ = varint::write(&mut b, (SIDH_P751_PUBLIC_KEY_SIZE + 1) as u64); b.push(sidhp751.role()); let pk = match &sidhp751 { @@ -100,7 +137,7 @@ impl EphemeralKeyPairSet { // FIPS point of view. Final key must be HKDF(salt, a FIPS-compliant algorithm secret). There is zero // actual security implication to the order. - b.push(EphemeralKeyAgreementAlgorithm::NISTP521ECDH as u8); + b.push(ALGORITHM_NISTP521ECDH); let _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64); let _ = b.write_all(self.p521.public_key_bytes()); @@ -115,41 +152,61 @@ impl EphemeralKeyPairSet { /// the ratchet sequence, or rather a key derived from it for this purpose. /// /// Since ephemeral secrets should only be used once, this consumes the object. - pub fn agree(self, time_ticks: i64, static_secret: &SymmetricSecret, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>, other_public_bytes: &[u8]) -> Option { - let (mut key, mut c25519_ratchet_count, mut sidhp751_ratchet_count, mut nistp521_ratchet_count) = previous_ephemeral_secret.map_or_else(|| { + pub fn agree(self, time_ticks: i64, static_secret: &SymmetricSecret, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>, other_public_bytes: &[u8]) -> Result { + let (mut key, mut ratchet_count, mut c25519_ratchet_count, mut sidhp751_ratchet_count, mut nistp521_ratchet_count) = previous_ephemeral_secret.map_or_else(|| { ( static_secret.ephemeral_ratchet_key.clone(), 0, 0, + 0, 0 ) }, |previous_ephemeral_secret| { ( previous_ephemeral_secret.secret.ephemeral_ratchet_key.clone(), + previous_ephemeral_secret.ratchet_count, previous_ephemeral_secret.c25519_ratchet_count, previous_ephemeral_secret.sidhp751_ratchet_count, previous_ephemeral_secret.nistp521_ratchet_count ) }); - let mut it_happened = false; - let mut fips_compliant_exchange = false; // ends up true if last algorithm was FIPS compliant + let mut it_happened = false; // set to true if at least one exchange occurred + let mut fips_compliant_exchange = false; // is true in the end if the last algorithm was FIPS-compliant let mut other_public_bytes = other_public_bytes; - // Check that the other side's ratchet state matches ours. If not the ratchet must restart. - if other_public_bytes.is_empty() { - return None; + // If the other side's ratchet counter is less than ours it means this may be a replayed + // public key (or duplicate packet) and should be ignored. If it's greater it's a state + // mismatch since there's no other way it could be from the future. + let other_ratchet_count = varint::read(&mut other_public_bytes); + if other_ratchet_count.is_err() { + return Err(EphemeralKeyAgreementError::InvalidData); } - if (other_public_bytes[0] & EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE) == 0 { - if previous_ephemeral_secret.is_some() { - return None; + let other_ratchet_count = other_ratchet_count.unwrap().0; + if other_ratchet_count < ratchet_count { + return Err(EphemeralKeyAgreementError::OldPublic); + } else if other_ratchet_count > ratchet_count { + return Err(EphemeralKeyAgreementError::StateMismatch); + } + + // Now check the other side's HMAC of the ratchet state to fully verify that the ratchet + // is aligned properly. + if other_public_bytes.is_empty() { + return Err(EphemeralKeyAgreementError::InvalidData); + } + if (other_public_bytes[0] & EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE_HMAC) != 0 { + if other_public_bytes.len() < 49 { + return Err(EphemeralKeyAgreementError::InvalidData); + } + if self.state_hmac.as_ref().map_or(true, |state_hmac| state_hmac != &other_public_bytes[1..49]) { + return Err(EphemeralKeyAgreementError::StateMismatch); + } + other_public_bytes = &other_public_bytes[49..]; + } else { + if self.state_hmac.is_some() { + return Err(EphemeralKeyAgreementError::OldPublic); } other_public_bytes = &other_public_bytes[1..]; - } else { - if other_public_bytes.len() < 17 || previous_ephemeral_secret.map_or(false, |previous_ephemeral_secret| other_public_bytes[1..17] != previous_ephemeral_secret.ratchet_state) { - return None; - } - other_public_bytes = &other_public_bytes[17..]; } while !other_public_bytes.is_empty() { @@ -157,15 +214,15 @@ impl EphemeralKeyPairSet { other_public_bytes = &other_public_bytes[1..]; let key_len = varint::read(&mut other_public_bytes); if key_len.is_err() { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } let key_len = key_len.unwrap().0 as usize; - match cipher.try_into() { + match cipher { - Ok(EphemeralKeyAgreementAlgorithm::C25519) => { + ALGORITHM_C25519 => { if other_public_bytes.len() < C25519_PUBLIC_KEY_SIZE || key_len != C25519_PUBLIC_KEY_SIZE { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } let c25519_secret = self.c25519.agree(&other_public_bytes[0..C25519_PUBLIC_KEY_SIZE]); @@ -177,9 +234,9 @@ impl EphemeralKeyPairSet { c25519_ratchet_count += 1; }, - Ok(EphemeralKeyAgreementAlgorithm::SIDHP751) => { + ALGORITHM_SIDHP751 => { if other_public_bytes.len() < (SIDH_P751_PUBLIC_KEY_SIZE + 1) || key_len != (SIDH_P751_PUBLIC_KEY_SIZE + 1) { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } let _ = match self.sidhp751.as_ref() { @@ -207,20 +264,20 @@ impl EphemeralKeyPairSet { other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..]; }, - Ok(EphemeralKeyAgreementAlgorithm::NISTP521ECDH) => { + ALGORITHM_NISTP751ECDH => { if other_public_bytes.len() < P521_PUBLIC_KEY_SIZE || key_len != P521_PUBLIC_KEY_SIZE { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } let p521_public = P521PublicKey::from_bytes(&other_public_bytes[0..P521_PUBLIC_KEY_SIZE]); other_public_bytes = &other_public_bytes[P521_PUBLIC_KEY_SIZE..]; if p521_public.is_none() { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } let p521_key = self.p521.agree(p521_public.as_ref().unwrap()); if p521_key.is_none() { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0); @@ -229,9 +286,9 @@ impl EphemeralKeyPairSet { nistp521_ratchet_count += 1; }, - Err(_) => { + _ => { if other_public_bytes.len() < key_len { - return None; + return Err(EphemeralKeyAgreementError::InvalidData); } other_public_bytes = &other_public_bytes[key_len..]; } @@ -240,10 +297,10 @@ impl EphemeralKeyPairSet { } return if it_happened { - let rs = zt_kbkdf_hmac_sha384(&key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_STATE_ID, 0, 0); - Some(EphemeralSymmetricSecret { + ratchet_count += 1; + Ok(EphemeralSymmetricSecret { secret: SymmetricSecret::new(key), - ratchet_state: (&rs.0[0..16]).try_into().unwrap(), + ratchet_count, rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME, expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME, c25519_ratchet_count, @@ -254,17 +311,17 @@ impl EphemeralKeyPairSet { fips_compliant_exchange }) } else { - None + Err(EphemeralKeyAgreementError::NoCompatibleAlgorithms) }; } } -/// Symmetric secret representing a step in the ephemeral keying ratchet. +/// An ephemeral symmetric secret with usage timers and counters. pub(crate) struct EphemeralSymmetricSecret { /// Current ephemeral secret key. pub secret: SymmetricSecret, - /// An identifier used to check negotiation of the ratchet. - ratchet_state: [u8; 16], + /// Total number of ratchets that has occurred. + ratchet_count: u64, /// Time at or after which we should start trying to re-key. rekey_time: i64, /// Time after which this key is no longer valid. @@ -280,7 +337,7 @@ pub(crate) struct EphemeralSymmetricSecret { /// Number of times this secret has been used to decrypt. decrypt_uses: AtomicU32, /// True if most recent key exchange was NIST/FIPS compliant. - fips_compliant_exchange: bool, + pub fips_compliant_exchange: bool, } impl EphemeralSymmetricSecret { @@ -307,6 +364,7 @@ impl EphemeralSymmetricSecret { } } +/// Internal container for SIDH key pairs tracking whether this is the "alice" or "bob" side in the SIDH protocol. enum SIDHEphemeralKeyPair { Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice), Bob(SIDHPublicKeyBob, SIDHSecretKeyBob) @@ -364,10 +422,10 @@ mod tests { for t in 1..ratchets+1 { let alice_public = alice.public_bytes(); let bob_public = bob.public_bytes(); - let alice_key = alice.agree(1, &static_secret, prev_alice_key.as_ref(), bob_public.as_slice()); - let bob_key = bob.agree(1, &static_secret, prev_bob_key.as_ref(), alice_public.as_slice()); - assert!(alice_key.is_some()); - assert!(bob_key.is_some()); + let alice_key = alice.agree(t, &static_secret, prev_alice_key.as_ref(), bob_public.as_slice()); + let bob_key = bob.agree(t, &static_secret, prev_bob_key.as_ref(), alice_public.as_slice()); + assert!(alice_key.is_ok()); + assert!(bob_key.is_ok()); let alice_key = alice_key.unwrap(); let bob_key = bob_key.unwrap(); assert_eq!(&alice_key.secret.key.0, &bob_key.secret.key.0); @@ -379,10 +437,12 @@ mod tests { } let last_alice_key = prev_alice_key.unwrap(); let last_bob_key = prev_bob_key.unwrap(); - assert_eq!(last_alice_key.c25519_ratchet_count, ratchets); - assert_eq!(last_bob_key.c25519_ratchet_count, ratchets); - assert_eq!(last_alice_key.nistp521_ratchet_count, ratchets); - assert_eq!(last_bob_key.nistp521_ratchet_count, ratchets); + assert_eq!(last_alice_key.ratchet_count, ratchets as u64); + assert_eq!(last_bob_key.ratchet_count, ratchets as u64); + assert_eq!(last_alice_key.c25519_ratchet_count, ratchets as u64); + assert_eq!(last_bob_key.c25519_ratchet_count, ratchets as u64); + assert_eq!(last_alice_key.nistp521_ratchet_count, ratchets as u64); + assert_eq!(last_bob_key.nistp521_ratchet_count, ratchets as u64); assert_eq!(last_alice_key.sidhp751_ratchet_count, last_bob_key.sidhp751_ratchet_count); assert!(last_alice_key.sidhp751_ratchet_count >= 1); } diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 5f286d332..57a10179e 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -42,7 +42,7 @@ pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_NEXT_KEY: u8 = b'e'; /// KBKDF usage label for generating the ratchet state ID (which is not actually a key). -pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_STATE_ID: u8 = b'E'; +pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_STATE_KEY: u8 = b'E'; /// Try to re-key ephemeral keys after this time. pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes @@ -181,37 +181,6 @@ pub const IDENTITY_V0_POW_THRESHOLD: u8 = 17; /// This is lower than the V0 threshold, causing the V0 part of V1 identities to verify on old nodes. pub const IDENTITY_V1_POW_THRESHOLD: u8 = 9; -#[derive(Clone, Copy)] -#[repr(u8)] -pub enum EphemeralKeyAgreementAlgorithm { - C25519 = 1, - SIDHP751 = 2, - NISTP521ECDH = 3 -} - -impl EphemeralKeyAgreementAlgorithm { - pub fn is_fips_compliant(&self) -> bool { - match self { - Self::NISTP521ECDH => true, - _ => false - } - } -} - -impl TryFrom for EphemeralKeyAgreementAlgorithm { - type Error = (); - - #[inline(always)] - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(Self::C25519), - 2 => Ok(Self::SIDHP751), - 3 => Ok(Self::NISTP521ECDH), - _ => Err(()) - } - } -} - /// Compress a packet and return true if compressed. /// The 'dest' buffer must be empty (will panic otherwise). A return value of false indicates an error or /// that the data was not compressible. The state of the destination buffer is undefined on a return diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index 4afa845fa..a97c4d6af 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -38,6 +38,9 @@ pub(crate) struct SymmetricSecret { /// A key used as input to the ephemeral key ratcheting mechanism. pub ephemeral_ratchet_key: Secret, + /// A key used to verify the state of the ephemeral ratchet. + pub ephemeral_ratchet_state_key: Secret, + /// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers. pub aes_gmac_siv: Pool, } @@ -54,6 +57,7 @@ impl SymmetricSecret { pub fn new(base_key: Secret) -> SymmetricSecret { let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); let usage_ephemeral_ratchet = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_NEXT_KEY, 0, 0); + let usage_ephemeral_ratchet_state = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_STATE_KEY, 0, 0); let aes_factory = AesGmacSivPoolFactory( zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0), zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0)); @@ -61,6 +65,7 @@ impl SymmetricSecret { key: base_key, packet_hmac_key: usage_packet_hmac, ephemeral_ratchet_key: usage_ephemeral_ratchet, + ephemeral_ratchet_state_key: usage_ephemeral_ratchet_state, aes_gmac_siv: Pool::new(2, aes_factory), } }