Reputation: 51
I am using an AWS KMS asymmetric ECC (ES256) key to sign and verify tokens using API calls in Python3. The response returns
{
"KeyId": "arn:aws:kms:us-east-1:000000000000:key/1234",
"Signature": "b'0D\\x02 \\x18\\xd9\\x13\\x96\\x9d\\xb00p\\xc9H\\'-\\xc6@{\\xd1V\\xf5\\xeb\\x83\"1\\x0e:\\x98\\xb4\\xea6P\\x0f\\xdf\\x82\\x02 \\x1fPt\\xc0\\x81pB\\xaa\\xe7_\\xf2~\\x91BlU\\x05\\xba1\\xe0\\xfe\\xf6\\xe4\\xebL\\nl\\xfc\\xf8\\xce\\xc6_'",
"SigningAlgorithm": "ECDSA_SHA_256"
}
If I use AWS CLI the response "Signature" is base64 encoded:
{
"KeyId": "arn:aws:kms:us-east-1:000000000000:key/1234",
"Signature": "MEQCIBqjAiAnMKkugooWU6/AqCyfhQUocoiVeIIf8lL2p7YbAiBEJOl2cp9HzQNufiMBezIjZuSGW6ID13l7JSzTgLlv+g==",
"SigningAlgorithm": "ECDSA_SHA_256"
}
Whenever I attempt to base64 encode base64.urlsafe_b64encode(string)
the "Signature" using the API response in Python3, I cannot verify the signature. Here's an example of the error I receive:
An error occurred (InvalidKeyUsageException) when calling the Verify operation: asn1: structure error: tags don't match (16 vs {class:1 tag:2 length:39 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} ecdsaSignature @2
How can I base64 encode the "Signature" bytes string returned from the API and still be able to verify?
UPDATE:
If I update to base64.b4encode
instead of base64.urlsafe_encode
I get the same result:
def _sign_token(self, key_id, message):
signature = kms_client.sign(
KeyId=key_id,
Message=message,
MessageType="RAW",
SigningAlgorithm="ECDSA_SHA_256",
)
logger.log(msg=signature, level=cl.log_level)
return base64.b64encode(signature["Signature"]).decode()
log message returns:
{"asctime": "2021-04-03 19:40:27,593", "levelname": "INFO", "name": "util.sign", "lineno": 214, "message": null, "env": "local", "KeyId": "arn:aws:kms:us-east-1:000000000000:key/309d8dc5-409e-4f57-96a8-c71223c84dc2", "Signature": "b'0E\x02!\x00\xcc\x82\xf4$@?\x9e\n\xd7$\x94\x9f.\x1d5\x19{W\x1e\xcff\x8b&\xf0\xef\x88\xcf^"\xf6\xa4\x1b\x02 \x0c\x18\xab?\x93\xd5\x88Cx\xf6\x0c\x1b\xcf\xd9\xd9\xd2\xd4"\xcf\x94\xede_>\x8c\x01~J\xea\x0ezB'", "SigningAlgorithm": "ECDSA_SHA_256", "ResponseMetadata": {"HTTPStatusCode": 200, "HTTPHeaders": {"content-type": "application/x-amz-json-1.1", "content-length": "233", "connection": "close", "access-control-allow-origin": "*", "access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", "access-control-allow-headers": "authorization,content-type,content-length,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging", "access-control-expose-headers": "x-amz-version-id", "date": "Sat, 03 Apr 2021 19:40:27 GMT", "server": "hypercorn-h11"}, "RetryAttempts": 0}}
Verify error:
"stacktrace": "An error occurred (InvalidKeyUsageException) when calling the Verify operation: asn1: structure error: tags don't match (16 vs {class:1 tag:13 length:69 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} ecdsaSignature @2"
def _verify_signature(self, key_id, message, signature):
response = kms_client.verify(
KeyId=key_id,
Message=message,
MessageType="RAW",
Signature=signature,
SigningAlgorithm="ECDSA_SHA_256",
)
logger.log(msg=response, level=cl.log_level)
return response
client.verify requires signature to be bytes https://boto3.amazonaws.com/v1/documentation/api/1.11.4/reference/services/kms.html#KMS.Client.verify
If I leave off the .encode()
I get the same:
"stacktrace": "An error occurred (InvalidKeyUsageException) when calling the Verify operation: asn1: structure error: tags don't match (16 vs {class:1 tag:13 length:69 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} ecdsaSignature @2"
def _sign_token(self, key_id, message):
signature = kms_client.sign(
KeyId=key_id,
Message=message,
MessageType="RAW",
SigningAlgorithm="ECDSA_SHA_256",
)
logger.log(msg=signature, level=cl.log_level)
return base64.b64encode(signature["Signature"])
NOTE:
If I pass the client.sign response "Signature" to client.verify without base64 encoding, verification passes.
Upvotes: 1
Views: 1999
Reputation: 51
The AWS support process worked. It turned out I was doing a decode()
operation on the base64 encoded signature when attempting to verify instead of base64.b64decode()
Upvotes: 0
Reputation: 133929
The string encoded by AWS cli is not urlsafe-base64 encoded! It is encoded using the standard base64, i.e. base64.b64encode
.
Standard base64 encoding uses three metacharacters /
, +
and =
. /
and +
are not safe to use in either paths or query strings, and URL-safe encoding replaces these two with _
and -
respectively. Since /
and +
are present in the AWS CLI encoded string, it can be deduced that it uses standard base64 encoding and not the URL-safe one.
The return value of base64.b64encode
is bytes
object. Usually Base-64 encoding is used to encode binary into text. To get the value as text instead you need to decode
it (as UTF-8 into unicode string, for example):
base64_encoded_as_str = base64.b64encode(binary_value).decode()
Upvotes: 1