-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathbrowser.js
186 lines (166 loc) · 6.1 KB
/
browser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/**
* Browser ecies-parity implementation.
*
* This is based of the eccrypto js module
*
*/
"use strict";
var EC = require("elliptic").ec;
var ec = new EC("secp256k1");
var cryptoObj = global.crypto || global.msCrypto || {};
var subtle = cryptoObj.subtle || cryptoObj.webkitSubtle;
// Implemented in parity
var PARITY_DEFAULT_HMAC = Buffer.from([0,0]);
function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}
// Use the browser RNG
function randomBytes(size) {
var arr = new Uint8Array(size);
global.crypto.getRandomValues(arr);
return new Buffer(arr);
}
// Get the browser SHA256 implementation
var sha256 = exports.sha256 = function sha256(msg) {
return subtle.digest({name: "SHA-256"}, msg).then(function(hash) {
return new Buffer(new Uint8Array(hash));
});
}
// The KDF as implemented in Parity
var kdf = exports.kdf = async function(secret, outputLength) {
let ctr = 1;
let written = 0;
let result = Buffer.from('');
while (written < outputLength) {
let ctrs = Buffer.from([ctr >> 24, ctr >> 16, ctr >> 8, ctr]);
let hashResult = await sha256(Buffer.concat([ctrs,secret]));
result = Buffer.concat([result, hashResult])
written += 32;
ctr +=1;
}
return result;
}
// AES-128-CTR is used in the Parity implementation
// Get the AES-128-CTR browser implementation
function getAes(op) {
return function(counter, key, data) {
var importAlgorithm = {
name: "AES-CTR",
};
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
return keyp.then(function(cryptoKey) {
var encAlgorithm = {
name: "AES-CTR",
counter: counter,
length: 128,
};
return subtle[op](encAlgorithm, cryptoKey, data);
}).then(function(result) {
return Buffer.from(new Uint8Array(result));
});
};
}
var aesCtrEncrypt = getAes("encrypt");
var aesCtrDecrypt = 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 Buffer.from(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);
});
}
// Obtain the public elliptic curve key from a private
var getPublic = exports.getPublic = function(privateKey) {
assert(privateKey.length === 32, "Bad private key");
return new Buffer(ec.keyFromPrivate(privateKey).getPublic("arr"));
};
// ECDSA
exports.sign = function(privateKey, msg) {
return new Promise(function(resolve) {
assert(privateKey.length === 32, "Bad private key");
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()));
});
};
// Verify ECDSA signatures
exports.verify = function(publicKey, msg, sig) {
return new Promise(function(resolve, reject) {
assert(publicKey.length === 65, "Bad public key");
assert(publicKey[0] === 4, "Bad public key");
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
if (ec.verify(msg, sig, publicKey)) {
resolve(null);
} else {
reject(new Error("Bad signature"));
}
});
};
//ECDH
var derive = exports.derive = function(privateKeyA, publicKeyB) {
return new Promise(function(resolve) {
assert(Buffer.isBuffer(privateKeyA), "Bad input");
assert(Buffer.isBuffer(publicKeyB), "Bad input");
assert(privateKeyA.length === 32, "Bad private key");
assert(publicKeyB.length === 65, "Bad public key");
assert(publicKeyB[0] === 4, "Bad public key");
let keyA = ec.keyFromPrivate(privateKeyA);
let keyB = ec.keyFromPublic(publicKeyB);
let Px = keyA.derive(keyB.getPublic()); // BN instance
resolve(new Buffer(Px.toArray()));
});
};
// Encrypt AES-128-CTR and serialise as in Parity
// Serialisation: <ephemPubKey><IV><CipherText><HMAC>
exports.encrypt = async function(publicKeyTo, msg, opts) {
assert(subtle, "WebCryptoAPI is not available");
opts = opts || {};
let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
let ephemPublicKey = getPublic(ephemPrivateKey);
let sharedPx = await derive(ephemPrivateKey, publicKeyTo);
let hash = await kdf(sharedPx, 32);
let iv = opts.iv || randomBytes(16);
let encryptionKey = hash.slice(0, 16);
let macKey = await sha256(hash.slice(16));
let ciphertext = await aesCtrEncrypt(iv, encryptionKey, msg);
let dataToMac = Buffer.concat([iv, ciphertext, PARITY_DEFAULT_HMAC]);
let HMAC = await hmacSha256Sign(macKey, dataToMac);
return Buffer.concat([ephemPublicKey,iv,ciphertext,HMAC]);
};
// Decrypt serialised AES-128-CTR
exports.decrypt = async function(privateKey, encrypted) {
assert(subtle, "WebCryptoAPI is not available");
let metaLength = 1 + 64 + 16 + 32;
assert(encrypted.length > metaLength, "Invalid Ciphertext. Data is too small")
assert(encrypted[0] >= 2 && encrypted[0] <= 4, "Not valid ciphertext.")
// deserialise
let ephemPublicKey = encrypted.slice(0,65);
let cipherTextLength = encrypted.length - metaLength;
let iv = encrypted.slice(65,65 + 16);
let cipherAndIv = encrypted.slice(65, 65+16+ cipherTextLength);
let ciphertext = cipherAndIv.slice(16);
let msgMac = encrypted.slice(65+16+ cipherTextLength);
// check HMAC
var px = await derive(privateKey, ephemPublicKey);
var hash = await kdf(px,32);
var encryptionKey = hash.slice(0, 16);
var macKey = await sha256(hash.slice(16));
var dataToMac = Buffer.concat([cipherAndIv, PARITY_DEFAULT_HMAC]);
var hmacGood = await hmacSha256Verify(macKey, dataToMac,msgMac);
assert(hmacGood, "Incorrect MAC");
// decrypt message
var plainText = await aesCtrDecrypt(iv, encryptionKey, ciphertext);
return new Buffer(new Uint8Array(plainText));
};