diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/authenticator/src/crypto/openssl.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator/src/crypto/openssl.rs')
-rw-r--r-- | third_party/rust/authenticator/src/crypto/openssl.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/crypto/openssl.rs b/third_party/rust/authenticator/src/crypto/openssl.rs new file mode 100644 index 0000000000..84479bf49e --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/openssl.rs @@ -0,0 +1,165 @@ +use super::CryptoError; +use openssl::bn::BigNumContext; +use openssl::derive::Deriver; +use openssl::ec::{EcGroup, EcKey, PointConversionForm}; +use openssl::error::ErrorStack; +use openssl::hash::{hash, MessageDigest}; +use openssl::nid::Nid; +use openssl::pkey::{PKey, Private, Public}; +use openssl::rand::rand_bytes; +use openssl::sign::Signer; +use openssl::symm::{Cipher, Crypter, Mode}; +use std::os::raw::c_int; + +#[cfg(test)] +use openssl::ec::EcPoint; + +#[cfg(test)] +use openssl::bn::BigNum; + +const AES_BLOCK_SIZE: usize = 16; + +impl From<ErrorStack> for CryptoError { + fn from(e: ErrorStack) -> Self { + CryptoError::Backend(format!("{e}")) + } +} + +impl From<&ErrorStack> for CryptoError { + fn from(e: &ErrorStack) -> Self { + CryptoError::Backend(format!("{e}")) + } +} + +pub type Result<T> = std::result::Result<T, CryptoError>; + +/// ECDH using OpenSSL types. Computes the x coordinate of scalar multiplication of `peer_public` +/// by `client_private`. +fn ecdh_openssl_raw(client_private: EcKey<Private>, peer_public: EcKey<Public>) -> Result<Vec<u8>> { + let client_pkey = PKey::from_ec_key(client_private)?; + let peer_pkey = PKey::from_ec_key(peer_public)?; + let mut deriver = Deriver::new(&client_pkey)?; + deriver.set_peer(&peer_pkey)?; + let shared_point = deriver.derive_to_vec()?; + Ok(shared_point) +} + +/// 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>)> { + let peer_public = EcKey::public_key_from_der(peer_spki)?; + + // Hard-coding the P256 group here is easier than extracting a group name from peer_public and + // comparing it with P256. We'll fail in key derivation if peer_public is on the wrong curve. + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + + let mut bn_ctx = BigNumContext::new()?; + let client_private = EcKey::generate(&group)?; + let client_public_sec1 = client_private.public_key().to_bytes( + &group, + PointConversionForm::UNCOMPRESSED, + &mut bn_ctx, + )?; + + let shared_point = ecdh_openssl_raw(client_private, peer_public)?; + + Ok((shared_point, client_public_sec1)) +} + +/// 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>> { + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + let mut encrypter = Crypter::new(Cipher::aes_256_cbc(), Mode::Encrypt, key, Some(iv))?; + encrypter.pad(false); + + let in_len = data.len(); + if in_len % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + // OpenSSL would panic if we didn't allocate an extra block here. + let mut out = vec![0; in_len + AES_BLOCK_SIZE]; + let mut out_len = 0; + out_len += encrypter.update(data, out.as_mut_slice())?; + out_len += encrypter.finalize(out.as_mut_slice())?; + debug_assert_eq!(in_len, out_len); + + out.truncate(out_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>> { + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + let mut encrypter = Crypter::new(Cipher::aes_256_cbc(), Mode::Decrypt, key, Some(iv))?; + encrypter.pad(false); + + let in_len = data.len(); + if in_len % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + // OpenSSL would panic if we didn't allocate an extra block here. + let mut out = vec![0; in_len + AES_BLOCK_SIZE]; + let mut out_len = 0; + out_len += encrypter.update(data, out.as_mut_slice())?; + out_len += encrypter.finalize(out.as_mut_slice())?; + debug_assert_eq!(in_len, out_len); + + out.truncate(out_len); + Ok(out) +} + +/// Textbook HMAC-SHA256 +pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Vec<u8>> { + let key = PKey::hmac(key)?; + let mut signer = Signer::new(MessageDigest::sha256(), &key)?; + signer.update(data)?; + Ok(signer.sign_to_vec()?) +} + +pub fn sha256(data: &[u8]) -> Result<Vec<u8>> { + let digest = hash(MessageDigest::sha256(), data)?; + Ok(digest.as_ref().to_vec()) +} + +pub fn random_bytes(count: usize) -> Result<Vec<u8>> { + if count > c_int::MAX as usize { + return Err(CryptoError::LibraryFailure); + } + let mut out = vec![0u8; count]; + rand_bytes(&mut out)?; + 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>> { + let peer_public = EcKey::public_key_from_der(peer_spki)?; + let group = peer_public.group(); + + let mut client_pub_sec1 = vec![]; + client_pub_sec1.push(0x04); // SEC 1 encoded uncompressed point + client_pub_sec1.extend_from_slice(&client_public_x); + client_pub_sec1.extend_from_slice(&client_public_y); + + let mut ctx = BigNumContext::new()?; + let client_pub_point = EcPoint::from_bytes(&group, &client_pub_sec1, &mut ctx)?; + let client_priv_bignum = BigNum::from_slice(client_private)?; + let client_private = + EcKey::from_private_components(&group, &client_priv_bignum, &client_pub_point)?; + + let shared_point = ecdh_openssl_raw(client_private, peer_public)?; + + Ok(shared_point) +} |