summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/matrix-sdk/crypto/CrossSigning.js
blob: be8c9607f4e8116d559c4e54c9c1415f02ec48aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.UserTrustLevel = exports.DeviceTrustLevel = exports.CrossSigningLevel = exports.CrossSigningInfo = void 0;
exports.createCryptoStoreCacheCallbacks = createCryptoStoreCacheCallbacks;
exports.requestKeysDuringVerification = requestKeysDuringVerification;
var _olmlib = require("./olmlib");
var _logger = require("../logger");
var _indexeddbCryptoStore = require("../crypto/store/indexeddb-crypto-store");
var _aes = require("./aes");
var _cryptoApi = require("../crypto-api");
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
                                                                                                                                                                                                                                                                                                                                                                                          Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                          Licensed under the Apache License, Version 2.0 (the "License");
                                                                                                                                                                                                                                                                                                                                                                                          you may not use this file except in compliance with the License.
                                                                                                                                                                                                                                                                                                                                                                                          You may obtain a copy of the License at
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                              http://www.apache.org/licenses/LICENSE-2.0
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                          Unless required by applicable law or agreed to in writing, software
                                                                                                                                                                                                                                                                                                                                                                                          distributed under the License is distributed on an "AS IS" BASIS,
                                                                                                                                                                                                                                                                                                                                                                                          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                                                                                                                                                                                                                                                                                                                                                                          See the License for the specific language governing permissions and
                                                                                                                                                                                                                                                                                                                                                                                          limitations under the License.
                                                                                                                                                                                                                                                                                                                                                                                          */ /**
                                                                                                                                                                                                                                                                                                                                                                                              * Cross signing methods
                                                                                                                                                                                                                                                                                                                                                                                              */
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
function publicKeyFromKeyInfo(keyInfo) {
  // `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
  // We assume only a single key, and we want the bare form without type
  // prefix, so we select the values.
  return Object.values(keyInfo.keys)[0];
}
class CrossSigningInfo {
  /**
   * Information about a user's cross-signing keys
   *
   * @param userId - the user that the information is about
   * @param callbacks - Callbacks used to interact with the app
   *     Requires getCrossSigningKey and saveCrossSigningKeys
   * @param cacheCallbacks - Callbacks used to interact with the cache
   */
  constructor(userId, callbacks = {}, cacheCallbacks = {}) {
    this.userId = userId;
    this.callbacks = callbacks;
    this.cacheCallbacks = cacheCallbacks;
    _defineProperty(this, "keys", {});
    _defineProperty(this, "firstUse", true);
    // This tracks whether we've ever verified this user with any identity.
    // When you verify a user, any devices online at the time that receive
    // the verifying signature via the homeserver will latch this to true
    // and can use it in the future to detect cases where the user has
    // become unverified later for any reason.
    _defineProperty(this, "crossSigningVerifiedBefore", false);
  }
  static fromStorage(obj, userId) {
    const res = new CrossSigningInfo(userId);
    for (const prop in obj) {
      if (obj.hasOwnProperty(prop)) {
        // @ts-ignore - ts doesn't like this and nor should we
        res[prop] = obj[prop];
      }
    }
    return res;
  }
  toStorage() {
    return {
      keys: this.keys,
      firstUse: this.firstUse,
      crossSigningVerifiedBefore: this.crossSigningVerifiedBefore
    };
  }

