1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-10 07:22:27 +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
* @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) {
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
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 = {
self_signing_key: {
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) => {
// store the newly generated account keys
this._cryptoStore.storeAccountKeys(txn, info.accountKeys);
}).then(() => {
// re-check the SSK in the device store if necessary
return this._crypto.checkOwnSskTrust();
}).then(() => {
// upload the public part of the account keys
return this.uploadDeviceSigningKeys(keys);
}).then(() => {
return this._crypto._signObject(data.auth_data);
}).then(() => {
return this._http.authedRequest(
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) => {
this._cryptoStore.storeAccountKeys(txn, accountKeys);
});
await this._crypto.checkOwnSskTrust();
} catch(e) {
decryption.free();
throw e;

View File

@@ -266,51 +266,63 @@ Crypto.prototype.init = async function() {
*/
Crypto.prototype._onDeviceListUserSskUpdated = async function(userId) {
if (userId === this._userId) {
// If we see an update to our own SSK, check it against the SSK we have and,
// if it matches, mark it as verified
this.checkOwnSskTrust();
}
}
// First, get the pubkey of the one we can see
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();
/*
* Check the copy of our SSK that we have in the device list and see if it
* matches our private part. If it does, mark it as trusted.
*/
Crypto.prototype.checkOwnSskTrust = async function() {
const userId = this._userId;
// 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 we see an update to our own SSK, check it against the SSK we have and,
// if it matches, mark it as verified
// First, get the pubkey of the one we can see
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");
return;
}
let signing;
let localPubkey;
try {
signing = new global.Olm.PkSigning();
localPubkey = signing.init_with_seed(Buffer.from(accountKeys.self_signing_key_seed, 'base64'))
} finally {
if (signing) signing.free();
signing = null;
}
if (!localPubkey) {
logger.error("Unable to compute public key for stored SSK seed");
}
});
if (!accountKeys || !accountKeys.self_signing_key_seed) {
logger.info("Ignoring new self-signing key for us because we have no private part stored");
return;
}
let signing;
let localPubkey;
try {
signing = new global.Olm.PkSigning();
localPubkey = signing.init_with_seed(Buffer.from(accountKeys.self_signing_key_seed, 'base64'))
} finally {
if (signing) signing.free();
signing = null;
}
if (!localPubkey) {
logger.error("Unable to compute public key for stored SSK seed");
}
// Finally, are they the same?
if (seenPubkey === localPubkey) {
logger.info("Published self-signing key matches local copy: marking as verified");
this.setSskVerification(userId, SskInfo.SskVerification.VERIFIED);
} else {
logger.info(
"Published self-signing key DOES NOT match local copy! Local: " +
localPubkey + ", published: " + seenPubkey,
);
}
// Finally, are they the same?
if (seenPubkey === localPubkey) {
logger.info("Published self-signing key matches local copy: marking as verified");
this.setSskVerification(userId, SskInfo.SskVerification.VERIFIED);
// Now we may be able to trust our key backup
await this.checkKeyBackup();
} else {
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)) {
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(
this._userId, sigInfo.deviceId,
);
@@ -423,7 +462,13 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
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;
};
@@ -2340,11 +2385,17 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
* @param {Object} obj Object to which we will add a 'signatures' property
*/
Crypto.prototype._signObject = async function(obj) {
const sigs = {};
sigs[this._userId] = {};
const sigs = obj.signatures || {};
const unsigned = obj.unsigned;
delete obj.signatures;
delete obj.unsigned;
sigs[this._userId] = sigs[this._userId] || {};
sigs[this._userId]["ed25519:" + this._deviceId] =
await this._olmDevice.sign(anotherjson.stringify(obj));
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
// anotherjson
delete obj.unsigned;
delete obj.signatures;
const json = anotherjson.stringify(obj);
const mangledObj = Object.assign({}, obj);
delete mangledObj.unsigned;
delete mangledObj.signatures;
const json = anotherjson.stringify(mangledObj);
olmDevice.verifySignature(
signingKey, json, signature,

View File

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