UNPKG

2key-ratchet

Version:

2key-ratchet is an implementation of a Double Ratchet protocol and X3DH in TypeScript utilizing WebCrypto.

884 lines (862 loc) 33.8 kB
/** * Copyright (c) 2016-2020, Peculiar Ventures, All rights reserved. */ import { Convert, combine, isEqual } from 'pvtsutils'; import { __decorate } from 'tslib'; import { ProtobufProperty, ProtobufElement, ObjectProto, ArrayBufferConverter } from 'tsprotobuf'; import { EventEmitter } from 'events'; const SIGN_ALGORITHM_NAME = "ECDSA"; const DH_ALGORITHM_NAME = "ECDH"; const SECRET_KEY_NAME = "AES-CBC"; const HASH_NAME = "SHA-256"; const HMAC_NAME = "HMAC"; const MAX_RATCHET_STACK_SIZE = 20; const INFO_TEXT = Convert.FromBinary("InfoText"); const INFO_RATCHET = Convert.FromBinary("InfoRatchet"); const INFO_MESSAGE_KEYS = Convert.FromBinary("InfoMessageKeys"); let engine = null; if (typeof self !== "undefined") { engine = { crypto: self.crypto, name: "WebCrypto", }; } function setEngine(name, crypto) { engine = { crypto, name, }; } function getEngine() { if (!engine) { throw new Error("WebCrypto engine is empty. Use setEngine to resolve it."); } return engine; } let Curve = (() => { class Curve { static async generateKeyPair(type, extractable) { const name = type; const usage = type === "ECDSA" ? ["sign", "verify"] : ["deriveKey", "deriveBits"]; const keys = await getEngine().crypto.subtle .generateKey({ name, namedCurve: this.NAMED_CURVE }, extractable, usage); const publicKey = await ECPublicKey.create(keys.publicKey); const res = { privateKey: keys.privateKey, publicKey, }; return res; } static deriveBytes(privateKey, publicKey) { return getEngine().crypto.subtle.deriveBits({ name: "ECDH", public: publicKey.key }, privateKey, 256); } static verify(signingKey, message, signature) { return getEngine().crypto.subtle .verify({ name: "ECDSA", hash: this.DIGEST_ALGORITHM }, signingKey.key, signature, message); } static async sign(signingKey, message) { return getEngine().crypto.subtle.sign({ name: "ECDSA", hash: this.DIGEST_ALGORITHM }, signingKey, message); } static async ecKeyPairToJson(key) { return { privateKey: key.privateKey, publicKey: key.publicKey.key, thumbprint: await key.publicKey.thumbprint(), }; } static async ecKeyPairFromJson(keys) { return { privateKey: keys.privateKey, publicKey: await ECPublicKey.create(keys.publicKey), }; } } Curve.NAMED_CURVE = "P-256"; Curve.DIGEST_ALGORITHM = "SHA-512"; return Curve; })(); const AES_ALGORITHM = { name: "AES-CBC", length: 256 }; class Secret { static randomBytes(size) { const array = new Uint8Array(size); getEngine().crypto.getRandomValues(array); return array.buffer; } static digest(alg, message) { return getEngine().crypto.subtle.digest(alg, message); } static encrypt(key, data, iv) { return getEngine().crypto.subtle.encrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); } static decrypt(key, data, iv) { return getEngine().crypto.subtle.decrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); } static importHMAC(raw) { return getEngine().crypto.subtle .importKey("raw", raw, { name: HMAC_NAME, hash: { name: HASH_NAME } }, false, ["sign", "verify"]); } static importAES(raw) { return getEngine().crypto.subtle.importKey("raw", raw, AES_ALGORITHM, false, ["encrypt", "decrypt"]); } static async sign(key, data) { return await getEngine().crypto.subtle.sign({ name: HMAC_NAME, hash: HASH_NAME }, key, data); } static async HKDF(IKM, keysCount = 1, salt, info = new ArrayBuffer(0)) { if (!salt) { salt = await this.importHMAC(new Uint8Array(32).buffer); } const PRKBytes = await this.sign(salt, IKM); const infoBuffer = new ArrayBuffer(32 + info.byteLength + 1); const PRK = await this.importHMAC(PRKBytes); const T = [new ArrayBuffer(0)]; for (let i = 0; i < keysCount; i++) { T[i + 1] = await this.sign(PRK, combine(T[i], info, new Uint8Array([i + 1]).buffer)); } return T.slice(1); } } class ECPublicKey { static async create(publicKey) { const res = new this(); const algName = publicKey.algorithm.name.toUpperCase(); if (!(algName === "ECDH" || algName === "ECDSA")) { throw new Error("Error: Unsupported asymmetric key algorithm."); } if (publicKey.type !== "public") { throw new Error("Error: Expected key type to be public but it was not."); } res.key = publicKey; const jwk = await getEngine().crypto.subtle.exportKey("jwk", publicKey); if (!(jwk.x && jwk.y)) { throw new Error("Wrong JWK data for EC public key. Parameters x and y are required."); } const x = Convert.FromBase64Url(jwk.x); const y = Convert.FromBase64Url(jwk.y); const xy = Convert.ToBinary(x) + Convert.ToBinary(y); res.serialized = Convert.FromBinary(xy); res.id = await res.thumbprint(); return res; } static async importKey(bytes, type) { const x = Convert.ToBase64Url(bytes.slice(0, 32)); const y = Convert.ToBase64Url(bytes.slice(32)); const jwk = { crv: Curve.NAMED_CURVE, kty: "EC", x, y, }; const usage = (type === "ECDSA" ? ["verify"] : []); const key = await getEngine().crypto.subtle .importKey("jwk", jwk, { name: type, namedCurve: Curve.NAMED_CURVE }, true, usage); const res = await ECPublicKey.create(key); return res; } serialize() { return this.serialized; } async thumbprint() { const bytes = await this.serialize(); const thumbprint = await Secret.digest("SHA-256", bytes); return Convert.ToHex(thumbprint); } async isEqual(other) { if (!(other && other instanceof ECPublicKey)) { return false; } return isEqual(this.serialized, other.serialized); } } class Identity { constructor(id, signingKey, exchangeKey) { this.id = id; this.signingKey = signingKey; this.exchangeKey = exchangeKey; this.preKeys = []; this.signedPreKeys = []; } static async fromJSON(obj) { const signingKey = await Curve.ecKeyPairFromJson(obj.signingKey); const exchangeKey = await Curve.ecKeyPairFromJson(obj.exchangeKey); const res = new this(obj.id, signingKey, exchangeKey); res.createdAt = new Date(obj.createdAt); await res.fromJSON(obj); return res; } static async create(id, signedPreKeyAmount = 0, preKeyAmount = 0, extractable = false) { const signingKey = await Curve.generateKeyPair(SIGN_ALGORITHM_NAME, extractable); const exchangeKey = await Curve.generateKeyPair(DH_ALGORITHM_NAME, extractable); const res = new Identity(id, signingKey, exchangeKey); res.createdAt = new Date(); for (let i = 0; i < preKeyAmount; i++) { res.preKeys.push(await Curve.generateKeyPair("ECDH", extractable)); } for (let i = 0; i < signedPreKeyAmount; i++) { res.signedPreKeys.push(await Curve.generateKeyPair("ECDH", extractable)); } return res; } async toJSON() { const preKeys = []; const signedPreKeys = []; for (const key of this.preKeys) { preKeys.push(await Curve.ecKeyPairToJson(key)); } for (const key of this.signedPreKeys) { signedPreKeys.push(await Curve.ecKeyPairToJson(key)); } return { createdAt: this.createdAt.toISOString(), exchangeKey: await Curve.ecKeyPairToJson(this.exchangeKey), id: this.id, preKeys, signedPreKeys, signingKey: await Curve.ecKeyPairToJson(this.signingKey), }; } async fromJSON(obj) { this.id = obj.id; this.signingKey = await Curve.ecKeyPairFromJson(obj.signingKey); this.exchangeKey = await Curve.ecKeyPairFromJson(obj.exchangeKey); this.preKeys = []; for (const key of obj.preKeys) { this.preKeys.push(await Curve.ecKeyPairFromJson(key)); } this.signedPreKeys = []; for (const key of obj.signedPreKeys) { this.signedPreKeys.push(await Curve.ecKeyPairFromJson(key)); } } } class RemoteIdentity { static fill(protocol) { const res = new RemoteIdentity(); res.fill(protocol); return res; } static async fromJSON(obj) { const res = new this(); await res.fromJSON(obj); return res; } fill(protocol) { this.signingKey = protocol.signingKey; this.exchangeKey = protocol.exchangeKey; this.signature = protocol.signature; this.createdAt = protocol.createdAt; } verify() { return Curve.verify(this.signingKey, this.exchangeKey.serialize(), this.signature); } async toJSON() { return { createdAt: this.createdAt.toISOString(), exchangeKey: await this.exchangeKey.key, id: this.id, signature: this.signature, signingKey: await this.signingKey.key, thumbprint: await this.signingKey.thumbprint(), }; } async fromJSON(obj) { this.id = obj.id; this.signature = obj.signature; this.signingKey = await ECPublicKey.create(obj.signingKey); this.exchangeKey = await ECPublicKey.create(obj.exchangeKey); this.createdAt = new Date(obj.createdAt); const ok = await this.verify(); if (!ok) { throw new Error("Error: Wrong signature for RemoteIdentity"); } } } let BaseProtocol = (() => { let BaseProtocol = class BaseProtocol extends ObjectProto { }; __decorate([ ProtobufProperty({ id: 0, type: "uint32", defaultValue: 1 }) ], BaseProtocol.prototype, "version", void 0); BaseProtocol = __decorate([ ProtobufElement({ name: "Base" }) ], BaseProtocol); return BaseProtocol; })(); class ECDSAPublicKeyConverter { static async set(value) { return new Uint8Array(value.serialize()); } static async get(value) { return ECPublicKey.importKey(value.buffer, "ECDSA"); } } class ECDHPublicKeyConverter { static async set(value) { return new Uint8Array(value.serialize()); } static async get(value) { return ECPublicKey.importKey(value.buffer, "ECDH"); } } class DateConverter { static async set(value) { return new Uint8Array(Convert.FromString(value.toISOString())); } static async get(value) { return new Date(Convert.ToString(value)); } } let IdentityProtocol = (() => { var IdentityProtocol_1; let IdentityProtocol = IdentityProtocol_1 = class IdentityProtocol extends BaseProtocol { static async fill(identity) { const res = new IdentityProtocol_1(); await res.fill(identity); return res; } async sign(key) { this.signature = await Curve.sign(key, this.exchangeKey.serialize()); } async verify() { return await Curve.verify(this.signingKey, this.exchangeKey.serialize(), this.signature); } async fill(identity) { this.signingKey = identity.signingKey.publicKey; this.exchangeKey = identity.exchangeKey.publicKey; this.createdAt = identity.createdAt; await this.sign(identity.signingKey.privateKey); } }; __decorate([ ProtobufProperty({ id: 1, converter: ECDSAPublicKeyConverter }) ], IdentityProtocol.prototype, "signingKey", void 0); __decorate([ ProtobufProperty({ id: 2, converter: ECDHPublicKeyConverter }) ], IdentityProtocol.prototype, "exchangeKey", void 0); __decorate([ ProtobufProperty({ id: 3 }) ], IdentityProtocol.prototype, "signature", void 0); __decorate([ ProtobufProperty({ id: 4, converter: DateConverter }) ], IdentityProtocol.prototype, "createdAt", void 0); IdentityProtocol = IdentityProtocol_1 = __decorate([ ProtobufElement({ name: "Identity" }) ], IdentityProtocol); return IdentityProtocol; })(); let MessageProtocol = (() => { let MessageProtocol = class MessageProtocol extends BaseProtocol { }; __decorate([ ProtobufProperty({ id: 1, converter: ECDHPublicKeyConverter, required: true }) ], MessageProtocol.prototype, "senderRatchetKey", void 0); __decorate([ ProtobufProperty({ id: 2, type: "uint32", required: true }) ], MessageProtocol.prototype, "counter", void 0); __decorate([ ProtobufProperty({ id: 3, type: "uint32", required: true }) ], MessageProtocol.prototype, "previousCounter", void 0); __decorate([ ProtobufProperty({ id: 4, converter: ArrayBufferConverter, required: true }) ], MessageProtocol.prototype, "cipherText", void 0); MessageProtocol = __decorate([ ProtobufElement({ name: "Message" }) ], MessageProtocol); return MessageProtocol; })(); let MessageSignedProtocol = (() => { let MessageSignedProtocol = class MessageSignedProtocol extends BaseProtocol { async sign(hmacKey) { this.signature = await this.signHMAC(hmacKey); } async verify(hmacKey) { const signature = await this.signHMAC(hmacKey); return isEqual(signature, this.signature); } async getSignedRaw() { const receiverKey = this.receiverKey.serialize(); const senderKey = this.senderKey.serialize(); const message = await this.message.exportProto(); const data = combine(receiverKey, senderKey, message); return data; } async signHMAC(macKey) { const data = await this.getSignedRaw(); const signature = await Secret.sign(macKey, data); return signature; } }; __decorate([ ProtobufProperty({ id: 1, converter: ECDSAPublicKeyConverter, required: true }) ], MessageSignedProtocol.prototype, "senderKey", void 0); __decorate([ ProtobufProperty({ id: 2, parser: MessageProtocol, required: true }) ], MessageSignedProtocol.prototype, "message", void 0); __decorate([ ProtobufProperty({ id: 3, required: true }) ], MessageSignedProtocol.prototype, "signature", void 0); MessageSignedProtocol = __decorate([ ProtobufElement({ name: "MessageSigned" }) ], MessageSignedProtocol); return MessageSignedProtocol; })(); let PreKeyMessageProtocol = (() => { let PreKeyMessageProtocol = class PreKeyMessageProtocol extends BaseProtocol { }; __decorate([ ProtobufProperty({ id: 1, type: "uint32", required: true }) ], PreKeyMessageProtocol.prototype, "registrationId", void 0); __decorate([ ProtobufProperty({ id: 2, type: "uint32" }) ], PreKeyMessageProtocol.prototype, "preKeyId", void 0); __decorate([ ProtobufProperty({ id: 3, type: "uint32", required: true }) ], PreKeyMessageProtocol.prototype, "preKeySignedId", void 0); __decorate([ ProtobufProperty({ id: 4, converter: ECDHPublicKeyConverter, required: true }) ], PreKeyMessageProtocol.prototype, "baseKey", void 0); __decorate([ ProtobufProperty({ id: 5, parser: IdentityProtocol, required: true }) ], PreKeyMessageProtocol.prototype, "identity", void 0); __decorate([ ProtobufProperty({ id: 6, parser: MessageSignedProtocol, required: true }) ], PreKeyMessageProtocol.prototype, "signedMessage", void 0); PreKeyMessageProtocol = __decorate([ ProtobufElement({ name: "PreKeyMessage" }) ], PreKeyMessageProtocol); return PreKeyMessageProtocol; })(); let PreKeyProtocol = (() => { let PreKeyProtocol = class PreKeyProtocol extends BaseProtocol { }; __decorate([ ProtobufProperty({ id: 1, type: "uint32", required: true }) ], PreKeyProtocol.prototype, "id", void 0); __decorate([ ProtobufProperty({ id: 2, converter: ECDHPublicKeyConverter, required: true }) ], PreKeyProtocol.prototype, "key", void 0); PreKeyProtocol = __decorate([ ProtobufElement({ name: "PreKey" }) ], PreKeyProtocol); return PreKeyProtocol; })(); let PreKeySignedProtocol = (() => { let PreKeySignedProtocol = class PreKeySignedProtocol extends PreKeyProtocol { async sign(key) { this.signature = await Curve.sign(key, this.key.serialize()); } verify(key) { return Curve.verify(key, this.key.serialize(), this.signature); } }; __decorate([ ProtobufProperty({ id: 3, converter: ArrayBufferConverter, required: true }) ], PreKeySignedProtocol.prototype, "signature", void 0); PreKeySignedProtocol = __decorate([ ProtobufElement({ name: "PreKeySigned" }) ], PreKeySignedProtocol); return PreKeySignedProtocol; })(); let PreKeyBundleProtocol = (() => { let PreKeyBundleProtocol = class PreKeyBundleProtocol extends BaseProtocol { }; __decorate([ ProtobufProperty({ id: 1, type: "uint32", required: true }) ], PreKeyBundleProtocol.prototype, "registrationId", void 0); __decorate([ ProtobufProperty({ id: 2, parser: IdentityProtocol, required: true }) ], PreKeyBundleProtocol.prototype, "identity", void 0); __decorate([ ProtobufProperty({ id: 3, parser: PreKeyProtocol }) ], PreKeyBundleProtocol.prototype, "preKey", void 0); __decorate([ ProtobufProperty({ id: 4, parser: PreKeySignedProtocol, required: true }) ], PreKeyBundleProtocol.prototype, "preKeySigned", void 0); PreKeyBundleProtocol = __decorate([ ProtobufElement({ name: "PreKeyBundle" }) ], PreKeyBundleProtocol); return PreKeyBundleProtocol; })(); class Stack { constructor(maxSize = 20) { this.items = []; this.maxSize = maxSize; } get length() { return this.items.length; } get latest() { return this.items[this.length - 1]; } push(item) { if (this.length === this.maxSize) { this.items = this.items.slice(1); } this.items.push(item); } async toJSON() { const res = []; for (const item of this.items) { res.push(await item.toJSON()); } return res; } async fromJSON(obj) { this.items = obj; } } const CIPHER_KEY_KDF_INPUT = new Uint8Array([1]).buffer; const ROOT_KEY_KDF_INPUT = new Uint8Array([2]).buffer; class SymmetricRatchet { constructor(rootKey) { this.counter = 0; this.rootKey = rootKey; } static async fromJSON(obj) { const res = new this(obj.rootKey); res.fromJSON(obj); return res; } async toJSON() { return { counter: this.counter, rootKey: this.rootKey, }; } async fromJSON(obj) { this.counter = obj.counter; this.rootKey = obj.rootKey; } async calculateKey(rootKey) { const cipherKeyBytes = await Secret.sign(rootKey, CIPHER_KEY_KDF_INPUT); const nextRootKeyBytes = await Secret.sign(rootKey, ROOT_KEY_KDF_INPUT); const res = { cipher: cipherKeyBytes, rootKey: await Secret.importHMAC(nextRootKeyBytes), }; return res; } async click() { const rootKey = this.rootKey; const res = await this.calculateKey(rootKey); this.rootKey = res.rootKey; this.counter++; return res.cipher; } } class SendingRatchet extends SymmetricRatchet { async encrypt(message) { const cipherKey = await this.click(); const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); const aesKey = await Secret.importAES(keys[0]); const hmacKey = await Secret.importHMAC(keys[1]); const iv = keys[2].slice(0, 16); const cipherText = await Secret.encrypt(aesKey, message, iv); return { cipherText, hmacKey, }; } } class ReceivingRatchet extends SymmetricRatchet { constructor() { super(...arguments); this.keys = []; } async toJSON() { const res = (await super.toJSON()); res.keys = this.keys; return res; } async fromJSON(obj) { await super.fromJSON(obj); this.keys = obj.keys; } async decrypt(message, counter) { const cipherKey = await this.getKey(counter); const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); const aesKey = await Secret.importAES(keys[0]); const hmacKey = await Secret.importHMAC(keys[1]); const iv = keys[2].slice(0, 16); const cipherText = await Secret.decrypt(aesKey, message, iv); return { cipherText, hmacKey, }; } async getKey(counter) { while (this.counter <= counter) { const cipherKey = await this.click(); this.keys.push(cipherKey); } const key = this.keys[counter]; return key; } } async function authenticateA(IKa, EKa, IKb, SPKb, OPKb) { const DH1 = await Curve.deriveBytes(IKa.exchangeKey.privateKey, SPKb); const DH2 = await Curve.deriveBytes(EKa.privateKey, IKb); const DH3 = await Curve.deriveBytes(EKa.privateKey, SPKb); let DH4 = new ArrayBuffer(0); if (OPKb) { DH4 = await Curve.deriveBytes(EKa.privateKey, OPKb); } const _F = new Uint8Array(32); for (let i = 0; i < _F.length; i++) { _F[i] = 0xff; } const F = _F.buffer; const KM = combine(F, DH1, DH2, DH3, DH4); const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); return await Secret.importHMAC(keys[0]); } async function authenticateB(IKb, SPKb, IKa, EKa, OPKb) { const DH1 = await Curve.deriveBytes(SPKb.privateKey, IKa); const DH2 = await Curve.deriveBytes(IKb.exchangeKey.privateKey, EKa); const DH3 = await Curve.deriveBytes(SPKb.privateKey, EKa); let DH4 = new ArrayBuffer(0); if (OPKb) { DH4 = await Curve.deriveBytes(OPKb, EKa); } const _F = new Uint8Array(32); for (let i = 0; i < _F.length; i++) { _F[i] = 0xff; } const F = _F.buffer; const KM = combine(F, DH1, DH2, DH3, DH4); const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); return await Secret.importHMAC(keys[0]); } class AsymmetricRatchet extends EventEmitter { constructor(options = {}) { super(); this.options = options; this.counter = 0; this.currentStep = new DHRatchetStep(); this.steps = new DHRatchetStepStack(MAX_RATCHET_STACK_SIZE); this.promises = {}; } static async create(identity, protocol, options = {}) { let rootKey; const ratchet = new AsymmetricRatchet(options); if (protocol instanceof PreKeyBundleProtocol) { if (!await protocol.identity.verify()) { throw new Error("Error: Remote client's identity key is invalid."); } if (!await protocol.preKeySigned.verify(protocol.identity.signingKey)) { throw new Error("Error: Remote client's signed prekey is invalid."); } ratchet.currentRatchetKey = await ratchet.generateRatchetKey(); ratchet.currentStep.remoteRatchetKey = protocol.preKeySigned.key; ratchet.remoteIdentity = RemoteIdentity.fill(protocol.identity); ratchet.remoteIdentity.id = protocol.registrationId; ratchet.remotePreKeyId = protocol.preKey.id; ratchet.remotePreKeySignedId = protocol.preKeySigned.id; rootKey = await authenticateA(identity, ratchet.currentRatchetKey, protocol.identity.exchangeKey, protocol.preKeySigned.key, protocol.preKey.key); } else { if (!await protocol.identity.verify()) { throw new Error("Error: Remote client's identity key is invalid."); } const signedPreKey = identity.signedPreKeys[protocol.preKeySignedId]; if (!signedPreKey) { throw new Error(`Error: PreKey with id ${protocol.preKeySignedId} not found`); } let preKey; if (protocol.preKeyId !== void 0) { preKey = identity.preKeys[protocol.preKeyId]; } ratchet.remoteIdentity = RemoteIdentity.fill(protocol.identity); ratchet.currentRatchetKey = signedPreKey; rootKey = await authenticateB(identity, ratchet.currentRatchetKey, protocol.identity.exchangeKey, protocol.signedMessage.message.senderRatchetKey, preKey && preKey.privateKey); } ratchet.identity = identity; ratchet.id = identity.id; ratchet.rootKey = rootKey; return ratchet; } static async fromJSON(identity, remote, obj) { const res = new AsymmetricRatchet(); res.identity = identity; res.remoteIdentity = remote; await res.fromJSON(obj); return res; } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } async decrypt(protocol) { return this.queuePromise("encrypt", async () => { const remoteRatchetKey = protocol.message.senderRatchetKey; const message = protocol.message; if (protocol.message.previousCounter < this.counter - MAX_RATCHET_STACK_SIZE) { throw new Error("Error: Too old message"); } let step = this.steps.getStep(remoteRatchetKey); if (!step) { const ratchetStep = new DHRatchetStep(); ratchetStep.remoteRatchetKey = remoteRatchetKey; this.steps.push(ratchetStep); this.currentStep = ratchetStep; step = ratchetStep; } if (!step.receivingChain) { step.receivingChain = await this.createChain(this.currentRatchetKey.privateKey, remoteRatchetKey, ReceivingRatchet); } const decryptedMessage = await step.receivingChain.decrypt(message.cipherText, message.counter); this.update(); protocol.senderKey = this.remoteIdentity.signingKey; protocol.receiverKey = this.identity.signingKey.publicKey; if (!await protocol.verify(decryptedMessage.hmacKey)) { throw new Error("Error: The Message did not successfully verify!"); } return decryptedMessage.cipherText; }); } async encrypt(message) { return this.queuePromise("encrypt", async () => { if (this.currentStep.receivingChain && !this.currentStep.sendingChain) { this.counter++; this.currentRatchetKey = await this.generateRatchetKey(); } if (!this.currentStep.sendingChain) { if (!this.currentStep.remoteRatchetKey) { throw new Error("currentStep has empty remoteRatchetKey"); } this.currentStep.sendingChain = await this.createChain(this.currentRatchetKey.privateKey, this.currentStep.remoteRatchetKey, SendingRatchet); } const encryptedMessage = await this.currentStep.sendingChain.encrypt(message); this.update(); let preKeyMessage; if (this.steps.length === 0 && !this.currentStep.receivingChain && this.currentStep.sendingChain.counter === 1) { preKeyMessage = new PreKeyMessageProtocol(); preKeyMessage.registrationId = this.identity.id; preKeyMessage.preKeyId = this.remotePreKeyId; preKeyMessage.preKeySignedId = this.remotePreKeySignedId; preKeyMessage.baseKey = this.currentRatchetKey.publicKey; await preKeyMessage.identity.fill(this.identity); } const signedMessage = new MessageSignedProtocol(); signedMessage.receiverKey = this.remoteIdentity.signingKey; signedMessage.senderKey = this.identity.signingKey.publicKey; signedMessage.message.cipherText = encryptedMessage.cipherText; signedMessage.message.counter = this.currentStep.sendingChain.counter - 1; signedMessage.message.previousCounter = this.counter; signedMessage.message.senderRatchetKey = this.currentRatchetKey.publicKey; await signedMessage.sign(encryptedMessage.hmacKey); if (preKeyMessage) { preKeyMessage.signedMessage = signedMessage; return preKeyMessage; } else { return signedMessage; } }); } async hasRatchetKey(key) { let ecKey; if (!(key instanceof ECPublicKey)) { ecKey = await ECPublicKey.create(key); } else { ecKey = key; } for (const item of this.steps.items) { if (await item.remoteRatchetKey.isEqual(ecKey)) { return true; } } return false; } async toJSON() { return { counter: this.counter, ratchetKey: await Curve.ecKeyPairToJson(this.currentRatchetKey), remoteIdentity: await this.remoteIdentity.signingKey.thumbprint(), rootKey: this.rootKey, steps: await this.steps.toJSON(), }; } async fromJSON(obj) { this.currentRatchetKey = await Curve.ecKeyPairFromJson(obj.ratchetKey); this.counter = obj.counter; this.rootKey = obj.rootKey; for (const step of obj.steps) { this.currentStep = await DHRatchetStep.fromJSON(step); this.steps.push(this.currentStep); } } update() { this.emit("update"); } generateRatchetKey() { return Curve.generateKeyPair("ECDH", !!this.options.exportableKeys); } async createChain(ourRatchetKey, theirRatchetKey, ratchetClass) { const derivedBytes = await Curve.deriveBytes(ourRatchetKey, theirRatchetKey); const keys = await Secret.HKDF(derivedBytes, 2, this.rootKey, INFO_RATCHET); const rootKey = await Secret.importHMAC(keys[0]); const chainKey = await Secret.importHMAC(keys[1]); const chain = new ratchetClass(chainKey); this.rootKey = rootKey; return chain; } queuePromise(key, fn) { const prev = this.promises[key] || Promise.resolve(); const cur = this.promises[key] = prev.then(fn, fn); cur.then(() => { if (this.promises[key] === cur) { delete this.promises[key]; } }); return cur; } } class DHRatchetStep { static async fromJSON(obj) { const res = new this(); await res.fromJSON(obj); return res; } async toJSON() { const res = {}; if (this.remoteRatchetKey) { res.remoteRatchetKey = this.remoteRatchetKey.key; } if (this.sendingChain) { res.sendingChain = await this.sendingChain.toJSON(); } if (this.receivingChain) { res.receivingChain = await this.receivingChain.toJSON(); } return res; } async fromJSON(obj) { if (obj.remoteRatchetKey) { this.remoteRatchetKey = await ECPublicKey.create(obj.remoteRatchetKey); } if (obj.sendingChain) { this.sendingChain = await SendingRatchet.fromJSON(obj.sendingChain); } if (obj.receivingChain) { this.receivingChain = await ReceivingRatchet.fromJSON(obj.receivingChain); } } } class DHRatchetStepStack extends Stack { getStep(remoteRatchetKey) { let found; this.items.some((step) => { if (step.remoteRatchetKey.id === remoteRatchetKey.id) { found = step; } return !!found; }); return found; } } export { AsymmetricRatchet, Curve, ECPublicKey, Identity, IdentityProtocol, MessageSignedProtocol, PreKeyBundleProtocol, PreKeyMessageProtocol, RemoteIdentity, Secret, getEngine, setEngine };