summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/crypto/nss.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/crypto/nss.rs')
-rw-r--r--third_party/rust/authenticator/src/crypto/nss.rs481
1 files changed, 481 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/crypto/nss.rs b/third_party/rust/authenticator/src/crypto/nss.rs
new file mode 100644
index 0000000000..56c23db5dd
--- /dev/null
+++ b/third_party/rust/authenticator/src/crypto/nss.rs
@@ -0,0 +1,481 @@
+use super::CryptoError;
+use nss_gk_api::p11::{
+ PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp,
+ PK11_Encrypt, PK11_ExportDERPrivateKeyInfo, PK11_GenerateKeyPairWithOpFlags,
+ PK11_GenerateRandom, PK11_HashBuf, PK11_ImportDERPrivateKeyInfoAndReturnKey, PK11_ImportSymKey,
+ PK11_PubDeriveWithKDF, PK11_SignWithMechanism, PrivateKey, PublicKey,
+ SECKEY_DecodeDERSubjectPublicKeyInfo, SECKEY_ExtractPublicKey, SECOidTag, Slot,
+ SubjectPublicKeyInfo, AES_BLOCK_SIZE, PK11_ATTR_EXTRACTABLE, PK11_ATTR_INSENSITIVE,
+ PK11_ATTR_SESSION, SHA256_LENGTH,
+};
+use nss_gk_api::{IntoResult, SECItem, SECItemBorrowed, ScopedSECItem, PR_FALSE};
+use pkcs11_bindings::{
+ CKA_DERIVE, CKA_ENCRYPT, CKA_SIGN, CKD_NULL, CKF_DERIVE, CKM_AES_CBC, CKM_ECDH1_DERIVE,
+ CKM_ECDSA_SHA256, CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC,
+};
+use std::convert::TryFrom;
+use std::os::raw::{c_int, c_uint};
+use std::ptr;
+
+use super::der;
+
+#[cfg(test)]
+use nss_gk_api::p11::PK11_VerifyWithMechanism;
+
+impl From<nss_gk_api::Error> for CryptoError {
+ fn from(e: nss_gk_api::Error) -> Self {
+ CryptoError::Backend(format!("{e}"))
+ }
+}
+
+pub type Result<T> = std::result::Result<T, CryptoError>;
+
+fn nss_public_key_from_der_spki(spki: &[u8]) -> Result<PublicKey> {
+ // TODO: replace this with an nss-gk-api function
+ // https://github.com/mozilla/nss-gk-api/issues/7
+ let mut spki_item = SECItemBorrowed::wrap(spki);
+ let spki_item_ptr: *mut SECItem = spki_item.as_mut();
+ let nss_spki = unsafe {
+ SubjectPublicKeyInfo::from_ptr(SECKEY_DecodeDERSubjectPublicKeyInfo(spki_item_ptr))?
+ };
+ let public_key = unsafe { PublicKey::from_ptr(SECKEY_ExtractPublicKey(*nss_spki))? };
+ Ok(public_key)
+}
+
+/// ECDH using NSS types. Computes the x coordinate of scalar multiplication of `peer_public` by
+/// `client_private`.
+fn ecdh_nss_raw(client_private: PrivateKey, peer_public: PublicKey) -> Result<Vec<u8>> {
+ let ecdh_x_coord = unsafe {
+ PK11_PubDeriveWithKDF(
+ *client_private,
+ *peer_public,
+ PR_FALSE,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ CKM_ECDH1_DERIVE,
+ CKM_SHA512_HMAC, // unused
+ CKA_DERIVE, // unused
+ 0,
+ CKD_NULL,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ )
+ .into_result()?
+ };
+ let ecdh_x_coord_bytes = ecdh_x_coord.as_bytes()?;
+ Ok(ecdh_x_coord_bytes.to_vec())
+}
+
+fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
+ // Hard-coding the P256 OID here is easier than extracting a group name from peer_public and
+ // comparing it with P256. We'll fail in `PK11_GenerateKeyPairWithOpFlags` if peer_public is on
+ // the wrong curve.
+ let oid_bytes = der::object_id(der::OID_SECP256R1_BYTES)?;
+ let mut oid = SECItemBorrowed::wrap(&oid_bytes);
+ let oid_ptr: *mut SECItem = oid.as_mut();
+
+ let slot = Slot::internal()?;
+
+ let mut client_public_ptr = ptr::null_mut();
+
+ // We have to be careful with error handling between the `PK11_GenerateKeyPairWithOpFlags` and
+ // `PublicKey::from_ptr` calls here, so I've wrapped them in the same unsafe block as a
+ // warning. TODO(jms) Replace this once there is a safer alternative.
+ // https://github.com/mozilla/nss-gk-api/issues/1
+ unsafe {
+ let client_private =
+ // Type of `param` argument depends on mechanism. For EC keygen it is
+ // `SECKEYECParams *` which is a typedef for `SECItem *`.
+ PK11_GenerateKeyPairWithOpFlags(
+ *slot,
+ CKM_EC_KEY_PAIR_GEN,
+ oid_ptr.cast(),
+ &mut client_public_ptr,
+ PK11_ATTR_EXTRACTABLE | PK11_ATTR_INSENSITIVE | PK11_ATTR_SESSION,
+ CKF_DERIVE,
+ CKF_DERIVE,
+ ptr::null_mut(),
+ )
+ .into_result()?;
+
+ let client_public = PublicKey::from_ptr(client_public_ptr)?;
+
+ Ok((client_private, client_public))
+ }
+}
+
+/// This returns a PKCS#8 ECPrivateKey and an uncompressed SEC1 public key.
+pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> {
+ nss_gk_api::init();
+
+ let (client_private, client_public) = generate_p256_nss()?;
+
+ let pkcs8_priv = unsafe {
+ let pkcs8_priv_item: ScopedSECItem =
+ PK11_ExportDERPrivateKeyInfo(*client_private, ptr::null_mut()).into_result()?;
+ pkcs8_priv_item.into_vec()
+ };
+
+ let sec1_pub = client_public.key_data()?;
+
+ Ok((pkcs8_priv, sec1_pub))
+}
+
+pub fn ecdsa_p256_sha256_sign_raw(private: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ let slot = Slot::internal()?;
+
+ let imported_private: PrivateKey = unsafe {
+ let mut imported_private_ptr = ptr::null_mut();
+ PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ *slot,
+ SECItemBorrowed::wrap(private).as_mut(),
+ ptr::null_mut(),
+ ptr::null_mut(),
+ PR_FALSE,
+ PR_FALSE,
+ 255, /* todo: expose KU_ flags in nss-gk-api */
+ &mut imported_private_ptr,
+ ptr::null_mut(),
+ );
+ imported_private_ptr.into_result()?
+ };
+
+ let signature_buf = vec![0; 64];
+ unsafe {
+ PK11_SignWithMechanism(
+ *imported_private,
+ CKM_ECDSA_SHA256,
+ ptr::null_mut(),
+ SECItemBorrowed::wrap(&signature_buf).as_mut(),
+ SECItemBorrowed::wrap(data).as_mut(),
+ )
+ .into_result()?;
+ }
+
+ let (r, s) = signature_buf.split_at(32);
+ der::sequence(&[&der::integer(r)?, &der::integer(s)?])
+}
+
+/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates
+/// an ephemeral P256 key pair. Returns
+/// 1) the x coordinate of the shared point, and
+/// 2) the uncompressed SEC 1 encoding of the ephemeral public key.
+pub fn ecdhe_p256_raw(peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
+ nss_gk_api::init();
+
+ let peer_public = nss_public_key_from_der_spki(peer_spki)?;
+
+ let (client_private, client_public) = generate_p256_nss()?;
+
+ let shared_point = ecdh_nss_raw(client_private, peer_public)?;
+
+ Ok((shared_point, client_public.key_data()?))
+}
+
+/// AES-256-CBC encryption for data that is a multiple of the AES block size (16 bytes) in length.
+/// Uses the zero IV if `iv` is None.
+pub fn encrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ if key.len() != 32 {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]);
+
+ if iv.len() != AES_BLOCK_SIZE {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let in_len = match c_uint::try_from(data.len()) {
+ Ok(in_len) => in_len,
+ _ => return Err(CryptoError::LibraryFailure),
+ };
+
+ if data.len() % AES_BLOCK_SIZE != 0 {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let slot = Slot::internal()?;
+
+ let sym_key = unsafe {
+ PK11_ImportSymKey(
+ *slot,
+ CKM_AES_CBC,
+ PK11Origin::PK11_OriginUnwrap,
+ CKA_ENCRYPT,
+ SECItemBorrowed::wrap(key).as_mut(),
+ ptr::null_mut(),
+ )
+ .into_result()?
+ };
+
+ let mut params = SECItemBorrowed::wrap(iv);
+ let params_ptr: *mut SECItem = params.as_mut();
+ let mut out_len: c_uint = 0;
+ let mut out = vec![0; data.len()];
+ unsafe {
+ PK11_Encrypt(
+ *sym_key,
+ CKM_AES_CBC,
+ params_ptr,
+ out.as_mut_ptr(),
+ &mut out_len,
+ in_len,
+ data.as_ptr(),
+ in_len,
+ )
+ .into_result()?
+ }
+ // CKM_AES_CBC should have output length equal to input length.
+ debug_assert_eq!(out_len, in_len);
+
+ Ok(out)
+}
+
+/// AES-256-CBC decryption for data that is a multiple of the AES block size (16 bytes) in length.
+/// Uses the zero IV if `iv` is None.
+pub fn decrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ if key.len() != 32 {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]);
+
+ if iv.len() != AES_BLOCK_SIZE {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let in_len = match c_uint::try_from(data.len()) {
+ Ok(in_len) => in_len,
+ _ => return Err(CryptoError::LibraryFailure),
+ };
+
+ if data.len() % AES_BLOCK_SIZE != 0 {
+ return Err(CryptoError::LibraryFailure);
+ }
+
+ let slot = Slot::internal()?;
+
+ let sym_key = unsafe {
+ PK11_ImportSymKey(
+ *slot,
+ CKM_AES_CBC,
+ PK11Origin::PK11_OriginUnwrap,
+ CKA_ENCRYPT,
+ SECItemBorrowed::wrap(key).as_mut(),
+ ptr::null_mut(),
+ )
+ .into_result()?
+ };
+
+ let mut params = SECItemBorrowed::wrap(iv);
+ let params_ptr: *mut SECItem = params.as_mut();
+ let mut out_len: c_uint = 0;
+ let mut out = vec![0; data.len()];
+ unsafe {
+ PK11_Decrypt(
+ *sym_key,
+ CKM_AES_CBC,
+ params_ptr,
+ out.as_mut_ptr(),
+ &mut out_len,
+ in_len,
+ data.as_ptr(),
+ in_len,
+ )
+ .into_result()?
+ }
+ // CKM_AES_CBC should have output length equal to input length.
+ debug_assert_eq!(out_len, in_len);
+
+ Ok(out)
+}
+
+/// Textbook HMAC-SHA256
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ let data_len = match u32::try_from(data.len()) {
+ Ok(data_len) => data_len,
+ _ => return Err(CryptoError::LibraryFailure),
+ };
+
+ let slot = Slot::internal()?;
+ let sym_key = unsafe {
+ PK11_ImportSymKey(
+ *slot,
+ CKM_SHA256_HMAC,
+ PK11Origin::PK11_OriginUnwrap,
+ CKA_SIGN,
+ SECItemBorrowed::wrap(key).as_mut(),
+ ptr::null_mut(),
+ )
+ .into_result()?
+ };
+ let param = SECItemBorrowed::make_empty();
+ let context = unsafe {
+ PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, *sym_key, param.as_ref())
+ .into_result()?
+ };
+ unsafe { PK11_DigestOp(*context, data.as_ptr(), data_len).into_result()? };
+ let mut digest = vec![0u8; SHA256_LENGTH];
+ let mut digest_len = 0u32;
+ unsafe {
+ PK11_DigestFinal(
+ *context,
+ digest.as_mut_ptr(),
+ &mut digest_len,
+ digest.len() as u32,
+ )
+ .into_result()?
+ }
+ assert_eq!(digest_len as usize, SHA256_LENGTH);
+ Ok(digest)
+}
+
+/// Textbook SHA256
+pub fn sha256(data: &[u8]) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ let data_len: i32 = match i32::try_from(data.len()) {
+ Ok(data_len) => data_len,
+ _ => return Err(CryptoError::LibraryFailure),
+ };
+ let mut digest = vec![0u8; SHA256_LENGTH];
+ unsafe {
+ PK11_HashBuf(
+ SECOidTag::SEC_OID_SHA256,
+ digest.as_mut_ptr(),
+ data.as_ptr(),
+ data_len,
+ )
+ .into_result()?
+ };
+ Ok(digest)
+}
+
+pub fn random_bytes(count: usize) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ let count_cint: c_int = match c_int::try_from(count) {
+ Ok(c) => c,
+ _ => return Err(CryptoError::LibraryFailure),
+ };
+
+ let mut out = vec![0u8; count];
+ unsafe { PK11_GenerateRandom(out.as_mut_ptr(), count_cint).into_result()? };
+ Ok(out)
+}
+
+#[cfg(test)]
+pub fn test_ecdh_p256_raw(
+ peer_spki: &[u8],
+ client_public_x: &[u8],
+ client_public_y: &[u8],
+ client_private: &[u8],
+) -> Result<Vec<u8>> {
+ nss_gk_api::init();
+
+ let peer_public = nss_public_key_from_der_spki(peer_spki)?;
+
+ // NSS has no mechanism to import a raw elliptic curve coordinate as a private key.
+ // We need to encode it in an RFC 5208 PrivateKeyInfo:
+ //
+ // PrivateKeyInfo ::= SEQUENCE {
+ // version Version,
+ // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+ // privateKey PrivateKey,
+ // attributes [0] IMPLICIT Attributes OPTIONAL }
+ //
+ // Version ::= INTEGER
+ // PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ // PrivateKey ::= OCTET STRING
+ // Attributes ::= SET OF Attribute
+ //
+ // The privateKey field will contain an RFC 5915 ECPrivateKey:
+ // ECPrivateKey ::= SEQUENCE {
+ // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ // privateKey OCTET STRING,
+ // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ // publicKey [1] BIT STRING OPTIONAL
+ // }
+
+ // PrivateKeyInfo
+ let priv_key_info = der::sequence(&[
+ // version
+ &der::integer(&[0x00])?,
+ // privateKeyAlgorithm
+ &der::sequence(&[
+ &der::object_id(der::OID_EC_PUBLIC_KEY_BYTES)?,
+ &der::object_id(der::OID_SECP256R1_BYTES)?,
+ ])?,
+ // privateKey
+ &der::octet_string(
+ // ECPrivateKey
+ &der::sequence(&[
+ // version
+ &der::integer(&[0x01])?,
+ // privateKey
+ &der::octet_string(client_private)?,
+ // publicKey
+ &der::context_specific_explicit_tag(
+ 1, // publicKey
+ &der::bit_string(&[&[0x04], client_public_x, client_public_y].concat())?,
+ )?,
+ ])?,
+ )?,
+ ])?;
+
+ // Now we can import the private key.
+ let slot = Slot::internal()?;
+ let mut priv_key_info_item = SECItemBorrowed::wrap(&priv_key_info);
+ let priv_key_info_item_ptr: *mut SECItem = priv_key_info_item.as_mut();
+ let mut client_private_ptr = ptr::null_mut();
+ unsafe {
+ PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ *slot,
+ priv_key_info_item_ptr,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ PR_FALSE,
+ PR_FALSE,
+ 255, /* todo: expose KU_ flags in nss-gk-api */
+ &mut client_private_ptr,
+ ptr::null_mut(),
+ )
+ };
+ let client_private = unsafe { PrivateKey::from_ptr(client_private_ptr) }?;
+
+ let shared_point = ecdh_nss_raw(client_private, peer_public)?;
+
+ Ok(shared_point)
+}
+
+#[cfg(test)]
+pub fn test_ecdsa_p256_sha256_verify_raw(
+ public: &[u8],
+ signature: &[u8],
+ data: &[u8],
+) -> Result<()> {
+ nss_gk_api::init();
+
+ let signature = der::read_p256_sig(signature)?;
+ let public = nss_public_key_from_der_spki(public)?;
+ unsafe {
+ PK11_VerifyWithMechanism(
+ *public,
+ CKM_ECDSA_SHA256,
+ ptr::null_mut(),
+ SECItemBorrowed::wrap(&signature).as_mut(),
+ SECItemBorrowed::wrap(data).as_mut(),
+ ptr::null_mut(),
+ )
+ .into_result()?
+ }
+ Ok(())
+}