mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-15 00:43:45 +02:00
VL1 now says HELLO!!!
This commit is contained in:
parent
36a105ecbf
commit
a61bcaf0f7
15 changed files with 180 additions and 115 deletions
|
@ -1,21 +1,21 @@
|
|||
AES-GMAC-SIV
|
||||
======
|
||||
|
||||
This is a Rust implementation of AES-GMAC-SIV, a FIPS-compliant SIV mode for AES-256, with underlying cryptographic primitives provided by either macOS CryptoCore, OpenSSL, or (eventually, not implemented yet) WinCrypt. The appropriate API is automatically selected at compile time.
|
||||
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.
|
||||
|
||||
This is designed for use with the ZeroTier protocol but could be used for other message based protocols where a secure authenticated cipher construction is needed.
|
||||
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.
|
||||
|
||||
## Introduction
|
||||
|
||||
AES-GMAC-SIV is a "synthetic IV" (SIV) cipher construction implemented using only FIPS and NIST approved cryptographic building blocks: AES and GMAC (the MAC component of GCM). It can for FIPS purposes be described as "AES-CTR authenticated with GMAC" both of which are permitted algorithms. It was created because while similar to [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV) that mode uses a non-standard MAC called POLYVAL in place of GMAC. POLYVAL is just GMAC in little-endian, but the fact that it is not standard GMAC means it's not found in most cryptographic libraries and is not approved by FIPS and most other cryptographic standards.
|
||||
|
||||
## Why SIV?
|
||||
## About SIV Modes
|
||||
|
||||
Stream ciphers like AES-CTR, ChaCha20, and others require a number called an initialization vector (IV) for each use. These and most other stream ciphers work by XORing a key stream with plaintext, so if an IV is used more than once security is compromised. Since XOR is commutative, if two different messages are encrypted with the same key stream a simple XOR can reveal that key stream and decrypt both messages. This is a common pitfall with any XOR based symmetric cipher construction.
|
||||
|
||||
Repeating the IV is dangerous with many MAC functions in authenticated constructions as well. For AES-GCM the use of a duplicate IV with GMAC can allow an attacker to forge messages, which for many use cases is a worse security failure than letting an attacker decrypt a few messages.
|
||||
Repeating the IV is dangerous with many MAC (message authentication) functions as well. [It's particularly dangerous in AES-GCM](https://crypto.stackexchange.com/questions/26790/how-bad-it-is-using-the-same-iv-twice-with-aes-gcm), where one nonce reuse with GMAC (the MAC function in AES-GCM) could allow an attacker to forge messages.
|
||||
|
||||
SIV modes provide strong protection against IV reuse and improve security bounds for protocols using small IVs (like ZeroTier's 64-bit message ID) by generating a larger *synthetic IV* from a random or sequential external IV and the message plaintext. Even if the externally provided IV is duplicated, the larger SIV IV won't repeat unless the plaintext is also identical. In this case all you've done is leaked the fact that you have sent a duplicate message. The security of that message and its authentication function is not compromised.
|
||||
SIV stands for *synthetic IV*. SIV modes work by applying a MAC function to the plaintext first, then using the resulting authentication code plus an IV to initialize a stream cipher. This provides much stronger protection against IV reuse by making the actual IV dependent on the plaintext. Changes to the plaintext will therefore change the IV even if the one supplied to the function is duplicated.
|
||||
|
||||
SIV modes might seem like paranoia, but accidental IV reuse is easier than you might think. Here's a few scenarios where it might happen:
|
||||
|
||||
|
@ -30,11 +30,13 @@ SIV modes might seem like paranoia, but accidental IV reuse is easier than you m
|
|||
|
||||
... and so on. "Sudden death" on IV re-use is a foot-gun that's worth removing.
|
||||
|
||||
## AES-GMAC-SIV construction
|
||||
## AES-GMAC-SIV
|
||||
|
||||

|
||||
|
||||
Two initialization keys, which can be derived from one using a parameterized KDF:
|
||||
*Inputs are green, outputs are blue. Grey indicates simple non-cryptographic operations. Red indicates cryptographic steps.*
|
||||
|
||||
Two initialization keys, which can be derived from a single key using a key derivation function or hashing with a 512-bit hash function and using the first and second 256 bits:
|
||||
|
||||
1. K0, a 256-bit AES key used to initialize AES-GMAC.
|
||||
2. K1, a second (and different) 256-bit AES key used to initialize AES-ECB and AES-CTR.
|
||||
|
@ -53,7 +55,7 @@ Encryption steps:
|
|||
4. Feed plaintext into GMAC to compute final MAC.
|
||||
5. XOR lower 64 bits and higher 64 bits of 128-bit GMAC tag to yield a 64-bit tag.
|
||||
6. Concatenate original 64-bit input IV and 64-bit shortened tag to form a 128-bit block.
|
||||
7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV. (AES-ECB is perfectly safe if only one block is encrypted.)
|
||||
7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV. (ECB is secure if only one block is encrypted.)
|
||||
8. Clear bit 31 (from the right) in the tag and use this to initialize AES-CTR with the first 96 bits being the AES-CTR IV and the remaining 31 bits being the AES-CTR "index" or counter. This provides what amounts to a 127-bit AES-CTR IV. The most significant bit of the counter is cleared so that poor quality AES-CTR implementations that only use a 32-bit wrapping counter will not wrap at message sizes up to 2^31 bytes. Wrapping technically wouldn't hurt anything unless the implementation generates a fault on wrap, but avoid this in case some cryptographic accelerator somewhere does so.
|
||||
9. Encrypt plaintext with AES-CTR and send this along with the encrypted IV+tag from step 7 (without CTR counter bit 31 cleared). The per-message unique 64-bit IV supplied by the caller at encryption **should not** be sent as it is recovered during decryption by decrypting the IV+tag blob. Sending it wastes space and reveals slightly more state information to an attacker, since without the input IV an attacker doesn't know if it has in fact been duplicated.
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ mod tests {
|
|||
use sha2::Digest;
|
||||
use std::time::SystemTime;
|
||||
|
||||
const TV0_KEYS: [&'static [u8]; 2] = ["00000000000000000000000000000000".as_bytes(), "11111111111111111111111111111111".as_bytes()];
|
||||
|
||||
/// Test vectors consist of a series of input sizes, a SHA384 hash of a resulting ciphertext, and an expected tag.
|
||||
/// Input is a standard byte array consisting of bytes 0, 1, 2, 3, ..., 255 and then cycling back to 0 over and over
|
||||
/// and is provided both as ciphertext and associated data (AAD).
|
||||
#[allow(unused)]
|
||||
const TEST_VECTORS: [(usize, &'static str, &'static str); 85] = [
|
||||
(0, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "43847e644239134deccf5538162c861e"),
|
||||
|
@ -117,6 +122,7 @@ mod tests {
|
|||
s
|
||||
}
|
||||
|
||||
/// Run a bunch of test vectors.
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
let mut test_pt = [0_u8; 65536];
|
||||
|
@ -126,7 +132,7 @@ mod tests {
|
|||
test_pt[i] = i as u8;
|
||||
test_aad[i] = i as u8;
|
||||
}
|
||||
let mut c = AesGmacSiv::new("00000000000000000000000000000000".as_bytes(), "11111111111111111111111111111111".as_bytes());
|
||||
let mut c = AesGmacSiv::new(TV0_KEYS[0], TV0_KEYS[1]);
|
||||
for (test_length, expected_ct_sha384, expected_tag) in TEST_VECTORS.iter() {
|
||||
test_ct.fill(0);
|
||||
c.reset();
|
||||
|
@ -147,6 +153,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Test repeated encrypt/decrypt and run a benchmark. Run with --nocapture to see it.
|
||||
#[test]
|
||||
fn encrypt_decrypt() {
|
||||
let aes_key_0: [u8; 32] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32];
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
use poly1305::universal_hash::{NewUniversalHash, UniversalHash};
|
||||
use poly1305::universal_hash::NewUniversalHash;
|
||||
|
||||
/// The poly1305 message authentication function.
|
||||
#[repr(transparent)]
|
||||
pub struct Poly1305(poly1305::Poly1305);
|
||||
pub struct Poly1305(poly1305::Poly1305, [u8; 16], usize);
|
||||
|
||||
pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32;
|
||||
pub const POLY1305_MAC_SIZE: usize = 16;
|
||||
|
||||
impl Poly1305 {
|
||||
#[inline(always)]
|
||||
pub fn new(key: &[u8]) -> Poly1305 {
|
||||
assert_eq!(key.len(), 32);
|
||||
Self(poly1305::Poly1305::new(poly1305::Key::from_slice(key)))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&mut self, data: &[u8]) {
|
||||
self.0.update_padded(data);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish(self) -> [u8; POLY1305_MAC_SIZE] {
|
||||
self.0.finalize().into_bytes().into()
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn compute(one_time_key: &[u8], message: &[u8]) -> [u8; POLY1305_MAC_SIZE] {
|
||||
poly1305::Poly1305::new(poly1305::Key::from_slice(one_time_key)).compute_unpadded(message).into_bytes().into()
|
||||
}
|
||||
|
||||
unsafe impl Send for Poly1305 {}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::poly1305::*;
|
||||
|
||||
const TV0_INPUT: [u8; 32] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
const TV0_KEY: [u8; 32] = [0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35];
|
||||
const TV0_TAG: [u8; 16] = [0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07];
|
||||
|
||||
const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21];
|
||||
const TV1_KEY: [u8; 32] = [0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35];
|
||||
const TV1_TAG: [u8; 16] = [0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0];
|
||||
|
||||
#[test]
|
||||
fn poly1305() {
|
||||
assert_eq!(TV0_TAG, compute(&TV0_KEY, &TV0_INPUT));
|
||||
assert_eq!(TV1_TAG, compute(&TV1_KEY, &TV1_INPUT));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,11 +161,12 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
|
|||
ciphertext[56..60].copy_from_slice(&(u32::from_ne_bytes(unsafe { *plaintext.as_ptr().add(56).cast::<[u8; 4]>() }) ^ x14.to_le()).to_ne_bytes());
|
||||
ciphertext[60..64].copy_from_slice(&(u32::from_ne_bytes(unsafe { *plaintext.as_ptr().add(60).cast::<[u8; 4]>() }) ^ x15.to_le()).to_ne_bytes());
|
||||
}
|
||||
plaintext = &plaintext[64..];
|
||||
ciphertext = &mut ciphertext[64..];
|
||||
|
||||
j8 = j8.wrapping_add(1);
|
||||
j9 = j9.wrapping_add((j8 == 0) as u32);
|
||||
|
||||
plaintext = &plaintext[64..];
|
||||
ciphertext = &mut ciphertext[64..];
|
||||
} else {
|
||||
if !plaintext.is_empty() {
|
||||
let remainder = [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()];
|
||||
|
@ -187,3 +188,28 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
|
|||
unsafe { self.crypt(&*slice_from_raw_parts(data.as_ptr(), data.len()), &mut *slice_from_raw_parts_mut(data.as_mut_ptr(), data.len())) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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_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,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn salsa20() {
|
||||
let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV);
|
||||
let mut ks = [0_u8; 64];
|
||||
s20.crypt_in_place(&mut ks);
|
||||
assert_eq!(ks, SALSA_20_TV0_KS);
|
||||
|
||||
let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV);
|
||||
let mut ks = [0_u8; 32];
|
||||
s20.crypt_in_place(&mut ks);
|
||||
assert_eq!(ks, &SALSA_20_TV0_KS[..32]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ zerotier-core-crypto = { path = "../zerotier-core-crypto" }
|
|||
async-trait = "^0"
|
||||
base64 = "^0"
|
||||
lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] }
|
||||
parking_lot = "^0"
|
||||
parking_lot = { version = "^0", features = [], default-features = false }
|
||||
lazy_static = "^1"
|
||||
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "local-parellels-test",
|
||||
"url": "http://root.zerotier.com/root.zerotier.com.json",
|
||||
"revision": 1,
|
||||
"members": [ {
|
||||
"identity": "bc47f54ab2:0:cfb92160bab1da37f31247ded76d8327c00c4d3e49d8a424c6ba16fe3e77b949ab782426584b0169e7b38f7679ea24f38cea637a7a93a9272bfcb0ff461c1e97",
|
||||
"endpoints": [ "udp:10.211.55.3/9993" ],
|
||||
"signature": [ 1, 21, 160, 59, 81, 229, 164, 176, 225, 189, 66, 211, 25, 209, 30, 130, 254, 218, 121, 84, 129, 109, 18, 0, 156, 97, 56, 40, 165, 114, 211, 89, 25, 114, 144, 93, 237, 206, 52, 219, 60, 173, 178, 40, 22, 76, 42, 215, 158, 82, 12, 218, 128, 28, 158, 167, 108, 68, 80, 6, 211, 53, 70, 22, 5 ],
|
||||
"priority": 0
|
||||
} ]
|
||||
}
|
|
@ -80,6 +80,7 @@ impl<I: Interface> NetworkHypervisor<I> {
|
|||
pub fn add_update_default_root_set(&self) -> bool {
|
||||
let mut buf: Buffer<4096> = Buffer::new();
|
||||
buf.set_to(include_bytes!("../default-rootset/root.zerotier.com.bin"));
|
||||
//buf.set_to(include_bytes!("../default-rootset/local-parallels-test.bin"));
|
||||
let mut cursor = 0;
|
||||
self.add_update_root_set(RootSet::unmarshal(&buf, &mut cursor).unwrap())
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ impl<const L: usize> Buffer<L> {
|
|||
#[inline(always)]
|
||||
pub fn as_bytes_starting_at(&self, start: usize) -> std::io::Result<&[u8]> {
|
||||
if start <= self.0 {
|
||||
Ok(&self.1[start..])
|
||||
Ok(&self.1[start..self.0])
|
||||
} else {
|
||||
Err(overflow_err())
|
||||
}
|
||||
|
|
|
@ -150,6 +150,13 @@ impl Identity {
|
|||
///
|
||||
/// The boolean indicates whether or not an upgrade occurred. An error occurs if this identity is
|
||||
/// invalid or missing its private key(s). This does nothing if no upgrades are possible.
|
||||
///
|
||||
/// NOTE: upgrading is not deterministic. This generates a new set of NIST P-384 keys and the new
|
||||
/// identity contains these and a signature by the original keys and by the new keys to bind them
|
||||
/// together. However repeated calls to upgrade() will generate different secondary keys. This should
|
||||
/// only be used once to upgrade and then save a new identity.
|
||||
///
|
||||
/// It would be possible to change this in the future, with care.
|
||||
pub fn upgrade(&mut self) -> Result<bool, InvalidParameterError> {
|
||||
if self.secret.is_none() {
|
||||
return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key"));
|
||||
|
@ -301,25 +308,20 @@ impl Identity {
|
|||
}
|
||||
|
||||
/// Verify a signature against this identity.
|
||||
pub fn verify(&self, msg: &[u8], mut signature: &[u8]) -> bool {
|
||||
pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool {
|
||||
if signature.len() == 96 {
|
||||
// legacy ed25519-only signature with hash included detected by their unique size.
|
||||
ed25519_verify(&self.ed25519, &signature[..64], msg)
|
||||
// LEGACY: ed25519-only signature with hash included, detected by having a unique size of 96 bytes
|
||||
return ed25519_verify(&self.ed25519, &signature[..64], msg);
|
||||
} else if let Some(algorithm) = signature.get(0) {
|
||||
if *algorithm == Self::ALGORITHM_EC_NIST_P384 && signature.len() == (1 + P384_ECDSA_SIGNATURE_SIZE) {
|
||||
if let Some(p384) = self.p384.as_ref() {
|
||||
p384.ecdsa.verify(msg, &signature[1..])
|
||||
} else {
|
||||
false
|
||||
return p384.ecdsa.verify(msg, &signature[1..]);
|
||||
}
|
||||
} else if *algorithm == Self::ALGORITHM_X25519 && signature.len() == (1 + ED25519_SIGNATURE_SIZE) {
|
||||
ed25519_verify(&self.ed25519, &signature[1..], msg)
|
||||
} else {
|
||||
false
|
||||
return ed25519_verify(&self.ed25519, &signature[1..], msg);
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> {
|
||||
|
@ -349,6 +351,8 @@ impl Identity {
|
|||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
|
@ -909,6 +913,26 @@ mod tests {
|
|||
#[allow(unused_imports)]
|
||||
use zerotier_core_crypto::hex;
|
||||
|
||||
#[test]
|
||||
fn v0_identity() {
|
||||
let self_agree_expected = hex::from_string("de904fc90ff3a2b96b739b926e623113f5334c80841b654509b77916c4c4a6eb0ca69ec6ed01a7f04aee17c546b30ba4");
|
||||
|
||||
// Test self-agree with a known good x25519-only (v0) identity.
|
||||
let id = Identity::from_str("728efdb79d:0:3077ed0084d8d48a3ac628af6b45d9351e823bff34bc4376cddfc77a3d73a966c7d347bdcc1244d0e99e1b9c961ff5e963092e90ca43b47ff58c114d2d699664:2afaefcd1dca336ed59957eb61919b55009850b0b7088af3ee142672b637d1d49cc882b30a006f9eee42f2211ef8fe1cbe99a16a4436737fc158ce2243c15f12").unwrap();
|
||||
let self_agree = id.agree(&id).unwrap();
|
||||
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
|
||||
|
||||
// Identity should be upgradable.
|
||||
let mut upgraded = id.clone();
|
||||
assert!(upgraded.upgrade().unwrap());
|
||||
|
||||
// Upgraded identity should generate the same result when agreeing with the old non-upgraded identity.
|
||||
let self_agree = id.agree(&upgraded).unwrap();
|
||||
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
|
||||
let self_agree = upgraded.agree(&id).unwrap();
|
||||
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
|
||||
}
|
||||
|
||||
const GOOD_V0_IDENTITIES: [&'static str; 10] = [
|
||||
"51ef313c3a:0:79fee239cf79833be3a9068565661dc33e04759fa0f7e2218d10f1a51d441f1bf71332eba26dfc3755ce60e14650fe68dede66cf145e429972a7f51e026374de:6d12b1c5e0eae3983a5ee5872fa9061963d9e2f8cdd85adab54bdec4bd67f538cafc91b8b5b93fca658a630aab030ec10d66235f2443ccf362c55c41ae01b46e",
|
||||
"9532db97eb:0:86a2c3a7d08be09f794188ef86014f54b699577536db1ded58537c9159020b48c962ff7f25501ada8ef20b604dd29fb1a915966aaffe1ef6a589527525599f10:06ab13d2704583451bb326feb5c3d9bfe7879aa327669ff33150a42c04464aa5435cec79d952e0af970142e9d8c8a0dd26deadf9b9ba2f1cb454bf2ac22e53e6",
|
||||
|
|
|
@ -497,11 +497,11 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
pub async fn handle_incoming_physical_packet<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: &SI::LocalSocket, source_local_interface: &SI::LocalInterface, mut data: PooledPacketBuffer) {
|
||||
debug_event!(
|
||||
si,
|
||||
"[vl1] #{} {}->{} via {} length {} via socket {}@{}",
|
||||
"[vl1] {} -> #{} {}->{} length {} (on socket {}@{})",
|
||||
source_endpoint.to_string(),
|
||||
data.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| zerotier_core_crypto::hex::to_string(pid)),
|
||||
data.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| zerotier_core_crypto::hex::to_string(src)),
|
||||
data.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| zerotier_core_crypto::hex::to_string(dest)),
|
||||
source_endpoint.to_string(),
|
||||
data.len(),
|
||||
source_local_socket.to_string(),
|
||||
source_local_interface.to_string()
|
||||
|
@ -517,11 +517,11 @@ 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, "-- #{: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(frag0) = assembled_packet.frags[0].as_ref() {
|
||||
debug_event!(si, "-- #{:0>16x} packet fully assembled!", fragment_header_id);
|
||||
debug_event!(si, "[vl1] #{:0>16x} packet fully assembled!", fragment_header_id);
|
||||
|
||||
if let Ok(packet_header) = frag0.struct_at::<PacketHeader>(0) {
|
||||
if let Some(source) = Address::from_bytes(&packet_header.src) {
|
||||
|
@ -537,7 +537,7 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
if let Ok(packet_header) = data.struct_at::<PacketHeader>(0) {
|
||||
debug_event!(si, "-- #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id));
|
||||
debug_event!(si, "[vl1] #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id));
|
||||
|
||||
if let Some(source) = Address::from_bytes(&packet_header.src) {
|
||||
if let Some(peer) = self.peer(source) {
|
||||
|
@ -557,9 +557,9 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
{
|
||||
debug_packet_id = u64::from_be_bytes(fragment_header.id);
|
||||
}
|
||||
debug_event!(si, "-- #{:0>16x} forwarding packet fragment to {}", debug_packet_id, dest.to_string());
|
||||
debug_event!(si, "[vl1] #{:0>16x} forwarding packet fragment to {}", debug_packet_id, dest.to_string());
|
||||
if fragment_header.increment_hops() > FORWARD_MAX_HOPS {
|
||||
debug_event!(si, "-- #{:0>16x} discarded: max hops exceeded!", debug_packet_id);
|
||||
debug_event!(si, "[vl1] #{:0>16x} discarded: max hops exceeded!", debug_packet_id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
@ -568,9 +568,9 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
{
|
||||
debug_packet_id = u64::from_be_bytes(packet_header.id);
|
||||
}
|
||||
debug_event!(si, "-- #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string());
|
||||
debug_event!(si, "[vl1] #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string());
|
||||
if packet_header.increment_hops() > FORWARD_MAX_HOPS {
|
||||
debug_event!(si, "-- #{:0>16x} discarded: max hops exceeded!", u64::from_be_bytes(packet_header.id));
|
||||
debug_event!(si, "[vl1] #{:0>16x} discarded: max hops exceeded!", u64::from_be_bytes(packet_header.id));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
@ -581,7 +581,7 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
if let Some(peer) = self.peer(dest) {
|
||||
// TODO: SHOULD we forward? Need a way to check.
|
||||
peer.forward(si, time_ticks, data.as_ref()).await;
|
||||
debug_event!(si, "-- #{:0>16x} forwarded successfully", debug_packet_id);
|
||||
debug_event!(si, "[vl1] #{:0>16x} forwarded successfully", debug_packet_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -618,9 +618,8 @@ impl<SI: SystemInterface> Node<SI> {
|
|||
|
||||
pub fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc<Path<SI>> {
|
||||
if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) {
|
||||
path.clone()
|
||||
} else {
|
||||
self.paths.write().entry(PathKey::Copied(ep.clone(), local_socket.clone())).or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))).clone()
|
||||
return path.clone();
|
||||
}
|
||||
return self.paths.write().entry(PathKey::Copied(ep.clone(), local_socket.clone())).or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))).clone();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ use parking_lot::{Mutex, RwLock};
|
|||
|
||||
use zerotier_core_crypto::aes_gmac_siv::AesCtr;
|
||||
use zerotier_core_crypto::hash::*;
|
||||
use zerotier_core_crypto::poly1305::Poly1305;
|
||||
use zerotier_core_crypto::random::{get_bytes_secure, next_u64_secure};
|
||||
use zerotier_core_crypto::salsa::Salsa;
|
||||
use zerotier_core_crypto::secret::Secret;
|
||||
|
@ -62,32 +61,30 @@ pub struct Peer<SI: SystemInterface> {
|
|||
remote_protocol_version: AtomicU8,
|
||||
}
|
||||
|
||||
/// Create initialized instances of Salsa20/12 and Poly1305 for a packet.
|
||||
/// This is deprecated and is not used with AES-GMAC-SIV.
|
||||
fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa<12>, Poly1305) {
|
||||
/// Create initialized instances of Salsa20/12 and Poly1305 for a packet (LEGACY).
|
||||
fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa<12>, [u8; 32]) {
|
||||
// Create a per-packet key from the IV, source, destination, and packet size.
|
||||
let mut key: Secret<32> = secret.key.first_n();
|
||||
let hb = header.as_bytes();
|
||||
for i in 0..18 {
|
||||
key.0[i] ^= hb[i];
|
||||
}
|
||||
key.0[18] ^= hb[packet_constants::FLAGS_FIELD_INDEX] & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS;
|
||||
key.0[19] ^= (packet_size >> 8) as u8;
|
||||
key.0[20] ^= packet_size as u8;
|
||||
key.0[18] ^= header.flags_cipher_hops & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS;
|
||||
key.0[19] ^= packet_size as u8;
|
||||
key.0[20] ^= packet_size.wrapping_shr(8) as u8;
|
||||
|
||||
let mut salsa = Salsa::<12>::new(&key.0, &header.id);
|
||||
|
||||
let mut poly1305_key = [0_u8; 32];
|
||||
salsa.crypt_in_place(&mut poly1305_key);
|
||||
|
||||
(salsa, Poly1305::new(&poly1305_key))
|
||||
(salsa, poly1305_key)
|
||||
}
|
||||
|
||||
/// Attempt AEAD packet encryption and MAC validation. Returns message ID on success.
|
||||
fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option<PooledPacketBuffer>], payload: &mut PacketBuffer) -> Option<MessageId> {
|
||||
packet_frag0_payload_bytes.get(0).map_or(None, |verb| {
|
||||
match header.cipher() {
|
||||
security_constants::CIPHER_NOCRYPT_POLY1305 => {
|
||||
let cipher = header.cipher();
|
||||
match cipher {
|
||||
security_constants::CIPHER_NOCRYPT_POLY1305 | security_constants::CIPHER_SALSA2012_POLY1305 => {
|
||||
if (verb & packet_constants::VERB_MASK) == verbs::VL1_HELLO {
|
||||
let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE;
|
||||
for f in fragments.iter() {
|
||||
|
@ -97,9 +94,12 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8],
|
|||
for f in fragments.iter() {
|
||||
let _ = f.as_ref().map(|f| f.as_bytes_starting_at(packet_constants::HEADER_SIZE).map(|f| payload.append_bytes(f)));
|
||||
}
|
||||
let (_, mut poly) = salsa_poly_create(secret, header, total_packet_len);
|
||||
poly.update(payload.as_bytes());
|
||||
if poly.finish()[0..8].eq(&header.mac) {
|
||||
let (mut salsa, poly1305_key) = salsa_poly_create(secret, header, total_packet_len);
|
||||
let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, &payload.as_bytes());
|
||||
if mac[0..8].eq(&header.mac) {
|
||||
if cipher == security_constants::CIPHER_SALSA2012_POLY1305 {
|
||||
salsa.crypt_in_place(payload.as_bytes_mut());
|
||||
}
|
||||
Some(u64::from_ne_bytes(header.id))
|
||||
} else {
|
||||
None
|
||||
|
@ -110,29 +110,6 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8],
|
|||
}
|
||||
}
|
||||
|
||||
security_constants::CIPHER_SALSA2012_POLY1305 => {
|
||||
let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE;
|
||||
for f in fragments.iter() {
|
||||
total_packet_len += f.as_ref().map_or(0, |f| f.len());
|
||||
}
|
||||
let (mut salsa, mut poly) = salsa_poly_create(secret, header, total_packet_len);
|
||||
poly.update(packet_frag0_payload_bytes);
|
||||
let _ = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()).map(|b| salsa.crypt(packet_frag0_payload_bytes, b));
|
||||
for f in fragments.iter() {
|
||||
let _ = f.as_ref().map(|f| {
|
||||
f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE).map(|f| {
|
||||
poly.update(f);
|
||||
let _ = payload.append_bytes_get_mut(f.len()).map(|b| salsa.crypt(f, b));
|
||||
})
|
||||
});
|
||||
}
|
||||
if poly.finish()[0..8].eq(&header.mac) {
|
||||
Some(u64::from_ne_bytes(header.id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
security_constants::CIPHER_AES_GMAC_SIV => {
|
||||
let mut aes = secret.aes_gmac_siv.get();
|
||||
aes.decrypt_init(&header.aes_gmac_siv_tag());
|
||||
|
@ -255,14 +232,14 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
/// those fragments after the main packet header and first chunk.
|
||||
///
|
||||
/// This returns true if the packet decrypted and passed authentication.
|
||||
pub(crate) async fn receive<PH: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, ph: &PH, time_ticks: i64, source_path: &Arc<Path<SI>>, header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) {
|
||||
pub(crate) async fn receive<PH: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, ph: &PH, time_ticks: i64, source_path: &Arc<Path<SI>>, packet_header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) {
|
||||
if let Ok(packet_frag0_payload_bytes) = frag0.as_bytes_starting_at(packet_constants::VERB_INDEX) {
|
||||
//let mut payload = unsafe { PacketBuffer::new_without_memzero() };
|
||||
let mut payload = PacketBuffer::new();
|
||||
|
||||
// First try decrypting and authenticating with an ephemeral secret if one is negotiated.
|
||||
let (forward_secrecy, mut message_id) = if let Some(ephemeral_secret) = self.ephemeral_symmetric_key.read().as_ref() {
|
||||
if let Some(message_id) = try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, header, fragments, &mut payload) {
|
||||
if let Some(message_id) = try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, packet_header, fragments, &mut payload) {
|
||||
// Decryption successful with ephemeral secret
|
||||
(true, message_id)
|
||||
} else {
|
||||
|
@ -276,11 +253,13 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
|
||||
// If forward_secrecy is false it means the ephemeral key failed. Try decrypting with the permanent key.
|
||||
if !forward_secrecy {
|
||||
if let Some(message_id2) = try_aead_decrypt(&self.identity_symmetric_key, packet_frag0_payload_bytes, header, fragments, &mut payload) {
|
||||
payload.clear();
|
||||
if let Some(message_id2) = try_aead_decrypt(&self.identity_symmetric_key, packet_frag0_payload_bytes, packet_header, fragments, &mut payload) {
|
||||
// Decryption successful with static secret.
|
||||
message_id = message_id2;
|
||||
} else {
|
||||
// Packet failed to decrypt using either ephemeral or permament key, reject.
|
||||
debug_event!(si, "[vl1] #{:0>16x} failed authentication", u64::from_be_bytes(packet_header.id));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -308,6 +287,8 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
// If we made it here it decrypted and passed authentication.
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
debug_event!(si, "[vl1] #{:0>16x} decrypted and authenticated, verb: {:0>2x}", u64::from_be_bytes(packet_header.id), (verb & packet_constants::VERB_MASK) as u32);
|
||||
|
||||
if (verb & packet_constants::VERB_FLAG_COMPRESSED) != 0 {
|
||||
let mut decompressed_payload: [u8; packet_constants::SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
decompressed_payload[0] = verb;
|
||||
|
@ -473,8 +454,10 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
hello_fixed_headers.timestamp = si.time_clock().to_be_bytes();
|
||||
}
|
||||
|
||||
// Full identity of the node establishing the session.
|
||||
assert!(self.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
|
||||
assert_eq!(packet.len(), 41);
|
||||
|
||||
// Full identity of this node.
|
||||
assert!(node.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
|
||||
|
||||
// Append two reserved bytes, currently always zero.
|
||||
assert!(packet.append_padding(0, 2).is_ok());
|
||||
|
@ -525,9 +508,9 @@ impl<SI: SystemInterface> Peer<SI> {
|
|||
drop(hmac);
|
||||
|
||||
// Set poly1305 in header, which is the only authentication for old nodes.
|
||||
let (_, mut poly) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len());
|
||||
poly.update(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(&poly.finish()[0..8]);
|
||||
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]);
|
||||
|
||||
self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ num-traits = "^0"
|
|||
tokio = { version = "^1", features = ["full"], default-features = false }
|
||||
serde = { version = "^1", features = ["derive"], default-features = false }
|
||||
serde_json = { version = "^1", features = ["std"], default-features = false }
|
||||
parking_lot = "^0"
|
||||
parking_lot = { version = "^0", features = [], default-features = false }
|
||||
lazy_static = "^1"
|
||||
clap = { version = "^3", features = ["std", "suggestions"], default-features = false }
|
||||
log = "^0"
|
||||
|
|
|
@ -111,10 +111,10 @@ impl Default for GlobalSettings {
|
|||
|
||||
impl GlobalSettings {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 9] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw"];
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 10] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi"];
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt", "tailscale"];
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = [];
|
||||
|
|
|
@ -197,11 +197,16 @@ impl SystemInterface for ServiceImpl {
|
|||
if !sockets.is_empty() {
|
||||
if let Some(specific_interface) = local_interface {
|
||||
for (_, p) in sockets.iter() {
|
||||
for s in p.sockets.iter() {
|
||||
if s.interface.eq(specific_interface) {
|
||||
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
|
||||
return true;
|
||||
if !p.sockets.is_empty() {
|
||||
let mut i = (random::next_u32_secure() as usize) % p.sockets.len();
|
||||
for _ in 0..p.sockets.len() {
|
||||
let s = p.sockets.get(i).unwrap();
|
||||
if s.interface.eq(specific_interface) {
|
||||
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
i = (i + 1) % p.sockets.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,12 +216,16 @@ impl SystemInterface for ServiceImpl {
|
|||
let rn = random::xorshift64_random() as usize;
|
||||
for i in 0..bound_ports.len() {
|
||||
let p = sockets.get(*bound_ports.get(rn.wrapping_add(i) % bound_ports.len()).unwrap()).unwrap();
|
||||
for s in p.sockets.iter() {
|
||||
if !sent_on_interfaces.contains(&s.interface) {
|
||||
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
|
||||
sent_on_interfaces.insert(s.interface.clone());
|
||||
break;
|
||||
if !p.sockets.is_empty() {
|
||||
let mut i = (random::next_u32_secure() as usize) % p.sockets.len();
|
||||
for _ in 0..p.sockets.len() {
|
||||
let s = p.sockets.get(i).unwrap();
|
||||
if !sent_on_interfaces.contains(&s.interface) {
|
||||
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
|
||||
sent_on_interfaces.insert(s.interface.clone());
|
||||
}
|
||||
}
|
||||
i = (i + 1) % p.sockets.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue