feat: Derandomize the rosenpass protocol by adding testvectors

- Introduced a new module `test_vector_sets.rs` containing test vector definitions for deterministic protocol testing.
- Added a new test file `test_vector_crypto_server.rs` to validate the protocol implementation using captured internal randomness.
- Added serialization and deserialization of `Secret`, `Public`, and `PublicBox` types in `serialization.rs`.
- Added necessary dependencies in `Cargo.toml` for test vectors: assert_tv, serde and base64
- Updated audit records in `audits.toml` and `imports.lock` for new dependencies.
This commit is contained in:
Amin Faez
2025-08-18 15:50:16 +02:00
parent 5c909b4ab9
commit 223fbd551f
27 changed files with 1383 additions and 16 deletions

View File

@@ -19,6 +19,10 @@ rand = { workspace = true }
memsec = { workspace = true }
allocator-api2 = { workspace = true }
log = { workspace = true }
assert_tv = { workspace = true }
base64 = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }
[dev-dependencies]
allocator-api2-tests = { workspace = true }

View File

@@ -47,4 +47,6 @@ mod secret;
pub use crate::secret::Secret;
pub mod policy;
mod serialization;
pub use crate::policy::*;

View File

@@ -0,0 +1,214 @@
use crate::{Public, PublicBox, Secret};
use base64::Engine;
use serde::de::{Error as DeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
fn encode_b64(bytes: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(bytes)
}
fn decode_b64(s: &str) -> Result<Vec<u8>, String> {
base64::engine::general_purpose::STANDARD
.decode(s.as_bytes())
.map_err(|e| format!("Couldn't decode base64: {e}"))
}
struct B64BytesVisitor;
impl<'de> Visitor<'de> for B64BytesVisitor {
type Value = Vec<u8>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a base64-encoded string")
}
fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
decode_b64(v).map_err(E::custom)
}
fn visit_string<E: DeError>(self, v: String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
}
impl<const N: usize> Serialize for Secret<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&encode_b64(self.secret()))
}
}
impl<'de, const N: usize> Deserialize<'de> for Secret<N> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes: Vec<u8> = deserializer.deserialize_string(B64BytesVisitor)?;
if bytes.len() != N {
return Err(D::Error::custom(format!(
"Unexpected length: got {}, expected {}",
bytes.len(),
N
)));
}
// Copies from heap bytes into the internal storage;
// no large stack temporaries.
Ok(Secret::<N>::from_slice(bytes.as_slice()))
}
}
impl<const N: usize> Serialize for Public<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&encode_b64(&self.value))
}
}
impl<'de, const N: usize> Deserialize<'de> for Public<N> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes: Vec<u8> = deserializer.deserialize_string(B64BytesVisitor)?;
if bytes.len() != N {
return Err(D::Error::custom(format!(
"Unexpected length: got {}, expected {}",
bytes.len(),
N
)));
}
Ok(Public::<N>::from_slice(bytes.as_slice()))
}
}
impl<const N: usize> Serialize for PublicBox<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&encode_b64(self.inner.value.as_slice()))
}
}
impl<'de, const N: usize> Deserialize<'de> for PublicBox<N> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes: Vec<u8> = deserializer.deserialize_string(B64BytesVisitor)?;
if bytes.len() != N {
return Err(D::Error::custom(format!(
"Unexpected length: got {}, expected {}",
bytes.len(),
N
)));
}
// Allocate Public<N> on the heap and copy bytes into it
let mut inner = Box::new(Public::<N>::zero());
inner.copy_from_slice(bytes.as_slice());
Ok(PublicBox { inner })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::secret_policy_use_only_malloc_secrets;
use serde::{de::DeserializeOwned, Serialize};
use serde_json;
// Generic helper: serialize to JSON, then deserialize back.
fn roundtrip_json<T>(value: &T) -> T
where
T: Serialize + DeserializeOwned,
{
let json = serde_json::to_string(value).expect("serialize");
serde_json::from_str::<T>(&json).expect("deserialize")
}
pub fn test_init_secret_memory_policy() {
secret_policy_use_only_malloc_secrets();
}
#[test]
fn secret_roundtrip_json() {
test_init_secret_memory_policy();
const N: usize = 32;
let src = [0xABu8; N];
let original = Secret::<N>::from_slice(&src);
let recovered: Secret<N> = roundtrip_json(&original);
assert_eq!(
original.secret(),
recovered.secret(),
"Secret bytes must match after roundtrip"
);
}
#[test]
fn public_roundtrip_json() {
const N: usize = 48;
let src = [0x11u8; N];
let original = Public::<N>::from_slice(&src);
let json = serde_json::to_string(&original).expect("serialize");
let recovered: Public<N> = serde_json::from_str(&json).expect("deserialize");
// Avoid relying on private fields: compare canonical serialization strings.
let json2 = serde_json::to_string(&recovered).expect("re-serialize");
assert_eq!(
json, json2,
"Public must serialize identically after roundtrip"
);
}
#[test]
fn public_box_roundtrip_json() {
const N: usize = 64;
let src = [0x7Fu8; N];
let original = PublicBox::<N>::new(src);
let json = serde_json::to_string(&original).expect("serialize");
let recovered: PublicBox<N> = serde_json::from_str(&json).expect("deserialize");
let json2 = serde_json::to_string(&recovered).expect("re-serialize");
assert_eq!(
json, json2,
"PublicBox must serialize identically after roundtrip"
);
}
#[test]
fn secret_len_mismatch_is_error() {
test_init_secret_memory_policy();
const N_SMALL: usize = 16;
const N_BIG: usize = 32;
let src = [0x55u8; N_SMALL];
let small = Secret::<N_SMALL>::from_slice(&src);
let json = serde_json::to_string(&small).expect("serialize");
// Attempt to deserialize a 16-byte payload into Secret<32> should fail.
let res = serde_json::from_str::<Secret<N_BIG>>(&json);
assert!(
res.is_err(),
"Deserializing into a larger fixed size must error"
);
}
#[test]
fn public_len_mismatch_is_error() {
const N_SMALL: usize = 24;
const N_BIG: usize = 40;
let src = [0x33u8; N_SMALL];
let small = Public::<N_SMALL>::from_slice(&src);
let json = serde_json::to_string(&small).expect("serialize");
let res = serde_json::from_str::<Public<N_BIG>>(&json);
assert!(
res.is_err(),
"Deserializing into a larger fixed size must error"
);
}
#[test]
fn public_box_len_mismatch_is_error() {
const N_SMALL: usize = 8;
const N_BIG: usize = 12;
let src = [0xE0u8; N_SMALL];
let small = PublicBox::<N_SMALL>::new(src);
let json = serde_json::to_string(&small).expect("serialize");
let res = serde_json::from_str::<PublicBox<N_BIG>>(&json);
assert!(
res.is_err(),
"Deserializing into a different fixed size must error"
);
}
}