You've already forked matrix-js-sdk
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -62,6 +62,10 @@ export default class SskInfo {
|
||||
return res;
|
||||
}
|
||||
|
||||
getKeyId() {
|
||||
return Object.keys(this.keys)[0];
|
||||
}
|
||||
|
||||
getFingerprint() {
|
||||
return Object.values(this.keys)[0];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user