mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Use a state hash for determining if ephemeral ratchet can advance, and some big perf improvements in SIDH.
This commit is contained in:
parent
be90abdc52
commit
b335c631a9
6 changed files with 105 additions and 114 deletions
|
@ -25,7 +25,7 @@ impl SHA512 {
|
|||
|
||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
|
||||
m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key");;
|
||||
m.set_key(key).expect("FATAL: invalid HMAC-SHA512 key");
|
||||
m.update(msg).expect("FATAL: HMAC-SHA512 failed");
|
||||
let mut h = [0_u8; SHA512_HASH_SIZE];
|
||||
m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed");
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//! This module contains internal curve representation and operations
|
||||
//! for SIDH, which is not part of the public API.
|
||||
|
||||
use crate::sidhp751::fp::Fp751Element;
|
||||
use crate::sidhp751::fp::{Fp751Element, FP751_NUM_WORDS};
|
||||
use crate::sidhp751::field::{PrimeFieldElement, ExtensionFieldElement};
|
||||
use crate::sidhp751::constants::*;
|
||||
|
||||
|
@ -19,9 +19,7 @@ use subtle::{ConditionallySelectable, Choice};
|
|||
|
||||
#[cfg(test)]
|
||||
use quickcheck::{Gen, Arbitrary};
|
||||
use std::mem::zeroed;
|
||||
|
||||
// Macro to assign tuples, as Rust does not allow tuples as lvalue.
|
||||
macro_rules! assign{
|
||||
{($v1:ident, $v2:ident) = $e:expr} =>
|
||||
{
|
||||
|
@ -36,7 +34,7 @@ macro_rules! assign{
|
|||
// = 256
|
||||
const CONST_256: ExtensionFieldElement = ExtensionFieldElement {
|
||||
A: Fp751Element([0x249ad67, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7300000, 0x9973da8b, 0x73815496, 0x46718c7f, 0x856657c1, 0xe363a697, 0x461860e4,0xbba838cd, 0xf9fd6510,0x06993c0c, 0x4e1a3c3f, 0xef5b75c7, 0x55ab]),
|
||||
B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
|
||||
B: Fp751Element([0_u32; FP751_NUM_WORDS])
|
||||
};
|
||||
|
||||
/// A point on the projective line `P^1(F_{p^2})`.
|
||||
|
@ -193,8 +191,7 @@ impl Arbitrary for ProjectivePoint {
|
|||
impl ProjectivePoint {
|
||||
/// Creates a new zero `ProejctivePoint`.
|
||||
pub fn new() -> ProjectivePoint {
|
||||
unsafe { zeroed() }
|
||||
//ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
|
||||
ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -731,8 +728,7 @@ impl Arbitrary for ProjectivePrimeFieldPoint {
|
|||
impl ProjectivePrimeFieldPoint {
|
||||
/// Creates a new zero `ProjectivePrimeFieldPoint`.
|
||||
pub fn new() -> ProjectivePrimeFieldPoint {
|
||||
unsafe { zeroed() }
|
||||
//ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() }
|
||||
ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() }
|
||||
}
|
||||
|
||||
pub fn from_affine(x: &PrimeFieldElement) -> ProjectivePrimeFieldPoint {
|
||||
|
|
|
@ -14,7 +14,6 @@ use crate::sidhp751::fp::*;
|
|||
use std::fmt::Debug;
|
||||
|
||||
use std::cmp::{Eq, PartialEq};
|
||||
use std::mem::zeroed;
|
||||
use std::ops::*;
|
||||
|
||||
use subtle::ConditionallySelectable;
|
||||
|
@ -179,18 +178,17 @@ impl Arbitrary for ExtensionFieldElement {
|
|||
impl ExtensionFieldElement {
|
||||
/// Construct a zero `ExtensionFieldElement`.
|
||||
pub fn zero() -> ExtensionFieldElement {
|
||||
unsafe { zeroed() }
|
||||
//ExtensionFieldElement{
|
||||
// A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
|
||||
// B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
|
||||
//}
|
||||
ExtensionFieldElement{
|
||||
A: Fp751Element::zero(),
|
||||
B: Fp751Element::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a one `ExtensionFieldElement`.
|
||||
pub fn one() -> ExtensionFieldElement {
|
||||
ExtensionFieldElement{
|
||||
A: Fp751Element([0x249ad, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x83100000, 0x375c6c66, 0x5527b1e4, 0x3f4f24d0, 0x697797bf, 0xac5c4e2e, 0xc89db7b2, 0xd2076956, 0x4ca4b439, 0x7512c7e9, 0x10f7926c, 0x24bce5e2, 0x2d5b]),
|
||||
B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
|
||||
B: Fp751Element([0_u32; FP751_NUM_WORDS]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,11 +404,11 @@ impl Arbitrary for PrimeFieldElement {
|
|||
|
||||
impl PrimeFieldElement {
|
||||
/// Construct a zero `PrimeFieldElement`.
|
||||
#[inline(always)]
|
||||
pub fn zero() -> PrimeFieldElement {
|
||||
unsafe { zeroed() }
|
||||
//PrimeFieldElement{
|
||||
// A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
|
||||
//}
|
||||
PrimeFieldElement{
|
||||
A: Fp751Element::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a one `PrimeFieldElement`.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
use crate::random::SecureRandom;
|
||||
|
||||
use std::mem::{size_of, MaybeUninit, zeroed};
|
||||
use std::mem::size_of;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Neg;
|
||||
|
||||
|
@ -18,8 +18,6 @@ use quickcheck::{Arbitrary, Gen};
|
|||
use subtle::{ConditionallySelectable, Choice};
|
||||
use rand_core::RngCore;
|
||||
|
||||
// Macro to assign tuples, as Rust does not allow tuples as lvalue.
|
||||
#[macro_export]
|
||||
macro_rules! assign{
|
||||
{($v1:ident, $v2:expr) = $e:expr} =>
|
||||
{
|
||||
|
@ -31,7 +29,6 @@ macro_rules! assign{
|
|||
};
|
||||
}
|
||||
|
||||
// X86 finite field arithmetic
|
||||
const RADIX: u32 = 32;
|
||||
pub const FP751_NUM_WORDS: usize = 24;
|
||||
const P751_ZERO_WORDS: usize = 11;
|
||||
|
@ -40,44 +37,11 @@ const P751: [u32; FP751_NUM_WORDS] = [4294967295, 4294967295, 4294967295, 429496
|
|||
const P751P1: [u32; FP751_NUM_WORDS] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4004511744, 1241020584, 3823933061, 335006838, 3667237658, 3605784694, 139368551, 1555191624, 2237838596, 2545605734, 236097695, 3577870108, 28645];
|
||||
const P751X2: [u32; FP751_NUM_WORDS] = [4294967294, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 3714056191, 2482041169, 3352898826, 670013677, 3039508020, 2916602093, 278737103, 3110383248, 180709896, 796244173, 472195391, 2860772920, 57291];
|
||||
|
||||
fn digit_x_digit(a: u32, b: u32, c: &mut [u32; 2]) {
|
||||
#[allow(non_upper_case_globals)]
|
||||
const sizeof_u32: u32 = size_of::<u32>() as u32;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const mask_low: u32 = <u32>::MAX >> (sizeof_u32 * 4);
|
||||
#[allow(non_upper_case_globals)]
|
||||
const mask_high: u32 = <u32>::MAX << (sizeof_u32 * 4);
|
||||
|
||||
let al = a & mask_low;
|
||||
let ah = a >> (sizeof_u32 * 4);
|
||||
let bl = b & mask_low;
|
||||
let bh = b >> (sizeof_u32 * 4);
|
||||
|
||||
let albl = al * bl;
|
||||
let albh = al * bh;
|
||||
let ahbl = ah * bl;
|
||||
let ahbh = ah * bh;
|
||||
let c0 = albl & mask_low;
|
||||
|
||||
let mut res1 = albl >> (sizeof_u32 * 4);
|
||||
let mut res2 = ahbl & mask_low;
|
||||
let mut res3 = albh & mask_low;
|
||||
let mut temp = res1 + res2 + res3;
|
||||
let mut carry = temp >> (sizeof_u32 * 4);
|
||||
c[0] = c0 ^ (temp << (sizeof_u32 * 4));
|
||||
|
||||
res1 = ahbl >> (sizeof_u32 * 4);
|
||||
res2 = albh >> (sizeof_u32 * 4);
|
||||
res3 = ahbh & mask_low;
|
||||
temp = res1 + res2 + res3 + carry;
|
||||
let c1 = temp & mask_low;
|
||||
carry = temp & mask_high;
|
||||
c[1] = c1 ^ ((ahbh & mask_high) + carry);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(multiplier: u32, multiplicant: u32, uv: &mut [u32; 2]) {
|
||||
digit_x_digit(multiplier, multiplicant, uv);
|
||||
let p = (multiplier as u64) * (multiplicant as u64);
|
||||
uv[0] = p as u32;
|
||||
uv[1] = (p >> 32) as u32;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -320,16 +284,14 @@ pub struct Fp751Element(pub (crate) [u32; FP751_NUM_WORDS]);
|
|||
pub struct Fp751ElementDist;
|
||||
|
||||
impl ConditionallySelectable for Fp751Element {
|
||||
#[inline(always)]
|
||||
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||
let mut bytes = unsafe { MaybeUninit::<Fp751Element>::uninit().assume_init() };
|
||||
let mut bytes = Fp751Element::zero();
|
||||
for i in 0..FP751_NUM_WORDS {
|
||||
bytes.0[i] = u32::conditional_select(&a.0[i], &b.0[i], choice);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn conditional_assign(&mut self, f: &Self, choice: Choice) {
|
||||
let mask = ((choice.unwrap_u8() as i32).neg()) as u32;
|
||||
for i in 0..FP751_NUM_WORDS {
|
||||
|
@ -376,8 +338,7 @@ impl Fp751Element {
|
|||
/// Construct a new zero `Fp751Element`.
|
||||
#[inline(always)]
|
||||
pub fn zero() -> Fp751Element {
|
||||
unsafe { zeroed() }
|
||||
//Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
|
||||
Fp751Element([0_u32; FP751_NUM_WORDS])
|
||||
}
|
||||
|
||||
/// Given an `Fp751Element` in Montgomery form, convert to little-endian bytes.
|
||||
|
@ -386,10 +347,8 @@ impl Fp751Element {
|
|||
let mut a = Fp751Element::zero();
|
||||
#[allow(non_snake_case)]
|
||||
let mut aR = Fp751X2::zero();
|
||||
//let one = Fp751Element([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
aR.0[..FP751_NUM_WORDS].clone_from_slice(&self.0);
|
||||
//aR = self * &one;
|
||||
a = aR.reduce(); // = a mod p in [0, 2p)
|
||||
a = a.strong_reduce(); // = a mod p in [0, p)
|
||||
|
||||
|
@ -399,7 +358,6 @@ impl Fp751Element {
|
|||
for i in 0..94 {
|
||||
j = i / 4;
|
||||
k = (i % 4) as u32;
|
||||
// Rust indexes are of type usize.
|
||||
bytes[i as usize] = (a.0[j as usize] >> (8 * k)) as u8;
|
||||
}
|
||||
bytes
|
||||
|
@ -439,9 +397,7 @@ impl Fp751X2 {
|
|||
// Construct a zero `Fp751X2`.
|
||||
#[inline(always)]
|
||||
pub fn zero() -> Fp751X2 {
|
||||
unsafe { zeroed() }
|
||||
//Fp751X2([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
// 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
|
||||
Fp751X2([0_u32; { 2 * FP751_NUM_WORDS }])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::io::Write;
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -19,47 +19,52 @@ use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHS
|
|||
use zerotier_core_crypto::varint;
|
||||
|
||||
use crate::vl1::Address;
|
||||
use crate::vl1::protocol::EphemeralKeyAgreementAlgorithm;
|
||||
use crate::vl1::protocol::{EphemeralKeyAgreementAlgorithm, EPHEMERAL_SECRET_USE_SIDH_EVERY_N_RATCHETS, EPHEMERAL_SECRET_REKEY_AFTER_TIME, EPHEMERAL_SECRET_REKEY_AFTER_USES, EPHEMERAL_SECRET_REJECT_AFTER_TIME};
|
||||
use crate::vl1::symmetricsecret::SymmetricSecret;
|
||||
|
||||
/// An ephemeral secret key negotiated to implement forward secrecy.
|
||||
pub struct EphemeralSecret {
|
||||
timestamp_ticks: i64,
|
||||
ratchet_count: u64,
|
||||
/// A set of ephemeral secret key pairs. Multiple algorithms are used.
|
||||
pub struct EphemeralKeyPairSet {
|
||||
previous_ratchet_state: Option<[u8; 16]>,
|
||||
c25519: C25519KeyPair,
|
||||
p521: P521KeyPair,
|
||||
sidhp751: Option<SIDHEphemeralKeyPair>,
|
||||
}
|
||||
|
||||
impl EphemeralSecret {
|
||||
/// Create a new ephemeral secret key.
|
||||
impl EphemeralKeyPairSet {
|
||||
/// Create a new ephemeral set of secret/public key pairs.
|
||||
///
|
||||
/// This contains key pairs for the asymmetric key agreement algorithms used and a
|
||||
/// timestamp used to enforce TTL.
|
||||
///
|
||||
/// SIDH is much slower than Curve25519 and NIST P-521, so it's only included every
|
||||
/// 256 clicks of the ratchet. The point of SIDH is forward secrecy out to the age
|
||||
/// of quantum computing in case someone is warehousing traffic today to analyze
|
||||
/// tomorrow. An attacker from 5-15 years from now will not be able to time travel
|
||||
/// back in time and steal an ephemeral SIDH secret key with a side channel attack.
|
||||
pub fn new(time_ticks: i64, local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self {
|
||||
let ratchet_count = previous_ephemeral_secret.map_or(0_u64, |previous_ephemeral_secret| previous_ephemeral_secret.next_ratchet_count);
|
||||
EphemeralSecret {
|
||||
timestamp_ticks: time_ticks,
|
||||
ratchet_count,
|
||||
/// SIDH is only used the first time since it's slow and its only purpose is to
|
||||
/// defend against further-future quantum computer attacks.
|
||||
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(|| {
|
||||
(Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)), None)
|
||||
}, |previous_ephemeral_secret| {
|
||||
(None, Some(previous_ephemeral_secret.ratchet_state.clone()))
|
||||
});
|
||||
EphemeralKeyPairSet {
|
||||
previous_ratchet_state,
|
||||
c25519: C25519KeyPair::generate(true),
|
||||
p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"),
|
||||
sidhp751: if (ratchet_count % 256) == 0 {
|
||||
Some(SIDHEphemeralKeyPair::generate(local_address, remote_address))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
sidhp751,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a public version of this ephemeral secret to share with our counterparty.
|
||||
///
|
||||
/// Note that the public key bundle is NOT self-signed or otherwise self-authenticating. It must
|
||||
/// be transmitted over an authenticated channel.
|
||||
pub fn public_bytes(&self) -> Vec<u8> {
|
||||
let mut b: Vec<u8> = Vec::with_capacity(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);
|
||||
|
||||
self.previous_ratchet_state.as_ref().map_or_else(|| {
|
||||
b.push(0); // no flags
|
||||
}, |previous_ratchet_state| {
|
||||
b.push(1); // flag: previous ephemeral secret hash included
|
||||
let _ = b.write_all(previous_ratchet_state);
|
||||
});
|
||||
|
||||
b.push(EphemeralKeyAgreementAlgorithm::C25519 as u8);
|
||||
let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64);
|
||||
|
@ -78,8 +83,8 @@ impl EphemeralSecret {
|
|||
|
||||
// FIPS note: any FIPS compliant ciphers must be last or the exchange will not be FIPS compliant. That's
|
||||
// because we chain/ratchet using KHDF and non-FIPS ciphers are considered "salt" inputs for HKDF from a
|
||||
// FIPS point of view. Final key must be HKDF(salt, FIPS-compliant algorithm secret). Order has no actual
|
||||
// implication for security.
|
||||
// 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);
|
||||
let _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64);
|
||||
|
@ -97,14 +102,36 @@ impl EphemeralSecret {
|
|||
///
|
||||
/// 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> {
|
||||
let mut key = previous_ephemeral_secret.map_or_else(|| {
|
||||
static_secret.next_ephemeral_ratchet_key.clone()
|
||||
let (mut key, mut c25519_ratchet_count, mut sidhp751_ratchet_count, mut nistp521_ratchet_count) = previous_ephemeral_secret.map_or_else(|| {
|
||||
(static_secret.next_ephemeral_ratchet_key.clone(), 0, 0, 0)
|
||||
}, |previous_ephemeral_secret| {
|
||||
Secret(SHA384::hmac(&static_secret.next_ephemeral_ratchet_key.0, &previous_ephemeral_secret.secret.next_ephemeral_ratchet_key.0))
|
||||
(
|
||||
Secret(SHA384::hmac(&static_secret.next_ephemeral_ratchet_key.0, &previous_ephemeral_secret.secret.next_ephemeral_ratchet_key.0)),
|
||||
previous_ephemeral_secret.c25519_ratchet_count,
|
||||
previous_ephemeral_secret.sidhp751_ratchet_count,
|
||||
previous_ephemeral_secret.nistp512_ratchet_count
|
||||
)
|
||||
});
|
||||
|
||||
let mut algs: Vec<EphemeralKeyAgreementAlgorithm> = Vec::with_capacity(3);
|
||||
let mut other_public_bytes = other_public_bytes;
|
||||
|
||||
// Make sure the state of the ratchet matches on both ends. Otherwise it must restart.
|
||||
if other_public_bytes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if (other_public_bytes[0] & 1) == 0 {
|
||||
if previous_ephemeral_secret.is_some() {
|
||||
return None;
|
||||
}
|
||||
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].ne(&previous_ephemeral_secret.ratchet_state)) {
|
||||
return None;
|
||||
}
|
||||
other_public_bytes = &other_public_bytes[17..];
|
||||
}
|
||||
|
||||
while !other_public_bytes.is_empty() {
|
||||
let cipher = other_public_bytes[0];
|
||||
other_public_bytes = &other_public_bytes[1..];
|
||||
|
@ -124,6 +151,7 @@ impl EphemeralSecret {
|
|||
other_public_bytes = &other_public_bytes[C25519_PUBLIC_KEY_SIZE..];
|
||||
key.0 = SHA384::hmac(&key.0, &c25519_secret.0);
|
||||
algs.push(EphemeralKeyAgreementAlgorithm::C25519);
|
||||
c25519_ratchet_count += 1;
|
||||
},
|
||||
|
||||
Ok(EphemeralKeyAgreementAlgorithm::SIDHP751) => {
|
||||
|
@ -149,6 +177,7 @@ impl EphemeralSecret {
|
|||
}.map(|sidh_secret| {
|
||||
key.0 = SHA384::hmac(&key.0, &sidh_secret.0);
|
||||
algs.push(EphemeralKeyAgreementAlgorithm::SIDHP751);
|
||||
sidh_ratchet_count += 1;
|
||||
});
|
||||
other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 1)..];
|
||||
},
|
||||
|
@ -168,6 +197,7 @@ impl EphemeralSecret {
|
|||
}
|
||||
key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0);
|
||||
algs.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH);
|
||||
nistp521_ratchet_count += 1;
|
||||
},
|
||||
|
||||
Err(_) => {
|
||||
|
@ -183,10 +213,13 @@ impl EphemeralSecret {
|
|||
return if !algs.is_empty() {
|
||||
Some(EphemeralSymmetricSecret {
|
||||
secret: SymmetricSecret::new(key),
|
||||
ratchet_state: SHA384::hash(&key.0)[0..16].try_into().unwrap(),
|
||||
agreement_algorithms: algs,
|
||||
agreement_timestamp_ticks: time_ticks,
|
||||
local_secret_timestamp_ticks: self.timestamp_ticks,
|
||||
next_ratchet_count: self.ratchet_count + 1,
|
||||
rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME,
|
||||
expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME,
|
||||
c25519_ratchet_count,
|
||||
sidhp751_ratchet_count,
|
||||
nistp512_ratchet_count,
|
||||
encrypt_uses: AtomicU32::new(0),
|
||||
decrypt_uses: AtomicU32::new(0)
|
||||
})
|
||||
|
@ -196,12 +229,16 @@ impl EphemeralSecret {
|
|||
}
|
||||
}
|
||||
|
||||
/// Symmetric secret representing a step in the ephemeral keying ratchet.
|
||||
pub struct EphemeralSymmetricSecret {
|
||||
secret: SymmetricSecret,
|
||||
ratchet_state: [u8; 16],
|
||||
agreement_algorithms: Vec<EphemeralKeyAgreementAlgorithm>,
|
||||
agreement_timestamp_ticks: i64,
|
||||
local_secret_timestamp_ticks: i64,
|
||||
next_ratchet_count: u64,
|
||||
rekey_time: i64,
|
||||
expire_time: i64,
|
||||
c25519_ratchet_count: u64,
|
||||
sidhp751_ratchet_count: u64,
|
||||
nistp512_ratchet_count: u64,
|
||||
encrypt_uses: AtomicU32,
|
||||
decrypt_uses: AtomicU32,
|
||||
}
|
||||
|
@ -209,19 +246,23 @@ pub struct EphemeralSymmetricSecret {
|
|||
impl EphemeralSymmetricSecret {
|
||||
#[inline(always)]
|
||||
pub fn use_secret_to_encrypt(&self) -> &SymmetricSecret {
|
||||
let _ = self.encrypt_uses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let _ = self.encrypt_uses.fetch_add(1, Ordering::Relaxed);
|
||||
&self.secret
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn use_secret_to_decrypt(&self) -> &SymmetricSecret {
|
||||
let _ = self.decrypt_uses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let _ = self.decrypt_uses.fetch_add(1, Ordering::Relaxed);
|
||||
&self.secret
|
||||
}
|
||||
|
||||
pub fn is_fips_compliant(&self) -> bool {
|
||||
self.agreement_algorithms.last().map_or(false, |alg| alg.is_fips_compliant())
|
||||
}
|
||||
|
||||
pub fn should_rekey(&self, time_ticks: i64) -> bool {
|
||||
time_ticks >= self.rekey_time || self.encrypt_uses.load(Ordering::Relaxed).max(self.decrypt_uses.load(Ordering::Relaxed)) >= EPHEMERAL_SECRET_REKEY_AFTER_USES
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -264,7 +305,7 @@ impl SIDHEphemeralKeyPair {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vl1::ephemeral::EphemeralSecret;
|
||||
use crate::vl1::ephemeral::EphemeralKeyPairSet;
|
||||
use crate::vl1::Address;
|
||||
use crate::vl1::symmetricsecret::SymmetricSecret;
|
||||
use zerotier_core_crypto::secret::Secret;
|
||||
|
@ -272,8 +313,8 @@ mod tests {
|
|||
#[test]
|
||||
fn ephemeral_agreement() {
|
||||
let static_secret = SymmetricSecret::new(Secret([1_u8; 48]));
|
||||
let alice = EphemeralSecret::new(1, Address::from_u64(0xdeadbeef00).unwrap(), Address::from_u64(0xbeefdead00).unwrap(), None);
|
||||
let bob = EphemeralSecret::new(1, Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap(), None);
|
||||
let alice = EphemeralKeyPairSet::new(Address::from_u64(0xdeadbeef00).unwrap(), Address::from_u64(0xbeefdead00).unwrap(), None);
|
||||
let bob = EphemeralKeyPairSet::new(Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap(), None);
|
||||
let alice_public_bytes = alice.public_bytes();
|
||||
let bob_public_bytes = bob.public_bytes();
|
||||
let alice_key = alice.agree(2, &static_secret, None, bob_public_bytes.as_slice()).unwrap();
|
||||
|
|
|
@ -51,16 +51,16 @@ pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
|
|||
pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET: u8 = b'E';
|
||||
|
||||
/// Try to re-key ephemeral keys after this time.
|
||||
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 1000 * 60 * 60; // 1 hour
|
||||
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes
|
||||
|
||||
/// Maximum number of times to use an ephemeral secret before trying to replace it.
|
||||
pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST security limit
|
||||
pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST/FIPS security bound of 2^31
|
||||
|
||||
/// Ephemeral secret reject after time.
|
||||
pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2;
|
||||
|
||||
/// Ephemeral secret reject after uses.
|
||||
pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST security limit
|
||||
pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST/FIPS security bound
|
||||
|
||||
/// Length of an address in bytes.
|
||||
pub const ADDRESS_SIZE: usize = 5;
|
||||
|
|
Loading…
Add table
Reference in a new issue