diff --git a/oauth3.core.js b/oauth3.core.js index 37d7c63..02085a1 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -207,12 +207,12 @@ } , jwt: { // decode only (no verification) - decode: function (str) { + decode: function (token, opts) { // 'abc.qrs.xyz' // [ 'abc', 'qrs', 'xyz' ] // {} - var parts = str.split(/\./g); + var parts = token.split(/\./g); var err; if (parts.length !== 3) { err = new Error("Invalid JWT: required 3 '.' separated components not "+parts.length); @@ -220,14 +220,55 @@ throw err; } - return JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])); + if (!opts || !opts.complete) { + return JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])); + } + return { + header: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[0])) + , payload: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])) + }; } - , verify: function (jwk, token) { + , verify: function (token, jwk) { + if (!OAUTH3.crypto) { + return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); + } + jwk = jwk.publicKey || jwk; + var parts = token.split(/\./g); var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.')); var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]); - return OAUTH3.crypto.core.verify(jwk, data, signature); + return OAUTH3.crypto.core.verify(jwk, data, signature).then(function () { + return OAUTH3.jwt.decode(token); + }); + } + , sign: function (payload, jwk) { + if (!OAUTH3.crypto) { + return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); + } + jwk = jwk.privateKey || jwk; + + var prom; + if (jwk.kid) { + prom = OAUTH3.PromiseA.resolve(jwk.kid); + } else { + prom = OAUTH3.crypto.thumbprintJwk(jwk); + } + + return prom.then(function (kid) { + // Currently the crypto part of the OAuth3 library only supports ES256 + var header = {type: 'JWT', alg: 'ES256', kid: kid}; + var input = [ + OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) + , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) + ].join('.'); + + return OAUTH3.crypto.core.sign(jwk, OAUTH3._binStr.binStrToBuffer(input)) + .then(OAUTH3._base64.bufferToUrlSafe) + .then(function (signature) { + return input + '.' + signature; + }); + }); } , freshness: function (tokenMeta, staletime, now) { // If the token doesn't expire then it's always fresh. diff --git a/oauth3.crypto.js b/oauth3.crypto.js index 67c27ff..85479fc 100644 --- a/oauth3.crypto.js +++ b/oauth3.crypto.js @@ -1,5 +1,5 @@ ;(function (exports) { -'use strict'; + 'use strict'; var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; @@ -82,6 +82,7 @@ }; function checkWebCrypto() { + /* global OAUTH3_crypto_fallback */ var loadFallback = function() { var prom; loadFallback = function () { return prom; }; @@ -102,11 +103,10 @@ return prom; }; function checkException(name, func) { - new OAUTH3.PromiseA(function (resolve) { resolve(func()); }) + OAUTH3.PromiseA.resolve().then(func) .then(function () { OAUTH3.crypto.core[name] = webCrypto[name]; - }) - .catch(function (err) { + }, function (err) { console.warn('error with WebCrypto', name, '- using fallback', err); loadFallback().then(function () { OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; @@ -195,101 +195,61 @@ .then(OAUTH3._base64.bufferToUrlSafe); }; - OAUTH3.crypto._createKey = function (ppid) { - var saltProm = OAUTH3.crypto.core.randomBytes(16); - var kekProm = saltProm.then(function (salt) { - return OAUTH3.crypto.core.pbkdf2(ppid, salt); - }); - - var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair() - .then(function (keyPair) { + OAUTH3.crypto.createKeyPair = function () { + // TODO: maybe support other types of key pairs, not just ECDSA P-256 + return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) { return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; keyPair.privateKey.kid = keyPair.publicKey.kid = kid; return keyPair; }); }); + }; + + OAUTH3.crypto.encryptKeyPair = function (keyPair, password) { + var saltProm = OAUTH3.crypto.core.randomBytes(16); + var kekProm = saltProm.then(function (salt) { + return OAUTH3.crypto.core.pbkdf2(password, salt); + }); return OAUTH3.PromiseA.all([ kekProm - , ecdsaProm , saltProm - , OAUTH3.crypto.core.randomBytes(16) , OAUTH3.crypto.core.randomBytes(12) - , OAUTH3.crypto.core.randomBytes(12) - ]).then(function (results) { + , ]).then(function (results) { var kek = results[0]; - var keyPair = results[1]; - var salt = results[2]; - var userSecret = results[3]; - var ecdsaIv = results[4]; - var secretIv = results[5]; + var salt = results[1]; + var ecdsaIv = results[2]; - return OAUTH3.PromiseA.all([ - OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey))) - , OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret) - ]) - .then(function (encrypted) { + var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)); + return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) { return { publicKey: keyPair.publicKey - , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) - , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) + , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted) , salt: OAUTH3._base64.bufferToUrlSafe(salt) , ecdsaIv: OAUTH3._base64.bufferToUrlSafe(ecdsaIv) - , secretIv: OAUTH3._base64.bufferToUrlSafe(secretIv) - }; + , }; }); }); }; - OAUTH3.crypto._decryptKey = function (ppid, storedObj) { + OAUTH3.crypto.decryptKeyPair = function (storedObj, password) { var salt = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); - return OAUTH3.crypto.core.pbkdf2(ppid, salt) + return OAUTH3.crypto.core.pbkdf2(password, salt) .then(function (key) { return OAUTH3.crypto.core.decrypt(key, iv, encJwk); }) .then(OAUTH3._binStr.bufferToBinStr) - .then(JSON.parse); - }; - - OAUTH3.crypto._getKey = function (ppid) { - return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid)) - .then(function (hash) { - var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); - var promise; - - if (window.localStorage.getItem(name) === null) { - promise = OAUTH3.crypto._createKey(ppid).then(function (key) { - window.localStorage.setItem(name, JSON.stringify(key)); - return key; - }); - } else { - promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name))); - } - - return promise.then(function (storedObj) { - return OAUTH3.crypto._decryptKey(ppid, storedObj); + .then(JSON.parse) + .then(function (privateKey) { + return { + privateKey: privateKey + , publicKey: storedObj.publicKey + , }; }); - }); - }; - - OAUTH3.crypto._signPayload = function (payload) { - return OAUTH3.crypto._getKey('some PPID').then(function (key) { - var header = {type: 'JWT', alg: key.alg, kid: key.kid}; - var input = [ - OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) - , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) - ].join('.'); - - return OAUTH3.crypto.core.sign(key, OAUTH3._binStr.binStrToBuffer(input)) - .then(OAUTH3._base64.bufferToUrlSafe) - .then(function (signature) { - return input + '.' + signature; - }); - }); }; }('undefined' !== typeof exports ? exports : window));