Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib-es5/utils/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ var LAYER_KEYWORD_PARAMS = {

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

var SUPPORTED_SIGNATURE_ALGORITHMS = ["sha1", "sha256"];
var DEFAULT_SIGNATURE_ALGORITHM = "sha1";

module.exports = {
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION,
DEFAULT_POSTER_OPTIONS,
Expand All @@ -79,5 +82,7 @@ module.exports = {
LAYER_KEYWORD_PARAMS,
TRANSFORMATION_PARAMS,
SIMPLE_PARAMS,
UPLOAD_PREFIX
UPLOAD_PREFIX,
SUPPORTED_SIGNATURE_ALGORITHMS,
DEFAULT_SIGNATURE_ALGORITHM
};
44 changes: 33 additions & 11 deletions lib-es5/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ var _require2 = require('./consts'),
LAYER_KEYWORD_PARAMS = _require2.LAYER_KEYWORD_PARAMS,
TRANSFORMATION_PARAMS = _require2.TRANSFORMATION_PARAMS,
SIMPLE_PARAMS = _require2.SIMPLE_PARAMS,
UPLOAD_PREFIX = _require2.UPLOAD_PREFIX;
UPLOAD_PREFIX = _require2.UPLOAD_PREFIX,
SUPPORTED_SIGNATURE_ALGORITHMS = _require2.SUPPORTED_SIGNATURE_ALGORITHMS,
DEFAULT_SIGNATURE_ALGORITHM = _require2.DEFAULT_SIGNATURE_ALGORITHM;

function textStyle(layer) {
var keywords = [];
Expand Down Expand Up @@ -757,6 +759,10 @@ function url(public_id) {
var api_secret = consumeOption(options, "api_secret", config().api_secret);
var url_suffix = consumeOption(options, "url_suffix");
var use_root_path = consumeOption(options, "use_root_path", config().use_root_path);
var signature_algorithm = consumeOption(options, "signature_algorithm", config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM);
if (long_url_signature) {
signature_algorithm = 'sha256';
}
var auth_token = consumeOption(options, "auth_token");
if (auth_token !== false) {
auth_token = exports.merge(config().auth_token, auth_token);
Expand Down Expand Up @@ -812,9 +818,8 @@ function url(public_id) {
}
// eslint-disable-next-line no-empty
} catch (error) {}
var shasum = crypto.createHash(long_url_signature ? 'sha256' : 'sha1');
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
signature = shasum.digest('base64').replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
var hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64');
signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
signature = `s--${signature}--`;
}
var prefix = unsigned_url_prefix(public_id, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution);
Expand Down Expand Up @@ -1009,9 +1014,24 @@ function api_sign_request(params_to_sign, api_secret) {

return `${k}=${toArray(v).join(",")}`;
}).sort().join("&");
var shasum = crypto.createHash('sha1');
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
return shasum.digest('hex');
return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex');
}

/**
* Computes hash from input string using specified algorithm.
* @private
* @param {string} input string which to compute hash from
* @param {string} signature_algorithm algorithm to use for computing hash
* @param {string} encoding type of encoding
* @return {string} computed hash value
*/
function computeHash(input, signature_algorithm, encoding) {
if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) {
throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`);
}
var hash = crypto.createHash(signature_algorithm);
hash.update(utf8_encode(input), 'binary');
return hash.digest(encoding);
}

function clear_blank(hash) {
Expand Down Expand Up @@ -1053,9 +1073,8 @@ function webhook_signature(data, timestamp) {
ensurePresenceOf({ data, timestamp });

var api_secret = ensureOption(options, 'api_secret');
var shasum = crypto.createHash('sha1');
shasum.update(data + timestamp + api_secret, 'binary');
return shasum.digest('hex');
var signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM);
return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex');
}

/**
Expand All @@ -1075,7 +1094,10 @@ function verifyNotificationSignature(body, timestamp, signature) {
if (timestamp < Date.now() - valid_for) {
return false;
}
var payload_hash = utils.webhook_signature(body, timestamp, { api_secret: config().api_secret });
var payload_hash = utils.webhook_signature(body, timestamp, {
api_secret: config().api_secret,
signature_algorithm: config().signature_algorithm
});
return signature === payload_hash;
}

Expand Down
7 changes: 6 additions & 1 deletion lib/utils/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ const LAYER_KEYWORD_PARAMS = {

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

const SUPPORTED_SIGNATURE_ALGORITHMS = ["sha1", "sha256"];
const DEFAULT_SIGNATURE_ALGORITHM = "sha1";

module.exports = {
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION,
DEFAULT_POSTER_OPTIONS,
Expand All @@ -140,5 +143,7 @@ module.exports = {
LAYER_KEYWORD_PARAMS,
TRANSFORMATION_PARAMS,
SIMPLE_PARAMS,
UPLOAD_PREFIX
UPLOAD_PREFIX,
SUPPORTED_SIGNATURE_ALGORITHMS,
DEFAULT_SIGNATURE_ALGORITHM
};
44 changes: 33 additions & 11 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ const {
LAYER_KEYWORD_PARAMS,
TRANSFORMATION_PARAMS,
SIMPLE_PARAMS,
UPLOAD_PREFIX
UPLOAD_PREFIX,
SUPPORTED_SIGNATURE_ALGORITHMS,
DEFAULT_SIGNATURE_ALGORITHM
} = require('./consts');

function textStyle(layer) {
Expand Down Expand Up @@ -692,6 +694,10 @@ function url(public_id, options = {}) {
let api_secret = consumeOption(options, "api_secret", config().api_secret);
let url_suffix = consumeOption(options, "url_suffix");
let use_root_path = consumeOption(options, "use_root_path", config().use_root_path);
let signature_algorithm = consumeOption(options, "signature_algorithm", config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM);
if (long_url_signature) {
signature_algorithm = 'sha256';
}
let auth_token = consumeOption(options, "auth_token");
if (auth_token !== false) {
auth_token = exports.merge(config().auth_token, auth_token);
Expand Down Expand Up @@ -735,9 +741,8 @@ function url(public_id, options = {}) {
// eslint-disable-next-line no-empty
} catch (error) {
}
let shasum = crypto.createHash(long_url_signature ? 'sha256' : 'sha1');
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
signature = shasum.digest('base64').replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
let hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64');
signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8);
signature = `s--${signature}--`;
}
let prefix = unsigned_url_prefix(
Expand Down Expand Up @@ -934,9 +939,24 @@ function api_sign_request(params_to_sign, api_secret) {
).map(
([k, v]) => `${k}=${toArray(v).join(",")}`
).sort().join("&");
let shasum = crypto.createHash('sha1');
shasum.update(utf8_encode(to_sign + api_secret), 'binary');
return shasum.digest('hex');
return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex');
}

/**
* Computes hash from input string using specified algorithm.
* @private
* @param {string} input string which to compute hash from
* @param {string} signature_algorithm algorithm to use for computing hash
* @param {string} encoding type of encoding
* @return {string} computed hash value
*/
function computeHash(input, signature_algorithm, encoding) {
if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) {
throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`);
}
let hash = crypto.createHash(signature_algorithm);
hash.update(utf8_encode(input), 'binary');
return hash.digest(encoding);
}

