KMS: Fix double hashing during sign/verify operations#7878
KMS: Fix double hashing during sign/verify operations#7878viren-nadkarni merged 4 commits intomasterfrom
Conversation
| def test_invalid_key_usage(self, kms_client, kms_create_key): | ||
| key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="RSA_4096")["KeyId"] | ||
| with pytest.raises(ClientError) as exc: | ||
| kms_client.sign( | ||
| MessageType="RAW", | ||
| Message="hello", | ||
| KeyId=key_id, | ||
| SigningAlgorithm="RSASSA_PSS_SHA_256", | ||
| ) | ||
| assert exc.match("InvalidKeyUsageException") | ||
|
|
||
| key_id = kms_create_key(KeyUsage="SIGN_VERIFY", KeySpec="RSA_4096")["KeyId"] | ||
| with pytest.raises(ClientError) as exc: | ||
| kms_client.encrypt( | ||
| Plaintext="hello", | ||
| KeyId=key_id, | ||
| EncryptionAlgorithm="RSAES_OAEP_SHA_256", | ||
| ) | ||
| assert exc.match("InvalidKeyUsageException") | ||
|
|
||
| @pytest.mark.parametrize( | ||
| "key_spec,algo", | ||
| [ | ||
| ("SYMMETRIC_DEFAULT", "SYMMETRIC_DEFAULT"), | ||
| ("RSA_2048", "RSAES_OAEP_SHA_256"), | ||
| ("ECC_NIST_P256", "RSAES_OAEP_SHA_1"), | ||
| ("ECC_SECG_P256K1", "RSAES_OAEP_SHA_256"), | ||
| # ("HMAC_256", "SYMMETRIC_DEFAULT"), # currently not supported in LocalStack | ||
| # ("SM2", "SM2PKE"), # currently not supported in LocalStack | ||
| ], | ||
| ) | ||
| def test_encrypt_decrypt(self, kms_client, kms_create_key, key_spec, algo): | ||
| key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec=key_spec)["KeyId"] | ||
| message = b"test message 123 !%$@ 1234567890" | ||
| algo = "RSASSA_PSS_SHA_256" if key_type == "rsa" else "ECDSA_SHA_256" | ||
| kwargs = {"KeyId": key_id, "Message": message, "SigningAlgorithm": algo} | ||
|
|
||
| signature_for_plaintext = kms_client.sign(MessageType="RAW", **kwargs)["Signature"] | ||
| signature_for_digest = kms_client.sign(MessageType="DIGEST", **kwargs)["Signature"] | ||
| assert signature_for_plaintext != signature_for_digest | ||
|
|
||
| # These following blocks basically test that MessageType="DIGEST" results in a different outcome | ||
| # from the default MessageType="RAW". As long as both Sign and Verify are called with the same MessageType, | ||
| # everything should work, while if this parameter mismatches between such two calls - the verifications is | ||
| # supposed to fail. | ||
| # | ||
| # There is a possibility that I do not understand how digests work, so can't write better tests. If we have | ||
| # any issues with the digests - know that the current approach is not a calculated design, but rather a guess. | ||
| signature = kms_client.sign(MessageType="RAW", **kwargs)["Signature"] | ||
| assert kms_client.verify(MessageType="RAW", Signature=signature, **kwargs)["SignatureValid"] | ||
|
|
||
| signature = kms_client.sign(MessageType="RAW", **kwargs)["Signature"] | ||
| with pytest.raises(kms_client.exceptions.KMSInvalidSignatureException): | ||
| assert not kms_client.verify(MessageType="DIGEST", Signature=signature, **kwargs)[ | ||
| "SignatureValid" | ||
| ] | ||
|
|
||
| signature = kms_client.sign(MessageType="DIGEST", **kwargs)["Signature"] | ||
| with pytest.raises(kms_client.exceptions.KMSInvalidSignatureException): | ||
| assert not kms_client.verify(MessageType="RAW", Signature=signature, **kwargs)[ | ||
| "SignatureValid" | ||
| ] | ||
|
|
||
| signature = kms_client.sign(MessageType="DIGEST", **kwargs)["Signature"] | ||
| assert kms_client.verify(MessageType="DIGEST", Signature=signature, **kwargs)[ | ||
| "SignatureValid" | ||
| ] | ||
| ciphertext = kms_client.encrypt( | ||
| KeyId=key_id, Plaintext=base64.b64encode(message), EncryptionAlgorithm=algo | ||
| )["CiphertextBlob"] | ||
| plaintext = kms_client.decrypt( | ||
| KeyId=key_id, CiphertextBlob=ciphertext, EncryptionAlgorithm=algo | ||
| )["Plaintext"] | ||
| assert base64.b64decode(plaintext) == message |
There was a problem hiding this comment.
New tests to cover Encrypt and Decrypt
whummer
left a comment
There was a problem hiding this comment.
Awesome set of changes @viren-nadkarni ! 🚀 Also big kudos to @paullallier for the detailed analysis of the issue and the initial implementation 🙌
Changes LGTM, only one small comment related to error handling.. In a future iteration, we could maybe introduce a few snapshots.
| # Ensure bad digest raises during signing | ||
| with pytest.raises(ClientError) as exc: | ||
| kms_client.sign(MessageType="DIGEST", Message=plaintext, **kwargs) | ||
| assert exc.match("ValidationException") |
There was a problem hiding this comment.
small nit: Since the tests are already AWS validated, we could also do a snapshot.match(..) here, which would also assert the other elements of the error response. (no need to change now, though, we could tackle the snapshots in a follow-up iteration 👍 ).
|
Thanks for getting this one into the codebase, @viren-nadkarni. |
This PR is based on investigation done by @paullallier in #6815 (comment)
LocalStack KMS Sign and Verify operations were double hashing the message payloads. This PR reworks these operations to rely on
cryptographyto perform hashing if required based on the value ofMessageTypeargument.The integration tests are also refactored to make the correct assertions.
Also checked that signatures produced by KMS:Sign can be correctly verified with OpenSSL (i.e. outside of the KMS:Verify operation).
Supersedes #7817
Fixes #6815, #7158