checkpoint

This commit is contained in:
John Smith
2022-10-30 19:29:31 -04:00
parent d94a023c32
commit 50718b7074
35 changed files with 334 additions and 220 deletions

View File

@@ -0,0 +1,292 @@
#![allow(dead_code)]
#![allow(clippy::absurd_extreme_comparisons)]
use super::*;
use crate::xx::*;
use crate::*;
use core::convert::TryInto;
// #[repr(C, packed)]
// struct EnvelopeHeader {
// // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
// magic: [u8; 4], // 0x00: 0x56 0x4C 0x49 0x44 ("VLID")
// version: u8, // 0x04: 0 = EnvelopeV0
// min_version: u8, // 0x05: 0 = EnvelopeV0
// max_version: u8, // 0x06: 0 = EnvelopeV0
// reserved: u8, // 0x07: Reserved for future use
// }
// #[repr(C, packed)]
// struct EnvelopeV0 {
// // Size is 106 bytes.
// magic: [u8; 4], // 0x00: 0x56 0x4C 0x49 0x44 ("VLID")
// version: u8, // 0x04: 0 = EnvelopeV0
// min_version: u8, // 0x05: 0 = EnvelopeV0
// max_version: u8, // 0x06: 0 = EnvelopeV0
// reserved: u8, // 0x07: Reserved for future use
// size: u16, // 0x08: Total size of the envelope including the encrypted operations message. Maximum size is 65,507 bytes, which is the data size limit for a single UDP message on IPv4.
// timestamp: u64, // 0x0A: Duration since UNIX_EPOCH in microseconds when this message is sent. Messages older than 10 seconds are dropped.
// nonce: [u8; 24], // 0x12: Random nonce for replay protection and for x25519
// 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)
// // 0x6A: message is appended (operations)
// // encrypted by XChaCha20Poly1305(nonce,x25519(recipient_id, sender_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.
// }
pub const MAX_ENVELOPE_SIZE: usize = 65507;
pub const MIN_ENVELOPE_SIZE: usize = 0x6A + 0x40; // Header + Signature
pub const ENVELOPE_MAGIC: &[u8; 4] = b"VLID";
pub type EnvelopeNonce = [u8; 24];
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Envelope {
version: u8,
min_version: u8,
max_version: u8,
timestamp: u64,
nonce: EnvelopeNonce,
sender_id: DHTKey,
recipient_id: DHTKey,
}
impl Envelope {
pub fn new(
version: u8,
timestamp: u64,
nonce: EnvelopeNonce,
sender_id: DHTKey,
recipient_id: DHTKey,
) -> Self {
assert!(sender_id.valid);
assert!(recipient_id.valid);
assert!(version >= MIN_CRYPTO_VERSION);
assert!(version <= MAX_CRYPTO_VERSION);
Self {
version,
min_version: MIN_CRYPTO_VERSION,
max_version: MAX_CRYPTO_VERSION,
timestamp,
nonce,
sender_id,
recipient_id,
}
}
pub fn from_signed_data(data: &[u8]) -> Result<Envelope, VeilidAPIError> {
// Ensure we are at least the length of the envelope
// Silent drop here, as we use zero length packets as part of the protocol for hole punching
if data.len() < MIN_ENVELOPE_SIZE {
return Err(VeilidAPIError::generic("envelope data too small"));
}
// Verify magic number
let magic: [u8; 4] = data[0x00..0x04]
.try_into()
.map_err(VeilidAPIError::internal)?;
if magic != *ENVELOPE_MAGIC {
return Err(VeilidAPIError::generic("bad magic number"));
}
// Check version
let version = data[0x04];
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
return Err(VeilidAPIError::parse_error(
"unsupported cryptography version",
version,
));
}
// Get min version
let min_version = data[0x05];
if min_version > version {
return Err(VeilidAPIError::parse_error("version too low", version));
}
// Get max version
let max_version = data[0x06];
if version > max_version {
return Err(VeilidAPIError::parse_error("version too high", version));
}
if min_version > max_version {
return Err(VeilidAPIError::generic("version information invalid"));
}
// Get size and ensure it matches the size of the envelope and is less than the maximum message size
let size: u16 = u16::from_le_bytes(
data[0x08..0x0A]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
if (size as usize) > MAX_ENVELOPE_SIZE {
return Err(VeilidAPIError::parse_error("envelope too large", size));
}
if (size as usize) != data.len() {
return Err(VeilidAPIError::parse_error(
"size doesn't match envelope size",
format!(
"size doesn't match envelope size: size={} data.len()={}",
size,
data.len()
),
));
}
// Get the timestamp
let timestamp: u64 = u64::from_le_bytes(
data[0x0A..0x12]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Get nonce and sender node id
let nonce: EnvelopeNonce = data[0x12..0x2A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let sender_id_slice: [u8; 32] = data[0x2A..0x4A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let recipient_id_slice: [u8; 32] = data[0x4A..0x6A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let sender_id = DHTKey::new(sender_id_slice);
let recipient_id = DHTKey::new(recipient_id_slice);
// Ensure sender_id and recipient_id are not the same
if sender_id == recipient_id {
return Err(VeilidAPIError::parse_error(
"sender_id should not be same as recipient_id",
recipient_id.encode(),
));
}
// Get signature
let signature = DHTSignature::new(
data[(data.len() - 64)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Validate signature
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
.map_err(VeilidAPIError::internal)?;
// Return envelope
Ok(Self {
version,
min_version,
max_version,
timestamp,
nonce,
sender_id,
recipient_id,
})
}
pub fn decrypt_body(
&self,
crypto: Crypto,
data: &[u8],
node_id_secret: &DHTKeySecret,
) -> Result<Vec<u8>, VeilidAPIError> {
// Get DH secret
let dh_secret = crypto.cached_dh(&self.sender_id, node_id_secret)?;
// Decrypt message without authentication
let body = Crypto::crypt_no_auth(&data[0x6A..data.len() - 64], &self.nonce, &dh_secret);
Ok(body)
}
pub fn to_encrypted_data(
&self,
crypto: Crypto,
body: &[u8],
node_id_secret: &DHTKeySecret,
) -> Result<Vec<u8>, VeilidAPIError> {
// Ensure sender node id is valid
if !self.sender_id.valid {
return Err(VeilidAPIError::generic("sender id is invalid"));
}
// Ensure recipient node id is valid
if !self.recipient_id.valid {
return Err(VeilidAPIError::generic("recipient id is invalid"));
}
// Ensure body isn't too long
let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE;
if envelope_size > MAX_ENVELOPE_SIZE {
return Err(VeilidAPIError::parse_error(
"envelope size is too large",
envelope_size,
));
}
let mut data = vec![0u8; envelope_size];
// Write magic
data[0x00..0x04].copy_from_slice(ENVELOPE_MAGIC);
// Write version
data[0x04] = self.version;
// Write min version
data[0x05] = self.min_version;
// Write max version
data[0x06] = self.max_version;
// Write size
data[0x08..0x0A].copy_from_slice(&(envelope_size as u16).to_le_bytes());
// Write timestamp
data[0x0A..0x12].copy_from_slice(&self.timestamp.to_le_bytes());
// Write nonce
data[0x12..0x2A].copy_from_slice(&self.nonce);
// Write sender node id
data[0x2A..0x4A].copy_from_slice(&self.sender_id.bytes);
// Write recipient node id
data[0x4A..0x6A].copy_from_slice(&self.recipient_id.bytes);
// Generate dh secret
let dh_secret = crypto.cached_dh(&self.recipient_id, node_id_secret)?;
// Encrypt and authenticate message
let encrypted_body = Crypto::crypt_no_auth(body, &self.nonce, &dh_secret);
// Write body
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)],
)?;
// Append the signature
data[(envelope_size - 64)..].copy_from_slice(&signature.bytes);
Ok(data)
}
pub fn get_version(&self) -> u8 {
self.version
}
pub fn get_min_max_version(&self) -> (u8, u8) {
(self.min_version, self.max_version)
}
pub fn get_timestamp(&self) -> u64 {
self.timestamp
}
pub fn get_nonce(&self) -> EnvelopeNonce {
self.nonce
}
pub fn get_sender_id(&self) -> DHTKey {
self.sender_id
}
pub fn get_recipient_id(&self) -> DHTKey {
self.recipient_id
}
}

View File

@@ -0,0 +1,462 @@
use crate::veilid_rng::*;
use crate::xx::*;
use crate::*;
use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use core::convert::{TryFrom, TryInto};
use core::fmt;
use core::hash::{Hash, Hasher};
use data_encoding::BASE64URL_NOPAD;
use digest::generic_array::typenum::U64;
use digest::{Digest, Output};
use ed25519_dalek::{Keypair, PublicKey, Signature};
use generic_array::GenericArray;
use serde::{Deserialize, Serialize};
//////////////////////////////////////////////////////////////////////
/// Length of a DHT key in bytes
#[allow(dead_code)]
pub const DHT_KEY_LENGTH: usize = 32;
/// Length of a DHT key in bytes after encoding to base64url
#[allow(dead_code)]
pub const DHT_KEY_LENGTH_ENCODED: usize = 43;
/// Length of a DHT secret in bytes
#[allow(dead_code)]
pub const DHT_KEY_SECRET_LENGTH: usize = 32;
/// Length of a DHT secret in bytes after encoding to base64url
#[allow(dead_code)]
pub const DHT_KEY_SECRET_LENGTH_ENCODED: usize = 43;
/// Length of a DHT signature in bytes
#[allow(dead_code)]
/// Length of a DHT signature in bytes after encoding to base64url
pub const DHT_SIGNATURE_LENGTH: usize = 64;
#[allow(dead_code)]
pub const DHT_SIGNATURE_LENGTH_ENCODED: usize = 86;
//////////////////////////////////////////////////////////////////////
macro_rules! byte_array_type {
($name:ident, $size:expr) => {
#[derive(Clone, Copy)]
pub struct $name {
pub bytes: [u8; $size],
pub valid: bool,
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s: String;
if self.valid {
s = self.encode();
} else {
s = "".to_owned();
}
s.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "" {
return Ok($name::default());
}
$name::try_decode(s.as_str()).map_err(serde::de::Error::custom)
}
}
impl $name {
pub fn new(bytes: [u8; $size]) -> Self {
Self { bytes, valid: true }
}
pub fn try_from_vec(v: Vec<u8>) -> Result<Self, VeilidAPIError> {
let mut this = Self {
bytes: [0u8; $size],
valid: true,
};
if v.len() != $size {
apibail_generic!(format!(
"Expected a Vec of length {} but it was {}",
$size,
v.len()
));
}
for n in 0..v.len() {
this.bytes[n] = v[n];
}
Ok(this)
}
pub fn bit(&self, index: usize) -> bool {
assert!(index < ($size * 8));
let bi = index / 8;
let ti = 7 - (index % 8);
((self.bytes[bi] >> ti) & 1) != 0
}
pub fn first_nonzero_bit(&self) -> Option<usize> {
for i in 0..$size {
let b = self.bytes[i];
if b != 0 {
for n in 0..8 {
if ((b >> (7 - n)) & 1u8) != 0u8 {
return Some((i * 8) + n);
}
}
panic!("wtf")
}
}
None
}
pub fn nibble(&self, index: usize) -> u8 {
assert!(index < ($size * 2));
let bi = index / 2;
if index & 1 == 0 {
(self.bytes[bi] >> 4) & 0xFu8
} else {
self.bytes[bi] & 0xFu8
}
}
pub fn first_nonzero_nibble(&self) -> Option<(usize, u8)> {
for i in 0..($size * 2) {
let n = self.nibble(i);
if n != 0 {
return Some((i, n));
}
}
None
}
pub fn encode(&self) -> String {
assert!(self.valid);
BASE64URL_NOPAD.encode(&self.bytes)
}
pub fn try_decode(input: &str) -> Result<Self, VeilidAPIError> {
let mut bytes = [0u8; $size];
let res = BASE64URL_NOPAD.decode_len(input.len());
match res {
Ok(v) => {
if v != $size {
apibail_generic!("Incorrect length in decode");
}
}
Err(_) => {
apibail_generic!("Failed to decode");
}
}
let res = BASE64URL_NOPAD.decode_mut(input.as_bytes(), &mut bytes);
match res {
Ok(_) => Ok(Self::new(bytes)),
Err(_) => apibail_generic!("Failed to decode"),
}
}
}
impl PartialOrd for $name {
fn partial_cmp(&self, other: &$name) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for $name {
fn cmp(&self, other: &$name) -> Ordering {
if !self.valid && !other.valid {
return Ordering::Equal;
}
if !self.valid && other.valid {
return Ordering::Less;
}
if self.valid && !other.valid {
return Ordering::Greater;
}
for n in 0..$size {
if self.bytes[n] < other.bytes[n] {
return Ordering::Less;
}
if self.bytes[n] > other.bytes[n] {
return Ordering::Greater;
}
}
Ordering::Equal
}
}
impl PartialEq<$name> for $name {
fn eq(&self, other: &$name) -> bool {
if self.valid != other.valid {
return false;
}
for n in 0..$size {
if self.bytes[n] != other.bytes[n] {
return false;
}
}
true
}
}
impl Eq for $name {}
impl Hash for $name {
fn hash<H: Hasher>(&self, state: &mut H) {
self.valid.hash(state);
if self.valid {
self.bytes.hash(state);
}
}
}
impl Default for $name {
fn default() -> Self {
let mut this = $name::new([0u8; $size]);
this.valid = false;
this
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from(self))
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, concat!(stringify!($name), "("))?;
write!(
f,
"{}",
if self.valid {
self.encode()
} else {
"".to_owned()
}
)?;
write!(f, ")")
}
}
impl From<&$name> for String {
fn from(value: &$name) -> Self {
if !value.valid {
return "".to_owned();
}
let mut s = String::new();
for n in 0..($size / 8) {
let b: [u8; 8] = value.bytes[n * 8..(n + 1) * 8].try_into().unwrap();
s.push_str(hex::encode(b).as_str());
}
s
}
}
impl TryFrom<String> for $name {
type Error = VeilidAPIError;
fn try_from(value: String) -> Result<Self, Self::Error> {
$name::try_from(value.as_str())
}
}
impl TryFrom<&str> for $name {
type Error = VeilidAPIError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut out = $name::default();
if value == "" {
return Ok(out);
}
if value.len() != ($size * 2) {
apibail_generic!(concat!(stringify!($name), " is incorrect length"));
}
match hex::decode_to_slice(value, &mut out.bytes) {
Ok(_) => {
out.valid = true;
Ok(out)
}
Err(err) => Err(VeilidAPIError::generic(err)),
}
}
}
};
}
byte_array_type!(DHTKey, DHT_KEY_LENGTH);
byte_array_type!(DHTKeySecret, DHT_KEY_SECRET_LENGTH);
byte_array_type!(DHTSignature, DHT_SIGNATURE_LENGTH);
byte_array_type!(DHTKeyDistance, DHT_KEY_LENGTH);
/////////////////////////////////////////
struct Blake3Digest512 {
dig: blake3::Hasher,
}
impl Digest for Blake3Digest512 {
type OutputSize = U64;
fn new() -> Self {
Self {
dig: blake3::Hasher::new(),
}
}
fn update(&mut self, data: impl AsRef<[u8]>) {
self.dig.update(data.as_ref());
}
fn chain(mut self, data: impl AsRef<[u8]>) -> Self
where
Self: Sized,
{
self.update(data);
self
}
fn finalize(self) -> Output<Self> {
let mut b = [0u8; 64];
self.dig.finalize_xof().fill(&mut b);
let mut out = GenericArray::<u8, U64>::default();
for n in 0..64 {
out[n] = b[n];
}
out
}
fn finalize_reset(&mut self) -> Output<Self> {
let mut b = [0u8; 64];
self.dig.finalize_xof().fill(&mut b);
let mut out = GenericArray::<u8, U64>::default();
for n in 0..64 {
out[n] = b[n];
}
self.reset();
out
}
fn reset(&mut self) {
self.dig.reset();
}
fn output_size() -> usize {
64
}
fn digest(data: &[u8]) -> Output<Self> {
let mut dig = blake3::Hasher::new();
dig.update(data);
let mut b = [0u8; 64];
dig.finalize_xof().fill(&mut b);
let mut out = GenericArray::<u8, U64>::default();
for n in 0..64 {
out[n] = b[n];
}
out
}
}
/////////////////////////////////////////
pub fn generate_secret() -> (DHTKey, DHTKeySecret) {
let mut csprng = VeilidRng {};
let keypair = Keypair::generate(&mut csprng);
let dht_key = DHTKey::new(keypair.public.to_bytes());
let dht_key_secret = DHTKeySecret::new(keypair.secret.to_bytes());
(dht_key, dht_key_secret)
}
pub fn sign(
dht_key: &DHTKey,
dht_key_secret: &DHTKeySecret,
data: &[u8],
) -> Result<DHTSignature, VeilidAPIError> {
assert!(dht_key.valid);
assert!(dht_key_secret.valid);
let mut kpb: [u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH] =
[0u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH];
kpb[..DHT_KEY_SECRET_LENGTH].copy_from_slice(&dht_key_secret.bytes);
kpb[DHT_KEY_SECRET_LENGTH..].copy_from_slice(&dht_key.bytes);
let keypair = Keypair::from_bytes(&kpb)
.map_err(|e| VeilidAPIError::parse_error("Keypair is invalid", e))?;
let mut dig = Blake3Digest512::new();
dig.update(data);
let sig = keypair
.sign_prehashed(dig, None)
.map_err(VeilidAPIError::internal)?;
let dht_sig = DHTSignature::new(sig.to_bytes());
Ok(dht_sig)
}
pub fn verify(
dht_key: &DHTKey,
data: &[u8],
signature: &DHTSignature,
) -> Result<(), VeilidAPIError> {
assert!(dht_key.valid);
assert!(signature.valid);
let pk = PublicKey::from_bytes(&dht_key.bytes)
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
let sig = Signature::from_bytes(&signature.bytes)
.map_err(|e| VeilidAPIError::parse_error("Signature is invalid", e))?;
let mut dig = Blake3Digest512::new();
dig.update(data);
pk.verify_prehashed(dig, None, &sig)
.map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?;
Ok(())
}
pub fn generate_hash(data: &[u8]) -> DHTKey {
DHTKey::new(*blake3::hash(data).as_bytes())
}
pub fn validate_hash(data: &[u8], dht_key: &DHTKey) -> bool {
assert!(dht_key.valid);
let bytes = *blake3::hash(data).as_bytes();
bytes == dht_key.bytes
}
pub fn validate_key(dht_key: &DHTKey, dht_key_secret: &DHTKeySecret) -> bool {
let data = vec![0u8; 512];
let sig = match sign(dht_key, dht_key_secret, &data) {
Ok(s) => s,
Err(_) => {
return false;
}
};
verify(dht_key, &data, &sig).is_ok()
}
pub fn distance(key1: &DHTKey, key2: &DHTKey) -> DHTKeyDistance {
assert!(key1.valid);
assert!(key2.valid);
let mut bytes = [0u8; DHT_KEY_LENGTH];
for (n, byte) in bytes.iter_mut().enumerate() {
*byte = key1.bytes[n] ^ key2.bytes[n];
}
DHTKeyDistance::new(bytes)
}
#[allow(dead_code)]
pub fn sort_closest_fn(key: DHTKey) -> impl FnMut(&DHTKey, &DHTKey) -> std::cmp::Ordering {
move |k1, k2| distance(k1, &key).cmp(&distance(k2, &key))
}

