Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ce8a4c0

Browse files
authored
Merge pull request #5327 from matrix-org/jryans/sso-4s-integration
Add security customisation points
2 parents 84111a2 + 388cb0e commit ce8a4c0

File tree

6 files changed

+134
-5
lines changed

6 files changed

+134
-5
lines changed

src/Lifecycle.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { InvalidStoreError } from "matrix-js-sdk/src/errors";
2323
import { MatrixClient } from "matrix-js-sdk/src/client";
2424

2525
import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
26+
import SecurityCustomisations from "./customisations/Security";
2627
import EventIndexPeg from './indexing/EventIndexPeg';
2728
import createMatrixClient from './utils/createMatrixClient';
2829
import Analytics from './Analytics';
@@ -567,6 +568,8 @@ function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void
567568
localStorage.setItem("mx_device_id", credentials.deviceId);
568569
}
569570

571+
SecurityCustomisations.persistCredentials?.(credentials);
572+
570573
console.log(`Session persisted for ${credentials.userId}`);
571574
}
572575

src/Login.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ limitations under the License.
2222
import Matrix from "matrix-js-sdk";
2323
import { MatrixClient } from "matrix-js-sdk/src/client";
2424
import { IMatrixClientCreds } from "./MatrixClientPeg";
25+
import SecurityCustomisations from "./customisations/Security";
2526

2627
interface ILoginOptions {
2728
defaultDeviceDisplayName?: string;
@@ -222,11 +223,15 @@ export async function sendLoginRequest(
222223
}
223224
}
224225

225-
return {
226+
const creds: IMatrixClientCreds = {
226227
homeserverUrl: hsUrl,
227228
identityServerUrl: isUrl,
228229
userId: data.user_id,
229230
deviceId: data.device_id,
230231
accessToken: data.access_token,
231232
};
233+
234+
SecurityCustomisations.examineLoginResponse?.(data, creds);
235+
236+
return creds;
232237
}

src/SecurityManager.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ import {MatrixClientPeg} from './MatrixClientPeg';
2222
import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
2323
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
2424
import { _t } from './languageHandler';
25-
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
25+
import { encodeBase64 } from "matrix-js-sdk/src/crypto/olmlib";
2626
import { isSecureBackupRequired } from './utils/WellKnownUtils';
2727
import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog';
2828
import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog';
2929
import SettingsStore from "./settings/SettingsStore";
30+
import SecurityCustomisations from "./customisations/Security";
3031

3132
// This stores the secret storage private keys in memory for the JS SDK. This is
3233
// only meant to act as a cache to avoid prompting the user multiple times
@@ -115,6 +116,13 @@ async function getSecretStorageKey(
115116
}
116117
}
117118

119+
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
120+
if (keyFromCustomisations) {
121+
console.log("Using key from security customisations (secret storage)")
122+
cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
123+
return [keyId, keyFromCustomisations];
124+
}
125+
118126
if (nonInteractive) {
119127
throw new Error("Could not unlock non-interactively");
120128
}
@@ -158,6 +166,12 @@ export async function getDehydrationKey(
158166
keyInfo: ISecretStorageKeyInfo,
159167
checkFunc: (Uint8Array) => void,
160168
): Promise<Uint8Array> {
169+
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
170+
if (keyFromCustomisations) {
171+
console.log("Using key from security customisations (dehydration)")
172+
return keyFromCustomisations;
173+
}
174+
161175
const inputToKey = makeInputToKey(keyInfo);
162176
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
163177
AccessSecretStorageDialog,
@@ -352,14 +366,19 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
352366
}
353367
console.log("Setting dehydration key");
354368
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
369+
} else if (!keyId) {
370+
console.warn("Not setting dehydration key: no SSSS key found");
355371
} else {
356-
console.log("Not setting dehydration key: no SSSS key found");
372+
console.log("Not setting dehydration key: feature disabled");
357373
}
358374
}
359375

