index : matrix-js-sdk

My fork of matrix-js-sdk

diff options
context:
space:
mode:
authorBruno Windels <[email protected]>2020-06-17 15:22:24 +0200
committerBruno Windels <[email protected]>2020-06-17 15:22:24 +0200
commit3c5b304b6b75f15aea2a0eabc23192b3cd515c6e (patch)
tree6be7510321339f88f201dbf0ee88efd11a6df5ca
parentf656698061f5575a66ba58fda99f8b379cb49d86 (diff)
downloadmatrix-js-sdk-3c5b304b6b75f15aea2a0eabc23192b3cd515c6e.tar.gz
move cross-signing bootstrapping over to builder
-rw-r--r--src/crypto/CrossSigning.js6
-rw-r--r--src/crypto/EncryptionSetup.js80
-rw-r--r--src/crypto/index.js65
3 files changed, 113 insertions, 38 deletions
diff --git a/src/crypto/CrossSigning.js b/src/crypto/CrossSigning.js
index ff5bc708..ec82a604 100644
--- a/src/crypto/CrossSigning.js
+++ b/src/crypto/CrossSigning.js
@@ -178,12 +178,12 @@ export class CrossSigningInfo extends EventEmitter {
* typically called in conjunction with the creation of new cross-signing
* keys.
*
- * @param {object} keys The keys to store
+ * @param {Map} keys The keys to store
* @param {SecretStorage} secretStorage The secret store using account data
*/
static async storeInSecretStorage(keys, secretStorage) {
- for (const type of Object.keys(keys)) {
- const encodedKey = encodeBase64(keys[type]);
+ for (const [type, privateKey] of keys) {
+ const encodedKey = encodeBase64(privateKey);
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
}
}
diff --git a/src/crypto/EncryptionSetup.js b/src/crypto/EncryptionSetup.js
index e706af55..9447fabd 100644
--- a/src/crypto/EncryptionSetup.js
+++ b/src/crypto/EncryptionSetup.js
@@ -1,5 +1,10 @@
import {MatrixEvent} from "../models/event";
import {EventEmitter} from "events";
+import {createCryptoStoreCacheCallbacks} from "./CrossSigning";
+import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
+import {
+ PREFIX_UNSTABLE,
+} from "../http-api";
/**
* Builds an EncryptionSetupOperation by calling any of the add.. methods.
@@ -24,6 +29,32 @@ export class EncryptionSetupBuilder {
this._keyBackupInfo = null;
}
+ /**
+ * Adds new cross-signing public keys
+ * @param {Object} auth auth dictionary needed to upload the new keys
+ * @param {Object} keys the new keys
+ */
+ addCrossSigningKeys(auth, keys) {
+ this._crossSigningKeys = {auth, keys};
+ }
+
+ /**
+ * Add signatures from a given user and device/x-sign key
+ * Used to sign the new cross-signing key with the device key
+ *
+ * @param {String} userId
+ * @param {String} deviceId
+ * @param {String} signature
+ */
+ addKeySignature(userId, deviceId, signature) {
+ if (!this._keySignatures) {
+ this._keySignatures = {};
+ }
+ const userSignatures = this._keySignatures[userId] || {};
+ this._keySignatures[userId] = userSignatures;
+ userSignatures[deviceId] = signature;
+ }
+
/**
* @param {String} type
@@ -48,6 +79,37 @@ export class EncryptionSetupBuilder {
);
}
+ /**
+ * Stores the created keys locally.
+ *
+ * This does not yet store the operation in a way that it can be restored,
+ * but that is the idea in the future.
+ *
+ * @param {Crypto} crypto
+ * @return {Promise}
+ */
+ async persist(crypto) {
+ // store self_signing and user_signing private key in cache
+ if (this._crossSigningKeys) {
+ const cacheCallbacks = createCryptoStoreCacheCallbacks(
+ crypto._cryptoStore, crypto._olmDevice);
+ for (const type of ["self_signing", "user_signing"]) {
+ // logger.log(`Cache ${type} cross-signing private key locally`);
+ const privateKey = this.crossSigningCallbacks.privateKeys.get(type);
+ await cacheCallbacks.storeCrossSigningKeyCache(type, privateKey);
+ }
+ // store own cross-sign pubkeys as trusted
+ await crypto._cryptoStore.doTxn(
+ 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
+ (txn) => {
+ crypto._cryptoStore.storeCrossSigningKeys(
+ txn, this._crossSigningKeys.keys);
+ },
+ );
+ }
+ }
+}
+
/**
* Can be created from EncryptionSetupBuilder, or
* (in a follow-up PR, not implemented yet) restored from storage, to retry.
@@ -80,6 +142,24 @@ export class EncryptionSetupOperation {
await baseApis.setAccountData(type, content);
}
}
+ // upload cross-signing keys
+ if (this._crossSigningKeys) {
+ const keys = {};
+ for (const [name, key] of Object.entries(this._crossSigningKeys.keys)) {
+ keys[name + "_key"] = key;
+ }
+ await baseApis.uploadDeviceSigningKeys(
+ this._crossSigningKeys.auth,
+ keys,
+ );
+ // pass the new keys to the main instance of our own CrossSigningInfo.
+ crypto._crossSigningInfo.setKeys(this._crossSigningKeys.keys);
+ }
+ // upload first cross-signing signatures with the new key
+ // (e.g. signing our own device)
+ if (this._keySignatures) {
+ await baseApis.uploadKeySignatures(this._keySignatures);
+ }
}
}
}
diff --git a/src/crypto/index.js b/src/crypto/index.js
index 16e6c4b5..28865eb7 100644
--- a/src/crypto/index.js
+++ b/src/crypto/index.js
@@ -476,7 +476,7 @@ Crypto.prototype.isCrossSigningReady = async function() {
* SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields.
*/
Crypto.prototype.bootstrapSecretStorage = async function({
- authUploadDeviceSigningKeys,
+ authUploadDeviceSigningKeys = async func => await func(),
createSecretStorageKey = async () => ({ }),
keyBackupInfo,
setupNewKeyBackup,
@@ -484,17 +484,6 @@ Crypto.prototype.bootstrapSecretStorage = async function({
getKeyBackupPassphrase,
} = {}) {
logger.log("Bootstrapping Secure Secret Storage");
-
- // Create cross-signing keys if they don't exist, as we want to sign the SSSS default
- // key with the cross-signing master key. The cross-signing master key is also used
- // to verify the signature on the SSSS default key when adding secrets, so we
- // effectively need it for both reading and writing secrets.
- const crossSigningPrivateKeys = {};
-
- // If we happen to reset cross-signing keys here, then we want access to the
- // cross-signing private keys, but only for the scope of this method, so we
- // use temporary callbacks to weave them through the various APIs.
- const appCallbacks = Object.assign({}, this._baseApis._cryptoCallbacks);
const builder = new EncryptionSetupBuilder(this._baseApis.store.accountData);
const secretStorage = new SecretStorage(
builder.accountDataClientAdapter,
@@ -529,14 +518,24 @@ Crypto.prototype.bootstrapSecretStorage = async function({
// reset the cross-signing keys
const resetCrossSigning = async () => {
- this._baseApis._cryptoCallbacks.saveCrossSigningKeys =
- keys => Object.assign(crossSigningPrivateKeys, keys);
- this._baseApis._cryptoCallbacks.getCrossSigningKey =
- name => crossSigningPrivateKeys[name];
- await this.resetCrossSigningKeys(
- CrossSigningLevel.MASTER,
- { authUploadDeviceSigningKeys },
- );
+ crossSigningInfo.resetKeys();
+ // sign master key with device key
+ await this._signObject(crossSigningInfo.keys.master);
+
+ await authUploadDeviceSigningKeys(authDict => {
+ builder.addCrossSigningKeys(authDict, crossSigningInfo.keys);
+ return Promise.resolve();
+ });
+
+ // cross-sign own device
+ const device = this._deviceList.getStoredDevice(this._userId, this._deviceId);
+ const deviceSignature = await crossSigningInfo.signDevice(this._userId, device);
+ builder.addKeySignature(this._userId, this._deviceId, deviceSignature);
+
+ if (keyBackupInfo) {
+ await crossSigningInfo.signObject(keyBackupInfo.auth_data, "master");
+ builder.addSessionBackup(keyBackupInfo);
+ }
};
const ensureCanCheckPassphrase = async (keyId, keyInfo) => {
@@ -631,7 +630,7 @@ Crypto.prototype.bootstrapSecretStorage = async function({
// Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing.
logger.log("Adding cross signing signature to key backup");
- await this._crossSigningInfo.signObject(
+ await crossSigningInfo.signObject(
keyBackupInfo.auth_data, "master",
);
await this._baseApis._http.authedRequest(
@@ -664,18 +663,16 @@ Crypto.prototype.bootstrapSecretStorage = async function({
}
}
- // If cross-signing keys were reset, store them in Secure Secret Storage.
- // This is done in a separate step so we can ensure secret storage has its
- // own key first.
- // XXX: We need to think about how to re-do these steps if they fail.
- // See also https://github.com/vector-im/riot-web/issues/11635
- if (Object.keys(crossSigningPrivateKeys).length) {
+ const crossSigningPrivateKeys = builder.crossSigningCallbacks.privateKeys;
+ if (crossSigningPrivateKeys.size) {
logger.log("Storing cross-signing private keys in secret storage");
// Assuming no app-supplied callback, default to storing in SSSS.
- if (!appCallbacks.saveCrossSigningKeys) {
+ if (!this._baseApis._cryptoCallbacks.saveCrossSigningKeys) {
+ // this is writing to in-memory account data in builder.accountDataClientAdapter
+ // so won't fail
await CrossSigningInfo.storeInSecretStorage(
crossSigningPrivateKeys,
- this._secretStorage,
+ secretStorage,
);
}
}
@@ -688,13 +685,7 @@ Crypto.prototype.bootstrapSecretStorage = async function({
await this._baseApis.createKeyBackupVersion(info);
}
- // Call `getCrossSigningKey` for side effect of caching private keys for
- // future gossiping to other devices if enabled via app level callbacks.
if (this._crossSigningInfo._cacheCallbacks) {
- for (const type of ["self_signing", "user_signing"]) {
- logger.log(`Cache ${type} cross-signing private key locally`);
- await this._crossSigningInfo.getCrossSigningKey(type);
- }
}
// and likewise for the session backup key
@@ -728,6 +719,10 @@ Crypto.prototype.bootstrapSecretStorage = async function({
const operation = builder.buildOperation();
await operation.apply(this);
+ // this persists private keys and public keys as trusted,
+ // only do this if apply succeeded for now as retry isn't in place yet
+ await builder.persist(this);
+ }
logger.log("Secure Secret Storage ready");
};