refactor(kms): close the low-severity follow-ups from the #5668 adversarial re-review (#5817)

* refactor(kms): share the DEK spec mapping and stop re-parsing opened envelopes

- generate_key_material is now the single spec->length mapping for every
  backend that mints DEKs itself; the inline copies in the Static and Local
  backends are gone, and ChaCha20 (32 bytes, same as AES_256) is accepted
  uniformly instead of only by Static.
- The pub(crate) client decrypt of the Local, Vault KV2 and Vault Transit
  backends returns (plaintext, master_key_id), so KmsBackend::decrypt no
  longer re-parses the envelope it just opened (one JSON parse per SSE GET
  instead of two, and unknown-field observability is no longer double-counted).
- Malformed-envelope parse failures now report CryptographicError("parse")
  on all backends; Local was the last one mapping them to SerializationError.
- The four KmsBackend::generate_data_key adapters take fields out of
  DataKeyInfo instead of cloning, dropping a redundant un-zeroized plaintext
  DEK copy and a full ciphertext clone per call; a missing plaintext now
  fails closed everywhere instead of returning an empty key on three of four
  backends.

* test(kms): pin legacy header fallback, stored-AAD, and decrypt key-id contracts

- a_legacy_aws_kms_object_without_the_cipher_header_still_opens rebuilds the
  true pre-internal-header shape (aws:kms mode + S3 key-id header, no
  x-rustfs-* headers) and asserts the fallback normalizes the cipher and
  re-projects it.
- a_rewritten_sse_c_context_header_fails_authentication is the SSE-C flank of
  the stored-AAD tamper check; metadata_without_stored_context_bytes_still_opens
  covers the derived-AAD path for both flavours and pins the seal side to the
  canonical bytes (mutation-verified).
- data_key_spec_controls_the_length_of_the_generated_key requires every
  backend in the matrix to honour all three specs, asserts the envelope
  records the requested spec, and round-trips each blob.
- corrupt_ciphertext_fails_cleanly pins unparseable ciphertext to
  CryptographicError instead of merely not-InternalError.
- Deleted the never-called assert_validation_error / assert_cryptographic_error
  helpers.
This commit is contained in:
唐小鸭
2026-08-08 05:41:50 +08:00
committed by GitHub
parent a0a8eaa0f3
commit 6633c80151
9 changed files with 315 additions and 134 deletions
+10 -16
View File
@@ -26,7 +26,7 @@
use crate::backends::{BackendCapabilities, KmsBackend, empty_key_page, list_keys_page_size};
use crate::config::{BackendConfig, KmsConfig};
use crate::encryption::{DataKeyEnvelope, context_aad};
use crate::encryption::{DataKeyEnvelope, context_aad, generate_key_material};
use crate::error::{KmsError, Result};
use crate::types::*;
use aes_gcm::{
@@ -124,16 +124,7 @@ impl StaticKmsBackend {
// The requested spec decides the DEK length; a caller that asked for
// AES_128 and silently got 256 bits would build objects whose recorded
// spec does not match their key material.
// Lengths track `KeySpec::key_size`; the request carries the spec as a
// string, so the mapping is repeated here rather than shared.
let key_length = match request.key_spec.as_str() {
"AES_256" | "ChaCha20" => 32,
"AES_128" => 16,
_ => return Err(KmsError::unsupported_algorithm(&request.key_spec)),
};
let mut plaintext = vec![0u8; key_length];
rand::rng().fill(&mut plaintext[..]);
let plaintext = generate_key_material(&request.key_spec)?;
// Encrypt DEK with AES-256-GCM using the static key directly
let key = Key::<Aes256Gcm>::from(*self.key);
@@ -167,7 +158,7 @@ impl StaticKmsBackend {
Ok(DataKeyInfo::new(
self.key_id.clone(),
0,
Some(plaintext.to_vec()),
Some(plaintext),
ciphertext,
request.key_spec.clone(),
))
@@ -360,17 +351,20 @@ impl KmsBackend for StaticKmsBackend {
encryption_context: request.encryption_context,
grant_tokens: Vec::new(),
};
let data_key = self.generate_data_key_envelope(&gen_req)?;
let mut data_key = self.generate_data_key_envelope(&gen_req)?;
// Fields are taken, not destructured or cloned: `DataKeyInfo` has a
// `Drop` impl, and a clone would leave a second un-zeroized plaintext
// DEK on the heap.
let plaintext_key = data_key
.plaintext
.clone()
.take()
.ok_or_else(|| KmsError::internal_error("Generated data key is missing plaintext"))?;
Ok(GenerateDataKeyResponse {
key_id: data_key.key_id.clone(),
key_id: std::mem::take(&mut data_key.key_id),
plaintext_key,
ciphertext_blob: data_key.ciphertext.clone(),
ciphertext_blob: std::mem::take(&mut data_key.ciphertext),
})
}