1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-11 19:37:30 +03:00

Sign & trust the key backup from the SSK

This commit is contained in:
David Baker
2019-02-01 15:49:20 +00:00
parent 1d58a64ee1
commit 910d0ec9c1
4 changed files with 118 additions and 51 deletions

View File

@@ -1035,7 +1035,7 @@ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
* @param {object} info Info object from prepareKeyBackupVersion * @param {object} info Info object from prepareKeyBackupVersion
* @returns {Promise<object>} Object with 'version' param indicating the version created * @returns {Promise<object>} Object with 'version' param indicating the version created
*/ */
MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk) { MatrixClient.prototype.createKeyBackupVersion = async function(info, auth, replacesSsk) {
if (this._crypto === null) { if (this._crypto === null) {
throw new Error("End-to-end encryption disabled"); throw new Error("End-to-end encryption disabled");
} }
@@ -1056,6 +1056,14 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk
// sign the USK with the SSK // sign the USK with the SSK
pkSign(uskInfo, Buffer.from(info.accountKeys.self_signing_key_seed, 'base64'), this.credentials.userId); pkSign(uskInfo, Buffer.from(info.accountKeys.self_signing_key_seed, 'base64'), this.credentials.userId);
// Now sig the backup auth data. Do it as this device first because crypto._signObject
// is dumb and bluntly replaces the whole signatures block...
// this can probably go away very soon in favour of just signing with the SSK.
await this._crypto._signObject(data.auth_data);
// now also sign the auth data with the SSK
pkSign(data.auth_data, Buffer.from(info.accountKeys.self_signing_key_seed, 'base64'), this.credentials.userId);
const keys = { const keys = {
self_signing_key: { self_signing_key: {
user_id: this.credentials.userId, user_id: this.credentials.userId,
@@ -1072,11 +1080,12 @@ MatrixClient.prototype.createKeyBackupVersion = function(info, auth, replacesSsk
return this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { return this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
// store the newly generated account keys // store the newly generated account keys
this._cryptoStore.storeAccountKeys(txn, info.accountKeys); this._cryptoStore.storeAccountKeys(txn, info.accountKeys);
}).then(() => {
// re-check the SSK in the device store if necessary
return this._crypto.checkOwnSskTrust();
}).then(() => { }).then(() => {
// upload the public part of the account keys // upload the public part of the account keys
return this.uploadDeviceSigningKeys(keys); return this.uploadDeviceSigningKeys(keys);
}).then(() => {
return this._crypto._signObject(data.auth_data);
}).then(() => { }).then(() => {
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", "/room_keys/version", undefined, data, undefined, "POST", "/room_keys/version", undefined, data,
@@ -1235,6 +1244,8 @@ MatrixClient.prototype._restoreKeyBackup = async function(
await this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { await this._cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.storeAccountKeys(txn, accountKeys); this._cryptoStore.storeAccountKeys(txn, accountKeys);
}); });
await this._crypto.checkOwnSskTrust();
} catch(e) { } catch(e) {
decryption.free(); decryption.free();
throw e; throw e;

View File

@@ -266,51 +266,63 @@ Crypto.prototype.init = async function() {
*/ */
Crypto.prototype._onDeviceListUserSskUpdated = async function(userId) { Crypto.prototype._onDeviceListUserSskUpdated = async function(userId) {
if (userId === this._userId) { if (userId === this._userId) {
// If we see an update to our own SSK, check it against the SSK we have and, this.checkOwnSskTrust();
// if it matches, mark it as verified }
}
// First, get the pubkey of the one we can see /*
const seenSsk = this._deviceList.getStoredSskForUser(userId); * Check the copy of our SSK that we have in the device list and see if it
if (!seenSsk) { * matches our private part. If it does, mark it as trusted.
logger.error("Got SSK update event for user " + userId + " but no new SSK found!"); */
return; Crypto.prototype.checkOwnSskTrust = async function() {
} const userId = this._userId;
const seenPubkey = seenSsk.getFingerprint();
// Now dig out the account keys and get the pubkey of the one in there // If we see an update to our own SSK, check it against the SSK we have and,
let accountKeys = null; // if it matches, mark it as verified
await this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.getAccountKeys(txn, keys => { // First, get the pubkey of the one we can see
accountKeys = keys; const seenSsk = this._deviceList.getStoredSskForUser(userId);
}); if (!seenSsk) {
logger.error("Got SSK update event for user " + userId + " but no new SSK found!");
return;
}
const seenPubkey = seenSsk.getFingerprint();
// Now dig out the account keys and get the pubkey of the one in there
let accountKeys = null;
await this._cryptoStore.doTxn('readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
this._cryptoStore.getAccountKeys(txn, keys => {
accountKeys = keys;
}); });
if (!accountKeys || !accountKeys.self_signing_key_seed) { });
logger.info("Ignoring new self-signing key for us because we have no private part stored"); if (!accountKeys || !accountKeys.self_signing_key_seed) {
return; logger.info("Ignoring new self-signing key for us because we have no private part stored");
} return;
let signing; }
let localPubkey; let signing;
try { let localPubkey;
signing = new global.Olm.PkSigning(); try {
localPubkey = signing.init_with_seed(Buffer.from(accountKeys.self_signing_key_seed, 'base64')) signing = new global.Olm.PkSigning();
} finally { localPubkey = signing.init_with_seed(Buffer.from(accountKeys.self_signing_key_seed, 'base64'))
if (signing) signing.free(); } finally {
signing = null; if (signing) signing.free();
} signing = null;
if (!localPubkey) { }
logger.error("Unable to compute public key for stored SSK seed"); if (!localPubkey) {
} logger.error("Unable to compute public key for stored SSK seed");
}
// Finally, are they the same? // Finally, are they the same?
if (seenPubkey === localPubkey) { if (seenPubkey === localPubkey) {
logger.info("Published self-signing key matches local copy: marking as verified"); logger.info("Published self-signing key matches local copy: marking as verified");
this.setSskVerification(userId, SskInfo.SskVerification.VERIFIED); this.setSskVerification(userId, SskInfo.SskVerification.VERIFIED);
} else { // Now we may be able to trust our key backup
logger.info( await this.checkKeyBackup();
"Published self-signing key DOES NOT match local copy! Local: " + } else {
localPubkey + ", published: " + seenPubkey, logger.info(
); "Published self-signing key DOES NOT match local copy! Local: " +
} localPubkey + ", published: " + seenPubkey,
);
} }
}; };
@@ -397,7 +409,34 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
} }
for (const keyId of Object.keys(mySigs)) { for (const keyId of Object.keys(mySigs)) {
const sigInfo = { deviceId: keyId.split(':')[1] }; // XXX: is this how we're supposed to get the device ID? const sigInfo = {};
// Could be an SSK but just say this is the device ID for backwards compat
sigInfo.deviceId = keyId.split(':')[1];
// first check to see if it's from our SSK
const ssk = this._deviceList.getStoredSskForUser(this._userId);
if (ssk && ssk.getKeyId() === keyId) {
sigInfo.self_signing_key = ssk;
try {
await olmlib.verifySignature(
this._olmDevice,
backupInfo.auth_data,
this._userId,
sigInfo.deviceId,
ssk.getFingerprint(),
);
sigInfo.valid = true;
} catch (e) {
console.log("Bad signature from ssk " + ssk.getKeyId(), e);
sigInfo.valid = false;
}
ret.sigs.push(sigInfo);
continue;
}
// Now look for a sig from a device
// At some point this can probably go away and we'll just support
// it being signed by the SSK
const device = this._deviceList.getStoredDevice( const device = this._deviceList.getStoredDevice(
this._userId, sigInfo.deviceId, this._userId, sigInfo.deviceId,
); );
@@ -423,7 +462,13 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
ret.sigs.push(sigInfo); ret.sigs.push(sigInfo);
} }
ret.usable = ret.sigs.some((s) => s.valid && s.device.isVerified()); ret.usable = ret.sigs.some((s) => {
return (
s.valid && (
(s.device && s.device.isVerified()) || (s.self_signing_key && s.self_signing_key.isVerified())
)
)
});
return ret; return ret;
}; };
@@ -2340,11 +2385,17 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
* @param {Object} obj Object to which we will add a 'signatures' property * @param {Object} obj Object to which we will add a 'signatures' property
*/ */
Crypto.prototype._signObject = async function(obj) { Crypto.prototype._signObject = async function(obj) {
const sigs = {}; const sigs = obj.signatures || {};
sigs[this._userId] = {}; const unsigned = obj.unsigned;
delete obj.signatures;
delete obj.unsigned;
sigs[this._userId] = sigs[this._userId] || {};
sigs[this._userId]["ed25519:" + this._deviceId] = sigs[this._userId]["ed25519:" + this._deviceId] =
await this._olmDevice.sign(anotherjson.stringify(obj)); await this._olmDevice.sign(anotherjson.stringify(obj));
obj.signatures = sigs; obj.signatures = sigs;
if (unsigned !== undefined) obj.unsigned = unsigned;
}; };

View File

@@ -283,9 +283,10 @@ const _verifySignature = module.exports.verifySignature = async function(
// prepare the canonical json: remove unsigned and signatures, and stringify with // prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson // anotherjson
delete obj.unsigned; const mangledObj = Object.assign({}, obj);
delete obj.signatures; delete mangledObj.unsigned;
const json = anotherjson.stringify(obj); delete mangledObj.signatures;
const json = anotherjson.stringify(mangledObj);
olmDevice.verifySignature( olmDevice.verifySignature(
signingKey, json, signature, signingKey, json, signature,

View File

@@ -62,6 +62,10 @@ export default class SskInfo {
return res; return res;
} }
getKeyId() {
return Object.keys(this.keys)[0];
}
getFingerprint() { getFingerprint() {
return Object.values(this.keys)[0]; return Object.values(this.keys)[0];
} }