mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-03 19:13:43 +02:00
Rusty stuff.
This commit is contained in:
parent
478d174f4e
commit
52585e9262
9 changed files with 315 additions and 66 deletions
1
network-hypervisor/Cargo.lock
generated
1
network-hypervisor/Cargo.lock
generated
|
@ -414,6 +414,7 @@ version = "2.0.0"
|
|||
dependencies = [
|
||||
"aes-gmac-siv",
|
||||
"ed25519-dalek",
|
||||
"gcrypt",
|
||||
"rand_core",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
|
|
@ -8,3 +8,4 @@ rand_core = "^0"
|
|||
aes-gmac-siv = { path = "../aes-gmac-siv" }
|
||||
x25519-dalek = "^1"
|
||||
ed25519-dalek = "^1"
|
||||
gcrypt = "^0"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use std::convert::TryInto;
|
||||
use ed25519_dalek::Digest;
|
||||
use std::io::Write;
|
||||
|
||||
pub const C25519_PUBLIC_KEY_SIZE: usize = 32;
|
||||
pub const C25519_SECRET_KEY_SIZE: usize = 32;
|
||||
|
@ -7,6 +9,7 @@ pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
|
|||
pub const ED25519_SECRET_KEY_SIZE: usize = 32;
|
||||
pub const ED25519_SIGNATURE_SIZE: usize = 64;
|
||||
|
||||
/// Curve25519 key pair for ECDH key agreement.
|
||||
pub struct C25519KeyPair(x25519_dalek::StaticSecret, x25519_dalek::PublicKey);
|
||||
|
||||
impl C25519KeyPair {
|
||||
|
@ -18,10 +21,16 @@ impl C25519KeyPair {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> C25519KeyPair {
|
||||
let pk = x25519_dalek::PublicKey::from(public_key.try_into().unwrap());
|
||||
let sk = x25519_dalek::StaticSecret::from(secret_key.try_into().unwrap());
|
||||
C25519KeyPair(sk, pk)
|
||||
pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> Option<C25519KeyPair> {
|
||||
if public_key.len() == 32 && secret_key.len() == 32 {
|
||||
let pk: [u8; 32] = public_key.try_into().unwrap();
|
||||
let sk: [u8; 32] = secret_key.try_into().unwrap();
|
||||
let pk = x25519_dalek::PublicKey::from(pk);
|
||||
let sk = x25519_dalek::StaticSecret::from(sk);
|
||||
Some(C25519KeyPair(sk, pk))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -34,14 +43,17 @@ impl C25519KeyPair {
|
|||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
/// Execute ECDH agreement and return a raw (un-hashed) shared secret key.
|
||||
#[inline(always)]
|
||||
pub fn agree(&self, their_public: &[u8]) -> [u8; C25519_SHARED_SECRET_SIZE] {
|
||||
let pk = x25519_dalek::PublicKey::from(their_public.try_into().unwrap());
|
||||
let pk: [u8; 32] = their_public.try_into().unwrap();
|
||||
let pk = x25519_dalek::PublicKey::from(pk);
|
||||
let sec = self.0.diffie_hellman(&pk);
|
||||
sec.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 key pair for EDDSA signatures.
|
||||
pub struct Ed25519KeyPair(ed25519_dalek::Keypair);
|
||||
|
||||
impl Ed25519KeyPair {
|
||||
|
@ -71,19 +83,37 @@ impl Ed25519KeyPair {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn sign(&self, msg: &[u8]) -> [u8; ED25519_SIGNATURE_SIZE] {
|
||||
let h = crate::crypto::hash::SHA512::hash(msg);
|
||||
self.0.sign_prehashed(h, None).unwrap().to_bytes()
|
||||
let mut h = ed25519_dalek::Sha512::new();
|
||||
let _ = h.write_all(msg);
|
||||
self.0.sign_prehashed(h.clone(), None).unwrap().to_bytes()
|
||||
}
|
||||
|
||||
/// Create a signature with the first 32 bytes of the SHA512 hash appended.
|
||||
/// ZeroTier does this for legacy reasons.
|
||||
/// ZeroTier does this for legacy reasons, but it's ignored in newer versions.
|
||||
#[inline(always)]
|
||||
pub fn sign_zt(&self, msg: &[u8]) -> [u8; 96] {
|
||||
let h = crate::crypto::hash::SHA512::hash(msg);
|
||||
let s = self.0.sign_prehashed(h, None).unwrap().as_ref();
|
||||
let mut h = ed25519_dalek::Sha512::new();
|
||||
let _ = h.write_all(msg);
|
||||
let sig = self.0.sign_prehashed(h.clone(), None).unwrap();
|
||||
let s = sig.as_ref();
|
||||
let mut s2 = [0_u8; 96];
|
||||
s2[0..64].copy_from_slice(s);
|
||||
s2[64..96].copy_from_slice(&h[0..32]);
|
||||
let h = h.finalize();
|
||||
s2[64..96].copy_from_slice(&h.as_slice()[0..32]);
|
||||
s2
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ed25519_verify(public_key: &[u8], signature: &[u8], msg: &[u8]) -> bool {
|
||||
if public_key.len() == 32 && signature.len() >= 64 {
|
||||
ed25519_dalek::PublicKey::from_bytes(public_key).map_or(false, |pk| {
|
||||
let mut h = ed25519_dalek::Sha512::new();
|
||||
let _ = h.write_all(msg);
|
||||
let sig: [u8; 64] = signature[0..64].try_into().unwrap();
|
||||
pk.verify_prehashed(h, None, &ed25519_dalek::Signature::from(sig)).is_ok()
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,32 @@ use std::mem::MaybeUninit;
|
|||
use std::convert::TryInto;
|
||||
use std::io::Write;
|
||||
|
||||
pub const SHA512_HASH_SIZE: usize = 64;
|
||||
|
||||
pub struct SHA512(gcrypt::digest::MessageDigest);
|
||||
|
||||
impl SHA512 {
|
||||
#[inline(always)]
|
||||
pub fn hash(b: &[u8]) -> [u8; 64] {
|
||||
pub fn hash(b: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
||||
let mut h = unsafe { MaybeUninit::<[u8; 64]>::uninit().assume_init() };
|
||||
gcrypt::digest::hash(gcrypt::digest::Algorithm::Sha512, b, &mut h);
|
||||
h
|
||||
}
|
||||
|
||||
/// Compute HMAC-SHA512(key, msg)
|
||||
#[inline(always)]
|
||||
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
|
||||
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
|
||||
let _ = m.set_key(key);
|
||||
let _ = m.update(msg);
|
||||
let mut h = [0_u8; SHA512_HASH_SIZE];
|
||||
m.get_mac(&mut h);
|
||||
h
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
SHA512(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha512).unwrap())
|
||||
Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha512).unwrap())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -28,60 +41,25 @@ impl SHA512 {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish(&mut self) -> [u8; 64] {
|
||||
pub fn finish(&mut self) -> [u8; SHA512_HASH_SIZE] {
|
||||
self.0.finish();
|
||||
self.0.get_only_digest().unwrap().try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Return a reference to an internally stored result.
|
||||
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
|
||||
#[inline(always)]
|
||||
pub fn finish_get_ref(&mut self) -> &[u8] {
|
||||
self.0.finish();
|
||||
self.0.get_only_digest().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for SHA512 {
|
||||
#[inline(always)]
|
||||
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.write(b)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.0.flush()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SHA384(gcrypt::digest::MessageDigest);
|
||||
|
||||
impl SHA384 {
|
||||
#[inline(always)]
|
||||
pub fn hash(b: &[u8]) -> [u8; 48] {
|
||||
let mut h = unsafe { MaybeUninit::<[u8; 48]>::uninit().assume_init() };
|
||||
gcrypt::digest::hash(gcrypt::digest::Algorithm::Sha512, b, &mut h);
|
||||
h
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
SHA384(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha384).unwrap())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset(&mut self) {
|
||||
self.0.reset();
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&mut self, b: &[u8]) {
|
||||
self.0.update(b);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish(&mut self) -> [u8; 48] {
|
||||
self.0.finish();
|
||||
self.0.get_only_digest().unwrap().try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for SHA384 {
|
||||
#[inline(always)]
|
||||
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.write(b)
|
||||
Ok(b.len())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -1 +1,219 @@
|
|||
use std::str::FromStr;
|
||||
use std::convert::TryInto;
|
||||
use std::io::Write;
|
||||
|
||||
use gcrypt::sexp::SExpression;
|
||||
|
||||
pub const P521_PUBLIC_KEY_SIZE: usize = 132;
|
||||
pub const P521_SECRET_KEY_SIZE: usize = 66;
|
||||
pub const P521_ECDSA_SIGNATURE_SIZE: usize = 132;
|
||||
pub const P521_ECDH_SHARED_SECRET_SIZE: usize = 132;
|
||||
|
||||
/*
|
||||
fn dump_sexp(exp: &SExpression) {
|
||||
if exp.len() == 1 {
|
||||
let s = exp.get_str(0);
|
||||
if s.is_ok() {
|
||||
print!("{}", s.unwrap());
|
||||
} else {
|
||||
let b = exp.get_bytes(0);
|
||||
if b.is_some() {
|
||||
print!("#{}#", crate::util::hex::to_string(b.unwrap()));
|
||||
} else {
|
||||
print!("()");
|
||||
}
|
||||
}
|
||||
} else if exp.len() > 0 {
|
||||
for i in 0..exp.len() {
|
||||
let v = exp.get(i as u32);
|
||||
if v.is_some() {
|
||||
if i == 0 {
|
||||
print!("(");
|
||||
} else {
|
||||
print!(" ");
|
||||
}
|
||||
dump_sexp(&v.unwrap());
|
||||
}
|
||||
}
|
||||
print!(")");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[inline(always)]
|
||||
fn hash_to_data_sexp(msg: &[u8]) -> [u8; 155] {
|
||||
let h = crate::crypto::hash::SHA512::hash(msg);
|
||||
let mut d = [0_u8; 155];
|
||||
d[0..24].copy_from_slice(b"(data(flags raw)(value #");
|
||||
let mut j = 24;
|
||||
for i in 0..64 {
|
||||
let b = h[i] as usize;
|
||||
d[j] = crate::util::hex::HEX_CHARS[b >> 4];
|
||||
d[j + 1] = crate::util::hex::HEX_CHARS[b & 0xf];
|
||||
j += 2;
|
||||
}
|
||||
d[152..155].copy_from_slice(b"#))");
|
||||
d
|
||||
}
|
||||
|
||||
pub struct P521PublicKey {
|
||||
public_key: SExpression,
|
||||
public_key_bytes: [u8; P521_PUBLIC_KEY_SIZE],
|
||||
}
|
||||
|
||||
/// NIST P-521 elliptic curve key pair.
|
||||
/// This supports both ECDSA signing and ECDH key agreement. In practice the same key pair
|
||||
/// is not used for both functions as this is considred bad practice.
|
||||
pub struct P521KeyPair {
|
||||
public_key: P521PublicKey,
|
||||
secret_key_for_ecdsa: SExpression, // secret key as a private-key S-expression
|
||||
secret_key_for_ecdh: SExpression, // secret key as a "data" S-expression for the weird gcrypt ECDH interface
|
||||
secret_key_bytes: [u8; P521_SECRET_KEY_SIZE],
|
||||
}
|
||||
|
||||
impl P521KeyPair {
|
||||
/// Generate a NIST P-521 key pair.
|
||||
/// If transient is true a faster but possibly somewhat less intensive pseudo-random number
|
||||
/// generator is used. This is for ephemeral keys, and has no effect on some platforms.
|
||||
pub fn generate(transient: bool) -> Option<P521KeyPair> {
|
||||
let sexp = SExpression::from_str(if transient { "(genkey(ecc(curve nistp521)(flags nocomp transient-key)))" } else { "(genkey(ecc(curve nistp521)(flags nocomp)))" }).unwrap();
|
||||
gcrypt::pkey::generate_key(&sexp).map_or(None, |kp| {
|
||||
let pk_exp = kp.find_token("public-key");
|
||||
let sk_exp = kp.find_token("private-key");
|
||||
if pk_exp.is_some() && sk_exp.is_some() {
|
||||
let pk_exp = pk_exp.unwrap();
|
||||
let sk_exp = sk_exp.unwrap();
|
||||
let pk = pk_exp.find_token("q");
|
||||
let sk = sk_exp.find_token("d");
|
||||
if pk.is_some() && sk.is_some() {
|
||||
let pktmp = pk.unwrap();
|
||||
let sktmp = sk.unwrap();
|
||||
let pk = pktmp.get_bytes(1);
|
||||
let sk = sktmp.get_bytes(1);
|
||||
if pk.is_some() && sk.is_some() {
|
||||
let pk = pk.unwrap();
|
||||
let sk = sk.unwrap();
|
||||
let mut kp = P521KeyPair {
|
||||
public_key: P521PublicKey {
|
||||
public_key: pk_exp,
|
||||
public_key_bytes: [0_u8; P521_PUBLIC_KEY_SIZE],
|
||||
},
|
||||
secret_key_for_ecdsa: sk_exp,
|
||||
secret_key_for_ecdh: SExpression::from_str(format!("(data(flags raw)(value #{}#))", crate::util::hex::to_string(sk)).as_str()).unwrap(),
|
||||
secret_key_bytes: [0_u8; P521_SECRET_KEY_SIZE],
|
||||
};
|
||||
kp.public_key.public_key_bytes[((P521_PUBLIC_KEY_SIZE + 1) - pk.len())..P521_PUBLIC_KEY_SIZE].copy_from_slice(&pk[1..]);
|
||||
kp.secret_key_bytes[(P521_SECRET_KEY_SIZE - sk.len())..P521_SECRET_KEY_SIZE].copy_from_slice(sk);
|
||||
return Some(kp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn public_key(&self) -> &P521PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
/// Get the raw ECC public "q" point for this key pair.
|
||||
/// The returned point is not compressed. To use this with other interfaces that expect a format
|
||||
/// prefix, prepend 0x04 to the beginning of this public key. This prefix is always the same in
|
||||
/// our system and so is omitted.
|
||||
#[inline(always)]
|
||||
pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] {
|
||||
&self.public_key.public_key_bytes
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn secret_key_bytes(&self) -> &[u8; P521_SECRET_KEY_SIZE] {
|
||||
&self.secret_key_bytes
|
||||
}
|
||||
|
||||
/// Create an ECDSA signature of the input message.
|
||||
pub fn sign(&self, msg: &[u8]) -> Option<[u8; P521_ECDSA_SIGNATURE_SIZE]> {
|
||||
let data = SExpression::from_str(unsafe { std::str::from_utf8_unchecked(&hash_to_data_sexp(msg)) }).unwrap();
|
||||
gcrypt::pkey::sign(&self.secret_key_for_ecdsa, &data).map_or(None, |sig| {
|
||||
let mut sig_bytes = [0_u8; P521_ECDSA_SIGNATURE_SIZE];
|
||||
if sig.find_token("r").map_or(false, |r| r.get_bytes(1).map_or(false, |r| {
|
||||
sig_bytes[(66 - r.len())..66].copy_from_slice(r);
|
||||
true
|
||||
} )) && sig.find_token("s").map_or(false, |s| s.get_bytes(1).map_or(false, |s| {
|
||||
sig_bytes[(66 + (66 - s.len()))..132].copy_from_slice(s);
|
||||
true
|
||||
})) {
|
||||
Some(sig_bytes)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute ECDH key agreement, returning a raw (un-hashed) shared secret.
|
||||
pub fn agree(&self, other_public: &P521PublicKey) -> Option<[u8; P521_ECDH_SHARED_SECRET_SIZE]> {
|
||||
gcrypt::pkey::encrypt(&other_public.public_key, &self.secret_key_for_ecdh).map_or(None, |k| {
|
||||
k.find_token("s").map_or(None, |s| s.get_bytes(1).map_or(None, |sb| {
|
||||
Some(sb[1..].try_into().unwrap())
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl P521PublicKey {
|
||||
pub fn from_bytes(b: &[u8]) -> Option<P521PublicKey> {
|
||||
if b.len() == P521_PUBLIC_KEY_SIZE {
|
||||
Some(P521PublicKey {
|
||||
public_key: SExpression::from_str(format!("(public-key(ecc(curve nistp521)(q #04{}#)))", crate::util::hex::to_string(b)).as_str()).unwrap(),
|
||||
public_key_bytes: b.try_into().unwrap(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool {
|
||||
if signature.len() == P521_ECDSA_SIGNATURE_SIZE {
|
||||
let data = SExpression::from_str(unsafe { std::str::from_utf8_unchecked(&hash_to_data_sexp(msg)) }).unwrap();
|
||||
let sig = SExpression::from_str(format!("(sig-val(ecdsa(r #{}#)(s #{}#)))", crate::util::hex::to_string(&signature[0..66]), crate::util::hex::to_string(&signature[66..132])).as_str()).unwrap();
|
||||
gcrypt::pkey::verify(&self.public_key, &data, &sig).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] {
|
||||
&self.public_key_bytes
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> [u8; P521_PUBLIC_KEY_SIZE] {
|
||||
self.public_key_bytes.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::crypto::p521::P521KeyPair;
|
||||
|
||||
#[test]
|
||||
fn generate_sign_verify_agree() {
|
||||
let kp = P521KeyPair::generate(false).unwrap();
|
||||
let kp2 = P521KeyPair::generate(false).unwrap();
|
||||
|
||||
let sig = kp.sign(&[0_u8]).unwrap();
|
||||
if !kp.public_key().verify(&[0_u8], &sig) {
|
||||
panic!("ECDSA verify failed");
|
||||
}
|
||||
if kp.public_key().verify(&[1_u8], &sig) {
|
||||
panic!("ECDSA verify succeeded for incorrect message");
|
||||
}
|
||||
|
||||
let sec0 = kp.agree(kp2.public_key()).unwrap();
|
||||
let sec1 = kp2.agree(kp.public_key()).unwrap();
|
||||
if !sec0.eq(&sec1) {
|
||||
panic!("ECDH secrets do not match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
mod crypto;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
}
|
||||
}
|
||||
pub mod crypto;
|
||||
pub mod vl1;
|
||||
pub mod util;
|
||||
|
|
25
network-hypervisor/src/util/hex.rs
Normal file
25
network-hypervisor/src/util/hex.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
pub(crate) const HEX_CHARS: [u8; 16] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f'];
|
||||
|
||||
/// Encode a binary string to a series of hex bytes.
|
||||
pub fn to_string(b: &[u8]) -> String {
|
||||
let mut s = String::new();
|
||||
s.reserve(b.len() * 2);
|
||||
for c in b {
|
||||
let x = *c as usize;
|
||||
s.push(HEX_CHARS[x >> 4] as char);
|
||||
s.push(HEX_CHARS[x & 0xf] as char);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Encode bytes from 'b' into hex characters in 'dest'.
|
||||
/// This will panic if the destination slice is smaller than twice the length of the source.
|
||||
pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) {
|
||||
let mut j = 0;
|
||||
for c in b {
|
||||
let x = *c as usize;
|
||||
dest[j] = HEX_CHARS[x >> 4];
|
||||
dest[j + 1] = HEX_CHARS[x & 0xf];
|
||||
j += 2;
|
||||
}
|
||||
}
|
1
network-hypervisor/src/util/mod.rs
Normal file
1
network-hypervisor/src/util/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod hex;
|
0
network-hypervisor/src/vl1/mod.rs
Normal file
0
network-hypervisor/src/vl1/mod.rs
Normal file
Loading…
Add table
Reference in a new issue