diff --git a/Cargo.lock b/Cargo.lock index d1fd9a370..fd2f9d0f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3671,12 +3671,13 @@ dependencies = [ "flatbuffers", "flate2", "futures", + "hex", "http 1.4.2", "http-body-util", "hyper", "hyper-util", "local-ip-address", - "md5", + "md-5 0.11.0", "opentelemetry-proto", "prost 0.14.4", "rand 0.10.2", @@ -8890,7 +8891,7 @@ dependencies = [ "libmimalloc-sys", "libsystemd", "matchit 0.9.2", - "md5", + "md-5 0.11.0", "metrics", "metrics-util", "mimalloc", @@ -9449,9 +9450,10 @@ dependencies = [ "async-trait", "base64 0.23.0", "chacha20poly1305", + "hex", "insta", "jiff", - "md5", + "md-5 0.11.0", "moka", "rand 0.10.2", "reqwest", @@ -9718,7 +9720,7 @@ dependencies = [ "hyper-util", "ipnetwork", "libunftp", - "md5", + "md-5 0.11.0", "percent-encoding", "proptest", "quick-xml", diff --git a/Cargo.toml b/Cargo.toml index 8f0a1db8b..f4de6dbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -265,7 +265,6 @@ memmap2 = "0.9.11" lz4 = "1.28.1" matchit = "0.9.2" md-5 = "0.11.0" -md5 = "0.8.1" mime_guess = "2.0.5" moka = { version = "0.12.15" } netif = "0.1.6" diff --git a/crates/e2e_test/Cargo.toml b/crates/e2e_test/Cargo.toml index 028441008..00ddbc47a 100644 --- a/crates/e2e_test/Cargo.toml +++ b/crates/e2e_test/Cargo.toml @@ -70,7 +70,8 @@ walkdir.workspace = true base64 = { workspace = true } rand = { workspace = true, features = ["serde"] } chrono = { workspace = true, features = ["serde"] } -md5 = { workspace = true } +hex = { workspace = true } +md-5 = { workspace = true } opentelemetry-proto = { workspace = true } prost.workspace = true sha2 = { workspace = true } diff --git a/crates/e2e_test/src/checksum_upload_test.rs b/crates/e2e_test/src/checksum_upload_test.rs index 622545dd6..efa4a81b8 100644 --- a/crates/e2e_test/src/checksum_upload_test.rs +++ b/crates/e2e_test/src/checksum_upload_test.rs @@ -24,9 +24,10 @@ mod tests { use aws_sdk_s3::types::{ChecksumAlgorithm, ChecksumMode, CompletedMultipartUpload, CompletedPart}; use aws_smithy_http_client::Builder as SmithyHttpClientBuilder; use base64::Engine; + use md5::{Digest as Md5Digest, Md5}; use rustfs_rio::{Checksum, ChecksumType as RioChecksumType}; use serial_test::serial; - use sha2::{Digest, Sha256}; + use sha2::Sha256; use tracing::info; fn create_s3_client(env: &RustFSTestEnvironment) -> Client { @@ -70,7 +71,9 @@ mod tests { } fn content_md5_base64(body: &[u8]) -> String { - let digest = md5::compute(body); + let mut hasher = Md5::new(); + hasher.update(body); + let digest = hasher.finalize(); base64::engine::general_purpose::STANDARD.encode(digest.as_slice()) } diff --git a/crates/e2e_test/src/fake_s3_target/mod.rs b/crates/e2e_test/src/fake_s3_target/mod.rs index ed7039930..48cbce288 100644 --- a/crates/e2e_test/src/fake_s3_target/mod.rs +++ b/crates/e2e_test/src/fake_s3_target/mod.rs @@ -25,6 +25,7 @@ use hyper::body::Incoming; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper_util::rt::{TokioIo, TokioTimer}; +use md5::{Digest as Md5Digest, Md5}; use s3s::access::{S3Access, S3AccessContext}; use s3s::auth::SimpleAuth; use s3s::dto::{ @@ -827,13 +828,25 @@ fn ensure_body_growth(current: usize, added: usize) -> S3Result { async fn md5_digest(body: Bytes, permit: OwnedSemaphorePermit) -> S3Result<([u8; 16], OwnedSemaphorePermit)> { if body.len() < 1024 * 1024 { - return Ok((md5::compute(body).0, permit)); + return Ok((md5_bytes(body), permit)); } - tokio::task::spawn_blocking(move || (md5::compute(body).0, permit)) + tokio::task::spawn_blocking(move || (md5_bytes(body), permit)) .await .map_err(|error| s3s::s3_error!(InternalError, "MD5 worker failed: {error}")) } +fn md5_bytes(input: impl AsRef<[u8]>) -> [u8; 16] { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hasher.finalize().into() +} + +fn md5_hex(input: impl AsRef<[u8]>) -> String { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hex::encode(hasher.finalize()) +} + fn ensure_store_budget(state: &StoreState, removed_bytes: usize, added_bytes: usize, adds_version: bool) -> S3Result { let total_bytes = state .total_bytes @@ -1005,7 +1018,7 @@ impl S3 for FakeBackend { Some(value) => value, None => { let (digest, _body_permit) = md5_digest(body.clone(), _body_permit).await?; - format!("{:x}", md5::Digest(digest)) + hex::encode(digest) } }; let version = ObjectVersion { @@ -1208,7 +1221,7 @@ impl S3 for FakeBackend { } let body = collect_stream(input.body, input.content_length, fault.as_ref(), &self.control).await?; let (digest, _body_permit) = md5_digest(body.clone(), _body_permit).await?; - let e_tag = format!("{:x}", md5::Digest(digest)); + let e_tag = hex::encode(digest); let mut state = lock(&self.store); let existing_bytes = state .uploads @@ -1336,7 +1349,7 @@ impl S3 for FakeBackend { .collect(); let (body, digests, _body_permits) = assemble_multipart(assembly_parts, total_len, _body_permits).await?; let part_count = requested.len(); - let e_tag = source_etag(&headers)?.unwrap_or_else(|| format!("{:x}-{part_count}", md5::compute(digests))); + let e_tag = source_etag(&headers)?.unwrap_or_else(|| format!("{}-{part_count}", md5_hex(digests))); let version = ObjectVersion { version_id: upload.version_id.clone(), body, diff --git a/crates/e2e_test/src/kms/common.rs b/crates/e2e_test/src/kms/common.rs index dcc370748..8d81cbc18 100644 --- a/crates/e2e_test/src/kms/common.rs +++ b/crates/e2e_test/src/kms/common.rs @@ -30,6 +30,7 @@ use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::ServerSideEncryption; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use http::header::{CONTENT_TYPE, HOST}; +use md5::{Digest as Md5Digest, Md5}; use rustfs_signer::constants::UNSIGNED_PAYLOAD; use rustfs_signer::sign_v4; use s3s::Body; @@ -68,7 +69,9 @@ pub fn skip_if_kms_admin_tool_unavailable(test_name: &str) -> bool { } pub fn sse_customer_key_md5_base64(key: &str) -> String { - BASE64.encode(md5::compute(key).0) + let mut hasher = Md5::new(); + hasher.update(key.as_bytes()); + BASE64.encode(hasher.finalize()) } pub async fn kms_admin_request( diff --git a/crates/e2e_test/src/kms/kms_edge_cases_test.rs b/crates/e2e_test/src/kms/kms_edge_cases_test.rs index a21af0282..fe4dcb66d 100644 --- a/crates/e2e_test/src/kms/kms_edge_cases_test.rs +++ b/crates/e2e_test/src/kms/kms_edge_cases_test.rs @@ -25,12 +25,18 @@ use super::common::{LocalKMSTestEnvironment, sse_customer_key_md5_base64}; use crate::common::{TEST_BUCKET, init_logging}; use aws_sdk_s3::types::ServerSideEncryption; use base64::Engine; -use md5::compute; +use md5::{Digest as Md5Digest, Md5}; use serial_test::serial; use std::sync::Arc; use tokio::sync::Semaphore; use tracing::{info, warn}; +fn md5_hex(input: impl AsRef<[u8]>) -> String { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hex::encode(hasher.finalize()) +} + /// Test encryption of zero-byte files (empty files) #[tokio::test] #[serial] @@ -294,7 +300,7 @@ async fn test_kms_invalid_key_scenarios() -> Result<(), Box) -> String { } fn sse_customer_key_md5_base64(key: &str) -> String { - base64::engine::general_purpose::STANDARD.encode(md5::compute(key).0) + let mut hasher = Md5::new(); + hasher.update(key.as_bytes()); + base64::engine::general_purpose::STANDARD.encode(hasher.finalize()) +} + +fn md5_hex(input: impl AsRef<[u8]>) -> String { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hex::encode(hasher.finalize()) } /// Env var consumed by the local SSE-S3 DEK provider when KMS is not configured. @@ -5664,7 +5673,7 @@ async fn test_signed_put_object_extract_returns_archive_etag() -> Result<(), Box client.create_bucket().bucket(bucket).send().await?; let archive = make_tar(&[("alpha.txt", b"alpha-body")], &[]).await; - let expected_etag = format!("\"{:x}\"", md5::compute(&archive)); + let expected_etag = format!("\"{}\"", md5_hex(&archive)); let response = client .put_object() diff --git a/crates/e2e_test/src/ssec_copy_test.rs b/crates/e2e_test/src/ssec_copy_test.rs index cf3245b6c..d5062d424 100644 --- a/crates/e2e_test/src/ssec_copy_test.rs +++ b/crates/e2e_test/src/ssec_copy_test.rs @@ -22,6 +22,7 @@ use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::{BucketVersioningStatus, CompletedMultipartUpload, CompletedPart, VersioningConfiguration}; use aws_smithy_http_client::Builder as SmithyHttpClientBuilder; use base64::Engine; +use md5::{Digest as Md5Digest, Md5}; use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; @@ -102,10 +103,12 @@ impl Intercept for ResponseHeaderCapture { fn customer_key(byte: u8) -> CustomerKey { let raw = [byte; 32]; + let mut hasher = Md5::new(); + hasher.update(raw); CustomerKey { raw: String::from_utf8_lossy(&raw).into_owned(), encoded: base64::engine::general_purpose::STANDARD.encode(raw), - md5: base64::engine::general_purpose::STANDARD.encode(md5::compute(raw).0), + md5: base64::engine::general_purpose::STANDARD.encode(hasher.finalize()), } } diff --git a/crates/kms/Cargo.toml b/crates/kms/Cargo.toml index 47b5c5fe7..370f1e90d 100644 --- a/crates/kms/Cargo.toml +++ b/crates/kms/Cargo.toml @@ -44,6 +44,7 @@ argon2 = { workspace = true } chacha20poly1305 = { workspace = true } rand = { workspace = true, features = ["serde"] } base64 = { workspace = true } +hex = { workspace = true } sha2 = { workspace = true } subtle = { workspace = true } zeroize = { workspace = true, features = ["derive"] } @@ -56,7 +57,7 @@ tempfile = { workspace = true } moka = { workspace = true, features = ["future"] } # Additional dependencies -md5 = { workspace = true } +md-5 = { workspace = true } arc-swap = { workspace = true } rustfs-utils = { workspace = true } rustfs-security-governance = { workspace = true } diff --git a/crates/kms/src/service.rs b/crates/kms/src/service.rs index 2cac74953..9b6874dfb 100644 --- a/crates/kms/src/service.rs +++ b/crates/kms/src/service.rs @@ -20,6 +20,7 @@ use crate::manager::KmsManager; use crate::types::*; use base64::Engine; use jiff::Zoned; +use md5::{Digest as Md5Digest, Md5}; use rand::random; use std::collections::HashMap; use std::io::Cursor; @@ -27,6 +28,12 @@ use tokio::io::{AsyncRead, AsyncReadExt}; use tracing::debug; use zeroize::Zeroize; +fn md5_hex(input: impl AsRef<[u8]>) -> String { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hex::encode(hasher.finalize()) +} + /// Data key for object encryption /// SECURITY: This struct automatically zeros sensitive key material when dropped #[derive(Debug, Clone)] @@ -486,8 +493,7 @@ impl ObjectEncryptionService { // Validate key MD5 if provided if let Some(expected_md5) = customer_key_md5 { - let actual_md5 = md5::compute(customer_key); - let actual_md5_hex = format!("{actual_md5:x}"); + let actual_md5_hex = md5_hex(customer_key); if actual_md5_hex != expected_md5.to_lowercase() { return Err(KmsError::validation_error("Customer key MD5 mismatch")); } diff --git a/crates/protocols/Cargo.toml b/crates/protocols/Cargo.toml index 003e4c4af..9baa72640 100644 --- a/crates/protocols/Cargo.toml +++ b/crates/protocols/Cargo.toml @@ -44,7 +44,7 @@ swift = [ "dep:tokio-util", "dep:serde", "dep:urlencoding", - "dep:md5", + "dep:md-5", "dep:quick-xml", "dep:hmac", "dep:sha1", @@ -108,7 +108,7 @@ http-body-util = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true, features = ["rt", "io", "compat"] } serde = { workspace = true, optional = true, features = ["derive"] } urlencoding = { workspace = true, optional = true } -md5 = { workspace = true, optional = true } +md-5 = { workspace = true, optional = true } quick-xml = { workspace = true, optional = true, features = ["serialize"] } hmac = { workspace = true, optional = true } sha1 = { workspace = true, optional = true } diff --git a/crates/protocols/src/swift/slo.rs b/crates/protocols/src/swift/slo.rs index ddb21d718..c53cd1f4d 100644 --- a/crates/protocols/src/swift/slo.rs +++ b/crates/protocols/src/swift/slo.rs @@ -21,6 +21,7 @@ use super::storage_api::large_object::HTTPRangeSpec; use super::{SwiftError, object}; use axum::http::{HeaderMap, Response, StatusCode}; +use md5::{Digest as Md5Digest, Md5}; use rustfs_credentials::Credentials; use s3s::Body; use serde::{Deserialize, Serialize}; @@ -81,9 +82,9 @@ impl SLOManifest { etag_concat.push_str(etag); } - // Calculate MD5 hash - let hash = md5::compute(etag_concat.as_bytes()); - format!("\"{:x}-{}\"", hash, self.segments.len()) + let mut hasher = Md5::new(); + hasher.update(etag_concat.as_bytes()); + format!("\"{}-{}\"", hex::encode(hasher.finalize()), self.segments.len()) } /// Validate manifest against actual segments diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 59e4d64d2..ca5cff296 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -164,7 +164,7 @@ const-str = { workspace = true, features = ["std", "proc"] } datafusion = { workspace = true, default-features = false, features = ["parquet", "recursive_protection", "sql"] } hex-simd.workspace = true matchit = { workspace = true } -md5.workspace = true +md-5 = { workspace = true } mime_guess = { workspace = true } percent-encoding = { workspace = true } pin-project-lite.workspace = true diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index e361dd4ee..1e789a20f 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -116,7 +116,7 @@ use crate::table_catalog; use bytes::Bytes; use futures::{Stream, StreamExt}; use http::{HeaderMap, HeaderValue, StatusCode}; -use md5::Context as Md5Context; +use md5::{Digest as Md5Digest, Md5}; use metrics::{counter, histogram}; use pin_project_lite::pin_project; use rustfs_concurrency::GetObjectQueueSnapshot; @@ -801,7 +801,7 @@ pin_project! { struct ExtractArchiveEtagReader { #[pin] inner: R, - md5: Md5Context, + md5: Md5, finished: bool, etag: Arc>>, } @@ -1854,7 +1854,7 @@ impl ExtractArchiveEtagReader { fn new(inner: R, etag: Arc>>) -> Self { Self { inner, - md5: Md5Context::new(), + md5: Md5::new(), finished: false, etag, } @@ -1870,11 +1870,11 @@ impl AsyncRead for ExtractArchiveEtagReader { Poll::Ready(Ok(())) => { let filled = &buf.filled()[before..]; if !filled.is_empty() { - this.md5.consume(filled); + this.md5.update(filled); } else if !*this.finished { *this.finished = true; if let Ok(mut etag) = this.etag.lock() { - *etag = Some(format!("{:x}", this.md5.clone().finalize())); + *etag = Some(hex_simd::encode_to_string(this.md5.clone().finalize(), hex_simd::AsciiCase::Lower)); } } Poll::Ready(Ok(())) diff --git a/rustfs/src/storage/sse.rs b/rustfs/src/storage/sse.rs index f98b4e72e..0cd426d6e 100644 --- a/rustfs/src/storage/sse.rs +++ b/rustfs/src/storage/sse.rs @@ -84,6 +84,7 @@ use chacha20poly1305::ChaCha20Poly1305; #[cfg(feature = "rio-v2")] use hmac::{Hmac, Mac}; use http::{HeaderMap, HeaderValue}; +use md5::{Digest as Md5Digest, Md5}; use rand::Rng; #[cfg(feature = "rio-v2")] use rand::RngExt; @@ -135,6 +136,16 @@ const SEALED_KEY_SIZE: usize = DARE_HEADER_SIZE + 32 + DARE_TAG_SIZE; #[cfg(feature = "rio-v2")] const OBJECT_KEY_DERIVATION_CONTEXT: &[u8] = b"object-encryption-key generation"; +fn md5_bytes(input: impl AsRef<[u8]>) -> [u8; 16] { + let mut hasher = Md5::new(); + hasher.update(input.as_ref()); + hasher.finalize().into() +} + +fn md5_base64(input: impl AsRef<[u8]>) -> String { + BASE64_STANDARD.encode(md5_bytes(input)) +} + use super::Error; use super::get_bucket_sse_config; use crate::error::ApiError; @@ -2527,7 +2538,7 @@ pub fn validate_ssec_params(params: SsecParams) -> Result Result [u8; 12] { let nonce_source = format!("{bucket}-{key}"); - let nonce_hash = md5::compute(nonce_source.as_bytes()); + let nonce_hash = md5_bytes(nonce_source.as_bytes()); let mut nonce = [0u8; 12]; - nonce.copy_from_slice(&nonce_hash.0[..12]); + nonce.copy_from_slice(&nonce_hash[..12]); nonce } @@ -2640,10 +2651,10 @@ mod tests { SseDekProvider, SsecParams, StorageError, TestSseDekProvider, apply_managed_decryption_material, apply_managed_encryption_material, encryption_material_to_metadata, extract_server_side_encryption_from_headers, extract_ssec_params_from_headers, extract_ssekms_context_from_headers, generate_ssec_nonce, is_managed_sse, - kms_operation_error, map_get_object_reader_error, mark_encrypted_multipart_metadata, normalize_managed_metadata, - reset_sse_dek_provider, resolve_effective_kms_key_id, sse_decryption, sse_encryption, sse_prepare_encryption, - strip_managed_encryption_metadata, validate_sse_headers_for_read, validate_sse_headers_for_write, validate_ssec_for_read, - validate_ssec_params, verify_ssec_key_match, + kms_operation_error, map_get_object_reader_error, mark_encrypted_multipart_metadata, md5_base64, + normalize_managed_metadata, reset_sse_dek_provider, resolve_effective_kms_key_id, sse_decryption, sse_encryption, + sse_prepare_encryption, strip_managed_encryption_metadata, validate_sse_headers_for_read, validate_sse_headers_for_write, + validate_ssec_for_read, validate_ssec_params, verify_ssec_key_match, }; #[cfg(feature = "rio-v2")] use super::{ @@ -2882,7 +2893,7 @@ mod tests { #[test] fn test_validate_ssec_params_success() { let key = BASE64_STANDARD.encode([42u8; 32]); - let key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let key_md5 = md5_base64([42u8; 32]); let params = SsecParams { algorithm: "AES256".to_string(), @@ -2899,7 +2910,7 @@ mod tests { #[test] fn test_validate_ssec_params_wrong_algorithm() { let key = BASE64_STANDARD.encode([42u8; 32]); - let key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let key_md5 = md5_base64([42u8; 32]); let params = SsecParams { algorithm: "AES128".to_string(), // Wrong algorithm @@ -2914,7 +2925,7 @@ mod tests { #[test] fn test_validate_ssec_params_wrong_key_length() { let key = BASE64_STANDARD.encode([42u8; 16]); // Only 16 bytes - let key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 16]).0); + let key_md5 = md5_base64([42u8; 16]); let params = SsecParams { algorithm: "AES256".to_string(), @@ -2946,7 +2957,7 @@ mod tests { let bucket = "test-bucket"; let key = "test-key"; let sse_key = BASE64_STANDARD.encode([42u8; 32]); - let sse_key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let sse_key_md5 = md5_base64([42u8; 32]); let content_size = 1024; let request_missing_md5 = EncryptionRequest { @@ -2999,7 +3010,7 @@ mod tests { async fn test_sse_prepare_encryption_rejects_partial_ssec_headers() { let bucket = "test-bucket"; let key = "test-key"; - let sse_key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let sse_key_md5 = md5_base64([42u8; 32]); let request_missing_algorithm = PrepareEncryptionRequest { bucket, @@ -3029,7 +3040,7 @@ mod tests { async fn test_sse_prepare_encryption_rejects_ssec_headers_without_customer_key() { let bucket = "test-bucket"; let key = "test-key"; - let sse_key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let sse_key_md5 = md5_base64([42u8; 32]); let request = PrepareEncryptionRequest { bucket, @@ -3087,7 +3098,7 @@ mod tests { let key = "object"; let customer_key_bytes = [0x24u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let customer_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let customer_key_md5 = md5_base64(customer_key_bytes); let metadata_one = ssec_direct_put_metadata(bucket, key, &customer_key, &customer_key_md5).await; let metadata_two = ssec_direct_put_metadata(bucket, key, &customer_key, &customer_key_md5).await; @@ -3128,7 +3139,7 @@ mod tests { let key = "object"; let customer_key_bytes = [0x24u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let customer_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let customer_key_md5 = md5_base64(customer_key_bytes); let mut metadata = HashMap::new(); metadata.insert("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()); @@ -3160,7 +3171,7 @@ mod tests { let key = "object"; let customer_key_bytes = [0x51u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let customer_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let customer_key_md5 = md5_base64(customer_key_bytes); let plaintext = b"attack at dawn - sse-c round trip".to_vec(); let metadata = ssec_direct_put_metadata(bucket, key, &customer_key, &customer_key_md5).await; @@ -3199,7 +3210,7 @@ mod tests { let key = "object"; let customer_key_bytes = [0x33u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let customer_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let customer_key_md5 = md5_base64(customer_key_bytes); let material = sse_prepare_encryption(PrepareEncryptionRequest { bucket, @@ -3261,7 +3272,7 @@ mod tests { let key = "test-key"; let customer_key_bytes = [0x24u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let sse_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let sse_key_md5 = md5_base64(customer_key_bytes); let request = PrepareEncryptionRequest { bucket, @@ -3356,7 +3367,7 @@ mod tests { let key = "test-key"; let content_size = 1024; let sse_key = BASE64_STANDARD.encode([42u8; 32]); - let sse_key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let sse_key_md5 = md5_base64([42u8; 32]); let request = EncryptionRequest { bucket, @@ -3745,7 +3756,7 @@ mod tests { async fn test_ssec_rio_v2_uses_sealed_object_key_metadata_roundtrip() { let customer_key_bytes = [0x42u8; 32]; let customer_key = BASE64_STANDARD.encode(customer_key_bytes); - let customer_key_md5 = BASE64_STANDARD.encode(md5::compute(customer_key_bytes).0); + let customer_key_md5 = md5_base64(customer_key_bytes); let material = sse_encryption(EncryptionRequest { bucket: "bucket", @@ -3825,7 +3836,7 @@ mod tests { ssekms_context: None, sse_customer_algorithm: Some("AES256".to_string()), sse_customer_key: Some(BASE64_STANDARD.encode(key_bytes)), - sse_customer_key_md5: Some(BASE64_STANDARD.encode(md5::compute(key_bytes).0)), + sse_customer_key_md5: Some(md5_base64(key_bytes)), content_size: 1, } } @@ -3834,10 +3845,7 @@ mod tests { let key_bytes = [key_byte; 32]; HashMap::from([ ("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()), - ( - "x-amz-server-side-encryption-customer-key-md5".to_string(), - BASE64_STANDARD.encode(md5::compute(key_bytes).0), - ), + ("x-amz-server-side-encryption-customer-key-md5".to_string(), md5_base64(key_bytes)), ]) } @@ -4547,7 +4555,7 @@ mod tests { fn test_validate_ssec_for_read_wrong_key() { // Key A is used to "encrypt" the object (stored MD5 is from key A). let key_a = [42u8; 32]; - let stored_md5 = BASE64_STANDARD.encode(md5::compute(key_a).0); + let stored_md5 = md5_base64(key_a); let mut metadata = HashMap::new(); metadata.insert("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()); @@ -4556,7 +4564,7 @@ mod tests { // Key B is a different key; its MD5 won't match stored MD5. let key_b = [99u8; 32]; let key_b_b64 = BASE64_STANDARD.encode(key_b); - let key_b_md5 = BASE64_STANDARD.encode(md5::compute(key_b).0); + let key_b_md5 = md5_base64(key_b); let err = validate_ssec_for_read(&metadata, Some(&key_b_b64), Some(&key_b_md5)).unwrap_err(); assert_eq!(err.code, S3ErrorCode::InvalidRequest); @@ -4566,7 +4574,7 @@ mod tests { fn test_validate_ssec_for_read_correct_key() { let key_bytes = [42u8; 32]; let key_b64 = BASE64_STANDARD.encode(key_bytes); - let key_md5 = BASE64_STANDARD.encode(md5::compute(key_bytes).0); + let key_md5 = md5_base64(key_bytes); let mut metadata = HashMap::new(); metadata.insert("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()); @@ -4582,7 +4590,7 @@ mod tests { // DIFFERENT key. The server must recompute MD5 from the key bytes and // reject the request because the recomputed MD5 won't match the header. let real_key = [42u8; 32]; - let stored_md5 = BASE64_STANDARD.encode(md5::compute(real_key).0); + let stored_md5 = md5_base64(real_key); let mut metadata = HashMap::new(); metadata.insert("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()); @@ -4694,7 +4702,7 @@ mod tests { #[test] fn test_validate_ssec_params_returns_invalid_request_on_bad_algorithm() { let key = BASE64_STANDARD.encode([42u8; 32]); - let key_md5 = BASE64_STANDARD.encode(md5::compute([42u8; 32]).0); + let key_md5 = md5_base64([42u8; 32]); let params = SsecParams { algorithm: "AES128".to_string(), key, @@ -4780,7 +4788,7 @@ mod tests { ssekms_context: None, sse_customer_algorithm: Some("unsupported-algo".to_string()), sse_customer_key: Some(sse_key), - sse_customer_key_md5: Some(BASE64_STANDARD.encode(md5::compute([42u8; 32]).0)), + sse_customer_key_md5: Some(md5_base64([42u8; 32])), content_size: 1024, }; let err = sse_encryption(request_unsupported_algorithm).await.unwrap_err();