How to debug S3 presigned URL SignatureDoesNotMatch?

I’m getting a presigned URL back from localstack like

http://localhost:4566/foo-bar/0188fe44-e614-7f77-9bc6-6f69fbba27dc/May%2014th.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230627T200308Z&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost&X-Amz-Expires=900&X-Amz-Credential=test%2F20230627%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=7646dedd61e081b7ef564710f84ee68d0e66f98a7ff663b623cb351166a97541

When I try to use it, doing a PUT of the file I get a 403 SignatureDoesNotMatch.

The server code generating the pre-signed URL has these env vars set:

AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test
AWS_REGION=us-east-1
AWS_S3_ENDPOINT=http://localhost:4566

logs:

2023-06-27 16:22:31 2023-06-27T20:22:31.681 DEBUG --- [   asgi_gw_1] l.aws.serving.wsgi         : PUT localhost:4566/foo-bar/0188fe44-e614-7f77-9bc6-6f69fbba27dc/May%2014th.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230627T202218Z&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost&X-Amz-Expires=900&X-Amz-Credential=test%2F20230627%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=b5dd10bd85d0dc97cd9c5a02acd977ceeb409abc392f2cbf9e2f96430beaae5c
2023-06-27 16:22:31 2023-06-27T20:22:31.686 DEBUG --- [   asgi_gw_1] l.aws.protocol.serializer  : No accept header given. Using request's Content-Type (application/pdf) as preferred response Content-Type.
2023-06-27 16:22:31 2023-06-27T20:22:31.691  INFO --- [   asgi_gw_1] localstack.request.aws     : AWS s3.PutObject => 403 (SignatureDoesNotMatch); PutObjectRequest({'ACL': None, 'Bucket': 'wavebid-onboarding-licenses', 'CacheControl': 'no-cache', 'ContentDisposition': None, 'ContentEncoding': None, 'ContentLanguage': None, 'ContentLength': 27982, 'ContentMD5': None, 'ContentType': 'application/pdf', 'ChecksumAlgorithm': None, 'ChecksumCRC32': None, 'ChecksumCRC32C': None, 'ChecksumSHA1': None, 'ChecksumSHA256': None, 'Expires': None, 'GrantFullControl': None, 'GrantRead': None, 'GrantReadACP': None, 'GrantWriteACP': None, 'Key': '0188fe44-e614-7f77-9bc6-6f69fbba27dc/May 14th.pdf', 'Metadata': {}, 'ServerSideEncryption': None, 'StorageClass': None, 'WebsiteRedirectLocation': None, 'SSECustomerAlgorithm': None, 'SSECustomerKey': None, 'SSECustomerKeyMD5': None, 'SSEKMSKeyId': None, 'SSEKMSEncryptionContext': None, 'BucketKeyEnabled': None, 'RequestPayer': None, 'Tagging': None, 'ObjectLockMode': None, 'ObjectLockRetainUntilDate': None, 'ObjectLockLegalHoldStatus': None, 'ExpectedBucketOwner': None, 'Body': <_io.BytesIO object at 0x7fa99d9dba60>}, headers={'Cache-Control': 'no-cache', 'Postman-Token': '65ca5efd-2aa0-434c-8fc4-ebd2b455e8a1', 'Host': 'localhost:4566', 'Content-Length': '27982', 'Content-Type': 'application/pdf', 'x-localstack-tgt-api': 's3', 'Authorization': 'AWS4-HMAC-SHA256 Credential=injectedaccesskey/20160623/us-east-1/s3/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=1234', 'x-moto-account-id': '000000000000'}); SignatureDoesNotMatch(The request signature we calculated does not match the signature you provided. Check your key and signing method., headers={'Content-Type': 'application/xml', 'Content-Length': '2558', 'x-amz-request-id': '6beb3746-7084-4eba-85d3-6199262a1190', 'x-amz-id-2': 's9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=', 'Connection': 'close'})

Full response:

