signer.js (7217B)
1 var AWS = require('../core'), 2 url = AWS.util.url, 3 crypto = AWS.util.crypto.lib, 4 base64Encode = AWS.util.base64.encode, 5 inherit = AWS.util.inherit; 6 7 var queryEncode = function (string) { 8 var replacements = { 9 '+': '-', 10 '=': '_', 11 '/': '~' 12 }; 13 return string.replace(/[\+=\/]/g, function (match) { 14 return replacements[match]; 15 }); 16 }; 17 18 var signPolicy = function (policy, privateKey) { 19 var sign = crypto.createSign('RSA-SHA1'); 20 sign.write(policy); 21 return queryEncode(sign.sign(privateKey, 'base64')) 22 }; 23 24 var signWithCannedPolicy = function (url, expires, keyPairId, privateKey) { 25 var policy = JSON.stringify({ 26 Statement: [ 27 { 28 Resource: url, 29 Condition: { DateLessThan: { 'AWS:EpochTime': expires } } 30 } 31 ] 32 }); 33 34 return { 35 Expires: expires, 36 'Key-Pair-Id': keyPairId, 37 Signature: signPolicy(policy.toString(), privateKey) 38 }; 39 }; 40 41 var signWithCustomPolicy = function (policy, keyPairId, privateKey) { 42 policy = policy.replace(/\s/mg, policy); 43 44 return { 45 Policy: queryEncode(base64Encode(policy)), 46 'Key-Pair-Id': keyPairId, 47 Signature: signPolicy(policy, privateKey) 48 } 49 }; 50 51 var determineScheme = function (url) { 52 var parts = url.split('://'); 53 if (parts.length < 2) { 54 throw new Error('Invalid URL.'); 55 } 56 57 return parts[0].replace('*', ''); 58 }; 59 60 var getRtmpUrl = function (rtmpUrl) { 61 var parsed = url.parse(rtmpUrl); 62 return parsed.path.replace(/^\//, '') + (parsed.hash || ''); 63 }; 64 65 var getResource = function (url) { 66 switch (determineScheme(url)) { 67 case 'http': 68 case 'https': 69 return url; 70 case 'rtmp': 71 return getRtmpUrl(url); 72 default: 73 throw new Error('Invalid URI scheme. Scheme must be one of' 74 + ' http, https, or rtmp'); 75 } 76 }; 77 78 var handleError = function (err, callback) { 79 if (!callback || typeof callback !== 'function') { 80 throw err; 81 } 82 83 callback(err); 84 }; 85 86 var handleSuccess = function (result, callback) { 87 if (!callback || typeof callback !== 'function') { 88 return result; 89 } 90 91 callback(null, result); 92 }; 93 94 AWS.CloudFront.Signer = inherit({ 95 /** 96 * A signer object can be used to generate signed URLs and cookies for granting 97 * access to content on restricted CloudFront distributions. 98 * 99 * @see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html 100 * 101 * @param keyPairId [String] (Required) The ID of the CloudFront key pair 102 * being used. 103 * @param privateKey [String] (Required) A private key in RSA format. 104 */ 105 constructor: function Signer(keyPairId, privateKey) { 106 if (keyPairId === void 0 || privateKey === void 0) { 107 throw new Error('A key pair ID and private key are required'); 108 } 109 110 this.keyPairId = keyPairId; 111 this.privateKey = privateKey; 112 }, 113 114 /** 115 * Create a signed Amazon CloudFront Cookie. 116 * 117 * @param options [Object] The options to create a signed cookie. 118 * @option options url [String] The URL to which the signature will grant 119 * access. Required unless you pass in a full 120 * policy. 121 * @option options expires [Number] A Unix UTC timestamp indicating when the 122 * signature should expire. Required unless you 123 * pass in a full policy. 124 * @option options policy [String] A CloudFront JSON policy. Required unless 125 * you pass in a url and an expiry time. 126 * 127 * @param cb [Function] if a callback is provided, this function will 128 * pass the hash as the second parameter (after the error parameter) to 129 * the callback function. 130 * 131 * @return [Object] if called synchronously (with no callback), returns the 132 * signed cookie parameters. 133 * @return [null] nothing is returned if a callback is provided. 134 */ 135 getSignedCookie: function (options, cb) { 136 var signatureHash = 'policy' in options 137 ? signWithCustomPolicy(options.policy, this.keyPairId, this.privateKey) 138 : signWithCannedPolicy(options.url, options.expires, this.keyPairId, this.privateKey); 139 140 var cookieHash = {}; 141 for (var key in signatureHash) { 142 if (Object.prototype.hasOwnProperty.call(signatureHash, key)) { 143 cookieHash['CloudFront-' + key] = signatureHash[key]; 144 } 145 } 146 147 return handleSuccess(cookieHash, cb); 148 }, 149 150 /** 151 * Create a signed Amazon CloudFront URL. 152 * 153 * Keep in mind that URLs meant for use in media/flash players may have 154 * different requirements for URL formats (e.g. some require that the 155 * extension be removed, some require the file name to be prefixed 156 * - mp4:<path>, some require you to add "/cfx/st" into your URL). 157 * 158 * @param options [Object] The options to create a signed URL. 159 * @option options url [String] The URL to which the signature will grant 160 * access. Required. 161 * @option options expires [Number] A Unix UTC timestamp indicating when the 162 * signature should expire. Required unless you 163 * pass in a full policy. 164 * @option options policy [String] A CloudFront JSON policy. Required unless 165 * you pass in a url and an expiry time. 166 * 167 * @param cb [Function] if a callback is provided, this function will 168 * pass the URL as the second parameter (after the error parameter) to 169 * the callback function. 170 * 171 * @return [String] if called synchronously (with no callback), returns the 172 * signed URL. 173 * @return [null] nothing is returned if a callback is provided. 174 */ 175 getSignedUrl: function (options, cb) { 176 try { 177 var resource = getResource(options.url); 178 } catch (err) { 179 return handleError(err, cb); 180 } 181 182 var parsedUrl = url.parse(options.url, true), 183 signatureHash = Object.prototype.hasOwnProperty.call(options, 'policy') 184 ? signWithCustomPolicy(options.policy, this.keyPairId, this.privateKey) 185 : signWithCannedPolicy(resource, options.expires, this.keyPairId, this.privateKey); 186 187 parsedUrl.search = null; 188 for (var key in signatureHash) { 189 if (Object.prototype.hasOwnProperty.call(signatureHash, key)) { 190 parsedUrl.query[key] = signatureHash[key]; 191 } 192 } 193 194 try { 195 var signedUrl = determineScheme(options.url) === 'rtmp' 196 ? getRtmpUrl(url.format(parsedUrl)) 197 : url.format(parsedUrl); 198 } catch (err) { 199 return handleError(err, cb); 200 } 201 202 return handleSuccess(signedUrl, cb); 203 } 204 }); 205 206 module.exports = AWS.CloudFront.Signer;