aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
3.05k stars 573 forks source link

SignatureV4 calculates a different signature to AWS when there's parenthesis on the URL's path #5797

Closed pabloc-ba closed 6 months ago

pabloc-ba commented 7 months ago

Checkboxes for prior research

Describe the bug

My app calls a Lambda Function URL setup with IAM Auth. It signs the request using SignatureV4 lib, which works well pretty much all of the time, except when there is a parenthesis on the URL's path. AFAIK parenthesis are allowed on URLs, and they don't even need escaping. When I do this I get the typical:

The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

Most of the info online wasn't useful, as it assumes I must be doing something wrong with the keys or something else, but this works, it just doesn't handle parenthesis well or the same way as AWS does. This only seems to affect the path of the URL, as having parenthesis in the query string or the request body also works well, summarizing:

❌ PUT /files/hello(world).pdf {...}

✅ PUT /files?filename=hello(world).pdf {...}

✅ PUT /files/ {"filename": "hello(world).pdf"}

Here's some relevant parts of the code, in case it helps:

const { SignatureV4 } = require("@aws-sdk/signature-v4");
const { Sha256 } = require("@aws-crypto/sha256-js");
...
const service = target.AWSSigv4Service || "lambda";
const sigv4 = new SignatureV4({
    service: service,
    region: awsCreds.region || "us-east-1",
    credentials: awsCreds.credentials,
    sha256: Sha256,
});
...
const optionsToSign = {
    method: req.method || "GET",
    hostname: apiUrl.host,
    path: apiUrl.pathname,
    query: apiUrl.search ? Object.fromEntries(apiUrl.searchParams.entries()) : undefined,
    protocol: apiUrl.protocol,
    headers: {
        ...getHeaders(req.headers, stripHeadersFromRequest(service)),
        host: target.setHostHeader ? apiUrl.hostname : req.headers.host,
    },
    body: body ? body : undefined,
};
...
const signed = await sigv4.sign(optionsToSign);

SDK version number

@aws-crypto/sha256-js@4.0.0, @aws-sdk/signature-v4@3.374.0

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

"node -v": v18.19.0

Reproduction Steps

1) Call a Lambda Function URL, with IAM auth, with a parenthesis on the URL path

Observed Behavior

The request to AWS failed with 403 Forbidden and response:

{
  "message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
}

Expected Behavior

Expected it to work.

Possible Solution

-

Additional Information/Context

-

RanVaknin commented 7 months ago

Hi @pabloc-ba ,

You need to instruct the SDK to encode the path:

const sigv4 = new SignatureV4({
    service: service,
    region: awsCreds.region || "us-east-1",
    credentials: awsCreds.credentials,
    sha256: Sha256,
        uriEscapePath: true,
});

Thanks, Ran~

github-actions[bot] commented 6 months ago

This issue has not received a response in 1 week. If you still think there is a problem, please leave a comment to avoid the issue from automatically closing.

github-actions[bot] commented 6 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.