use aws_lc_rs::aead::{AES_256_GCM, Aad, LessSafeKey, Nonce, UnboundKey};
use aws_lc_rs::agreement;
use aws_lc_rs::hkdf::{HKDF_SHA256, Salt};
#[cfg(test)]
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug)]
pub enum CryptoError {
InvalidKeyLength,
InvalidNonceLength,
InvalidCiphertextLength,
EncryptionFailed,
DecryptionFailed,
EcdhFailed,
HkdfFailed,
CertificateGenerationFailed(String),
}
impl std::fmt::Display for CryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CryptoError::InvalidKeyLength => write!(f, "Invalid key length"),
CryptoError::InvalidNonceLength => write!(f, "Invalid nonce length"),
CryptoError::InvalidCiphertextLength => write!(f, "Invalid ciphertext length"),
CryptoError::EncryptionFailed => write!(f, "Encryption failed"),
CryptoError::DecryptionFailed => write!(f, "Decryption failed"),
CryptoError::EcdhFailed => write!(f, "ECDH key exchange failed"),
CryptoError::HkdfFailed => write!(f, "HKDF derivation failed"),
CryptoError::CertificateGenerationFailed(e) => {
write!(f, "Certificate generation failed: {}", e)
}
}
}
}
impl std::error::Error for CryptoError {}
impl From<CryptoError> for std::io::Error {
fn from(err: CryptoError) -> Self {
std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string())
}
}
pub fn perform_ecdh(
private_key: &[u8; 32],
public_key: &[u8; 32],
) -> Result<[u8; 32], CryptoError> {
let my_private_key = agreement::PrivateKey::from_private_key(&agreement::X25519, private_key)
.map_err(|_| CryptoError::EcdhFailed)?;
let _my_public_key = my_private_key
.compute_public_key()
.map_err(|_| CryptoError::EcdhFailed)?;
let peer_public_key =
agreement::UnparsedPublicKey::new(&agreement::X25519, public_key.as_ref());
let mut shared_secret = [0u8; 32];
agreement::agree(
&my_private_key,
peer_public_key,
CryptoError::EcdhFailed,
|key_material| {
shared_secret.copy_from_slice(key_material);
Ok(())
},
)?;
Ok(shared_secret)
}
pub fn derive_auth_key(
shared_secret: &[u8; 32],
salt: &[u8],
info: &[u8],
) -> Result<[u8; 32], CryptoError> {
debug_assert_eq!(salt.len(), 20, "salt must be exactly 20 bytes");
let salt = Salt::new(HKDF_SHA256, salt);
let prk = salt.extract(shared_secret);
let info_pieces = [info];
let okm = prk
.expand(&info_pieces, HKDF_SHA256)
.map_err(|_| CryptoError::HkdfFailed)?;
let mut auth_key = [0u8; 32];
okm.fill(&mut auth_key)
.map_err(|_| CryptoError::HkdfFailed)?;
Ok(auth_key)
}
pub fn encrypt_session_id(
plaintext: &[u8; 16],
auth_key: &[u8; 32],
nonce: &[u8],
aad: &[u8],
) -> Result<[u8; 32], CryptoError> {
debug_assert_eq!(nonce.len(), 12, "nonce must be exactly 12 bytes");
let unbound_key =
UnboundKey::new(&AES_256_GCM, auth_key).map_err(|_| CryptoError::EncryptionFailed)?;
let sealing_key = LessSafeKey::new(unbound_key);
let nonce_obj =
Nonce::try_assume_unique_for_key(nonce).map_err(|_| CryptoError::InvalidNonceLength)?;
let aad_obj = Aad::from(aad);
let mut in_out = plaintext.to_vec();
sealing_key
.seal_in_place_append_tag(nonce_obj, aad_obj, &mut in_out)
.map_err(|_| CryptoError::EncryptionFailed)?;
if in_out.len() != 32 {
return Err(CryptoError::EncryptionFailed);
}
let mut result = [0u8; 32];
result.copy_from_slice(&in_out);
Ok(result)
}
pub fn decrypt_session_id(
ciphertext_and_tag: &[u8; 32],
auth_key: &[u8; 32],
nonce: &[u8],
aad: &[u8],
) -> Result<[u8; 16], CryptoError> {
debug_assert_eq!(nonce.len(), 12, "nonce must be exactly 12 bytes");
let unbound_key =
UnboundKey::new(&AES_256_GCM, auth_key).map_err(|_| CryptoError::DecryptionFailed)?;
let opening_key = LessSafeKey::new(unbound_key);
let nonce_obj =
Nonce::try_assume_unique_for_key(nonce).map_err(|_| CryptoError::InvalidNonceLength)?;
let aad_obj = Aad::from(aad);
let mut in_out = ciphertext_and_tag.to_vec();
let plaintext = opening_key
.open_in_place(nonce_obj, aad_obj, &mut in_out)
.map_err(|_| CryptoError::DecryptionFailed)?;
if plaintext.len() != 16 {
return Err(CryptoError::DecryptionFailed);
}
let mut result = [0u8; 16];
result.copy_from_slice(plaintext);
Ok(result)
}
#[cfg(test)]
fn create_session_id(version: [u8; 3], timestamp: u32, short_id: &[u8; 8]) -> [u8; 32] {
let mut session_id = [0u8; 32];
session_id[0] = version[0]; session_id[1] = version[1]; session_id[2] = version[2]; session_id[4..8].copy_from_slice(×tamp.to_be_bytes());
session_id[8..16].copy_from_slice(short_id);
session_id
}
#[cfg(test)]
fn get_current_timestamp() -> u32 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs() as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ecdh() {
let private_key_a = [1u8; 32];
let private_key_b = [2u8; 32];
let priv_a =
agreement::PrivateKey::from_private_key(&agreement::X25519, &private_key_a).unwrap();
let pub_a = priv_a.compute_public_key().unwrap();
let priv_b =
agreement::PrivateKey::from_private_key(&agreement::X25519, &private_key_b).unwrap();
let pub_b = priv_b.compute_public_key().unwrap();
let mut shared_a_bytes = [0u8; 32];
agreement::agree(
&priv_a,
agreement::UnparsedPublicKey::new(&agreement::X25519, pub_b.as_ref()),
(),
|key_material| {
shared_a_bytes.copy_from_slice(key_material);
Ok(())
},
)
.unwrap();
let mut shared_b_bytes = [0u8; 32];
agreement::agree(
&priv_b,
agreement::UnparsedPublicKey::new(&agreement::X25519, pub_a.as_ref()),
(),
|key_material| {
shared_b_bytes.copy_from_slice(key_material);
Ok(())
},
)
.unwrap();
assert_eq!(shared_a_bytes, shared_b_bytes);
}
#[test]
fn test_session_id_structure() {
let version = [1, 8, 1]; let timestamp = get_current_timestamp();
let short_id = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let session_id = create_session_id(version, timestamp, &short_id);
assert_eq!(session_id[0], 1); assert_eq!(session_id[1], 8); assert_eq!(session_id[2], 1); assert_eq!(session_id[3], 0);
let extracted_timestamp =
u32::from_be_bytes([session_id[4], session_id[5], session_id[6], session_id[7]]);
assert_eq!(extracted_timestamp, timestamp);
assert_eq!(&session_id[8..16], &short_id[..]);
for byte in &session_id[16..] {
assert_eq!(*byte, 0);
}
}
#[test]
fn test_version_comparison() {
let v1_8_1 = [1u8, 8, 1];
let v1_8_0 = [1u8, 8, 0];
let v1_9_0 = [1u8, 9, 0];
let v2_0_0 = [2u8, 0, 0];
assert!(v1_8_0 < v1_8_1);
assert!(v1_8_1 < v1_9_0);
assert!(v1_9_0 < v2_0_0);
assert!(v1_8_1 > v1_8_0);
}
#[test]
fn test_timestamp_validation_logic() {
let now = get_current_timestamp();
let max_diff_ms = 60000u64;
let max_diff_secs = max_diff_ms / 1000;
let diff = now.abs_diff(now);
assert!((diff as u64) <= max_diff_secs);
let past_timestamp = now - 30;
let diff = now.abs_diff(past_timestamp);
assert!((diff as u64) <= max_diff_secs);
let future_timestamp = now + 30;
let diff = now.abs_diff(future_timestamp);
assert!((diff as u64) <= max_diff_secs);
let old_timestamp = now.saturating_sub(120);
let diff = now.abs_diff(old_timestamp);
assert!((diff as u64) > max_diff_secs);
let future_timestamp = now + 120;
let diff = now.abs_diff(future_timestamp);
assert!((diff as u64) > max_diff_secs);
}
#[test]
fn test_session_id_encryption_preserves_structure() {
let version = [1, 8, 1];
let timestamp = 1234567890u32;
let short_id = [0xAB; 8];
let session_id = create_session_id(version, timestamp, &short_id);
let client_private = [0x01; 32];
let server_public = [0x02; 32];
let shared_secret = perform_ecdh(&client_private, &server_public).unwrap();
let salt = [0x03; 20];
let auth_key = derive_auth_key(&shared_secret, &salt, b"REALITY").unwrap();
let plaintext: [u8; 16] = session_id[0..16].try_into().unwrap();
let nonce = [0x04; 12];
let aad = b"test additional authenticated data";
let encrypted = encrypt_session_id(&plaintext, &auth_key, &nonce, aad).unwrap();
let decrypted = decrypt_session_id(&encrypted, &auth_key, &nonce, aad).unwrap();
assert_eq!(decrypted[0], version[0]);
assert_eq!(decrypted[1], version[1]);
assert_eq!(decrypted[2], version[2]);
assert_eq!(decrypted[3], 0);
let recovered_timestamp =
u32::from_be_bytes([decrypted[4], decrypted[5], decrypted[6], decrypted[7]]);
assert_eq!(recovered_timestamp, timestamp);
assert_eq!(&decrypted[8..16], &short_id[..]);
}
#[test]
fn test_hkdf() {
let shared_secret = [0x42u8; 32];
let salt = [0x43u8; 20];
let info = b"REALITY";
let auth_key = derive_auth_key(&shared_secret, &salt, info).unwrap();
assert_eq!(auth_key.len(), 32);
let auth_key2 = derive_auth_key(&shared_secret, &salt, info).unwrap();
assert_eq!(auth_key, auth_key2);
let salt2 = [0x44u8; 20];
let auth_key3 = derive_auth_key(&shared_secret, &salt2, info).unwrap();
assert_ne!(auth_key, auth_key3);
}
#[test]
fn test_aes_gcm_roundtrip() {
let plaintext = [0x55u8; 16];
let auth_key = [0x66u8; 32];
let nonce = [0x77u8; 12];
let aad = b"additional authenticated data";
let encrypted = encrypt_session_id(&plaintext, &auth_key, &nonce, aad).unwrap();
assert_eq!(encrypted.len(), 32);
let decrypted = decrypt_session_id(&encrypted, &auth_key, &nonce, aad).unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_aes_gcm_wrong_key_fails() {
let plaintext = [0x55u8; 16];
let auth_key = [0x66u8; 32];
let wrong_key = [0x67u8; 32];
let nonce = [0x77u8; 12];
let aad = b"additional authenticated data";
let encrypted = encrypt_session_id(&plaintext, &auth_key, &nonce, aad).unwrap();
let result = decrypt_session_id(&encrypted, &wrong_key, &nonce, aad);
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_wrong_aad_fails() {
let plaintext = [0x55u8; 16];
let auth_key = [0x66u8; 32];
let nonce = [0x77u8; 12];
let aad = b"additional authenticated data";
let wrong_aad = b"wrong additional authenticated data";
let encrypted = encrypt_session_id(&plaintext, &auth_key, &nonce, aad).unwrap();
let result = decrypt_session_id(&encrypted, &auth_key, &nonce, wrong_aad);
assert!(result.is_err());
}
#[test]
fn test_create_session_id() {
let version = [1, 8, 1];
let timestamp = 1234567890u32;
let short_id = [0xAB; 8];
let session_id = create_session_id(version, timestamp, &short_id);
assert_eq!(session_id[0], 1);
assert_eq!(session_id[1], 8);
assert_eq!(session_id[2], 1);
assert_eq!(session_id[3], 0);
let ts = u32::from_be_bytes([session_id[4], session_id[5], session_id[6], session_id[7]]);
assert_eq!(ts, timestamp);
assert_eq!(&session_id[8..16], &short_id[..]);
for byte in &session_id[16..] {
assert_eq!(*byte, 0);
}
}
#[test]
fn test_validation_scenarios() {
println!("\n=== REALITY Validation Test Scenarios ===\n");
println!("Scenario 1: Version Validation");
println!(" Client version: [1, 8, 1]");
println!(" Min version: [1, 8, 0]");
println!(" Max version: [1, 9, 0]");
println!(" Expected: PASS ✓");
let client_version = [1u8, 8, 1];
let min_version = [1u8, 8, 0];
let max_version = [1u8, 9, 0];
assert!(client_version >= min_version);
assert!(client_version <= max_version);
println!("\nScenario 2: Version Below Minimum");
println!(" Client version: [1, 7, 5]");
println!(" Min version: [1, 8, 0]");
println!(" Expected: FAIL ✗");
let old_client_version = [1u8, 7, 5];
assert!(old_client_version < min_version);
println!("\nScenario 3: Version Above Maximum");
println!(" Client version: [2, 0, 0]");
println!(" Max version: [1, 9, 0]");
println!(" Expected: FAIL ✗");
let new_client_version = [2u8, 0, 0];
assert!(new_client_version > max_version);
println!("\nScenario 4: Timestamp Within Bounds");
let now = get_current_timestamp();
let client_timestamp = now - 30; let max_diff_ms = 60000u64;
let diff = now.abs_diff(client_timestamp);
println!(" Server time: {}", now);
println!(" Client time: {} (30 seconds ago)", client_timestamp);
println!(" Max allowed: {} seconds", max_diff_ms / 1000);
println!(" Actual diff: {} seconds", diff);
println!(" Expected: PASS ✓");
assert!((diff as u64) <= (max_diff_ms / 1000));
println!("\nScenario 5: Timestamp Out of Bounds");
let old_timestamp = now - 120; let diff = now.abs_diff(old_timestamp);
println!(" Server time: {}", now);
println!(" Client time: {} (2 minutes ago)", old_timestamp);
println!(" Max allowed: {} seconds", max_diff_ms / 1000);
println!(" Actual diff: {} seconds", diff);
println!(" Expected: FAIL ✗");
assert!((diff as u64) > (max_diff_ms / 1000));
println!("\nScenario 6: Future Timestamp Validation");
let future_timestamp = now + 45; let diff = now.abs_diff(future_timestamp);
println!(" Server time: {}", now);
println!(" Client time: {} (45 seconds in future)", future_timestamp);
println!(" Max allowed: {} seconds", max_diff_ms / 1000);
println!(" Actual diff: {} seconds", diff);
println!(" Expected: PASS ✓");
assert!((diff as u64) <= (max_diff_ms / 1000));
println!("\n=== All Validation Scenarios Passed ===\n");
}
}