More cleanup and one more tweak to ephemeral keys. Add a ratchet counter to prevent replay attacks.

This commit is contained in:
Adam Ierymenko 2022-01-13 13:01:44 -05:00
parent c39f38d818
commit 07cfd12620
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
5 changed files with 136 additions and 103 deletions

View file

@ -9,6 +9,10 @@
#[macro_use] #[macro_use]
extern crate lazy_static; 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 util;
pub mod error; pub mod error;
pub mod vl1; pub mod vl1;
@ -26,11 +30,6 @@ pub type PacketBufferFactory = crate::util::buffer::PooledBufferFactory<{ crate:
/// Source for instances of PacketBuffer /// Source for instances of PacketBuffer
pub type PacketBufferPool = crate::util::pool::Pool<crate::util::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, crate::PacketBufferFactory>; pub type PacketBufferPool = crate::util::pool::Pool<crate::util::buffer::Buffer<{ crate::vl1::protocol::PACKET_SIZE_MAX }>, 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 * Protocol versions
* *
@ -60,13 +59,13 @@ pub const VERSION_STR: &'static str = "1.99.1";
* 10 - 1.4.0 ... 1.4.6 * 10 - 1.4.0 ... 1.4.6
* + Contained early pre-alpha versions of multipath, which are deprecated * + Contained early pre-alpha versions of multipath, which are deprecated
* 11 - 1.6.0 ... 2.0.0 * 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 * 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) include an extra HMAC to harden authentication
* + HELLO and OK(HELLO) carry meta-data in a dictionary that's encrypted * + HELLO and OK(HELLO) use a dictionary for better extensibilit.
* + 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
*/ */
pub const VERSION_PROTO: u8 = 20; pub const VERSION_PROTO: u8 = 20;

View file

@ -27,7 +27,7 @@ pub const TYPE_HTTP: u8 = 8;
pub const TYPE_WEBRTC: u8 = 9; pub const TYPE_WEBRTC: u8 = 9;
pub const TYPE_ZEROTIER_ENCAP: u8 = 10; 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. /// Currently only a few of these are supported. The rest are reserved for future use.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]

View file

