6

I am attempting to create a signed S3 URL using Javascript & NodeJS. I have used this specification.

var crypto     = require('crypto'),
    date       = 1331290899,
    resource   = '/myfile.txt',
    awskey     = "XXXX",
    awssecret  = "XXXX";

var stringToSign ='GET\n\n\n' + date + '\n\n' + resource;

var sig = encodeURIComponent(crypto.createHmac('sha1', awssecret).update(stringToSign ).digest('base64'));

var url = "https://s3-eu-west-1.amazonaws.com/mybucket" +
       resource + "?AWSAccessKeyId=" + awskey + "&Expires="+ date +  
      "&Signature="+ sig

This creates a url similar to this:

https://s3-eu-west-1.amazonaws.com/mybucket/test.txt?AWSAccessKeyId=XXXXXX&Expires=1331290899&Signature=EciGxdQ1uOqgFDCRon4vPqTiCLc%3D

However, I receive the following error when accessing it:

SignatureDoesNotMatch

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

What am I doing wrong when creating the signature?

EDIT - ATTEMPT WITH KNOX

I am now attempting to use Knox to produce a signed URL. I need to add headers with the request to force download. I have edited the following:

Added amazonHeaders: 'response-content-disposition:attachment', to client.signedUrl- http://jsfiddle.net/BpGNM/1/

Added options.amazonHeaders + '\n' + to auth.queryStringToSign - http://jsfiddle.net/6b8Tm/

The message that is now being sent to auth.hmacSha1 to create the the sig is:

'GET\n\n\n1321374212\nresponse-content-disposition:attachment\n/meshmesh-dev/test/Readme.md'

I have then tried to access my new URL with the response-content-disposition=attachment added as GET var. However, I am still receiving the same error stated above.

1
  • Having the same issue as you, was this ever solved? Commented Aug 6, 2014 at 0:39

3 Answers 3

8

I would try using Knox along with Node.Js . Its known to be a great combination and also itself utilizes the Node.JS Crypto library which is kind of what you're trying to do - saving you time:)

More info here : https://github.com/LearnBoost/knox

Than, you could just do something like:

var knox = require('knox');
var s3Client = knox.createClient({
    key: 'XXX',
    secret: 'XXX',
    bucket: 'XXX'
});

var expires = new Date();
expires.setMinutes(expires.getMinutes() + 30);
var url =  s3Client.signedUrl(filename, expires);

Edit: You could also look into Knox and just check what the signedUrl function does and implement that yourself.Than you could add to the auth.signQuery call an extra option called amazonHeaders:

Client.prototype.signedUrl = function(filename, expiration){
  var epoch = Math.floor(expiration.getTime()/1000);
  var signature = auth.signQuery({
    amazonHeaders: 'response-content-disposition:attachment',
    secret: this.secret,
    date: epoch,
    resource: '/' + this.bucket + url.parse(filename).pathname
  });

  return this.url(filename) +
    '?Expires=' + epoch +
    '&AWSAccessKeyId=' + this.key +
    '&Signature=' + encodeURIComponent(signature);
};

Shai.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks. I was using Knox, but I needed to send headers with the request (response-content-disposition':attachment) - so I tried to sign my own URLs. Any ideas how this is done with Knox?
I have updated my question above with the suggested edits to Knox. However, I have the same result.
I have to admit i'm kinda clueless from this point on :) Sorry i can't be of more assistance.
Did it work. I tried updating the knox client.js with amazonHeaders "response-content-disposition:attachment; filename=#{file._id}". But the response headers doesn't seem to have the content-disposition set
Current knox (since Mar 2013) supports a qs on the signedUrl options where you can put the response-content-disposition, no need to update the code. Use it like: var headers = { 'response-content-disposition': 'attachment;filename=test.pdf'};, var url = client.signedUrl('/test.pdf', expires, { qs: headers });
|
3

maybe one too many newlines?

var stringToSign ='GET\n\n\n' + date + '\n\n' + resource;

If its any help here is a rubbish PHP implementation which definitely works:

class myS3Helper{
public function getSignedImageLink($timeout = 1800)
    {

        $now = new Zend_Date(); //Gives us a time object that is set to NOW
        $now->setTimezone('UTC'); //Set to UTC a-la AWS requirements
        $now->addSecond($timeout);
        $expirationTime = $now->getTimestamp(); //returns unix timestamp representation of the time.

        $signature = urlencode(
                base64_encode(
                        hash_hmac(
                                'sha1', $this->_generateStringToSign($expirationTime),
                                $my_aws_secretkey, 
                                true
                                )
                        )
                );

        //FIXME make this less ugly when I know it works
        $url = 'https://';
        $url .= Zend_Service_Amazon_S3::S3_ENDPOINT; //e.g s3.amazonaws.com
        $url .= $this->_getImagePath(); //e.g /mybucket/myFirstCar.jpg
        $url .='?AWSAccessKeyId=' . $my_aws_key;
        $url .='&Signature=' . $signature; //signature as returned by below function
        $url .='&Expires=' . $expirationTime;

        return $url;


    }

    protected function _generateStringToSign($expires)
    {   

        $string = "GET\n"; //Methods
        $string .= "\n";
        $string .= "\n";
        $string .= "$expires\n"; //Expires
        $string .= $this->_getImagePath();

        return $string;
    }

}

EDIT--

Have a look at this node.js s3 upload code, (it's not mine but found it lying around on my mac - so if anyone can attribute it to someone let me know and i'll do the props). Hopefully this might help (3rd time lucky)

https://gist.github.com/1370593

4 Comments

thanks. I will try and figure out what the function above is doing right. However, even without the extra \n after the expiry date - the sig still fails.
Sorry its a bit obtuse and not in the right language for you but it's the first thing i had to hand - if you have any questions let me know.
One last shot... Should the bucket be in the resource .e.g /mybucket/test.txt not just /text.txt?
Unfortunately the same result.
0

My implementation using AWS-SDK and Rx.

import AWS from "aws-sdk"
import Rx from 'rx'

/*
* Credentials could be loaded from env variables
* http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
* */

const s3 = new AWS.S3({apiVersion: '2006-03-01'});

export function getS3SignedImage(objectKey) {
    return Rx.Observable.create(function (observer) {
        s3.getSignedUrl('getObject',{
            Bucket: process.env.AWS_BUCKET,
            Key: objectKey
        }, (err, data) => {
            if (err) {
                return observer.onError(err);
            }
            observer.onNext(data);
            observer.onCompleted();
        });
    });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.