Switch to Ed25519 signature + XChaCha20

also add infoq nodeinfo
This commit is contained in:
John Smith 2022-04-03 21:35:14 -04:00
parent a33473d8ea
commit fe1754b84b
8 changed files with 135 additions and 46 deletions

1
Cargo.lock generated
View File

@ -4186,6 +4186,7 @@ dependencies = [
"capnp", "capnp",
"capnpc", "capnpc",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"chacha20",
"chacha20poly1305", "chacha20poly1305",
"chrono", "chrono",
"config 0.12.0", "config 0.12.0",

View File

@ -25,6 +25,7 @@ hex = "^0"
generic-array = "^0" generic-array = "^0"
secrecy = "^0" secrecy = "^0"
chacha20poly1305 = "^0" chacha20poly1305 = "^0"
chacha20 = "^0"
hashlink = { path = "../external/hashlink", features = ["serde_impl"] } hashlink = { path = "../external/hashlink", features = ["serde_impl"] }
serde-big-array = "^0" serde-big-array = "^0"
futures-util = { version = "^0", default_features = false, features = ["alloc"] } futures-util = { version = "^0", default_features = false, features = ["alloc"] }

View File

@ -2,6 +2,8 @@ use super::key::*;
use crate::intf::*; use crate::intf::*;
use crate::xx::*; use crate::xx::*;
use crate::*; use crate::*;
use chacha20::cipher::{NewCipher, StreamCipher};
use chacha20::XChaCha20;
use chacha20poly1305 as ch; use chacha20poly1305 as ch;
use chacha20poly1305::aead::{AeadInPlace, NewAead}; use chacha20poly1305::aead::{AeadInPlace, NewAead};
use core::convert::TryInto; use core::convert::TryInto;
@ -16,7 +18,7 @@ pub type SharedSecret = [u8; 32];
pub type Nonce = [u8; 24]; pub type Nonce = [u8; 24];
const DH_CACHE_SIZE: usize = 1024; const DH_CACHE_SIZE: usize = 1024;
pub const ENCRYPTION_OVERHEAD: usize = 16; pub const AEAD_OVERHEAD: usize = 16;
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
struct DHCacheKey { struct DHCacheKey {
@ -222,7 +224,7 @@ impl Crypto {
s s
} }
pub fn decrypt_in_place( pub fn decrypt_in_place_aead(
body: &mut Vec<u8>, body: &mut Vec<u8>,
nonce: &Nonce, nonce: &Nonce,
shared_secret: &SharedSecret, shared_secret: &SharedSecret,
@ -236,20 +238,20 @@ impl Crypto {
.map_err(logthru_crypto!()) .map_err(logthru_crypto!())
} }
pub fn decrypt( pub fn decrypt_aead(
body: &[u8], body: &[u8],
nonce: &Nonce, nonce: &Nonce,
shared_secret: &SharedSecret, shared_secret: &SharedSecret,
associated_data: Option<&[u8]>, associated_data: Option<&[u8]>,
) -> Result<Vec<u8>, String> { ) -> Result<Vec<u8>, String> {
let mut out = body.to_vec(); let mut out = body.to_vec();
Self::decrypt_in_place(&mut out, nonce, shared_secret, associated_data) Self::decrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
.map_err(map_to_string) .map_err(map_to_string)
.map_err(logthru_crypto!())?; .map_err(logthru_crypto!())?;
Ok(out) Ok(out)
} }
pub fn encrypt_in_place( pub fn encrypt_in_place_aead(
body: &mut Vec<u8>, body: &mut Vec<u8>,
nonce: &Nonce, nonce: &Nonce,
shared_secret: &SharedSecret, shared_secret: &SharedSecret,
@ -264,16 +266,27 @@ impl Crypto {
.map_err(logthru_crypto!()) .map_err(logthru_crypto!())
} }
pub fn encrypt( pub fn encrypt_aead(
body: &[u8], body: &[u8],
nonce: &Nonce, nonce: &Nonce,
shared_secret: &SharedSecret, shared_secret: &SharedSecret,
associated_data: Option<&[u8]>, associated_data: Option<&[u8]>,
) -> Result<Vec<u8>, String> { ) -> Result<Vec<u8>, String> {
let mut out = body.to_vec(); let mut out = body.to_vec();
Self::encrypt_in_place(&mut out, nonce, shared_secret, associated_data) Self::encrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
.map_err(map_to_string) .map_err(map_to_string)
.map_err(logthru_crypto!())?; .map_err(logthru_crypto!())?;
Ok(out) Ok(out)
} }
pub fn crypt_in_place_no_auth(body: &mut Vec<u8>, nonce: &Nonce, shared_secret: &SharedSecret) {
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
cipher.apply_keystream(body);
}
pub fn crypt_no_auth(body: &[u8], nonce: &Nonce, shared_secret: &SharedSecret) -> Vec<u8> {
let mut out = body.to_vec();
Self::crypt_in_place_no_auth(&mut out, nonce, shared_secret);
out
}
} }

View File

@ -29,14 +29,13 @@ use core::convert::TryInto;
// sender_id: [u8; 32], // 0x2A: Node ID of the message source, which is the Ed25519 public key of the sender (must be verified with find_node if this is a new node_id/address combination) // sender_id: [u8; 32], // 0x2A: Node ID of the message source, which is the Ed25519 public key of the sender (must be verified with find_node if this is a new node_id/address combination)
// recipient_id: [u8; 32], // 0x4A: Node ID of the intended recipient, which is the Ed25519 public key of the recipient (must be the receiving node, or a relay lease holder) // recipient_id: [u8; 32], // 0x4A: Node ID of the intended recipient, which is the Ed25519 public key of the recipient (must be the receiving node, or a relay lease holder)
// // 0x6A: message is appended (operations) // // 0x6A: message is appended (operations)
// // encrypted by XChaCha20Poly1305(nonce,x25519(recipient_id, sender_secret_key)) // // encrypted by XChaCha20Poly1305(nonce,x25519(recipient_id, sender_secret_key))
// // decryptable by XChaCha20Poly1305(nonce,x25519(sender_id, recipient_secret_key)) // signature: [u8; 64], // 0x?? (end-0x40): Ed25519 signature of the entire envelope including header is appended to the packet
// // entire header needs to be included in message digest, relays are not allowed to modify the envelope without invalidating the signature. // // entire header needs to be included in message digest, relays are not allowed to modify the envelope without invalidating the signature.
// } // }
pub const MAX_ENVELOPE_SIZE: usize = 65507; pub const MAX_ENVELOPE_SIZE: usize = 65507;
pub const MIN_ENVELOPE_SIZE: usize = 106; pub const MIN_ENVELOPE_SIZE: usize = 0x6A + 0x40; // Header + Signature
pub const AEAD_ADDITIONAL_SIZE: usize = 16;
pub const ENVELOPE_MAGIC: &[u8; 4] = b"VLID"; pub const ENVELOPE_MAGIC: &[u8; 4] = b"VLID";
pub const MIN_VERSION: u8 = 0u8; pub const MIN_VERSION: u8 = 0u8;
pub const MAX_VERSION: u8 = 0u8; pub const MAX_VERSION: u8 = 0u8;
@ -77,7 +76,7 @@ impl Envelope {
} }
} }
pub fn from_data(data: &[u8]) -> Result<Envelope, ()> { pub fn from_signed_data(data: &[u8]) -> Result<Envelope, ()> {
// Ensure we are at least the length of the envelope // Ensure we are at least the length of the envelope
if data.len() < MIN_ENVELOPE_SIZE { if data.len() < MIN_ENVELOPE_SIZE {
trace!("envelope too small: len={}", data.len()); trace!("envelope too small: len={}", data.len());
@ -155,6 +154,12 @@ impl Envelope {
return Err(()); return Err(());
} }
// Get signature
let signature = DHTSignature::new(data[(data.len() - 64)..].try_into().map_err(drop)?);
// Validate signature
verify(&sender_id, &data[0..(data.len() - 64)], &signature).map_err(drop)?;
// Return envelope // Return envelope
Ok(Self { Ok(Self {
version, version,
@ -176,13 +181,8 @@ impl Envelope {
// Get DH secret // Get DH secret
let dh_secret = crypto.cached_dh(&self.sender_id, node_id_secret)?; let dh_secret = crypto.cached_dh(&self.sender_id, node_id_secret)?;
// Decrypt message and authenticate, including the envelope header as associated data to authenticate // Decrypt message without authentication
let body = Crypto::decrypt( let body = Crypto::crypt_no_auth(&data[0x6A..data.len() - 64], &self.nonce, &dh_secret);
&data[0x6A..],
&self.nonce,
&dh_secret,
Some(&data[0..MIN_ENVELOPE_SIZE]),
)?;
Ok(body) Ok(body)
} }
@ -203,7 +203,7 @@ impl Envelope {
} }
// Ensure body isn't too long // Ensure body isn't too long
let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE + AEAD_ADDITIONAL_SIZE; let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE;
if envelope_size > MAX_ENVELOPE_SIZE { if envelope_size > MAX_ENVELOPE_SIZE {
return Err(()); return Err(());
} }
@ -234,11 +234,23 @@ impl Envelope {
.map_err(drop)?; .map_err(drop)?;
// Encrypt and authenticate message // Encrypt and authenticate message
let encrypted_body = let encrypted_body = Crypto::crypt_no_auth(body, &self.nonce, &dh_secret);
Crypto::encrypt(body, &self.nonce, &dh_secret, Some(&data[0..0x6A])).map_err(drop)?;
// Write body // Write body
data[0x6A..].copy_from_slice(encrypted_body.as_slice()); if !encrypted_body.is_empty() {
data[0x6A..envelope_size - 64].copy_from_slice(encrypted_body.as_slice());
}
// Sign the envelope
let signature = sign(
&self.sender_id,
node_id_secret,
&data[0..(envelope_size - 64)],
)
.map_err(drop)?;
// Append the signature
data[(envelope_size - 64)..].copy_from_slice(&signature.bytes);
Ok(data) Ok(data)
} }

View File

@ -541,7 +541,7 @@ impl NetworkManager {
// Decode envelope header (may fail signature validation) // Decode envelope header (may fail signature validation)
let envelope = let envelope =
Envelope::from_data(data).map_err(|_| "envelope failed to decode".to_owned())?; Envelope::from_signed_data(data).map_err(|_| "envelope failed to decode".to_owned())?;
// Get routing table and rpc processor // Get routing table and rpc processor
let (routing_table, rpc) = { let (routing_table, rpc) = {

View File

@ -93,7 +93,7 @@ impl RPCProcessor {
) )
.map_err(map_error_internal!("dh failed"))?; .map_err(map_error_internal!("dh failed"))?;
let enc_msg_data = let enc_msg_data =
Crypto::encrypt(blob_data.as_slice(), &nonce, &dh_secret, None) Crypto::encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None)
.map_err(map_error_internal!("encryption failed"))?; .map_err(map_error_internal!("encryption failed"))?;
rhd_builder.set_blob(enc_msg_data.as_slice()); rhd_builder.set_blob(enc_msg_data.as_slice());
@ -118,7 +118,7 @@ impl RPCProcessor {
&safety_route.secret_key, &safety_route.secret_key,
) )
.map_err(map_error_internal!("dh failed"))?; .map_err(map_error_internal!("dh failed"))?;
let enc_msg_data = Crypto::encrypt(blob_data.as_slice(), &nonce, &dh_secret, None) let enc_msg_data = Crypto::encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None)
.map_err(map_error_internal!("encryption failed"))?; .map_err(map_error_internal!("encryption failed"))?;
first_rhd_builder.set_blob(enc_msg_data.as_slice()); first_rhd_builder.set_blob(enc_msg_data.as_slice());
@ -148,7 +148,7 @@ impl RPCProcessor {
.crypto .crypto
.cached_dh(&pr_pk, &sr.secret_key) .cached_dh(&pr_pk, &sr.secret_key)
.map_err(map_error_internal!("dh failed"))?; .map_err(map_error_internal!("dh failed"))?;
let enc_msg_data = Crypto::encrypt(&message_data, &nonce, &dh_secret, None) let enc_msg_data = Crypto::encrypt_aead(&message_data, &nonce, &dh_secret, None)
.map_err(map_error_internal!("encryption failed"))?; .map_err(map_error_internal!("encryption failed"))?;
// Prepare route operation // Prepare route operation

View File

@ -21,8 +21,8 @@ async fn shutdown(api: VeilidAPI) {
trace!("test_table_store: finished"); trace!("test_table_store: finished");
} }
pub async fn test_enc_dec() { pub async fn test_aead() {
trace!("test_enc_dec"); trace!("test_aead");
let n1 = Crypto::get_random_nonce(); let n1 = Crypto::get_random_nonce();
let n2 = loop { let n2 = loop {
@ -44,52 +44,101 @@ pub async fn test_enc_dec() {
let body2 = body.clone(); let body2 = body.clone();
let size_before_encrypt = body.len(); let size_before_encrypt = body.len();
assert!( assert!(
Crypto::encrypt_in_place(&mut body, &n1, &ss1, None).is_ok(), Crypto::encrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
"encrypt should succeed" "encrypt should succeed"
); );
let size_after_encrypt = body.len(); let size_after_encrypt = body.len();
assert!( assert!(
size_after_encrypt - size_before_encrypt == ENCRYPTION_OVERHEAD, size_after_encrypt - size_before_encrypt == AEAD_OVERHEAD,
"overhead should match" "overhead should match"
); );
let mut body3 = body.clone(); let mut body3 = body.clone();
let mut body4 = body.clone(); let mut body4 = body.clone();
let mut body5 = body.clone(); let mut body5 = body.clone();
assert!( assert!(
Crypto::decrypt_in_place(&mut body, &n1, &ss1, None).is_ok(), Crypto::decrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
"decrypt should succeed" "decrypt should succeed"
); );
assert_eq!(body, body2, "results should be the same"); assert_eq!(body, body2, "results should be the same");
assert!( assert!(
Crypto::decrypt_in_place(&mut body3, &n2, &ss1, None).is_err(), Crypto::decrypt_in_place_aead(&mut body3, &n2, &ss1, None).is_err(),
"decrypt with wrong nonce should fail" "decrypt with wrong nonce should fail"
); );
assert_ne!(body3, body, "failure changes data"); assert_ne!(body3, body, "failure changes data");
assert!( assert!(
Crypto::decrypt_in_place(&mut body4, &n1, &ss2, None).is_err(), Crypto::decrypt_in_place_aead(&mut body4, &n1, &ss2, None).is_err(),
"decrypt with wrong secret should fail" "decrypt with wrong secret should fail"
); );
assert_ne!(body4, body, "failure changes data"); assert_ne!(body4, body, "failure changes data");
assert!( assert!(
Crypto::decrypt_in_place(&mut body5, &n1, &ss2, Some(b"foobar")).is_err(), Crypto::decrypt_in_place_aead(&mut body5, &n1, &ss2, Some(b"foobar")).is_err(),
"decrypt with wrong associated data should fail" "decrypt with wrong associated data should fail"
); );
assert_ne!(body5, body, "failure changes data"); assert_ne!(body5, body, "failure changes data");
assert!( assert!(
Crypto::decrypt(LOREM_IPSUM, &n1, &ss1, None).is_err(), Crypto::decrypt_aead(LOREM_IPSUM, &n1, &ss1, None).is_err(),
"should fail authentication" "should fail authentication"
); );
let body5 = Crypto::encrypt(LOREM_IPSUM, &n1, &ss1, None).unwrap(); let body5 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
let body6 = Crypto::decrypt(&body5, &n1, &ss1, None).unwrap(); let body6 = Crypto::decrypt_aead(&body5, &n1, &ss1, None).unwrap();
let body7 = Crypto::encrypt(LOREM_IPSUM, &n1, &ss1, None).unwrap(); let body7 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
assert_eq!(body6, LOREM_IPSUM); assert_eq!(body6, LOREM_IPSUM);
assert_eq!(body5, body7); assert_eq!(body5, body7);
} }
pub async fn test_no_auth() {
trace!("test_no_auth");
let n1 = Crypto::get_random_nonce();
let n2 = loop {
let n = Crypto::get_random_nonce();
if n != n1 {
break n;
}
};
let ss1 = Crypto::get_random_secret();
let ss2 = loop {
let ss = Crypto::get_random_secret();
if ss != ss1 {
break ss;
}
};
let mut body = LOREM_IPSUM.to_vec();
let body2 = body.clone();
let size_before_encrypt = body.len();
Crypto::crypt_in_place_no_auth(&mut body, &n1, &ss1);
let size_after_encrypt = body.len();
assert_eq!(
size_after_encrypt, size_before_encrypt,
"overhead should match"
);
let mut body3 = body.clone();
let mut body4 = body.clone();
Crypto::crypt_in_place_no_auth(&mut body, &n1, &ss1);
assert_eq!(body, body2, "result after decrypt should be the same");
Crypto::crypt_in_place_no_auth(&mut body3, &n2, &ss1);
assert_ne!(body3, body, "decrypt should not be equal with wrong nonce");
Crypto::crypt_in_place_no_auth(&mut body4, &n1, &ss2);
assert_ne!(body4, body, "decrypt should not be equal with wrong secret");
let body5 = Crypto::crypt_no_auth(LOREM_IPSUM, &n1, &ss1);
let body6 = Crypto::crypt_no_auth(&body5, &n1, &ss1);
let body7 = Crypto::crypt_no_auth(LOREM_IPSUM, &n1, &ss1);
assert_eq!(body6, LOREM_IPSUM);
assert_eq!(body5, body7);
}
pub async fn test_dh(crypto: Crypto) { pub async fn test_dh(crypto: Crypto) {
trace!("test_dh"); trace!("test_dh");
let (dht_key, dht_key_secret) = key::generate_secret(); let (dht_key, dht_key_secret) = key::generate_secret();
@ -119,7 +168,8 @@ pub async fn test_dh(crypto: Crypto) {
pub async fn test_all() { pub async fn test_all() {
let api = startup().await; let api = startup().await;
let crypto = api.crypto().unwrap(); let crypto = api.crypto().unwrap();
test_enc_dec().await; test_aead().await;
test_no_auth().await;
test_dh(crypto).await; test_dh(crypto).await;
shutdown(api.clone()).await; shutdown(api.clone()).await;
assert!(api.is_shutdown()); assert!(api.is_shutdown());

View File

@ -33,19 +33,31 @@ pub async fn test_envelope_round_trip() {
// Deserialize from bytes // Deserialize from bytes
let envelope2 = let envelope2 =
Envelope::from_data(&enc_data).expect("failed to deserialize envelope from data"); Envelope::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
let body2 = envelope2 let body2 = envelope2
.decrypt_body(crypto.clone(), &enc_data, &recipient_secret) .decrypt_body(crypto.clone(), &enc_data, &recipient_secret)
.expect("failed to decrypt envelope body"); .expect("failed to decrypt envelope body");
envelope2
.decrypt_body(crypto.clone(), &enc_data, &sender_secret)
.expect_err("should have failed to decrypt using wrong secret");
// Compare envelope and body // Compare envelope and body
assert_eq!(envelope, envelope2); assert_eq!(envelope, envelope2);
assert_eq!(body.to_vec(), body2); assert_eq!(body.to_vec(), body2);
// Deserialize from modified bytes
let enc_data_len = enc_data.len();
let mut mod_enc_data = enc_data.clone();
mod_enc_data[enc_data_len - 1] ^= 0x80u8;
assert!(
Envelope::from_signed_data(&mod_enc_data).is_err(),
"should have failed to decode envelope with modified signature"
);
let mut mod_enc_data2 = enc_data.clone();
mod_enc_data2[enc_data_len - 65] ^= 0x80u8;
assert!(
Envelope::from_signed_data(&mod_enc_data2).is_err(),
"should have failed to decode envelope with modified data"
);
api.shutdown().await; api.shutdown().await;
} }