Closed tonyipm closed 1 month ago
That's interesting that some apps escape the paths client side and others don't. There is an option in go fiber to unescape paths, but we might have the reverse issue with this if the client is calculating the signature based on the escaped path:
// When set to true, converts all encoded characters in the route back
// before setting the path for the context, so that the routing,
// the returning of the current url from the context `ctx.Path()`
// and the parameters `ctx.Params(%key%)` with decoded characters will work
//
// Default: false
UnescapePath [bool](https://pkg.go.dev/builtin#bool) `json:"unescape_path"`
Looks like we will need to do some investigation on how to handle all cases of client sending escaped and non-escaped paths.
Hi Ben thanks for looking at this. From a purely cryptographic standpoint, the purpose of the hashing is to verify that nothing has been altered in the URL during its transmission across the network. To this end the client should be calculating its hash on exactly the string it is sending and not alter anything afterwards. Then the server end should calculate the hash of exactly the string received before altering it in any way. If this protocol is followed, then escaping/not escaping becomes irrelevant as all that happens 'outside' of the 'hash window'. The issue here as I see it is that the server end is modifying the incoming message and then calculating a hash on a modified string, which will always fail.
Hi Ben thanks for looking at this. From a purely cryptographic standpoint, the purpose of the hashing is to verify that nothing has been altered in the URL during its transmission across the network. To this end the client should be calculating its hash on exactly the string it is sending and not alter anything afterwards. Then the server end should calculate the hash of exactly the string received before altering it in any way. If this protocol is followed, then escaping/not escaping becomes irrelevant as all that happens 'outside' of the 'hash window'. The issue here as I see it is that the server end is modifying the incoming message and then calculating a hash on a modified string, which will always fail.
Thats a good point. I think we do have access to the raw URL. We should probably be using that for the signature check rather than the parsed URL that fiber might have messed with.
After some investigation, it looks like this is possibly an error in the cpp sdk. I was able to build the test and run that against the gateway to reproduce the error. However, when I run wireshark in front of the gateway, I see the incoming request with the "test=test" instead of the escaped path "test%3Dtest". But enabling debug logs on the cpp app side, I can see that it's calculating the request based on the escaped url.
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Calculated sha256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 for payload.
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Canonical Header String: amz-sdk-invocation-id:4B1B8404-2311-4D62-A2DC-1606144B2778
amz-sdk-request:attempt=1
content-length:0
content-md5:1B2M2Y8AsgTpgAmY7PhCfg==
content-type:binary/octet-stream
host:192.168.136.129:7070
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20240907T225134Z
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Signed Headers value:amz-sdk-invocation-id;amz-sdk-request;content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Canonical Request String: PUT
/test/test%3Dtest
amz-sdk-invocation-id:4B1B8404-2311-4D62-A2DC-1606144B2778
amz-sdk-request:attempt=1
content-length:0
content-md5:1B2M2Y8AsgTpgAmY7PhCfg==
content-type:binary/octet-stream
host:192.168.136.129:7070
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20240907T225134Z
amz-sdk-invocation-id;amz-sdk-request;content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Final String to sign: AWS4-HMAC-SHA256
20240907T225134Z
20240907/us-east-1/s3/aws4_request
31d8ed9cf1f5b58ebc6c02656832a1fa20829ebe6cdb11fe4677f86d38aedda6
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Final computed signing hash: 9d0a61fa645e031b27788bfc68373d8ea7cc3bf6398a245674e84e8d5d9827dc
[DEBUG] 2024-09-07 22:51:34.744 AWSAuthV4Signer [140646209770240] Signing request with: AWS4-HMAC-SHA256 Credential=test/20240907/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date, Signature=9d0a61fa645e031b27788bfc68373d8ea7cc3bf6398a245674e84e8d5d9827dc
[DEBUG] 2024-09-07 22:51:34.744 AWSClient [140646209770240] Request Successfully signed
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] Making request to http://192.168.136.129:7070/test/test=test
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] Including headers:
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] amz-sdk-invocation-id: 4B1B8404-2311-4D62-A2DC-1606144B2778
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] amz-sdk-request: attempt=1
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] authorization: AWS4-HMAC-SHA256 Credential=test/20240907/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date, Signature=9d0a61fa645e031b27788bfc68373d8ea7cc3bf6398a245674e84e8d5d9827dc
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] content-length: 0
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] content-md5: 1B2M2Y8AsgTpgAmY7PhCfg==
[TRACE] 2024-09-07 22:51:34.745 CurlHttpClient [140646209770240] content-type: binary/octet-stream
[TRACE] 2024-09-07 22:51:34.745 CurlHttpClient [140646209770240] host: 192.168.136.129:7070
[TRACE] 2024-09-07 22:51:34.745 CurlHttpClient [140646209770240] user-agent: aws-sdk-cpp/1.11.400 ua/2.0 md/aws-crt#0.28.2 os/Linux/4.18.0-553.16.1.el8_10.x86_64 md/arch#x86_64 lang/c++#C++11 md/GCC#8.5.0 api/S3
[TRACE] 2024-09-07 22:51:34.745 CurlHttpClient [140646209770240] x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
[TRACE] 2024-09-07 22:51:34.745 CurlHttpClient [140646209770240] x-amz-date: 20240907T225134Z
In the above debug output, I see the following in the Canonical Request String:
/test/test%3Dtest
But then we see the curl request being made is using the unescaped path:
[TRACE] 2024-09-07 22:51:34.744 CurlHttpClient [140646209770240] Making request to http://192.168.136.129:7070/test/test=test
I see the same pattern when making a (successful) request to AWS. I'm assuming that AWS might be falling back to trying the escaped path if the raw one fails?
To enable logging in the test example, I added the following:
#include <aws/core/utils/logging/ConsoleLogSystem.h>
using namespace Aws::Utils::Logging;
Aws::SDKOptions options;
options.loggingOptions.logLevel = LogLevel::Debug;
options.loggingOptions.logger_create_fn = [] { return std::make_shared<ConsoleLogSystem>(LogLevel::Trace); };
Ok, further investigation into AWS signing doc: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html shows that we do need to run UriEncode() on resource. So the cpp sdk is doing the right thing after all with the ways its calculating the signature.
Thanks again Ben!
Describe the bug
Error: putObject: The request signature we calculated does not match the signature you provided. Check your key and signing method.
To Reproduce Unfortunately this requires the aws s3 SDK for C++ and the example programs. https://aws.amazon.com/sdk-for-cpp/ git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp git clone https://github.com/awsdocs/aws-doc-sdk-examples.git
Having built the example programs, you should find a 'run_put_object' binary, this takes a filename and bucket name as parameters. If the filename contains an equals sign, such as 'one=theother' then when running the example program against aws s3, the file is correctly uploaded and is displayed with equals sign in aws s3 ls. However Versity returns the error shown above. If you don't want to build the examples yourself, I can provide the binary compiled for linux x64.
I did a little digging, and it would appear that versity is calculating a cryptographic hash on the incoming URL and comparing to a hash previously calculated and sent across by the aws sdk. However versity seems to be working with a version of the URL that has '=' escaped out to %253D, I am guessing this is done by the fibre front end? In any case it means that the calculated hash does not match the one generated by the SDK at source (using non-escaped '=') and so the request is rejected immediately.
Expected behavior Same behaviour as s3, ie. aws cpp sdk put-object should be able to upload a filename containing '='. Interestingly, filenames containing '*' and '<' appear to work OK. Also, this works OK with '=' using the aws cli, which appears to do its own escaping of '=' internally, unlike the cpp sdk which sends it as a raw character.
Server Version Built from git clone on 2-sep-24
Additional context AWS sdk and example programs cloned on 2-sep-24