From 6ec723ec1fea148578c9ecc84c46462c7afbf1ff Mon Sep 17 00:00:00 2001 From: tigerbot Date: Mon, 13 Mar 2017 13:37:06 -0600 Subject: [PATCH] implemented verification of JWT signatures --- oauth3.core.js | 52 +++++++++++++++++++++++++++++++++++++------ oauth3.issuer.mock.js | 43 +++++++---------------------------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/oauth3.core.js b/oauth3.core.js index f36482b..97d6bb9 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -14,6 +14,27 @@ return err; } } + , _binStr: { + bufferToBinStr: function (buf) { + return Array.prototype.map.call(new Uint8Array(buf), function(ch) { + return String.fromCharCode(ch); + }).join(''); + } + , binStrToBuffer: function (str) { + var buf; + + if ('undefined' !== typeof Uint8Array) { + buf = new Uint8Array(str.length); + } else { + buf = []; + } + + Array.prototype.forEach.call(str, function (ch, ind) { + buf[ind] = ch.charCodeAt(0); + }); + return buf; + } + } , _base64: { atob: function (base64) { // atob must be called from the global context @@ -43,6 +64,12 @@ b64 = b64.replace(/=+/g, ''); return b64; } + , urlSafeToBuffer: function (str) { + return OAUTH3._binStr.binStrToBuffer(OAUTH3._base64.decodeUrlSafe(str)); + } + , bufferToUrlSafe: function (buf) { + return OAUTH3._base64.encodeUrlSafe(OAUTH3._binStr.bufferToBinStr(buf)); + } } , uri: { normalize: function (uri) { @@ -181,15 +208,26 @@ // { header: {}, payload: {}, signature: '' } var parts = str.split(/\./g); var jsons = parts.slice(0, 2).map(function (urlsafe64) { - var b64 = OAUTH3._base64.decodeUrlSafe(urlsafe64); - return b64; + return JSON.parse(OAUTH3._base64.decodeUrlSafe(urlsafe64)); }); - return { - header: JSON.parse(jsons[0]) - , payload: JSON.parse(jsons[1]) - , signature: parts[2] // should remain url-safe base64 - }; + return { header: jsons[0], payload: jsons[1] }; + } + , verify: function (str, pubKey) { + var parts = str.split(/\./g); + var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.')); + var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]); + + var keyPromise; + if (pubKey instanceof OAUTH3._browser.window.CryptoKey) { + keyPromise = OAUTH3.PromiseA.resolve(pubKey); + } else { + keyPromise = OAUTH3._browser.window.crypto.subtle.importKey('jwk', pubKey, {name: 'ECDSA', namedCurve: pubKey.crv}, false, ['verify']); + } + + return keyPromise.then(function (key) { + return OAUTH3._browser.window.crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, data); + }); } , freshness: function (tokenMeta, staletime, _now) { staletime = staletime || (15 * 60); diff --git a/oauth3.issuer.mock.js b/oauth3.issuer.mock.js index d64122f..d51f4b6 100644 --- a/oauth3.issuer.mock.js +++ b/oauth3.issuer.mock.js @@ -3,33 +3,6 @@ var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; - OAUTH3.utils.bufferToBinStr = function (buf) { - return Array.prototype.map.call(new Uint8Array(buf), function(ch) { - return String.fromCharCode(ch); - }).join(''); - }; - OAUTH3.utils.binStrToBuffer = function (str) { - var buf; - - if ('undefined' !== typeof Uint8Array) { - buf = new Uint8Array(str.length); - } else { - buf = []; - } - - Array.prototype.forEach.call(str, function (ch, ind) { - buf[ind] = ch.charCodeAt(0); - }); - return buf; - }; - - OAUTH3._base64.urlSafeToBuffer = function (str) { - return OAUTH3.utils.binStrToBuffer(OAUTH3._base64.decodeUrlSafe(str)); - }; - OAUTH3._base64.bufferToUrlSafe = function (buf) { - return OAUTH3._base64.encodeUrlSafe(OAUTH3.utils.bufferToBinStr(buf)); - }; - OAUTH3.crypto = {}; OAUTH3.crypto.fingerprintJWK = function (jwk) { var keys; @@ -51,7 +24,7 @@ } var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}'; - return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binStrToBuffer(jwkStr)) + return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(jwkStr)) .then(OAUTH3._base64.bufferToUrlSafe); }; @@ -59,7 +32,7 @@ var kekPromise, ecdsaPromise, secretPromise; var salt = window.crypto.getRandomValues(new Uint8Array(16)); - kekPromise = window.crypto.subtle.importKey('raw', OAUTH3.utils.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) + kekPromise = window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) .then(function (key) { var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['encrypt']); @@ -92,8 +65,8 @@ }); return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) { - var ecdsaJwk = OAUTH3.utils.binStrToBuffer(JSON.stringify(keys[1].privateKey)); - var secretJwk = OAUTH3.utils.binStrToBuffer(JSON.stringify(keys[2])); + var ecdsaJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[1].privateKey)); + var secretJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[2])); var ecdsaIv = window.crypto.getRandomValues(new Uint8Array(12)); var secretIv = window.crypto.getRandomValues(new Uint8Array(12)); @@ -119,7 +92,7 @@ var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); - return window.crypto.subtle.importKey('raw', OAUTH3.utils.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) + return window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) .then(function (key) { var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['decrypt']); @@ -127,7 +100,7 @@ .then(function (key) { return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk); }) - .then(OAUTH3.utils.bufferToBinStr) + .then(OAUTH3._binStr.bufferToBinStr) .then(JSON.parse) .then(function (jwk) { return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) @@ -140,7 +113,7 @@ }; OAUTH3.crypto._getKey = function (ppid) { - return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binStrToBuffer(ppid)) + return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(ppid)) .then(function (hash) { var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); var promise; @@ -168,7 +141,7 @@ , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) ].join('.'); - return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3.utils.binStrToBuffer(input)) + return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3._binStr.binStrToBuffer(input)) .then(function (signature) { return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature); });