implemented storage of the encrypted private ECDSA JWK

This commit is contained in:
tigerbot 2017-03-06 18:39:46 -07:00
parent 338f62439a
commit 9ec66a10b2
1 changed files with 100 additions and 7 deletions

View File

@ -24,16 +24,109 @@
};
OAUTH3.crypto = {};
OAUTH3.crypto._generateKey = function () {
return window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
.catch(function (err) {
console.error('failed to generate ECDSA key', err);
return OAUTH3.PromiseA.reject(err);
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 ecdsaPromise, kekPromise;
var salt = window.crypto.getRandomValues(new Uint8Array(16));
ecdsaPromise = window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
.then(function (key) { return window.crypto.subtle.exportKey('jwk', key.privateKey); })
.then(function (jwk) {
return OAUTH3.crypto.fingerprintJWK(jwk).then(function (kid) {
delete jwk.ext;
delete jwk.key_ops;
jwk.alg = 'ES256';
jwk.kid = kid;
return OAUTH3.PromiseA.resolve(jwk);
});
});
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']);
});
return OAUTH3.PromiseA.all([ecdsaPromise, kekPromise]).then(function (keys) {
var jwkBuf = OAUTH3.utils.binaryStringToBuffer(JSON.stringify(keys[0]));
var iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, keys[1], jwkBuf).then(function (encrypted) {
return {
kid: keys[0].kid
, key: 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.key));
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']);
});
};
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 OAUTH3.resolve(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._generateKey().then(function (key) {
return OAUTH3.crypto._getKey('some PPID').then(function (key) {
var header = {type: 'jwt', alg: 'ES256'};
var input = [
OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null))
@ -42,7 +135,7 @@
return window.crypto.subtle.sign(
{name: 'ECDSA', hash: {name: 'SHA-256'}}
, key.privateKey
, key
, OAUTH3.utils.binaryStringToBuffer(input)
).then(function (signature) {
var base64Sig = OAUTH3._base64.encodeUrlSafe(OAUTH3.utils.bufferToBinaryString(signature));