<?xml version='1.0' encoding='utf-8'?>
<Error>
    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
    <RequestId>854a1d0c-83a0-42e3-9b27-a0ae4323ac2c</RequestId>
    <AWSAccessKeyId>test</AWSAccessKeyId>
    <CanonicalRequest>PUT
/foo-bar/0188fe44-e614-7f77-9bc6-6f69fbba27dc/May%2014th.pdf
X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=test%2F20230627%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20230627T202532Z&amp;X-Amz-Expires=900&amp;X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost
content-length:27982
content-type:application/pdf
host:localhost:4566

content-length;content-type;host
UNSIGNED-PAYLOAD</CanonicalRequest>
    <CanonicalRequestBytes>5055 540A 2F77 6176 6562 6964 2D6F 6E62 6F61 7264 696E 672D 6C69 6365 6E73 6573 2F30 3138 3866 6534 342D 6536 3134 2D37 6637 372D 3962 6336 2D36 6636 3966 6262 6132 3764 632F 4D61 7925 3230 3134 7468 2E70 6466 0A58 2D41 6D7A 2D41 6C67 6F72 6974 686D 3D41 5753 342D 484D 4143 2D53 4841 3235 3626 582D 416D 7A2D 4372 6564 656E 7469 616C 3D74 6573 7425 3246 3230 3233 3036 3237 2532 4675 732D 6561 7374 2D31 2532 4673 3325 3246 6177 7334 5F72 6571 7565 7374 2658 2D41 6D7A 2D44 6174 653D 3230 3233 3036 3237 5432 3032 3533 325A 2658 2D41 6D7A 2D45 7870 6972 6573 3D39 3030 2658 2D41 6D7A 2D53 6967 6E65 6448 6561 6465 7273 3D63 6F6E 7465 6E74 2D6C 656E 6774 6825 3342 636F 6E74 656E 742D 7479 7065 2533 4268 6F73 740A 636F 6E74 656E 742D 6C65 6E67 7468 3A32 3739 3832 0A63 6F6E 7465 6E74 2D74 7970 653A 6170 706C 6963 6174 696F 6E2F 7064 660A 686F 7374 3A6C 6F63 616C 686F 7374 3A34 3536 360A 0A63 6F6E 7465 6E74 2D6C 656E 6774 683B 636F 6E74 656E 742D 7479 7065 3B68 6F73 740A 554E 5349 474E 4544 2D50 4159 4C4F 4144</CanonicalRequestBytes>
    <HostId>9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg=</HostId>
    <SignatureProvided>2d71f772a06265c8c0d15895ebadf1a0a3b8f8e42a01aa7b677651c44d581c35</SignatureProvided>
    <StringToSign>AWS4-HMAC-SHA256
20230627T202532Z
20230627/us-east-1/s3/aws4_request
b9a90dfe2cdbc8ff2cdb0d356c67553fafce996918ac2318c75e8ad82e6d826e</StringToSign>
    <StringToSignBytes>41 5753 342D 484D 4143 2D53 4841 3235 360A 3230 3233 3036 3237 5432 3032 3533 325A 0A32 3032 3330 3632 372F 7573 2D65 6173 742D 312F 7333 2F61 7773 345F 7265 7175 6573 740A 6239 6139 3064 6665 3263 6462 6338 6666 3263 6462 3064 3335 3663 3637 3535 3366 6166 6365 3939 3639 3138 6163 3233 3138 6337 3565 3861 6438 3265 3664 3832 3665</StringToSignBytes>
</Error>

Where can I start to figure this out? Thanks.

edit: tried Postman, Java (Kotlin) code and curl so far. Getting invalid signature for all of them.

curl -v --upload-file 'May 14th.pdf' 'pre-signed-url-goes-here'

edit2: I think there’s a timing/racing condition bug. Occasionally, my integration test passes.

edit3: Happens w/ 2.1, 2.0 and 1.4 docker images. The 1st upload (almost) always works, no uploads after that work.

Hi @efenderbosch

This looks like a bug in our signature validation system. Could you please file a bug report here with steps to reproduce?