View File

@@ -0,0 +1,320 @@
mod envelope;
mod key;
mod receipt;
mod value;
pub mod tests;
pub use envelope::*;
pub use key::*;
pub use receipt::*;
pub use value::*;
pub const MIN_CRYPTO_VERSION: u8 = 0u8;
pub const MAX_CRYPTO_VERSION: u8 = 0u8;
use crate::xx::*;
use crate::*;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::XChaCha20;
use chacha20poly1305 as ch;
use chacha20poly1305::aead::{AeadInPlace, NewAead};
use core::convert::TryInto;
use curve25519_dalek as cd;
use ed25519_dalek as ed;
use hashlink::linked_hash_map::Entry;
use hashlink::LruCache;
use serde::{Deserialize, Serialize};
use x25519_dalek as xd;
pub type SharedSecret = [u8; 32];
pub type Nonce = [u8; 24];
const DH_CACHE_SIZE: usize = 1024;
pub const AEAD_OVERHEAD: usize = 16;
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
struct DHCacheKey {
key: DHTKey,
secret: DHTKeySecret,
}
#[derive(Serialize, Deserialize)]
struct DHCacheValue {
shared_secret: SharedSecret,
}
type DHCache = LruCache<DHCacheKey, DHCacheValue>;
fn cache_to_bytes(cache: &DHCache) -> Vec<u8> {
let cnt: usize = cache.len();
let mut out: Vec<u8> = Vec::with_capacity(cnt * (32 + 32 + 32));
for e in cache.iter() {
out.extend(&e.0.key.bytes);
out.extend(&e.0.secret.bytes);
out.extend(&e.1.shared_secret);
}
let mut rev: Vec<u8> = Vec::with_capacity(out.len());
for d in out.chunks(32 + 32 + 32).rev() {
rev.extend(d);
}
rev
}
fn bytes_to_cache(bytes: &[u8], cache: &mut DHCache) {
for d in bytes.chunks(32 + 32 + 32) {
let k = DHCacheKey {
key: DHTKey::new(d[0..32].try_into().expect("asdf")),
secret: DHTKeySecret::new(d[32..64].try_into().expect("asdf")),
};
let v = DHCacheValue {
shared_secret: d[64..96].try_into().expect("asdf"),
};
cache.insert(k, v);
}
}
struct CryptoInner {
table_store: TableStore,
node_id: DHTKey,
node_id_secret: DHTKeySecret,
dh_cache: DHCache,
flush_future: Option<SendPinBoxFuture<()>>,
}
#[derive(Clone)]
pub struct Crypto {
config: VeilidConfig,
inner: Arc<Mutex<CryptoInner>>,
}
impl Crypto {
fn new_inner(table_store: TableStore) -> CryptoInner {
CryptoInner {
table_store,
node_id: Default::default(),
node_id_secret: Default::default(),
dh_cache: DHCache::new(DH_CACHE_SIZE),
flush_future: None,
}
}
pub fn new(config: VeilidConfig, table_store: TableStore) -> Self {
Self {
config,
inner: Arc::new(Mutex::new(Self::new_inner(table_store))),
}
}
pub async fn init(&self) -> EyreResult<()> {
trace!("Crypto::init");
// make local copy of node id for easy access
let (table_store, node_id) = {
let mut inner = self.inner.lock();
let c = self.config.get();
inner.node_id = c.network.node_id;
inner.node_id_secret = c.network.node_id_secret;
(inner.table_store.clone(), c.network.node_id)
};
// load caches if they are valid for this node id
let mut db = table_store.open("crypto_caches", 1).await?;
let caches_valid = match db.load(0, b"node_id").await? {
Some(v) => v.as_slice() == node_id.bytes,
None => false,
};
if caches_valid {
if let Some(b) = db.load(0, b"dh_cache").await? {
let mut inner = self.inner.lock();
bytes_to_cache(&b, &mut inner.dh_cache);
}
} else {
drop(db);
table_store.delete("crypto_caches").await?;
db = table_store.open("crypto_caches", 1).await?;
db.store(0, b"node_id", &node_id.bytes).await?;
}
// Schedule flushing
let this = self.clone();
let flush_future = intf::interval(60000, move || {
let this = this.clone();
async move {
if let Err(e) = this.flush().await {
warn!("flush failed: {}", e);
}
}
});
self.inner.lock().flush_future = Some(flush_future);
Ok(())
}
pub async fn flush(&self) -> EyreResult<()> {
//trace!("Crypto::flush");
let (table_store, cache_bytes) = {
let inner = self.inner.lock();
let cache_bytes = cache_to_bytes(&inner.dh_cache);
(inner.table_store.clone(), cache_bytes)
};
let db = table_store.open("crypto_caches", 1).await?;
db.store(0, b"dh_cache", &cache_bytes).await?;
Ok(())
}
pub async fn terminate(&self) {
trace!("Crypto::terminate");
let flush_future = self.inner.lock().flush_future.take();
if let Some(f) = flush_future {
f.await;
}
trace!("starting termination flush");
match self.flush().await {
Ok(_) => {
trace!("finished termination flush");
}
Err(e) => {
error!("failed termination flush: {}", e);
}
};
}
fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, VeilidAPIError> {
let bytes = key.to_bytes();
let compressed = cd::edwards::CompressedEdwardsY(bytes);
let point = compressed
.decompress()
.ok_or_else(|| VeilidAPIError::internal("ed25519_to_x25519_pk failed"))?;
let mp = point.to_montgomery();
Ok(xd::PublicKey::from(mp.to_bytes()))
}
fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, VeilidAPIError> {
let exp = ed::ExpandedSecretKey::from(key);
let bytes: [u8; ed::EXPANDED_SECRET_KEY_LENGTH] = exp.to_bytes();
let lowbytes: [u8; 32] = bytes[0..32].try_into().map_err(VeilidAPIError::internal)?;
Ok(xd::StaticSecret::from(lowbytes))
}
pub fn cached_dh(
&self,
key: &DHTKey,
secret: &DHTKeySecret,
) -> Result<SharedSecret, VeilidAPIError> {
Ok(
match self.inner.lock().dh_cache.entry(DHCacheKey {
key: *key,
secret: *secret,
}) {
Entry::Occupied(e) => e.get().shared_secret,
Entry::Vacant(e) => {
let shared_secret = Self::compute_dh(key, secret)?;
e.insert(DHCacheValue { shared_secret });
shared_secret
}
},
)
}
///////////
// These are safe to use regardless of initialization status
pub fn compute_dh(key: &DHTKey, secret: &DHTKeySecret) -> Result<SharedSecret, VeilidAPIError> {
assert!(key.valid);
assert!(secret.valid);
let pk_ed = ed::PublicKey::from_bytes(&key.bytes).map_err(VeilidAPIError::internal)?;
let pk_xd = Self::ed25519_to_x25519_pk(&pk_ed)?;
let sk_ed = ed::SecretKey::from_bytes(&secret.bytes).map_err(VeilidAPIError::internal)?;
let sk_xd = Self::ed25519_to_x25519_sk(&sk_ed)?;
Ok(sk_xd.diffie_hellman(&pk_xd).to_bytes())
}
pub fn get_random_nonce() -> Nonce {
let mut nonce = [0u8; 24];
intf::random_bytes(&mut nonce).unwrap();
nonce
}
pub fn get_random_secret() -> SharedSecret {
let mut s = [0u8; 32];
intf::random_bytes(&mut s).unwrap();
s
}
pub fn decrypt_in_place_aead(
body: &mut Vec<u8>,
nonce: &Nonce,
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<(), VeilidAPIError> {
let key = ch::Key::from(*shared_secret);
let xnonce = ch::XNonce::from(*nonce);
let aead = ch::XChaCha20Poly1305::new(&key);
aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
.map_err(map_to_string)
.map_err(VeilidAPIError::generic)
}
pub fn decrypt_aead(
body: &[u8],
nonce: &Nonce,
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<Vec<u8>, VeilidAPIError> {
let mut out = body.to_vec();
Self::decrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
.map_err(map_to_string)
.map_err(VeilidAPIError::generic)?;
Ok(out)
}
pub fn encrypt_in_place_aead(
body: &mut Vec<u8>,
nonce: &Nonce,
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<(), VeilidAPIError> {
let key = ch::Key::from(*shared_secret);
let xnonce = ch::XNonce::from(*nonce);
let aead = ch::XChaCha20Poly1305::new(&key);
aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
.map_err(map_to_string)
.map_err(VeilidAPIError::generic)
}
pub fn encrypt_aead(
body: &[u8],
nonce: &Nonce,
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<Vec<u8>, VeilidAPIError> {
let mut out = body.to_vec();
Self::encrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
.map_err(map_to_string)
.map_err(VeilidAPIError::generic)?;
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_b2b_no_auth(
in_buf: &[u8],
nonce: &Nonce,
shared_secret: &SharedSecret,
) -> Vec<u8> {
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
// Allocate uninitialized memory, aligned to 8 byte boundary because capnp is faster this way
// and the Vec returned here will be used to hold decrypted rpc messages
let mut out_buf = unsafe { aligned_8_u8_vec_uninit(in_buf.len()) };
cipher.apply_keystream_b2b(in_buf, &mut out_buf).unwrap();
out_buf
}
pub fn crypt_no_auth(body: &[u8], nonce: &Nonce, shared_secret: &SharedSecret) -> Vec<u8> {
Self::crypt_b2b_no_auth(body, nonce, shared_secret)
}
}

View File

@@ -0,0 +1,206 @@
#![allow(dead_code)]
#![allow(clippy::absurd_extreme_comparisons)]
use super::*;
use crate::xx::*;
use crate::*;
use core::convert::TryInto;
use data_encoding::BASE64URL_NOPAD;
// #[repr(C, packed)]
// struct ReceiptHeader {
// // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
// magic: [u8; 4], // 0x00: 0x52 0x43 0x50 0x54 ("RCPT")
// version: u8, // 0x04: 0 = ReceiptV0
// reserved: u8, // 0x05: Reserved for future use
// }
// #[repr(C, packed)]
// struct ReceiptV0 {
// // Size is 106 bytes.
// magic: [u8; 4], // 0x00: 0x52 0x43 0x50 0x54 ("RCPT")
// version: u8, // 0x04: 0 = ReceiptV0
// reserved: u8, // 0x05: Reserved for future use
// size: u16, // 0x06: Total size of the receipt including the extra data and the signature. Maximum size is 1152 bytes.
// nonce: [u8; 24], // 0x08: Randomly chosen bytes that represent a unique receipt. Could be used to encrypt the extra data, but it's not required.
// sender_id: [u8; 32], // 0x20: Node ID of the message source, which is the Ed25519 public key of the sender
// extra_data: [u8; ??], // 0x40: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1024 bytes)
// signature: [u8; 64], // 0x?? (end-0x40): Ed25519 signature of the entire receipt including header and extra data is appended to the packet
// }
pub const MAX_RECEIPT_SIZE: usize = 1152;
pub const MAX_EXTRA_DATA_SIZE: usize = 1024;
pub const MIN_RECEIPT_SIZE: usize = 128;
pub const RECEIPT_MAGIC: &[u8; 4] = b"RCPT";
pub type ReceiptNonce = [u8; 24];
pub trait Encodable {
fn encode(&self) -> String;
}
impl Encodable for ReceiptNonce {
fn encode(&self) -> String {
BASE64URL_NOPAD.encode(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Receipt {
version: u8,
nonce: ReceiptNonce,
sender_id: DHTKey,
extra_data: Vec<u8>,
}
impl Receipt {
pub fn try_new<D: AsRef<[u8]>>(
version: u8,
nonce: ReceiptNonce,
sender_id: DHTKey,
extra_data: D,
) -> Result<Self, VeilidAPIError> {
assert!(sender_id.valid);
if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE {
return Err(VeilidAPIError::parse_error(
"extra data too large for receipt",
extra_data.as_ref().len(),
));
}
Ok(Self {
version,
nonce,
sender_id,
extra_data: Vec::from(extra_data.as_ref()),
})
}
pub fn from_signed_data(data: &[u8]) -> Result<Receipt, VeilidAPIError> {
// Ensure we are at least the length of the envelope
if data.len() < MIN_RECEIPT_SIZE {
return Err(VeilidAPIError::parse_error("receipt too small", data.len()));
}
// Verify magic number
let magic: [u8; 4] = data[0x00..0x04]
.try_into()
.map_err(VeilidAPIError::internal)?;
if magic != *RECEIPT_MAGIC {
return Err(VeilidAPIError::generic("bad magic number"));
}
// Check version
let version = data[0x04];
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
return Err(VeilidAPIError::parse_error(
"unsupported cryptography version",
version,
));
}
// Get size and ensure it matches the size of the envelope and is less than the maximum message size
let size: u16 = u16::from_le_bytes(
data[0x06..0x08]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
if (size as usize) > MAX_RECEIPT_SIZE {
return Err(VeilidAPIError::parse_error(
"receipt size is too large",
size,
));
}
if (size as usize) != data.len() {
return Err(VeilidAPIError::parse_error(
"size doesn't match receipt size",
format!("size={} data.len()={}", size, data.len()),
));
}
// Get sender id
let sender_id = DHTKey::new(
data[0x20..0x40]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Get signature
let signature = DHTSignature::new(
data[(data.len() - 64)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Validate signature
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
.map_err(VeilidAPIError::generic)?;
// Get nonce
let nonce: ReceiptNonce = data[0x08..0x20]
.try_into()
.map_err(VeilidAPIError::internal)?;
// Get extra data and signature
let extra_data: Vec<u8> = Vec::from(&data[0x40..(data.len() - 64)]);
// Return receipt
Ok(Self {
version,
nonce,
sender_id,
extra_data,
})
}
pub fn to_signed_data(&self, secret: &DHTKeySecret) -> Result<Vec<u8>, VeilidAPIError> {
// Ensure sender node id is valid
if !self.sender_id.valid {
return Err(VeilidAPIError::internal("sender id is invalid"));
}
// Ensure extra data isn't too long
let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE;
if receipt_size > MAX_RECEIPT_SIZE {
return Err(VeilidAPIError::parse_error(
"receipt too large",
receipt_size,
));
}
let mut data: Vec<u8> = vec![0u8; receipt_size];
// Write magic
data[0x00..0x04].copy_from_slice(RECEIPT_MAGIC);
// Write version
data[0x04] = self.version;
// Write size
data[0x06..0x08].copy_from_slice(&(receipt_size as u16).to_le_bytes());
// Write nonce
data[0x08..0x20].copy_from_slice(&self.nonce);
// Write sender node id
data[0x20..0x40].copy_from_slice(&self.sender_id.bytes);
// Write extra data
if !self.extra_data.is_empty() {
data[0x40..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice());
}
// Sign the receipt
let signature = sign(&self.sender_id, secret, &data[0..(receipt_size - 64)])
.map_err(VeilidAPIError::generic)?;
// Append the signature
data[(receipt_size - 64)..].copy_from_slice(&signature.bytes);
Ok(data)
}
pub fn get_version(&self) -> u8 {
self.version
}
pub fn get_nonce(&self) -> ReceiptNonce {
self.nonce
}
pub fn get_sender_id(&self) -> DHTKey {
self.sender_id
}
pub fn get_extra_data(&self) -> &[u8] {
&self.extra_data
}
}

View File

@@ -0,0 +1,5 @@
pub mod test_crypto;
pub mod test_dht_key;
pub mod test_envelope_receipt;
use super::*;

View File

@@ -0,0 +1,175 @@
use super::*;
use crate::tests::common::test_veilid_config::*;
use crate::xx::*;
use crate::*;
static LOREM_IPSUM:&[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
async fn startup() -> VeilidAPI {
trace!("test_table_store: starting");
let (update_callback, config_callback) = setup_veilid_core();
let api = api_startup(update_callback, config_callback)
.await
.expect("startup failed");
api
}
async fn shutdown(api: VeilidAPI) {
trace!("test_table_store: shutting down");
api.shutdown().await;
trace!("test_table_store: finished");
}
pub async fn test_aead() {
trace!("test_aead");
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();
assert!(
Crypto::encrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
"encrypt should succeed"
);
let size_after_encrypt = body.len();
assert!(
size_after_encrypt - size_before_encrypt == AEAD_OVERHEAD,
"overhead should match"
);
let mut body3 = body.clone();
let mut body4 = body.clone();
let mut body5 = body.clone();
assert!(
Crypto::decrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
"decrypt should succeed"
);
assert_eq!(body, body2, "results should be the same");
assert!(
Crypto::decrypt_in_place_aead(&mut body3, &n2, &ss1, None).is_err(),
"decrypt with wrong nonce should fail"
);
assert_ne!(body3, body, "failure changes data");
assert!(
Crypto::decrypt_in_place_aead(&mut body4, &n1, &ss2, None).is_err(),
"decrypt with wrong secret should fail"
);
assert_ne!(body4, body, "failure changes data");
assert!(
Crypto::decrypt_in_place_aead(&mut body5, &n1, &ss2, Some(b"foobar")).is_err(),
"decrypt with wrong associated data should fail"
);
assert_ne!(body5, body, "failure changes data");
assert!(
Crypto::decrypt_aead(LOREM_IPSUM, &n1, &ss1, None).is_err(),
"should fail authentication"
);
let body5 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
let body6 = Crypto::decrypt_aead(&body5, &n1, &ss1, None).unwrap();
let body7 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
assert_eq!(body6, LOREM_IPSUM);
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) {
trace!("test_dh");
let (dht_key, dht_key_secret) = key::generate_secret();
let (dht_key2, dht_key_secret2) = key::generate_secret();
let r1 = Crypto::compute_dh(&dht_key, &dht_key_secret2).unwrap();
let r2 = Crypto::compute_dh(&dht_key2, &dht_key_secret).unwrap();
let r3 = Crypto::compute_dh(&dht_key, &dht_key_secret2).unwrap();
let r4 = Crypto::compute_dh(&dht_key2, &dht_key_secret).unwrap();
assert_eq!(r1, r2);
assert_eq!(r3, r4);
assert_eq!(r2, r3);
trace!("dh: {:?}", r1);
// test cache
let r5 = crypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
let r6 = crypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
let r7 = crypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
let r8 = crypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
assert_eq!(r1, r5);
assert_eq!(r2, r6);
assert_eq!(r3, r7);
assert_eq!(r4, r8);
trace!("cached_dh: {:?}", r5);
}
pub async fn test_all() {
let api = startup().await;
let crypto = api.crypto().unwrap();
test_aead().await;
test_no_auth().await;
test_dh(crypto).await;
shutdown(api.clone()).await;
assert!(api.is_shutdown());
}

View File

@@ -0,0 +1,307 @@
#![allow(clippy::bool_assert_comparison)]
use super::*;
use crate::xx::*;
use core::convert::TryFrom;
static LOREM_IPSUM:&str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
static CHEEZBURGER: &str = "I can has cheezburger";
static EMPTY_KEY: [u8; key::DHT_KEY_LENGTH] = [0u8; key::DHT_KEY_LENGTH];
static EMPTY_KEY_SECRET: [u8; key::DHT_KEY_SECRET_LENGTH] = [0u8; key::DHT_KEY_SECRET_LENGTH];
pub async fn test_generate_secret() {
// Verify keys generate
let (dht_key, dht_key_secret) = key::generate_secret();
let (dht_key2, dht_key_secret2) = key::generate_secret();
// Verify byte patterns are different between public and secret
assert_ne!(dht_key.bytes, dht_key_secret.bytes);
assert_ne!(dht_key2.bytes, dht_key_secret2.bytes);
// Verify the keys and secrets are different across keypairs
assert_ne!(dht_key, dht_key2);
assert_ne!(dht_key_secret, dht_key_secret2);
}
pub async fn test_sign_and_verify() {
// Make two keys
let (dht_key, dht_key_secret) = key::generate_secret();
let (dht_key2, dht_key_secret2) = key::generate_secret();
// Sign the same message twice
let dht_sig = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
trace!("dht_sig: {:?}", dht_sig);
let dht_sig_b = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
// Sign a second message
let dht_sig_c = key::sign(&dht_key, &dht_key_secret, CHEEZBURGER.as_bytes()).unwrap();
trace!("dht_sig_c: {:?}", dht_sig_c);
// Verify they are the same signature
assert_eq!(dht_sig, dht_sig_b);
// Sign the same message with a different key
let dht_sig2 = key::sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
// Verify a different key gives a different signature
assert_ne!(dht_sig2, dht_sig_b);
// Try using the wrong secret to sign
let a1 = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
let a2 = key::sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
let b1 = key::sign(&dht_key, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
let b2 = key::sign(&dht_key2, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
assert_ne!(a1, b1);
assert_ne!(a2, b2);
assert_ne!(a1, b2);
assert_ne!(a2, b1);
assert_ne!(a1, a2);
assert_ne!(b1, b2);
assert_ne!(a1, b2);
assert_ne!(b1, a2);
assert_eq!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1), Ok(()));
assert_eq!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a2), Ok(()));
assert!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &b1).is_err());
assert!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &b2).is_err());
// Try verifications that should work
assert_eq!(
key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig),
Ok(())
);
assert_eq!(
key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b),
Ok(())
);
assert_eq!(
key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2),
Ok(())
);
assert_eq!(
key::verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c),
Ok(())
);
// Try verifications that shouldn't work
assert!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig).is_err());
assert!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2).is_err());
assert!(key::verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c).is_err());
assert!(key::verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig).is_err());
}
pub async fn test_key_conversions() {
// Test default key
let (dht_key, dht_key_secret) = (key::DHTKey::default(), key::DHTKeySecret::default());
assert_eq!(dht_key.bytes, EMPTY_KEY);
assert!(!dht_key.valid);
assert_eq!(dht_key_secret.bytes, EMPTY_KEY_SECRET);
assert!(!dht_key_secret.valid);
let dht_key_string = String::from(&dht_key);
trace!("dht_key_string: {:?}", dht_key_string);
let dht_key_string2 = String::from(&dht_key);
trace!("dht_key_string2: {:?}", dht_key_string2);
assert_eq!(dht_key_string, dht_key_string2);
let dht_key_secret_string = String::from(&dht_key_secret);
trace!("dht_key_secret_string: {:?}", dht_key_secret_string);
assert_eq!(dht_key_secret_string, dht_key_string);
// Make different keys
let (dht_key2, dht_key_secret2) = key::generate_secret();
trace!("dht_key2: {:?}", dht_key2);
trace!("dht_key_secret2: {:?}", dht_key_secret2);
let (dht_key3, _dht_key_secret3) = key::generate_secret();
trace!("dht_key3: {:?}", dht_key3);
trace!("_dht_key_secret3: {:?}", _dht_key_secret3);
let dht_key2_string = String::from(&dht_key2);
let dht_key2_string2 = String::from(&dht_key2);
let dht_key3_string = String::from(&dht_key3);
assert_eq!(dht_key2_string, dht_key2_string2);
assert_ne!(dht_key3_string, dht_key2_string);
let dht_key_secret2_string = String::from(&dht_key_secret2);
assert_ne!(dht_key_secret2_string, dht_key_secret_string);
assert_ne!(dht_key_secret2_string, dht_key2_string);
// Assert they convert back correctly
let dht_key_back = key::DHTKey::try_from(dht_key_string.as_str()).unwrap();
let dht_key_back2 = key::DHTKey::try_from(dht_key_string2.as_str()).unwrap();
assert_eq!(dht_key_back, dht_key_back2);
assert_eq!(dht_key_back, dht_key);
assert_eq!(dht_key_back2, dht_key);
let dht_key_secret_back = key::DHTKeySecret::try_from(dht_key_secret_string.as_str()).unwrap();
assert_eq!(dht_key_secret_back, dht_key_secret);
let dht_key2_back = key::DHTKey::try_from(dht_key2_string.as_str()).unwrap();
let dht_key2_back2 = key::DHTKey::try_from(dht_key2_string2.as_str()).unwrap();
assert_eq!(dht_key2_back, dht_key2_back2);
assert_eq!(dht_key2_back, dht_key2);
assert_eq!(dht_key2_back2, dht_key2);
let dht_key_secret2_back =
key::DHTKeySecret::try_from(dht_key_secret2_string.as_str()).unwrap();
assert_eq!(dht_key_secret2_back, dht_key_secret2);
// Assert string roundtrip
assert_eq!(String::from(&dht_key2_back), dht_key2_string);
assert!(key::DHTKey::try_from("") == Ok(key::DHTKey::default()));
assert!(key::DHTKeySecret::try_from("") == Ok(key::DHTKeySecret::default()));
// These conversions should fail
assert!(key::DHTKey::try_from("whatever").is_err());
assert!(key::DHTKeySecret::try_from("whatever").is_err());
assert!(key::DHTKey::try_from(" ").is_err());
assert!(key::DHTKeySecret::try_from(" ").is_err());
assert!(key::DHTKey::try_from(
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
)
.is_err());
assert!(key::DHTKeySecret::try_from(
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
)
.is_err());
}
pub async fn test_encode_decode() {
let dht_key = key::DHTKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
let dht_key_secret =
key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
let dht_key_b = key::DHTKey::new(EMPTY_KEY);
let dht_key_secret_b = key::DHTKeySecret::new(EMPTY_KEY_SECRET);
assert_eq!(dht_key, dht_key_b);
assert_eq!(dht_key_secret, dht_key_secret_b);
let (dht_key2, dht_key_secret2) = key::generate_secret();
let e1 = dht_key.encode();
trace!("e1: {:?}", e1);
assert_eq!(e1, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned());
let e1s = dht_key_secret.encode();
trace!("e1s: {:?}", e1s);
let e2 = dht_key2.encode();
trace!("e2: {:?}", e2);
let e2s = dht_key_secret2.encode();
trace!("e2s: {:?}", e2s);
let d1 = key::DHTKey::try_decode(e1.as_str()).unwrap();
trace!("d1: {:?}", d1);
assert_eq!(dht_key, d1);
let d1s = key::DHTKeySecret::try_decode(e1s.as_str()).unwrap();
trace!("d1s: {:?}", d1s);
assert_eq!(dht_key_secret, d1s);
let d2 = key::DHTKey::try_decode(e2.as_str()).unwrap();
trace!("d2: {:?}", d2);
assert_eq!(dht_key2, d2);
let d2s = key::DHTKeySecret::try_decode(e2s.as_str()).unwrap();
trace!("d2s: {:?}", d2s);
assert_eq!(dht_key_secret2, d2s);
// Failures
let f1 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
assert!(f1.is_err());
let f2 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&");
assert!(f2.is_err());
}
async fn test_hash() {
let mut s = BTreeSet::<key::DHTKey>::new();
let k1 = key::generate_hash("abc".as_bytes());
let k2 = key::generate_hash("abcd".as_bytes());
let k3 = key::generate_hash("".as_bytes());
let k4 = key::generate_hash(" ".as_bytes());
let k5 = key::generate_hash(LOREM_IPSUM.as_bytes());
let k6 = key::generate_hash(CHEEZBURGER.as_bytes());
s.insert(k1);
s.insert(k2);
s.insert(k3);
s.insert(k4);
s.insert(k5);
s.insert(k6);
assert_eq!(s.len(), 6);
let v1 = key::generate_hash("abc".as_bytes());
let v2 = key::generate_hash("abcd".as_bytes());
let v3 = key::generate_hash("".as_bytes());
let v4 = key::generate_hash(" ".as_bytes());
let v5 = key::generate_hash(LOREM_IPSUM.as_bytes());
let v6 = key::generate_hash(CHEEZBURGER.as_bytes());
assert_eq!(k1, v1);
assert_eq!(k2, v2);
assert_eq!(k3, v3);
assert_eq!(k4, v4);
assert_eq!(k5, v5);
assert_eq!(k6, v6);
key::validate_hash("abc".as_bytes(), &v1);
key::validate_hash("abcd".as_bytes(), &v2);
key::validate_hash("".as_bytes(), &v3);
key::validate_hash(" ".as_bytes(), &v4);
key::validate_hash(LOREM_IPSUM.as_bytes(), &v5);
key::validate_hash(CHEEZBURGER.as_bytes(), &v6);
}
async fn test_operations() {
let k1 = key::generate_hash(LOREM_IPSUM.as_bytes());
let k2 = key::generate_hash(CHEEZBURGER.as_bytes());
let k3 = key::generate_hash("abc".as_bytes());
// Get distance
let d1 = key::distance(&k1, &k2);
let d2 = key::distance(&k2, &k1);
let d3 = key::distance(&k1, &k3);
let d4 = key::distance(&k2, &k3);
trace!("d1={:?}", d1);
trace!("d2={:?}", d2);
trace!("d3={:?}", d3);
trace!("d4={:?}", d4);
// Verify commutativity
assert_eq!(d1, d2);
assert!(d1 <= d2);
assert!(d1 >= d2);
assert!(d1 >= d2);
assert!(d1 <= d2);
assert_eq!(d2, d1);
assert!(d2 <= d1);
assert!(d2 >= d1);
assert!(d2 >= d1);
assert!(d2 <= d1);
// Verify nibbles
assert_eq!(d1.nibble(0), 0x9u8);
assert_eq!(d1.nibble(1), 0x4u8);
assert_eq!(d1.nibble(2), 0x3u8);
assert_eq!(d1.nibble(3), 0x6u8);
assert_eq!(d1.nibble(63), 0x6u8);
assert_eq!(d1.first_nonzero_nibble(), Some((0, 0x9u8)));
assert_eq!(d2.first_nonzero_nibble(), Some((0, 0x9u8)));
assert_eq!(d3.first_nonzero_nibble(), Some((1, 0x4u8)));
assert_eq!(d4.first_nonzero_nibble(), Some((0, 0x9u8)));
// Verify bits
assert_eq!(d1.bit(0), true);
assert_eq!(d1.bit(1), false);
assert_eq!(d1.bit(7), false);
assert_eq!(d1.bit(8), false);
assert_eq!(d1.bit(14), true);
assert_eq!(d1.bit(15), false);
assert_eq!(d1.bit(254), true);
assert_eq!(d1.bit(255), false);
assert_eq!(d1.first_nonzero_bit(), Some(0));
assert_eq!(d2.first_nonzero_bit(), Some(0));
assert_eq!(d3.first_nonzero_bit(), Some(5));
assert_eq!(d4.first_nonzero_bit(), Some(0));
}
pub async fn test_all() {
test_generate_secret().await;
test_sign_and_verify().await;
test_key_conversions().await;
test_encode_decode().await;
test_hash().await;
test_operations().await;
}

