diff options
Diffstat (limited to 'toolkit/components/telemetry/dap/ffi-gtest')
5 files changed, 281 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/dap/ffi-gtest/Cargo.toml b/toolkit/components/telemetry/dap/ffi-gtest/Cargo.toml new file mode 100644 index 0000000000..756d93a74e --- /dev/null +++ b/toolkit/components/telemetry/dap/ffi-gtest/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dap_ffi-gtest" +version = "0.1.0" +authors = [ + "Simon Friedberger <simon@mozilla.com>", +] +license = "MPL-2.0" +description = "Tests for Rust code for DAP; mainly encoding and NSS bindings." +edition = "2021" + +[dependencies] +dap_ffi = { path = "../ffi" } +hex = { version = "0.4.3", features = ["serde"] } +prio = {version = "0.9.0", default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } + +[lib] +path = "test.rs" diff --git a/toolkit/components/telemetry/dap/ffi-gtest/PrgAes128_tests.json b/toolkit/components/telemetry/dap/ffi-gtest/PrgAes128_tests.json new file mode 100644 index 0000000000..f433e3ef31 --- /dev/null +++ b/toolkit/components/telemetry/dap/ffi-gtest/PrgAes128_tests.json @@ -0,0 +1 @@ +[{"seed":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"info_string":[0,0,0,0,0,0,0,0,0,0],"buffer1_out":[73,12,252,221,214,131,103,213,8,125,53,152,93,163,78,35,181,41,221,100],"buffer2_out":[244,233,25,27,130,143,245,245,158,25,70,216,49,52,48,166,234,182,146,63,140,157,155,191,24,195,111,244,212,81,93,76,198,93,108,144,80,116,232,76,229,167,252,3,88,209,226,11,167,130,127,16,165,185,225,16,200,194,93,70,166,7,104,33,164,93,26,14,98,188,210,45,76,191,10,107,145,174,4,247,99,162,77,183,198,246,163,162,1,109,16,172,213,145,124,163,219,215,60,58,210,65,21,106,109,244,51,140,167,82,216,222,113,105,194,189,119,146,17,170,232,216,191,224,64,216,54,37,242,62,127,108,232,195,19,20,0,168,102,98,72,30,21,198,235,241,35,230,107,24,81,75,174,49,10,177,238,183,131,209,64,95,220,30,87,230,221,72,66,201,106,44,22,52,39,159,73,157,120,133,3,103,114,54,48,59,223,200,37,182,24,160,43,224,39,242,20,252,24,197,181,91,1,189,78,207,184,200,98,141,141,172,212,22,13,86,63,54,85,97,230,123,117,85,60,48,111,136,254,126,252,250,21,126,157,127,72,148,100,205,179,154,67,69,149,96,95,62,241,104,30,63,72,198,75,238,42,174,128,118,110,8,105,176,219,24,69,17,69,76,9,56,146,195,198,12,89,50,133,144,43,93,98,45,54,253,48,72,38,128,108,22,173,8,228,180,254,96,224,103,215,255,163,189,142,35,18,102,166,241,225,16,231,106,31,29,230,172,108,134,57,69,126,120,45,60,149,96,91,17,43,220,103,217,94,149,25,111,50,252,237,147,4,21,230,128,132,41,51,132,6,134,167,155,179,79,38,181,129,149,223,125,192,48,71,122,69,160,136,172,171,62,135,206,109,219,68,184,173,248,255,120,31,195,85,207,177,158,241,42,246,250,7,124,135,67,6,2,149,107,98,118,63,54,55,104,176,194,128,79,49,220,31,31,185,63,205,176,36,28,17,34,138,162,2,77,60,82,174,137,223,14,113,206,111,132,76,246,185,64,161,205,118,132,142,133,165,75,139,161,244,42,189,21,198,199,9,252,244,181,36,210,46,13,173,199,33,252,174,231,207,112,132,192,146,201,55,45,90,176,47,111,190,198,154,191,178,238,103,255,239,130,179,60,84,217,156,246,208,179]},{"seed":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"info_string":[105,110,102,111,32,115,116,114,105,110,103],"buffer1_out":[],"buffer2_out":[204,243,190,112,76,152,33,130,173,41,97,233,121,90,136,170]},{"seed":[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],"info_string":[105,110,102,111,32,115,116,114,105,110,103],"buffer1_out":[],"buffer2_out":[134,173,103,37,215,0,146,211,132,6,147,110,147,170,26,196]},{"seed":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"info_string":[110,110,102,111,32,115,116,114,105,110,103],"buffer1_out":[],"buffer2_out":[245,62,144,220,139,16,59,178,153,145,113,98,101,104,47,213]},{"seed":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],"info_string":[],"buffer1_out":[67,49],"buffer2_out":[108,157,199,13,12]},{"seed":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],"info_string":[110,110,110,110,110],"buffer1_out":[152,11],"buffer2_out":[186,202,32,223,212]}]
\ No newline at end of file diff --git a/toolkit/components/telemetry/dap/ffi-gtest/TestDAPTelemetry.cpp b/toolkit/components/telemetry/dap/ffi-gtest/TestDAPTelemetry.cpp new file mode 100644 index 0000000000..080224ad99 --- /dev/null +++ b/toolkit/components/telemetry/dap/ffi-gtest/TestDAPTelemetry.cpp @@ -0,0 +1,23 @@ + +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/DAPTelemetryBindings.h" + +using namespace mozilla; + +extern "C" void dap_test_prg(); +TEST(DAPTelemetryTests, TestPrg) +{ dap_test_prg(); } + +extern "C" void dap_test_hpke_encrypt(); +TEST(DAPTelemetryTests, TestHpkeEnc) +{ dap_test_hpke_encrypt(); } + +extern "C" void dap_test_encoding(); +TEST(DAPTelemetryTests, TestReportSerialization) +{ dap_test_encoding(); } diff --git a/toolkit/components/telemetry/dap/ffi-gtest/moz.build b/toolkit/components/telemetry/dap/ffi-gtest/moz.build new file mode 100644 index 0000000000..b8444a26d5 --- /dev/null +++ b/toolkit/components/telemetry/dap/ffi-gtest/moz.build @@ -0,0 +1,7 @@ +UNIFIED_SOURCES = ["TestDAPTelemetry.cpp"] +FINAL_LIBRARY = "xul-gtest" + +TEST_HARNESS_FILES.gtest += [ + "../../../../../security/nss/gtests/pk11_gtest/hpke-vectors.json", + "PrgAes128_tests.json", +] diff --git a/toolkit/components/telemetry/dap/ffi-gtest/test.rs b/toolkit/components/telemetry/dap/ffi-gtest/test.rs new file mode 100644 index 0000000000..8db79f5f05 --- /dev/null +++ b/toolkit/components/telemetry/dap/ffi-gtest/test.rs @@ -0,0 +1,230 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use serde::Deserialize; +use std::ffi::c_void; +use std::fs::File; +use std::io::Cursor; + +use thin_vec::ThinVec; + +use dap_ffi::prg::PrgAes128Alt; +use dap_ffi::types::Report; + +use prio::codec::{Decode, Encode}; +use prio::vdaf::prg::{Prg, SeedStream}; + +#[no_mangle] +pub extern "C" fn dap_test_encoding() { + let r = Report::new_dummy(); + let mut encoded = Vec::<u8>::new(); + Report::encode(&r, &mut encoded); + let decoded = Report::decode(&mut Cursor::new(&encoded)).expect("Report decoding failed!"); + if r != decoded { + println!("Report:"); + println!("{:?}", r); + println!("Encoded Report:"); + println!("{:?}", encoded); + println!("Decoded Report:"); + println!("{:?}", decoded); + panic!("Report changed after encoding & decoding."); + } +} + +#[derive(Deserialize, Debug)] +struct PrgTestCase { + seed: [u8; 16], + info_string: Vec<u8>, + buffer1_out: Vec<u8>, + buffer2_out: Vec<u8>, +} + +#[no_mangle] +pub extern "C" fn dap_test_prg() { + let file = File::open("PrgAes128_tests.json").unwrap(); + let testcases: Vec<PrgTestCase> = serde_json::from_reader(file).unwrap(); + for testcase in testcases { + let mut p = PrgAes128Alt::init(&testcase.seed); + p.update(&testcase.info_string); + let mut s = p.into_seed_stream(); + let mut b1 = vec![0u8; testcase.buffer1_out.len()]; + s.fill(&mut b1); + assert_eq!(b1, testcase.buffer1_out); + let mut b2 = vec![0u8; testcase.buffer2_out.len()]; + s.fill(&mut b2); + assert_eq!(b2, testcase.buffer2_out); + } +} + +extern "C" { + pub fn dapHpkeEncrypt( + aContext: *mut c_void, + aAad: *mut u8, + aAadLength: u32, + aPlaintext: *mut u8, + aPlaintextLength: u32, + aOutputShare: &mut ThinVec<u8>, + ) -> bool; + pub fn dapSetupHpkeContextForTesting( + aKey: *const u8, + aKeyLength: u32, + aInfo: *mut u8, + aInfoLength: u32, + aPkEm: *const u8, + aPkEmLength: u32, + aSkEm: *const u8, + aSkEmLength: u32, + aOutputEncapsulatedKey: &mut ThinVec<u8>, + ) -> *mut c_void; + pub fn dapDestroyHpkeContext(aContext: *mut c_void); +} + +struct HpkeContext(*mut c_void); + +impl Drop for HpkeContext { + fn drop(&mut self) { + unsafe { + dapDestroyHpkeContext(self.0); + } + } +} + +type Testsuites = Vec<CiphersuiteTest>; + +#[derive(Debug, Deserialize)] +pub struct HexString(#[serde(with = "hex")] Vec<u8>); +impl AsRef<[u8]> for HexString { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} +#[derive(Debug, Deserialize)] +struct CiphersuiteTest { + mode: i64, + kem_id: i64, + kdf_id: i64, + aead_id: i64, + info: HexString, + #[serde(rename = "ikmR")] + ikm_r: HexString, + #[serde(rename = "ikmE")] + ikm_e: HexString, + #[serde(rename = "skRm")] + sk_r_m: HexString, + #[serde(rename = "skEm")] + sk_e_m: HexString, + #[serde(rename = "pkRm")] + pk_r_m: HexString, + #[serde(rename = "pkEm")] + pk_e_m: HexString, + enc: HexString, + shared_secret: HexString, + key_schedule_context: HexString, + secret: HexString, + key: HexString, + base_nonce: HexString, + exporter_secret: HexString, + encryptions: Vec<Encryption>, + exports: Vec<Export>, + psk: Option<HexString>, + psk_id: Option<HexString>, + ikm_s: Option<HexString>, + sk_sm: Option<HexString>, + pk_sm: Option<HexString>, +} + +#[derive(Debug, Deserialize)] +pub struct Encryption { + pub aad: HexString, + pub ciphertext: HexString, + pub nonce: HexString, + pub plaintext: HexString, +} + +#[derive(Debug, Deserialize)] +pub struct Export { + pub exporter_context: HexString, + #[serde(rename = "L")] + pub length: i64, + pub exported_value: HexString, +} + +#[no_mangle] +pub extern "C" fn dap_test_hpke_encrypt() { + let file = File::open("hpke-vectors.json").unwrap(); + let tests: Testsuites = serde_json::from_reader(file).unwrap(); + + let mut have_tested = false; + + for (test_idx, test) in tests.into_iter().enumerate() { + // Mode must be "Base" + if test.mode != 0 + // KEM must be DHKEM(X25519, HKDF-SHA256) + || test.kem_id != 32 + // KDF must be HKDF-SHA256 + || test.kdf_id != 1 + // AEAD must be AES-128-GCM + || test.aead_id != 1 + { + continue; + } + + have_tested = true; + + let mut pk_r_serialized = test.pk_r_m.0; + let mut info = test.info.0; + let mut pk_e_serialized = test.pk_e_m.0; + let mut sk_e_serialized = test.sk_e_m.0; + + let mut encapsulated_key = ThinVec::<u8>::new(); + + let ctx = HpkeContext(unsafe { + dapSetupHpkeContextForTesting( + pk_r_serialized.as_mut_ptr(), + pk_r_serialized.len().try_into().unwrap(), + info.as_mut_ptr(), + info.len().try_into().unwrap(), + pk_e_serialized.as_mut_ptr(), + pk_e_serialized.len().try_into().unwrap(), + sk_e_serialized.as_mut_ptr(), + sk_e_serialized.len().try_into().unwrap(), + &mut encapsulated_key, + ) + }); + if ctx.0.is_null() { + panic!("Failed to set up HPKE context."); + } + if encapsulated_key != test.enc.0 { + panic!("Encapsulated key is wrong!"); + } + + for (encryption_idx, encryption) in test.encryptions.into_iter().enumerate() { + let mut encrypted_share = ThinVec::<u8>::new(); + + let mut aad = encryption.aad.0.clone(); + let mut pt = encryption.plaintext.0.clone(); + unsafe { + dapHpkeEncrypt( + ctx.0, + aad.as_mut_ptr(), + aad.len().try_into().unwrap(), + pt.as_mut_ptr(), + pt.len().try_into().unwrap(), + &mut encrypted_share, + ); + } + + if encrypted_share != encryption.ciphertext.0 { + println!("Test: {}, Encryption: {}", test_idx, encryption_idx); + println!("Expected:"); + println!("{:?}", encryption.ciphertext.0); + println!("Actual:"); + println!("{:?}", encrypted_share); + panic!("Encryption outputs did not match!"); + } + } + } + + assert!(have_tested); +} |