use super::*;
use crate::routing_table::*;
use core::convert::TryInto;
fourcc_type!(EnvelopeVersion);
pub const ENVELOPE_VERSION_ENV0: EnvelopeVersion = EnvelopeVersion::new(*b"ENV0");
pub const ENV0_NONCE_LENGTH: usize = 24;
pub const ENV0_SIGNATURE_LENGTH: usize = 64;
pub const ENV0_MAX_ENVELOPE_SIZE: usize = 65507;
pub const ENV0_MIN_ENVELOPE_SIZE: usize = 0x6A + 0x40;
pub const VALID_ENVELOPE_VERSIONS: [EnvelopeVersion; 1] = [ENVELOPE_VERSION_ENV0];
pub const MAX_ENVELOPE_VERSIONS: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Envelope {
ENV0 { env0: EnvelopeENV0 },
}
impl Envelope {
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub fn try_new_env0(
crypto: &Crypto,
crypto_kind: CryptoKind,
timestamp: Timestamp,
nonce: Nonce,
sender_id: NodeId,
recipient_id: NodeId,
) -> VeilidAPIResult<Self> {
Ok(Self::ENV0 {
env0: EnvelopeENV0::try_new(
crypto,
crypto_kind,
timestamp,
nonce,
sender_id,
recipient_id,
)?,
})
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn try_from_signed_data(
crypto: &Crypto,
data: Bytes,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Self> {
if data.len() < 4 {
apibail_generic!("envelope header too small");
}
let version: EnvelopeVersion = data[0x00..0x04]
.try_into()
.map_err(VeilidAPIError::internal)?;
match version {
ENVELOPE_VERSION_ENV0 => Ok(Self::ENV0 {
env0: EnvelopeENV0::try_from_signed_data(crypto, data, network_key).await?,
}),
_ => {
apibail_parse_error!("unsupported envelope version", version);
}
}
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn decrypt_body(
&self,
crypto: &Crypto,
data: Bytes,
secret_key: &SecretKey,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Bytes> {
match self {
Envelope::ENV0 { env0 } => {
env0.decrypt_body(crypto, data, secret_key, network_key)
.await
}
}
}
#[cfg_attr(
feature = "instrument",
instrument(level = "trace", target = "envelope", skip_all, err, fields(__VEILID_LOG_KEY = crypto.log_key()))
)]
pub async fn to_encrypted_data(
&self,
crypto: &Crypto,
body: Bytes,
secret_key: &SecretKey,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Bytes> {
match self {
Envelope::ENV0 { env0 } => {
env0.to_encrypted_data(crypto, body, secret_key, network_key)
.await
}
}
}
pub fn get_version(&self) -> EnvelopeVersion {
match self {
Envelope::ENV0 { env0: _ } => ENVELOPE_VERSION_ENV0,
}
}
pub fn get_crypto_kind(&self) -> CryptoKind {
match self {
Envelope::ENV0 { env0 } => env0.get_crypto_kind(),
}
}
pub fn get_timestamp(&self) -> Timestamp {
match self {
Envelope::ENV0 { env0 } => env0.get_timestamp(),
}
}
pub fn get_sender_id(&self) -> NodeId {
match self {
Envelope::ENV0 { env0 } => env0.get_sender_id(),
}
}
pub fn get_recipient_id(&self) -> NodeId {
match self {
Envelope::ENV0 { env0 } => env0.get_recipient_id(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvelopeENV0 {
crypto_kind: CryptoKind,
timestamp: Timestamp,
nonce: Nonce,
bare_sender_id: BareNodeId,
bare_recipient_id: BareNodeId,
}
impl EnvelopeENV0 {
fn try_new(
crypto: &Crypto,
crypto_kind: CryptoKind,
timestamp: Timestamp,
nonce: Nonce,
sender_id: NodeId,
recipient_id: NodeId,
) -> VeilidAPIResult<Self> {
let vcrypto = Self::validate_crypto_kind(crypto, crypto_kind)?;
vcrypto.check_nonce(&nonce)?;
Self::check_node_id(crypto_kind, &sender_id)?;
Self::check_node_id(crypto_kind, &recipient_id)?;
Ok(Self {
crypto_kind,
timestamp,
nonce,
bare_sender_id: sender_id.value(),
bare_recipient_id: recipient_id.value(),
})
}
async fn try_from_signed_data(
crypto: &Crypto,
data: Bytes,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Self> {
if data.len() < ENV0_MIN_ENVELOPE_SIZE {
apibail_generic!("envelope data too small");
}
let crypto_kind = CryptoKind::new(
data[0x04..0x08]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let vcrypto = Self::validate_crypto_kind(crypto, crypto_kind)?.as_async();
let size: u16 = u16::from_le_bytes(
data[0x08..0x0A]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
if (size as usize) > ENV0_MAX_ENVELOPE_SIZE {
apibail_parse_error!("envelope too large", size);
}
if (size as usize) != data.len() {
apibail_parse_error!(
"size doesn't match envelope size",
format!(
"size doesn't match envelope size: size={} data.len()={}",
size,
data.len()
)
);
}
let timestamp: Timestamp = u64::from_le_bytes(
data[0x0A..0x12]
.try_into()
.map_err(VeilidAPIError::internal)?,
)
.into();
let mut nonce_slice: [u8; ENV0_NONCE_LENGTH] = data[0x12..0x2A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let mut sender_id_slice: [u8; HASH_COORDINATE_LENGTH] = data[0x2A..0x4A]
.try_into()
.map_err(VeilidAPIError::internal)?;
let mut recipient_id_slice: [u8; HASH_COORDINATE_LENGTH] = data[0x4A..0x6A]
.try_into()
.map_err(VeilidAPIError::internal)?;
if let Some(nk) = network_key.as_ref() {
for n in 0..ENV0_NONCE_LENGTH {
nonce_slice[n] ^= nk[n];
}
for n in 0..HASH_COORDINATE_LENGTH {
sender_id_slice[n] ^= nk[n];
}
for n in 0..HASH_COORDINATE_LENGTH {
recipient_id_slice[n] ^= nk[n];
}
}
let nonce: Nonce = Nonce::new(&nonce_slice);
let bare_sender_id = BareNodeId::new(&sender_id_slice);
let bare_recipient_id = BareNodeId::new(&recipient_id_slice);
if bare_sender_id == bare_recipient_id {
apibail_parse_error!(
"bare_sender_id should not be same as bare_recipient_id",
bare_recipient_id.encode()
);
}
let sender_public_key = PublicKey::new(crypto_kind, BarePublicKey::new(&bare_sender_id));
let bare_signature = BareSignature::new(
data[(data.len() - ENV0_SIGNATURE_LENGTH)..]
.try_into()
.map_err(VeilidAPIError::internal)?,
);
let signature = Signature::new(crypto_kind, bare_signature);
if !vcrypto
.verify(
&sender_public_key,
data.slice(0..(data.len() - ENV0_SIGNATURE_LENGTH)),
&signature,
)
.await
.map_err(VeilidAPIError::internal)?
{
apibail_parse_error!("signature verification of envelope failed", signature);
}
Ok(Self {
crypto_kind,
timestamp,
nonce,
bare_sender_id,
bare_recipient_id,
})
}
pub async fn decrypt_body(
&self,
crypto: &Crypto,
data: Bytes,
secret_key: &SecretKey,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Bytes> {
let vcrypto = crypto
.get_async(self.crypto_kind)
.expect_or_log("need to ensure only valid crypto kinds here");
vcrypto.check_secret_key(secret_key)?;
let sender_public_key =
PublicKey::new(self.crypto_kind, BarePublicKey::new(&self.bare_sender_id));
let mut dh_secret = vcrypto.cached_dh(&sender_public_key, secret_key).await?;
if let Some(nk) = network_key.as_ref() {
let mut dh_secret_bytes = dh_secret.ref_value().to_vec();
for n in 0..dh_secret_bytes.len() {
dh_secret_bytes[n] ^= nk[n % dh_secret_bytes.len()];
}
dh_secret =
SharedSecret::new(dh_secret.kind(), BareSharedSecret::new(&dh_secret_bytes));
}
let body = vcrypto
.crypt_no_auth_unaligned(
data.slice(0x6A..data.len() - ENV0_SIGNATURE_LENGTH),
&self.nonce,
&dh_secret,
)
.await?;
let body = decompress_size_prepended(&body, Some(ENV0_MAX_ENVELOPE_SIZE))?;
Ok(body.into())
}
pub async fn to_encrypted_data(
&self,
crypto: &Crypto,
body: Bytes,
secret_key: &SecretKey,
network_key: &Option<BareSharedSecret>,
) -> VeilidAPIResult<Bytes> {
let vcrypto = crypto
.get_async(self.crypto_kind)
.expect_or_log("need to ensure only valid crypto kinds here");
vcrypto.check_secret_key(secret_key)?;
let uncompressed_body_size: usize = body.len() + ENV0_MIN_ENVELOPE_SIZE;
if uncompressed_body_size > ENV0_MAX_ENVELOPE_SIZE {
apibail_parse_error!(
"envelope size before compression is too large",
uncompressed_body_size
);
}
let body = Bytes::from(compress_prepend_size(&body));
let envelope_size: usize = body.len() + ENV0_MIN_ENVELOPE_SIZE;
if envelope_size > ENV0_MAX_ENVELOPE_SIZE {
apibail_parse_error!(
"envelope size after compression is too large",
envelope_size
);
}
let recipient_public_key = PublicKey::new(
self.crypto_kind,
BarePublicKey::new(&self.bare_recipient_id),
);
let mut dh_secret = vcrypto.cached_dh(&recipient_public_key, secret_key).await?;
let mut data = BytesMut::zeroed(envelope_size);
data[0x00..0x04].copy_from_slice(&ENVELOPE_VERSION_ENV0.0);
data[0x04..0x08].copy_from_slice(self.crypto_kind.bytes());
data[0x08..0x0A].copy_from_slice(&(envelope_size as u16).to_le_bytes());
data[0x0A..0x12].copy_from_slice(&self.timestamp.as_u64().to_le_bytes());
data[0x12..0x2A].copy_from_slice(&self.nonce);
data[0x2A..0x4A].copy_from_slice(&self.bare_sender_id);
data[0x4A..0x6A].copy_from_slice(&self.bare_recipient_id);
if let Some(nk) = network_key.as_ref() {
let mut dh_secret_bytes = dh_secret.ref_value().to_vec();
for n in 0..dh_secret_bytes.len() {
dh_secret_bytes[n] ^= nk[n % dh_secret_bytes.len()];
}
for n in 0..ENV0_NONCE_LENGTH {
data[0x12 + n] ^= nk[n];
}
for n in 0..HASH_COORDINATE_LENGTH {
data[0x2A + n] ^= nk[n];
}
for n in 0..HASH_COORDINATE_LENGTH {
data[0x4A + n] ^= nk[n];
}
dh_secret =
SharedSecret::new(dh_secret.kind(), BareSharedSecret::new(&dh_secret_bytes));
}
let data = vcrypto
.crypt_b2b_no_auth(body, data, 0x6A, &self.nonce, &dh_secret)
.await?;
let sender_public_key =
PublicKey::new(self.crypto_kind, BarePublicKey::new(&self.bare_sender_id));
let data = vcrypto
.sign_in_place(
&sender_public_key,
secret_key,
data,
0..envelope_size - ENV0_SIGNATURE_LENGTH,
envelope_size - ENV0_SIGNATURE_LENGTH,
)
.await?;
Ok(data.freeze())
}
pub fn get_crypto_kind(&self) -> CryptoKind {
self.crypto_kind
}
pub fn get_timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn get_sender_id(&self) -> NodeId {
NodeId::new(self.crypto_kind, self.bare_sender_id.clone())
}
pub fn get_recipient_id(&self) -> NodeId {
NodeId::new(self.crypto_kind, self.bare_recipient_id.clone())
}
fn validate_crypto_kind(
crypto: &Crypto,
crypto_kind: CryptoKind,
) -> VeilidAPIResult<CryptoSystemGuard<'_>> {
let vcrypto = crypto
.get(crypto_kind)
.ok_or_else(|| VeilidAPIError::parse_error("unsupported crypto kind", crypto_kind))?;
if vcrypto.nonce_length() != ENV0_NONCE_LENGTH
|| vcrypto.hash_digest_length() != HASH_COORDINATE_LENGTH
|| vcrypto.public_key_length() != HASH_COORDINATE_LENGTH
|| vcrypto.signature_length() != ENV0_SIGNATURE_LENGTH
{
apibail_generic!("unsupported crypto kind for this envelope type");
}
Ok(vcrypto)
}
fn check_node_id(crypto_kind: CryptoKind, node_id: &NodeId) -> VeilidAPIResult<()> {
if node_id.kind() != crypto_kind {
apibail_parse_error!("invalid crypto kind for ENV0", node_id.kind());
}
if node_id.ref_value().len() != HASH_COORDINATE_LENGTH {
apibail_parse_error!("invalid node_id length for ENV0", node_id.ref_value().len());
}
Ok(())
}
}