break everything

This commit is contained in:
John Smith
2023-02-07 21:44:50 -05:00
parent 9d826b27db
commit a58a87719c
61 changed files with 1278 additions and 863 deletions

View File

@@ -1,4 +1,4 @@
use crate::*;
use super::*;
use core::cmp::{Eq, Ord, PartialEq, PartialOrd};
use core::convert::{TryFrom, TryInto};
@@ -11,24 +11,42 @@ use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as
//////////////////////////////////////////////////////////////////////
/// Length of a DHT key in bytes
/// Length of a public key in bytes
#[allow(dead_code)]
pub const DHT_KEY_LENGTH: usize = 32;
/// Length of a DHT key in bytes after encoding to base64url
pub const PUBLIC_KEY_LENGTH: usize = 32;
/// Length of a public 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
pub const PUBLIC_KEY_LENGTH_ENCODED: usize = 43;
/// Length of a secret key in bytes
#[allow(dead_code)]
pub const DHT_KEY_SECRET_LENGTH: usize = 32;
/// Length of a DHT secret in bytes after encoding to base64url
pub const SECRET_KEY_LENGTH: usize = 32;
/// Length of a secret key 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
pub const SECRET_KEY_LENGTH_ENCODED: usize = 43;
/// Length of a signature in bytes
#[allow(dead_code)]
/// Length of a DHT signature in bytes after encoding to base64url
pub const DHT_SIGNATURE_LENGTH: usize = 64;
pub const SIGNATURE_LENGTH: usize = 64;
/// Length of a signature in bytes after encoding to base64url
#[allow(dead_code)]
pub const DHT_SIGNATURE_LENGTH_ENCODED: usize = 86;
pub const SIGNATURE_LENGTH_ENCODED: usize = 86;
/// Length of a nonce in bytes
#[allow(dead_code)]
pub const NONCE_LENGTH: usize = 24;
/// Length of a nonce in bytes after encoding to base64url
#[allow(dead_code)]
pub const NONCE_LENGTH_ENCODED: usize = 32;
/// Length of a shared secret in bytes
#[allow(dead_code)]
pub const SHARED_SECRET_LENGTH: usize = 32;
/// Length of a shared secret in bytes after encoding to base64url
#[allow(dead_code)]
pub const SHARED_SECRET_LENGTH_ENCODED: usize = 43;
//////////////////////////////////////////////////////////////////////
pub trait Encodable {
fn encode(&self) -> String;
}
//////////////////////////////////////////////////////////////////////
@@ -141,14 +159,13 @@ macro_rules! byte_array_type {
None
}
pub fn encode(&self) -> String {
BASE64URL_NOPAD.encode(&self.bytes)
}
pub fn try_decode<S: AsRef<str>>(input: S) -> Result<Self, VeilidAPIError> {
let b = input.as_ref().as_bytes();
Self::try_decode_bytes(b)
}
pub fn try_decode_bytes(b: &[u8]) -> Result<Self, VeilidAPIError> {
let mut bytes = [0u8; $size];
let res = BASE64URL_NOPAD.decode_len(input.as_ref().len());
let res = BASE64URL_NOPAD.decode_len(b.len());
match res {
Ok(v) => {
if v != $size {
@@ -160,7 +177,7 @@ macro_rules! byte_array_type {
}
}
let res = BASE64URL_NOPAD.decode_mut(input.as_ref().as_bytes(), &mut bytes);
let res = BASE64URL_NOPAD.decode_mut(b, &mut bytes);
match res {
Ok(_) => Ok(Self::new(bytes)),
Err(_) => apibail_generic!("Failed to decode"),
@@ -168,6 +185,11 @@ macro_rules! byte_array_type {
}
}
impl Encodable for $name {
fn encode(&self) -> String {
BASE64URL_NOPAD.encode(&self.bytes)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//write!(f, "{}", String::from(self))
@@ -224,7 +246,9 @@ macro_rules! byte_array_type {
/////////////////////////////////////////
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);
byte_array_type!(PublicKey, PUBLIC_KEY_LENGTH);
byte_array_type!(SecretKey, SECRET_KEY_LENGTH);
byte_array_type!(Signature, SIGNATURE_LENGTH);
byte_array_type!(PublicKeyDistance, PUBLIC_KEY_LENGTH);
byte_array_type!(Nonce, NONCE_LENGTH);
byte_array_type!(SharedSecret, SHARED_SECRET_LENGTH);

View File

@@ -2,14 +2,14 @@ use super::*;
pub trait CryptoSystem {
// Accessors
fn version(&self) -> CryptoVersion;
fn kind(&self) -> CryptoKind;
fn crypto(&self) -> Crypto;
// Cached Operations
fn cached_dh(
&self,
key: &DHTKey,
secret: &DHTKeySecret,
key: &PublicKey,
secret: &SecretKey,
) -> Result<SharedSecret, VeilidAPIError>;
// Generation
@@ -17,40 +17,40 @@ pub trait CryptoSystem {
fn random_shared_secret(&self) -> SharedSecret;
fn compute_dh(
&self,
key: &DHTKey,
secret: &DHTKeySecret,
key: &PublicKey,
secret: &SecretKey,
) -> Result<SharedSecret, VeilidAPIError>;
fn generate_keypair(&self) -> (DHTKey, DHTKeySecret);
fn generate_hash(&self, data: &[u8]) -> DHTKey;
fn generate_keypair(&self) -> (PublicKey, SecretKey);
fn generate_hash(&self, data: &[u8]) -> PublicKey;
fn generate_hash_reader(
&self,
reader: &mut dyn std::io::Read,
) -> Result<DHTKey, VeilidAPIError>;
) -> Result<PublicKey, VeilidAPIError>;
// Validation
fn validate_keypair(&self, dht_key: &DHTKey, dht_key_secret: &DHTKeySecret) -> bool;
fn validate_hash(&self, data: &[u8], dht_key: &DHTKey) -> bool;
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool;
fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool;
fn validate_hash_reader(
&self,
reader: &mut dyn std::io::Read,
dht_key: &DHTKey,
dht_key: &PublicKey,
) -> Result<bool, VeilidAPIError>;
// Distance Metric
fn distance(&self, key1: &DHTKey, key2: &DHTKey) -> DHTKeyDistance;
fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> PublicKeyDistance;
// Authentication
fn sign(
&self,
dht_key: &DHTKey,
dht_key_secret: &DHTKeySecret,
dht_key: &PublicKey,
dht_key_secret: &SecretKey,
data: &[u8],
) -> Result<DHTSignature, VeilidAPIError>;
) -> Result<Signature, VeilidAPIError>;
fn verify(
&self,
dht_key: &DHTKey,
dht_key: &PublicKey,
data: &[u8],
signature: &DHTSignature,
signature: &Signature,
) -> Result<(), VeilidAPIError>;
// AEAD Encrypt/Decrypt

View File

@@ -1,73 +1,65 @@
#![allow(dead_code)]
#![allow(clippy::absurd_extreme_comparisons)]
use super::*;
use crate::routing_table::VersionRange;
use crate::*;
use core::convert::TryInto;
/// Envelopes are versioned along with crypto versions
/// Envelopes are versioned
///
/// These are the formats for the on-the-wire serialization performed by this module
///
/// #[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
/// // Size is at least 4 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
/// magic: [u8; 3], // 0x00: 0x56 0x4C 0x44 ("VLD")
/// version: u8, // 0x03: 0 = EnvelopeV0
/// }
///
/// #[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 is 106 bytes without signature and 170 with signature
/// magic: [u8; 3], // 0x00: 0x56 0x4C 0x44 ("VLD")
/// version: u8, // 0x03: 0 = EnvelopeV0
/// crypto_kind: [u8; 4], // 0x04: CryptoSystemVersion FOURCC code (CryptoKind)
/// 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)
/// nonce: [u8; 24], // 0x12: Random nonce for replay protection and for dh
/// sender_id: [u8; 32], // 0x2A: Node ID of the message source, which is the 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 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
/// signature: [u8; 64], // 0x?? (end-0x40): 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];
pub const ENVELOPE_MAGIC: &[u8; 3] = b"VLD";
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Envelope {
version: u8,
min_version: u8,
max_version: u8,
crypto_kind: CryptoKind,
timestamp: Timestamp,
nonce: EnvelopeNonce,
sender_id: DHTKey,
recipient_id: DHTKey,
nonce: Nonce,
sender_id: PublicKey,
recipient_id: PublicKey,
}
impl Envelope {
pub fn new(
version: u8,
crypto_kind: CryptoKind,
timestamp: Timestamp,
nonce: EnvelopeNonce,
sender_id: DHTKey,
recipient_id: DHTKey,
nonce: Nonce,
sender_id: PublicKey,
recipient_id: PublicKey,
) -> Self {
assert!(version >= MIN_CRYPTO_VERSION);
assert!(version <= MAX_CRYPTO_VERSION);
assert!(version >= MIN_ENVELOPE_VERSION);
assert!(version <= MAX_ENVELOPE_VERSION);
assert!(VALID_CRYPTO_KINDS.contains(&crypto_kind));
Self {
version,
min_version: MIN_CRYPTO_VERSION,
max_version: MAX_CRYPTO_VERSION,
crypto_kind,
timestamp,
nonce,
sender_id,
@@ -75,7 +67,7 @@ impl Envelope {
}
}
pub fn from_signed_data(data: &[u8]) -> Result<Envelope, VeilidAPIError> {
pub fn from_signed_data(crypto: Crypto, 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 {
@@ -83,33 +75,28 @@ impl Envelope {
}
// Verify magic number
let magic: [u8; 4] = data[0x00..0x04]
let magic: [u8; 3] = data[0x00..0x03]
.try_into()
.map_err(VeilidAPIError::internal)?;
if magic != *ENVELOPE_MAGIC {
apibail_generic!("bad magic number");
}
// Check version
let version = data[0x04];
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
apibail_parse_error!("unsupported cryptography version", version);
// Check envelope version
let version = data[0x03];
if version > MAX_ENVELOPE_VERSION || version < MIN_ENVELOPE_VERSION {
apibail_parse_error!("unsupported envelope version", version);
}
// Get min version
let min_version = data[0x05];
if min_version > version {
apibail_parse_error!("version too low", version);
}
// Get max version
let max_version = data[0x06];
if version > max_version {
apibail_parse_error!("version too high", version);
}
if min_version > max_version {
apibail_generic!("version information invalid");
}
// Check crypto kind
let crypto_kind = CryptoKind(
data[0x04..0x08]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let Some(vcrypto) = crypto.get(crypto_kind) else {
apibail_parse_error!("unsupported crypto kind", crypto_kind);
};
// 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(
@@ -140,17 +127,18 @@ impl Envelope {
.into();
// Get nonce and sender node id
let nonce: EnvelopeNonce = data[0x12..0x2A]
let nonce_slice: [u8; NONCE_LENGTH] = data[0x12..0x2A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let sender_id_slice: [u8; 32] = data[0x2A..0x4A]
let sender_id_slice: [u8; PUBLIC_KEY_LENGTH] = data[0x2A..0x4A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let recipient_id_slice: [u8; 32] = data[0x4A..0x6A]
let recipient_id_slice: [u8; PUBLIC_KEY_LENGTH] = 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);
let nonce: Nonce = Nonce::new(nonce_slice);
let sender_id = PublicKey::new(sender_id_slice);
let recipient_id = PublicKey::new(recipient_id_slice);
// Ensure sender_id and recipient_id are not the same
if sender_id == recipient_id {
@@ -161,21 +149,21 @@ impl Envelope {
}
// Get signature
let signature = DHTSignature::new(
let signature = Signature::new(
data[(data.len() - 64)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Validate signature
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
vcrypto
.verify(&sender_id, &data[0..(data.len() - 64)], &signature)
.map_err(VeilidAPIError::internal)?;
// Return envelope
Ok(Self {
version,
min_version,
max_version,
crypto_kind,
timestamp,
nonce,
sender_id,
@@ -187,10 +175,12 @@ impl Envelope {
&self,
crypto: Crypto,
data: &[u8],
node_id_secret: &DHTKeySecret,
node_id_secret: &SecretKey,
) -> Result<Vec<u8>, VeilidAPIError> {
// Get DH secret
let vcrypto = crypto.get(self.version)?;
let vcrypto = crypto
.get(self.crypto_kind)
.expect("need to ensure only valid crypto kinds here");
let dh_secret = vcrypto.cached_dh(&self.sender_id, node_id_secret)?;
// Decrypt message without authentication
@@ -204,40 +194,41 @@ impl Envelope {
&self,
crypto: Crypto,
body: &[u8],
node_id_secret: &DHTKeySecret,
node_id_secret: &SecretKey,
) -> Result<Vec<u8>, VeilidAPIError> {
// Ensure body isn't too long
let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE;
if envelope_size > MAX_ENVELOPE_SIZE {
apibail_parse_error!("envelope size is too large", envelope_size);
}
// Generate dh secret
let vcrypto = crypto
.get(self.crypto_kind)
.expect("need to ensure only valid crypto kinds here");
let dh_secret = vcrypto.cached_dh(&self.recipient_id, node_id_secret)?;
// Write envelope body
let mut data = vec![0u8; envelope_size];
// Write magic
data[0x00..0x04].copy_from_slice(ENVELOPE_MAGIC);
data[0x00..0x03].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;
data[0x03] = self.version;
// Write crypto kind
data[0x04..0x08].copy_from_slice(&self.crypto_kind.0);
// 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.as_u64().to_le_bytes());
// Write nonce
data[0x12..0x2A].copy_from_slice(&self.nonce);
data[0x12..0x2A].copy_from_slice(&self.nonce.bytes);
// 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 vcrypto = crypto.get(self.version)?;
let dh_secret = vcrypto.cached_dh(&self.recipient_id, node_id_secret)?;
// Encrypt and authenticate message
let encrypted_body = vcrypto.crypt_no_auth_aligned_8(body, &self.nonce, &dh_secret);
let encrypted_body = vcrypto.crypt_no_auth_unaligned(body, &self.nonce, &dh_secret);
// Write body
if !encrypted_body.is_empty() {
@@ -245,7 +236,7 @@ impl Envelope {
}
// Sign the envelope
let signature = sign(
let signature = vcrypto.sign(
&self.sender_id,
node_id_secret,
&data[0..(envelope_size - 64)],
@@ -261,25 +252,22 @@ impl Envelope {
self.version
}
pub fn get_min_max_version(&self) -> VersionRange {
VersionRange {
min: self.min_version,
max: self.max_version,
}
pub fn get_crypto_kind(&self) -> CryptoKind {
self.crypto_kind
}
pub fn get_timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn get_nonce(&self) -> EnvelopeNonce {
pub fn get_nonce(&self) -> Nonce {
self.nonce
}
pub fn get_sender_id(&self) -> DHTKey {
pub fn get_sender_id(&self) -> PublicKey {
self.sender_id
}
pub fn get_recipient_id(&self) -> DHTKey {
pub fn get_recipient_id(&self) -> PublicKey {
self.recipient_id
}
}

View File

@@ -1,21 +1,20 @@
mod byte_array_types;
mod envelope;
mod key;
mod receipt;
mod types;
mod value;
pub mod crypto_system;
pub mod tests;
pub mod v0;
pub mod vld0;
pub use byte_array_types::*;
pub use crypto_system::*;
pub use envelope::*;
pub use key::*;
pub use receipt::*;
pub use types::*;
pub use value::*;
pub type CryptoVersion = u8;
pub const MIN_CRYPTO_VERSION: CryptoVersion = 0u8;
pub const MAX_CRYPTO_VERSION: CryptoVersion = 0u8;
pub use vld0::*;
use crate::*;
use core::convert::TryInto;
@@ -23,17 +22,18 @@ use hashlink::linked_hash_map::Entry;
use hashlink::LruCache;
use serde::{Deserialize, Serialize};
pub type SharedSecret = [u8; 32];
pub type Nonce = [u8; 24];
pub type CryptoSystemVersion = Arc<dyn CryptoSystem + Send + Sync>;
pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_VLD0];
pub const MIN_ENVELOPE_VERSION: u8 = 0u8;
pub const MAX_ENVELOPE_VERSION: u8 = 0u8;
const DH_CACHE_SIZE: usize = 4096;
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
struct DHCacheKey {
key: DHTKey,
secret: DHTKeySecret,
key: PublicKey,
secret: SecretKey,
}
#[derive(Serialize, Deserialize)]
@@ -48,7 +48,7 @@ fn cache_to_bytes(cache: &DHCache) -> Vec<u8> {
for e in cache.iter() {
out.extend(&e.0.key.bytes);
out.extend(&e.0.secret.bytes);
out.extend(&e.1.shared_secret);
out.extend(&e.1.shared_secret.bytes);
}
let mut rev: Vec<u8> = Vec::with_capacity(out.len());
for d in out.chunks(32 + 32 + 32).rev() {
@@ -60,70 +60,101 @@ fn cache_to_bytes(cache: &DHCache) -> Vec<u8> {
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")),
key: PublicKey::new(d[0..32].try_into().expect("asdf")),
secret: SecretKey::new(d[32..64].try_into().expect("asdf")),
};
let v = DHCacheValue {
shared_secret: d[64..96].try_into().expect("asdf"),
shared_secret: SharedSecret::new(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<()>>,
crypto_v0: Option<Arc<dyn CryptoSystem + Send + Sync>>,
crypto_vld0: Option<Arc<dyn CryptoSystem + Send + Sync>>,
}
struct CryptoUnlockedInner {
config: VeilidConfig,
table_store: TableStore,
protected_store: ProtectedStore,
}
/// Crypto factory implementation
#[derive(Clone)]
pub struct Crypto {
config: VeilidConfig,
unlocked_inner: Arc<CryptoUnlockedInner>,
inner: Arc<Mutex<CryptoInner>>,
}
impl Crypto {
fn new_inner(table_store: TableStore) -> CryptoInner {
fn new_inner() -> CryptoInner {
CryptoInner {
table_store,
node_id: Default::default(),
node_id_secret: Default::default(),
dh_cache: DHCache::new(DH_CACHE_SIZE),
flush_future: None,
crypto_v0: None,
crypto_vld0: None,
}
}
pub fn new(config: VeilidConfig, table_store: TableStore) -> Self {
pub fn new(
config: VeilidConfig,
table_store: TableStore,
protected_store: ProtectedStore,
) -> Self {
let out = Self {
config,
inner: Arc::new(Mutex::new(Self::new_inner(table_store))),
unlocked_inner: Arc::new(CryptoUnlockedInner {
config,
table_store,
protected_store,
}),
inner: Arc::new(Mutex::new(Self::new_inner())),
};
out.inner.lock().crypto_v0 = Some(Arc::new(v0::CryptoV0System::new(out.clone())));
out.inner.lock().crypto_vld0 = Some(Arc::new(vld0::CryptoSystemVLD0::new(out.clone())));
out
}
pub async fn init(&self) -> EyreResult<()> {
trace!("Crypto::init");
let table_store = self.unlocked_inner.table_store.clone();
// Init node id from config
if let Err(e) = self
.unlocked_inner
.config
.init_node_ids(self.clone(), self.unlocked_inner.protected_store.clone())
.await
{
return Err(e).wrap_err("init node id failed");
}
// 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.unwrap();
inner.node_id_secret = c.network.node_id_secret.unwrap();
(inner.table_store.clone(), c.network.node_id)
let mut cache_validity_key: Vec<u8> = Vec::new();
{
let c = self.unlocked_inner.config.get();
for ck in &VALID_CRYPTO_KINDS {
cache_validity_key.append(
&mut c
.network
.routing_table
.node_ids
.get(ck)
.unwrap()
.node_id
.unwrap()
.bytes
.to_vec(),
);
}
};
// 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")? {
Some(v) => v.as_slice() == node_id.unwrap().bytes,
let caches_valid = match db.load(0, b"cache_validity_key")? {
Some(v) => v == cache_validity_key,
None => false,
};
if caches_valid {
@@ -135,7 +166,8 @@ impl Crypto {
drop(db);
table_store.delete("crypto_caches").await?;
db = table_store.open("crypto_caches", 1).await?;
db.store(0, b"node_id", &node_id.unwrap().bytes).await?;
db.store(0, b"cache_validity_key", &cache_validity_key)
.await?;
}
// Schedule flushing
@@ -155,13 +187,16 @@ impl Crypto {
pub async fn flush(&self) -> EyreResult<()> {
//trace!("Crypto::flush");
let (table_store, cache_bytes) = {
let cache_bytes = {
let inner = self.inner.lock();
let cache_bytes = cache_to_bytes(&inner.dh_cache);
(inner.table_store.clone(), cache_bytes)
cache_to_bytes(&inner.dh_cache)
};
let db = table_store.open("crypto_caches", 1).await?;
let db = self
.unlocked_inner
.table_store
.open("crypto_caches", 1)
.await?;
db.store(0, b"dh_cache", &cache_bytes).await?;
Ok(())
}
@@ -183,26 +218,67 @@ impl Crypto {
};
}
// Factory
fn get(&self, version: CryptoVersion) -> Result<CryptoSystemVersion, VeilidAPIError> {
/// Factory method to get a specific crypto version
pub fn get(&self, kind: CryptoKind) -> Option<CryptoSystemVersion> {
let inner = self.inner.lock();
match version {
0u8 => Ok(inner.crypto_v0.clone().unwrap()),
_ => Err(VeilidAPIError::InvalidArgument {
context: "Unsupported crypto version".to_owned(),
argument: "version".to_owned(),
value: format!("{}", version),
}),
match kind {
CRYPTO_KIND_VLD0 => Some(inner.crypto_vld0.clone().unwrap()),
_ => None,
}
}
/// Signature set verification
/// Returns the set of signature cryptokinds that validate and are supported
/// If any cryptokinds are supported and do not validate, the whole operation
/// returns an error
pub fn verify_signatures<F, R>(
&self,
data: &[u8],
signatures: &[TypedKeySignature],
transform: F,
) -> Result<Vec<R>, VeilidAPIError>
where
F: Fn(&TypedKeySignature) -> R,
{
let mut out = Vec::<R>::with_capacity(signatures.len());
for sig in signatures {
if let Some(vcrypto) = self.get(sig.kind) {
vcrypto.verify(&sig.key, data, &sig.signature)?;
out.push(transform(sig));
}
}
Ok(out)
}
/// Signature set generation
/// Generates the set of signatures that are supported
/// Any cryptokinds that are not supported are silently dropped
pub fn generate_signatures<F, R>(
&self,
data: &[u8],
keypairs: &[TypedKeyPair],
transform: F,
) -> Result<Vec<R>, VeilidAPIError>
where
F: Fn(&TypedKeyPair, Signature) -> R,
{
let mut out = Vec::<R>::with_capacity(keypairs.len());
for kp in keypairs {
if let Some(vcrypto) = self.get(kp.kind) {
let sig = vcrypto.sign(&kp.key, &kp.secret, data)?;
out.push(transform(kp, sig))
}
}
Ok(out)
}
// Internal utilities
fn cached_dh_internal<T: CryptoSystem>(
&self,
vcrypto: &T,
key: &DHTKey,
secret: &DHTKeySecret,
key: &PublicKey,
secret: &SecretKey,
) -> Result<SharedSecret, VeilidAPIError> {
Ok(
match self.inner.lock().dh_cache.entry(DHCacheKey {

View File

@@ -3,64 +3,57 @@
use super::*;
use crate::*;
use core::convert::TryInto;
use data_encoding::BASE64URL_NOPAD;
/// Out-of-band receipts are versioned along with crypto versions
/// Out-of-band receipts are versioned along with envelope versions
///
/// These are the formats for the on-the-wire serialization performed by this module
///
/// #[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
/// // Size is at least 4 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
/// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP")
/// version: u8, // 0x03: 0 = ReceiptV0
/// }
///
/// #[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
/// // Size is 66 bytes without extra data and signature, 130 with signature
/// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP")
/// version: u8, // 0x03: 0 = ReceiptV0
/// crypto_kind: [u8; 4], // 0x04: CryptoSystemVersion FOURCC code
/// size: u16, // 0x08: Total size of the receipt including the extra data and the signature. Maximum size is 1380 bytes.
/// nonce: [u8; 24], // 0x0A: 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], // 0x22: Node ID of the message source, which is the public key of the sender
/// extra_data: [u8; ??], // 0x42: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1250 bytes)
/// signature: [u8; 64], // 0x?? (end-0x40): 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)
}
}
pub const MAX_RECEIPT_SIZE: usize = 1380;
pub const MAX_EXTRA_DATA_SIZE: usize = MAX_RECEIPT_SIZE - MIN_RECEIPT_SIZE; // 1250
pub const MIN_RECEIPT_SIZE: usize = 130;
pub const RECEIPT_MAGIC: &[u8; 3] = b"RCP";
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Receipt {
version: u8,
nonce: ReceiptNonce,
sender_id: DHTKey,
crypto_kind: CryptoKind,
nonce: Nonce,
sender_id: PublicKey,
extra_data: Vec<u8>,
}
impl Receipt {
pub fn try_new<D: AsRef<[u8]>>(
version: u8,
nonce: ReceiptNonce,
sender_id: DHTKey,
crypto_kind: CryptoKind,
nonce: Nonce,
sender_id: PublicKey,
extra_data: D,
) -> Result<Self, VeilidAPIError> {
assert!(version >= MIN_ENVELOPE_VERSION);
assert!(version <= MAX_ENVELOPE_VERSION);
assert!(VALID_CRYPTO_KINDS.contains(&crypto_kind));
if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE {
apibail_parse_error!(
"extra data too large for receipt",
@@ -69,20 +62,21 @@ impl Receipt {
}
Ok(Self {
version,
crypto_kind,
nonce,
sender_id,
extra_data: Vec::from(extra_data.as_ref()),
})
}
pub fn from_signed_data(data: &[u8]) -> Result<Receipt, VeilidAPIError> {
pub fn from_signed_data(crypto: Crypto, data: &[u8]) -> Result<Receipt, VeilidAPIError> {
// Ensure we are at least the length of the envelope
if data.len() < MIN_RECEIPT_SIZE {
apibail_parse_error!("receipt too small", data.len());
}
// Verify magic number
let magic: [u8; 4] = data[0x00..0x04]
let magic: [u8; 3] = data[0x00..0x03]
.try_into()
.map_err(VeilidAPIError::internal)?;
if magic != *RECEIPT_MAGIC {
@@ -90,14 +84,24 @@ impl Receipt {
}
// Check version
let version = data[0x04];
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
apibail_parse_error!("unsupported cryptography version", version);
let version = data[0x03];
if version > MAX_ENVELOPE_VERSION || version < MIN_ENVELOPE_VERSION {
apibail_parse_error!("unsupported envelope version", version);
}
// Check crypto kind
let crypto_kind = CryptoKind(
data[0x04..0x08]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let Some(vcrypto) = crypto.get(crypto_kind) else {
apibail_parse_error!("unsupported crypto kind", crypto_kind);
};
// 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]
data[0x08..0x0A]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
@@ -112,64 +116,80 @@ impl Receipt {
}
// Get sender id
let sender_id = DHTKey::new(
data[0x20..0x40]
let sender_id = PublicKey::new(
data[0x22..0x42]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Get signature
let signature = DHTSignature::new(
let signature = Signature::new(
data[(data.len() - 64)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Validate signature
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
vcrypto
.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)?;
let nonce: Nonce = Nonce::new(
data[0x0A..0x22]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
// Get extra data and signature
let extra_data: Vec<u8> = Vec::from(&data[0x40..(data.len() - 64)]);
let extra_data: Vec<u8> = Vec::from(&data[0x42..(data.len() - 64)]);
// Return receipt
Ok(Self {
version,
crypto_kind,
nonce,
sender_id,
extra_data,
})
}
pub fn to_signed_data(&self, secret: &DHTKeySecret) -> Result<Vec<u8>, VeilidAPIError> {
pub fn to_signed_data(
&self,
crypto: Crypto,
secret: &SecretKey,
) -> Result<Vec<u8>, VeilidAPIError> {
// Ensure extra data isn't too long
let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE;
if receipt_size > MAX_RECEIPT_SIZE {
apibail_parse_error!("receipt too large", receipt_size);
}
// Get crypto version
let vcrypto = crypto
.get(self.crypto_kind)
.expect("need to ensure only valid crypto kinds here");
let mut data: Vec<u8> = vec![0u8; receipt_size];
// Write magic
data[0x00..0x04].copy_from_slice(RECEIPT_MAGIC);
data[0x00..0x03].copy_from_slice(RECEIPT_MAGIC);
// Write version
data[0x04] = self.version;
data[0x03] = self.version;
// Write crypto kind
data[0x04..0x08].copy_from_slice(&self.crypto_kind.0);
// Write size
data[0x06..0x08].copy_from_slice(&(receipt_size as u16).to_le_bytes());
data[0x08..0x0A].copy_from_slice(&(receipt_size as u16).to_le_bytes());
// Write nonce
data[0x08..0x20].copy_from_slice(&self.nonce);
data[0x0A..0x22].copy_from_slice(&self.nonce.bytes);
// Write sender node id
data[0x20..0x40].copy_from_slice(&self.sender_id.bytes);
data[0x22..0x42].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());
data[0x42..(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)])
let signature = vcrypto
.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);
@@ -181,11 +201,15 @@ impl Receipt {
self.version
}
pub fn get_nonce(&self) -> ReceiptNonce {
pub fn get_crypto_kind(&self) -> CryptoKind {
self.crypto_kind
}
pub fn get_nonce(&self) -> Nonce {
self.nonce
}
pub fn get_sender_id(&self) -> DHTKey {
pub fn get_sender_id(&self) -> PublicKey {
self.sender_id
}
pub fn get_extra_data(&self) -> &[u8] {

View File

@@ -1,6 +1,6 @@
pub mod test_crypto;
pub mod test_dht_key;
pub mod test_envelope_receipt;
pub mod test_types;
use super::*;
use crate::tests::common::test_veilid_config::*;

View File

@@ -167,7 +167,7 @@ pub async fn test_all() {
let crypto = api.crypto().unwrap();
// Test versions
for v in MIN_CRYPTO_VERSION..=MAX_CRYPTO_VERSION {
for v in VALID_CRYPTO_KINDS {
let vcrypto = crypto.get(v).unwrap();
test_aead(vcrypto.clone()).await;
test_no_auth(vcrypto.clone()).await;

View File

@@ -8,7 +8,14 @@ pub async fn test_envelope_round_trip(vcrypto: CryptoSystemVersion) {
let nonce = vcrypto.random_nonce();
let (sender_id, sender_secret) = vcrypto.generate_keypair();
let (recipient_id, recipient_secret) = vcrypto.generate_keypair();
let envelope = Envelope::new(vcrypto.version(), ts, nonce, sender_id, recipient_id);
let envelope = Envelope::new(
MAX_ENVELOPE_VERSION,
vcrypto.kind(),
ts,
nonce,
sender_id,
recipient_id,
);
// Create arbitrary body
let body = b"This is an arbitrary body";
@@ -19,8 +26,8 @@ pub async fn test_envelope_round_trip(vcrypto: CryptoSystemVersion) {
.expect("failed to encrypt data");
// Deserialize from bytes
let envelope2 =
Envelope::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
let envelope2 = Envelope::from_signed_data(vcrypto.crypto(), &enc_data)
.expect("failed to deserialize envelope from data");
let body2 = envelope2
.decrypt_body(vcrypto.crypto(), &enc_data, &recipient_secret)
@@ -35,13 +42,13 @@ pub async fn test_envelope_round_trip(vcrypto: CryptoSystemVersion) {
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(),
Envelope::from_signed_data(vcrypto.crypto(), &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(),
Envelope::from_signed_data(vcrypto.crypto(), &mod_enc_data2).is_err(),
"should have failed to decode envelope with modified data"
);
}
@@ -54,20 +61,21 @@ pub async fn test_receipt_round_trip(vcrypto: CryptoSystemVersion) {
// Create receipt
let nonce = vcrypto.random_nonce();
let (sender_id, sender_secret) = vcrypto.generate_keypair();
let receipt = Receipt::try_new(0, nonce, sender_id, body).expect("should not fail");
let receipt = Receipt::try_new(MAX_ENVELOPE_VERSION, vcrypto.kind(), nonce, sender_id, body)
.expect("should not fail");
// Serialize to bytes
let mut enc_data = receipt
.to_signed_data(&sender_secret)
.to_signed_data(vcrypto.crypto(), &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");
let receipt2 = Receipt::from_signed_data(vcrypto.crypto(), &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)
Receipt::from_signed_data(vcrypto.crypto(), &enc_data)
.expect_err("should have failed to decrypt using wrong secret");
// Compare receipts
@@ -79,7 +87,7 @@ pub async fn test_all() {
let crypto = api.crypto().unwrap();
// Test versions
for v in MIN_CRYPTO_VERSION..=MAX_CRYPTO_VERSION {
for v in VALID_CRYPTO_KINDS {
let vcrypto = crypto.get(v).unwrap();
test_envelope_round_trip(vcrypto.clone()).await;

View File

@@ -5,8 +5,8 @@ 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];
static EMPTY_KEY: [u8; PUBLIC_KEY_LENGTH] = [0u8; PUBLIC_KEY_LENGTH];
static EMPTY_KEY_SECRET: [u8; SECRET_KEY_LENGTH] = [0u8; SECRET_KEY_LENGTH];
pub async fn test_generate_secret(vcrypto: CryptoSystemVersion) {
// Verify keys generate
@@ -119,7 +119,7 @@ pub async fn test_sign_and_verify(vcrypto: CryptoSystemVersion) {
pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) {
// Test default key
let (dht_key, dht_key_secret) = (key::DHTKey::default(), key::DHTKeySecret::default());
let (dht_key, dht_key_secret) = (PublicKey::default(), SecretKey::default());
assert_eq!(dht_key.bytes, EMPTY_KEY);
assert_eq!(dht_key_secret.bytes, EMPTY_KEY_SECRET);
let dht_key_string = String::from(&dht_key);
@@ -150,50 +150,49 @@ pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) {
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();
let dht_key_back = PublicKey::try_from(dht_key_string.as_str()).unwrap();
let dht_key_back2 = PublicKey::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();
let dht_key_secret_back = SecretKey::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();
let dht_key2_back = PublicKey::try_from(dht_key2_string.as_str()).unwrap();
let dht_key2_back2 = PublicKey::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();
let dht_key_secret2_back = SecretKey::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);
// 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(" ").is_err());
assert!(key::DHTKeySecret::try_from(" ").is_err());
assert!(key::DHTKey::try_from(
assert!(PublicKey::try_from("whatever").is_err());
assert!(SecretKey::try_from("whatever").is_err());
assert!(PublicKey::try_from("").is_err());
assert!(SecretKey::try_from("").is_err());
assert!(PublicKey::try_from(" ").is_err());
assert!(SecretKey::try_from(" ").is_err());
assert!(PublicKey::try_from(
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
)
.is_err());
assert!(key::DHTKeySecret::try_from(
assert!(SecretKey::try_from(
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
)
.is_err());
}
pub async fn test_encode_decode(vcrypto: CryptoSystemVersion) {
let dht_key = key::DHTKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
let dht_key = PublicKey::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);
SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
let dht_key_b = PublicKey::new(EMPTY_KEY);
let dht_key_secret_b = SecretKey::new(EMPTY_KEY_SECRET);
assert_eq!(dht_key, dht_key_b);
assert_eq!(dht_key_secret, dht_key_secret_b);
@@ -209,31 +208,31 @@ pub async fn test_encode_decode(vcrypto: CryptoSystemVersion) {
let e2s = dht_key_secret2.encode();
trace!("e2s: {:?}", e2s);
let d1 = key::DHTKey::try_decode(e1.as_str()).unwrap();
let d1 = PublicKey::try_decode(e1.as_str()).unwrap();
trace!("d1: {:?}", d1);
assert_eq!(dht_key, d1);
let d1s = key::DHTKeySecret::try_decode(e1s.as_str()).unwrap();
let d1s = SecretKey::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();
let d2 = PublicKey::try_decode(e2.as_str()).unwrap();
trace!("d2: {:?}", d2);
assert_eq!(dht_key2, d2);
let d2s = key::DHTKeySecret::try_decode(e2s.as_str()).unwrap();
let d2s = SecretKey::try_decode(e2s.as_str()).unwrap();
trace!("d2s: {:?}", d2s);
assert_eq!(dht_key_secret2, d2s);
// Failures
let f1 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
let f1 = SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
assert!(f1.is_err());
let f2 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&");
let f2 = SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&");
assert!(f2.is_err());
}
async fn test_hash(vcrypto: CryptoSystemVersion) {
let mut s = BTreeSet::<key::DHTKey>::new();
let mut s = BTreeSet::<PublicKey>::new();
let k1 = vcrypto.generate_hash("abc".as_bytes());
let k2 = vcrypto.generate_hash("abcd".as_bytes());
@@ -333,7 +332,7 @@ pub async fn test_all() {
let crypto = api.crypto().unwrap();
// Test versions
for v in MIN_CRYPTO_VERSION..=MAX_CRYPTO_VERSION {
for v in VALID_CRYPTO_KINDS {
let vcrypto = crypto.get(v).unwrap();
test_generate_secret(vcrypto.clone()).await;

View File

@@ -0,0 +1,233 @@
use super::*;
use core::cmp::{Eq, Ord, PartialEq, PartialOrd};
use core::convert::{TryFrom, TryInto};
use core::fmt;
use core::hash::Hash;
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
/// Cryptography version fourcc code
#[derive(
Copy,
Debug,
Default,
Clone,
Hash,
PartialOrd,
Ord,
PartialEq,
Eq,
Serialize,
Deserialize,
RkyvArchive,
RkyvSerialize,
RkyvDeserialize,
)]
#[archive_attr(repr(C), derive(CheckBytes, PartialOrd, Ord, PartialEq, Eq))]
pub struct CryptoKind(pub [u8; 4]);
impl From<[u8; 4]> for CryptoKind {
fn from(b: [u8; 4]) -> Self {
Self(b)
}
}
impl TryFrom<&[u8]> for CryptoKind {
type Error = VeilidAPIError;
fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
Ok(Self(b.try_into().map_err(VeilidAPIError::generic)?))
}
}
impl fmt::Display for CryptoKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", String::from_utf8_lossy(&self.0))
}
}
impl FromStr for CryptoKind {
type Err = VeilidAPIError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
s.as_bytes().try_into().map_err(VeilidAPIError::generic)?,
))
}
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize,
)]
#[archive_attr(repr(C), derive(CheckBytes))]
pub struct TypedKey {
pub kind: CryptoKind,
pub key: PublicKey,
}
impl TypedKey {
pub fn new(kind: CryptoKind, key: PublicKey) -> Self {
Self { kind, key }
}
}
impl fmt::Display for TypedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.kind, self.key.encode())
}
}
impl FromStr for TypedKey {
type Err = VeilidAPIError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let b = s.as_bytes();
if b.len() != (5 + PUBLIC_KEY_LENGTH_ENCODED) || b[4..5] != b":"[..] {
apibail_parse_error!("invalid typed key", s);
}
let kind: CryptoKind = b[0..4].try_into().expect("should not fail to convert");
let key = PublicKey::try_decode_bytes(&b[5..])?;
Ok(Self { kind, key })
}
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize,
)]
#[archive_attr(repr(C), derive(CheckBytes))]
pub struct TypedKeyPair {
pub kind: CryptoKind,
pub key: PublicKey,
pub secret: SecretKey,
}
impl TypedKeyPair {
pub fn new(kind: CryptoKind, key: PublicKey, secret: SecretKey) -> Self {
Self { kind, key, secret }
}
}
impl fmt::Display for TypedKeyPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{}:{}:{}",
self.kind,
self.key.encode(),
self.secret.encode()
)
}
}
impl FromStr for TypedKeyPair {
type Err = VeilidAPIError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let b = s.as_bytes();
if b.len() != (5 + PUBLIC_KEY_LENGTH_ENCODED + 1 + SECRET_KEY_LENGTH_ENCODED)
|| b[4..5] != b":"[..]
|| b[5 + PUBLIC_KEY_LENGTH_ENCODED..6 + PUBLIC_KEY_LENGTH_ENCODED] != b":"[..]
{
apibail_parse_error!("invalid typed key pair", s);
}
let kind: CryptoKind = b[0..4].try_into().expect("should not fail to convert");
let key = PublicKey::try_decode_bytes(&b[5..5 + PUBLIC_KEY_LENGTH_ENCODED])?;
let secret = SecretKey::try_decode_bytes(&b[5 + PUBLIC_KEY_LENGTH_ENCODED + 1..])?;
Ok(Self { kind, key, secret })
}
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize,
)]
#[archive_attr(repr(C), derive(CheckBytes))]
pub struct TypedSignature {
pub kind: CryptoKind,
pub signature: Signature,
}
impl TypedSignature {
pub fn new(kind: CryptoKind, signature: Signature) -> Self {
Self { kind, signature }
}
pub fn from_keyed(tks: &TypedKeySignature) -> Self {
Self {
kind: tks.kind,
signature: tks.signature,
}
}
pub fn from_pair_sig(tkp: &TypedKeyPair, sig: Signature) -> Self {
Self {
kind: tkp.kind,
signature: sig,
}
}
}
impl fmt::Display for TypedSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.kind, self.signature.encode())
}
}
impl FromStr for TypedSignature {
type Err = VeilidAPIError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let b = s.as_bytes();
if b.len() != (5 + SIGNATURE_LENGTH_ENCODED) || b[4..5] != b":"[..] {
apibail_parse_error!("invalid typed signature", s);
}
let kind: CryptoKind = b[0..4].try_into()?;
let signature = Signature::try_decode_bytes(&b[5..])?;
Ok(Self { kind, signature })
}
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize,
)]
#[archive_attr(repr(C), derive(CheckBytes))]
pub struct TypedKeySignature {
pub kind: CryptoKind,
pub key: PublicKey,
pub signature: Signature,
}
impl TypedKeySignature {
pub fn new(kind: CryptoKind, key: PublicKey, signature: Signature) -> Self {
Self {
kind,
key,
signature,
}
}
pub fn as_typed_signature(&self) -> TypedSignature {
TypedSignature {
kind: self.kind,
signature: self.signature,
}
}
}
impl fmt::Display for TypedKeySignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{}:{}:{}",
self.kind,
self.key.encode(),
self.signature.encode()
)
}
}
impl FromStr for TypedKeySignature {
type Err = VeilidAPIError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let b = s.as_bytes();
if b.len() != (5 + PUBLIC_KEY_LENGTH_ENCODED + 1 + SIGNATURE_LENGTH_ENCODED)
|| b[4] != b':'
|| b[5 + PUBLIC_KEY_LENGTH_ENCODED] != b':'
{
apibail_parse_error!("invalid typed key signature", s);
}
let kind: CryptoKind = b[0..4].try_into().expect("should not fail to convert");
let key = PublicKey::try_decode_bytes(&b[5..5 + PUBLIC_KEY_LENGTH_ENCODED])?;
let signature = Signature::try_decode_bytes(&b[5 + PUBLIC_KEY_LENGTH_ENCODED + 1..])?;
Ok(Self {
kind,
key,
signature,
})
}
}

View File

@@ -1,7 +1,7 @@
pub mod blake3digest512;
pub use blake3digest512::*;
pub use super::*;
use super::*;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::XChaCha20;
@@ -11,11 +11,10 @@ use core::convert::TryInto;
use curve25519_dalek as cd;
use digest::Digest;
use ed25519_dalek as ed;
use ed25519_dalek::{Keypair, PublicKey, Signature};
use x25519_dalek as xd;
const AEAD_OVERHEAD: usize = 16;
pub const CRYPTO_KIND_VLD0: CryptoKind = CryptoKind([b'V', b'L', b'D', b'0']);
fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, VeilidAPIError> {
let bytes = key.to_bytes();
@@ -35,80 +34,80 @@ fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, VeilidA
/// V1 CryptoSystem
#[derive(Clone)]
pub struct CryptoV0System {
pub struct CryptoSystemVLD0 {
crypto: Crypto,
}
impl CryptoV0System {
impl CryptoSystemVLD0 {
pub fn new(crypto: Crypto) -> Self {
Self { crypto }
}
}
impl CryptoSystem for CryptoV0System {
impl CryptoSystem for CryptoSystemVLD0 {
// Accessors
fn version(&self) -> CryptoVersion {
return 0u8;
fn kind(&self) -> CryptoKind {
CRYPTO_KIND_VLD0
}
fn crypto(&self) -> Crypto {
return self.crypto.clone();
self.crypto.clone()
}
// Cached Operations
fn cached_dh(
&self,
key: &DHTKey,
secret: &DHTKeySecret,
key: &PublicKey,
secret: &SecretKey,
) -> Result<SharedSecret, VeilidAPIError> {
self.crypto
.cached_dh_internal::<CryptoV0System>(self, key, secret)
.cached_dh_internal::<CryptoSystemVLD0>(self, key, secret)
}
// Generation
fn random_nonce(&self) -> Nonce {
let mut nonce = [0u8; 24];
random_bytes(&mut nonce).unwrap();
nonce
Nonce::new(nonce)
}
fn random_shared_secret(&self) -> SharedSecret {
let mut s = [0u8; 32];
random_bytes(&mut s).unwrap();
s
SharedSecret::new(s)
}
fn compute_dh(
&self,
key: &DHTKey,
secret: &DHTKeySecret,
key: &PublicKey,
secret: &SecretKey,
) -> Result<SharedSecret, VeilidAPIError> {
let pk_ed = ed::PublicKey::from_bytes(&key.bytes).map_err(VeilidAPIError::internal)?;
let pk_xd = ed25519_to_x25519_pk(&pk_ed)?;
let sk_ed = ed::SecretKey::from_bytes(&secret.bytes).map_err(VeilidAPIError::internal)?;
let sk_xd = ed25519_to_x25519_sk(&sk_ed)?;
Ok(sk_xd.diffie_hellman(&pk_xd).to_bytes())
Ok(SharedSecret::new(sk_xd.diffie_hellman(&pk_xd).to_bytes()))
}
fn generate_keypair(&self) -> (DHTKey, DHTKeySecret) {
fn generate_keypair(&self) -> (PublicKey, SecretKey) {
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());
let keypair = ed::Keypair::generate(&mut csprng);
let dht_key = PublicKey::new(keypair.public.to_bytes());
let dht_key_secret = SecretKey::new(keypair.secret.to_bytes());
(dht_key, dht_key_secret)
}
fn generate_hash(&self, data: &[u8]) -> DHTKey {
DHTKey::new(*blake3::hash(data).as_bytes())
fn generate_hash(&self, data: &[u8]) -> PublicKey {
PublicKey::new(*blake3::hash(data).as_bytes())
}
fn generate_hash_reader(
&self,
reader: &mut dyn std::io::Read,
) -> Result<DHTKey, VeilidAPIError> {
) -> Result<PublicKey, VeilidAPIError> {
let mut hasher = blake3::Hasher::new();
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
Ok(DHTKey::new(*hasher.finalize().as_bytes()))
Ok(PublicKey::new(*hasher.finalize().as_bytes()))
}
// Validation
fn validate_keypair(&self, dht_key: &DHTKey, dht_key_secret: &DHTKeySecret) -> bool {
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool {
let data = vec![0u8; 512];
let sig = match self.sign(dht_key, dht_key_secret, &data) {
Ok(s) => s,
@@ -118,7 +117,7 @@ impl CryptoSystem for CryptoV0System {
};
self.verify(dht_key, &data, &sig).is_ok()
}
fn validate_hash(&self, data: &[u8], dht_key: &DHTKey) -> bool {
fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool {
let bytes = *blake3::hash(data).as_bytes();
bytes == dht_key.bytes
@@ -126,7 +125,7 @@ impl CryptoSystem for CryptoV0System {
fn validate_hash_reader(
&self,
reader: &mut dyn std::io::Read,
dht_key: &DHTKey,
dht_key: &PublicKey,
) -> Result<bool, VeilidAPIError> {
let mut hasher = blake3::Hasher::new();
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
@@ -134,29 +133,29 @@ impl CryptoSystem for CryptoV0System {
Ok(bytes == dht_key.bytes)
}
// Distance Metric
fn distance(&self, key1: &DHTKey, key2: &DHTKey) -> DHTKeyDistance {
let mut bytes = [0u8; DHT_KEY_LENGTH];
fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> PublicKeyDistance {
let mut bytes = [0u8; PUBLIC_KEY_LENGTH];
for (n, byte) in bytes.iter_mut().enumerate() {
*byte = key1.bytes[n] ^ key2.bytes[n];
}
DHTKeyDistance::new(bytes)
PublicKeyDistance::new(bytes)
}
// Authentication
fn sign(
&self,
dht_key: &DHTKey,
dht_key_secret: &DHTKeySecret,
dht_key: &PublicKey,
dht_key_secret: &SecretKey,
data: &[u8],
) -> Result<DHTSignature, VeilidAPIError> {
let mut kpb: [u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH] =
[0u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH];
) -> Result<Signature, VeilidAPIError> {
let mut kpb: [u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] =
[0u8; SECRET_KEY_LENGTH + PUBLIC_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)
kpb[..SECRET_KEY_LENGTH].copy_from_slice(&dht_key_secret.bytes);
kpb[SECRET_KEY_LENGTH..].copy_from_slice(&dht_key.bytes);
let keypair = ed::Keypair::from_bytes(&kpb)
.map_err(|e| VeilidAPIError::parse_error("Keypair is invalid", e))?;
let mut dig = Blake3Digest512::new();
@@ -166,18 +165,18 @@ impl CryptoSystem for CryptoV0System {
.sign_prehashed(dig, None)
.map_err(VeilidAPIError::internal)?;
let dht_sig = DHTSignature::new(sig.to_bytes());
let dht_sig = Signature::new(sig.to_bytes());
Ok(dht_sig)
}
fn verify(
&self,
dht_key: &DHTKey,
dht_key: &PublicKey,
data: &[u8],
signature: &DHTSignature,
signature: &Signature,
) -> Result<(), VeilidAPIError> {
let pk = PublicKey::from_bytes(&dht_key.bytes)
let pk = ed::PublicKey::from_bytes(&dht_key.bytes)
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
let sig = Signature::from_bytes(&signature.bytes)
let sig = ed::Signature::from_bytes(&signature.bytes)
.map_err(|e| VeilidAPIError::parse_error("Signature is invalid", e))?;
let mut dig = Blake3Digest512::new();
@@ -199,8 +198,8 @@ impl CryptoSystem for CryptoV0System {
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<(), VeilidAPIError> {
let key = ch::Key::from(*shared_secret);
let xnonce = ch::XNonce::from(*nonce);
let key = ch::Key::from(shared_secret.bytes);
let xnonce = ch::XNonce::from(nonce.bytes);
let aead = ch::XChaCha20Poly1305::new(&key);
aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
.map_err(map_to_string)
@@ -228,8 +227,8 @@ impl CryptoSystem for CryptoV0System {
shared_secret: &SharedSecret,
associated_data: Option<&[u8]>,
) -> Result<(), VeilidAPIError> {
let key = ch::Key::from(*shared_secret);
let xnonce = ch::XNonce::from(*nonce);
let key = ch::Key::from(shared_secret.bytes);
let xnonce = ch::XNonce::from(nonce.bytes);
let aead = ch::XChaCha20Poly1305::new(&key);
aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
@@ -258,7 +257,7 @@ impl CryptoSystem for CryptoV0System {
nonce: &Nonce,
shared_secret: &SharedSecret,
) {
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
cipher.apply_keystream(body);
}
@@ -269,7 +268,7 @@ impl CryptoSystem for CryptoV0System {
nonce: &Nonce,
shared_secret: &SharedSecret,
) {
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
cipher.apply_keystream_b2b(in_buf, &mut out_buf).unwrap();
}