chore(deps): migrate direct encoding deps to simd (#6690)

This commit is contained in:
houseme
2026-08-27 03:17:24 +08:00
committed by GitHub
parent 31031f2a46
commit ba7785d61d
81 changed files with 523 additions and 505 deletions
-1
View File
@@ -310,7 +310,6 @@ astral-tokio-tar = { workspace = true }
atoi = { workspace = true }
atomic_enum = { workspace = true }
async_zip = { workspace = true, default-features = false, features = ["tokio", "deflate"] }
base64 = { workspace = true }
zeroize = { workspace = true }
hmac = { workspace = true }
sha2 = { workspace = true }
+2 -3
View File
@@ -439,7 +439,6 @@ fn dispatch(entry: AuditEntry) {
#[cfg(test)]
mod tests {
use super::*;
use base64::Engine;
use rustfs_kms::backends::local::LocalKmsBackend;
use rustfs_kms::config::KmsConfig;
use rustfs_kms::types::{CreateKeyRequest, DeleteKeyRequest, DescribeKeyRequest, GenerateDataKeyRequest, KeySpec};
@@ -689,8 +688,8 @@ mod tests {
.expect("data key should be generated");
// What the endpoint hands back, and therefore what must not reappear.
let plaintext_b64 = base64::prelude::BASE64_STANDARD.encode(&response.plaintext_key);
let ciphertext_b64 = base64::prelude::BASE64_STANDARD.encode(&response.ciphertext_blob);
let plaintext_b64 = base64_simd::STANDARD.encode_to_string(&response.plaintext_key);
let ciphertext_b64 = base64_simd::STANDARD.encode_to_string(&response.ciphertext_blob);
assert!(!response.plaintext_key.is_empty(), "the test must drive real key material");
let redacted = rustfs_kms::redact_encryption_context(&std::collections::HashMap::from([
+9 -7
View File
@@ -46,7 +46,7 @@ use crate::admin::router::{AdminOperation, Operation, S3Router};
use crate::admin::runtime_sources::{current_deployment_id, current_kms_runtime_service_manager};
use crate::auth::{check_key_valid, get_session_token};
use crate::server::{ADMIN_PREFIX, RemoteAddr};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use base64_simd::STANDARD as BASE64;
use hyper::{HeaderMap, Method, StatusCode};
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
@@ -429,7 +429,7 @@ impl BackupEnvironment {
let decoded = Zeroizing::new(
BASE64
.decode(raw_kek.trim())
.decode_to_vec(raw_kek.trim())
.map_err(|_| (StatusCode::PRECONDITION_FAILED, format!("{ENV_KMS_BACKUP_KEK} must be base64-encoded")))?,
);
if decoded.len() != 32 {
@@ -516,7 +516,9 @@ fn reuses_business_secret(kek_material: &[u8], raw_kek: &str, config: &KmsConfig
if raw_kek == secret.as_str() || secret.as_bytes() == kek_material {
return true;
}
BASE64.decode(secret.as_str()).is_ok_and(|decoded| decoded == kek_material)
BASE64
.decode_to_vec(secret.as_str())
.is_ok_and(|decoded| decoded == kek_material)
})
}
@@ -1185,7 +1187,7 @@ mod tests {
const DEPLOYMENT: &str = "deployment-under-test";
fn test_kek_bytes() -> Vec<u8> {
BASE64.decode(TEST_KEK_B64).expect("test KEK must decode")
BASE64.decode_to_vec(TEST_KEK_B64).expect("test KEK must decode")
}
fn local_config(key_dir: PathBuf) -> KmsConfig {
@@ -1310,7 +1312,7 @@ mod tests {
.expect_err("an empty KEK must be refused");
assert_eq!(error.0, StatusCode::PRECONDITION_FAILED);
let short = BASE64.encode([0x11; 16]);
let short = BASE64.encode_to_string([0x11; 16]);
let error = BackupEnvironment::build(PathBuf::from("/tmp/root"), &short, "kek".to_string(), 1, &config)
.expect_err("a KEK that is not 32 bytes must be refused");
assert_eq!(error.0, StatusCode::PRECONDITION_FAILED);
@@ -1353,7 +1355,7 @@ mod tests {
);
// An unrelated KEK is accepted.
let independent = BASE64.encode([0x5a; 32]);
let independent = BASE64.encode_to_string([0x5a; 32]);
assert!(BackupEnvironment::build(PathBuf::from("/tmp/root"), &independent, "kek".to_string(), 1, &config).is_ok());
}
@@ -1372,7 +1374,7 @@ mod tests {
let master_key = "local-master-key-super-secret";
let vault_token = "hvs.vault-token-super-secret";
let approle_secret = "approle-secret-id-super-secret";
let static_key = BASE64.encode([0x7c; 32]);
let static_key = BASE64.encode_to_string([0x7c; 32]);
let local = local_config_with_master_key(PathBuf::from("/var/lib/rustfs/kms"), master_key);
let kv2 = vault_kv2_config(VaultAuthMethod::Token {
+2 -6
View File
@@ -1422,12 +1422,8 @@ mod tests {
#[test]
fn static_kms_config_is_not_persisted_with_cluster_configuration() {
use base64::Engine as _;
let config = rustfs_kms::KmsConfig::static_kms(
"static-key".to_string(),
base64::engine::general_purpose::STANDARD.encode([0x5au8; 32]),
);
let config =
rustfs_kms::KmsConfig::static_kms("static-key".to_string(), base64_simd::STANDARD.encode_to_string([0x5au8; 32]));
assert!(ensure_kms_config_persistable(&config).is_err());
}
@@ -408,7 +408,6 @@ impl Operation for UntagKmsKeyHandler {
mod tests {
use super::*;
use crate::admin::handlers::kms_keys::stable_json_value;
use base64::Engine as _;
use rustfs_kms::KmsManager;
use rustfs_kms::backends::local::LocalKmsBackend;
use rustfs_kms::backends::static_kms::StaticKmsBackend;
@@ -431,8 +430,7 @@ mod tests {
/// is the backend that must answer every metadata update with a capability
/// gap rather than a failure of the request.
async fn static_service() -> ObjectEncryptionService {
let config =
KmsConfig::static_kms("static-key".to_string(), base64::engine::general_purpose::STANDARD.encode([0x42u8; 32]));
let config = KmsConfig::static_kms("static-key".to_string(), base64_simd::STANDARD.encode_to_string([0x42u8; 32]));
let backend = Arc::new(
StaticKmsBackend::new(config.clone())
.await
+2 -3
View File
@@ -21,7 +21,6 @@ use crate::admin::runtime_sources::{current_kms_runtime_service_manager, current
use crate::auth::{check_key_valid, get_session_token};
use crate::kms_deletion_gate::current_key_impact;
use crate::server::{ADMIN_PREFIX, RemoteAddr};
use base64::Engine;
use hyper::{HeaderMap, Method, StatusCode};
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
@@ -1535,8 +1534,8 @@ impl Operation for GenerateDataKeyHandler {
Ok(response) => {
let api_response = GenerateDataKeyApiResponse {
key_id: response.key_id,
plaintext_key: base64::prelude::BASE64_STANDARD.encode(&response.plaintext_key),
ciphertext_blob: base64::prelude::BASE64_STANDARD.encode(&response.ciphertext_blob),
plaintext_key: base64_simd::STANDARD.encode_to_string(&response.plaintext_key),
ciphertext_blob: base64_simd::STANDARD.encode_to_string(&response.ciphertext_blob),
};
let data = serde_json::to_vec(&api_response)
+21 -22
View File
@@ -56,9 +56,8 @@ use crate::storage::storage_api::{
delete_config_no_lock, lock_bucket_targets_metadata, read_config_no_lock, save_config_no_lock, with_config_object_read_lock,
with_config_object_write_lock,
};
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64_simd::STANDARD as BASE64_STANDARD;
use base64_simd::URL_SAFE_NO_PAD;
use futures::StreamExt;
use hmac::{Hmac, Mac};
use http::header::{CONTENT_TYPE, HOST};
@@ -1645,7 +1644,7 @@ fn hash_client_secret(secret: Option<&str>) -> String {
let mut hasher = Sha256::new();
hasher.update(secret.as_bytes());
URL_SAFE_NO_PAD.encode(hasher.finalize())
URL_SAFE_NO_PAD.encode_to_string(hasher.finalize())
}
fn config_enabled(value: Option<String>) -> bool {
@@ -3775,7 +3774,7 @@ impl SiteReplicationRepairTask<'_> {
digest.update(self.path().as_bytes());
digest.update([0]);
digest.update(payload);
Ok(URL_SAFE_NO_PAD.encode(digest.finalize()))
Ok(URL_SAFE_NO_PAD.encode_to_string(digest.finalize()))
}
async fn send(&self, transport: &PeerTransport, access_key: &str, secret_key: &str) -> S3Result<Vec<u8>> {
@@ -3862,7 +3861,7 @@ fn site_replication_repair_plan_token(state: &SiteReplicationState, plan: &SiteR
for (_, task) in site_replication_repair_tasks(plan) {
digest.update(task.id()?.as_bytes());
}
Ok(URL_SAFE_NO_PAD.encode(digest.finalize()))
Ok(URL_SAFE_NO_PAD.encode_to_string(digest.finalize()))
}
fn site_replication_repair_preflight_token(
@@ -3892,7 +3891,7 @@ fn site_replication_repair_preflight_token(
digest.update(event.path.as_bytes());
digest.update(&[0]);
}
Ok(URL_SAFE_NO_PAD.encode(digest.finalize().into_bytes()))
Ok(URL_SAFE_NO_PAD.encode_to_string(digest.finalize().into_bytes()))
}
fn site_replication_repair_task_checkpoint_id(
@@ -3906,7 +3905,7 @@ fn site_replication_repair_task_checkpoint_id(
digest.update(peer_deployment_id.as_bytes());
digest.update(&[0]);
digest.update(task.id()?.as_bytes());
Ok(URL_SAFE_NO_PAD.encode(digest.finalize().into_bytes()))
Ok(URL_SAFE_NO_PAD.encode_to_string(digest.finalize().into_bytes()))
}
fn site_replication_repair_sites(
@@ -4488,11 +4487,11 @@ fn raw_config_to_string(raw: &[u8]) -> Option<String> {
}
fn raw_config_to_base64(raw: &[u8]) -> Option<String> {
(!raw.is_empty()).then(|| BASE64_STANDARD.encode(raw))
(!raw.is_empty()).then(|| BASE64_STANDARD.encode_to_string(raw))
}
fn encode_bucket_meta_wire_value(value: Option<String>) -> Option<String> {
value.map(|raw| BASE64_STANDARD.encode(raw.as_bytes()))
value.map(|raw| BASE64_STANDARD.encode_to_string(raw.as_bytes()))
}
fn encode_bucket_meta_wire_item(mut item: SRBucketMeta) -> SRBucketMeta {
@@ -4508,7 +4507,7 @@ fn encode_bucket_meta_wire_item(mut item: SRBucketMeta) -> SRBucketMeta {
fn decode_bucket_meta_wire_value(raw: &str) -> Vec<u8> {
BASE64_STANDARD
.decode(raw.as_bytes())
.decode_to_vec(raw.as_bytes())
.ok()
.filter(|decoded| std::str::from_utf8(decoded).is_ok())
.unwrap_or_else(|| raw.as_bytes().to_vec())
@@ -7717,7 +7716,7 @@ fn site_resync_page(status: &SRResyncOpStatus, limit: usize, offset: usize) -> S
};
let encoded = serde_json::to_vec(&token)
.map_err(|err| S3Error::with_message(S3ErrorCode::InternalError, format!("encode resync cursor failed: {err}")))?;
URL_SAFE_NO_PAD.encode(encoded)
URL_SAFE_NO_PAD.encode_to_string(encoded)
} else {
String::new()
};
@@ -7736,7 +7735,7 @@ fn parse_site_resync_page(query: &HashMap<String, String>, status: &SRResyncOpSt
}
let offset = if let Some(value) = query.get("continuationToken") {
let decoded = URL_SAFE_NO_PAD
.decode(value)
.decode_to_vec(value)
.map_err(|_| s3_error!(InvalidRequest, "invalid resync continuation token"))?;
let token: SiteResyncContinuationToken =
serde_json::from_slice(&decoded).map_err(|_| s3_error!(InvalidRequest, "invalid resync continuation token"))?;
@@ -14098,7 +14097,7 @@ mod tests {
let bucket = SRBucketInfo {
bucket: "photos".to_string(),
created_at: Some(OffsetDateTime::UNIX_EPOCH),
object_lock_config: Some(BASE64_STANDARD.encode("<ObjectLockConfiguration/>")),
object_lock_config: Some(BASE64_STANDARD.encode_to_string("<ObjectLockConfiguration/>")),
..Default::default()
};
let bootstrap = bootstrap_bucket_make_op_path(&bucket);
@@ -14625,10 +14624,10 @@ mod tests {
SRBucketInfo {
bucket: "photos".to_string(),
policy: Some(serde_json::json!({"Statement": []})),
versioning: Some(BASE64_STANDARD.encode("<VersioningConfiguration/>")),
quota_config: Some(BASE64_STANDARD.encode(r#"{"quota":1024}"#)),
expiry_lc_config: Some(BASE64_STANDARD.encode("<LifecycleConfiguration/>")),
object_lock_config: Some(BASE64_STANDARD.encode("<ObjectLockConfiguration/>")),
versioning: Some(BASE64_STANDARD.encode_to_string("<VersioningConfiguration/>")),
quota_config: Some(BASE64_STANDARD.encode_to_string(r#"{"quota":1024}"#)),
expiry_lc_config: Some(BASE64_STANDARD.encode_to_string("<LifecycleConfiguration/>")),
object_lock_config: Some(BASE64_STANDARD.encode_to_string("<ObjectLockConfiguration/>")),
created_at: Some(OffsetDateTime::UNIX_EPOCH),
api_version: Some(SITE_REPL_API_VERSION.to_string()),
..Default::default()
@@ -14667,7 +14666,7 @@ mod tests {
"photos".to_string(),
SRBucketInfo {
bucket: "photos".to_string(),
expiry_lc_config: Some(BASE64_STANDARD.encode("<LifecycleConfiguration/>")),
expiry_lc_config: Some(BASE64_STANDARD.encode_to_string("<LifecycleConfiguration/>")),
api_version: Some(SITE_REPL_API_VERSION.to_string()),
..Default::default()
},
@@ -17637,7 +17636,7 @@ mod tests {
fn test_metainfo_bucket_config_values_are_base64_encoded() {
let raw = br#"<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>"#;
assert_eq!(raw_config_to_base64(raw), Some(BASE64_STANDARD.encode(raw)));
assert_eq!(raw_config_to_base64(raw), Some(BASE64_STANDARD.encode_to_string(raw)));
assert_ne!(raw_config_to_base64(raw), raw_config_to_string(raw));
assert_eq!(raw_config_to_base64(&[]), None);
}
@@ -18182,8 +18181,8 @@ mod tests {
};
let dep_a_xml = site_config_xml("dep-b");
let dep_b_xml = site_config_xml("dep-a");
let dep_a_b64 = BASE64_STANDARD.encode(dep_a_xml.as_bytes());
let dep_b_b64 = BASE64_STANDARD.encode(dep_b_xml.as_bytes());
let dep_a_b64 = BASE64_STANDARD.encode_to_string(dep_a_xml.as_bytes());
let dep_b_b64 = BASE64_STANDARD.encode_to_string(dep_b_xml.as_bytes());
// Both sites present the complete config in base64 wire form → NOT a mismatch.
assert_eq!(
+2 -3
View File
@@ -14,7 +14,6 @@
use std::time::Duration;
use base64::Engine as _;
use chrono::{DateTime, Utc};
use reqwest::{Client, StatusCode, Url, header};
use rustls::RootCertStore;
@@ -228,8 +227,8 @@ impl ConnectClient {
pending: &PendingRegistration,
identity: &super::identity::DeviceIdentity,
) -> Result<DeviceCredential, ClientError> {
let csr_der = base64::engine::general_purpose::STANDARD
.decode(&pending.certificate_request)
let csr_der = base64_simd::STANDARD
.decode_to_vec(&pending.certificate_request)
.map_err(|_| ClientError::PendingRegistration)?;
let transcript = RegistrationTranscript::build(
&token.registration_token_uid,
+7 -8
View File
@@ -20,8 +20,7 @@
//! module produces, so any divergence is a protocol break rather than a
//! local behaviour change.
use base64::Engine as _;
use base64::engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use base64_simd::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use p256::ecdsa::signature::{Signer as _, Verifier as _};
use p256::ecdsa::{Signature, SigningKey};
use p256::elliptic_curve::Generate as _;
@@ -112,7 +111,7 @@ impl RegistrationTranscript {
}
let expiry = expires_unix.to_string();
let csr_digest = BASE64_URL_NO_PAD.encode(Sha256::digest(certificate_request));
let csr_digest = BASE64_URL_NO_PAD.encode_to_string(Sha256::digest(certificate_request));
let fields: [(&'static str, &str); FIELD_COUNT] = [
("registrationTokenUid", registration_token_uid),
@@ -238,7 +237,7 @@ impl DeviceIdentity {
/// Standard padded base64 of the certificate request, as the body carries it.
pub fn certificate_request_base64(&self) -> Result<String, IdentityError> {
Ok(BASE64_STANDARD.encode(self.certificate_request_der()?))
Ok(BASE64_STANDARD.encode_to_string(self.certificate_request_der()?))
}
/// Sign a transcript, producing the low-S fixed-width proof.
@@ -252,20 +251,20 @@ impl DeviceIdentity {
RegistrationProof {
algorithm: PROOF_ALGORITHM.to_string(),
value: BASE64_URL_NO_PAD.encode(canonical.to_bytes()),
value: BASE64_URL_NO_PAD.encode_to_string(canonical.to_bytes()),
}
}
pub(crate) fn sign_pending_registration_state(&self, state: &[u8]) -> String {
let signature: Signature = self.signing_key.sign(state);
BASE64_URL_NO_PAD.encode(signature.normalize_s().to_bytes())
BASE64_URL_NO_PAD.encode_to_string(signature.normalize_s().to_bytes())
}
pub(crate) fn verifies_pending_registration_state(&self, state: &[u8], proof: &str) -> bool {
let Ok(octets) = BASE64_URL_NO_PAD.decode(proof) else {
let Ok(octets) = BASE64_URL_NO_PAD.decode_to_vec(proof) else {
return false;
};
if BASE64_URL_NO_PAD.encode(&octets) != proof {
if BASE64_URL_NO_PAD.encode_to_string(&octets) != proof {
return false;
}
let Ok(signature) = Signature::from_slice(&octets) else {
+3 -3
View File
@@ -25,7 +25,7 @@ use std::path::Path;
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use base64_simd::URL_SAFE_NO_PAD;
#[cfg(target_os = "linux")]
use p256::ecdsa::{Signature, SigningKey, signature::Signer as _};
#[cfg(target_os = "linux")]
@@ -163,7 +163,7 @@ fn write_offline_bundle_unix(
let produced_at = OffsetDateTime::from_unix_timestamp(context.produced_at_unix).map_err(|_| BundleError::InvalidMetadata)?;
let produced_at = produced_at.format(&Rfc3339).map_err(|_| BundleError::InvalidMetadata)?;
let nonce = URL_SAFE_NO_PAD.encode(context.nonce);
let nonce = URL_SAFE_NO_PAD.encode_to_string(context.nonce);
let device_key_id = hex_lower(&Sha256::digest(key.public_key_der()));
let manifest_entries = entries
.iter()
@@ -397,7 +397,7 @@ fn sign(key: &DeviceIdentity, manifest: &[u8]) -> Result<String, BundleError> {
input.push(0);
input.extend_from_slice(manifest);
let signature: Signature = signing_key.sign(&input);
Ok(URL_SAFE_NO_PAD.encode(signature.normalize_s().to_bytes()))
Ok(URL_SAFE_NO_PAD.encode_to_string(signature.normalize_s().to_bytes()))
}
#[cfg(target_os = "linux")]
+9 -10
View File
@@ -32,8 +32,7 @@
//! are frozen beside it. Reordering the checks changes which reason a given
//! artifact produces, which is itself part of the contract.
use base64::Engine as _;
use base64::engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use base64_simd::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use p256::ecdsa::signature::{Signer as _, Verifier as _};
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::pkcs8::DecodePrivateKey as _;
@@ -367,7 +366,7 @@ impl OfflineEnrollment {
// The octets that were transmitted. They are never re-serialised: every
// later step signs and parses this same buffer.
let bytes = BASE64_STANDARD
.decode(envelope.bytes.as_bytes())
.decode_to_vec(envelope.bytes.as_bytes())
.map_err(|_| EnrollmentError::MalformedDocument)?;
// Step 2: routing only.
@@ -440,8 +439,8 @@ impl OfflineEnrollment {
challenge_nonce: &challenge.nonce,
challenge_proof: &challenge.challenge_proof,
device_key_id: key_id(&point),
device_public_key: BASE64_URL_NO_PAD.encode(point),
device_nonce: BASE64_URL_NO_PAD.encode(device_nonce),
device_public_key: BASE64_URL_NO_PAD.encode_to_string(point),
device_nonce: BASE64_URL_NO_PAD.encode_to_string(device_nonce),
produced_at,
};
@@ -451,7 +450,7 @@ impl OfflineEnrollment {
let signature = sign(key, TAG_RESPONSE, &bytes)?;
let envelope = SignedDocument {
bytes: BASE64_STANDARD.encode(&bytes),
bytes: BASE64_STANDARD.encode_to_string(&bytes),
signature: DocumentSignature {
algorithm: SIGNATURE_ALGORITHM.to_owned(),
key_id: document.device_key_id,
@@ -537,7 +536,7 @@ fn verify_trust_chain(
/// checked against these, never against a re-encoding of the parsed link.
fn decode_trust_link(entry: &SignedDocument) -> Result<(TrustLink, Vec<u8>), EnrollmentError> {
let bytes = BASE64_STANDARD
.decode(entry.bytes.as_bytes())
.decode_to_vec(entry.bytes.as_bytes())
.map_err(|_| EnrollmentError::MalformedDocument)?;
let link = serde_json::from_slice(&bytes).map_err(|_| EnrollmentError::TrustChainInvalid)?;
Ok((link, bytes))
@@ -559,7 +558,7 @@ fn decode_signature(signature: &DocumentSignature) -> Result<Signature, Enrollme
}
let decoded = BASE64_URL_NO_PAD
.decode(value)
.decode_to_vec(value)
.map_err(|_| EnrollmentError::SignatureMalformed)?;
let octets: [u8; SIGNATURE_OCTETS] = decoded
.as_slice()
@@ -602,7 +601,7 @@ fn sign(key: &DeviceIdentity, tag: &[u8], bytes: &[u8]) -> Result<String, Enroll
let signature: Signature = signing_key.sign(&signature_input(tag, bytes));
let canonical = signature.normalize_s();
Ok(BASE64_URL_NO_PAD.encode(canonical.to_bytes()))
Ok(BASE64_URL_NO_PAD.encode_to_string(canonical.to_bytes()))
}
/// The device's public point, recovered from the DER encoding the identity
@@ -627,7 +626,7 @@ fn decode_public_key(value: &str) -> Option<(VerifyingKey, [u8; PUBLIC_KEY_OCTET
return None;
}
let point: [u8; PUBLIC_KEY_OCTETS] = BASE64_URL_NO_PAD.decode(value).ok()?.try_into().ok()?;
let point: [u8; PUBLIC_KEY_OCTETS] = BASE64_URL_NO_PAD.decode_to_vec(value).ok()?.try_into().ok()?;
if point[0] != UNCOMPRESSED_POINT {
return None;
}
+9 -10
View File
@@ -15,8 +15,7 @@
use std::io::Read;
use std::sync::Arc;
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use base64_simd::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use p256::ecdsa::signature::Signer as _;
use p256::ecdsa::{Signature, SigningKey};
use p256::pkcs8::DecodePrivateKey as _;
@@ -89,10 +88,10 @@ impl RegistrationToken {
}
let document: RegistrationTokenDocument = serde_json::from_slice(&bytes).map_err(TokenError::Invalid)?;
let decoded = BASE64_URL_NO_PAD
.decode(&document.registration_token_secret)
.decode_to_vec(&document.registration_token_secret)
.map(Zeroizing::new)
.map_err(|_| TokenError::SecretShape)?;
if decoded.len() != 32 || BASE64_URL_NO_PAD.encode(&decoded) != document.registration_token_secret {
if decoded.len() != 32 || BASE64_URL_NO_PAD.encode_to_string(&decoded) != document.registration_token_secret {
return Err(TokenError::SecretShape);
}
if !is_uuid_v7(&document.registration_token_uid)
@@ -198,10 +197,10 @@ impl<'a> RotationRequest<'a> {
request_id: &'a str,
certificate_request: &'a str,
) -> Result<Self, CredentialValidationError> {
let csr_der = base64::engine::general_purpose::STANDARD
.decode(certificate_request)
let csr_der = base64_simd::STANDARD
.decode_to_vec(certificate_request)
.map_err(|_| CredentialValidationError::CertificateRequest)?;
let csr_digest = BASE64_URL_NO_PAD.encode(Sha256::digest(&csr_der));
let csr_digest = BASE64_URL_NO_PAD.encode_to_string(Sha256::digest(&csr_der));
let transcript = rotation_transcript(credential_fingerprint, device_name, request_id, &csr_digest)?;
let key = identity
.to_pkcs8_der()
@@ -216,7 +215,7 @@ impl<'a> RotationRequest<'a> {
certificate_request,
proof: ProofOwned {
algorithm: "ES256".to_string(),
value: BASE64_URL_NO_PAD.encode(canonical.to_bytes()),
value: BASE64_URL_NO_PAD.encode_to_string(canonical.to_bytes()),
},
})
}
@@ -475,8 +474,8 @@ pub(crate) fn public_key_fingerprint(identity: &DeviceIdentity) -> String {
}
pub(crate) fn certificate_request_matches(encoded: &str, identity: &DeviceIdentity) -> Result<bool, CredentialValidationError> {
let der = base64::engine::general_purpose::STANDARD
.decode(encoded)
let der = base64_simd::STANDARD
.decode_to_vec(encoded)
.map_err(|_| CredentialValidationError::CertificateRequest)?;
let (remaining, request) =
X509CertificationRequest::from_der(&der).map_err(|_| CredentialValidationError::CertificateRequest)?;
+2 -6
View File
@@ -446,7 +446,6 @@ mod tests {
use crate::server::{refresh_audit_module_enabled, refresh_notify_module_enabled};
use crate::storage::access::ReqInfo;
use crate::storage::request_context::RequestContext;
use base64::Engine as _;
use http::{Extensions, HeaderMap, HeaderValue, Method, Uri};
use metrics::{Counter, CounterFn, Gauge, GaugeFn, Histogram, HistogramFn, Key, KeyName, Metadata, SharedString, Unit};
use rustfs_audit::ObjectVersion;
@@ -765,10 +764,7 @@ mod tests {
std::collections::HashMap::from([
("x-amz-server-side-encryption".to_string(), "aws:kms".to_string()),
("x-rustfs-encryption-key-id".to_string(), "finance-key".to_string()),
(
"x-rustfs-encryption-key".to_string(),
base64::engine::general_purpose::STANDARD.encode([7u8; 48]),
),
("x-rustfs-encryption-key".to_string(), base64_simd::STANDARD.encode_to_string([7u8; 48])),
("x-rustfs-encryption-algorithm".to_string(), "aws:kms".to_string()),
])
}
@@ -840,7 +836,7 @@ mod tests {
let rendered = serde_json::to_string(&tags).expect("audit tags serialize");
assert!(
!rendered.contains(&base64::engine::general_purpose::STANDARD.encode([7u8; 48])),
!rendered.contains(&base64_simd::STANDARD.encode_to_string([7u8; 48])),
"the audit entry must not carry the wrapped data key: {rendered}"
);
},
+154 -114
View File
@@ -83,7 +83,7 @@ use aes_gcm::{
aead::{Aead, KeyInit},
};
use async_trait::async_trait;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use base64_simd::STANDARD as BASE64_STANDARD;
#[cfg(feature = "rio-v2")]
use chacha20poly1305::ChaCha20Poly1305;
#[cfg(feature = "rio-v2")]
@@ -153,7 +153,7 @@ fn md5_bytes(input: impl AsRef<[u8]>) -> [u8; 16] {
}
fn md5_base64(input: impl AsRef<[u8]>) -> String {
BASE64_STANDARD.encode(md5_bytes(input))
BASE64_STANDARD.encode_to_string(md5_bytes(input))
}
use super::Error;
@@ -562,7 +562,7 @@ pub(crate) fn extract_ssekms_context_from_headers(headers: &HeaderMap) -> Result
let value = v
.to_str()
.map_err(|_| sse_invalid_argument("The x-amz-server-side-encryption-context header must be valid UTF-8."))?;
let decoded = BASE64_STANDARD.decode(value).map_err(|_| {
let decoded = BASE64_STANDARD.decode_to_vec(value).map_err(|_| {
sse_invalid_argument("The x-amz-server-side-encryption-context header must be valid base64-encoded JSON.")
})?;
@@ -1320,7 +1320,7 @@ fn stored_envelope_master_key_version(metadata: &HashMap<String, String>) -> Opt
// this lookup never reads, so the normalized result is identical without it.
let encoded = normalize_managed_metadata(metadata, None);
let encoded = encoded.get(INTERNAL_ENCRYPTION_KEY_HEADER)?;
let envelope = BASE64_STANDARD.decode(encoded).ok()?;
let envelope = BASE64_STANDARD.decode_to_vec(encoded).ok()?;
envelope_master_key_version(&envelope)
}
@@ -1516,7 +1516,7 @@ fn build_object_encryption_context(
fn encode_minio_kms_context(context: &HashMap<String, String>) -> Result<String, ApiError> {
let encoded = serde_json::to_vec(context)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to serialize KMS context: {e}"))))?;
Ok(BASE64_STANDARD.encode(encoded))
Ok(BASE64_STANDARD.encode_to_string(encoded))
}
fn decode_minio_kms_context(metadata: &HashMap<String, String>) -> Result<Option<HashMap<String, String>>, ApiError> {
@@ -1524,7 +1524,7 @@ fn decode_minio_kms_context(metadata: &HashMap<String, String>) -> Result<Option
return Ok(None);
};
let decoded = BASE64_STANDARD
.decode(encoded)
.decode_to_vec(encoded)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode MinIO KMS context: {e}"))))?;
serde_json::from_slice(&decoded)
.map(Some)
@@ -1680,7 +1680,7 @@ fn unseal_object_key(
#[cfg(feature = "rio-v2")]
fn try_decode_minio_sealed_key(bytes: &str) -> Result<Option<[u8; SEALED_KEY_SIZE]>, ApiError> {
let decoded = BASE64_STANDARD
.decode(bytes)
.decode_to_vec(bytes)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode sealed object key: {e}"))))?;
match decoded.as_slice().try_into() {
Ok(sealed_key) => Ok(Some(sealed_key)),
@@ -1691,7 +1691,7 @@ fn try_decode_minio_sealed_key(bytes: &str) -> Result<Option<[u8; SEALED_KEY_SIZ
#[cfg(feature = "rio-v2")]
fn try_decode_minio_sealing_iv(bytes: &str) -> Result<Option<[u8; SEALED_KEY_IV_SIZE]>, ApiError> {
let decoded = BASE64_STANDARD
.decode(bytes)
.decode_to_vec(bytes)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode sealing IV: {e}"))))?;
match decoded.as_slice().try_into() {
Ok(iv) => Ok(Some(iv)),
@@ -1758,23 +1758,29 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result<
// `rio-v2` the SSE-C path uses `EncryptionKeyKind::Object` and the sealed-key
// block below, so this branch is not taken.
if material.key_kind == EncryptionKeyKind::Direct {
metadata.insert(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode(material.base_nonce));
metadata.insert(
INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(material.base_nonce),
);
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode(material.base_nonce),
BASE64_STANDARD.encode_to_string(material.base_nonce),
);
}
#[cfg(feature = "rio-v2")]
if let Some(sealed) = &material.managed_sealed_key {
metadata.insert(MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode(sealed.iv));
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(sealed.iv),
);
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(),
MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.to_string(),
);
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(sealed.sealed_key),
BASE64_STANDARD.encode_to_string(sealed.sealed_key),
);
}
}
@@ -1816,14 +1822,23 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result<
}
if material.key_kind == EncryptionKeyKind::Direct {
metadata.insert(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode(encrypted_data_key));
metadata.insert(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode(material.base_nonce));
metadata.insert(
INTERNAL_ENCRYPTION_KEY_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(encrypted_data_key),
);
metadata.insert(
INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(material.base_nonce),
);
metadata.insert(INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(), material.algorithm.as_str().to_string());
}
#[cfg(feature = "rio-v2")]
if let Some(sealed) = &material.managed_sealed_key {
metadata.insert(MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode(sealed.iv));
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(sealed.iv),
);
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(),
MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.to_string(),
@@ -1835,25 +1850,25 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result<
SSEType::SseS3 => {
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(sealed.sealed_key),
BASE64_STANDARD.encode_to_string(sealed.sealed_key),
);
}
SSEType::SseKms => {
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_KMS_SEALED_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(sealed.sealed_key),
BASE64_STANDARD.encode_to_string(sealed.sealed_key),
);
}
SSEType::SseC => {}
}
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_KMS_DATA_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(encrypted_data_key),
BASE64_STANDARD.encode_to_string(encrypted_data_key),
);
} else if cfg!(feature = "rio-v2") {
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode(material.base_nonce),
BASE64_STANDARD.encode_to_string(material.base_nonce),
);
metadata.insert(
MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(),
@@ -1862,7 +1877,7 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result<
if let Some(kms_key_id) = &material.kms_key_id {
metadata.insert(MINIO_INTERNAL_ENCRYPTION_KMS_KEY_ID_HEADER.to_string(), kms_key_id.to_string());
}
let encoded_key = BASE64_STANDARD.encode(encrypted_data_key);
let encoded_key = BASE64_STANDARD.encode_to_string(encrypted_data_key);
match material.sse_type {
SSEType::SseS3 => {
metadata.insert(MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER.to_string(), encoded_key);
@@ -2352,7 +2367,7 @@ fn read_stored_ssec_nonce(metadata: &HashMap<String, String>, bucket: &str, key:
metadata
.get(INTERNAL_ENCRYPTION_IV_HEADER)
.or_else(|| metadata.get(MINIO_INTERNAL_ENCRYPTION_IV_HEADER))
.and_then(|encoded| BASE64_STANDARD.decode(encoded).ok())
.and_then(|encoded| BASE64_STANDARD.decode_to_vec(encoded).ok())
.and_then(|bytes| <[u8; 12]>::try_from(bytes.as_slice()).ok())
.unwrap_or_else(|| generate_ssec_nonce(bucket, key))
}
@@ -2651,7 +2666,7 @@ async fn apply_managed_decryption_material_inner(
.or_else(|| metadata.get(MINIO_INTERNAL_ENCRYPTION_KMS_DATA_KEY_HEADER))
.ok_or_else(|| ApiError::from(StorageError::other("Missing encrypted key in metadata")))?;
let encrypted_data_key = BASE64_STANDARD
.decode(encrypted_key_b64)
.decode_to_vec(encrypted_key_b64)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode encrypted key: {e}"))))?;
(
encrypted_data_key,
@@ -2678,14 +2693,14 @@ async fn apply_managed_decryption_material_inner(
.get(INTERNAL_ENCRYPTION_KEY_HEADER)
.ok_or_else(|| ApiError::from(StorageError::other("Missing encrypted key in metadata")))?;
let encrypted_data_key = BASE64_STANDARD
.decode(encrypted_key_b64)
.decode_to_vec(encrypted_key_b64)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode encrypted key: {e}"))))?;
let iv_b64 = normalized_metadata
.get(INTERNAL_ENCRYPTION_IV_HEADER)
.ok_or_else(|| ApiError::from(StorageError::other("Missing IV in metadata")))?;
let iv = BASE64_STANDARD
.decode(iv_b64)
.decode_to_vec(iv_b64)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode IV: {e}"))))?;
if iv.len() != 12 {
@@ -2876,7 +2891,7 @@ pub(crate) async fn rewrap_object_encryption_metadata(
return Ok(ObjectDekRewrapOutcome::NotApplicable);
};
let encrypted_data_key = BASE64_STANDARD
.decode(envelope_b64)
.decode_to_vec(envelope_b64)
.map_err(|e| ApiError::from(StorageError::other(format!("Failed to decode encrypted key: {e}"))))?;
// Only RustFS envelopes are rewrappable here; MinIO's builtin-KMS
// ciphertext is opaque bytes owned by a different root of trust.
@@ -2918,7 +2933,7 @@ pub(crate) async fn rewrap_object_encryption_metadata(
MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER,
];
let old_envelope_b64 = envelope_b64.clone();
let new_envelope_b64 = BASE64_STANDARD.encode(&response.ciphertext);
let new_envelope_b64 = BASE64_STANDARD.encode_to_string(&response.ciphertext);
let mut overrides = HashMap::new();
for (stored_name, stored_value) in metadata {
let is_envelope_slot = REWRAP_ENVELOPE_HEADERS
@@ -3256,7 +3271,7 @@ fn decrypt_minio_kms_data_key(encrypted_dek: &[u8], master_key: &[u8; 32], aad:
{
let decode = |what: &str, value: &str| -> Result<Vec<u8>, ApiError> {
BASE64_STANDARD
.decode(value)
.decode_to_vec(value)
.map_err(|e| ApiError::from(StorageError::other(format!("Invalid MinIO KMS {what}: {e}"))))
};
let mut body = decode("ciphertext", &json.bytes)?;
@@ -3346,7 +3361,7 @@ fn parse_simple_sse_cmk(cmk_value: &str) -> Result<[u8; 32], ApiError> {
)));
}
let decoded = BASE64_STANDARD
.decode(trimmed)
.decode_to_vec(trimmed)
.map_err(|e| ApiError::from(StorageError::other(format!("__RUSTFS_SSE_SIMPLE_CMK must be valid base64: {e}"))))?;
let master_key: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {
ApiError::from(StorageError::other(format!(
@@ -3401,7 +3416,7 @@ impl LocalSseDekProvider {
));
};
let decoded = BASE64_STANDARD.decode(raw_value.trim()).map_err(|err| {
let decoded = BASE64_STANDARD.decode_to_vec(raw_value.trim()).map_err(|err| {
sse_not_configured(format!(
"RUSTFS_SSE_S3_MASTER_KEY must be valid base64 for SSE-S3 when KMS is not configured: {err}"
))
@@ -3427,8 +3442,8 @@ impl LocalSseDekProvider {
.encrypt(&nonce, dek.as_slice())
.map_err(|_| ApiError::from(StorageError::other("Failed to encrypt DEK")))?;
let nonce = BASE64_STANDARD.encode(nonce);
let ciphertext = BASE64_STANDARD.encode(ciphertext);
let nonce = BASE64_STANDARD.encode_to_string(nonce);
let ciphertext = BASE64_STANDARD.encode_to_string(ciphertext);
serde_json::to_string(&LocalSseDekEnvelope {
version: LOCAL_SSE_DEK_FORMAT_VERSION,
nonce: &nonce,
@@ -3468,10 +3483,10 @@ impl LocalSseDekProvider {
}
};
let nonce_vec = BASE64_STANDARD
.decode(nonce)
.decode_to_vec(nonce)
.map_err(|_| ApiError::from(StorageError::other("Invalid nonce format")))?;
let ciphertext = BASE64_STANDARD
.decode(ciphertext)
.decode_to_vec(ciphertext)
.map_err(|_| ApiError::from(StorageError::other("Invalid ciphertext format")))?;
let key = Key::<Aes256Gcm>::from(cmk_value);
@@ -3792,7 +3807,7 @@ fn parse_minio_managed_sealed_key(
/// crate carries no JSON codec; any decode failure returns `None`, which skips
/// the context mapping exactly like the historical inline `if let Ok` chain.
fn recode_minio_kms_context(value: &str) -> Option<String> {
let decoded = BASE64_STANDARD.decode(value).ok()?;
let decoded = BASE64_STANDARD.decode_to_vec(value).ok()?;
let context = serde_json::from_slice::<HashMap<String, String>>(&decoded).ok()?;
serde_json::to_string(&context).ok()
}
@@ -3817,7 +3832,7 @@ pub fn validate_ssec_params(params: SsecParams) -> Result<ValidatedSsecParams, A
)));
}
let key_bytes = BASE64_STANDARD.decode(&params.key).map_err(|e| {
let key_bytes = BASE64_STANDARD.decode_to_vec(&params.key).map_err(|e| {
error!("Failed to decode SSE-C key: {}", e);
ssec_invalid_request("Invalid SSE-C key: not valid Base64.")
})?;
@@ -4035,10 +4050,10 @@ mod tests {
// Not valid base64.
assert!(super::parse_simple_sse_cmk("@@@not-base64@@@").is_err());
// Valid base64 but wrong length (16 bytes).
let short = BASE64_STANDARD.encode([1u8; 16]);
let short = BASE64_STANDARD.encode_to_string([1u8; 16]);
assert!(super::parse_simple_sse_cmk(&short).is_err());
// All-zero 32-byte key is rejected.
let zero = BASE64_STANDARD.encode([0u8; 32]);
let zero = BASE64_STANDARD.encode_to_string([0u8; 32]);
assert!(super::parse_simple_sse_cmk(&zero).is_err());
}
@@ -4055,14 +4070,14 @@ mod tests {
fn parse_simple_sse_cmk_accepts_valid_32_byte_key() {
let mut key = [0u8; 32];
key[0] = 7;
let encoded = BASE64_STANDARD.encode(key);
let encoded = BASE64_STANDARD.encode_to_string(key);
let got = super::parse_simple_sse_cmk(&encoded).expect("valid 32-byte key must parse");
assert_eq!(got, key);
}
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use async_trait::async_trait;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use base64_simd::STANDARD as BASE64_STANDARD;
use http::{HeaderMap, HeaderValue};
use rustfs_kms::types::ObjectEncryptionContext;
use rustfs_rio::{DecryptReader, EncryptReader};
@@ -4094,13 +4109,13 @@ mod tests {
#[tokio::test]
async fn object_encryption_resolver_returns_ssec_read_material() {
let key = [0x31; 32];
let key_b64 = BASE64_STANDARD.encode(key);
let key_b64 = BASE64_STANDARD.encode_to_string(key);
let key_md5 = md5_base64(key);
let nonce = [0x42; 12];
let metadata = HashMap::from([
("X-Amz-Server-Side-Encryption-Customer-Algorithm".to_string(), "AES256".to_string()),
("X-Amz-Server-Side-Encryption-Customer-Key-Md5".to_string(), key_md5.clone()),
("X-Rustfs-Encryption-Iv".to_string(), BASE64_STANDARD.encode(nonce)),
("X-Rustfs-Encryption-Iv".to_string(), BASE64_STANDARD.encode_to_string(nonce)),
]);
let mut headers = HeaderMap::new();
headers.insert("x-amz-server-side-encryption-customer-algorithm", HeaderValue::from_static("AES256"));
@@ -4131,7 +4146,7 @@ mod tests {
#[tokio::test]
async fn object_encryption_resolver_rejects_missing_or_invalid_ssec_algorithm() {
let key = [0x31; 32];
let key_b64 = BASE64_STANDARD.encode(key);
let key_b64 = BASE64_STANDARD.encode_to_string(key);
let key_md5 = md5_base64(key);
let metadata = HashMap::from([
("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()),
@@ -4246,7 +4261,7 @@ mod tests {
}
fn local_sse_master_key_b64() -> String {
BASE64_STANDARD.encode([0x24u8; 32])
BASE64_STANDARD.encode_to_string([0x24u8; 32])
}
#[test]
@@ -4303,8 +4318,8 @@ mod tests {
#[test]
fn test_extract_ssekms_context_from_headers_decodes_base64_json() {
let mut headers = http::HeaderMap::new();
let encoded =
BASE64_STANDARD.encode(serde_json::to_vec(&HashMap::from([("tenant".to_string(), "alpha".to_string())])).unwrap());
let encoded = BASE64_STANDARD
.encode_to_string(serde_json::to_vec(&HashMap::from([("tenant".to_string(), "alpha".to_string())])).unwrap());
headers.insert(AMZ_SERVER_SIDE_ENCRYPTION_KMS_CONTEXT, HeaderValue::from_str(&encoded).unwrap());
let context = extract_ssekms_context_from_headers(&headers)
@@ -4401,7 +4416,7 @@ mod tests {
#[test]
fn test_validate_ssec_params_success() {
let key = BASE64_STANDARD.encode([42u8; 32]);
let key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let key_md5 = md5_base64([42u8; 32]);
let params = SsecParams {
@@ -4418,7 +4433,7 @@ mod tests {
#[test]
fn test_validate_ssec_params_wrong_algorithm() {
let key = BASE64_STANDARD.encode([42u8; 32]);
let key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let key_md5 = md5_base64([42u8; 32]);
let params = SsecParams {
@@ -4433,7 +4448,7 @@ mod tests {
#[test]
fn test_validate_ssec_params_wrong_key_length() {
let key = BASE64_STANDARD.encode([42u8; 16]); // Only 16 bytes
let key = BASE64_STANDARD.encode_to_string([42u8; 16]); // Only 16 bytes
let key_md5 = md5_base64([42u8; 16]);
let params = SsecParams {
@@ -4448,8 +4463,8 @@ mod tests {
#[test]
fn test_validate_ssec_params_wrong_md5() {
let key = BASE64_STANDARD.encode([42u8; 32]);
let key_md5 = BASE64_STANDARD.encode([99u8; 16]); // Wrong MD5
let key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let key_md5 = BASE64_STANDARD.encode_to_string([99u8; 16]); // Wrong MD5
let params = SsecParams {
algorithm: "AES256".to_string(),
@@ -4465,7 +4480,7 @@ mod tests {
async fn test_sse_encryption_rejects_partial_ssec_headers() {
let bucket = "test-bucket";
let key = "test-key";
let sse_key = BASE64_STANDARD.encode([42u8; 32]);
let sse_key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let sse_key_md5 = md5_base64([42u8; 32]);
let content_size = 1024;
@@ -4612,7 +4627,7 @@ mod tests {
let bucket = "bucket";
let key = "object";
let customer_key_bytes = [0x24u8; 32];
let customer_key = BASE64_STANDARD.encode(customer_key_bytes);
let customer_key = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let customer_key_md5 = md5_base64(customer_key_bytes);
let metadata_one = ssec_direct_put_metadata(bucket, key, &customer_key, &customer_key_md5).await;
@@ -4636,7 +4651,7 @@ mod tests {
.await
.expect("sse-c decryption material");
assert_eq!(
BASE64_STANDARD.encode(decrypted.base_nonce),
BASE64_STANDARD.encode_to_string(decrypted.base_nonce),
*iv,
"decrypt must read the persisted random nonce back"
);
@@ -4653,7 +4668,7 @@ mod tests {
let bucket = "bucket";
let key = "object";
let customer_key_bytes = [0x24u8; 32];
let customer_key = BASE64_STANDARD.encode(customer_key_bytes);
let customer_key = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let customer_key_md5 = md5_base64(customer_key_bytes);
let mut metadata = HashMap::new();
@@ -4685,7 +4700,7 @@ mod tests {
let bucket = "bucket";
let key = "object";
let customer_key_bytes = [0x51u8; 32];
let customer_key = BASE64_STANDARD.encode(customer_key_bytes);
let customer_key = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let customer_key_md5 = md5_base64(customer_key_bytes);
let plaintext = b"attack at dawn - sse-c round trip".to_vec();
@@ -4693,7 +4708,7 @@ mod tests {
// Encrypt with the key + nonce that were persisted at PUT time.
let enc_iv = BASE64_STANDARD
.decode(metadata.get(INTERNAL_ENCRYPTION_IV_HEADER).expect("persisted IV"))
.decode_to_vec(metadata.get(INTERNAL_ENCRYPTION_IV_HEADER).expect("persisted IV"))
.expect("valid base64 IV");
let cipher = Aes256Gcm::new_from_slice(&customer_key_bytes).expect("cipher");
let ciphertext = cipher
@@ -4724,7 +4739,7 @@ mod tests {
let bucket = "bucket";
let key = "object";
let customer_key_bytes = [0x33u8; 32];
let customer_key = BASE64_STANDARD.encode(customer_key_bytes);
let customer_key = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let customer_key_md5 = md5_base64(customer_key_bytes);
let material = sse_prepare_encryption(PrepareEncryptionRequest {
@@ -4750,7 +4765,7 @@ mod tests {
.clone();
// Random, not the deterministic bucket/key derivation.
assert_ne!(
BASE64_STANDARD.decode(&session_iv).expect("valid IV")[..],
BASE64_STANDARD.decode_to_vec(&session_iv).expect("valid IV")[..],
generate_ssec_nonce(bucket, key)[..]
);
@@ -4779,7 +4794,7 @@ mod tests {
let part_two_nonce = resolve_part_nonce(2).await;
assert_eq!(part_one_nonce, part_two_nonce, "all parts of one upload must share the persisted nonce");
assert_eq!(BASE64_STANDARD.encode(part_one_nonce), session_iv);
assert_eq!(BASE64_STANDARD.encode_to_string(part_one_nonce), session_iv);
}
#[cfg(feature = "rio-v2")]
@@ -4788,7 +4803,7 @@ mod tests {
let bucket = "test-bucket";
let key = "test-key";
let customer_key_bytes = [0x24u8; 32];
let customer_key = BASE64_STANDARD.encode(customer_key_bytes);
let customer_key = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let sse_key_md5 = md5_base64(customer_key_bytes);
let request = PrepareEncryptionRequest {
@@ -4966,7 +4981,7 @@ mod tests {
let bucket = "test-bucket";
let key = "test-key";
let content_size = 1024;
let sse_key = BASE64_STANDARD.encode([42u8; 32]);
let sse_key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let sse_key_md5 = md5_base64([42u8; 32]);
let request = EncryptionRequest {
@@ -5084,7 +5099,7 @@ mod tests {
reset_sse_dek_provider();
let envelope = probe_envelope_json();
let envelope_b64 = BASE64_STANDARD.encode(&envelope);
let envelope_b64 = BASE64_STANDARD.encode_to_string(&envelope);
let client_context = HashMap::from([("tenant".to_string(), "alpha".to_string())]);
let metadata = HashMap::from([
("x-amz-server-side-encryption".to_string(), "aws:kms".to_string()),
@@ -5095,7 +5110,7 @@ mod tests {
// A sealed object key: different bytes, must not be rewritten.
(
MINIO_INTERNAL_ENCRYPTION_KMS_SEALED_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(b"sealed-object-key-not-the-envelope"),
BASE64_STANDARD.encode_to_string(b"sealed-object-key-not-the-envelope"),
),
(
MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER.to_string(),
@@ -5118,7 +5133,7 @@ mod tests {
panic!("expected a rewrapped outcome, got {outcome:?}");
};
let new_b64 = BASE64_STANDARD.encode(&new_ciphertext);
let new_b64 = BASE64_STANDARD.encode_to_string(&new_ciphertext);
assert_eq!(
overrides,
HashMap::from([
@@ -5149,7 +5164,7 @@ mod tests {
let _guard = lock_sse_test_state().await;
reset_sse_dek_provider();
let envelope_b64 = BASE64_STANDARD.encode(probe_envelope_json());
let envelope_b64 = BASE64_STANDARD.encode_to_string(probe_envelope_json());
let metadata = HashMap::from([
("x-amz-server-side-encryption".to_string(), "AES256".to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), envelope_b64),
@@ -5221,7 +5236,7 @@ mod tests {
// SSE-C object: customer-key encryption never reaches KMS.
let ssec = HashMap::from([
("X-Amz-Server-Side-Encryption-Customer-Algorithm".to_string(), "AES256".to_string()),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([1u8; 12])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([1u8; 12])),
]);
let outcome = rewrap_object_encryption_metadata("bucket", "object", &ssec)
.await
@@ -5233,7 +5248,7 @@ mod tests {
("x-amz-server-side-encryption".to_string(), "aws:kms".to_string()),
(
MINIO_INTERNAL_ENCRYPTION_KMS_DATA_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(b"opaque-minio-sealed-bytes"),
BASE64_STANDARD.encode_to_string(b"opaque-minio-sealed-bytes"),
),
]);
let outcome = rewrap_object_encryption_metadata("bucket", "object", &minio)
@@ -5327,7 +5342,7 @@ mod tests {
.get(MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER)
.expect("minio kms context header should exist");
let decoded_context: HashMap<String, String> =
serde_json::from_slice(&BASE64_STANDARD.decode(encoded_context).expect("decode base64 context"))
serde_json::from_slice(&BASE64_STANDARD.decode_to_vec(encoded_context).expect("decode base64 context"))
.expect("decode json context");
assert_eq!(decoded_context, client_context);
@@ -5347,7 +5362,8 @@ mod tests {
let mut wrong_metadata = metadata.clone();
wrong_metadata.insert(
MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER.to_string(),
BASE64_STANDARD.encode(serde_json::to_vec(&HashMap::from([("tenant".to_string(), "beta".to_string())])).unwrap()),
BASE64_STANDARD
.encode_to_string(serde_json::to_vec(&HashMap::from([("tenant".to_string(), "beta".to_string())])).unwrap()),
);
let err = sse_decryption(DecryptionRequest {
bucket: "bucket",
@@ -5370,8 +5386,8 @@ mod tests {
#[cfg(feature = "rio-v2")]
#[test]
fn test_encryption_material_to_metadata_persists_minio_managed_headers() {
let encoded_nonce = BASE64_STANDARD.encode([9u8; 12]);
let encoded_key = BASE64_STANDARD.encode([1u8, 2, 3, 4]);
let encoded_nonce = BASE64_STANDARD.encode_to_string([9u8; 12]);
let encoded_key = BASE64_STANDARD.encode_to_string([1u8, 2, 3, 4]);
let metadata = encryption_material_to_metadata(&EncryptionMaterial {
sse_type: SSEType::SseKms,
server_side_encryption: ServerSideEncryption::from_static(ServerSideEncryption::AWS_KMS),
@@ -5608,9 +5624,12 @@ mod tests {
let metadata = HashMap::from([
(
MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(b"encrypted-key"),
BASE64_STANDARD.encode_to_string(b"encrypted-key"),
),
(
MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string([0x11u8; 12]),
),
(MINIO_INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([0x11u8; 12])),
(
MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(),
MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.to_string(),
@@ -5622,9 +5641,12 @@ mod tests {
assert_eq!(
normalized.get(INTERNAL_ENCRYPTION_KEY_HEADER),
Some(&BASE64_STANDARD.encode(b"encrypted-key"))
Some(&BASE64_STANDARD.encode_to_string(b"encrypted-key"))
);
assert_eq!(
normalized.get(INTERNAL_ENCRYPTION_IV_HEADER),
Some(&BASE64_STANDARD.encode_to_string([0x11u8; 12]))
);
assert_eq!(normalized.get(INTERNAL_ENCRYPTION_IV_HEADER), Some(&BASE64_STANDARD.encode([0x11u8; 12])));
assert_eq!(
normalized.get(INTERNAL_ENCRYPTION_ALGORITHM_HEADER),
Some(&MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.to_string())
@@ -5677,8 +5699,11 @@ mod tests {
let sealed_key = metadata
.get(MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER)
.expect("minio sealed key should be stored");
assert_eq!(BASE64_STANDARD.decode(sealing_iv).expect("decode iv").len(), SEALED_KEY_IV_SIZE);
assert_eq!(BASE64_STANDARD.decode(sealed_key).expect("decode sealed key").len(), SEALED_KEY_SIZE);
assert_eq!(BASE64_STANDARD.decode_to_vec(sealing_iv).expect("decode iv").len(), SEALED_KEY_IV_SIZE);
assert_eq!(
BASE64_STANDARD.decode_to_vec(sealed_key).expect("decode sealed key").len(),
SEALED_KEY_SIZE
);
let decrypted = sse_decryption(DecryptionRequest {
bucket: "bucket",
@@ -5726,7 +5751,7 @@ mod tests {
#[tokio::test]
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 = BASE64_STANDARD.encode_to_string(customer_key_bytes);
let customer_key_md5 = md5_base64(customer_key_bytes);
let material = sse_encryption(EncryptionRequest {
@@ -5831,7 +5856,7 @@ mod tests {
ssekms_key_id: None,
ssekms_context: None,
sse_customer_algorithm: Some("AES256".to_string()),
sse_customer_key: Some(BASE64_STANDARD.encode(key_bytes)),
sse_customer_key: Some(BASE64_STANDARD.encode_to_string(key_bytes)),
sse_customer_key_md5: Some(md5_base64(key_bytes)),
content_size: 1,
principal: None,
@@ -6111,7 +6136,11 @@ mod tests {
let ciphertext = cipher
.encrypt(&legacy_nonce, dek.as_slice())
.expect("legacy wrap should succeed");
let legacy_payload = format!("{}:{}", BASE64_STANDARD.encode(legacy_nonce), BASE64_STANDARD.encode(ciphertext));
let legacy_payload = format!(
"{}:{}",
BASE64_STANDARD.encode_to_string(legacy_nonce),
BASE64_STANDARD.encode_to_string(ciphertext)
);
let decrypted = TestSseDekProvider::decrypt_dek(&legacy_payload, cmk).expect("legacy payload should remain decryptable");
assert_eq!(decrypted, dek);
@@ -6121,8 +6150,8 @@ mod tests {
fn test_decrypt_dek_rejects_unknown_json_version() {
let envelope = serde_json::json!({
"version": super::LOCAL_SSE_DEK_FORMAT_VERSION + 1,
"nonce": BASE64_STANDARD.encode([0u8; 12]),
"ciphertext": BASE64_STANDARD.encode([0u8; 48]),
"nonce": BASE64_STANDARD.encode_to_string([0u8; 12]),
"ciphertext": BASE64_STANDARD.encode_to_string([0u8; 48]),
})
.to_string();
@@ -6293,13 +6322,19 @@ mod tests {
async_with_vars(
[
("__RUSTFS_SSE_SIMPLE_CMK", None::<String>),
("RUSTFS_SSE_S3_MASTER_KEY", Some(BASE64_STANDARD.encode(local_master_key))),
("RUSTFS_SSE_S3_MASTER_KEY", Some(BASE64_STANDARD.encode_to_string(local_master_key))),
],
async {
let metadata = HashMap::from([
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AWS_KMS.to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode(encrypted_dek)),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode(data_key.nonce)),
(
INTERNAL_ENCRYPTION_KEY_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(encrypted_dek),
),
(
INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
BASE64_STANDARD.encode_to_string(data_key.nonce),
),
(INTERNAL_ENCRYPTION_KEY_ID_HEADER.to_string(), "legacy-local-key".to_string()),
]);
@@ -6327,8 +6362,8 @@ mod tests {
}"#;
let metadata = HashMap::from([
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AES256.to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode(kms_envelope)),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([0x14; 12])),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode_to_string(kms_envelope)),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([0x14; 12])),
(INTERNAL_ENCRYPTION_KEY_ID_HEADER.to_string(), "test-key-id".to_string()),
]);
let error = match apply_managed_decryption_material("bucket", "object", &metadata, None).await {
@@ -6374,8 +6409,8 @@ mod tests {
}"#;
let metadata = HashMap::from([
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AES256.to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode(kms_envelope)),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([0x14; 12])),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode_to_string(kms_envelope)),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([0x14; 12])),
(INTERNAL_ENCRYPTION_KEY_ID_HEADER.to_string(), "envelope-key".to_string()),
]);
@@ -6431,9 +6466,9 @@ mod tests {
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AES256.to_string()),
(
INTERNAL_ENCRYPTION_KEY_HEADER.to_string(),
BASE64_STANDARD.encode(b"local-provider-format"),
BASE64_STANDARD.encode_to_string(b"local-provider-format"),
),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([0x14; 12])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([0x14; 12])),
(INTERNAL_ENCRYPTION_KEY_ID_HEADER.to_string(), "test-key-id".to_string()),
]);
let error = match apply_managed_decryption_material("bucket", "object", &metadata, None).await {
@@ -6447,14 +6482,16 @@ mod tests {
#[tokio::test]
async fn test_kms_sse_dek_provider_uses_latest_reconfigured_service() {
use base64::Engine as _;
use rustfs_kms::config::KmsConfig;
let _guard = lock_sse_test_state().await;
let manager = Arc::new(rustfs_kms::KmsServiceManager::new());
manager
.reconfigure(KmsConfig::static_kms("first-key".to_string(), BASE64_STANDARD.encode([0x11; 32])))
.reconfigure(KmsConfig::static_kms(
"first-key".to_string(),
BASE64_STANDARD.encode_to_string([0x11; 32]),
))
.await
.expect("first KMS reconfigure should succeed");
@@ -6468,7 +6505,10 @@ mod tests {
.expect("provider should use the initial service");
manager
.reconfigure(KmsConfig::static_kms("second-key".to_string(), BASE64_STANDARD.encode([0x22; 32])))
.reconfigure(KmsConfig::static_kms(
"second-key".to_string(),
BASE64_STANDARD.encode_to_string([0x22; 32]),
))
.await
.expect("second KMS reconfigure should succeed");
@@ -6556,7 +6596,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_b64 = BASE64_STANDARD.encode_to_string(key_b);
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();
@@ -6566,7 +6606,7 @@ mod tests {
#[test]
fn test_validate_ssec_for_read_correct_key() {
let key_bytes = [42u8; 32];
let key_b64 = BASE64_STANDARD.encode(key_bytes);
let key_b64 = BASE64_STANDARD.encode_to_string(key_bytes);
let key_md5 = md5_base64(key_bytes);
let mut metadata = HashMap::new();
@@ -6591,7 +6631,7 @@ mod tests {
// Attacker has a different key but tries to pass the stored MD5 as their header
let fake_key = [99u8; 32];
let fake_key_b64 = BASE64_STANDARD.encode(fake_key);
let fake_key_b64 = BASE64_STANDARD.encode_to_string(fake_key);
let err = validate_ssec_for_read(&metadata, Some(&fake_key_b64), Some(&stored_md5)).unwrap_err();
assert_eq!(err.code, S3ErrorCode::InvalidRequest);
@@ -6709,7 +6749,7 @@ mod tests {
#[test]
fn test_validate_ssec_params_returns_invalid_request_on_bad_algorithm() {
let key = BASE64_STANDARD.encode([42u8; 32]);
let key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let key_md5 = md5_base64([42u8; 32]);
let params = SsecParams {
algorithm: "AES128".to_string(),
@@ -6722,11 +6762,11 @@ mod tests {
#[test]
fn test_validate_ssec_params_returns_invalid_request_on_bad_md5() {
let key = BASE64_STANDARD.encode([42u8; 32]);
let key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let params = SsecParams {
algorithm: "AES256".to_string(),
key,
key_md5: BASE64_STANDARD.encode([99u8; 16]),
key_md5: BASE64_STANDARD.encode_to_string([99u8; 16]),
};
let err = validate_ssec_params(params).unwrap_err();
assert_eq!(err.code, S3ErrorCode::InvalidRequest);
@@ -6772,8 +6812,8 @@ mod tests {
async fn test_sse_encryption_errors_on_invalid_ssec_params() {
let bucket = "test-bucket";
let key = "test-key";
let sse_key = BASE64_STANDARD.encode([42u8; 32]);
let wrong_md5 = BASE64_STANDARD.encode([99u8; 16]);
let sse_key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let wrong_md5 = BASE64_STANDARD.encode_to_string([99u8; 16]);
let request_wrong_md5 = EncryptionRequest {
bucket,
@@ -6898,8 +6938,8 @@ mod tests {
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AWS_KMS.to_string()),
("x-amz-server-side-encryption-aws-kms-key-id".to_string(), "finance-key".to_string()),
(INTERNAL_ENCRYPTION_KEY_ID_HEADER.to_string(), "finance-key".to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode([7u8; 48])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([9u8; 12])),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode_to_string([7u8; 48])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([9u8; 12])),
(INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(), "aws:kms".to_string()),
])
}
@@ -6907,8 +6947,8 @@ mod tests {
fn sse_s3_object_metadata() -> HashMap<String, String> {
HashMap::from([
("x-amz-server-side-encryption".to_string(), ServerSideEncryption::AES256.to_string()),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode([7u8; 48])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode([9u8; 12])),
(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), BASE64_STANDARD.encode_to_string([7u8; 48])),
(INTERNAL_ENCRYPTION_IV_HEADER.to_string(), BASE64_STANDARD.encode_to_string([9u8; 12])),
(INTERNAL_ENCRYPTION_ALGORITHM_HEADER.to_string(), "AES256".to_string()),
])
}
@@ -7001,7 +7041,7 @@ mod tests {
#[tokio::test]
async fn ssec_requests_are_exempt_from_kms_key_authorization() {
let (principal, authorizer) = enforcing_principal(false);
let customer_key = BASE64_STANDARD.encode([42u8; 32]);
let customer_key = BASE64_STANDARD.encode_to_string([42u8; 32]);
let customer_key_md5 = md5_base64([42u8; 32]);
let outcome = sse_encryption(EncryptionRequest {
@@ -7166,8 +7206,8 @@ mod tests {
let rendered = format!("{write_tags:?}");
let encrypted_data_key = material.encrypted_data_key.clone().expect("managed sse wraps a data key");
for secret in [
BASE64_STANDARD.encode(&encrypted_data_key),
BASE64_STANDARD.encode(material.key_bytes),
BASE64_STANDARD.encode_to_string(&encrypted_data_key),
BASE64_STANDARD.encode_to_string(material.key_bytes),
format!("{:?}", material.key_bytes),
format!("{encrypted_data_key:?}"),
"acct-4711".to_string(),
@@ -7207,7 +7247,7 @@ mod tests {
ssekms_key_id: None,
ssekms_context: None,
sse_customer_algorithm: Some(SSECustomerAlgorithm::from("AES256".to_string())),
sse_customer_key: Some(SSECustomerKey::from(BASE64_STANDARD.encode(key))),
sse_customer_key: Some(SSECustomerKey::from(BASE64_STANDARD.encode_to_string(key))),
sse_customer_key_md5: Some(SSECustomerKeyMD5::from(md5_base64(key))),
content_size: 128,
principal: Some(&principal),
@@ -7353,7 +7393,7 @@ mod tests {
#[tokio::test]
async fn classification_matches_ssec_validation_and_headers() {
let key = [0x42u8; 32];
let key_b64 = BASE64_STANDARD.encode(key);
let key_b64 = BASE64_STANDARD.encode_to_string(key);
let key_md5 = md5_base64(key);
let metadata = HashMap::from([
("x-amz-server-side-encryption-customer-algorithm".to_string(), "AES256".to_string()),
@@ -7395,7 +7435,7 @@ mod tests {
bucket: "finance",
key: "ledger.csv",
metadata: &metadata,
sse_customer_key: Some(&SSECustomerKey::from(BASE64_STANDARD.encode(other_key))),
sse_customer_key: Some(&SSECustomerKey::from(BASE64_STANDARD.encode_to_string(other_key))),
sse_customer_key_md5: Some(&SSECustomerKeyMD5::from(md5_base64(other_key))),
principal: None,
})
@@ -7405,7 +7445,7 @@ mod tests {
bucket: "finance",
key: "ledger.csv",
metadata: &metadata,
sse_customer_key: Some(&SSECustomerKey::from(BASE64_STANDARD.encode(other_key))),
sse_customer_key: Some(&SSECustomerKey::from(BASE64_STANDARD.encode_to_string(other_key))),
sse_customer_key_md5: Some(&SSECustomerKeyMD5::from(md5_base64(other_key))),
principal: None,
})
@@ -7510,7 +7550,7 @@ mod tests {
#[test]
fn stored_envelope_master_key_version_reads_both_metadata_families() {
let envelope = BASE64_STANDARD.encode(audit_test_envelope(Some(2)));
let envelope = BASE64_STANDARD.encode_to_string(audit_test_envelope(Some(2)));
// RustFS-branded stored key.
let metadata = HashMap::from([(INTERNAL_ENCRYPTION_KEY_HEADER.to_string(), envelope.clone())]);
+11 -12
View File
@@ -20,8 +20,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use base64_simd::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use rustfs::connect::identity::{DeviceIdentity, IdentityError, RegistrationTranscript};
use rustfs::connect::identity_store::{IdentityStore, StoreError};
@@ -66,8 +65,8 @@ fn transcript_reproduces_every_accept_vector() {
let token = &vector["tokenRecord"];
let request = &vector["request"];
let csr = base64::engine::general_purpose::STANDARD
.decode(
let csr = base64_simd::STANDARD
.decode_to_vec(
request["certificateRequest"]
.as_str()
.expect("vector carries a certificate request"),
@@ -118,8 +117,8 @@ fn published_proofs_verify_over_locally_rebuilt_transcripts() {
let token = &vector["tokenRecord"];
let request = &vector["request"];
let csr = base64::engine::general_purpose::STANDARD
.decode(request["certificateRequest"].as_str().unwrap())
let csr = base64_simd::STANDARD
.decode_to_vec(request["certificateRequest"].as_str().unwrap())
.expect("certificate request is base64");
let transcript = RegistrationTranscript::build(
@@ -134,7 +133,7 @@ fn published_proofs_verify_over_locally_rebuilt_transcripts() {
.expect("transcript builds");
let raw = BASE64_URL_NO_PAD
.decode(request["proof"]["value"].as_str().expect("vector carries a proof"))
.decode_to_vec(request["proof"]["value"].as_str().expect("vector carries a proof"))
.expect("proof decodes");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
assert_eq!(
@@ -183,10 +182,10 @@ fn csr_octets_matching_golden_digest() -> Vec<u8> {
let Some(encoded) = vector["request"]["certificateRequest"].as_str() else {
continue;
};
let der = base64::engine::general_purpose::STANDARD
.decode(encoded)
let der = base64_simd::STANDARD
.decode_to_vec(encoded)
.expect("certificate request is base64");
let digest = BASE64_URL_NO_PAD.encode(<sha2::Sha256 as sha2::Digest>::digest(&der));
let digest = BASE64_URL_NO_PAD.encode_to_string(<sha2::Sha256 as sha2::Digest>::digest(&der));
if digest == want {
return der;
}
@@ -302,7 +301,7 @@ fn proof_is_a_canonical_low_s_signature_that_verifies() {
"the proof must use the base64url alphabet with no padding"
);
let raw = BASE64_URL_NO_PAD.decode(&proof.value).expect("proof decodes");
let raw = BASE64_URL_NO_PAD.decode_to_vec(&proof.value).expect("proof decodes");
assert_eq!(raw.len(), 64, "the signature is a fixed-width r || s");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
@@ -334,7 +333,7 @@ fn proof_does_not_verify_over_a_different_transcript() {
let other = transcript_from_fixture_inputs(b"a different certificate request").expect("transcript builds");
assert_ne!(transcript.as_bytes(), other.as_bytes());
let raw = BASE64_URL_NO_PAD.decode(&proof.value).expect("proof decodes");
let raw = BASE64_URL_NO_PAD.decode_to_vec(&proof.value).expect("proof decodes");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
let verifying = <p256::ecdsa::VerifyingKey as p256::pkcs8::DecodePublicKey>::from_public_key_der(&identity.public_key_der())
.expect("public key decodes");
+3 -3
View File
@@ -22,7 +22,7 @@ use std::process::Command;
#[cfg(target_os = "linux")]
use std::time::Duration;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use base64_simd::URL_SAFE_NO_PAD;
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier as _};
use p256::pkcs8::DecodePublicKey as _;
use rustfs::connect::DeviceIdentity;
@@ -169,7 +169,7 @@ fn connect_offline_bundle_is_deterministic_bounded_and_signed_over_exact_manifes
assert_eq!(signature_document["signedFile"], "manifest.json");
assert_eq!(signature_document["domainSeparationTag"], "rustfs-support-bundle-v1");
let signature_bytes: [u8; 64] = URL_SAFE_NO_PAD
.decode(signature_document["value"].as_str().expect("signature value"))
.decode_to_vec(signature_document["value"].as_str().expect("signature value"))
.expect("signature base64url")
.try_into()
.expect("fixed-width signature");
@@ -197,7 +197,7 @@ fn connect_offline_bundle_is_deterministic_bounded_and_signed_over_exact_manifes
"organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50/clusters/0198f3a1-5d00-7b20-9c31-1d2e3f4a5b61"
);
assert_eq!(manifest["deviceName"], DEVICE_NAME);
assert_eq!(manifest["nonce"], URL_SAFE_NO_PAD.encode([0x2a; 32]));
assert_eq!(manifest["nonce"], URL_SAFE_NO_PAD.encode_to_string([0x2a; 32]));
assert_eq!(manifest["producedAt"], "2026-05-04T02:00:00Z");
assert_eq!(manifest["redactionVersion"], REDACTION_VERSION);
assert_eq!(manifest["rulesetHash"], RULESET_HASH);
+23 -18
View File
@@ -29,9 +29,8 @@
use std::fs;
use std::path::PathBuf;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use base64_simd::STANDARD as BASE64_STANDARD;
use base64_simd::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use rustfs::connect::identity::DeviceIdentity;
use rustfs::connect::offline::{EnrollmentError, OfflineEnrollment, VerifiedChallenge};
use serde_json::Value;
@@ -133,7 +132,7 @@ fn envelope(document: &Value) -> Vec<u8> {
/// The raw octets the signature covers, exactly as transmitted.
fn signed_octets(document: &Value) -> Vec<u8> {
BASE64_STANDARD
.decode(field(document, "bytes"))
.decode_to_vec(field(document, "bytes"))
.expect("document bytes are padded base64")
}
@@ -158,7 +157,9 @@ fn hex_to_bytes(hex: &str) -> Vec<u8> {
/// Turn a fixture's unpadded-base64url SEC1 point into a usable verifying key.
fn verifying_key(sec1_base64url: &str) -> p256::ecdsa::VerifyingKey {
let point = BASE64_URL_NO_PAD.decode(sec1_base64url).expect("public key is base64url");
let point = BASE64_URL_NO_PAD
.decode_to_vec(sec1_base64url)
.expect("public key is base64url");
assert_eq!(point.len(), 65, "the protocol freezes a 65 octet uncompressed SEC1 point");
let mut der = hex_to_bytes(SPKI_PREFIX_HEX);
@@ -215,7 +216,7 @@ fn answered_challenge(response_vector: &Value) -> (Value, VerifiedChallenge) {
fn device_nonce_of(document: &Value) -> [u8; 32] {
let raw = BASE64_URL_NO_PAD
.decode(field(&signed_document(document), "deviceNonce"))
.decode_to_vec(field(&signed_document(document), "deviceNonce"))
.expect("deviceNonce is base64url");
raw.try_into().expect("replay.nonceLengthBytes freezes a 32 octet nonce")
}
@@ -330,7 +331,7 @@ fn e2e_public_chain_matches_the_challenge_and_every_signature_verifies() {
for link in chain.as_array().expect("E2E chain is a list") {
assert_eq!(field(&link["signature"], "keyId"), issuer_id.as_str());
let signature = BASE64_URL_NO_PAD
.decode(field(&link["signature"], "value"))
.decode_to_vec(field(&link["signature"], "value"))
.expect("trust-link signature is base64url");
issuer
.verify(
@@ -345,7 +346,7 @@ fn e2e_public_chain_matches_the_challenge_and_every_signature_verifies() {
assert_eq!(field(&challenge, "connectKeyId"), issuer_id.as_str());
let signature = BASE64_URL_NO_PAD
.decode(field(&challenge_envelope["signature"], "value"))
.decode_to_vec(field(&challenge_envelope["signature"], "value"))
.expect("challenge signature is base64url");
issuer
.verify(
@@ -571,7 +572,7 @@ fn response_reject_vectors_are_artifacts_build_response_cannot_emit() {
let presented = verifying_key(field(&refused, "devicePublicKey"));
let raw = BASE64_URL_NO_PAD
.decode(field(&vector["document"]["signature"], "value"))
.decode_to_vec(field(&vector["document"]["signature"], "value"))
.expect("signature is base64url");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
assert!(
@@ -685,8 +686,12 @@ fn malleated_high_s_signature_is_refused_although_it_verifies_mathematically() {
let malleated_value = field(&malleated, "value").to_string();
assert_ne!(genuine_value, malleated_value, "the malleation must be a different encoding");
let genuine = BASE64_URL_NO_PAD.decode(&genuine_value).expect("signature is base64url");
let raw = BASE64_URL_NO_PAD.decode(&malleated_value).expect("signature is base64url");
let genuine = BASE64_URL_NO_PAD
.decode_to_vec(&genuine_value)
.expect("signature is base64url");
let raw = BASE64_URL_NO_PAD
.decode_to_vec(&malleated_value)
.expect("signature is base64url");
assert_eq!(raw.len(), 64, "the malleation is well formed at 64 octets");
assert_eq!(raw[..32], genuine[..32], "the malleation shares r with the genuine signature");
assert_ne!(raw[32..], genuine[32..], "the malleation replaces s with n - s");
@@ -797,7 +802,7 @@ fn assert_response_proves_possession(built_envelope: &Value, label: &str) {
"{label}: the signature must use the base64url alphabet with no padding"
);
let bytes = BASE64_URL_NO_PAD.decode(value).expect("signature is base64url");
let bytes = BASE64_URL_NO_PAD.decode_to_vec(value).expect("signature is base64url");
assert_eq!(bytes.len(), 64, "{label}: the signature is a fixed-width r || s");
let signature = p256::ecdsa::Signature::from_slice(&bytes).expect("signature parses");
assert_eq!(
@@ -815,7 +820,7 @@ fn assert_response_proves_possession(built_envelope: &Value, label: &str) {
// SubjectPublicKeyInfo, not of the bare point and not of the transfer
// encoding.
let mut spki = hex_to_bytes(SPKI_PREFIX_HEX);
spki.extend_from_slice(&BASE64_URL_NO_PAD.decode(presented).expect("public key is base64url"));
spki.extend_from_slice(&BASE64_URL_NO_PAD.decode_to_vec(presented).expect("public key is base64url"));
let fingerprint = sha256_hex(&spki);
assert_eq!(
field(&built, "deviceKeyId"),
@@ -859,12 +864,12 @@ fn built_response_binds_the_challenge_proof_and_proves_possession_of_the_device_
assert_eq!(
field(&built, "devicePublicKey"),
BASE64_URL_NO_PAD.encode(&key.public_key_der()[hex_to_bytes(SPKI_PREFIX_HEX).len()..]),
BASE64_URL_NO_PAD.encode_to_string(&key.public_key_der()[hex_to_bytes(SPKI_PREFIX_HEX).len()..]),
"the presented key must be the key that was passed in"
);
assert_eq!(
field(&built, "deviceNonce"),
BASE64_URL_NO_PAD.encode([0x11; 32]),
BASE64_URL_NO_PAD.encode_to_string([0x11; 32]),
"the device nonce must be the one that was passed in"
);
assert!(field(&built, "producedAt").ends_with('Z'), "producedAt is a UTC RFC 3339 instant");
@@ -901,8 +906,8 @@ fn built_response_carries_no_private_key_material() {
for (description, needle) in [
("the PKCS#8 encoding", pkcs8.to_vec()),
("the raw private scalar", scalar.to_vec()),
("the scalar in base64url", BASE64_URL_NO_PAD.encode(scalar).into_bytes()),
("the scalar in standard base64", BASE64_STANDARD.encode(scalar).into_bytes()),
("the scalar in base64url", BASE64_URL_NO_PAD.encode_to_string(scalar).into_bytes()),
("the scalar in standard base64", BASE64_STANDARD.encode_to_string(scalar).into_bytes()),
("the scalar in hex", scalar_hex.into_bytes()),
] {
assert!(
@@ -914,7 +919,7 @@ fn built_response_carries_no_private_key_material() {
// The public half must be there, so the absence above is a statement about
// what was excluded rather than about a haystack that would not have found
// the private half either.
let point = BASE64_URL_NO_PAD.encode(&key.public_key_der()[hex_to_bytes(SPKI_PREFIX_HEX).len()..]);
let point = BASE64_URL_NO_PAD.encode_to_string(&key.public_key_der()[hex_to_bytes(SPKI_PREFIX_HEX).len()..]);
assert!(
haystack.windows(point.len()).any(|window| window == point.as_bytes()),
"the response must still present the public key"
+4 -5
View File
@@ -18,8 +18,7 @@ use std::io::Write as _;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use base64::Engine as _;
use base64::engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use base64_simd::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use bytes::Bytes;
use http_body_util::{BodyExt as _, Full};
use hyper::service::service_fn;
@@ -359,9 +358,9 @@ fn verify_rotation_request(request: &Value, current_public_key: &[u8], fingerpri
assert_eq!(request["protocolVersion"], "v1");
assert_eq!(request["proof"]["algorithm"], "ES256");
let csr = BASE64_STANDARD
.decode(request["certificateRequest"].as_str().expect("certificateRequest"))
.decode_to_vec(request["certificateRequest"].as_str().expect("certificateRequest"))
.expect("CSR base64");
let csr_digest = BASE64_URL_NO_PAD.encode(Sha256::digest(&csr));
let csr_digest = BASE64_URL_NO_PAD.encode_to_string(Sha256::digest(&csr));
let request_id = request["requestId"].as_str().expect("requestId");
let transcript = rebuilt_rotation_transcript(
b"RUSTFS-CONNECT-CREDENTIAL-ROTATION-V1",
@@ -369,7 +368,7 @@ fn verify_rotation_request(request: &Value, current_public_key: &[u8], fingerpri
);
let encoded = request["proof"]["value"].as_str().expect("proof value");
assert_eq!(encoded.len(), 86);
let raw = BASE64_URL_NO_PAD.decode(encoded).expect("proof base64url");
let raw = BASE64_URL_NO_PAD.decode_to_vec(encoded).expect("proof base64url");
let signature = Signature::from_slice(&raw).expect("fixed-width signature");
assert_eq!(signature.normalize_s(), signature, "rotation proof must be low-S");
let verifying = VerifyingKey::from_public_key_der(current_public_key).expect("current public key");