Skip to content

Commit 0ac8957

Browse files
author
RTLcoil
authored
Add SHA-256 support for auth signatures (#479)
1 parent 5629a82 commit 0ac8957

File tree

6 files changed

+130
-32
lines changed

6 files changed

+130
-32
lines changed

lib-es5/utils/consts.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ var LAYER_KEYWORD_PARAMS = {
7070

7171
var UPLOAD_PREFIX = "https://api.cloudinary.com";
7272

73+
var SUPPORTED_SIGNATURE_ALGORITHMS = ["sha1", "sha256"];
74+
var DEFAULT_SIGNATURE_ALGORITHM = "sha1";
75+
7376
module.exports = {
7477
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION,
7578
DEFAULT_POSTER_OPTIONS,
@@ -79,5 +82,7 @@ module.exports = {
7982
LAYER_KEYWORD_PARAMS,
8083
TRANSFORMATION_PARAMS,
8184
SIMPLE_PARAMS,
82-
UPLOAD_PREFIX
85+
UPLOAD_PREFIX,
86+
SUPPORTED_SIGNATURE_ALGORITHMS,
87+
DEFAULT_SIGNATURE_ALGORITHM
8388
};

lib-es5/utils/index.js

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ var _require2 = require('./consts'),
9999
LAYER_KEYWORD_PARAMS = _require2.LAYER_KEYWORD_PARAMS,
100100
TRANSFORMATION_PARAMS = _require2.TRANSFORMATION_PARAMS,
101101
SIMPLE_PARAMS = _require2.SIMPLE_PARAMS,
102-
UPLOAD_PREFIX = _require2.UPLOAD_PREFIX;
102+
UPLOAD_PREFIX = _require2.UPLOAD_PREFIX,
103+
SUPPORTED_SIGNATURE_ALGORITHMS = _require2.SUPPORTED_SIGNATURE_ALGORITHMS,
104+
DEFAULT_SIGNATURE_ALGORITHM = _require2.DEFAULT_SIGNATURE_ALGORITHM;
103105

104106
function textStyle(layer) {
105107
var keywords = [];
@@ -757,6 +759,10 @@ function url(public_id) {
757759
var api_secret = consumeOption(options, "api_secret", config().api_secret);
758760
var url_suffix = consumeOption(options, "url_suffix");
759761
var use_root_path = consumeOption(options, "use_root_path", config().use_root_path);
762+
var signature_algorithm = consumeOption(options, "signature_algorithm", config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM);
763+
if (long_url_signature) {
764+
signature_algorithm = 'sha256';
765+
}
760766
var auth_token = consumeOption(options, "auth_token");
761767
if (auth_token !== false) {
762768
auth_token = exports.merge(config().auth_token, auth_token);
@@ -812,9 +818,8 @@ function url(public_id) {
812818
}
813819
// eslint-disable-next-line no-empty
814820
} catch (error) {}
815-
var shasum = crypto.createHash(long_url_signature ? 'sha256' : 'sha1');
816-
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
817-
signature = shasum.digest('base64').replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
821+
var hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64');
822+
signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
818823
signature = `s--${signature}--`;
819824
}
820825
var prefix = unsigned_url_prefix(public_id, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution);
@@ -1009,9 +1014,24 @@ function api_sign_request(params_to_sign, api_secret) {
10091014

10101015
return `${k}=${toArray(v).join(",")}`;
10111016
}).sort().join("&");
1012-
var shasum = crypto.createHash('sha1');
1013-
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
1014-
return shasum.digest('hex');
1017+
return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex');
1018+
}
1019+
1020+
/**
1021+
* Computes hash from input string using specified algorithm.
1022+
* @private
1023+
* @param {string} input string which to compute hash from
1024+
* @param {string} signature_algorithm algorithm to use for computing hash
1025+
* @param {string} encoding type of encoding
1026+
* @return {string} computed hash value
1027+
*/
1028+
function computeHash(input, signature_algorithm, encoding) {
1029+
if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) {
1030+
throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`);
1031+
}
1032+
var hash = crypto.createHash(signature_algorithm);
1033+
hash.update(utf8_encode(input), 'binary');
1034+
return hash.digest(encoding);
10151035
}
10161036

10171037
function clear_blank(hash) {
@@ -1053,9 +1073,8 @@ function webhook_signature(data, timestamp) {
10531073
ensurePresenceOf({ data, timestamp });
10541074

10551075
var api_secret = ensureOption(options, 'api_secret');
1056-
var shasum = crypto.createHash('sha1');
1057-
shasum.update(data + timestamp + api_secret, 'binary');
1058-
return shasum.digest('hex');
1076+
var signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM);
1077+
return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex');
10591078
}
10601079

10611080
/**
@@ -1075,7 +1094,10 @@ function verifyNotificationSignature(body, timestamp, signature) {
10751094
if (timestamp < Date.now() - valid_for) {
10761095
return false;
10771096
}
1078-
var payload_hash = utils.webhook_signature(body, timestamp, { api_secret: config().api_secret });
1097+
var payload_hash = utils.webhook_signature(body, timestamp, {
1098+
api_secret: config().api_secret,
1099+
signature_algorithm: config().signature_algorithm
1100+
});
10791101
return signature === payload_hash;
10801102
}
10811103

lib/utils/consts.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ const LAYER_KEYWORD_PARAMS = {
131131

132132
const UPLOAD_PREFIX = "https://api.cloudinary.com";
133133

134+
const SUPPORTED_SIGNATURE_ALGORITHMS = ["sha1", "sha256"];
135+
const DEFAULT_SIGNATURE_ALGORITHM = "sha1";
136+
134137
module.exports = {
135138
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION,
136139
DEFAULT_POSTER_OPTIONS,
@@ -140,5 +143,7 @@ module.exports = {
140143
LAYER_KEYWORD_PARAMS,
141144
TRANSFORMATION_PARAMS,
142145
SIMPLE_PARAMS,
143-
UPLOAD_PREFIX
146+
UPLOAD_PREFIX,
147+
SUPPORTED_SIGNATURE_ALGORITHMS,
148+
DEFAULT_SIGNATURE_ALGORITHM
144149
};

lib/utils/index.js

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ const {
8585
LAYER_KEYWORD_PARAMS,
8686
TRANSFORMATION_PARAMS,
8787
SIMPLE_PARAMS,
88-
UPLOAD_PREFIX
88+
UPLOAD_PREFIX,
89+
SUPPORTED_SIGNATURE_ALGORITHMS,
90+
DEFAULT_SIGNATURE_ALGORITHM
8991
} = require('./consts');
9092

9193
function textStyle(layer) {
@@ -692,6 +694,10 @@ function url(public_id, options = {}) {
692694
let api_secret = consumeOption(options, "api_secret", config().api_secret);
693695
let url_suffix = consumeOption(options, "url_suffix");
694696
let use_root_path = consumeOption(options, "use_root_path", config().use_root_path);
697+
let signature_algorithm = consumeOption(options, "signature_algorithm", config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM);
698+
if (long_url_signature) {
699+
signature_algorithm = 'sha256';
700+
}
695701
let auth_token = consumeOption(options, "auth_token");
696702
if (auth_token !== false) {
697703
auth_token = exports.merge(config().auth_token, auth_token);
@@ -735,9 +741,8 @@ function url(public_id, options = {}) {
735741
// eslint-disable-next-line no-empty
736742
} catch (error) {
737743
}
738-
let shasum = crypto.createHash(long_url_signature ? 'sha256' : 'sha1');
739-
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
740-
signature = shasum.digest('base64').replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
744+
let hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64');
745+
signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
741746
signature = `s--${signature}--`;
742747
}
743748
let prefix = unsigned_url_prefix(
@@ -934,9 +939,24 @@ function api_sign_request(params_to_sign, api_secret) {
934939
).map(
935940
([k, v]) => `${k}=${toArray(v).join(",")}`
936941
).sort().join("&");
937-
let shasum = crypto.createHash('sha1');
938-
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
939-
return shasum.digest('hex');
942+
return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex');
943+
}
944+
945+
/**
946+
* Computes hash from input string using specified algorithm.
947+
* @private
948+
* @param {string} input string which to compute hash from
949+
* @param {string} signature_algorithm algorithm to use for computing hash
950+
* @param {string} encoding type of encoding
951+
* @return {string} computed hash value
952+
*/
953+
function computeHash(input, signature_algorithm, encoding) {
954+
if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) {
955+
throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`);
956+
}
957+
let hash = crypto.createHash(signature_algorithm);
958+
hash.update(utf8_encode(input), 'binary');
959+
return hash.digest(encoding);
940960
}
941961

