diff options
Diffstat (limited to 'third_party/rust/ohttp')
24 files changed, 3992 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..23da91c02a --- /dev/null +++ b/third_party/rust/ohttp/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"a7b96e36564b8e947fe382f5e1397c7449d5c006dd09526842f5272b714b62aa","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":"23faf6a41042123038b861998f4f4254f26395292afdf018c22bf701f0d1e926","src/err.rs":"c98a96e1c1c5f83263b69e5cdfd0f157bc4669115e000df89b61cbfdd4899b50","src/hpke.rs":"21182eed9bd71ea59fba1670491f845a7f773d1285b89d2772e052f8a37f475c","src/lib.rs":"6b30e8295d34dae2636a92b8770a3babb0a2fd239a07f9a60b2e38936ba43ae7","src/nss/aead.rs":"f7c0c78b14ee9cfe07966226605301962c21047d57a2857fbf96c2258bed4797","src/nss/err.rs":"5e6f35bce8ed12fb9de2374576dc96f6c2ec1f04c080e98280367c70efb04139","src/nss/hkdf.rs":"3dcc51084f37c4d4899364ef6a9092eafe5ba1927476e5cf5e2fc39fb7480d16","src/nss/hpke.rs":"a3a8f23bcdce690925f0a675deb508aa99c20d82f5d437acefc9c26d32efd46a","src/nss/mod.rs":"7dc88bef24a2d80a0c46a7e1cb24f2568bd75c1d082b3b39f005cc92873ab0cf","src/nss/p11.rs":"c350326590d4f4a89cd9db635a1ba994a5070711b03cef35816fb9ffe5c206a7","src/rand.rs":"be3a82fb6090b5cb833c2b8ba6e72690b9f44f7f91477e2c5b70b75623174b87","src/rh/aead.rs":"e188fa99282b93ce08547c04af54167e2c7f110563dd2e4d25280c2e6f677669","src/rh/hkdf.rs":"64ec546f439b348a505dcb5c0953de288b4df5b5ce9bde5d88f26ebba91e9c6d","src/rh/hpke.rs":"aa66254ddce8da7a75b9bd1057f3a9cdc859605a676d99ed6e08f5040a995169","src/rh/mod.rs":"d6045628f9b95d75e8bfcc30d55b1fc8b5ea9e2a4c45e20102f4f33c0b711a0c"},"package":"3e4e4bdb61a3d6563cfba7344325a9f1e44e7b7d212dcec7afd247ca8deecdf9"}
\ 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..ee244ba262 --- /dev/null +++ b/third_party/rust/ohttp/Cargo.toml @@ -0,0 +1,108 @@ +# 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.2.3" +authors = ["Martin Thomson <mt@lowentropy.net>"] +build = "build.rs" +description = "Oblivious HTTP (draft-ietf-ohai-ohttp)" +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 + +[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..ddba6b6d2c --- /dev/null +++ b/third_party/rust/ohttp/build.rs @@ -0,0 +1,435 @@ +// 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; + use std::env; + use std::fs; + use std::path::{Path, PathBuf}; + use std::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..84c5a602c2 --- /dev/null +++ b/third_party/rust/ohttp/src/err.rs @@ -0,0 +1,76 @@ +#[derive(Debug)] +pub enum Error { + /// A problem occurred with the AEAD. + #[cfg(feature = "rust-hpke")] + Aead(aead::Error), + /// A problem occurred during cryptographic processing. + #[cfg(feature = "nss")] + Crypto(crate::nss::Error), + /// An error was found in the format. + Format, + /// A problem occurred with HPKE. + #[cfg(feature = "rust-hpke")] + Hpke(::hpke::HpkeError), + /// An internal error occurred. + Internal, + /// The wrong type of key was provided for the selected KEM. + InvalidKeyType, + /// The wrong KEM was specified. + InvalidKem, + /// An IO error. + Io(std::io::Error), + /// The key ID was invalid. + KeyId, + /// A field was truncated. + Truncated, + /// The configuration was not supported. + Unsupported, + /// The configuration contained too many symmetric suites. + TooManySymmetricSuites, +} + +macro_rules! forward_errors { + {$($(#[$m:meta])* $t:path => $v:ident),* $(,)?} => { + $( + $(#[$m])* + impl From<$t> for Error { + fn from(e: $t) -> Self { + Self::$v(e) + } + } + )* + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + $( $(#[$m])* Self::$v(e) => Some(e), )* + _ => None, + } + } + } + }; +} + +forward_errors! { + #[cfg(feature = "rust-hpke")] + aead::Error => Aead, + #[cfg(feature = "nss")] + crate::nss::Error => Crypto, + #[cfg(feature = "rust-hpke")] + ::hpke::HpkeError => Hpke, + std::io::Error => Io, +} + +impl From<std::num::TryFromIntError> for Error { + fn from(_v: std::num::TryFromIntError) -> Self { + Self::TooManySymmetricSuites + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{:?}", self) + } +} + +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..7567cd2e97 --- /dev/null +++ b/third_party/rust/ohttp/src/lib.rs @@ -0,0 +1,590 @@ +#![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; +use std::convert::TryFrom; +use std::io::{BufReader, Read}; +use std::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) + } +} + +/// 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 (response_nonce, ct) = enc_response.split_at(entropy(self.hpke.config())); + 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::hpke::{Aead, Kdf, Kem}; + use crate::{ClientRequest, KeyConfig, KeyId, Server, SymmetricSuite}; + use log::trace; + + 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); + } + + #[test] + fn response_truncated() { + 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)); + + assert!(client_response.decapsulate(&enc_response[..16]).is_err()); + } + + #[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..7ffb402b64 --- /dev/null +++ b/third_party/rust/ohttp/src/nss/aead.rs @@ -0,0 +1,320 @@ +use super::err::secstatus_to_res; +use super::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, +}; +use super::p11::{Item, SymKey}; +use crate::err::{Error, Res}; +use crate::hpke::Aead as AeadId; +use log::trace; +use std::convert::{TryFrom, TryInto}; +use std::mem; +use std::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; + use super::super::init; + use super::{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..31529f3773 --- /dev/null +++ b/third_party/rust/ohttp/src/nss/err.rs @@ -0,0 +1,139 @@ +// 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}; + use super::{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..dcf0fabd2d --- /dev/null +++ b/third_party/rust/ohttp/src/nss/hkdf.rs @@ -0,0 +1,288 @@ +use super::super::hpke::{Aead, Kdf}; +use super::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, +}; +use super::p11::{ParamItem, SymKey}; +use crate::err::Res; +use log::trace; +use std::convert::TryFrom; +use std::os::raw::c_int; +use std::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; + use super::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..7ddb058ce9 --- /dev/null +++ b/third_party/rust/ohttp/src/nss/hpke.rs @@ -0,0 +1,338 @@ +use super::super::hpke::{Aead, Kdf, Kem}; +use super::err::{sec::SEC_ERROR_INVALID_ARGS, secstatus_to_res, Error}; +use super::p11::{sys, Item, PrivateKey, PublicKey, Slot, SymKey}; +use crate::err::Res; +use log::{log_enabled, trace}; +use std::convert::TryFrom; +use std::ops::Deref; +use std::os::raw::c_uint; +use std::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(¶ms); + + // 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; + use crate::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..08040ee908 --- /dev/null +++ b/third_party/rust/ohttp/src/nss/p11.rs @@ -0,0 +1,292 @@ +// 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; +use std::marker::PhantomData; +use std::mem; +use std::os::raw::{c_int, c_uint}; +use std::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..0935a13634 --- /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; +use crate::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; + use super::super::super::init; + use super::{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..67ec3616e9 --- /dev/null +++ b/third_party/rust/ohttp/src/rh/hkdf.rs @@ -0,0 +1,223 @@ +#![allow(dead_code)] // TODO: remove + +use super::SymKey; +use crate::err::{Error, Res}; +use crate::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; + use super::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..62c01e01bd --- /dev/null +++ b/third_party/rust/ohttp/src/rh/hpke.rs @@ -0,0 +1,503 @@ +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), + )) => { + 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), + )) => { + 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; + use crate::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") + } + } +} |