oauth3.js/oauth3.issuer.mock.js

244 lines
8.9 KiB
JavaScript

;(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, secretPromise;
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: 128}, 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]
};
});
});
secretPromise = window.crypto.subtle.generateKey({name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt'])
.then(function (key) {
return window.crypto.subtle.exportKey('jwk', key);
});
return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) {
var ecdsaJwk = OAUTH3.utils.binaryStringToBuffer(JSON.stringify(keys[1].privateKey));
var secretJwk = OAUTH3.utils.binaryStringToBuffer(JSON.stringify(keys[2]));
var ecdsaIv = window.crypto.getRandomValues(new Uint8Array(12));
var secretIv = window.crypto.getRandomValues(new Uint8Array(12));
return OAUTH3.PromiseA.all([
window.crypto.subtle.encrypt({name: 'AES-GCM', iv: ecdsaIv}, keys[0], ecdsaJwk)
, window.crypto.subtle.encrypt({name: 'AES-GCM', iv: secretIv}, keys[0], secretJwk)
])
.then(function (encrypted) {
return {
publicKey: keys[1].publicKey
, privateKey: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(encrypted[0]))
, userSecret: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(encrypted[1]))
, salt: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(salt))
, ecdsaIv: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(ecdsaIv))
, secretIv: OAUTH3._base64.btoa(OAUTH3.utils.bufferToBinaryString(secretIv))
};
});
});
};
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.ecdsaIv));
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: 128}, 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 OAUTH3.PromiseA.resolve({
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));