support setting up dehydration from blank account, SSO support, and other fixes
parent
1c2e05e925
commit
4398f1eb94
|
@ -30,6 +30,16 @@ import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
let secretStorageKeys = {};
|
let secretStorageKeys = {};
|
||||||
let secretStorageBeingAccessed = false;
|
let secretStorageBeingAccessed = false;
|
||||||
|
|
||||||
|
let dehydrationInfo = {};
|
||||||
|
|
||||||
|
export function cacheDehydrationKey(key, keyInfo = {}) {
|
||||||
|
dehydrationInfo = {key, keyInfo};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDehydrationKeyCache() {
|
||||||
|
return dehydrationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
function isCachingAllowed() {
|
function isCachingAllowed() {
|
||||||
return secretStorageBeingAccessed;
|
return secretStorageBeingAccessed;
|
||||||
}
|
}
|
||||||
|
@ -64,6 +74,22 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
return [name, secretStorageKeys[name]];
|
return [name, secretStorageKeys[name]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we dehydrated a device, see if that key works for SSSS
|
||||||
|
if (dehydrationInfo.key) {
|
||||||
|
try {
|
||||||
|
if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationInfo.key, info)) {
|
||||||
|
const key = dehydrationInfo.key;
|
||||||
|
// Save to cache to avoid future prompts in the current session
|
||||||
|
if (isCachingAllowed()) {
|
||||||
|
secretStorageKeys[name] = key;
|
||||||
|
}
|
||||||
|
dehydrationInfo = {};
|
||||||
|
return [name, key];
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
dehydrationInfo = {};
|
||||||
|
}
|
||||||
|
|
||||||
const inputToKey = async ({ passphrase, recoveryKey }) => {
|
const inputToKey = async ({ passphrase, recoveryKey }) => {
|
||||||
if (passphrase) {
|
if (passphrase) {
|
||||||
return deriveKey(
|
return deriveKey(
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||||
import DeviceListener from "./DeviceListener";
|
import DeviceListener from "./DeviceListener";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
||||||
|
import {decodeBase64, encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -311,6 +312,25 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
console.log("No pickle key available");
|
console.log("No pickle key available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rehydrationKeyInfoJSON = sessionStorage.getItem("mx_rehydration_key_info");
|
||||||
|
const rehydrationKeyInfo = rehydrationKeyInfoJSON && JSON.parse(rehydrationKeyInfoJSON);
|
||||||
|
const rehydrationKeyB64 = sessionStorage.getItem("mx_rehydration_key");
|
||||||
|
const rehydrationKey = rehydrationKeyB64 && decodeBase64(rehydrationKeyB64);
|
||||||
|
const rehydrationOlmPickle = sessionStorage.getItem("mx_rehydration_account");
|
||||||
|
let olmAccount;
|
||||||
|
if (rehydrationOlmPickle) {
|
||||||
|
olmAccount = new global.Olm.Account();
|
||||||
|
try {
|
||||||
|
olmAccount.unpickle("DEFAULT_KEY", rehydrationOlmPickle);
|
||||||
|
} catch {
|
||||||
|
olmAccount.free();
|
||||||
|
olmAccount = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionStorage.removeItem("mx_rehydration_key_info");
|
||||||
|
sessionStorage.removeItem("mx_rehydration_key");
|
||||||
|
sessionStorage.removeItem("mx_rehydration_account");
|
||||||
|
|
||||||
console.log(`Restoring session for ${userId}`);
|
console.log(`Restoring session for ${userId}`);
|
||||||
await _doSetLoggedIn({
|
await _doSetLoggedIn({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
@ -320,6 +340,9 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
pickleKey: pickleKey,
|
pickleKey: pickleKey,
|
||||||
|
rehydrationKey: rehydrationKey,
|
||||||
|
rehydrationKeyInfo: rehydrationKeyInfo,
|
||||||
|
olmAccount: olmAccount,
|
||||||
}, false);
|
}, false);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -463,7 +486,13 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
|
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
_persistCredentialsToLocalStorage(credentials);
|
// drop dehydration key and olm account before persisting. (Those
|
||||||
|
// get persisted for token login, but aren't needed at this point.)
|
||||||
|
const strippedCredentials = Object.assign({}, credentials);
|
||||||
|
delete strippedCredentials.rehydrationKeyInfo;
|
||||||
|
delete strippedCredentials.rehydrationKey;
|
||||||
|
delete strippedCredentials.olmAcconut;
|
||||||
|
_persistCredentialsToLocalStorage(strippedCredentials);
|
||||||
|
|
||||||
// The user registered as a PWLU (PassWord-Less User), the generated password
|
// The user registered as a PWLU (PassWord-Less User), the generated password
|
||||||
// is cached here such that the user can change it at a later time.
|
// is cached here such that the user can change it at a later time.
|
||||||
|
@ -528,6 +557,19 @@ function _persistCredentialsToLocalStorage(credentials) {
|
||||||
localStorage.setItem("mx_device_id", credentials.deviceId);
|
localStorage.setItem("mx_device_id", credentials.deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporarily save dehydration information if it's provided. This is
|
||||||
|
// needed for token logins, because the page reloads after the login, so we
|
||||||
|
// can't keep it in memory.
|
||||||
|
if (credentials.rehydrationKeyInfo) {
|
||||||
|
sessionStorage.setItem("mx_rehydration_key_info", JSON.stringify(credentials.rehydrationKeyInfo));
|
||||||
|
}
|
||||||
|
if (credentials.rehydrationKey) {
|
||||||
|
sessionStorage.setItem("mx_rehydration_key", encodeBase64(credentials.rehydrationKey));
|
||||||
|
}
|
||||||
|
if (credentials.olmAccount) {
|
||||||
|
sessionStorage.setItem("mx_rehydration_account", credentials.olmAccount.pickle("DEFAULT_KEY"));
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Session persisted for ${credentials.userId}`);
|
console.log(`Session persisted for ${credentials.userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/Login.js
18
src/Login.js
|
@ -20,7 +20,12 @@ limitations under the License.
|
||||||
|
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { AccessCancelledError, confirmToDismiss } from "./CrossSigningManager";
|
import {
|
||||||
|
AccessCancelledError,
|
||||||
|
cacheDehydrationKey,
|
||||||
|
confirmToDismiss,
|
||||||
|
getDehydrationKeyCache,
|
||||||
|
} from "./CrossSigningManager";
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
|
import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
|
||||||
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
||||||
|
@ -164,9 +169,6 @@ export default class Login {
|
||||||
* @returns {MatrixClientCreds}
|
* @returns {MatrixClientCreds}
|
||||||
*/
|
*/
|
||||||
export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
|
export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
|
||||||
let rehydrationKeyInfo;
|
|
||||||
let rehydrationKey;
|
|
||||||
|
|
||||||
const client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
idBaseUrl: isUrl,
|
idBaseUrl: isUrl,
|
||||||
|
@ -190,14 +192,16 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dehydrationKeyCache = getDehydrationKeyCache();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
userId: data.user_id,
|
userId: data.user_id,
|
||||||
deviceId: data.device_id,
|
deviceId: data.device_id,
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
rehydrationKeyInfo,
|
rehydrationKeyInfo: dehydrationKeyCache.keyInfo,
|
||||||
rehydrationKey,
|
rehydrationKey: dehydrationKeyCache.key,
|
||||||
olmAccount: data._olm_account,
|
olmAccount: data._olm_account,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -243,5 +247,7 @@ async function getDehydrationKey(keyInfo) {
|
||||||
throw new AccessCancelledError();
|
throw new AccessCancelledError();
|
||||||
}
|
}
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
|
// need to copy the key because rehydration (unpickling) will clobber it
|
||||||
|
cacheDehydrationKey(new Uint8Array(key), keyInfo);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||||
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
||||||
import * as StorageManager from './utils/StorageManager';
|
import * as StorageManager from './utils/StorageManager';
|
||||||
import IdentityAuthClient from './IdentityAuthClient';
|
import IdentityAuthClient from './IdentityAuthClient';
|
||||||
import { crossSigningCallbacks } from './CrossSigningManager';
|
import { cacheDehydrationKey, crossSigningCallbacks } from './CrossSigningManager';
|
||||||
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
||||||
|
|
||||||
export interface IMatrixClientCreds {
|
export interface IMatrixClientCreds {
|
||||||
|
@ -270,33 +270,43 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (creds.olmAccount) {
|
if (creds.olmAccount) {
|
||||||
|
console.log("got a dehydrated account");
|
||||||
opts.deviceToImport = {
|
opts.deviceToImport = {
|
||||||
olmDevice: {
|
olmDevice: {
|
||||||
pickledAccount: creds.olmAccount.pickle("DEFAULT_KEY"),
|
pickledAccount: creds.olmAccount.pickle(creds.pickleKey || "DEFAULT_KEY"),
|
||||||
sessions: [],
|
sessions: [],
|
||||||
pickleKey: "DEFAULT_KEY",
|
pickleKey: creds.pickleKey || "DEFAULT_KEY",
|
||||||
},
|
},
|
||||||
userId: creds.userId,
|
userId: creds.userId,
|
||||||
deviceId: creds.deviceId,
|
deviceId: creds.deviceId,
|
||||||
};
|
};
|
||||||
|
creds.olmAccount.free();
|
||||||
} else {
|
} else {
|
||||||
opts.userId = creds.userId;
|
opts.userId = creds.userId;
|
||||||
opts.deviceId = creds.deviceId;
|
opts.deviceId = creds.deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: modify crossSigningCallbacks.getSecretStorageKey so that it tries using rehydrationkey and/or saves the passphrase info
|
|
||||||
|
|
||||||
// These are always installed regardless of the labs flag so that
|
// These are always installed regardless of the labs flag so that
|
||||||
// cross-signing features can toggle on without reloading and also be
|
// cross-signing features can toggle on without reloading and also be
|
||||||
// accessed immediately after login.
|
// accessed immediately after login.
|
||||||
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks);
|
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks);
|
||||||
|
|
||||||
this.matrixClient = createMatrixClient(opts);
|
// set dehydration key after cross-signing gets set up -- we wait until
|
||||||
|
// cross-signing is set up because we want to cross-sign the dehydrated
|
||||||
|
// key
|
||||||
|
const origGetSecretStorageKey = opts.cryptoCallbacks.getSecretStorageKey
|
||||||
|
opts.cryptoCallbacks.getSecretStorageKey = async (keyinfo, ssssItemName) => {
|
||||||
|
const [name, key] = await origGetSecretStorageKey(keyinfo, ssssItemName);
|
||||||
|
this.matrixClient.setDehydrationKey(key, {passphrase: keyinfo.keys[name].passphrase});
|
||||||
|
return [name, key];
|
||||||
|
}
|
||||||
|
|
||||||
if (creds.rehydrationKey) {
|
if (creds.rehydrationKey) {
|
||||||
this.matrixClient.cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo || {});
|
cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.matrixClient = createMatrixClient(opts);
|
||||||
|
|
||||||
// we're going to add eventlisteners for each matrix event tile, so the
|
// we're going to add eventlisteners for each matrix event tile, so the
|
||||||
// potential number of event listeners is quite high.
|
// potential number of event listeners is quite high.
|
||||||
this.matrixClient.setMaxListeners(500);
|
this.matrixClient.setMaxListeners(500);
|
||||||
|
|
|
@ -304,6 +304,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const dehydrationKeyInfo =
|
||||||
|
this._recoveryKey.keyInfo && this._recoveryKey.keyInfo.passphrase
|
||||||
|
? {passphrase: this._recoveryKey.keyInfo.passphrase}
|
||||||
|
: {};
|
||||||
|
await cli.setDehydrationKey(this._recoveryKey.privateKey, dehydrationKeyInfo);
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
|
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
|
||||||
|
|
Loading…
Reference in New Issue