function clear_blank(hash) {
Expand Down Expand Up @@ -966,9 +986,8 @@ function webhook_signature(data, timestamp, options = {}) {
ensurePresenceOf({ data, timestamp });

let api_secret = ensureOption(options, 'api_secret');
let shasum = crypto.createHash('sha1');
shasum.update(data + timestamp + api_secret, 'binary');
return shasum.digest('hex');
let signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM);
return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex');
}

/**
Expand All @@ -986,7 +1005,10 @@ function verifyNotificationSignature(body, timestamp, signature, valid_for = 720
if (timestamp < Date.now() - valid_for) {
return false;
}
const payload_hash = utils.webhook_signature(body, timestamp, { api_secret: config().api_secret });
const payload_hash = utils.webhook_signature(body, timestamp, {
api_secret: config().api_secret,
signature_algorithm: config().signature_algorithm
});
return signature === payload_hash;
}

Expand Down
36 changes: 35 additions & 1 deletion test/unit/cloudinary_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ describe("cloudinary", function () {
cloud_name: "test123",
api_key: 'a',
api_secret: 'b',
responsive_width_transformation: null
responsive_width_transformation: null,
signature_algorithm: 'sha1'
}));
});
it("should use cloud_name from config", function () {
Expand Down Expand Up @@ -471,6 +472,30 @@ describe("cloudinary", function () {
undef: void 0
}, "1234")).to.eql("f05cfe85cee78e7e997b3c7da47ba212dcbf1ea5");
});
it("should correctly sign api requests with signature algorithm SHA1", function () {
cloudinary.config({ signature_algorithm: 'sha1' });
expect(cloudinary.utils.api_sign_request({
username: "user@cloudinary.com",
timestamp: 1568810420,
cloud_name: "dn6ot3ged"
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("14c00ba6d0dfdedbc86b316847d95b9e6cd46d94");
});
it("should correctly sign api requests with signature algorithm SHA1 as default", function () {
cloudinary.config({ signature_algorithm: null });
expect(cloudinary.utils.api_sign_request({
username: "user@cloudinary.com",
timestamp: 1568810420,
cloud_name: "dn6ot3ged"
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("14c00ba6d0dfdedbc86b316847d95b9e6cd46d94");
});
it("should correctly sign api requests with signature algorithm SHA256", function () {
cloudinary.config({ signature_algorithm: 'sha256' });
expect(cloudinary.utils.api_sign_request({
username: "user@cloudinary.com",
timestamp: 1568810420,
cloud_name: "dn6ot3ged"
}, "hdcixPpR2iKERPwqvH6sHdK9cyac")).to.eql("45ddaa4fa01f0c2826f32f669d2e4514faf275fe6df053f1a150e7beae58a3bd");
});
it("should correctly build signed preloaded image", function () {
expect(cloudinary.utils.signed_preloaded_image({
resource_type: "image",
Expand Down Expand Up @@ -827,4 +852,13 @@ describe("cloudinary", function () {
result = cloudinary.utils.url("sample.jpg", options);
expect(result).to.eql('http://res.cloudinary.com/test123/image/upload/s--v2fTPYTu--/sample.jpg');
});
it("should generate urls with signature algorithm SHA256 when sign_url is true", function () {
var options, result;
options = {
sign_url: true,
signature_algorithm: 'sha256'
};
result = cloudinary.utils.url("sample.jpg", options);
expect(result).to.eql('http://res.cloudinary.com/test123/image/upload/s--2hbrSMPO--/sample.jpg');
});
});
24 changes: 17 additions & 7 deletions test/utils/utils_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ describe("utils", function () {
private_cdn: false,
secure: false,
cname: null,
cdn_subdomain: false
cdn_subdomain: false,
signature_algorithm: undefined
}));
this.orig = clone(this.cfg);
cloud_name = cloudinary.config("cloud_name");
Expand Down Expand Up @@ -1438,6 +1439,20 @@ describe("utils", function () {
)
).to.eql(true);
});
it("should return true when signature with algorithm SHA256 is valid", function () {
cloudinary.config({
api_secret: 'hardcoded',
signature_algorithm: 'sha256'
});
const distant_future_timestamp = 7952342400000; // 2222-01-01T00:00:00Z
expect(
utils.verifyNotificationSignature(
response_json,
distant_future_timestamp,
"6c5a29fd8815772fbac2f10ae741e093d0859313947ef8fadeb29126ded6649c"
)
).to.eql(true);
});
it("should return false when signature is not valid", function () {
response_signature = utils.webhook_signature(response_json, valid_response_timestamp, {
api_secret: cloudinary.config().api_secret
Expand Down Expand Up @@ -1485,18 +1500,13 @@ describe("utils", function () {
});
});
context("sign URLs", function () {
var configBck = void 0;
before(function () {
configBck = cloudinary.config();
beforeEach(function () {
cloudinary.config({
cloud_name: 'test123',
api_key: "1234",
api_secret: "b"
});
});
after(function () {
cloudinary.config(configBck);
});
it("should correctly sign URLs", function () {
test_cloudinary_url("image.jpg", {
version: 1234,
Expand Down