2020-03-24 17:57:04 +03:00
|
|
|
/*
|
|
|
|
Copyright 2020 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 EventEmitter from 'events';
|
|
|
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
|
|
|
import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManager';
|
2020-05-01 00:08:00 +03:00
|
|
|
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
2020-03-24 17:57:04 +03:00
|
|
|
|
|
|
|
export const PHASE_INTRO = 0;
|
2020-06-02 18:32:15 +03:00
|
|
|
export const PHASE_RECOVERY_KEY = 1;
|
|
|
|
export const PHASE_BUSY = 2;
|
|
|
|
export const PHASE_DONE = 3; //final done stage, but still showing UX
|
|
|
|
export const PHASE_CONFIRM_SKIP = 4;
|
|
|
|
export const PHASE_FINISHED = 5; //UX can be closed
|
2020-03-24 17:57:04 +03:00
|
|
|
|
|
|
|
export class SetupEncryptionStore extends EventEmitter {
|
|
|
|
static sharedInstance() {
|
|
|
|
if (!global.mx_SetupEncryptionStore) global.mx_SetupEncryptionStore = new SetupEncryptionStore();
|
|
|
|
return global.mx_SetupEncryptionStore;
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
|
|
|
if (this._started) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._started = true;
|
2020-06-02 18:32:15 +03:00
|
|
|
this.phase = PHASE_BUSY;
|
2020-03-24 17:57:04 +03:00
|
|
|
this.verificationRequest = null;
|
|
|
|
this.backupInfo = null;
|
2020-06-02 18:32:15 +03:00
|
|
|
|
|
|
|
// ID of the key that the secrets we want are encrypted with
|
|
|
|
this.keyId = null;
|
|
|
|
// Descriptor of the key that the secrets we want are encrypted with
|
|
|
|
this.keyInfo = null;
|
|
|
|
|
2020-03-24 17:57:04 +03:00
|
|
|
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
2020-05-01 00:08:00 +03:00
|
|
|
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
2020-06-02 18:32:15 +03:00
|
|
|
|
|
|
|
this.fetchKeyInfo();
|
2020-03-24 17:57:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
stop() {
|
|
|
|
if (!this._started) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._started = false;
|
|
|
|
if (this.verificationRequest) {
|
|
|
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
|
|
}
|
|
|
|
if (MatrixClientPeg.get()) {
|
|
|
|
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
2020-05-01 00:08:00 +03:00
|
|
|
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
2020-03-24 17:57:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-02 18:32:15 +03:00
|
|
|
async fetchKeyInfo() {
|
|
|
|
const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false);
|
|
|
|
if (Object.keys(keys).length === 0) {
|
|
|
|
this.keyId = null;
|
|
|
|
this.keyInfo = null;
|
|
|
|
} else {
|
|
|
|
// If the secret is stored under more than one key, we just pick an arbitrary one
|
|
|
|
this.keyId = Object.keys(keys)[0];
|
|
|
|
this.keyInfo = keys[this.keyId];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.phase = PHASE_INTRO;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
2020-06-02 19:55:27 +03:00
|
|
|
async startKeyReset() {
|
|
|
|
try {
|
|
|
|
await accessSecretStorage(() => {}, {forceReset: true});
|
|
|
|
// If the keys are reset, the trust status event will fire and we'll change state
|
|
|
|
} catch (e) {
|
|
|
|
// dialog was cancelled - stay on the current screen
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-02 18:32:15 +03:00
|
|
|
async useRecoveryKey() {
|
|
|
|
this.phase = PHASE_RECOVERY_KEY;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
|
|
|
cancelUseRecoveryKey() {
|
|
|
|
this.phase = PHASE_INTRO;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
|
|
|
async setupWithRecoveryKey(recoveryKey) {
|
|
|
|
this.startTrustCheck({[this.keyId]: recoveryKey});
|
|
|
|
}
|
|
|
|
|
2020-03-24 17:57:04 +03:00
|
|
|
async usePassPhrase() {
|
2020-06-02 18:32:15 +03:00
|
|
|
this.startTrustCheck();
|
|
|
|
}
|
|
|
|
|
|
|
|
async startTrustCheck(withKeys) {
|
2020-03-24 17:57:04 +03:00
|
|
|
this.phase = PHASE_BUSY;
|
|
|
|
this.emit("update");
|
|
|
|
const cli = MatrixClientPeg.get();
|
|
|
|
try {
|
|
|
|
const backupInfo = await cli.getKeyBackupVersion();
|
|
|
|
this.backupInfo = backupInfo;
|
|
|
|
this.emit("update");
|
|
|
|
// The control flow is fairly twisted here...
|
|
|
|
// For the purposes of completing security, we only wait on getting
|
|
|
|
// as far as the trust check and then show a green shield.
|
|
|
|
// We also begin the key backup restore as well, which we're
|
|
|
|
// awaiting inside `accessSecretStorage` only so that it keeps your
|
|
|
|
// passphase cached for that work. This dialog itself will only wait
|
|
|
|
// on the first trust check, and the key backup restore will happen
|
|
|
|
// in the background.
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
accessSecretStorage(async () => {
|
|
|
|
await cli.checkOwnCrossSigningTrust();
|
|
|
|
resolve();
|
|
|
|
if (backupInfo) {
|
|
|
|
// A complete restore can take many minutes for large
|
|
|
|
// accounts / slow servers, so we allow the dialog
|
|
|
|
// to advance before this.
|
|
|
|
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
|
|
|
}
|
2020-06-02 18:32:15 +03:00
|
|
|
}, {
|
|
|
|
withKeys,
|
|
|
|
passphraseOnly: true,
|
2020-03-24 17:57:04 +03:00
|
|
|
}).catch(reject);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (cli.getCrossSigningId()) {
|
|
|
|
this.phase = PHASE_DONE;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
if (!(e instanceof AccessCancelledError)) {
|
|
|
|
console.log(e);
|
|
|
|
}
|
|
|
|
// this will throw if the user hits cancel, so ignore
|
|
|
|
this.phase = PHASE_INTRO;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 00:08:00 +03:00
|
|
|
_onUserTrustStatusChanged = async (userId) => {
|
|
|
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
|
|
|
const crossSigningReady = await MatrixClientPeg.get().isCrossSigningReady();
|
|
|
|
if (crossSigningReady) {
|
|
|
|
this.phase = PHASE_DONE;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-24 17:57:04 +03:00
|
|
|
onVerificationRequest = async (request) => {
|
|
|
|
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
|
|
|
|
|
|
|
if (this.verificationRequest) {
|
|
|
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
|
|
}
|
|
|
|
this.verificationRequest = request;
|
|
|
|
await request.accept();
|
|
|
|
request.on("change", this.onVerificationRequestChange);
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:58:00 +03:00
|
|
|
onVerificationRequestChange = async () => {
|
2020-03-24 17:57:04 +03:00
|
|
|
if (this.verificationRequest.cancelled) {
|
|
|
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
|
|
this.verificationRequest = null;
|
|
|
|
this.emit("update");
|
2020-05-01 00:08:00 +03:00
|
|
|
} else if (this.verificationRequest.phase === VERIF_PHASE_DONE) {
|
|
|
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
|
|
this.verificationRequest = null;
|
|
|
|
// At this point, the verification has finished, we just need to wait for
|
|
|
|
// cross signing to be ready to use, so wait for the user trust status to
|
2020-05-01 12:58:00 +03:00
|
|
|
// change (or change to DONE if it's already ready).
|
|
|
|
const crossSigningReady = await MatrixClientPeg.get().isCrossSigningReady();
|
|
|
|
this.phase = crossSigningReady ? PHASE_DONE : PHASE_BUSY;
|
2020-05-01 00:08:00 +03:00
|
|
|
this.emit("update");
|
2020-03-24 17:57:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
skip() {
|
|
|
|
this.phase = PHASE_CONFIRM_SKIP;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
|
|
|
skipConfirm() {
|
|
|
|
this.phase = PHASE_FINISHED;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
|
|
|
returnAfterSkip() {
|
|
|
|
this.phase = PHASE_INTRO;
|
|
|
|
this.emit("update");
|
|
|
|
}
|
|
|
|
|
|
|
|
done() {
|
|
|
|
this.phase = PHASE_FINISHED;
|
|
|
|
this.emit("update");
|
2020-04-03 13:49:08 +03:00
|
|
|
// async - ask other clients for keys, if necessary
|
|
|
|
MatrixClientPeg.get()._crypto.cancelAndResendAllOutgoingKeyRequests();
|
2020-03-24 17:57:04 +03:00
|
|
|
}
|
|
|
|
}
|