summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nss-gk-api/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nss-gk-api/src')
-rw-r--r--third_party/rust/nss-gk-api/src/err.rs257
-rw-r--r--third_party/rust/nss-gk-api/src/exp.rs25
-rw-r--r--third_party/rust/nss-gk-api/src/lib.rs173
-rw-r--r--third_party/rust/nss-gk-api/src/p11.rs194
-rw-r--r--third_party/rust/nss-gk-api/src/prio.rs21
-rw-r--r--third_party/rust/nss-gk-api/src/prtypes.rs22
-rw-r--r--third_party/rust/nss-gk-api/src/ssl.rs157
-rw-r--r--third_party/rust/nss-gk-api/src/time.rs255
-rw-r--r--third_party/rust/nss-gk-api/src/util.rs246
9 files changed, 1350 insertions, 0 deletions
diff --git a/third_party/rust/nss-gk-api/src/err.rs b/third_party/rust/nss-gk-api/src/err.rs
new file mode 100644
index 0000000000..cc52b51f63
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/err.rs
@@ -0,0 +1,257 @@
+// 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)]
+#![allow(clippy::upper_case_acronyms)]
+
+use std::os::raw::c_char;
+use std::str::Utf8Error;
+
+use crate::nss_prelude::*;
+use crate::prtypes::*;
+
+include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
+mod codes {
+ #![allow(non_snake_case)]
+ include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
+ include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
+ include!(concat!(env!("OUT_DIR"), "/mozpkix.rs"));
+}
+pub use codes::mozilla_pkix_ErrorCode as mozpkix;
+pub use codes::SECErrorCodes as sec;
+pub use codes::SSLErrorCodes as ssl;
+pub mod nspr {
+ include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
+}
+
+pub type Res<T> = Result<T, Error>;
+
+#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
+pub enum Error {
+ AeadError,
+ CertificateLoading,
+ CipherInitFailure,
+ CreateSslSocket,
+ EchRetry(Vec<u8>),
+ HkdfError,
+ InternalError,
+ IntegerOverflow,
+ InvalidEpoch,
+ MixedHandshakeMethod,
+ NoDataAvailable,
+ NssError {
+ name: String,
+ code: PRErrorCode,
+ desc: String,
+ },
+ OverrunError,
+ SelfEncryptFailure,
+ StringError,
+ TimeTravelError,
+ UnsupportedCipher,
+ UnsupportedVersion,
+}
+
+impl Error {
+ pub(crate) fn last_nss_error() -> Self {
+ Self::from(unsafe { PR_GetError() })
+ }
+}
+
+impl std::error::Error for Error {
+ #[must_use]
+ fn cause(&self) -> Option<&dyn std::error::Error> {
+ None
+ }
+ #[must_use]
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Error: {:?}", self)
+ }
+}
+
+impl From<std::num::TryFromIntError> for Error {
+ #[must_use]
+ fn from(_: std::num::TryFromIntError) -> Self {
+ Self::IntegerOverflow
+ }
+}
+impl From<std::ffi::NulError> for Error {
+ #[must_use]
+ fn from(_: std::ffi::NulError) -> Self {
+ Self::InternalError
+ }
+}
+impl From<Utf8Error> for Error {
+ fn from(_: Utf8Error) -> Self {
+ Self::StringError
+ }
+}
+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) },
+ "...",
+ );
+ Self::NssError { name, code, 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 is_blocked(result: &Res<()>) -> bool {
+ match result {
+ Err(Error::NssError { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR,
+ _ => false,
+ }
+}
+
+pub trait IntoResult
+{
+ /// The `Ok` type for the result.
+ type Ok;
+
+ /// Unsafe in our implementors because they take a pointer and have no way
+ /// to ensure that the pointer is valid. An invalid pointer could cause UB
+ /// in `impl Drop for Scoped`.
+ unsafe fn into_result(self) -> Result<Self::Ok, Error>;
+}
+
+pub unsafe fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> {
+ if ptr.is_null() {
+ Err(Error::last_nss_error())
+ } else {
+ Ok(ptr)
+ }
+}
+
+// This can be used to implement `IntoResult` for pointer types that do not make
+// sense as smart pointers. For smart pointers use `scoped_ptr!`.
+macro_rules! impl_into_result {
+ ($pointer:ty) => {
+ impl $crate::err::IntoResult for *mut $pointer {
+ type Ok = *mut $pointer;
+
+ unsafe fn into_result(self) -> Result<Self::Ok, $crate::err::Error> {
+ $crate::err::into_result(self)
+ }
+ }
+ }
+}
+
+impl IntoResult for SECStatus {
+ type Ok = ();
+
+ unsafe fn into_result(self) -> Result<(), Error> {
+ if self == SECSuccess {
+ Ok(())
+ } else {
+ Err(Error::last_nss_error())
+ }
+ }
+}
+
+pub fn secstatus_to_res(code: SECStatus) -> Res<()> {
+ // Unsafe in the trait, but this impl should be safe.
+ unsafe { SECStatus::into_result(code) }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::err::{self, is_blocked, secstatus_to_res, Error, PRErrorCode, PR_SetError};
+ use crate::ssl::{SECFailure, SECSuccess};
+ use test_fixture::fixture_init;
+
+ fn set_error_code(code: PRErrorCode) {
+ // This code doesn't work without initializing NSS first.
+ fixture_init();
+ unsafe {
+ PR_SetError(code, 0);
+ }
+ }
+
+ #[test]
+ fn error_code() {
+ fixture_init();
+ assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ);
+ assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL);
+ assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR);
+ }
+
+ #[test]
+ fn is_ok() {
+ assert!(secstatus_to_res(SECSuccess).is_ok());
+ }
+
+ #[test]
+ fn is_err() {
+ set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ match r.unwrap_err() {
+ Error::NssError { name, code, desc } => {
+ assert_eq!(name, "SSL_ERROR_BAD_MAC_READ");
+ assert_eq!(code, -12273);
+ assert_eq!(
+ desc,
+ "SSL received a record with an incorrect Message Authentication Code."
+ );
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn is_err_zero_code() {
+ set_error_code(0);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ match r.unwrap_err() {
+ Error::NssError { name, code, .. } => {
+ assert_eq!(name, "UNKNOWN_ERROR");
+ assert_eq!(code, 0);
+ // Note that we don't test |desc| here because that comes from
+ // strerror(0), which is platform-dependent.
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn blocked() {
+ set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR);
+ let r = secstatus_to_res(SECFailure);
+ assert!(r.is_err());
+ assert!(is_blocked(&r));
+ match r.unwrap_err() {
+ Error::NssError { name, code, desc } => {
+ assert_eq!(name, "PR_WOULD_BLOCK_ERROR");
+ assert_eq!(code, -5998);
+ assert_eq!(desc, "The operation would have blocked");
+ }
+ _ => panic!("bad error type"),
+ }
+ }
+}
diff --git a/third_party/rust/nss-gk-api/src/exp.rs b/third_party/rust/nss-gk-api/src/exp.rs
new file mode 100644
index 0000000000..9f2255c1dc
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/exp.rs
@@ -0,0 +1,25 @@
+// 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.
+
+/* TODO
+macro_rules! experimental_api {
+ ( $n:ident ( $( $a:ident : $t:ty ),* $(,)? ) ) => {
+ #[allow(non_snake_case)]
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) unsafe fn $n ( $( $a : $t ),* ) -> Result<(), crate::err::Error> {
+ const EXP_FUNCTION: &str = stringify!($n);
+ let n = ::std::ffi::CString::new(EXP_FUNCTION)?;
+ let f = crate::ssl::SSL_GetExperimentalAPI(n.as_ptr());
+ if f.is_null() {
+ return Err(crate::err::Error::InternalError);
+ }
+ let f: unsafe extern "C" fn( $( $t ),* ) -> crate::SECStatus = ::std::mem::transmute(f);
+ let rv = f( $( $a ),* );
+ crate::err::secstatus_to_res(rv)
+ }
+ };
+}
+*/
diff --git a/third_party/rust/nss-gk-api/src/lib.rs b/third_party/rust/nss-gk-api/src/lib.rs
new file mode 100644
index 0000000000..1cd0f2a6e8
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/lib.rs
@@ -0,0 +1,173 @@
+// 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.
+
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+// Bindgen auto generated code
+// won't adhere to the clippy rules below
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::unseparated_literal_suffix)]
+#![allow(clippy::used_underscore_binding)]
+
+#[macro_use]
+pub mod err;
+#[macro_use]
+mod exp;
+#[macro_use]
+mod util;
+
+pub mod p11;
+mod prio;
+mod ssl;
+pub mod time;
+
+pub use err::{Error, IntoResult, secstatus_to_res};
+pub use p11::{PrivateKey, PublicKey, SymKey};
+pub use util::*;
+
+use once_cell::sync::OnceCell;
+
+use std::ffi::CString;
+use std::path::{Path, PathBuf};
+use std::ptr::null;
+
+const MINIMUM_NSS_VERSION: &str = "3.74";
+
+#[allow(non_snake_case)]
+#[allow(non_upper_case_globals)]
+pub mod nss_prelude {
+ pub use crate::prtypes::*;
+ pub use _SECStatus::*;
+ include!(concat!(env!("OUT_DIR"), "/nss_prelude.rs"));
+}
+pub use nss_prelude::{SECItem, SECItemArray, SECItemType, SECStatus};
+
+#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)]
+#[allow(clippy::upper_case_acronyms)]
+#[allow(unknown_lints, clippy::borrow_as_ptr)]
+mod nss {
+ use crate::nss_prelude::*;
+ include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+pub mod prtypes;
+pub use prtypes::*;
+
+// Shadow these bindgen created values to correct their type.
+pub const PR_FALSE: PRBool = prtypes::PR_FALSE as PRBool;
+pub const PR_TRUE: PRBool = prtypes::PR_TRUE as PRBool;
+
+enum NssLoaded {
+ External,
+ NoDb,
+ Db(Box<Path>),
+}
+
+impl Drop for NssLoaded {
+ fn drop(&mut self) {
+ if !matches!(self, Self::External) {
+ unsafe {
+ secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed");
+ }
+ }
+ }
+}
+
+static INITIALIZED: OnceCell<NssLoaded> = OnceCell::new();
+
+fn already_initialized() -> bool {
+ unsafe { nss::NSS_IsInitialized() != 0 }
+}
+
+fn version_check() {
+ let min_ver = CString::new(MINIMUM_NSS_VERSION).unwrap();
+ assert_ne!(
+ unsafe { nss::NSS_VersionCheck(min_ver.as_ptr()) },
+ 0,
+ "Minimum NSS version of {} not supported",
+ MINIMUM_NSS_VERSION,
+ );
+}
+
+/// Initialize NSS. This only executes the initialization routines once, so if there is any chance that
+pub fn init() {
+ // Set time zero.
+ time::init();
+ INITIALIZED.get_or_init(|| {
+ unsafe {
+ version_check();
+ if already_initialized() {
+ return NssLoaded::External;
+ }
+
+ secstatus_to_res(nss::NSS_NoDB_Init(null())).expect("NSS_NoDB_Init failed");
+ secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed");
+
+ NssLoaded::NoDb
+ }
+ });
+}
+
+/// This enables SSLTRACE by calling a simple, harmless function to trigger its
+/// side effects. SSLTRACE is not enabled in NSS until a socket is made or
+/// global options are accessed. Reading an option is the least impact approach.
+/// This allows us to use SSLTRACE in all of our unit tests and programs.
+#[cfg(debug_assertions)]
+fn enable_ssl_trace() {
+ let opt = ssl::Opt::Locking.as_int();
+ let mut v: ::std::os::raw::c_int = 0;
+ secstatus_to_res(unsafe { ssl::SSL_OptionGetDefault(opt, &mut v) })
+ .expect("SSL_OptionGetDefault failed");
+}
+
+/// Initialize with a database.
+/// # Panics
+/// If NSS cannot be initialized.
+pub fn init_db<P: Into<PathBuf>>(dir: P) {
+ time::init();
+ INITIALIZED.get_or_init(|| {
+ unsafe {
+ version_check();
+ if already_initialized() {
+ return NssLoaded::External;
+ }
+
+ let path = dir.into();
+ assert!(path.is_dir());
+ let pathstr = path.to_str().expect("path converts to string").to_string();
+ let dircstr = CString::new(pathstr).unwrap();
+ let empty = CString::new("").unwrap();
+ secstatus_to_res(nss::NSS_Initialize(
+ dircstr.as_ptr(),
+ empty.as_ptr(),
+ empty.as_ptr(),
+ nss::SECMOD_DB.as_ptr().cast(),
+ nss::NSS_INIT_READONLY,
+ ))
+ .expect("NSS_Initialize failed");
+
+ secstatus_to_res(nss::NSS_SetDomesticPolicy()).expect("NSS_SetDomesticPolicy failed");
+ secstatus_to_res(ssl::SSL_ConfigServerSessionIDCache(
+ 1024,
+ 0,
+ 0,
+ dircstr.as_ptr(),
+ ))
+ .expect("SSL_ConfigServerSessionIDCache failed");
+
+ #[cfg(debug_assertions)]
+ enable_ssl_trace();
+
+ NssLoaded::Db(path.into_boxed_path())
+ }
+ });
+}
+
+/// # Panics
+/// If NSS isn't initialized.
+pub fn assert_initialized() {
+ INITIALIZED.get().expect("NSS not initialized with init or init_db");
+}
diff --git a/third_party/rust/nss-gk-api/src/p11.rs b/third_party/rust/nss-gk-api/src/p11.rs
new file mode 100644
index 0000000000..5eb6ea040c
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/p11.rs
@@ -0,0 +1,194 @@
+// 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)]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use crate::err::{secstatus_to_res, Error, Res};
+use crate::util::SECItemMut;
+
+use pkcs11_bindings::CKA_VALUE;
+
+use std::convert::TryFrom;
+use std::os::raw::{c_int, c_uint};
+
+#[must_use]
+pub fn hex_with_len(buf: impl AsRef<[u8]>) -> String {
+ use std::fmt::Write;
+ let buf = buf.as_ref();
+ let mut ret = String::with_capacity(10 + buf.len() * 2);
+ write!(&mut ret, "[{}]: ", buf.len()).unwrap();
+ for b in buf {
+ write!(&mut ret, "{:02x}", b).unwrap();
+ }
+ ret
+}
+
+#[allow(clippy::upper_case_acronyms)]
+#[allow(clippy::unreadable_literal)]
+#[allow(unknown_lints, clippy::borrow_as_ptr)]
+mod nss_p11 {
+ use crate::prtypes::*;
+ use crate::nss_prelude::*;
+ include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
+}
+
+use crate::prtypes::*;
+pub use nss_p11::*;
+
+// Shadow these bindgen created values to correct their type.
+pub const SHA256_LENGTH: usize = nss_p11::SHA256_LENGTH as usize;
+pub const AES_BLOCK_SIZE: usize = nss_p11::AES_BLOCK_SIZE as usize;
+
+scoped_ptr!(Certificate, CERTCertificate, CERT_DestroyCertificate);
+scoped_ptr!(CertList, CERTCertList, CERT_DestroyCertList);
+
+scoped_ptr!(SubjectPublicKeyInfo, CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo);
+
+scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey);
+impl_clone!(PublicKey, SECKEY_CopyPublicKey);
+
+impl PublicKey {
+ /// Get the HPKE serialization of the public key.
+ ///
+ /// # Errors
+ /// When the key cannot be exported, which can be because the type is not supported.
+ /// # Panics
+ /// When keys are too large to fit in `c_uint/usize`. So only on programming error.
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut buf = vec![0; 100];
+ let mut len: c_uint = 0;
+ secstatus_to_res(unsafe {
+ 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)
+ }
+}
+
+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_with_len(b))
+ } else {
+ write!(f, "Opaque PublicKey")
+ }
+ }
+}
+
+scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey);
+impl_clone!(PrivateKey, SECKEY_CopyPrivateKey);
+
+impl PrivateKey {
+ /// Get the bits of the private key.
+ ///
+ /// # Errors
+ /// When the key cannot be exported, which can be because the type is not supported
+ /// or because the key data cannot be extracted from the PKCS#11 module.
+ /// # Panics
+ /// When the values are too large to fit. So never.
+ pub fn key_data(&self) -> Res<Vec<u8>> {
+ let mut key_item = SECItemMut::make_empty();
+ secstatus_to_res(unsafe {
+ PK11_ReadRawAttribute(
+ PK11ObjectType::PK11_TypePrivKey,
+ (**self).cast(),
+ CKA_VALUE,
+ key_item.as_mut(),
+ )
+ })?;
+ Ok(key_item.as_slice().to_owned())
+ }
+}
+
+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_with_len(b))
+ } else {
+ write!(f, "Opaque PrivateKey")
+ }
+ }
+}
+
+scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
+
+impl Slot {
+ pub fn internal() -> Res<Self> {
+ unsafe { Slot::from_ptr(PK11_GetInternalSlot()) }
+ }
+}
+
+// Note: PK11SymKey is internally reference counted
+scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
+impl_clone!(SymKey, PK11_ReferenceSymKey);
+
+impl SymKey {
+ /// You really don't want to use this.
+ ///
+ /// # Errors
+ /// Internal errors in case of failures in NSS.
+ pub fn as_bytes(&self) -> Res<&[u8]> {
+ secstatus_to_res(unsafe { PK11_ExtractKeyValue(**self) })?;
+
+ let key_item = unsafe { PK11_GetKeyData(**self) };
+ // 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::InternalError),
+ Some(key) => Ok(unsafe { std::slice::from_raw_parts(key.data, key.len as usize) }),
+ }
+ }
+}
+
+impl std::fmt::Debug for SymKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Ok(b) = self.as_bytes() {
+ write!(f, "SymKey {}", hex_with_len(b))
+ } else {
+ write!(f, "Opaque SymKey")
+ }
+ }
+}
+
+unsafe fn destroy_pk11_context(ctxt: *mut PK11Context) {
+ PK11_DestroyContext(ctxt, PRBool::from(true));
+}
+scoped_ptr!(Context, PK11Context, destroy_pk11_context);
+
+/// Generate a randomized buffer.
+/// # Panics
+/// When `size` is too large or NSS fails.
+#[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
+}
+
+impl_into_result!(SECOidData);
+
+#[cfg(test)]
+mod test {
+ use super::random;
+ use test_fixture::fixture_init;
+
+ #[test]
+ fn randomness() {
+ fixture_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/nss-gk-api/src/prio.rs b/third_party/rust/nss-gk-api/src/prio.rs
new file mode 100644
index 0000000000..8468e08eb3
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/prio.rs
@@ -0,0 +1,21 @@
+// 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(clippy::upper_case_acronyms)]
+#![allow(
+ dead_code,
+ non_upper_case_globals,
+ non_snake_case,
+ clippy::cognitive_complexity,
+ clippy::empty_enum,
+ clippy::too_many_lines,
+ unknown_lints,
+ clippy::borrow_as_ptr
+)]
+
+use crate::prtypes::*;
+
+include!(concat!(env!("OUT_DIR"), "/nspr_io.rs"));
diff --git a/third_party/rust/nss-gk-api/src/prtypes.rs b/third_party/rust/nss-gk-api/src/prtypes.rs
new file mode 100644
index 0000000000..2416ec5ddc
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/prtypes.rs
@@ -0,0 +1,22 @@
+// 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(clippy::upper_case_acronyms)]
+#![allow(
+ dead_code,
+ non_upper_case_globals,
+ non_snake_case,
+ clippy::cognitive_complexity,
+ clippy::empty_enum,
+ clippy::too_many_lines,
+ unknown_lints,
+ clippy::borrow_as_ptr
+)]
+
+pub use PRStatus_PR_FAILURE as PR_FAILURE;
+pub use PRStatus_PR_SUCCESS as PR_SUCCESS;
+
+include!(concat!(env!("OUT_DIR"), "/nspr_types.rs"));
diff --git a/third_party/rust/nss-gk-api/src/ssl.rs b/third_party/rust/nss-gk-api/src/ssl.rs
new file mode 100644
index 0000000000..1d74154bc7
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/ssl.rs
@@ -0,0 +1,157 @@
+// 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,
+ non_camel_case_types,
+ non_upper_case_globals,
+ non_snake_case,
+ clippy::cognitive_complexity,
+ clippy::too_many_lines,
+ clippy::upper_case_acronyms,
+ unknown_lints,
+ clippy::borrow_as_ptr
+)]
+
+use crate::err::{secstatus_to_res, Res};
+use crate::nss_prelude::*;
+use crate::prio::PRFileDesc;
+
+mod nss_ssl {
+ use crate::err::PRErrorCode;
+ use crate::nss_prelude::*;
+ use crate::p11::CERTCertList;
+ use crate::prio::{
+ PRFileDesc,
+ PRFileInfo,
+ PRFileInfo64,
+ PRIOVec,
+ };
+
+ include!(concat!(env!("OUT_DIR"), "/nss_ssl.rs"));
+}
+pub use nss_ssl::*;
+
+mod SSLOption {
+ include!(concat!(env!("OUT_DIR"), "/nss_sslopt.rs"));
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Opt {
+ Locking,
+ Tickets,
+ OcspStapling,
+ Alpn,
+ ExtendedMasterSecret,
+ SignedCertificateTimestamps,
+ EarlyData,
+ RecordSizeLimit,
+ Tls13CompatMode,
+ HelloDowngradeCheck,
+ SuppressEndOfEarlyData,
+}
+
+impl Opt {
+ // Cast is safe here because SSLOptions are within the i32 range
+ #[allow(clippy::cast_possible_wrap)]
+ pub(crate) fn as_int(self) -> PRInt32 {
+ let i = match self {
+ Self::Locking => SSLOption::SSL_NO_LOCKS,
+ Self::Tickets => SSLOption::SSL_ENABLE_SESSION_TICKETS,
+ Self::OcspStapling => SSLOption::SSL_ENABLE_OCSP_STAPLING,
+ Self::Alpn => SSLOption::SSL_ENABLE_ALPN,
+ Self::ExtendedMasterSecret => SSLOption::SSL_ENABLE_EXTENDED_MASTER_SECRET,
+ Self::SignedCertificateTimestamps => SSLOption::SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+ Self::EarlyData => SSLOption::SSL_ENABLE_0RTT_DATA,
+ Self::RecordSizeLimit => SSLOption::SSL_RECORD_SIZE_LIMIT,
+ Self::Tls13CompatMode => SSLOption::SSL_ENABLE_TLS13_COMPAT_MODE,
+ Self::HelloDowngradeCheck => SSLOption::SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
+ Self::SuppressEndOfEarlyData => SSLOption::SSL_SUPPRESS_END_OF_EARLY_DATA,
+ };
+ i as PRInt32
+ }
+
+ // Some options are backwards, like SSL_NO_LOCKS, so use this to manage that.
+ fn map_enabled(self, enabled: bool) -> PRIntn {
+ let v = match self {
+ Self::Locking => !enabled,
+ _ => enabled,
+ };
+ PRIntn::from(v)
+ }
+
+ pub(crate) fn set(self, fd: *mut PRFileDesc, value: bool) -> Res<()> {
+ secstatus_to_res(unsafe { SSL_OptionSet(fd, self.as_int(), self.map_enabled(value)) })
+ }
+}
+
+/*
+ * TODO: these will be moved to a dedicated module
+ *
+experimental_api!(SSL_GetCurrentEpoch(
+ fd: *mut PRFileDesc,
+ read_epoch: *mut u16,
+ write_epoch: *mut u16,
+));
+experimental_api!(SSL_HelloRetryRequestCallback(
+ fd: *mut PRFileDesc,
+ cb: SSLHelloRetryRequestCallback,
+ arg: *mut c_void,
+));
+experimental_api!(SSL_RecordLayerWriteCallback(
+ fd: *mut PRFileDesc,
+ cb: SSLRecordWriteCallback,
+ arg: *mut c_void,
+));
+experimental_api!(SSL_RecordLayerData(
+ fd: *mut PRFileDesc,
+ epoch: Epoch,
+ ct: SSLContentType::Type,
+ data: *const u8,
+ len: c_uint,
+));
+experimental_api!(SSL_SendSessionTicket(
+ fd: *mut PRFileDesc,
+ extra: *const u8,
+ len: c_uint,
+));
+experimental_api!(SSL_SetMaxEarlyDataSize(fd: *mut PRFileDesc, size: u32));
+experimental_api!(SSL_SetResumptionToken(
+ fd: *mut PRFileDesc,
+ token: *const u8,
+ len: c_uint,
+));
+experimental_api!(SSL_SetResumptionTokenCallback(
+ fd: *mut PRFileDesc,
+ cb: SSLResumptionTokenCallback,
+ arg: *mut c_void,
+));
+
+experimental_api!(SSL_GetResumptionTokenInfo(
+ token: *const u8,
+ token_len: c_uint,
+ info: *mut SSLResumptionTokenInfo,
+ len: c_uint,
+));
+
+experimental_api!(SSL_DestroyResumptionTokenInfo(
+ info: *mut SSLResumptionTokenInfo,
+));
+*/
+
+#[cfg(test)]
+mod tests {
+ use super::{SSL_GetNumImplementedCiphers, SSL_NumImplementedCiphers};
+
+ #[test]
+ fn num_ciphers() {
+ assert!(unsafe { SSL_NumImplementedCiphers } > 0);
+ assert!(unsafe { SSL_GetNumImplementedCiphers() } > 0);
+ assert_eq!(unsafe { SSL_NumImplementedCiphers }, unsafe {
+ SSL_GetNumImplementedCiphers()
+ });
+ }
+}
diff --git a/third_party/rust/nss-gk-api/src/time.rs b/third_party/rust/nss-gk-api/src/time.rs
new file mode 100644
index 0000000000..1abc276dda
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/time.rs
@@ -0,0 +1,255 @@
+// 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(clippy::upper_case_acronyms)]
+
+use crate::nss_prelude::PRInt64;
+use crate::err::{Error, Res};
+//use crate::prio::PRFileDesc;
+//use crate::ssl::SSLTimeFunc;
+
+use once_cell::sync::OnceCell;
+//use std::boxed::Box;
+use std::convert::{TryFrom, TryInto};
+use std::ops::Deref;
+//use std::os::raw::c_void;
+//use std::pin::Pin;
+use std::time::{Duration, Instant};
+
+include!(concat!(env!("OUT_DIR"), "/nspr_time.rs"));
+
+/* TODO move to exp module
+experimental_api!(SSL_SetTimeFunc(
+ fd: *mut PRFileDesc,
+ cb: SSLTimeFunc,
+ arg: *mut c_void,
+));
+*/
+
+/// This struct holds the zero time used for converting between `Instant` and `PRTime`.
+#[derive(Debug)]
+struct TimeZero {
+ instant: Instant,
+ prtime: PRTime,
+}
+
+impl TimeZero {
+ /// This function sets a baseline from an instance of `Instant`.
+ /// This allows for the possibility that code that uses these APIs will create
+ /// instances of `Instant` before any of this code is run. If `Instant`s older than
+ /// `BASE_TIME` are used with these conversion functions, they will fail.
+ /// To avoid that, we make sure that this sets the base time using the first value
+ /// it sees if it is in the past. If it is not, then use `Instant::now()` instead.
+ pub fn baseline(t: Instant) -> Self {
+ let now = Instant::now();
+ let prnow = unsafe { PR_Now() };
+
+ if now <= t {
+ // `t` is in the future, just use `now`.
+ Self {
+ instant: now,
+ prtime: prnow,
+ }
+ } else {
+ let elapsed = Interval::from(now.duration_since(now));
+ // An error from these unwrap functions would require
+ // ridiculously long application running time.
+ let prelapsed: PRTime = elapsed.try_into().unwrap();
+ Self {
+ instant: t,
+ prtime: prnow.checked_sub(prelapsed).unwrap(),
+ }
+ }
+ }
+}
+
+static BASE_TIME: OnceCell<TimeZero> = OnceCell::new();
+
+fn get_base() -> &'static TimeZero {
+ BASE_TIME.get_or_init(|| TimeZero {
+ instant: Instant::now(),
+ prtime: unsafe { PR_Now() },
+ })
+}
+
+pub fn init() {
+ let _ = get_base();
+}
+
+/// Time wraps Instant and provides conversion functions into `PRTime`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Time {
+ t: Instant,
+}
+
+impl Deref for Time {
+ type Target = Instant;
+ fn deref(&self) -> &Self::Target {
+ &self.t
+ }
+}
+
+impl From<Instant> for Time {
+ /// Convert from an Instant into a Time.
+ fn from(t: Instant) -> Self {
+ // Call `TimeZero::baseline(t)` so that time zero can be set.
+ BASE_TIME.get_or_init(|| TimeZero::baseline(t));
+ Self { t }
+ }
+}
+
+impl TryFrom<PRTime> for Time {
+ type Error = Error;
+ fn try_from(prtime: PRTime) -> Res<Self> {
+ let base = get_base();
+ if let Some(delta) = prtime.checked_sub(base.prtime) {
+ let d = Duration::from_micros(delta.try_into()?);
+ base.instant
+ .checked_add(d)
+ .map_or(Err(Error::TimeTravelError), |t| Ok(Self { t }))
+ } else {
+ Err(Error::TimeTravelError)
+ }
+ }
+}
+
+impl TryInto<PRTime> for Time {
+ type Error = Error;
+ fn try_into(self) -> Res<PRTime> {
+ let base = get_base();
+ let delta = self
+ .t
+ .checked_duration_since(base.instant)
+ .ok_or(Error::TimeTravelError)?;
+ if let Ok(d) = PRTime::try_from(delta.as_micros()) {
+ d.checked_add(base.prtime).ok_or(Error::TimeTravelError)
+ } else {
+ Err(Error::TimeTravelError)
+ }
+ }
+}
+
+impl From<Time> for Instant {
+ #[must_use]
+ fn from(t: Time) -> Self {
+ t.t
+ }
+}
+
+/// Interval wraps Duration and provides conversion functions into `PRTime`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Interval {
+ d: Duration,
+}
+
+impl Deref for Interval {
+ type Target = Duration;
+ fn deref(&self) -> &Self::Target {
+ &self.d
+ }
+}
+
+impl TryFrom<PRTime> for Interval {
+ type Error = Error;
+ fn try_from(prtime: PRTime) -> Res<Self> {
+ Ok(Self {
+ d: Duration::from_micros(u64::try_from(prtime)?),
+ })
+ }
+}
+
+impl From<Duration> for Interval {
+ fn from(d: Duration) -> Self {
+ Self { d }
+ }
+}
+
+impl TryInto<PRTime> for Interval {
+ type Error = Error;
+ fn try_into(self) -> Res<PRTime> {
+ Ok(PRTime::try_from(self.d.as_micros())?)
+ }
+}
+
+/* TODO: restore, experimental only.
+/// `TimeHolder` maintains a `PRTime` value in a form that is accessible to the TLS stack.
+#[derive(Debug)]
+pub struct TimeHolder {
+ t: Pin<Box<PRTime>>,
+}
+
+impl TimeHolder {
+ unsafe extern "C" fn time_func(arg: *mut c_void) -> PRTime {
+ let p = arg as *const PRTime;
+ *p.as_ref().unwrap()
+ }
+
+ pub fn bind(&mut self, fd: *mut PRFileDesc) -> Res<()> {
+ unsafe { SSL_SetTimeFunc(fd, Some(Self::time_func), &mut *self.t as *mut _ as *mut c_void) }
+ }
+
+ pub fn set(&mut self, t: Instant) -> Res<()> {
+ *self.t = Time::from(t).try_into()?;
+ Ok(())
+ }
+}
+
+impl Default for TimeHolder {
+ fn default() -> Self {
+ TimeHolder { t: Box::pin(0) }
+ }
+}
+*/
+
+#[cfg(test)]
+mod test {
+ use super::{get_base, init, Interval, PRTime, Time};
+ use crate::err::Res;
+ use std::convert::{TryFrom, TryInto};
+ use std::time::{Duration, Instant};
+
+ #[test]
+ fn convert_stable() {
+ init();
+ let now = Time::from(Instant::now());
+ let pr: PRTime = now.try_into().expect("convert to PRTime with truncation");
+ let t2 = Time::try_from(pr).expect("convert to Instant");
+ let pr2: PRTime = t2.try_into().expect("convert to PRTime again");
+ assert_eq!(pr, pr2);
+ let t3 = Time::try_from(pr2).expect("convert to Instant again");
+ assert_eq!(t2, t3);
+ }
+
+ #[test]
+ fn past_time() {
+ init();
+ let base = get_base();
+ assert!(Time::try_from(base.prtime - 1).is_err());
+ }
+
+ #[test]
+ fn negative_time() {
+ init();
+ assert!(Time::try_from(-1).is_err());
+ }
+
+ #[test]
+ fn negative_interval() {
+ init();
+ assert!(Interval::try_from(-1).is_err());
+ }
+
+ #[test]
+ // We allow replace_consts here because
+ // std::u64::max_value() isn't available
+ // in all of our targets
+ fn overflow_interval() {
+ init();
+ let interval = Interval::from(Duration::from_micros(u64::max_value()));
+ let res: Res<PRTime> = interval.try_into();
+ assert!(res.is_err());
+ }
+}
diff --git a/third_party/rust/nss-gk-api/src/util.rs b/third_party/rust/nss-gk-api/src/util.rs
new file mode 100644
index 0000000000..f06542eb45
--- /dev/null
+++ b/third_party/rust/nss-gk-api/src/util.rs
@@ -0,0 +1,246 @@
+// 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 std::convert::TryFrom;
+use std::marker::PhantomData;
+use std::mem;
+use std::os::raw::c_uint;
+use std::ptr::null_mut;
+
+use crate::prtypes::*;
+use crate::nss_prelude::*;
+
+/// Implement a smart pointer for NSS objects.
+///
+/// Most of the time the pointer is like a `Box`, but there are exceptions (e.g.
+/// PK11SymKey is internally reference counted so its pointer is like an `Arc`.)
+///
+/// Named "scoped" because that is what NSS calls its `unique_ptr` typedefs.
+macro_rules! scoped_ptr {
+ ($name:ident, $target:ty, $dtor:path) => {
+ pub struct $name {
+ ptr: *mut $target,
+ }
+
+ impl $name {
+ /// Create a new instance of `$name` from a pointer.
+ ///
+ /// # Errors
+ /// When passed a null pointer generates an error.
+ pub unsafe fn from_ptr(raw: *mut $target) -> Result<Self, $crate::err::Error> {
+ let ptr = $crate::err::into_result(raw)?;
+ Ok(Self { ptr })
+ }
+ }
+
+ impl $crate::err::IntoResult for *mut $target {
+ type Ok = $name;
+
+ unsafe fn into_result(self) -> Result<Self::Ok, $crate::err::Error> {
+ $name::from_ptr(self)
+ }
+ }
+
+ impl std::ops::Deref for $name {
+ type Target = *mut $target;
+ #[must_use]
+ fn deref(&self) -> &*mut $target {
+ &self.ptr
+ }
+ }
+
+ // Original implements DerefMut, but is that really a good idea?
+
+ impl Drop for $name {
+ fn drop(&mut self) {
+ unsafe { $dtor(self.ptr) };
+ }
+ }
+ }
+}
+
+macro_rules! impl_clone {
+ ($name:ty, $nss_fn:path) => {
+ impl Clone for $name {
+ #[must_use]
+ fn clone(&self) -> Self {
+ let ptr = unsafe { $nss_fn(self.ptr) };
+ assert!(!ptr.is_null());
+ Self { ptr }
+ }
+ }
+ }
+}
+
+impl SECItem {
+ /// Return contents as a slice.
+ ///
+ /// Unsafe due to calling from_raw_parts, or if 'a outlives &self. This
+ /// unsafety is encapsulated by the `as_slice` method of `SECItemBorrowed`
+ /// and `SECItemMut`.
+ ///
+ /// Note that safe code can construct a SECItem pointing to anything. The
+ /// same is not true of the safe wrappers `SECItemMut` and `SECItemBorrowed`
+ /// because their inner SECItem is private.
+ pub unsafe fn as_slice<'a>(&self) -> &'a [u8] {
+ // Sanity check the type, as some types don't count bytes in `Item::len`.
+ assert_eq!(self.type_, SECItemType::siBuffer);
+ // Note: `from_raw_parts` requires non-null `data` even for zero-length
+ // slices.
+ if self.len != 0 {
+ std::slice::from_raw_parts(self.data, usize::try_from(self.len).unwrap())
+ } else {
+ &[]
+ }
+ }
+}
+
+/// An owned SECItem.
+///
+/// The SECItem structure is allocated by Rust. The buffer referenced by the
+/// SECItem is allocated by NSS. `SECITEM_FreeItem` will be called to free the
+/// buffer when the SECItemMut is dropped.
+///
+/// This is used with NSS functions that return a variable amount of data.
+#[repr(transparent)]
+pub struct SECItemMut {
+ inner: SECItem,
+}
+
+impl<'a> Drop for SECItemMut {
+ #[allow(unused_must_use)]
+ fn drop(&mut self) {
+ // FreeItem unconditionally frees the buffer referenced by the SECItem.
+ // If the second argument is true, it also frees the SECItem itself,
+ // which we don't want to do, because rust owns that memory.
+ unsafe { SECITEM_FreeItem(&mut self.inner, PRBool::from(false)) };
+ }
+}
+
+impl AsRef<SECItem> for SECItemMut {
+ fn as_ref(&self) -> &SECItem {
+ &self.inner
+ }
+}
+
+impl AsMut<SECItem> for SECItemMut {
+ fn as_mut(&mut self) -> &mut SECItem {
+ &mut self.inner
+ }
+}
+
+impl SECItemMut {
+ /// Return contents as a slice.
+ pub fn as_slice(&self) -> &[u8] {
+ unsafe { self.inner.as_slice() }
+ }
+
+ /// Make an empty `SECItemMut` for passing as a mutable `*SECItem` argument.
+ pub fn make_empty() -> SECItemMut {
+ SECItemMut {
+ inner: SECItem {
+ type_: SECItemType::siBuffer,
+ data: null_mut(),
+ len: 0,
+ }
+ }
+ }
+}
+
+/// A borrowed SECItem.
+///
+/// The SECItem structure is allocated by Rust. The buffer referenced by the
+/// SECItem may be allocated either by Rust or NSS. The SECItem does not own the
+/// buffer and will not free it when dropped.
+///
+/// This is usually used to pass a reference to some borrowed rust memory to
+/// NSS. It is occasionally used to accept non-owned output data from NSS.
+#[repr(transparent)]
+pub struct SECItemBorrowed<'a> {
+ inner: SECItem,
+ phantom_data: PhantomData<&'a u8>,
+}
+
+impl<'a> AsRef<SECItem> for SECItemBorrowed<'a> {
+ fn as_ref(&self) -> &SECItem {
+ &self.inner
+ }
+}
+
+impl<'a> AsMut<SECItem> for SECItemBorrowed<'a> {
+ /// Get a mutable reference to the underlying SECItem struct.
+ ///
+ /// Note that even if the SECItem struct is mutable, the buffer it
+ /// references may not be. Take care not to pass the mutable
+ /// SECItem to NSS routines that will violate mutability rules.
+ //
+ // TODO: Should we make the danger more obvious, by using a non-trait method
+ // with "unsafe" in the name, or an unsafe method?
+ fn as_mut(&mut self) -> &mut SECItem {
+ &mut self.inner
+ }
+}
+
+impl<'a> SECItemBorrowed<'a> {
+ /// Return contents as a slice.
+ pub fn as_slice(&self) -> &'a [u8] {
+ unsafe { self.inner.as_slice() }
+ }
+
+ /// Create an empty `SECItemBorrowed`.
+ ///
+ /// This can be used (1) to pass an empty item as an argument, and (2) as an
+ /// output parameter when NSS returns a pointer to NSS-owned memory that
+ /// should not be freed when the SECItem is dropped. If the memory should
+ /// be freed when the SECItem is dropped, use SECItemMut.
+ ///
+ /// It is safe to let the caller specify any lifetime here because no
+ /// borrowing is actually taking place. However, if the pointer in the
+ /// returned item is modified, care must be taken that the specified
+ /// lifetime accurately reflects the data referenced by the pointer.
+ pub fn make_empty() -> SECItemBorrowed<'a> {
+ SECItemBorrowed {
+ inner: SECItem {
+ type_: SECItemType::siBuffer,
+ data: null_mut(),
+ len: 0,
+ },
+ phantom_data: PhantomData,
+ }
+ }
+
+ /// Create a `SECItemBorrowed` wrapping a slice.
+ ///
+ /// 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,
+ /// or those that treat their argument as `const`.
+ pub fn wrap(buf: &'a [u8]) -> SECItemBorrowed<'a> {
+ SECItemBorrowed {
+ inner: SECItem {
+ type_: SECItemType::siBuffer,
+ data: buf.as_ptr() as *mut u8,
+ len: c_uint::try_from(buf.len()).unwrap(),
+ },
+ phantom_data: PhantomData,
+ }
+ }
+
+ /// Create a `SECItemBorrowed` wrapping a struct.
+ ///
+ /// 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,
+ /// or those that treat their argument as `const`.
+ pub fn wrap_struct<T>(v: &'a T) -> SECItemBorrowed<'a> {
+ SECItemBorrowed {
+ inner: SECItem {
+ type_: SECItemType::siBuffer,
+ data: (v as *const T as *mut T).cast(),
+ len: c_uint::try_from(mem::size_of::<T>()).unwrap(),
+ },
+ phantom_data: PhantomData,
+ }
+ }
+}