eccrypto/browser.js

165 lines
5.2 KiB
JavaScript
Raw Normal View History

/**
* Browser eccrypto implementation.
*/
"use strict";
var EC = require("elliptic").ec;
var ec = new EC("secp256k1");
2015-01-13 14:21:11 +01:00
// TODO(Kagami): Try to support IE11.
var subtle = window.crypto.subtle || window.crypto.webkitSubtle;
2015-01-12 18:50:21 +01:00
function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
2015-01-06 00:49:32 +01:00
}
2015-01-12 18:50:21 +01:00
}
2015-01-13 14:21:11 +01:00
function randomBytes(size) {
var arr = new Uint8Array(size);
window.crypto.getRandomValues(arr);
return new Buffer(arr);
}
function sha512(msg) {
return subtle.digest({name: "SHA-512"}, msg).then(function(hash) {
return new Buffer(new Uint8Array(hash));
});
}
function getAes(op) {
2015-01-14 00:29:39 +01:00
return function(iv, key, data) {
2015-01-13 14:21:11 +01:00
var importAlgorithm = {name: "AES-CBC"};
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
return keyp.then(function(cryptoKey) {
var encAlgorithm = {name: "AES-CBC", iv: iv};
2015-01-14 00:29:39 +01:00
return subtle[op](encAlgorithm, cryptoKey, data);
}).then(function(result) {
return new Buffer(new Uint8Array(result));
2015-01-13 14:21:11 +01:00
});
};
}
var aesCbcEncrypt = getAes("encrypt");
var aesCbcDecrypt = getAes("decrypt");
function hmacSha256Sign(key, msg) {
var algorithm = {name: "HMAC", hash: {name: "SHA-256"}};
var keyp = subtle.importKey("raw", key, algorithm, false, ["sign"]);
return keyp.then(function(cryptoKey) {
return subtle.sign(algorithm, cryptoKey, msg);
}).then(function(sig) {
return new Buffer(new Uint8Array(sig));
});
}
function hmacSha256Verify(key, msg, sig) {
var algorithm = {name: "HMAC", hash: {name: "SHA-256"}};
var keyp = subtle.importKey("raw", key, algorithm, false, ["verify"]);
return keyp.then(function(cryptoKey) {
return subtle.verify(algorithm, cryptoKey, sig, msg);
});
}
var getPublic = exports.getPublic = function(privateKey) {
2015-01-12 20:17:29 +01:00
// This function has sync API so we throw an error immediately.
2015-01-12 18:50:21 +01:00
assert(privateKey.length === 32, "Bad private key");
// XXX(Kagami): `elliptic.utils.encode` returns array for every
// encoding except `hex`.
2015-01-18 21:23:16 +01:00
return new Buffer(ec.keyFromPrivate(privateKey).getPublic("arr"));
};
2015-01-14 01:09:37 +01:00
// NOTE(Kagami): We don't use promise shim in Browser implementation
// because it's supported natively in new browsers (see
// <http://caniuse.com/#feat=promises>) and we can use only new browsers
// because of the WebCryptoAPI (see
// <http://caniuse.com/#feat=cryptography>).
exports.sign = function(privateKey, msg) {
2015-01-12 18:50:21 +01:00
return new Promise(function(resolve) {
2015-01-13 14:21:11 +01:00
assert(privateKey.length === 32, "Bad private key");
2015-01-21 01:04:56 +01:00
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
resolve(new Buffer(ec.sign(msg, privateKey, {canonical: true}).toDER()));
2015-01-12 18:50:21 +01:00
});
};
2015-01-13 14:21:11 +01:00
exports.verify = function(publicKey, msg, sig) {
2015-01-12 18:50:21 +01:00
return new Promise(function(resolve, reject) {
2015-01-13 14:21:11 +01:00
assert(publicKey.length === 65, "Bad public key");
assert(publicKey[0] === 4, "Bad public key");
2015-01-21 01:04:56 +01:00
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
2015-01-20 21:18:03 +01:00
if (ec.verify(msg, sig, publicKey)) {
resolve();
} else {
reject(new Error("Bad signature"));
}
2015-01-12 18:50:21 +01:00
});
};
2015-01-12 20:17:29 +01:00
2015-01-13 14:21:11 +01:00
var derive = exports.derive = function(privateKeyA, publicKeyB) {
2015-01-12 20:17:29 +01:00
return new Promise(function(resolve) {
2015-01-13 14:21:11 +01:00
assert(privateKeyA.length === 32, "Bad private key");
assert(publicKeyB.length === 65, "Bad public key");
assert(publicKeyB[0] === 4, "Bad public key");
2015-01-18 21:23:16 +01:00
var keyA = ec.keyFromPrivate(privateKeyA);
var keyB = ec.keyFromPublic(publicKeyB);
2015-01-12 20:17:29 +01:00
var Px = keyA.derive(keyB.getPublic()); // BN instance
2015-01-20 21:18:03 +01:00
resolve(new Buffer(Px.toArray()));
2015-01-13 14:21:11 +01:00
});
};
exports.encrypt = function(publicKeyTo, msg, opts) {
assert(subtle, "WebCryptoAPI is not supported");
opts = opts || {};
// Tmp variables to save context from flat promises;
2015-01-14 00:29:39 +01:00
var iv, ephemPublicKey, ciphertext, macKey;
2015-01-13 14:21:11 +01:00
return new Promise(function(resolve) {
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
ephemPublicKey = getPublic(ephemPrivateKey);
resolve(derive(ephemPrivateKey, publicKeyTo));
}).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
iv = opts.iv || randomBytes(16);
var encryptionKey = hash.slice(0, 32);
macKey = hash.slice(32);
return aesCbcEncrypt(iv, encryptionKey, msg);
2015-01-14 00:29:39 +01:00
}).then(function(data) {
ciphertext = data;
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
2015-01-13 14:21:11 +01:00
return hmacSha256Sign(macKey, dataToMac);
}).then(function(mac) {
return {
iv: iv,
ephemPublicKey: ephemPublicKey,
2015-01-14 00:29:39 +01:00
ciphertext: ciphertext,
2015-01-13 14:21:11 +01:00
mac: mac,
};
});
};
exports.decrypt = function(privateKey, opts) {
assert(subtle, "WebCryptoAPI is not supported");
2015-01-14 00:29:39 +01:00
// Tmp variable to save context from flat promises;
2015-01-13 14:21:11 +01:00
var encryptionKey;
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
encryptionKey = hash.slice(0, 32);
var macKey = hash.slice(32);
var dataToMac = Buffer.concat([
opts.iv,
opts.ephemPublicKey,
2015-01-14 00:29:39 +01:00
opts.ciphertext
2015-01-13 14:21:11 +01:00
]);
return hmacSha256Verify(macKey, dataToMac, opts.mac);
2015-01-14 00:29:39 +01:00
}).then(function(macGood) {
assert(macGood, "Bad MAC");
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
2015-01-13 14:21:11 +01:00
}).then(function(msg) {
return new Buffer(new Uint8Array(msg));
2015-01-12 20:17:29 +01:00
});
};