mhart / aws4

Signs and prepares Node.js requests using AWS Signature Version 4
MIT License
704 stars 176 forks source link

The request signature we calculated does not match the signature you provided. Check your key and signing method. #76

Closed chasen-bettinger closed 6 years ago

chasen-bettinger commented 6 years ago
opts= {
 hostname: @config.bucket + '.s3.amazonaws.com'
 method: 'POST'
 service: 's3'
 path: "/#{params.filename}"
 region: @config.region
 headers:
    'Content-Type': params.contentType#
 body: params.file
 signQuery: true
}

aws4.sign opts, aws_credentials
return opts

and later when I make the network request... Using the request-promise library.

options=
 method: opts.method
 headers: opts.headers
 uri: 'https://' + opts.hostname + opts.path
 body: opts.body

request options, (res)->
// Error here

The request signature we calculated does not match the signature you provided. Check your key and signing method.

mhart commented 6 years ago

I'm not sure what language that is? CoffeeScript?

Can you please post a better reproduction with actual values (including the structure of aws_credentials, although just use dummy variables for your credentials)

chasen-bettinger commented 6 years ago

I understand why I was getting that error. I have since changed things around so the code underneath this text will be different from the one above. However, I am receiving another error now: 405 This method is not allowed on this resource.

Let me explain in detail what I am doing. I am making a proxy call first to receive all of the information necessary to upload to S3. In that proxy call, I am calling aws4.sign with these options:

params.file is:

 fd = new FormData
fd.append 'file', file[0]

params.contentType is:

'application/pdf'
 var opts = {
        host: "https://s3.amazonaws.com/bucket-name/" + params.filename,
        method: 'POST',
        service: 's3',
        region: 'us-east-1',
        headers: {
          'Content-Type': params.contentType
        },
        body: params.file
      };
      aws_credentials = {
        secretAccessKey: 'DUMMY_KEY',
        accessKeyId: 'DUMMY_KEY'
      };
      aws4.sign(opts, aws_credentials);
     return opts

Then I return those values to the client. When the user does want to upload them to S3, that is when I make another server call. Again, I am using request-promise because I need the request to be promised.

 opts = {
        uri: host,
        method: method,
        headers: [
          {
            Authorization: headers.Authorization,
            'Content-Length': headers['Content-Length'],
            'Content-Type': headers['Content-Type'],
            Host: headers.Host,
            'X-Amz-Content-Sha256': headers['X-Amz-Content-Sha256'],
            'X-Amz-Date': headers['X-Amz-Date']
          }
        ],
        body: body
      };

These values are the same as the values from above.

Then, I do: request(opts, function(response) { ..... }) and receive 405 This method is not allowed on this resource.. I believe that this is an issue with my bucket policy. However, I have access to POST objects to an S3 bucket and CORS allows POST.

inakiarroyo commented 6 years ago

I am having the same issue, this is my configuration:

 Auth.currentCredentials().then((creds: AmplifyUserCredentials) => {

      const credentials = Auth.essentialCredentials(creds);

      const request = {
        host: 'myapi.com/',
        service: 'execute-api',
        baseURL: 'http://'myapi.com/',
        region: 'eu-west-1',
        method: 'GET',
        path: '/',
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Credentials': true,
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        }
      };

      const signedRequest = sign(request,
        {
          secretAccessKey: credentials.secretAccessKey,
          accessKeyId: credentials.accessKeyId,
          sessionToken: credentials.sessionToken
        }
      );

      delete signedRequest.headers['Host'];
      // delete signedRequest.headers['Content-Length']; (Update: removed)

      Axios.get('helloworld/', signedRequest)
      .then((data) => {
        console.log(data);
      })
      .catch((error) => {
        console.log(error);
      });
});

The current Headers, their configuration and values seem alright, I've tested so many different ways, like leaving just the method, host, service and path into the request, removing the headers, changing the CORS value, but the never seems to work. The aws4 generated Signature is different than the one aws generates or expects.

{"data": "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."}
chasen-bettinger commented 6 years ago

@iarroyo5 In your case, I believe it is because you are deleting two properties from your signed request. What I have learned is that when you sign the request, you must act as if the data is immutable. If you change any one piece of that data that you have signed, you will receive that error.

inakiarroyo commented 6 years ago

Hi @chasen-bettinger, thanks for your fast reply, but, that was my first thought. I can't leave Host into the headers as the browser refuse it with this error on the console:

xhr.js?b50d:126 Refused to set unsafe header "Host"

and even in this case (with no Host), the response is still responding with 403 status and with this message of error:

{"data": "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".

Results Credential is generated 👍 SignedHeaders is generated 👍 Signature is generated 👎 (the hash is different than aws is returning on the 403 response)


Posible response: Don't think this is the problem as checking the source code of Amplify it also removes the Host header https://github.com/aws-amplify/amplify-js/blob/master/packages/api/src/RestClient.ts#L262


Update

inakiarroyo commented 6 years ago

After a long process of debugging and comparing the input and the output of the @aws-amplify/core/Signer class, I've made it work with this specific configuration on the request object:

const request = {
  method: 'GET',
  url: 'https://myapi.com/helloworld/',
  host: 'myapi.com',
  path: '/helloworld/',
  service: 'execute-api',
  region: 'eu-west-1',
  data: null
};

For everyone with the same kind of problem like I had, make sure you are adding the the trailing / on the path, on url and not on the host

mhart commented 6 years ago

@iarroyo5 aws4 doesn't support the url or data properties here – you can safely leave them out

mhart commented 6 years ago

(and method is GET by default)

inakiarroyo commented 6 years ago

I've just checked without those three keys and it also works.

const request = {
  host: 'api.mylivepigeon.com',
  path: '/helloworld/',
  service: 'execute-api',
  region: 'eu-west-1'
};
mhart commented 6 years ago

Closing this out – not an issue with aws4

simsketch commented 4 years ago

In my case I was using s3.getSignedUrl('getObject') when I needed to be using s3.getSignedUrl('putObject'), which is why the signatures didn't match.