| 
									
										
										
										
											2023-01-03 11:55:46 -08:00
										 |  |  | // Copyright 2020 Signal Messenger, LLC
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | import PQueue from 'p-queue'; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | import { isNumber, omit, orderBy } from 'lodash'; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import EventTarget from './EventTarget'; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | import type { | 
					
						
							|  |  |  |   UploadKeysType, | 
					
						
							|  |  |  |   UploadKyberPreKeyType, | 
					
						
							|  |  |  |   UploadPreKeyType, | 
					
						
							|  |  |  |   UploadSignedPreKeyType, | 
					
						
							|  |  |  |   WebAPIType, | 
					
						
							|  |  |  | } from './WebAPI'; | 
					
						
							|  |  |  | import type { | 
					
						
							|  |  |  |   CompatPreKeyType, | 
					
						
							|  |  |  |   KeyPairType, | 
					
						
							|  |  |  |   KyberPreKeyType, | 
					
						
							|  |  |  |   PniKeyMaterialType, | 
					
						
							|  |  |  | } from './Types.d'; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | import ProvisioningCipher from './ProvisioningCipher'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { IncomingWebSocketRequest } from './WebsocketResources'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import createTaskWithTimeout from './TaskWithTimeout'; | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  | import * as Bytes from '../Bytes'; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  | import { RemoveAllConfiguration } from '../types/RemoveAllConfiguration'; | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | import * as Errors from '../types/errors'; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  | import { senderCertificateService } from '../services/senderCertificate'; | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | import { | 
					
						
							|  |  |  |   deriveAccessKey, | 
					
						
							|  |  |  |   generateRegistrationId, | 
					
						
							|  |  |  |   getRandomBytes, | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |   decryptDeviceName, | 
					
						
							|  |  |  |   encryptDeviceName, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | } from '../Crypto'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   generateKeyPair, | 
					
						
							|  |  |  |   generateSignedPreKey, | 
					
						
							|  |  |  |   generatePreKey, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   generateKyberPreKey, | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | } from '../Curve'; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | import type { ServiceIdString, PniString } from '../types/ServiceId'; | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  | import { | 
					
						
							|  |  |  |   ServiceIdKind, | 
					
						
							|  |  |  |   normalizeAci, | 
					
						
							|  |  |  |   toTaggedPni, | 
					
						
							|  |  |  |   isUntaggedPniString, | 
					
						
							|  |  |  | } from '../types/ServiceId'; | 
					
						
							| 
									
										
										
										
											2021-03-22 14:08:52 -07:00
										 |  |  | import { isMoreRecentThan, isOlderThan } from '../util/timestamp'; | 
					
						
							| 
									
										
										
										
											2021-05-05 11:39:16 -05:00
										 |  |  | import { ourProfileKeyService } from '../services/ourProfileKey'; | 
					
						
							| 
									
										
										
										
											2022-09-15 12:17:15 -07:00
										 |  |  | import { assertDev, strictAssert } from '../util/assert'; | 
					
						
							| 
									
										
										
										
											2022-06-01 17:48:16 +00:00
										 |  |  | import { getRegionCodeForNumber } from '../util/libphonenumberUtil'; | 
					
						
							| 
									
										
										
										
											2021-06-07 11:27:02 -05:00
										 |  |  | import { getProvisioningUrl } from '../util/getProvisioningUrl'; | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  | import { isNotNil } from '../util/isNotNil'; | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  | import { SignalService as Proto } from '../protobuf'; | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  | import * as log from '../logging/log'; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | import type { StorageAccessType } from '../types/Storage'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | type StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [kind in ServiceIdKind]: keyof StorageAccessType; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-03 15:26:00 -07:00
										 |  |  | const DAY = 24 * 60 * 60 * 1000; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const STARTING_KEY_ID = 1; | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | const PROFILE_KEY_LENGTH = 32; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | const KEY_TOO_OLD_THRESHOLD = 14 * DAY; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [ServiceIdKind.ACI]: 'maxKyberPreKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.Unknown]: 'maxKyberPreKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI', | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY; | 
					
						
							|  |  |  | const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5; | 
					
						
							|  |  |  | const LAST_RESORT_KEY_MINIMUM = 5; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [ServiceIdKind.ACI]: 'lastResortKeyUpdateTime', | 
					
						
							|  |  |  |   [ServiceIdKind.Unknown]: 'lastResortKeyUpdateTime', | 
					
						
							|  |  |  |   [ServiceIdKind.PNI]: 'lastResortKeyUpdateTimePNI', | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const PRE_KEY_ARCHIVE_AGE = 90 * DAY; | 
					
						
							|  |  |  | const PRE_KEY_GEN_BATCH_SIZE = 100; | 
					
						
							|  |  |  | const PRE_KEY_MAX_COUNT = 200; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [ServiceIdKind.ACI]: 'maxPreKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.Unknown]: 'maxPreKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.PNI]: 'maxPreKeyIdPNI', | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | const PRE_KEY_MINIMUM = 10; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [ServiceIdKind.ACI]: 'signedKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.Unknown]: 'signedKeyId', | 
					
						
							|  |  |  |   [ServiceIdKind.PNI]: 'signedKeyIdPNI', | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const SIGNED_PRE_KEY_ROTATION_AGE = DAY * 1.5; | 
					
						
							|  |  |  | const SIGNED_PRE_KEY_MINIMUM = 5; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = { | 
					
						
							|  |  |  |   [ServiceIdKind.ACI]: 'signedKeyUpdateTime', | 
					
						
							|  |  |  |   [ServiceIdKind.Unknown]: 'signedKeyUpdateTime', | 
					
						
							|  |  |  |   [ServiceIdKind.PNI]: 'signedKeyUpdateTimePNI', | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  | type CreateAccountOptionsType = Readonly<{ | 
					
						
							|  |  |  |   number: string; | 
					
						
							|  |  |  |   verificationCode: string; | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |   aciKeyPair: KeyPairType; | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |   pniKeyPair?: KeyPairType; | 
					
						
							|  |  |  |   profileKey?: Uint8Array; | 
					
						
							|  |  |  |   deviceName?: string; | 
					
						
							|  |  |  |   userAgent?: string; | 
					
						
							|  |  |  |   readReceipts?: boolean; | 
					
						
							|  |  |  |   accessKey?: Uint8Array; | 
					
						
							|  |  |  | }>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  | function getNextKeyId( | 
					
						
							|  |  |  |   kind: ServiceIdKind, | 
					
						
							|  |  |  |   keys: StorageKeyByServiceIdKind | 
					
						
							|  |  |  | ): number { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   const id = window.storage.get(keys[kind]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (isNumber(id)) { | 
					
						
							|  |  |  |     return id; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // For PNI ids, start with existing ACI id
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   if (kind === ServiceIdKind.PNI) { | 
					
						
							|  |  |  |     return window.storage.get(keys[ServiceIdKind.ACI], STARTING_KEY_ID); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return STARTING_KEY_ID; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | export default class AccountManager extends EventTarget { | 
					
						
							|  |  |  |   pending: Promise<void>; | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   pendingQueue?: PQueue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-23 10:23:50 -07:00
										 |  |  |   constructor(private readonly server: WebAPIType) { | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     super(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.pending = Promise.resolve(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   private async queueTask<T>(task: () => Promise<T>): Promise<T> { | 
					
						
							|  |  |  |     this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 }); | 
					
						
							|  |  |  |     const taskWithTimeout = createTaskWithTimeout(task, 'AccountManager task'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return this.pendingQueue.add(taskWithTimeout); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async requestVoiceVerification(number: string, token: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-11-30 18:51:53 +01:00
										 |  |  |     return this.server.requestVerificationVoice(number, token); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async requestSMSVerification(number: string, token: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-11-30 18:51:53 +01:00
										 |  |  |     return this.server.requestVerificationSMS(number, token); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   encryptDeviceName(name: string, identityKey: KeyPairType): string | null { | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     if (!name) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |     const encrypted = encryptDeviceName(name, identityKey.pubKey); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  |     const proto = new Proto.DeviceName(); | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |     proto.ephemeralPublic = encrypted.ephemeralPublic; | 
					
						
							|  |  |  |     proto.syntheticIv = encrypted.syntheticIv; | 
					
						
							|  |  |  |     proto.ciphertext = encrypted.ciphertext; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  |     const bytes = Proto.DeviceName.encode(proto).finish(); | 
					
						
							|  |  |  |     return Bytes.toBase64(bytes); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async decryptDeviceName(base64: string): Promise<string> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourAci = window.textsecure.storage.user.getCheckedAci(); | 
					
						
							| 
									
										
										
										
											2021-11-11 16:43:05 -06:00
										 |  |  |     const identityKey = | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       window.textsecure.storage.protocol.getIdentityKeyPair(ourAci); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     if (!identityKey) { | 
					
						
							|  |  |  |       throw new Error('decryptDeviceName: No identity key pair!'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  |     const bytes = Bytes.fromBase64(base64); | 
					
						
							|  |  |  |     const proto = Proto.DeviceName.decode(bytes); | 
					
						
							| 
									
										
										
										
											2022-09-15 12:17:15 -07:00
										 |  |  |     assertDev( | 
					
						
							| 
									
										
										
										
											2021-07-02 12:21:24 -07:00
										 |  |  |       proto.ephemeralPublic && proto.syntheticIv && proto.ciphertext, | 
					
						
							|  |  |  |       'Missing required fields in DeviceName' | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |     const name = decryptDeviceName(proto, identityKey.privKey); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return name; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async maybeUpdateDeviceName(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-11-11 16:43:05 -06:00
										 |  |  |     const isNameEncrypted = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getDeviceNameEncrypted(); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     if (isNameEncrypted) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  |     const deviceName = storage.user.getDeviceName(); | 
					
						
							| 
									
										
										
										
											2022-08-15 14:53:33 -07:00
										 |  |  |     const identityKeyPair = storage.protocol.getIdentityKeyPair( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       storage.user.getCheckedAci() | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     ); | 
					
						
							|  |  |  |     strictAssert( | 
					
						
							|  |  |  |       identityKeyPair !== undefined, | 
					
						
							|  |  |  |       "Can't encrypt device name without identity key pair" | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |     const base64 = this.encryptDeviceName(deviceName || '', identityKeyPair); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (base64) { | 
					
						
							|  |  |  |       await this.server.updateDeviceName(base64); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async deviceNameIsEncrypted(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     await window.textsecure.storage.user.setDeviceNameEncrypted(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   async registerSingleDevice( | 
					
						
							|  |  |  |     number: string, | 
					
						
							|  |  |  |     verificationCode: string | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     await this.queueTask(async () => { | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |       const aciKeyPair = generateKeyPair(); | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |       const pniKeyPair = generateKeyPair(); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       const profileKey = getRandomBytes(PROFILE_KEY_LENGTH); | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |       const accessKey = deriveAccessKey(profileKey); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |       const registrationBaton = this.server.startRegistration(); | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await this.createAccount({ | 
					
						
							|  |  |  |           number, | 
					
						
							|  |  |  |           verificationCode, | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |           aciKeyPair, | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |           pniKeyPair, | 
					
						
							|  |  |  |           profileKey, | 
					
						
							|  |  |  |           accessKey, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         const uploadKeys = async (kind: ServiceIdKind) => { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind); | 
					
						
							|  |  |  |           await this.server.registerKeys(keys, kind); | 
					
						
							|  |  |  |           await this._confirmKeys(keys, kind); | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         await uploadKeys(ServiceIdKind.ACI); | 
					
						
							|  |  |  |         await uploadKeys(ServiceIdKind.PNI); | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |       } finally { | 
					
						
							|  |  |  |         this.server.finishRegistration(registrationBaton); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |       await this.registrationDone(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async registerSecondDevice( | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     setProvisioningUrl: (url: string) => void, | 
					
						
							| 
									
										
										
										
											2021-12-03 11:46:44 -06:00
										 |  |  |     confirmNumber: (number?: string) => Promise<string> | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     const provisioningCipher = new ProvisioningCipher(); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  |     const pubKey = await provisioningCipher.getPublicKey(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     let envelopeCallbacks: | 
					
						
							|  |  |  |       | { | 
					
						
							|  |  |  |           resolve(data: Proto.ProvisionEnvelope): void; | 
					
						
							|  |  |  |           reject(error: Error): void; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       | undefined; | 
					
						
							|  |  |  |     const envelopePromise = new Promise<Proto.ProvisionEnvelope>( | 
					
						
							|  |  |  |       (resolve, reject) => { | 
					
						
							|  |  |  |         envelopeCallbacks = { resolve, reject }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     const wsr = await this.server.getProvisioningResource({ | 
					
						
							|  |  |  |       handleRequest(request: IncomingWebSocketRequest) { | 
					
						
							|  |  |  |         if ( | 
					
						
							|  |  |  |           request.path === '/v1/address' && | 
					
						
							|  |  |  |           request.verb === 'PUT' && | 
					
						
							|  |  |  |           request.body | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |           const proto = Proto.ProvisioningUuid.decode(request.body); | 
					
						
							|  |  |  |           const { uuid } = proto; | 
					
						
							|  |  |  |           if (!uuid) { | 
					
						
							|  |  |  |             throw new Error('registerSecondDevice: expected a UUID'); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           const url = getProvisioningUrl(uuid, pubKey); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 19:02:03 -05:00
										 |  |  |           window.SignalCI?.setProvisioningURL(url); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |           setProvisioningUrl(url); | 
					
						
							|  |  |  |           request.respond(200, 'OK'); | 
					
						
							|  |  |  |         } else if ( | 
					
						
							|  |  |  |           request.path === '/v1/message' && | 
					
						
							|  |  |  |           request.verb === 'PUT' && | 
					
						
							|  |  |  |           request.body | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |           const envelope = Proto.ProvisionEnvelope.decode(request.body); | 
					
						
							|  |  |  |           request.respond(200, 'OK'); | 
					
						
							|  |  |  |           wsr.close(); | 
					
						
							|  |  |  |           envelopeCallbacks?.resolve(envelope); | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |           log.error('Unknown websocket message', request.path); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info('provisioning socket open'); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     wsr.addEventListener('close', ({ code, reason }) => { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.info(`provisioning socket closed. Code: ${code} Reason: ${reason}`); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       // Note: if we have resolved the envelope already - this has no effect
 | 
					
						
							|  |  |  |       envelopeCallbacks?.reject(new Error('websocket closed')); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const envelope = await envelopePromise; | 
					
						
							|  |  |  |     const provisionMessage = await provisioningCipher.decrypt(envelope); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await this.queueTask(async () => { | 
					
						
							|  |  |  |       const deviceName = await confirmNumber(provisionMessage.number); | 
					
						
							|  |  |  |       if (typeof deviceName !== 'string' || deviceName.length === 0) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           'AccountManager.registerSecondDevice: Invalid device name' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if ( | 
					
						
							|  |  |  |         !provisionMessage.number || | 
					
						
							|  |  |  |         !provisionMessage.provisioningCode || | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |         !provisionMessage.aciKeyPair | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       ) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           'AccountManager.registerSecondDevice: Provision message was missing key data' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |       const registrationBaton = this.server.startRegistration(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await this.createAccount({ | 
					
						
							|  |  |  |           number: provisionMessage.number, | 
					
						
							|  |  |  |           verificationCode: provisionMessage.provisioningCode, | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |           aciKeyPair: provisionMessage.aciKeyPair, | 
					
						
							|  |  |  |           pniKeyPair: provisionMessage.pniKeyPair, | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |           profileKey: provisionMessage.profileKey, | 
					
						
							|  |  |  |           deviceName, | 
					
						
							|  |  |  |           userAgent: provisionMessage.userAgent, | 
					
						
							|  |  |  |           readReceipts: provisionMessage.readReceipts, | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         const uploadKeys = async (kind: ServiceIdKind) => { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind); | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           try { | 
					
						
							|  |  |  |             await this.server.registerKeys(keys, kind); | 
					
						
							|  |  |  |             await this._confirmKeys(keys, kind); | 
					
						
							|  |  |  |           } catch (error) { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |             if (kind === ServiceIdKind.PNI) { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |               log.error( | 
					
						
							|  |  |  |                 'Failed to upload PNI prekeys. Moving on', | 
					
						
							|  |  |  |                 Errors.toLogFormat(error) | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |               return; | 
					
						
							| 
									
										
										
										
											2022-04-22 12:02:23 -07:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |             throw error; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         await uploadKeys(ServiceIdKind.ACI); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         if (provisionMessage.pniKeyPair) { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           await uploadKeys(ServiceIdKind.PNI); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-17 22:26:50 +01:00
										 |  |  |       } finally { | 
					
						
							|  |  |  |         this.server.finishRegistration(registrationBaton); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       await this.registrationDone(); | 
					
						
							| 
									
										
										
										
											2021-06-09 15:28:54 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   private getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  |     const store = storage.protocol; | 
					
						
							|  |  |  |     let identityKey: KeyPairType | undefined; | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       identityKey = store.getIdentityKeyPair(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       const errorText = Errors.toLogFormat(error); | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |         `getIdentityKeyOrThrow: Failed to fetch identity key - ${errorText}` | 
					
						
							| 
									
										
										
										
											2023-06-08 18:08:24 -07:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-08 18:08:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (!identityKey) { | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |       throw new Error('getIdentityKeyOrThrow: Missing identity key'); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-08 18:08:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     return identityKey; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async generateNewPreKeys( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     count: number | 
					
						
							|  |  |  |   ): Promise<Array<UploadPreKeyType>> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.generateNewPreKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  |     const store = storage.protocol; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const startId = getNextKeyId(serviceIdKind, PRE_KEY_ID_KEY); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     log.info(`${logId}: Generating ${count} new keys starting at ${startId}`); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (typeof startId !== 'number') { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         `${logId}: Invalid ${PRE_KEY_ID_KEY[serviceIdKind]} in storage` | 
					
						
							| 
									
										
										
										
											2023-06-08 18:08:24 -07:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const toSave: Array<CompatPreKeyType> = []; | 
					
						
							|  |  |  |     for (let keyId = startId; keyId < startId + count; keyId += 1) { | 
					
						
							|  |  |  |       toSave.push(generatePreKey(keyId)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |     await Promise.all([ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       store.storePreKeys(ourServiceId, toSave), | 
					
						
							|  |  |  |       storage.put(PRE_KEY_ID_KEY[serviceIdKind], startId + count), | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return toSave.map(key => ({ | 
					
						
							|  |  |  |       keyId: key.keyId, | 
					
						
							|  |  |  |       publicKey: key.keyPair.pubKey, | 
					
						
							|  |  |  |     })); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   private async generateNewKyberPreKeys( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     count: number | 
					
						
							|  |  |  |   ): Promise<Array<UploadKyberPreKeyType>> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.generateNewKyberPreKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  |     const store = storage.protocol; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const startId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     log.info(`${logId}: Generating ${count} new keys starting at ${startId}`); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (typeof startId !== 'number') { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         `${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage` | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const identityKey = this.getIdentityKeyOrThrow(ourServiceId); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const toSave: Array<Omit<KyberPreKeyType, 'id'>> = []; | 
					
						
							|  |  |  |     const toUpload: Array<UploadKyberPreKeyType> = []; | 
					
						
							|  |  |  |     const now = Date.now(); | 
					
						
							|  |  |  |     for (let keyId = startId; keyId < startId + count; keyId += 1) { | 
					
						
							|  |  |  |       const record = generateKyberPreKey(identityKey, keyId); | 
					
						
							|  |  |  |       toSave.push({ | 
					
						
							|  |  |  |         createdAt: now, | 
					
						
							|  |  |  |         data: record.serialize(), | 
					
						
							|  |  |  |         isConfirmed: false, | 
					
						
							|  |  |  |         isLastResort: false, | 
					
						
							|  |  |  |         keyId, | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |         ourServiceId, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |       toUpload.push({ | 
					
						
							|  |  |  |         keyId, | 
					
						
							|  |  |  |         publicKey: record.publicKey().serialize(), | 
					
						
							|  |  |  |         signature: record.signature(), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-11 13:29:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |     await Promise.all([ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       store.storeKyberPreKeys(ourServiceId, toSave), | 
					
						
							|  |  |  |       storage.put(KYBER_KEY_ID_KEY[serviceIdKind], startId + count), | 
					
						
							| 
									
										
										
										
											2023-07-14 12:18:32 -07:00
										 |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return toUpload; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async maybeUpdateKeys(serviceIdKind: ServiceIdKind): Promise<void> { | 
					
						
							|  |  |  |     const logId = `maybeUpdateKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     await this.queueTask(async () => { | 
					
						
							| 
									
										
										
										
											2023-08-21 13:15:10 -07:00
										 |  |  |       const { storage } = window.textsecure; | 
					
						
							|  |  |  |       let identityKey: KeyPairType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							|  |  |  |         identityKey = this.getIdentityKeyOrThrow(ourServiceId); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							|  |  |  |         if (serviceIdKind === ServiceIdKind.PNI) { | 
					
						
							|  |  |  |           log.info( | 
					
						
							|  |  |  |             `${logId}: Not enough information to update PNI keys`, | 
					
						
							|  |  |  |             Errors.toLogFormat(error) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         throw error; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       const { count: preKeyCount, pqCount: kyberPreKeyCount } = | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         await this.server.getMyKeyCounts(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       let preKeys: Array<UploadPreKeyType> | undefined; | 
					
						
							|  |  |  |       if (preKeyCount < PRE_KEY_MINIMUM) { | 
					
						
							|  |  |  |         log.info( | 
					
						
							|  |  |  |           `${logId}: Server prekey count is ${preKeyCount}, generating a new set` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         preKeys = await this.generateNewPreKeys( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           serviceIdKind, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           PRE_KEY_GEN_BATCH_SIZE | 
					
						
							| 
									
										
										
										
											2020-06-11 13:29:14 -07:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       let pqPreKeys: Array<UploadKyberPreKeyType> | undefined; | 
					
						
							|  |  |  |       if (kyberPreKeyCount < PRE_KEY_MINIMUM) { | 
					
						
							|  |  |  |         log.info( | 
					
						
							|  |  |  |           `${logId}: Server kyber prekey count is ${kyberPreKeyCount}, generating a new set` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         pqPreKeys = await this.generateNewKyberPreKeys( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           serviceIdKind, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           PRE_KEY_GEN_BATCH_SIZE | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         serviceIdKind | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if ( | 
					
						
							|  |  |  |         !preKeys?.length && | 
					
						
							|  |  |  |         !signedPreKey && | 
					
						
							|  |  |  |         !pqLastResortPreKey && | 
					
						
							|  |  |  |         !pqPreKeys?.length | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         log.info(`${logId}: No new keys are needed; returning early`); | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       const keySummary: Array<string> = []; | 
					
						
							|  |  |  |       if (preKeys?.length) { | 
					
						
							|  |  |  |         keySummary.push(`${!preKeys?.length || 0} prekeys`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (signedPreKey) { | 
					
						
							|  |  |  |         keySummary.push('a signed prekey'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (pqLastResortPreKey) { | 
					
						
							|  |  |  |         keySummary.push('a last-resort kyber prekey'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (pqPreKeys?.length) { | 
					
						
							|  |  |  |         keySummary.push(`${!pqPreKeys?.length || 0} kyber prekeys`); | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       log.info(`${logId}: Uploading with ${keySummary.join(', ')}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const toUpload = { | 
					
						
							|  |  |  |         identityKey: identityKey.pubKey, | 
					
						
							|  |  |  |         preKeys, | 
					
						
							|  |  |  |         pqPreKeys, | 
					
						
							|  |  |  |         pqLastResortPreKey, | 
					
						
							|  |  |  |         signedPreKey, | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-28 17:26:39 -07:00
										 |  |  |       await this.server.registerKeys(toUpload, serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await this._confirmKeys(toUpload, serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const { count: updatedPreKeyCount, pqCount: updatedKyberPreKeyCount } = | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         await this.server.getMyKeyCounts(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |       log.info( | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         `${logId}: Successfully updated; ` + | 
					
						
							|  |  |  |           `server prekey count: ${updatedPreKeyCount}, ` + | 
					
						
							|  |  |  |           `server kyber prekey count: ${updatedKyberPreKeyCount}` | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await this._cleanSignedPreKeys(serviceIdKind); | 
					
						
							|  |  |  |       await this._cleanLastResortKeys(serviceIdKind); | 
					
						
							|  |  |  |       await this._cleanPreKeys(serviceIdKind); | 
					
						
							|  |  |  |       await this._cleanKyberPreKeys(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   areKeysOutOfDate(serviceIdKind: ServiceIdKind): boolean { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const signedPreKeyTime = window.storage.get( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       0 | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const lastResortKeyTime = window.storage.get( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       0 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (isOlderThan(signedPreKeyTime, KEY_TOO_OLD_THRESHOLD)) { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (isOlderThan(lastResortKeyTime, KEY_TOO_OLD_THRESHOLD)) { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   private async maybeUpdateSignedPreKey( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   ): Promise<UploadSignedPreKeyType | undefined> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							|  |  |  |     const signedKeyId = getNextKeyId(serviceIdKind, SIGNED_PRE_KEY_ID_KEY); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (typeof signedKeyId !== 'number') { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         `${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[serviceIdKind]} in storage` | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const keys = await store.loadSignedPreKeys(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const sortedKeys = orderBy(keys, ['created_at'], ['desc']); | 
					
						
							|  |  |  |     const confirmedKeys = sortedKeys.filter(key => key.confirmed); | 
					
						
							|  |  |  |     const mostRecent = confirmedKeys[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const lastUpdate = mostRecent?.created_at; | 
					
						
							|  |  |  |     if (isMoreRecentThan(lastUpdate || 0, SIGNED_PRE_KEY_ROTATION_AGE)) { | 
					
						
							|  |  |  |       log.warn( | 
					
						
							|  |  |  |         `${logId}: ${confirmedKeys.length} confirmed keys, ` + | 
					
						
							|  |  |  |           `most recent was created ${lastUpdate}. No need to update.` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const existing = window.storage.get( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind] | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							|  |  |  |       if (lastUpdate && !existing) { | 
					
						
							|  |  |  |         log.warn(`${logId}: Updating last update time to ${lastUpdate}`); | 
					
						
							|  |  |  |         await window.storage.put( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           lastUpdate | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2021-12-09 20:45:21 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const identityKey = this.getIdentityKeyOrThrow(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const key = await generateSignedPreKey(identityKey, signedKeyId); | 
					
						
							|  |  |  |     log.info(`${logId}: Saving new signed prekey`, key.keyId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await Promise.all([ | 
					
						
							|  |  |  |       window.textsecure.storage.put( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         SIGNED_PRE_KEY_ID_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         signedKeyId + 1 | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       store.storeSignedPreKey(ourServiceId, key.keyId, key.keyPair), | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       keyId: key.keyId, | 
					
						
							|  |  |  |       publicKey: key.keyPair.pubKey, | 
					
						
							|  |  |  |       signature: key.signature, | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   private async maybeUpdateLastResortKyberKey( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   ): Promise<UploadSignedPreKeyType | undefined> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `maybeUpdateLastResortKyberKey(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							|  |  |  |     const kyberKeyId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (typeof kyberKeyId !== 'number') { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         `${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage` | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const keys = store.loadKyberPreKeys(ourServiceId, { isLastResort: true }); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const sortedKeys = orderBy(keys, ['createdAt'], ['desc']); | 
					
						
							|  |  |  |     const confirmedKeys = sortedKeys.filter(key => key.isConfirmed); | 
					
						
							|  |  |  |     const mostRecent = confirmedKeys[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const lastUpdate = mostRecent?.createdAt; | 
					
						
							|  |  |  |     if (isMoreRecentThan(lastUpdate || 0, LAST_RESORT_KEY_ROTATION_AGE)) { | 
					
						
							|  |  |  |       log.warn( | 
					
						
							|  |  |  |         `${logId}: ${confirmedKeys.length} confirmed keys, ` + | 
					
						
							|  |  |  |           `most recent was created ${lastUpdate}. No need to update.` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const existing = window.storage.get( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind] | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       ); | 
					
						
							|  |  |  |       if (lastUpdate && !existing) { | 
					
						
							|  |  |  |         log.warn(`${logId}: Updating last update time to ${lastUpdate}`); | 
					
						
							|  |  |  |         await window.storage.put( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |           lastUpdate | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const identityKey = this.getIdentityKeyOrThrow(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const keyId = kyberKeyId; | 
					
						
							|  |  |  |     const record = await generateKyberPreKey(identityKey, keyId); | 
					
						
							|  |  |  |     log.info(`${logId}: Saving new last resort prekey`, keyId); | 
					
						
							|  |  |  |     const key = { | 
					
						
							|  |  |  |       createdAt: Date.now(), | 
					
						
							|  |  |  |       data: record.serialize(), | 
					
						
							|  |  |  |       isConfirmed: false, | 
					
						
							|  |  |  |       isLastResort: true, | 
					
						
							|  |  |  |       keyId, | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |       ourServiceId, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await Promise.all([ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       window.textsecure.storage.put( | 
					
						
							|  |  |  |         KYBER_KEY_ID_KEY[serviceIdKind], | 
					
						
							|  |  |  |         kyberKeyId + 1 | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       store.storeKyberPreKeys(ourServiceId, [key]), | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       keyId, | 
					
						
							|  |  |  |       publicKey: record.publicKey().serialize(), | 
					
						
							|  |  |  |       signature: record.signature(), | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   // Exposed only for tests
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async _cleanSignedPreKeys(serviceIdKind: ServiceIdKind): Promise<void> { | 
					
						
							|  |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.cleanSignedPreKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const allKeys = store.loadSignedPreKeys(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const sortedKeys = orderBy(allKeys, ['created_at'], ['desc']); | 
					
						
							|  |  |  |     const confirmed = sortedKeys.filter(key => key.confirmed); | 
					
						
							|  |  |  |     const unconfirmed = sortedKeys.filter(key => !key.confirmed); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const recent = sortedKeys[0] ? sortedKeys[0].keyId : 'none'; | 
					
						
							| 
									
										
										
										
											2021-08-03 15:26:00 -07:00
										 |  |  |     const recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none'; | 
					
						
							|  |  |  |     const recentUnconfirmed = unconfirmed[0] ? unconfirmed[0].keyId : 'none'; | 
					
						
							| 
									
										
										
										
											2022-09-27 09:33:56 -07:00
										 |  |  |     log.info(`${logId}: Most recent signed key: ${recent}`); | 
					
						
							|  |  |  |     log.info(`${logId}: Most recent confirmed signed key: ${recentConfirmed}`); | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2022-09-27 09:33:56 -07:00
										 |  |  |       `${logId}: Most recent unconfirmed signed key: ${recentUnconfirmed}` | 
					
						
							| 
									
										
										
										
											2021-08-03 15:26:00 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2022-09-27 09:33:56 -07:00
										 |  |  |       `${logId}: Total signed key count:`, | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       sortedKeys.length, | 
					
						
							| 
									
										
										
										
											2021-08-03 15:26:00 -07:00
										 |  |  |       '-', | 
					
						
							|  |  |  |       confirmed.length, | 
					
						
							|  |  |  |       'confirmed' | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     // Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than SIGNED_PRE_KEY_ARCHIVE_AGE
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const toDelete: Array<number> = []; | 
					
						
							|  |  |  |     sortedKeys.forEach((key, index) => { | 
					
						
							|  |  |  |       if (index < SIGNED_PRE_KEY_MINIMUM) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const createdAt = key.created_at || 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (isOlderThan(createdAt, SIGNED_PRE_KEY_ARCHIVE_AGE)) { | 
					
						
							|  |  |  |         const timestamp = new Date(createdAt).toJSON(); | 
					
						
							|  |  |  |         const confirmedText = key.confirmed ? ' (confirmed)' : ''; | 
					
						
							|  |  |  |         log.info( | 
					
						
							|  |  |  |           `${logId}: Removing signed prekey: ${key.keyId} with ` + | 
					
						
							|  |  |  |             `timestamp ${timestamp}${confirmedText}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         toDelete.push(key.keyId); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     if (toDelete.length > 0) { | 
					
						
							|  |  |  |       log.info(`${logId}: Removing ${toDelete.length} signed prekeys`); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.removeSignedPreKeys(ourServiceId, toDelete); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Exposed only for tests
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async _cleanLastResortKeys(serviceIdKind: ServiceIdKind): Promise<void> { | 
					
						
							|  |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.cleanLastResortKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const allKeys = store.loadKyberPreKeys(ourServiceId, { | 
					
						
							|  |  |  |       isLastResort: true, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const sortedKeys = orderBy(allKeys, ['createdAt'], ['desc']); | 
					
						
							|  |  |  |     const confirmed = sortedKeys.filter(key => key.isConfirmed); | 
					
						
							|  |  |  |     const unconfirmed = sortedKeys.filter(key => !key.isConfirmed); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const recent = sortedKeys[0] ? sortedKeys[0].keyId : 'none'; | 
					
						
							|  |  |  |     const recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none'; | 
					
						
							|  |  |  |     const recentUnconfirmed = unconfirmed[0] ? unconfirmed[0].keyId : 'none'; | 
					
						
							|  |  |  |     log.info(`${logId}: Most recent last resort key: ${recent}`); | 
					
						
							|  |  |  |     log.info( | 
					
						
							|  |  |  |       `${logId}: Most recent confirmed last resort key: ${recentConfirmed}` | 
					
						
							| 
									
										
										
										
											2021-08-03 15:26:00 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     log.info( | 
					
						
							|  |  |  |       `${logId}: Most recent unconfirmed last resort key: ${recentUnconfirmed}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     log.info( | 
					
						
							|  |  |  |       `${logId}: Total last resort key count:`, | 
					
						
							|  |  |  |       sortedKeys.length, | 
					
						
							|  |  |  |       '-', | 
					
						
							|  |  |  |       confirmed.length, | 
					
						
							|  |  |  |       'confirmed' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than LAST_RESORT_KEY_ARCHIVE_AGE
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const toDelete: Array<number> = []; | 
					
						
							|  |  |  |     sortedKeys.forEach((key, index) => { | 
					
						
							|  |  |  |       if (index < LAST_RESORT_KEY_MINIMUM) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const createdAt = key.createdAt || 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (isOlderThan(createdAt, LAST_RESORT_KEY_ARCHIVE_AGE)) { | 
					
						
							|  |  |  |         const timestamp = new Date(createdAt).toJSON(); | 
					
						
							|  |  |  |         const confirmedText = key.isConfirmed ? ' (confirmed)' : ''; | 
					
						
							|  |  |  |         log.info( | 
					
						
							|  |  |  |           `${logId}: Removing last resort key: ${key.keyId} with ` + | 
					
						
							|  |  |  |             `timestamp ${timestamp}${confirmedText}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         toDelete.push(key.keyId); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     if (toDelete.length > 0) { | 
					
						
							|  |  |  |       log.info(`${logId}: Removing ${toDelete.length} last resort keys`); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.removeKyberPreKeys(ourServiceId, toDelete); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async _cleanPreKeys(serviceIdKind: ServiceIdKind): Promise<void> { | 
					
						
							|  |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.cleanPreKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const preKeys = store.loadPreKeys(ourServiceId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const toDelete: Array<number> = []; | 
					
						
							|  |  |  |     const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sortedKeys.forEach((key, index) => { | 
					
						
							|  |  |  |       if (index < PRE_KEY_MAX_COUNT) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const createdAt = key.createdAt || 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (isOlderThan(createdAt, PRE_KEY_ARCHIVE_AGE)) { | 
					
						
							|  |  |  |         toDelete.push(key.keyId); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     log.info(`${logId}: ${sortedKeys.length} total prekeys`); | 
					
						
							|  |  |  |     if (toDelete.length > 0) { | 
					
						
							|  |  |  |       log.info(`${logId}: Removing ${toDelete.length} obsolete prekeys`); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.removePreKeys(ourServiceId, toDelete); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async _cleanKyberPreKeys(serviceIdKind: ServiceIdKind): Promise<void> { | 
					
						
							|  |  |  |     const ourServiceId = | 
					
						
							|  |  |  |       window.textsecure.storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = window.textsecure.storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.cleanKyberPreKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const preKeys = store.loadKyberPreKeys(ourServiceId, { | 
					
						
							|  |  |  |       isLastResort: false, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const toDelete: Array<number> = []; | 
					
						
							|  |  |  |     const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sortedKeys.forEach((key, index) => { | 
					
						
							|  |  |  |       if (index < PRE_KEY_MAX_COUNT) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const createdAt = key.createdAt || 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (isOlderThan(createdAt, PRE_KEY_ARCHIVE_AGE)) { | 
					
						
							|  |  |  |         toDelete.push(key.keyId); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     log.info(`${logId}: ${sortedKeys.length} total prekeys`); | 
					
						
							|  |  |  |     if (toDelete.length > 0) { | 
					
						
							|  |  |  |       log.info(`${logId}: Removing ${toDelete.length} kyber keys`); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.removeKyberPreKeys(ourServiceId, toDelete); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |   async createAccount({ | 
					
						
							|  |  |  |     number, | 
					
						
							|  |  |  |     verificationCode, | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |     aciKeyPair, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |     pniKeyPair, | 
					
						
							|  |  |  |     profileKey, | 
					
						
							|  |  |  |     deviceName, | 
					
						
							|  |  |  |     userAgent, | 
					
						
							|  |  |  |     readReceipts, | 
					
						
							|  |  |  |     accessKey, | 
					
						
							|  |  |  |   }: CreateAccountOptionsType): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |     let password = Bytes.toBase64(getRandomBytes(16)); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     password = password.substring(0, password.length - 2); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  |     const registrationId = generateRegistrationId(); | 
					
						
							| 
									
										
										
										
											2022-10-04 17:50:07 -07:00
										 |  |  |     const pniRegistrationId = generateRegistrationId(); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |     const previousNumber = storage.user.getNumber(); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const previousACI = storage.user.getAci(); | 
					
						
							|  |  |  |     const previousPNI = storage.user.getPni(); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     let encryptedDeviceName; | 
					
						
							|  |  |  |     if (deviceName) { | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |       encryptedDeviceName = this.encryptDeviceName(deviceName, aciKeyPair); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       await this.deviceNameIsEncrypted(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       `createAccount: Number is ${number}, password has length: ${ | 
					
						
							|  |  |  |         password ? password.length : 'none' | 
					
						
							|  |  |  |       }`
 | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:50:07 -07:00
										 |  |  |     const response = await this.server.confirmCode({ | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       number, | 
					
						
							| 
									
										
										
										
											2022-10-04 17:50:07 -07:00
										 |  |  |       code: verificationCode, | 
					
						
							|  |  |  |       newPassword: password, | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       registrationId, | 
					
						
							| 
									
										
										
										
											2022-10-04 17:50:07 -07:00
										 |  |  |       pniRegistrationId, | 
					
						
							|  |  |  |       deviceName: encryptedDeviceName, | 
					
						
							|  |  |  |       accessKey, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourAci = normalizeAci(response.uuid, 'createAccount'); | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |     strictAssert( | 
					
						
							|  |  |  |       isUntaggedPniString(response.pni), | 
					
						
							|  |  |  |       'Response pni must be untagged' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const ourPni = toTaggedPni(response.pni); | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const uuidChanged = previousACI && ourAci && previousACI !== ourAci; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 11:54:20 -07:00
										 |  |  |     // We only consider the number changed if we didn't have a UUID before
 | 
					
						
							|  |  |  |     const numberChanged = | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |       !previousACI && previousNumber && previousNumber !== number; | 
					
						
							| 
									
										
										
										
											2021-06-08 11:54:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (uuidChanged || numberChanged) { | 
					
						
							|  |  |  |       if (uuidChanged) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.warn( | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  |           'createAccount: New uuid is different from old uuid; deleting all previous data' | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-06-08 11:54:20 -07:00
										 |  |  |       if (numberChanged) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.warn( | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  |           'createAccount: New number is different from old number; deleting all previous data' | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |         await storage.protocol.removeAllData(); | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  |         log.info('createAccount: Successfully deleted previous data'); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.error( | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |           'Something went wrong deleting data from previous number', | 
					
						
							| 
									
										
										
										
											2022-11-22 10:43:43 -08:00
										 |  |  |           Errors.toLogFormat(error) | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       log.info('createAccount: Erasing configuration (soft)'); | 
					
						
							|  |  |  |       await storage.protocol.removeAllConfiguration( | 
					
						
							|  |  |  |         RemoveAllConfiguration.Soft | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 10:31:34 -07:00
										 |  |  |     await senderCertificateService.clear(); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |     const previousUuids = [previousACI, previousPNI].filter(isNotNil); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (previousUuids.length > 0) { | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |       await Promise.all([ | 
					
						
							|  |  |  |         storage.put( | 
					
						
							|  |  |  |           'identityKeyMap', | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |           omit(storage.get('identityKeyMap') || {}, previousUuids) | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |         ), | 
					
						
							|  |  |  |         storage.put( | 
					
						
							|  |  |  |           'registrationIdMap', | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |           omit(storage.get('registrationIdMap') || {}, previousUuids) | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |         ), | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-23 10:23:50 -07:00
										 |  |  |     // `setCredentials` needs to be called
 | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     // before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
 | 
					
						
							| 
									
										
										
										
											2023-01-01 13:41:40 +02:00
										 |  |  |     // indirectly calls `ConversationController.getConversationId()` which
 | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     // initializes the conversation for the given number (our number) which
 | 
					
						
							|  |  |  |     // calls out to the user storage API to get the stored UUID and number
 | 
					
						
							|  |  |  |     // information.
 | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     await storage.user.setCredentials({ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       aci: ourAci, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |       pni: ourPni, | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |       number, | 
					
						
							| 
									
										
										
										
											2021-07-23 10:23:50 -07:00
										 |  |  |       deviceId: response.deviceId ?? 1, | 
					
						
							|  |  |  |       deviceName: deviceName ?? undefined, | 
					
						
							|  |  |  |       password, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 08:57:09 -08:00
										 |  |  |     // This needs to be done very early, because it changes how things are saved in the
 | 
					
						
							|  |  |  |     //   database. Your identity, for example, in the saveIdentityWithAttributes call
 | 
					
						
							|  |  |  |     //   below.
 | 
					
						
							| 
									
										
										
										
											2022-12-05 14:46:54 -08:00
										 |  |  |     const { conversation } = window.ConversationController.maybeMergeContacts({ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       aci: ourAci, | 
					
						
							| 
									
										
										
										
											2022-08-16 16:52:34 -07:00
										 |  |  |       pni: ourPni, | 
					
						
							| 
									
										
										
										
											2021-01-15 08:57:09 -08:00
										 |  |  |       e164: number, | 
					
						
							| 
									
										
										
										
											2022-01-20 14:44:25 -08:00
										 |  |  |       reason: 'createAccount', | 
					
						
							| 
									
										
										
										
											2021-01-15 08:57:09 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 14:46:54 -08:00
										 |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       throw new Error('registrationDone: no conversation!'); | 
					
						
							| 
									
										
										
										
											2021-01-15 08:57:09 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |     const identityAttrs = { | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |       firstUse: true, | 
					
						
							|  |  |  |       timestamp: Date.now(), | 
					
						
							|  |  |  |       verified: storage.protocol.VerifiedStatus.VERIFIED, | 
					
						
							|  |  |  |       nonblockingApproval: true, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // update our own identity key, which may have changed
 | 
					
						
							|  |  |  |     // if we're relinking after a reinstall on the master device
 | 
					
						
							|  |  |  |     await Promise.all([ | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       storage.protocol.saveIdentityWithAttributes(ourAci, { | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |         ...identityAttrs, | 
					
						
							| 
									
										
										
										
											2022-03-01 15:01:21 -08:00
										 |  |  |         publicKey: aciKeyPair.pubKey, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |       }), | 
					
						
							|  |  |  |       pniKeyPair | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         ? storage.protocol.saveIdentityWithAttributes(ourPni, { | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |             ...identityAttrs, | 
					
						
							|  |  |  |             publicKey: pniKeyPair.pubKey, | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         : Promise.resolve(), | 
					
						
							|  |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     const identityKeyMap = { | 
					
						
							|  |  |  |       ...(storage.get('identityKeyMap') || {}), | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       [ourAci]: aciKeyPair, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |       ...(pniKeyPair | 
					
						
							|  |  |  |         ? { | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |             [ourPni]: pniKeyPair, | 
					
						
							| 
									
										
										
										
											2021-12-03 03:06:32 +01:00
										 |  |  |           } | 
					
						
							|  |  |  |         : {}), | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  |     const registrationIdMap = { | 
					
						
							|  |  |  |       ...(storage.get('registrationIdMap') || {}), | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       [ourAci]: registrationId, | 
					
						
							| 
									
										
										
										
											2022-10-04 17:50:07 -07:00
										 |  |  |       [ourPni]: pniRegistrationId, | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await storage.put('identityKeyMap', identityKeyMap); | 
					
						
							|  |  |  |     await storage.put('registrationIdMap', registrationIdMap); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     if (profileKey) { | 
					
						
							| 
									
										
										
										
											2021-05-05 11:39:16 -05:00
										 |  |  |       await ourProfileKeyService.set(profileKey); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (userAgent) { | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |       await storage.put('userAgent', userAgent); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     await storage.put('read-receipt-setting', Boolean(readReceipts)); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 17:48:16 +00:00
										 |  |  |     const regionCode = getRegionCodeForNumber(number); | 
					
						
							| 
									
										
										
										
											2021-09-09 19:38:11 -07:00
										 |  |  |     await storage.put('regionCode', regionCode); | 
					
						
							|  |  |  |     await storage.protocol.hydrateCaches(); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   // Exposed only for testing
 | 
					
						
							|  |  |  |   public async _confirmKeys( | 
					
						
							|  |  |  |     keys: UploadKeysType, | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AccountManager.confirmKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  |     const store = storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const updatedAt = Date.now(); | 
					
						
							|  |  |  |     const { signedPreKey, pqLastResortPreKey } = keys; | 
					
						
							|  |  |  |     if (signedPreKey) { | 
					
						
							|  |  |  |       log.info(`${logId}: confirming signed prekey key`, signedPreKey.keyId); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.confirmSignedPreKey(ourServiceId, signedPreKey.keyId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       await window.storage.put( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         updatedAt | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       log.info(`${logId}: signedPreKey was not uploaded, not confirming`); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     if (pqLastResortPreKey) { | 
					
						
							|  |  |  |       log.info( | 
					
						
							|  |  |  |         `${logId}: confirming last resort key`, | 
					
						
							|  |  |  |         pqLastResortPreKey.keyId | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await store.confirmKyberPreKey(ourServiceId, pqLastResortPreKey.keyId); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       await window.storage.put( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |         LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind], | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |         updatedAt | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       log.info(`${logId}: pqLastResortPreKey was not uploaded, not confirming`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   // Very similar to maybeUpdateKeys, but will always generate prekeys and doesn't upload
 | 
					
						
							|  |  |  |   async _generateKeys( | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     count: number, | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     serviceIdKind: ServiceIdKind, | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     maybeIdentityKey?: KeyPairType | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   ): Promise<UploadKeysType> { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const logId = `AcountManager.generateKeys(${serviceIdKind})`; | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const store = storage.protocol; | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |     const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |     const identityKey = | 
					
						
							|  |  |  |       maybeIdentityKey ?? store.getIdentityKeyPair(ourServiceId); | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     strictAssert(identityKey, 'generateKeys: No identity key pair!'); | 
					
						
							| 
									
										
										
										
											2021-04-16 16:13:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const preKeys = await this.generateNewPreKeys(serviceIdKind, count); | 
					
						
							|  |  |  |     const pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind, count); | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey( | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       serviceIdKind | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     log.info( | 
					
						
							|  |  |  |       `${logId}: Generated ` + | 
					
						
							|  |  |  |         `${preKeys.length} pre keys, ` + | 
					
						
							|  |  |  |         `${pqPreKeys.length} kyber pre keys, ` + | 
					
						
							|  |  |  |         `${pqLastResortPreKey ? 'a' : 'NO'} last resort kyber pre key, ` + | 
					
						
							|  |  |  |         `and ${signedPreKey ? 'a' : 'NO'} signed pre key.` | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     // These are primarily for the summaries they log out
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     await this._cleanPreKeys(serviceIdKind); | 
					
						
							|  |  |  |     await this._cleanKyberPreKeys(serviceIdKind); | 
					
						
							|  |  |  |     await this._cleanLastResortKeys(serviceIdKind); | 
					
						
							|  |  |  |     await this._cleanSignedPreKeys(serviceIdKind); | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       identityKey: identityKey.pubKey, | 
					
						
							|  |  |  |       preKeys, | 
					
						
							|  |  |  |       pqPreKeys, | 
					
						
							|  |  |  |       pqLastResortPreKey, | 
					
						
							|  |  |  |       signedPreKey, | 
					
						
							| 
									
										
										
										
											2022-03-25 10:36:08 -07:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-24 14:53:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |   private async registrationDone(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info('registration done'); | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  |     this.dispatchEvent(new Event('registration')); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |   async setPni( | 
					
						
							|  |  |  |     pni: PniString, | 
					
						
							|  |  |  |     keyMaterial?: PniKeyMaterialType | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     const logId = `AccountManager.setPni(${pni})`; | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |     const { storage } = window.textsecure; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |     const oldPni = storage.user.getPni(); | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |     if (oldPni === pni && !keyMaterial) { | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |     log.info(`${logId}: updating from ${oldPni}`); | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |     if (oldPni) { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await storage.protocol.removeOurOldPni(oldPni); | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await storage.user.setPni(pni); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |     if (keyMaterial) { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |       await storage.protocol.updateOurPniKeyMaterial(pni, keyMaterial); | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Intentionally not awaiting since this is processed on encrypted queue
 | 
					
						
							|  |  |  |       // of MessageReceiver.
 | 
					
						
							| 
									
										
										
										
											2022-12-21 10:41:48 -08:00
										 |  |  |       void this.queueTask(async () => { | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2023-08-10 18:43:33 +02:00
										 |  |  |           await this.maybeUpdateKeys(ServiceIdKind.PNI); | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |         } catch (error) { | 
					
						
							|  |  |  |           log.error( | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |             `${logId}: Failed to upload PNI prekeys. Moving on`, | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |             Errors.toLogFormat(error) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // PNI has changed and credentials are no longer valid
 | 
					
						
							|  |  |  |       await storage.put('groupCredentials', []); | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2023-07-14 09:53:20 -07:00
										 |  |  |       log.warn(`${logId}: no key material`); | 
					
						
							| 
									
										
										
										
											2022-07-28 09:35:29 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-07-18 15:32:00 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-04-13 10:37:29 -07:00
										 |  |  | } |