implemented dynamic loading of fallback crypto functions
This commit is contained in:
parent
740c973afc
commit
01580dd6b3
|
@ -10,23 +10,25 @@
|
|||
return createHash('sha256').update(buf).digest();
|
||||
}
|
||||
|
||||
function encrypt(data, password, salt, iv) {
|
||||
function runPbkdf2(password, salt) {
|
||||
// Derived AES key is 128 bit, and the function takes a size in bytes.
|
||||
var aesKey = pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256');
|
||||
var cipher = aes.createCipheriv('aes-128-gcm', aesKey, Buffer(iv));
|
||||
return pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256');
|
||||
}
|
||||
|
||||
function encrypt(key, data, iv) {
|
||||
var cipher = aes.createCipheriv('aes-128-gcm', Buffer(key), Buffer(iv));
|
||||
|
||||
return Buffer.concat([cipher.update(Buffer(data)), cipher.final(), cipher.getAuthTag()]);
|
||||
}
|
||||
|
||||
function decrypt(data, password, salt, iv) {
|
||||
var aesKey = pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256');
|
||||
var decipher = aes.createDecipheriv('aes-128-gcm', aesKey, Buffer(iv));
|
||||
function decrypt(key, data, iv) {
|
||||
var decipher = aes.createDecipheriv('aes-128-gcm', Buffer(key), Buffer(iv));
|
||||
|
||||
decipher.setAuthTag(Buffer(data.slice(-16)));
|
||||
return Buffer.concat([decipher.update(Buffer(data.slice(0, -16))), decipher.final()]);
|
||||
}
|
||||
|
||||
function convertBN(bn) {
|
||||
function bnToB64(bn) {
|
||||
if (bn.red) {
|
||||
bn = bn.fromRed();
|
||||
}
|
||||
|
@ -39,21 +41,31 @@
|
|||
key_ops: ['verify']
|
||||
, kty: 'EC'
|
||||
, crv: 'P-256'
|
||||
, x: convertBN(key.getPublic().x)
|
||||
, y: convertBN(key.getPublic().y)
|
||||
, x: bnToB64(key.getPublic().x)
|
||||
, y: bnToB64(key.getPublic().y)
|
||||
};
|
||||
|
||||
var privJwk = JSON.parse(JSON.stringify(pubJwk));
|
||||
privJwk.key_ops = ['sign'];
|
||||
privJwk.d = convertBN(key.getPrivate());
|
||||
privJwk.d = bnToB64(key.getPrivate());
|
||||
|
||||
return {privateKey: privJwk, publicKey: pubJwk};
|
||||
}
|
||||
|
||||
function bnToBuffer(bn, size) {
|
||||
var buf = bn.toArrayLike(Buffer);
|
||||
if (!size || buf.length === size) {
|
||||
return buf;
|
||||
}
|
||||
if (buf > size) {
|
||||
throw new Error("EC signature number bigger than expected");
|
||||
}
|
||||
return Buffer.concat([Buffer(size-buf.length).fill(0), buf]);
|
||||
}
|
||||
function sign(jwk, msg) {
|
||||
var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64'));
|
||||
var sig = key.sign(sha256(msg));
|
||||
return Buffer.concat([Buffer(sig.r, 'hex'), Buffer(sig.s, 'hex')]);
|
||||
return Buffer.concat([bnToBuffer(sig.r, 32), bnToBuffer(sig.s, 32)]);
|
||||
}
|
||||
|
||||
function verify(jwk, msg, signature) {
|
||||
|
@ -65,10 +77,19 @@
|
|||
return key.verify(sha256(msg), sig);
|
||||
}
|
||||
|
||||
exports.sha256 = function () { return Promise.resolve(sha256.apply(this, arguments)); };
|
||||
exports.encrypt = function () { return Promise.resolve(encrypt.apply(this, arguments)); };
|
||||
exports.decrypt = function () { return Promise.resolve(decrypt.apply(this, arguments)); };
|
||||
exports.sign = function () { return Promise.resolve(sign.apply(this, arguments)); };
|
||||
exports.verify = function () { return Promise.resolve(verify.apply(this, arguments)); };
|
||||
exports.genEcdsaKeyPair = function () { return Promise.resolve(genEcdsaKeyPair.apply(this, arguments)); };
|
||||
function promiseWrap(func) {
|
||||
return function() {
|
||||
var args = arguments;
|
||||
return new Promise(function (resolve) {
|
||||
resolve(func.apply(null, args));
|
||||
});
|
||||
};
|
||||
}
|
||||
exports.sha256 = promiseWrap(sha256);
|
||||
exports.pbkdf2 = promiseWrap(runPbkdf2);
|
||||
exports.encrypt = promiseWrap(encrypt);
|
||||
exports.decrypt = promiseWrap(decrypt);
|
||||
exports.sign = promiseWrap(sign);
|
||||
exports.verify = promiseWrap(verify);
|
||||
exports.genEcdsaKeyPair = promiseWrap(genEcdsaKeyPair);
|
||||
}());
|
|
@ -9,12 +9,12 @@
|
|||
var rename = require('gulp-rename');
|
||||
|
||||
gulp.task('default', function () {
|
||||
return browserify('./browserify/crypto-index.js', {standalone: 'OAUTH3_crypto'}).bundle()
|
||||
.pipe(source('browserify/crypto-index.js'))
|
||||
.pipe(rename('oauth3.crypto.js'))
|
||||
return browserify('./browserify/crypto.fallback.js', {standalone: 'OAUTH3_crypto_fallback'}).bundle()
|
||||
.pipe(source('browserify/crypto.fallback.js'))
|
||||
.pipe(rename('oauth3.crypto.fallback.js'))
|
||||
.pipe(gulp.dest('./'))
|
||||
.pipe(streamify(uglify()))
|
||||
.pipe(rename('oauth3.crypto.min.js'))
|
||||
.pipe(rename('oauth3.crypto.fallback.min.js'))
|
||||
.pipe(gulp.dest('./'))
|
||||
;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
;(function (exports) {
|
||||
'use strict';
|
||||
|
||||
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
||||
|
||||
var loadFallback = function() {
|
||||
var prom;
|
||||
loadFallback = function () { return prom; };
|
||||
|
||||
prom = new OAUTH3.PromiseA(function (resolve) {
|
||||
var body = document.getElementsByTagName('body')[0];
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.onload = resolve;
|
||||
script.onreadystatechange = function () {
|
||||
if (this.readyState === 'complete' || this.readyState === 'loaded') {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js';
|
||||
body.appendChild(script);
|
||||
});
|
||||
return prom;
|
||||
};
|
||||
|
||||
var webCrypto = {};
|
||||
webCrypto.sha256 = function (buf) {
|
||||
return crypto.subtle.digest({name: 'SHA-256'}, buf);
|
||||
};
|
||||
|
||||
webCrypto.pbkdf2 = function (password, salt) {
|
||||
return crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey'])
|
||||
.then(function (key) {
|
||||
var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}};
|
||||
return crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt']);
|
||||
})
|
||||
.then(function (key) {
|
||||
return crypto.subtle.exportKey('raw', key);
|
||||
});
|
||||
};
|
||||
|
||||
webCrypto.encrypt = function (rawKey, data, iv) {
|
||||
return crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt'])
|
||||
.then(function (key) {
|
||||
return crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data);
|
||||
});
|
||||
};
|
||||
webCrypto.decrypt = function (rawKey, data, iv) {
|
||||
return crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt'])
|
||||
.then(function (key) {
|
||||
return crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, data);
|
||||
});
|
||||
};
|
||||
|
||||
webCrypto.genEcdsaKeyPair = function () {
|
||||
return crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
|
||||
.then(function (keyPair) {
|
||||
return OAUTH3.PromiseA.all([
|
||||
crypto.subtle.exportKey('jwk', keyPair.privateKey)
|
||||
, crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
||||
]);
|
||||
}).then(function (jwkPair) {
|
||||
return { privateKey: jwkPair[0], publicKey: jwkPair[1] };
|
||||
});
|
||||
};
|
||||
|
||||
webCrypto.sign = function (jwk, msg) {
|
||||
return crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign'])
|
||||
.then(function (key) {
|
||||
return crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, msg);
|
||||
})
|
||||
.then(function (sig) {
|
||||
return new Uint8Array(sig);
|
||||
});
|
||||
};
|
||||
webCrypto.verify = function (jwk, msg, signature) {
|
||||
// If the JWK has properties that should only exist on the private key or is missing
|
||||
// "verify" in the key_ops, importing in as a public key won't work.
|
||||
if (jwk.hasOwnProperty('d') || jwk.hasOwnProperty('key_ops')) {
|
||||
jwk = JSON.parse(JSON.stringify(jwk));
|
||||
delete jwk.d;
|
||||
delete jwk.key_ops;
|
||||
}
|
||||
|
||||
return crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['verify'])
|
||||
.then(function (key) {
|
||||
return crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, msg);
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto = {};
|
||||
OAUTH3.crypto.core = {};
|
||||
function checkWebCrypto() {
|
||||
function checkException(name, func) {
|
||||
new OAUTH3.PromiseA(function (resolve) { resolve(func()); })
|
||||
.then(function () {
|
||||
OAUTH3.crypto.core[name] = webCrypto[name];
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.warn('error with WebCrypto', name, '- using fallback', err);
|
||||
loadFallback().then(function () {
|
||||
OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name];
|
||||
});
|
||||
});
|
||||
}
|
||||
function checkResult(name, expected, func) {
|
||||
checkException(name, function () {
|
||||
return func()
|
||||
.then(function (result) {
|
||||
if (typeof expected === typeof result) {
|
||||
return result;
|
||||
}
|
||||
return OAUTH3._base64.bufferToUrlSafe(result);
|
||||
})
|
||||
.then(function (result) {
|
||||
if (result !== expected) {
|
||||
throw new Error("result ("+result+") doesn't match expectation ("+expected+")");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
|
||||
var dataBuf = OAUTH3._base64.urlSafeToBuffer('1234567890abcdefghijklmn');
|
||||
var keyBuf = OAUTH3._base64.urlSafeToBuffer('l_Aeoqk6ePjwjCYrlHrgrg');
|
||||
var encBuf = OAUTH3._base64.urlSafeToBuffer('Ji_gEtcNElUONSR4Mf9S75davXjh_6-oQN9AgO5UF8rERw');
|
||||
checkResult('sha256', 'BwMveUm2V1axuERvUoxM4dScgNl9yKhER9a6p80GXj4', function () {
|
||||
return webCrypto.sha256(dataBuf);
|
||||
});
|
||||
checkResult('pbkdf2', OAUTH3._base64.bufferToUrlSafe(keyBuf), function () {
|
||||
return webCrypto.pbkdf2('password', zeroBuf);
|
||||
});
|
||||
checkResult('encrypt', OAUTH3._base64.bufferToUrlSafe(encBuf), function () {
|
||||
return webCrypto.encrypt(keyBuf, dataBuf, zeroBuf.slice(0, 12));
|
||||
});
|
||||
checkResult('decrypt', OAUTH3._base64.bufferToUrlSafe(dataBuf), function () {
|
||||
return webCrypto.decrypt(keyBuf, encBuf, zeroBuf.slice(0, 12));
|
||||
});
|
||||
|
||||
var jwk = {
|
||||
kty: "EC"
|
||||
, crv: "P-256"
|
||||
, d: "ChXx7ea5YtEltCufA8CVb0lQv3glcCfcSpEgdedgIP0"
|
||||
, x: "Akt5ZDbytcKS5UQMURvGb_UIMS4qFctDwrX8bX22ato"
|
||||
, y: "cV7nhpWNT1FeRIbdold4jLtgsEpZBFcNy3p2E5mqvto"
|
||||
};
|
||||
var sig = OAUTH3._base64.urlSafeToBuffer('nc3F8qeP8OXpfqPD9tTcFQg0Wfp37RTAppLPIKE1ZupR_8Aba64hNExwd1dOk802OFQxaECPDZCkKe7WA9RXAg');
|
||||
checkResult('verify', true, function() {
|
||||
return webCrypto.verify(jwk, dataBuf, sig);
|
||||
});
|
||||
// The results of these functions are less predictable, so we can't check their return value.
|
||||
checkException('genEcdsaKeyPair', function () {
|
||||
return webCrypto.genEcdsaKeyPair();
|
||||
});
|
||||
checkException('sign', function () {
|
||||
return webCrypto.sign(jwk, dataBuf);
|
||||
});
|
||||
}
|
||||
checkWebCrypto();
|
||||
|
||||
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._binStr.binStrToBuffer(jwkStr))
|
||||
.then(OAUTH3._base64.bufferToUrlSafe);
|
||||
};
|
||||
|
||||
OAUTH3.crypto._createKey = function (ppid) {
|
||||
var kekPromise, ecdsaPromise, secretPromise;
|
||||
var salt = window.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
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']);
|
||||
});
|
||||
|
||||
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._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));
|
||||
|
||||
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.bufferToUrlSafe(encrypted[0])
|
||||
, userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1])
|
||||
, salt: OAUTH3._base64.bufferToUrlSafe(salt)
|
||||
, ecdsaIv: OAUTH3._base64.bufferToUrlSafe(ecdsaIv)
|
||||
, secretIv: OAUTH3._base64.bufferToUrlSafe(secretIv)
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto._decryptKey = function (ppid, storedObj) {
|
||||
var salt = OAUTH3._base64.urlSafeToBuffer(storedObj.salt);
|
||||
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
|
||||
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
|
||||
|
||||
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']);
|
||||
})
|
||||
.then(function (key) {
|
||||
return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk);
|
||||
})
|
||||
.then(OAUTH3._binStr.bufferToBinStr)
|
||||
.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._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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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._binStr.binStrToBuffer(input))
|
||||
.then(function (signature) {
|
||||
return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}('undefined' !== typeof exports ? exports : window));
|
|
@ -3,151 +3,6 @@
|
|||
|
||||
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
||||
|
||||
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._binStr.binStrToBuffer(jwkStr))
|
||||
.then(OAUTH3._base64.bufferToUrlSafe);
|
||||
};
|
||||
|
||||
OAUTH3.crypto._createKey = function (ppid) {
|
||||
var kekPromise, ecdsaPromise, secretPromise;
|
||||
var salt = window.crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
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']);
|
||||
});
|
||||
|
||||
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._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));
|
||||
|
||||
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.bufferToUrlSafe(encrypted[0])
|
||||
, userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1])
|
||||
, salt: OAUTH3._base64.bufferToUrlSafe(salt)
|
||||
, ecdsaIv: OAUTH3._base64.bufferToUrlSafe(ecdsaIv)
|
||||
, secretIv: OAUTH3._base64.bufferToUrlSafe(secretIv)
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto._decryptKey = function (ppid, storedObj) {
|
||||
var salt = OAUTH3._base64.urlSafeToBuffer(storedObj.salt);
|
||||
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
|
||||
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
|
||||
|
||||
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']);
|
||||
})
|
||||
.then(function (key) {
|
||||
return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk);
|
||||
})
|
||||
.then(OAUTH3._binStr.bufferToBinStr)
|
||||
.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._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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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._binStr.binStrToBuffer(input))
|
||||
.then(function (signature) {
|
||||
return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.authn.resourceOwnerPassword = OAUTH3.authz.resourceOwnerPassword = function (directive, opts) {
|
||||
var providerUri = directive.issuer;
|
||||
|
||||
|
|
|
@ -8,18 +8,17 @@
|
|||
"install": "./node_modules/.bin/gulp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"atob": "^2.0.3",
|
||||
"browserify": "^14.1.0",
|
||||
"browserify-aes": "^1.0.6",
|
||||
"btoa": "^1.1.2",
|
||||
"create-hash": "^1.1.2",
|
||||
"elliptic": "^6.4.0",
|
||||
"pbkdf2": "^3.0.9",
|
||||
|
||||
"browserify": "^14.1.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-cli": "^1.2.2",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-streamify": "^1.0.2",
|
||||
"gulp-uglify": "^2.1.0",
|
||||
"pbkdf2": "^3.0.9",
|
||||
"vinyl-source-stream": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue