mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
chore(deps): migrate direct encoding deps to simd (#6690)
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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
@@ -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(¶ms.key).map_err(|e| {
|
||||
let key_bytes = BASE64_STANDARD.decode_to_vec(¶ms.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())]);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user