1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2026-01-03 23:22:30 +03:00

Verify e2e keys on download

Check the signature on downloaded e2e keys, and ignore those that don't match.
This commit is contained in:
Richard van der Hoff
2016-08-04 15:26:02 +01:00
parent 6001077c34
commit f10467e81f
4 changed files with 182 additions and 48 deletions

View File

@@ -125,6 +125,23 @@ OlmDevice.prototype._saveSession = function(deviceKey, session) {
};
/**
* get an OlmUtility and call the given function
*
* @param {function} func
* @return {object} result of func
* @private
*/
OlmDevice.prototype._getUtility = function(func) {
var utility = new Olm.Utility();
try {
return func(utility);
} finally {
utility.free();
}
};
/**
* Signs a message with the ed25519 key for this account.
*
@@ -332,5 +349,25 @@ OlmDevice.prototype.decryptMessage = function(
});
};
/**
* Verify an ed25519 signature.
*
* @param {string} key ed25519 key
* @param {string} message message which was signed
* @param {string} signature base64-encoded signature to be checked
*
* @raises {Error} if there is a problem with the verification. If the key was
* too small then the message will be "OLM.INVALID_BASE64". If the signature
* was invalid then the message will be "OLM.BAD_MESSAGE_MAC".
*/
OlmDevice.prototype.verifySignature = function(
key, message, signature
) {
this._getUtility(function(util) {
util.ed25519_verify(key, message, signature);
});
};
/** */
module.exports = OlmDevice;

View File

