Search code examples
javascriptamazon-web-servicesamazon-s3aws-signature

Signature Does not Match AWS S3 using Javascript Scripting


I was trying to write a code in Js for GET request via REST API. I am trying to generate signature using the shared key and access key provided by me


 var https = require('https');
 var crypto = require('crypto');
 
 function sign(key, message) {
   return crypto.createHmac('sha256', key).update(message).digest();
 }
 
 function getSignatureKey(key, dateStamp, regionName, serviceName) {
   kDate = sign('AWS4' + key, dateStamp);
   kRegion = sign(kDate, regionName);
   kService = sign(kRegion, serviceName);
   kSigning = sign(kService, 'aws4_request');
   return kSigning;
 }
 
 // values retrieved from Cognito Federation
 accessKey = "MYACCESSKEY";
 secretKey = "my/sharedkey";
 
 region = "us-east-1";
 serviceName = "s3";
 
 // ex 20180518T210317Z
 var now = new Date();
 amzdate = now.toJSON().replace(/[-:]/g, "").replace(/\.[0-9]*/, "");
 datestamp = now.toJSON().replace(/-/g, "").replace(/T.*/, "");
 
 // prepare to send an HTTP request to https://your-api-gateway.execute-api.eu-west-2.amazonaws.com/stage/secure/endpoint
 apiMethod = "GET";
 apiHost = "my_host_name.com";
apiEndpoint="/bucket_name/object_name";

 apiQueryString = "";
 canonicalHeaders = "host:" + apiHost + "\nx-amz-date:" + amzdate +"\n";
   //"\nx-amz-security-token:" + sessionToken + "\n"
 signedHeaders = "host;x-amz-date;";
 payloadHash = crypto.createHash('sha256').update('').digest('hex');
 canonicalRequest = apiMethod + "\n" + apiEndpoint + "\n" + apiQueryString +
   "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
 console.log('preparing to invoke canonical request:');
 console.log(canonicalRequest);
 
 // ************* TASK 2: CREATE THE STRING TO SIGN*************
 // Match the algorithm to the hashing algorithm you use, either SHA-1 or
 // SHA-256 (recommended)
 algorithm = 'AWS4-HMAC-SHA256';
 credentialScope = datestamp + '/' + region + '/' + serviceName + '/' +
   'aws4_request';
 stringToSign = algorithm + '\n' + amzdate + '\n' + credentialScope + '\n' +
   crypto.createHash('sha256').update(canonicalRequest).digest('hex');
 
 // ************* TASK 3: CALCULATE THE SIGNATURE *************
 // Create the signing key using the function defined above.
 signingKey = getSignatureKey(secretKey, datestamp, region, serviceName);
 // Sign the string_to_sign using the signing_key
 signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest(
   'hex');
 
 
 // ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
 // The signing information can be either in a query string value or in
 // a header named Authorization. This code shows how to use a header.
 // Create authorization header and add to request headers
 authorizationHeader = algorithm + ' ' + 'Credential=' + accessKey + '/' +
   credentialScope + ', ' + 'SignedHeaders=' + signedHeaders + ', ' +
   'Signature=' + signature;
 
   process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0

 var options = {
   method: apiMethod,
   host: apiHost,
   path: apiEndpoint,
   headers: {
     'X-Amz-Date': amzdate,
     'Authorization': authorizationHeader
   }
 };
 
 callback = function(response) {
   var str = '';
 
   //another chunk of data has been recieved, so append it to `str`
   response.on('data', function(chunk) {
     str += chunk;
   });
   console.log("CALLBACK",str);

   //the whole response has been recieved, so we just print it out here
   response.on('end', function() {
     console.log('Complete: ' + str);
   });
 }
 console.log(options);
 https.request(options, callback).end();

I am getting signature mismatch error.

Complete: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>SignatureDoesNotMatch</Code><Resource>archivestorage/objectNameNotDecodedYet</Resource>
<Message>The request signature we calculated does not match the signature you provided. Check your secret access key and signing method.
</Message></Error>

Same header are passed via postman and it is working fine. see this Can somebody help me out where I am making mistake ? Thanks in advance


