Skip to content

[bug] KMS:Decrypt ignores request-side KeyId; wrong KeyId silently succeeds instead of IncorrectKeyException #5

@mizzy

Description

@mizzy

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions