mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-23 03:25:12 +01:00
Implement secret sharing
This commit is contained in:
parent
e6a9e39c7d
commit
631b2f059f
@ -43,9 +43,11 @@ import {
|
|||||||
readKey as ssssReadKey,
|
readKey as ssssReadKey,
|
||||||
writeKey as ssssWriteKey,
|
writeKey as ssssWriteKey,
|
||||||
removeKey as ssssRemoveKey,
|
removeKey as ssssRemoveKey,
|
||||||
keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey
|
keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey,
|
||||||
|
SecretStorage,
|
||||||
|
SharedSecret,
|
||||||
|
SecretFetcher
|
||||||
} from "./ssss/index";
|
} from "./ssss/index";
|
||||||
import {SecretStorage} from "./ssss/SecretStorage";
|
|
||||||
import {ObservableValue, RetainedObservableValue} from "../observable/value";
|
import {ObservableValue, RetainedObservableValue} from "../observable/value";
|
||||||
import {CallHandler} from "./calls/CallHandler";
|
import {CallHandler} from "./calls/CallHandler";
|
||||||
import {RoomStateHandlerSet} from "./room/state/RoomStateHandlerSet";
|
import {RoomStateHandlerSet} from "./room/state/RoomStateHandlerSet";
|
||||||
@ -109,6 +111,7 @@ export class Session {
|
|||||||
this._createRoomEncryption = this._createRoomEncryption.bind(this);
|
this._createRoomEncryption = this._createRoomEncryption.bind(this);
|
||||||
this._forgetArchivedRoom = this._forgetArchivedRoom.bind(this);
|
this._forgetArchivedRoom = this._forgetArchivedRoom.bind(this);
|
||||||
this.needsKeyBackup = new ObservableValue(false);
|
this.needsKeyBackup = new ObservableValue(false);
|
||||||
|
this.secretFetcher = new SecretFetcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
get fingerprintKey() {
|
get fingerprintKey() {
|
||||||
@ -198,6 +201,20 @@ export class Session {
|
|||||||
});
|
});
|
||||||
this._megolmDecryption = new MegOlmDecryption(this._keyLoader, this._olmWorker);
|
this._megolmDecryption = new MegOlmDecryption(this._keyLoader, this._olmWorker);
|
||||||
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption: this._megolmDecryption});
|
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption: this._megolmDecryption});
|
||||||
|
this._sharedSecret = new SharedSecret({
|
||||||
|
hsApi: this._hsApi,
|
||||||
|
storage: this._storage,
|
||||||
|
deviceMessageHandler: this._deviceMessageHandler,
|
||||||
|
deviceTracker: this._deviceTracker,
|
||||||
|
ourUserId: this.userId,
|
||||||
|
olmEncryption: this._olmEncryption,
|
||||||
|
crypto: this._platform.crypto,
|
||||||
|
encoding: this._platform.encoding,
|
||||||
|
crossSigning: this._crossSigning,
|
||||||
|
logger: this._platform.logger,
|
||||||
|
});
|
||||||
|
this.secretFetcher.setSecretSharing(this._sharedSecret);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createRoomEncryption(room, encryptionParams) {
|
_createRoomEncryption(room, encryptionParams) {
|
||||||
@ -251,11 +268,11 @@ export class Session {
|
|||||||
this._keyBackup.get().dispose();
|
this._keyBackup.get().dispose();
|
||||||
this._keyBackup.set(undefined);
|
this._keyBackup.set(undefined);
|
||||||
}
|
}
|
||||||
const crossSigning = this._crossSigning.get();
|
// const crossSigning = this._crossSigning.get();
|
||||||
if (crossSigning) {
|
// if (crossSigning) {
|
||||||
crossSigning.dispose();
|
// crossSigning.dispose();
|
||||||
this._crossSigning.set(undefined);
|
// this._crossSigning.set(undefined);
|
||||||
}
|
// }
|
||||||
const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform, this._olm);
|
const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform, this._olm);
|
||||||
if (await this._tryLoadSecretStorage(key, log)) {
|
if (await this._tryLoadSecretStorage(key, log)) {
|
||||||
// only after having read a secret, write the key
|
// only after having read a secret, write the key
|
||||||
@ -331,7 +348,9 @@ export class Session {
|
|||||||
const isValid = await secretStorage.hasValidKeyForAnyAccountData();
|
const isValid = await secretStorage.hasValidKeyForAnyAccountData();
|
||||||
log.set("isValid", isValid);
|
log.set("isValid", isValid);
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
await this._loadSecretStorageServices(secretStorage, log);
|
this._secretStorage = secretStorage;
|
||||||
|
await this._loadSecretStorageService(log);
|
||||||
|
this.secretFetcher.setSecretStorage(secretStorage);
|
||||||
}
|
}
|
||||||
return isValid;
|
return isValid;
|
||||||
});
|
});
|
||||||
@ -359,29 +378,6 @@ export class Session {
|
|||||||
log.set("no_backup", true);
|
log.set("no_backup", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this._features.crossSigning) {
|
|
||||||
await log.wrap("enable cross-signing", async log => {
|
|
||||||
const crossSigning = new CrossSigning({
|
|
||||||
storage: this._storage,
|
|
||||||
secretStorage,
|
|
||||||
platform: this._platform,
|
|
||||||
olm: this._olm,
|
|
||||||
olmUtil: this._olmUtil,
|
|
||||||
deviceTracker: this._deviceTracker,
|
|
||||||
deviceMessageHandler: this._deviceMessageHandler,
|
|
||||||
hsApi: this._hsApi,
|
|
||||||
ownUserId: this.userId,
|
|
||||||
e2eeAccount: this._e2eeAccount,
|
|
||||||
deviceId: this.deviceId,
|
|
||||||
});
|
|
||||||
if (await crossSigning.load(log)) {
|
|
||||||
this._crossSigning.set(crossSigning);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
crossSigning.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.catch(err);
|
log.catch(err);
|
||||||
}
|
}
|
||||||
@ -588,6 +584,32 @@ export class Session {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._features.crossSigning) {
|
||||||
|
this._platform.logger.run("enable cross-signing", async log => {
|
||||||
|
const crossSigning = new CrossSigning({
|
||||||
|
storage: this._storage,
|
||||||
|
// secretStorage: this._secretStorage,
|
||||||
|
secretFetcher: this.secretFetcher,
|
||||||
|
platform: this._platform,
|
||||||
|
olm: this._olm,
|
||||||
|
olmUtil: this._olmUtil,
|
||||||
|
deviceTracker: this._deviceTracker,
|
||||||
|
deviceMessageHandler: this._deviceMessageHandler,
|
||||||
|
hsApi: this._hsApi,
|
||||||
|
ownUserId: this.userId,
|
||||||
|
e2eeAccount: this._e2eeAccount,
|
||||||
|
deviceId: this.deviceId,
|
||||||
|
});
|
||||||
|
this._crossSigning.set(crossSigning);
|
||||||
|
// if (await crossSigning.load(log)) {
|
||||||
|
// this._crossSigning.set(crossSigning);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// crossSigning.dispose();
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
await this._keyBackup.get()?.start(log);
|
await this._keyBackup.get()?.start(log);
|
||||||
await this._crossSigning.get()?.start(log);
|
await this._crossSigning.get()?.start(log);
|
||||||
|
|
||||||
|
46
src/matrix/ssss/SecretFetcher.ts
Normal file
46
src/matrix/ssss/SecretFetcher.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {SecretStorage} from "./SecretStorage";
|
||||||
|
import type {SharedSecret} from "./SharedSecret";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a wrapper around SecretStorage and SecretSharing so that
|
||||||
|
* you don't need to always check both sources for something.
|
||||||
|
*/
|
||||||
|
export class SecretFetcher {
|
||||||
|
public secretStorage: SecretStorage;
|
||||||
|
public secretSharing: SharedSecret;
|
||||||
|
|
||||||
|
async getSecret(name: string): Promise<string | undefined> {
|
||||||
|
;
|
||||||
|
return await this.secretStorage?.readSecret(name) ??
|
||||||
|
await this.secretSharing?.getLocallyStoredSecret(name);
|
||||||
|
// note that we don't ask another device for secret here
|
||||||
|
// that should be done explicitly since it can take arbitrary
|
||||||
|
// amounts of time to be fulfilled as the other devices may
|
||||||
|
// be offline etc...
|
||||||
|
}
|
||||||
|
|
||||||
|
setSecretStorage(storage: SecretStorage) {
|
||||||
|
this.secretStorage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSecretSharing(sharing: SharedSecret) {
|
||||||
|
this.secretSharing = sharing;
|
||||||
|
this.secretSharing.setSecretFetcher(this);
|
||||||
|
}
|
||||||
|
}
|
277
src/matrix/ssss/SharedSecret.ts
Normal file
277
src/matrix/ssss/SharedSecret.ts
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {HomeServerApi} from "../net/HomeServerApi";
|
||||||
|
import type {Storage} from "../storage/idb/Storage";
|
||||||
|
import type {DeviceMessageHandler} from "../DeviceMessageHandler.js"
|
||||||
|
import type {DeviceTracker} from "../e2ee/DeviceTracker";
|
||||||
|
import type {ILogger, ILogItem} from "../../logging/types";
|
||||||
|
import type {Encryption as OlmEncryption} from "../e2ee/olm/Encryption";
|
||||||
|
import type {Crypto} from "../../platform/web/dom/Crypto.js";
|
||||||
|
import type {Encoding} from "../../platform/web/utils/Encoding.js";
|
||||||
|
import type {CrossSigning} from "../verification/CrossSigning";
|
||||||
|
import type {SecretFetcher} from "./SecretFetcher";
|
||||||
|
import type {ObservableValue} from "../../observable/value";
|
||||||
|
import {makeTxnId, formatToDeviceMessagesPayload} from "../common.js";
|
||||||
|
import {Deferred} from "../../utils/Deferred";
|
||||||
|
import {StoreNames} from "../storage/common";
|
||||||
|
import {SESSION_E2EE_KEY_PREFIX} from "../e2ee/common";
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
hsApi: HomeServerApi;
|
||||||
|
storage: Storage;
|
||||||
|
deviceMessageHandler: DeviceMessageHandler;
|
||||||
|
deviceTracker: DeviceTracker;
|
||||||
|
ourUserId: string;
|
||||||
|
olmEncryption: OlmEncryption;
|
||||||
|
crypto: Crypto;
|
||||||
|
encoding: Encoding;
|
||||||
|
crossSigning: ObservableValue<CrossSigning | undefined>;
|
||||||
|
logger: ILogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
const enum EVENT_TYPE {
|
||||||
|
REQUEST = "m.secret.request",
|
||||||
|
SEND = "m.secret.send",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SharedSecret {
|
||||||
|
private readonly hsApi: HomeServerApi;
|
||||||
|
private readonly storage: Storage;
|
||||||
|
private readonly deviceMessageHandler: DeviceMessageHandler;
|
||||||
|
private readonly deviceTracker: DeviceTracker;
|
||||||
|
private readonly ourUserId: string;
|
||||||
|
private readonly olmEncryption: OlmEncryption;
|
||||||
|
private readonly waitMap: Map<string, Deferred<any>> = new Map();
|
||||||
|
private readonly crypto: Crypto;
|
||||||
|
private readonly encoding: Encoding;
|
||||||
|
private readonly aesEncryption: AESEncryption;
|
||||||
|
private readonly crossSigning: ObservableValue<CrossSigning | undefined>;
|
||||||
|
private readonly logger: ILogger;
|
||||||
|
private secretFetcher: SecretFetcher;
|
||||||
|
|
||||||
|
constructor(options: Options) {
|
||||||
|
this.hsApi = options.hsApi;
|
||||||
|
this.storage = options.storage;
|
||||||
|
this.deviceMessageHandler = options.deviceMessageHandler;
|
||||||
|
this.deviceTracker = options.deviceTracker;
|
||||||
|
this.ourUserId = options.ourUserId;
|
||||||
|
this.olmEncryption = options.olmEncryption;
|
||||||
|
this.crypto = options.crypto;
|
||||||
|
this.encoding = options.encoding;
|
||||||
|
this.crossSigning = options.crossSigning;
|
||||||
|
this.logger = options.logger;
|
||||||
|
this.aesEncryption = new AESEncryption(this.storage, this.crypto, this.encoding);
|
||||||
|
(window as any).foo = this;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init() {
|
||||||
|
this.deviceMessageHandler.on("message", ({ encrypted }) => {
|
||||||
|
const type: EVENT_TYPE = encrypted?.event.type;
|
||||||
|
switch (type) {
|
||||||
|
case EVENT_TYPE.REQUEST: {
|
||||||
|
this._respondToRequest(encrypted);
|
||||||
|
}
|
||||||
|
case EVENT_TYPE.SEND: {
|
||||||
|
const { request_id } = encrypted.event.content;
|
||||||
|
const deffered = this.waitMap.get(request_id);
|
||||||
|
deffered?.resolve(encrypted);
|
||||||
|
this.waitMap.delete(request_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.aesEncryption.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _respondToRequest(request) {
|
||||||
|
await this.logger.run("SharedSecret.respondToRequest", async (log) => {
|
||||||
|
if (!this.shouldRespondToRequest(request, log)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const requestContent = request.event.content;
|
||||||
|
const id = requestContent.request_id;
|
||||||
|
const deviceId = requestContent.requesting_device_id;
|
||||||
|
const name = requestContent.name;
|
||||||
|
|
||||||
|
const secret = await this.secretFetcher.getSecret(name);
|
||||||
|
if (!secret) {
|
||||||
|
// Can't share a secret that we don't know about.
|
||||||
|
log.log({ l: "Secret not available to share" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = { secret, request_id: id };
|
||||||
|
const device = await this.deviceTracker.deviceForId(this.ourUserId, deviceId, this.hsApi, log);
|
||||||
|
if (!device) {
|
||||||
|
log.log({ l: "Cannot find device", deviceId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messages = await log.wrap("olm encrypt", log => this.olmEncryption.encrypt(
|
||||||
|
EVENT_TYPE.SEND, content, [device], this.hsApi, log));
|
||||||
|
console.log("messages", messages);
|
||||||
|
const payload = formatToDeviceMessagesPayload(messages);
|
||||||
|
console.log("payload", payload);
|
||||||
|
await this.hsApi.sendToDevice("m.room.encrypted", payload, makeTxnId(), {log}).response();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async shouldRespondToRequest(request: any, log: ILogItem): Promise<boolean> {
|
||||||
|
return log.wrap("SecretSharing.shouldRespondToRequest", async () => {
|
||||||
|
const crossSigning = this.crossSigning.get();
|
||||||
|
if (!crossSigning) {
|
||||||
|
// We're not in a position to respond to this request
|
||||||
|
log.log({ crossSigningNotAvailable: true });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = request.event.content;
|
||||||
|
if (
|
||||||
|
request.event.sender !== this.ourUserId ||
|
||||||
|
!(
|
||||||
|
content.name &&
|
||||||
|
content.action &&
|
||||||
|
content.requesting_device_id &&
|
||||||
|
content.request_id
|
||||||
|
) ||
|
||||||
|
content.action === "request_cancellation"
|
||||||
|
) {
|
||||||
|
// 1. Ensure that the message came from the same user as us
|
||||||
|
// 2. Validate message format
|
||||||
|
// 3. Check if this is a cancellation
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check that the device is verified
|
||||||
|
const deviceId = content.requesting_device_id;
|
||||||
|
const device = await this.deviceTracker.deviceForId(this.ourUserId, deviceId, this.hsApi, log);
|
||||||
|
if (!device) {
|
||||||
|
log.log({ l: "Device could not be acquired", deviceId });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!await crossSigning.isOurUserDeviceTrusted(device, log)) {
|
||||||
|
log.log({ l: "Device not trusted, returning" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocallyStoredSecret(name: string): Promise<any> {
|
||||||
|
const txn = await this.storage.readTxn([
|
||||||
|
this.storage.storeNames.sharedSecrets,
|
||||||
|
]);
|
||||||
|
const storedSecret = await txn.sharedSecrets.get(name);
|
||||||
|
if (storedSecret) {
|
||||||
|
const secret = await this.aesEncryption.decrypt(storedSecret.encrypted);
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: this will break if two different pieces of code call this method
|
||||||
|
requestSecret(name: string, log: ILogItem): Promise<string> {
|
||||||
|
return log.wrap("SharedSecret.requestSecret", async (_log) => {
|
||||||
|
const request_id = makeTxnId();
|
||||||
|
const promise = this.trackSecretRequest(request_id);
|
||||||
|
await this.sendRequestForSecret(name, request_id, _log);
|
||||||
|
const result = await promise;
|
||||||
|
const secret = result.event.content.secret;
|
||||||
|
await this.writeToStorage(name, secret);
|
||||||
|
return secret;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async writeToStorage(name:string, secret: any) {
|
||||||
|
const encrypted = await this.aesEncryption.encrypt(secret);
|
||||||
|
const txn = await this.storage.readWriteTxn([StoreNames.sharedSecrets]);
|
||||||
|
txn.sharedSecrets.set(name, { encrypted });
|
||||||
|
}
|
||||||
|
|
||||||
|
private trackSecretRequest(request_id: string): Promise<any> {
|
||||||
|
const deferred = new Deferred();
|
||||||
|
this.waitMap.set(request_id, deferred);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendRequestForSecret(name: string, request_id: string, log: ILogItem) {
|
||||||
|
const content = {
|
||||||
|
action: "request",
|
||||||
|
name,
|
||||||
|
request_id,
|
||||||
|
requesting_device_id: this.deviceTracker.ownDeviceId,
|
||||||
|
}
|
||||||
|
let devices = await this.deviceTracker.devicesForUsers([this.ourUserId], this.hsApi, log);
|
||||||
|
devices = devices.filter(d => d.device_id !== this.deviceTracker.ownDeviceId);
|
||||||
|
const messages = await log.wrap("olm encrypt", log => this.olmEncryption.encrypt(
|
||||||
|
EVENT_TYPE.REQUEST, content, devices, this.hsApi, log));
|
||||||
|
console.log("messages", messages);
|
||||||
|
const payload = formatToDeviceMessagesPayload(messages);
|
||||||
|
console.log("payload", payload);
|
||||||
|
await this.hsApi.sendToDevice("m.room.encrypted", payload, makeTxnId(), {log}).response();
|
||||||
|
}
|
||||||
|
|
||||||
|
setSecretFetcher(secretFetcher: SecretFetcher): void {
|
||||||
|
this.secretFetcher = secretFetcher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AESEncryption {
|
||||||
|
private key: JsonWebKey;
|
||||||
|
private iv: Uint8Array;
|
||||||
|
|
||||||
|
constructor(private storage: Storage, private crypto: Crypto, private encoding: Encoding) { };
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const storageKey = `${SESSION_E2EE_KEY_PREFIX}localAESKey`;
|
||||||
|
// 1. Check if we're already storing the AES key
|
||||||
|
const txn = await this.storage.readTxn([StoreNames.session]);
|
||||||
|
let { key, iv } = await txn.session.get(storageKey) ?? {};
|
||||||
|
|
||||||
|
// 2. If no key, create it and store in session store
|
||||||
|
if (!key) {
|
||||||
|
key = await this.crypto.aes.generateKey("jwk");
|
||||||
|
iv = await this.crypto.aes.generateIV();
|
||||||
|
const txn = await this.storage.readWriteTxn([StoreNames.session]);
|
||||||
|
txn.session.set(storageKey, { key, iv });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Set props
|
||||||
|
this.key = key;
|
||||||
|
this.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
async encrypt(secret: string): Promise<Uint8Array> {
|
||||||
|
const data = this.encoding.utf8.encode(secret);
|
||||||
|
const encrypted = await this.crypto.aes.encryptCTR({
|
||||||
|
jwkKey: this.key,
|
||||||
|
iv: this.iv,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(ciphertext: Uint8Array): Promise<string> {
|
||||||
|
const buffer = await this.crypto.aes.decryptCTR({
|
||||||
|
jwkKey: this.key,
|
||||||
|
iv: this.iv,
|
||||||
|
data: ciphertext,
|
||||||
|
});
|
||||||
|
const secret = this.encoding.utf8.decode(buffer);
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ import {ToDeviceChannel} from "./SAS/channel/Channel";
|
|||||||
import {VerificationEventType} from "./SAS/channel/types";
|
import {VerificationEventType} from "./SAS/channel/types";
|
||||||
import {ObservableMap} from "../../observable/map";
|
import {ObservableMap} from "../../observable/map";
|
||||||
import {SASRequest} from "./SAS/SASRequest";
|
import {SASRequest} from "./SAS/SASRequest";
|
||||||
import type {SecretStorage} from "../ssss/SecretStorage";
|
import {SecretFetcher} from "../ssss";
|
||||||
import type {Storage} from "../storage/idb/Storage";
|
import type {Storage} from "../storage/idb/Storage";
|
||||||
import type {Platform} from "../../platform/web/Platform";
|
import type {Platform} from "../../platform/web/Platform";
|
||||||
import type {DeviceTracker} from "../e2ee/DeviceTracker";
|
import type {DeviceTracker} from "../e2ee/DeviceTracker";
|
||||||
@ -80,7 +80,7 @@ enum MSKVerification {
|
|||||||
|
|
||||||
export class CrossSigning {
|
export class CrossSigning {
|
||||||
private readonly storage: Storage;
|
private readonly storage: Storage;
|
||||||
private readonly secretStorage: SecretStorage;
|
private readonly secretFetcher: SecretFetcher;
|
||||||
private readonly platform: Platform;
|
private readonly platform: Platform;
|
||||||
private readonly deviceTracker: DeviceTracker;
|
private readonly deviceTracker: DeviceTracker;
|
||||||
private readonly olm: Olm;
|
private readonly olm: Olm;
|
||||||
@ -97,7 +97,7 @@ export class CrossSigning {
|
|||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
storage: Storage,
|
storage: Storage,
|
||||||
secretStorage: SecretStorage,
|
secretFetcher: SecretFetcher,
|
||||||
deviceTracker: DeviceTracker,
|
deviceTracker: DeviceTracker,
|
||||||
platform: Platform,
|
platform: Platform,
|
||||||
olm: Olm,
|
olm: Olm,
|
||||||
@ -109,7 +109,7 @@ export class CrossSigning {
|
|||||||
deviceMessageHandler: DeviceMessageHandler,
|
deviceMessageHandler: DeviceMessageHandler,
|
||||||
}) {
|
}) {
|
||||||
this.storage = options.storage;
|
this.storage = options.storage;
|
||||||
this.secretStorage = options.secretStorage;
|
this.secretFetcher = options.secretFetcher;
|
||||||
this.platform = options.platform;
|
this.platform = options.platform;
|
||||||
this.deviceTracker = options.deviceTracker;
|
this.deviceTracker = options.deviceTracker;
|
||||||
this.olm = options.olm;
|
this.olm = options.olm;
|
||||||
@ -208,6 +208,7 @@ export class CrossSigning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleSASDeviceMessage({ unencrypted: event }) {
|
private handleSASDeviceMessage({ unencrypted: event }) {
|
||||||
|
if (!event) { return; }
|
||||||
const txnId = event.content.transaction_id;
|
const txnId = event.content.transaction_id;
|
||||||
/**
|
/**
|
||||||
* If we receive an event for the current/previously finished
|
* If we receive an event for the current/previously finished
|
||||||
@ -304,6 +305,20 @@ export class CrossSigning {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isOurUserDeviceTrusted(device: DeviceKey, log: ILogItem): Promise<boolean> {
|
||||||
|
return await log.wrap("CrossSigning.getDeviceTrust", async () => {
|
||||||
|
const ourSSK = await this.deviceTracker.getCrossSigningKeyForUser(this.ownUserId, KeyUsage.SelfSigning, this.hsApi, log);
|
||||||
|
if (!ourSSK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const verification = this.hasValidSignatureFrom(device, ourSSK, log);
|
||||||
|
if (verification === SignatureVerification.Valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getUserTrust(userId: string, log: ILogItem): Promise<UserTrust> {
|
getUserTrust(userId: string, log: ILogItem): Promise<UserTrust> {
|
||||||
return log.wrap("CrossSigning.getUserTrust", async log => {
|
return log.wrap("CrossSigning.getUserTrust", async log => {
|
||||||
log.set("id", userId);
|
log.set("id", userId);
|
||||||
@ -421,7 +436,7 @@ export class CrossSigning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getSigningKey(usage: KeyUsage): Promise<Uint8Array | undefined> {
|
private async getSigningKey(usage: KeyUsage): Promise<Uint8Array | undefined> {
|
||||||
const seedStr = await this.secretStorage.readSecret(`m.cross_signing.${usage}`);
|
const seedStr = await this.secretFetcher.getSecret(`m.cross_signing.${usage}`);
|
||||||
if (seedStr) {
|
if (seedStr) {
|
||||||
return new Uint8Array(this.platform.encoding.base64.decode(seedStr));
|
return new Uint8Array(this.platform.encoding.base64.decode(seedStr));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user