Search code examples
amazon-web-servicesamazon-iamaws-api-gateway

SignatureDoesNotMatch on AWS signed v4 PUT request presigned url


Currently, I have an issue by creating a valid signature v4 presigned url for a PUT request.

The urls are generated on the server side and are then provided to clients. The clients should use the urls to upload a file over an API Gateway into an Amazon S3 bucket. To authenticate the request API Gateway IAM authentication is used.

For my use case, a direct upload into an S3 bucket via "s3-presigned-url" is not possible.

The following code describes the generation of the presigned url and is written in Typescript. The generation of the signature v4 url is based on the AWS provided package @aws-sdk/signature-v4.

import { SignatureV4 } from "@aws-sdk/signature-v4";
import { Sha256 } from "@aws-crypto/sha256-js";
import { formatUrl } from "@aws-sdk/util-format-url";

const createSignedUrl = async (credentials: {
  accessKeyId: string,
  secretAccessKey: string,
  sessionToken: string,
}, requestParams: {
  method: "GET" | "PUT",
  host: string,
  protocol: string,
  path: string,
}) => {
  const sigv4 = new SignatureV4({
    service: "execute-api",
    region: process.env.AWS_REGION!,
    credentials: {
      accessKeyId: credentials.accessKeyId,
      secretAccessKey: credentials.secretAccessKey,
      sessionToken: credentials.sessionToken,
    },
    sha256: Sha256,
    applyChecksum: false
  });

  const signedUrlRequest = await sigv4.presign({
    method: requestParams.method,
    hostname: requestParams.host,
    path: requestParams.path,
    protocol: requestParams.protocol,
    headers: {
      host: requestParams.host,
    },
  }, {
    expiresIn: EXPIRES_IN,
  });

  const signedUrl = formatUrl(signedUrlRequest);
  return signedUrl
};

I use Postman to test the presinged urls.

If I generate a presigned url for an GET request, everything works fine.

If I generate a presigned url for an PUT request and don't set a body in Postman for the PUT request, everything works fine. But I have an empty file in my bucket ;-(.

If I generate a presigned url for an PUT request and set a body in Postman (via Body -> binary -> [select file]), it fails!

Error message:

The request signature we calculated does not match the signature you provided. ...

The AWS documentation https://docs.aws.amazon.com/general/latest/gr/create-signed-request.html describes that the payload has to be hashed within the canonical request. But I don't have the payload at that time.

Is there also an UNSIGNED-PAYLOAD option if I want to generate an presigned url for a PUT request that is sent to an API Gateway, like described in the documentation for the AWS S3 service? How do I configure the SignatureV4 object or the presign(...) method call to generate a valid PUT request url with UNSIGNED-PAYLOAD?


Solution

  • I was able to compare my generated canonical requests with the canonical request that is expected by the Amazon API Gateway. The Amazon API Gateway always expects a hash of the payload no matter if I add the query param X-Amz-Content-Sha256=UNSIGNED-PAYLOAD to the url or not. Thus the option "UNSIGNED-PAYLOAD" as canonical request hash value for API Gateway IAM Authentication is not possible, as would be possible with Amazon S3 service.