summaryrefslogtreecommitdiffstats
path: root/third_party/rust/ohttp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/ohttp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/ohttp')
-rw-r--r--third_party/rust/ohttp/.cargo-checksum.json1
-rw-r--r--third_party/rust/ohttp/Cargo.toml111
-rw-r--r--third_party/rust/ohttp/README.md33
-rw-r--r--third_party/rust/ohttp/bindings/bindings.toml130
-rw-r--r--third_party/rust/ohttp/bindings/nspr_err.h7
-rw-r--r--third_party/rust/ohttp/bindings/nspr_error.h7
-rw-r--r--third_party/rust/ohttp/bindings/nss_init.h7
-rw-r--r--third_party/rust/ohttp/bindings/nss_p11.h10
-rw-r--r--third_party/rust/ohttp/bindings/nss_secerr.h7
-rw-r--r--third_party/rust/ohttp/build.rs436
-rw-r--r--third_party/rust/ohttp/src/err.rs40
-rw-r--r--third_party/rust/ohttp/src/hpke.rs92
-rw-r--r--third_party/rust/ohttp/src/lib.rs657
-rw-r--r--third_party/rust/ohttp/src/nss/aead.rs329
-rw-r--r--third_party/rust/ohttp/src/nss/err.rs141
-rw-r--r--third_party/rust/ohttp/src/nss/hkdf.rs289
-rw-r--r--third_party/rust/ohttp/src/nss/hpke.rs341
-rw-r--r--third_party/rust/ohttp/src/nss/mod.rs67
-rw-r--r--third_party/rust/ohttp/src/nss/p11.rs294
-rw-r--r--third_party/rust/ohttp/src/rand.rs15
-rw-r--r--third_party/rust/ohttp/src/rh/aead.rs257
-rw-r--r--third_party/rust/ohttp/src/rh/hkdf.rs224
-rw-r--r--third_party/rust/ohttp/src/rh/hpke.rs508
-rw-r--r--third_party/rust/ohttp/src/rh/mod.rs47
24 files changed, 4050 insertions, 0 deletions
diff --git a/third_party/rust/ohttp/.cargo-checksum.json b/third_party/rust/ohttp/.cargo-checksum.json
new file mode 100644
index 0000000000..992a776617
--- /dev/null
+++ b/third_party/rust/ohttp/.cargo-checksum.json
@@ -0,0 +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"} \ No newline at end of file
diff --git a/third_party/rust/ohttp/Cargo.toml b/third_party/rust/ohttp/Cargo.toml
new file mode 100644
index 0000000000..be7e3e3737
--- /dev/null
+++ b/third_party/rust/ohttp/Cargo.toml
@@ -0,0 +1,111 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "ohttp"
+version = "0.3.1"
+authors = ["Martin Thomson <mt@lowentropy.net>"]
+build = "build.rs"
+description = "Oblivious HTTP"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/martinthomson/ohttp"
+
+[dependencies.aead]
+version = "0.4"
+features = ["std"]
+optional = true
+
+[dependencies.aes-gcm]
+version = "0.9"
+optional = true
+
+[dependencies.byteorder]
+version = "1.3"
+
+[dependencies.chacha20poly1305]
+version = "0.8"
+optional = true
+
+[dependencies.hex]
+version = "0.4"
+
+[dependencies.hkdf]
+version = "0.11"
+optional = true
+
+[dependencies.hpke]
+version = "0.7"
+features = ["std"]
+optional = true
+default-features = false
+
+[dependencies.lazy_static]
+version = "1.4"
+
+[dependencies.log]
+version = "0.4.0"
+default-features = false
+
+[dependencies.rand]
+version = "0.8"
+optional = true
+
+[dependencies.sha2]
+version = "0.9"
+optional = true
+
+[dependencies.thiserror]
+version = "1"
+
+[dev-dependencies.env_logger]
+version = "0.9"
+default-features = false
+
+[build-dependencies.bindgen]
+version = "0.63"
+features = ["runtime"]
+optional = true
+default-features = false
+
+[build-dependencies.mozbuild]
+version = "0.1"
+optional = true
+
+[build-dependencies.serde]
+version = "1.0"
+
+[build-dependencies.serde_derive]
+version = "1.0"
+
+[build-dependencies.toml]
+version = "0.5"
+
+[features]
+client = []
+default = [
+ "client",
+ "server",
+ "rust-hpke",
+]
+gecko = ["mozbuild"]
+nss = ["bindgen"]
+rust-hpke = [
+ "hpke/x25519",
+ "rand",
+ "aead",
+ "aes-gcm",
+ "chacha20poly1305",
+ "hkdf",
+ "sha2",
+]
+server = []
diff --git a/third_party/rust/ohttp/README.md b/third_party/rust/ohttp/README.md
new file mode 100644
index 0000000000..5ac73e3c45
--- /dev/null
+++ b/third_party/rust/ohttp/README.md
@@ -0,0 +1,33 @@
+# Oblivious HTTP
+
+This is a rust implementation of [Oblivious
+HTTP](https://ietf-wg-ohai.github.io/oblivious-http/draft-ietf-ohai-ohttp.html).
+
+This work is undergoing active revision in the IETF and so are these
+implementations. Use at your own risk.
+
+This crate uses either [hpke](https://github.com/rozbb/rust-hpke) or
+[NSS](https://firefox-source-docs.mozilla.org/security/nss/index.html) for
+cryptographic primitives.
+
+
+## Using
+
+The API documentation is currently sparse, but the API is fairly small and
+descriptive.
+
+The `ohttp` crate has the following features:
+
+- `client` enables the client-side processing of oblivious HTTP messages:
+ encrypting requests and decrypting responses. This is enabled by default.
+
+- `server` enables the server-side processing of oblivious HTTP messages:
+ decrypting requests and encrypting responses. This is enabled by default.
+
+- `rust-hpke` selects the [hpke](https://github.com/rozbb/rust-hpke) crate for
+ HPKE encryption. This is enabled by default and cannot be enabled at the same
+ time as `nss`.
+
+- `nss` selects
+ [NSS](https://firefox-source-docs.mozilla.org/security/nss/index.html). This is
+ disabled by default and cannot be enabled at the same time as `rust-hpke`.
diff --git a/third_party/rust/ohttp/bindings/bindings.toml b/third_party/rust/ohttp/bindings/bindings.toml
new file mode 100644
index 0000000000..3f4a4262b3
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/bindings.toml
@@ -0,0 +1,130 @@
+# In this file, every section corresponds to a header file.
+# A corresponding binding file will be created in $OUT_DIR.
+
+[nspr_err]
+# NSPR doesn't use an enum for errors, so we have to pull in everything in the header file.
+# Specifying no types, functions, or variables does that, but then exclude some.
+exclude = [
+ "nspr_.*",
+ "PR_MAX_ERROR",
+ "ERROR_TABLE_BASE_nspr"
+]
+
+[nspr_error]
+functions = [
+ "PR_ErrorToName",
+ "PR_ErrorToString",
+ "PR_GetError",
+ "PR_SetError",
+]
+variables = [
+ "PR_LANGUAGE_I_DEFAULT",
+]
+
+[nss_secerr]
+types = ["SECErrorCodes"]
+enums = ["SECErrorCodes"]
+
+[nss_init]
+functions = [
+ "NSS_IsInitialized",
+ "NSS_NoDB_Init",
+ "NSS_Shutdown",
+]
+
+[nss_p11]
+types = [
+ "CK_ATTRIBUTE_TYPE",
+ "CK_BBOOL",
+ "CK_FLAGS",
+ "CK_GENERATOR_FUNCTION",
+ "CK_HKDF_PARAMS",
+ "CK_MECHANISM_TYPE",
+ "CK_OBJECT_HANDLE",
+ "CK_ULONG",
+ "SECItem",
+ "SECItemArray",
+ "SECOidData",
+]
+functions = [
+ "PK11_AEADOp",
+ "PK11_CreateContextBySymKey",
+ "PK11_Derive",
+ "PK11_DestroyContext",
+ "PK11_Encrypt",
+ "PK11_ExtractKeyValue",
+ "PK11_FreeSlot",
+ "PK11_FreeSymKey",
+ "PK11_GenerateKeyPairWithOpFlags",
+ "PK11_GenerateRandom",
+ "PK11_GetBlockSize",
+ "PK11_GetInternalSlot",
+ "PK11_GetKeyData",
+ "PK11_GetMechanism",
+ "PK11_HPKE_Deserialize",
+ "PK11_HPKE_DestroyContext",
+ "PK11_HPKE_ExportSecret",
+ "PK11_HPKE_GetEncapPubKey",
+ "PK11_HPKE_NewContext",
+ "PK11_HPKE_Open",
+ "PK11_HPKE_Seal",
+ "PK11_HPKE_Serialize",
+ "PK11_HPKE_SetupR",
+ "PK11_HPKE_SetupS",
+ "PK11_HPKE_ValidateParameters",
+ "PK11_ImportSymKey",
+ "PK11_ReadRawAttribute",
+ "PK11_ReferenceSymKey",
+ "SECITEM_FreeItem",
+ "SECKEY_CopyPrivateKey",
+ "SECKEY_CopyPublicKey",
+ "SECKEY_DestroyPrivateKey",
+ "SECKEY_DestroyPublicKey",
+ "SECOID_FindOIDByTag",
+]
+enums = [
+ "HpkeAeadId",
+ "HpkeKdfId",
+ "HpkeKemId",
+ "PK11ObjectType",
+ "PK11Origin",
+ "SECItemType",
+ "SECOidTag",
+]
+opaque = [
+ "HpkeContext",
+ "PK11Context",
+ "PK11SlotInfo",
+ "PK11SymKey",
+ "SECKEYPrivateKey",
+ "SECKEYPublicKey",
+]
+variables = [
+ "CK_INVALID_HANDLE",
+ "CKA_DECRYPT",
+ "CKA_DERIVE",
+ "CKA_ENCRYPT",
+ "CKA_NSS_MESSAGE",
+ "CKA_SIGN",
+ "CKA_VALUE",
+ "CKF_HKDF_SALT_DATA",
+ "CKF_HKDF_SALT_NULL",
+ "CKF_DERIVE",
+ "CKG_GENERATE_COUNTER_XOR",
+ "CKG_NO_GENERATE",
+ "CKM_AES_GCM",
+ "CKM_CHACHA20_POLY1305",
+ "CKM_EC_KEY_PAIR_GEN",
+ "CKM_HKDF_DATA",
+ "CKM_HKDF_DERIVE",
+ "CKM_HKDF_KEY_GEN",
+ "CKM_INVALID_MECHANISM",
+ "CKM_SHA256",
+ "HPKE_DRAFT_VERSION",
+ "PK11_ATTR_INSENSITIVE",
+ "PK11_ATTR_PRIVATE",
+ "PK11_ATTR_PUBLIC",
+ "PK11_ATTR_SENSITIVE",
+ "PK11_ATTR_SESSION",
+ "SEC_ASN1_OBJECT_ID",
+]
diff --git a/third_party/rust/ohttp/bindings/nspr_err.h b/third_party/rust/ohttp/bindings/nspr_err.h
new file mode 100644
index 0000000000..204e771c49
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/nspr_err.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prerr.h"
diff --git a/third_party/rust/ohttp/bindings/nspr_error.h b/third_party/rust/ohttp/bindings/nspr_error.h
new file mode 100644
index 0000000000..8ff8ce202d
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/nspr_error.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "prerror.h"
diff --git a/third_party/rust/ohttp/bindings/nss_init.h b/third_party/rust/ohttp/bindings/nss_init.h
new file mode 100644
index 0000000000..a4beb38f69
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/nss_init.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "nss.h"
diff --git a/third_party/rust/ohttp/bindings/nss_p11.h b/third_party/rust/ohttp/bindings/nss_p11.h
new file mode 100644
index 0000000000..237e620445
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/nss_p11.h
@@ -0,0 +1,10 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#define NSS_ENABLE_DRAFT_HPKE
+#include "secoidt.h"
+#include "keyhi.h"
+#include "pk11pub.h"
diff --git a/third_party/rust/ohttp/bindings/nss_secerr.h b/third_party/rust/ohttp/bindings/nss_secerr.h
new file mode 100644
index 0000000000..c2b2d4020c
--- /dev/null
+++ b/third_party/rust/ohttp/bindings/nss_secerr.h
@@ -0,0 +1,7 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#include "secerr.h"
diff --git a/third_party/rust/ohttp/build.rs b/third_party/rust/ohttp/build.rs
new file mode 100644
index 0000000000..a63be2c1c3
--- /dev/null
+++ b/third_party/rust/ohttp/build.rs
@@ -0,0 +1,436 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![deny(clippy::pedantic)]
+
+#[cfg(feature = "nss")]
+mod nss {
+ use bindgen::Builder;
+ use serde_derive::Deserialize;
+ use std::{
+ collections::HashMap,
+ env, fs,
+ path::{Path, PathBuf},
+ process::Command,
+ };
+
+ const BINDINGS_DIR: &str = "bindings";
+ const BINDINGS_CONFIG: &str = "bindings.toml";
+
+ // This is the format of a single section of the configuration file.
+ #[derive(Deserialize)]
+ struct Bindings {
+ /// types that are explicitly included
+ #[serde(default)]
+ types: Vec<String>,
+ /// functions that are explicitly included
+ #[serde(default)]
+ functions: Vec<String>,
+ /// variables (and `#define`s) that are explicitly included
+ #[serde(default)]
+ variables: Vec<String>,
+ /// types that should be explicitly marked as opaque
+ #[serde(default)]
+ opaque: Vec<String>,
+ /// enumerations that are turned into a module (without this, the enum is
+ /// mapped using the default, which means that the individual values are
+ /// formed with an underscore as <enum_type>_<enum_value_name>).
+ #[serde(default)]
+ enums: Vec<String>,
+
+ /// Any item that is specifically excluded; if none of the types, functions,
+ /// or variables fields are specified, everything defined will be mapped,
+ /// so this can be used to limit that.
+ #[serde(default)]
+ exclude: Vec<String>,
+
+ /// Whether the file is to be interpreted as C++
+ #[serde(default)]
+ cplusplus: bool,
+ }
+
+ fn is_debug() -> bool {
+ env::var("DEBUG")
+ .map(|d| d.parse::<bool>().unwrap_or(false))
+ .unwrap_or(false)
+ }
+
+ // bindgen needs access to libclang.
+ // On windows, this doesn't just work, you have to set LIBCLANG_PATH.
+ // Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
+ fn setup_clang() {
+ if env::consts::OS != "windows" {
+ return;
+ }
+ println!("rerun-if-env-changed=LIBCLANG_PATH");
+ println!("rerun-if-env-changed=MOZBUILD_STATE_PATH");
+ if env::var("LIBCLANG_PATH").is_ok() {
+ return;
+ }
+ let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") {
+ PathBuf::from(dir.trim())
+ } else {
+ eprintln!("warning: Building without a gecko setup is not likely to work.");
+ eprintln!(" A working libclang is needed to build neqo.");
+ eprintln!(" Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
+ eprintln!();
+ eprintln!(" We recommend checking out https://github.com/mozilla/gecko-dev");
+ eprintln!(" Then run `./mach bootstrap` which will retrieve clang.");
+ eprintln!(" Make sure to export MOZBUILD_STATE_PATH when building.");
+ return;
+ };
+ let libclang_dir = mozbuild_root.join("clang").join("lib");
+ if libclang_dir.is_dir() {
+ env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
+ println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap());
+ } else {
+ println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko");
+ }
+ }
+
+ fn nss_dir() -> Option<PathBuf> {
+ // Note that this returns a relative path because UNC
+ // paths on windows cause certain tools to explode.
+ env::var("NSS_DIR").ok().map(|dir| {
+ let dir = PathBuf::from(dir.trim());
+ assert!(dir.is_dir());
+ dir
+ })
+ }
+
+ fn get_bash() -> PathBuf {
+ // When running under MOZILLABUILD, we need to make sure not to invoke
+ // another instance of bash that might be sitting around (like WSL).
+ match env::var("MOZILLABUILD") {
+ Ok(d) => PathBuf::from(d).join("msys").join("bin").join("bash.exe"),
+ Err(_) => PathBuf::from("bash"),
+ }
+ }
+
+ fn run_build_script(dir: &Path) {
+ let mut build_nss = vec![
+ String::from("./build.sh"),
+ String::from("-Ddisable_tests=1"),
+ String::from("-Denable_draft_hpke=1"),
+ ];
+ if is_debug() {
+ build_nss.push(String::from("--static"));
+ } else {
+ build_nss.push(String::from("-o"));
+ }
+ if let Ok(d) = env::var("NSS_JOBS") {
+ build_nss.push(String::from("-j"));
+ build_nss.push(d);
+ }
+ let status = Command::new(get_bash())
+ .args(build_nss)
+ .current_dir(dir)
+ .status()
+ .expect("couldn't start NSS build");
+ assert!(status.success(), "NSS build failed");
+ }
+
+ fn dynamic_link() {
+ let libs = if env::consts::OS == "windows" {
+ &["nssutil3.dll", "nss3.dll"]
+ } else {
+ &["nssutil3", "nss3"]
+ };
+ dynamic_link_both(libs);
+ }
+
+ fn dynamic_link_both(extra_libs: &[&str]) {
+ let nspr_libs = if env::consts::OS == "windows" {
+ &["libplds4", "libplc4", "libnspr4"]
+ } else {
+ &["plds4", "plc4", "nspr4"]
+ };
+ for lib in nspr_libs.iter().chain(extra_libs) {
+ println!("cargo:rustc-link-lib=dylib={}", lib);
+ }
+ }
+
+ fn static_link() {
+ 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");
+ }
+ for lib in static_libs {
+ println!("cargo:rustc-link-lib=static={}", lib);
+ }
+
+ // 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"]);
+ }
+ if env::consts::OS == "macos" {
+ other_libs.push("sqlite3");
+ }
+ dynamic_link_both(&other_libs);
+ }
+
+ fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
+ let nsprinclude = nsstarget.join("include").join("nspr");
+ let nssinclude = nssdist.join("public").join("nss");
+ let includes = vec![nsprinclude, nssinclude];
+ for i in &includes {
+ println!("cargo:include={}", i.to_str().unwrap());
+ }
+ includes
+ }
+
+ fn build_bindings(base: &str, bindings: &Bindings, flags: &[String]) {
+ let suffix = if bindings.cplusplus { ".hpp" } else { ".h" };
+ let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix);
+ let header = header_path.to_str().unwrap();
+ let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs");
+
+ println!("cargo:rerun-if-changed={}", header);
+
+ let mut builder = Builder::default().header(header);
+ builder = builder.generate_comments(false);
+ builder = builder.size_t_is_usize(true);
+
+ builder = builder.clang_arg("-v");
+
+ builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT");
+ if env::consts::OS == "windows" {
+ builder = builder.clang_arg("-DWIN");
+ } else if env::consts::OS == "macos" {
+ builder = builder.clang_arg("-DDARWIN");
+ } else if env::consts::OS == "linux" {
+ builder = builder.clang_arg("-DLINUX");
+ } else if env::consts::OS == "android" {
+ builder = builder.clang_arg("-DLINUX");
+ builder = builder.clang_arg("-DANDROID");
+ }
+ if bindings.cplusplus {
+ builder = builder.clang_args(&["-x", "c++", "-std=c++11"]);
+ }
+
+ builder = builder.clang_args(flags);
+
+ // Apply the configuration.
+ for v in &bindings.types {
+ builder = builder.allowlist_type(v);
+ }
+ for v in &bindings.functions {
+ builder = builder.allowlist_function(v);
+ }
+ for v in &bindings.variables {
+ builder = builder.allowlist_var(v);
+ }
+ for v in &bindings.exclude {
+ builder = builder.blocklist_item(v);
+ }
+ for v in &bindings.opaque {
+ builder = builder.opaque_type(v);
+ }
+ for v in &bindings.enums {
+ builder = builder.constified_enum_module(v);
+ }
+
+ let bindings = builder.generate().expect("unable to generate bindings");
+ bindings
+ .write_to_file(out)
+ .expect("couldn't write bindings");
+ }
+
+ fn build_nss(nss: &Path) -> Vec<String> {
+ setup_clang();
+
+ run_build_script(nss);
+
+ // $NSS_DIR/../dist/
+ let nssdist = nss.parent().unwrap().join("dist");
+ println!("cargo:rerun-if-env-changed=NSS_TARGET");
+ let nsstarget = env::var("NSS_TARGET")
+ .unwrap_or_else(|_| fs::read_to_string(nssdist.join("latest")).unwrap());
+ let nsstarget = nssdist.join(nsstarget.trim());
+
+ let includes = get_includes(&nsstarget, &nssdist);
+
+ let nsslibdir = nsstarget.join("lib");
+ println!(
+ "cargo:rustc-link-search=native={}",
+ nsslibdir.to_str().unwrap()
+ );
+ if is_debug() {
+ static_link();
+ } else {
+ dynamic_link();
+ }
+
+ let mut flags: Vec<String> = Vec::new();
+ for i in includes {
+ flags.push(String::from("-I") + i.to_str().unwrap());
+ }
+
+ flags
+ }
+
+ fn pkg_config() -> Vec<String> {
+ let modversion = Command::new("pkg-config")
+ .args(&["--modversion", "nss"])
+ .output()
+ .expect("pkg-config reports NSS as absent")
+ .stdout;
+ let modversion_str = String::from_utf8(modversion).expect("non-UTF8 from pkg-config");
+ let mut v = modversion_str.split('.');
+ assert_eq!(
+ v.next(),
+ Some("3"),
+ "NSS version 3.62 or higher is needed (or set $NSS_DIR)"
+ );
+ if let Some(minor) = v.next() {
+ let minor = minor
+ .trim_end()
+ .parse::<u32>()
+ .expect("NSS minor version is not a number");
+ assert!(
+ minor >= 62,
+ "NSS version 3.62 or higher is needed (or set $NSS_DIR)",
+ );
+ }
+
+ let cfg = Command::new("pkg-config")
+ .args(&["--cflags", "--libs", "nss"])
+ .output()
+ .expect("NSS flags not returned by pkg-config")
+ .stdout;
+ let cfg_str = String::from_utf8(cfg).expect("non-UTF8 from pkg-config");
+
+ let mut flags: Vec<String> = Vec::new();
+ for f in cfg_str.split(' ') {
+ if let Some(include) = f.strip_prefix("-I") {
+ flags.push(String::from(f));
+ println!("cargo:include={}", include);
+ } else if let Some(path) = f.strip_prefix("-L") {
+ println!("cargo:rustc-link-search=native={}", path);
+ } else if let Some(lib) = f.strip_prefix("-l") {
+ println!("cargo:rustc-link-lib=dylib={}", lib);
+ } else {
+ println!("Warning: Unknown flag from pkg-config: {}", f);
+ }
+ }
+
+ flags
+ }
+
+ #[cfg(feature = "gecko")]
+ fn setup_for_gecko() -> Vec<String> {
+ use mozbuild::TOPOBJDIR;
+
+ let mut flags: Vec<String> = Vec::new();
+
+ let fold_libs = mozbuild::config::MOZ_FOLD_LIBS;
+ let libs = if fold_libs {
+ vec!["nss3"]
+ } else {
+ vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"]
+ };
+
+ for lib in &libs {
+ println!("cargo:rustc-link-lib=dylib={}", lib);
+ }
+
+ if fold_libs {
+ println!(
+ "cargo:rustc-link-search=native={}",
+ TOPOBJDIR.join("security").to_str().unwrap()
+ );
+ } else {
+ println!(
+ "cargo:rustc-link-search=native={}",
+ TOPOBJDIR.join("dist").join("bin").to_str().unwrap()
+ );
+ let nsslib_path = TOPOBJDIR.join("security").join("nss").join("lib");
+ println!(
+ "cargo:rustc-link-search=native={}",
+ nsslib_path.join("nss").join("nss_nss3").to_str().unwrap()
+ );
+ println!(
+ "cargo:rustc-link-search=native={}",
+ nsslib_path.join("ssl").join("ssl_ssl3").to_str().unwrap()
+ );
+ println!(
+ "cargo:rustc-link-search=native={}",
+ TOPOBJDIR
+ .join("config")
+ .join("external")
+ .join("nspr")
+ .join("pr")
+ .to_str()
+ .unwrap()
+ );
+ }
+
+ let flags_path = TOPOBJDIR.join("netwerk/socket/neqo/extra-bindgen-flags");
+
+ println!("cargo:rerun-if-changed={}", flags_path.to_str().unwrap());
+ flags = fs::read_to_string(flags_path)
+ .expect("Failed to read extra-bindgen-flags file")
+ .split_whitespace()
+ .map(std::borrow::ToOwned::to_owned)
+ .collect();
+
+ flags.push(String::from("-include"));
+ flags.push(
+ TOPOBJDIR
+ .join("dist")
+ .join("include")
+ .join("mozilla-config.h")
+ .to_str()
+ .unwrap()
+ .to_string(),
+ );
+ flags
+ }
+
+ #[cfg(not(feature = "gecko"))]
+ fn setup_for_gecko() -> Vec<String> {
+ unreachable!()
+ }
+
+ pub fn build() {
+ println!("cargo:rerun-if-env-changed=NSS_DIR");
+ let flags = if cfg!(feature = "gecko") {
+ setup_for_gecko()
+ } else {
+ nss_dir().map_or_else(pkg_config, |nss| build_nss(&nss))
+ };
+
+ let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG);
+ println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap());
+ let config = fs::read_to_string(config_file).expect("unable to read binding configuration");
+ let config: HashMap<String, Bindings> = ::toml::from_str(&config).unwrap();
+
+ for (k, v) in &config {
+ build_bindings(k, v, &flags[..]);
+ }
+ }
+}
+
+fn main() {
+ #[cfg(feature = "nss")]
+ nss::build();
+}
diff --git a/third_party/rust/ohttp/src/err.rs b/third_party/rust/ohttp/src/err.rs
new file mode 100644
index 0000000000..f69886b300
--- /dev/null
+++ b/third_party/rust/ohttp/src/err.rs
@@ -0,0 +1,40 @@
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[cfg(feature = "rust-hpke")]
+ #[error("a problem occurred with the AEAD")]
+ Aead(#[from] aead::Error),
+ #[cfg(feature = "nss")]
+ #[error("a problem occurred during cryptographic processing: {0}")]
+ Crypto(#[from] crate::nss::Error),
+ #[error("an error was found in the format")]
+ Format,
+ #[error("a problem occurred with HPKE: {0}")]
+ #[cfg(feature = "rust-hpke")]
+ Hpke(#[from] ::hpke::HpkeError),
+ #[error("an internal error occurred")]
+ Internal,
+ #[error("the wrong type of key was provided for the selected KEM")]
+ InvalidKeyType,
+ #[error("the wrong KEM was specified")]
+ InvalidKem,
+ #[error("io error: {0}")]
+ Io(#[from] std::io::Error),
+ #[error("the key ID was invalid")]
+ KeyId,
+ #[error("a field was truncated")]
+ Truncated,
+ #[error("the configuration was not supported")]
+ Unsupported,
+ #[error("the configuration contained too many symmetric suites")]
+ TooManySymmetricSuites,
+}
+
+impl From<std::num::TryFromIntError> for Error {
+ fn from(_v: std::num::TryFromIntError) -> Self {
+ Self::TooManySymmetricSuites
+ }
+}
+
+pub type Res<T> = Result<T, Error>;
diff --git a/third_party/rust/ohttp/src/hpke.rs b/third_party/rust/ohttp/src/hpke.rs
new file mode 100644
index 0000000000..7bca571923
--- /dev/null
+++ b/third_party/rust/ohttp/src/hpke.rs
@@ -0,0 +1,92 @@
+macro_rules! convert_enum {
+ ($(#[$meta:meta])* $vis:vis enum $name:ident {
+ $($(#[$vmeta:meta])* $vname:ident $(= $val:expr)?,)*
+ }) => {
+ $(#[$meta])*
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ $vis enum $name {
+ $($(#[$vmeta])* $vname $(= $val)?,)*
+ }
+
+ impl std::convert::TryFrom<u16> for $name {
+ type Error = crate::Error;
+
+ fn try_from(v: u16) -> Result<Self, Self::Error> {
+ match v {
+ $(x if x == $name::$vname as u16 => Ok($name::$vname),)*
+ _ => Err(crate::Error::Unsupported),
+ }
+ }
+ }
+
+ impl std::convert::From<$name> for u16 {
+ fn from(v: $name) -> u16 {
+ v as u16
+ }
+ }
+ }
+}
+
+convert_enum! {
+pub enum Kem {
+ X25519Sha256 = 32,
+}
+}
+
+impl Kem {
+ #[must_use]
+ pub fn n_enc(self) -> usize {
+ match self {
+ Kem::X25519Sha256 => 32,
+ }
+ }
+
+ #[must_use]
+ pub fn n_pk(self) -> usize {
+ match self {
+ Kem::X25519Sha256 => 32,
+ }
+ }
+}
+
+convert_enum! {
+ pub enum Kdf {
+ HkdfSha256 = 1,
+ HkdfSha384 = 2,
+ HkdfSha512 = 3,
+ }
+}
+
+convert_enum! {
+ pub enum Aead {
+ Aes128Gcm = 1,
+ Aes256Gcm = 2,
+ ChaCha20Poly1305 = 3,
+ }
+}
+
+impl Aead {
+ /// The size of the key for this AEAD.
+ #[must_use]
+ pub fn n_k(self) -> usize {
+ match self {
+ Aead::Aes128Gcm => 16,
+ Aead::Aes256Gcm | Aead::ChaCha20Poly1305 => 32,
+ }
+ }
+
+ /// The size of the nonce for this AEAD.
+ #[must_use]
+ pub fn n_n(self) -> usize {
+ match self {
+ Aead::Aes128Gcm | Aead::Aes256Gcm | Aead::ChaCha20Poly1305 => 12,
+ }
+ }
+
+ /// The size of the tag for this AEAD.
+ #[must_use]
+ #[allow(clippy::unused_self)] // This is only presently constant.
+ pub fn n_t(self) -> usize {
+ 16
+ }
+}
diff --git a/third_party/rust/ohttp/src/lib.rs b/third_party/rust/ohttp/src/lib.rs
new file mode 100644
index 0000000000..15c4649ad8
--- /dev/null
+++ b/third_party/rust/ohttp/src/lib.rs
@@ -0,0 +1,657 @@
+#![deny(warnings, clippy::pedantic)]
+#![allow(clippy::missing_errors_doc)] // I'm too lazy
+#![cfg_attr(
+ not(all(feature = "client", feature = "server")),
+ allow(dead_code, unused_imports)
+)]
+
+mod err;
+pub mod hpke;
+#[cfg(feature = "nss")]
+mod nss;
+#[cfg(feature = "rust-hpke")]
+mod rand;
+#[cfg(feature = "rust-hpke")]
+mod rh;
+
+pub use err::Error;
+
+use crate::hpke::{Aead as AeadId, Kdf, Kem};
+use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
+use err::Res;
+use log::trace;
+use std::{
+ cmp::max,
+ convert::TryFrom,
+ io::{BufReader, Read},
+ mem::size_of,
+};
+
+#[cfg(feature = "nss")]
+use nss::random;
+#[cfg(feature = "nss")]
+use nss::{
+ aead::{Aead, Mode, NONCE_LEN},
+ hkdf::{Hkdf, KeyMechanism},
+ hpke::{generate_key_pair, Config as HpkeConfig, Exporter, HpkeR, HpkeS},
+ PrivateKey, PublicKey,
+};
+
+#[cfg(feature = "rust-hpke")]
+use crate::rand::random;
+#[cfg(feature = "rust-hpke")]
+use rh::{
+ aead::{Aead, Mode, NONCE_LEN},
+ hkdf::{Hkdf, KeyMechanism},
+ hpke::{
+ derive_key_pair, generate_key_pair, Config as HpkeConfig, Exporter, HpkeR, HpkeS,
+ PrivateKey, PublicKey,
+ },
+};
+
+/// The request header is a `KeyId` and 2 each for KEM, KDF, and AEAD identifiers
+const REQUEST_HEADER_LEN: usize = size_of::<KeyId>() + 6;
+const INFO_REQUEST: &[u8] = b"message/bhttp request";
+/// The info used for HPKE export is `INFO_REQUEST`, a zero byte, and the header.
+const INFO_LEN: usize = INFO_REQUEST.len() + 1 + REQUEST_HEADER_LEN;
+const LABEL_RESPONSE: &[u8] = b"message/bhttp response";
+const INFO_KEY: &[u8] = b"key";
+const INFO_NONCE: &[u8] = b"nonce";
+
+/// The type of a key identifier.
+pub type KeyId = u8;
+
+pub fn init() {
+ #[cfg(feature = "nss")]
+ 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);
+ info.extend_from_slice(INFO_REQUEST);
+ info.push(0);
+ info.write_u8(key_id)?;
+ info.write_u16::<NetworkEndian>(u16::from(config.kem()))?;
+ info.write_u16::<NetworkEndian>(u16::from(config.kdf()))?;
+ info.write_u16::<NetworkEndian>(u16::from(config.aead()))?;
+ trace!("HPKE info: {}", hex::encode(&info));
+ Ok(info)
+}
+
+/// This is the sort of information we expect to receive from the receiver.
+/// This might not be necessary if we agree on a format.
+#[cfg(feature = "client")]
+pub struct ClientRequest {
+ hpke: HpkeS,
+ header: Vec<u8>,
+}
+
+#[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)?;
+ // TODO(mt) choose the best config, not just the first.
+ let selected = config.select(config.symmetric[0])?;
+
+ // Build the info, which contains the message header.
+ let info = build_info(config.key_id, selected)?;
+ let hpke = HpkeS::new(selected, &mut config.pk, &info)?;
+
+ let header = Vec::from(&info[INFO_REQUEST.len() + 1..]);
+ debug_assert_eq!(header.len(), REQUEST_HEADER_LEN);
+ Ok(Self { hpke, header })
+ }
+
+ /// 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)> {
+ let extra =
+ self.hpke.config().kem().n_enc() + self.hpke.config().aead().n_t() + request.len();
+ let expected_len = self.header.len() + extra;
+
+ let mut enc_request = self.header;
+ enc_request.reserve_exact(extra);
+
+ let enc = self.hpke.enc()?;
+ enc_request.extend_from_slice(&enc);
+
+ let mut ct = self.hpke.seal(&[], request)?;
+ enc_request.append(&mut ct);
+
+ debug_assert_eq!(expected_len, enc_request.len());
+ Ok((enc_request, ClientResponse::new(self.hpke, enc)))
+ }
+}
+
+/// A server can handle multiple requests.
+/// 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")]
+pub struct Server {
+ config: KeyConfig,
+}
+
+#[cfg(feature = "server")]
+impl Server {
+ /// Create a new server configuration.
+ /// # Panics
+ /// If the configuration doesn't include a private key.
+ pub fn new(config: KeyConfig) -> Res<Self> {
+ assert!(config.sk.is_some());
+ Ok(Self { config })
+ }
+
+ /// Get the configuration that this server uses.
+ #[must_use]
+ pub fn config(&self) -> &KeyConfig {
+ &self.config
+ }
+
+ /// Remove encapsulation on a message.
+ /// # 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)> {
+ if enc_request.len() < REQUEST_HEADER_LEN {
+ return Err(Error::Truncated);
+ }
+ let mut r = BufReader::new(enc_request);
+ let key_id = r.read_u8()?;
+ if key_id != self.config.key_id {
+ return Err(Error::KeyId);
+ }
+ let kem_id = Kem::try_from(r.read_u16::<NetworkEndian>()?)?;
+ if kem_id != self.config.kem {
+ return Err(Error::InvalidKem);
+ }
+ let kdf_id = Kdf::try_from(r.read_u16::<NetworkEndian>()?)?;
+ let aead_id = AeadId::try_from(r.read_u16::<NetworkEndian>()?)?;
+ let sym = SymmetricSuite::new(kdf_id, aead_id);
+
+ let info = build_info(
+ key_id,
+ HpkeConfig::new(self.config.kem, sym.kdf(), sym.aead()),
+ )?;
+
+ let cfg = self.config.select(sym)?;
+ let mut enc = vec![0; cfg.kem().n_enc()];
+ r.read_exact(&mut enc)?;
+ let mut hpke = HpkeR::new(
+ cfg,
+ &self.config.pk,
+ self.config.sk.as_mut().unwrap(),
+ &enc,
+ &info,
+ )?;
+
+ let mut ct = Vec::new();
+ r.read_to_end(&mut ct)?;
+
+ let request = hpke.open(&[], &ct)?;
+ Ok((request, ServerResponse::new(&hpke, enc)?))
+ }
+}
+
+fn entropy(config: HpkeConfig) -> usize {
+ max(config.aead().n_n(), config.aead().n_k())
+}
+
+fn make_aead(
+ mode: Mode,
+ cfg: HpkeConfig,
+ exp: &impl Exporter,
+ enc: Vec<u8>,
+ response_nonce: &[u8],
+) -> Res<Aead> {
+ let secret = exp.export(LABEL_RESPONSE, entropy(cfg))?;
+ let mut salt = enc;
+ salt.extend_from_slice(response_nonce);
+
+ let hkdf = Hkdf::new(cfg.kdf());
+ let prk = hkdf.extract(&salt, &secret)?;
+
+ let key = hkdf.expand_key(&prk, INFO_KEY, KeyMechanism::Aead(cfg.aead()))?;
+ let iv = hkdf.expand_data(&prk, INFO_NONCE, cfg.aead().n_n())?;
+ let nonce_base = <[u8; NONCE_LEN]>::try_from(iv).unwrap();
+
+ Aead::new(mode, cfg.aead(), &key, nonce_base)
+}
+
+/// An object for encapsulating responses.
+/// The only way to obtain one of these is through `Server::decapsulate()`.
+#[cfg(feature = "server")]
+pub struct ServerResponse {
+ response_nonce: Vec<u8>,
+ aead: Aead,
+}
+
+#[cfg(feature = "server")]
+impl ServerResponse {
+ fn new(hpke: &HpkeR, enc: Vec<u8>) -> Res<Self> {
+ let response_nonce = random(entropy(hpke.config()));
+ let aead = make_aead(Mode::Encrypt, hpke.config(), hpke, enc, &response_nonce)?;
+ Ok(Self {
+ response_nonce,
+ aead,
+ })
+ }
+
+ /// Consume this object by encapsulating a response.
+ pub fn encapsulate(mut self, response: &[u8]) -> Res<Vec<u8>> {
+ let mut enc_response = self.response_nonce;
+ let mut ct = self.aead.seal(&[], response)?;
+ enc_response.append(&mut ct);
+ Ok(enc_response)
+ }
+}
+
+#[cfg(feature = "server")]
+impl std::fmt::Debug for ServerResponse {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("ServerResponse")
+ }
+}
+
+/// An object for decapsulating responses.
+/// The only way to obtain one of these is through `ClientRequest::encapsulate()`.
+#[cfg(feature = "client")]
+pub struct ClientResponse {
+ hpke: HpkeS,
+ enc: Vec<u8>,
+}
+
+#[cfg(feature = "client")]
+impl ClientResponse {
+ /// Private method for constructing one of these.
+ /// Doesn't do anything because we don't have the nonce yet, so
+ /// the work that can be done is limited.
+ fn new(hpke: HpkeS, enc: Vec<u8>) -> Self {
+ Self { hpke, enc }
+ }
+
+ /// Consume this object by decapsulating a response.
+ pub fn decapsulate(self, enc_response: &[u8]) -> Res<Vec<u8>> {
+ let mid = entropy(self.hpke.config());
+ if mid >= enc_response.len() {
+ return Err(Error::Truncated);
+ }
+ let (response_nonce, ct) = enc_response.split_at(mid);
+ let mut aead = make_aead(
+ Mode::Decrypt,
+ self.hpke.config(),
+ &self.hpke,
+ self.enc,
+ response_nonce,
+ )?;
+ aead.open(&[], 0, ct) // 0 is the sequence number
+ }
+}
+
+#[cfg(all(test, feature = "client", feature = "server"))]
+mod test {
+ use crate::{
+ err::Res,
+ hpke::{Aead, Kdf, Kem},
+ ClientRequest, Error, KeyConfig, KeyId, Server, SymmetricSuite,
+ };
+ use log::trace;
+ use std::{fmt::Debug, io::ErrorKind};
+
+ 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),
+ ];
+
+ const REQUEST: &[u8] = &[
+ 0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70, 0x73, 0x0b, 0x65, 0x78, 0x61,
+ 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x01, 0x2f,
+ ];
+ const RESPONSE: &[u8] = &[0x01, 0x40, 0xc8];
+
+ fn init() {
+ crate::init();
+ let _ = env_logger::try_init();
+ }
+
+ #[test]
+ fn request_response() {
+ init();
+
+ let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
+ let mut 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 (enc_request, client_response) = client.encapsulate(REQUEST).unwrap();
+ trace!("Request: {}", hex::encode(REQUEST));
+ trace!("Encapsulated Request: {}", hex::encode(&enc_request));
+
+ let (request, server_response) = server.decapsulate(&enc_request).unwrap();
+ assert_eq!(&request[..], REQUEST);
+
+ let enc_response = server_response.encapsulate(RESPONSE).unwrap();
+ trace!("Encapsulated Response: {}", hex::encode(&enc_response));
+
+ let response = client_response.decapsulate(&enc_response).unwrap();
+ assert_eq!(&response[..], RESPONSE);
+ trace!("Response: {}", hex::encode(RESPONSE));
+ }
+
+ #[test]
+ fn two_requests() {
+ init();
+
+ let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
+ let mut server = Server::new(server_config).unwrap();
+ let encoded_config = server.config().encode().unwrap();
+
+ let client1 = ClientRequest::new(&encoded_config).unwrap();
+ let (enc_request1, client_response1) = client1.encapsulate(REQUEST).unwrap();
+ let client2 = ClientRequest::new(&encoded_config).unwrap();
+ let (enc_request2, client_response2) = client2.encapsulate(REQUEST).unwrap();
+ assert_ne!(enc_request1, enc_request2);
+
+ let (request1, server_response1) = server.decapsulate(&enc_request1).unwrap();
+ assert_eq!(&request1[..], REQUEST);
+ let (request2, server_response2) = server.decapsulate(&enc_request2).unwrap();
+ assert_eq!(&request2[..], REQUEST);
+
+ let enc_response1 = server_response1.encapsulate(RESPONSE).unwrap();
+ let enc_response2 = server_response2.encapsulate(RESPONSE).unwrap();
+ assert_ne!(enc_response1, enc_response2);
+
+ let response1 = client_response1.decapsulate(&enc_response1).unwrap();
+ assert_eq!(&response1[..], RESPONSE);
+ let response2 = client_response2.decapsulate(&enc_response2).unwrap();
+ assert_eq!(&response2[..], RESPONSE);
+ }
+
+ fn assert_truncated<T: Debug>(res: Res<T>) {
+ match res.unwrap_err() {
+ Error::Truncated => {}
+ #[cfg(feature = "rust-hpke")]
+ Error::Aead(_) => {}
+ #[cfg(feature = "nss")]
+ Error::Crypto(_) => {}
+ Error::Io(e) => assert_eq!(e.kind(), ErrorKind::UnexpectedEof),
+ e => panic!("unexpected error type: {e:?}"),
+ }
+ }
+
+ fn request_truncated(cut: usize) {
+ init();
+
+ let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
+ let mut server = Server::new(server_config).unwrap();
+ let encoded_config = server.config().encode().unwrap();
+
+ let client = ClientRequest::new(&encoded_config).unwrap();
+ let (enc_request, _) = client.encapsulate(REQUEST).unwrap();
+
+ let res = server.decapsulate(&enc_request[..cut]);
+ assert_truncated(res);
+ }
+
+ #[test]
+ fn request_truncated_header() {
+ request_truncated(4);
+ }
+
+ #[test]
+ fn request_truncated_enc() {
+ // header is 7, enc is 32
+ request_truncated(24);
+ }
+
+ #[test]
+ fn request_truncated_ct() {
+ // header and enc is 39, aead needs at least 16 more
+ request_truncated(42);
+ }
+
+ fn response_truncated(cut: usize) {
+ init();
+
+ let server_config = KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
+ let mut server = Server::new(server_config).unwrap();
+ let encoded_config = server.config().encode().unwrap();
+
+ let client = ClientRequest::new(&encoded_config).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 res = client_response.decapsulate(&enc_response[..cut]);
+ assert_truncated(res);
+ }
+
+ #[test]
+ fn response_truncated_ct() {
+ // nonce is 16, aead needs at least 16 more
+ response_truncated(20);
+ }
+
+ #[test]
+ fn response_truncated_nonce() {
+ response_truncated(7);
+ }
+
+ #[cfg(feature = "rust-hpke")]
+ #[test]
+ fn derive_key_pair() {
+ const IKM: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ ];
+ const EXPECTED_CONFIG: &[u8] = &[
+ 0x01, 0x00, 0x20, 0xfc, 0x01, 0x38, 0x93, 0x64, 0x10, 0x31, 0x1a, 0x0c, 0x64, 0x1a,
+ 0x5c, 0xa0, 0x86, 0x39, 0x1d, 0xe8, 0xe7, 0x03, 0x82, 0x33, 0x3f, 0x6d, 0x64, 0x49,
+ 0x25, 0x21, 0xad, 0x7d, 0xc7, 0x8a, 0x5d, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x03,
+ ];
+
+ init();
+
+ let config = KeyConfig::parse(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);
+ assert_eq!(config.kem, new_config.kem);
+ assert_eq!(config.symmetric, new_config.symmetric);
+
+ let server = Server::new(new_config).unwrap();
+ let encoded_config = server.config().encode().unwrap();
+ assert_eq!(EXPECTED_CONFIG, encoded_config);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/aead.rs b/third_party/rust/ohttp/src/nss/aead.rs
new file mode 100644
index 0000000000..6e36e33756
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/aead.rs
@@ -0,0 +1,329 @@
+use super::{
+ err::secstatus_to_res,
+ p11::{
+ sys::{
+ self, PK11Context, PK11_AEADOp, PK11_CreateContextBySymKey, PRBool, CKA_DECRYPT,
+ CKA_ENCRYPT, CKA_NSS_MESSAGE, CKG_GENERATE_COUNTER_XOR, CKG_NO_GENERATE, CKM_AES_GCM,
+ CKM_CHACHA20_POLY1305, CK_ATTRIBUTE_TYPE, CK_GENERATOR_FUNCTION, CK_MECHANISM_TYPE,
+ },
+ Item, SymKey,
+ },
+};
+use crate::{
+ err::{Error, Res},
+ hpke::Aead as AeadId,
+};
+use log::trace;
+use std::{
+ convert::{TryFrom, TryInto},
+ mem,
+ os::raw::c_int,
+};
+
+/// All the nonces are the same length. Exploit that.
+pub const NONCE_LEN: usize = 12;
+/// The portion of the nonce that is a counter.
+const COUNTER_LEN: usize = mem::size_of::<SequenceNumber>();
+/// The NSS API insists on us identifying the tag separately, which is awful.
+/// All of the AEAD functions here have a tag of this length, so use a fixed offset.
+const TAG_LEN: usize = 16;
+
+pub type SequenceNumber = u64;
+
+/// All the lengths used by `PK11_AEADOp` are signed. This converts to that.
+fn c_int_len<T>(l: T) -> c_int
+where
+ T: TryInto<c_int>,
+ T::Error: std::error::Error,
+{
+ l.try_into().unwrap()
+}
+
+unsafe fn destroy_aead_context(ctx: *mut PK11Context) {
+ sys::PK11_DestroyContext(ctx, PRBool::from(true));
+}
+scoped_ptr!(Context, PK11Context, destroy_aead_context);
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Mode {
+ Encrypt,
+ Decrypt,
+}
+
+impl Mode {
+ fn p11mode(self) -> CK_ATTRIBUTE_TYPE {
+ CK_ATTRIBUTE_TYPE::from(
+ CKA_NSS_MESSAGE
+ | match self {
+ Self::Encrypt => CKA_ENCRYPT,
+ Self::Decrypt => CKA_DECRYPT,
+ },
+ )
+ }
+}
+
+/// This is an AEAD instance that uses the
+pub struct Aead {
+ mode: Mode,
+ ctx: Context,
+ nonce_base: [u8; NONCE_LEN],
+}
+
+impl Aead {
+ fn mech(algorithm: AeadId) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match algorithm {
+ // The key size determines which AES variant is used.
+ AeadId::Aes128Gcm | AeadId::Aes256Gcm => CKM_AES_GCM,
+ AeadId::ChaCha20Poly1305 => CKM_CHACHA20_POLY1305,
+ })
+ }
+
+ #[cfg(test)]
+ pub fn import_key(algorithm: AeadId, key: &[u8]) -> Res<SymKey> {
+ let slot = super::p11::Slot::internal()?;
+ let ptr = unsafe {
+ sys::PK11_ImportSymKey(
+ *slot,
+ Self::mech(algorithm),
+ sys::PK11Origin::PK11_OriginUnwrap,
+ sys::CK_ATTRIBUTE_TYPE::from(sys::CKA_ENCRYPT | sys::CKA_DECRYPT),
+ &mut super::p11::Item::wrap(key),
+ std::ptr::null_mut(),
+ )
+ };
+ SymKey::from_ptr(ptr)
+ }
+
+ pub fn new(
+ mode: Mode,
+ algorithm: AeadId,
+ key: &SymKey,
+ nonce_base: [u8; NONCE_LEN],
+ ) -> Res<Self> {
+ trace!(
+ "New AEAD: key={} nonce_base={}",
+ hex::encode(key.key_data()?),
+ hex::encode(nonce_base)
+ );
+
+ let ptr = unsafe {
+ PK11_CreateContextBySymKey(
+ Self::mech(algorithm),
+ mode.p11mode(),
+ **key,
+ &Item::wrap(&nonce_base[..]),
+ )
+ };
+ Ok(Self {
+ mode,
+ ctx: Context::from_ptr(ptr)?,
+ nonce_base,
+ })
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ assert_eq!(self.mode, Mode::Encrypt);
+ // A copy for the nonce generator to write into. But we don't use the value.
+ let mut nonce = self.nonce_base;
+ // Ciphertext with enough space for the tag.
+ // Even though we give the operation a separate buffer for the tag,
+ // reserve the capacity on allocation.
+ let mut ct = vec![0; pt.len() + TAG_LEN];
+ let mut ct_len: c_int = 0;
+ let mut tag = vec![0; TAG_LEN];
+ secstatus_to_res(unsafe {
+ PK11_AEADOp(
+ *self.ctx,
+ CK_GENERATOR_FUNCTION::from(CKG_GENERATE_COUNTER_XOR),
+ c_int_len(NONCE_LEN - COUNTER_LEN), // Fixed portion of the nonce.
+ nonce.as_mut_ptr(),
+ c_int_len(nonce.len()),
+ aad.as_ptr(),
+ c_int_len(aad.len()),
+ ct.as_mut_ptr(),
+ &mut ct_len,
+ c_int_len(ct.len()), // signed :(
+ tag.as_mut_ptr(),
+ c_int_len(tag.len()),
+ pt.as_ptr(),
+ c_int_len(pt.len()),
+ )
+ })?;
+ ct.truncate(usize::try_from(ct_len).unwrap());
+ debug_assert_eq!(ct.len(), pt.len());
+ ct.append(&mut tag);
+ Ok(ct)
+ }
+
+ pub fn open(&mut self, aad: &[u8], seq: SequenceNumber, ct: &[u8]) -> Res<Vec<u8>> {
+ assert_eq!(self.mode, Mode::Decrypt);
+ let mut nonce = self.nonce_base;
+ for (i, n) in nonce.iter_mut().rev().take(COUNTER_LEN).enumerate() {
+ *n ^= u8::try_from((seq >> (8 * i)) & 0xff).unwrap();
+ }
+ let mut pt = vec![0; ct.len()]; // NSS needs more space than it uses for plaintext.
+ let mut pt_len: c_int = 0;
+ let pt_expected = ct.len().checked_sub(TAG_LEN).ok_or(Error::Truncated)?;
+ secstatus_to_res(unsafe {
+ PK11_AEADOp(
+ *self.ctx,
+ CK_GENERATOR_FUNCTION::from(CKG_NO_GENERATE),
+ c_int_len(NONCE_LEN - COUNTER_LEN), // Fixed portion of the nonce.
+ nonce.as_mut_ptr(),
+ c_int_len(nonce.len()),
+ aad.as_ptr(),
+ c_int_len(aad.len()),
+ pt.as_mut_ptr(),
+ &mut pt_len,
+ c_int_len(pt.len()), // signed :(
+ ct.as_ptr().add(pt_expected) as *mut _, // const cast :(
+ c_int_len(TAG_LEN),
+ ct.as_ptr(),
+ c_int_len(pt_expected),
+ )
+ })?;
+ let len = usize::try_from(pt_len).unwrap();
+ debug_assert_eq!(len, pt_expected);
+ pt.truncate(len);
+ Ok(pt)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ super::{super::hpke::Aead as AeadId, init},
+ Aead, Mode, SequenceNumber, NONCE_LEN,
+ };
+
+ /// Check that the first invocation of encryption matches expected values.
+ /// Also check decryption of the same.
+ fn check0(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ init();
+ let k = Aead::import_key(algorithm, key).unwrap();
+
+ let mut enc = Aead::new(Mode::Encrypt, algorithm, &k, *nonce).unwrap();
+ let ciphertext = enc.seal(aad, pt).unwrap();
+ assert_eq!(&ciphertext[..], ct);
+
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, 0, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ fn decrypt(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ seq: SequenceNumber,
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ let k = Aead::import_key(algorithm, key).unwrap();
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, seq, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ /// This tests the AEAD in QUIC in combination with the HKDF code.
+ /// This is an AEAD-only example.
+ #[test]
+ fn quic_retry() {
+ const KEY: &[u8] = &[
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68,
+ 0xc8, 0x4e,
+ ];
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
+ ];
+ const AAD: &[u8] = &[
+ 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0xff, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65,
+ 0x6e,
+ ];
+ const CT: &[u8] = &[
+ 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58, 0xfb, 0x3f, 0x0f, 0x24,
+ 0x96, 0xba,
+ ];
+ check0(AeadId::Aes128Gcm, KEY, NONCE, AAD, &[], CT);
+ }
+
+ #[test]
+ fn quic_server_initial() {
+ const ALG: AeadId = AeadId::Aes128Gcm;
+ const KEY: &[u8] = &[
+ 0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3, 0x79, 0xb6, 0x06,
+ 0x7e, 0x37,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3e,
+ ];
+ // Note that this integrates the sequence number of 1 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3f,
+ ];
+ const AAD: &[u8] = &[
+ 0xc1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62,
+ 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01,
+ ];
+ const PT: &[u8] = &[
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03,
+ 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78,
+ 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43,
+ 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00,
+ 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0,
+ 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83,
+ 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03,
+ 0x04,
+ ];
+ const CT: &[u8] = &[
+ 0x5a, 0x48, 0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58, 0x16,
+ 0xb6, 0x39, 0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75, 0x54, 0x78, 0x0b, 0xb3,
+ 0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c, 0xf7, 0x3c, 0x3e, 0xc2, 0x49, 0x3a, 0x18,
+ 0x39, 0xb3, 0xdb, 0xcb, 0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7, 0x68, 0x4d, 0xf3, 0x54,
+ 0x8e, 0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73, 0xcc, 0x3f, 0x3b, 0xde, 0xd7, 0x4b,
+ 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84, 0x02, 0x2f, 0x8e, 0xf4, 0xcd, 0xd9, 0x37, 0x95,
+ 0xd7, 0x7d, 0x06, 0xed, 0xbb, 0x7a, 0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd,
+ 0xca, 0x3d, 0x20, 0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,
+ 0xd0, 0x74, 0xee,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ decrypt(ALG, KEY, NONCE_BASE, 1, AAD, PT, CT);
+ }
+
+ #[test]
+ fn quic_chacha() {
+ const ALG: AeadId = AeadId::ChaCha20Poly1305;
+ const KEY: &[u8] = &[
+ 0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20, 0x94, 0xf6, 0x9c,
+ 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88, 0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79,
+ 0xfb, 0x23, 0xe1, 0xc8,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1, 0x44,
+ ];
+ // Note that this integrates the sequence number of 654360564 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x6d, 0x41, 0x7e, 0xb0,
+ ];
+ const AAD: &[u8] = &[0x42, 0x00, 0xbf, 0xf4];
+ const PT: &[u8] = &[0x01];
+ const CT: &[u8] = &[
+ 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2,
+ 0x5a, 0x5b, 0xfb,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ // Now use the real nonce and sequence number from the example.
+ decrypt(ALG, KEY, NONCE_BASE, 654_360_564, AAD, PT, CT);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/err.rs b/third_party/rust/ohttp/src/nss/err.rs
new file mode 100644
index 0000000000..af85066de9
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/err.rs
@@ -0,0 +1,141 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(
+ dead_code,
+ clippy::upper_case_acronyms,
+ clippy::module_name_repetitions
+)]
+
+use super::{SECStatus, SECSuccess};
+use crate::err::Res;
+use std::os::raw::c_char;
+
+include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
+mod codes {
+ #![allow(non_snake_case)]
+ include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
+}
+pub use codes::SECErrorCodes as sec;
+pub mod nspr {
+ include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Error {
+ name: String,
+ code: PRErrorCode,
+ desc: String,
+}
+
+impl Error {
+ /// Get an internal error.
+ pub(crate) fn internal() -> Self {
+ Self::from(sec::SEC_ERROR_LIBRARY_FAILURE)
+ }
+
+ /// Get the last error, as returned by `PR_GetError()`.
+ pub(crate) fn last() -> crate::Error {
+ crate::Error::from(Self::from(unsafe { PR_GetError() }))
+ }
+}
+
+impl From<PRErrorCode> for Error {
+ fn from(code: PRErrorCode) -> Self {
+ let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
+ let desc = wrap_str_fn(
+ || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
+ "...",
+ );
+ Error { name, code, desc }
+ }
+}
+
+impl std::error::Error for Error {}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Error {} ({}): {}", self.name, self.code, self.desc)
+ }
+}
+
+use std::ffi::CStr;
+
+fn wrap_str_fn<F>(f: F, dflt: &str) -> String
+where
+ F: FnOnce() -> *const c_char,
+{
+ unsafe {
+ let p = f();
+ if p.is_null() {
+ return dflt.to_string();
+ }
+ CStr::from_ptr(p).to_string_lossy().into_owned()
+ }
+}
+
+pub fn secstatus_to_res(rv: SECStatus) -> Res<()> {
+ if rv == SECSuccess {
+ Ok(())
+ } else {
+ Err(Error::last())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ super::{init, SECFailure, SECSuccess},
+ secstatus_to_res, PRErrorCode, PR_SetError,
+ };
+
+ fn set_error_code(code: PRErrorCode) {
+ // This code doesn't work without initializing NSS first.
+ init();
+ unsafe {
+ PR_SetError(code, 0);
+ }
+ }
+
+ #[test]
+ fn error_code() {
+ init();
+ assert_eq!(166 - 0x2000, super::sec::SEC_ERROR_LIBPKIX_INTERNAL);
+ assert_eq!(-5998, super::nspr::PR_WOULD_BLOCK_ERROR);
+ }
+
+ #[test]
+ fn is_ok() {
+ assert!(secstatus_to_res(SECSuccess).is_ok());
+ }
+
+ #[test]
+ fn is_err() {
+ set_error_code(super::sec::SEC_ERROR_BAD_DATABASE);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ if let crate::Error::Crypto(e) = r.unwrap_err() {
+ assert_eq!(e.name, "SEC_ERROR_BAD_DATABASE");
+ assert_eq!(e.code, 18 - 0x2000);
+ assert_eq!(e.desc, "security library: bad database.");
+ } else {
+ panic!();
+ }
+ }
+
+ #[test]
+ fn is_err_zero_code() {
+ set_error_code(0);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ if let crate::Error::Crypto(e) = r.unwrap_err() {
+ assert_eq!(e.name, "UNKNOWN_ERROR");
+ assert_eq!(e.code, 0);
+ } else {
+ panic!();
+ }
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/hkdf.rs b/third_party/rust/ohttp/src/nss/hkdf.rs
new file mode 100644
index 0000000000..e53c944008
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/hkdf.rs
@@ -0,0 +1,289 @@
+use super::{
+ super::hpke::{Aead, Kdf},
+ p11::{
+ sys::{
+ self, CKA_DERIVE, CKF_HKDF_SALT_DATA, CKF_HKDF_SALT_NULL, CKM_AES_GCM,
+ CKM_CHACHA20_POLY1305, CKM_HKDF_DATA, CKM_HKDF_DERIVE, CKM_SHA256, CK_BBOOL,
+ CK_HKDF_PARAMS, CK_INVALID_HANDLE, CK_MECHANISM_TYPE, CK_OBJECT_HANDLE, CK_ULONG,
+ },
+ ParamItem, SymKey,
+ },
+};
+use crate::err::Res;
+use log::trace;
+use std::{convert::TryFrom, os::raw::c_int, ptr::null_mut};
+
+#[derive(Clone, Copy)]
+pub enum KeyMechanism {
+ Aead(Aead),
+ #[allow(dead_code)] // We don't use this one.
+ Hkdf,
+}
+
+impl KeyMechanism {
+ fn mech(self) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match self {
+ Self::Aead(Aead::Aes128Gcm) | Self::Aead(Aead::Aes256Gcm) => CKM_AES_GCM,
+ Self::Aead(Aead::ChaCha20Poly1305) => CKM_CHACHA20_POLY1305,
+ Self::Hkdf => CKM_HKDF_DERIVE,
+ })
+ }
+
+ fn len(self) -> usize {
+ match self {
+ Self::Aead(a) => a.n_k(),
+ Self::Hkdf => 0, // Let the underlying module decide.
+ }
+ }
+}
+
+pub struct Hkdf {
+ kdf: Kdf,
+}
+
+impl Hkdf {
+ pub fn new(kdf: Kdf) -> Self {
+ Self { kdf }
+ }
+
+ #[cfg(test)]
+ pub fn import_ikm(ikm: &[u8]) -> Res<SymKey> {
+ let slot = super::p11::Slot::internal()?;
+ let ptr = unsafe {
+ sys::PK11_ImportSymKey(
+ *slot,
+ CK_MECHANISM_TYPE::from(sys::CKM_HKDF_KEY_GEN),
+ sys::PK11Origin::PK11_OriginUnwrap,
+ sys::CK_ATTRIBUTE_TYPE::from(sys::CKA_SIGN),
+ &mut super::p11::Item::wrap(ikm),
+ null_mut(),
+ )
+ };
+ SymKey::from_ptr(ptr)
+ }
+
+ fn mech(&self) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match self.kdf {
+ Kdf::HkdfSha256 => CKM_SHA256,
+ _ => unimplemented!(),
+ })
+ }
+
+ pub fn extract(&self, salt: &[u8], ikm: &SymKey) -> Res<SymKey> {
+ let salt_type = if salt.is_empty() {
+ CKF_HKDF_SALT_NULL
+ } else {
+ CKF_HKDF_SALT_DATA
+ };
+ let mut params = CK_HKDF_PARAMS {
+ bExtract: CK_BBOOL::from(true),
+ bExpand: CK_BBOOL::from(false),
+ prfHashMechanism: self.mech(),
+ ulSaltType: CK_ULONG::from(salt_type),
+ pSalt: salt.as_ptr() as *mut _, // const-cast = bad API
+ ulSaltLen: CK_ULONG::try_from(salt.len()).unwrap(),
+ hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
+ pInfo: null_mut(),
+ ulInfoLen: 0,
+ };
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **ikm,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ params_item.ptr(),
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ 0,
+ )
+ };
+
+ let prk = SymKey::from_ptr(ptr)?;
+ trace!(
+ "HKDF extract: salt={} ikm={} prk={}",
+ hex::encode(salt),
+ hex::encode(ikm.key_data()?),
+ hex::encode(prk.key_data()?),
+ );
+ Ok(prk)
+ }
+
+ // NB: `info` must outlive the returned value.
+ fn expand_params(&self, info: &[u8]) -> CK_HKDF_PARAMS {
+ CK_HKDF_PARAMS {
+ bExtract: CK_BBOOL::from(false),
+ bExpand: CK_BBOOL::from(true),
+ prfHashMechanism: self.mech(),
+ ulSaltType: CK_ULONG::from(CKF_HKDF_SALT_NULL),
+ pSalt: null_mut(),
+ ulSaltLen: 0,
+ hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
+ pInfo: info.as_ptr() as *mut _, // const-cast = bad API
+ ulInfoLen: CK_ULONG::try_from(info.len()).unwrap(),
+ }
+ }
+
+ pub fn expand_key(&self, prk: &SymKey, info: &[u8], key_mech: KeyMechanism) -> Res<SymKey> {
+ let mut params = self.expand_params(info);
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **prk,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ params_item.ptr(),
+ key_mech.mech(),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ c_int::try_from(key_mech.len()).unwrap(),
+ )
+ };
+ let okm = SymKey::from_ptr(ptr)?;
+ trace!(
+ "HKDF expand_key: prk={} info={} okm={}",
+ hex::encode(prk.key_data()?),
+ hex::encode(info),
+ hex::encode(okm.key_data()?),
+ );
+ Ok(okm)
+ }
+
+ pub fn expand_data(&self, prk: &SymKey, info: &[u8], len: usize) -> Res<Vec<u8>> {
+ let mut params = self.expand_params(info);
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **prk,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DATA),
+ params_item.ptr(),
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ c_int::try_from(len).unwrap(),
+ )
+ };
+ let k = SymKey::from_ptr(ptr)?;
+ let r = Vec::from(k.key_data()?);
+ trace!(
+ "HKDF expand_data: prk={} info={} okm={}",
+ hex::encode(prk.key_data()?),
+ hex::encode(info),
+ hex::encode(&r),
+ );
+ Ok(r)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{super::super::hpke::Kdf, Hkdf};
+ use crate::init;
+
+ fn sha256_example(
+ ikm: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ l: usize,
+ expected_prk: &[u8],
+ expected_okm: &[u8],
+ ) {
+ init();
+ let hkdf = Hkdf::new(Kdf::HkdfSha256);
+ let k_ikm = Hkdf::import_ikm(ikm).unwrap();
+ let prk = hkdf.extract(salt, &k_ikm).unwrap();
+ let prk_data = prk.key_data().unwrap();
+ assert_eq!(prk_data, expected_prk);
+
+ let out = hkdf.expand_data(&prk, info, l).unwrap();
+ assert_eq!(&out[..], expected_okm);
+ }
+
+ /// Example 1 from <https://tools.ietf.org/html/rfc5869#appendix-A.1>
+ #[test]
+ fn example1() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ ];
+ const INFO: &[u8] = &[0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b,
+ 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a,
+ 0xd7, 0xc2, 0xb3, 0xe5,
+ ];
+ const OKM: &[u8] = &[
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
+ 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
+ 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 2 from <https://tools.ietf.org/html/rfc5869#appendix-A.2>
+ #[test]
+ fn example2() {
+ const IKM: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ];
+ const SALT: &[u8] = &[
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+ 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
+ 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ ];
+ const INFO: &[u8] = &[
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd,
+ 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ ];
+ const L: usize = 82;
+ const PRK: &[u8] = &[
+ 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35,
+ 0xb4, 0x5c, 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01, 0x4a, 0x19, 0x3f, 0x40,
+ 0xc1, 0x5f, 0xc2, 0x44,
+ ];
+ const OKM: &[u8] = &[
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
+ 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
+ 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
+ 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+ 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
+ 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 3 from <https://tools.ietf.org/html/rfc5869#appendix-A.3>
+ #[test]
+ fn example3() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[];
+ const INFO: &[u8] = &[];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64,
+ 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c,
+ 0x29, 0x3c, 0xcb, 0x04,
+ ];
+ const OKM: &[u8] = &[
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
+ 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
+ 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/hpke.rs b/third_party/rust/ohttp/src/nss/hpke.rs
new file mode 100644
index 0000000000..95adef3764
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/hpke.rs
@@ -0,0 +1,341 @@
+use super::{
+ super::hpke::{Aead, Kdf, Kem},
+ err::{sec::SEC_ERROR_INVALID_ARGS, secstatus_to_res, Error},
+ p11::{sys, Item, PrivateKey, PublicKey, Slot, SymKey},
+};
+use crate::err::Res;
+use log::{log_enabled, trace};
+use std::{
+ convert::TryFrom,
+ ops::Deref,
+ os::raw::c_uint,
+ ptr::{addr_of_mut, null, null_mut},
+};
+
+pub use sys::{HpkeAeadId as AeadId, HpkeKdfId as KdfId, HpkeKemId as KemId};
+
+/// Configuration for `Hpke`.
+#[derive(Clone, Copy)]
+pub struct Config {
+ kem: Kem,
+ kdf: Kdf,
+ aead: Aead,
+}
+
+impl Config {
+ pub fn new(kem: Kem, kdf: Kdf, aead: Aead) -> Self {
+ Self { kem, kdf, aead }
+ }
+
+ pub fn kem(self) -> Kem {
+ self.kem
+ }
+
+ pub fn kdf(self) -> Kdf {
+ self.kdf
+ }
+
+ pub fn aead(self) -> Aead {
+ self.aead
+ }
+
+ pub fn supported(self) -> bool {
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_ValidateParameters(
+ KemId::Type::from(u16::from(self.kem)),
+ KdfId::Type::from(u16::from(self.kdf)),
+ AeadId::Type::from(u16::from(self.aead)),
+ )
+ })
+ .is_ok()
+ }
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ kem: Kem::X25519Sha256,
+ kdf: Kdf::HkdfSha256,
+ aead: Aead::Aes128Gcm,
+ }
+ }
+}
+
+pub trait Exporter {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey>;
+}
+
+unsafe fn destroy_hpke_context(cx: *mut sys::HpkeContext) {
+ sys::PK11_HPKE_DestroyContext(cx, sys::PRBool::from(true));
+}
+
+scoped_ptr!(HpkeContext, sys::HpkeContext, destroy_hpke_context);
+
+impl HpkeContext {
+ fn new(config: Config) -> Res<Self> {
+ let ptr = unsafe {
+ sys::PK11_HPKE_NewContext(
+ KemId::Type::from(u16::from(config.kem)),
+ KdfId::Type::from(u16::from(config.kdf)),
+ AeadId::Type::from(u16::from(config.aead)),
+ null_mut(),
+ null(),
+ )
+ };
+ Self::from_ptr(ptr)
+ }
+}
+
+impl Exporter for HpkeContext {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ let mut out: *mut sys::PK11SymKey = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_ExportSecret(
+ self.ptr,
+ &Item::wrap(info),
+ c_uint::try_from(len).unwrap(),
+ &mut out,
+ )
+ })?;
+ SymKey::from_ptr(out)
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeS {
+ context: HpkeContext,
+ config: Config,
+}
+
+impl HpkeS {
+ /// Create a new context that uses the KEM mode for sending.
+ #[allow(clippy::similar_names)]
+ pub fn new(config: Config, pk_r: &mut PublicKey, info: &[u8]) -> Res<Self> {
+ let (sk_e, pk_e) = generate_key_pair(config.kem)?;
+ let context = HpkeContext::new(config)?;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_SetupS(*context, *pk_e, *sk_e, **pk_r, &Item::wrap(info))
+ })?;
+ Ok(Self { context, config })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ /// Get the encapsulated KEM secret.
+ pub fn enc(&self) -> Res<Vec<u8>> {
+ let v = unsafe { sys::PK11_HPKE_GetEncapPubKey(*self.context) };
+ let r = unsafe { v.as_ref() }.ok_or_else(|| Error::from(SEC_ERROR_INVALID_ARGS))?;
+ // This is just an alias, so we can't use `Item`.
+ let len = usize::try_from(r.len).unwrap();
+ let slc = unsafe { std::slice::from_raw_parts(r.data, len) };
+ Ok(Vec::from(slc))
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ let mut out: *mut sys::SECItem = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Seal(*self.context, &Item::wrap(aad), &Item::wrap(pt), &mut out)
+ })?;
+ let v = Item::from_ptr(out)?;
+ Ok(unsafe { v.into_vec() })
+ }
+}
+
+impl Exporter for HpkeS {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ self.context.export(info, len)
+ }
+}
+
+impl Deref for HpkeS {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeR {
+ context: HpkeContext,
+ config: Config,
+}
+
+impl HpkeR {
+ /// Create a new context that uses the KEM mode for sending.
+ #[allow(clippy::similar_names)]
+ pub fn new(
+ config: Config,
+ pk_r: &PublicKey,
+ sk_r: &mut PrivateKey,
+ enc: &[u8],
+ info: &[u8],
+ ) -> Res<Self> {
+ let context = HpkeContext::new(config)?;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_SetupR(
+ *context,
+ **pk_r,
+ **sk_r,
+ &Item::wrap(enc),
+ &Item::wrap(info),
+ )
+ })?;
+ Ok(Self { context, config })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ pub fn decode_public_key(kem: Kem, k: &[u8]) -> Res<PublicKey> {
+ // NSS uses a context for this, but we don't want that, but a dummy one works fine.
+ let context = HpkeContext::new(Config {
+ kem,
+ ..Config::default()
+ })?;
+ let mut ptr: *mut sys::SECKEYPublicKey = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Deserialize(
+ *context,
+ k.as_ptr(),
+ c_uint::try_from(k.len()).unwrap(),
+ &mut ptr,
+ )
+ })?;
+ PublicKey::from_ptr(ptr)
+ }
+
+ pub fn open(&mut self, aad: &[u8], ct: &[u8]) -> Res<Vec<u8>> {
+ let mut out: *mut sys::SECItem = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Open(*self.context, &Item::wrap(aad), &Item::wrap(ct), &mut out)
+ })?;
+ let v = Item::from_ptr(out)?;
+ Ok(unsafe { v.into_vec() })
+ }
+}
+
+impl Exporter for HpkeR {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ self.context.export(info, len)
+ }
+}
+
+impl Deref for HpkeR {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+/// Generate a key pair for the identified KEM.
+pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> {
+ assert_eq!(kem, Kem::X25519Sha256);
+ let slot = Slot::internal()?;
+
+ let oid_data = unsafe { sys::SECOID_FindOIDByTag(sys::SECOidTag::SEC_OID_CURVE25519) };
+ let oid = unsafe { oid_data.as_ref() }.ok_or_else(Error::internal)?;
+ let oid_slc =
+ unsafe { std::slice::from_raw_parts(oid.oid.data, usize::try_from(oid.oid.len).unwrap()) };
+ let mut params: Vec<u8> = Vec::with_capacity(oid_slc.len() + 2);
+ params.push(u8::try_from(sys::SEC_ASN1_OBJECT_ID).unwrap());
+ params.push(u8::try_from(oid.oid.len).unwrap());
+ params.extend_from_slice(oid_slc);
+
+ let mut public_ptr: *mut sys::SECKEYPublicKey = null_mut();
+ let mut wrapped = Item::wrap(&params);
+
+ // Try to make an insensitive key so that we can read the key data for tracing.
+ let insensitive_secret_ptr = if log_enabled!(log::Level::Trace) {
+ unsafe {
+ sys::PK11_GenerateKeyPairWithOpFlags(
+ *slot,
+ sys::CK_MECHANISM_TYPE::from(sys::CKM_EC_KEY_PAIR_GEN),
+ addr_of_mut!(wrapped).cast(),
+ &mut public_ptr,
+ sys::PK11_ATTR_SESSION | sys::PK11_ATTR_INSENSITIVE | sys::PK11_ATTR_PUBLIC,
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ null_mut(),
+ )
+ }
+ } else {
+ null_mut()
+ };
+ assert_eq!(insensitive_secret_ptr.is_null(), public_ptr.is_null());
+ let secret_ptr = if insensitive_secret_ptr.is_null() {
+ unsafe {
+ sys::PK11_GenerateKeyPairWithOpFlags(
+ *slot,
+ sys::CK_MECHANISM_TYPE::from(sys::CKM_EC_KEY_PAIR_GEN),
+ addr_of_mut!(wrapped).cast(),
+ &mut public_ptr,
+ sys::PK11_ATTR_SESSION | sys::PK11_ATTR_SENSITIVE | sys::PK11_ATTR_PRIVATE,
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ null_mut(),
+ )
+ }
+ } else {
+ insensitive_secret_ptr
+ };
+ assert_eq!(secret_ptr.is_null(), public_ptr.is_null());
+ let sk = PrivateKey::from_ptr(secret_ptr)?;
+ let pk = PublicKey::from_ptr(public_ptr)?;
+ trace!("Generated key pair: sk={:?} pk={:?}", sk, pk);
+ Ok((sk, pk))
+}
+
+#[cfg(test)]
+mod test {
+ use super::{generate_key_pair, Config, HpkeR, HpkeS};
+ use crate::{hpke::Aead, init};
+
+ const INFO: &[u8] = b"info";
+ const AAD: &[u8] = b"aad";
+ const PT: &[u8] = b"message";
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ #[test]
+ fn make() {
+ init();
+ let cfg = Config::default();
+ let (mut 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();
+ }
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ fn seal_open(aead: Aead) {
+ // Setup
+ init();
+ let cfg = Config {
+ aead,
+ ..Config::default()
+ };
+ assert!(cfg.supported());
+ let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
+
+ // Send
+ let mut hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
+ let enc = hpke_s.enc().unwrap();
+ 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 pt = hpke_r.open(AAD, &ct).unwrap();
+ assert_eq!(&pt[..], PT);
+ }
+
+ #[test]
+ fn seal_open_gcm() {
+ seal_open(Aead::Aes128Gcm);
+ }
+
+ #[test]
+ fn seal_open_chacha() {
+ seal_open(Aead::ChaCha20Poly1305);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/mod.rs b/third_party/rust/ohttp/src/nss/mod.rs
new file mode 100644
index 0000000000..7040e18664
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/mod.rs
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod err;
+#[macro_use]
+mod p11;
+pub mod aead;
+pub mod hkdf;
+pub mod hpke;
+
+pub use self::p11::{random, PrivateKey, PublicKey, SymKey};
+use err::secstatus_to_res;
+pub use err::Error;
+use lazy_static::lazy_static;
+use std::ptr::null;
+
+#[allow(clippy::pedantic, non_upper_case_globals, clippy::upper_case_acronyms)]
+mod nss_init {
+ include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+use nss_init::SECStatus;
+#[allow(non_upper_case_globals)]
+const SECSuccess: SECStatus = nss_init::_SECStatus_SECSuccess;
+#[cfg(test)]
+#[allow(non_upper_case_globals)]
+const SECFailure: SECStatus = nss_init::_SECStatus_SECFailure;
+
+#[derive(PartialEq, Eq)]
+enum NssLoaded {
+ External,
+ NoDb,
+}
+
+impl Drop for NssLoaded {
+ fn drop(&mut self) {
+ if *self == Self::NoDb {
+ unsafe {
+ secstatus_to_res(nss_init::NSS_Shutdown()).expect("NSS Shutdown failed");
+ }
+ }
+ }
+}
+
+lazy_static! {
+ static ref INITIALIZED: NssLoaded = {
+ if already_initialized() {
+ return NssLoaded::External;
+ }
+
+ secstatus_to_res(unsafe { nss_init::NSS_NoDB_Init(null()) }).expect("NSS_NoDB_Init failed");
+
+ NssLoaded::NoDb
+ };
+}
+
+fn already_initialized() -> bool {
+ unsafe { nss_init::NSS_IsInitialized() != 0 }
+}
+
+/// Initialize NSS. This only executes the initialization routines once.
+pub fn init() {
+ lazy_static::initialize(&INITIALIZED);
+}
diff --git a/third_party/rust/ohttp/src/nss/p11.rs b/third_party/rust/ohttp/src/nss/p11.rs
new file mode 100644
index 0000000000..7a1bff5b4a
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/p11.rs
@@ -0,0 +1,294 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::err::{secstatus_to_res, Error};
+use crate::err::Res;
+use std::{
+ convert::TryFrom,
+ marker::PhantomData,
+ mem,
+ os::raw::{c_int, c_uint},
+ ptr::null_mut,
+};
+
+#[allow(
+ clippy::pedantic,
+ clippy::upper_case_acronyms,
+ dead_code,
+ deref_nullptr,
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals
+)]
+pub mod sys {
+ include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
+}
+
+use sys::{
+ PK11ObjectType, PK11SlotInfo, PK11SymKey, PK11_ExtractKeyValue, PK11_FreeSlot, PK11_FreeSymKey,
+ PK11_GenerateRandom, PK11_GetInternalSlot, PK11_GetKeyData, PK11_ReadRawAttribute,
+ PK11_ReferenceSymKey, PRBool, SECITEM_FreeItem, SECItem, SECItemType, SECKEYPrivateKey,
+ SECKEYPublicKey, SECKEY_DestroyPrivateKey, SECKEY_DestroyPublicKey, CKA_VALUE,
+ CK_ATTRIBUTE_TYPE,
+};
+
+macro_rules! scoped_ptr {
+ ($scoped:ident, $target:ty, $dtor:path) => {
+ pub struct $scoped {
+ ptr: *mut $target,
+ }
+
+ impl $scoped {
+ pub fn from_ptr(ptr: *mut $target) -> Result<Self, crate::err::Error> {
+ if ptr.is_null() {
+ Err(crate::nss::err::Error::last())
+ } else {
+ Ok(Self { ptr })
+ }
+ }
+ }
+
+ impl std::ops::Deref for $scoped {
+ type Target = *mut $target;
+ #[must_use]
+ fn deref(&self) -> &*mut $target {
+ &self.ptr
+ }
+ }
+
+ impl std::ops::DerefMut for $scoped {
+ fn deref_mut(&mut self) -> &mut *mut $target {
+ &mut self.ptr
+ }
+ }
+
+ impl Drop for $scoped {
+ fn drop(&mut self) {
+ let _ = unsafe { $dtor(self.ptr) };
+ }
+ }
+ };
+}
+
+scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey);
+
+impl PrivateKey {
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut key_item = SECItem {
+ type_: SECItemType::siBuffer,
+ data: null_mut(),
+ len: 0,
+ };
+ secstatus_to_res(unsafe {
+ PK11_ReadRawAttribute(
+ PK11ObjectType::PK11_TypePrivKey,
+ (**self).cast(),
+ CK_ATTRIBUTE_TYPE::from(CKA_VALUE),
+ &mut key_item,
+ )
+ })?;
+ let slc = unsafe {
+ std::slice::from_raw_parts(key_item.data, usize::try_from(key_item.len).unwrap())
+ };
+ let key = Vec::from(slc);
+ // The data that `key_item` refers to needs to be freed, but we can't
+ // use the scoped `Item` implementation. This is OK as long as nothing
+ // panics between `PK11_ReadRawAttribute` succeeding and here.
+ unsafe {
+ SECITEM_FreeItem(&mut key_item, PRBool::from(false));
+ }
+ Ok(key)
+ }
+}
+unsafe impl Send for PrivateKey {}
+
+impl Clone for PrivateKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { sys::SECKEY_CopyPrivateKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for PrivateKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PrivateKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PrivateKey")
+ }
+ }
+}
+
+scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey);
+
+impl PublicKey {
+ /// Get the HPKE serialization of the public key.
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut buf = vec![0; 100];
+ let mut len: c_uint = 0;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Serialize(
+ **self,
+ buf.as_mut_ptr(),
+ &mut len,
+ c_uint::try_from(buf.len()).unwrap(),
+ )
+ })?;
+ buf.truncate(usize::try_from(len).unwrap());
+ Ok(buf)
+ }
+}
+
+unsafe impl Send for PublicKey {}
+
+impl Clone for PublicKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { sys::SECKEY_CopyPublicKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for PublicKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PublicKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PublicKey")
+ }
+ }
+}
+
+scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
+
+impl Slot {
+ pub(crate) fn internal() -> Res<Self> {
+ let p = unsafe { PK11_GetInternalSlot() };
+ Slot::from_ptr(p)
+ }
+}
+
+scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
+
+impl SymKey {
+ /// You really don't want to use this.
+ ///
+ /// # Errors
+ /// Some keys cannot be inspected in this way.
+ /// Also, internal errors in case of failures in NSS.
+ pub fn key_data(&self) -> Res<&[u8]> {
+ secstatus_to_res(unsafe { PK11_ExtractKeyValue(self.ptr) })?;
+
+ let key_item = unsafe { PK11_GetKeyData(self.ptr) };
+ // This is accessing a value attached to the key, so we can treat this as a borrow.
+ match unsafe { key_item.as_mut() } {
+ None => Err(Error::last()),
+ Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }),
+ }
+ }
+}
+
+impl Clone for SymKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { PK11_ReferenceSymKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for SymKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "SymKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque SymKey")
+ }
+ }
+}
+
+unsafe impl Send for SymKey {}
+
+/// Generate a randomized buffer.
+#[must_use]
+pub fn random(size: usize) -> Vec<u8> {
+ let mut buf = vec![0; size];
+ secstatus_to_res(unsafe {
+ PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap())
+ })
+ .unwrap();
+ buf
+}
+
+pub(crate) struct ParamItem<'a, T: 'a> {
+ item: SECItem,
+ marker: PhantomData<&'a T>,
+}
+
+impl<'a, T: Sized + 'a> ParamItem<'a, T> {
+ pub fn new(v: &'a mut T) -> Self {
+ let item = SECItem {
+ type_: SECItemType::siBuffer,
+ data: (v as *mut T).cast::<u8>(),
+ len: c_uint::try_from(mem::size_of::<T>()).unwrap(),
+ };
+ Self {
+ item,
+ marker: PhantomData::default(),
+ }
+ }
+
+ pub fn ptr(&mut self) -> *mut SECItem {
+ std::ptr::addr_of_mut!(self.item)
+ }
+}
+
+unsafe fn destroy_secitem(item: *mut SECItem) {
+ SECITEM_FreeItem(item, PRBool::from(true));
+}
+scoped_ptr!(Item, SECItem, destroy_secitem);
+
+impl Item {
+ /// Create a wrapper for a slice of this object.
+ /// Creating this object is technically safe, but using it is extremely dangerous.
+ /// Minimally, it can only be passed as a `const SECItem*` argument to functions.
+ pub(crate) fn wrap(buf: &[u8]) -> SECItem {
+ SECItem {
+ type_: SECItemType::siBuffer,
+ data: buf.as_ptr() as *mut u8,
+ len: c_uint::try_from(buf.len()).unwrap(),
+ }
+ }
+
+ /// This dereferences the pointer held by the item and makes a copy of the
+ /// content that is referenced there.
+ ///
+ /// # Safety
+ /// This dereferences two pointers. It doesn't get much less safe.
+ pub(crate) unsafe fn into_vec(self) -> Vec<u8> {
+ let b = self.ptr.as_ref().unwrap();
+ // Sanity check the type, as some types don't count bytes in `Item::len`.
+ assert_eq!(b.type_, SECItemType::siBuffer);
+ let slc = std::slice::from_raw_parts(b.data, usize::try_from(b.len).unwrap());
+ Vec::from(slc)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::random;
+ use crate::init;
+
+ #[test]
+ fn randomness() {
+ init();
+ // If this ever fails, there is either a bug, or it's time to buy a lottery ticket.
+ assert_ne!(random(16), random(16));
+ }
+}
diff --git a/third_party/rust/ohttp/src/rand.rs b/third_party/rust/ohttp/src/rand.rs
new file mode 100644
index 0000000000..381b83f86a
--- /dev/null
+++ b/third_party/rust/ohttp/src/rand.rs
@@ -0,0 +1,15 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use ::rand::{thread_rng, RngCore};
+
+#[must_use]
+pub fn random(size: usize) -> Vec<u8> {
+ let mut rng = thread_rng();
+ let mut buf = vec![0; size];
+ rng.fill_bytes(&mut buf);
+ buf
+}
diff --git a/third_party/rust/ohttp/src/rh/aead.rs b/third_party/rust/ohttp/src/rh/aead.rs
new file mode 100644
index 0000000000..76a7e4443c
--- /dev/null
+++ b/third_party/rust/ohttp/src/rh/aead.rs
@@ -0,0 +1,257 @@
+#![allow(dead_code)] // TODO: remove
+
+use super::SymKey;
+use crate::{err::Res, hpke::Aead as AeadId};
+use aead::{AeadMut, Key, NewAead, Nonce, Payload};
+use aes_gcm::{Aes128Gcm, Aes256Gcm};
+use chacha20poly1305::ChaCha20Poly1305;
+use std::convert::TryFrom;
+
+/// All the nonces are the same length. Exploit that.
+pub const NONCE_LEN: usize = 12;
+const COUNTER_LEN: usize = 8;
+const TAG_LEN: usize = 16;
+
+type SequenceNumber = u64;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Mode {
+ Encrypt,
+ Decrypt,
+}
+
+enum AeadEngine {
+ Aes128Gcm(Box<Aes128Gcm>),
+ Aes256Gcm(Box<Aes256Gcm>),
+ ChaCha20Poly1305(Box<ChaCha20Poly1305>),
+}
+
+// Dispatch functions; this just shows how janky that this sort of abstraction can be.
+// If this grows too much, this is fairly clearly responsive to using a macro.
+impl AeadEngine {
+ fn encrypt(&mut self, nonce: &[u8], pt: Payload) -> Res<Vec<u8>> {
+ let tag = match self {
+ Self::Aes128Gcm(e) => e.encrypt(Nonce::<Aes128Gcm>::from_slice(nonce), pt)?,
+ Self::Aes256Gcm(e) => e.encrypt(Nonce::<Aes256Gcm>::from_slice(nonce), pt)?,
+ Self::ChaCha20Poly1305(e) => {
+ e.encrypt(Nonce::<ChaCha20Poly1305>::from_slice(nonce), pt)?
+ }
+ };
+ Ok(tag)
+ }
+ fn decrypt(&mut self, nonce: &[u8], pt: Payload) -> Res<Vec<u8>> {
+ let tag = match self {
+ Self::Aes128Gcm(e) => e.decrypt(Nonce::<Aes128Gcm>::from_slice(nonce), pt)?,
+ Self::Aes256Gcm(e) => e.decrypt(Nonce::<Aes256Gcm>::from_slice(nonce), pt)?,
+ Self::ChaCha20Poly1305(e) => {
+ e.decrypt(Nonce::<ChaCha20Poly1305>::from_slice(nonce), pt)?
+ }
+ };
+ Ok(tag)
+ }
+}
+
+/// A switch-hitting AEAD that uses a selected primitive.
+pub struct Aead {
+ mode: Mode,
+ aead: AeadEngine,
+ nonce_base: [u8; NONCE_LEN],
+ seq: SequenceNumber,
+}
+
+impl Aead {
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn new(
+ mode: Mode,
+ algorithm: AeadId,
+ key: &SymKey,
+ nonce_base: [u8; NONCE_LEN],
+ ) -> Res<Self> {
+ let aead = match algorithm {
+ AeadId::Aes128Gcm => AeadEngine::Aes128Gcm(Box::new(Aes128Gcm::new(
+ Key::<Aes128Gcm>::from_slice(key.as_ref()),
+ ))),
+ AeadId::Aes256Gcm => AeadEngine::Aes256Gcm(Box::new(Aes256Gcm::new(
+ Key::<Aes256Gcm>::from_slice(key.as_ref()),
+ ))),
+ AeadId::ChaCha20Poly1305 => AeadEngine::ChaCha20Poly1305(Box::new(
+ ChaCha20Poly1305::new(Key::<ChaCha20Poly1305>::from_slice(key.as_ref())),
+ )),
+ };
+ Ok(Self {
+ mode,
+ aead,
+ nonce_base,
+ seq: 0,
+ })
+ }
+
+ #[cfg(test)]
+ #[allow(clippy::unnecessary_wraps)]
+ fn import_key(_alg: AeadId, k: &[u8]) -> Res<SymKey> {
+ Ok(SymKey::from(k))
+ }
+
+ fn nonce(&self, seq: SequenceNumber) -> Vec<u8> {
+ let mut nonce = Vec::from(self.nonce_base);
+ for (i, n) in nonce.iter_mut().rev().take(COUNTER_LEN).enumerate() {
+ *n ^= u8::try_from((seq >> (8 * i)) & 0xff).unwrap();
+ }
+ nonce
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ assert_eq!(self.mode, Mode::Encrypt);
+ // 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 })?;
+ 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 })?;
+ Ok(pt)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ super::super::{hpke::Aead as AeadId, init},
+ Aead, Mode, SequenceNumber, NONCE_LEN,
+ };
+
+ /// Check that the first invocation of encryption matches expected values.
+ /// Also check decryption of the same.
+ fn check0(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ init();
+ let k = Aead::import_key(algorithm, key).unwrap();
+
+ let mut enc = Aead::new(Mode::Encrypt, algorithm, &k, *nonce).unwrap();
+ let ciphertext = enc.seal(aad, pt).unwrap();
+ assert_eq!(&ciphertext[..], ct);
+
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, 0, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ fn decrypt(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ seq: SequenceNumber,
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ let k = Aead::import_key(algorithm, key).unwrap();
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, seq, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ /// This tests the AEAD in QUIC in combination with the HKDF code.
+ /// This is an AEAD-only example.
+ #[test]
+ fn quic_retry() {
+ const KEY: &[u8] = &[
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68,
+ 0xc8, 0x4e,
+ ];
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
+ ];
+ const AAD: &[u8] = &[
+ 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0xff, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65,
+ 0x6e,
+ ];
+ const CT: &[u8] = &[
+ 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58, 0xfb, 0x3f, 0x0f, 0x24,
+ 0x96, 0xba,
+ ];
+ check0(AeadId::Aes128Gcm, KEY, NONCE, AAD, &[], CT);
+ }
+
+ #[test]
+ fn quic_server_initial() {
+ const ALG: AeadId = AeadId::Aes128Gcm;
+ const KEY: &[u8] = &[
+ 0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3, 0x79, 0xb6, 0x06,
+ 0x7e, 0x37,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3e,
+ ];
+ // Note that this integrates the sequence number of 1 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3f,
+ ];
+ const AAD: &[u8] = &[
+ 0xc1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62,
+ 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01,
+ ];
+ const PT: &[u8] = &[
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03,
+ 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78,
+ 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43,
+ 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00,
+ 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0,
+ 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83,
+ 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03,
+ 0x04,
+ ];
+ const CT: &[u8] = &[
+ 0x5a, 0x48, 0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58, 0x16,
+ 0xb6, 0x39, 0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75, 0x54, 0x78, 0x0b, 0xb3,
+ 0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c, 0xf7, 0x3c, 0x3e, 0xc2, 0x49, 0x3a, 0x18,
+ 0x39, 0xb3, 0xdb, 0xcb, 0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7, 0x68, 0x4d, 0xf3, 0x54,
+ 0x8e, 0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73, 0xcc, 0x3f, 0x3b, 0xde, 0xd7, 0x4b,
+ 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84, 0x02, 0x2f, 0x8e, 0xf4, 0xcd, 0xd9, 0x37, 0x95,
+ 0xd7, 0x7d, 0x06, 0xed, 0xbb, 0x7a, 0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd,
+ 0xca, 0x3d, 0x20, 0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,
+ 0xd0, 0x74, 0xee,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ decrypt(ALG, KEY, NONCE_BASE, 1, AAD, PT, CT);
+ }
+
+ #[test]
+ fn quic_chacha() {
+ const ALG: AeadId = AeadId::ChaCha20Poly1305;
+ const KEY: &[u8] = &[
+ 0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20, 0x94, 0xf6, 0x9c,
+ 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88, 0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79,
+ 0xfb, 0x23, 0xe1, 0xc8,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1, 0x44,
+ ];
+ // Note that this integrates the sequence number of 654360564 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x6d, 0x41, 0x7e, 0xb0,
+ ];
+ const AAD: &[u8] = &[0x42, 0x00, 0xbf, 0xf4];
+ const PT: &[u8] = &[0x01];
+ const CT: &[u8] = &[
+ 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2,
+ 0x5a, 0x5b, 0xfb,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ // Now use the real nonce and sequence number from the example.
+ decrypt(ALG, KEY, NONCE_BASE, 654_360_564, AAD, PT, CT);
+ }
+}
diff --git a/third_party/rust/ohttp/src/rh/hkdf.rs b/third_party/rust/ohttp/src/rh/hkdf.rs
new file mode 100644
index 0000000000..aeb3a8d6e8
--- /dev/null
+++ b/third_party/rust/ohttp/src/rh/hkdf.rs
@@ -0,0 +1,224 @@
+#![allow(dead_code)] // TODO: remove
+
+use super::SymKey;
+use crate::{
+ err::{Error, Res},
+ hpke::{Aead, Kdf},
+};
+use hkdf::Hkdf as HkdfImpl;
+use log::trace;
+use sha2::{Sha256, Sha384, Sha512};
+
+#[derive(Clone, Copy)]
+pub enum KeyMechanism {
+ Aead(Aead),
+ #[allow(dead_code)] // We don't use this one.
+ Hkdf,
+}
+
+impl KeyMechanism {
+ fn len(self) -> usize {
+ match self {
+ Self::Aead(a) => a.n_k(),
+ Self::Hkdf => 0, // Let the underlying module decide.
+ }
+ }
+}
+
+pub enum Hkdf {
+ Sha256,
+ Sha384,
+ Sha512,
+}
+
+impl Hkdf {
+ pub fn new(kdf: Kdf) -> Self {
+ match kdf {
+ Kdf::HkdfSha256 => Self::Sha256,
+ Kdf::HkdfSha384 => Self::Sha384,
+ Kdf::HkdfSha512 => Self::Sha512,
+ }
+ }
+
+ #[cfg(test)]
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn import_ikm(ikm: &[u8]) -> Res<SymKey> {
+ Ok(SymKey::from(ikm))
+ }
+
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn extract(&self, salt: &[u8], ikm: &SymKey) -> Res<SymKey> {
+ let prk = match self {
+ Self::Sha256 => {
+ SymKey::from(HkdfImpl::<Sha256>::extract(Some(salt), &ikm.0).0.as_slice())
+ }
+ Self::Sha384 => {
+ SymKey::from(HkdfImpl::<Sha384>::extract(Some(salt), &ikm.0).0.as_slice())
+ }
+ Self::Sha512 => {
+ SymKey::from(HkdfImpl::<Sha512>::extract(Some(salt), &ikm.0).0.as_slice())
+ }
+ };
+ trace!(
+ "HKDF extract: salt={} ikm={:?} prk={:?}",
+ hex::encode(salt),
+ ikm,
+ prk
+ );
+ Ok(prk)
+ }
+
+ pub fn expand_key(&self, prk: &SymKey, info: &[u8], key_mech: KeyMechanism) -> Res<SymKey> {
+ let okm = SymKey::from(self.expand_data(prk, info, key_mech.len())?);
+ trace!(
+ "HKDF expand_key: prk={:?} info={} okm={:?}",
+ prk,
+ hex::encode(info),
+ okm,
+ );
+ Ok(okm)
+ }
+
+ pub fn expand_data(&self, prk: &SymKey, info: &[u8], len: usize) -> Res<Vec<u8>> {
+ let mut okm = vec![0; len];
+ match self {
+ Self::Sha256 => {
+ let h = HkdfImpl::<Sha256>::from_prk(&prk.0).map_err(|_| Error::Internal)?;
+ h.expand(info, &mut okm).map_err(|_| Error::Internal)?;
+ }
+ Self::Sha384 => {
+ let h = HkdfImpl::<Sha384>::from_prk(&prk.0).map_err(|_| Error::Internal)?;
+ h.expand(info, &mut okm).map_err(|_| Error::Internal)?;
+ }
+ Self::Sha512 => {
+ let h = HkdfImpl::<Sha512>::from_prk(&prk.0).map_err(|_| Error::Internal)?;
+ h.expand(info, &mut okm).map_err(|_| Error::Internal)?;
+ }
+ }
+ trace!(
+ "HKDF expand_data: prk={:?} info={} len={} okm={:?}",
+ prk,
+ hex::encode(info),
+ len,
+ hex::encode(&okm),
+ );
+ Ok(okm)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{super::super::hpke::Kdf, Hkdf};
+ use crate::init;
+
+ fn sha256_example(
+ ikm: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ l: usize,
+ expected_prk: &[u8],
+ expected_okm: &[u8],
+ ) {
+ init();
+ let hkdf = Hkdf::new(Kdf::HkdfSha256);
+ let k_ikm = Hkdf::import_ikm(ikm).unwrap();
+ let prk = hkdf.extract(salt, &k_ikm).unwrap();
+ let prk_data = prk.key_data().unwrap();
+ assert_eq!(prk_data, expected_prk);
+
+ let out = hkdf.expand_data(&prk, info, l).unwrap();
+ assert_eq!(&out[..], expected_okm);
+ }
+
+ /// Example 1 from <https://tools.ietf.org/html/rfc5869#appendix-A.1>
+ #[test]
+ fn example1() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ ];
+ const INFO: &[u8] = &[0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b,
+ 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a,
+ 0xd7, 0xc2, 0xb3, 0xe5,
+ ];
+ const OKM: &[u8] = &[
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
+ 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
+ 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 2 from <https://tools.ietf.org/html/rfc5869#appendix-A.2>
+ #[test]
+ fn example2() {
+ const IKM: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ];
+ const SALT: &[u8] = &[
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+ 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
+ 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ ];
+ const INFO: &[u8] = &[
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd,
+ 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ ];
+ const L: usize = 82;
+ const PRK: &[u8] = &[
+ 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35,
+ 0xb4, 0x5c, 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01, 0x4a, 0x19, 0x3f, 0x40,
+ 0xc1, 0x5f, 0xc2, 0x44,
+ ];
+ const OKM: &[u8] = &[
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
+ 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
+ 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
+ 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+ 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
+ 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 3 from <https://tools.ietf.org/html/rfc5869#appendix-A.3>
+ #[test]
+ fn example3() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[];
+ const INFO: &[u8] = &[];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64,
+ 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c,
+ 0x29, 0x3c, 0xcb, 0x04,
+ ];
+ const OKM: &[u8] = &[
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
+ 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
+ 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+}
diff --git a/third_party/rust/ohttp/src/rh/hpke.rs b/third_party/rust/ohttp/src/rh/hpke.rs
new file mode 100644
index 0000000000..2ffa08f709
--- /dev/null
+++ b/third_party/rust/ohttp/src/rh/hpke.rs
@@ -0,0 +1,508 @@
+use super::SymKey;
+use crate::{
+ hpke::{Aead, Kdf, Kem},
+ Error, Res,
+};
+use ::hpke::{
+ aead::{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,
+};
+use ::rand::thread_rng;
+use log::trace;
+use std::ops::Deref;
+
+/// Configuration for `Hpke`.
+#[derive(Clone, Copy)]
+pub struct Config {
+ kem: Kem,
+ kdf: Kdf,
+ aead: Aead,
+}
+
+impl Config {
+ pub fn new(kem: Kem, kdf: Kdf, aead: Aead) -> Self {
+ Self { kem, kdf, aead }
+ }
+
+ pub fn kem(self) -> Kem {
+ self.kem
+ }
+
+ pub fn kdf(self) -> Kdf {
+ self.kdf
+ }
+
+ pub fn aead(self) -> Aead {
+ self.aead
+ }
+
+ pub fn supported(self) -> bool {
+ // TODO support more options
+ self.kdf == Kdf::HkdfSha256 && matches!(self.aead, Aead::Aes128Gcm | Aead::ChaCha20Poly1305)
+ }
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ kem: Kem::X25519Sha256,
+ kdf: Kdf::HkdfSha256,
+ aead: Aead::Aes128Gcm,
+ }
+ }
+}
+
+pub enum PublicKey {
+ X25519(<<X25519HkdfSha256 as HpkeKem>::Kex as KeyExchange>::PublicKey),
+}
+
+impl PublicKey {
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ Ok(match self {
+ Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
+ })
+ }
+}
+
+impl std::fmt::Debug for PublicKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PublicKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PublicKey")
+ }
+ }
+}
+
+pub enum PrivateKey {
+ X25519(<<X25519HkdfSha256 as HpkeKem>::Kex as KeyExchange>::PrivateKey),
+}
+
+impl PrivateKey {
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ Ok(match self {
+ Self::X25519(k) => Vec::from(k.to_bytes().as_slice()),
+ })
+ }
+}
+
+impl std::fmt::Debug for PrivateKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PrivateKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PrivateKey")
+ }
+ }
+}
+
+// TODO: Use macros here. To do that, we needs concat_ident!(), but it's not ready.
+// This is what a macro that uses concat_ident!() might produce, written out in full.
+enum SenderContextX25519HkdfSha256HkdfSha256 {
+ AesGcm128(Box<AeadCtxS<AesGcm128, HkdfSha256, X25519HkdfSha256>>),
+ ChaCha20Poly1305(Box<AeadCtxS<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>>),
+}
+
+enum SenderContextX25519HkdfSha256 {
+ HkdfSha256(SenderContextX25519HkdfSha256HkdfSha256),
+}
+
+enum SenderContext {
+ X25519HkdfSha256(SenderContextX25519HkdfSha256),
+}
+
+impl SenderContext {
+ fn seal(&mut self, plaintext: &mut [u8], aad: &[u8]) -> Res<Vec<u8>> {
+ Ok(match self {
+ Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
+ SenderContextX25519HkdfSha256HkdfSha256::AesGcm128(context),
+ )) => {
+ let tag = context.seal(plaintext, aad)?;
+ Vec::from(tag.to_bytes().as_slice())
+ }
+ Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
+ SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
+ )) => {
+ let tag = context.seal(plaintext, aad)?;
+ Vec::from(tag.to_bytes().as_slice())
+ }
+ })
+ }
+
+ fn export(&self, info: &[u8], out_buf: &mut [u8]) -> Res<()> {
+ match self {
+ Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
+ SenderContextX25519HkdfSha256HkdfSha256::AesGcm128(context),
+ )) => {
+ context.export(info, out_buf)?;
+ }
+ Self::X25519HkdfSha256(SenderContextX25519HkdfSha256::HkdfSha256(
+ SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
+ )) => {
+ context.export(info, out_buf)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub trait Exporter {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey>;
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeS {
+ context: SenderContext,
+ enc: Vec<u8>,
+ config: Config,
+}
+
+impl HpkeS {
+ /// Create a new context that uses the KEM mode for sending.
+ pub fn new(config: Config, pk_r: &mut PublicKey, info: &[u8]) -> Res<Self> {
+ let mut csprng = thread_rng();
+
+ macro_rules! dispatch_hpkes_new {
+ {
+ ($c:expr, $pk:expr, $csprng:expr): [$({
+ $kemid:path => $kem:path,
+ $kdfid:path => $kdf:path,
+ $aeadid:path => $aead:path,
+ $pke:path, $ctxt1:path, $ctxt2:path, $ctxt3:path $(,)?
+ }),* $(,)?]
+ } => {
+ match ($c, $pk) {
+ $(
+ (
+ Config {
+ kem: $kemid,
+ kdf: $kdfid,
+ aead: $aeadid,
+ },
+ $pke(pk_r),
+ ) => {
+ let (enc, context) = setup_sender::<$aead, $kdf, $kem, _>(
+ &OpModeS::Base,
+ pk_r,
+ info,
+ $csprng,
+ )?;
+ ($ctxt1($ctxt2($ctxt3(Box::new(context)))), enc)
+ }
+ )*
+ _ => return Err(Error::InvalidKeyType),
+ }
+ };
+ }
+ let (context, enc) = dispatch_hpkes_new! { (config, pk_r, &mut csprng): [
+ {
+ Kem::X25519Sha256 => X25519HkdfSha256,
+ Kdf::HkdfSha256 => HkdfSha256,
+ Aead::Aes128Gcm => AesGcm128,
+ PublicKey::X25519,
+ SenderContext::X25519HkdfSha256,
+ SenderContextX25519HkdfSha256::HkdfSha256,
+ SenderContextX25519HkdfSha256HkdfSha256::AesGcm128,
+ },
+ {
+ Kem::X25519Sha256 => X25519HkdfSha256,
+ Kdf::HkdfSha256 => HkdfSha256,
+ Aead::ChaCha20Poly1305 => ChaCha20Poly1305,
+ PublicKey::X25519,
+ SenderContext::X25519HkdfSha256,
+ SenderContextX25519HkdfSha256::HkdfSha256,
+ SenderContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305,
+ },
+ ]};
+ let enc = Vec::from(enc.to_bytes().as_slice());
+ Ok(Self {
+ context,
+ enc,
+ config,
+ })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ /// Get the encapsulated KEM secret.
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn enc(&self) -> Res<Vec<u8>> {
+ Ok(self.enc.clone())
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ let mut buf = pt.to_owned();
+ let mut tag = self.context.seal(&mut buf, aad)?;
+ buf.append(&mut tag);
+ Ok(buf)
+ }
+}
+
+impl Exporter for HpkeS {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ let mut buf = vec![0; len];
+ self.context.export(info, &mut buf)?;
+ Ok(SymKey::from(buf))
+ }
+}
+
+impl Deref for HpkeS {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+enum ReceiverContextX25519HkdfSha256HkdfSha256 {
+ AesGcm128(Box<AeadCtxR<AesGcm128, HkdfSha256, X25519HkdfSha256>>),
+ ChaCha20Poly1305(Box<AeadCtxR<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>>),
+}
+
+enum ReceiverContextX25519HkdfSha256 {
+ HkdfSha256(ReceiverContextX25519HkdfSha256HkdfSha256),
+}
+
+enum ReceiverContext {
+ X25519HkdfSha256(ReceiverContextX25519HkdfSha256),
+}
+
+impl ReceiverContext {
+ fn open<'a>(&mut self, ciphertext: &'a mut [u8], aad: &[u8]) -> Res<&'a [u8]> {
+ Ok(match self {
+ Self::X25519HkdfSha256(ReceiverContextX25519HkdfSha256::HkdfSha256(
+ ReceiverContextX25519HkdfSha256HkdfSha256::AesGcm128(context),
+ )) => {
+ if ciphertext.len() < AeadTag::<AesGcm128>::size() {
+ return Err(Error::Truncated);
+ }
+ let (ct, tag) =
+ ciphertext.split_at_mut(ciphertext.len() - AeadTag::<AesGcm128>::size());
+ let tag = AeadTag::<AesGcm128>::from_bytes(tag)?;
+ context.open(ct, aad, &tag)?;
+ ct
+ }
+ Self::X25519HkdfSha256(ReceiverContextX25519HkdfSha256::HkdfSha256(
+ ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
+ )) => {
+ if ciphertext.len() < AeadTag::<ChaCha20Poly1305>::size() {
+ return Err(Error::Truncated);
+ }
+ let (ct, tag) =
+ ciphertext.split_at_mut(ciphertext.len() - AeadTag::<ChaCha20Poly1305>::size());
+ let tag = AeadTag::<ChaCha20Poly1305>::from_bytes(tag)?;
+ context.open(ct, aad, &tag)?;
+ ct
+ }
+ })
+ }
+
+ fn export(&self, info: &[u8], out_buf: &mut [u8]) -> Res<()> {
+ match self {
+ Self::X25519HkdfSha256(ReceiverContextX25519HkdfSha256::HkdfSha256(
+ ReceiverContextX25519HkdfSha256HkdfSha256::AesGcm128(context),
+ )) => {
+ context.export(info, out_buf)?;
+ }
+ Self::X25519HkdfSha256(ReceiverContextX25519HkdfSha256::HkdfSha256(
+ ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305(context),
+ )) => {
+ context.export(info, out_buf)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeR {
+ context: ReceiverContext,
+ config: Config,
+}
+
+impl HpkeR {
+ /// Create a new context that uses the KEM mode for sending.
+ #[allow(clippy::similar_names)]
+ pub fn new(
+ config: Config,
+ _pk_r: &PublicKey,
+ sk_r: &mut PrivateKey,
+ enc: &[u8],
+ info: &[u8],
+ ) -> Res<Self> {
+ macro_rules! dispatch_hpker_new {
+ {
+ ($c:ident, $sk:ident): [$({
+ $kemid:path => $kem:path,
+ $kdfid:path => $kdf:path,
+ $aeadid:path => $aead:path,
+ $ske:path, $ctxt1:path, $ctxt2:path, $ctxt3:path $(,)?
+ }),* $(,)?]
+ } => {
+ match ($c, $sk) {
+ $(
+ (
+ Config {
+ kem: $kemid,
+ kdf: $kdfid,
+ aead: $aeadid,
+ },
+ $ske(sk_r),
+ ) => {
+ let enc = EncappedKey::from_bytes(enc)?;
+ let context = setup_receiver::<$aead, $kdf, $kem>(
+ &OpModeR::Base,
+ sk_r,
+ &enc,
+ info,
+ )?;
+ $ctxt1($ctxt2($ctxt3(Box::new(context))))
+ }
+ )*
+ _ => return Err(Error::InvalidKeyType),
+ }
+ };
+ }
+ let context = dispatch_hpker_new! {(config, sk_r): [
+ {
+ Kem::X25519Sha256 => X25519HkdfSha256,
+ Kdf::HkdfSha256 => HkdfSha256,
+ Aead::Aes128Gcm => AesGcm128,
+ PrivateKey::X25519,
+ ReceiverContext::X25519HkdfSha256,
+ ReceiverContextX25519HkdfSha256::HkdfSha256,
+ ReceiverContextX25519HkdfSha256HkdfSha256::AesGcm128,
+ },
+ {
+ Kem::X25519Sha256 => X25519HkdfSha256,
+ Kdf::HkdfSha256 => HkdfSha256,
+ Aead::ChaCha20Poly1305 => ChaCha20Poly1305,
+ PrivateKey::X25519,
+ ReceiverContext::X25519HkdfSha256,
+ ReceiverContextX25519HkdfSha256::HkdfSha256,
+ ReceiverContextX25519HkdfSha256HkdfSha256::ChaCha20Poly1305,
+ },
+ ]};
+ Ok(Self { context, config })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ 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)?)
+ }
+ })
+ }
+
+ pub fn open(&mut self, aad: &[u8], ct: &[u8]) -> Res<Vec<u8>> {
+ let mut buf = ct.to_owned();
+ let pt_len = self.context.open(&mut buf, aad)?.len();
+ buf.truncate(pt_len);
+ Ok(buf)
+ }
+}
+
+impl Exporter for HpkeR {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ let mut buf = vec![0; len];
+ self.context.export(info, &mut buf)?;
+ Ok(SymKey::from(buf))
+ }
+}
+
+impl Deref for HpkeR {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+/// Generate a key pair for the identified KEM.
+#[allow(clippy::unnecessary_wraps)]
+pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> {
+ let mut csprng = thread_rng();
+ let (sk, pk) = match kem {
+ Kem::X25519Sha256 => {
+ let (sk, pk) = X25519HkdfSha256::gen_keypair(&mut csprng);
+ (PrivateKey::X25519(sk), PublicKey::X25519(pk))
+ }
+ };
+ trace!("Generated key pair: sk={:?} pk={:?}", sk, pk);
+ Ok((sk, pk))
+}
+
+#[allow(clippy::unnecessary_wraps)]
+pub fn derive_key_pair(kem: Kem, ikm: &[u8]) -> Res<(PrivateKey, PublicKey)> {
+ let (sk, pk) = match kem {
+ Kem::X25519Sha256 => {
+ let (sk, pk) = X25519HkdfSha256::derive_keypair(ikm);
+ (PrivateKey::X25519(sk), PublicKey::X25519(pk))
+ }
+ };
+ trace!("Derived key pair: sk={:?} pk={:?}", sk, pk);
+ Ok((sk, pk))
+}
+
+#[cfg(test)]
+mod test {
+ use super::{generate_key_pair, Config, HpkeR, HpkeS};
+ use crate::{hpke::Aead, init};
+
+ const INFO: &[u8] = b"info";
+ const AAD: &[u8] = b"aad";
+ const PT: &[u8] = b"message";
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ #[test]
+ fn make() {
+ init();
+ let cfg = Config::default();
+ let (mut 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();
+ }
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ fn seal_open(aead: Aead) {
+ // Setup
+ init();
+ let cfg = Config {
+ aead,
+ ..Config::default()
+ };
+ assert!(cfg.supported());
+ let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
+
+ // Send
+ let mut hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
+ let enc = hpke_s.enc().unwrap();
+ 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 pt = hpke_r.open(AAD, &ct).unwrap();
+ assert_eq!(&pt[..], PT);
+ }
+
+ #[test]
+ fn seal_open_gcm() {
+ seal_open(Aead::Aes128Gcm);
+ }
+
+ #[test]
+ fn seal_open_chacha() {
+ seal_open(Aead::ChaCha20Poly1305);
+ }
+}
diff --git a/third_party/rust/ohttp/src/rh/mod.rs b/third_party/rust/ohttp/src/rh/mod.rs
new file mode 100644
index 0000000000..8f91a2ab17
--- /dev/null
+++ b/third_party/rust/ohttp/src/rh/mod.rs
@@ -0,0 +1,47 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub mod aead;
+pub mod hkdf;
+pub mod hpke;
+
+use crate::err::Res;
+
+pub struct SymKey(Vec<u8>);
+
+impl SymKey {
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn key_data(&self) -> Res<&[u8]> {
+ Ok(&self.0)
+ }
+}
+
+impl From<Vec<u8>> for SymKey {
+ fn from(v: Vec<u8>) -> Self {
+ SymKey(v)
+ }
+}
+impl From<&[u8]> for SymKey {
+ fn from(v: &[u8]) -> Self {
+ SymKey(v.to_owned())
+ }
+}
+
+impl AsRef<[u8]> for SymKey {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl std::fmt::Debug for SymKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "SymKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque SymKey")
+ }
+ }
+}