218 lines
7.3 KiB
Rust
218 lines
7.3 KiB
Rust
#![allow(dead_code)]
|
|
#![allow(clippy::absurd_extreme_comparisons)]
|
|
use super::*;
|
|
use crate::*;
|
|
use core::convert::TryInto;
|
|
|
|
/// 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 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 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 = 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,
|
|
crypto_kind: CryptoKind,
|
|
nonce: Nonce,
|
|
sender_id: PublicKey,
|
|
extra_data: Vec<u8>,
|
|
}
|
|
|
|
impl Receipt {
|
|
pub fn try_new<D: AsRef<[u8]>>(
|
|
version: u8,
|
|
crypto_kind: CryptoKind,
|
|
nonce: Nonce,
|
|
sender_id: PublicKey,
|
|
extra_data: D,
|
|
) -> Result<Self, VeilidAPIError> {
|
|
assert!(VALID_ENVELOPE_VERSIONS.contains(&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",
|
|
extra_data.as_ref().len()
|
|
);
|
|
}
|
|
Ok(Self {
|
|
version,
|
|
crypto_kind,
|
|
nonce,
|
|
sender_id,
|
|
extra_data: Vec::from(extra_data.as_ref()),
|
|
})
|
|
}
|
|
|
|
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; 3] = data[0x00..0x03]
|
|
.try_into()
|
|
.map_err(VeilidAPIError::internal)?;
|
|
if magic != *RECEIPT_MAGIC {
|
|
apibail_generic!("bad magic number");
|
|
}
|
|
|
|
// Check version
|
|
let version = data[0x03];
|
|
if !VALID_ENVELOPE_VERSIONS.contains(&version) {
|
|
apibail_parse_error!("unsupported envelope version", version);
|
|
}
|
|
|
|
// Check crypto kind
|
|
let crypto_kind = FourCC(
|
|
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[0x08..0x0A]
|
|
.try_into()
|
|
.map_err(VeilidAPIError::internal)?,
|
|
);
|
|
if (size as usize) > MAX_RECEIPT_SIZE {
|
|
apibail_parse_error!("receipt size is too large", size);
|
|
}
|
|
if (size as usize) != data.len() {
|
|
apibail_parse_error!(
|
|
"size doesn't match receipt size",
|
|
format!("size={} data.len()={}", size, data.len())
|
|
);
|
|
}
|
|
|
|
// Get sender id
|
|
let sender_id = PublicKey::new(
|
|
data[0x22..0x42]
|
|
.try_into()
|
|
.map_err(VeilidAPIError::internal)?,
|
|
);
|
|
|
|
// Get signature
|
|
let signature = Signature::new(
|
|
data[(data.len() - 64)..]
|
|
.try_into()
|
|
.map_err(VeilidAPIError::internal)?,
|
|
);
|
|
|
|
// Validate signature
|
|
vcrypto
|
|
.verify(&sender_id, &data[0..(data.len() - 64)], &signature)
|
|
.map_err(VeilidAPIError::generic)?;
|
|
|
|
// Get nonce
|
|
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[0x42..(data.len() - 64)]);
|
|
|
|
// Return receipt
|
|
Ok(Self {
|
|
version,
|
|
crypto_kind,
|
|
nonce,
|
|
sender_id,
|
|
extra_data,
|
|
})
|
|
}
|
|
|
|
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..0x03].copy_from_slice(RECEIPT_MAGIC);
|
|
// Write 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(&(receipt_size as u16).to_le_bytes());
|
|
// Write nonce
|
|
data[0x0A..0x22].copy_from_slice(&self.nonce.bytes);
|
|
// Write sender node id
|
|
data[0x22..0x42].copy_from_slice(&self.sender_id.bytes);
|
|
// Write extra data
|
|
if !self.extra_data.is_empty() {
|
|
data[0x42..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice());
|
|
}
|
|
// Sign the receipt
|
|
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);
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_version(&self) -> u8 {
|
|
self.version
|
|
}
|
|
|
|
pub fn get_crypto_kind(&self) -> CryptoKind {
|
|
self.crypto_kind
|
|
}
|
|
|
|
pub fn get_nonce(&self) -> Nonce {
|
|
self.nonce
|
|
}
|
|
|
|
pub fn get_sender_id(&self) -> PublicKey {
|
|
self.sender_id
|
|
}
|
|
pub fn get_extra_data(&self) -> &[u8] {
|
|
&self.extra_data
|
|
}
|
|
}
|