Bug 1901507 - Upgrade ohttp crate to latest version (0.5.1) r=necko-reviewers,glandium,supply-chain-reviewers,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D213526
This commit is contained in:
Valentin Gosu 2024-08-08 12:52:30 +00:00
parent d4eaf72722
commit 10a45dcf15
20 changed files with 852 additions and 307 deletions

8
Cargo.lock generated
View File

@ -435,7 +435,7 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.63.999"
version = "0.64.999"
dependencies = [
"bindgen 0.69.4",
]
@ -4344,11 +4344,11 @@ dependencies = [
[[package]]
name = "ohttp"
version = "0.3.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850ce328ec7e4dc1a9446c56aef700d21d914268c8529b96017a2bf10f74b70f"
checksum = "578cb11a3fb5c85697ed8bb850d5ad1cbf819d3eea05c2b253cf1d240fbb10c5"
dependencies = [
"bindgen 0.63.999",
"bindgen 0.64.999",
"byteorder",
"hex",
"lazy_static",

View File

@ -1,6 +1,6 @@
[package]
name = "bindgen"
version = "0.63.999"
version = "0.64.999"
edition = "2018"
license = "BSD-3-Clause"

View File

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
nserror = { path = "../../../../xpcom/rust/nserror" }
ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client", "server"] }
ohttp = { version = "0.5", default-features = false, features = ["gecko", "nss", "client", "server"] }
rand = "0.8"
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
xpcom = { path = "../../../../xpcom/rust/xpcom" }

View File

@ -110,7 +110,7 @@ impl ObliviousHttpServer {
&self,
enc_request: &ThinVec<u8>,
) -> Result<RefPtr<nsIObliviousHttpServerResponse>, nsresult> {
let mut server = self.server.borrow_mut();
let server = self.server.borrow_mut();
let (request, server_response) = server
.decapsulate(enc_request)
.map_err(|_| NS_ERROR_FAILURE)?;
@ -138,7 +138,7 @@ impl ObliviousHttp {
) -> Result<RefPtr<nsIObliviousHttpClientRequest>, nsresult> {
ohttp::init();
let client = ClientRequest::new(encoded_config).map_err(|_| NS_ERROR_FAILURE)?;
let client = ClientRequest::from_encoded_config(encoded_config).map_err(|_| NS_ERROR_FAILURE)?;
let (enc_request, response) = client.encapsulate(request).map_err(|_| NS_ERROR_FAILURE)?;
let oblivious_http_client_response =
ObliviousHttpClientResponse::allocate(InitObliviousHttpClientResponse {

View File

@ -373,8 +373,8 @@ user-login = "seanmonstar"
user-name = "Sean McArthur"
[[publisher.ohttp]]
version = "0.3.1"
when = "2023-02-23"
version = "0.5.1"
when = "2024-01-10"
user-id = 128763
user-login = "martinthomson"
user-name = "Martin Thomson"
@ -559,6 +559,13 @@ user-id = 2017
user-login = "mbrubeck"
user-name = "Matt Brubeck"
[[publisher.syn]]
version = "1.0.109"
when = "2023-02-24"
user-id = 3618
user-login = "dtolnay"
user-name = "David Tolnay"
[[publisher.syn]]
version = "2.0.68"
when = "2024-06-23"
@ -1125,6 +1132,12 @@ criteria = "safe-to-deploy"
delta = "0.31.1 -> 0.32.0"
notes = "Various new features and refactorings as one would expect from an object parsing crate, all looks good."
[[audits.bytecode-alliance.audits.peeking_take_while]]
who = "Nick Fitzgerald <fitzgen@gmail.com>"
criteria = "safe-to-deploy"
version = "1.0.0"
notes = "I am the author of this crate."
[[audits.bytecode-alliance.audits.percent-encoding]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"b92b4dceca6d654156f3be0c62061f4f869ed92d27fc2178be59fe8efa588db4","README.md":"a97309a7b0c65dbcbd43e6c64bf99e023cd07e9d3de5c4559f33db430edafebd","bindings/bindings.toml":"a016870127b63151e760c964d687934a4883ee165bdd9718341c8dd50be5a3f2","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nss_init.h":"cd4dffd0c629ece5786736dd6d26db8a96f56fd56ef95b150c623c41080c2f9e","bindings/nss_p11.h":"a16f60d0210d5823f2d92d0c04988a0bb1da85901388490cb3e755a62cc7d5dd","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","build.rs":"3a67dd5475792d9ce5d081f1f6d2abf3fc29b02d2fc2dcebf0c44a1fc2e6abcd","src/err.rs":"99127514f826c97072d33891e07277dd21108c877c1cd66b7be23131be183235","src/hpke.rs":"21182eed9bd71ea59fba1670491f845a7f773d1285b89d2772e052f8a37f475c","src/lib.rs":"d79c9edbeac382db424a1c0612cca353b275e16d7162b7b0877f8cb8874cfa80","src/nss/aead.rs":"780c47f57a6fa3e498d46f9935dc112c3b010658fa2d3874975b205f7b436ff3","src/nss/err.rs":"d367116bf9840ebe06d358066a1755f7d9936438dbc2e4e71a6b345d05e4bcd5","src/nss/hkdf.rs":"d70147f27be9defcb2cd67da9e49a4f09adecd0a2ebad646eae94d09512b1b0b","src/nss/hpke.rs":"4d216f653e1a588402646f0041f97fa1ef1907f4003e16dc5dfde73bfb568686","src/nss/mod.rs":"7dc88bef24a2d80a0c46a7e1cb24f2568bd75c1d082b3b39f005cc92873ab0cf","src/nss/p11.rs":"9bcf9ec65dc357f805b951fff875b5d7d91e0bf7170d96fe2044933e94950b8a","src/rand.rs":"be3a82fb6090b5cb833c2b8ba6e72690b9f44f7f91477e2c5b70b75623174b87","src/rh/aead.rs":"44321f6098e44b75ed586ebee843bed4edb88e9f8a384457a28ae8ab3d5245ea","src/rh/hkdf.rs":"4c6e59ca24498939e9b8da2da6336ae2f62396101f1b9b25f1ae587ab21c1a9d","src/rh/hpke.rs":"0744c71cfc33bdf22c4889e58bcc947a9debcb0a03890cf7e6d9353526bd4578","src/rh/mod.rs":"d6045628f9b95d75e8bfcc30d55b1fc8b5ea9e2a4c45e20102f4f33c0b711a0c"},"package":"850ce328ec7e4dc1a9446c56aef700d21d914268c8529b96017a2bf10f74b70f"}
{"files":{"Cargo.toml":"f93723973762c48df66c9e2125da726559e9bc0bf5ed38fb4e776af3381502fc","README.md":"a97309a7b0c65dbcbd43e6c64bf99e023cd07e9d3de5c4559f33db430edafebd","bindings/bindings.toml":"a016870127b63151e760c964d687934a4883ee165bdd9718341c8dd50be5a3f2","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nss_init.h":"cd4dffd0c629ece5786736dd6d26db8a96f56fd56ef95b150c623c41080c2f9e","bindings/nss_p11.h":"a16f60d0210d5823f2d92d0c04988a0bb1da85901388490cb3e755a62cc7d5dd","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","build.rs":"75f7bc67d2757bf68f1ba64258a7a7db4b94de5403936b73d8140e9558c30698","src/config.rs":"d39a765f561423f320e0be8c6aaabf3c3fb85248468b345aa43f778974280945","src/err.rs":"533b382c7e74906521395fdff4b514981535fe1b45b307528c547bb545765adf","src/hpke.rs":"b98b3a1d2e87bd89d43d89e2513e2d3211832aef3fd2157337939186aa7c8531","src/lib.rs":"bfc972343aa3fa6707b0aaea04c25f1b6fa2ff3f9e9761ae6a09f9be52fce214","src/nss/aead.rs":"01b3b1654a310a276edb5750d2d0e3ee14943f8695f413d176b65623369d81be","src/nss/err.rs":"d367116bf9840ebe06d358066a1755f7d9936438dbc2e4e71a6b345d05e4bcd5","src/nss/hkdf.rs":"d70147f27be9defcb2cd67da9e49a4f09adecd0a2ebad646eae94d09512b1b0b","src/nss/hpke.rs":"cbd353d2f0c8db82fcf25dac2f2d2554e8a6258c4b21e0279e618fb2f992c49c","src/nss/mod.rs":"bca4b6fcc807b7b146989fe500c0e5b249ddfcaff1a795cc8b84a70631b50a60","src/nss/p11.rs":"1761896f0359c37cd186e34daaf1b582e907a123fe987b937c314f7b2cecc9de","src/rand.rs":"be3a82fb6090b5cb833c2b8ba6e72690b9f44f7f91477e2c5b70b75623174b87","src/rh/aead.rs":"88dd43ffec20a8870a424d27dbda0a9db74f4b3d1b03097220854a304ab091cd","src/rh/hkdf.rs":"4c6e59ca24498939e9b8da2da6336ae2f62396101f1b9b25f1ae587ab21c1a9d","src/rh/hpke.rs":"8d7de700e48bf4f6a711e6c5df1eb4a7ae7d33c65d75728e9d1c8c797f474e7a","src/rh/mod.rs":"d6045628f9b95d75e8bfcc30d55b1fc8b5ea9e2a4c45e20102f4f33c0b711a0c"},"package":"578cb11a3fb5c85697ed8bb850d5ad1cbf819d3eea05c2b253cf1d240fbb10c5"}

View File

@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "ohttp"
version = "0.3.1"
version = "0.5.1"
authors = ["Martin Thomson <mt@lowentropy.net>"]
build = "build.rs"
description = "Oblivious HTTP"
@ -44,11 +44,25 @@ version = "0.11"
optional = true
[dependencies.hpke]
version = "0.7"
features = ["std"]
version = "0.10.0"
features = [
"std",
"x25519",
]
optional = true
default-features = false
[dependencies.hpke-pq]
version = "0.10.1"
features = [
"std",
"x25519",
"xyber768d00",
]
optional = true
default-features = false
package = "hpke_pq"
[dependencies.lazy_static]
version = "1.4"
@ -72,7 +86,7 @@ version = "0.9"
default-features = false
[build-dependencies.bindgen]
version = "0.63"
version = "0.64"
features = ["runtime"]
optional = true
default-features = false
@ -91,21 +105,27 @@ version = "1.0"
version = "0.5"
[features]
app-svc = ["nss"]
client = []
default = [
"client",
"server",
"rust-hpke",
]
gecko = ["mozbuild"]
external-sqlite = []
gecko = [
"nss",
"mozbuild",
]
nss = ["bindgen"]
pq = ["hpke-pq"]
rust-hpke = [
"hpke/x25519",
"rand",
"aead",
"aes-gcm",
"chacha20poly1305",
"hkdf",
"sha2",
"hpke",
]
server = []

View File

@ -133,59 +133,108 @@ mod nss {
assert!(status.success(), "NSS build failed");
}
fn dynamic_link() {
let libs = if env::consts::OS == "windows" {
&["nssutil3.dll", "nss3.dll"]
fn nspr_libs() -> Vec<&'static str> {
if env::consts::OS == "windows" {
vec!["libplds4", "libplc4", "libnspr4"]
} else {
&["nssutil3", "nss3"]
};
dynamic_link_both(libs);
vec!["plds4", "plc4", "nspr4"]
}
}
fn dynamic_link_both(extra_libs: &[&str]) {
let nspr_libs = if env::consts::OS == "windows" {
&["libplds4", "libplc4", "libnspr4"]
fn dynamic_link() {
let mut libs = if env::consts::OS == "windows" {
vec!["nssutil3.dll", "nss3.dll"]
} else {
&["plds4", "plc4", "nspr4"]
vec!["nssutil3", "nss3"]
};
for lib in nspr_libs.iter().chain(extra_libs) {
libs.append(&mut nspr_libs());
for lib in &libs {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
}
fn static_link() {
fn static_softoken_libs(nsslibdir: &Path) -> Vec<&'static str> {
let mut static_libs = vec!["pk11wrap_static", "softokn_static", "freebl_static"];
// NSS optionally builds platform-specific acceleration libraries as
// separate static libraries.
let accel_libs = &[
"gcm-aes-x86_c_lib",
"sha-x86_c_lib",
"hw-acc-crypto-avx",
"hw-acc-crypto-avx2",
"armv8_c_lib",
"gcm-aes-arm32-neon_c_lib",
"gcm-aes-aarch64_c_lib",
// NOTE: The intel-gcm-* libraries are already automatically
// included in freebl_static as source files.
];
// Build rules are complex, so simply check the lib directory to see if
// any of the accelerator libraries were built to decide what to
// include. Check different variations of the filename to handle
// platform differences.
for libname in accel_libs {
let filename = if env::consts::OS == "windows" {
format!("{libname}.lib")
} else {
format!("lib{libname}.a")
};
if nsslibdir.join(filename).is_file() {
static_libs.push(libname);
}
}
static_libs
}
fn static_link(nsslibdir: &Path, use_static_softoken: bool, use_static_nspr: bool) {
let mut static_libs = vec![
"certdb",
"certhi",
"cryptohi",
"freebl",
"nss_static",
"nssb",
"nssdev",
"nsspki",
"nssutil",
"pk11wrap",
"pkcs12",
"pkcs7",
"smime",
"softokn_static",
];
if env::consts::OS != "macos" {
static_libs.push("sqlite");
let mut dynamic_libs = vec![];
if use_static_softoken {
// Statically link pk11/softokn/freebl
static_libs.append(&mut static_softoken_libs(nsslibdir));
} else {
// Use dlopen to get softokn3.so
static_libs.push("pk11wrap");
}
for lib in static_libs {
println!("cargo:rustc-link-lib=static={}", lib);
if use_static_nspr {
static_libs.append(&mut nspr_libs());
} else {
dynamic_libs.append(&mut nspr_libs());
}
if cfg!(not(feature = "external-sqlite")) && env::consts::OS != "macos" {
static_libs.push("sqlite");
}
// Dynamic libs that aren't transitively included by NSS libs.
let mut other_libs = Vec::new();
if env::consts::OS != "windows" {
other_libs.extend_from_slice(&["pthread", "dl", "c", "z"]);
dynamic_libs.extend_from_slice(&["pthread", "dl", "c", "z"]);
}
if env::consts::OS == "macos" {
other_libs.push("sqlite3");
if cfg!(not(feature = "external-sqlite")) && env::consts::OS == "macos" {
dynamic_libs.push("sqlite3");
}
for lib in &static_libs {
println!("cargo:rustc-link-lib=static={}", lib);
}
for lib in &dynamic_libs {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
dynamic_link_both(&other_libs);
}
fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
@ -275,7 +324,9 @@ mod nss {
nsslibdir.to_str().unwrap()
);
if is_debug() {
static_link();
let use_static_softoken = true;
let use_static_nspr = true;
static_link(&nsslibdir, use_static_softoken, use_static_nspr);
} else {
dynamic_link();
}
@ -411,10 +462,48 @@ mod nss {
unreachable!()
}
#[cfg(feature = "app-svc")]
fn setup_for_app_svc() -> Vec<String> {
// Locate the NSS libraries that application_services is using.
// NOTE: This directory has a slightly different layout than then normal
// 'dist' directory that NSS builds output.
let nss_dir = nss_dir().expect("NSS_DIR env must be set for app_svc builds");
if !nss_dir.exists() {
eprintln!(
"NSS_DIR path (obtained via `env`) does not exist: {}",
nss_dir.display()
);
panic!("It looks like NSS is not built. Please run `libs/verify-[platform]-environment.sh` in application-services first!");
}
let lib_dir = nss_dir.join("lib");
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_string_lossy()
);
// For app_svc builds, we use static linking of NSS.
let use_static_softoken = true;
let use_static_nspr = true;
static_link(&lib_dir, use_static_softoken, use_static_nspr);
let include_dir = nss_dir.join("include");
println!("cargo:include={}", include_dir.to_string_lossy());
vec![String::from("-I") + &include_dir.join("nss").to_string_lossy()]
}
#[cfg(not(feature = "app-svc"))]
fn setup_for_app_svc() -> Vec<String> {
unreachable!()
}
pub fn build() {
println!("cargo:rerun-if-env-changed=NSS_DIR");
let flags = if cfg!(feature = "gecko") {
setup_for_gecko()
} else if cfg!(feature = "app-svc") {
setup_for_app_svc()
} else {
nss_dir().map_or_else(pkg_config, |nss| build_nss(&nss))
};

376
third_party/rust/ohttp/src/config.rs vendored Normal file
View File

@ -0,0 +1,376 @@
use crate::{
err::{Error, Res},
hpke::{Aead as AeadId, Kdf, Kem},
KeyId,
};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use std::{
convert::TryFrom,
io::{BufRead, BufReader, Cursor, Read},
};
#[cfg(feature = "nss")]
use crate::nss::{
hpke::{generate_key_pair, Config as HpkeConfig, HpkeR},
PrivateKey, PublicKey,
};
#[cfg(feature = "rust-hpke")]
use crate::rh::hpke::{
derive_key_pair, generate_key_pair, Config as HpkeConfig, HpkeR, PrivateKey, PublicKey,
};
/// A tuple of KDF and AEAD identifiers.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SymmetricSuite {
kdf: Kdf,
aead: AeadId,
}
impl SymmetricSuite {
#[must_use]
pub const fn new(kdf: Kdf, aead: AeadId) -> Self {
Self { kdf, aead }
}
#[must_use]
pub fn kdf(self) -> Kdf {
self.kdf
}
#[must_use]
pub fn aead(self) -> AeadId {
self.aead
}
}
/// The key configuration of a server. This can be used by both client and server.
/// An important invariant of this structure is that it does not include
/// any combination of KEM, KDF, and AEAD that is not supported.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
pub struct KeyConfig {
pub(crate) key_id: KeyId,
pub(crate) kem: Kem,
pub(crate) symmetric: Vec<SymmetricSuite>,
pub(crate) sk: Option<PrivateKey>,
pub(crate) pk: PublicKey,
}
impl KeyConfig {
fn strip_unsupported(symmetric: &mut Vec<SymmetricSuite>, kem: Kem) {
symmetric.retain(|s| HpkeConfig::new(kem, s.kdf(), s.aead()).supported());
}
/// Construct a configuration for the server side.
/// # Panics
/// If the configurations don't include a supported configuration.
pub fn new(key_id: u8, kem: Kem, mut symmetric: Vec<SymmetricSuite>) -> Res<Self> {
Self::strip_unsupported(&mut symmetric, kem);
assert!(!symmetric.is_empty());
let (sk, pk) = generate_key_pair(kem)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: Some(sk),
pk,
})
}
/// Derive a configuration for the server side from input keying material,
/// using the `DeriveKeyPair` functionality of the HPKE KEM defined here:
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-12.html#section-4>
/// # Panics
/// If the configurations don't include a supported configuration.
#[allow(unused)]
pub fn derive(
key_id: u8,
kem: Kem,
mut symmetric: Vec<SymmetricSuite>,
ikm: &[u8],
) -> Res<Self> {
#[cfg(feature = "rust-hpke")]
{
Self::strip_unsupported(&mut symmetric, kem);
assert!(!symmetric.is_empty());
let (sk, pk) = derive_key_pair(kem, ikm)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: Some(sk),
pk,
})
}
#[cfg(not(feature = "rust-hpke"))]
{
Err(Error::Unsupported)
}
}
/// Encode a list of key configurations.
///
/// This produces the key configuration format that is used for
/// the "application/ohttp-keys" media type.
/// Each item in the list is written as per [`encode()`].
///
/// # Panics
/// Not as a result of this function.
///
/// [`encode()`]: Self::encode
pub fn encode_list(list: &[impl AsRef<Self>]) -> Res<Vec<u8>> {
let mut buf = Vec::new();
for c in list {
let offset = buf.len();
buf.write_u16::<NetworkEndian>(0)?;
c.as_ref().write(&mut buf)?;
let len = buf.len() - offset - 2;
buf[offset] = u8::try_from(len >> 8)?;
buf[offset + 1] = u8::try_from(len & 0xff).unwrap();
}
Ok(buf)
}
fn write(&self, buf: &mut Vec<u8>) -> Res<()> {
buf.write_u8(self.key_id)?;
buf.write_u16::<NetworkEndian>(u16::from(self.kem))?;
let pk_buf = self.pk.key_data()?;
buf.extend_from_slice(&pk_buf);
buf.write_u16::<NetworkEndian>((self.symmetric.len() * 4).try_into()?)?;
for s in &self.symmetric {
buf.write_u16::<NetworkEndian>(u16::from(s.kdf()))?;
buf.write_u16::<NetworkEndian>(u16::from(s.aead()))?;
}
Ok(())
}
/// Encode into a wire format. This shares a format with the core of ECH:
///
/// ```tls-format
/// opaque HpkePublicKey[Npk];
/// uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke
/// uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke
/// uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke
///
/// struct {
/// HpkeKdfId kdf_id;
/// HpkeAeadId aead_id;
/// } ECHCipherSuite;
///
/// struct {
/// uint8 key_id;
/// HpkeKemId kem_id;
/// HpkePublicKey public_key;
/// ECHCipherSuite cipher_suites<4..2^16-4>;
/// } ECHKeyConfig;
/// ```
/// # Panics
/// Not as a result of this function.
pub fn encode(&self) -> Res<Vec<u8>> {
let mut buf = Vec::new();
self.write(&mut buf)?;
Ok(buf)
}
/// Construct a configuration from the encoded server configuration.
/// The format of `encoded_config` is the output of `Self::encode`.
pub fn decode(encoded_config: &[u8]) -> Res<Self> {
let end_position = u64::try_from(encoded_config.len())?;
let mut r = Cursor::new(encoded_config);
let key_id = r.read_u8()?;
let kem = Kem::try_from(r.read_u16::<NetworkEndian>()?)?;
// Note that the KDF and AEAD doesn't matter here.
let kem_config = HpkeConfig::new(kem, Kdf::HkdfSha256, AeadId::Aes128Gcm);
if !kem_config.supported() {
return Err(Error::Unsupported);
}
let mut pk_buf = vec![0; kem_config.kem().n_pk()];
r.read_exact(&mut pk_buf)?;
let sym_len = r.read_u16::<NetworkEndian>()?;
let mut sym = vec![0; usize::from(sym_len)];
r.read_exact(&mut sym)?;
if sym.is_empty() || (sym.len() % 4 != 0) {
return Err(Error::Format);
}
let sym_count = sym.len() / 4;
let mut sym_r = BufReader::new(&sym[..]);
let mut symmetric = Vec::with_capacity(sym_count);
for _ in 0..sym_count {
let kdf = Kdf::try_from(sym_r.read_u16::<NetworkEndian>()?)?;
let aead = AeadId::try_from(sym_r.read_u16::<NetworkEndian>()?)?;
symmetric.push(SymmetricSuite::new(kdf, aead));
}
// Check that there was nothing extra and we are at the end of the buffer.
if r.position() != end_position {
return Err(Error::Format);
}
Self::strip_unsupported(&mut symmetric, kem);
let pk = HpkeR::decode_public_key(kem_config.kem(), &pk_buf)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: None,
pk,
})
}
/// Decode a list of key configurations.
/// This only returns the valid and supported key configurations;
/// unsupported configurations are dropped silently.
pub fn decode_list(encoded_list: &[u8]) -> Res<Vec<Self>> {
let end_position = u64::try_from(encoded_list.len())?;
let mut r = Cursor::new(encoded_list);
let mut configs = Vec::new();
loop {
if r.position() == end_position {
break;
}
let len = usize::from(r.read_u16::<NetworkEndian>()?);
let buf = r.fill_buf()?;
if len > buf.len() {
return Err(Error::Truncated);
}
let res = Self::decode(&buf[..len]);
r.consume(len);
match res {
Ok(config) => configs.push(config),
Err(Error::Unsupported) => continue,
Err(e) => return Err(e),
}
}
Ok(configs)
}
/// Select creates a new configuration that contains the identified symmetric suite.
///
/// # Errors
/// If the given suite is not supported by this configuration.
pub fn select(&self, sym: SymmetricSuite) -> Res<HpkeConfig> {
if self.symmetric.contains(&sym) {
let config = HpkeConfig::new(self.kem, sym.kdf(), sym.aead());
Ok(config)
} else {
Err(Error::Unsupported)
}
}
}
impl AsRef<Self> for KeyConfig {
fn as_ref(&self) -> &Self {
self
}
}
#[cfg(test)]
mod test {
use crate::{
hpke::{Aead, Kdf, Kem},
init, Error, KeyConfig, KeyId, SymmetricSuite,
};
use std::iter::zip;
const KEY_ID: KeyId = 1;
const KEM: Kem = Kem::X25519Sha256;
const SYMMETRIC: &[SymmetricSuite] = &[
SymmetricSuite::new(Kdf::HkdfSha256, Aead::Aes128Gcm),
SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305),
];
#[test]
fn encode_decode_config_list() {
const COUNT: usize = 3;
init();
let mut configs = Vec::with_capacity(COUNT);
configs.resize_with(COUNT, || {
KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap()
});
let buf = KeyConfig::encode_list(&configs).unwrap();
let decoded_list = KeyConfig::decode_list(&buf).unwrap();
for (original, decoded) in zip(&configs, &decoded_list) {
assert_eq!(decoded.key_id, original.key_id);
assert_eq!(decoded.kem, original.kem);
assert_eq!(
decoded.pk.key_data().unwrap(),
original.pk.key_data().unwrap()
);
assert!(decoded.sk.is_none());
assert!(original.sk.is_some());
}
// Check that truncation errors in `KeyConfig::decode` are caught.
assert!(KeyConfig::decode_list(&buf[..buf.len() - 3]).is_err());
}
#[test]
fn empty_config_list() {
let list = KeyConfig::decode_list(&[]).unwrap();
assert!(list.is_empty());
// A reserved KEM ID is not bad. Note that we don't check that the data
// following the KEM ID is even the minimum length, allowing this to be
// zero bytes, where you need at least some bytes in a public key and some
// bytes to identify at least one KDF and AEAD (i.e., more than 6 bytes).
let list = KeyConfig::decode_list(&[0, 3, 0, 0, 0]).unwrap();
assert!(list.is_empty());
}
#[test]
fn bad_config_list_length() {
init();
// A one byte length for a config.
let res = KeyConfig::decode_list(&[0]);
assert!(matches!(res, Err(Error::Io(_))));
}
#[test]
fn decode_bad_config() {
init();
let mut x25519 = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC))
.unwrap()
.encode()
.unwrap();
{
// Truncation tests.
let trunc = |n: usize| KeyConfig::decode(&x25519[..n]);
// x25519, truncated inside the KEM ID.
assert!(matches!(trunc(2), Err(Error::Io(_))));
// ... inside the public key.
assert!(matches!(trunc(4), Err(Error::Io(_))));
// ... inside the length of the KDF+AEAD list.
assert!(matches!(trunc(36), Err(Error::Io(_))));
// ... inside the KDF+AEAD list.
assert!(matches!(trunc(38), Err(Error::Io(_))));
}
// And then with an extra byte at the end.
x25519.push(0);
assert!(matches!(KeyConfig::decode(&x25519), Err(Error::Format)));
}
/// Truncate the KDF+AEAD list badly.
#[test]
fn truncate_kdf_aead_list() {
init();
let mut x25519 = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC))
.unwrap()
.encode()
.unwrap();
x25519.truncate(38);
assert_eq!(usize::from(x25519[36]), SYMMETRIC.len() * 4);
x25519[36] = 1;
assert!(matches!(KeyConfig::decode(&x25519), Err(Error::Format)));
}
}

View File

@ -10,9 +10,12 @@ pub enum Error {
Crypto(#[from] crate::nss::Error),
#[error("an error was found in the format")]
Format,
#[cfg(all(feature = "rust-hpke", not(feature = "pq")))]
#[error("a problem occurred with HPKE: {0}")]
#[cfg(feature = "rust-hpke")]
Hpke(#[from] ::hpke::HpkeError),
#[cfg(all(feature = "rust-hpke", feature = "pq"))]
#[error("a problem occurred with HPKE: {0}")]
Hpke(#[from] ::hpke_pq::HpkeError),
#[error("an internal error occurred")]
Internal,
#[error("the wrong type of key was provided for the selected KEM")]

View File

@ -13,7 +13,9 @@ macro_rules! convert_enum {
fn try_from(v: u16) -> Result<Self, Self::Error> {
match v {
$(x if x == $name::$vname as u16 => Ok($name::$vname),)*
$($(#[$vmeta])*
x if x == $name::$vname as u16
=> Ok($name::$vname),)*
_ => Err(crate::Error::Unsupported),
}
}
@ -30,6 +32,9 @@ macro_rules! convert_enum {
convert_enum! {
pub enum Kem {
X25519Sha256 = 32,
#[cfg(feature = "pq")]
X25519Kyber768Draft00 = 48,
}
}
@ -38,6 +43,9 @@ impl Kem {
pub fn n_enc(self) -> usize {
match self {
Kem::X25519Sha256 => 32,
#[cfg(feature = "pq")]
Kem::X25519Kyber768Draft00 => 1120,
}
}
@ -45,6 +53,9 @@ impl Kem {
pub fn n_pk(self) -> usize {
match self {
Kem::X25519Sha256 => 32,
#[cfg(feature = "pq")]
Kem::X25519Kyber768Draft00 => 1216,
}
}
}

View File

@ -5,6 +5,7 @@
allow(dead_code, unused_imports)
)]
mod config;
mod err;
pub mod hpke;
#[cfg(feature = "nss")]
@ -14,11 +15,16 @@ mod rand;
#[cfg(feature = "rust-hpke")]
mod rh;
pub use err::Error;
pub use crate::{
config::{KeyConfig, SymmetricSuite},
err::Error,
};
use crate::hpke::{Aead as AeadId, Kdf, Kem};
use crate::{
err::Res,
hpke::{Aead as AeadId, Kdf, Kem},
};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use err::Res;
use log::trace;
use std::{
cmp::max,
@ -28,25 +34,21 @@ use std::{
};
#[cfg(feature = "nss")]
use nss::random;
use crate::nss::random;
#[cfg(feature = "nss")]
use nss::{
use crate::nss::{
aead::{Aead, Mode, NONCE_LEN},
hkdf::{Hkdf, KeyMechanism},
hpke::{generate_key_pair, Config as HpkeConfig, Exporter, HpkeR, HpkeS},
PrivateKey, PublicKey,
hpke::{Config as HpkeConfig, Exporter, HpkeR, HpkeS},
};
#[cfg(feature = "rust-hpke")]
use crate::rand::random;
#[cfg(feature = "rust-hpke")]
use rh::{
use crate::rh::{
aead::{Aead, Mode, NONCE_LEN},
hkdf::{Hkdf, KeyMechanism},
hpke::{
derive_key_pair, generate_key_pair, Config as HpkeConfig, Exporter, HpkeR, HpkeS,
PrivateKey, PublicKey,
},
hpke::{Config as HpkeConfig, Exporter, HpkeR, HpkeS},
};
/// The request header is a `KeyId` and 2 each for KEM, KDF, and AEAD identifiers
@ -66,187 +68,6 @@ pub fn init() {
nss::init();
}
/// A tuple of KDF and AEAD identifiers.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SymmetricSuite {
kdf: Kdf,
aead: AeadId,
}
impl SymmetricSuite {
#[must_use]
pub const fn new(kdf: Kdf, aead: AeadId) -> Self {
Self { kdf, aead }
}
#[must_use]
pub fn kdf(self) -> Kdf {
self.kdf
}
#[must_use]
pub fn aead(self) -> AeadId {
self.aead
}
}
/// The key configuration of a server. This can be used by both client and server.
/// An important invariant of this structure is that it does not include
/// any combination of KEM, KDF, and AEAD that is not supported.
pub struct KeyConfig {
key_id: KeyId,
kem: Kem,
symmetric: Vec<SymmetricSuite>,
sk: Option<PrivateKey>,
pk: PublicKey,
}
impl KeyConfig {
fn strip_unsupported(symmetric: &mut Vec<SymmetricSuite>, kem: Kem) {
symmetric.retain(|s| HpkeConfig::new(kem, s.kdf(), s.aead()).supported());
}
/// Construct a configuration for the server side.
/// # Panics
/// If the configurations don't include a supported configuration.
pub fn new(key_id: u8, kem: Kem, mut symmetric: Vec<SymmetricSuite>) -> Res<Self> {
Self::strip_unsupported(&mut symmetric, kem);
assert!(!symmetric.is_empty());
let (sk, pk) = generate_key_pair(kem)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: Some(sk),
pk,
})
}
/// Derive a configuration for the server side from input keying material,
/// using the `DeriveKeyPair` functionality of the HPKE KEM defined here:
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-12.html#section-4>
/// # Panics
/// If the configurations don't include a supported configuration.
#[allow(unused)]
pub fn derive(
key_id: u8,
kem: Kem,
mut symmetric: Vec<SymmetricSuite>,
ikm: &[u8],
) -> Res<Self> {
#[cfg(feature = "rust-hpke")]
{
Self::strip_unsupported(&mut symmetric, kem);
assert!(!symmetric.is_empty());
let (sk, pk) = derive_key_pair(kem, ikm)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: Some(sk),
pk,
})
}
#[cfg(not(feature = "rust-hpke"))]
{
Err(Error::Unsupported)
}
}
/// Encode into a wire format. This shares a format with the core of ECH:
///
/// ```tls-format
/// opaque HpkePublicKey[Npk];
/// uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke
/// uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke
/// uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke
///
/// struct {
/// HpkeKdfId kdf_id;
/// HpkeAeadId aead_id;
/// } ECHCipherSuite;
///
/// struct {
/// uint8 key_id;
/// HpkeKemId kem_id;
/// HpkePublicKey public_key;
/// ECHCipherSuite cipher_suites<4..2^16-4>;
/// } ECHKeyConfig;
/// ```
/// # Panics
/// Not as a result of this function.
pub fn encode(&self) -> Res<Vec<u8>> {
let mut buf = Vec::new();
buf.write_u8(self.key_id)?;
buf.write_u16::<NetworkEndian>(u16::from(self.kem))?;
let pk_buf = self.pk.key_data()?;
buf.extend_from_slice(&pk_buf);
buf.write_u16::<NetworkEndian>((self.symmetric.len() * 4).try_into()?)?;
for s in &self.symmetric {
buf.write_u16::<NetworkEndian>(u16::from(s.kdf()))?;
buf.write_u16::<NetworkEndian>(u16::from(s.aead()))?;
}
Ok(buf)
}
/// Construct a configuration from the encoded server configuration.
/// The format of `encoded_config` is the output of `Self::encode`.
fn parse(encoded_config: &[u8]) -> Res<Self> {
let mut r = BufReader::new(encoded_config);
let key_id = r.read_u8()?;
let kem = Kem::try_from(r.read_u16::<NetworkEndian>()?)?;
// Note that the KDF and AEAD doesn't matter here.
let kem_config = HpkeConfig::new(kem, Kdf::HkdfSha256, AeadId::Aes128Gcm);
if !kem_config.supported() {
return Err(Error::Unsupported);
}
let mut pk_buf = vec![0; kem_config.kem().n_pk()];
r.read_exact(&mut pk_buf)?;
let sym_len = r.read_u16::<NetworkEndian>()?;
let mut sym = vec![0; usize::from(sym_len)];
r.read_exact(&mut sym)?;
if sym.is_empty() || (sym.len() % 4 != 0) {
return Err(Error::Format);
}
let sym_count = sym.len() / 4;
let mut sym_r = BufReader::new(&sym[..]);
let mut symmetric = Vec::with_capacity(sym_count);
for _ in 0..sym_count {
let kdf = Kdf::try_from(sym_r.read_u16::<NetworkEndian>()?)?;
let aead = AeadId::try_from(sym_r.read_u16::<NetworkEndian>()?)?;
symmetric.push(SymmetricSuite::new(kdf, aead));
}
// Check that there was nothing extra.
let mut tmp = [0; 1];
if r.read(&mut tmp)? > 0 {
return Err(Error::Format);
}
Self::strip_unsupported(&mut symmetric, kem);
let pk = HpkeR::decode_public_key(kem_config.kem(), &pk_buf)?;
Ok(Self {
key_id,
kem,
symmetric,
sk: None,
pk,
})
}
fn select(&self, sym: SymmetricSuite) -> Res<HpkeConfig> {
if self.symmetric.contains(&sym) {
let config = HpkeConfig::new(self.kem, sym.kdf(), sym.aead());
Ok(config)
} else {
Err(Error::Unsupported)
}
}
}
/// Construct the info parameter we use to initialize an `HpkeS` instance.
fn build_info(key_id: KeyId, config: HpkeConfig) -> Res<Vec<u8>> {
let mut info = Vec::with_capacity(INFO_LEN);
@ -270,11 +91,8 @@ pub struct ClientRequest {
#[cfg(feature = "client")]
impl ClientRequest {
/// Reads an encoded configuration and constructs a single use client sender.
/// See `KeyConfig::encode` for the structure details.
#[allow(clippy::similar_names)] // for `sk_s` and `pk_s`
pub fn new(encoded_config: &[u8]) -> Res<Self> {
let mut config = KeyConfig::parse(encoded_config)?;
/// Construct a `ClientRequest` from a specific `KeyConfig` instance.
pub fn from_config(config: &mut KeyConfig) -> Res<Self> {
// TODO(mt) choose the best config, not just the first.
let selected = config.select(config.symmetric[0])?;
@ -287,6 +105,25 @@ impl ClientRequest {
Ok(Self { hpke, header })
}
/// Reads an encoded configuration and constructs a single use client sender.
/// See `KeyConfig::decode` for the structure details.
pub fn from_encoded_config(encoded_config: &[u8]) -> Res<Self> {
let mut config = KeyConfig::decode(encoded_config)?;
Self::from_config(&mut config)
}
/// Reads an encoded list of configurations and constructs a single use client sender
/// from the first supported configuration.
/// See `KeyConfig::decode_list` for the structure details.
pub fn from_encoded_config_list(encoded_config_list: &[u8]) -> Res<Self> {
let mut configs = KeyConfig::decode_list(encoded_config_list)?;
if let Some(mut config) = configs.pop() {
Self::from_config(&mut config)
} else {
Err(Error::Unsupported)
}
}
/// Encapsulate a request. This consumes this object.
/// This produces a response handler and the bytes of an encapsulated request.
pub fn encapsulate(mut self, request: &[u8]) -> Res<(Vec<u8>, ClientResponse)> {
@ -312,6 +149,7 @@ impl ClientRequest {
/// It holds a single key pair and can generate a configuration.
/// (A more complex server would have multiple key pairs. This is simple.)
#[cfg(feature = "server")]
#[derive(Debug, Clone)]
pub struct Server {
config: KeyConfig,
}
@ -336,7 +174,7 @@ impl Server {
/// # Panics
/// Not as a consequence of this code, but Rust won't know that for sure.
#[allow(clippy::similar_names)] // for kem_id and key_id
pub fn decapsulate(&mut self, enc_request: &[u8]) -> Res<(Vec<u8>, ServerResponse)> {
pub fn decapsulate(&self, enc_request: &[u8]) -> Res<(Vec<u8>, ServerResponse)> {
if enc_request.len() < REQUEST_HEADER_LEN {
return Err(Error::Truncated);
}
@ -364,7 +202,7 @@ impl Server {
let mut hpke = HpkeR::new(
cfg,
&self.config.pk,
self.config.sk.as_mut().unwrap(),
self.config.sk.as_ref().unwrap(),
&enc,
&info,
)?;
@ -475,9 +313,10 @@ impl ClientResponse {
#[cfg(all(test, feature = "client", feature = "server"))]
mod test {
use crate::{
config::SymmetricSuite,
err::Res,
hpke::{Aead, Kdf, Kem},
ClientRequest, Error, KeyConfig, KeyId, Server, SymmetricSuite,
ClientRequest, Error, KeyConfig, KeyId, Server,
};
use log::trace;
use std::{fmt::Debug, io::ErrorKind};
@ -497,7 +336,7 @@ mod test {
fn init() {
crate::init();
let _ = env_logger::try_init();
_ = env_logger::try_init(); // ignore errors here
}
#[test]
@ -505,11 +344,11 @@ mod test {
init();
let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
let mut server = Server::new(server_config).unwrap();
let server = Server::new(server_config).unwrap();
let encoded_config = server.config().encode().unwrap();
trace!("Config: {}", hex::encode(&encoded_config));
let client = ClientRequest::new(&encoded_config).unwrap();
let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
trace!("Request: {}", hex::encode(REQUEST));
trace!("Encapsulated Request: {}", hex::encode(&enc_request));
@ -530,12 +369,12 @@ mod test {
init();
let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
let mut server = Server::new(server_config).unwrap();
let server = Server::new(server_config).unwrap();
let encoded_config = server.config().encode().unwrap();
let client1 = ClientRequest::new(&encoded_config).unwrap();
let client1 = ClientRequest::from_encoded_config(&encoded_config).unwrap();
let (enc_request1, client_response1) = client1.encapsulate(REQUEST).unwrap();
let client2 = ClientRequest::new(&encoded_config).unwrap();
let client2 = ClientRequest::from_encoded_config(&encoded_config).unwrap();
let (enc_request2, client_response2) = client2.encapsulate(REQUEST).unwrap();
assert_ne!(enc_request1, enc_request2);
@ -570,10 +409,10 @@ mod test {
init();
let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
let mut server = Server::new(server_config).unwrap();
let server = Server::new(server_config).unwrap();
let encoded_config = server.config().encode().unwrap();
let client = ClientRequest::new(&encoded_config).unwrap();
let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
let (enc_request, _) = client.encapsulate(REQUEST).unwrap();
let res = server.decapsulate(&enc_request[..cut]);
@ -601,10 +440,10 @@ mod test {
init();
let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
let mut server = Server::new(server_config).unwrap();
let server = Server::new(server_config).unwrap();
let encoded_config = server.config().encode().unwrap();
let client = ClientRequest::new(&encoded_config).unwrap();
let client = ClientRequest::from_encoded_config(&encoded_config).unwrap();
let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
let (request, server_response) = server.decapsulate(&enc_request).unwrap();
@ -643,7 +482,7 @@ mod test {
init();
let config = KeyConfig::parse(EXPECTED_CONFIG).unwrap();
let config = KeyConfig::decode(EXPECTED_CONFIG).unwrap();
let new_config = KeyConfig::derive(KEY_ID, KEM, Vec::from(SYMMETRIC), IKM).unwrap();
assert_eq!(config.key_id, new_config.key_id);
@ -654,4 +493,31 @@ mod test {
let encoded_config = server.config().encode().unwrap();
assert_eq!(EXPECTED_CONFIG, encoded_config);
}
#[test]
fn request_from_config_list() {
init();
let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
let server = Server::new(server_config).unwrap();
let encoded_config = server.config().encode().unwrap();
let mut header: [u8; 2] = [0; 2];
header[0] = u8::try_from((encoded_config.len() & 0xFF00) >> 8).unwrap();
header[1] = u8::try_from(encoded_config.len() & 0xFF).unwrap();
let mut encoded_config_list = Vec::new();
encoded_config_list.extend(header.to_vec());
encoded_config_list.extend(encoded_config);
let client = ClientRequest::from_encoded_config_list(&encoded_config_list).unwrap();
let (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
let (request, server_response) = server.decapsulate(&enc_request).unwrap();
assert_eq!(&request[..], REQUEST);
let enc_response = server_response.encapsulate(RESPONSE).unwrap();
let response = client_response.decapsulate(&enc_response).unwrap();
assert_eq!(&response[..], RESPONSE);
}
}

View File

@ -44,6 +44,8 @@ unsafe fn destroy_aead_context(ctx: *mut PK11Context) {
}
scoped_ptr!(Context, PK11Context, destroy_aead_context);
unsafe impl Send for Context {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
Encrypt,

View File

@ -86,6 +86,8 @@ impl HpkeContext {
}
}
unsafe impl Send for HpkeContext {}
impl Exporter for HpkeContext {
fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
let mut out: *mut sys::PK11SymKey = null_mut();
@ -168,7 +170,7 @@ impl HpkeR {
pub fn new(
config: Config,
pk_r: &PublicKey,
sk_r: &mut PrivateKey,
sk_r: &PrivateKey,
enc: &[u8],
info: &[u8],
) -> Res<Self> {
@ -290,7 +292,7 @@ pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> {
#[cfg(test)]
mod test {
use super::{generate_key_pair, Config, HpkeR, HpkeS};
use super::{generate_key_pair, Config, HpkeContext, HpkeR, HpkeS};
use crate::{hpke::Aead, init};
const INFO: &[u8] = b"info";
@ -302,9 +304,9 @@ mod test {
fn make() {
init();
let cfg = Config::default();
let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let (sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
let _hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &hpke_s.enc().unwrap(), INFO).unwrap();
let _hpke_r = HpkeR::new(cfg, &pk_r, &sk_r, &hpke_s.enc().unwrap(), INFO).unwrap();
}
#[allow(clippy::similar_names)] // for sk_x and pk_x
@ -316,7 +318,7 @@ mod test {
..Config::default()
};
assert!(cfg.supported());
let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let (sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
// Send
let mut hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
@ -324,7 +326,7 @@ mod test {
let ct = hpke_s.seal(AAD, PT).unwrap();
// Receive
let mut hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &enc, INFO).unwrap();
let mut hpke_r = HpkeR::new(cfg, &pk_r, &sk_r, &enc, INFO).unwrap();
let pt = hpke_r.open(AAD, &ct).unwrap();
assert_eq!(&pt[..], PT);
}
@ -338,4 +340,19 @@ mod test {
fn seal_open_chacha() {
seal_open(Aead::ChaCha20Poly1305);
}
#[test]
fn send_hpkecontext() {
use std::{sync::mpsc, thread};
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let context = HpkeContext::new(Config::default());
tx.send(context).unwrap();
});
let context = rx.recv().unwrap();
drop(context);
}
}

View File

@ -11,7 +11,7 @@ pub mod aead;
pub mod hkdf;
pub mod hpke;
pub use self::p11::{random, PrivateKey, PublicKey, SymKey};
pub use self::p11::{random, PrivateKey, PublicKey};
use err::secstatus_to_res;
pub use err::Error;
use lazy_static::lazy_static;
@ -37,7 +37,7 @@ enum NssLoaded {
impl Drop for NssLoaded {
fn drop(&mut self) {
if *self == Self::NoDb {
if matches!(self, Self::NoDb) {
unsafe {
secstatus_to_res(nss_init::NSS_Shutdown()).expect("NSS Shutdown failed");
}

View File

@ -67,7 +67,7 @@ macro_rules! scoped_ptr {
impl Drop for $scoped {
fn drop(&mut self) {
let _ = unsafe { $dtor(self.ptr) };
unsafe { $dtor(self.ptr) };
}
}
};
@ -240,7 +240,7 @@ impl<'a, T: Sized + 'a> ParamItem<'a, T> {
};
Self {
item,
marker: PhantomData::default(),
marker: PhantomData,
}
}

View File

@ -54,7 +54,7 @@ impl AeadEngine {
/// A switch-hitting AEAD that uses a selected primitive.
pub struct Aead {
mode: Mode,
aead: AeadEngine,
engine: AeadEngine,
nonce_base: [u8; NONCE_LEN],
seq: SequenceNumber,
}
@ -80,7 +80,7 @@ impl Aead {
};
Ok(Self {
mode,
aead,
engine: aead,
nonce_base,
seq: 0,
})
@ -105,14 +105,14 @@ impl Aead {
// A copy for the nonce generator to write into. But we don't use the value.
let nonce = self.nonce(self.seq);
self.seq += 1;
let ct = self.aead.encrypt(&nonce, Payload { msg: pt, aad })?;
let ct = self.engine.encrypt(&nonce, Payload { msg: pt, aad })?;
Ok(ct)
}
pub fn open(&mut self, aad: &[u8], seq: SequenceNumber, ct: &[u8]) -> Res<Vec<u8>> {
assert_eq!(self.mode, Mode::Decrypt);
let nonce = self.nonce(seq);
let pt = self.aead.decrypt(&nonce, Payload { msg: ct, aad })?;
let pt = self.engine.decrypt(&nonce, Payload { msg: ct, aad })?;
Ok(pt)
}
}

View File

@ -3,15 +3,23 @@ use crate::{
hpke::{Aead, Kdf, Kem},
Error, Res,
};
use ::hpke::{
aead::{AeadTag, AesGcm128, ChaCha20Poly1305},
#[cfg(not(feature = "pq"))]
use ::hpke as rust_hpke;
#[cfg(feature = "pq")]
use ::hpke_pq as rust_hpke;
use rust_hpke::{
aead::{AeadCtxR, AeadCtxS, AeadTag, AesGcm128, ChaCha20Poly1305},
kdf::HkdfSha256,
kem::{Kem as HpkeKem, X25519HkdfSha256},
kex::{KeyExchange, X25519},
op_mode::{OpModeR, OpModeS},
setup::{setup_receiver, setup_sender},
AeadCtxR, AeadCtxS, Deserializable, EncappedKey, Serializable,
kem::{Kem as KemTrait, X25519HkdfSha256},
setup_receiver, setup_sender, Deserializable, OpModeR, OpModeS, Serializable,
};
#[cfg(feature = "pq")]
use rust_hpke::kem::X25519Kyber768Draft00;
use ::rand::thread_rng;
use log::trace;
use std::ops::Deref;
@ -57,8 +65,13 @@ impl Default for Config {
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum PublicKey {
X25519(<<X25519HkdfSha256 as HpkeKem>::Kex as KeyExchange>::PublicKey),
X25519(<X25519HkdfSha256 as KemTrait>::PublicKey),
#[cfg(feature = "pq")]
X25519Kyber768Draft00(<X25519Kyber768Draft00 as KemTrait>::PublicKey),
}
impl PublicKey {
@ -66,6 +79,9 @@ impl PublicKey {
pub fn key_data(&self) -> Res<Vec<u8>> {
Ok(match self {
Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(k) => Vec::from(k.to_bytes().as_slice()),
})
}
}
@ -80,8 +96,13 @@ impl std::fmt::Debug for PublicKey {
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum PrivateKey {
X25519(<<X25519HkdfSha256 as HpkeKem>::Kex as KeyExchange>::PrivateKey),
X25519(<X25519HkdfSha256 as KemTrait>::PrivateKey),
#[cfg(feature = "pq")]
X25519Kyber768Draft00(<X25519Kyber768Draft00 as KemTrait>::PrivateKey),
}
impl PrivateKey {
@ -89,6 +110,9 @@ impl PrivateKey {
pub fn key_data(&self) -> Res<Vec<u8>> {
Ok(match self {
Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(k) => Vec::from(k.to_bytes().as_slice()),
})
}
}
@ -110,12 +134,25 @@ enum SenderContextX25519HkdfSha256HkdfSha256 {
ChaCha20Poly1305(Box<AeadCtxS<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>>),
}
#[cfg(feature = "pq")]
enum SenderContextX25519Kyber768Draft00HkdfSha256 {
AesGcm128(Box<AeadCtxS<AesGcm128, HkdfSha256, X25519Kyber768Draft00>>),
}
enum SenderContextX25519HkdfSha256 {
HkdfSha256(SenderContextX25519HkdfSha256HkdfSha256),
}
#[cfg(feature = "pq")]
enum SenderContextX25519Kyber768Draft00 {
HkdfSha256(SenderContextX25519Kyber768Draft00HkdfSha256),
}
enum SenderContext {
X25519HkdfSha256(SenderContextX25519HkdfSha256),
#[cfg(feature = "pq")]
X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00),
}
impl SenderContext {
@ -124,13 +161,21 @@ impl SenderContext {
Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
SenderContextX25519HkdfSha256HkdfSha256::AesGcm128(context),
)) => {
let tag = context.seal(plaintext, aad)?;
let tag = context.seal_in_place_detached(plaintext, aad)?;
Vec::from(tag.to_bytes().as_slice())
}
Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
)) => {
let tag = context.seal(plaintext, aad)?;
let tag = context.seal_in_place_detached(plaintext, aad)?;
Vec::from(tag.to_bytes().as_slice())
}
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00::HkdfSha256(
SenderContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context),
)) => {
let tag = context.seal_in_place_detached(plaintext, aad)?;
Vec::from(tag.to_bytes().as_slice())
}
})
@ -148,6 +193,13 @@ impl SenderContext {
)) => {
context.export(info, out_buf)?;
}
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(SenderContextX25519Kyber768Draft00::HkdfSha256(
SenderContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context),
)) => {
context.export(info, out_buf)?;
}
}
Ok(())
}
@ -171,7 +223,7 @@ impl HpkeS {
macro_rules! dispatch_hpkes_new {
{
($c:expr, $pk:expr, $csprng:expr): [$({
($c:expr, $pk:expr, $csprng:expr): [$( $(#[$meta:meta])* {
$kemid:path => $kem:path,
$kdfid:path => $kdf:path,
$aeadid:path => $aead:path,
@ -180,6 +232,7 @@ impl HpkeS {
} => {
match ($c, $pk) {
$(
$(#[$meta])*
(
Config {
kem: $kemid,
@ -194,13 +247,17 @@ impl HpkeS {
info,
$csprng,
)?;
($ctxt1($ctxt2($ctxt3(Box::new(context)))), enc)
(
$ctxt1($ctxt2($ctxt3(Box::new(context)))),
Vec::from(enc.to_bytes().as_slice()),
)
}
)*
_ => return Err(Error::InvalidKeyType),
}
};
}
let (context, enc) = dispatch_hpkes_new! { (config, pk_r, &mut csprng): [
{
Kem::X25519Sha256 => X25519HkdfSha256,
@ -220,8 +277,19 @@ impl HpkeS {
SenderContextX25519HkdfSha256::HkdfSha256,
SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305,
},
#[cfg(feature = "pq")]
{
Kem::X25519Kyber768Draft00 => X25519Kyber768Draft00,
Kdf::HkdfSha256 => HkdfSha256,
Aead::Aes128Gcm => AesGcm128,
PublicKey::X25519Kyber768Draft00,
SenderContext::X25519Kyber768Draft00,
SenderContextX25519Kyber768Draft00::HkdfSha256,
SenderContextX25519Kyber768Draft00HkdfSha256::AesGcm128,
},
]};
let enc = Vec::from(enc.to_bytes().as_slice());
Ok(Self {
context,
enc,
@ -267,12 +335,25 @@ enum ReceiverContextX25519HkdfSha256HkdfSha256 {
ChaCha20Poly1305(Box<AeadCtxR<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>>),
}
#[cfg(feature = "pq")]
enum ReceiverContextX25519Kyber768Draft00HkdfSha256 {
AesGcm128(Box<AeadCtxR<AesGcm128, HkdfSha256, X25519Kyber768Draft00>>),
}
enum ReceiverContextX25519HkdfSha256 {
HkdfSha256(ReceiverContextX25519HkdfSha256HkdfSha256),
}
#[cfg(feature = "pq")]
enum ReceiverContextX25519Kyber768Draft00 {
HkdfSha256(ReceiverContextX25519Kyber768Draft00HkdfSha256),
}
enum ReceiverContext {
X25519HkdfSha256(ReceiverContextX25519HkdfSha256),
#[cfg(feature = "pq")]
X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00),
}
impl ReceiverContext {
@ -284,10 +365,10 @@ impl ReceiverContext {
if ciphertext.len() < AeadTag::<AesGcm128>::size() {
return Err(Error::Truncated);
}
let (ct, tag) =
let (ct, tag_slice) =
ciphertext.split_at_mut(ciphertext.len() - AeadTag::<AesGcm128>::size());
let tag = AeadTag::<AesGcm128>::from_bytes(tag)?;
context.open(ct, aad, &tag)?;
let tag = AeadTag::<AesGcm128>::from_bytes(tag_slice)?;
context.open_in_place_detached(ct, aad, &tag)?;
ct
}
Self::X25519HkdfSha256(ReceiverContextX25519HkdfSha256::HkdfSha256(
@ -296,10 +377,24 @@ impl ReceiverContext {
if ciphertext.len() < AeadTag::<ChaCha20Poly1305>::size() {
return Err(Error::Truncated);
}
let (ct, tag) =
let (ct, tag_slice) =
ciphertext.split_at_mut(ciphertext.len() - AeadTag::<ChaCha20Poly1305>::size());
let tag = AeadTag::<ChaCha20Poly1305>::from_bytes(tag)?;
context.open(ct, aad, &tag)?;
let tag = AeadTag::<ChaCha20Poly1305>::from_bytes(tag_slice)?;
context.open_in_place_detached(ct, aad, &tag)?;
ct
}
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00::HkdfSha256(
ReceiverContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context),
)) => {
if ciphertext.len() < AeadTag::<AesGcm128>::size() {
return Err(Error::Truncated);
}
let (ct, tag_slice) =
ciphertext.split_at_mut(ciphertext.len() - AeadTag::<AesGcm128>::size());
let tag = AeadTag::<AesGcm128>::from_bytes(tag_slice)?;
context.open_in_place_detached(ct, aad, &tag)?;
ct
}
})
@ -317,6 +412,13 @@ impl ReceiverContext {
)) => {
context.export(info, out_buf)?;
}
#[cfg(feature = "pq")]
Self::X25519Kyber768Draft00(ReceiverContextX25519Kyber768Draft00::HkdfSha256(
ReceiverContextX25519Kyber768Draft00HkdfSha256::AesGcm128(context),
)) => {
context.export(info, out_buf)?;
}
}
Ok(())
}
@ -334,13 +436,13 @@ impl HpkeR {
pub fn new(
config: Config,
_pk_r: &PublicKey,
sk_r: &mut PrivateKey,
sk_r: &PrivateKey,
enc: &[u8],
info: &[u8],
) -> Res<Self> {
macro_rules! dispatch_hpker_new {
{
($c:ident, $sk:ident): [$({
($c:ident, $sk:ident): [$( $(#[$meta:meta])* {
$kemid:path => $kem:path,
$kdfid:path => $kdf:path,
$aeadid:path => $aead:path,
@ -349,6 +451,7 @@ impl HpkeR {
} => {
match ($c, $sk) {
$(
$(#[$meta])*
(
Config {
kem: $kemid,
@ -357,7 +460,7 @@ impl HpkeR {
},
$ske(sk_r),
) => {
let enc = EncappedKey::from_bytes(enc)?;
let enc = <$kem as KemTrait>::EncappedKey::from_bytes(enc)?;
let context = setup_receiver::<$aead, $kdf, $kem>(
&OpModeR::Base,
sk_r,
@ -390,7 +493,19 @@ impl HpkeR {
ReceiverContextX25519HkdfSha256::HkdfSha256,
ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305,
},
#[cfg(feature = "pq")]
{
Kem::X25519Kyber768Draft00 => X25519Kyber768Draft00,
Kdf::HkdfSha256 => HkdfSha256,
Aead::Aes128Gcm => AesGcm128,
PrivateKey::X25519Kyber768Draft00,
ReceiverContext::X25519Kyber768Draft00,
ReceiverContextX25519Kyber768Draft00::HkdfSha256,
ReceiverContextX25519Kyber768Draft00HkdfSha256::AesGcm128,
},
]};
Ok(Self { context, config })
}
@ -401,8 +516,13 @@ impl HpkeR {
pub fn decode_public_key(kem: Kem, k: &[u8]) -> Res<PublicKey> {
Ok(match kem {
Kem::X25519Sha256 => {
PublicKey::X25519(<X25519 as KeyExchange>::PublicKey::from_bytes(k)?)
PublicKey::X25519(<X25519HkdfSha256 as KemTrait>::PublicKey::from_bytes(k)?)
}
#[cfg(feature = "pq")]
Kem::X25519Kyber768Draft00 => PublicKey::X25519Kyber768Draft00(
<X25519Kyber768Draft00 as KemTrait>::PublicKey::from_bytes(k)?,
),
})
}
@ -438,6 +558,15 @@ pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> {
let (sk, pk) = X25519HkdfSha256::gen_keypair(&mut csprng);
(PrivateKey::X25519(sk), PublicKey::X25519(pk))
}
#[cfg(feature = "pq")]
Kem::X25519Kyber768Draft00 => {
let (sk, pk) = X25519Kyber768Draft00::gen_keypair(&mut csprng);
(
PrivateKey::X25519Kyber768Draft00(sk),
PublicKey::X25519Kyber768Draft00(pk),
)
}
};
trace!("Generated key pair: sk={:?} pk={:?}", sk, pk);
Ok((sk, pk))
@ -450,6 +579,15 @@ pub fn derive_key_pair(kem: Kem, ikm: &[u8]) -> Res<(PrivateKey, PublicKey)> {
let (sk, pk) = X25519HkdfSha256::derive_keypair(ikm);
(PrivateKey::X25519(sk), PublicKey::X25519(pk))
}
#[cfg(feature = "pq")]
Kem::X25519Kyber768Draft00 => {
let (sk, pk) = X25519Kyber768Draft00::derive_keypair(ikm);
(
PrivateKey::X25519Kyber768Draft00(sk),
PublicKey::X25519Kyber768Draft00(pk),
)
}
};
trace!("Derived key pair: sk={:?} pk={:?}", sk, pk);
Ok((sk, pk))
@ -458,7 +596,10 @@ pub fn derive_key_pair(kem: Kem, ikm: &[u8]) -> Res<(PrivateKey, PublicKey)> {
#[cfg(test)]
mod test {
use super::{generate_key_pair, Config, HpkeR, HpkeS};
use crate::{hpke::Aead, init};
use crate::{
hpke::{Aead, Kem},
init,
};
const INFO: &[u8] = b"info";
const AAD: &[u8] = b"aad";
@ -469,21 +610,22 @@ mod test {
fn make() {
init();
let cfg = Config::default();
let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let (sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
let _hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &hpke_s.enc().unwrap(), INFO).unwrap();
let _hpke_r = HpkeR::new(cfg, &pk_r, &sk_r, &hpke_s.enc().unwrap(), INFO).unwrap();
}
#[allow(clippy::similar_names)] // for sk_x and pk_x
fn seal_open(aead: Aead) {
fn seal_open(aead: Aead, kem: Kem) {
// Setup
init();
let cfg = Config {
kem,
aead,
..Config::default()
};
assert!(cfg.supported());
let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
let (sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
// Send
let mut hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
@ -491,18 +633,24 @@ mod test {
let ct = hpke_s.seal(AAD, PT).unwrap();
// Receive
let mut hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &enc, INFO).unwrap();
let mut hpke_r = HpkeR::new(cfg, &pk_r, &sk_r, &enc, INFO).unwrap();
let pt = hpke_r.open(AAD, &ct).unwrap();
assert_eq!(&pt[..], PT);
}
#[test]
fn seal_open_gcm() {
seal_open(Aead::Aes128Gcm);
seal_open(Aead::Aes128Gcm, Kem::X25519Sha256);
}
#[test]
fn seal_open_chacha() {
seal_open(Aead::ChaCha20Poly1305);
seal_open(Aead::ChaCha20Poly1305, Kem::X25519Sha256);
}
#[cfg(feature = "pq")]
#[test]
fn seal_open_xyber768d00() {
seal_open(Aead::Aes128Gcm, Kem::X25519Kyber768Draft00);
}
}

View File

@ -18,7 +18,7 @@ cstr = "0.2"
viaduct = "0.1"
url = "2.1"
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client"] }
ohttp = { version = "0.5", default-features = false, features = ["gecko", "nss", "client"] }
bhttp = "0.3"
thiserror = "1.0"
mozbuild = "0.1"

View File

@ -106,7 +106,7 @@ fn ohttp_upload(upload_request: PingUploadRequest) -> Result<UploadResult, Viadu
ohttp::init();
});
let ohttp_request = ohttp::ClientRequest::new(config)?;
let ohttp_request = ohttp::ClientRequest::from_encoded_config(config)?;
let (capsule, ohttp_response) = ohttp_request.encapsulate(&binary_request)?;
const OHTTP_RELAY_URL: &str = "https://mozilla-ohttp.fastly-edge.com/";