diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/nss | |
parent | Initial commit. (diff) | |
download | firefox-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.json | 1 | ||||
-rw-r--r-- | third_party/rust/nss/Cargo.toml | 21 | ||||
-rw-r--r-- | third_party/rust/nss/README.md | 21 | ||||
-rw-r--r-- | third_party/rust/nss/src/aes.rs | 120 | ||||
-rw-r--r-- | third_party/rust/nss/src/ec.rs | 423 | ||||
-rw-r--r-- | third_party/rust/nss/src/ecdh.rs | 46 | ||||
-rw-r--r-- | third_party/rust/nss/src/error.rs | 24 | ||||
-rw-r--r-- | third_party/rust/nss/src/lib.rs | 17 | ||||
-rw-r--r-- | third_party/rust/nss/src/pbkdf2.rs | 78 | ||||
-rw-r--r-- | third_party/rust/nss/src/pk11/context.rs | 123 | ||||
-rw-r--r-- | third_party/rust/nss/src/pk11/mod.rs | 8 | ||||
-rw-r--r-- | third_party/rust/nss/src/pk11/slot.rs | 25 | ||||
-rw-r--r-- | third_party/rust/nss/src/pk11/sym_key.rs | 93 | ||||
-rw-r--r-- | third_party/rust/nss/src/pk11/types.rs | 220 | ||||
-rw-r--r-- | third_party/rust/nss/src/secport.rs | 23 | ||||
-rw-r--r-- | third_party/rust/nss/src/util.rs | 129 |
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) +} |