implemented verification of JWT signatures
This commit is contained in:
parent
4b63e38c1f
commit
6ec723ec1f
|
@ -14,6 +14,27 @@
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
, _binStr: {
|
||||||
|
bufferToBinStr: function (buf) {
|
||||||
|
return Array.prototype.map.call(new Uint8Array(buf), function(ch) {
|
||||||
|
return String.fromCharCode(ch);
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
, binStrToBuffer: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
, _base64: {
|
, _base64: {
|
||||||
atob: function (base64) {
|
atob: function (base64) {
|
||||||
// atob must be called from the global context
|
// atob must be called from the global context
|
||||||
|
@ -43,6 +64,12 @@
|
||||||
b64 = b64.replace(/=+/g, '');
|
b64 = b64.replace(/=+/g, '');
|
||||||
return b64;
|
return b64;
|
||||||
}
|
}
|
||||||
|
, urlSafeToBuffer: function (str) {
|
||||||
|
return OAUTH3._binStr.binStrToBuffer(OAUTH3._base64.decodeUrlSafe(str));
|
||||||
|
}
|
||||||
|
, bufferToUrlSafe: function (buf) {
|
||||||
|
return OAUTH3._base64.encodeUrlSafe(OAUTH3._binStr.bufferToBinStr(buf));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
, uri: {
|
, uri: {
|
||||||
normalize: function (uri) {
|
normalize: function (uri) {
|
||||||
|
@ -181,15 +208,26 @@
|
||||||
// { header: {}, payload: {}, signature: '' }
|
// { header: {}, payload: {}, signature: '' }
|
||||||
var parts = str.split(/\./g);
|
var parts = str.split(/\./g);
|
||||||
var jsons = parts.slice(0, 2).map(function (urlsafe64) {
|
var jsons = parts.slice(0, 2).map(function (urlsafe64) {
|
||||||
var b64 = OAUTH3._base64.decodeUrlSafe(urlsafe64);
|
return JSON.parse(OAUTH3._base64.decodeUrlSafe(urlsafe64));
|
||||||
return b64;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { header: jsons[0], payload: jsons[1] };
|
||||||
header: JSON.parse(jsons[0])
|
}
|
||||||
, payload: JSON.parse(jsons[1])
|
, verify: function (str, pubKey) {
|
||||||
, signature: parts[2] // should remain url-safe base64
|
var parts = str.split(/\./g);
|
||||||
};
|
var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.'));
|
||||||
|
var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]);
|
||||||
|
|
||||||
|
var keyPromise;
|
||||||
|
if (pubKey instanceof OAUTH3._browser.window.CryptoKey) {
|
||||||
|
keyPromise = OAUTH3.PromiseA.resolve(pubKey);
|
||||||
|
} else {
|
||||||
|
keyPromise = OAUTH3._browser.window.crypto.subtle.importKey('jwk', pubKey, {name: 'ECDSA', namedCurve: pubKey.crv}, false, ['verify']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPromise.then(function (key) {
|
||||||
|
return OAUTH3._browser.window.crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
, freshness: function (tokenMeta, staletime, _now) {
|
, freshness: function (tokenMeta, staletime, _now) {
|
||||||
staletime = staletime || (15 * 60);
|
staletime = staletime || (15 * 60);
|
||||||
|
|
|
@ -3,33 +3,6 @@
|
||||||
|
|
||||||
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
||||||
|
|
||||||
OAUTH3.utils.bufferToBinStr = function (buf) {
|
|
||||||
return Array.prototype.map.call(new Uint8Array(buf), function(ch) {
|
|
||||||
return String.fromCharCode(ch);
|
|
||||||
}).join('');
|
|
||||||
};
|
|
||||||
OAUTH3.utils.binStrToBuffer = 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._base64.urlSafeToBuffer = function (str) {
|
|
||||||
return OAUTH3.utils.binStrToBuffer(OAUTH3._base64.decodeUrlSafe(str));
|
|
||||||
};
|
|
||||||
OAUTH3._base64.bufferToUrlSafe = function (buf) {
|
|
||||||
return OAUTH3._base64.encodeUrlSafe(OAUTH3.utils.bufferToBinStr(buf));
|
|
||||||
};
|
|
||||||
|
|
||||||
OAUTH3.crypto = {};
|
OAUTH3.crypto = {};
|
||||||
OAUTH3.crypto.fingerprintJWK = function (jwk) {
|
OAUTH3.crypto.fingerprintJWK = function (jwk) {
|
||||||
var keys;
|
var keys;
|
||||||
|
@ -51,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}';
|
var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}';
|
||||||
return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binStrToBuffer(jwkStr))
|
return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(jwkStr))
|
||||||
.then(OAUTH3._base64.bufferToUrlSafe);
|
.then(OAUTH3._base64.bufferToUrlSafe);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,7 +32,7 @@
|
||||||
var kekPromise, ecdsaPromise, secretPromise;
|
var kekPromise, ecdsaPromise, secretPromise;
|
||||||
var salt = window.crypto.getRandomValues(new Uint8Array(16));
|
var salt = window.crypto.getRandomValues(new Uint8Array(16));
|
||||||
|
|
||||||
kekPromise = window.crypto.subtle.importKey('raw', OAUTH3.utils.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey'])
|
kekPromise = window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey'])
|
||||||
.then(function (key) {
|
.then(function (key) {
|
||||||
var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}};
|
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']);
|
return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['encrypt']);
|
||||||
|
@ -92,8 +65,8 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) {
|
return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) {
|
||||||
var ecdsaJwk = OAUTH3.utils.binStrToBuffer(JSON.stringify(keys[1].privateKey));
|
var ecdsaJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[1].privateKey));
|
||||||
var secretJwk = OAUTH3.utils.binStrToBuffer(JSON.stringify(keys[2]));
|
var secretJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[2]));
|
||||||
var ecdsaIv = window.crypto.getRandomValues(new Uint8Array(12));
|
var ecdsaIv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||||
var secretIv = window.crypto.getRandomValues(new Uint8Array(12));
|
var secretIv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
|
||||||
|
@ -119,7 +92,7 @@
|
||||||
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
|
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
|
||||||
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
|
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
|
||||||
|
|
||||||
return window.crypto.subtle.importKey('raw', OAUTH3.utils.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey'])
|
return window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey'])
|
||||||
.then(function (key) {
|
.then(function (key) {
|
||||||
var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}};
|
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']);
|
return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['decrypt']);
|
||||||
|
@ -127,7 +100,7 @@
|
||||||
.then(function (key) {
|
.then(function (key) {
|
||||||
return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk);
|
return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk);
|
||||||
})
|
})
|
||||||
.then(OAUTH3.utils.bufferToBinStr)
|
.then(OAUTH3._binStr.bufferToBinStr)
|
||||||
.then(JSON.parse)
|
.then(JSON.parse)
|
||||||
.then(function (jwk) {
|
.then(function (jwk) {
|
||||||
return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign'])
|
return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign'])
|
||||||
|
@ -140,7 +113,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
OAUTH3.crypto._getKey = function (ppid) {
|
OAUTH3.crypto._getKey = function (ppid) {
|
||||||
return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3.utils.binStrToBuffer(ppid))
|
return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(ppid))
|
||||||
.then(function (hash) {
|
.then(function (hash) {
|
||||||
var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash);
|
var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash);
|
||||||
var promise;
|
var promise;
|
||||||
|
@ -168,7 +141,7 @@
|
||||||
, OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null))
|
, OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null))
|
||||||
].join('.');
|
].join('.');
|
||||||
|
|
||||||
return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3.utils.binStrToBuffer(input))
|
return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3._binStr.binStrToBuffer(input))
|
||||||
.then(function (signature) {
|
.then(function (signature) {
|
||||||
return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature);
|
return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue