ZeroTierOne/network-hypervisor/src/crypto/p521.rs

258 lines
10 KiB
Rust

use std::str::FromStr;
use std::convert::TryInto;
use gcrypt::sexp::SExpression;
use crate::crypto::secret::Secret;
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, // the same secret key as a "data" S-expression for the weird gcrypt ECDH interface
secret_key_bytes: Secret<{ 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: SExpression::from_str(format!("(private-key(ecc(curve nistp521)(q #{}#)(d #{}#)))", crate::util::hex::to_string(pk), crate::util::hex::to_string(sk)).as_str()).unwrap(),
secret_key_for_ecdh: SExpression::from_str(format!("(data(flags raw)(value #{}#))", crate::util::hex::to_string(sk)).as_str()).unwrap(),
secret_key_bytes: Secret::default(),
};
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.0[(P521_SECRET_KEY_SIZE - sk.len())..P521_SECRET_KEY_SIZE].copy_from_slice(sk);
return Some(kp);
}
}
}
return None;
})
}
/// Construct this key pair from both a public and a private key.
pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option<P521KeyPair> {
if secret_bytes.len() != P521_SECRET_KEY_SIZE {
return None;
}
let public_key = P521PublicKey::from_bytes(public_bytes);
if public_key.is_none() {
return None;
}
Some(P521KeyPair {
public_key: public_key.unwrap(),
secret_key_for_ecdsa: SExpression::from_str(format!("(private-key(ecc(curve nistp521)(q #04{}#)(d #{}#)))", crate::util::hex::to_string(public_bytes), crate::util::hex::to_string(secret_bytes)).as_str()).unwrap(),
secret_key_for_ecdh: SExpression::from_str(format!("(data(flags raw)(value #{}#))", crate::util::hex::to_string(secret_bytes)).as_str()).unwrap(),
secret_key_bytes: Secret::from_bytes(secret_bytes),
})
}
#[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) -> &Secret<{ P521_SECRET_KEY_SIZE }> {
&self.secret_key_bytes
}
/// Create an ECDSA signature of the input message.
/// Message data does not need to be pre-hashed.
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<Secret<{ 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(Secret(sb[1..].try_into().unwrap()))
}))
})
}
}
impl P521PublicKey {
/// Construct a public key from a byte serialized representation.
/// None is returned if the input is not valid. No advanced checking such as
/// determining if this is a point on the curve is performed.
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
}
}
/// Verify a signature.
/// Message data does not need to be pre-hashed.
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
}
}
impl PartialEq for P521PublicKey {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.public_key_bytes.eq(&other.public_key_bytes)
}
}
impl Eq for P521PublicKey {}
impl Clone for P521PublicKey {
fn clone(&self) -> Self {
P521PublicKey::from_bytes(&self.public_key_bytes).unwrap()
}
}
#[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");
}
let kp3 = P521KeyPair::from_bytes(kp.public_key_bytes(), kp.secret_key_bytes().as_ref()).unwrap();
let sig = kp3.sign(&[3_u8]).unwrap();
if !kp.public_key().verify(&[3_u8], &sig) {
panic!("ECDSA verify failed (from key reconstructed from bytes)");
}
}
}