summaryrefslogtreecommitdiffstats
path: root/third_party/rust/ohttp/src/nss
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/ohttp/src/nss
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/ohttp/src/nss')
-rw-r--r--third_party/rust/ohttp/src/nss/aead.rs329
-rw-r--r--third_party/rust/ohttp/src/nss/err.rs141
-rw-r--r--third_party/rust/ohttp/src/nss/hkdf.rs289
-rw-r--r--third_party/rust/ohttp/src/nss/hpke.rs341
-rw-r--r--third_party/rust/ohttp/src/nss/mod.rs67
-rw-r--r--third_party/rust/ohttp/src/nss/p11.rs294
6 files changed, 1461 insertions, 0 deletions
diff --git a/third_party/rust/ohttp/src/nss/aead.rs b/third_party/rust/ohttp/src/nss/aead.rs
new file mode 100644
index 0000000000..6e36e33756
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/aead.rs
@@ -0,0 +1,329 @@
+use super::{
+ err::secstatus_to_res,
+ p11::{
+ sys::{
+ self, PK11Context, PK11_AEADOp, PK11_CreateContextBySymKey, PRBool, CKA_DECRYPT,
+ CKA_ENCRYPT, CKA_NSS_MESSAGE, CKG_GENERATE_COUNTER_XOR, CKG_NO_GENERATE, CKM_AES_GCM,
+ CKM_CHACHA20_POLY1305, CK_ATTRIBUTE_TYPE, CK_GENERATOR_FUNCTION, CK_MECHANISM_TYPE,
+ },
+ Item, SymKey,
+ },
+};
+use crate::{
+ err::{Error, Res},
+ hpke::Aead as AeadId,
+};
+use log::trace;
+use std::{
+ convert::{TryFrom, TryInto},
+ mem,
+ os::raw::c_int,
+};
+
+/// All the nonces are the same length. Exploit that.
+pub const NONCE_LEN: usize = 12;
+/// The portion of the nonce that is a counter.
+const COUNTER_LEN: usize = mem::size_of::<SequenceNumber>();
+/// The NSS API insists on us identifying the tag separately, which is awful.
+/// All of the AEAD functions here have a tag of this length, so use a fixed offset.
+const TAG_LEN: usize = 16;
+
+pub type SequenceNumber = u64;
+
+/// All the lengths used by `PK11_AEADOp` are signed. This converts to that.
+fn c_int_len<T>(l: T) -> c_int
+where
+ T: TryInto<c_int>,
+ T::Error: std::error::Error,
+{
+ l.try_into().unwrap()
+}
+
+unsafe fn destroy_aead_context(ctx: *mut PK11Context) {
+ sys::PK11_DestroyContext(ctx, PRBool::from(true));
+}
+scoped_ptr!(Context, PK11Context, destroy_aead_context);
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Mode {
+ Encrypt,
+ Decrypt,
+}
+
+impl Mode {
+ fn p11mode(self) -> CK_ATTRIBUTE_TYPE {
+ CK_ATTRIBUTE_TYPE::from(
+ CKA_NSS_MESSAGE
+ | match self {
+ Self::Encrypt => CKA_ENCRYPT,
+ Self::Decrypt => CKA_DECRYPT,
+ },
+ )
+ }
+}
+
+/// This is an AEAD instance that uses the
+pub struct Aead {
+ mode: Mode,
+ ctx: Context,
+ nonce_base: [u8; NONCE_LEN],
+}
+
+impl Aead {
+ fn mech(algorithm: AeadId) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match algorithm {
+ // The key size determines which AES variant is used.
+ AeadId::Aes128Gcm | AeadId::Aes256Gcm => CKM_AES_GCM,
+ AeadId::ChaCha20Poly1305 => CKM_CHACHA20_POLY1305,
+ })
+ }
+
+ #[cfg(test)]
+ pub fn import_key(algorithm: AeadId, key: &[u8]) -> Res<SymKey> {
+ let slot = super::p11::Slot::internal()?;
+ let ptr = unsafe {
+ sys::PK11_ImportSymKey(
+ *slot,
+ Self::mech(algorithm),
+ sys::PK11Origin::PK11_OriginUnwrap,
+ sys::CK_ATTRIBUTE_TYPE::from(sys::CKA_ENCRYPT | sys::CKA_DECRYPT),
+ &mut super::p11::Item::wrap(key),
+ std::ptr::null_mut(),
+ )
+ };
+ SymKey::from_ptr(ptr)
+ }
+
+ pub fn new(
+ mode: Mode,
+ algorithm: AeadId,
+ key: &SymKey,
+ nonce_base: [u8; NONCE_LEN],
+ ) -> Res<Self> {
+ trace!(
+ "New AEAD: key={} nonce_base={}",
+ hex::encode(key.key_data()?),
+ hex::encode(nonce_base)
+ );
+
+ let ptr = unsafe {
+ PK11_CreateContextBySymKey(
+ Self::mech(algorithm),
+ mode.p11mode(),
+ **key,
+ &Item::wrap(&nonce_base[..]),
+ )
+ };
+ Ok(Self {
+ mode,
+ ctx: Context::from_ptr(ptr)?,
+ nonce_base,
+ })
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ assert_eq!(self.mode, Mode::Encrypt);
+ // A copy for the nonce generator to write into. But we don't use the value.
+ let mut nonce = self.nonce_base;
+ // Ciphertext with enough space for the tag.
+ // Even though we give the operation a separate buffer for the tag,
+ // reserve the capacity on allocation.
+ let mut ct = vec![0; pt.len() + TAG_LEN];
+ let mut ct_len: c_int = 0;
+ let mut tag = vec![0; TAG_LEN];
+ secstatus_to_res(unsafe {
+ PK11_AEADOp(
+ *self.ctx,
+ CK_GENERATOR_FUNCTION::from(CKG_GENERATE_COUNTER_XOR),
+ c_int_len(NONCE_LEN - COUNTER_LEN), // Fixed portion of the nonce.
+ nonce.as_mut_ptr(),
+ c_int_len(nonce.len()),
+ aad.as_ptr(),
+ c_int_len(aad.len()),
+ ct.as_mut_ptr(),
+ &mut ct_len,
+ c_int_len(ct.len()), // signed :(
+ tag.as_mut_ptr(),
+ c_int_len(tag.len()),
+ pt.as_ptr(),
+ c_int_len(pt.len()),
+ )
+ })?;
+ ct.truncate(usize::try_from(ct_len).unwrap());
+ debug_assert_eq!(ct.len(), pt.len());
+ ct.append(&mut tag);
+ Ok(ct)
+ }
+
+ pub fn open(&mut self, aad: &[u8], seq: SequenceNumber, ct: &[u8]) -> Res<Vec<u8>> {
+ assert_eq!(self.mode, Mode::Decrypt);
+ let mut nonce = self.nonce_base;
+ for (i, n) in nonce.iter_mut().rev().take(COUNTER_LEN).enumerate() {
+ *n ^= u8::try_from((seq >> (8 * i)) & 0xff).unwrap();
+ }
+ let mut pt = vec![0; ct.len()]; // NSS needs more space than it uses for plaintext.
+ let mut pt_len: c_int = 0;
+ let pt_expected = ct.len().checked_sub(TAG_LEN).ok_or(Error::Truncated)?;
+ secstatus_to_res(unsafe {
+ PK11_AEADOp(
+ *self.ctx,
+ CK_GENERATOR_FUNCTION::from(CKG_NO_GENERATE),
+ c_int_len(NONCE_LEN - COUNTER_LEN), // Fixed portion of the nonce.
+ nonce.as_mut_ptr(),
+ c_int_len(nonce.len()),
+ aad.as_ptr(),
+ c_int_len(aad.len()),
+ pt.as_mut_ptr(),
+ &mut pt_len,
+ c_int_len(pt.len()), // signed :(
+ ct.as_ptr().add(pt_expected) as *mut _, // const cast :(
+ c_int_len(TAG_LEN),
+ ct.as_ptr(),
+ c_int_len(pt_expected),
+ )
+ })?;
+ let len = usize::try_from(pt_len).unwrap();
+ debug_assert_eq!(len, pt_expected);
+ pt.truncate(len);
+ Ok(pt)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ super::{super::hpke::Aead as AeadId, init},
+ Aead, Mode, SequenceNumber, NONCE_LEN,
+ };
+
+ /// Check that the first invocation of encryption matches expected values.
+ /// Also check decryption of the same.
+ fn check0(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ init();
+ let k = Aead::import_key(algorithm, key).unwrap();
+
+ let mut enc = Aead::new(Mode::Encrypt, algorithm, &k, *nonce).unwrap();
+ let ciphertext = enc.seal(aad, pt).unwrap();
+ assert_eq!(&ciphertext[..], ct);
+
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, 0, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ fn decrypt(
+ algorithm: AeadId,
+ key: &[u8],
+ nonce: &[u8; NONCE_LEN],
+ seq: SequenceNumber,
+ aad: &[u8],
+ pt: &[u8],
+ ct: &[u8],
+ ) {
+ let k = Aead::import_key(algorithm, key).unwrap();
+ let mut dec = Aead::new(Mode::Decrypt, algorithm, &k, *nonce).unwrap();
+ let plaintext = dec.open(aad, seq, ct).unwrap();
+ assert_eq!(&plaintext[..], pt);
+ }
+
+ /// This tests the AEAD in QUIC in combination with the HKDF code.
+ /// This is an AEAD-only example.
+ #[test]
+ fn quic_retry() {
+ const KEY: &[u8] = &[
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68,
+ 0xc8, 0x4e,
+ ];
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
+ ];
+ const AAD: &[u8] = &[
+ 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0xff, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65,
+ 0x6e,
+ ];
+ const CT: &[u8] = &[
+ 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58, 0xfb, 0x3f, 0x0f, 0x24,
+ 0x96, 0xba,
+ ];
+ check0(AeadId::Aes128Gcm, KEY, NONCE, AAD, &[], CT);
+ }
+
+ #[test]
+ fn quic_server_initial() {
+ const ALG: AeadId = AeadId::Aes128Gcm;
+ const KEY: &[u8] = &[
+ 0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3, 0x79, 0xb6, 0x06,
+ 0x7e, 0x37,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3e,
+ ];
+ // Note that this integrates the sequence number of 1 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3f,
+ ];
+ const AAD: &[u8] = &[
+ 0xc1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62,
+ 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01,
+ ];
+ const PT: &[u8] = &[
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03,
+ 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78,
+ 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43,
+ 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00,
+ 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0,
+ 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83,
+ 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03,
+ 0x04,
+ ];
+ const CT: &[u8] = &[
+ 0x5a, 0x48, 0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58, 0x16,
+ 0xb6, 0x39, 0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75, 0x54, 0x78, 0x0b, 0xb3,
+ 0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c, 0xf7, 0x3c, 0x3e, 0xc2, 0x49, 0x3a, 0x18,
+ 0x39, 0xb3, 0xdb, 0xcb, 0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7, 0x68, 0x4d, 0xf3, 0x54,
+ 0x8e, 0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73, 0xcc, 0x3f, 0x3b, 0xde, 0xd7, 0x4b,
+ 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84, 0x02, 0x2f, 0x8e, 0xf4, 0xcd, 0xd9, 0x37, 0x95,
+ 0xd7, 0x7d, 0x06, 0xed, 0xbb, 0x7a, 0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd,
+ 0xca, 0x3d, 0x20, 0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,
+ 0xd0, 0x74, 0xee,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ decrypt(ALG, KEY, NONCE_BASE, 1, AAD, PT, CT);
+ }
+
+ #[test]
+ fn quic_chacha() {
+ const ALG: AeadId = AeadId::ChaCha20Poly1305;
+ const KEY: &[u8] = &[
+ 0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20, 0x94, 0xf6, 0x9c,
+ 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88, 0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79,
+ 0xfb, 0x23, 0xe1, 0xc8,
+ ];
+ const NONCE_BASE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1, 0x44,
+ ];
+ // Note that this integrates the sequence number of 654360564 from the example,
+ // otherwise we can't use a sequence number of 0 to encrypt.
+ const NONCE: &[u8; NONCE_LEN] = &[
+ 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x6d, 0x41, 0x7e, 0xb0,
+ ];
+ const AAD: &[u8] = &[0x42, 0x00, 0xbf, 0xf4];
+ const PT: &[u8] = &[0x01];
+ const CT: &[u8] = &[
+ 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2,
+ 0x5a, 0x5b, 0xfb,
+ ];
+ check0(ALG, KEY, NONCE, AAD, PT, CT);
+ // Now use the real nonce and sequence number from the example.
+ decrypt(ALG, KEY, NONCE_BASE, 654_360_564, AAD, PT, CT);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/err.rs b/third_party/rust/ohttp/src/nss/err.rs
new file mode 100644
index 0000000000..af85066de9
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/err.rs
@@ -0,0 +1,141 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(
+ dead_code,
+ clippy::upper_case_acronyms,
+ clippy::module_name_repetitions
+)]
+
+use super::{SECStatus, SECSuccess};
+use crate::err::Res;
+use std::os::raw::c_char;
+
+include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
+mod codes {
+ #![allow(non_snake_case)]
+ include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
+}
+pub use codes::SECErrorCodes as sec;
+pub mod nspr {
+ include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Error {
+ name: String,
+ code: PRErrorCode,
+ desc: String,
+}
+
+impl Error {
+ /// Get an internal error.
+ pub(crate) fn internal() -> Self {
+ Self::from(sec::SEC_ERROR_LIBRARY_FAILURE)
+ }
+
+ /// Get the last error, as returned by `PR_GetError()`.
+ pub(crate) fn last() -> crate::Error {
+ crate::Error::from(Self::from(unsafe { PR_GetError() }))
+ }
+}
+
+impl From<PRErrorCode> for Error {
+ fn from(code: PRErrorCode) -> Self {
+ let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
+ let desc = wrap_str_fn(
+ || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
+ "...",
+ );
+ Error { name, code, desc }
+ }
+}
+
+impl std::error::Error for Error {}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Error {} ({}): {}", self.name, self.code, self.desc)
+ }
+}
+
+use std::ffi::CStr;
+
+fn wrap_str_fn<F>(f: F, dflt: &str) -> String
+where
+ F: FnOnce() -> *const c_char,
+{
+ unsafe {
+ let p = f();
+ if p.is_null() {
+ return dflt.to_string();
+ }
+ CStr::from_ptr(p).to_string_lossy().into_owned()
+ }
+}
+
+pub fn secstatus_to_res(rv: SECStatus) -> Res<()> {
+ if rv == SECSuccess {
+ Ok(())
+ } else {
+ Err(Error::last())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ super::{init, SECFailure, SECSuccess},
+ secstatus_to_res, PRErrorCode, PR_SetError,
+ };
+
+ fn set_error_code(code: PRErrorCode) {
+ // This code doesn't work without initializing NSS first.
+ init();
+ unsafe {
+ PR_SetError(code, 0);
+ }
+ }
+
+ #[test]
+ fn error_code() {
+ init();
+ assert_eq!(166 - 0x2000, super::sec::SEC_ERROR_LIBPKIX_INTERNAL);
+ assert_eq!(-5998, super::nspr::PR_WOULD_BLOCK_ERROR);
+ }
+
+ #[test]
+ fn is_ok() {
+ assert!(secstatus_to_res(SECSuccess).is_ok());
+ }
+
+ #[test]
+ fn is_err() {
+ set_error_code(super::sec::SEC_ERROR_BAD_DATABASE);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ if let crate::Error::Crypto(e) = r.unwrap_err() {
+ assert_eq!(e.name, "SEC_ERROR_BAD_DATABASE");
+ assert_eq!(e.code, 18 - 0x2000);
+ assert_eq!(e.desc, "security library: bad database.");
+ } else {
+ panic!();
+ }
+ }
+
+ #[test]
+ fn is_err_zero_code() {
+ set_error_code(0);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ if let crate::Error::Crypto(e) = r.unwrap_err() {
+ assert_eq!(e.name, "UNKNOWN_ERROR");
+ assert_eq!(e.code, 0);
+ } else {
+ panic!();
+ }
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/hkdf.rs b/third_party/rust/ohttp/src/nss/hkdf.rs
new file mode 100644
index 0000000000..e53c944008
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/hkdf.rs
@@ -0,0 +1,289 @@
+use super::{
+ super::hpke::{Aead, Kdf},
+ p11::{
+ sys::{
+ self, CKA_DERIVE, CKF_HKDF_SALT_DATA, CKF_HKDF_SALT_NULL, CKM_AES_GCM,
+ CKM_CHACHA20_POLY1305, CKM_HKDF_DATA, CKM_HKDF_DERIVE, CKM_SHA256, CK_BBOOL,
+ CK_HKDF_PARAMS, CK_INVALID_HANDLE, CK_MECHANISM_TYPE, CK_OBJECT_HANDLE, CK_ULONG,
+ },
+ ParamItem, SymKey,
+ },
+};
+use crate::err::Res;
+use log::trace;
+use std::{convert::TryFrom, os::raw::c_int, ptr::null_mut};
+
+#[derive(Clone, Copy)]
+pub enum KeyMechanism {
+ Aead(Aead),
+ #[allow(dead_code)] // We don't use this one.
+ Hkdf,
+}
+
+impl KeyMechanism {
+ fn mech(self) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match self {
+ Self::Aead(Aead::Aes128Gcm) | Self::Aead(Aead::Aes256Gcm) => CKM_AES_GCM,
+ Self::Aead(Aead::ChaCha20Poly1305) => CKM_CHACHA20_POLY1305,
+ Self::Hkdf => CKM_HKDF_DERIVE,
+ })
+ }
+
+ fn len(self) -> usize {
+ match self {
+ Self::Aead(a) => a.n_k(),
+ Self::Hkdf => 0, // Let the underlying module decide.
+ }
+ }
+}
+
+pub struct Hkdf {
+ kdf: Kdf,
+}
+
+impl Hkdf {
+ pub fn new(kdf: Kdf) -> Self {
+ Self { kdf }
+ }
+
+ #[cfg(test)]
+ pub fn import_ikm(ikm: &[u8]) -> Res<SymKey> {
+ let slot = super::p11::Slot::internal()?;
+ let ptr = unsafe {
+ sys::PK11_ImportSymKey(
+ *slot,
+ CK_MECHANISM_TYPE::from(sys::CKM_HKDF_KEY_GEN),
+ sys::PK11Origin::PK11_OriginUnwrap,
+ sys::CK_ATTRIBUTE_TYPE::from(sys::CKA_SIGN),
+ &mut super::p11::Item::wrap(ikm),
+ null_mut(),
+ )
+ };
+ SymKey::from_ptr(ptr)
+ }
+
+ fn mech(&self) -> CK_MECHANISM_TYPE {
+ CK_MECHANISM_TYPE::from(match self.kdf {
+ Kdf::HkdfSha256 => CKM_SHA256,
+ _ => unimplemented!(),
+ })
+ }
+
+ pub fn extract(&self, salt: &[u8], ikm: &SymKey) -> Res<SymKey> {
+ let salt_type = if salt.is_empty() {
+ CKF_HKDF_SALT_NULL
+ } else {
+ CKF_HKDF_SALT_DATA
+ };
+ let mut params = CK_HKDF_PARAMS {
+ bExtract: CK_BBOOL::from(true),
+ bExpand: CK_BBOOL::from(false),
+ prfHashMechanism: self.mech(),
+ ulSaltType: CK_ULONG::from(salt_type),
+ pSalt: salt.as_ptr() as *mut _, // const-cast = bad API
+ ulSaltLen: CK_ULONG::try_from(salt.len()).unwrap(),
+ hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
+ pInfo: null_mut(),
+ ulInfoLen: 0,
+ };
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **ikm,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ params_item.ptr(),
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ 0,
+ )
+ };
+
+ let prk = SymKey::from_ptr(ptr)?;
+ trace!(
+ "HKDF extract: salt={} ikm={} prk={}",
+ hex::encode(salt),
+ hex::encode(ikm.key_data()?),
+ hex::encode(prk.key_data()?),
+ );
+ Ok(prk)
+ }
+
+ // NB: `info` must outlive the returned value.
+ fn expand_params(&self, info: &[u8]) -> CK_HKDF_PARAMS {
+ CK_HKDF_PARAMS {
+ bExtract: CK_BBOOL::from(false),
+ bExpand: CK_BBOOL::from(true),
+ prfHashMechanism: self.mech(),
+ ulSaltType: CK_ULONG::from(CKF_HKDF_SALT_NULL),
+ pSalt: null_mut(),
+ ulSaltLen: 0,
+ hSaltKey: CK_OBJECT_HANDLE::from(CK_INVALID_HANDLE),
+ pInfo: info.as_ptr() as *mut _, // const-cast = bad API
+ ulInfoLen: CK_ULONG::try_from(info.len()).unwrap(),
+ }
+ }
+
+ pub fn expand_key(&self, prk: &SymKey, info: &[u8], key_mech: KeyMechanism) -> Res<SymKey> {
+ let mut params = self.expand_params(info);
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **prk,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ params_item.ptr(),
+ key_mech.mech(),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ c_int::try_from(key_mech.len()).unwrap(),
+ )
+ };
+ let okm = SymKey::from_ptr(ptr)?;
+ trace!(
+ "HKDF expand_key: prk={} info={} okm={}",
+ hex::encode(prk.key_data()?),
+ hex::encode(info),
+ hex::encode(okm.key_data()?),
+ );
+ Ok(okm)
+ }
+
+ pub fn expand_data(&self, prk: &SymKey, info: &[u8], len: usize) -> Res<Vec<u8>> {
+ let mut params = self.expand_params(info);
+ let mut params_item = ParamItem::new(&mut params);
+ let ptr = unsafe {
+ sys::PK11_Derive(
+ **prk,
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DATA),
+ params_item.ptr(),
+ CK_MECHANISM_TYPE::from(CKM_HKDF_DERIVE),
+ CK_MECHANISM_TYPE::from(CKA_DERIVE),
+ c_int::try_from(len).unwrap(),
+ )
+ };
+ let k = SymKey::from_ptr(ptr)?;
+ let r = Vec::from(k.key_data()?);
+ trace!(
+ "HKDF expand_data: prk={} info={} okm={}",
+ hex::encode(prk.key_data()?),
+ hex::encode(info),
+ hex::encode(&r),
+ );
+ Ok(r)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{super::super::hpke::Kdf, Hkdf};
+ use crate::init;
+
+ fn sha256_example(
+ ikm: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ l: usize,
+ expected_prk: &[u8],
+ expected_okm: &[u8],
+ ) {
+ init();
+ let hkdf = Hkdf::new(Kdf::HkdfSha256);
+ let k_ikm = Hkdf::import_ikm(ikm).unwrap();
+ let prk = hkdf.extract(salt, &k_ikm).unwrap();
+ let prk_data = prk.key_data().unwrap();
+ assert_eq!(prk_data, expected_prk);
+
+ let out = hkdf.expand_data(&prk, info, l).unwrap();
+ assert_eq!(&out[..], expected_okm);
+ }
+
+ /// Example 1 from <https://tools.ietf.org/html/rfc5869#appendix-A.1>
+ #[test]
+ fn example1() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ ];
+ const INFO: &[u8] = &[0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b,
+ 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a,
+ 0xd7, 0xc2, 0xb3, 0xe5,
+ ];
+ const OKM: &[u8] = &[
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
+ 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
+ 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 2 from <https://tools.ietf.org/html/rfc5869#appendix-A.2>
+ #[test]
+ fn example2() {
+ const IKM: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ ];
+ const SALT: &[u8] = &[
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+ 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
+ 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ ];
+ const INFO: &[u8] = &[
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd,
+ 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ ];
+ const L: usize = 82;
+ const PRK: &[u8] = &[
+ 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35,
+ 0xb4, 0x5c, 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01, 0x4a, 0x19, 0x3f, 0x40,
+ 0xc1, 0x5f, 0xc2, 0x44,
+ ];
+ const OKM: &[u8] = &[
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
+ 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
+ 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
+ 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+ 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
+ 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+
+ /// Example 3 from <https://tools.ietf.org/html/rfc5869#appendix-A.3>
+ #[test]
+ fn example3() {
+ const IKM: &[u8] = &[
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ ];
+ const SALT: &[u8] = &[];
+ const INFO: &[u8] = &[];
+ const L: usize = 42;
+ const PRK: &[u8] = &[
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64,
+ 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c,
+ 0x29, 0x3c, 0xcb, 0x04,
+ ];
+ const OKM: &[u8] = &[
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
+ 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
+ 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
+ ];
+ sha256_example(IKM, SALT, INFO, L, PRK, OKM);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/hpke.rs b/third_party/rust/ohttp/src/nss/hpke.rs
new file mode 100644
index 0000000000..95adef3764
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/hpke.rs
@@ -0,0 +1,341 @@
+use super::{
+ super::hpke::{Aead, Kdf, Kem},
+ err::{sec::SEC_ERROR_INVALID_ARGS, secstatus_to_res, Error},
+ p11::{sys, Item, PrivateKey, PublicKey, Slot, SymKey},
+};
+use crate::err::Res;
+use log::{log_enabled, trace};
+use std::{
+ convert::TryFrom,
+ ops::Deref,
+ os::raw::c_uint,
+ ptr::{addr_of_mut, null, null_mut},
+};
+
+pub use sys::{HpkeAeadId as AeadId, HpkeKdfId as KdfId, HpkeKemId as KemId};
+
+/// Configuration for `Hpke`.
+#[derive(Clone, Copy)]
+pub struct Config {
+ kem: Kem,
+ kdf: Kdf,
+ aead: Aead,
+}
+
+impl Config {
+ pub fn new(kem: Kem, kdf: Kdf, aead: Aead) -> Self {
+ Self { kem, kdf, aead }
+ }
+
+ pub fn kem(self) -> Kem {
+ self.kem
+ }
+
+ pub fn kdf(self) -> Kdf {
+ self.kdf
+ }
+
+ pub fn aead(self) -> Aead {
+ self.aead
+ }
+
+ pub fn supported(self) -> bool {
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_ValidateParameters(
+ KemId::Type::from(u16::from(self.kem)),
+ KdfId::Type::from(u16::from(self.kdf)),
+ AeadId::Type::from(u16::from(self.aead)),
+ )
+ })
+ .is_ok()
+ }
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ kem: Kem::X25519Sha256,
+ kdf: Kdf::HkdfSha256,
+ aead: Aead::Aes128Gcm,
+ }
+ }
+}
+
+pub trait Exporter {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey>;
+}
+
+unsafe fn destroy_hpke_context(cx: *mut sys::HpkeContext) {
+ sys::PK11_HPKE_DestroyContext(cx, sys::PRBool::from(true));
+}
+
+scoped_ptr!(HpkeContext, sys::HpkeContext, destroy_hpke_context);
+
+impl HpkeContext {
+ fn new(config: Config) -> Res<Self> {
+ let ptr = unsafe {
+ sys::PK11_HPKE_NewContext(
+ KemId::Type::from(u16::from(config.kem)),
+ KdfId::Type::from(u16::from(config.kdf)),
+ AeadId::Type::from(u16::from(config.aead)),
+ null_mut(),
+ null(),
+ )
+ };
+ Self::from_ptr(ptr)
+ }
+}
+
+impl Exporter for HpkeContext {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ let mut out: *mut sys::PK11SymKey = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_ExportSecret(
+ self.ptr,
+ &Item::wrap(info),
+ c_uint::try_from(len).unwrap(),
+ &mut out,
+ )
+ })?;
+ SymKey::from_ptr(out)
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeS {
+ context: HpkeContext,
+ config: Config,
+}
+
+impl HpkeS {
+ /// Create a new context that uses the KEM mode for sending.
+ #[allow(clippy::similar_names)]
+ pub fn new(config: Config, pk_r: &mut PublicKey, info: &[u8]) -> Res<Self> {
+ let (sk_e, pk_e) = generate_key_pair(config.kem)?;
+ let context = HpkeContext::new(config)?;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_SetupS(*context, *pk_e, *sk_e, **pk_r, &Item::wrap(info))
+ })?;
+ Ok(Self { context, config })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ /// Get the encapsulated KEM secret.
+ pub fn enc(&self) -> Res<Vec<u8>> {
+ let v = unsafe { sys::PK11_HPKE_GetEncapPubKey(*self.context) };
+ let r = unsafe { v.as_ref() }.ok_or_else(|| Error::from(SEC_ERROR_INVALID_ARGS))?;
+ // This is just an alias, so we can't use `Item`.
+ let len = usize::try_from(r.len).unwrap();
+ let slc = unsafe { std::slice::from_raw_parts(r.data, len) };
+ Ok(Vec::from(slc))
+ }
+
+ pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Res<Vec<u8>> {
+ let mut out: *mut sys::SECItem = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Seal(*self.context, &Item::wrap(aad), &Item::wrap(pt), &mut out)
+ })?;
+ let v = Item::from_ptr(out)?;
+ Ok(unsafe { v.into_vec() })
+ }
+}
+
+impl Exporter for HpkeS {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ self.context.export(info, len)
+ }
+}
+
+impl Deref for HpkeS {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub struct HpkeR {
+ context: HpkeContext,
+ config: Config,
+}
+
+impl HpkeR {
+ /// Create a new context that uses the KEM mode for sending.
+ #[allow(clippy::similar_names)]
+ pub fn new(
+ config: Config,
+ pk_r: &PublicKey,
+ sk_r: &mut PrivateKey,
+ enc: &[u8],
+ info: &[u8],
+ ) -> Res<Self> {
+ let context = HpkeContext::new(config)?;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_SetupR(
+ *context,
+ **pk_r,
+ **sk_r,
+ &Item::wrap(enc),
+ &Item::wrap(info),
+ )
+ })?;
+ Ok(Self { context, config })
+ }
+
+ pub fn config(&self) -> Config {
+ self.config
+ }
+
+ pub fn decode_public_key(kem: Kem, k: &[u8]) -> Res<PublicKey> {
+ // NSS uses a context for this, but we don't want that, but a dummy one works fine.
+ let context = HpkeContext::new(Config {
+ kem,
+ ..Config::default()
+ })?;
+ let mut ptr: *mut sys::SECKEYPublicKey = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Deserialize(
+ *context,
+ k.as_ptr(),
+ c_uint::try_from(k.len()).unwrap(),
+ &mut ptr,
+ )
+ })?;
+ PublicKey::from_ptr(ptr)
+ }
+
+ pub fn open(&mut self, aad: &[u8], ct: &[u8]) -> Res<Vec<u8>> {
+ let mut out: *mut sys::SECItem = null_mut();
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Open(*self.context, &Item::wrap(aad), &Item::wrap(ct), &mut out)
+ })?;
+ let v = Item::from_ptr(out)?;
+ Ok(unsafe { v.into_vec() })
+ }
+}
+
+impl Exporter for HpkeR {
+ fn export(&self, info: &[u8], len: usize) -> Res<SymKey> {
+ self.context.export(info, len)
+ }
+}
+
+impl Deref for HpkeR {
+ type Target = Config;
+ fn deref(&self) -> &Self::Target {
+ &self.config
+ }
+}
+
+/// Generate a key pair for the identified KEM.
+pub fn generate_key_pair(kem: Kem) -> Res<(PrivateKey, PublicKey)> {
+ assert_eq!(kem, Kem::X25519Sha256);
+ let slot = Slot::internal()?;
+
+ let oid_data = unsafe { sys::SECOID_FindOIDByTag(sys::SECOidTag::SEC_OID_CURVE25519) };
+ let oid = unsafe { oid_data.as_ref() }.ok_or_else(Error::internal)?;
+ let oid_slc =
+ unsafe { std::slice::from_raw_parts(oid.oid.data, usize::try_from(oid.oid.len).unwrap()) };
+ let mut params: Vec<u8> = Vec::with_capacity(oid_slc.len() + 2);
+ params.push(u8::try_from(sys::SEC_ASN1_OBJECT_ID).unwrap());
+ params.push(u8::try_from(oid.oid.len).unwrap());
+ params.extend_from_slice(oid_slc);
+
+ let mut public_ptr: *mut sys::SECKEYPublicKey = null_mut();
+ let mut wrapped = Item::wrap(&params);
+
+ // Try to make an insensitive key so that we can read the key data for tracing.
+ let insensitive_secret_ptr = if log_enabled!(log::Level::Trace) {
+ unsafe {
+ sys::PK11_GenerateKeyPairWithOpFlags(
+ *slot,
+ sys::CK_MECHANISM_TYPE::from(sys::CKM_EC_KEY_PAIR_GEN),
+ addr_of_mut!(wrapped).cast(),
+ &mut public_ptr,
+ sys::PK11_ATTR_SESSION | sys::PK11_ATTR_INSENSITIVE | sys::PK11_ATTR_PUBLIC,
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ null_mut(),
+ )
+ }
+ } else {
+ null_mut()
+ };
+ assert_eq!(insensitive_secret_ptr.is_null(), public_ptr.is_null());
+ let secret_ptr = if insensitive_secret_ptr.is_null() {
+ unsafe {
+ sys::PK11_GenerateKeyPairWithOpFlags(
+ *slot,
+ sys::CK_MECHANISM_TYPE::from(sys::CKM_EC_KEY_PAIR_GEN),
+ addr_of_mut!(wrapped).cast(),
+ &mut public_ptr,
+ sys::PK11_ATTR_SESSION | sys::PK11_ATTR_SENSITIVE | sys::PK11_ATTR_PRIVATE,
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ sys::CK_FLAGS::from(sys::CKF_DERIVE),
+ null_mut(),
+ )
+ }
+ } else {
+ insensitive_secret_ptr
+ };
+ assert_eq!(secret_ptr.is_null(), public_ptr.is_null());
+ let sk = PrivateKey::from_ptr(secret_ptr)?;
+ let pk = PublicKey::from_ptr(public_ptr)?;
+ trace!("Generated key pair: sk={:?} pk={:?}", sk, pk);
+ Ok((sk, pk))
+}
+
+#[cfg(test)]
+mod test {
+ use super::{generate_key_pair, Config, HpkeR, HpkeS};
+ use crate::{hpke::Aead, init};
+
+ const INFO: &[u8] = b"info";
+ const AAD: &[u8] = b"aad";
+ const PT: &[u8] = b"message";
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ #[test]
+ fn make() {
+ init();
+ let cfg = Config::default();
+ let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
+ let hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
+ let _hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &hpke_s.enc().unwrap(), INFO).unwrap();
+ }
+
+ #[allow(clippy::similar_names)] // for sk_x and pk_x
+ fn seal_open(aead: Aead) {
+ // Setup
+ init();
+ let cfg = Config {
+ aead,
+ ..Config::default()
+ };
+ assert!(cfg.supported());
+ let (mut sk_r, mut pk_r) = generate_key_pair(cfg.kem()).unwrap();
+
+ // Send
+ let mut hpke_s = HpkeS::new(cfg, &mut pk_r, INFO).unwrap();
+ let enc = hpke_s.enc().unwrap();
+ let ct = hpke_s.seal(AAD, PT).unwrap();
+
+ // Receive
+ let mut hpke_r = HpkeR::new(cfg, &pk_r, &mut sk_r, &enc, INFO).unwrap();
+ let pt = hpke_r.open(AAD, &ct).unwrap();
+ assert_eq!(&pt[..], PT);
+ }
+
+ #[test]
+ fn seal_open_gcm() {
+ seal_open(Aead::Aes128Gcm);
+ }
+
+ #[test]
+ fn seal_open_chacha() {
+ seal_open(Aead::ChaCha20Poly1305);
+ }
+}
diff --git a/third_party/rust/ohttp/src/nss/mod.rs b/third_party/rust/ohttp/src/nss/mod.rs
new file mode 100644
index 0000000000..7040e18664
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/mod.rs
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod err;
+#[macro_use]
+mod p11;
+pub mod aead;
+pub mod hkdf;
+pub mod hpke;
+
+pub use self::p11::{random, PrivateKey, PublicKey, SymKey};
+use err::secstatus_to_res;
+pub use err::Error;
+use lazy_static::lazy_static;
+use std::ptr::null;
+
+#[allow(clippy::pedantic, non_upper_case_globals, clippy::upper_case_acronyms)]
+mod nss_init {
+ include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+use nss_init::SECStatus;
+#[allow(non_upper_case_globals)]
+const SECSuccess: SECStatus = nss_init::_SECStatus_SECSuccess;
+#[cfg(test)]
+#[allow(non_upper_case_globals)]
+const SECFailure: SECStatus = nss_init::_SECStatus_SECFailure;
+
+#[derive(PartialEq, Eq)]
+enum NssLoaded {
+ External,
+ NoDb,
+}
+
+impl Drop for NssLoaded {
+ fn drop(&mut self) {
+ if *self == Self::NoDb {
+ unsafe {
+ secstatus_to_res(nss_init::NSS_Shutdown()).expect("NSS Shutdown failed");
+ }
+ }
+ }
+}
+
+lazy_static! {
+ static ref INITIALIZED: NssLoaded = {
+ if already_initialized() {
+ return NssLoaded::External;
+ }
+
+ secstatus_to_res(unsafe { nss_init::NSS_NoDB_Init(null()) }).expect("NSS_NoDB_Init failed");
+
+ NssLoaded::NoDb
+ };
+}
+
+fn already_initialized() -> bool {
+ unsafe { nss_init::NSS_IsInitialized() != 0 }
+}
+
+/// Initialize NSS. This only executes the initialization routines once.
+pub fn init() {
+ lazy_static::initialize(&INITIALIZED);
+}
diff --git a/third_party/rust/ohttp/src/nss/p11.rs b/third_party/rust/ohttp/src/nss/p11.rs
new file mode 100644
index 0000000000..7a1bff5b4a
--- /dev/null
+++ b/third_party/rust/ohttp/src/nss/p11.rs
@@ -0,0 +1,294 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::err::{secstatus_to_res, Error};
+use crate::err::Res;
+use std::{
+ convert::TryFrom,
+ marker::PhantomData,
+ mem,
+ os::raw::{c_int, c_uint},
+ ptr::null_mut,
+};
+
+#[allow(
+ clippy::pedantic,
+ clippy::upper_case_acronyms,
+ dead_code,
+ deref_nullptr,
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals
+)]
+pub mod sys {
+ include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
+}
+
+use sys::{
+ PK11ObjectType, PK11SlotInfo, PK11SymKey, PK11_ExtractKeyValue, PK11_FreeSlot, PK11_FreeSymKey,
+ PK11_GenerateRandom, PK11_GetInternalSlot, PK11_GetKeyData, PK11_ReadRawAttribute,
+ PK11_ReferenceSymKey, PRBool, SECITEM_FreeItem, SECItem, SECItemType, SECKEYPrivateKey,
+ SECKEYPublicKey, SECKEY_DestroyPrivateKey, SECKEY_DestroyPublicKey, CKA_VALUE,
+ CK_ATTRIBUTE_TYPE,
+};
+
+macro_rules! scoped_ptr {
+ ($scoped:ident, $target:ty, $dtor:path) => {
+ pub struct $scoped {
+ ptr: *mut $target,
+ }
+
+ impl $scoped {
+ pub fn from_ptr(ptr: *mut $target) -> Result<Self, crate::err::Error> {
+ if ptr.is_null() {
+ Err(crate::nss::err::Error::last())
+ } else {
+ Ok(Self { ptr })
+ }
+ }
+ }
+
+ impl std::ops::Deref for $scoped {
+ type Target = *mut $target;
+ #[must_use]
+ fn deref(&self) -> &*mut $target {
+ &self.ptr
+ }
+ }
+
+ impl std::ops::DerefMut for $scoped {
+ fn deref_mut(&mut self) -> &mut *mut $target {
+ &mut self.ptr
+ }
+ }
+
+ impl Drop for $scoped {
+ fn drop(&mut self) {
+ let _ = unsafe { $dtor(self.ptr) };
+ }
+ }
+ };
+}
+
+scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey);
+
+impl PrivateKey {
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut key_item = SECItem {
+ type_: SECItemType::siBuffer,
+ data: null_mut(),
+ len: 0,
+ };
+ secstatus_to_res(unsafe {
+ PK11_ReadRawAttribute(
+ PK11ObjectType::PK11_TypePrivKey,
+ (**self).cast(),
+ CK_ATTRIBUTE_TYPE::from(CKA_VALUE),
+ &mut key_item,
+ )
+ })?;
+ let slc = unsafe {
+ std::slice::from_raw_parts(key_item.data, usize::try_from(key_item.len).unwrap())
+ };
+ let key = Vec::from(slc);
+ // The data that `key_item` refers to needs to be freed, but we can't
+ // use the scoped `Item` implementation. This is OK as long as nothing
+ // panics between `PK11_ReadRawAttribute` succeeding and here.
+ unsafe {
+ SECITEM_FreeItem(&mut key_item, PRBool::from(false));
+ }
+ Ok(key)
+ }
+}
+unsafe impl Send for PrivateKey {}
+
+impl Clone for PrivateKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { sys::SECKEY_CopyPrivateKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for PrivateKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PrivateKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PrivateKey")
+ }
+ }
+}
+
+scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey);
+
+impl PublicKey {
+ /// Get the HPKE serialization of the public key.
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut buf = vec![0; 100];
+ let mut len: c_uint = 0;
+ secstatus_to_res(unsafe {
+ sys::PK11_HPKE_Serialize(
+ **self,
+ buf.as_mut_ptr(),
+ &mut len,
+ c_uint::try_from(buf.len()).unwrap(),
+ )
+ })?;
+ buf.truncate(usize::try_from(len).unwrap());
+ Ok(buf)
+ }
+}
+
+unsafe impl Send for PublicKey {}
+
+impl Clone for PublicKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { sys::SECKEY_CopyPublicKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for PublicKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "PublicKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque PublicKey")
+ }
+ }
+}
+
+scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
+
+impl Slot {
+ pub(crate) fn internal() -> Res<Self> {
+ let p = unsafe { PK11_GetInternalSlot() };
+ Slot::from_ptr(p)
+ }
+}
+
+scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
+
+impl SymKey {
+ /// You really don't want to use this.
+ ///
+ /// # Errors
+ /// Some keys cannot be inspected in this way.
+ /// Also, internal errors in case of failures in NSS.
+ pub fn key_data(&self) -> Res<&[u8]> {
+ secstatus_to_res(unsafe { PK11_ExtractKeyValue(self.ptr) })?;
+
+ let key_item = unsafe { PK11_GetKeyData(self.ptr) };
+ // This is accessing a value attached to the key, so we can treat this as a borrow.
+ match unsafe { key_item.as_mut() } {
+ None => Err(Error::last()),
+ Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }),
+ }
+ }
+}
+
+impl Clone for SymKey {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { PK11_ReferenceSymKey(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+}
+
+impl std::fmt::Debug for SymKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.key_data() {
+ write!(f, "SymKey {}", hex::encode(b))
+ } else {
+ write!(f, "Opaque SymKey")
+ }
+ }
+}
+
+unsafe impl Send for SymKey {}
+
+/// Generate a randomized buffer.
+#[must_use]
+pub fn random(size: usize) -> Vec<u8> {
+ let mut buf = vec![0; size];
+ secstatus_to_res(unsafe {
+ PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap())
+ })
+ .unwrap();
+ buf
+}
+
+pub(crate) struct ParamItem<'a, T: 'a> {
+ item: SECItem,
+ marker: PhantomData<&'a T>,
+}
+
+impl<'a, T: Sized + 'a> ParamItem<'a, T> {
+ pub fn new(v: &'a mut T) -> Self {
+ let item = SECItem {
+ type_: SECItemType::siBuffer,
+ data: (v as *mut T).cast::<u8>(),
+ len: c_uint::try_from(mem::size_of::<T>()).unwrap(),
+ };
+ Self {
+ item,
+ marker: PhantomData::default(),
+ }
+ }
+
+ pub fn ptr(&mut self) -> *mut SECItem {
+ std::ptr::addr_of_mut!(self.item)
+ }
+}
+
+unsafe fn destroy_secitem(item: *mut SECItem) {
+ SECITEM_FreeItem(item, PRBool::from(true));
+}
+scoped_ptr!(Item, SECItem, destroy_secitem);
+
+impl Item {
+ /// Create a wrapper for a slice of this object.
+ /// Creating this object is technically safe, but using it is extremely dangerous.
+ /// Minimally, it can only be passed as a `const SECItem*` argument to functions.
+ pub(crate) fn wrap(buf: &[u8]) -> SECItem {
+ SECItem {
+ type_: SECItemType::siBuffer,
+ data: buf.as_ptr() as *mut u8,
+ len: c_uint::try_from(buf.len()).unwrap(),
+ }
+ }
+
+ /// This dereferences the pointer held by the item and makes a copy of the
+ /// content that is referenced there.
+ ///
+ /// # Safety
+ /// This dereferences two pointers. It doesn't get much less safe.
+ pub(crate) unsafe fn into_vec(self) -> Vec<u8> {
+ let b = self.ptr.as_ref().unwrap();
+ // Sanity check the type, as some types don't count bytes in `Item::len`.
+ assert_eq!(b.type_, SECItemType::siBuffer);
+ let slc = std::slice::from_raw_parts(b.data, usize::try_from(b.len).unwrap());
+ Vec::from(slc)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::random;
+ use crate::init;
+
+ #[test]
+ fn randomness() {
+ init();
+ // If this ever fails, there is either a bug, or it's time to buy a lottery ticket.
+ assert_ne!(random(16), random(16));
+ }
+}