Rust crypto core: FrodoKEM port, hybrid suite, Fury, STREAM, key files
FrodoKEM-640-SHAKE is ported from CIRCL v1.6.4 (the RustCrypto crate implements the incompatible ISO variant) and reproduces the reference NIST KAT digest. ML-KEM-768 comes from RustCrypto ml-kem. Derivation, encapsulation, implicit rejection, KEK, header MAC, header parsing, STREAM and Argon2id key files all match vectors from the Go oracle, Go's files decrypt in Rust, and Go decrypts Rust's. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub fn root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
}
|
||||
|
||||
/// Whether the Go toolchain is available for oracle-backed tests. When it
|
||||
/// is not, those tests print SKIPPED and return; CI installs Go so they
|
||||
/// always run there (VAULT_REQUIRE_ORACLE=1 turns a skip into a failure).
|
||||
pub fn have_go() -> bool {
|
||||
let ok = Command::new("go").arg("version").output().is_ok_and(|o| o.status.success());
|
||||
if !ok && std::env::var_os("VAULT_REQUIRE_ORACLE").is_some() {
|
||||
panic!("VAULT_REQUIRE_ORACLE is set but go is not available");
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
pub fn skip(why: &str) {
|
||||
eprintln!("SKIPPED: {why}");
|
||||
}
|
||||
|
||||
/// Builds the Go oracle CLI once per test process ($VAULT_GO_ORACLE wins).
|
||||
pub fn go_oracle() -> Option<PathBuf> {
|
||||
static BIN: OnceLock<Option<PathBuf>> = OnceLock::new();
|
||||
BIN.get_or_init(|| {
|
||||
if let Some(p) = std::env::var_os("VAULT_GO_ORACLE") {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
if !have_go() {
|
||||
return None;
|
||||
}
|
||||
let out = root().join("target").join("vault-go-oracle");
|
||||
let st = Command::new("go")
|
||||
.args(["build", "-o"])
|
||||
.arg(&out)
|
||||
.arg(".")
|
||||
.current_dir(root().join("oracle/go"))
|
||||
.env("CGO_ENABLED", "0")
|
||||
.status()
|
||||
.expect("run go build");
|
||||
assert!(st.success(), "building the Go oracle failed");
|
||||
Some(out)
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn go_test(pkg: &str, test: &str, env: &[(&str, &Path)]) -> std::process::Output {
|
||||
let mut c = Command::new("go");
|
||||
c.args(["test", "-count=1", "-v", "-run", &format!("^{test}$"), pkg])
|
||||
.current_dir(root().join("oracle/go"));
|
||||
for (k, v) in env {
|
||||
c.env(k, v);
|
||||
}
|
||||
c.output().expect("run go test")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//! Rust-to-Go: files the Rust implementation encrypts must open in the
|
||||
//! frozen Go oracle, and files it corrupts must not.
|
||||
|
||||
mod common;
|
||||
|
||||
use vault::crypto::{fury, kem};
|
||||
|
||||
fn pattern(n: usize, salt: u8) -> Vec<u8> {
|
||||
(0..n).map(|i| (i.wrapping_mul(7) as u8) ^ salt).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_decrypts_rust_files() {
|
||||
if !common::have_go() {
|
||||
return common::skip("go not installed");
|
||||
}
|
||||
let dir = tempfile_dir();
|
||||
let id = kem::Identity::from_suite_seed(kem::SUITE_ID, &pattern(32, 1)).unwrap();
|
||||
for size in [0usize, 1, 16, 65535, 65536, 65537, 131072, 300_001] {
|
||||
let mut out = Vec::new();
|
||||
fury::encrypt_to(&mut out, &id.recipient(), &pattern(size, 0x5a), &mut vault::rng::os());
|
||||
std::fs::write(dir.join(format!("pattern-{size}.fury")), &out).unwrap();
|
||||
if size == 65537 {
|
||||
let mut bad = out.clone();
|
||||
bad.truncate(bad.len() - 1);
|
||||
std::fs::write(dir.join("bad-truncated.fury"), &bad).unwrap();
|
||||
let mut bad = out.clone();
|
||||
bad.push(0);
|
||||
std::fs::write(dir.join("bad-trailing.fury"), &bad).unwrap();
|
||||
let mut bad = out;
|
||||
let mid = bad.len() / 2;
|
||||
bad[mid] ^= 4;
|
||||
std::fs::write(dir.join("bad-flipped.fury"), &bad).unwrap();
|
||||
}
|
||||
}
|
||||
let o = common::go_test("./internal/pqcrypt", "TestOracleDecrypt", &[("VAULT_ORACLE_DECRYPT", &dir)]);
|
||||
let log = String::from_utf8_lossy(&o.stdout);
|
||||
assert!(
|
||||
o.status.success(),
|
||||
"go oracle rejected Rust output:\n{log}{}",
|
||||
String::from_utf8_lossy(&o.stderr)
|
||||
);
|
||||
assert_eq!(log.matches(".fury ok").count(), 8, "{log}");
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
fn tempfile_dir() -> std::path::PathBuf {
|
||||
let d = std::env::temp_dir().join(format!(
|
||||
"vault-interop-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
std::fs::create_dir_all(&d).unwrap();
|
||||
d
|
||||
}
|
||||
Reference in New Issue
Block a user