@ -6,6 +6,8 @@
* https://www.zerotier.com/ * https://www.zerotier.com/
*/ */
use std::fmt::{Debug, Display};
use std::error::Error;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use std::io::Write; use std::io::Write;
use std::convert::TryInto; use std::convert::TryInto;
@ -23,14 +25,44 @@ use crate::vl1::Address;
use crate::vl1::protocol::*; use crate::vl1::protocol::*;
use crate::vl1::symmetricsecret::SymmetricSecret; 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 { <Self as Display>::fmt(self, f) }
}
impl Error for EphemeralKeyAgreementError {}
/// A set of ephemeral secret key pairs. Multiple algorithms are used. /// A set of ephemeral secret key pairs. Multiple algorithms are used.
pub(crate) struct EphemeralKeyPairSet { pub(crate) struct EphemeralKeyPairSet {
previous_ratchet_state: Option<[u8; 16]>, // Previous state of ratchet on which this agreement should build previous_ratchet_count: u64, // Previous ratchet count, next ratchet should be this + 1
c25519: C25519KeyPair, // Hipster DJB cryptography state_hmac: Option<[u8; 48]>, // HMAC of previous ratchet count, if there was a previous state
p521: P521KeyPair, // US Federal Government cryptography c25519: C25519KeyPair, // Hipster DJB cryptography
sidhp751: Option<SIDHEphemeralKeyPair>, // Post-quantum moon math cryptography (not used in every ratchet tick) p521: P521KeyPair, // US Federal Government cryptography
sidhp751: Option<SIDHEphemeralKeyPair>, // Post-quantum moon math cryptography (not used in every ratchet tick)
} }
impl EphemeralKeyPairSet { impl EphemeralKeyPairSet {
@ -39,15 +71,16 @@ impl EphemeralKeyPairSet {
/// This contains key pairs for the asymmetric key agreement algorithms used and a /// This contains key pairs for the asymmetric key agreement algorithms used and a
/// timestamp used to enforce TTL. /// timestamp used to enforce TTL.
pub fn new(local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { 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)), Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)),
0,
None None
) )
}, |previous_ephemeral_secret| { }, |previous_ephemeral_secret| {
( (
if previous_ephemeral_secret.ratchet_state[0] == 0 { if (previous_ephemeral_secret.ratchet_count & 0xff) == 0 {
// We include SIDH with a probability of 1/256, which for a 5 minute re-key interval // 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 // 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 // to guard against long term warehousing for eventual cracking with a QC, so this
// should be good enough for that threat model. // should be good enough for that threat model.
@ -55,11 +88,13 @@ impl EphemeralKeyPairSet {
} else { } else {
None 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 { EphemeralKeyPairSet {
previous_ratchet_state, previous_ratchet_count,
state_hmac,
c25519: C25519KeyPair::generate(true), c25519: C25519KeyPair::generate(true),
p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"), p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"),
sidhp751, sidhp751,
@ -73,19 +108,21 @@ impl EphemeralKeyPairSet {
pub fn public_bytes(&self) -> Vec<u8> { pub fn public_bytes(&self) -> Vec<u8> {
let mut b: Vec<u8> = Vec::with_capacity(SHA384_HASH_SIZE + 8 + C25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + SIDH_P751_PUBLIC_KEY_SIZE); let mut b: Vec<u8> = 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() { let _ = varint::write(&mut b, self.previous_ratchet_count);
b.push(EPHEMERAL_PUBLIC_FLAG_HAVE_RATCHET_STATE);
let _ = b.write_all(self.previous_ratchet_state.as_ref().unwrap()); 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 { } else {
b.push(0); 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 _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64);
let _ = b.write_all(&self.c25519.public_bytes()); let _ = b.write_all(&self.c25519.public_bytes());
let _ = self.sidhp751.as_ref().map(|sidhp751| { 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); let _ = varint::write(&mut b, (SIDH_P751_PUBLIC_KEY_SIZE + 1) as u64);
b.push(sidhp751.role()); b.push(sidhp751.role());
let pk = match &sidhp751 { 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 // FIPS point of view. Final key must be HKDF(salt, a FIPS-compliant algorithm secret). There is zero
// actual security implication to the order. // 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 _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64);
let _ = b.write_all(self.p521.public_key_bytes()); 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. /// 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. /// 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<EphemeralSymmetricSecret> { pub fn agree(self, time_ticks: i64, static_secret: &SymmetricSecret, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>, other_public_bytes: &[u8]) -> Result<EphemeralSymmetricSecret, EphemeralKeyAgreementError> {
let (mut key, mut c25519_ratchet_count, mut sidhp751_ratchet_count, mut nistp521_ratchet_count) = previous_ephemeral_secret.map_or_else(|| { 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(), static_secret.ephemeral_ratchet_key.clone(),
0, 0,
0, 0,
0,
0 0
) )
}, |previous_ephemeral_secret| { }, |previous_ephemeral_secret| {
( (
previous_ephemeral_secret.secret.ephemeral_ratchet_key.clone(), previous_ephemeral_secret.secret.ephemeral_ratchet_key.clone(),
previous_ephemeral_secret.ratchet_count,
previous_ephemeral_secret.c25519_ratchet_count, previous_ephemeral_secret.c25519_ratchet_count,
previous_ephemeral_secret.sidhp751_ratchet_count, previous_ephemeral_secret.sidhp751_ratchet_count,
previous_ephemeral_secret.nistp521_ratchet_count previous_ephemeral_secret.nistp521_ratchet_count
) )
}); });
let mut it_happened = false; let mut it_happened = false; // set to true if at least one exchange occurred
let mut fips_compliant_exchange = false; // ends up true if last algorithm was FIPS compliant 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; 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 the other side's ratchet counter is less than ours it means this may be a replayed
if other_public_bytes.is_empty() { // public key (or duplicate packet) and should be ignored. If it's greater it's a state
return None; // 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 { let other_ratchet_count = other_ratchet_count.unwrap().0;
if previous_ephemeral_secret.is_some() { if other_ratchet_count < ratchet_count {
return None; 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..]; 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() { while !other_public_bytes.is_empty() {
@ -157,15 +214,15 @@ impl EphemeralKeyPairSet {
other_public_bytes = &other_public_bytes[1..]; other_public_bytes = &other_public_bytes[1..];
let key_len = varint::read(&mut other_public_bytes); let key_len = varint::read(&mut other_public_bytes);
if key_len.is_err() { if key_len.is_err() {
return None; return Err(EphemeralKeyAgreementError::InvalidData);
} }
let key_len = key_len.unwrap().0 as usize; 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 { 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]); let c25519_secret = self.c25519.agree(&other_public_bytes[0..C25519_PUBLIC_KEY_SIZE]);
@ -177,9 +234,9 @@ impl EphemeralKeyPairSet {
c25519_ratchet_count += 1; 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) { 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() { let _ = match self.sidhp751.as_ref() {
@ -207,20 +264,20 @@ impl EphemeralKeyPairSet {
other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..]; 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 { 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]); let p521_public = P521PublicKey::from_bytes(&other_public_bytes[0..P521_PUBLIC_KEY_SIZE]);
other_public_bytes = &other_public_bytes[P521_PUBLIC_KEY_SIZE..]; other_public_bytes = &other_public_bytes[P521_PUBLIC_KEY_SIZE..];
if p521_public.is_none() { if p521_public.is_none() {
return None; return Err(EphemeralKeyAgreementError::InvalidData);
} }
let p521_key = self.p521.agree(p521_public.as_ref().unwrap()); let p521_key = self.p521.agree(p521_public.as_ref().unwrap());
if p521_key.is_none() { if p521_key.is_none() {
return None; return Err(EphemeralKeyAgreementError::InvalidData);
} }
key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0); key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0);
@ -229,9 +286,9 @@ impl EphemeralKeyPairSet {
nistp521_ratchet_count += 1; nistp521_ratchet_count += 1;
}, },
Err(_) => { _ => {
if other_public_bytes.len() < key_len { if other_public_bytes.len() < key_len {
return None; return Err(EphemeralKeyAgreementError::InvalidData);
} }
other_public_bytes = &other_public_bytes[key_len..]; other_public_bytes = &other_public_bytes[key_len..];
} }
@ -240,10 +297,10 @@ impl EphemeralKeyPairSet {
} }
return if it_happened { return if it_happened {
let rs = zt_kbkdf_hmac_sha384(&key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_STATE_ID, 0, 0); ratchet_count += 1;
Some(EphemeralSymmetricSecret { Ok(EphemeralSymmetricSecret {
secret: SymmetricSecret::new(key), secret: SymmetricSecret::new(key),
ratchet_state: (&rs.0[0..16]).try_into().unwrap(), ratchet_count,
rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME, rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME,
expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME, expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME,
c25519_ratchet_count, c25519_ratchet_count,
@ -254,17 +311,17 @@ impl EphemeralKeyPairSet {
fips_compliant_exchange fips_compliant_exchange
}) })
} else { } 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 { pub(crate) struct EphemeralSymmetricSecret {
/// Current ephemeral secret key. /// Current ephemeral secret key.
pub secret: SymmetricSecret, pub secret: SymmetricSecret,
/// An identifier used to check negotiation of the ratchet. /// Total number of ratchets that has occurred.
ratchet_state: [u8; 16], ratchet_count: u64,
/// Time at or after which we should start trying to re-key. /// Time at or after which we should start trying to re-key.
rekey_time: i64, rekey_time: i64,
/// Time after which this key is no longer valid. /// 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. /// Number of times this secret has been used to decrypt.
decrypt_uses: AtomicU32, decrypt_uses: AtomicU32,
/// True if most recent key exchange was NIST/FIPS compliant. /// True if most recent key exchange was NIST/FIPS compliant.
fips_compliant_exchange: bool, pub fips_compliant_exchange: bool,
} }
impl EphemeralSymmetricSecret { 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 { enum SIDHEphemeralKeyPair {
Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice), Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice),
Bob(SIDHPublicKeyBob, SIDHSecretKeyBob) Bob(SIDHPublicKeyBob, SIDHSecretKeyBob)
@ -364,10 +422,10 @@ mod tests {
for t in 1..ratchets+1 { for t in 1..ratchets+1 {
let alice_public = alice.public_bytes(); let alice_public = alice.public_bytes();
let bob_public = bob.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 alice_key = alice.agree(t, &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()); let bob_key = bob.agree(t, &static_secret, prev_bob_key.as_ref(), alice_public.as_slice());
assert!(alice_key.is_some()); assert!(alice_key.is_ok());
assert!(bob_key.is_some()); assert!(bob_key.is_ok());
let alice_key = alice_key.unwrap(); let alice_key = alice_key.unwrap();
let bob_key = bob_key.unwrap(); let bob_key = bob_key.unwrap();
assert_eq!(&alice_key.secret.key.0, &bob_key.secret.key.0); 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_alice_key = prev_alice_key.unwrap();
let last_bob_key = prev_bob_key.unwrap(); let last_bob_key = prev_bob_key.unwrap();
assert_eq!(last_alice_key.c25519_ratchet_count, ratchets); assert_eq!(last_alice_key.ratchet_count, ratchets as u64);
assert_eq!(last_bob_key.c25519_ratchet_count, ratchets); assert_eq!(last_bob_key.ratchet_count, ratchets as u64);
assert_eq!(last_alice_key.nistp521_ratchet_count, ratchets); assert_eq!(last_alice_key.c25519_ratchet_count, ratchets as u64);
assert_eq!(last_bob_key.nistp521_ratchet_count, ratchets); 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_eq!(last_alice_key.sidhp751_ratchet_count, last_bob_key.sidhp751_ratchet_count);
assert!(last_alice_key.sidhp751_ratchet_count >= 1); assert!(last_alice_key.sidhp751_ratchet_count >= 1);
} }

View file

@ -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'; 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). /// 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. /// Try to re-key ephemeral keys after this time.
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes 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. /// 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; 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<u8> for EphemeralKeyAgreementAlgorithm {
type Error = ();
#[inline(always)]
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::C25519),
2 => Ok(Self::SIDHP751),
3 => Ok(Self::NISTP521ECDH),
_ => Err(())
}
}
}
/// Compress a packet and return true if compressed. /// 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 /// 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 /// that the data was not compressible. The state of the destination buffer is undefined on a return

View file

@ -38,6 +38,9 @@ pub(crate) struct SymmetricSecret {
/// A key used as input to the ephemeral key ratcheting mechanism. /// A key used as input to the ephemeral key ratcheting mechanism.
pub ephemeral_ratchet_key: Secret<SHA384_HASH_SIZE>, pub ephemeral_ratchet_key: Secret<SHA384_HASH_SIZE>,
/// A key used to verify the state of the ephemeral ratchet.
pub ephemeral_ratchet_state_key: Secret<SHA384_HASH_SIZE>,
/// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers. /// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers.
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>, pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
} }
@ -54,6 +57,7 @@ impl SymmetricSecret {
pub fn new(base_key: Secret<SHA384_HASH_SIZE>) -> SymmetricSecret { pub fn new(base_key: Secret<SHA384_HASH_SIZE>) -> SymmetricSecret {
let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); 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 = 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( 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_K0, 0, 0),
zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 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, key: base_key,
packet_hmac_key: usage_packet_hmac, packet_hmac_key: usage_packet_hmac,
ephemeral_ratchet_key: usage_ephemeral_ratchet, ephemeral_ratchet_key: usage_ephemeral_ratchet,
ephemeral_ratchet_state_key: usage_ephemeral_ratchet_state,
aes_gmac_siv: Pool::new(2, aes_factory), aes_gmac_siv: Pool::new(2, aes_factory),
} }
} }