  /**
   * Calls the app callback to ask for a private key
   *
   * @param type - The key type ("master", "self_signing", or "user_signing")
   * @param expectedPubkey - The matching public key or undefined to use
   *     the stored public key for the given key type.
   * @returns An array with [ public key, Olm.PkSigning ]
   */
  async getCrossSigningKey(type, expectedPubkey) {
    const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0;
    if (!this.callbacks.getCrossSigningKey) {
      throw new Error("No getCrossSigningKey callback supplied");
    }
    if (expectedPubkey === undefined) {
      expectedPubkey = this.getId(type);
    }
    function validateKey(key) {
      if (!key) return;
      const signing = new global.Olm.PkSigning();
      const gotPubkey = signing.init_with_seed(key);
      if (gotPubkey === expectedPubkey) {
        return [gotPubkey, signing];
      }
      signing.free();
    }
    let privkey = null;
    if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
      privkey = await this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey);
    }
    const cacheresult = validateKey(privkey);
    if (cacheresult) {
      return cacheresult;
    }
    privkey = await this.callbacks.getCrossSigningKey(type, expectedPubkey);
    const result = validateKey(privkey);
    if (result) {
      if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
        await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
      }
      return result;
    }

    /* No keysource even returned a key */
    if (!privkey) {
      throw new Error("getCrossSigningKey callback for " + type + " returned falsey");
    }

    /* We got some keys from the keysource, but none of them were valid */
    throw new Error("Key type " + type + " from getCrossSigningKey callback did not match");
  }

  /**
   * Check whether the private keys exist in secret storage.
   * XXX: This could be static, be we often seem to have an instance when we
   * want to know this anyway...
   *
   * @param secretStorage - The secret store using account data
   * @returns map of key name to key info the secret is encrypted
   *     with, or null if it is not present or not encrypted with a trusted
   *     key
   */
  async isStoredInSecretStorage(secretStorage) {
    // check what SSSS keys have encrypted the master key (if any)
    const stored = (await secretStorage.isStored("m.cross_signing.master")) || {};
    // then check which of those SSSS keys have also encrypted the SSK and USK
    function intersect(s) {
      for (const k of Object.keys(stored)) {
        if (!s[k]) {
          delete stored[k];
        }
      }
    }
    for (const type of ["self_signing", "user_signing"]) {
      intersect((await secretStorage.isStored(`m.cross_signing.${type}`)) || {});
    }
    return Object.keys(stored).length ? stored : null;
  }

  /**
   * Store private keys in secret storage for use by other devices. This is
   * typically called in conjunction with the creation of new cross-signing
   * keys.
   *
   * @param keys - The keys to store
   * @param secretStorage - The secret store using account data
   */
  static async storeInSecretStorage(keys, secretStorage) {
    for (const [type, privateKey] of keys) {
      const encodedKey = (0, _olmlib.encodeBase64)(privateKey);
      await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
    }
  }

  /**
   * Get private keys from secret storage created by some other device. This
   * also passes the private keys to the app-specific callback.
   *
   * @param type - The type of key to get.  One of "master",
   * "self_signing", or "user_signing".
   * @param secretStorage - The secret store using account data
   * @returns The private key
   */
  static async getFromSecretStorage(type, secretStorage) {
    const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
    if (!encodedKey) {
      return null;
    }
    return (0, _olmlib.decodeBase64)(encodedKey);
  }

  /**
   * Check whether the private keys exist in the local key cache.
   *
   * @param type - The type of key to get. One of "master",
   * "self_signing", or "user_signing". Optional, will check all by default.
   * @returns True if all keys are stored in the local cache.
   */
  async isStoredInKeyCache(type) {
    const cacheCallbacks = this.cacheCallbacks;
    if (!cacheCallbacks) return false;
    const types = type ? [type] : ["master", "self_signing", "user_signing"];
    for (const t of types) {
      if (!(await cacheCallbacks.getCrossSigningKeyCache?.(t))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Get cross-signing private keys from the local cache.
   *
   * @returns A map from key type (string) to private key (Uint8Array)
   */
  async getCrossSigningKeysFromCache() {
    const keys = new Map();
    const cacheCallbacks = this.cacheCallbacks;
    if (!cacheCallbacks) return keys;
    for (const type of ["master", "self_signing", "user_signing"]) {
      const privKey = await cacheCallbacks.getCrossSigningKeyCache?.(type);
      if (!privKey) {
        continue;
      }
      keys.set(type, privKey);
    }
    return keys;
  }

  /**
   * Get the ID used to identify the user. This can also be used to test for
   * the existence of a given key type.
   *
   * @param type - The type of key to get the ID of.  One of "master",
   * "self_signing", or "user_signing".  Defaults to "master".
   *
   * @returns the ID
   */
  getId(type = "master") {
    if (!this.keys[type]) return null;
    const keyInfo = this.keys[type];
    return publicKeyFromKeyInfo(keyInfo);
  }

  /**
   * Create new cross-signing keys for the given key types. The public keys
   * will be held in this class, while the private keys are passed off to the
   * `saveCrossSigningKeys` application callback.
   *
   * @param level - The key types to reset
   */
  async resetKeys(level) {
    if (!this.callbacks.saveCrossSigningKeys) {
      throw new Error("No saveCrossSigningKeys callback supplied");
    }

    // If we're resetting the master key, we reset all keys
    if (level === undefined || level & CrossSigningLevel.MASTER || !this.keys.master) {
      level = CrossSigningLevel.MASTER | CrossSigningLevel.USER_SIGNING | CrossSigningLevel.SELF_SIGNING;
    } else if (level === 0) {
      return;
    }
    const privateKeys = {};
    const keys = {};
    let masterSigning;
    let masterPub;
    try {
      if (level & CrossSigningLevel.MASTER) {
        masterSigning = new global.Olm.PkSigning();
        privateKeys.master = masterSigning.generate_seed();
        masterPub = masterSigning.init_with_seed(privateKeys.master);
        keys.master = {
          user_id: this.userId,
          usage: ["master"],
          keys: {
            ["ed25519:" + masterPub]: masterPub
          }
        };
      } else {
        [masterPub, masterSigning] = await this.getCrossSigningKey("master");
      }
      if (level & CrossSigningLevel.SELF_SIGNING) {
        const sskSigning = new global.Olm.PkSigning();
        try {
          privateKeys.self_signing = sskSigning.generate_seed();
          const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
          keys.self_signing = {
            user_id: this.userId,
            usage: ["self_signing"],
            keys: {
              ["ed25519:" + sskPub]: sskPub
            }
          };
          (0, _olmlib.pkSign)(keys.self_signing, masterSigning, this.userId, masterPub);
        } finally {
          sskSigning.free();
        }
      }
      if (level & CrossSigningLevel.USER_SIGNING) {
        const uskSigning = new global.Olm.PkSigning();
        try {
          privateKeys.user_signing = uskSigning.generate_seed();
          const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
          keys.user_signing = {
            user_id: this.userId,
            usage: ["user_signing"],
            keys: {
              ["ed25519:" + uskPub]: uskPub
            }
          };
          (0, _olmlib.pkSign)(keys.user_signing, masterSigning, this.userId, masterPub);
        } finally {
          uskSigning.free();
        }
      }
      Object.assign(this.keys, keys);
      this.callbacks.saveCrossSigningKeys(privateKeys);
    } finally {
      if (masterSigning) {
        masterSigning.free();
      }
    }
  }

  /**
   * unsets the keys, used when another session has reset the keys, to disable cross-signing
   */
  clearKeys() {
    this.keys = {};
  }
  setKeys(keys) {
    const signingKeys = {};
    if (keys.master) {
      if (keys.master.user_id !== this.userId) {
        const error = "Mismatched user ID " + keys.master.user_id + " in master key from " + this.userId;
        _logger.logger.error(error);
        throw new Error(error);
      }
      if (!this.keys.master) {
        // this is the first key we've seen, so first-use is true
        this.firstUse = true;
      } else if (publicKeyFromKeyInfo(keys.master) !== this.getId()) {
        // this is a different key, so first-use is false
        this.firstUse = false;
      } // otherwise, same key, so no change
      signingKeys.master = keys.master;
    } else if (this.keys.master) {
      signingKeys.master = this.keys.master;
    } else {
      throw new Error("Tried to set cross-signing keys without a master key");
    }
    const masterKey = publicKeyFromKeyInfo(signingKeys.master);

    // verify signatures
    if (keys.user_signing) {
      if (keys.user_signing.user_id !== this.userId) {
        const error = "Mismatched user ID " + keys.master.user_id + " in user_signing key from " + this.userId;
        _logger.logger.error(error);
        throw new Error(error);
      }
      try {
        (0, _olmlib.pkVerify)(keys.user_signing, masterKey, this.userId);
      } catch (e) {
        _logger.logger.error("invalid signature on user-signing key");
        // FIXME: what do we want to do here?
        throw e;
      }
    }
    if (keys.self_signing) {
      if (keys.self_signing.user_id !== this.userId) {
        const error = "Mismatched user ID " + keys.master.user_id + " in self_signing key from " + this.userId;
        _logger.logger.error(error);
        throw new Error(error);
      }
      try {
        (0, _olmlib.pkVerify)(keys.self_signing, masterKey, this.userId);
      } catch (e) {
        _logger.logger.error("invalid signature on self-signing key");
        // FIXME: what do we want to do here?
        throw e;
      }
    }

    // if everything checks out, then save the keys
    if (keys.master) {
      this.keys.master = keys.master;
      // if the master key is set, then the old self-signing and user-signing keys are obsolete
      delete this.keys["self_signing"];
      delete this.keys["user_signing"];
    }
    if (keys.self_signing) {
      this.keys.self_signing = keys.self_signing;
    }
    if (keys.user_signing) {
      this.keys.user_signing = keys.user_signing;
    }
  }
  updateCrossSigningVerifiedBefore(isCrossSigningVerified) {
    // It is critical that this value latches forward from false to true but
    // never back to false to avoid a downgrade attack.
    if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
      this.crossSigningVerifiedBefore = true;
    }
  }
  async signObject(data, type) {
    if (!this.keys[type]) {
      throw new Error("Attempted to sign with " + type + " key but no such key present");
    }
    const [pubkey, signing] = await this.getCrossSigningKey(type);
    try {
      (0, _olmlib.pkSign)(data, signing, this.userId, pubkey);
      return data;
    } finally {
      signing.free();
    }
  }
  async signUser(key) {
    if (!this.keys.user_signing) {
      _logger.logger.info("No user signing key: not signing user");
      return;
    }
    return this.signObject(key.keys.master, "user_signing");
  }
  async signDevice(userId, device) {
    if (userId !== this.userId) {
      throw new Error(`Trying to sign ${userId}'s device; can only sign our own device`);
    }
    if (!this.keys.self_signing) {
      _logger.logger.info("No self signing key: not signing device");
      return;
    }
    return this.signObject({
      algorithms: device.algorithms,
      keys: device.keys,
      device_id: device.deviceId,
      user_id: userId
    }, "self_signing");
  }

  /**
   * Check whether a given user is trusted.
   *
   * @param userCrossSigning - Cross signing info for user
   *
   * @returns
   */
  checkUserTrust(userCrossSigning) {
    // if we're checking our own key, then it's trusted if the master key
    // and self-signing key match
    if (this.userId === userCrossSigning.userId && this.getId() && this.getId() === userCrossSigning.getId() && this.getId("self_signing") && this.getId("self_signing") === userCrossSigning.getId("self_signing")) {
      return new UserTrustLevel(true, true, this.firstUse);
    }
    if (!this.keys.user_signing) {
      // If there's no user signing key, they can't possibly be verified.
      // They may be TOFU trusted though.
      return new UserTrustLevel(false, false, userCrossSigning.firstUse);
    }
    let userTrusted;
    const userMaster = userCrossSigning.keys.master;
    const uskId = this.getId("user_signing");
    try {
      (0, _olmlib.pkVerify)(userMaster, uskId, this.userId);
      userTrusted = true;
    } catch (e) {
      userTrusted = false;
    }
    return new UserTrustLevel(userTrusted, userCrossSigning.crossSigningVerifiedBefore, userCrossSigning.firstUse);
  }

  /**
   * Check whether a given device is trusted.
   *
   * @param userCrossSigning - Cross signing info for user
   * @param device - The device to check
   * @param localTrust - Whether the device is trusted locally
   * @param trustCrossSignedDevices - Whether we trust cross signed devices
   *
   * @returns
   */
  checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) {
    const userTrust = this.checkUserTrust(userCrossSigning);
    const userSSK = userCrossSigning.keys.self_signing;
    if (!userSSK) {
      // if the user has no self-signing key then we cannot make any
      // trust assertions about this device from cross-signing
      return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices);
    }
    const deviceObj = deviceToObject(device, userCrossSigning.userId);
    try {
      // if we can verify the user's SSK from their master key...
      (0, _olmlib.pkVerify)(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
      // ...and this device's key from their SSK...
      (0, _olmlib.pkVerify)(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId);
      // ...then we trust this device as much as far as we trust the user
      return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust, trustCrossSignedDevices);
    } catch (e) {
      return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices);
    }
  }

  /**
   * @returns Cache callbacks
   */
  getCacheCallbacks() {
    return this.cacheCallbacks;
  }
}
exports.CrossSigningInfo = CrossSigningInfo;
function deviceToObject(device, userId) {
  return {
    algorithms: device.algorithms,
    keys: device.keys,
    device_id: device.deviceId,
    user_id: userId,
    signatures: device.signatures
  };
}
let CrossSigningLevel = /*#__PURE__*/function (CrossSigningLevel) {
  CrossSigningLevel[CrossSigningLevel["MASTER"] = 4] = "MASTER";
  CrossSigningLevel[CrossSigningLevel["USER_SIGNING"] = 2] = "USER_SIGNING";
  CrossSigningLevel[CrossSigningLevel["SELF_SIGNING"] = 1] = "SELF_SIGNING";
  return CrossSigningLevel;
}({});
/**
 * Represents the ways in which we trust a user
 */