View File

@@ -0,0 +1,92 @@
use super::*;
use crate::tests::common::test_veilid_config::*;
use crate::xx::*;
use crate::*;
pub async fn test_envelope_round_trip() {
info!("--- test envelope round trip ---");
let (update_callback, config_callback) = setup_veilid_core();
let api = api_startup(update_callback, config_callback)
.await
.expect("startup failed");
// Get crypto
let crypto = api.crypto().unwrap();
// Create envelope
let ts = 0x12345678ABCDEF69u64;
let nonce = Crypto::get_random_nonce();
let (sender_id, sender_secret) = generate_secret();
let (recipient_id, recipient_secret) = generate_secret();
let envelope = Envelope::new(0, ts, nonce, sender_id, recipient_id);
// Create arbitrary body
let body = b"This is an arbitrary body";
// Serialize to bytes
let enc_data = envelope
.to_encrypted_data(crypto.clone(), body, &sender_secret)
.expect("failed to encrypt data");
// Deserialize from bytes
let envelope2 =
Envelope::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
let body2 = envelope2
.decrypt_body(crypto.clone(), &enc_data, &recipient_secret)
.expect("failed to decrypt envelope body");
// Compare envelope and body
assert_eq!(envelope, envelope2);
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;
}
pub async fn test_receipt_round_trip() {
info!("--- test receipt round trip ---");
// Create arbitrary body
let body = b"This is an arbitrary body";
// Create receipt
let nonce = Crypto::get_random_nonce();
let (sender_id, sender_secret) = generate_secret();
let receipt = Receipt::try_new(0, nonce, sender_id, body).expect("should not fail");
// Serialize to bytes
let mut enc_data = receipt
.to_signed_data(&sender_secret)
.expect("failed to make signed data");
// Deserialize from bytes
let receipt2 =
Receipt::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
// Should not validate even when a single bit is changed
enc_data[5] = 0x01;
Receipt::from_signed_data(&enc_data)
.expect_err("should have failed to decrypt using wrong secret");
// Compare receipts
assert_eq!(receipt, receipt2);
}
pub async fn test_all() {
test_envelope_round_trip().await;
test_receipt_round_trip().await;
}

View File