98 lines
3.2 KiB
JavaScript
98 lines
3.2 KiB
JavaScript
;(function () {
|
|
'use strict';
|
|
|
|
var createHash = require('create-hash');
|
|
var pbkdf2 = require('pbkdf2');
|
|
var aes = require('browserify-aes');
|
|
var ec = require('elliptic/lib/elliptic/ec')('p256');
|
|
|
|
function sha256(buf) {
|
|
return createHash('sha256').update(buf).digest();
|
|
}
|
|
|
|
function runPbkdf2(password, salt) {
|
|
// Derived AES key is 128 bit, and the function takes a size in bytes.
|
|
return pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256');
|
|
}
|
|
|
|
function encrypt(key, iv, data) {
|
|
var cipher = aes.createCipheriv('aes-128-gcm', Buffer(key), Buffer(iv));
|
|
|
|
return Buffer.concat([cipher.update(Buffer(data)), cipher.final(), cipher.getAuthTag()]);
|
|
}
|
|
|
|
function decrypt(key, iv, data) {
|
|
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 bnToBuffer(bn, size) {
|
|
var buf = bn.toArrayLike(Buffer);
|
|
|
|
if (!size || buf.length === size) {
|
|
return buf;
|
|
} else if (buf.length < size) {
|
|
return Buffer.concat([Buffer(size-buf.length).fill(0), buf]);
|
|
} else if (buf.length > size) {
|
|
throw new Error('EC signature number bigger than expected');
|
|
}
|
|
throw new Error('invalid size "'+size+'" converting BigNumber to Buffer');
|
|
}
|
|
function bnToB64(bn) {
|
|
var b64 = bnToBuffer(bn).toString('base64');
|
|
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
|
|
}
|
|
function genEcdsaKeyPair() {
|
|
var key = ec.genKeyPair();
|
|
var pubJwk = {
|
|
key_ops: ['verify']
|
|
, kty: 'EC'
|
|
, crv: 'P-256'
|
|
, x: bnToB64(key.getPublic().getX())
|
|
, y: bnToB64(key.getPublic().getY())
|
|
};
|
|
|
|
var privJwk = JSON.parse(JSON.stringify(pubJwk));
|
|
privJwk.key_ops = ['sign'];
|
|
privJwk.d = bnToB64(key.getPrivate());
|
|
|
|
return {privateKey: privJwk, publicKey: pubJwk};
|
|
}
|
|
|
|
function sign(jwk, msg) {
|
|
var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64'));
|
|
var sig = key.sign(sha256(msg));
|
|
return Buffer.concat([bnToBuffer(sig.r, 32), bnToBuffer(sig.s, 32)]);
|
|
}
|
|
|
|
function verify(jwk, msg, signature) {
|
|
var key = ec.keyFromPublic({x: Buffer(jwk.x, 'base64'), y: Buffer(jwk.y, 'base64')});
|
|
var sig = {
|
|
r: Buffer(signature.slice(0, signature.length/2))
|
|
, s: Buffer(signature.slice(signature.length/2))
|
|
};
|
|
return key.verify(sha256(msg), sig);
|
|
}
|
|
|
|
function promiseWrap(func) {
|
|
return function() {
|
|
var args = arguments;
|
|
// This fallback file should only be used when the browser doesn't support everything we
|
|
// need with WebCrypto. Since it is only used in the browser we should be able to assume
|
|
// that OAUTH3 has been placed in the global scope and that we can access it here.
|
|
return new OAUTH3.PromiseA(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);
|
|
}());
|