mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-05-08 14:43:44 +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] {
|
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");
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue