Skip to content

Commit 4ce57fd

Browse files
crypto: migrate non-TLS crypto primitives from openssl/sha2 to aws-lc-rs
Migrate 5 leaf crates that use openssl or sha2/digest for hashing and signing (not TLS) to aws-lc-rs equivalents. This consolidates crypto backends ahead of the full TLS migration. Crates migrated: - mz-auth: SCRAM-SHA-256 (openssl HMAC/SHA → aws-lc-rs HMAC/digest) - mz-ssh-util: SSH key fingerprinting (openssl SHA-256 → aws-lc-rs digest) - mz-expr: scalar hash functions (sha1/sha2/subtle → aws-lc-rs) - mz-avro: schema fingerprinting (digest/sha2 → aws-lc-rs digest) - mz-license-keys: signature verification (sha2 → aws-lc-rs digest) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c648256 commit 4ce57fd

File tree

13 files changed

+117
-121
lines changed

13 files changed

+117
-121
lines changed

Cargo.lock

Lines changed: 5 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/auth/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ serde = "1.0.219"
1616
proptest-derive = "0.8.0"
1717
proptest = "1.10.0"
1818
static_assertions = "1.1"
19-
openssl = { version = "0.10.76", features = ["vendored"] }
19+
aws-lc-rs = "1"
2020
itertools = "0.14.0"
2121

2222
[features]

src/auth/src/hash.rs

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
use std::fmt::Display;
1414
use std::num::NonZeroU32;
1515

16+
use aws_lc_rs::constant_time::verify_slices_are_equal;
17+
use aws_lc_rs::digest;
18+
use aws_lc_rs::hmac;
19+
use aws_lc_rs::rand::{SecureRandom, SystemRandom};
1620
use base64::prelude::*;
1721
use itertools::Itertools;
1822
use mz_ore::secure::{Zeroize, Zeroizing};
@@ -67,13 +71,13 @@ pub enum VerifyError {
6771

6872
#[derive(Debug)]
6973
pub enum HashError {
70-
Openssl(openssl::error::ErrorStack),
74+
Crypto(aws_lc_rs::error::Unspecified),
7175
}
7276

7377
impl Display for HashError {
7478
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7579
match self {
76-
HashError::Openssl(e) => write!(f, "OpenSSL error: {}", e),
80+
HashError::Crypto(e) => write!(f, "crypto error: {}", e),
7781
}
7882
}
7983
}
@@ -84,8 +88,9 @@ pub fn hash_password(
8488
password: &Password,
8589
iterations: &NonZeroU32,
8690
) -> Result<PasswordHash, HashError> {
91+
let rng = SystemRandom::new();
8792
let mut salt = Zeroizing::new([0u8; DEFAULT_SALT_SIZE]);
88-
openssl::rand::rand_bytes(&mut *salt).map_err(HashError::Openssl)?;
93+
rng.fill(&mut *salt).map_err(HashError::Crypto)?;
8994

9095
let hash = hash_password_inner(
9196
&HashOpts {
@@ -103,8 +108,9 @@ pub fn hash_password(
103108
}
104109

105110
pub fn generate_nonce(client_nonce: &str) -> Result<String, HashError> {
111+
let rng = SystemRandom::new();
106112
let mut nonce = Zeroizing::new([0u8; 24]);
107-
openssl::rand::rand_bytes(&mut *nonce).map_err(HashError::Openssl)?;
113+
rng.fill(&mut *nonce).map_err(HashError::Crypto)?;
108114
let nonce = BASE64_STANDARD.encode(&*nonce);
109115
let new_nonce = format!("{}{}", client_nonce, nonce);
110116
Ok(new_nonce)
@@ -134,10 +140,7 @@ pub fn scram256_hash(password: &Password, iterations: &NonZeroU32) -> Result<Str
134140
}
135141

136142
fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
137-
if a.len() != b.len() {
138-
return false;
139-
}
140-
openssl::memcmp::eq(a, b)
143+
verify_slices_are_equal(a, b).is_ok()
141144
}
142145

143146
/// Verifies a password against a SCRAM-SHA-256 hash.
@@ -205,7 +208,8 @@ pub fn sasl_verify(
205208
.collect(),
206209
);
207210

208-
if !constant_time_compare(&openssl::sha::sha256(&client_key), &stored_key) {
211+
let computed_stored_key = digest::digest(&digest::SHA256, &client_key);
212+
if !constant_time_compare(computed_stored_key.as_ref(), &stored_key) {
209213
return Err(VerifyError::InvalidPassword);
210214
}
211215

@@ -215,18 +219,9 @@ pub fn sasl_verify(
215219
}
216220

217221
fn generate_signature(key: &[u8], message: &str) -> Result<Zeroizing<Vec<u8>>, VerifyError> {
218-
let signing_key =
219-
openssl::pkey::PKey::hmac(key).map_err(|e| VerifyError::Hash(HashError::Openssl(e)))?;
220-
let mut signer =
221-
openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), &signing_key)
222-
.map_err(|e| VerifyError::Hash(HashError::Openssl(e)))?;
223-
signer
224-
.update(message.as_bytes())
225-
.map_err(|e| VerifyError::Hash(HashError::Openssl(e)))?;
226-
let signature = signer
227-
.sign_to_vec()
228-
.map_err(|e| VerifyError::Hash(HashError::Openssl(e)))?;
229-
Ok(Zeroizing::new(signature))
222+
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, key);
223+
let tag = hmac::sign(&signing_key, message.as_bytes());
224+
Ok(Zeroizing::new(tag.as_ref().to_vec()))
230225
}
231226

232227
// Generate a mock challenge based on the username and client nonce
@@ -236,11 +231,13 @@ pub fn mock_sasl_challenge(username: &str, mock_nonce: &str, iterations: &NonZer
236231
let mut buf = Vec::with_capacity(username.len() + mock_nonce.len());
237232
buf.extend_from_slice(username.as_bytes());
238233
buf.extend_from_slice(mock_nonce.as_bytes());
239-
let digest = openssl::sha::sha256(&buf);
234+
let hash = digest::digest(&digest::SHA256, &buf);
235+
let mut salt = [0u8; DEFAULT_SALT_SIZE];
236+
salt.copy_from_slice(hash.as_ref());
240237

241238
HashOpts {
242239
iterations: iterations.to_owned(),
243-
salt: digest,
240+
salt,
244241
}
245242
}
246243

@@ -313,17 +310,16 @@ impl Display for ScramSha256Hash {
313310
}
314311

315312
fn scram256_hash_inner(hashed_password: PasswordHash) -> ScramSha256Hash {
316-
let signing_key = openssl::pkey::PKey::hmac(&hashed_password.hash).unwrap();
317-
let mut signer =
318-
openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), &signing_key).unwrap();
319-
signer.update(b"Client Key").unwrap();
320-
let client_key = Zeroizing::new(signer.sign_to_vec().unwrap());
321-
let stored_key = openssl::sha::sha256(&client_key);
322-
let mut signer =
323-
openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), &signing_key).unwrap();
324-
signer.update(b"Server Key").unwrap();
313+
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &hashed_password.hash);
314+
let client_key_tag = hmac::sign(&signing_key, b"Client Key");
315+
let client_key = Zeroizing::new(client_key_tag.as_ref().to_vec());
316+
let stored_key_digest = digest::digest(&digest::SHA256, &client_key);
317+
let mut stored_key = [0u8; SHA256_OUTPUT_LEN];
318+
stored_key.copy_from_slice(stored_key_digest.as_ref());
319+
320+
let server_key_tag = hmac::sign(&signing_key, b"Server Key");
325321
let mut server_key = Zeroizing::new([0u8; SHA256_OUTPUT_LEN]);
326-
signer.sign(server_key.as_mut()).unwrap();
322+
server_key.copy_from_slice(server_key_tag.as_ref());
327323

328324
ScramSha256Hash {
329325
iterations: hashed_password.iterations,
@@ -338,14 +334,13 @@ fn hash_password_inner(
338334
password: &[u8],
339335
) -> Result<[u8; SHA256_OUTPUT_LEN], HashError> {
340336
let mut salted_password = Zeroizing::new([0u8; SHA256_OUTPUT_LEN]);
341-
openssl::pkcs5::pbkdf2_hmac(
342-
password,
337+
aws_lc_rs::pbkdf2::derive(
338+
aws_lc_rs::pbkdf2::PBKDF2_HMAC_SHA256,
339+
opts.iterations,
343340
&opts.salt,
344-
opts.iterations.get().try_into().unwrap(),
345-
openssl::hash::MessageDigest::sha256(),
341+
password,
346342
&mut *salted_password,
347-
)
348-
.map_err(HashError::Openssl)?;
343+
);
349344
Ok(*salted_password)
350345
}
351346

@@ -358,7 +353,7 @@ mod tests {
358353
const DEFAULT_ITERATIONS: NonZeroU32 = NonZeroU32::new(60).expect("Trust me on this");
359354

360355
#[mz_ore::test]
361-
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `OPENSSL_init_ssl` on OS `linux`
356+
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function on OS `linux`
362357
fn test_hash_password() {
363358
let password = "password".to_string();
364359
let iterations = NonZeroU32::new(100).expect("Trust me on this");
@@ -370,7 +365,7 @@ mod tests {
370365
}
371366

372367
#[mz_ore::test]
373-
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `OPENSSL_init_ssl` on OS `linux`
368+
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function on OS `linux`
374369
fn test_scram256_hash() {
375370
let password = "password".into();
376371
let scram_hash =
@@ -436,12 +431,9 @@ mod tests {
436431
let salted_password = hash_password_with_opts(&opts, &password)
437432
.expect("hash password")
438433
.hash;
439-
let signing_key = openssl::pkey::PKey::hmac(&salted_password).expect("signing key");
440-
let mut signer =
441-
openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), &signing_key)
442-
.expect("signer");
443-
signer.update(b"Client Key").expect("update");
444-
let client_key = signer.sign_to_vec().expect("client key");
434+
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &salted_password);
435+
let client_key = hmac::sign(&signing_key, b"Client Key");
436+
let client_key = client_key.as_ref();
445437
// client_proof = client_key XOR client_signature
446438
let client_signature =
447439
generate_signature(&stored_key, auth_message).expect("client signature");

src/avro/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ anyhow = "1.0.102"
1919
byteorder = { version = "1.4.3", optional = true }
2020
chrono = { version = "0.4.39", default-features = false, features = ["std"] }
2121
crc32fast = { version = "1.3.2", optional = true }
22-
digest = "0.10.7"
22+
aws-lc-rs = "1"
2323
enum-kinds = "0.5.1"
2424
flate2 = "1.1.9"
2525
itertools = "0.14.0"
@@ -28,7 +28,6 @@ rand = "0.9.2"
2828
regex = "1.12.3"
2929
serde = { version = "1.0.219", features = ["derive"] }
3030
serde_json = "1.0.149"
31-
sha2 = "0.10.9"
3231
snap = { version = "1.1.1", optional = true }
3332
tracing = "0.1.44"
3433
uuid = "1.19.0"

src/avro/src/reader.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
use std::collections::BTreeMap;
2727
use std::str::{FromStr, from_utf8};
2828

29+
use aws_lc_rs::digest;
2930
use serde_json::from_slice;
30-
use sha2::Sha256;
3131

3232
use crate::decode::{AvroRead, decode};
3333
use crate::error::{DecodeError, Error as AvroError};
@@ -195,8 +195,8 @@ impl<R: AvroRead> Reader<R> {
195195
let header = Header::from_reader(&mut inner)?;
196196

197197
let writer_schema = &header.writer_schema;
198-
let resolved_schema = if reader_schema.fingerprint::<Sha256>().bytes
199-
!= writer_schema.fingerprint::<Sha256>().bytes
198+
let resolved_schema = if reader_schema.fingerprint(&digest::SHA256).bytes
199+
!= writer_schema.fingerprint(&digest::SHA256).bytes
200200
{
201201
Some(resolve_schemas(writer_schema, reader_schema)?)
202202
} else {

src/avro/src/schema.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use std::rc::Rc;
3232
use std::str::FromStr;
3333
use std::sync::LazyLock;
3434

35-
use digest::Digest;
35+
use aws_lc_rs::digest;
3636
use itertools::Itertools;
3737
use mz_ore::assert_none;
3838
use regex::Regex;
@@ -1495,11 +1495,10 @@ impl Schema {
14951495
///
14961496
/// [Parsing Canonical Form]:
14971497
/// https://avro.apache.org/docs/++version++/specification#parsing-canonical-form-for-schemas
1498-
pub fn fingerprint<D: Digest>(&self) -> SchemaFingerprint {
1499-
let mut d = D::new();
1500-
d.update(self.canonical_form());
1498+
pub fn fingerprint(&self, algorithm: &'static digest::Algorithm) -> SchemaFingerprint {
1499+
let hash = digest::digest(algorithm, self.canonical_form().as_bytes());
15011500
SchemaFingerprint {
1502-
bytes: d.finalize().to_vec(),
1501+
bytes: hash.as_ref().to_vec(),
15031502
}
15041503
}
15051504

@@ -3076,8 +3075,6 @@ mod tests {
30763075
#[mz_ore::test]
30773076
#[cfg_attr(miri, ignore)] // unsupported operation: inline assembly is not supported
30783077
fn test_schema_fingerprint() {
3079-
use sha2::Sha256;
3080-
30813078
let raw_schema = r#"
30823079
{
30833080
"type": "record",
@@ -3091,9 +3088,13 @@ mod tests {
30913088
let expected_canonical = r#"{"name":"test","type":"record","fields":[{"name":"a","type":"long"},{"name":"b","type":"string"}]}"#;
30923089
let schema = Schema::from_str(raw_schema).unwrap();
30933090
assert_eq!(&schema.canonical_form(), expected_canonical);
3094-
let expected_fingerprint = format!("{:02x}", Sha256::digest(expected_canonical));
3091+
let expected_fingerprint = digest::digest(&digest::SHA256, expected_canonical.as_bytes())
3092+
.as_ref()
3093+
.iter()
3094+
.map(|b| format!("{b:02x}"))
3095+
.collect::<String>();
30953096
assert_eq!(
3096-
format!("{}", schema.fingerprint::<Sha256>()),
3097+
format!("{}", schema.fingerprint(&digest::SHA256)),
30973098
expected_fingerprint
30983099
);
30993100

@@ -3117,9 +3118,13 @@ mod tests {
31173118
let expected_canonical = r#"{"name":"ns.r1","type":"record","fields":[{"name":"f1","type":{"name":"ns.r2","type":"fixed","size":1}}]}"#;
31183119
let schema = Schema::from_str(raw_schema).unwrap();
31193120
assert_eq!(&schema.canonical_form(), expected_canonical);
3120-
let expected_fingerprint = format!("{:02x}", Sha256::digest(expected_canonical));
3121+
let expected_fingerprint = digest::digest(&digest::SHA256, expected_canonical.as_bytes())
3122+
.as_ref()
3123+
.iter()
3124+
.map(|b| format!("{b:02x}"))
3125+
.collect::<String>();
31213126
assert_eq!(
3122-
format!("{}", schema.fingerprint::<Sha256>()),
3127+
format!("{}", schema.fingerprint(&digest::SHA256)),
31233128
expected_fingerprint
31243129
);
31253130
}

src/expr/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ regex-syntax = "0.8.10"
6262
seahash = "4.1.0"
6363
serde = { version = "1.0.219", features = ["derive"] }
6464
serde_json = "1.0.149"
65-
sha1 = "0.10.6"
66-
sha2 = "0.10.9"
67-
subtle = "2.6.1"
65+
aws-lc-rs = "1"
6866
tracing = "0.1.44"
6967
uncased = "0.9.7"
7068
unicode-normalization = "0.1.25"

0 commit comments

Comments
 (0)