exports.CrossSigningLevel = CrossSigningLevel;
class UserTrustLevel {
  constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) {
    this.crossSigningVerified = crossSigningVerified;
    this.crossSigningVerifiedBefore = crossSigningVerifiedBefore;
    this.tofu = tofu;
  }

  /**
   * @returns true if this user is verified via any means
   */
  isVerified() {
    return this.isCrossSigningVerified();
  }

  /**
   * @returns true if this user is verified via cross signing
   */
  isCrossSigningVerified() {
    return this.crossSigningVerified;
  }

  /**
   * @returns true if we ever verified this user before (at least for
   * the history of verifications observed by this device).
   */
  wasCrossSigningVerified() {
    return this.crossSigningVerifiedBefore;
  }

  /**
   * @returns true if this user's key is trusted on first use
   */
  isTofu() {
    return this.tofu;
  }
}

/**
 * Represents the ways in which we trust a device.
 *
 * @deprecated Use {@link DeviceVerificationStatus}.
 */
exports.UserTrustLevel = UserTrustLevel;
class DeviceTrustLevel extends _cryptoApi.DeviceVerificationStatus {
  constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices, signedByOwner = false) {
    super({
      crossSigningVerified,
      tofu,
      localVerified,
      trustCrossSignedDevices,
      signedByOwner
    });
  }
  static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) {
    return new DeviceTrustLevel(userTrustLevel.isCrossSigningVerified(), userTrustLevel.isTofu(), localVerified, trustCrossSignedDevices, true);
  }

  /**
   * @returns true if this device is verified via cross signing
   */
  isCrossSigningVerified() {
    return this.crossSigningVerified;
  }

  /**
   * @returns true if this device is verified locally
   */
  isLocallyVerified() {
    return this.localVerified;
  }

  /**
   * @returns true if this device is trusted from a user's key
   * that is trusted on first use
   */
  isTofu() {
    return this.tofu;
  }
}
exports.DeviceTrustLevel = DeviceTrustLevel;
function createCryptoStoreCacheCallbacks(store, olmDevice) {
  return {
    getCrossSigningKeyCache: async function (type, _expectedPublicKey) {
      const key = await new Promise(resolve => {
        store.doTxn("readonly", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
          store.getSecretStorePrivateKey(txn, resolve, type);
        });
      });
      if (key && key.ciphertext) {
        const pickleKey = Buffer.from(olmDevice.pickleKey);
        const decrypted = await (0, _aes.decryptAES)(key, pickleKey, type);
        return (0, _olmlib.decodeBase64)(decrypted);
      } else {
        return key;
      }
    },
    storeCrossSigningKeyCache: async function (type, key) {
      if (!(key instanceof Uint8Array)) {
        throw new Error(`storeCrossSigningKeyCache expects Uint8Array, got ${key}`);
      }
      const pickleKey = Buffer.from(olmDevice.pickleKey);
      const encryptedKey = await (0, _aes.encryptAES)((0, _olmlib.encodeBase64)(key), pickleKey, type);
      return store.doTxn("readwrite", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
        store.storeSecretStorePrivateKey(txn, type, encryptedKey);
      });
    }
  };
}
/**
 * Request cross-signing keys from another device during verification.
 *
 * @param baseApis - base Matrix API interface
 * @param userId - The user ID being verified
 * @param deviceId - The device ID being verified
 */
