Use a state hash for determining if ephemeral ratchet can advance, and some big perf improvements in SIDH.

This commit is contained in:
Adam Ierymenko 2021-11-17 10:17:23 -05:00
parent be90abdc52
commit b335c631a9
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
6 changed files with 105 additions and 114 deletions

View file

@ -25,7 +25,7 @@ impl SHA512 {
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] { pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap(); 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"); m.update(msg).expect("FATAL: HMAC-SHA512 failed");
let mut h = [0_u8; SHA512_HASH_SIZE]; let mut h = [0_u8; SHA512_HASH_SIZE];
m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed"); m.get_mac(&mut h).expect("FATAL: HMAC-SHA512 failed");

View file

@ -9,7 +9,7 @@
//! This module contains internal curve representation and operations //! This module contains internal curve representation and operations
//! for SIDH, which is not part of the public API. //! 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::field::{PrimeFieldElement, ExtensionFieldElement};
use crate::sidhp751::constants::*; use crate::sidhp751::constants::*;
@ -19,9 +19,7 @@ use subtle::{ConditionallySelectable, Choice};
#[cfg(test)] #[cfg(test)]
use quickcheck::{Gen, Arbitrary}; use quickcheck::{Gen, Arbitrary};
use std::mem::zeroed;
// Macro to assign tuples, as Rust does not allow tuples as lvalue.
macro_rules! assign{ macro_rules! assign{
{($v1:ident, $v2:ident) = $e:expr} => {($v1:ident, $v2:ident) = $e:expr} =>
{ {
@ -36,7 +34,7 @@ macro_rules! assign{
// = 256 // = 256
const CONST_256: ExtensionFieldElement = ExtensionFieldElement { 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]), 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})`. /// A point on the projective line `P^1(F_{p^2})`.
@ -193,8 +191,7 @@ impl Arbitrary for ProjectivePoint {
impl ProjectivePoint { impl ProjectivePoint {
/// Creates a new zero `ProejctivePoint`. /// Creates a new zero `ProejctivePoint`.
pub fn new() -> ProjectivePoint { pub fn new() -> ProjectivePoint {
unsafe { zeroed() } ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
//ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -731,8 +728,7 @@ impl Arbitrary for ProjectivePrimeFieldPoint {
impl ProjectivePrimeFieldPoint { impl ProjectivePrimeFieldPoint {
/// Creates a new zero `ProjectivePrimeFieldPoint`. /// Creates a new zero `ProjectivePrimeFieldPoint`.
pub fn new() -> 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 { pub fn from_affine(x: &PrimeFieldElement) -> ProjectivePrimeFieldPoint {

View file

@ -14,7 +14,6 @@ use crate::sidhp751::fp::*;
use std::fmt::Debug; use std::fmt::Debug;
use std::cmp::{Eq, PartialEq}; use std::cmp::{Eq, PartialEq};
use std::mem::zeroed;
use std::ops::*; use std::ops::*;
use subtle::ConditionallySelectable; use subtle::ConditionallySelectable;
@ -179,18 +178,17 @@ impl Arbitrary for ExtensionFieldElement {
impl ExtensionFieldElement { impl ExtensionFieldElement {
/// Construct a zero `ExtensionFieldElement`. /// Construct a zero `ExtensionFieldElement`.
pub fn zero() -> ExtensionFieldElement { pub fn zero() -> ExtensionFieldElement {
unsafe { zeroed() } ExtensionFieldElement{
//ExtensionFieldElement{ A: Fp751Element::zero(),
// 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::zero(),
// 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]), }
//}
} }
/// Construct a one `ExtensionFieldElement`. /// Construct a one `ExtensionFieldElement`.
pub fn one() -> ExtensionFieldElement { pub fn one() -> ExtensionFieldElement {
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]), 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 { impl PrimeFieldElement {
/// Construct a zero `PrimeFieldElement`. /// Construct a zero `PrimeFieldElement`.
#[inline(always)]
pub fn zero() -> PrimeFieldElement { pub fn zero() -> PrimeFieldElement {
unsafe { zeroed() } PrimeFieldElement{
//PrimeFieldElement{ A: Fp751Element::zero(),
// 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]), }
//}
} }
/// Construct a one `PrimeFieldElement`. /// Construct a one `PrimeFieldElement`.

View file

@ -8,7 +8,7 @@
use crate::random::SecureRandom; use crate::random::SecureRandom;
use std::mem::{size_of, MaybeUninit, zeroed}; use std::mem::size_of;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Neg; use std::ops::Neg;
@ -18,8 +18,6 @@ use quickcheck::{Arbitrary, Gen};
use subtle::{ConditionallySelectable, Choice}; use subtle::{ConditionallySelectable, Choice};
use rand_core::RngCore; use rand_core::RngCore;
// Macro to assign tuples, as Rust does not allow tuples as lvalue.
#[macro_export]
macro_rules! assign{ macro_rules! assign{
{($v1:ident, $v2:expr) = $e:expr} => {($v1:ident, $v2:expr) = $e:expr} =>
{ {
@ -31,7 +29,6 @@ macro_rules! assign{
}; };
} }
// X86 finite field arithmetic
const RADIX: u32 = 32; const RADIX: u32 = 32;
pub const FP751_NUM_WORDS: usize = 24; pub const FP751_NUM_WORDS: usize = 24;
const P751_ZERO_WORDS: usize = 11; 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 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]; 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)] #[inline(always)]
fn mul(multiplier: u32, multiplicant: u32, uv: &mut [u32; 2]) { 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)] #[inline(always)]
@ -320,16 +284,14 @@ pub struct Fp751Element(pub (crate) [u32; FP751_NUM_WORDS]);
pub struct Fp751ElementDist; pub struct Fp751ElementDist;
impl ConditionallySelectable for Fp751Element { impl ConditionallySelectable for Fp751Element {
#[inline(always)]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { 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 { for i in 0..FP751_NUM_WORDS {
bytes.0[i] = u32::conditional_select(&a.0[i], &b.0[i], choice); bytes.0[i] = u32::conditional_select(&a.0[i], &b.0[i], choice);
} }
bytes bytes
} }
#[inline(always)]
fn conditional_assign(&mut self, f: &Self, choice: Choice) { fn conditional_assign(&mut self, f: &Self, choice: Choice) {
let mask = ((choice.unwrap_u8() as i32).neg()) as u32; let mask = ((choice.unwrap_u8() as i32).neg()) as u32;
for i in 0..FP751_NUM_WORDS { for i in 0..FP751_NUM_WORDS {
@ -376,8 +338,7 @@ impl Fp751Element {
/// Construct a new zero `Fp751Element`. /// Construct a new zero `Fp751Element`.
#[inline(always)] #[inline(always)]
pub fn zero() -> Fp751Element { pub fn zero() -> Fp751Element {
unsafe { zeroed() } Fp751Element([0_u32; FP751_NUM_WORDS])
//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])
} }
/// Given an `Fp751Element` in Montgomery form, convert to little-endian bytes. /// Given an `Fp751Element` in Montgomery form, convert to little-endian bytes.
@ -386,10 +347,8 @@ impl Fp751Element {
let mut a = Fp751Element::zero(); let mut a = Fp751Element::zero();
#[allow(non_snake_case)] #[allow(non_snake_case)]
let mut aR = Fp751X2::zero(); 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.0[..FP751_NUM_WORDS].clone_from_slice(&self.0);
//aR = self * &one;
a = aR.reduce(); // = a mod p in [0, 2p) a = aR.reduce(); // = a mod p in [0, 2p)
a = a.strong_reduce(); // = a mod p in [0, p) a = a.strong_reduce(); // = a mod p in [0, p)
@ -399,7 +358,6 @@ impl Fp751Element {
for i in 0..94 { for i in 0..94 {
j = i / 4; j = i / 4;
k = (i % 4) as u32; 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[i as usize] = (a.0[j as usize] >> (8 * k)) as u8;
} }
bytes bytes
@ -439,9 +397,7 @@ impl Fp751X2 {
// Construct a zero `Fp751X2`. // Construct a zero `Fp751X2`.
#[inline(always)] #[inline(always)]
pub fn zero() -> Fp751X2 { pub fn zero() -> Fp751X2 {
unsafe { zeroed() } Fp751X2([0_u32; { 2 * FP751_NUM_WORDS }])
//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])
} }
} }

View file

@ -6,7 +6,7 @@
* https://www.zerotier.com/ * https://www.zerotier.com/
*/ */
use std::sync::atomic::AtomicU32; use std::sync::atomic::{AtomicU32, Ordering};
use std::io::Write; use std::io::Write;
use std::convert::TryInto; use std::convert::TryInto;
@ -19,47 +19,52 @@ use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHS
use zerotier_core_crypto::varint; use zerotier_core_crypto::varint;
use crate::vl1::Address; 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; use crate::vl1::symmetricsecret::SymmetricSecret;
/// An ephemeral secret key negotiated to implement forward secrecy. /// A set of ephemeral secret key pairs. Multiple algorithms are used.
pub struct EphemeralSecret { pub struct EphemeralKeyPairSet {
timestamp_ticks: i64, previous_ratchet_state: Option<[u8; 16]>,
ratchet_count: u64,
c25519: C25519KeyPair, c25519: C25519KeyPair,
p521: P521KeyPair, p521: P521KeyPair,
sidhp751: Option<SIDHEphemeralKeyPair>, sidhp751: Option<SIDHEphemeralKeyPair>,
} }
impl EphemeralSecret { impl EphemeralKeyPairSet {
/// Create a new ephemeral secret key. /// Create a new ephemeral set of secret/public key pairs.
/// ///
/// 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.
/// ///
/// SIDH is much slower than Curve25519 and NIST P-521, so it's only included every /// SIDH is only used the first time since it's slow and its only purpose is to
/// 256 clicks of the ratchet. The point of SIDH is forward secrecy out to the age /// defend against further-future quantum computer attacks.
/// of quantum computing in case someone is warehousing traffic today to analyze pub fn new(local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self {
/// tomorrow. An attacker from 5-15 years from now will not be able to time travel let (sidhp751, previous_ratchet_state) = previous_ephemeral_secret.map_or_else(|| {
/// back in time and steal an ephemeral SIDH secret key with a side channel attack. (Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)), None)
pub fn new(time_ticks: i64, local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { }, |previous_ephemeral_secret| {
let ratchet_count = previous_ephemeral_secret.map_or(0_u64, |previous_ephemeral_secret| previous_ephemeral_secret.next_ratchet_count); (None, Some(previous_ephemeral_secret.ratchet_state.clone()))
EphemeralSecret { });
timestamp_ticks: time_ticks, EphemeralKeyPairSet {
ratchet_count, previous_ratchet_state,
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: if (ratchet_count % 256) == 0 { sidhp751,
Some(SIDHEphemeralKeyPair::generate(local_address, remote_address))
} else {
None
},
} }
} }
/// Create a public version of this ephemeral secret to share with our counterparty. /// 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> { 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); b.push(EphemeralKeyAgreementAlgorithm::C25519 as u8);
let _ = varint::write(&mut b, C25519_PUBLIC_KEY_SIZE as u64); 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 // 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 // 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 // FIPS point of view. Final key must be HKDF(salt, a FIPS-compliant algorithm secret). There is zero
// implication for security. // actual security implication to the order.
b.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH as u8); b.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH as u8);
let _ = varint::write(&mut b, P521_PUBLIC_KEY_SIZE as u64); 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. /// 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]) -> Option<EphemeralSymmetricSecret> {
let mut key = previous_ephemeral_secret.map_or_else(|| { 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() (static_secret.next_ephemeral_ratchet_key.clone(), 0, 0, 0)
}, |previous_ephemeral_secret| { }, |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 algs: Vec<EphemeralKeyAgreementAlgorithm> = Vec::with_capacity(3);
let mut other_public_bytes = other_public_bytes; 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() { while !other_public_bytes.is_empty() {
let cipher = other_public_bytes[0]; let cipher = other_public_bytes[0];
other_public_bytes = &other_public_bytes[1..]; other_public_bytes = &other_public_bytes[1..];
@ -124,6 +151,7 @@ impl EphemeralSecret {
other_public_bytes = &other_public_bytes[C25519_PUBLIC_KEY_SIZE..]; other_public_bytes = &other_public_bytes[C25519_PUBLIC_KEY_SIZE..];
key.0 = SHA384::hmac(&key.0, &c25519_secret.0); key.0 = SHA384::hmac(&key.0, &c25519_secret.0);
algs.push(EphemeralKeyAgreementAlgorithm::C25519); algs.push(EphemeralKeyAgreementAlgorithm::C25519);
c25519_ratchet_count += 1;
}, },
Ok(EphemeralKeyAgreementAlgorithm::SIDHP751) => { Ok(EphemeralKeyAgreementAlgorithm::SIDHP751) => {
@ -149,6 +177,7 @@ impl EphemeralSecret {
}.map(|sidh_secret| { }.map(|sidh_secret| {
key.0 = SHA384::hmac(&key.0, &sidh_secret.0); key.0 = SHA384::hmac(&key.0, &sidh_secret.0);
algs.push(EphemeralKeyAgreementAlgorithm::SIDHP751); algs.push(EphemeralKeyAgreementAlgorithm::SIDHP751);
sidh_ratchet_count += 1;
}); });
other_public_bytes = &other_public_bytes[(SIDH_P751_PUBLIC_KEY_SIZE + 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); key.0 = SHA384::hmac(&key.0, &p521_key.unwrap().0);
algs.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH); algs.push(EphemeralKeyAgreementAlgorithm::NistP521ECDH);
nistp521_ratchet_count += 1;
}, },
Err(_) => { Err(_) => {
@ -183,10 +213,13 @@ impl EphemeralSecret {
return if !algs.is_empty() { return if !algs.is_empty() {
Some(EphemeralSymmetricSecret { Some(EphemeralSymmetricSecret {
secret: SymmetricSecret::new(key), secret: SymmetricSecret::new(key),
ratchet_state: SHA384::hash(&key.0)[0..16].try_into().unwrap(),
agreement_algorithms: algs, agreement_algorithms: algs,
agreement_timestamp_ticks: time_ticks, rekey_time: time_ticks + EPHEMERAL_SECRET_REKEY_AFTER_TIME,
local_secret_timestamp_ticks: self.timestamp_ticks, expire_time: time_ticks + EPHEMERAL_SECRET_REJECT_AFTER_TIME,
next_ratchet_count: self.ratchet_count + 1, c25519_ratchet_count,
sidhp751_ratchet_count,
nistp512_ratchet_count,
encrypt_uses: AtomicU32::new(0), encrypt_uses: AtomicU32::new(0),
decrypt_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 { pub struct EphemeralSymmetricSecret {
secret: SymmetricSecret, secret: SymmetricSecret,
ratchet_state: [u8; 16],
agreement_algorithms: Vec<EphemeralKeyAgreementAlgorithm>, agreement_algorithms: Vec<EphemeralKeyAgreementAlgorithm>,
agreement_timestamp_ticks: i64, rekey_time: i64,
local_secret_timestamp_ticks: i64, expire_time: i64,
next_ratchet_count: u64, c25519_ratchet_count: u64,
sidhp751_ratchet_count: u64,
nistp512_ratchet_count: u64,
encrypt_uses: AtomicU32, encrypt_uses: AtomicU32,
decrypt_uses: AtomicU32, decrypt_uses: AtomicU32,
} }
@ -209,19 +246,23 @@ pub struct EphemeralSymmetricSecret {
impl EphemeralSymmetricSecret { impl EphemeralSymmetricSecret {
#[inline(always)] #[inline(always)]
pub fn use_secret_to_encrypt(&self) -> &SymmetricSecret { 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 &self.secret
} }
#[inline(always)] #[inline(always)]
pub fn use_secret_to_decrypt(&self) -> &SymmetricSecret { 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 &self.secret
} }
pub fn is_fips_compliant(&self) -> bool { pub fn is_fips_compliant(&self) -> bool {
self.agreement_algorithms.last().map_or(false, |alg| alg.is_fips_compliant()) 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)] #[derive(Copy, Clone)]
@ -264,7 +305,7 @@ impl SIDHEphemeralKeyPair {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::vl1::ephemeral::EphemeralSecret; use crate::vl1::ephemeral::EphemeralKeyPairSet;
use crate::vl1::Address; use crate::vl1::Address;
use crate::vl1::symmetricsecret::SymmetricSecret; use crate::vl1::symmetricsecret::SymmetricSecret;
use zerotier_core_crypto::secret::Secret; use zerotier_core_crypto::secret::Secret;
@ -272,8 +313,8 @@ mod tests {
#[test] #[test]
fn ephemeral_agreement() { fn ephemeral_agreement() {
let static_secret = SymmetricSecret::new(Secret([1_u8; 48])); 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 alice = EphemeralKeyPairSet::new(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 bob = EphemeralKeyPairSet::new(Address::from_u64(0xbeefdead00).unwrap(), Address::from_u64(0xdeadbeef00).unwrap(), None);
let alice_public_bytes = alice.public_bytes(); let alice_public_bytes = alice.public_bytes();
let bob_public_bytes = bob.public_bytes(); let bob_public_bytes = bob.public_bytes();
let alice_key = alice.agree(2, &static_secret, None, bob_public_bytes.as_slice()).unwrap(); let alice_key = alice.agree(2, &static_secret, None, bob_public_bytes.as_slice()).unwrap();

View file

@ -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'; pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET: 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 = 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. /// 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. /// Ephemeral secret reject after time.
pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2; pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2;
/// Ephemeral secret reject after uses. /// 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. /// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5; pub const ADDRESS_SIZE: usize = 5;