230 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // 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<void> {
 | |
|     await window.textsecure.storage.protocol.storeSession(
 | |
|       encodedNameFromAddress(address),
 | |
|       record,
 | |
|       { zone: this.zone }
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   async getSession(name: ProtocolAddress): Promise<SessionRecord | null> {
 | |
|     const encodedName = encodedNameFromAddress(name);
 | |
|     const record = await window.textsecure.storage.protocol.loadSession(
 | |
|       encodedName,
 | |
|       { zone: this.zone }
 | |
|     );
 | |
| 
 | |
|     return record || null;
 | |
|   }
 | |
| 
 | |
|   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 = {
 | |
|   readonly zone?: Zone;
 | |
| };
 | |
| 
 | |
| export class IdentityKeys extends IdentityKeyStore {
 | |
|   private readonly zone: Zone | undefined;
 | |
| 
 | |
|   constructor({ zone }: IdentityKeysOptions = {}) {
 | |
|     super();
 | |
|     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());
 | |
| 
 | |
|     // 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<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;
 | |
|   }
 | |
| }
 | 
