summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nss
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/nss
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/nss')
-rw-r--r--third_party/rust/nss/.cargo-checksum.json1
-rw-r--r--third_party/rust/nss/Cargo.toml21
-rw-r--r--third_party/rust/nss/README.md21
-rw-r--r--third_party/rust/nss/src/aes.rs120
-rw-r--r--third_party/rust/nss/src/ec.rs423
-rw-r--r--third_party/rust/nss/src/ecdh.rs46
-rw-r--r--third_party/rust/nss/src/error.rs24
-rw-r--r--third_party/rust/nss/src/lib.rs17
-rw-r--r--third_party/rust/nss/src/pbkdf2.rs78
-rw-r--r--third_party/rust/nss/src/pk11/context.rs123
-rw-r--r--third_party/rust/nss/src/pk11/mod.rs8
-rw-r--r--third_party/rust/nss/src/pk11/slot.rs25
-rw-r--r--third_party/rust/nss/src/pk11/sym_key.rs93
-rw-r--r--third_party/rust/nss/src/pk11/types.rs220
-rw-r--r--third_party/rust/nss/src/secport.rs23
-rw-r--r--third_party/rust/nss/src/util.rs129
16 files changed, 1372 insertions, 0 deletions
diff --git a/third_party/rust/nss/.cargo-checksum.json b/third_party/rust/nss/.cargo-checksum.json
new file mode 100644
index 0000000000..bfb33bc621
--- /dev/null
+++ b/third_party/rust/nss/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"17ff6446ce5ef3fb08620171d719241c6315571aa839d4b039f34a2ec9fa6fc4","README.md":"14dd59e435d179c21c3b4b880bbe3cc6e5999b9f9ac9431f3f9aa3f43902e3fa","src/aes.rs":"820a74d1c1b9b5c818f5e4c4b39afb4346e56b8512a0f280c0bd92b763f50486","src/ec.rs":"e5e95504b68f22d949df4c533e35246f0088bc87976fd7d829dcc15f57a84741","src/ecdh.rs":"6a970e6a30dfba4c5f4d113a5b5f3a814ee650a54eba903f8a50b47e180a1ceb","src/error.rs":"da4a39cef14403d3b34f2f4bbf1bb93e07dff0e4fa7e8e3c931604859c922ee4","src/lib.rs":"34950c67f33e6f10e0488fd1d8a4e9ba52b19a48d00b5f0e00b067d33dc60c0d","src/pbkdf2.rs":"d797520182e45fe8d0d076d76c80bcc6fbfaa767dc9ae3670ca9b5938c0bec6c","src/pk11/context.rs":"895dcf08ed59f47c3ae867cf5d8cc79a04df8b61ac702484fc85acf595f71980","src/pk11/mod.rs":"d78368654f9a8bc12f1403c4a096b63cf9834820ea6ed48418b9afaa0fc2299e","src/pk11/slot.rs":"9f0aa039a55e7b26dc2dd5d2d3451497af71d147513f59e9c89b1166e89b2dda","src/pk11/sym_key.rs":"6dd1bae6e4c97665d0535fd0165736a2174edcb316f068ac3a8c73e5d4c20509","src/pk11/types.rs":"60e5899ba89d13d055d529b3e6e355b8d02f0f037b8d0c076671617088833d0c","src/secport.rs":"cd85d4d22f995ed2c3162ec62af093c4b2b1deeb7bac42002d47d7d69e54cb1c","src/util.rs":"e9843ebb2bae1c343da0e5a0840aabfcdd743b83bb836a8b751b43afa6f43cd9"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/nss/Cargo.toml b/third_party/rust/nss/Cargo.toml
new file mode 100644
index 0000000000..0f779232b6
--- /dev/null
+++ b/third_party/rust/nss/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "nss"
+version = "0.1.0"
+authors = ["application-services@mozilla.com"]
+edition = "2018"
+license = "MPL-2.0"
+
+[lib]
+crate-type = ["lib"]
+
+[dependencies]
+base64 = "0.12"
+thiserror = "1.0"
+error-support = { path = "../../error" }
+nss_sys = { path = "nss_sys" }
+serde = "1"
+serde_derive = "1"
+
+[features]
+default = []
+gecko = ["nss_sys/gecko"]
diff --git a/third_party/rust/nss/README.md b/third_party/rust/nss/README.md
new file mode 100644
index 0000000000..5c8b626999
--- /dev/null
+++ b/third_party/rust/nss/README.md
@@ -0,0 +1,21 @@
+## nss
+
+This crate provides various cryptographic routines backed by
+[NSS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS).
+
+The API is designed to operate at approximately the same level of abstraction as the
+[`crypto.subtle`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) API, although the details are obviously
+different given the different host language. It provides:
+
+* Cryptographically secure [pseudorandom number generation](./src/pk11/slot.rs).
+* Cryptographic [digests](./src/pk11/context.rs) and [hkdf](./src/pk11/sym_key.rs).
+* [AES encryption and decryption](./src/aes.rs) in various modes.
+* Generation, import and export of [elliptic-curve keys](./src/ec.rs).
+* ECDH [key agreement](./src/ecdh.rs).
+* Constant-time [string comparison](./src/secport.rs).
+
+Like the `crypto.subtle` API, these primitives are quite low-level and involve some subtlety in order to use correctly.
+Consumers should prefer the higher-level abstractions offered by the [rc_crypto](../) crate where possible.
+
+These features are in turn built on even-lower-level bindings to the raw NSS API, provided by the [nss_sys](./nss_sys)
+crate.
diff --git a/third_party/rust/nss/src/aes.rs b/third_party/rust/nss/src/aes.rs
new file mode 100644
index 0000000000..280c2a66f2
--- /dev/null
+++ b/third_party/rust/nss/src/aes.rs
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::sym_key::import_sym_key,
+ util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+use std::{
+ convert::TryFrom,
+ mem,
+ os::raw::{c_uchar, c_uint},
+};
+
+const AES_GCM_TAG_LENGTH: usize = 16;
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum Operation {
+ Encrypt,
+ Decrypt,
+}
+
+pub fn aes_gcm_crypt(
+ key: &[u8],
+ nonce: &[u8],
+ aad: &[u8],
+ data: &[u8],
+ operation: Operation,
+) -> Result<Vec<u8>> {
+ let mut gcm_params = nss_sys::CK_GCM_PARAMS {
+ pIv: nonce.as_ptr() as nss_sys::CK_BYTE_PTR,
+ ulIvLen: nss_sys::CK_ULONG::try_from(nonce.len())?,
+ ulIvBits: nss_sys::CK_ULONG::try_from(
+ nonce
+ .len()
+ .checked_mul(8)
+ .ok_or_else(|| ErrorKind::InternalError)?,
+ )?,
+ pAAD: aad.as_ptr() as nss_sys::CK_BYTE_PTR,
+ ulAADLen: nss_sys::CK_ULONG::try_from(aad.len())?,
+ ulTagBits: nss_sys::CK_ULONG::try_from(AES_GCM_TAG_LENGTH * 8)?,
+ };
+ let mut params = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: &mut gcm_params as *mut _ as *mut c_uchar,
+ len: c_uint::try_from(mem::size_of::<nss_sys::CK_GCM_PARAMS>())?,
+ };
+ common_crypt(
+ nss_sys::CKM_AES_GCM.into(),
+ key,
+ data,
+ AES_GCM_TAG_LENGTH,
+ &mut params,
+ operation,
+ )
+}
+
+pub fn aes_cbc_crypt(
+ key: &[u8],
+ nonce: &[u8],
+ data: &[u8],
+ operation: Operation,
+) -> Result<Vec<u8>> {
+ let mut params = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: nonce.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(nonce.len())?,
+ };
+ common_crypt(
+ nss_sys::CKM_AES_CBC_PAD.into(),
+ key,
+ data,
+ usize::try_from(nss_sys::AES_BLOCK_SIZE)?, // CBC mode might pad the result.
+ &mut params,
+ operation,
+ )
+}
+
+pub fn common_crypt(
+ mech: nss_sys::CK_MECHANISM_TYPE,
+ key: &[u8],
+ data: &[u8],
+ extra_data_len: usize,
+ params: &mut nss_sys::SECItem,
+ operation: Operation,
+) -> Result<Vec<u8>> {
+ ensure_nss_initialized();
+ // Most of the following code is inspired by the Firefox WebCrypto implementation:
+ // https://searchfox.org/mozilla-central/rev/f46e2bf881d522a440b30cbf5cf8d76fc212eaf4/dom/crypto/WebCryptoTask.cpp#566
+ // CKA_ENCRYPT always is fine.
+ let sym_key = import_sym_key(mech, nss_sys::CKA_ENCRYPT.into(), &key)?;
+ // Initialize the output buffer (enough space for padding / a full tag).
+ let result_max_len = data
+ .len()
+ .checked_add(extra_data_len)
+ .ok_or_else(|| ErrorKind::InternalError)?;
+ let mut out_len: c_uint = 0;
+ let mut out = vec![0u8; result_max_len];
+ let result_max_len_uint = c_uint::try_from(result_max_len)?;
+ let data_len = c_uint::try_from(data.len())?;
+ let f = match operation {
+ Operation::Decrypt => nss_sys::PK11_Decrypt,
+ Operation::Encrypt => nss_sys::PK11_Encrypt,
+ };
+ map_nss_secstatus(|| unsafe {
+ f(
+ sym_key.as_mut_ptr(),
+ mech,
+ params,
+ out.as_mut_ptr(),
+ &mut out_len,
+ result_max_len_uint,
+ data.as_ptr(),
+ data_len,
+ )
+ })?;
+ out.truncate(usize::try_from(out_len)?);
+ Ok(out)
+}
diff --git a/third_party/rust/nss/src/ec.rs b/third_party/rust/nss/src/ec.rs
new file mode 100644
index 0000000000..30a96419de
--- /dev/null
+++ b/third_party/rust/nss/src/ec.rs
@@ -0,0 +1,423 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::{
+ self,
+ context::HashAlgorithm,
+ slot,
+ types::{Pkcs11Object, PrivateKey as PK11PrivateKey, PublicKey as PK11PublicKey},
+ },
+ util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+use serde_derive::{Deserialize, Serialize};
+use std::{
+ convert::TryFrom,
+ mem,
+ ops::Deref,
+ os::raw::{c_uchar, c_uint, c_void},
+ ptr,
+};
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
+#[repr(u8)]
+pub enum Curve {
+ P256,
+ P384,
+}
+
+impl Curve {
+ pub fn get_field_len(&self) -> u32 {
+ match &self {
+ Curve::P256 => 32,
+ Curve::P384 => 48,
+ }
+ }
+}
+
+const CRV_P256: &str = "P-256";
+const CRV_P384: &str = "P-384";
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct EcKey {
+ curve: String,
+ // The `d` value of the EC Key.
+ private_key: Vec<u8>,
+ // The uncompressed x,y-representation of the public component of the EC Key.
+ public_key: Vec<u8>,
+}
+
+impl EcKey {
+ pub fn new(curve: Curve, private_key: &[u8], public_key: &[u8]) -> Self {
+ let curve = match curve {
+ Curve::P256 => CRV_P256,
+ Curve::P384 => CRV_P384,
+ };
+ Self {
+ curve: curve.to_owned(),
+ private_key: private_key.to_vec(),
+ public_key: public_key.to_vec(),
+ }
+ }
+
+ pub fn from_coordinates(curve: Curve, d: &[u8], x: &[u8], y: &[u8]) -> Result<Self> {
+ let ec_point = create_ec_point_for_coordinates(x, y)?;
+ Ok(EcKey::new(curve, d, &ec_point))
+ }
+
+ pub fn curve(&self) -> Curve {
+ if self.curve == CRV_P256 {
+ return Curve::P256;
+ } else if self.curve == CRV_P384 {
+ return Curve::P384;
+ }
+ unimplemented!("It is impossible to create a curve object with a different CRV.")
+ }
+
+ pub fn public_key(&self) -> &[u8] {
+ &self.public_key
+ }
+
+ pub fn private_key(&self) -> &[u8] {
+ &self.private_key
+ }
+}
+
+fn create_ec_point_for_coordinates(x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
+ if x.len() != y.len() {
+ return Err(ErrorKind::InternalError.into());
+ }
+ let mut buf = vec![0u8; x.len() + y.len() + 1];
+ buf[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
+ let mut offset = 1;
+ buf[offset..offset + x.len()].copy_from_slice(x);
+ offset += x.len();
+ buf[offset..offset + y.len()].copy_from_slice(y);
+ Ok(buf)
+}
+
+pub fn generate_keypair(curve: Curve) -> Result<(PrivateKey, PublicKey)> {
+ ensure_nss_initialized();
+ // 1. Create EC params
+ let params_buf = create_ec_params_for_curve(curve)?;
+ let mut params = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: params_buf.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(params_buf.len())?,
+ };
+
+ // 2. Generate the key pair
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/f46e2bf881d522a440b30cbf5cf8d76fc212eaf4/dom/crypto/WebCryptoTask.cpp#2389
+ let mech = nss_sys::CKM_EC_KEY_PAIR_GEN;
+ let slot = slot::get_internal_slot()?;
+ let mut pub_key: *mut nss_sys::SECKEYPublicKey = ptr::null_mut();
+ let prv_key = PrivateKey::from(curve, unsafe {
+ PK11PrivateKey::from_ptr(nss_sys::PK11_GenerateKeyPair(
+ slot.as_mut_ptr(),
+ mech.into(),
+ &mut params as *mut _ as *mut c_void,
+ &mut pub_key,
+ nss_sys::PR_FALSE,
+ nss_sys::PR_FALSE,
+ ptr::null_mut(),
+ ))?
+ });
+ let pub_key = PublicKey::from(curve, unsafe { PK11PublicKey::from_ptr(pub_key)? });
+ Ok((prv_key, pub_key))
+}
+
+pub struct PrivateKey {
+ curve: Curve,
+ wrapped: PK11PrivateKey,
+}
+
+impl Deref for PrivateKey {
+ type Target = PK11PrivateKey;
+ #[inline]
+ fn deref(&self) -> &PK11PrivateKey {
+ &self.wrapped
+ }
+}
+
+impl PrivateKey {
+ pub fn convert_to_public_key(&self) -> Result<PublicKey> {
+ let mut pub_key = self.wrapped.convert_to_public_key()?;
+
+ // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1562046.
+ let field_len = self.curve.get_field_len();
+ let expected_len = 2 * field_len + 1;
+ let mut pub_value = unsafe { (*pub_key.as_ptr()).u.ec.publicValue };
+ if pub_value.len == expected_len - 2 {
+ let old_pub_value_raw = unsafe { sec_item_as_slice(&mut pub_value)?.to_vec() };
+ let mut new_pub_value_raw = vec![0u8; usize::try_from(expected_len)?];
+ new_pub_value_raw[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
+ new_pub_value_raw[1] = u8::try_from(old_pub_value_raw.len())?;
+ new_pub_value_raw[2..].copy_from_slice(&old_pub_value_raw);
+ pub_key = PublicKey::from_bytes(self.curve, &new_pub_value_raw)?.wrapped;
+ }
+ Ok(PublicKey {
+ wrapped: pub_key,
+ curve: self.curve,
+ })
+ }
+
+ #[inline]
+ pub(crate) fn from(curve: Curve, key: PK11PrivateKey) -> Self {
+ Self {
+ curve,
+ wrapped: key,
+ }
+ }
+
+ pub fn curve(&self) -> Curve {
+ self.curve
+ }
+
+ pub fn private_value(&self) -> Result<Vec<u8>> {
+ let mut private_value = self.read_raw_attribute(nss_sys::CKA_VALUE.into()).unwrap();
+ let private_key = unsafe { sec_item_as_slice(private_value.as_mut_ref())?.to_vec() };
+ Ok(private_key)
+ }
+
+ fn from_nss_params(
+ curve: Curve,
+ ec_params: &[u8],
+ ec_point: &[u8],
+ private_value: &[u8],
+ ) -> Result<Self> {
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/CryptoKey.cpp#322
+ // These explicit variable type declarations are *VERY* important, as we pass to NSS a pointer to them
+ // and we need these variables to be of the right size!
+ let mut private_key_value: nss_sys::CK_OBJECT_CLASS = nss_sys::CKO_PRIVATE_KEY.into();
+ let mut false_value: nss_sys::CK_BBOOL = nss_sys::CK_FALSE;
+ let mut ec_value: nss_sys::CK_KEY_TYPE = nss_sys::CKK_EC.into();
+ let bbool_size = mem::size_of::<nss_sys::CK_BBOOL>();
+ let key_template = vec![
+ ck_attribute(
+ nss_sys::CKA_CLASS.into(),
+ &mut private_key_value as *mut _ as *mut c_void,
+ mem::size_of::<nss_sys::CK_OBJECT_CLASS>(),
+ )?,
+ ck_attribute(
+ nss_sys::CKA_KEY_TYPE.into(),
+ &mut ec_value as *mut _ as *mut c_void,
+ mem::size_of::<nss_sys::CK_KEY_TYPE>(),
+ )?,
+ ck_attribute(
+ nss_sys::CKA_TOKEN.into(),
+ &mut false_value as *mut _ as *mut c_void,
+ bbool_size,
+ )?,
+ ck_attribute(
+ nss_sys::CKA_SENSITIVE.into(),
+ &mut false_value as *mut _ as *mut c_void,
+ bbool_size,
+ )?,
+ ck_attribute(
+ nss_sys::CKA_PRIVATE.into(),
+ &mut false_value as *mut _ as *mut c_void,
+ bbool_size,
+ )?,
+ // PrivateKeyFromPrivateKeyTemplate sets the ID.
+ ck_attribute(nss_sys::CKA_ID.into(), ptr::null_mut(), 0)?,
+ ck_attribute(
+ nss_sys::CKA_EC_PARAMS.into(),
+ ec_params.as_ptr() as *mut c_void,
+ ec_params.len(),
+ )?,
+ ck_attribute(
+ nss_sys::CKA_EC_POINT.into(),
+ ec_point.as_ptr() as *mut c_void,
+ ec_point.len(),
+ )?,
+ ck_attribute(
+ nss_sys::CKA_VALUE.into(),
+ private_value.as_ptr() as *mut c_void,
+ private_value.len(),
+ )?,
+ ];
+ Ok(Self::from(
+ curve,
+ PK11PrivateKey::from_private_key_template(key_template)?,
+ ))
+ }
+
+ pub fn import(ec_key: &EcKey) -> Result<Self> {
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/66086345467c69685434dd1c5177b30a7511b1a5/dom/crypto/CryptoKey.cpp#652
+ ensure_nss_initialized();
+ let curve = ec_key.curve();
+ let ec_params = create_ec_params_for_curve(curve)?;
+ Self::from_nss_params(curve, &ec_params, &ec_key.public_key, &ec_key.private_key)
+ }
+
+ pub fn export(&self) -> Result<EcKey> {
+ let public_key = self.convert_to_public_key()?;
+ let public_key_bytes = public_key.to_bytes()?;
+ let private_key_bytes = self.private_value()?;
+ Ok(EcKey::new(
+ self.curve,
+ &private_key_bytes,
+ &public_key_bytes,
+ ))
+ }
+}
+
+#[inline]
+fn ck_attribute(
+ r#type: nss_sys::CK_ATTRIBUTE_TYPE,
+ p_value: nss_sys::CK_VOID_PTR,
+ value_len: usize,
+) -> Result<nss_sys::CK_ATTRIBUTE> {
+ Ok(nss_sys::CK_ATTRIBUTE {
+ type_: r#type,
+ pValue: p_value,
+ ulValueLen: nss_sys::CK_ULONG::try_from(value_len)?,
+ })
+}
+
+pub struct PublicKey {
+ curve: Curve,
+ wrapped: PK11PublicKey,
+}
+
+impl Deref for PublicKey {
+ type Target = PK11PublicKey;
+ #[inline]
+ fn deref(&self) -> &PK11PublicKey {
+ &self.wrapped
+ }
+}
+
+impl PublicKey {
+ #[inline]
+ pub(crate) fn from(curve: Curve, key: PK11PublicKey) -> Self {
+ Self {
+ curve,
+ wrapped: key,
+ }
+ }
+
+ pub fn curve(&self) -> Curve {
+ self.curve
+ }
+
+ /// ECDSA verify operation
+ pub fn verify(
+ &self,
+ message: &[u8],
+ signature: &[u8],
+ hash_algorithm: HashAlgorithm,
+ ) -> Result<()> {
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/b2716c233e9b4398fc5923cbe150e7f83c7c6c5b/dom/crypto/WebCryptoTask.cpp#1144
+ let signature = nss_sys::SECItem {
+ len: u32::try_from(signature.len())?,
+ data: signature.as_ptr() as *mut u8,
+ type_: 0,
+ };
+ let hash = pk11::context::hash_buf(&hash_algorithm, message)?;
+ let hash = nss_sys::SECItem {
+ len: u32::try_from(hash.len())?,
+ data: hash.as_ptr() as *mut u8,
+ type_: 0,
+ };
+ map_nss_secstatus(|| unsafe {
+ nss_sys::PK11_VerifyWithMechanism(
+ self.as_mut_ptr(),
+ nss_sys::PK11_MapSignKeyType((*self.wrapped.as_ptr()).keyType),
+ ptr::null(),
+ &signature,
+ &hash,
+ ptr::null_mut(),
+ )
+ })?;
+ Ok(())
+ }
+
+ pub fn to_bytes(&self) -> Result<Vec<u8>> {
+ // Some public keys we create do not have an associated PCKS#11 slot
+ // therefore we cannot use `read_raw_attribute(CKA_EC_POINT)`
+ // so we read the `publicValue` field directly instead.
+ let mut ec_point = unsafe { (*self.as_ptr()).u.ec.publicValue };
+ let public_key = unsafe { sec_item_as_slice(&mut ec_point)?.to_vec() };
+ check_pub_key_bytes(&public_key, self.curve)?;
+ Ok(public_key)
+ }
+
+ pub fn from_bytes(curve: Curve, bytes: &[u8]) -> Result<PublicKey> {
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/CryptoKey.cpp#1078
+ check_pub_key_bytes(bytes, curve)?;
+ let key_data = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: bytes.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(bytes.len())?,
+ };
+ let params_buf = create_ec_params_for_curve(curve)?;
+ let params = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: params_buf.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(params_buf.len())?,
+ };
+
+ let pub_key = nss_sys::SECKEYPublicKey {
+ arena: ptr::null_mut(),
+ keyType: nss_sys::KeyType::ecKey as u32,
+ pkcs11Slot: ptr::null_mut(),
+ pkcs11ID: nss_sys::CK_INVALID_HANDLE.into(),
+ u: nss_sys::SECKEYPublicKeyStr_u {
+ ec: nss_sys::SECKEYECPublicKey {
+ DEREncodedParams: params,
+ publicValue: key_data,
+ encoding: nss_sys::ECPointEncoding::ECPoint_Uncompressed as u32,
+ size: 0,
+ },
+ },
+ };
+ Ok(Self::from(curve, unsafe {
+ PK11PublicKey::from_ptr(nss_sys::SECKEY_CopyPublicKey(&pub_key))?
+ }))
+ }
+}
+
+fn check_pub_key_bytes(bytes: &[u8], curve: Curve) -> Result<()> {
+ let field_len = curve.get_field_len();
+ // Check length of uncompressed point coordinates. There are 2 field elements
+ // and a leading "point form" octet (which must be EC_POINT_FORM_UNCOMPRESSED).
+ if bytes.len() != usize::try_from(2 * field_len + 1)? {
+ return Err(ErrorKind::InternalError.into());
+ }
+ // No support for compressed points.
+ if bytes[0] != u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)? {
+ return Err(ErrorKind::InternalError.into());
+ }
+ Ok(())
+}
+
+fn create_ec_params_for_curve(curve: Curve) -> Result<Vec<u8>> {
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/WebCryptoCommon.h#299
+ let curve_oid_tag = match curve {
+ Curve::P256 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP256R1,
+ Curve::P384 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP384R1,
+ };
+ // Retrieve curve data by OID tag.
+ let oid_data = unsafe { nss_sys::SECOID_FindOIDByTag(curve_oid_tag as u32) };
+ if oid_data.is_null() {
+ return Err(ErrorKind::InternalError.into());
+ }
+ // Set parameters
+ let oid_data_len = unsafe { (*oid_data).oid.len };
+ let mut buf = vec![0u8; usize::try_from(oid_data_len)? + 2];
+ buf[0] = c_uchar::try_from(nss_sys::SEC_ASN1_OBJECT_ID)?;
+ buf[1] = c_uchar::try_from(oid_data_len)?;
+ let oid_data_data =
+ unsafe { std::slice::from_raw_parts((*oid_data).oid.data, usize::try_from(oid_data_len)?) };
+ buf[2..].copy_from_slice(oid_data_data);
+ Ok(buf)
+}
diff --git a/third_party/rust/nss/src/ecdh.rs b/third_party/rust/nss/src/ecdh.rs
new file mode 100644
index 0000000000..9e91b0dc0d
--- /dev/null
+++ b/third_party/rust/nss/src/ecdh.rs
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ ec::{PrivateKey, PublicKey},
+ error::*,
+ pk11::types::SymKey,
+ util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+
+pub fn ecdh_agreement(priv_key: &PrivateKey, pub_key: &PublicKey) -> Result<Vec<u8>> {
+ ensure_nss_initialized();
+ if priv_key.curve() != pub_key.curve() {
+ return Err(ErrorKind::InternalError.into());
+ }
+ // The following code is adapted from:
+ // https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/WebCryptoTask.cpp#2835
+
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ let sym_key = unsafe {
+ SymKey::from_ptr(nss_sys::PK11_PubDeriveWithKDF(
+ priv_key.as_mut_ptr(),
+ pub_key.as_mut_ptr(),
+ nss_sys::PR_FALSE,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ nss_sys::CKM_ECDH1_DERIVE.into(),
+ nss_sys::CKM_SHA512_HMAC.into(),
+ nss_sys::CKA_SIGN.into(),
+ 0,
+ nss_sys::CKD_NULL.into(),
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ ))?
+ };
+
+ map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by `sym_key` which we copy into `buf`.
+ let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+ let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+ Ok(buf.to_vec())
+}
diff --git a/third_party/rust/nss/src/error.rs b/third_party/rust/nss/src/error.rs
new file mode 100644
index 0000000000..e2ef5a49f9
--- /dev/null
+++ b/third_party/rust/nss/src/error.rs
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum ErrorKind {
+ #[error("NSS could not be initialized")]
+ NSSInitFailure,
+ #[error("NSS error: {0} {1}")]
+ NSSError(i32, String),
+ #[error("Internal crypto error")]
+ InternalError,
+ #[error("Conversion error: {0}")]
+ ConversionError(#[from] std::num::TryFromIntError),
+ #[error("Base64 decode error: {0}")]
+ Base64Decode(#[from] base64::DecodeError),
+}
+
+error_support::define_error! {
+ ErrorKind {
+ (Base64Decode, base64::DecodeError),
+ (ConversionError, std::num::TryFromIntError),
+ }
+}
diff --git a/third_party/rust/nss/src/lib.rs b/third_party/rust/nss/src/lib.rs
new file mode 100644
index 0000000000..9ab529d370
--- /dev/null
+++ b/third_party/rust/nss/src/lib.rs
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+#[macro_use]
+mod util;
+pub mod aes;
+pub mod ec;
+pub mod ecdh;
+mod error;
+pub mod pbkdf2;
+pub mod pk11;
+pub mod secport;
+pub use crate::error::{Error, ErrorKind, Result};
+pub use util::ensure_nss_initialized as ensure_initialized;
diff --git a/third_party/rust/nss/src/pbkdf2.rs b/third_party/rust/nss/src/pbkdf2.rs
new file mode 100644
index 0000000000..7b51766ff1
--- /dev/null
+++ b/third_party/rust/nss/src/pbkdf2.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr};
+use crate::{
+ error::*,
+ pk11::{
+ slot::get_internal_slot,
+ types::{AlgorithmID, SymKey},
+ },
+};
+
+// Expose for consumers to choose the hashing algorithm
+// Currently only SHA256 supported
+pub use crate::pk11::context::HashAlgorithm;
+use nss_sys::SECOidTag;
+use std::convert::TryFrom;
+
+// ***** BASED ON THE FOLLOWING IMPLEMENTATION *****
+// https://searchfox.org/mozilla-central/rev/8ccea36c4fb09412609fb738c722830d7098602b/dom/crypto/WebCryptoTask.cpp#2567
+
+pub fn pbkdf2_key_derive(
+ password: &[u8],
+ salt: &[u8],
+ iterations: u32,
+ hash_algorithm: HashAlgorithm,
+ out: &mut [u8],
+) -> Result<()> {
+ ensure_nss_initialized();
+ let oid_tag = match hash_algorithm {
+ HashAlgorithm::SHA256 => SECOidTag::SEC_OID_HMAC_SHA256 as u32,
+ HashAlgorithm::SHA384 => SECOidTag::SEC_OID_HMAC_SHA384 as u32,
+ };
+ let mut sec_salt = nss_sys::SECItem {
+ len: u32::try_from(salt.len())?,
+ data: salt.as_ptr() as *mut u8,
+ type_: 0,
+ };
+ let alg_id = unsafe {
+ AlgorithmID::from_ptr(nss_sys::PK11_CreatePBEV2AlgorithmID(
+ SECOidTag::SEC_OID_PKCS5_PBKDF2 as u32,
+ SECOidTag::SEC_OID_HMAC_SHA1 as u32,
+ oid_tag,
+ i32::try_from(out.len())?,
+ i32::try_from(iterations)?,
+ &mut sec_salt as *mut nss_sys::SECItem,
+ ))?
+ };
+
+ let slot = get_internal_slot()?;
+ let mut sec_pw = nss_sys::SECItem {
+ len: u32::try_from(password.len())?,
+ data: password.as_ptr() as *mut u8,
+ type_: 0,
+ };
+ let sym_key = unsafe {
+ SymKey::from_ptr(nss_sys::PK11_PBEKeyGen(
+ slot.as_mut_ptr(),
+ alg_id.as_mut_ptr(),
+ &mut sec_pw as *mut nss_sys::SECItem,
+ nss_sys::PR_FALSE,
+ std::ptr::null_mut(),
+ ))?
+ };
+ map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by `sym_key` which we copy into `buf`
+ let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+ let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+ // Stop panic in swap_with_slice by returning an error if the sizes mismatch
+ if buf.len() != out.len() {
+ return Err(ErrorKind::InternalError.into());
+ }
+ out.swap_with_slice(buf);
+ Ok(())
+}
diff --git a/third_party/rust/nss/src/pk11/context.rs b/third_party/rust/nss/src/pk11/context.rs
new file mode 100644
index 0000000000..b587379dd3
--- /dev/null
+++ b/third_party/rust/nss/src/pk11/context.rs
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::{
+ sym_key::import_sym_key,
+ types::{Context, SymKey},
+ },
+ util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+use std::{convert::TryFrom, ptr};
+
+#[derive(Copy, Clone, Debug)]
+#[repr(u8)]
+pub enum HashAlgorithm {
+ SHA256,
+ SHA384,
+}
+
+impl HashAlgorithm {
+ fn result_len(&self) -> u32 {
+ match self {
+ HashAlgorithm::SHA256 => nss_sys::SHA256_LENGTH,
+ HashAlgorithm::SHA384 => nss_sys::SHA384_LENGTH,
+ }
+ }
+
+ fn as_hmac_mechanism(&self) -> u32 {
+ match self {
+ HashAlgorithm::SHA256 => nss_sys::CKM_SHA256_HMAC,
+ HashAlgorithm::SHA384 => nss_sys::CKM_SHA384_HMAC,
+ }
+ }
+
+ pub(crate) fn as_hkdf_mechanism(&self) -> u32 {
+ match self {
+ HashAlgorithm::SHA256 => nss_sys::CKM_NSS_HKDF_SHA256,
+ HashAlgorithm::SHA384 => nss_sys::CKM_NSS_HKDF_SHA384,
+ }
+ }
+}
+
+impl From<&HashAlgorithm> for nss_sys::SECOidTag {
+ fn from(alg: &HashAlgorithm) -> Self {
+ match alg {
+ HashAlgorithm::SHA256 => nss_sys::SECOidTag::SEC_OID_SHA256,
+ HashAlgorithm::SHA384 => nss_sys::SECOidTag::SEC_OID_SHA384,
+ }
+ }
+}
+
+pub fn hash_buf(algorithm: &HashAlgorithm, data: &[u8]) -> Result<Vec<u8>> {
+ ensure_nss_initialized();
+ let result_len = usize::try_from(algorithm.result_len())?;
+ let mut out = vec![0u8; result_len];
+ let data_len = i32::try_from(data.len())?;
+ map_nss_secstatus(|| unsafe {
+ nss_sys::PK11_HashBuf(
+ Into::<nss_sys::SECOidTag>::into(algorithm) as u32,
+ out.as_mut_ptr(),
+ data.as_ptr(),
+ data_len,
+ )
+ })?;
+ Ok(out)
+}
+
+pub fn hmac_sign(digest_alg: &HashAlgorithm, sym_key_bytes: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ let mech = digest_alg.as_hmac_mechanism();
+ let sym_key = import_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), sym_key_bytes)?;
+ let context = create_context_by_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), &sym_key)?;
+ Ok(hash_buf_with_context(&context, data)?)
+}
+
+/// Similar to hash_buf except the consumer has to provide the digest context.
+fn hash_buf_with_context(context: &Context, data: &[u8]) -> Result<Vec<u8>> {
+ ensure_nss_initialized();
+ map_nss_secstatus(|| unsafe { nss_sys::PK11_DigestBegin(context.as_mut_ptr()) })?;
+ let data_len = u32::try_from(data.len())?;
+ map_nss_secstatus(|| unsafe {
+ nss_sys::PK11_DigestOp(context.as_mut_ptr(), data.as_ptr(), data_len)
+ })?;
+ // We allocate the maximum possible length for the out buffer then we'll
+ // slice it after nss fills `out_len`.
+ let mut out_len: u32 = 0;
+ let mut out = vec![0u8; nss_sys::HASH_LENGTH_MAX as usize];
+ map_nss_secstatus(|| unsafe {
+ nss_sys::PK11_DigestFinal(
+ context.as_mut_ptr(),
+ out.as_mut_ptr(),
+ &mut out_len,
+ nss_sys::HASH_LENGTH_MAX,
+ )
+ })?;
+ out.truncate(usize::try_from(out_len)?);
+ Ok(out)
+}
+
+/// Safe wrapper around PK11_CreateContextBySymKey that
+/// de-allocates memory when the context goes out of
+/// scope.
+pub fn create_context_by_sym_key(
+ mechanism: nss_sys::CK_MECHANISM_TYPE,
+ operation: nss_sys::CK_ATTRIBUTE_TYPE,
+ sym_key: &SymKey,
+) -> Result<Context> {
+ ensure_nss_initialized();
+ let mut param = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: ptr::null_mut(),
+ len: 0,
+ };
+ unsafe {
+ Context::from_ptr(nss_sys::PK11_CreateContextBySymKey(
+ mechanism,
+ operation,
+ sym_key.as_mut_ptr(),
+ &mut param,
+ ))
+ }
+}
diff --git a/third_party/rust/nss/src/pk11/mod.rs b/third_party/rust/nss/src/pk11/mod.rs
new file mode 100644
index 0000000000..550ea5ccd7
--- /dev/null
+++ b/third_party/rust/nss/src/pk11/mod.rs
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod context;
+pub mod slot;
+pub mod sym_key;
+pub mod types;
diff --git a/third_party/rust/nss/src/pk11/slot.rs b/third_party/rust/nss/src/pk11/slot.rs
new file mode 100644
index 0000000000..0024b2e1c3
--- /dev/null
+++ b/third_party/rust/nss/src/pk11/slot.rs
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::types::Slot,
+ util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+use std::convert::TryFrom;
+
+pub fn generate_random(data: &mut [u8]) -> Result<()> {
+ // `NSS_Init` will initialize the RNG with data from `/dev/urandom`.
+ ensure_nss_initialized();
+ let len = i32::try_from(data.len())?;
+ map_nss_secstatus(|| unsafe { nss_sys::PK11_GenerateRandom(data.as_mut_ptr(), len) })?;
+ Ok(())
+}
+
+/// Safe wrapper around `PK11_GetInternalSlot` that
+/// de-allocates memory when the slot goes out of
+/// scope.
+pub(crate) fn get_internal_slot() -> Result<Slot> {
+ unsafe { Slot::from_ptr(nss_sys::PK11_GetInternalSlot()) }
+}
diff --git a/third_party/rust/nss/src/pk11/sym_key.rs b/third_party/rust/nss/src/pk11/sym_key.rs
new file mode 100644
index 0000000000..769984ff3f
--- /dev/null
+++ b/third_party/rust/nss/src/pk11/sym_key.rs
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::{context::HashAlgorithm, slot, types::SymKey},
+ util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+use std::{
+ convert::TryFrom,
+ mem,
+ os::raw::{c_uchar, c_uint, c_ulong},
+ ptr,
+};
+
+pub fn hkdf_expand(
+ digest_alg: &HashAlgorithm,
+ key_bytes: &[u8],
+ info: &[u8],
+ len: usize,
+) -> Result<Vec<u8>> {
+ ensure_nss_initialized();
+ let mech = digest_alg.as_hkdf_mechanism();
+ // Most of the following code is inspired by the Firefox WebCrypto implementation:
+ // https://searchfox.org/mozilla-central/rev/ee3905439acbf81e9c829ece0b46d09d2fa26c5c/dom/crypto/WebCryptoTask.cpp#2530-2597
+ // Except that we only do the expand part, which explains why we use null pointers below.
+ let mut hkdf_params = nss_sys::CK_NSS_HKDFParams {
+ bExtract: nss_sys::CK_FALSE,
+ pSalt: ptr::null_mut(),
+ ulSaltLen: 0,
+ bExpand: nss_sys::CK_TRUE,
+ pInfo: info.as_ptr() as *mut u8,
+ ulInfoLen: c_ulong::try_from(info.len())?,
+ };
+ let mut params = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: &mut hkdf_params as *mut _ as *mut c_uchar,
+ len: u32::try_from(mem::size_of::<nss_sys::CK_NSS_HKDFParams>())?,
+ };
+ let base_key = import_sym_key(mech.into(), nss_sys::CKA_WRAP.into(), key_bytes)?;
+ let derived_len = i32::try_from(len)?;
+ let sym_key = unsafe {
+ SymKey::from_ptr(
+ // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+ // derived symmetric key and don't matter because we ignore them anyway.
+ nss_sys::PK11_Derive(
+ base_key.as_mut_ptr(),
+ mech.into(),
+ &mut params,
+ nss_sys::CKM_SHA512_HMAC.into(),
+ nss_sys::CKA_SIGN.into(),
+ derived_len,
+ ),
+ )?
+ };
+ map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+ // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+ // just refers to a buffer managed by `sym_key` which we copy into `out`.
+ let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+ if u32::try_from(len)? > key_data.len {
+ return Err(ErrorKind::InternalError.into());
+ }
+ let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+ Ok(buf.to_vec())
+}
+
+/// Safe wrapper around PK11_ImportSymKey that
+/// de-allocates memory when the key goes out of
+/// scope.
+pub(crate) fn import_sym_key(
+ mechanism: nss_sys::CK_MECHANISM_TYPE,
+ operation: nss_sys::CK_ATTRIBUTE_TYPE,
+ buf: &[u8],
+) -> Result<SymKey> {
+ ensure_nss_initialized();
+ let mut item = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: buf.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(buf.len())?,
+ };
+ let slot = slot::get_internal_slot()?;
+ unsafe {
+ SymKey::from_ptr(nss_sys::PK11_ImportSymKey(
+ slot.as_mut_ptr(),
+ mechanism,
+ nss_sys::PK11Origin::PK11_OriginUnwrap as u32,
+ operation,
+ &mut item,
+ ptr::null_mut(),
+ ))
+ }
+}
diff --git a/third_party/rust/nss/src/pk11/types.rs b/third_party/rust/nss/src/pk11/types.rs
new file mode 100644
index 0000000000..6ea51eeedd
--- /dev/null
+++ b/third_party/rust/nss/src/pk11/types.rs
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ error::*,
+ pk11::slot::{generate_random, get_internal_slot},
+ util::{map_nss_secstatus, ScopedPtr},
+};
+use std::{
+ convert::TryFrom,
+ ops::Deref,
+ os::raw::{c_int, c_uchar, c_uint, c_void},
+ ptr,
+};
+
+scoped_ptr!(SymKey, nss_sys::PK11SymKey, nss_sys::PK11_FreeSymKey);
+scoped_ptr!(
+ PrivateKey,
+ nss_sys::SECKEYPrivateKey,
+ nss_sys::SECKEY_DestroyPrivateKey
+);
+scoped_ptr!(
+ PublicKey,
+ nss_sys::SECKEYPublicKey,
+ nss_sys::SECKEY_DestroyPublicKey
+);
+scoped_ptr!(
+ GenericObject,
+ nss_sys::PK11GenericObject,
+ nss_sys::PK11_DestroyGenericObject
+);
+scoped_ptr!(Context, nss_sys::PK11Context, pk11_destroy_context_true);
+scoped_ptr!(Slot, nss_sys::PK11SlotInfo, nss_sys::PK11_FreeSlot);
+
+scoped_ptr!(
+ AlgorithmID,
+ nss_sys::SECAlgorithmID,
+ secoid_destroy_algorithm_id_true
+);
+
+#[inline]
+unsafe fn secoid_destroy_algorithm_id_true(alg_id: *mut nss_sys::SECAlgorithmID) {
+ nss_sys::SECOID_DestroyAlgorithmID(alg_id, nss_sys::PR_TRUE);
+}
+
+#[inline]
+unsafe fn pk11_destroy_context_true(context: *mut nss_sys::PK11Context) {
+ nss_sys::PK11_DestroyContext(context, nss_sys::PR_TRUE);
+}
+
+// Trait for types that have PCKS#11 attributes that are readable. See
+// https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/security/nss/lib/pk11wrap/pk11pub.h#842-864
+pub(crate) unsafe trait Pkcs11Object: ScopedPtr {
+ const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType;
+ fn read_raw_attribute(
+ &self,
+ attribute_type: nss_sys::CK_ATTRIBUTE_TYPE,
+ ) -> Result<ScopedSECItem> {
+ let mut out_sec = ScopedSECItem::empty(nss_sys::SECItemType::siBuffer);
+ map_nss_secstatus(|| unsafe {
+ nss_sys::PK11_ReadRawAttribute(
+ Self::PK11_OBJECT_TYPE as u32,
+ self.as_mut_ptr() as *mut c_void,
+ attribute_type,
+ out_sec.as_mut_ref(),
+ )
+ })?;
+ Ok(out_sec)
+ }
+}
+
+unsafe impl Pkcs11Object for GenericObject {
+ const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypeGeneric;
+}
+unsafe impl Pkcs11Object for PrivateKey {
+ const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypePrivKey;
+}
+unsafe impl Pkcs11Object for PublicKey {
+ const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypePubKey;
+}
+unsafe impl Pkcs11Object for SymKey {
+ const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypeSymKey;
+}
+
+// From https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/NSS_API_Guidelines#Thread_Safety:
+// "Data structures that are read only, like SECKEYPublicKeys or PK11SymKeys, need not be protected."
+unsafe impl Send for PrivateKey {}
+unsafe impl Send for PublicKey {}
+
+impl PrivateKey {
+ pub fn convert_to_public_key(&self) -> Result<PublicKey> {
+ Ok(unsafe { PublicKey::from_ptr(nss_sys::SECKEY_ConvertToPublicKey(self.as_mut_ptr()))? })
+ }
+
+ // To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
+ // generates a random ID for each key. The given template must contain an
+ // attribute slot for a key ID, but it must consist of a null pointer and have a
+ // length of 0.
+ pub(crate) fn from_private_key_template(
+ mut template: Vec<nss_sys::CK_ATTRIBUTE>,
+ ) -> Result<Self> {
+ // Generate a random 160-bit object ID. This ID must be unique.
+ let mut obj_id_buf = vec![0u8; 160 / 8];
+ generate_random(&mut obj_id_buf)?;
+ let mut obj_id = nss_sys::SECItem {
+ type_: nss_sys::SECItemType::siBuffer as u32,
+ data: obj_id_buf.as_ptr() as *mut c_uchar,
+ len: c_uint::try_from(obj_id_buf.len())?,
+ };
+ let slot = get_internal_slot()?;
+ let mut pre_existing_key = unsafe {
+ nss_sys::PK11_FindKeyByKeyID(slot.as_mut_ptr(), &mut obj_id, std::ptr::null_mut())
+ };
+ if !pre_existing_key.is_null() {
+ // Note that we can't just call SECKEY_DestroyPrivateKey here because that
+ // will destroy the PKCS#11 object that is backing a preexisting key (that
+ // we still have a handle on somewhere else in memory). If that object were
+ // destroyed, cryptographic operations performed by that other key would
+ // fail.
+ unsafe {
+ destroy_private_key_without_destroying_pkcs11_object(pre_existing_key);
+ }
+ // Try again with a new ID (but only once - collisions are very unlikely).
+ generate_random(&mut obj_id_buf)?;
+ pre_existing_key = unsafe {
+ nss_sys::PK11_FindKeyByKeyID(slot.as_mut_ptr(), &mut obj_id, std::ptr::null_mut())
+ };
+ if !pre_existing_key.is_null() {
+ unsafe {
+ destroy_private_key_without_destroying_pkcs11_object(pre_existing_key);
+ }
+ return Err(ErrorKind::InternalError.into());
+ }
+ }
+ let template_len = c_int::try_from(template.len())?;
+ let mut id_attr: &mut nss_sys::CK_ATTRIBUTE = template
+ .iter_mut()
+ .find(|&&mut attr| {
+ attr.type_ == nss_sys::CKA_ID.into()
+ && attr.pValue.is_null()
+ && attr.ulValueLen == 0
+ })
+ .ok_or_else(|| ErrorKind::InternalError)?;
+ id_attr.pValue = obj_id_buf.as_mut_ptr() as *mut c_void;
+ id_attr.ulValueLen = nss_sys::CK_ULONG::try_from(obj_id_buf.len())?;
+ // We use `PK11_CreateGenericObject` instead of `PK11_CreateManagedGenericObject`
+ // to leak the reference on purpose because `PK11_FindKeyByKeyID` will take
+ // ownership of it.
+ let _obj = unsafe {
+ GenericObject::from_ptr(nss_sys::PK11_CreateGenericObject(
+ slot.as_mut_ptr(),
+ template.as_mut_ptr(),
+ template_len,
+ nss_sys::PR_FALSE,
+ ))?
+ };
+ // Have NSS translate the object to a private key.
+ Ok(unsafe {
+ PrivateKey::from_ptr(nss_sys::PK11_FindKeyByKeyID(
+ slot.as_mut_ptr(),
+ &mut obj_id,
+ std::ptr::null_mut(),
+ ))?
+ })
+ }
+}
+
+// This is typically used by functions receiving a pointer to an `out SECItem`,
+// where we allocate the struct, but NSS allocates the elements it points to.
+pub(crate) struct ScopedSECItem {
+ wrapped: nss_sys::SECItem,
+}
+
+impl ScopedSECItem {
+ pub(crate) fn empty(r#type: nss_sys::SECItemType) -> Self {
+ ScopedSECItem {
+ wrapped: nss_sys::SECItem {
+ type_: r#type as u32,
+ data: ptr::null_mut(),
+ len: 0,
+ },
+ }
+ }
+
+ pub(crate) fn as_mut_ref(&mut self) -> &mut nss_sys::SECItem {
+ &mut self.wrapped
+ }
+}
+
+impl Deref for ScopedSECItem {
+ type Target = nss_sys::SECItem;
+ #[inline]
+ fn deref(&self) -> &nss_sys::SECItem {
+ &self.wrapped
+ }
+}
+
+impl Drop for ScopedSECItem {
+ fn drop(&mut self) {
+ unsafe {
+ // PR_FALSE asks the NSS allocator not to free the SECItem
+ // itself, and just the pointee of `self.wrapped.data`.
+ nss_sys::SECITEM_FreeItem(&mut self.wrapped, nss_sys::PR_FALSE);
+ }
+ }
+}
+
+// This helper function will release the memory backing a SECKEYPrivateKey and
+// any resources acquired in its creation. It will leave the backing PKCS#11
+// object untouched, however. This should only be called from
+// PrivateKeyFromPrivateKeyTemplate.
+// From: https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/CryptoKey.cpp#80
+unsafe fn destroy_private_key_without_destroying_pkcs11_object(
+ key: *mut nss_sys::SECKEYPrivateKey,
+) {
+ assert!(!key.is_null());
+ nss_sys::PK11_FreeSlot((*key).pkcs11Slot);
+ nss_sys::PORT_FreeArena((*key).arena, nss_sys::PR_TRUE);
+}
diff --git a/third_party/rust/nss/src/secport.rs b/third_party/rust/nss/src/secport.rs
new file mode 100644
index 0000000000..fc56257155
--- /dev/null
+++ b/third_party/rust/nss/src/secport.rs
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util::ensure_nss_initialized;
+use std::os::raw::c_void;
+
+pub fn secure_memcmp(a: &[u8], b: &[u8]) -> bool {
+ ensure_nss_initialized();
+ // NSS_SecureMemcmp will compare N elements fron our slices,
+ // so make sure they are the same length first.
+ if a.len() != b.len() {
+ return false;
+ }
+ let result = unsafe {
+ nss_sys::NSS_SecureMemcmp(
+ a.as_ptr() as *const c_void,
+ b.as_ptr() as *const c_void,
+ a.len(),
+ )
+ };
+ result == 0
+}
diff --git a/third_party/rust/nss/src/util.rs b/third_party/rust/nss/src/util.rs
new file mode 100644
index 0000000000..39bf26c622
--- /dev/null
+++ b/third_party/rust/nss/src/util.rs
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use nss_sys::*;
+use std::{convert::TryFrom, ffi::CString, os::raw::c_char, sync::Once};
+
+// This is the NSS version that this crate is claiming to be compatible with.
+// We check it at runtime using `NSS_VersionCheck`.
+pub const COMPATIBLE_NSS_VERSION: &str = "3.26";
+
+static NSS_INIT: Once = Once::new();
+
+pub fn ensure_nss_initialized() {
+ NSS_INIT.call_once(|| {
+ let version_ptr = CString::new(COMPATIBLE_NSS_VERSION).unwrap();
+ if unsafe { NSS_VersionCheck(version_ptr.as_ptr()) == PR_FALSE } {
+ panic!("Incompatible NSS version!")
+ }
+ let empty = CString::default();
+ let flags = NSS_INIT_READONLY
+ | NSS_INIT_NOCERTDB
+ | NSS_INIT_NOMODDB
+ | NSS_INIT_FORCEOPEN
+ | NSS_INIT_OPTIMIZESPACE;
+ let context = unsafe {
+ NSS_InitContext(
+ empty.as_ptr(),
+ empty.as_ptr(),
+ empty.as_ptr(),
+ empty.as_ptr(),
+ std::ptr::null_mut(),
+ flags,
+ )
+ };
+ if context.is_null() {
+ let error = get_last_error();
+ panic!("Could not initialize NSS: {}", error);
+ }
+ })
+}
+
+pub fn map_nss_secstatus<F>(callback: F) -> Result<()>
+where
+ F: FnOnce() -> SECStatus,
+{
+ if callback() == SECStatus::SECSuccess {
+ return Ok(());
+ }
+ Err(get_last_error())
+}
+
+/// Retrieve and wrap the last NSS/NSPR error in the current thread.
+#[cold]
+pub fn get_last_error() -> Error {
+ let error_code = unsafe { PR_GetError() };
+ let error_text: String = usize::try_from(unsafe { PR_GetErrorTextLength() })
+ .map(|error_text_len| {
+ let mut out_str = vec![0u8; error_text_len + 1];
+ unsafe { PR_GetErrorText(out_str.as_mut_ptr() as *mut c_char) };
+ CString::new(&out_str[0..error_text_len])
+ .unwrap_or_else(|_| CString::default())
+ .to_str()
+ .unwrap_or_else(|_| "")
+ .to_owned()
+ })
+ .unwrap_or_else(|_| "".to_string());
+ ErrorKind::NSSError(error_code, error_text).into()
+}
+
+pub(crate) trait ScopedPtr
+where
+ Self: std::marker::Sized,
+{
+ type RawType;
+ unsafe fn from_ptr(ptr: *mut Self::RawType) -> Result<Self>;
+ fn as_ptr(&self) -> *const Self::RawType;
+ fn as_mut_ptr(&self) -> *mut Self::RawType;
+}
+
+// The macro defines a wrapper around pointers refering to types allocated by NSS,
+// calling their NSS destructor method when they go out of scope to avoid memory leaks.
+// The `as_ptr`/`as_mut_ptr` are provided to retrieve the raw pointers to pass to
+// NSS functions that consume them.
+#[macro_export]
+macro_rules! scoped_ptr {
+ ($scoped:ident, $target:ty, $dtor:path) => {
+ pub struct $scoped {
+ ptr: *mut $target,
+ }
+
+ impl crate::util::ScopedPtr for $scoped {
+ type RawType = $target;
+
+ #[allow(dead_code)]
+ unsafe fn from_ptr(ptr: *mut $target) -> crate::error::Result<$scoped> {
+ if !ptr.is_null() {
+ Ok($scoped { ptr })
+ } else {
+ Err(crate::error::ErrorKind::InternalError.into())
+ }
+ }
+
+ #[inline]
+ fn as_ptr(&self) -> *const $target {
+ self.ptr
+ }
+
+ #[inline]
+ fn as_mut_ptr(&self) -> *mut $target {
+ self.ptr
+ }
+ }
+
+ impl Drop for $scoped {
+ fn drop(&mut self) {
+ assert!(!self.ptr.is_null());
+ unsafe { $dtor(self.ptr) };
+ }
+ }
+ };
+}
+
+pub(crate) unsafe fn sec_item_as_slice(sec_item: &mut SECItem) -> Result<&mut [u8]> {
+ let sec_item_buf_len = usize::try_from(sec_item.len)?;
+ let buf = std::slice::from_raw_parts_mut(sec_item.data, sec_item_buf_len);
+ Ok(buf)
+}