diff --git a/package.json b/package.json index 75d76392..17df48d5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "license": "GPL-3.0", "dependencies": { + "async-mutex": "^0.5.0", "curve25519-js": "^0.0.4", "protobufjs": "6.8.8" }, diff --git a/src/crypto.js b/src/crypto.js index 6db46d86..2fb44dc6 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -2,7 +2,8 @@ 'use strict'; -const nodeCrypto = require('crypto'); +const { webcrypto } = require('crypto'); +const subtle = webcrypto.subtle; const assert = require('assert'); @@ -14,43 +15,45 @@ function assertBuffer(value) { } -function encrypt(key, data, iv) { +async function encrypt(key, data, iv) { assertBuffer(key); assertBuffer(data); assertBuffer(iv); - const cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv); - return Buffer.concat([cipher.update(data), cipher.final()]); + const cryptoKey = await subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']); + const encrypted = await subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, data); + return Buffer.from(encrypted); } -function decrypt(key, data, iv) { +async function decrypt(key, data, iv) { assertBuffer(key); assertBuffer(data); assertBuffer(iv); - const decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv); - return Buffer.concat([decipher.update(data), decipher.final()]); + const cryptoKey = await subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['decrypt']); + const decrypted = await subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, data); + return Buffer.from(decrypted); } -function calculateMAC(key, data) { +async function calculateMAC(key, data) { assertBuffer(key); assertBuffer(data); - const hmac = nodeCrypto.createHmac('sha256', key); - hmac.update(data); - return Buffer.from(hmac.digest()); + const cryptoKey = await subtle.importKey( + 'raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] + ); + const mac = await subtle.sign('HMAC', cryptoKey, data); + return Buffer.from(mac); } -function hash(data) { - assertBuffer(data); - const sha512 = nodeCrypto.createHash('sha512'); - sha512.update(data); - return sha512.digest(); +async function hash(data) { + const result = await subtle.digest('SHA-512', data); + return Buffer.from(result); } // Salts always end up being 32 bytes -function deriveSecrets(input, salt, info, chunks) { +async function deriveSecrets(input, salt, info, chunks) { // Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks assertBuffer(input); assertBuffer(salt); @@ -60,31 +63,37 @@ function deriveSecrets(input, salt, info, chunks) { } chunks = chunks || 3; assert(chunks >= 1 && chunks <= 3); - const PRK = calculateMAC(salt, input); + const PRK = await calculateMAC(salt, input); const infoArray = new Uint8Array(info.byteLength + 1 + 32); infoArray.set(info, 32); infoArray[infoArray.length - 1] = 1; - const signed = [calculateMAC(PRK, Buffer.from(infoArray.slice(32)))]; + const signed = [await calculateMAC(PRK, Buffer.from(infoArray.slice(32)))]; if (chunks > 1) { infoArray.set(signed[signed.length - 1]); infoArray[infoArray.length - 1] = 2; - signed.push(calculateMAC(PRK, Buffer.from(infoArray))); + signed.push(await calculateMAC(PRK, Buffer.from(infoArray))); } if (chunks > 2) { infoArray.set(signed[signed.length - 1]); infoArray[infoArray.length - 1] = 3; - signed.push(calculateMAC(PRK, Buffer.from(infoArray))); + signed.push(await calculateMAC(PRK, Buffer.from(infoArray))); } return signed; } -function verifyMAC(data, key, mac, length) { - const calculatedMac = calculateMAC(key, data).slice(0, length); +async function verifyMAC(data, key, mac, length) { + const calculatedMac = (await calculateMAC(key, data)).subarray(0, length); if (mac.length !== length || calculatedMac.length !== length) { - throw new Error("Bad MAC length"); + throw new Error("Bad MAC length Expected: " + length + + " Got: " + mac.length + " and " + calculatedMac.length); + } + let diff = 0; + for (let i = 0; i < length; i++) { + diff |= mac[i] ^ calculatedMac[i]; } - if (!mac.equals(calculatedMac)) { - throw new Error("Bad MAC"); + if (diff !== 0) { + throw new Error("Bad MAC Expected: " + calculatedMac.toString('hex') + + " Got: " + mac.toString('hex')); } } diff --git a/src/curve.js b/src/curve.js index 55b4b809..149befa0 100644 --- a/src/curve.js +++ b/src/curve.js @@ -2,12 +2,15 @@ 'use strict'; const curveJs = require('curve25519-js'); -const nodeCrypto = require('crypto'); +const { webcrypto } = require('crypto'); +const subtle = webcrypto.subtle; + +// DER prefixes for X25519 keys (used for WebCrypto import/export) // from: https://github.com/digitalbazaar/x25519-key-agreement-key-2019/blob/master/lib/crypto.js const PUBLIC_KEY_DER_PREFIX = Buffer.from([ 48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0 ]); - + const PRIVATE_KEY_DER_PREFIX = Buffer.from([ 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32 ]); @@ -38,7 +41,7 @@ function scrubPubKeyFormat(pubKey) { throw new Error("Invalid public key"); } if (pubKey.byteLength == 33) { - return pubKey.slice(1); + return pubKey.subarray(1); } else { console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey"); return pubKey; @@ -58,68 +61,57 @@ function unclampEd25519PrivateKey(clampedSk) { return unclampedSk; } -exports.getPublicFromPrivateKey = function(privKey) { +exports.getPublicFromPrivateKey = async function(privKey) { const unclampedPK = unclampEd25519PrivateKey(privKey); const keyPair = curveJs.generateKeyPair(unclampedPK); return prefixKeyInPublicKey(Buffer.from(keyPair.public)); }; -exports.generateKeyPair = function() { - try { - const {publicKey: publicDerBytes, privateKey: privateDerBytes} = nodeCrypto.generateKeyPairSync( - 'x25519', - { - publicKeyEncoding: { format: 'der', type: 'spki' }, - privateKeyEncoding: { format: 'der', type: 'pkcs8' } - } - ); - const pubKey = publicDerBytes.slice(PUBLIC_KEY_DER_PREFIX.length, PUBLIC_KEY_DER_PREFIX.length + 32); - - const privKey = privateDerBytes.slice(PRIVATE_KEY_DER_PREFIX.length, PRIVATE_KEY_DER_PREFIX.length + 32); - - return { - pubKey: prefixKeyInPublicKey(pubKey), - privKey - }; - } catch(e) { - const keyPair = curveJs.generateKeyPair(nodeCrypto.randomBytes(32)); - return { - privKey: Buffer.from(keyPair.private), - pubKey: prefixKeyInPublicKey(Buffer.from(keyPair.public)), - }; - } +exports.generateKeyPair = async function() { + const keyPair = await subtle.generateKey({ name: 'X25519' }, true, ['deriveBits']); + + const publicKeyRaw = await subtle.exportKey('raw', keyPair.publicKey); + const privateKeyDer = await subtle.exportKey('pkcs8', keyPair.privateKey); + + const pubKey = Buffer.from(publicKeyRaw); + const privKey = Buffer.from(new Uint8Array(privateKeyDer).subarray(PRIVATE_KEY_DER_PREFIX.length)); + + return { + pubKey: prefixKeyInPublicKey(pubKey), + privKey + }; }; -exports.calculateAgreement = function(pubKey, privKey) { +exports.calculateAgreement = async function(pubKey, privKey) { pubKey = scrubPubKeyFormat(pubKey); validatePrivKey(privKey); if (!pubKey || pubKey.byteLength != 32) { throw new Error("Invalid public key"); } - if(typeof nodeCrypto.diffieHellman === 'function') { - const nodePrivateKey = nodeCrypto.createPrivateKey({ - key: Buffer.concat([PRIVATE_KEY_DER_PREFIX, privKey]), - format: 'der', - type: 'pkcs8' - }); - const nodePublicKey = nodeCrypto.createPublicKey({ - key: Buffer.concat([PUBLIC_KEY_DER_PREFIX, pubKey]), - format: 'der', - type: 'spki' - }); - - return nodeCrypto.diffieHellman({ - privateKey: nodePrivateKey, - publicKey: nodePublicKey, - }); - } else { - const secret = curveJs.sharedKey(privKey, pubKey); - return Buffer.from(secret); - } + const privateKeyObj = await subtle.importKey( + 'pkcs8', + Buffer.concat([PRIVATE_KEY_DER_PREFIX, privKey]), + { name: 'X25519' }, + false, + ['deriveBits'] + ); + const publicKeyObj = await subtle.importKey( + 'raw', + pubKey, + { name: 'X25519' }, + false, + [] + ); + + const shared = await subtle.deriveBits({ name: 'X25519', public: publicKeyObj }, privateKeyObj, 256); + return Buffer.from(shared); }; -exports.calculateSignature = function(privKey, message) { +// XEdDSA signatures use Curve25519 keys converted to Ed25519-style — not supported by +// WebCrypto, so we keep using curve25519-js here but expose an async interface for +// consistency with the rest of the module. +exports.calculateSignature = async function(privKey, message) { validatePrivKey(privKey); if (!message) { throw new Error("Invalid message"); @@ -127,7 +119,7 @@ exports.calculateSignature = function(privKey, message) { return Buffer.from(curveJs.sign(privKey, message)); }; -exports.verifySignature = function(pubKey, msg, sig, isInit) { +exports.verifySignature = async function(pubKey, msg, sig, isInit) { pubKey = scrubPubKeyFormat(pubKey); if (!pubKey || pubKey.byteLength != 32) { throw new Error("Invalid public key"); diff --git a/src/keyhelper.js b/src/keyhelper.js index 5d7f417f..7eeb26cb 100644 --- a/src/keyhelper.js +++ b/src/keyhelper.js @@ -1,7 +1,7 @@ // vim: ts=4:sw=4:expandtab const curve = require('./curve'); -const nodeCrypto = require('crypto'); +const { webcrypto } = require('crypto'); function isNonNegativeInteger(n) { return (typeof n === 'number' && (n % 1) === 0 && n >= 0); @@ -10,11 +10,11 @@ function isNonNegativeInteger(n) { exports.generateIdentityKeyPair = curve.generateKeyPair; exports.generateRegistrationId = function() { - var registrationId = Uint16Array.from(nodeCrypto.randomBytes(2))[0]; + var registrationId = webcrypto.getRandomValues(new Uint16Array(1))[0]; return registrationId & 0x3fff; }; -exports.generateSignedPreKey = function(identityKeyPair, signedKeyId) { +exports.generateSignedPreKey = async function(identityKeyPair, signedKeyId) { if (!(identityKeyPair.privKey instanceof Buffer) || identityKeyPair.privKey.byteLength != 32 || !(identityKeyPair.pubKey instanceof Buffer) || @@ -24,8 +24,8 @@ exports.generateSignedPreKey = function(identityKeyPair, signedKeyId) { if (!isNonNegativeInteger(signedKeyId)) { throw new TypeError('Invalid argument for signedKeyId: ' + signedKeyId); } - const keyPair = curve.generateKeyPair(); - const sig = curve.calculateSignature(identityKeyPair.privKey, keyPair.pubKey); + const keyPair = await curve.generateKeyPair(); + const sig = await curve.calculateSignature(identityKeyPair.privKey, keyPair.pubKey); return { keyId: signedKeyId, keyPair: keyPair, @@ -33,11 +33,11 @@ exports.generateSignedPreKey = function(identityKeyPair, signedKeyId) { }; }; -exports.generatePreKey = function(keyId) { +exports.generatePreKey = async function(keyId) { if (!isNonNegativeInteger(keyId)) { throw new TypeError('Invalid argument for keyId: ' + keyId); } - const keyPair = curve.generateKeyPair(); + const keyPair = await curve.generateKeyPair(); return { keyId, keyPair diff --git a/src/numeric_fingerprint.js b/src/numeric_fingerprint.js index baadb58e..5404ead9 100644 --- a/src/numeric_fingerprint.js +++ b/src/numeric_fingerprint.js @@ -6,7 +6,7 @@ var VERSION = 0; async function iterateHash(data, key, count) { const combined = (new Uint8Array(Buffer.concat([data, key]))).buffer; - const result = crypto.hash(combined); + const result = await crypto.hash(combined); if (--count === 0) { return result; } else { diff --git a/src/queue_job.js b/src/queue_job.js index baab89c4..78e24e58 100644 --- a/src/queue_job.js +++ b/src/queue_job.js @@ -10,6 +10,33 @@ const _queueAsyncBuckets = new Map(); const _gcLimit = 10000; + + +/* +* This is a wrapper around the async function that will reject if it +* takes longer than the specified timeout. This is useful for +* preventing a job from hanging indefinitely. The default timeout +* is 30 seconds. +*/ +function withTimeout(fn, ms = 15000) { + if (typeof fn !== 'function') { + throw new TypeError('fn must be a function to wrap received ' + typeof fn); + } + if (typeof ms !== 'number') { + throw new TypeError('ms must be a number to wrap received ' + typeof ms); + } + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error('Job timed out')), ms); + fn().then((res) => { + clearTimeout(timer); + resolve(res); + }).catch((err) => { + clearTimeout(timer); + reject(err); + }); + }); +} + async function _asyncQueueExecutor(queue, cleanup) { let offt = 0; while (true) { @@ -17,7 +44,7 @@ async function _asyncQueueExecutor(queue, cleanup) { for (let i = offt; i < limit; i++) { const job = queue[i]; try { - job.resolve(await job.awaitable()); + job.resolve(await withTimeout(job.awaitable)); // if the job takes longer than 15 seconds, it will be rejected } catch(e) { job.reject(e); } diff --git a/src/session_builder.js b/src/session_builder.js index 7b7d8520..28736df5 100644 --- a/src/session_builder.js +++ b/src/session_builder.js @@ -23,9 +23,9 @@ class SessionBuilder { if (!await this.storage.isTrustedIdentity(this.addr.id, device.identityKey)) { throw new errors.UntrustedIdentityKeyError(this.addr.id, device.identityKey); } - curve.verifySignature(device.identityKey, device.signedPreKey.publicKey, + await curve.verifySignature(device.identityKey, device.signedPreKey.publicKey, device.signedPreKey.signature, true); - const baseKey = curve.generateKeyPair(); + const baseKey = await curve.generateKeyPair(); const devicePreKey = device.preKey && device.preKey.publicKey; const session = await this.initSession(true, baseKey, undefined, device.identityKey, devicePreKey, device.signedPreKey.publicKey, @@ -43,7 +43,7 @@ class SessionBuilder { } else { const openSession = record.getOpenSession(); if (openSession) { - console.warn("Closing stale open session for new outgoing prekey bundle"); + // console.warn("Closing stale open session for new outgoing prekey bundle"); record.closeSession(openSession); } } @@ -71,7 +71,7 @@ class SessionBuilder { } const existingOpenSession = record.getOpenSession(); if (existingOpenSession) { - console.warn("Closing open session in favor of incoming prekey bundle"); + // console.warn("Closing open session in favor of incoming prekey bundle"); record.closeSession(existingOpenSession); } record.setSession(await this.initSession(false, preKeyPair, signedPreKeyPair, @@ -103,9 +103,9 @@ class SessionBuilder { sharedSecret[i] = 0xff; } const ourIdentityKey = await this.storage.getOurIdentity(); - const a1 = curve.calculateAgreement(theirSignedPubKey, ourIdentityKey.privKey); - const a2 = curve.calculateAgreement(theirIdentityPubKey, ourSignedKey.privKey); - const a3 = curve.calculateAgreement(theirSignedPubKey, ourSignedKey.privKey); + const a1 = await curve.calculateAgreement(theirSignedPubKey, ourIdentityKey.privKey); + const a2 = await curve.calculateAgreement(theirIdentityPubKey, ourSignedKey.privKey); + const a3 = await curve.calculateAgreement(theirSignedPubKey, ourSignedKey.privKey); if (isInitiator) { sharedSecret.set(new Uint8Array(a1), 32); sharedSecret.set(new Uint8Array(a2), 32 * 2); @@ -115,16 +115,16 @@ class SessionBuilder { } sharedSecret.set(new Uint8Array(a3), 32 * 3); if (ourEphemeralKey && theirEphemeralPubKey) { - const a4 = curve.calculateAgreement(theirEphemeralPubKey, ourEphemeralKey.privKey); + const a4 = await curve.calculateAgreement(theirEphemeralPubKey, ourEphemeralKey.privKey); sharedSecret.set(new Uint8Array(a4), 32 * 4); } - const masterKey = crypto.deriveSecrets(Buffer.from(sharedSecret), Buffer.alloc(32), + const masterKey = await crypto.deriveSecrets(Buffer.from(sharedSecret), Buffer.alloc(32), Buffer.from("WhisperText")); const session = SessionRecord.createEntry(); session.registrationId = registrationId; session.currentRatchet = { rootKey: masterKey[0], - ephemeralKeyPair: isInitiator ? curve.generateKeyPair() : ourSignedKey, + ephemeralKeyPair: isInitiator ? await curve.generateKeyPair() : ourSignedKey, lastRemoteEphemeralKey: theirSignedPubKey, previousCounter: 0 }; @@ -140,15 +140,15 @@ class SessionBuilder { // If we're initiating we go ahead and set our first sending ephemeral key now, // otherwise we figure it out when we first maybeStepRatchet with the remote's // ephemeral key - this.calculateSendingRatchet(session, theirSignedPubKey); + await this.calculateSendingRatchet(session, theirSignedPubKey); } return session; } - calculateSendingRatchet(session, remoteKey) { + async calculateSendingRatchet(session, remoteKey) { const ratchet = session.currentRatchet; - const sharedSecret = curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey); - const masterKey = crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from("WhisperRatchet")); + const sharedSecret = await curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey); + const masterKey = await crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from("WhisperRatchet")); session.addChain(ratchet.ephemeralKeyPair.pubKey, { messageKeys: {}, chainKey: { diff --git a/src/session_cipher.js b/src/session_cipher.js index 0e6df11e..b465dc93 100644 --- a/src/session_cipher.js +++ b/src/session_cipher.js @@ -58,7 +58,7 @@ class SessionCipher { await this.storage.storeSession(this.addr.toString(), record); } - async queueJob(awaitable) { + async queueJob(awaitable) { return await queueJob(this.addr.toString(), awaitable); } @@ -82,26 +82,26 @@ class SessionCipher { if (chain.chainType === ChainType.RECEIVING) { throw new Error("Tried to encrypt on a receiving chain"); } - this.fillMessageKeys(chain, chain.chainKey.counter + 1); - const keys = crypto.deriveSecrets(chain.messageKeys[chain.chainKey.counter], + await this.fillMessageKeys(chain, chain.chainKey.counter + 1); + const keys = await crypto.deriveSecrets(chain.messageKeys[chain.chainKey.counter], Buffer.alloc(32), Buffer.from("WhisperMessageKeys")); delete chain.messageKeys[chain.chainKey.counter]; const msg = protobufs.WhisperMessage.create(); msg.ephemeralKey = session.currentRatchet.ephemeralKeyPair.pubKey; msg.counter = chain.chainKey.counter; msg.previousCounter = session.currentRatchet.previousCounter; - msg.ciphertext = crypto.encrypt(keys[0], data, keys[2].slice(0, 16)); + msg.ciphertext = await crypto.encrypt(keys[0], data, keys[2].subarray(0, 16)); const msgBuf = protobufs.WhisperMessage.encode(msg).finish(); const macInput = Buffer.alloc(msgBuf.byteLength + (33 * 2) + 1); macInput.set(ourIdentityKey.pubKey); macInput.set(session.indexInfo.remoteIdentityKey, 33); macInput[33 * 2] = this._encodeTupleByte(VERSION, VERSION); macInput.set(msgBuf, (33 * 2) + 1); - const mac = crypto.calculateMAC(keys[1], macInput); + const mac = await crypto.calculateMAC(keys[1], macInput); const result = Buffer.alloc(msgBuf.byteLength + 9); result[0] = this._encodeTupleByte(VERSION, VERSION); result.set(msgBuf, 1); - result.set(mac.slice(0, 8), msgBuf.byteLength + 1); + result.set(mac.subarray(0, 8), msgBuf.byteLength + 1); await this.storeRecord(record); let type, body; if (session.pendingPreKey) { @@ -224,12 +224,12 @@ class SessionCipher { } const messageProto = messageBuffer.slice(1, -8); const message = protobufs.WhisperMessage.decode(messageProto); - this.maybeStepRatchet(session, message.ephemeralKey, message.previousCounter); + await this.maybeStepRatchet(session, message.ephemeralKey, message.previousCounter); const chain = session.getChain(message.ephemeralKey); if (chain.chainType === ChainType.SENDING) { throw new Error("Tried to decrypt on a sending chain"); } - this.fillMessageKeys(chain, message.counter); + await this.fillMessageKeys(chain, message.counter); if (!chain.messageKeys.hasOwnProperty(message.counter)) { // Most likely the message was already decrypted and we are trying to process // twice. This can happen if the user restarts before the server gets an ACK. @@ -237,7 +237,7 @@ class SessionCipher { } const messageKey = chain.messageKeys[message.counter]; delete chain.messageKeys[message.counter]; - const keys = crypto.deriveSecrets(messageKey, Buffer.alloc(32), + const keys = await crypto.deriveSecrets(messageKey, Buffer.alloc(32), Buffer.from("WhisperMessageKeys")); const ourIdentityKey = await this.storage.getOurIdentity(); const macInput = Buffer.alloc(messageProto.byteLength + (33 * 2) + 1); @@ -247,13 +247,13 @@ class SessionCipher { macInput.set(messageProto, (33 * 2) + 1); // This is where we most likely fail if the session is not a match. // Don't misinterpret this as corruption. - crypto.verifyMAC(macInput, keys[1], messageBuffer.slice(-8), 8); - const plaintext = crypto.decrypt(keys[0], message.ciphertext, keys[2].slice(0, 16)); + await crypto.verifyMAC(macInput, keys[1], messageBuffer.slice(-8), 8); + const plaintext = await crypto.decrypt(keys[0], message.ciphertext, keys[2].subarray(0, 16)); delete session.pendingPreKey; return plaintext; } - fillMessageKeys(chain, counter) { + async fillMessageKeys(chain, counter) { if (chain.chainKey.counter >= counter) { return; } @@ -263,39 +263,42 @@ class SessionCipher { if (chain.chainKey.key === undefined) { throw new errors.SessionError('Chain closed'); } - const key = chain.chainKey.key; - chain.messageKeys[chain.chainKey.counter + 1] = crypto.calculateMAC(key, Buffer.from([1])); - chain.chainKey.key = crypto.calculateMAC(key, Buffer.from([2])); - chain.chainKey.counter += 1; - return this.fillMessageKeys(chain, counter); + while (chain.chainKey.counter < counter) { + const key = chain.chainKey.key; + const nextCounter = chain.chainKey.counter + 1; + + chain.messageKeys[nextCounter] = await crypto.calculateMAC(key, Buffer.from([1])); + chain.chainKey.key = await crypto.calculateMAC(key, Buffer.from([2])); + chain.chainKey.counter = nextCounter; + } } - maybeStepRatchet(session, remoteKey, previousCounter) { + async maybeStepRatchet(session, remoteKey, previousCounter) { if (session.getChain(remoteKey)) { return; } const ratchet = session.currentRatchet; let previousRatchet = session.getChain(ratchet.lastRemoteEphemeralKey); if (previousRatchet) { - this.fillMessageKeys(previousRatchet, previousCounter); + await this.fillMessageKeys(previousRatchet, previousCounter); delete previousRatchet.chainKey.key; // Close } - this.calculateRatchet(session, remoteKey, false); + await this.calculateRatchet(session, remoteKey, false); // Now swap the ephemeral key and calculate the new sending chain const prevCounter = session.getChain(ratchet.ephemeralKeyPair.pubKey); if (prevCounter) { ratchet.previousCounter = prevCounter.chainKey.counter; session.deleteChain(ratchet.ephemeralKeyPair.pubKey); } - ratchet.ephemeralKeyPair = curve.generateKeyPair(); - this.calculateRatchet(session, remoteKey, true); + ratchet.ephemeralKeyPair = await curve.generateKeyPair(); + await this.calculateRatchet(session, remoteKey, true); ratchet.lastRemoteEphemeralKey = remoteKey; } - calculateRatchet(session, remoteKey, sending) { + async calculateRatchet(session, remoteKey, sending) { let ratchet = session.currentRatchet; - const sharedSecret = curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey); - const masterKey = crypto.deriveSecrets(sharedSecret, ratchet.rootKey, + const sharedSecret = await curve.calculateAgreement(remoteKey, ratchet.ephemeralKeyPair.privKey); + const masterKey = await crypto.deriveSecrets(sharedSecret, ratchet.rootKey, Buffer.from("WhisperRatchet"), /*chunks*/ 2); const chainKey = sending ? ratchet.ephemeralKeyPair.pubKey : remoteKey; session.addChain(chainKey, { diff --git a/src/session_record.js b/src/session_record.js index 7626a392..c41657f6 100644 --- a/src/session_record.js +++ b/src/session_record.js @@ -270,7 +270,7 @@ class SessionRecord { console.warn("Session already closed", session); return; } - console.info("Closing session:", session); + // console.info("Closing session:", session); session.indexInfo.closed = Date.now(); } @@ -278,7 +278,7 @@ class SessionRecord { if (!this.isClosed(session)) { console.warn("Session already open"); } - console.info("Opening session:", session); + //console.info("Opening session:", session); session.indexInfo.closed = -1; } @@ -298,7 +298,7 @@ class SessionRecord { } } if (oldestKey) { - console.info("Removing old closed session:", oldestSession); + // console.info("Removing old closed session:", oldestSession); delete this.sessions[oldestKey]; } else { throw new Error('Corrupt sessions object');