diff options
Diffstat (limited to 'third_party/rust/nss-gk-api/src')
-rw-r--r-- | third_party/rust/nss-gk-api/src/err.rs | 257 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/exp.rs | 25 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/lib.rs | 173 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/p11.rs | 194 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/prio.rs | 21 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/prtypes.rs | 22 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/ssl.rs | 157 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/time.rs | 255 | ||||
-rw-r--r-- | third_party/rust/nss-gk-api/src/util.rs | 246 |
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, + } + } +} |