360376
// `return await` needed here to ensure `finally` block runs after the
361377
// inner operation completes.
362378
return await func();
379+
} catch (e) {
380+
SecurityCustomisations.catchAccessSecretStorageError?.(e);
381+
console.error(e);
363382
} finally {
364383
// Clear secret storage key cache now that work is complete
365384
secretStorageBeingAccessed = false;

src/async-components/views/dialogs/security/CreateSecretStorageDialog.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import DialogButtons from "../../../../components/views/elements/DialogButtons";
3232
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
3333
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
3434
import { getSecureBackupSetupMethods, isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
35+
import SecurityCustomisations from "../../../../customisations/Security";
3536

3637
const PHASE_LOADING = 0;
3738
const PHASE_LOADERROR = 1;
@@ -99,7 +100,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
99100

100101
this._passphraseField = createRef();
101102

102-
this._fetchBackupInfo();
103+
MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
104+
103105
if (this.state.accountPassword) {
104106
// If we have an account password in memory, let's simplify and
105107
// assume it means password auth is also supported for device
@@ -110,13 +112,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
110112
this._queryKeyUploadAuth();
111113
}
112114

113-
MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
115+
this._getInitialPhase();
114116
}
115117

116118
componentWillUnmount() {
117119
MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
118120
}
119121

122+
_getInitialPhase() {
123+
const keyFromCustomisations = SecurityCustomisations.createSecretStorageKey?.();
124+
if (keyFromCustomisations) {
125+
console.log("Created key via customisations, jumping to bootstrap step");
126+
this._recoveryKey = {
127+
privateKey: keyFromCustomisations,
128+
};
129+
this._bootstrapSecretStorage();
130+
return;
131+
}
132+
133+
this._fetchBackupInfo();
134+
}
135+
120136
async _fetchBackupInfo() {
121137
try {
122138
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();

src/customisations/Security.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright 2020 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { IMatrixClientCreds } from "../MatrixClientPeg";
18+
import { Kind as SetupEncryptionKind } from "../toasts/SetupEncryptionToast";
19+
20+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
21+
function examineLoginResponse(
22+
response: any,
23+
credentials: IMatrixClientCreds,
24+
): void {
25+
// E.g. add additional data to the persisted credentials
26+
}
27+
28+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
29+
function persistCredentials(
30+
credentials: IMatrixClientCreds,
31+
): void {
32+
// E.g. store any additional credential fields
33+
}
34+
35+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
36+
function createSecretStorageKey(): Uint8Array {
37+
// E.g. generate or retrieve secret storage key somehow
38+
return null;
39+
}
40+
41+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
42+
function getSecretStorageKey(): Uint8Array {
43+
// E.g. retrieve secret storage key from some other place
44+
return null;
45+
}
46+
47+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
48+
function catchAccessSecretStorageError(e: Error): void {
49+
// E.g. notify the user in some way
50+
}
51+
52+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
53+
function setupEncryptionNeeded(kind: SetupEncryptionKind): boolean {
54+
// E.g. trigger some kind of setup
55+
return false;
56+
}
57+
58+
// This interface summarises all available customisation points and also marks
59+
// them all as optional. This allows customisers to only define and export the
60+
// customisations they need while still maintaining type safety.
61+
export interface ISecurityCustomisations {
62+
examineLoginResponse?: (
63+
response: any,
64+
credentials: IMatrixClientCreds,
65+
) => void;
66+
persistCredentials?: (
67+
credentials: IMatrixClientCreds,
68+
) => void;
69+
createSecretStorageKey?: () => Uint8Array,
70+
getSecretStorageKey?: () => Uint8Array,
71+
catchAccessSecretStorageError?: (
72+
e: Error,
73+
) => void,
74+
setupEncryptionNeeded?: (
75+
kind: SetupEncryptionKind,
76+
) => boolean,
77+
}
78+
79+
// A real customisation module will define and export one or more of the
80+
// customisation points that make up `ISecurityCustomisations`.
81+
export default {} as ISecurityCustomisations;

src/toasts/SetupEncryptionToast.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEnc
2222
import { accessSecretStorage } from "../SecurityManager";
2323
import ToastStore from "../stores/ToastStore";
2424
import GenericToast from "../components/views/toasts/GenericToast";
25+
import SecurityCustomisations from "../customisations/Security";
2526

2627
const TOAST_KEY = "setupencryption";
2728

@@ -78,6 +79,10 @@ const onReject = () => {
7879
};
7980

8081
export const showToast = (kind: Kind) => {
82+
if (SecurityCustomisations.setupEncryptionNeeded?.(kind)) {
83+
return;
84+
}
85+
8186
const onAccept = async () => {
8287
if (kind === Kind.VERIFY_THIS_SESSION) {
8388
Modal.createTrackedDialog("Verify session", "Verify session", SetupEncryptionDialog,

0 commit comments

Comments
 (0)