diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index dfb0485a0..0912d0127 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -312,6 +312,9 @@ impl DefaultObjectUsecase { let sse_customer_key = sse_customer_key.or(h_key); let sse_customer_key_md5 = sse_customer_key_md5.or(h_md5); + // Merge server_side_encryption from headers (fallback when S3 layer does not populate input) + let server_side_encryption = server_side_encryption.or(extract_server_side_encryption_from_headers(&req.headers)?); + // Validate object key validate_object_key(&key, "PUT")?; diff --git a/rustfs/src/storage/mod.rs b/rustfs/src/storage/mod.rs index 63a45a007..49aab9913 100644 --- a/rustfs/src/storage/mod.rs +++ b/rustfs/src/storage/mod.rs @@ -35,7 +35,7 @@ mod sse_test; pub(crate) use ecfs_extend::*; pub(crate) use sse::{ - DecryptionRequest, EncryptionRequest, PrepareEncryptionRequest, extract_ssec_params_from_headers, sse_decryption, - sse_encryption, sse_prepare_encryption, strip_managed_encryption_metadata, validate_sse_headers_for_read, - validate_sse_headers_for_write, validate_ssec_for_read, + DecryptionRequest, EncryptionRequest, PrepareEncryptionRequest, extract_server_side_encryption_from_headers, + extract_ssec_params_from_headers, sse_decryption, sse_encryption, sse_prepare_encryption, strip_managed_encryption_metadata, + validate_sse_headers_for_read, validate_sse_headers_for_write, validate_ssec_for_read, }; diff --git a/rustfs/src/storage/sse.rs b/rustfs/src/storage/sse.rs index 83590cf80..679b2b4df 100644 --- a/rustfs/src/storage/sse.rs +++ b/rustfs/src/storage/sse.rs @@ -379,6 +379,21 @@ pub(crate) fn extract_ssec_params_from_headers(headers: &HeaderMap) -> Result Result, ApiError> { + match headers.get("x-amz-server-side-encryption") { + None => Ok(None), + Some(v) => v + .to_str() + .map(|s| Some(ServerSideEncryption::from(s.to_string()))) + .map_err(|_| sse_invalid_argument("The x-amz-server-side-encryption header must be valid UTF-8.")), + } +} + #[inline] pub(crate) fn validate_sse_headers_for_write( server_side_encryption: Option<&ServerSideEncryption>, @@ -388,6 +403,15 @@ pub(crate) fn validate_sse_headers_for_write( sse_customer_key_md5: Option<&SSECustomerKeyMD5>, require_sse_customer_key: bool, ) -> Result<(), ApiError> { + if let Some(sse) = server_side_encryption { + let s = sse.as_str(); + if s != ServerSideEncryption::AES256 && s != ServerSideEncryption::AWS_KMS { + return Err(sse_invalid_argument( + "The SSE algorithm specified is not supported. The valid values are AES256 or aws:kms.", + )); + } + } + let has_ssec_headers = sse_customer_algorithm.is_some() || sse_customer_key.is_some() || sse_customer_key_md5.is_some(); let has_managed_headers = server_side_encryption.is_some() || ssekms_key_id.is_some(); @@ -1752,6 +1776,17 @@ mod tests { assert_eq!(err.code, S3ErrorCode::InvalidArgument); } + #[test] + fn test_extract_server_side_encryption_from_headers_rejects_invalid_utf8() { + let mut headers = http::HeaderMap::new(); + let invalid_utf8 = HeaderValue::from_bytes(b"aes:kms-\x80-invalid").unwrap(); + headers.insert("x-amz-server-side-encryption", invalid_utf8); + let result = extract_server_side_encryption_from_headers(&headers); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.code, S3ErrorCode::InvalidArgument); + } + #[test] fn test_validate_sse_headers_for_write_rejects_algorithm_without_key() { let algorithm = SSECustomerAlgorithm::from("AES256".to_string()); @@ -1785,6 +1820,15 @@ mod tests { assert_eq!(err.code, S3ErrorCode::InvalidRequest); } + #[test] + fn test_validate_sse_headers_for_write_rejects_invalid_sse_algorithm() { + let bad_sse = ServerSideEncryption::from_static("aes:kms"); + let result = validate_sse_headers_for_write(Some(&bad_sse), None, None, None, None, true); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.code, S3ErrorCode::InvalidArgument); + } + #[test] fn test_validate_sse_headers_for_write_rejects_ssec_with_managed_sse() { let algorithm = SSECustomerAlgorithm::from("AES256".to_string()); diff --git a/scripts/s3-tests/implemented_tests.txt b/scripts/s3-tests/implemented_tests.txt index 7e147d0fd..4a921be23 100644 --- a/scripts/s3-tests/implemented_tests.txt +++ b/scripts/s3-tests/implemented_tests.txt @@ -20,7 +20,7 @@ # - SSE-C: Server-side encryption with customer-provided keys # - Object ownership: Bucket ownership controls # -# Total: 177 tests +# Total: 182 tests test_basic_key_count test_bucket_create_naming_bad_short_one @@ -162,7 +162,12 @@ test_encryption_sse_c_present test_encryption_sse_c_other_key test_encryption_sse_c_no_key test_encryption_sse_c_no_md5 +test_encryption_key_no_sse_c +test_encryption_sse_c_invalid_md5 test_put_obj_enc_conflict_c_s3 +test_put_obj_enc_conflict_c_kms +test_put_obj_enc_conflict_s3_kms +test_put_obj_enc_conflict_bad_enc_kms # ListObjectsV2 delimiter and encoding tests test_bucket_list_encoding_basic diff --git a/scripts/s3-tests/unimplemented_tests.txt b/scripts/s3-tests/unimplemented_tests.txt index 8982eba62..00cdd75a8 100644 --- a/scripts/s3-tests/unimplemented_tests.txt +++ b/scripts/s3-tests/unimplemented_tests.txt @@ -25,8 +25,6 @@ test_bucket_policy_put_obj_kms_s3 test_bucket_policy_put_obj_s3_kms test_copy_enc test_copy_part_enc -test_encryption_key_no_sse_c -test_encryption_sse_c_invalid_md5 test_encryption_sse_c_multipart_bad_download test_get_versioned_object_attributes test_lifecycle_delete @@ -62,9 +60,6 @@ test_put_bucket_logging test_put_bucket_logging_errors test_put_bucket_logging_permissions test_put_bucket_logging_policy_wildcard -test_put_obj_enc_conflict_bad_enc_kms -test_put_obj_enc_conflict_c_kms -test_put_obj_enc_conflict_s3_kms test_rm_bucket_logging test_sse_kms_no_key test_sse_kms_not_declared