// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only /* eslint-disable max-classes-per-file */ /* eslint-disable class-methods-use-this */ import { isNumber } from 'lodash'; import { Direction, IdentityKeyStore, PreKeyRecord, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, SenderKeyRecord, SenderKeyStore, SessionRecord, SessionStore, SignedPreKeyRecord, SignedPreKeyStore, Uuid, } from '@signalapp/signal-client'; import { freezePreKey, freezeSignedPreKey } from './SignalProtocolStore'; import { typedArrayToArrayBuffer } from './Crypto'; import { Zone } from './util/Zone'; function encodedNameFromAddress(address: ProtocolAddress): string { const name = address.name(); const deviceId = address.deviceId(); const encodedName = `${name}.${deviceId}`; return encodedName; } export type SessionsOptions = { readonly zone?: Zone; }; export class Sessions extends SessionStore { private readonly zone: Zone | undefined; constructor(options: SessionsOptions = {}) { super(); this.zone = options.zone; } async saveSession( address: ProtocolAddress, record: SessionRecord ): Promise { await window.textsecure.storage.protocol.storeSession( encodedNameFromAddress(address), record, { zone: this.zone } ); } async getSession(name: ProtocolAddress): Promise { const encodedName = encodedNameFromAddress(name); const record = await window.textsecure.storage.protocol.loadSession( encodedName, { zone: this.zone } ); return record || null; } async getExistingSessions( addresses: Array ): Promise> { const encodedAddresses = addresses.map(encodedNameFromAddress); return window.textsecure.storage.protocol.loadSessions(encodedAddresses, { zone: this.zone, }); } } export type IdentityKeysOptions = { readonly zone?: Zone; }; export class IdentityKeys extends IdentityKeyStore { private readonly zone: Zone | undefined; constructor({ zone }: IdentityKeysOptions = {}) { super(); this.zone = zone; } async getIdentityKey(): Promise { const keyPair = await window.textsecure.storage.protocol.getIdentityKeyPair(); if (!keyPair) { throw new Error('IdentityKeyStore/getIdentityKey: No identity key!'); } return PrivateKey.deserialize(Buffer.from(keyPair.privKey)); } async getLocalRegistrationId(): Promise { const id = await window.textsecure.storage.protocol.getLocalRegistrationId(); if (!isNumber(id)) { throw new Error( 'IdentityKeyStore/getLocalRegistrationId: No registration id!' ); } return id; } async getIdentity(address: ProtocolAddress): Promise { const encodedName = encodedNameFromAddress(address); const key = await window.textsecure.storage.protocol.loadIdentityKey( encodedName ); if (!key) { return null; } return PublicKey.deserialize(Buffer.from(key)); } async saveIdentity(name: ProtocolAddress, key: PublicKey): Promise { const encodedName = encodedNameFromAddress(name); const publicKey = typedArrayToArrayBuffer(key.serialize()); // Pass `zone` to let `saveIdentity` archive sibling sessions when identity // key changes. return window.textsecure.storage.protocol.saveIdentity( encodedName, publicKey, false, { zone: this.zone } ); } async isTrustedIdentity( name: ProtocolAddress, key: PublicKey, direction: Direction ): Promise { const encodedName = encodedNameFromAddress(name); const publicKey = typedArrayToArrayBuffer(key.serialize()); return window.textsecure.storage.protocol.isTrustedIdentity( encodedName, publicKey, direction ); } } export class PreKeys extends PreKeyStore { async savePreKey(id: number, record: PreKeyRecord): Promise { await window.textsecure.storage.protocol.storePreKey( id, freezePreKey(record) ); } async getPreKey(id: number): Promise { const preKey = await window.textsecure.storage.protocol.loadPreKey(id); if (preKey === undefined) { throw new Error(`getPreKey: PreKey ${id} not found`); } return preKey; } async removePreKey(id: number): Promise { await window.textsecure.storage.protocol.removePreKey(id); } } export class SenderKeys extends SenderKeyStore { async saveSenderKey( sender: ProtocolAddress, distributionId: Uuid, record: SenderKeyRecord ): Promise { const encodedAddress = encodedNameFromAddress(sender); await window.textsecure.storage.protocol.saveSenderKey( encodedAddress, distributionId, record ); } async getSenderKey( sender: ProtocolAddress, distributionId: Uuid ): Promise { const encodedAddress = encodedNameFromAddress(sender); const senderKey = await window.textsecure.storage.protocol.getSenderKey( encodedAddress, distributionId ); return senderKey || null; } } export class SignedPreKeys extends SignedPreKeyStore { async saveSignedPreKey( id: number, record: SignedPreKeyRecord ): Promise { await window.textsecure.storage.protocol.storeSignedPreKey( id, freezeSignedPreKey(record), true ); } async getSignedPreKey(id: number): Promise { const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey( id ); if (!signedPreKey) { throw new Error(`getSignedPreKey: SignedPreKey ${id} not found`); } return signedPreKey; } }