Solution

  • It got fixed.

    I missed "X-Amz-Content-Sha256" as header.

    Once I supplied "X-Amz-Content-Sha256": payloadHash and requested the data, It worked like a charm .

    Sharing the code. It will be the first working code that I found in internet.

    /** The following is a NodeJS port of the AWS SigV4 signing sample code (Python) to NodeJS
     *  The addition of the Authorization header has been informed by the use of Postman
     *  For more information see the following documentation:
     *  http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
     */
    
     var https = require('https');
     var crypto = require('crypto');
     
     function sign(key, message) {
       return crypto.createHmac('sha256', key).update(message).digest();
     }
     
     function getSignatureKey(key, dateStamp, regionName, serviceName) {
       kDate = sign('AWS4' + key, dateStamp);
       kRegion = sign(kDate, regionName);
       kService = sign(kRegion, serviceName);
       kSigning = sign(kService, 'aws4_request');
       return kSigning;
     }
     
     // values retrieved from Cognito Federation
     accessKey = "MYACCESSKEY";
     secretKey = "my/sharedkey";
    
     
     //sessionToken = "";
     
     region = "us-east-1";
     serviceName = "s3";
     
     // ex 20180518T210317Z
     var now = new Date();
     amzdate = now.toJSON().replace(/[-:]/g, "").replace(/\.[0-9]*/, "");
     datestamp = now.toJSON().replace(/-/g, "").replace(/T.*/, "");
     
     // prepare to send an HTTP request to https://your-api-gateway.execute-api.eu-west-2.amazonaws.com/stage/secure/endpoint
     apiMethod = "GET";
     apiHost = "my_host_name.com";
     apiEndpoint="/bucket_name/object_name";
    
     apiQueryString = "";
     canonicalHeaders = "host:" + apiHost + "\nx-amz-date:" + amzdate +"\n";
       //"\nx-amz-security-token:" + sessionToken + "\n"
     signedHeaders = "host;x-amz-date;";
     payloadHash = crypto.createHash('sha256').update('').digest('hex');
     canonicalRequest = apiMethod + "\n" + apiEndpoint + "\n" + apiQueryString +
       "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
     console.log('preparing to invoke canonical request:');
     console.log(canonicalRequest);
     
     // ************* TASK 2: CREATE THE STRING TO SIGN*************
     // Match the algorithm to the hashing algorithm you use, either SHA-1 or
     // SHA-256 (recommended)
     algorithm = 'AWS4-HMAC-SHA256';
     credentialScope = datestamp + '/' + region + '/' + serviceName + '/' +
       'aws4_request';
     stringToSign = algorithm + '\n' + amzdate + '\n' + credentialScope + '\n' +
       crypto.createHash('sha256').update(canonicalRequest).digest('hex');
     
     // ************* TASK 3: CALCULATE THE SIGNATURE *************
     // Create the signing key using the function defined above.
     signingKey = getSignatureKey(secretKey, datestamp, region, serviceName);
     //console.log("BEFORE SIGNATURE:",signingKey);
     // Sign the string_to_sign using the signing_key
     signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest(
       'hex');
       
       //console.log("ALGORITHM",algorithm);
       //console.log("SIGNATURE:",signature);
       //console.log("X-AMZ-DATE",amzdate);
       //console.log("X-AMZ-CRDENTIAL",Credential);
     
     // ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
     // The signing information can be either in a query string value or in
     // a header named Authorization. This code shows how to use a header.
     // Create authorization header and add to request headers
     authorizationHeader = algorithm + ' ' + 'Credential=' + accessKey + '/' +
       credentialScope + ', ' + 'SignedHeaders=' + signedHeaders + ', ' +
       'Signature=' + signature;
       //'5e838e45edf32f084705619603ba0acb77961658fedb9a570c771919c9dcf60a';
     
       //console.log("AUTH header ",authorizationHeader);
       process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0
    
     
     var options = {
       method: apiMethod,
       host: apiHost,
       path: apiEndpoint,
       headers: {
        "X-Amz-Content-Sha256": payloadHash,
        "X-Amz-Date": amzdate,
        "Authorization": authorizationHeader
       }
     };
     
     callback = function(response) {
       var str = '';
     
       //another chunk of data has been recieved, so append it to `str`
       response.on('data', function(chunk) {
         str += chunk;
       });
       console.log("CALLBACK",str);
    
       //the whole response has been recieved, so we just print it out here
       response.on('end', function() {
         console.log('Complete: ' + str);
       });
     }
     console.log(options);
     https.request(options, callback).end();