signal-desktop/ts/LibSignalStores.ts

231 lines
5.7 KiB
TypeScript
Raw Normal View History

// 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';
2021-05-19 21:25:56 +00:00
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 = {
2021-05-19 21:25:56 +00:00
readonly zone?: Zone;
};
export class Sessions extends SessionStore {
2021-05-19 21:25:56 +00:00
private readonly zone: Zone | undefined;
2021-05-19 21:25:56 +00:00
constructor(options: SessionsOptions = {}) {
super();
2021-05-19 21:25:56 +00:00
this.zone = options.zone;
}
async saveSession(
address: ProtocolAddress,
record: SessionRecord
): Promise<void> {
await window.textsecure.storage.protocol.storeSession(
encodedNameFromAddress(address),
record,
2021-05-19 21:25:56 +00:00
{ zone: this.zone }
);
}
async getSession(name: ProtocolAddress): Promise<SessionRecord | null> {
const encodedName = encodedNameFromAddress(name);
const record = await window.textsecure.storage.protocol.loadSession(
encodedName,
2021-05-19 21:25:56 +00:00
{ zone: this.zone }
);
return record || null;
}
2021-05-25 22:40:04 +00:00
async getExistingSessions(
addresses: Array<ProtocolAddress>
): Promise<Array<SessionRecord>> {
const encodedAddresses = addresses.map(encodedNameFromAddress);
return window.textsecure.storage.protocol.loadSessions(encodedAddresses, {
zone: this.zone,
});
}
}
export type IdentityKeysOptions = {
2021-05-19 21:25:56 +00:00
readonly zone?: Zone;
};
export class IdentityKeys extends IdentityKeyStore {
2021-05-19 21:25:56 +00:00
private readonly zone: Zone | undefined;
2021-05-19 21:25:56 +00:00
constructor({ zone }: IdentityKeysOptions = {}) {
super();
2021-05-19 21:25:56 +00:00
this.zone = zone;
}
async getIdentityKey(): Promise<PrivateKey> {
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<number> {
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<PublicKey | null> {
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<boolean> {
const encodedName = encodedNameFromAddress(name);
const publicKey = typedArrayToArrayBuffer(key.serialize());
2021-05-19 21:25:56 +00:00
// Pass `zone` to let `saveIdentity` archive sibling sessions when identity
// key changes.
return window.textsecure.storage.protocol.saveIdentity(
encodedName,
publicKey,
false,
2021-05-19 21:25:56 +00:00
{ zone: this.zone }
);
}
async isTrustedIdentity(
name: ProtocolAddress,
key: PublicKey,
direction: Direction
): Promise<boolean> {
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<void> {
await window.textsecure.storage.protocol.storePreKey(
id,
freezePreKey(record)
);
}
async getPreKey(id: number): Promise<PreKeyRecord> {
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<void> {
await window.textsecure.storage.protocol.removePreKey(id);
}
}
export class SenderKeys extends SenderKeyStore {
async saveSenderKey(
sender: ProtocolAddress,
distributionId: Uuid,
record: SenderKeyRecord
): Promise<void> {
const encodedAddress = encodedNameFromAddress(sender);
await window.textsecure.storage.protocol.saveSenderKey(
encodedAddress,
distributionId,
record
);
}
async getSenderKey(
sender: ProtocolAddress,
distributionId: Uuid
): Promise<SenderKeyRecord | null> {
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<void> {
await window.textsecure.storage.protocol.storeSignedPreKey(
id,
freezeSignedPreKey(record),
true
);
}
async getSignedPreKey(id: number): Promise<SignedPreKeyRecord> {
const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey(
id
);
if (!signedPreKey) {
throw new Error(`getSignedPreKey: SignedPreKey ${id} not found`);
}
return signedPreKey;
}
}