| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | // Copyright 2016-2021 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* eslint-disable class-methods-use-this */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | import PQueue from 'p-queue'; | 
					
						
							|  |  |  | import { isNumber } from 'lodash'; | 
					
						
							|  |  |  | import * as z from 'zod'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   Direction, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   PreKeyRecord, | 
					
						
							|  |  |  |   PrivateKey, | 
					
						
							|  |  |  |   PublicKey, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   SenderKeyRecord, | 
					
						
							|  |  |  |   SessionRecord, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   SignedPreKeyRecord, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  | } from '@signalapp/signal-client'; | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   constantTimeEqual, | 
					
						
							|  |  |  |   fromEncodedBinaryToArrayBuffer, | 
					
						
							|  |  |  |   typedArrayToArrayBuffer, | 
					
						
							|  |  |  | } from './Crypto'; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | import { isNotNil } from './util/isNotNil'; | 
					
						
							| 
									
										
										
										
											2021-03-22 14:08:52 -07:00
										 |  |  | import { isMoreRecentThan } from './util/timestamp'; | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   sessionRecordToProtobuf, | 
					
						
							|  |  |  |   sessionStructureToArrayBuffer, | 
					
						
							|  |  |  | } from './util/sessionTranslation'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   KeyPairType, | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   IdentityKeyType, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   SenderKeyType, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   SessionType, | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   SignedPreKeyType, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   OuterSignedPrekeyType, | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   PreKeyType, | 
					
						
							|  |  |  |   UnprocessedType, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   UnprocessedUpdateType, | 
					
						
							|  |  |  | } from './textsecure/Types.d'; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const VerifiedStatus = { | 
					
						
							|  |  |  |   DEFAULT: 0, | 
					
						
							|  |  |  |   VERIFIED: 1, | 
					
						
							|  |  |  |   UNVERIFIED: 2, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function validateVerifiedStatus(status: number): boolean { | 
					
						
							|  |  |  |   if ( | 
					
						
							|  |  |  |     status === VerifiedStatus.DEFAULT || | 
					
						
							|  |  |  |     status === VerifiedStatus.VERIFIED || | 
					
						
							|  |  |  |     status === VerifiedStatus.UNVERIFIED | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | const identityKeySchema = z.object({ | 
					
						
							|  |  |  |   id: z.string(), | 
					
						
							|  |  |  |   publicKey: z.instanceof(ArrayBuffer), | 
					
						
							|  |  |  |   firstUse: z.boolean(), | 
					
						
							|  |  |  |   timestamp: z.number().refine((value: number) => value % 1 === 0 && value > 0), | 
					
						
							|  |  |  |   verified: z.number().refine(validateVerifiedStatus), | 
					
						
							|  |  |  |   nonblockingApproval: z.boolean(), | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | function validateIdentityKey(attrs: unknown): attrs is IdentityKeyType { | 
					
						
							|  |  |  |   // We'll throw if this doesn't match
 | 
					
						
							|  |  |  |   identityKeySchema.parse(attrs); | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | async function normalizeEncodedAddress( | 
					
						
							|  |  |  |   encodedAddress: string | 
					
						
							|  |  |  | ): Promise<string> { | 
					
						
							|  |  |  |   const [identifier, deviceId] = window.textsecure.utils.unencodeNumber( | 
					
						
							|  |  |  |     encodedAddress | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const conv = window.ConversationController.getOrCreate( | 
					
						
							|  |  |  |       identifier, | 
					
						
							|  |  |  |       'private' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return `${conv.get('id')}.${deviceId}`; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     window.log.error(`could not get conversation for identifier ${identifier}`); | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  | type HasIdType<T> = { | 
					
						
							|  |  |  |   id: T; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | type CacheEntryType<DBType, HydratedType> = | 
					
						
							|  |  |  |   | { | 
					
						
							|  |  |  |       hydrated: false; | 
					
						
							|  |  |  |       fromDB: DBType; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   | { hydrated: true; fromDB: DBType; item: HydratedType }; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  | async function _fillCaches<ID, T extends HasIdType<ID>, HydratedType>( | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   object: SignalProtocolStore, | 
					
						
							|  |  |  |   field: keyof SignalProtocolStore, | 
					
						
							|  |  |  |   itemsPromise: Promise<Array<T>> | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							|  |  |  |   const items = await itemsPromise; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   const cache = new Map<ID, CacheEntryType<T, HydratedType>>(); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   for (let i = 0, max = items.length; i < max; i += 1) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const fromDB = items[i]; | 
					
						
							|  |  |  |     const { id } = fromDB; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     cache.set(id, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       fromDB, | 
					
						
							|  |  |  |       hydrated: false, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.log.info(`SignalProtocolStore: Finished caching ${field} data`); | 
					
						
							|  |  |  |   // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
 | 
					
						
							|  |  |  |   object[field] = cache as any; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | export function hydrateSession(session: SessionType): SessionRecord { | 
					
						
							|  |  |  |   return SessionRecord.deserialize(Buffer.from(session.record, 'base64')); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function hydratePublicKey(identityKey: IdentityKeyType): PublicKey { | 
					
						
							|  |  |  |   return PublicKey.deserialize(Buffer.from(identityKey.publicKey)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function hydratePreKey(preKey: PreKeyType): PreKeyRecord { | 
					
						
							|  |  |  |   const publicKey = PublicKey.deserialize(Buffer.from(preKey.publicKey)); | 
					
						
							|  |  |  |   const privateKey = PrivateKey.deserialize(Buffer.from(preKey.privateKey)); | 
					
						
							|  |  |  |   return PreKeyRecord.new(preKey.id, publicKey, privateKey); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function hydrateSignedPreKey( | 
					
						
							|  |  |  |   signedPreKey: SignedPreKeyType | 
					
						
							|  |  |  | ): SignedPreKeyRecord { | 
					
						
							|  |  |  |   const createdAt = signedPreKey.created_at; | 
					
						
							|  |  |  |   const pubKey = PublicKey.deserialize(Buffer.from(signedPreKey.publicKey)); | 
					
						
							|  |  |  |   const privKey = PrivateKey.deserialize(Buffer.from(signedPreKey.privateKey)); | 
					
						
							|  |  |  |   const signature = Buffer.from([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return SignedPreKeyRecord.new( | 
					
						
							|  |  |  |     signedPreKey.id, | 
					
						
							|  |  |  |     createdAt, | 
					
						
							|  |  |  |     pubKey, | 
					
						
							|  |  |  |     privKey, | 
					
						
							|  |  |  |     signature | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | export function freezeSession(session: SessionRecord): string { | 
					
						
							|  |  |  |   return session.serialize().toString('base64'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function freezePublicKey(publicKey: PublicKey): ArrayBuffer { | 
					
						
							|  |  |  |   return typedArrayToArrayBuffer(publicKey.serialize()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function freezePreKey(preKey: PreKeyRecord): KeyPairType { | 
					
						
							|  |  |  |   const keyPair = { | 
					
						
							|  |  |  |     pubKey: typedArrayToArrayBuffer(preKey.publicKey().serialize()), | 
					
						
							|  |  |  |     privKey: typedArrayToArrayBuffer(preKey.privateKey().serialize()), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   return keyPair; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export function freezeSignedPreKey( | 
					
						
							|  |  |  |   signedPreKey: SignedPreKeyRecord | 
					
						
							|  |  |  | ): KeyPairType { | 
					
						
							|  |  |  |   const keyPair = { | 
					
						
							|  |  |  |     pubKey: typedArrayToArrayBuffer(signedPreKey.publicKey().serialize()), | 
					
						
							|  |  |  |     privKey: typedArrayToArrayBuffer(signedPreKey.privateKey().serialize()), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   return keyPair; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // We add a this parameter to avoid an 'implicit any' error on the next line
 | 
					
						
							|  |  |  | const EventsMixin = (function EventsMixin(this: unknown) { | 
					
						
							|  |  |  |   window._.assign(this, window.Backbone.Events); | 
					
						
							|  |  |  |   // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
					
						
							|  |  |  | } as any) as typeof window.Backbone.EventsMixin; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class SignalProtocolStore extends EventsMixin { | 
					
						
							|  |  |  |   // Enums used across the app
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   VerifiedStatus = VerifiedStatus; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Cached values
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ourIdentityKey?: KeyPairType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ourRegistrationId?: number; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   identityKeys?: Map<string, CacheEntryType<IdentityKeyType, PublicKey>>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   senderKeys?: Map<string, CacheEntryType<SenderKeyType, SenderKeyRecord>>; | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   sessions?: Map<string, CacheEntryType<SessionType, SessionRecord>>; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   preKeys?: Map<number, CacheEntryType<PreKeyType, PreKeyRecord>>; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   signedPreKeys?: Map< | 
					
						
							|  |  |  |     number, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     CacheEntryType<SignedPreKeyType, SignedPreKeyRecord> | 
					
						
							|  |  |  |   >; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   senderKeyQueues: Map<string, PQueue> = new Map<string, PQueue>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   sessionQueues: Map<string, PQueue> = new Map<string, PQueue>(); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   async hydrateCaches(): Promise<void> { | 
					
						
							|  |  |  |     await Promise.all([ | 
					
						
							|  |  |  |       (async () => { | 
					
						
							|  |  |  |         const item = await window.Signal.Data.getItemById('identityKey'); | 
					
						
							|  |  |  |         this.ourIdentityKey = item ? item.value : undefined; | 
					
						
							|  |  |  |       })(), | 
					
						
							|  |  |  |       (async () => { | 
					
						
							|  |  |  |         const item = await window.Signal.Data.getItemById('registrationId'); | 
					
						
							|  |  |  |         this.ourRegistrationId = item ? item.value : undefined; | 
					
						
							|  |  |  |       })(), | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       _fillCaches<string, IdentityKeyType, PublicKey>( | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         this, | 
					
						
							|  |  |  |         'identityKeys', | 
					
						
							|  |  |  |         window.Signal.Data.getAllIdentityKeys() | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       _fillCaches<string, SessionType, SessionRecord>( | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         this, | 
					
						
							|  |  |  |         'sessions', | 
					
						
							|  |  |  |         window.Signal.Data.getAllSessions() | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       _fillCaches<number, PreKeyType, PreKeyRecord>( | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         this, | 
					
						
							|  |  |  |         'preKeys', | 
					
						
							|  |  |  |         window.Signal.Data.getAllPreKeys() | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       _fillCaches<string, SenderKeyType, SenderKeyRecord>( | 
					
						
							|  |  |  |         this, | 
					
						
							|  |  |  |         'senderKeys', | 
					
						
							|  |  |  |         window.Signal.Data.getAllSenderKeys() | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       _fillCaches<number, SignedPreKeyType, SignedPreKeyRecord>( | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         this, | 
					
						
							|  |  |  |         'signedPreKeys', | 
					
						
							|  |  |  |         window.Signal.Data.getAllSignedPreKeys() | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getIdentityKeyPair(): Promise<KeyPairType | undefined> { | 
					
						
							|  |  |  |     return this.ourIdentityKey; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getLocalRegistrationId(): Promise<number | undefined> { | 
					
						
							|  |  |  |     return this.ourRegistrationId; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // PreKeys
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   async loadPreKey(keyId: number): Promise<PreKeyRecord | undefined> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     if (!this.preKeys) { | 
					
						
							|  |  |  |       throw new Error('loadPreKey: this.preKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const entry = this.preKeys.get(keyId); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (!entry) { | 
					
						
							|  |  |  |       window.log.error('Failed to fetch prekey:', keyId); | 
					
						
							|  |  |  |       return undefined; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (entry.hydrated) { | 
					
						
							|  |  |  |       window.log.info('Successfully fetched prekey (cache hit):', keyId); | 
					
						
							|  |  |  |       return entry.item; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const item = hydratePreKey(entry.fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.preKeys.set(keyId, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       hydrated: true, | 
					
						
							|  |  |  |       fromDB: entry.fromDB, | 
					
						
							|  |  |  |       item, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     window.log.info('Successfully fetched prekey (cache miss):', keyId); | 
					
						
							|  |  |  |     return item; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async storePreKey(keyId: number, keyPair: KeyPairType): Promise<void> { | 
					
						
							|  |  |  |     if (!this.preKeys) { | 
					
						
							|  |  |  |       throw new Error('storePreKey: this.preKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     if (this.preKeys.has(keyId)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error(`storePreKey: prekey ${keyId} already exists!`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const fromDB = { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       id: keyId, | 
					
						
							|  |  |  |       publicKey: keyPair.pubKey, | 
					
						
							|  |  |  |       privateKey: keyPair.privKey, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     await window.Signal.Data.createOrUpdatePreKey(fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.preKeys.set(keyId, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       hydrated: false, | 
					
						
							|  |  |  |       fromDB, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removePreKey(keyId: number): Promise<void> { | 
					
						
							|  |  |  |     if (!this.preKeys) { | 
					
						
							|  |  |  |       throw new Error('removePreKey: this.preKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       this.trigger('removePreKey'); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'removePreKey error triggering removePreKey:', | 
					
						
							|  |  |  |         error && error.stack ? error.stack : error | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.preKeys.delete(keyId); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     await window.Signal.Data.removePreKeyById(keyId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async clearPreKeyStore(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     if (this.preKeys) { | 
					
						
							|  |  |  |       this.preKeys.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     await window.Signal.Data.removeAllPreKeys(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Signed PreKeys
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async loadSignedPreKey( | 
					
						
							|  |  |  |     keyId: number | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   ): Promise<SignedPreKeyRecord | undefined> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     if (!this.signedPreKeys) { | 
					
						
							|  |  |  |       throw new Error('loadSignedPreKey: this.signedPreKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const entry = this.signedPreKeys.get(keyId); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (!entry) { | 
					
						
							|  |  |  |       window.log.error('Failed to fetch signed prekey:', keyId); | 
					
						
							|  |  |  |       return undefined; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (entry.hydrated) { | 
					
						
							|  |  |  |       window.log.info('Successfully fetched signed prekey (cache hit):', keyId); | 
					
						
							|  |  |  |       return entry.item; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const item = hydrateSignedPreKey(entry.fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.signedPreKeys.set(keyId, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       hydrated: true, | 
					
						
							|  |  |  |       item, | 
					
						
							|  |  |  |       fromDB: entry.fromDB, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     window.log.info('Successfully fetched signed prekey (cache miss):', keyId); | 
					
						
							|  |  |  |     return item; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async loadSignedPreKeys(): Promise<Array<OuterSignedPrekeyType>> { | 
					
						
							|  |  |  |     if (!this.signedPreKeys) { | 
					
						
							|  |  |  |       throw new Error('loadSignedPreKeys: this.signedPreKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (arguments.length > 0) { | 
					
						
							|  |  |  |       throw new Error('loadSignedPreKeys takes no arguments'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const entries = Array.from(this.signedPreKeys.values()); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     return entries.map(entry => { | 
					
						
							|  |  |  |       const preKey = entry.fromDB; | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         pubKey: preKey.publicKey, | 
					
						
							|  |  |  |         privKey: preKey.privateKey, | 
					
						
							|  |  |  |         created_at: preKey.created_at, | 
					
						
							|  |  |  |         keyId: preKey.id, | 
					
						
							|  |  |  |         confirmed: preKey.confirmed, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   // Note that this is also called in update scenarios, for confirming that signed prekeys
 | 
					
						
							|  |  |  |   //   have indeed been accepted by the server.
 | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   async storeSignedPreKey( | 
					
						
							|  |  |  |     keyId: number, | 
					
						
							|  |  |  |     keyPair: KeyPairType, | 
					
						
							|  |  |  |     confirmed?: boolean | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (!this.signedPreKeys) { | 
					
						
							|  |  |  |       throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const fromDB = { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       id: keyId, | 
					
						
							|  |  |  |       publicKey: keyPair.pubKey, | 
					
						
							|  |  |  |       privateKey: keyPair.privKey, | 
					
						
							|  |  |  |       created_at: Date.now(), | 
					
						
							|  |  |  |       confirmed: Boolean(confirmed), | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     await window.Signal.Data.createOrUpdateSignedPreKey(fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.signedPreKeys.set(keyId, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       hydrated: false, | 
					
						
							|  |  |  |       fromDB, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeSignedPreKey(keyId: number): Promise<void> { | 
					
						
							|  |  |  |     if (!this.signedPreKeys) { | 
					
						
							|  |  |  |       throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.signedPreKeys.delete(keyId); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     await window.Signal.Data.removeSignedPreKeyById(keyId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async clearSignedPreKeysStore(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     if (this.signedPreKeys) { | 
					
						
							|  |  |  |       this.signedPreKeys.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     await window.Signal.Data.removeAllSignedPreKeys(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |   // Sender Key Queue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async enqueueSenderKeyJob<T>( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     task: () => Promise<T> | 
					
						
							|  |  |  |   ): Promise<T> { | 
					
						
							|  |  |  |     const senderId = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |     const queue = this._getSenderKeyQueue(senderId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return queue.add<T>(task); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _createSenderKeyQueue(): PQueue { | 
					
						
							|  |  |  |     return new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _getSenderKeyQueue(senderId: string): PQueue { | 
					
						
							|  |  |  |     const cachedQueue = this.senderKeyQueues.get(senderId); | 
					
						
							|  |  |  |     if (cachedQueue) { | 
					
						
							|  |  |  |       return cachedQueue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const freshQueue = this._createSenderKeyQueue(); | 
					
						
							|  |  |  |     this.senderKeyQueues.set(senderId, freshQueue); | 
					
						
							|  |  |  |     return freshQueue; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Sender Keys
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private getSenderKeyId(senderKeyId: string, distributionId: string): string { | 
					
						
							|  |  |  |     return `${senderKeyId}--${distributionId}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async saveSenderKey( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     distributionId: string, | 
					
						
							|  |  |  |     record: SenderKeyRecord | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (!this.senderKeys) { | 
					
						
							|  |  |  |       throw new Error('saveSenderKey: this.senderKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const senderId = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |       const id = this.getSenderKeyId(senderId, distributionId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const fromDB: SenderKeyType = { | 
					
						
							|  |  |  |         id, | 
					
						
							|  |  |  |         senderId, | 
					
						
							|  |  |  |         distributionId, | 
					
						
							|  |  |  |         data: record.serialize(), | 
					
						
							|  |  |  |         lastUpdatedDate: Date.now(), | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await window.Signal.Data.createOrUpdateSenderKey(fromDB); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.senderKeys.set(id, { | 
					
						
							|  |  |  |         hydrated: true, | 
					
						
							|  |  |  |         fromDB, | 
					
						
							|  |  |  |         item: record, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       const errorString = error && error.stack ? error.stack : error; | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         `saveSenderKey: failed to save senderKey ${encodedAddress}/${distributionId}: ${errorString}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getSenderKey( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     distributionId: string | 
					
						
							|  |  |  |   ): Promise<SenderKeyRecord | undefined> { | 
					
						
							|  |  |  |     if (!this.senderKeys) { | 
					
						
							|  |  |  |       throw new Error('getSenderKey: this.senderKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const senderId = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |       const id = this.getSenderKeyId(senderId, distributionId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const entry = this.senderKeys.get(id); | 
					
						
							|  |  |  |       if (!entry) { | 
					
						
							|  |  |  |         window.log.error('Failed to fetch sender key:', id); | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (entry.hydrated) { | 
					
						
							|  |  |  |         window.log.info('Successfully fetched signed prekey (cache hit):', id); | 
					
						
							|  |  |  |         return entry.item; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const item = SenderKeyRecord.deserialize(entry.fromDB.data); | 
					
						
							|  |  |  |       this.senderKeys.set(id, { | 
					
						
							|  |  |  |         hydrated: true, | 
					
						
							|  |  |  |         item, | 
					
						
							|  |  |  |         fromDB: entry.fromDB, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       window.log.info('Successfully fetched signed prekey (cache miss):', id); | 
					
						
							|  |  |  |       return item; | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       const errorString = error && error.stack ? error.stack : error; | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         `getSenderKey: failed to load senderKey ${encodedAddress}/${distributionId}: ${errorString}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   // Session Queue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async enqueueSessionJob<T>( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     task: () => Promise<T> | 
					
						
							|  |  |  |   ): Promise<T> { | 
					
						
							|  |  |  |     const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |     const queue = this._getSessionQueue(id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return queue.add<T>(task); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _createSessionQueue(): PQueue { | 
					
						
							|  |  |  |     return new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _getSessionQueue(id: string): PQueue { | 
					
						
							|  |  |  |     const cachedQueue = this.sessionQueues.get(id); | 
					
						
							|  |  |  |     if (cachedQueue) { | 
					
						
							|  |  |  |       return cachedQueue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const freshQueue = this._createSessionQueue(); | 
					
						
							|  |  |  |     this.sessionQueues.set(id, freshQueue); | 
					
						
							|  |  |  |     return freshQueue; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   // Sessions
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   async loadSession( | 
					
						
							|  |  |  |     encodedAddress: string | 
					
						
							|  |  |  |   ): Promise<SessionRecord | undefined> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('loadSession: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('loadSession: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       const entry = this.sessions.get(id); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (!entry) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       if (entry.hydrated) { | 
					
						
							|  |  |  |         return entry.item; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const item = await this._maybeMigrateSession(entry.fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       this.sessions.set(id, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         hydrated: true, | 
					
						
							|  |  |  |         item, | 
					
						
							|  |  |  |         fromDB: entry.fromDB, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       return item; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       const errorString = error && error.stack ? error.stack : error; | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         `loadSession: failed to load session ${encodedAddress}: ${errorString}` | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       return undefined; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   private async _maybeMigrateSession( | 
					
						
							|  |  |  |     session: SessionType | 
					
						
							|  |  |  |   ): Promise<SessionRecord> { | 
					
						
							|  |  |  |     // Already migrated, return record directly
 | 
					
						
							|  |  |  |     if (session.version === 2) { | 
					
						
							|  |  |  |       return hydrateSession(session); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Not yet converted, need to translate to new format
 | 
					
						
							|  |  |  |     if (session.version !== undefined) { | 
					
						
							|  |  |  |       throw new Error('_maybeMigrateSession: Unknown session version type!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const keyPair = await this.getIdentityKeyPair(); | 
					
						
							|  |  |  |     if (!keyPair) { | 
					
						
							|  |  |  |       throw new Error('_maybeMigrateSession: No identity key for ourself!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const localRegistrationId = await this.getLocalRegistrationId(); | 
					
						
							|  |  |  |     if (!isNumber(localRegistrationId)) { | 
					
						
							|  |  |  |       throw new Error('_maybeMigrateSession: No registration id for ourself!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const localUserData = { | 
					
						
							|  |  |  |       identityKeyPublic: keyPair.pubKey, | 
					
						
							|  |  |  |       registrationId: localRegistrationId, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info( | 
					
						
							|  |  |  |       `_maybeMigrateSession: Migrating session with id ${session.id}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const sessionProto = sessionRecordToProtobuf( | 
					
						
							|  |  |  |       JSON.parse(session.record), | 
					
						
							|  |  |  |       localUserData | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return SessionRecord.deserialize( | 
					
						
							|  |  |  |       Buffer.from(sessionStructureToArrayBuffer(sessionProto)) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   async storeSession( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     record: SessionRecord | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('storeSession: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('storeSession: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     const unencoded = window.textsecure.utils.unencodeNumber(encodedAddress); | 
					
						
							|  |  |  |     const deviceId = parseInt(unencoded[1], 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       const fromDB = { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         id, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         version: 2, | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         conversationId: window.textsecure.utils.unencodeNumber(id)[0], | 
					
						
							|  |  |  |         deviceId, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         record: record.serialize().toString('base64'), | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await window.Signal.Data.createOrUpdateSession(fromDB); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       this.sessions.set(id, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         hydrated: true, | 
					
						
							|  |  |  |         fromDB, | 
					
						
							|  |  |  |         item: record, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       const errorString = error && error.stack ? error.stack : error; | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         `storeSession: Save failed fo ${encodedAddress}: ${errorString}` | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw error; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getDeviceIds(identifier: string): Promise<Array<number>> { | 
					
						
							|  |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('getDeviceIds: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('getDeviceIds: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const id = window.ConversationController.getConversationId(identifier); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       if (!id) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           `getDeviceIds: No conversationId found for identifier ${identifier}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       const allSessions = Array.from(this.sessions.values()); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       const entries = allSessions.filter( | 
					
						
							|  |  |  |         session => session.fromDB.conversationId === id | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       const openIds = await Promise.all( | 
					
						
							|  |  |  |         entries.map(async entry => { | 
					
						
							|  |  |  |           if (entry.hydrated) { | 
					
						
							|  |  |  |             const record = entry.item; | 
					
						
							|  |  |  |             if (record.hasCurrentState()) { | 
					
						
							|  |  |  |               return entry.fromDB.deviceId; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return undefined; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |           const record = await this._maybeMigrateSession(entry.fromDB); | 
					
						
							|  |  |  |           if (record.hasCurrentState()) { | 
					
						
							|  |  |  |             return entry.fromDB.deviceId; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return undefined; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       return openIds.filter(isNotNil); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         `getDeviceIds: Failed to get device ids for identifier ${identifier}`, | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         error && error.stack ? error.stack : error | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return []; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeSession(encodedAddress: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('removeSession: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info('removeSession: deleting session for', encodedAddress); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |       await window.Signal.Data.removeSessionById(id); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       this.sessions.delete(id); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.error( | 
					
						
							|  |  |  |         `removeSession: Failed to delete session for ${encodedAddress}` | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeAllSessions(identifier: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('removeAllSessions: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('removeAllSessions: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info('removeAllSessions: deleting sessions for', identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const id = window.ConversationController.getConversationId(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const entries = Array.from(this.sessions.values()); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     for (let i = 0, max = entries.length; i < max; i += 1) { | 
					
						
							|  |  |  |       const entry = entries[i]; | 
					
						
							|  |  |  |       if (entry.fromDB.conversationId === id) { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |         this.sessions.delete(entry.fromDB.id); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await window.Signal.Data.removeSessionsByConversation(identifier); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   private async _archiveSession( | 
					
						
							|  |  |  |     entry?: CacheEntryType<SessionType, SessionRecord> | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     if (!entry) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await this.enqueueSessionJob(entry.fromDB.id, async () => { | 
					
						
							|  |  |  |       const item = entry.hydrated | 
					
						
							|  |  |  |         ? entry.item | 
					
						
							|  |  |  |         : await this._maybeMigrateSession(entry.fromDB); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!item.hasCurrentState()) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       item.archiveCurrentState(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await this.storeSession(entry.fromDB.id, item); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async archiveSession(encodedAddress: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('archiveSession: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info(`archiveSession: session for ${encodedAddress}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const entry = this.sessions.get(id); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     await this._archiveSession(entry); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async archiveSiblingSessions(encodedAddress: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('archiveSiblingSessions: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info( | 
					
						
							|  |  |  |       'archiveSiblingSessions: archiving sibling sessions for', | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       encodedAddress | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const id = await normalizeEncodedAddress(encodedAddress); | 
					
						
							|  |  |  |     const [identifier, deviceId] = window.textsecure.utils.unencodeNumber(id); | 
					
						
							|  |  |  |     const deviceIdNumber = parseInt(deviceId, 10); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const allEntries = Array.from(this.sessions.values()); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const entries = allEntries.filter( | 
					
						
							|  |  |  |       entry => | 
					
						
							|  |  |  |         entry.fromDB.conversationId === identifier && | 
					
						
							|  |  |  |         entry.fromDB.deviceId !== deviceIdNumber | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       entries.map(async entry => { | 
					
						
							|  |  |  |         await this._archiveSession(entry); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async archiveAllSessions(identifier: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.sessions) { | 
					
						
							|  |  |  |       throw new Error('archiveAllSessions: this.sessions not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.log.info( | 
					
						
							|  |  |  |       'archiveAllSessions: archiving all sessions for', | 
					
						
							|  |  |  |       identifier | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const id = window.ConversationController.getConversationId(identifier); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     const allEntries = Array.from(this.sessions.values()); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const entries = allEntries.filter( | 
					
						
							|  |  |  |       entry => entry.fromDB.conversationId === id | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     await Promise.all( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       entries.map(async entry => { | 
					
						
							|  |  |  |         await this._archiveSession(entry); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async clearSessionStore(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     if (this.sessions) { | 
					
						
							|  |  |  |       this.sessions.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     window.Signal.Data.removeAllSessions(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Identity Keys
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getIdentityRecord(identifier: string): IdentityKeyType | undefined { | 
					
						
							|  |  |  |     if (!this.identityKeys) { | 
					
						
							|  |  |  |       throw new Error('getIdentityRecord: this.identityKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const id = window.ConversationController.getConversationId(identifier); | 
					
						
							|  |  |  |       if (!id) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           `getIdentityRecord: No conversation id for identifier ${identifier}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       const entry = this.identityKeys.get(id); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       if (!entry) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return entry.fromDB; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } catch (e) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         `getIdentityRecord: Failed to get identity record for identifier ${identifier}` | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       return undefined; | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async isTrustedIdentity( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     publicKey: ArrayBuffer, | 
					
						
							|  |  |  |     direction: number | 
					
						
							|  |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (!this.identityKeys) { | 
					
						
							|  |  |  |       throw new Error('getIdentityRecord: this.identityKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('isTrustedIdentity: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     const identifier = window.textsecure.utils.unencodeNumber( | 
					
						
							|  |  |  |       encodedAddress | 
					
						
							|  |  |  |     )[0]; | 
					
						
							|  |  |  |     const ourNumber = window.textsecure.storage.user.getNumber(); | 
					
						
							|  |  |  |     const ourUuid = window.textsecure.storage.user.getUuid(); | 
					
						
							|  |  |  |     const isOurIdentifier = | 
					
						
							|  |  |  |       (ourNumber && identifier === ourNumber) || | 
					
						
							|  |  |  |       (ourUuid && identifier === ourUuid); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isOurIdentifier) { | 
					
						
							|  |  |  |       if (identityRecord && identityRecord.publicKey) { | 
					
						
							|  |  |  |         return constantTimeEqual(identityRecord.publicKey, publicKey); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       window.log.warn( | 
					
						
							|  |  |  |         'isTrustedIdentity: No local record for our own identifier. Returning true.' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (direction) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       case Direction.Sending: | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         return this.isTrustedForSending(publicKey, identityRecord); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       case Direction.Receiving: | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         return true; | 
					
						
							|  |  |  |       default: | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |         throw new Error(`isTrustedIdentity: Unknown direction: ${direction}`); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   isTrustedForSending( | 
					
						
							|  |  |  |     publicKey: ArrayBuffer, | 
					
						
							|  |  |  |     identityRecord?: IdentityKeyType | 
					
						
							|  |  |  |   ): boolean { | 
					
						
							|  |  |  |     if (!identityRecord) { | 
					
						
							|  |  |  |       window.log.info( | 
					
						
							|  |  |  |         'isTrustedForSending: No previous record, returning true...' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const existing = identityRecord.publicKey; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!existing) { | 
					
						
							|  |  |  |       window.log.info('isTrustedForSending: Nothing here, returning true...'); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!constantTimeEqual(existing, publicKey)) { | 
					
						
							|  |  |  |       window.log.info("isTrustedForSending: Identity keys don't match..."); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (identityRecord.verified === VerifiedStatus.UNVERIFIED) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.error('isTrustedIdentity: Needs unverified approval!'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (this.isNonBlockingApprovalRequired(identityRecord)) { | 
					
						
							|  |  |  |       window.log.error('isTrustedForSending: Needs non-blocking approval!'); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async loadIdentityKey(identifier: string): Promise<ArrayBuffer | undefined> { | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('loadIdentityKey: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     const id = window.textsecure.utils.unencodeNumber(identifier)[0]; | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (identityRecord) { | 
					
						
							|  |  |  |       return identityRecord.publicKey; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async _saveIdentityKey(data: IdentityKeyType): Promise<void> { | 
					
						
							|  |  |  |     if (!this.identityKeys) { | 
					
						
							|  |  |  |       throw new Error('_saveIdentityKey: this.identityKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { id } = data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     await window.Signal.Data.createOrUpdateIdentityKey(data); | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     this.identityKeys.set(id, { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       hydrated: false, | 
					
						
							|  |  |  |       fromDB: data, | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async saveIdentity( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     publicKey: ArrayBuffer, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     nonblockingApproval = false | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (!this.identityKeys) { | 
					
						
							|  |  |  |       throw new Error('saveIdentity: this.identityKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('saveIdentity: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (!(publicKey instanceof ArrayBuffer)) { | 
					
						
							|  |  |  |       // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |       publicKey = fromEncodedBinaryToArrayBuffer(publicKey); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (typeof nonblockingApproval !== 'boolean') { | 
					
						
							|  |  |  |       // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |       nonblockingApproval = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identifier = window.textsecure.utils.unencodeNumber( | 
					
						
							|  |  |  |       encodedAddress | 
					
						
							|  |  |  |     )[0]; | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  |     const id = window.ConversationController.getOrCreate( | 
					
						
							|  |  |  |       identifier, | 
					
						
							|  |  |  |       'private' | 
					
						
							|  |  |  |     ).get('id'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!identityRecord || !identityRecord.publicKey) { | 
					
						
							|  |  |  |       // Lookup failed, or the current key was removed, so save this one.
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.info('saveIdentity: Saving new identity...'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       await this._saveIdentityKey({ | 
					
						
							|  |  |  |         id, | 
					
						
							|  |  |  |         publicKey, | 
					
						
							|  |  |  |         firstUse: true, | 
					
						
							|  |  |  |         timestamp: Date.now(), | 
					
						
							|  |  |  |         verified: VerifiedStatus.DEFAULT, | 
					
						
							|  |  |  |         nonblockingApproval, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const oldpublicKey = identityRecord.publicKey; | 
					
						
							|  |  |  |     if (!constantTimeEqual(oldpublicKey, publicKey)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.info('saveIdentity: Replacing existing identity...'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       const previousStatus = identityRecord.verified; | 
					
						
							|  |  |  |       let verifiedStatus; | 
					
						
							|  |  |  |       if ( | 
					
						
							|  |  |  |         previousStatus === VerifiedStatus.VERIFIED || | 
					
						
							|  |  |  |         previousStatus === VerifiedStatus.UNVERIFIED | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         verifiedStatus = VerifiedStatus.UNVERIFIED; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         verifiedStatus = VerifiedStatus.DEFAULT; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await this._saveIdentityKey({ | 
					
						
							|  |  |  |         id, | 
					
						
							|  |  |  |         publicKey, | 
					
						
							|  |  |  |         firstUse: false, | 
					
						
							|  |  |  |         timestamp: Date.now(), | 
					
						
							|  |  |  |         verified: verifiedStatus, | 
					
						
							|  |  |  |         nonblockingApproval, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         this.trigger('keychange', identifier); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							|  |  |  |         window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |           'saveIdentity: error triggering keychange:', | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |           error && error.stack ? error.stack : error | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       await this.archiveSiblingSessions(encodedAddress); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (this.isNonBlockingApprovalRequired(identityRecord)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.info('saveIdentity: Setting approval status...'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       identityRecord.nonblockingApproval = nonblockingApproval; | 
					
						
							|  |  |  |       await this._saveIdentityKey(identityRecord); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   isNonBlockingApprovalRequired(identityRecord: IdentityKeyType): boolean { | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       !identityRecord.firstUse && | 
					
						
							| 
									
										
										
										
											2021-03-22 14:08:52 -07:00
										 |  |  |       isMoreRecentThan(identityRecord.timestamp, TIMESTAMP_THRESHOLD) && | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       !identityRecord.nonblockingApproval | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async saveIdentityWithAttributes( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     attributes: Partial<IdentityKeyType> | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error( | 
					
						
							|  |  |  |         'saveIdentityWithAttributes: encodedAddress was undefined/null' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identifier = window.textsecure.utils.unencodeNumber( | 
					
						
							|  |  |  |       encodedAddress | 
					
						
							|  |  |  |     )[0]; | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  |     const conv = window.ConversationController.getOrCreate( | 
					
						
							|  |  |  |       identifier, | 
					
						
							|  |  |  |       'private' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const id = conv.get('id'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const updates: Partial<IdentityKeyType> = { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       ...identityRecord, | 
					
						
							|  |  |  |       ...attributes, | 
					
						
							|  |  |  |       id, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (validateIdentityKey(updates)) { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       await this._saveIdentityKey(updates); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setApproval( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     nonblockingApproval: boolean | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('setApproval: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (typeof nonblockingApproval !== 'boolean') { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('setApproval: Invalid approval status'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identifier = window.textsecure.utils.unencodeNumber( | 
					
						
							|  |  |  |       encodedAddress | 
					
						
							|  |  |  |     )[0]; | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!identityRecord) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error(`setApproval: No identity record for ${identifier}`); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     identityRecord.nonblockingApproval = nonblockingApproval; | 
					
						
							|  |  |  |     await this._saveIdentityKey(identityRecord); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async setVerified( | 
					
						
							|  |  |  |     encodedAddress: string, | 
					
						
							|  |  |  |     verifiedStatus: number, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     publicKey?: ArrayBuffer | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (encodedAddress === null || encodedAddress === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('setVerified: encodedAddress was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (!validateVerifiedStatus(verifiedStatus)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('setVerified: Invalid verified status'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (arguments.length > 2 && !(publicKey instanceof ArrayBuffer)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('setVerified: Invalid public key'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(encodedAddress); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!identityRecord) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error(`setVerified: No identity record for ${encodedAddress}`); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!publicKey || constantTimeEqual(identityRecord.publicKey, publicKey)) { | 
					
						
							|  |  |  |       identityRecord.verified = verifiedStatus; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       if (validateIdentityKey(identityRecord)) { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |         await this._saveIdentityKey(identityRecord); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.info( | 
					
						
							|  |  |  |         'setVerified: No identity record for specified publicKey' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getVerified(identifier: string): Promise<number> { | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('getVerified: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  |     if (!identityRecord) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error(`getVerified: No identity record for ${identifier}`); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const verifiedStatus = identityRecord.verified; | 
					
						
							|  |  |  |     if (validateVerifiedStatus(verifiedStatus)) { | 
					
						
							|  |  |  |       return verifiedStatus; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return VerifiedStatus.DEFAULT; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Resolves to true if a new identity key was saved
 | 
					
						
							|  |  |  |   processContactSyncVerificationState( | 
					
						
							|  |  |  |     identifier: string, | 
					
						
							|  |  |  |     verifiedStatus: number, | 
					
						
							|  |  |  |     publicKey: ArrayBuffer | 
					
						
							|  |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (verifiedStatus === VerifiedStatus.UNVERIFIED) { | 
					
						
							|  |  |  |       return this.processUnverifiedMessage( | 
					
						
							|  |  |  |         identifier, | 
					
						
							|  |  |  |         verifiedStatus, | 
					
						
							|  |  |  |         publicKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return this.processVerifiedMessage(identifier, verifiedStatus, publicKey); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // This function encapsulates the non-Java behavior, since the mobile apps don't
 | 
					
						
							|  |  |  |   //   currently receive contact syncs and therefore will see a verify sync with
 | 
					
						
							|  |  |  |   //   UNVERIFIED status
 | 
					
						
							|  |  |  |   async processUnverifiedMessage( | 
					
						
							|  |  |  |     identifier: string, | 
					
						
							|  |  |  |     verifiedStatus: number, | 
					
						
							|  |  |  |     publicKey?: ArrayBuffer | 
					
						
							|  |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error( | 
					
						
							|  |  |  |         'processUnverifiedMessage: identifier was undefined/null' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('processUnverifiedMessage: Invalid public key'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let isEqual = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (identityRecord && publicKey) { | 
					
						
							|  |  |  |       isEqual = constantTimeEqual(publicKey, identityRecord.publicKey); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       identityRecord && | 
					
						
							|  |  |  |       isEqual && | 
					
						
							|  |  |  |       identityRecord.verified !== VerifiedStatus.UNVERIFIED | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await this.setVerified(identifier, verifiedStatus, publicKey); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (!identityRecord || !isEqual) { | 
					
						
							|  |  |  |       await this.saveIdentityWithAttributes(identifier, { | 
					
						
							|  |  |  |         publicKey, | 
					
						
							|  |  |  |         verified: verifiedStatus, | 
					
						
							|  |  |  |         firstUse: false, | 
					
						
							|  |  |  |         timestamp: Date.now(), | 
					
						
							|  |  |  |         nonblockingApproval: true, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (identityRecord && !isEqual) { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           this.trigger('keychange', identifier); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |             'processUnverifiedMessage: error triggering keychange:', | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |             error && error.stack ? error.stack : error | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await this.archiveAllSessions(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // The situation which could get us here is:
 | 
					
						
							|  |  |  |     //   1. had a previous key
 | 
					
						
							|  |  |  |     //   2. new key is the same
 | 
					
						
							|  |  |  |     //   3. desired new status is same as what we had before
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // This matches the Java method as of
 | 
					
						
							|  |  |  |   //   https://github.com/signalapp/Signal-Android/blob/d0bb68e1378f689e4d10ac6a46014164992ca4e4/src/org/thoughtcrime/securesms/util/IdentityUtil.java#L188
 | 
					
						
							|  |  |  |   async processVerifiedMessage( | 
					
						
							|  |  |  |     identifier: string, | 
					
						
							|  |  |  |     verifiedStatus: number, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     publicKey?: ArrayBuffer | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('processVerifiedMessage: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (!validateVerifiedStatus(verifiedStatus)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('processVerifiedMessage: Invalid verified status'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('processVerifiedMessage: Invalid public key'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let isEqual = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (identityRecord && publicKey) { | 
					
						
							|  |  |  |       isEqual = constantTimeEqual(publicKey, identityRecord.publicKey); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!identityRecord && verifiedStatus === VerifiedStatus.DEFAULT) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       window.log.info( | 
					
						
							|  |  |  |         'processVerifiedMessage: No existing record for default status' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       identityRecord && | 
					
						
							|  |  |  |       isEqual && | 
					
						
							|  |  |  |       identityRecord.verified !== VerifiedStatus.DEFAULT && | 
					
						
							|  |  |  |       verifiedStatus === VerifiedStatus.DEFAULT | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await this.setVerified(identifier, verifiedStatus, publicKey); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       verifiedStatus === VerifiedStatus.VERIFIED && | 
					
						
							|  |  |  |       (!identityRecord || | 
					
						
							|  |  |  |         (identityRecord && !isEqual) || | 
					
						
							|  |  |  |         (identityRecord && identityRecord.verified !== VerifiedStatus.VERIFIED)) | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await this.saveIdentityWithAttributes(identifier, { | 
					
						
							|  |  |  |         publicKey, | 
					
						
							|  |  |  |         verified: verifiedStatus, | 
					
						
							|  |  |  |         firstUse: false, | 
					
						
							|  |  |  |         timestamp: Date.now(), | 
					
						
							|  |  |  |         nonblockingApproval: true, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (identityRecord && !isEqual) { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           this.trigger('keychange', identifier); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							|  |  |  |             'processVerifiedMessage error triggering keychange:', | 
					
						
							|  |  |  |             error && error.stack ? error.stack : error | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await this.archiveAllSessions(identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // true signifies that we overwrote a previous key with a new one
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // We get here if we got a new key and the status is DEFAULT. If the
 | 
					
						
							|  |  |  |     //   message is out of date, we don't want to lose whatever more-secure
 | 
					
						
							|  |  |  |     //   state we had before.
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   isUntrusted(identifier: string): boolean { | 
					
						
							|  |  |  |     if (identifier === null || identifier === undefined) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error('isUntrusted: identifier was undefined/null'); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const identityRecord = this.getIdentityRecord(identifier); | 
					
						
							|  |  |  |     if (!identityRecord) { | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       throw new Error(`isUntrusted: No identity record for ${identifier}`); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							| 
									
										
										
										
											2021-03-22 14:08:52 -07:00
										 |  |  |       isMoreRecentThan(identityRecord.timestamp, TIMESTAMP_THRESHOLD) && | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       !identityRecord.nonblockingApproval && | 
					
						
							|  |  |  |       !identityRecord.firstUse | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeIdentityKey(identifier: string): Promise<void> { | 
					
						
							|  |  |  |     if (!this.identityKeys) { | 
					
						
							|  |  |  |       throw new Error('removeIdentityKey: this.identityKeys not yet cached!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const id = window.ConversationController.getConversationId(identifier); | 
					
						
							|  |  |  |     if (id) { | 
					
						
							| 
									
										
										
										
											2021-05-13 18:18:43 -07:00
										 |  |  |       this.identityKeys.delete(id); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |       await window.Signal.Data.removeIdentityKeyById(id); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await this.removeAllSessions(id); | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Not yet processed messages - for resiliency
 | 
					
						
							|  |  |  |   getUnprocessedCount(): Promise<number> { | 
					
						
							|  |  |  |     return window.Signal.Data.getUnprocessedCount(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getAllUnprocessed(): Promise<Array<UnprocessedType>> { | 
					
						
							|  |  |  |     return window.Signal.Data.getAllUnprocessed(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getUnprocessedById(id: string): Promise<UnprocessedType | undefined> { | 
					
						
							|  |  |  |     return window.Signal.Data.getUnprocessedById(id); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   addUnprocessed(data: UnprocessedType): Promise<string> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     // We need to pass forceSave because the data has an id already, which will cause
 | 
					
						
							|  |  |  |     //   an update instead of an insert.
 | 
					
						
							|  |  |  |     return window.Signal.Data.saveUnprocessed(data, { | 
					
						
							|  |  |  |       forceSave: true, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addMultipleUnprocessed(array: Array<UnprocessedType>): Promise<void> { | 
					
						
							|  |  |  |     // We need to pass forceSave because the data has an id already, which will cause
 | 
					
						
							|  |  |  |     //   an update instead of an insert.
 | 
					
						
							|  |  |  |     return window.Signal.Data.saveUnprocesseds(array, { | 
					
						
							|  |  |  |       forceSave: true, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   updateUnprocessedAttempts(id: string, attempts: number): Promise<void> { | 
					
						
							|  |  |  |     return window.Signal.Data.updateUnprocessedAttempts(id, attempts); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |   updateUnprocessedWithData( | 
					
						
							|  |  |  |     id: string, | 
					
						
							|  |  |  |     data: UnprocessedUpdateType | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     return window.Signal.Data.updateUnprocessedWithData(id, data); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   updateUnprocessedsWithData( | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     items: Array<{ id: string; data: UnprocessedUpdateType }> | 
					
						
							| 
									
										
										
										
											2021-04-05 15:18:19 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-02-26 15:42:45 -08:00
										 |  |  |     return window.Signal.Data.updateUnprocessedsWithData(items); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   removeUnprocessed(idOrArray: string | Array<string>): Promise<void> { | 
					
						
							|  |  |  |     return window.Signal.Data.removeUnprocessed(idOrArray); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   removeAllUnprocessed(): Promise<void> { | 
					
						
							|  |  |  |     return window.Signal.Data.removeAllUnprocessed(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeAllData(): Promise<void> { | 
					
						
							|  |  |  |     await window.Signal.Data.removeAll(); | 
					
						
							|  |  |  |     await this.hydrateCaches(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.storage.reset(); | 
					
						
							|  |  |  |     await window.storage.fetch(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.ConversationController.reset(); | 
					
						
							|  |  |  |     await window.ConversationController.load(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async removeAllConfiguration(): Promise<void> { | 
					
						
							|  |  |  |     await window.Signal.Data.removeAllConfiguration(); | 
					
						
							|  |  |  |     await this.hydrateCaches(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     window.storage.reset(); | 
					
						
							|  |  |  |     await window.storage.fetch(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | window.SignalProtocolStore = SignalProtocolStore; |