Merge pull request #5307 from matrix-org/jryans/sso-4s-integration

Convert `src/SecurityManager.js` to TypeScript
This commit is contained in:
J. Ryan Stinnett 2020-10-12 11:41:12 +01:00 committed by GitHub
commit 26e63c23b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 35 deletions

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
import * as ModernizrStatic from "modernizr"; import * as ModernizrStatic from "modernizr";
import ContentMessages from "../ContentMessages"; import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg"; import { IMatrixClientPeg } from "../MatrixClientPeg";

View file

@ -17,6 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix';
import {MatrixClient} from 'matrix-js-sdk/src/client'; import {MatrixClient} from 'matrix-js-sdk/src/client';
import {MemoryStore} from 'matrix-js-sdk/src/store/memory'; import {MemoryStore} from 'matrix-js-sdk/src/store/memory';
import * as utils from 'matrix-js-sdk/src/utils'; import * as utils from 'matrix-js-sdk/src/utils';
@ -249,8 +250,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
} }
private createClient(creds: IMatrixClientCreds): void { private createClient(creds: IMatrixClientCreds): void {
// TODO: Make these opts typesafe with the js-sdk const opts: ICreateClientOpts = {
const opts = {
baseUrl: creds.homeserverUrl, baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl, idBaseUrl: creds.identityServerUrl,
accessToken: creds.accessToken, accessToken: creds.accessToken,

View file

@ -132,7 +132,7 @@ export class ModalManager {
public createTrackedDialogAsync<T extends any[]>( public createTrackedDialogAsync<T extends any[]>(
analyticsAction: string, analyticsAction: string,
analyticsInfo: string, analyticsInfo: string,
...rest: Parameters<ModalManager["appendDialogAsync"]> ...rest: Parameters<ModalManager["createDialogAsync"]>
) { ) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialogAsync<T>(...rest); return this.createDialogAsync<T>(...rest);

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import Modal from './Modal'; import Modal from './Modal';
import * as sdk from './index'; import * as sdk from './index';
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
@ -31,15 +33,18 @@ import SettingsStore from "./settings/SettingsStore";
// during the same single operation. Use `accessSecretStorage` below to scope a // during the same single operation. Use `accessSecretStorage` below to scope a
// single secret storage operation, as it will clear the cached keys once the // single secret storage operation, as it will clear the cached keys once the
// operation ends. // operation ends.
let secretStorageKeys = {}; let secretStorageKeys: Record<string, Uint8Array> = {};
let secretStorageKeyInfo = {}; let secretStorageKeyInfo: Record<string, ISecretStorageKeyInfo> = {};
let secretStorageBeingAccessed = false; let secretStorageBeingAccessed = false;
let nonInteractive = false; let nonInteractive = false;
let dehydrationCache = {}; let dehydrationCache: {
key?: Uint8Array,
keyInfo?: ISecretStorageKeyInfo,
} = {};
function isCachingAllowed() { function isCachingAllowed(): boolean {
return secretStorageBeingAccessed; return secretStorageBeingAccessed;
} }
@ -50,7 +55,7 @@ function isCachingAllowed() {
* *
* @returns {bool} * @returns {bool}
*/ */
export function isSecretStorageBeingAccessed() { export function isSecretStorageBeingAccessed(): boolean {
return secretStorageBeingAccessed; return secretStorageBeingAccessed;
} }
@ -60,7 +65,7 @@ export class AccessCancelledError extends Error {
} }
} }
async function confirmToDismiss() { async function confirmToDismiss(): Promise<boolean> {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const [sure] = await Modal.createDialog(QuestionDialog, { const [sure] = await Modal.createDialog(QuestionDialog, {
title: _t("Cancel entering passphrase?"), title: _t("Cancel entering passphrase?"),
@ -72,7 +77,9 @@ async function confirmToDismiss() {
return !sure; return !sure;
} }
function makeInputToKey(keyInfo) { function makeInputToKey(
keyInfo: ISecretStorageKeyInfo,
): (keyParams: { passphrase: string, recoveryKey: string }) => Promise<Uint8Array> {
return async ({ passphrase, recoveryKey }) => { return async ({ passphrase, recoveryKey }) => {
if (passphrase) { if (passphrase) {
return deriveKey( return deriveKey(
@ -86,7 +93,10 @@ function makeInputToKey(keyInfo) {
}; };
} }
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { async function getSecretStorageKey(
{ keys: keyInfos }: { keys: Record<string, ISecretStorageKeyInfo> },
ssssItemName,
): Promise<[string, Uint8Array]> {
const keyInfoEntries = Object.entries(keyInfos); const keyInfoEntries = Object.entries(keyInfos);
if (keyInfoEntries.length > 1) { if (keyInfoEntries.length > 1) {
throw new Error("Multiple storage key requests not implemented"); throw new Error("Multiple storage key requests not implemented");
@ -100,7 +110,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
if (dehydrationCache.key) { if (dehydrationCache.key) {
if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) { if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
cacheSecretStorageKey(keyId, dehydrationCache.key, keyInfo); cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
return [keyId, dehydrationCache.key]; return [keyId, dehydrationCache.key];
} }
} }
@ -139,12 +149,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
const key = await inputToKey(input); const key = await inputToKey(input);
// Save to cache to avoid future prompts in the current session // Save to cache to avoid future prompts in the current session
cacheSecretStorageKey(keyId, key, keyInfo); cacheSecretStorageKey(keyId, keyInfo, key);
return [keyId, key]; return [keyId, key];
} }
export async function getDehydrationKey(keyInfo, checkFunc) { export async function getDehydrationKey(
keyInfo: ISecretStorageKeyInfo,
checkFunc: (Uint8Array) => void,
): Promise<Uint8Array> {
const inputToKey = makeInputToKey(keyInfo); const inputToKey = makeInputToKey(keyInfo);
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
AccessSecretStorageDialog, AccessSecretStorageDialog,
@ -185,20 +198,24 @@ export async function getDehydrationKey(keyInfo, checkFunc) {
return key; return key;
} }
function cacheSecretStorageKey(keyId, key, keyInfo) { function cacheSecretStorageKey(
keyId: string,
keyInfo: ISecretStorageKeyInfo,
key: Uint8Array,
): void {
if (isCachingAllowed()) { if (isCachingAllowed()) {
secretStorageKeys[keyId] = key; secretStorageKeys[keyId] = key;
secretStorageKeyInfo[keyId] = keyInfo; secretStorageKeyInfo[keyId] = keyInfo;
} }
} }
const onSecretRequested = async function({ async function onSecretRequested(
user_id: userId, userId: string,
device_id: deviceId, deviceId: string,
request_id: requestId, requestId: string,
name, name: string,
device_trust: deviceTrust, deviceTrust: IDeviceTrustLevel,
}) { ): Promise<string> {
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (userId !== client.getUserId()) { if (userId !== client.getUserId()) {
@ -233,16 +250,16 @@ const onSecretRequested = async function({
return key && encodeBase64(key); return key && encodeBase64(key);
} }
console.warn("onSecretRequested didn't recognise the secret named ", name); console.warn("onSecretRequested didn't recognise the secret named ", name);
}; }
export const crossSigningCallbacks = { export const crossSigningCallbacks: ICryptoCallbacks = {
getSecretStorageKey, getSecretStorageKey,
cacheSecretStorageKey, cacheSecretStorageKey,
onSecretRequested, onSecretRequested,
getDehydrationKey, getDehydrationKey,
}; };
export async function promptForBackupPassphrase() { export async function promptForBackupPassphrase(): Promise<Uint8Array> {
let key; let key;
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
@ -292,7 +309,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
/* options = */ { /* options = */ {
onBeforeClose(reason) { onBeforeClose: async (reason) => {
// If Secure Backup is required, you cannot leave the modal. // If Secure Backup is required, you cannot leave the modal.
if (reason === "backgroundClick") { if (reason === "backgroundClick") {
return !isSecureBackupRequired(); return !isSecureBackupRequired();
@ -329,10 +346,10 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
const keyId = Object.keys(secretStorageKeys)[0]; const keyId = Object.keys(secretStorageKeys)[0];
if (keyId && SettingsStore.getValue("feature_dehydration")) { if (keyId && SettingsStore.getValue("feature_dehydration")) {
const dehydrationKeyInfo = let dehydrationKeyInfo = {};
secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase if (secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase) {
? {passphrase: secretStorageKeyInfo[keyId].passphrase} dehydrationKeyInfo = { passphrase: secretStorageKeyInfo[keyId].passphrase };
: {}; }
console.log("Setting dehydration key"); console.log("Setting dehydration key");
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device"); await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
} else { } else {
@ -354,7 +371,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
} }
// FIXME: this function name is a bit of a mouthful // FIXME: this function name is a bit of a mouthful
export async function tryToUnlockSecretStorageWithDehydrationKey(client) { export async function tryToUnlockSecretStorageWithDehydrationKey(
client: MatrixClient,
): Promise<void> {
const key = dehydrationCache.key; const key = dehydrationCache.key;
let restoringBackup = false; let restoringBackup = false;
if (key && await client.isSecretStorageReady()) { if (key && await client.isSecretStorageReady()) {
@ -366,10 +385,10 @@ export async function tryToUnlockSecretStorageWithDehydrationKey(client) {
// we also need to set a new dehydrated device to replace the // we also need to set a new dehydrated device to replace the
// device we rehydrated // device we rehydrated
const dehydrationKeyInfo = let dehydrationKeyInfo = {};
dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase if (dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase) {
? {passphrase: dehydrationCache.keyInfo.passphrase} dehydrationKeyInfo = { passphrase: dehydrationCache.keyInfo.passphrase };
: {}; }
await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device"); await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device");
// and restore from backup // and restore from backup