942962
function clear_blank(hash) {
@@ -966,9 +986,8 @@ function webhook_signature(data, timestamp, options = {}) {
966986
ensurePresenceOf({ data, timestamp });
967987

968988
let api_secret = ensureOption(options, 'api_secret');
969-
let shasum = crypto.createHash('sha1');
970-
shasum.update(data + timestamp + api_secret, 'binary');
971-
return shasum.digest('hex');
989+
let signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM);
990+
return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex');
972991
}
973992

974993
/**
@@ -986,7 +1005,10 @@ function verifyNotificationSignature(body, timestamp, signature, valid_for = 720
9861005
if (timestamp < Date.now() - valid_for) {
9871006
return false;
9881007
}
989-
const payload_hash = utils.webhook_signature(body, timestamp, { api_secret: config().api_secret });
1008+
const payload_hash = utils.webhook_signature(body, timestamp, {
1009+
api_secret: config().api_secret,
1010+
signature_algorithm: config().signature_algorithm
1011+
});
9901012
return signature === payload_hash;
9911013
}
9921014

test/unit/cloudinary_spec.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ describe("cloudinary", function () {
77
cloud_name: "test123",
88
api_key: 'a',
99
api_secret: 'b',
10-
responsive_width_transformation: null
10+
responsive_width_transformation: null,
11+
signature_algorithm: 'sha1'
1112
}));
1213
});
1314
it("should use cloud_name from config", function () {
@@ -471,6 +472,30 @@ describe("cloudinary", function () {
471472
undef: void 0
472473
}, "1234")).to.eql("f05cfe85cee78e7e997b3c7da47ba212dcbf1ea5");
473474
});
475+
it("should correctly sign api requests with signature algorithm SHA1", function () {
476+
cloudinary.config({ signature_algorithm: 'sha1' });
477+
expect(cloudinary.utils.api_sign_request({
478+
username: "[email protected]",
479+
timestamp: 1568810420,
480+
cloud_name: "dn6ot3ged"
481+
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("14c00ba6d0dfdedbc86b316847d95b9e6cd46d94");
482+
});
483+
it("should correctly sign api requests with signature algorithm SHA1 as default", function () {
484+
cloudinary.config({ signature_algorithm: null });
485+
expect(cloudinary.utils.api_sign_request({
486+
username: "[email protected]",
487+
timestamp: 1568810420,
488+
cloud_name: "dn6ot3ged"
489+
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("14c00ba6d0dfdedbc86b316847d95b9e6cd46d94");
490+
});
491+
it("should correctly sign api requests with signature algorithm SHA256", function () {
492+
cloudinary.config({ signature_algorithm: 'sha256' });
493+
expect(cloudinary.utils.api_sign_request({
494+
username: "[email protected]",
495+
timestamp: 1568810420,
496+
cloud_name: "dn6ot3ged"
497+
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("45ddaa4fa01f0c2826f32f669d2e4514faf275fe6df053f1a150e7beae58a3bd");
498+
});
474499
it("should correctly build signed preloaded image", function () {
475500
expect(cloudinary.utils.signed_preloaded_image({
476501
resource_type: "image",
@@ -827,4 +852,13 @@ describe("cloudinary", function () {
827852
result = cloudinary.utils.url("sample.jpg", options);
828853
expect(result).to.eql('http://res.cloudinary.com/test123/image/upload/s--v2fTPYTu--/sample.jpg');
829854
});
855+
it("should generate urls with signature algorithm SHA256 when sign_url is true", function () {
856+
var options, result;
857+
options = {
858+
sign_url: true,
859+
signature_algorithm: 'sha256'
860+
};
861+
result = cloudinary.utils.url("sample.jpg", options);
862+
expect(result).to.eql('http://res.cloudinary.com/test123/image/upload/s--2hbrSMPO--/sample.jpg');
863+
});
830864
});

test/utils/utils_spec.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ describe("utils", function () {
3434
private_cdn: false,
3535
secure: false,
3636
cname: null,
37-
cdn_subdomain: false
37+
cdn_subdomain: false,
38+
signature_algorithm: undefined
3839
}));
3940
this.orig = clone(this.cfg);
4041
cloud_name = cloudinary.config("cloud_name");
@@ -1438,6 +1439,20 @@ describe("utils", function () {
14381439
)
14391440
).to.eql(true);
14401441
});
1442+
it("should return true when signature with algorithm SHA256 is valid", function () {
1443+
cloudinary.config({
1444+
api_secret: 'hardcoded',
1445+
signature_algorithm: 'sha256'
1446+
});
1447+
const distant_future_timestamp = 7952342400000; // 2222-01-01T00:00:00Z
1448+
expect(
1449+
utils.verifyNotificationSignature(
1450+
response_json,
1451+
distant_future_timestamp,
1452+
"6c5a29fd8815772fbac2f10ae741e093d0859313947ef8fadeb29126ded6649c"
1453+
)
1454+
).to.eql(true);
1455+
});
14411456
it("should return false when signature is not valid", function () {
14421457
response_signature = utils.webhook_signature(response_json, valid_response_timestamp, {
14431458
api_secret: cloudinary.config().api_secret
@@ -1485,18 +1500,13 @@ describe("utils", function () {
14851500
});
14861501
});
14871502
context("sign URLs", function () {
1488-
var configBck = void 0;
1489-
before(function () {
1490-
configBck = cloudinary.config();
1503+
beforeEach(function () {
14911504
cloudinary.config({
14921505
cloud_name: 'test123',
14931506
api_key: "1234",
14941507
api_secret: "b"
14951508
});
14961509
});
1497-
after(function () {
1498-
cloudinary.config(configBck);
1499-
});
15001510
it("should correctly sign URLs", function () {
15011511
test_cloudinary_url("image.jpg", {
15021512
version: 1234,

0 commit comments

Comments
 (0)