libsignal-skyzopedia 1.0.0

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.
@@ -0,0 +1,7 @@
1
+
2
+ const BaseKeyType = {
3
+ OURS: 1,
4
+ THEIRS: 2
5
+ };
6
+
7
+ module.exports = BaseKeyType;
@@ -0,0 +1,6 @@
1
+ const ChainType = {
2
+ SENDING: 1,
3
+ RECEIVING: 2
4
+ };
5
+
6
+ module.exports = ChainType;
package/src/crypto.js ADDED
@@ -0,0 +1,98 @@
1
+ // vim: ts=4:sw=4
2
+
3
+ 'use strict';
4
+
5
+ const nodeCrypto = require('crypto');
6
+ const assert = require('assert');
7
+
8
+
9
+ function assertBuffer(value) {
10
+ if (!(value instanceof Buffer)) {
11
+ throw TypeError(`Expected Buffer instead of: ${value.constructor.name}`);
12
+ }
13
+ return value;
14
+ }
15
+
16
+
17
+ function encrypt(key, data, iv) {
18
+ assertBuffer(key);
19
+ assertBuffer(data);
20
+ assertBuffer(iv);
21
+ const cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
22
+ return Buffer.concat([cipher.update(data), cipher.final()]);
23
+ }
24
+
25
+
26
+ function decrypt(key, data, iv) {
27
+ assertBuffer(key);
28
+ assertBuffer(data);
29
+ assertBuffer(iv);
30
+ const decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
31
+ return Buffer.concat([decipher.update(data), decipher.final()]);
32
+ }
33
+
34
+
35
+ function calculateMAC(key, data) {
36
+ assertBuffer(key);
37
+ assertBuffer(data);
38
+ const hmac = nodeCrypto.createHmac('sha256', key);
39
+ hmac.update(data);
40
+ return Buffer.from(hmac.digest());
41
+ }
42
+
43
+
44
+ function hash(data) {
45
+ assertBuffer(data);
46
+ const sha512 = nodeCrypto.createHash('sha512');
47
+ sha512.update(data);
48
+ return sha512.digest();
49
+ }
50
+
51
+
52
+ // Salts always end up being 32 bytes
53
+ function deriveSecrets(input, salt, info, chunks) {
54
+ // Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
55
+ assertBuffer(input);
56
+ assertBuffer(salt);
57
+ assertBuffer(info);
58
+ if (salt.byteLength != 32) {
59
+ throw new Error("Got salt of incorrect length");
60
+ }
61
+ chunks = chunks || 3;
62
+ assert(chunks >= 1 && chunks <= 3);
63
+ const PRK = calculateMAC(salt, input);
64
+ const infoArray = new Uint8Array(info.byteLength + 1 + 32);
65
+ infoArray.set(info, 32);
66
+ infoArray[infoArray.length - 1] = 1;
67
+ const signed = [calculateMAC(PRK, Buffer.from(infoArray.slice(32)))];
68
+ if (chunks > 1) {
69
+ infoArray.set(signed[signed.length - 1]);
70
+ infoArray[infoArray.length - 1] = 2;
71
+ signed.push(calculateMAC(PRK, Buffer.from(infoArray)));
72
+ }
73
+ if (chunks > 2) {
74
+ infoArray.set(signed[signed.length - 1]);
75
+ infoArray[infoArray.length - 1] = 3;
76
+ signed.push(calculateMAC(PRK, Buffer.from(infoArray)));
77
+ }
78
+ return signed;
79
+ }
80
+
81
+ function verifyMAC(data, key, mac, length) {
82
+ const calculatedMac = calculateMAC(key, data).slice(0, length);
83
+ if (mac.length !== length || calculatedMac.length !== length) {
84
+ throw new Error("Bad MAC length");
85
+ }
86
+ if (!mac.equals(calculatedMac)) {
87
+ throw new Error("Bad MAC");
88
+ }
89
+ }
90
+
91
+ module.exports = {
92
+ deriveSecrets,
93
+ decrypt,
94
+ encrypt,
95
+ hash,
96
+ calculateMAC,
97
+ verifyMAC
98
+ };
package/src/curve.js ADDED
@@ -0,0 +1,120 @@
1
+
2
+ 'use strict';
3
+
4
+ const curveJs = require('curve25519-js');
5
+ const nodeCrypto = require('crypto');
6
+ // from: https://github.com/digitalbazaar/x25519-key-agreement-key-2019/blob/master/lib/crypto.js
7
+ const PUBLIC_KEY_DER_PREFIX = Buffer.from([
8
+ 48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0
9
+ ]);
10
+
11
+ const PRIVATE_KEY_DER_PREFIX = Buffer.from([
12
+ 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32
13
+ ]);
14
+
15
+ function validatePrivKey(privKey) {
16
+ if (privKey === undefined) {
17
+ throw new Error("Undefined private key");
18
+ }
19
+ if (!(privKey instanceof Buffer)) {
20
+ throw new Error(`Invalid private key type: ${privKey.constructor.name}`);
21
+ }
22
+ if (privKey.byteLength != 32) {
23
+ throw new Error(`Incorrect private key length: ${privKey.byteLength}`);
24
+ }
25
+ }
26
+
27
+ function scrubPubKeyFormat(pubKey) {
28
+ if (!(pubKey instanceof Buffer)) {
29
+ throw new Error(`Invalid public key type: ${pubKey.constructor.name}`);
30
+ }
31
+ if (pubKey === undefined || ((pubKey.byteLength != 33 || pubKey[0] != 5) && pubKey.byteLength != 32)) {
32
+ throw new Error("Invalid public key");
33
+ }
34
+ if (pubKey.byteLength == 33) {
35
+ return pubKey.slice(1);
36
+ } else {
37
+ console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
38
+ return pubKey;
39
+ }
40
+ }
41
+
42
+ exports.generateKeyPair = function() {
43
+ if(typeof nodeCrypto.generateKeyPairSync === 'function') {
44
+ const {publicKey: publicDerBytes, privateKey: privateDerBytes} = nodeCrypto.generateKeyPairSync(
45
+ 'x25519',
46
+ {
47
+ publicKeyEncoding: { format: 'der', type: 'spki' },
48
+ privateKeyEncoding: { format: 'der', type: 'pkcs8' }
49
+ }
50
+ );
51
+ // 33 bytes
52
+ // first byte = 5 (version byte)
53
+ const pubKey = publicDerBytes.slice(PUBLIC_KEY_DER_PREFIX.length-1, PUBLIC_KEY_DER_PREFIX.length + 32);
54
+ pubKey[0] = 5;
55
+
56
+ const privKey = privateDerBytes.slice(PRIVATE_KEY_DER_PREFIX.length, PRIVATE_KEY_DER_PREFIX.length + 32);
57
+
58
+ return {
59
+ pubKey,
60
+ privKey
61
+ };
62
+ } else {
63
+ const keyPair = curveJs.generateKeyPair(nodeCrypto.randomBytes(32));
64
+ return {
65
+ privKey: Buffer.from(keyPair.private),
66
+ pubKey: Buffer.from(keyPair.public),
67
+ };
68
+ }
69
+ };
70
+
71
+ exports.calculateAgreement = function(pubKey, privKey) {
72
+ pubKey = scrubPubKeyFormat(pubKey);
73
+ validatePrivKey(privKey);
74
+ if (!pubKey || pubKey.byteLength != 32) {
75
+ throw new Error("Invalid public key");
76
+ }
77
+
78
+ if(typeof nodeCrypto.diffieHellman === 'function') {
79
+ const nodePrivateKey = nodeCrypto.createPrivateKey({
80
+ key: Buffer.concat([PRIVATE_KEY_DER_PREFIX, privKey]),
81
+ format: 'der',
82
+ type: 'pkcs8'
83
+ });
84
+ const nodePublicKey = nodeCrypto.createPublicKey({
85
+ key: Buffer.concat([PUBLIC_KEY_DER_PREFIX, pubKey]),
86
+ format: 'der',
87
+ type: 'spki'
88
+ });
89
+
90
+ return nodeCrypto.diffieHellman({
91
+ privateKey: nodePrivateKey,
92
+ publicKey: nodePublicKey,
93
+ });
94
+ } else {
95
+ const secret = curveJs.sharedKey(privKey, pubKey);
96
+ return Buffer.from(secret);
97
+ }
98
+ };
99
+
100
+ exports.calculateSignature = function(privKey, message) {
101
+ validatePrivKey(privKey);
102
+ if (!message) {
103
+ throw new Error("Invalid message");
104
+ }
105
+ return Buffer.from(curveJs.sign(privKey, message));
106
+ };
107
+
108
+ exports.verifySignature = function(pubKey, msg, sig) {
109
+ pubKey = scrubPubKeyFormat(pubKey);
110
+ if (!pubKey || pubKey.byteLength != 32) {
111
+ throw new Error("Invalid public key");
112
+ }
113
+ if (!msg) {
114
+ throw new Error("Invalid message");
115
+ }
116
+ if (!sig || sig.byteLength != 64) {
117
+ throw new Error("Invalid signature");
118
+ }
119
+ return curveJs.verify(pubKey, msg, sig);
120
+ };
package/src/errors.js ADDED
@@ -0,0 +1,33 @@
1
+ // vim: ts=4:sw=4:expandtab
2
+
3
+ exports.SignalError = class SignalError extends Error {};
4
+
5
+ exports.UntrustedIdentityKeyError = class UntrustedIdentityKeyError extends exports.SignalError {
6
+ constructor(addr, identityKey) {
7
+ super();
8
+ this.name = 'UntrustedIdentityKeyError';
9
+ this.addr = addr;
10
+ this.identityKey = identityKey;
11
+ }
12
+ };
13
+
14
+ exports.SessionError = class SessionError extends exports.SignalError {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'SessionError';
18
+ }
19
+ };
20
+
21
+ exports.MessageCounterError = class MessageCounterError extends exports.SessionError {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'MessageCounterError';
25
+ }
26
+ };
27
+
28
+ exports.PreKeyError = class PreKeyError extends exports.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
+ const curve = require('./curve');
4
+ const nodeCrypto = require('crypto');
5
+
6
+ function isNonNegativeInteger(n) {
7
+ return (typeof n === 'number' && (n % 1) === 0 && n >= 0);
8
+ }
9
+
10
+ exports.generateIdentityKeyPair = curve.generateKeyPair;
11
+
12
+ exports.generateRegistrationId = function() {
13
+ var registrationId = Uint16Array.from(nodeCrypto.randomBytes(2))[0];
14
+ return registrationId & 0x3fff;
15
+ };
16
+
17
+ exports.generateSignedPreKey = function(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
+ exports.generatePreKey = function(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
+ const crypto = require('./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
+ exports.FingerprintGenerator = function(iterations) {
52
+ this.iterations = iterations;
53
+ };
54
+
55
+ exports.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,10 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ textsecure: {
5
+ WhisperMessage,
6
+ PreKeyWhisperMessage
7
+ }
8
+ } = require('./WhisperTextProtocol.js');
9
+
10
+ module.exports = { WhisperMessage, PreKeyWhisperMessage };
@@ -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
+ module.exports = ProtocolAddress;
@@ -0,0 +1,69 @@
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
+ 'use strict';
8
+
9
+
10
+ const _queueAsyncBuckets = new Map();
11
+ const _gcLimit = 10000;
12
+
13
+ async function _asyncQueueExecutor(queue, cleanup) {
14
+ let offt = 0;
15
+ while (true) {
16
+ let limit = Math.min(queue.length, _gcLimit); // Break up thundering hurds for GC duty.
17
+ for (let i = offt; i < limit; i++) {
18
+ const job = queue[i];
19
+ try {
20
+ job.resolve(await job.awaitable());
21
+ } catch(e) {
22
+ job.reject(e);
23
+ }
24
+ }
25
+ if (limit < queue.length) {
26
+ /* Perform lazy GC of queue for faster iteration. */
27
+ if (limit >= _gcLimit) {
28
+ queue.splice(0, limit);
29
+ offt = 0;
30
+ } else {
31
+ offt = limit;
32
+ }
33
+ } else {
34
+ break;
35
+ }
36
+ }
37
+ cleanup();
38
+ }
39
+
40
+ module.exports = function(bucket, awaitable) {
41
+ /* Run the async awaitable only when all other async calls registered
42
+ * here have completed (or thrown). The bucket argument is a hashable
43
+ * key representing the task queue to use. */
44
+ if (!awaitable.name) {
45
+ // Make debuging easier by adding a name to this function.
46
+ Object.defineProperty(awaitable, 'name', {writable: true});
47
+ if (typeof bucket === 'string') {
48
+ awaitable.name = bucket;
49
+ } else {
50
+ console.warn("Unhandled bucket type (for naming):", typeof bucket, bucket);
51
+ }
52
+ }
53
+ let inactive;
54
+ if (!_queueAsyncBuckets.has(bucket)) {
55
+ _queueAsyncBuckets.set(bucket, []);
56
+ inactive = true;
57
+ }
58
+ const queue = _queueAsyncBuckets.get(bucket);
59
+ const job = new Promise((resolve, reject) => queue.push({
60
+ awaitable,
61
+ resolve,
62
+ reject
63
+ }));
64
+ if (inactive) {
65
+ /* An executor is not currently active; Start one now. */
66
+ _asyncQueueExecutor(queue, () => _queueAsyncBuckets.delete(bucket));
67
+ }
68
+ return job;
69
+ };
@@ -0,0 +1,164 @@
1
+
2
+ 'use strict';
3
+
4
+ const BaseKeyType = require('./base_key_type');
5
+ const ChainType = require('./chain_type');
6
+ const SessionRecord = require('./session_record');
7
+ const crypto = require('./crypto');
8
+ const curve = require('./curve');
9
+ const errors = require('./errors');
10
+ const queueJob = require('./queue_job');
11
+
12
+
13
+ class SessionBuilder {
14
+
15
+ constructor(storage, protocolAddress) {
16
+ this.addr = protocolAddress;
17
+ this.storage = storage;
18
+ }
19
+
20
+ async initOutgoing(device) {
21
+ const fqAddr = this.addr.toString();
22
+ return await queueJob(fqAddr, async () => {
23
+ if (!await this.storage.isTrustedIdentity(this.addr.id, device.identityKey)) {
24
+ throw new errors.UntrustedIdentityKeyError(this.addr.id, device.identityKey);
25
+ }
26
+ curve.verifySignature(device.identityKey, device.signedPreKey.publicKey,
27
+ device.signedPreKey.signature);
28
+ const baseKey = curve.generateKeyPair();
29
+ const devicePreKey = device.preKey && device.preKey.publicKey;
30
+ const session = await this.initSession(true, baseKey, undefined, device.identityKey,
31
+ devicePreKey, device.signedPreKey.publicKey,
32
+ device.registrationId);
33
+ session.pendingPreKey = {
34
+ signedKeyId: device.signedPreKey.keyId,
35
+ baseKey: baseKey.pubKey
36
+ };
37
+ if (device.preKey) {
38
+ session.pendingPreKey.preKeyId = device.preKey.keyId;
39
+ }
40
+ let record = await this.storage.loadSession(fqAddr);
41
+ if (!record) {
42
+ record = new SessionRecord();
43
+ } else {
44
+ const openSession = record.getOpenSession();
45
+ if (openSession) {
46
+ //console.warn("Closing stale open session for new outgoing prekey bundle");
47
+ record.closeSession(openSession);
48
+ }
49
+ }
50
+ record.setSession(session);
51
+ await this.storage.storeSession(fqAddr, record);
52
+ });
53
+ }
54
+
55
+ async initIncoming(record, message) {
56
+ const fqAddr = this.addr.toString();
57
+ if (!await this.storage.isTrustedIdentity(fqAddr, message.identityKey)) {
58
+ throw new errors.UntrustedIdentityKeyError(this.addr.id, message.identityKey);
59
+ }
60
+ if (record.getSession(message.baseKey)) {
61
+ // This just means we haven't replied.
62
+ return;
63
+ }
64
+ const preKeyPair = await this.storage.loadPreKey(message.preKeyId);
65
+ if (message.preKeyId && !preKeyPair) {
66
+ throw new errors.PreKeyError('Invalid PreKey ID');
67
+ }
68
+ const signedPreKeyPair = await this.storage.loadSignedPreKey(message.signedPreKeyId);
69
+ if (!signedPreKeyPair) {
70
+ throw new errors.PreKeyError("Missing SignedPreKey");
71
+ }
72
+ const existingOpenSession = record.getOpenSession();
73
+ if (existingOpenSession) {
74
+ //console.warn("Closing open session in favor of incoming prekey bundle");
75
+ record.closeSession(existingOpenSession);
76
+ }
77
+ record.setSession(await this.initSession(false, preKeyPair, signedPreKeyPair,
78
+ message.identityKey, message.baseKey,
79
+ undefined, message.registrationId));
80
+ return message.preKeyId;
81
+ }
82
+
83
+ async initSession(isInitiator, ourEphemeralKey, ourSignedKey, theirIdentityPubKey,
84
+ theirEphemeralPubKey, theirSignedPubKey, registrationId) {
85
+ if (isInitiator) {
86
+ if (ourSignedKey) {
87
+ throw new Error("Invalid call to initSession");
88
+ }
89
+ ourSignedKey = ourEphemeralKey;
90
+ } else {
91
+ if (theirSignedPubKey) {
92
+ throw new Error("Invalid call to initSession");
93
+ }
94
+ theirSignedPubKey = theirEphemeralPubKey;
95
+ }
96
+ let sharedSecret;
97
+ if (!ourEphemeralKey || !theirEphemeralPubKey) {
98
+ sharedSecret = new Uint8Array(32 * 4);
99
+ } else {
100
+ sharedSecret = new Uint8Array(32 * 5);
101
+ }
102
+ for (var i = 0; i < 32; i++) {
103
+ sharedSecret[i] = 0xff;
104
+ }
105
+ const ourIdentityKey = await this.storage.getOurIdentity();
106
+ const a1 = curve.calculateAgreement(theirSignedPubKey, ourIdentityKey.privKey);
107
+ const a2 = curve.calculateAgreement(theirIdentityPubKey, ourSignedKey.privKey);
108
+ const a3 = curve.calculateAgreement(theirSignedPubKey, ourSignedKey.privKey);
109
+ if (isInitiator) {
110
+ sharedSecret.set(new Uint8Array(a1), 32);
111
+ sharedSecret.set(new Uint8Array(a2), 32 * 2);
112
+ } else {
113
+ sharedSecret.set(new Uint8Array(a1), 32 * 2);
114
+ sharedSecret.set(new Uint8Array(a2), 32);
115
+ }
116
+ sharedSecret.set(new Uint8Array(a3), 32 * 3);
117
+ if (ourEphemeralKey && theirEphemeralPubKey) {
118
+ const a4 = curve.calculateAgreement(theirEphemeralPubKey, ourEphemeralKey.privKey);
119
+ sharedSecret.set(new Uint8Array(a4), 32 * 4);
120
+ }
121
+ const masterKey = crypto.deriveSecrets(Buffer.from(sharedSecret), Buffer.alloc(32),
122
+ Buffer.from("WhisperText"));
123
+ const session = SessionRecord.createEntry();
124
+ session.registrationId = registrationId;
125
+ session.currentRatchet = {
126
+ rootKey: masterKey[0],
127
+ ephemeralKeyPair: isInitiator ? curve.generateKeyPair() : ourSignedKey,
128
+ lastRemoteEphemeralKey: theirSignedPubKey,
129
+ previousCounter: 0
130
+ };
131
+ session.indexInfo = {
132
+ created: Date.now(),
133
+ used: Date.now(),
134
+ remoteIdentityKey: theirIdentityPubKey,
135
+ baseKey: isInitiator ? ourEphemeralKey.pubKey : theirEphemeralPubKey,
136
+ baseKeyType: isInitiator ? BaseKeyType.OURS : BaseKeyType.THEIRS,
137
+ closed: -1
138
+ };
139
+ if (isInitiator) {
140
+ // If we're initiating we go ahead and set our first sending ephemeral key now,
141
+ // otherwise we figure it out when we first maybeStepRatchet with the remote's
142
+ // ephemeral key
143
+ this.calculateSendingRatchet(session, theirSignedPubKey);
144
+ }
145
+ return session;
146
+ }
147
+
148
+ calculateSendingRatchet(session, remoteKey) {
149
+ const ratchet = session.currentRatchet;
150
+ const sharedSecret = curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey);
151
+ const masterKey = crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from("WhisperRatchet"));
152
+ session.addChain(ratchet.ephemeralKeyPair.pubKey, {
153
+ messageKeys: {},
154
+ chainKey: {
155
+ counter: -1,
156
+ key: masterKey[1]
157
+ },
158
+ chainType: ChainType.SENDING
159
+ });
160
+ ratchet.rootKey = masterKey[0];
161
+ }
162
+ }
163
+
164
+ module.exports = SessionBuilder;