Affected AWS service
kms
Summary
winterbaume-kms's Decrypt handler resolves the key from the ciphertext header alone and ignores the request-side KeyId parameter. Real AWS validates KeyId against the ciphertext and returns IncorrectKeyException on mismatch. Against the mock, a Decrypt call with the wrong KeyId silently returns plaintext.
Reproduction
Cargo.toml:
[dependencies]
aws-config = "1"
aws-sdk-kms = "1"
tokio = { version = "1", features = ["full"] }
winterbaume-core = "0.2"
winterbaume-kms = "0.2"
src/main.rs:
use aws_sdk_kms::config::BehaviorVersion;
use aws_sdk_kms::primitives::Blob;
use winterbaume_core::MockAws;
use winterbaume_kms::KmsService;
#[tokio::main]
async fn main() {
let mock = MockAws::builder().with_service(KmsService::new()).build();
let config = aws_config::defaults(BehaviorVersion::latest())
.http_client(mock.http_client())
.credentials_provider(mock.credentials_provider())
.region(aws_sdk_kms::config::Region::new("us-east-1"))
.load().await;
let client = aws_sdk_kms::Client::new(&config);
let key_a = client.create_key().send().await.unwrap();
let key_a_id = key_a.key_metadata().unwrap().key_id().to_string();
let key_b = client.create_key().send().await.unwrap();
let key_b_id = key_b.key_metadata().unwrap().key_id().to_string();
let enc = client.encrypt()
.key_id(&key_a_id)
.plaintext(Blob::new(b"hello".to_vec()))
.send().await.unwrap();
// Decrypt with WRONG KeyId.
let resp = client.decrypt()
.ciphertext_blob(enc.ciphertext_blob().unwrap().clone())
.key_id(&key_b_id)
.send().await;
match resp {
Ok(out) => println!("decrypt(KeyId=key_b) -> Ok, plaintext = {:?}",
String::from_utf8_lossy(out.plaintext().unwrap().as_ref())),
Err(e) => println!("decrypt(KeyId=key_b) -> Err: {e:?}"),
}
}
Expected behaviour
Per the AWS KMS Decrypt API documentation, on the KeyId parameter:
Enter a key ID of the KMS key that was used to encrypt the ciphertext. If you identify a different KMS key, the Decrypt operation throws an IncorrectKeyException.
And from the same page's Errors section:
IncorrectKeyException — The request was rejected because the specified KMS key cannot decrypt the data. The KeyId in a Decrypt request and the SourceKeyId in a ReEncrypt request must identify the same KMS key that was used to encrypt the ciphertext. HTTP Status Code: 400
So when the client supplies KeyId = key_b but the ciphertext was encrypted under key_a, real AWS rejects the call with IncorrectKeyException / 400.
Actual behaviour
The repro above prints:
key_a_id = e8b1dce0-94e0-4ef4-bd37-e4c97515b01a
key_b_id = 211e73fb-3b2c-41b7-8b75-7493e621d339
decrypt(KeyId=key_b) -> Ok, plaintext = "hello"
Decrypt returns 200 OK with the original plaintext even though the request-side KeyId does not match the ciphertext's key. The handler appears to read the key id from the ciphertext header and never compare it against the request KeyId.
winterbaume version / commit
4b4cc9a
Affected AWS service
kms
Summary
winterbaume-kms'sDecrypthandler resolves the key from the ciphertext header alone and ignores the request-sideKeyIdparameter. Real AWS validatesKeyIdagainst the ciphertext and returnsIncorrectKeyExceptionon mismatch. Against the mock, aDecryptcall with the wrongKeyIdsilently returns plaintext.Reproduction
Cargo.toml:src/main.rs:Expected behaviour
Per the AWS KMS
DecryptAPI documentation, on theKeyIdparameter:And from the same page's Errors section:
So when the client supplies
KeyId = key_bbut the ciphertext was encrypted underkey_a, real AWS rejects the call withIncorrectKeyException/ 400.Actual behaviour
The repro above prints:
Decryptreturns200 OKwith the original plaintext even though the request-sideKeyIddoes not match the ciphertext's key. The handler appears to read the key id from the ciphertext header and never compare it against the requestKeyId.winterbaume version / commit
4b4cc9a