async function requestKeysDuringVerification(baseApis, userId, deviceId) {
  // If this is a self-verification, ask the other party for keys
  if (baseApis.getUserId() !== userId) {
    return;
  }
  _logger.logger.log("Cross-signing: Self-verification done; requesting keys");
  // This happens asynchronously, and we're not concerned about waiting for
  // it. We return here in order to test.
  return new Promise((resolve, reject) => {
    const client = baseApis;
    const original = client.crypto.crossSigningInfo;

    // We already have all of the infrastructure we need to validate and
    // cache cross-signing keys, so instead of replicating that, here we set
    // up callbacks that request them from the other device and call
    // CrossSigningInfo.getCrossSigningKey() to validate/cache
    const crossSigning = new CrossSigningInfo(original.userId, {
      getCrossSigningKey: async type => {
        _logger.logger.debug("Cross-signing: requesting secret", type, deviceId);
        const {
          promise
        } = client.requestSecret(`m.cross_signing.${type}`, [deviceId]);
        const result = await promise;
        const decoded = (0, _olmlib.decodeBase64)(result);
        return Uint8Array.from(decoded);
      }
    }, original.getCacheCallbacks());
    crossSigning.keys = original.keys;

    // XXX: get all keys out if we get one key out
    // https://github.com/vector-im/element-web/issues/12604
    // then change here to reject on the timeout
    // Requests can be ignored, so don't wait around forever
    const timeout = new Promise(resolve => {
      setTimeout(resolve, KEY_REQUEST_TIMEOUT_MS, new Error("Timeout"));
    });

    // also request and cache the key backup key
    const backupKeyPromise = (async () => {
      const cachedKey = await client.crypto.getSessionBackupPrivateKey();
      if (!cachedKey) {
        _logger.logger.info("No cached backup key found. Requesting...");
        const secretReq = client.requestSecret("m.megolm_backup.v1", [deviceId]);
        const base64Key = await secretReq.promise;
        _logger.logger.info("Got key backup key, decoding...");
        const decodedKey = (0, _olmlib.decodeBase64)(base64Key);
        _logger.logger.info("Decoded backup key, storing...");
        await client.crypto.storeSessionBackupPrivateKey(Uint8Array.from(decodedKey));
        _logger.logger.info("Backup key stored. Starting backup restore...");
        const backupInfo = await client.getKeyBackupVersion();
        // no need to await for this - just let it go in the bg
        client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => {
          _logger.logger.info("Backup restored.");
        });
      }
    })();

    // We call getCrossSigningKey() for its side-effects
    Promise.race([Promise.all([crossSigning.getCrossSigningKey("master"), crossSigning.getCrossSigningKey("self_signing"), crossSigning.getCrossSigningKey("user_signing"), backupKeyPromise]), timeout]).then(resolve, reject);
  }).catch(e => {
    _logger.logger.warn("Cross-signing: failure while requesting keys:", e);
  });
}