fix(s3): reject invalid SSE algorithm (aes:kms) in PutObject (#2008)

This commit is contained in:
安正超
2026-03-01 00:56:26 +08:00
committed by GitHub
parent 1872bdcedd
commit f93db578df
5 changed files with 56 additions and 9 deletions
+3
View File
@@ -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")?;
+3 -3
View File
@@ -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,
};
+44
View File
@@ -379,6 +379,21 @@ pub(crate) fn extract_ssec_params_from_headers(headers: &HeaderMap) -> Result<Ss
Ok((algorithm, key, key_md5))
}
/// Extract x-amz-server-side-encryption from request headers.
/// Used as fallback when the S3 layer does not populate it in the input struct.
///
/// Returns an error if the header is present but cannot be parsed as valid UTF-8,
/// ensuring malformed headers do not bypass validation.
pub(crate) fn extract_server_side_encryption_from_headers(headers: &HeaderMap) -> Result<Option<ServerSideEncryption>, 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());
+6 -1
View File
@@ -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
-5
View File
@@ -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