Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating signed URLs for Google Cloud Storage using NodeJS

I'm trying to create a signature for a privately stored file in Google Cloud Storage; so that I can distribute a time-limited link.

Currently doing this and it makes a signature that's too short ... where am I going wrong?

var crypto = require("crypto");

var ttl = new Date().getTime() + 3600;
var id = 'the_target_file.txt';
var bucketName = 'bucket_name';
var POLICY_JSON = "GET\n" + "\n" + "\n" + ttl + "\n" + '/' + bucketName + '/' + id;

// stringify and encode the policy
var stringPolicy = JSON.stringify(POLICY_JSON);
var base64Policy = Buffer(stringPolicy, "utf-8").toString("base64");

// sign the base64 encoded policy
var privateKey = "MY_PRIVATE_KEY";
var sha256 = crypto.createHmac("sha256", privateKey);
var signature = sha256.update(new Buffer(base64Policy, "utf-8")).digest("base64");

console.log ( signature );
like image 627
stukennedy Avatar asked Dec 24 '13 03:12

stukennedy


People also ask

How do I get signed URL for Google Cloud Storage?

Options for generating a signed URL Simply specify Cloud Storage resources, point to the host storage.googleapis.com , and use Google HMAC credentials in the process of generating the signed URL.

How do I create a signed URL?

To create a valid pre-signed URL for your object, you must provide your security credentials, specify a bucket name, an object key, specify the HTTP method (for instance the method is "GET" to download the object) and expiration date and time. Anyone who receives the pre-signed URL can then access the object.


2 Answers

There is an API/module for getting signed URLs now.

module: https://www.npmjs.com/package/@google-cloud/storage
API docs: https://googleapis.dev/nodejs/storage/latest/File.html#getSignedUrl

Example

var storage = require('@google-cloud/storage')();
var myBucket = storage.bucket('my-bucket');

var file = myBucket.file('my-file');

//-
// Generate a URL that allows temporary access to download your file.
//-
var request = require('request');

var config = {
  action: 'read',
  expires: '03-17-2025'  // this could also include time (MM-DD-YYYYTHH:MM:SSZ)
};

file.getSignedUrl(config, function(err, url) {
  if (err) {
    console.error(err);
    return;
  }

  // The file is now available to read from this URL.
  request(url, function(err, resp) {
    // resp.statusCode = 200
  });
});
like image 117
Philipp Kyeck Avatar answered Sep 19 '22 01:09

Philipp Kyeck


Realised what I was doing wrong ... I was hashing the policy string instead of signing it. The below code now gives me the correct output.

var crypto = require("crypto");
var fs = require("fs");

var expiry = new Date().getTime() + 3600;
var key = 'the_target_file';
var bucketName = 'bucket_name';
var accessId = 'my_access_id';
var stringPolicy = "GET\n" + "\n" + "\n" + expiry + "\n" + '/' + bucketName + '/' + key; 
var privateKey = fs.readFileSync("gcs.pem","utf8");
var signature = encodeURIComponent(crypto.createSign('sha256').update(stringPolicy).sign(privateKey,"base64"));   
var signedUrl = "https://" + bucketName + ".commondatastorage.googleapis.com/" + key +"?GoogleAccessId=" + accessId + "&Expires=" + expiry + "&Signature=" + signature;

console.log(signedUrl);

For completeness ... here is a PHP version that does the same thing, which I used to check my results

$expiry = time() + 3600;
$key = 'the_target_file';
$bucketName = 'bucket_name';
$accessId = 'my_access_id';
$stringPolicy = "GET\n\n\n".$expiry."\n/".$bucketName."/".$key;
$fp = fopen('gcs.pem', 'r');
$priv_key = fread($fp, 8192);
fclose($fp);
$pkeyid = openssl_get_privatekey($priv_key,"password"); 
if (openssl_sign( $stringPolicy, $signature, $pkeyid, 'sha256' )) {
    $signature = urlencode( base64_encode( $signature ) );    
    echo 'https://'.$bucketName.'.commondatastorage.googleapis.com/'.
              $key.'?GoogleAccessId='.$accessId.'&Expires='.$expiry.'&Signature='.$signature;
}
like image 25
stukennedy Avatar answered Sep 21 '22 01:09

stukennedy