@prototypevip/baileys 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Defaults/index.js +3 -3
- package/lib/Signal/Group/group_cipher.js +1 -1
- package/lib/Signal/Group/keyhelper.js +1 -1
- package/lib/Signal/Group/sender-chain-key.js +1 -1
- package/lib/Signal/Group/sender-key-message.js +1 -1
- package/lib/Signal/Group/sender-message-key.js +1 -1
- package/lib/Signal/libsignal-wasm/LICENSE +621 -0
- package/lib/Signal/libsignal-wasm/NOTICE.md +11 -0
- package/lib/Signal/libsignal-wasm/README.md +64 -0
- package/lib/Signal/libsignal-wasm/index.d.ts +55 -0
- package/lib/Signal/libsignal-wasm/index.js +14 -0
- package/lib/Signal/libsignal-wasm/protos/WhisperTextProtocol.proto +28 -0
- package/lib/Signal/libsignal-wasm/src/WhisperTextProtocol.js +931 -0
- package/lib/Signal/libsignal-wasm/src/base_key_type.js +7 -0
- package/lib/Signal/libsignal-wasm/src/chain_type.js +6 -0
- package/lib/Signal/libsignal-wasm/src/crypto.d.ts +19 -0
- package/lib/Signal/libsignal-wasm/src/crypto.js +77 -0
- package/lib/Signal/libsignal-wasm/src/curve.d.ts +22 -0
- package/lib/Signal/libsignal-wasm/src/curve.js +69 -0
- package/lib/Signal/libsignal-wasm/src/errors.js +33 -0
- package/lib/Signal/libsignal-wasm/src/keyhelper.js +45 -0
- package/lib/Signal/libsignal-wasm/src/numeric_fingerprint.js +72 -0
- package/lib/Signal/libsignal-wasm/src/protobufs.js +5 -0
- package/lib/Signal/libsignal-wasm/src/protocol_address.js +40 -0
- package/lib/Signal/libsignal-wasm/src/queue_job.js +68 -0
- package/lib/Signal/libsignal-wasm/src/session_builder.js +162 -0
- package/lib/Signal/libsignal-wasm/src/session_cipher.js +336 -0
- package/lib/Signal/libsignal-wasm/src/session_record.js +316 -0
- package/lib/Signal/libsignal.js +2 -2
- package/lib/Socket/index.js +33 -1
- package/lib/Socket/messages-send.js +61 -7
- package/lib/Store/prototype-store.js +34 -22
- package/lib/Utils/crypto.js +1 -1
- package/lib/Utils/generics.js +4 -27
- package/lib/Utils/index.js +2 -0
- package/lib/Utils/integrity-guard.js +281 -0
- package/lib/Utils/interactive-message-handler.js +28 -8
- package/lib/Utils/socket-guard.js +154 -0
- package/package.json +1 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function decrypt(
|
|
2
|
+
key: Uint8Array,
|
|
3
|
+
ciphertext: Uint8Array,
|
|
4
|
+
iv: Uint8Array
|
|
5
|
+
): Promise<Uint8Array>;
|
|
6
|
+
|
|
7
|
+
export function encrypt(
|
|
8
|
+
key: Uint8Array,
|
|
9
|
+
plaintext: Uint8Array,
|
|
10
|
+
iv: Uint8Array
|
|
11
|
+
): Promise<Buffer>;
|
|
12
|
+
|
|
13
|
+
export function calculateMAC(key: Buffer, data: Uint8Array): Uint8Array;
|
|
14
|
+
|
|
15
|
+
export function deriveSecrets(
|
|
16
|
+
key: Uint8Array,
|
|
17
|
+
salt: Buffer,
|
|
18
|
+
info: Buffer
|
|
19
|
+
): [Buffer, Buffer];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// vim: ts=4:sw=4
|
|
2
|
+
|
|
3
|
+
import nodeCrypto from 'crypto';
|
|
4
|
+
import assert from 'assert';
|
|
5
|
+
import { hkdf } from 'whatsapp-rust-bridge';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function assertBuffer(value) {
|
|
9
|
+
if (!(value instanceof Buffer)) {
|
|
10
|
+
throw TypeError(`Expected Buffer instead of: ${value.constructor.name}`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export function encrypt(key, data, iv) {
|
|
17
|
+
assertBuffer(key);
|
|
18
|
+
assertBuffer(data);
|
|
19
|
+
assertBuffer(iv);
|
|
20
|
+
const cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
|
|
21
|
+
return Buffer.concat([cipher.update(data), cipher.final()]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export function decrypt(key, data, iv) {
|
|
26
|
+
assertBuffer(key);
|
|
27
|
+
assertBuffer(data);
|
|
28
|
+
assertBuffer(iv);
|
|
29
|
+
const decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
30
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function calculateMAC(key, data) {
|
|
35
|
+
assertBuffer(key);
|
|
36
|
+
assertBuffer(data);
|
|
37
|
+
const hmac = nodeCrypto.createHmac('sha256', key);
|
|
38
|
+
hmac.update(data);
|
|
39
|
+
return Buffer.from(hmac.digest());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export function hash(data) {
|
|
44
|
+
assertBuffer(data);
|
|
45
|
+
const sha512 = nodeCrypto.createHash('sha512');
|
|
46
|
+
sha512.update(data);
|
|
47
|
+
return sha512.digest();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// Salts always end up being 32 bytes
|
|
52
|
+
export function deriveSecrets(input, salt, info, chunks) {
|
|
53
|
+
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
|
|
54
|
+
assertBuffer(input);
|
|
55
|
+
assertBuffer(salt);
|
|
56
|
+
assertBuffer(info);
|
|
57
|
+
if (salt.byteLength != 32) {
|
|
58
|
+
throw new Error("Got salt of incorrect length");
|
|
59
|
+
}
|
|
60
|
+
chunks = chunks || 3;
|
|
61
|
+
assert(chunks >= 1 && chunks <= 3);
|
|
62
|
+
|
|
63
|
+
const result = hkdf(input, chunks * 32, { salt, info: info.toString('utf8') });
|
|
64
|
+
return Array.from({ length: chunks }, (_, i) =>
|
|
65
|
+
Buffer.from(result.slice(i * 32, (i + 1) * 32))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function verifyMAC(data, key, mac, length) {
|
|
70
|
+
const calculatedMac = calculateMAC(key, data).slice(0, length);
|
|
71
|
+
if (mac.length !== length || calculatedMac.length !== length) {
|
|
72
|
+
throw new Error("Bad MAC length");
|
|
73
|
+
}
|
|
74
|
+
if (!mac.equals(calculatedMac)) {
|
|
75
|
+
throw new Error("Bad MAC");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface KeyPairType {
|
|
2
|
+
pubKey: Uint8Array;
|
|
3
|
+
privKey: Uint8Array;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function generateKeyPair(): KeyPairType;
|
|
7
|
+
|
|
8
|
+
export function calculateAgreement(
|
|
9
|
+
publicKey: Uint8Array,
|
|
10
|
+
privateKey: Uint8Array
|
|
11
|
+
): Uint8Array;
|
|
12
|
+
|
|
13
|
+
export function calculateSignature(
|
|
14
|
+
privateKey: Uint8Array,
|
|
15
|
+
message: Uint8Array
|
|
16
|
+
): Uint8Array;
|
|
17
|
+
|
|
18
|
+
export function verifySignature(
|
|
19
|
+
publicKey: Uint8Array,
|
|
20
|
+
message: Uint8Array,
|
|
21
|
+
signature: Uint8Array
|
|
22
|
+
): boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
import { generateKeyPair as wasmGenerateKeyPair, calculateAgreement as wasmCalculateAgreement,
|
|
3
|
+
calculateSignature as wasmCalculateSignature, verifySignature as wasmVerifySignature,
|
|
4
|
+
getPublicFromPrivateKey as wasmGetPublicFromPrivateKey } from 'whatsapp-rust-bridge';
|
|
5
|
+
|
|
6
|
+
function validatePrivKey(privKey) {
|
|
7
|
+
if (privKey === undefined) {
|
|
8
|
+
throw new Error("Undefined private key");
|
|
9
|
+
}
|
|
10
|
+
if (!(privKey instanceof Buffer)) {
|
|
11
|
+
throw new Error(`Invalid private key type: ${privKey.constructor.name}`);
|
|
12
|
+
}
|
|
13
|
+
if (privKey.byteLength != 32) {
|
|
14
|
+
throw new Error(`Incorrect private key length: ${privKey.byteLength}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function scrubPubKeyFormat(pubKey) {
|
|
19
|
+
if (!(pubKey instanceof Buffer)) {
|
|
20
|
+
throw new Error(`Invalid public key type: ${pubKey.constructor.name}`);
|
|
21
|
+
}
|
|
22
|
+
if (pubKey === undefined || ((pubKey.byteLength != 33 || pubKey[0] != 5) && pubKey.byteLength != 32)) {
|
|
23
|
+
throw new Error("Invalid public key");
|
|
24
|
+
}
|
|
25
|
+
if (pubKey.byteLength == 33) {
|
|
26
|
+
return pubKey.slice(1);
|
|
27
|
+
} else {
|
|
28
|
+
console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
|
|
29
|
+
return pubKey;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getPublicFromPrivateKey(privKey) {
|
|
34
|
+
return Buffer.from(wasmGetPublicFromPrivateKey(privKey));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generateKeyPair() {
|
|
38
|
+
const kp = wasmGenerateKeyPair();
|
|
39
|
+
return {
|
|
40
|
+
pubKey: Buffer.from(kp.pubKey),
|
|
41
|
+
privKey: Buffer.from(kp.privKey)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function calculateAgreement(pubKey, privKey) {
|
|
46
|
+
pubKey = scrubPubKeyFormat(pubKey);
|
|
47
|
+
validatePrivKey(privKey);
|
|
48
|
+
return Buffer.from(wasmCalculateAgreement(pubKey, privKey));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function calculateSignature(privKey, message) {
|
|
52
|
+
validatePrivKey(privKey);
|
|
53
|
+
if (!message) {
|
|
54
|
+
throw new Error("Invalid message");
|
|
55
|
+
}
|
|
56
|
+
return Buffer.from(wasmCalculateSignature(privKey, message));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function verifySignature(pubKey, msg, sig, isInit) {
|
|
60
|
+
if (isInit) return true;
|
|
61
|
+
pubKey = scrubPubKeyFormat(pubKey);
|
|
62
|
+
if (!msg) {
|
|
63
|
+
throw new Error("Invalid message");
|
|
64
|
+
}
|
|
65
|
+
if (!sig || sig.byteLength != 64) {
|
|
66
|
+
throw new Error("Invalid signature");
|
|
67
|
+
}
|
|
68
|
+
return wasmVerifySignature(pubKey, msg, sig);
|
|
69
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// vim: ts=4:sw=4:expandtab
|
|
2
|
+
|
|
3
|
+
export class SignalError extends Error {}
|
|
4
|
+
|
|
5
|
+
export class UntrustedIdentityKeyError extends SignalError {
|
|
6
|
+
constructor(addr, identityKey) {
|
|
7
|
+
super();
|
|
8
|
+
this.name = 'UntrustedIdentityKeyError';
|
|
9
|
+
this.addr = addr;
|
|
10
|
+
this.identityKey = identityKey;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SessionError extends SignalError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'SessionError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class MessageCounterError extends SessionError {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'MessageCounterError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PreKeyError extends SessionError {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'PreKeyError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// vim: ts=4:sw=4:expandtab
|
|
2
|
+
|
|
3
|
+
import * as curve from './curve.js';
|
|
4
|
+
import nodeCrypto from 'crypto';
|
|
5
|
+
|
|
6
|
+
function isNonNegativeInteger(n) {
|
|
7
|
+
return (typeof n === 'number' && (n % 1) === 0 && n >= 0);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const generateIdentityKeyPair = curve.generateKeyPair;
|
|
11
|
+
|
|
12
|
+
export function generateRegistrationId() {
|
|
13
|
+
var registrationId = Uint16Array.from(nodeCrypto.randomBytes(2))[0];
|
|
14
|
+
return registrationId & 0x3fff;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function generateSignedPreKey(identityKeyPair, signedKeyId) {
|
|
18
|
+
if (!(identityKeyPair.privKey instanceof Buffer) ||
|
|
19
|
+
identityKeyPair.privKey.byteLength != 32 ||
|
|
20
|
+
!(identityKeyPair.pubKey instanceof Buffer) ||
|
|
21
|
+
identityKeyPair.pubKey.byteLength != 33) {
|
|
22
|
+
throw new TypeError('Invalid argument for identityKeyPair');
|
|
23
|
+
}
|
|
24
|
+
if (!isNonNegativeInteger(signedKeyId)) {
|
|
25
|
+
throw new TypeError('Invalid argument for signedKeyId: ' + signedKeyId);
|
|
26
|
+
}
|
|
27
|
+
const keyPair = curve.generateKeyPair();
|
|
28
|
+
const sig = curve.calculateSignature(identityKeyPair.privKey, keyPair.pubKey);
|
|
29
|
+
return {
|
|
30
|
+
keyId: signedKeyId,
|
|
31
|
+
keyPair: keyPair,
|
|
32
|
+
signature: sig
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function generatePreKey(keyId) {
|
|
37
|
+
if (!isNonNegativeInteger(keyId)) {
|
|
38
|
+
throw new TypeError('Invalid argument for keyId: ' + keyId);
|
|
39
|
+
}
|
|
40
|
+
const keyPair = curve.generateKeyPair();
|
|
41
|
+
return {
|
|
42
|
+
keyId,
|
|
43
|
+
keyPair
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import * as crypto from './crypto.js';
|
|
3
|
+
|
|
4
|
+
var VERSION = 0;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async function iterateHash(data, key, count) {
|
|
8
|
+
const combined = (new Uint8Array(Buffer.concat([data, key]))).buffer;
|
|
9
|
+
const result = crypto.hash(combined);
|
|
10
|
+
if (--count === 0) {
|
|
11
|
+
return result;
|
|
12
|
+
} else {
|
|
13
|
+
return iterateHash(result, key, count);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
function shortToArrayBuffer(number) {
|
|
19
|
+
return new Uint16Array([number]).buffer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getEncodedChunk(hash, offset) {
|
|
23
|
+
var chunk = ( hash[offset] * Math.pow(2,32) +
|
|
24
|
+
hash[offset+1] * Math.pow(2,24) +
|
|
25
|
+
hash[offset+2] * Math.pow(2,16) +
|
|
26
|
+
hash[offset+3] * Math.pow(2,8) +
|
|
27
|
+
hash[offset+4] ) % 100000;
|
|
28
|
+
var s = chunk.toString();
|
|
29
|
+
while (s.length < 5) {
|
|
30
|
+
s = '0' + s;
|
|
31
|
+
}
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getDisplayStringFor(identifier, key, iterations) {
|
|
36
|
+
const bytes = Buffer.concat([
|
|
37
|
+
shortToArrayBuffer(VERSION),
|
|
38
|
+
key,
|
|
39
|
+
identifier
|
|
40
|
+
]);
|
|
41
|
+
const arraybuf = (new Uint8Array(bytes)).buffer;
|
|
42
|
+
const output = new Uint8Array(await iterateHash(arraybuf, key, iterations));
|
|
43
|
+
return getEncodedChunk(output, 0) +
|
|
44
|
+
getEncodedChunk(output, 5) +
|
|
45
|
+
getEncodedChunk(output, 10) +
|
|
46
|
+
getEncodedChunk(output, 15) +
|
|
47
|
+
getEncodedChunk(output, 20) +
|
|
48
|
+
getEncodedChunk(output, 25);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function FingerprintGenerator(iterations) {
|
|
52
|
+
this.iterations = iterations;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
FingerprintGenerator.prototype = {
|
|
56
|
+
createFor: function(localIdentifier, localIdentityKey,
|
|
57
|
+
remoteIdentifier, remoteIdentityKey) {
|
|
58
|
+
if (typeof localIdentifier !== 'string' ||
|
|
59
|
+
typeof remoteIdentifier !== 'string' ||
|
|
60
|
+
!(localIdentityKey instanceof ArrayBuffer) ||
|
|
61
|
+
!(remoteIdentityKey instanceof ArrayBuffer)) {
|
|
62
|
+
throw new Error('Invalid arguments');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return Promise.all([
|
|
66
|
+
getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
|
|
67
|
+
getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
|
|
68
|
+
]).then(function(fingerprints) {
|
|
69
|
+
return fingerprints.sort().join('');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// vim: ts=4:sw=4:expandtab
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProtocolAddress {
|
|
5
|
+
|
|
6
|
+
static from(encodedAddress) {
|
|
7
|
+
if (typeof encodedAddress !== 'string' || !encodedAddress.match(/.*\.\d+/)) {
|
|
8
|
+
throw new Error('Invalid address encoding');
|
|
9
|
+
}
|
|
10
|
+
const parts = encodedAddress.split('.');
|
|
11
|
+
return new this(parts[0], parseInt(parts[1]));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(id, deviceId) {
|
|
15
|
+
if (typeof id !== 'string') {
|
|
16
|
+
throw new TypeError('id required for addr');
|
|
17
|
+
}
|
|
18
|
+
if (id.indexOf('.') !== -1) {
|
|
19
|
+
throw new TypeError('encoded addr detected');
|
|
20
|
+
}
|
|
21
|
+
this.id = id;
|
|
22
|
+
if (typeof deviceId !== 'number') {
|
|
23
|
+
throw new TypeError('number required for deviceId');
|
|
24
|
+
}
|
|
25
|
+
this.deviceId = deviceId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toString() {
|
|
29
|
+
return `${this.id}.${this.deviceId}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
is(other) {
|
|
33
|
+
if (!(other instanceof ProtocolAddress)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return other.id === this.id && other.deviceId === this.deviceId;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default ProtocolAddress;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// vim: ts=4:sw=4:expandtab
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* jobQueue manages multiple queues indexed by device to serialize
|
|
5
|
+
* session io ops on the database.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const _queueAsyncBuckets = new Map();
|
|
10
|
+
const _gcLimit = 10000;
|
|
11
|
+
|
|
12
|
+
async function _asyncQueueExecutor(queue, cleanup) {
|
|
13
|
+
let offt = 0;
|
|
14
|
+
while (true) {
|
|
15
|
+
let limit = Math.min(queue.length, _gcLimit); // Break up thundering hurds for GC duty.
|
|
16
|
+
for (let i = offt; i < limit; i++) {
|
|
17
|
+
const job = queue[i];
|
|
18
|
+
try {
|
|
19
|
+
job.resolve(await job.awaitable());
|
|
20
|
+
} catch(e) {
|
|
21
|
+
job.reject(e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (limit < queue.length) {
|
|
25
|
+
/* Perform lazy GC of queue for faster iteration. */
|
|
26
|
+
if (limit >= _gcLimit) {
|
|
27
|
+
queue.splice(0, limit);
|
|
28
|
+
offt = 0;
|
|
29
|
+
} else {
|
|
30
|
+
offt = limit;
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
cleanup();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function(bucket, awaitable) {
|
|
40
|
+
/* Run the async awaitable only when all other async calls registered
|
|
41
|
+
* here have completed (or thrown). The bucket argument is a hashable
|
|
42
|
+
* key representing the task queue to use. */
|
|
43
|
+
if (!awaitable.name) {
|
|
44
|
+
// Make debuging easier by adding a name to this function.
|
|
45
|
+
Object.defineProperty(awaitable, 'name', {writable: true});
|
|
46
|
+
if (typeof bucket === 'string') {
|
|
47
|
+
awaitable.name = bucket;
|
|
48
|
+
} else {
|
|
49
|
+
console.warn("Unhandled bucket type (for naming):", typeof bucket, bucket);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let inactive;
|
|
53
|
+
if (!_queueAsyncBuckets.has(bucket)) {
|
|
54
|
+
_queueAsyncBuckets.set(bucket, []);
|
|
55
|
+
inactive = true;
|
|
56
|
+
}
|
|
57
|
+
const queue = _queueAsyncBuckets.get(bucket);
|
|
58
|
+
const job = new Promise((resolve, reject) => queue.push({
|
|
59
|
+
awaitable,
|
|
60
|
+
resolve,
|
|
61
|
+
reject
|
|
62
|
+
}));
|
|
63
|
+
if (inactive) {
|
|
64
|
+
/* An executor is not currently active; Start one now. */
|
|
65
|
+
_asyncQueueExecutor(queue, () => _queueAsyncBuckets.delete(bucket));
|
|
66
|
+
}
|
|
67
|
+
return job;
|
|
68
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
|
|
2
|
+
import BaseKeyType from './base_key_type.js';
|
|
3
|
+
import ChainType from './chain_type.js';
|
|
4
|
+
import SessionRecord from './session_record.js';
|
|
5
|
+
import * as crypto from './crypto.js';
|
|
6
|
+
import * as curve from './curve.js';
|
|
7
|
+
import * as errors from './errors.js';
|
|
8
|
+
import queueJob from './queue_job.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionBuilder {
|
|
12
|
+
|
|
13
|
+
constructor(storage, protocolAddress) {
|
|
14
|
+
this.addr = protocolAddress;
|
|
15
|
+
this.storage = storage;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async initOutgoing(device) {
|
|
19
|
+
const fqAddr = this.addr.toString();
|
|
20
|
+
return await queueJob(fqAddr, async () => {
|
|
21
|
+
if (!await this.storage.isTrustedIdentity(this.addr.id, device.identityKey)) {
|
|
22
|
+
throw new errors.UntrustedIdentityKeyError(this.addr.id, device.identityKey);
|
|
23
|
+
}
|
|
24
|
+
curve.verifySignature(device.identityKey, device.signedPreKey.publicKey,
|
|
25
|
+
device.signedPreKey.signature, true);
|
|
26
|
+
const baseKey = curve.generateKeyPair();
|
|
27
|
+
const devicePreKey = device.preKey && device.preKey.publicKey;
|
|
28
|
+
const session = await this.initSession(true, baseKey, undefined, device.identityKey,
|
|
29
|
+
devicePreKey, device.signedPreKey.publicKey,
|
|
30
|
+
device.registrationId);
|
|
31
|
+
session.pendingPreKey = {
|
|
32
|
+
signedKeyId: device.signedPreKey.keyId,
|
|
33
|
+
baseKey: baseKey.pubKey
|
|
34
|
+
};
|
|
35
|
+
if (device.preKey) {
|
|
36
|
+
session.pendingPreKey.preKeyId = device.preKey.keyId;
|
|
37
|
+
}
|
|
38
|
+
let record = await this.storage.loadSession(fqAddr);
|
|
39
|
+
if (!record) {
|
|
40
|
+
record = new SessionRecord();
|
|
41
|
+
} else {
|
|
42
|
+
const openSession = record.getOpenSession();
|
|
43
|
+
if (openSession) {
|
|
44
|
+
// console.warn("Closing stale open session for new outgoing prekey bundle"); temporary fix
|
|
45
|
+
record.closeSession(openSession);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
record.setSession(session);
|
|
49
|
+
await this.storage.storeSession(fqAddr, record);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async initIncoming(record, message) {
|
|
54
|
+
const fqAddr = this.addr.toString();
|
|
55
|
+
if (!await this.storage.isTrustedIdentity(fqAddr, message.identityKey)) {
|
|
56
|
+
throw new errors.UntrustedIdentityKeyError(this.addr.id, message.identityKey);
|
|
57
|
+
}
|
|
58
|
+
if (record.getSession(message.baseKey)) {
|
|
59
|
+
// This just means we haven't replied.
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const preKeyPair = await this.storage.loadPreKey(message.preKeyId);
|
|
63
|
+
if (message.preKeyId && !preKeyPair) {
|
|
64
|
+
throw new errors.PreKeyError('Invalid PreKey ID');
|
|
65
|
+
}
|
|
66
|
+
const signedPreKeyPair = await this.storage.loadSignedPreKey(message.signedPreKeyId);
|
|
67
|
+
if (!signedPreKeyPair) {
|
|
68
|
+
throw new errors.PreKeyError("Missing SignedPreKey");
|
|
69
|
+
}
|
|
70
|
+
const existingOpenSession = record.getOpenSession();
|
|
71
|
+
if (existingOpenSession) {
|
|
72
|
+
console.warn("Closing open session in favor of incoming prekey bundle");
|
|
73
|
+
record.closeSession(existingOpenSession);
|
|
74
|
+
}
|
|
75
|
+
record.setSession(await this.initSession(false, preKeyPair, signedPreKeyPair,
|
|
76
|
+
message.identityKey, message.baseKey,
|
|
77
|
+
undefined, message.registrationId));
|
|
78
|
+
return message.preKeyId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async initSession(isInitiator, ourEphemeralKey, ourSignedKey, theirIdentityPubKey,
|
|
82
|
+
theirEphemeralPubKey, theirSignedPubKey, registrationId) {
|
|
83
|
+
if (isInitiator) {
|
|
84
|
+
if (ourSignedKey) {
|
|
85
|
+
throw new Error("Invalid call to initSession");
|
|
86
|
+
}
|
|
87
|
+
ourSignedKey = ourEphemeralKey;
|
|
88
|
+
} else {
|
|
89
|
+
if (theirSignedPubKey) {
|
|
90
|
+
throw new Error("Invalid call to initSession");
|
|
91
|
+
}
|
|
92
|
+
theirSignedPubKey = theirEphemeralPubKey;
|
|
93
|
+
}
|
|
94
|
+
let sharedSecret;
|
|
95
|
+
if (!ourEphemeralKey || !theirEphemeralPubKey) {
|
|
96
|
+
sharedSecret = new Uint8Array(32 * 4);
|
|
97
|
+
} else {
|
|
98
|
+
sharedSecret = new Uint8Array(32 * 5);
|
|
99
|
+
}
|
|
100
|
+
for (var i = 0; i < 32; i++) {
|
|
101
|
+
sharedSecret[i] = 0xff;
|
|
102
|
+
}
|
|
103
|
+
const ourIdentityKey = await this.storage.getOurIdentity();
|
|
104
|
+
const a1 = curve.calculateAgreement(theirSignedPubKey, ourIdentityKey.privKey);
|
|
105
|
+
const a2 = curve.calculateAgreement(theirIdentityPubKey, ourSignedKey.privKey);
|
|
106
|
+
const a3 = curve.calculateAgreement(theirSignedPubKey, ourSignedKey.privKey);
|
|
107
|
+
if (isInitiator) {
|
|
108
|
+
sharedSecret.set(new Uint8Array(a1), 32);
|
|
109
|
+
sharedSecret.set(new Uint8Array(a2), 32 * 2);
|
|
110
|
+
} else {
|
|
111
|
+
sharedSecret.set(new Uint8Array(a1), 32 * 2);
|
|
112
|
+
sharedSecret.set(new Uint8Array(a2), 32);
|
|
113
|
+
}
|
|
114
|
+
sharedSecret.set(new Uint8Array(a3), 32 * 3);
|
|
115
|
+
if (ourEphemeralKey && theirEphemeralPubKey) {
|
|
116
|
+
const a4 = curve.calculateAgreement(theirEphemeralPubKey, ourEphemeralKey.privKey);
|
|
117
|
+
sharedSecret.set(new Uint8Array(a4), 32 * 4);
|
|
118
|
+
}
|
|
119
|
+
const masterKey = crypto.deriveSecrets(Buffer.from(sharedSecret), Buffer.alloc(32),
|
|
120
|
+
Buffer.from("WhisperText"));
|
|
121
|
+
const session = SessionRecord.createEntry();
|
|
122
|
+
session.registrationId = registrationId;
|
|
123
|
+
session.currentRatchet = {
|
|
124
|
+
rootKey: masterKey[0],
|
|
125
|
+
ephemeralKeyPair: isInitiator ? curve.generateKeyPair() : ourSignedKey,
|
|
126
|
+
lastRemoteEphemeralKey: theirSignedPubKey,
|
|
127
|
+
previousCounter: 0
|
|
128
|
+
};
|
|
129
|
+
session.indexInfo = {
|
|
130
|
+
created: Date.now(),
|
|
131
|
+
used: Date.now(),
|
|
132
|
+
remoteIdentityKey: theirIdentityPubKey,
|
|
133
|
+
baseKey: isInitiator ? ourEphemeralKey.pubKey : theirEphemeralPubKey,
|
|
134
|
+
baseKeyType: isInitiator ? BaseKeyType.OURS : BaseKeyType.THEIRS,
|
|
135
|
+
closed: -1
|
|
136
|
+
};
|
|
137
|
+
if (isInitiator) {
|
|
138
|
+
// If we're initiating we go ahead and set our first sending ephemeral key now,
|
|
139
|
+
// otherwise we figure it out when we first maybeStepRatchet with the remote's
|
|
140
|
+
// ephemeral key
|
|
141
|
+
this.calculateSendingRatchet(session, theirSignedPubKey);
|
|
142
|
+
}
|
|
143
|
+
return session;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
calculateSendingRatchet(session, remoteKey) {
|
|
147
|
+
const ratchet = session.currentRatchet;
|
|
148
|
+
const sharedSecret = curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey);
|
|
149
|
+
const masterKey = crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from("WhisperRatchet"));
|
|
150
|
+
session.addChain(ratchet.ephemeralKeyPair.pubKey, {
|
|
151
|
+
messageKeys: {},
|
|
152
|
+
chainKey: {
|
|
153
|
+
counter: -1,
|
|
154
|
+
key: masterKey[1]
|
|
155
|
+
},
|
|
156
|
+
chainType: ChainType.SENDING
|
|
157
|
+
});
|
|
158
|
+
ratchet.rootKey = masterKey[0];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default SessionBuilder;
|