/*! * algorithms/hmac.js - HMAC-based "signatures" * * Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file. */ "use strict"; var CONSTANTS = require("./constants"), forge = require("../deps/forge.js"), DataBuffer = require("../util/databuffer.js"), helpers = require("./helpers.js"); function hmacSignFN(name) { var md = name.replace("HS", "SHA").toLowerCase(), hash = name.replace("HS", "SHA-"); function checkKeyLength(len, key) { len = (len || CONSTANTS.HASHLENGTH[hash]) / 8; if (len > key.length) { return Promise.reject(new Error("invalid key length")); } return Promise.resolve(key); } // ### Fallback Implementation -- uses forge var fallback = function(key, pdata, props) { props = props || {}; var promise; promise = checkKeyLength(props.length, key); promise = promise.then(function() { var sig = forge.hmac.create(); sig.start(md, key.toString("binary")); sig.update(pdata.toString("binary")); sig = Buffer.from(sig.digest().bytes(), "binary"); return { data: pdata, mac: sig } }); return promise; }; // ### WebCryptoAPI Implementation var webcrypto = function(key, pdata, props) { props = props || {}; var alg = { name: "HMAC", hash: { name: hash } }; var promise; promise = checkKeyLength(props.length, key); promise = promise.then(function() { return helpers.subtleCrypto.importKey("raw", key, alg, true, ["sign"]); }); promise = promise.then(function(key) { return helpers.subtleCrypto.sign(alg, key, pdata); }); promise = promise.then(function(result) { var sig = Buffer.from(result); return { data: pdata, mac: sig }; }); return promise; }; // ### NodeJS implementation var nodejs = function(key, pdata, props) { props = props || {}; var promise; promise = checkKeyLength(props.length, key); promise = promise.then(function() { var hmac = helpers.nodeCrypto.createHmac(md, key); hmac.update(pdata); var sig = hmac.digest(); return { data: pdata, mac: sig }; }); return promise; }; return helpers.setupFallback(nodejs, webcrypto, fallback); } function hmacVerifyFN(name) { var md = name.replace("HS", "SHA").toLowerCase(), hash = name.replace("HS", "SHA-"); function compare(len, expected, actual) { len = (len || CONSTANTS.HASHLENGTH[hash]) / 8; var valid = true; for (var idx = 0; len > idx; idx++) { valid = valid && (expected[idx] === actual[idx]); } return valid; } // ### Fallback Implementation -- uses forge var fallback = function(key, pdata, mac, props) { props = props || {}; var vrfy = forge.hmac.create(); vrfy.start(md, new DataBuffer(key)); vrfy.update(pdata.toString("binary")); vrfy = Buffer.from(vrfy.digest().bytes(), "binary"); if (compare(props.length, mac, vrfy)) { return Promise.resolve({ data: pdata, mac: mac, valid: true }); } else { return Promise.reject(new Error("verification failed")); } }; var webcrypto = function(key, pdata, mac, props) { props = props || {}; var alg = { name: "HMAC", hash: { name: hash } }; var promise; if (props.length) { promise = helpers.subtleCrypto.importKey("raw", key, alg, true, ["sign"]); promise = promise.then(function(key) { return helpers.subtleCrypto.sign(alg, key, pdata); }); promise = promise.then(function(result) { var sig = Buffer.from(result); return compare(props.length, mac, sig); }); } else { promise = helpers.subtleCrypto.importKey("raw", key, alg, true, ["verify"]); promise = promise.then(function(key) { return helpers.subtleCrypto.verify(alg, key, mac, pdata); }); } promise = promise.then(function(result) { if (!result) { return Promise.reject(new Error("verifaction failed")); } return { data: pdata, mac: mac, valid: true }; }); return promise; }; var nodejs = function(key, pdata, mac, props) { props = props || {}; var hmac = helpers.nodeCrypto.createHmac(md, key); hmac.update(pdata); var sig = hmac.digest(); if (!compare(props.length, mac, sig)) { throw new Error("verification failed"); } return { data: pdata, mac: sig, valid: true }; }; return helpers.setupFallback(nodejs, webcrypto, fallback); } // ### Public API // * [name].sign // * [name].verify var hmac = {}; [ "HS1", "HS256", "HS384", "HS512" ].forEach(function(alg) { hmac[alg] = { sign: hmacSignFN(alg), verify: hmacVerifyFN(alg) }; }); module.exports = hmac;