An absolute ton of work on Identity (simpler code) and session... some of which is getting redone but committing this.

This commit is contained in:
Adam Ierymenko 2022-08-02 13:44:52 -04:00
parent a9c2aeb415
commit e41992bc3c
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
17 changed files with 2232 additions and 1240 deletions

99
ZSSP.md Normal file
View file

@ -0,0 +1,99 @@
# ZeroTier Secure Session Protocol
ZeroTier V2 uses a secure session protocol providing forward security, identity anonymity, and opaqueness on the wire. To this it also adds obfuscation to make ZeroTier indistinguishable from other protocols on the wire by a naive observer who doesn't know participant identities.
## Design Goals and Principles
- Strong forward secrecy
- Privacy (e.g. identities are not visible in the clear)
- Indistinguishability on the wire
- Simplicity (both state machine and implementation)
- Highest practical performance on common hardware (x86_64, ARM64, network accelerators)
- NIST, FIPS, and NSA CNSA (formerly Suite B) compliant
- Session init is trivially distinguishable from legacy ZeroTier traffic (by the correct recipient node at least) to allow efficient legacy support
- General enough to be useful outside ZeroTier (perhaps with minor modifications)
## Packet Format
All packets have the following basic layout:
-- begin common header
[1] KKK_UTTT: K == key index, _ == reserved, U == first field is unencrypted, T == packet type
[4] 32-bit counter (little-endian)
[6] 48-bit recipient session ID (all zero in INIT)
-- end common header (11 bytes)
[...] unencrypted payload (packet type specific, usually absent)
-- begin encrypted payload
[...] encrypted payload
-- end encrypted payload
[...] cipher-specific authentication tag (16 or 48 bytes)
## Packet Types and Payload
0: DATA Session data
1: INIT Alice: start new session, send A's key(s)
2: INIT_ACK Bob: confirm INIT, send B's key(s)
3: DATAGRAM Sessionless datagram
4: REKEY New ephemeral key(s)
5: REKEY_ACK Acknowledge new ephemeral key(s)
6-7: reserved
DATA carries an arbitrary payload.
All other types carry a payload consisting of pair(s) of single byte field types followed by field data. Field order is not significant except for INIT and DATAGRAM, which place on ephemeral key in an unencrypted payload section before encryption starts. The unencrypted payload section is empty for all other packet types.
## Symmetric Cipher Modes and Key Management
Encryption and authentication are achieved using one of two available cipher modes: AES-256-CTR+HMAC-SHA384 or AES-256-GCM. The packet type determines which mode must be used. DATA packets use AES-256-GCM while all other packet types use AES-256-CTR+HMAC-SHA384. These two modes differ only by which MAC is used, since GCM is just CTR+GMAC.
HMAC authentication is computed over the entire packet after encryption but before obfuscation (see below). GCM authentication covers the encrypted payload only (there is no unencrypted payload in DATA), but since the header forms most of the nonce/IV it is also effectively included (without having to be added as AAD).
HMAC-based key derivation is used to derive sub-keys for each mode to avoid using the same key for multiple purposes or algorithms. The KDF used in ZeroTier is as follows:
HMAC-SHA384(first 48 bytes of master key, [0, 0, 0, 0, 'Z', 'T', label, 0, 0, 0, 0, 0x01, 0x80])
This construction is based on [section 5.1 page 12 of NIST SP 800-108](https://csrc.nist.gov/publications/detail/sp/800-108/final) with the label prefixed by the ASCII characters "ZT" and the counter always being set to zero.
The following single byte labels are used:
'c' - AES-256-CTR (first 32 bytes of derived key)
'm' - HMAC-SHA384 (48 bytes, used in combination with AES-CTR but derive a different key)
'g' - AES-256-GCM (first 32 bytes of derived key)
Both CTR and GCM take a 12-byte nonce that is constructed from the 11-byte common header and a single byte indicating whether this is "alice" (INIT sender) or "bob" (responder) to prevent nonce reuse when alice and bob share the same session key.
Nonce bytes: TCCCCSSSSSSR
T: Packed KKCCTTTT byte from header
C: Counter (little-endian)
S: Recipient session ID
R: Role: 0 for Alice (INIT sender), 1 for Bob (INIT recipient)
The counter may be initialized to any value at session start and MUST be incremented for each packet sent.
Re-keying is initiated (by either side) after 1,073,741,824 (2^30) encryptions or one hour, whichever comes first. Communication may continue until re-keying is successful unless the key usage counter reaches 2^32. This is a hard error and causes abandonment of the session. Re-keying at 1/4 this number should allow plenty of time for re-key success before this limit is reached. See below for more details on re-keying.
## Obfuscation
The unencrypted header and any unencrypted payload (such as the first ephemeral setup key) are obfuscated.
Obfuscation is performed by applying AES-256-CTR using the last 12 bytes of the packet (part of the HMAC or GCM tag) as a nonce and the first 32 bytes of the recipient node's static identity fingerprint as a key.
Deobfuscation therefore requires knowledge of the full identity of the recipient. An observer who doesn't know this identity only sees noise and can't distinguish ZeroTier traffic from any other encrypted traffic.
Obfuscation isn't essential. The protocol would function normally without it and its security guarantees around data privacy and authentication would not be impacted. The purpose of obfuscation is to harden the protocol against tracking and de-anonymization of users through bulk traffic analysis and to provide a very low CPU overhead way for nodes to filter out unwanted packets and maintain "radio silence."
(Even if someone did manage to guess (with a one in 2^53 chance) a valid obfuscated INIT header, they would still receive no response since the rest of the packet wouldn't be correct. They'd just manage to use a few more CPU cycles on the recipient for this one guess to be further examined and discarded when payload decoding or full HMAC authentication failed.)
## Session Setup
## Re-Keying
## Sessionless Datagrams
DATAGRAM packets are not associated with a session and do not initiate one. They are used when nodes need to exchange sporadic control plane messages and the overhead of establishing a session is not justified.
DATAGRAM packets look just like INIT but contain only the sender's identity, a data payload, and an optional auth data payload. The ephemeral key is used to derive a setup key as in INIT but these keys are only used once and then discarded.
## Credits
This protocol design is based on the [Noise protocol framework](http://noiseprotocol.org) by Trevor Perrin and [Wireguard](https://www.wireguard.com) by Jason A. Donenfeld. Most of the credit goes to them and whomever else was involved in these designs for the way this is constructed.

View file

@ -1,6 +1,8 @@
AES-GMAC-SIV
======
**DEPRECATION NOTICE: this mode has been deprecated in ZeroTier V2 via its secure session protocol. It is still included for use with older nodes but the V2 session protocol removes the advantage of a SIV mode and allows us to use more standard and faster simple AEAD modes like GCM.**
This is a Rust implementation of AES-GMAC-SIV, a FIPS-compliant SIV AEAD construction for AES-256. An interface to simple AES-CTR mode is also included.
It's implemented in terms of OpenSSL (libcrypto), macOS/iOS CryptoCore, or (soon) WinCrypt, with the latter two automatically selected at compile time on those platforms.

View file

@ -1,4 +1,4 @@
max_width = 180
max_width = 200
use_small_heuristics = "Max"
tab_spaces = 4
newline_style = "Unix"

View file

@ -6,19 +6,19 @@ mod fruit_flavored {
use std::os::raw::{c_int, c_void};
use std::ptr::{null, null_mut};
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCModeECB: i32 = 1;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCModeCTR: i32 = 4;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCModeGCM: i32 = 11;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCEncrypt: i32 = 0;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCDecrypt: i32 = 1;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCAlgorithmAES: i32 = 0;
#[allow(non_upper_case_globals)]
#[allow(non_upper_case_globals, unused)]
const kCCOptionECBMode: i32 = 2;
extern "C" {
@ -37,7 +37,7 @@ mod fruit_flavored {
cryyptor_ref: *mut *mut c_void,
) -> i32;
fn CCCryptorUpdate(cryptor_ref: *mut c_void, data_in: *const c_void, data_in_len: usize, data_out: *mut c_void, data_out_len: usize, data_out_written: *mut usize) -> i32;
fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
//fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32;
fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32;
@ -64,14 +64,8 @@ mod fruit_flavored {
panic!("AES supports 128, 192, or 256 bits keys");
}
let mut aes: Self = std::mem::zeroed();
let enc = CCCryptorCreateWithMode(kCCEncrypt, kCCModeECB, kCCAlgorithmAES, 0, null(), k.as_ptr().cast(), k.len(), null(), 0, 0, kCCOptionECBMode, &mut aes.0);
if enc != 0 {
panic!("CCCryptorCreateWithMode for ECB encrypt mode returned {}", enc);
}
let dec = CCCryptorCreateWithMode(kCCDecrypt, kCCModeECB, kCCAlgorithmAES, 0, null(), k.as_ptr().cast(), k.len(), null(), 0, 0, kCCOptionECBMode, &mut aes.1);
if dec != 0 {
panic!("CCCryptorCreateWithMode for ECB decrypt mode returned {}", dec);
}
assert_eq!(CCCryptorCreateWithMode(kCCEncrypt, kCCModeECB, kCCAlgorithmAES, 0, null(), k.as_ptr().cast(), k.len(), null(), 0, 0, kCCOptionECBMode, &mut aes.0), 0);
assert_eq!(CCCryptorCreateWithMode(kCCDecrypt, kCCModeECB, kCCAlgorithmAES, 0, null(), k.as_ptr().cast(), k.len(), null(), 0, 0, kCCOptionECBMode, &mut aes.1), 0);
aes
}
}
@ -118,74 +112,6 @@ mod fruit_flavored {
unsafe impl Send for Aes {}
unsafe impl Sync for Aes {}
pub struct AesCtr(*mut c_void);
impl Drop for AesCtr {
#[inline(always)]
fn drop(&mut self) {
unsafe { CCCryptorRelease(self.0) };
}
}
impl AesCtr {
/// Construct a new AES-CTR cipher.
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
pub fn new(k: &[u8]) -> Self {
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
panic!("AES supports 128, 192, or 256 bits keys");
}
unsafe {
let mut ptr: *mut c_void = null_mut();
let result = CCCryptorCreateWithMode(kCCEncrypt, kCCModeCTR, kCCAlgorithmAES, 0, [0_u64; 2].as_ptr().cast(), k.as_ptr().cast(), k.len(), null(), 0, 0, 0, &mut ptr);
if result != 0 {
panic!("CCCryptorCreateWithMode for CTR mode returned {}", result);
}
AesCtr(ptr)
}
}
/// Initialize AES-CTR for encryption or decryption with the given IV.
/// If it's already been used, this also resets the cipher. There is no separate reset.
pub fn init(&mut self, iv: &[u8]) {
unsafe {
if iv.len() == 16 {
if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 {
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
}
} else if iv.len() < 16 {
let mut iv2 = [0_u8; 16];
iv2[0..iv.len()].copy_from_slice(iv);
if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 {
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
}
} else {
panic!("CTR IV must be less than or equal to 16 bytes in length");
}
}
}
/// Encrypt or decrypt (same operation with CTR mode)
#[inline(always)]
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
unsafe {
assert!(output.len() >= input.len());
let mut data_out_written: usize = 0;
CCCryptorUpdate(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast(), output.len(), &mut data_out_written);
}
}
/// Encrypt or decrypt in place (same operation with CTR mode)
#[inline(always)]
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
unsafe {
let mut data_out_written: usize = 0;
CCCryptorUpdate(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast(), data.len(), &mut data_out_written);
}
}
}
unsafe impl Send for AesCtr {}
pub struct AesGcm(*mut c_void);
impl Drop for AesGcm {
@ -202,33 +128,40 @@ mod fruit_flavored {
}
unsafe {
let mut ptr: *mut c_void = null_mut();
let result = CCCryptorCreateWithMode(
if encrypt { kCCEncrypt } else { kCCDecrypt },
kCCModeGCM,
kCCAlgorithmAES,
0,
[0_u64; 2].as_ptr().cast(),
k.as_ptr().cast(),
k.len(),
null(),
0,
0,
0,
&mut ptr,
assert_eq!(
CCCryptorCreateWithMode(
if encrypt { kCCEncrypt } else { kCCDecrypt },
kCCModeGCM,
kCCAlgorithmAES,
0,
[0_u64; 2].as_ptr().cast(),
k.as_ptr().cast(),
k.len(),
null(),
0,
0,
0,
&mut ptr,
),
0
);
if result != 0 {
panic!("CCCryptorCreateWithMode for GCM mode returned {}", result);
}
AesGcm(ptr)
}
}
#[inline(always)]
pub fn init(&mut self, iv: &[u8]) {
assert_eq!(iv.len(), 12);
unsafe {
assert_eq!(CCCryptorGCMReset(self.0), 0);
CCCryptorGCMSetIV(self.0, iv.as_ptr().cast(), 12);
if iv.len() == 16 {
assert_eq!(CCCryptorGCMSetIV(self.0, iv.as_ptr().cast(), 16), 0);
} else if iv.len() < 16 {
let mut tmp = [0_u8; 16];
tmp[..iv.len()].copy_from_slice(iv);
assert_eq!(CCCryptorGCMSetIV(self.0, tmp.as_ptr().cast(), 16), 0);
} else {
panic!();
}
}
}
@ -241,10 +174,11 @@ mod fruit_flavored {
#[inline(always)]
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
assert_eq!(input.len(), output.len());
unsafe {
assert_eq!(input.len(), output.len());
let mut data_out_written: usize = 0;
CCCryptorUpdate(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast(), output.len(), &mut data_out_written);
assert_eq!(data_out_written, input.len());
}
}
@ -310,8 +244,7 @@ mod openssl {
impl Aes {
#[inline(always)]
pub fn new(k: &[u8]) -> Self {
let (mut c, mut d) =
(Crypter::new(aes_ecb_by_key_size(k.len()), Mode::Encrypt, k, None).unwrap(), Crypter::new(aes_ecb_by_key_size(k.len()), Mode::Decrypt, k, None).unwrap());
let (mut c, mut d) = (Crypter::new(aes_ecb_by_key_size(k.len()), Mode::Encrypt, k, None).unwrap(), Crypter::new(aes_ecb_by_key_size(k.len()), Mode::Decrypt, k, None).unwrap());
c.pad(false);
d.pad(false);
Self(UnsafeCell::new(c), UnsafeCell::new(d))
@ -361,49 +294,6 @@ mod openssl {
unsafe impl Send for Aes {}
unsafe impl Sync for Aes {}
pub struct AesCtr(Secret<32>, usize, Option<Crypter>);
impl AesCtr {
/// Construct a new AES-CTR cipher.
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
#[inline(always)]
pub fn new(k: &[u8]) -> Self {
let mut s: Secret<32> = Secret::default();
match k.len() {
16 | 24 | 32 => {
s.0[..k.len()].copy_from_slice(k);
Self(s, k.len(), None)
}
_ => {
panic!("AES supports 128, 192, or 256 bits keys");
}
}
}
/// Initialize AES-CTR for encryption or decryption with the given IV.
/// If it's already been used, this also resets the cipher. There is no separate reset.
#[inline(always)]
pub fn init(&mut self, iv: &[u8]) {
let mut c = Crypter::new(aes_ctr_by_key_size(self.1), Mode::Encrypt, &self.0 .0[..self.1], Some(iv)).unwrap();
c.pad(false);
let _ = self.2.replace(c);
}
/// Encrypt or decrypt (same operation with CTR mode)
#[inline(always)]
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
let _ = self.2.as_mut().unwrap().update(input, output);
}
/// Encrypt or decrypt in place (same operation with CTR mode)
#[inline(always)]
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
let _ = self.2.as_mut().unwrap().update(unsafe { &*std::slice::from_raw_parts(data.as_ptr(), data.len()) }, data);
}
}
unsafe impl Send for AesCtr {}
pub struct AesGcm(Secret<32>, usize, Option<Crypter>, bool);
impl AesGcm {
@ -467,7 +357,7 @@ mod openssl {
}
#[cfg(target_os = "macos")]
pub use fruit_flavored::{Aes, AesCtr, AesGcm};
pub use fruit_flavored::{Aes, AesGcm};
#[cfg(not(target_os = "macos"))]
pub use openssl::{Aes, AesCtr, AesGcm};
pub use openssl::{Aes, AesGcm};

View file

@ -1,5 +1,6 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use crate::hash::{hmac_sha384, hmac_sha512};
use crate::secret::Secret;
/*
@ -13,10 +14,10 @@ use crate::secret::Secret;
/// Derive a key using HMAC-SHA384 and a single byte label, ZeroTier variant with "ZT" preface.
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8) -> Secret<48> {
Secret(crate::hash::hmac_sha384(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x01, 0x80]))
Secret(hmac_sha384(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x01, 0x80]))
}
/// Derive a key using HMAC-SHA512 and a single byte label, ZeroTier variant with "ZT" preface.
pub fn zt_kbkdf_hmac_sha512(key: &[u8], label: u8) -> Secret<64> {
Secret(crate::hash::hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00]))
Secret(hmac_sha512(key, &[0, 0, 0, 0, b'Z', b'T', label, 0, 0, 0, 0, 0x02, 0x00]))
}

View file

@ -65,7 +65,8 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
);
while !plaintext.is_empty() {
let (mut x0, mut x1, mut x2, mut x3, mut x4, mut x5, mut x6, mut x7, mut x8, mut x9, mut x10, mut x11, mut x12, mut x13, mut x14, mut x15) = (j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15);
let (mut x0, mut x1, mut x2, mut x3, mut x4, mut x5, mut x6, mut x7, mut x8, mut x9, mut x10, mut x11, mut x12, mut x13, mut x14, mut x15) =
(j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15);
for _ in 0..(ROUNDS / 2) {
x4 ^= x0.wrapping_add(x12).rotate_left(7);
@ -123,7 +124,7 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
j9 = j9.wrapping_add((j8 == 0) as u32);
if plaintext.len() >= 64 {
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))]
{
// Slightly faster keystream XOR for little-endian platforms with unaligned load/store.
unsafe {
@ -148,7 +149,24 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
{
// Portable keystream XOR with alignment-safe access and native to little-endian conversion.
let keystream = [x0.to_le(), x1.to_le(), x2.to_le(), x3.to_le(), x4.to_le(), x5.to_le(), x6.to_le(), x7.to_le(), x8.to_le(), x9.to_le(), x10.to_le(), x11.to_le(), x12.to_le(), x13.to_le(), x14.to_le(), x15.to_le()];
let keystream = [
x0.to_le(),
x1.to_le(),
x2.to_le(),
x3.to_le(),
x4.to_le(),
x5.to_le(),
x6.to_le(),
x7.to_le(),
x8.to_le(),
x9.to_le(),
x10.to_le(),
x11.to_le(),
x12.to_le(),
x13.to_le(),
x14.to_le(),
x15.to_le(),
];
for i in 0..64 {
ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::<u8>().add(i) };
}
@ -157,7 +175,24 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
plaintext = &plaintext[64..];
ciphertext = &mut ciphertext[64..];
} else {
let keystream = [x0.to_le(), x1.to_le(), x2.to_le(), x3.to_le(), x4.to_le(), x5.to_le(), x6.to_le(), x7.to_le(), x8.to_le(), x9.to_le(), x10.to_le(), x11.to_le(), x12.to_le(), x13.to_le(), x14.to_le(), x15.to_le()];
let keystream = [
x0.to_le(),
x1.to_le(),
x2.to_le(),
x3.to_le(),
x4.to_le(),
x5.to_le(),
x6.to_le(),
x7.to_le(),
x8.to_le(),
x9.to_le(),
x10.to_le(),
x11.to_le(),
x12.to_le(),
x13.to_le(),
x14.to_le(),
x15.to_le(),
];
for i in 0..plaintext.len() {
ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::<u8>().add(i) };
}
@ -179,11 +214,15 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
mod tests {
use crate::salsa::*;
const SALSA_20_TV0_KEY: [u8; 32] = [0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c];
const SALSA_20_TV0_KEY: [u8; 32] = [
0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, 0x84, 0xd7, 0x2a, 0x7d, 0xd0,
0x23, 0x76, 0xc9, 0x1c,
];
const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9];
const SALSA_20_TV0_KS: [u8; 64] = [
0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59,
0x5c, 0x77, 0xdd, 0x59, 0x54, 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f,
0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, 0xf5, 0x4a, 0x10, 0xb8, 0x10,
0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, 0x59, 0x54, 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7,
0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f,
];
#[test]

View file

@ -1,6 +1,6 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use std::io::Write;
use std::io::{Read, Write};
use std::mem::{size_of, MaybeUninit};
use crate::util::pool::PoolFactory;
@ -26,6 +26,9 @@ impl<const L: usize> Default for Buffer<L> {
}
}
// Setting attributes this way causes the 'overflow' branches to be treated as unlikely by LLVM.
#[inline(never)]
#[cold]
fn overflow_err() -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "buffer overflow")
}
@ -98,12 +101,12 @@ impl<const L: usize> Buffer<L> {
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
&self.1[0..self.0]
&self.1[..self.0]
}
#[inline(always)]
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.1[0..self.0]
&mut self.1[..self.0]
}
#[inline(always)]
@ -134,6 +137,15 @@ impl<const L: usize> Buffer<L> {
}
}
#[inline(always)]
pub fn as_byte_range(&self, start: usize, end: usize) -> std::io::Result<&[u8]> {
if end <= self.0 {
Ok(&self.1[start..end])
} else {
Err(overflow_err())
}
}
pub fn clear(&mut self) {
self.1[0..self.0].fill(0);
self.0 = 0;
@ -322,6 +334,19 @@ impl<const L: usize> Buffer<L> {
}
}
#[inline(always)]
pub fn append_u64_le(&mut self, i: u64) -> std::io::Result<()> {
let ptr = self.0;
let end = ptr + 8;
if end <= L {
self.0 = end;
unsafe { self.write_obj_internal(ptr, i.to_le()) };
Ok(())
} else {
Err(overflow_err())
}
}
#[inline(always)]
pub fn bytes_fixed_at<const S: usize>(&self, ptr: usize) -> std::io::Result<&[u8; S]> {
if (ptr + S) <= self.0 {
@ -504,6 +529,19 @@ impl<const L: usize> Buffer<L> {
Err(overflow_err())
}
}
#[inline(always)]
pub fn read_u64_le(&self, cursor: &mut usize) -> std::io::Result<u64> {
let ptr = *cursor;
let end = ptr + 8;
debug_assert!(end <= L);
if end <= self.0 {
*cursor = end;
Ok(u64::from_le(unsafe { self.read_obj_internal(ptr) }))
} else {
Err(overflow_err())
}
}
}
impl<const L: usize> Write for Buffer<L> {
@ -554,6 +592,24 @@ impl<const L: usize> From<&[u8; L]> for Buffer<L> {
}
}
/// Implements std::io::Read for a buffer and a cursor.
pub struct BufferReader<'a, 'b, const L: usize>(&'a Buffer<L>, &'b mut usize);
impl<'a, 'b, const L: usize> BufferReader<'a, 'b, L> {
#[inline(always)]
pub fn new(b: &'a Buffer<L>, cursor: &'b mut usize) -> Self {
Self(b, cursor)
}
}
impl<'a, 'b, const L: usize> Read for BufferReader<'a, 'b, L> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
buf.copy_from_slice(self.0.read_bytes(buf.len(), self.1)?);
Ok(buf.len())
}
}
pub struct PooledBufferFactory<const L: usize>;
impl<const L: usize> PooledBufferFactory<L> {

View file

@ -9,8 +9,6 @@ pub(crate) mod pool;
pub use zerotier_core_crypto::hex;
pub use zerotier_core_crypto::varint;
pub(crate) const ZEROES: [u8; 64] = [0_u8; 64];
/// A value for ticks that indicates that something never happened, and is thus very long before zero ticks.
pub(crate) const NEVER_HAPPENED_TICKS: i64 = -2147483648;
@ -34,9 +32,31 @@ macro_rules! debug_event {
#[allow(unused_imports)]
pub(crate) use debug_event;
/// Obtain a reference to a sub-array within an existing byte array.
/// Obtain a view into a byte array cast as another byte array.
#[inline(always)]
pub(crate) fn byte_array_range<const A: usize, const START: usize, const LEN: usize>(a: &[u8; A]) -> &[u8; LEN] {
assert!((START + LEN) <= A);
unsafe { &*a.as_ptr().add(START).cast::<[u8; LEN]>() }
}
/// View a flat (Copy) object as a byte array.
#[inline(always)]
pub(crate) fn flat_object_as_bytes<T: Copy>(t: &T) -> &[u8] {
unsafe { &*std::ptr::slice_from_raw_parts((t as *const T).cast::<u8>(), std::mem::size_of::<T>()) }
}
/// Trait that annotates a type as being alignment neutral, such as a packed struct of all bytes and byte arrays.
pub(crate) unsafe trait AlignmentNeutral: Copy {}
/// View a byte array as a flat (Copy) object.
/// To be safe this can only be used with annotated alignment-neutral structs.
#[inline(always)]
pub(crate) fn bytes_as_flat_object<T: Copy + AlignmentNeutral>(b: &[u8]) -> &T {
assert!(b.len() >= std::mem::size_of::<T>());
unsafe { &*b.as_ptr().cast() }
}
/// Include this in a branch to hint the compiler that it's unlikely.
#[inline(never)]
#[cold]
pub(crate) extern "C" fn unlikely_branch() {}

View file

@ -3,7 +3,7 @@
use std::cmp::Ordering;
use std::convert::TryInto;
use std::hash::{Hash, Hasher};
use std::io::Write;
use std::io::{Read, Write};
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -16,9 +16,7 @@ use zerotier_core_crypto::secret::Secret;
use zerotier_core_crypto::x25519::*;
use crate::error::{InvalidFormatError, InvalidParameterError};
use crate::util::buffer::Buffer;
use crate::util::marshalable::Marshalable;
use crate::util::ZEROES;
use crate::util::{bytes_as_flat_object, flat_object_as_bytes, AlignmentNeutral};
use crate::vl1::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_FINGERPRINT_SIZE, IDENTITY_POW_THRESHOLD};
use crate::vl1::Address;
@ -41,7 +39,7 @@ pub struct IdentityP384Public {
/// Secret keys associated with an identity.
#[derive(Clone)]
pub struct IdentitySecret {
pub c25519: C25519KeyPair,
pub x25519: C25519KeyPair,
pub ed25519: Ed25519KeyPair,
pub p384: Option<IdentityP384Secret>,
}
@ -59,7 +57,7 @@ pub struct IdentitySecret {
#[derive(Clone)]
pub struct Identity {
pub address: Address,
pub c25519: [u8; C25519_PUBLIC_KEY_SIZE],
pub x25519: [u8; C25519_PUBLIC_KEY_SIZE],
pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE],
pub p384: Option<IdentityP384Public>,
pub secret: Option<IdentitySecret>,
@ -94,7 +92,7 @@ fn zt_address_derivation_work_function(digest: &mut [u8; 64]) {
assert!(!genmem.is_null());
let mut salsa: Salsa<20> = Salsa::new(&digest[..32], &digest[32..40]);
salsa.crypt(&ZEROES, &mut *genmem.cast::<[u8; 64]>());
salsa.crypt(&[0_u8; 64], &mut *genmem.cast::<[u8; 64]>());
let mut k = 0;
while k < (ADDRESS_DERIVATION_HASH_MEMORY_SIZE - 64) {
let i = k + 64;
@ -131,14 +129,21 @@ fn zt_address_derivation_work_function(digest: &mut [u8; 64]) {
}
impl Identity {
/// Curve25519 and Ed25519
pub const ALGORITHM_X25519: u8 = 0x01;
/// Length of an x25519-only public identity in byte array form.
pub const BYTE_LENGTH_X25519_PUBLIC: usize = ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + 1 + 1 + 2;
/// NIST P-384 ECDH and ECDSA
pub const ALGORITHM_EC_NIST_P384: u8 = 0x02;
/// Length of an x25519-only secret identity in byte array form.
pub const BYTE_LENGTH_X25519_SECRET: usize = Self::BYTE_LENGTH_X25519_PUBLIC + C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE;
/// Bit mask to include all algorithms.
pub const ALGORITHM_ALL: u8 = 0xff;
/// Length of a new dual-key public identity in byte array form.
pub const BYTE_LENGTH_X25519P384_PUBLIC: usize = Self::BYTE_LENGTH_X25519_PUBLIC + 1 + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE;
/// Length of a new dual-key secret identity in byte array form.
pub const BYTE_LENGTH_X25519P384_SECRET: usize = Self::BYTE_LENGTH_X25519P384_PUBLIC + C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE;
const ALGORITHM_X25519: u8 = 0x01;
const ALGORITHM_EC_NIST_P384: u8 = 0x02;
const FLAG_INCLUDES_SECRET: u8 = 0x80;
/// Generate a new identity.
pub fn generate() -> Self {
@ -147,13 +152,13 @@ impl Identity {
let ed25519 = Ed25519KeyPair::generate();
let ed25519_pub = ed25519.public_bytes();
let address;
let mut c25519;
let mut c25519_pub;
let mut x25519;
let mut x25519_pub;
loop {
c25519 = C25519KeyPair::generate();
c25519_pub = c25519.public_bytes();
x25519 = C25519KeyPair::generate();
x25519_pub = x25519.public_bytes();
sha.update(&c25519_pub);
sha.update(&x25519_pub);
sha.update(&ed25519_pub);
let mut digest = sha.finish();
zt_address_derivation_work_function(&mut digest);
@ -170,10 +175,10 @@ impl Identity {
}
let mut id = Self {
address,
c25519: c25519_pub,
x25519: x25519_pub,
ed25519: ed25519_pub,
p384: None,
secret: Some(IdentitySecret { c25519, ed25519, p384: None }),
secret: Some(IdentitySecret { x25519, ed25519, p384: None }),
fingerprint: [0_u8; IDENTITY_FINGERPRINT_SIZE], // replaced in upgrade()
};
@ -203,9 +208,10 @@ impl Identity {
let p384_ecdh = P384KeyPair::generate();
let p384_ecdsa = P384KeyPair::generate();
let mut self_sign_buf: Vec<u8> = Vec::with_capacity(ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + 4);
let mut self_sign_buf: Vec<u8> =
Vec::with_capacity(ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + 4);
let _ = self_sign_buf.write_all(&self.address.to_bytes());
let _ = self_sign_buf.write_all(&self.c25519);
let _ = self_sign_buf.write_all(&self.x25519);
let _ = self_sign_buf.write_all(&self.ed25519);
self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384);
let _ = self_sign_buf.write_all(p384_ecdh.public_key_bytes());
@ -240,20 +246,11 @@ impl Identity {
pub fn clone_without_secret(&self) -> Identity {
Self {
address: self.address,
c25519: self.c25519.clone(),
ed25519: self.ed25519.clone(),
x25519: self.x25519,
ed25519: self.ed25519,
p384: self.p384.clone(),
secret: None,
fingerprint: self.fingerprint.clone(),
}
}
/// Get a bit mask of algorithms present in this identity.
pub fn algorithms(&self) -> u8 {
if self.p384.is_some() {
Self::ALGORITHM_X25519 | Self::ALGORITHM_EC_NIST_P384
} else {
Self::ALGORITHM_X25519
fingerprint: self.fingerprint,
}
}
@ -264,7 +261,7 @@ impl Identity {
if let Some(p384) = self.p384.as_ref() {
let mut self_sign_buf: Vec<u8> = Vec::with_capacity(ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE);
let _ = self_sign_buf.write_all(&self.address.to_bytes());
let _ = self_sign_buf.write_all(&self.c25519);
let _ = self_sign_buf.write_all(&self.x25519);
let _ = self_sign_buf.write_all(&self.ed25519);
self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384);
let _ = self_sign_buf.write_all(p384.ecdh.as_bytes());
@ -283,7 +280,7 @@ impl Identity {
// NOTE: fingerprint is always computed on generation or deserialize so no need to check.
let mut sha = SHA512::new();
sha.update(&self.c25519);
sha.update(&self.x25519);
sha.update(&self.ed25519);
let mut digest = sha.finish();
zt_address_derivation_work_function(&mut digest);
@ -295,12 +292,12 @@ impl Identity {
///
/// An error can occur if this identity does not hold its secret portion or if either key is invalid.
///
/// If both sides have NIST P-384 keys then key agreement is performed using both Curve25519 and
/// NIST P-384 and the result is HMAC-SHA512(Curve25519 secret, NIST P-384 secret). This is FIPS
/// compliant since the Curve25519 secret is treated as a "salt" in HKDF.
/// For new identities with P-384 keys a hybrid agreement is performed using both X25519 and NIST P-384 ECDH.
/// The final key is derived as HMAC(x25519 secret, p-384 secret) to yield a FIPS-compliant key agreement with
/// the X25519 secret being used as a "salt" as far as FIPS is concerned.
pub fn agree(&self, other: &Identity) -> Option<Secret<64>> {
if let Some(secret) = self.secret.as_ref() {
let c25519_secret: Secret<64> = Secret(SHA512::hash(&secret.c25519.agree(&other.c25519).0));
let c25519_secret: Secret<64> = Secret(SHA512::hash(&secret.x25519.agree(&other.x25519).0));
// FIPS note: FIPS-compliant exchange algorithms must be the last algorithms in any HKDF chain
// for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered
@ -358,106 +355,274 @@ impl Identity {
return false;
}
pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> {
let mut b: Buffer<{ Self::MAX_MARSHAL_SIZE }> = Buffer::new();
assert!(self.marshal_with_options(&mut b, include_algorithms, include_private).is_ok());
b
pub fn to_public_bytes(&self) -> IdentityBytes {
if let Some(p384) = self.p384.as_ref() {
IdentityBytes::X25519P384Public(
flat_object_as_bytes(&packed::V1 {
v0: packed::V0 {
address: self.address.to_bytes(),
key_type: 0,
x25519: self.x25519,
ed25519: self.ed25519,
secret_length: 0,
reserved: 0x03,
ext_len: ((Self::BYTE_LENGTH_X25519P384_PUBLIC - Self::BYTE_LENGTH_X25519_PUBLIC) as u16).to_be_bytes(),
},
key_type_flags: Self::ALGORITHM_EC_NIST_P384,
ecdh: p384.ecdh.as_bytes().clone(),
ecdsa: p384.ecdsa.as_bytes().clone(),
ecdsa_self_signature: p384.ecdsa_self_signature,
ed25519_self_signature: p384.ed25519_self_signature,
})
.try_into()
.unwrap(),
)
} else {
IdentityBytes::X25519Public(
flat_object_as_bytes(&packed::V0 {
address: self.address.to_bytes(),
key_type: 0,
x25519: self.x25519,
ed25519: self.ed25519,
secret_length: 0,
reserved: 0x03,
ext_len: [0; 2],
})
.try_into()
.unwrap(),
)
}
}
pub fn marshal_with_options<const BL: usize>(&self, buf: &mut Buffer<BL>, include_algorithms: u8, include_private: bool) -> std::io::Result<()> {
let algorithms = self.algorithms() & include_algorithms;
let secret = self.secret.as_ref();
buf.append_bytes_fixed(&self.address.to_bytes())?;
if (algorithms & Self::ALGORITHM_X25519) != 0 {
buf.append_u8(0x00)?; // 0x00 is used for X25519 for backward compatibility with v0 identities
buf.append_bytes_fixed(&self.c25519)?;
buf.append_bytes_fixed(&self.ed25519)?;
if include_private && secret.is_some() {
let secret = secret.unwrap();
buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?;
buf.append_bytes_fixed(&secret.c25519.secret_bytes().0)?;
buf.append_bytes_fixed(&secret.ed25519.secret_bytes().0)?;
pub fn to_secret_bytes(&self) -> Option<IdentityBytes> {
self.secret.as_ref().map(|s| {
if let Some(p384) = s.p384.as_ref() {
IdentityBytes::X25519P384Secret(
flat_object_as_bytes(&packed::V1S {
v0s: packed::V0S {
address: self.address.to_bytes(),
key_type: 0,
x25519: self.x25519,
ed25519: self.ed25519,
secret_length: (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8,
x25519_secret: s.x25519.secret_bytes().0.clone(),
ed25519_secret: s.ed25519.secret_bytes().0.clone(),
reserved: 0x03,
ext_len: ((Self::BYTE_LENGTH_X25519P384_SECRET - Self::BYTE_LENGTH_X25519_SECRET) as u16).to_be_bytes(),
},
key_type_flags: Self::ALGORITHM_EC_NIST_P384 | Self::FLAG_INCLUDES_SECRET,
ecdh: p384.ecdh.public_key_bytes().clone(),
ecdsa: p384.ecdsa.public_key_bytes().clone(),
ecdsa_self_signature: self.p384.as_ref().unwrap().ecdsa_self_signature,
ed25519_self_signature: self.p384.as_ref().unwrap().ed25519_self_signature,
ecdh_secret: p384.ecdh.secret_key_bytes().0.clone(),
ecdsa_secret: p384.ecdsa.secret_key_bytes().0.clone(),
})
.try_into()
.unwrap(),
)
} else {
buf.append_u8(0)?;
IdentityBytes::X25519Secret(
flat_object_as_bytes(&packed::V0S {
address: self.address.to_bytes(),
key_type: 0,
x25519: self.x25519,
ed25519: self.ed25519,
secret_length: (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8,
x25519_secret: s.x25519.secret_bytes().0.clone(),
ed25519_secret: s.ed25519.secret_bytes().0.clone(),
reserved: 0x03,
ext_len: [0; 2],
})
.try_into()
.unwrap(),
)
}
}
/*
* LEGACY:
*
* The prefix of 0x03 is for backward compatibility. Older nodes will interpret this as
* an empty unidentified InetAddress object and will skip the number of bytes following it.
*
* For future compatibility the size field here will allow this to be extended, something
* that should have been in the protocol from the beginning.
*/
buf.append_u8(0x03)?;
let remaining_data_size_field_at = buf.len();
buf.append_padding(0, 2)?;
if (algorithms & Self::ALGORITHM_EC_NIST_P384) != 0 && self.p384.is_some() {
let p384 = self.p384.as_ref().unwrap();
let p384s = if include_private { secret.clone().map_or(None, |s| s.p384.as_ref()) } else { None };
buf.append_u8(Self::ALGORITHM_EC_NIST_P384)?;
buf.append_varint(if p384s.is_some() {
((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + (P384_SECRET_KEY_SIZE * 2)) as u64
} else {
((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u64
})?;
buf.append_bytes_fixed(p384.ecdh.as_bytes())?;
buf.append_bytes_fixed(p384.ecdsa.as_bytes())?;
buf.append_bytes_fixed(&p384.ecdsa_self_signature)?;
buf.append_bytes_fixed(&p384.ed25519_self_signature)?;
if let Some(p384s) = p384s {
buf.append_bytes_fixed(&p384s.ecdh.secret_key_bytes().0)?;
buf.append_bytes_fixed(&p384s.ecdsa.secret_key_bytes().0)?;
}
}
// Fill in the remaining data field earmarked above.
*buf.bytes_fixed_mut_at(remaining_data_size_field_at).unwrap() = (((buf.len() - remaining_data_size_field_at) - 2) as u16).to_be_bytes();
Ok(())
})
}
/// Marshal this identity as a string.
/// Convert a byte respresentation into an identity.
///
/// The include_algorithms bitmap controls which algorithms will be included, provided we have them.
/// If include_private is true private keys will be included, again if we have them.
pub fn to_string_with_options(&self, include_algorithms: u8, include_private: bool) -> String {
let include_p384 = self.p384.is_some() && ((include_algorithms & Self::ALGORITHM_EC_NIST_P384) != 0);
/// WARNING: this performs basic sanity checking but does NOT perform a full validation of address derivation or self-signatures.
pub fn from_bytes(b: &IdentityBytes) -> Option<Self> {
match b {
IdentityBytes::X25519Public(b) => {
let b: &packed::V0 = bytes_as_flat_object(b);
if b.key_type == 0 && b.secret_length == 0 && b.reserved == 0x03 && u16::from_be_bytes(b.ext_len) == 0 {
Some(Self {
address: Address::from_bytes_fixed(&b.address)?,
x25519: b.x25519,
ed25519: b.ed25519,
p384: None,
secret: None,
fingerprint: {
let mut sha = SHA384::new();
sha.update(&b.address);
sha.update(&b.x25519);
sha.update(&b.ed25519);
sha.finish()
},
})
} else {
None
}
}
IdentityBytes::X25519Secret(b) => {
let b: &packed::V0S = bytes_as_flat_object(b);
if b.key_type == 0 && b.secret_length == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 && b.reserved == 0x03 && u16::from_be_bytes(b.ext_len) == 0 {
Some(Self {
address: Address::from_bytes_fixed(&b.address)?,
x25519: b.x25519,
ed25519: b.ed25519,
p384: None,
secret: Some(IdentitySecret {
x25519: C25519KeyPair::from_bytes(&b.x25519, &b.x25519_secret)?,
ed25519: Ed25519KeyPair::from_bytes(&b.ed25519, &b.ed25519_secret)?,
p384: None,
}),
fingerprint: {
let mut sha = SHA384::new();
sha.update(&b.address);
sha.update(&b.x25519);
sha.update(&b.ed25519);
sha.finish()
},
})
} else {
None
}
}
IdentityBytes::X25519P384Public(b) => {
let b: &packed::V1 = bytes_as_flat_object(b);
if b.v0.key_type == 0
&& b.v0.secret_length == 0
&& b.v0.reserved == 0x03
&& u16::from_be_bytes(b.v0.ext_len) == (Self::BYTE_LENGTH_X25519P384_PUBLIC - Self::BYTE_LENGTH_X25519_PUBLIC) as u16
&& b.key_type_flags == Self::ALGORITHM_EC_NIST_P384
{
Some(Self {
address: Address::from_bytes_fixed(&b.v0.address)?,
x25519: b.v0.x25519,
ed25519: b.v0.ed25519,
p384: Some(IdentityP384Public {
ecdh: P384PublicKey::from_bytes(&b.ecdh)?,
ecdsa: P384PublicKey::from_bytes(&b.ecdsa)?,
ecdsa_self_signature: b.ecdsa_self_signature,
ed25519_self_signature: b.ed25519_self_signature,
}),
secret: None,
fingerprint: {
let mut sha = SHA384::new();
sha.update(&b.v0.address);
sha.update(&b.v0.x25519);
sha.update(&b.v0.ed25519);
sha.update(&[Self::ALGORITHM_EC_NIST_P384]);
sha.update(&b.ecdh);
sha.update(&b.ecdsa);
sha.finish()
},
})
} else {
None
}
}
IdentityBytes::X25519P384Secret(b) => {
let b: &packed::V1S = bytes_as_flat_object(b);
if b.v0s.key_type == 0
&& b.v0s.secret_length == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8
&& b.v0s.reserved == 0x03
&& u16::from_be_bytes(b.v0s.ext_len) == (Self::BYTE_LENGTH_X25519P384_SECRET - Self::BYTE_LENGTH_X25519_SECRET) as u16
&& b.key_type_flags == (Self::ALGORITHM_EC_NIST_P384 | Self::FLAG_INCLUDES_SECRET)
{
Some(Self {
address: Address::from_bytes_fixed(&b.v0s.address)?,
x25519: b.v0s.x25519,
ed25519: b.v0s.ed25519,
p384: Some(IdentityP384Public {
ecdh: P384PublicKey::from_bytes(&b.ecdh)?,
ecdsa: P384PublicKey::from_bytes(&b.ecdsa)?,
ecdsa_self_signature: b.ecdsa_self_signature,
ed25519_self_signature: b.ed25519_self_signature,
}),
secret: Some(IdentitySecret {
x25519: C25519KeyPair::from_bytes(&b.v0s.x25519, &b.v0s.x25519_secret)?,
ed25519: Ed25519KeyPair::from_bytes(&b.v0s.ed25519, &b.v0s.ed25519_secret)?,
p384: Some(IdentityP384Secret {
ecdh: P384KeyPair::from_bytes(&b.ecdh, &b.ecdh_secret)?,
ecdsa: P384KeyPair::from_bytes(&b.ecdsa, &b.ecdsa_secret)?,
}),
}),
fingerprint: {
let mut sha = SHA384::new();
sha.update(&b.v0s.address);
sha.update(&b.v0s.x25519);
sha.update(&b.v0s.ed25519);
sha.update(&[Self::ALGORITHM_EC_NIST_P384]);
sha.update(&b.ecdh);
sha.update(&b.ecdsa);
sha.finish()
},
})
} else {
None
}
}
}
}
let mut s = String::with_capacity(Self::MAX_MARSHAL_SIZE * 2);
/// Read an identity from a reader, inferring its total length from the stream.
pub fn read_bytes<R: Read>(r: &mut R) -> std::io::Result<Self> {
let mut buf = [0_u8; 512];
r.read_exact(&mut buf[..Self::BYTE_LENGTH_X25519_PUBLIC])?;
let x25519_public = bytes_as_flat_object::<packed::V0>(&buf);
let ext_len = u16::from_be_bytes(x25519_public.ext_len) as usize;
let obj_len = if x25519_public.secret_length == 0 {
let obj_len = ext_len + Self::BYTE_LENGTH_X25519_PUBLIC;
if ext_len > 0 {
r.read_exact(&mut buf[Self::BYTE_LENGTH_X25519_PUBLIC..obj_len])?;
}
obj_len
} else {
let obj_len = ext_len + Self::BYTE_LENGTH_X25519_SECRET;
if ext_len > 0 {
r.read_exact(&mut buf[Self::BYTE_LENGTH_X25519_PUBLIC..obj_len])?;
}
obj_len
};
IdentityBytes::try_from(&buf[..obj_len]).map_or_else(
|_| Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid identity")),
|b| Identity::from_bytes(&b).map_or_else(|| Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid identity")), |id| Ok(id)),
)
}
fn to_string_internal(&self, include_private: bool) -> String {
let mut s = String::with_capacity(1024);
s.push_str(self.address.to_string().as_str());
if (include_algorithms & Self::ALGORITHM_X25519) != 0 {
s.push_str(":0:"); // 0 used for x25519 for legacy reasons just like in marshal()
s.push_str(hex::to_string(&self.c25519).as_str());
s.push_str(hex::to_string(&self.ed25519).as_str());
if self.secret.is_some() && include_private {
let secret = self.secret.as_ref().unwrap();
s.push(':');
s.push_str(hex::to_string(secret.c25519.secret_bytes().as_bytes()).as_str());
s.push_str(hex::to_string(secret.ed25519.secret_bytes().as_bytes()).as_str());
} else if include_p384 {
s.push(':');
}
s.push_str(":0:"); // 0 used for x25519 for legacy reasons just like in marshal()
s.push_str(hex::to_string(&self.x25519).as_str());
s.push_str(hex::to_string(&self.ed25519).as_str());
if self.secret.is_some() && include_private {
let secret = self.secret.as_ref().unwrap();
s.push(':');
s.push_str(hex::to_string(secret.x25519.secret_bytes().as_bytes()).as_str());
s.push_str(hex::to_string(secret.ed25519.secret_bytes().as_bytes()).as_str());
}
if include_p384 {
let p384 = self.p384.as_ref().unwrap();
if let Some(p384) = self.p384.as_ref() {
if self.secret.is_none() || !include_private {
s.push(':');
}
s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature);
let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] =
concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature);
s.push_str(base64::encode_config(p384_joined, base64::URL_SAFE_NO_PAD).as_str());
if self.secret.is_some() && include_private {
let secret = self.secret.as_ref().unwrap();
if secret.p384.is_some() {
let p384_secret = secret.p384.as_ref().unwrap();
let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] = concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes());
let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] =
concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes());
s.push(':');
s.push_str(base64::encode_config(p384_secret_joined, base64::URL_SAFE_NO_PAD).as_str());
}
@ -467,17 +632,21 @@ impl Identity {
s
}
/// Get this identity in string form with all ciphers and with secrets (if present)
#[inline(always)]
pub fn to_public_string(&self) -> String {
self.to_string_internal(false)
}
#[inline(always)]
pub fn to_secret_string(&self) -> String {
self.to_string_with_options(Self::ALGORITHM_ALL, true)
self.to_string_internal(true)
}
}
impl ToString for Identity {
/// Get only the public portion of this identity as a string, including all cipher suites.
#[inline(always)]
fn to_string(&self) -> String {
self.to_string_with_options(Self::ALGORITHM_ALL, false)
self.to_string_internal(false)
}
}
@ -549,7 +718,7 @@ impl FromStr for Identity {
Ok(Identity {
address,
c25519: keys[0].as_slice()[0..32].try_into().unwrap(),
x25519: keys[0].as_slice()[0..32].try_into().unwrap(),
ed25519: keys[0].as_slice()[32..64].try_into().unwrap(),
p384: if keys[2].is_empty() {
None
@ -573,7 +742,7 @@ impl FromStr for Identity {
return Err(InvalidFormatError);
}
Some(IdentitySecret {
c25519: {
x25519: {
let tmp = C25519KeyPair::from_bytes(&keys[0].as_slice()[0..32], &keys[1].as_slice()[0..32]);
if tmp.is_none() {
return Err(InvalidFormatError);
@ -614,161 +783,6 @@ impl FromStr for Identity {
}
}
impl Marshalable for Identity {
/// Current sanity limit for the size of a marshaled Identity
/// This is padded just a little up to 512 and can be increased if new key types are ever added.
const MAX_MARSHAL_SIZE: usize = 25
+ ADDRESS_SIZE
+ C25519_PUBLIC_KEY_SIZE
+ ED25519_PUBLIC_KEY_SIZE
+ C25519_SECRET_KEY_SIZE
+ ED25519_SECRET_KEY_SIZE
+ P384_PUBLIC_KEY_SIZE
+ P384_PUBLIC_KEY_SIZE
+ P384_SECRET_KEY_SIZE
+ P384_SECRET_KEY_SIZE
+ P384_ECDSA_SIGNATURE_SIZE
+ ED25519_SIGNATURE_SIZE;
#[inline(always)]
fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
self.marshal_with_options(buf, Self::ALGORITHM_ALL, false)
}
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Identity> {
let address = Address::from_bytes(buf.read_bytes_fixed::<ADDRESS_SIZE>(cursor)?);
if !address.is_some() {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid address"));
}
let address = address.unwrap();
let mut x25519_public: Option<(&[u8; C25519_PUBLIC_KEY_SIZE], &[u8; ED25519_PUBLIC_KEY_SIZE])> = None;
let mut x25519_secret: Option<(&[u8; C25519_SECRET_KEY_SIZE], &[u8; ED25519_SECRET_KEY_SIZE])> = None;
let mut p384_ecdh_ecdsa_public: Option<(P384PublicKey, P384PublicKey, &[u8; P384_ECDSA_SIGNATURE_SIZE], &[u8; ED25519_SIGNATURE_SIZE])> = None;
let mut p384_ecdh_ecdsa_secret: Option<(&[u8; P384_SECRET_KEY_SIZE], &[u8; P384_SECRET_KEY_SIZE])> = None;
let mut eof = buf.len();
while *cursor < eof {
let algorithm = buf.read_u8(cursor)?;
match algorithm {
0_u8 | Self::ALGORITHM_X25519 => {
let a = buf.read_bytes_fixed::<C25519_PUBLIC_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<ED25519_PUBLIC_KEY_SIZE>(cursor)?;
x25519_public = Some((a, b));
let sec_size = buf.read_u8(cursor)?;
if sec_size == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 {
let a = buf.read_bytes_fixed::<C25519_SECRET_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<ED25519_SECRET_KEY_SIZE>(cursor)?;
x25519_secret = Some((a, b));
} else if sec_size != 0 {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid x25519 secret"));
}
}
0x03 => {
// This isn't an algorithm; each algorithm is identified by just one bit. This
// indicates the total size of the section after the x25519 keys for backward
// compatibility. See comments in marshal(). New versions can ignore this field.
let bytes_remaining = buf.read_u16(cursor)? as usize;
eof = *cursor + bytes_remaining;
}
Self::ALGORITHM_EC_NIST_P384 => {
let field_length = buf.read_varint(cursor)?;
let has_secret = if field_length == ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u64 {
false
} else {
if field_length == ((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + (P384_SECRET_KEY_SIZE * 2)) as u64 {
true
} else {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 public key"));
}
};
let a = P384PublicKey::from_bytes(buf.read_bytes_fixed::<P384_PUBLIC_KEY_SIZE>(cursor)?);
let b = P384PublicKey::from_bytes(buf.read_bytes_fixed::<P384_PUBLIC_KEY_SIZE>(cursor)?);
let c = buf.read_bytes_fixed::<P384_ECDSA_SIGNATURE_SIZE>(cursor)?;
let d = buf.read_bytes_fixed::<ED25519_SIGNATURE_SIZE>(cursor)?;
if a.is_none() || b.is_none() {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p384 public key"));
}
p384_ecdh_ecdsa_public = Some((a.unwrap(), b.unwrap(), c, d));
if has_secret {
let a = buf.read_bytes_fixed::<P384_SECRET_KEY_SIZE>(cursor)?;
let b = buf.read_bytes_fixed::<P384_SECRET_KEY_SIZE>(cursor)?;
p384_ecdh_ecdsa_secret = Some((a, b));
}
}
_ => {
*cursor += buf.read_varint(cursor)? as usize;
}
}
}
if x25519_public.is_none() {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "x25519 key missing"));
}
let x25519_public = x25519_public.unwrap();
let mut sha = SHA384::new();
sha.update(&address.to_bytes());
sha.update(x25519_public.0);
sha.update(x25519_public.1);
if p384_ecdh_ecdsa_public.is_some() {
let p384 = p384_ecdh_ecdsa_public.as_ref().unwrap();
sha.update(&[Self::ALGORITHM_EC_NIST_P384]);
sha.update(p384.0.as_bytes());
sha.update(p384.1.as_bytes());
}
Ok(Identity {
address,
c25519: x25519_public.0.clone(),
ed25519: x25519_public.1.clone(),
p384: if p384_ecdh_ecdsa_public.is_some() {
let p384_ecdh_ecdsa_public = p384_ecdh_ecdsa_public.as_ref().unwrap();
Some(IdentityP384Public {
ecdh: p384_ecdh_ecdsa_public.0.clone(),
ecdsa: p384_ecdh_ecdsa_public.1.clone(),
ecdsa_self_signature: p384_ecdh_ecdsa_public.2.clone(),
ed25519_self_signature: p384_ecdh_ecdsa_public.3.clone(),
})
} else {
None
},
secret: if x25519_secret.is_some() {
let x25519_secret = x25519_secret.unwrap();
let c25519_secret = C25519KeyPair::from_bytes(x25519_public.0, x25519_secret.0);
let ed25519_secret = Ed25519KeyPair::from_bytes(x25519_public.1, x25519_secret.1);
if c25519_secret.is_none() || ed25519_secret.is_none() {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "x25519 public key invalid"));
}
Some(IdentitySecret {
c25519: c25519_secret.unwrap(),
ed25519: ed25519_secret.unwrap(),
p384: if p384_ecdh_ecdsa_secret.is_some() && p384_ecdh_ecdsa_public.is_some() {
let p384_ecdh_ecdsa_public = p384_ecdh_ecdsa_public.as_ref().unwrap();
let p384_ecdh_ecdsa_secret = p384_ecdh_ecdsa_secret.as_ref().unwrap();
let p384_ecdh_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.0.as_bytes(), p384_ecdh_ecdsa_secret.0);
let p384_ecdsa_secret = P384KeyPair::from_bytes(p384_ecdh_ecdsa_public.1.as_bytes(), p384_ecdh_ecdsa_secret.1);
if p384_ecdh_secret.is_none() || p384_ecdsa_secret.is_none() {
return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "p384 secret key invalid"));
}
Some(IdentityP384Secret {
ecdh: p384_ecdh_secret.unwrap(),
ecdsa: p384_ecdsa_secret.unwrap(),
})
} else {
None
},
})
} else {
None
},
fingerprint: sha.finish(),
})
}
}
impl PartialEq for Identity {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
@ -779,6 +793,7 @@ impl PartialEq for Identity {
impl Eq for Identity {}
impl Ord for Identity {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address).then_with(|| self.fingerprint.cmp(&other.fingerprint))
}
@ -798,17 +813,121 @@ impl Hash for Identity {
}
}
mod packed {
use super::*;
#[derive(Copy, Clone)]
#[repr(C, packed)]
pub(super) struct V0 {
pub address: [u8; ADDRESS_SIZE],
pub key_type: u8,
pub x25519: [u8; C25519_PUBLIC_KEY_SIZE],
pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE],
pub secret_length: u8,
pub reserved: u8,
pub ext_len: [u8; 2],
}
#[derive(Copy, Clone)]
#[repr(C, packed)]
pub(super) struct V0S {
pub address: [u8; ADDRESS_SIZE],
pub key_type: u8,
pub x25519: [u8; C25519_PUBLIC_KEY_SIZE],
pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE],
pub secret_length: u8,
pub x25519_secret: [u8; C25519_SECRET_KEY_SIZE],
pub ed25519_secret: [u8; ED25519_SECRET_KEY_SIZE],
pub reserved: u8,
pub ext_len: [u8; 2],
}
#[derive(Copy, Clone)]
#[repr(C, packed)]
pub(super) struct V1 {
pub v0: V0,
pub key_type_flags: u8,
pub ecdh: [u8; P384_PUBLIC_KEY_SIZE],
pub ecdsa: [u8; P384_PUBLIC_KEY_SIZE],
pub ecdsa_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE],
}
#[derive(Copy, Clone)]
#[repr(C, packed)]
pub(super) struct V1S {
pub v0s: V0S,
pub key_type_flags: u8,
pub ecdh: [u8; P384_PUBLIC_KEY_SIZE],
pub ecdsa: [u8; P384_PUBLIC_KEY_SIZE],
pub ecdsa_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE],
pub ecdh_secret: [u8; P384_SECRET_KEY_SIZE],
pub ecdsa_secret: [u8; P384_SECRET_KEY_SIZE],
}
unsafe impl AlignmentNeutral for V0 {}
unsafe impl AlignmentNeutral for V0S {}
unsafe impl AlignmentNeutral for V1 {}
unsafe impl AlignmentNeutral for V1S {}
}
/// Identity rendered as a flat byte array.
///
/// Note that try_from() and into() here perform a straight conversion or cast between this and
/// byte slices. They don't check the actual format of the contained identity data.
#[derive(Clone, PartialEq, Eq)]
pub enum IdentityBytes {
X25519Public([u8; Identity::BYTE_LENGTH_X25519_PUBLIC]),
X25519Secret([u8; Identity::BYTE_LENGTH_X25519_SECRET]),
X25519P384Public([u8; Identity::BYTE_LENGTH_X25519P384_PUBLIC]),
X25519P384Secret([u8; Identity::BYTE_LENGTH_X25519P384_SECRET]),
}
impl<'a> From<&'a IdentityBytes> for &'a [u8] {
fn from(b: &'a IdentityBytes) -> Self {
match b {
IdentityBytes::X25519Public(b) => b,
IdentityBytes::X25519Secret(b) => b,
IdentityBytes::X25519P384Public(b) => b,
IdentityBytes::X25519P384Secret(b) => b,
}
}
}
impl TryFrom<&[u8]> for IdentityBytes {
type Error = std::array::TryFromSliceError;
fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
match b.len() {
Identity::BYTE_LENGTH_X25519_PUBLIC => Ok(Self::X25519Public(b.try_into()?)),
Identity::BYTE_LENGTH_X25519_SECRET => Ok(Self::X25519Secret(b.try_into()?)),
Identity::BYTE_LENGTH_X25519P384_PUBLIC => Ok(Self::X25519P384Public(b.try_into()?)),
_ => Ok(Self::X25519P384Secret(b.try_into()?)),
}
}
}
impl Drop for IdentityBytes {
fn drop(&mut self) {
// This can contain secrets, so zero it like Secret<> in the crypto core.
unsafe {
for i in 0..std::mem::size_of::<Self>() {
std::ptr::write_volatile((self as *mut Self).cast::<u8>().add(i), 0);
}
}
}
}
impl Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string_with_options(Self::ALGORITHM_ALL, false).as_str())
serializer.serialize_str(self.to_public_string().as_str())
} else {
let mut tmp: Buffer<{ Self::MAX_MARSHAL_SIZE }> = Buffer::new();
assert!(self.marshal_with_options(&mut tmp, Self::ALGORITHM_ALL, false).is_ok());
serializer.serialize_bytes(tmp.as_bytes())
serializer.serialize_bytes((&self.to_public_bytes()).into())
}
}
}
@ -826,14 +945,7 @@ impl<'de> serde::de::Visitor<'de> for IdentityVisitor {
where
E: serde::de::Error,
{
if v.len() <= Identity::MAX_MARSHAL_SIZE {
let mut tmp: Buffer<{ Identity::MAX_MARSHAL_SIZE }> = Buffer::new();
let _ = tmp.append_bytes(v);
let mut cursor = 0;
Identity::unmarshal(&tmp, &mut cursor).map_err(|e| E::custom(e.to_string()))
} else {
Err(E::custom("object too large"))
}
IdentityBytes::try_from(v).map_or_else(|e| Err(E::custom(e.to_string())), |b| Identity::from_bytes(&b).map_or_else(|| Err(E::custom("invalid identity")), |id| Ok(id)))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
@ -859,7 +971,6 @@ impl<'de> Deserialize<'de> for Identity {
#[cfg(test)]
mod tests {
use crate::util::marshalable::Marshalable;
use crate::vl1::identity::*;
use std::str::FromStr;
use zerotier_core_crypto::hex;
@ -892,10 +1003,10 @@ mod tests {
"ba0c4a4edd:0:4b75790dce1979b4cec38ca1eb81e0f348f757047c4ad5e8a463fe54f32142739ffd8c0bc9c95a45572d96173a11def1e653e6975343e4bc78d5b504e023aab8:28fa6bf3c103186c41575c91ee86887d21e0bdf77cdf4c36c9430c32e83affbee0b04da61312f4c990a18f2acf9031a6a2c4c69362f79f7f6d5621a3c8abf33c",
];
const GOOD_V1_IDENTITIES: [&'static str; 4] = [
"174cd00112:0:fd7e144befe03a8bca114094f576a6848224f35ef2c764f73d4b6f51ce54392127163722755be3e1de4375bec6d704e823acfa40180a39b7d76600c7776483b6::2:A9WKCt_BhL9EnKAb8SnPisFbCIXNDxFbtTxZiTjki9t5cu1xqEIOjk94s4r2CEaR5gOdpDyUGcoY0e1JjRRFK3CzVivW35eUhMKS1qQorcts35bYblMASQGp9ek047ROlKuzq4M5a-2Ymqb_fo1lhPxhaxLTmgsjmtllJJNknSOwGGYvQXukwzH4Vf0E-OWmkGrSWo6n7FkbuHwvKe7oPbhKwRQU03ya3-kM5vHGBglTOnN1NceXZvKwUhSiQ0zynt4OHrM5eJFlX3rE9mal5ml0l6CYs0Wh52byTHcav8A6tiSGNxxqU-BJ1EYEBG_kWB94gvGwImmBpaAR0xKfndIJ",
"4a23204ebb:0:ff61cddca9062501edc4390a8f218728fd58876ecfe2d757f611b9895d8e4e3a9cf8c6b49e18d5112f15c1a04715f0a579c2c43d7af0f3bb81de9a05135dcfcb::2:A4C5X0VLw98-g2Nc0ksfF50rt3-G-K5GGwmam2jUOGuz0IUH6cbryz3p4QjxFVEh9wM8VJUHF6yN7dpTwTWp6UMTVG5pFH6jSel-Z8GTSOJi3sybB-PFp0huY9oc0OOg1yuGbzKJFodkRmSCTku935hWVcV01HtNpNIScouXNVLIwDnxMd7iJ5J2pLJLS79Ofbf5VJWNXSIm4Ykse1BIv6vzbPZToQg_PXuIx8LFY1pGm8kb2Hx6FRUEDtP6jc3q5BDwVO8qVqatvpCTyjXWdBBURynH2FnmHeH_8u0mYse__wJoK5ICDRTxDKWWBOAOcS9pEQpjLuHnQex5cz9VI-gM",
"47e4f45e96:0:8a159c1ae1a81fb9f6d12027533e98178fca97c02edcb3b6f08c59578d54651cde7c596f9b7a8bb001b5ca7337ae99321a046bfcb8d5bdfd68a184a918000e7f::2:Aq0WCQ2jorXo2hlxKV8lQ0GX46DuisOSIRR8V3kg9oFbvnfa1c5W2lLMFtkqVqccAQPNPfU-Qfxh9lFUDKWNeS_bCx9Akt5Kw2yBi28eeySQP1DcHek4nVez9DG5-aTqnyyKnU3fNX34LuqMdTxZws4id33VXicY-sqKRobpyqFosC1U1SIiwqgSy290WBj_RKeZFU7QQF26cmbVF6bZ-OdYpcmNhdu_yzZ42Hh2W4duBHmJG1gDKGD-dXByaJVcnLIDwUMne8KSDZ7VaM_YfxWBmBBUqWUIpeeHnKLIlgF2ZJKgdUb8xzfUk0WzPkJfu2dcYduYGtVAG7r7jMatffMH",
"f172595d9d:0:739b4146fc7fff9d0234bd37b6d8c846b52bbad6f286bfc725580214b3a14149bf013dc23080d844a8cb1a0a32a6bb7caa455eeedc356660bee6cf80a7b586c1::2:A1_KXkfl5aHO6r7KmEf39Y5enSxWdiiVy6B5ZBWbhsT4sZV1Aqh4c_K2dH7s_QAydAIi-jrgvKshER7Dfn__xArPU_zWNvcW0-rCY3D0_y-Lr6b8_OjeZeN3Ry9cFkMhAyJE0x_n4uKGgFvtYFDjAtnGIzzzfnNf_h0dOFHZMRwcFRxtvo-e2hz-PuDsEQ_s350phi9N1Djtcy6aj-Cn_A0KzNpA5HFlWoOHTcNUDjZ64Eus47wemNl4lt3PBgQdcsk7MPsq122Da_LalAk_26CnbNzwifsfRnXHbHn7mf14r7YBb-8r-88LzL_Rtlnvcvhm6ib4pGFpv7TVkmkLvBoD",
"75a8a7a199:0:6322fe1ca7941571458bb0bd14faff0af915c2ca5ea5856a682de1040c8cbd1f79054a0c052b253316b17abd9cf34609c6ac0e26dd85ad169c26aa980a69d5e0:b8fcd2a25a0708176d81455a7b576b6835d87cc7b6b4701c914d65688f730f6d1a725018472570440e0ba1d6038be81e3415e8ba6dfa685ae2b582b12b90ad67:2:Az1IZD-cuJS11XWhCMEc6ZPwMpM_nQOwCHqHJaOywRxtkU9UC8BfOdGxYet-7fh3lgMxKntz_iqWD-7PAXoTpg2bDo-hKRT4zLkNm-KnUvqjiXl9W7RLYhvVd_qUXxQlY709m9hQ-omy_zRi3ZIysOMDaPwfyuwWUzhB09FiWn-MjiiUrujqqx66VfZ9rx_u63ZUvrTxWu6motVbS6eXMemIGUtU6UyhIXDIk2_WJceF9k7Bs7C7Ay00zuCTEHIH878e7LR4qPNnPPDRxQV3y5rO7WHsutl9wHSEINJAtYz7xJdk7IuccJfnDzhODroATwdSNtG0sASJU9UToUvWioEF:P5zAldkVK9P5uso9iOQ2hqgXS-J326gK-Z3l1_ZZehes8-0OYxUoHfs80ddG1MVGIOb3AFA0vi7S93wNUxRkg4kzzUl5rFAmy85iuiMCRpPY-8SG1500RI4dhWkRTuPR",
"a311a9d217:0:477c48a712daf785d7d5f2d2d693361047b102dbcd7eea2d6cabcb7149b0806815e4c4d7cd03a9b3ac87c3e27161724a5ad5c52d1c487c5484869e55e34966e5:f06d080800e3529ac2f5229cb1c07c6761fa1f2f1bc2447807f814e38252b24d6faf2da1ab6fb7db2460081dae877b9658714f3a2976f9ffcbe432151f69c3eb:2:A9_zhw9S4Kh-iOfCqEYjKg9Hyd9OabtE3F4FwlX87AEbeGRl0bRTOnqRNLpZPbwtNwJVJwKaY7dFCSc7Z03TJDSnFwz4HXy5KfdLEMHkJUv8ebfgNMuyc52f5ku3eyNE41uduXppdKzDbEJXiyBElMzaFBwK0q1zuDrRM1sjlaUto6bwsB8wPFC08PHsU98-sgJQXsWYYIlzhFRlW5Cjm-4QAfGOQ4BX1M5LLfzwxV0G4s895iLTAShb_fms8ITqiOGL4CjLFX2HYJ2fYanzrEFH3eYSQjZw0iu1iXVnrcaHpeICuFOIbbrR3Uhewa_kwyzHqIa1pUPONYak6Rje5PcD:1tf-W-tMafIY3VprzVNvcBtyC9i0JQCkVwUpTo7ej8wwdbRHntijqxcyZGEwQ0f_c-sucRPxzYIpA9l5V-QnXRNbWNNuA-1lHINJUVD4DCEc9Km2yJiWl2AmXVpN51KE",
"4a74fb8416:0:a6ce3d09a384e6d0b28c1f18ca63a95e3d15fb6eaa3177b3b6bc9f11c3dd1b312b50aaed5b919bafadd9c1e902e1f1f4d3944d43f289a01f9408974b9fe49455:10f8f8557856274c9d78b111ce2fe5e696e43a3139d6c685c6d620bb4946427873c80272f3573f460e156f8ac9eb4a8cf7aa7e9e7df152491be23405dd0fdf86:2:A55YK8R0MYVQi47wlDZxk7Ja3o_ahA88AxzmzQon79MF8HmMbxExwovGEk2TkuQ4xQLgWsM_vs-N-nlJUtUkxsd3ECHYs2s68LxzvpcmROIxakMr02ibclwJGnA_deivI2so8RjGsv4d18Up9lt3qNkor8N6SBuyhhEYPjjwFYKMn7oCqj_LDxVshYa9YtcHtjTABQPiKJaqJzpyZfquKrrofr18SBqJOPgtsw6Omuqv-IY384a-uT-51ic-RhW5eVqWQdVFj3-mIJEWUXIk9TKaAuh5soLFK9awXogG_cXCHTzQ9_POiJ8KW8VSE7OzC46wzYgllWXUKzKZCmwJ-7YP:IoLCcuTeUPiRv_TSSBkPX9ps0pfpcww9WR39SQsi4Q0H4sDk1q5tIgeLMNvwk2weIZpMo28n0evlyvuXw1P3LEEm2wY97D3ceGgwKgnAHwERLh6pM9de1_nZYAQYZOiS",
"466906c1fc:0:7e1df36857083f367b0301af07709748d074eef482f500493280abf22a95e3260ef73caf4496d4b847f6b6f0b9e45abc46c3fa4bb46bb35af207dac98a713609:58ff3e8bac5f1960152d4db8f149c426da2c5b29e53a4e22d86e8b10aee9c44453cd4d19103dd63fa920e9a6a7ff52ac326ed13499eea7b05a44911aaff85524:2:A4BWd-ehtSraBUI7NY_XgI-WHMbN6yVlAJQDiRH8vTk4824OCyNjQ19K5jGuLeEfawJtXG5njz03bvezpOToESZ0HKxPMz-6nz3fYpXMi-0bM6yyNij37t-Gb1_Ee_7JrLplVNfVUKokeG3I6W2W0pWWuSYeyfSSgXwdc3MkfIbLU7xPcR-7UU1FXHzSRRq9OxO3QMe15kVYpSFoxNGCyaMzDwgSLiXxqD3exi9zlKnlZl743vZuZmCjwq0bA7LT20GwT7Zr4qSnC7_XWokj8vofNlUKUziF5zfB5uM3Ml9_zz0HRc-oj42zrdWkXuJbJ7zXjXiFy82kNUllKykoTDoJ:bgOVQS8FrzIrIq_pNvwQfYDJ9HdxAPeAvfN_J8XxDFO2gvV-LGZmR_WWsiNiXVUhF9Y0mU_yZjw7kbenObxYua0OpTXpn-9TAQS1eYDCvR015eAGzpwFjlT85HJWbYfO",
];
#[test]
@ -903,12 +1014,11 @@ mod tests {
let gen = Identity::generate();
assert!(gen.agree(&gen).is_some());
assert!(gen.validate_identity());
let bytes = gen.to_buffer_with_options(Identity::ALGORITHM_ALL, true);
let string = gen.to_string_with_options(Identity::ALGORITHM_ALL, true);
let bytes = gen.to_secret_bytes().unwrap();
let string = gen.to_secret_string();
assert!(Identity::from_str(string.as_str()).unwrap().eq(&gen));
let mut cursor = 0_usize;
let gen_unmarshaled = Identity::unmarshal(&bytes, &mut cursor).unwrap();
let gen_unmarshaled = Identity::from_bytes(&bytes).unwrap();
assert!(gen_unmarshaled.secret.is_some());
if !gen_unmarshaled.eq(&gen) {
println!("{} != {}", hex::to_string(&gen_unmarshaled.fingerprint), hex::to_string(&gen.fingerprint));
@ -922,20 +1032,18 @@ mod tests {
for id_str in GOOD_V0_IDENTITIES {
let mut id = Identity::from_str(id_str).unwrap();
assert_eq!(id.to_string_with_options(Identity::ALGORITHM_ALL, true).as_str(), id_str);
assert_eq!(id.to_secret_string().as_str(), id_str);
assert!(id.validate_identity());
assert!(id.p384.is_none());
let idb = id.to_buffer_with_options(Identity::ALGORITHM_ALL, true);
let mut cursor = 0;
let id_unmarshal = Identity::unmarshal(&idb, &mut cursor).unwrap();
let idb = id.to_secret_bytes().unwrap();
let id_unmarshal = Identity::from_bytes(&idb).unwrap();
assert!(id == id_unmarshal);
assert!(id_unmarshal.secret.is_some());
let idb2 = id_unmarshal.to_buffer_with_options(Identity::ALGORITHM_ALL, false);
cursor = 0;
let id_unmarshal2 = Identity::unmarshal(&idb2, &mut cursor).unwrap();
let idb2 = id_unmarshal.to_public_bytes();
let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap();
assert!(id_unmarshal2 == id_unmarshal);
assert!(id_unmarshal2 == id);
assert!(id_unmarshal2.secret.is_none());
@ -953,19 +1061,18 @@ mod tests {
}
for id_str in GOOD_V1_IDENTITIES {
let id = Identity::from_str(id_str).unwrap();
assert_eq!(id.to_string_with_options(Identity::ALGORITHM_ALL, true).as_str(), id_str);
assert_eq!(id.to_secret_string().as_str(), id_str);
assert!(id.validate_identity());
assert!(id.p384.is_some());
assert!(id.secret.as_ref().unwrap().p384.is_some());
let idb = id.to_buffer_with_options(Identity::ALGORITHM_ALL, true);
let mut cursor = 0;
let id_unmarshal = Identity::unmarshal(&idb, &mut cursor).unwrap();
let idb = id.to_secret_bytes().unwrap();
let id_unmarshal = Identity::from_bytes(&idb).unwrap();
assert!(id == id_unmarshal);
cursor = 0;
let idb2 = id_unmarshal.to_buffer_with_options(Identity::ALGORITHM_ALL, false);
let id_unmarshal2 = Identity::unmarshal(&idb2, &mut cursor).unwrap();
let idb2 = id_unmarshal.to_public_bytes();
let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap();
assert!(id_unmarshal2 == id_unmarshal);
assert!(id_unmarshal2 == id);

View file

@ -11,7 +11,6 @@ mod mac;
mod path;
mod peer;
mod rootset;
mod session;
mod symmetricsecret;
mod whoisqueue;

View file

@ -68,14 +68,7 @@ pub trait SystemInterface: Sync + Send + 'static {
/// For endpoint types that support a packet TTL, the implementation may set the TTL
/// if the 'ttl' parameter is not zero. If the parameter is zero or TTL setting is not
/// supported, the default TTL should be used.
async fn wire_send(
&self,
endpoint: &Endpoint,
local_socket: Option<&Self::LocalSocket>,
local_interface: Option<&Self::LocalInterface>,
data: &[&[u8]],
packet_ttl: u8,
) -> bool;
async fn wire_send(&self, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>, data: &[&[u8]], packet_ttl: u8) -> bool;
/// Called to check and see if a physical address should be used for ZeroTier traffic to a node.
async fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option<&Self::LocalSocket>, local_interface: Option<&Self::LocalInterface>) -> bool;
@ -101,23 +94,13 @@ pub trait InnerProtocolInterface: Sync + Send + 'static {
/// Handle a packet, returning true if it was handled by the next layer.
///
/// Do not attempt to handle OK or ERROR. Instead implement handle_ok() and handle_error().
async fn handle_packet<SI: SystemInterface>(
&self,
source: &Peer<SI>,
source_path: &Path<SI>,
forward_secrecy: bool,
extended_authentication: bool,
verb: u8,
payload: &PacketBuffer,
) -> bool;
async fn handle_packet<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, verb: u8, payload: &PacketBuffer) -> bool;
/// Handle errors, returning true if the error was recognized.
async fn handle_error<SI: SystemInterface>(
&self,
source: &Peer<SI>,
source_path: &Path<SI>,
forward_secrecy: bool,
extended_authentication: bool,
in_re_verb: u8,
in_re_message_id: u64,
error_code: u8,
@ -126,17 +109,7 @@ pub trait InnerProtocolInterface: Sync + Send + 'static {
) -> bool;
/// Handle an OK, returing true if the OK was recognized.
async fn handle_ok<SI: SystemInterface>(
&self,
source: &Peer<SI>,
source_path: &Path<SI>,
forward_secrecy: bool,
extended_authentication: bool,
in_re_verb: u8,
in_re_message_id: u64,
payload: &PacketBuffer,
cursor: &mut usize,
) -> bool;
async fn handle_ok<SI: SystemInterface>(&self, source: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool;
/// Check if this remote peer has a trust relationship with this node.
///
@ -455,10 +428,7 @@ impl<SI: SystemInterface> Node<SI> {
)));
}
for i in bad_identities.iter() {
si.event(Event::SecurityWarning(format!(
"bad identity detected for address {} in at least one root set, ignoring (error creating peer object)",
i.address.to_string()
)));
si.event(Event::SecurityWarning(format!("bad identity detected for address {} in at least one root set, ignoring (error creating peer object)", i.address.to_string())));
}
let mut new_root_identities: Vec<Identity> = new_roots.iter().map(|(p, _)| p.identity.clone()).collect();
@ -590,17 +560,9 @@ impl<SI: SystemInterface> Node<SI> {
if fragment_header.is_fragment() {
#[cfg(debug_assertions)]
let fragment_header_id = u64::from_be_bytes(fragment_header.id);
debug_event!(
si,
"[vl1] #{:0>16x} fragment {} of {} received",
u64::from_be_bytes(fragment_header.id),
fragment_header.fragment_no(),
fragment_header.total_fragments()
);
debug_event!(si, "[vl1] #{:0>16x} fragment {} of {} received", u64::from_be_bytes(fragment_header.id), fragment_header.fragment_no(), fragment_header.total_fragments());
if let Some(assembled_packet) =
path.receive_fragment(fragment_header.packet_id(), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks)
{
if let Some(assembled_packet) = path.receive_fragment(fragment_header.packet_id(), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks) {
if let Some(frag0) = assembled_packet.frags[0].as_ref() {
#[cfg(debug_assertions)]
debug_event!(si, "[vl1] #{:0>16x} packet fully assembled!", fragment_header_id);
@ -608,8 +570,7 @@ impl<SI: SystemInterface> Node<SI> {
if let Ok(packet_header) = frag0.struct_at::<PacketHeader>(0) {
if let Some(source) = Address::from_bytes(&packet_header.src) {
if let Some(peer) = self.peer(source) {
peer.receive(self, si, ph, time_ticks, &path, &packet_header, frag0, &assembled_packet.frags[1..(assembled_packet.have as usize)])
.await;
peer.receive(self, si, ph, time_ticks, &path, &packet_header, frag0, &assembled_packet.frags[1..(assembled_packet.have as usize)]).await;
} else {
self.whois.query(self, si, source, Some(QueuedPacket::Fragmented(assembled_packet)));
}

View file

@ -7,12 +7,11 @@ use std::sync::{Arc, Weak};
use parking_lot::{Mutex, RwLock};
use zerotier_core_crypto::aes_gmac_siv::AesCtr;
use zerotier_core_crypto::hash::*;
use zerotier_core_crypto::random::{get_bytes_secure, next_u64_secure};
use zerotier_core_crypto::random::next_u64_secure;
use zerotier_core_crypto::salsa::Salsa;
use zerotier_core_crypto::secret::Secret;
use crate::util::buffer::BufferReader;
use crate::util::byte_array_range;
use crate::util::canonicalobject::CanonicalObject;
use crate::util::debug_event;
@ -39,7 +38,6 @@ struct RemoteNodeInfo {
reported_local_endpoints: HashMap<Endpoint, i64>,
care_of: Option<CareOf>,
remote_version: u64,
hello_extended_authentication: bool,
remote_protocol_version: u8,
}
@ -209,7 +207,6 @@ impl<SI: SystemInterface> Peer<SI> {
reported_local_endpoints: HashMap::new(),
care_of: None,
remote_version: 0,
hello_extended_authentication: false,
remote_protocol_version: 0,
}),
}
@ -353,8 +350,8 @@ impl<SI: SystemInterface> Peer<SI> {
let mut pos = UDP_DEFAULT_MTU;
let overrun_size = (packet_size - UDP_DEFAULT_MTU) as u32;
let fragment_count = (overrun_size / (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32)
+ (((overrun_size % (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) != 0) as u32);
let fragment_count =
(overrun_size / (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) + (((overrun_size % (UDP_DEFAULT_MTU - packet_constants::FRAGMENT_HEADER_SIZE) as u32) != 0) as u32);
debug_assert!(fragment_count <= packet_constants::FRAGMENT_COUNT_MAX as u32);
let mut header = FragmentHeader {
@ -404,11 +401,8 @@ impl<SI: SystemInterface> Peer<SI> {
};
let max_fragment_size = if path.endpoint.requires_fragmentation() { UDP_DEFAULT_MTU } else { usize::MAX };
let flags_cipher_hops = if packet.len() > max_fragment_size {
packet_constants::HEADER_FLAG_FRAGMENTED | security_constants::CIPHER_AES_GMAC_SIV
} else {
security_constants::CIPHER_AES_GMAC_SIV
};
let flags_cipher_hops =
if packet.len() > max_fragment_size { packet_constants::HEADER_FLAG_FRAGMENTED | security_constants::CIPHER_AES_GMAC_SIV } else { security_constants::CIPHER_AES_GMAC_SIV };
let mut aes_gmac_siv = self.identity_symmetric_key.aes_gmac_siv.get();
aes_gmac_siv.encrypt_init(&self.next_message_id().to_ne_bytes());
@ -509,47 +503,8 @@ impl<SI: SystemInterface> Peer<SI> {
}
debug_assert_eq!(packet.len(), 41);
assert!(packet.append_bytes((&node.identity.to_public_bytes()).into()).is_ok());
// Full identity of this node.
assert!(node.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
// Create session meta-data and append length of this section.
let session_metadata = self.create_session_metadata(node, None);
let session_metadata_len = session_metadata.len() + 16; // plus nonce
assert!(session_metadata_len <= 0xffff); // sanity check, should be impossible
assert!(packet.append_u16(session_metadata_len as u16).is_ok());
// Append a 16-byte AES-CTR nonce. LEGACY: for compatibility the last two bytes of this nonce
// are in fact an encryption of two zeroes with Salsa20/12, which old nodes will interpret as
// zero "moons." New nodes will just use these as part of the nonce.
let mut nonce = get_bytes_secure::<16>();
let mut salsa_iv = message_id.to_ne_bytes();
salsa_iv[7] &= 0xf8;
Salsa::<12>::new(&self.identity_symmetric_key.key.0[0..32], &salsa_iv).crypt(&crate::util::ZEROES[..2], &mut nonce[14..]);
assert!(packet.append_bytes_fixed(&nonce).is_ok());
// Write session meta-data, encrypted.
nonce[12] &= 0x7f; // mask off the MSB of the 32-bit counter part of the CTR nonce for compatibility with AES libraries that don't wrap
let salted_key = Secret(hmac_sha384(&message_id.to_ne_bytes(), self.identity_symmetric_key.hello_private_section_key.as_bytes()));
let mut aes = AesCtr::new(&salted_key.as_bytes()[0..32]);
aes.init(&nonce);
aes.crypt(session_metadata.as_slice(), packet.append_bytes_get_mut(session_metadata.len()).unwrap());
// Set fragment flag if the packet will need to be fragmented.
if (packet.len() + SHA384_HASH_SIZE) > max_fragment_size {
set_packet_fragment_flag(&mut packet);
}
// Seal packet with HMAC-SHA384 extended authentication.
let mut hmac = HMACSHA384::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
hmac.update(&self.identity.fingerprint);
hmac.update(&node.identity.fingerprint);
hmac.update(&message_id.to_ne_bytes());
hmac.update(&packet.as_bytes()[packet_constants::HEADER_SIZE..]);
assert!(packet.append_bytes_fixed(&hmac.finish()).is_ok());
drop(hmac);
// Set poly1305 in header, which is the only authentication for old nodes.
let (_, poly1305_key) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len());
let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, packet.as_bytes_starting_at(packet_constants::HEADER_SIZE).unwrap());
packet.as_mut()[packet_constants::MAC_FIELD_INDEX..packet_constants::MAC_FIELD_INDEX + 8].copy_from_slice(&mac[0..8]);
@ -601,24 +556,6 @@ impl<SI: SystemInterface> Peer<SI> {
};
if let Ok(mut verb) = payload.u8_at(0) {
let extended_authentication = (verb & packet_constants::VERB_FLAG_EXTENDED_AUTHENTICATION) != 0;
if extended_authentication {
if payload.len() >= SHA384_HASH_SIZE {
let actual_end_of_payload = payload.len() - SHA384_HASH_SIZE;
let mut hmac = HMACSHA384::new(self.identity_symmetric_key.packet_hmac_key.as_bytes());
hmac.update(&node.identity.fingerprint);
hmac.update(&self.identity.fingerprint);
hmac.update(&message_id.to_ne_bytes());
hmac.update(&payload.as_bytes()[..actual_end_of_payload]);
if !hmac.finish().eq(&payload.as_bytes()[actual_end_of_payload..]) {
return false;
}
payload.set_size(actual_end_of_payload);
} else {
return false;
}
}
if (verb & packet_constants::VERB_FLAG_COMPRESSED) != 0 {
let mut decompressed_payload = [0_u8; packet_constants::SIZE_MAX];
decompressed_payload[0] = verb;
@ -649,19 +586,15 @@ impl<SI: SystemInterface> Peer<SI> {
if match verb {
verbs::VL1_NOP => true,
verbs::VL1_HELLO => {
self.handle_incoming_hello(si, ph, node, time_ticks, message_id, source_path, packet_header.hops(), extended_authentication, &payload).await
}
verbs::VL1_ERROR => self.handle_incoming_error(si, ph, node, time_ticks, source_path, false, extended_authentication, &payload).await,
verbs::VL1_OK => {
self.handle_incoming_ok(si, ph, node, time_ticks, source_path, packet_header.hops(), path_is_known, false, extended_authentication, &payload).await
}
verbs::VL1_HELLO => self.handle_incoming_hello(si, ph, node, time_ticks, message_id, source_path, packet_header.hops(), &payload).await,
verbs::VL1_ERROR => self.handle_incoming_error(si, ph, node, time_ticks, source_path, &payload).await,
verbs::VL1_OK => self.handle_incoming_ok(si, ph, node, time_ticks, source_path, packet_header.hops(), path_is_known, &payload).await,
verbs::VL1_WHOIS => self.handle_incoming_whois(si, ph, node, time_ticks, message_id, &payload).await,
verbs::VL1_RENDEZVOUS => self.handle_incoming_rendezvous(si, node, time_ticks, message_id, source_path, &payload).await,
verbs::VL1_ECHO => self.handle_incoming_echo(si, ph, node, time_ticks, message_id, &payload).await,
verbs::VL1_PUSH_DIRECT_PATHS => self.handle_incoming_push_direct_paths(si, node, time_ticks, source_path, &payload).await,
verbs::VL1_USER_MESSAGE => self.handle_incoming_user_message(si, node, time_ticks, source_path, &payload).await,
_ => ph.handle_packet(self, &source_path, false, extended_authentication, verb, &payload).await,
_ => ph.handle_packet(self, &source_path, verb, &payload).await,
} {
// This needs to be saved AFTER processing the packet since some message types may use it to try to filter for replays.
self.last_incoming_message_id.store(message_id, Ordering::Relaxed);
@ -682,7 +615,6 @@ impl<SI: SystemInterface> Peer<SI> {
message_id: MessageId,
source_path: &Arc<Path<SI>>,
hops: u8,
extended_authentication: bool,
payload: &PacketBuffer,
) -> bool {
if !(ph.has_trust_relationship(&self.identity) || node.this_node_is_root() || node.is_peer_root(self)) {
@ -692,55 +624,14 @@ impl<SI: SystemInterface> Peer<SI> {
let mut cursor = 0;
if let Ok(hello_fixed_headers) = payload.read_struct::<message_component_structs::HelloFixedHeaderFields>(&mut cursor) {
if let Ok(identity) = Identity::unmarshal(payload, &mut cursor) {
if let Ok(identity) = Identity::read_bytes(&mut BufferReader::new(payload, &mut cursor)) {
if identity.eq(&self.identity) {
{
let mut remote_node_info = self.remote_node_info.write();
remote_node_info.remote_protocol_version = hello_fixed_headers.version_proto;
remote_node_info.hello_extended_authentication = extended_authentication;
remote_node_info.remote_version = (hello_fixed_headers.version_major as u64).wrapping_shl(48)
| (hello_fixed_headers.version_minor as u64).wrapping_shl(32)
| (u16::from_be_bytes(hello_fixed_headers.version_revision) as u64).wrapping_shl(16);
if hello_fixed_headers.version_proto >= 20 {
let mut session_metadata_len = payload.read_u16(&mut cursor).unwrap_or(0) as usize;
if session_metadata_len > 16 {
session_metadata_len -= 16;
if let Ok(nonce) = payload.read_bytes_fixed::<16>(&mut cursor) {
let mut nonce = nonce.clone();
if let Ok(session_metadata) = payload.read_bytes(session_metadata_len, &mut cursor) {
let mut session_metadata = session_metadata.to_vec();
nonce[12] &= 0x7f;
let salted_key = Secret(hmac_sha384(&message_id.to_ne_bytes(), self.identity_symmetric_key.hello_private_section_key.as_bytes()));
let mut aes = AesCtr::new(&salted_key.as_bytes()[0..32]);
aes.init(&nonce);
aes.crypt_in_place(session_metadata.as_mut_slice());
if let Some(session_metadata) = Dictionary::from_bytes(session_metadata.as_slice()) {
if let Some(instance_id) = session_metadata.get_bytes(session_metadata::INSTANCE_ID) {
// For new nodes that send an instance ID, we can try to filter out any possible replaying of old
// packets. A replay of an old HELLO could be used to at least probe for the existence of a node at
// a given IP address and port by eliciting an OK(HELLO) reply.
if remote_node_info.remote_instance_id.eq(instance_id) {
if message_id.wrapping_sub(self.last_incoming_message_id.load(Ordering::Relaxed)) > PACKET_RESPONSE_COUNTER_DELTA_MAX
&& time_ticks.wrapping_sub(self.last_receive_time_ticks.load(Ordering::Relaxed)) < PEER_HELLO_INTERVAL_MAX
{
debug_event!(si, "[vl1] dropping HELLO from {} due to possible replay of old packet", self.identity.address.to_string());
return true;
}
} else {
remote_node_info.remote_instance_id.fill(0);
let l = instance_id.len().min(remote_node_info.remote_instance_id.len());
(&mut remote_node_info.remote_instance_id[..l]).copy_from_slice(&instance_id[..l]);
}
}
}
}
}
}
}
}
let mut packet = PacketBuffer::new();
@ -770,36 +661,14 @@ impl<SI: SystemInterface> Peer<SI> {
return false;
}
async fn handle_incoming_error<PH: InnerProtocolInterface>(
&self,
_si: &SI,
ph: &PH,
_node: &Node<SI>,
_time_ticks: i64,
source_path: &Arc<Path<SI>>,
forward_secrecy: bool,
extended_authentication: bool,
payload: &PacketBuffer,
) -> bool {
async fn handle_incoming_error<PH: InnerProtocolInterface>(&self, _si: &SI, ph: &PH, _node: &Node<SI>, _time_ticks: i64, source_path: &Arc<Path<SI>>, payload: &PacketBuffer) -> bool {
let mut cursor = 0;
if let Ok(error_header) = payload.read_struct::<message_component_structs::ErrorHeader>(&mut cursor) {
let in_re_message_id: MessageId = u64::from_ne_bytes(error_header.in_re_message_id);
if self.message_id_counter.load(Ordering::Relaxed).wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX {
match error_header.in_re_verb {
_ => {
return ph
.handle_error(
self,
&source_path,
forward_secrecy,
extended_authentication,
error_header.in_re_verb,
in_re_message_id,
error_header.error_code,
payload,
&mut cursor,
)
.await;
return ph.handle_error(self, &source_path, error_header.in_re_verb, in_re_message_id, error_header.error_code, payload, &mut cursor).await;
}
}
}
@ -816,8 +685,6 @@ impl<SI: SystemInterface> Peer<SI> {
source_path: &Arc<Path<SI>>,
hops: u8,
path_is_known: bool,
forward_secrecy: bool,
extended_authentication: bool,
payload: &PacketBuffer,
) -> bool {
let mut cursor = 0;
@ -892,12 +759,7 @@ impl<SI: SystemInterface> Peer<SI> {
let reported_endpoint2 = reported_endpoint.clone();
if self.remote_node_info.write().reported_local_endpoints.insert(reported_endpoint, time_ticks).is_none() {
#[cfg(debug_assertions)]
debug_event!(
si,
"[vl1] {} reported new remote perspective, local endpoint: {}",
self.identity.address.to_string(),
reported_endpoint2.to_string()
);
debug_event!(si, "[vl1] {} reported new remote perspective, local endpoint: {}", self.identity.address.to_string(), reported_endpoint2.to_string());
}
}
}
@ -914,7 +776,7 @@ impl<SI: SystemInterface> Peer<SI> {
verbs::VL1_WHOIS => {
if node.is_peer_root(self) {
while cursor < payload.len() {
if let Ok(_whois_response) = Identity::unmarshal(payload, &mut cursor) {
if let Ok(_whois_response) = Identity::read_bytes(&mut BufferReader::new(payload, &mut cursor)) {
// TODO
} else {
break;
@ -926,7 +788,7 @@ impl<SI: SystemInterface> Peer<SI> {
}
_ => {
return ph.handle_ok(self, &source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor).await;
return ph.handle_ok(self, &source_path, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor).await;
}
}
}
@ -949,7 +811,7 @@ impl<SI: SystemInterface> Peer<SI> {
while cursor < payload.len() {
if let Ok(zt_address) = Address::unmarshal(payload, &mut cursor) {
if let Some(peer) = node.peer(zt_address) {
if !peer.identity.marshal(&mut packet).is_ok() {
if !packet.append_bytes((&peer.identity.to_public_bytes()).into()).is_ok() {
debug_event!(si, "unexpected error serializing an identity into a WHOIS packet response");
return false;
}

View file

@ -219,10 +219,10 @@ pub mod security_constants {
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
/// KBKDF usage label for AES-GCM session keys.
pub const KBKDF_KEY_USAGE_LABEL_AES_GCM_SESSION_KEY: u8 = b's';
pub const KBKDF_KEY_USAGE_LABEL_AES_GCM_SESSION_KEY: u8 = b'g';
/// KBKDF usage label for ephemeral session key ratcheting.
pub const KBKDF_KEY_USAGE_LABEL_RATCHET_KEY: u8 = b'r';
/// KBKDF usage label for AES-GCM session keys.
pub const KBKDF_KEY_USAGE_LABEL_AES_CTR_SESSION_KEY: u8 = b'c';
/// Try to re-key ephemeral keys after this time.
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes

View file

@ -3,7 +3,7 @@
use std::collections::BTreeSet;
use std::io::Write;
use crate::util::buffer::Buffer;
use crate::util::buffer::{Buffer, BufferReader};
use crate::util::marshalable::Marshalable;
use crate::vl1::identity::*;
use crate::vl1::Endpoint;
@ -109,7 +109,7 @@ impl RootSet {
buf.append_varint(self.revision)?;
buf.append_varint(self.members.len() as u64)?;
for m in self.members.iter() {
m.identity.marshal_with_options(buf, Identity::ALGORITHM_ALL, false)?;
buf.append_bytes((&m.identity.to_public_bytes()).into())?;
if m.endpoints.is_some() {
let endpoints = m.endpoints.as_ref().unwrap();
buf.append_varint(endpoints.len() as u64)?;
@ -269,7 +269,7 @@ impl Marshalable for RootSet {
let member_count = buf.read_varint(cursor)?;
for _ in 0..member_count {
let mut m = Root {
identity: Identity::unmarshal(buf, cursor)?,
identity: Identity::read_bytes(&mut BufferReader::new(buf, cursor))?,
endpoints: None,
signature: Vec::new(),
priority: 0,

File diff suppressed because it is too large Load diff

View file

@ -14,9 +14,6 @@ pub(crate) struct SymmetricSecret {
/// Master key from which other keys are derived.
pub key: Secret<64>,
/// Key used for HMAC extended validation on packets like HELLO.
pub packet_hmac_key: Secret<48>,
/// Pool of keyed AES-GMAC-SIV engines (pooled to avoid AES re-init every time).
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
}
@ -24,16 +21,11 @@ pub(crate) struct SymmetricSecret {
impl SymmetricSecret {
/// Create a new symmetric secret, deriving all sub-keys and such.
pub fn new(key: Secret<64>) -> SymmetricSecret {
let packet_hmac_key = zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_PACKET_HMAC);
let aes_factory = AesGmacSivPoolFactory(
zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0).first_n(),
zt_kbkdf_hmac_sha384(&key.0[..48], security_constants::KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1).first_n(),
);
SymmetricSecret {
key,
packet_hmac_key,
aes_gmac_siv: Pool::new(2, aes_factory),
}
SymmetricSecret { key, aes_gmac_siv: Pool::new(2, aes_factory) }
}
}

View file

@ -13,7 +13,7 @@ pub struct Switch {}
#[async_trait]
impl InnerProtocolInterface for Switch {
#[allow(unused)]
async fn handle_packet<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &PacketBuffer) -> bool {
async fn handle_packet<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, verb: u8, payload: &PacketBuffer) -> bool {
false
}
@ -22,8 +22,6 @@ impl InnerProtocolInterface for Switch {
&self,
peer: &Peer<SI>,
source_path: &Path<SI>,
forward_secrecy: bool,
extended_authentication: bool,
in_re_verb: u8,
in_re_message_id: u64,
error_code: u8,
@ -34,7 +32,7 @@ impl InnerProtocolInterface for Switch {
}
#[allow(unused)]
async fn handle_ok<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool {
async fn handle_ok<SI: SystemInterface>(&self, peer: &Peer<SI>, source_path: &Path<SI>, in_re_verb: u8, in_re_message_id: u64, payload: &PacketBuffer, cursor: &mut usize) -> bool {
false
}