@@ -220,7 +220,7 @@ Crypto.prototype.downloadKeys = function(userIds, forceDownload) {
var userStore = stored[userId];
var updated = _updateStoredDeviceKeysForUser(
userId, userStore, res.device_keys[userId]
self._olmDevice, userId, userStore, res.device_keys[userId]
);
if (updated) {
@@ -233,7 +233,8 @@ Crypto.prototype.downloadKeys = function(userIds, forceDownload) {
});
};
function _updateStoredDeviceKeysForUser(userId, userStore, userResult) {
function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
userResult) {
var updated = false;
// remove any devices in the store which aren't in the response
@@ -255,52 +256,86 @@ function _updateStoredDeviceKeysForUser(userId, userStore, userResult) {
continue;
}
var deviceRes = userResult[deviceId];
var deviceStore;
if (!deviceRes.keys) {
// no keys?
continue;
if (_storeDeviceKeys(
_olmDevice, userId, deviceId, userStore, userResult[deviceId]
)) {
updated = true;
}
var signKey = deviceRes.keys["ed25519:" + deviceId];
if (!signKey) {
console.log("Device " + userId + ": " +
deviceId + " has no ed25519 key");
continue;
}
if (deviceId in userStore) {
// already have this device.
deviceStore = userStore[deviceId];
if (deviceStore.keys["ed25519:" + deviceId] != signKey) {
// this should only happen if the list has been MITMed; we are
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
console.warn("Ed25519 key for device" + userId + ": " +
deviceId + " has changed");
continue;
}
} else {
userStore[deviceId] = deviceStore = {
verified: DeviceVerification.UNVERIFIED
};
}
// TODO: check signature. Remember that we need to check for
// _olmDevice.
deviceStore.keys = deviceRes.keys;
deviceStore.algorithms = deviceRes.algorithms;
deviceStore.unsigned = deviceRes.unsigned;
updated = true;
}
return updated;
}
/*
* Process a device in a /query response, and add it to the userStore
*
* returns true if a change was made, else false
*/
function _storeDeviceKeys(_olmDevice, userId, deviceId, userStore, deviceResult) {
if (!deviceResult.keys) {
// no keys?
return false;
}
var signKeyId = "ed25519:" + deviceId;
var signKey = deviceResult.keys[signKeyId];
if (!signKey) {
console.log("Device " + userId + ":" + deviceId +
" has no ed25519 key");
return false;
}
var unsigned = deviceResult.unsigned;
var signatures = deviceResult.signatures || {};
var userSigs = signatures[userId] || {};
var signature = userSigs[signKeyId];
if (!signature) {
console.log("Device " + userId + ":" + deviceId +
" is not signed");
return false;
}
// prepare the canonical json: remove 'unsigned' and sigxsnatures, and
// stringify with anotherjson
delete deviceResult.unsigned;
delete deviceResult.signatures;
var json = anotherjson.stringify(deviceResult);
console.log("msg:", json);
try {
_olmDevice.verifySignature(signKey, json, signature);
} catch (e) {
console.log("Unable to verify signature on device " +
userId + ":" + deviceId + ":", e);
return false;
}
var deviceStore;
if (deviceId in userStore) {
// already have this device.
deviceStore = userStore[deviceId];
if (deviceStore.keys["ed25519:" + deviceId] != signKey) {
// this should only happen if the list has been MITMed; we are
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
console.warn("Ed25519 key for device" + userId + ": " +
deviceId + " has changed");
return false;
}
} else {
userStore[deviceId] = deviceStore = {
verified: DeviceVerification.UNVERIFIED
};
}
deviceStore.keys = deviceResult.keys;
deviceStore.algorithms = deviceResult.algorithms;
deviceStore.unsigned = unsigned;
return true;
}
/**
* List the stored device keys for a user id

View File

@@ -443,6 +443,24 @@ describe("MatrixClient crypto", function() {
.catch(utils.failTest).done(done);
});
it("Ali gets keys with an invalid signature", function(done) {
q()
.then(bobUploadsKeys)
.then(function() {
// tamper bob's keys!
expect(bobDeviceKeys.keys["curve25519:" + bobDeviceId]).toBeDefined();
bobDeviceKeys.keys["curve25519:" + bobDeviceId] += "abc";
return q.all(aliClient.downloadKeys([bobUserId]),
aliQueryKeys());
})
.then(function() {
// should get an empty list
expect(aliClient.listDeviceKeys(bobUserId)).toEqual([]);
})
.catch(utils.failTest).done(done);
});
it("Ali enables encryption", function(done) {
q()
.then(bobUploadsKeys)

View File

@@ -184,34 +184,78 @@ describe("MatrixClient", function() {
describe("downloadKeys", function() {
it("should do an HTTP request and then store the keys", function(done) {
var borisKeys = {dev1: {algorithms: ["1"], keys: { "ed25519:dev1": "k1" }}};
var chazKeys = {dev2: {algorithms: ["2"], keys: { "ed25519:dev2": "k2" }}};
var ed25519key = "wV5E3EUSHpHuoZLljNzojlabjGdXT3Mz7rugG9zgbkI";
var borisKeys = {
dev1: {
algorithms: ["1"], keys: { "ed25519:dev1": ed25519key },
signatures: {
boris: {
"ed25519:dev1":
"u99n8WZ61G//K6eVgYc+RDLVapmjttxqhjNucIFGEIJ" +
"oA4TUY8FmiGv3zl0EA71zrvPDfnFL5XLNsdc55NGbDg"
}
},
unsigned: { "abc": "def" },
}
};
var chazKeys = {
dev2: {
algorithms: ["2"], keys: { "ed25519:dev2": ed25519key },
signatures: {
chaz: {
"ed25519:dev2":
"8eaeXUWy9AQzjaNVOjVLs4FQk+cgobkNS811EjZBCMA" +
"apd8aPOfE26E13nFFOCLC1V6fOH5wVo61hxGR/j4PBA"
}
},
unsigned: { "ghi": "def" },
}
};
var daveKeys = {
dev3: {
algorithms: ["3"], keys: { "ed25519:dev2": ed25519key },
signatures: {
dave: {
"ed25519:dev2":
"8eaeXUWy9AQzjaNVOjVLs4FQk+cgobkNS811EjZBCMA" +
"apd8aPOfE26E13nFFOCLC1V6fOH5wVo61hxGR/j4PBA"
}
},
unsigned: { "ghi": "def" },
}
};
httpBackend.when("POST", "/keys/query").check(function(req) {
expect(req.data).toEqual({device_keys: {boris: {}, chaz: {}}});
expect(req.data).toEqual({device_keys: {boris: {}, chaz: {}, dave: {}}});
}).respond(200, {
device_keys: {
boris: borisKeys,
chaz: chazKeys,
dave: daveKeys,
},
});
client.downloadKeys(["boris", "chaz"]).then(function(res) {
client.downloadKeys(["boris", "chaz", "dave"]).then(function(res) {
expect(res).toEqual({
boris: {
dev1: {
verified: 0, // DeviceVerification.UNVERIFIED
keys: { "ed25519:dev1": "k1" },
keys: { "ed25519:dev1": ed25519key },
algorithms: ["1"],
unsigned: { "abc": "def" },
},
},
chaz: {
dev2: {
verified: 0, // DeviceVerification.UNVERIFIED
keys: { "ed25519:dev2" : "k2" },
keys: { "ed25519:dev2" : ed25519key },
algorithms: ["2"],
unsigned: { "ghi": "def" },
},
},
dave: {
// dave's key fails validation.
},
});
}).catch(utils.failTest).done(done);