use crate::network::key_types::{X25519Keypair, X25519Pubkey, X25519Seckey};
use crate::network::onionreq::hop_encryption::{EncryptType, HopEncryption, HopEncryptionError, parse_enc_type};
pub const DEFAULT_MAX_SIZE: usize = 10_485_760;
#[derive(Debug, thiserror::Error)]
pub enum ParserError {
#[error("Request data too small")]
TooSmall,
#[error("Request data too big")]
TooBig,
#[error("Encrypted segment too small")]
SegmentTooSmall,
#[error("Metadata missing ephemeral_key")]
MissingEphemeralKey,
#[error("JSON parse error: {0}")]
JsonError(String),
#[error("Key error: {0}")]
KeyError(#[from] crate::network::key_types::KeyError),
#[error("Decryption error: {0}")]
DecryptionError(#[from] HopEncryptionError),
}
pub struct OnionReqParser {
#[allow(dead_code)]
keys: X25519Keypair,
enc: HopEncryption,
enc_type: EncryptType,
remote_pk: X25519Pubkey,
payload: Vec<u8>,
}
impl OnionReqParser {
pub fn new(
x25519_pubkey: &[u8],
x25519_privkey: &[u8],
req: &[u8],
max_size: usize,
) -> Result<Self, ParserError> {
let pk = X25519Pubkey::from_bytes(x25519_pubkey)?;
let sk = X25519Seckey::from_bytes(x25519_privkey)?;
let enc = HopEncryption::new(sk.clone(), pk, true);
if req.len() < 4 {
return Err(ParserError::TooSmall);
}
if req.len() > max_size {
return Err(ParserError::TooBig);
}
let size = u32::from_le_bytes([req[0], req[1], req[2], req[3]]) as usize;
let data = &req[4..];
if data.len() < size {
return Err(ParserError::SegmentTooSmall);
}
let ciphertext = &data[..size];
let metadata_bytes = &data[size..];
let metadata: serde_json::Value = serde_json::from_slice(metadata_bytes)
.map_err(|e| ParserError::JsonError(e.to_string()))?;
let enc_type = if let Some(et) = metadata.get("enc_type").and_then(|v| v.as_str()) {
parse_enc_type(et)?
} else {
EncryptType::AesGcm
};
let eph_key_str = metadata
.get("ephemeral_key")
.and_then(|v| v.as_str())
.ok_or(ParserError::MissingEphemeralKey)?;
let remote_pk = crate::network::key_types::parse_x25519_pubkey(eph_key_str)?;
let payload = enc.decrypt(enc_type, ciphertext, &remote_pk)?;
Ok(Self {
keys: (pk, sk),
enc,
enc_type,
remote_pk,
payload,
})
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
pub fn move_payload(&mut self) -> Vec<u8> {
std::mem::take(&mut self.payload)
}
pub fn remote_pubkey(&self) -> &X25519Pubkey {
&self.remote_pk
}
pub fn encrypt_reply(&self, reply: &[u8]) -> Result<Vec<u8>, HopEncryptionError> {
self.enc.encrypt(self.enc_type, reply, &self.remote_pk)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network::key_types::x25519_keypair;
use crate::network::onionreq::hop_encryption::HopEncryption;
#[test]
fn test_parser_roundtrip() {
let (server_pk, server_sk) = x25519_keypair();
let (client_pk, client_sk) = x25519_keypair();
let plaintext = b"Hello from client";
let client_enc = HopEncryption::new(client_sk, client_pk, false);
let ciphertext = client_enc
.encrypt(EncryptType::XChaCha20, plaintext, &server_pk)
.unwrap();
let metadata = serde_json::json!({
"ephemeral_key": client_pk.hex(),
"enc_type": "xchacha20",
});
let metadata_bytes = metadata.to_string().into_bytes();
let size_bytes = (ciphertext.len() as u32).to_le_bytes();
let mut req = Vec::new();
req.extend_from_slice(&size_bytes);
req.extend_from_slice(&ciphertext);
req.extend_from_slice(&metadata_bytes);
let parser = OnionReqParser::new(
server_pk.as_bytes(),
server_sk.as_bytes(),
&req,
DEFAULT_MAX_SIZE,
)
.unwrap();
assert_eq!(parser.payload(), plaintext);
}
#[test]
fn test_parser_too_small() {
let (pk, sk) = x25519_keypair();
let result = OnionReqParser::new(pk.as_bytes(), sk.as_bytes(), &[0, 0], DEFAULT_MAX_SIZE);
assert!(result.is_err());
}
}