;(function (exports) { 'use strict'; var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; OAUTH3.utils.bufferToBinaryString = function (buf) { return Array.prototype.map.call(new Uint8Array(buf), function(ch) { return String.fromCharCode(ch); }).join(''); }; OAUTH3.utils.binaryStringToBuffer = 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.crypto = {}; OAUTH3.crypto.fingerprintJWK = function (jwk) { var keys; if (jwk.kty === 'EC') { keys = ['crv', 'x', 'y']; } else if (jwk.kty === 'RSA') { keys = ['e', 'n']; } else if (jwk.kty === 'oct') { keys = ['k']; } else { return OAUTH3.PromiseA.reject(new Error('invalid JWK key type ' + jwk.kty)); } keys.push('kty'); keys.sort(); var missing = keys.filter(function (name) { return !jwk.hasOwnProperty(name); }); if (missing.length > 0) { return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing)); } var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}'; return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binaryStringToBuffer(jwkStr)) .then(OAUTH3.utils.bufferToBinaryString).then(OAUTH3._base64.btoa); }; OAUTH3.crypto._createKey = function (ppid) { var kekPromise, ecdsaPromise; var salt = window.crypto.getRandomValues(new Uint8Array(16)); kekPromise = window.crypto.subtle.importKey('raw', OAUTH3.utils.binaryStringToBuffer(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: 256}, false, ['encrypt']); }); ecdsaPromise = window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) .then(function (keyPair) { function tweakJWK(jwk) { return OAUTH3.crypto.fingerprintJWK(jwk).then(function (kid) { delete jwk.ext; jwk.alg = 'ES256'; jwk.kid = kid; return jwk; }); } return OAUTH3.PromiseA.all([ window.crypto.subtle.exportKey('jwk', keyPair.privateKey).then(tweakJWK) , window.crypto.subtle.exportKey('jwk', keyPair.publicKey).then(tweakJWK) ]).then(function (jwkPair) { return { privateKey: jwkPair[0] , publicKey: jwkPair[1] }; }); }); return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise]).then(function (keys) { var jwkBuf = OAUTH3.utils.binaryStringToBuffer(JSON.stringify(keys[1].privateKey)); var iv = window.crypto.getRandomValues(new Uint8Array(12)); return window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, keys[0], jwkBuf).then(function (encrypted) { return { publicKey: keys[1].publicKey , privateKey: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(encrypted)) , salt: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(salt)) , iv: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(iv)) }; }); }); }; OAUTH3.crypto._decryptKey = function (ppid, storedObj) { var salt = OAUTH3.utils.binaryStringToBuffer(OAUTH3._base64.atob(storedObj.salt)); var encJwk = OAUTH3.utils.binaryStringToBuffer(OAUTH3._base64.atob(storedObj.privateKey)); var iv = OAUTH3.utils.binaryStringToBuffer(OAUTH3._base64.atob(storedObj.iv)); return window.crypto.subtle.importKey('raw', OAUTH3.utils.binaryStringToBuffer(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: 256}, false, ['decrypt']); }) .then(function (key) { return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk); }) .then(OAUTH3.utils.bufferToBinaryString) .then(JSON.parse) .then(function (jwk) { return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) .then(function (key) { key.kid = jwk.kid; key.alg = jwk.alg; return key; }); }); }; OAUTH3.crypto._getKey = function (ppid) { return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binaryStringToBuffer(ppid)) .then(function (hash) { var name = 'kek-' + OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(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); }); }); }; 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 window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3.utils.binaryStringToBuffer(input)) .then(function (signature) { return input + '.' + OAUTH3._base64.encodeUrlSafe(OAUTH3.utils.bufferToBinaryString(signature)); }); }); }; OAUTH3.authn.resourceOwnerPassword = OAUTH3.authz.resourceOwnerPassword = function (directive, opts) { var providerUri = directive.issuer; if (opts.mockError) { return OAUTH3.PromiseA.resolve({data: {error_description: "fake error", error: "errorcode", error_uri: "https://blah"}}); } return OAUTH3._mockToken(providerUri, opts); }; OAUTH3.authn.loginMeta = function (directive, opts) { if (opts.mockError) { return OAUTH3.PromiseA.resolve({ data: { error: { message: "Yikes!", code: 'E' } } }); } return OAUTH3.PromiseA.resolve({ data: {} }); }; OAUTH3.authn.otp = function (directive, opts) { if (opts.mockError) { return OAUTH3.PromiseA.resolve({data: {error: {message: "Yikes!", code: 'E'}}}); } return OAUTH3.PromiseA.resolve({data: {uuid: "uuidblah"}}); }; OAUTH3.authz.scopes = function () { return { pending: ['oauth3_authn'] // not yet accepted , granted: [] // all granted, ever , requested: ['oauth3_authn'] // all requested, now , accepted: [] // granted (ever) and requested (now) }; }; OAUTH3.authz.grants = function (providerUri, opts) { if ('POST' === opts.method) { return OAUTH3._mockToken(providerUri, opts); } return OAUTH3.discover(providerUri, { client_id: providerUri , debug: opts.debug }).then(function (directive) { return { client: { name: "foo" , client_id: "localhost.foo.daplie.me:8443" , url: "https://localhost.foo.daplie.me:8443" } , grants: [] }; }); }; OAUTH3._refreshToken = function (providerUri, opts) { return OAUTH3._mockToken(providerUri, opts); }; OAUTH3._mockToken = function (providerUri, opts) { var payload = { exp: Math.round(Date.now() / 1000) + 900, sub: 'fakeUserId', scp: opts.scope }; return OAUTH3.crypto._signPayload(payload).then(function (accessToken) { return OAUTH3.hooks.session.refresh( opts.session || { provider_uri: providerUri , client_id: opts.client_id , client_uri: opts.client_uri || opts.clientUri } , { access_token: accessToken , refresh_token: accessToken , expires_in: "900" , scope: opts.scope } ); }); }; }('undefined' !== typeof exports ? exports : window));