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

save cross-signing keys from sync and verify new keys for user

This commit is contained in:
Hubert Chathi
2019-05-28 22:28:54 -04:00
parent 193ad9e09d
commit 53804cac5c
6 changed files with 507 additions and 160 deletions

View File

@@ -1,6 +1,7 @@
"use strict";
import expect from 'expect';
import Promise from 'bluebird';
const logger = require("../logger");
// load olm before the sdk if possible
import './olm-loader';
@@ -241,3 +242,133 @@ module.exports.awaitDecryption = function(event) {
});
});
};
const HttpResponse = module.exports.HttpResponse = function(
httpLookups, acceptKeepalives,
) {
this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.pendingLookup = null;
};
HttpResponse.prototype.request = function HttpResponse(
cb, method, path, qp, data, prefix,
) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
return Promise.resolve();
}
const next = this.httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
logger.log(logLine);
if (!next) { // no more things to return
if (this.pendingLookup) {
if (this.pendingLookup.method === method
&& this.pendingLookup.path === path) {
return this.pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
method + " " + path,
);
}
this.pendingLookup = {
promise: Promise.defer().promise,
method: method,
path: path,
};
return this.pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
if (next.expectBody) {
expect(next.expectBody).toEqual(data);
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
});
}
if (next.thenCall) {
process.nextTick(next.thenCall, 0); // next tick so we return first.
}
if (next.error) {
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
message: "Expected testing error",
data: next.error,
});
}
return Promise.resolve(next.data);
}
expect(true).toBe(false, "Expected different request. " + logLine);
return Promise.defer().promise;
};
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
HttpResponse.PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
HttpResponse.USER_ID = "@alice:bar";
HttpResponse.filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
HttpResponse.SYNC_DATA = {
next_batch: "s_5_3",
presence: { events: [] },
rooms: {},
};
HttpResponse.SYNC_RESPONSE = {
method: "GET",
path: "/sync",
data: HttpResponse.SYNC_DATA,
};
HttpResponse.defaultResponses = function(userId) {
return [
HttpResponse.PUSH_RULES_RESPONSE,
HttpResponse.filterResponse(userId),
HttpResponse.SYNC_RESPONSE,
];
};
module.exports.setHttpResponses = function setHttpResponses(
client, responses, acceptKeepalives,
) {
const httpResponseObj = new HttpResponse(responses, acceptKeepalives);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
client._http.authedRequest.andCall(httpReq);
client._http.authedRequestWithPrefix.andCall(httpReq);
client._http.requestWithPrefix.andCall(httpReq);
client._http.request.andCall(httpReq);
};

View File

@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,6 +24,8 @@ import olmlib from '../../../lib/crypto/olmlib';
import TestClient from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
async function makeTestClient(userInfo, options) {
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
@@ -86,12 +89,126 @@ describe("Cross Signing", function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.on("cross-signing:newKey", function(e) {
// FIXME: ???
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice.once("cross-signing:keysChanged", (e) => {
resolve(e);
});
});
alice.once("cross-signing:newKey", (e) => {
e.done(masterKey);
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes ssk, usk, device key
// client should emit event asking about ssk
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@alice:example.com",
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
},
"master_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
},
},
"self_signing_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@alice:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs"
+ "Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses);
await alice.startClient();
// once ssk is confirmed, device key should be trusted
await keyChangePromise;
expect(alice.checkUserTrust("@alice:example.com")).toBe(6);
expect(alice.checkDeviceTrust("@alice:example.com", "Osborne2")).toBe(7);
});
it("should use trust chain to determine device verification", async function() {

View File

@@ -21,14 +21,18 @@ limitations under the License.
import {pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import logger from '../logger';
function getPublicKey(keyInfo) {
return Object.entries(keyInfo.keys)[0];
}
function getPrivateKey(self, type, check) {
return new Promise((resolve, reject) => {
const askForKey = (error) => {
async function getPrivateKey(self, type, check) {
let error;
let pubkey;
let signing;
do {
[pubkey, signing] = await new Promise((resolve, reject) => {
self.emit("cross-signing:getKey", {
type: type,
error,
@@ -36,9 +40,11 @@ function getPrivateKey(self, type, check) {
// FIXME: the key needs to be interpreted?
const signing = new global.Olm.PkSigning();
const pubkey = signing.init_with_seed(key);
const error = check(pubkey, signing);
error = check(pubkey, signing);
if (error) {
return askForKey(error);
logger.error(error);
signing.free();
resolve([null, null]);
}
resolve([pubkey, signing]);
},
@@ -46,9 +52,9 @@ function getPrivateKey(self, type, check) {
reject(error || new Error("Cancelled"));
},
});
};
askForKey();
});
} while (!pubkey);
return [pubkey, signing];
}
export class CrossSigningInfo extends EventEmitter {
@@ -64,12 +70,11 @@ export class CrossSigningInfo extends EventEmitter {
// you can't change the userId
Object.defineProperty(this, 'userId', {
enumerabel: true,
enumerable: true,
value: userId,
});
this.keys = {};
this.fu = true;
// FIXME: add chain of ssks?
}
static fromStorage(obj, userId) {
@@ -85,20 +90,24 @@ export class CrossSigningInfo extends EventEmitter {
toStorage() {
return {
keys: this.keys,
verified: this.verified,
fu: this.fu,
};
}
/** Get the ID used to identify the user
*
* @param {string} type The type of key to get the ID of. One of "master",
* "self_signing", or "user_signing". Defaults to "master".
*
* @return {string} the ID
*/
getId() {
return getPublicKey(this.keys.master)[1];
getId(type) {
type = type || "master";
return this.keys[type] && getPublicKey(this.keys[type])[1];
}
async resetKeys(level) {
if (level === undefined || level & 4) {
if (level === undefined || level & 4 || !this.keys.master) {
level = CrossSigningLevel.MASTER;
} else if (level === 0) {
return;
@@ -109,6 +118,7 @@ export class CrossSigningInfo extends EventEmitter {
let masterSigning;
let masterPub;
try {
if (level & 4) {
masterSigning = new global.Olm.PkSigning();
privateKeys.master = masterSigning.generate_seed();
@@ -121,7 +131,8 @@ export class CrossSigningInfo extends EventEmitter {
},
};
} else {
[masterPub, masterSigning] = await getPrivateKey(this, "master", (pubkey) => {
[masterPub, masterSigning] = await getPrivateKey(
this, "master", (pubkey) => {
// make sure it agrees with the pubkey that we have
if (pubkey !== getPublicKey(this.keys.master)[1]) {
return "Key does not match";
@@ -132,6 +143,7 @@ export class CrossSigningInfo extends EventEmitter {
if (level & CrossSigningLevel.SELF_SIGNING) {
const sskSigning = new global.Olm.PkSigning();
try {
privateKeys.self_signing = sskSigning.generate_seed();
const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
keys.self_signing = {
@@ -142,10 +154,14 @@ export class CrossSigningInfo extends EventEmitter {
},
};
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
} finally {
sskSigning.free();
}
}
if (level & CrossSigningLevel.USER_SIGNING) {
const uskSigning = new global.Olm.PkSigning();
try {
privateKeys.user_signing = uskSigning.generate_seed();
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
keys.user_signing = {
@@ -156,40 +172,66 @@ export class CrossSigningInfo extends EventEmitter {
},
};
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
} finally {
uskSigning.free();
}
}
Object.assign(this.keys, keys);
this.emit("cross-signing:savePrivateKeys", privateKeys);
} finally {
if (masterSigning) {
masterSigning.free();
}
}
}
setKeys(keys) {
const signingKeys = {};
if (keys.master) {
if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in master key from " + this.userId;
logger.error(error);
throw new Error(error);
}
// First-Use is true if and only if we had no previous key for the user
this.fu = !(this.keys.self_signing);
signingKeys.master = keys.master;
if (!keys.user_signing || !keys.self_signing) {
throw new Error("Must have new self-signing and user-signing"
+ "keys when new master key is set");
}
} else {
} else if (this.keys.master) {
signingKeys.master = this.keys.master;
} else {
throw new Error("Tried to set cross-signing keys without a master key");
}
const masterKey = getPublicKey(signingKeys.master)[1];
// verify signatures
if (keys.user_signing) {
if (keys.user_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in user_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.user_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on user-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
if (keys.self_signing) {
if (keys.self_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in self_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.self_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on self-signing key");
// FIXME: what do we want to do here?
throw e;
}
@@ -198,6 +240,10 @@ export class CrossSigningInfo extends EventEmitter {
// if everything checks out, then save the keys
if (keys.master) {
this.keys.master = keys.master;
// if the master key is set, then the old self-signing and
// user-signing keys are obsolete
delete this.keys.self_signing;
delete this.keys.user_signing;
}
if (keys.self_signing) {
this.keys.self_signing = keys.self_signing;
@@ -211,9 +257,13 @@ export class CrossSigningInfo extends EventEmitter {
const [pubkey, usk] = await getPrivateKey(this, "user_signing", (key) => {
return;
});
try {
const otherMaster = key.keys.master;
pkSign(otherMaster, usk, this.userId, pubkey);
return otherMaster;
} finally {
usk.free();
}
}
async signDevice(userId, device) {
@@ -223,6 +273,7 @@ export class CrossSigningInfo extends EventEmitter {
const [pubkey, ssk] = await getPrivateKey(this, "self_signing", (key) => {
return;
});
try {
const keyObj = {
algorithms: device.algorithms,
keys: device.keys,
@@ -231,9 +282,25 @@ export class CrossSigningInfo extends EventEmitter {
};
pkSign(keyObj, ssk, this.userId, pubkey);
return keyObj;
} finally {
ssk.free();
}
}
checkUserTrust(userCrossSigning) {
if (this.userId === userCrossSigning.userId
&& this.getId() && this.getId() === userCrossSigning.getId()
&& this.getId("self_signing")
&& this.getId("self_signing") === userCrossSigning.getId("self_signing")) {
return CrossSigningVerification.VERIFIED
| (this.fu ? CrossSigningVerification.TOFU
: CrossSigningVerification.UNVERIFIED);
}
if (!this.keys.user_signing) {
return 0;
}
let userTrusted;
const userMaster = userCrossSigning.keys.master;
const uskId = getPublicKey(this.keys.user_signing)[1];
@@ -253,6 +320,9 @@ export class CrossSigningInfo extends EventEmitter {
const userTrust = this.checkUserTrust(userCrossSigning);
const userSSK = userCrossSigning.keys.self_signing;
if (!userSSK) {
return 0;
}
const deviceObj = deviceToObject(device, userCrossSigning.userId);
try {
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,7 +28,7 @@ import {EventEmitter} from 'events';
import logger from '../logger';
import DeviceInfo from './deviceinfo';
import {CrossSigningInfo, CrossSigningVerification} from './CrossSigning';
import {CrossSigningInfo} from './CrossSigning';
import olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
@@ -754,7 +755,9 @@ class DeviceListUpdateSerialiser {
downloadUsers, opts,
).then((res) => {
const dk = res.device_keys || {};
const master_keys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
// do each user in a separate promise, to avoid wedging the CPU
// (https://github.com/vector-im/riot-web/issues/3158)
@@ -765,7 +768,11 @@ class DeviceListUpdateSerialiser {
for (const userId of downloadUsers) {
prom = prom.delay(5).then(() => {
return this._processQueryResponseForUser(
userId, dk[userId], ssks[userId],
userId, dk[userId], {
master: master_keys[userId],
self_signing: ssks[userId],
user_signing: usks[userId],
},
);
});
}
@@ -790,9 +797,11 @@ class DeviceListUpdateSerialiser {
return deferred.promise;
}
async _processQueryResponseForUser(userId, dkResponse, sskResponse) {
async _processQueryResponseForUser(
userId, dkResponse, crossSigningResponse, sskResponse,
) {
logger.log('got device keys for ' + userId + ':', dkResponse);
logger.log('got self-signing keys for ' + userId + ':', sskResponse);
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
{
// map from deviceid -> deviceinfo for this user
@@ -818,19 +827,23 @@ class DeviceListUpdateSerialiser {
this._deviceList._setRawStoredDevicesForUser(userId, storage);
}
// now do the same for the self-signing key
// now do the same for the cross-signing keys
{
const ssk = this._deviceList.getRawStoredSskForUser(userId) || {};
if (crossSigningResponse && Object.keys(crossSigningResponse).length) {
const crossSigning
= this._deviceList.getStoredCrossSigningForUser(userId)
|| new CrossSigningInfo(userId);
const updated = await _updateStoredSelfSigningKeyForUser(
this._olmDevice, userId, ssk, sskResponse || {},
crossSigning.setKeys(crossSigningResponse);
this._deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
this._deviceList.setRawStoredSskForUser(userId, ssk);
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted
if (updated) this._deviceList.emit('userSskUpdated', userId);
this._deviceList.emit('userCrossSigningUpdated', userId);
}
}
}
}
@@ -883,55 +896,6 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
return updated;
}
async function _updateStoredSelfSigningKeyForUser(
_olmDevice, userId, userStore, userResult,
) {
// FIXME: this function may need modifying
let updated = false;
if (userResult.user_id !== userId) {
logger.warn("Mismatched user_id " + userResult.user_id +
" in self-signing key from " + userId);
return;
}
if (!userResult || !userResult.usage.includes('self_signing')) {
logger.warn(
"Self-signing key for " + userId +
" does not include 'self_signing' usage: ignoring",
);
return;
}
const keyCount = Object.keys(userResult.keys).length;
if (keyCount !== 1) {
logger.warn(
"Self-signing key block for " + userId + " has " +
keyCount + " keys: expected exactly 1. Ignoring.",
);
return;
}
let oldKeyId = null;
let oldKey = null;
if (userStore.keys && Object.keys(userStore.keys).length > 0) {
oldKeyId = Object.keys(userStore.keys)[0];
oldKey = userStore.keys[oldKeyId];
}
const newKeyId = Object.keys(userResult.keys)[0];
const newKey = userResult.keys[newKeyId];
if (oldKeyId !== newKeyId || oldKey !== newKey) {
updated = true;
logger.info(
"New self-signing key detected for " + userId +
": " + newKeyId + ", was previously " + oldKeyId,
);
userStore.user_id = userResult.user_id;
userStore.usage = userResult.usage;
userStore.keys = userResult.keys;
}
return updated;
}
/*
* Process a device in a /query response, and add it to the userStore
*
@@ -955,6 +919,7 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
}
const unsigned = deviceResult.unsigned || {};
const signatures = deviceResult.signatures || {};
try {
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
@@ -987,5 +952,6 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
deviceStore.keys = deviceResult.keys || {};
deviceStore.algorithms = deviceResult.algorithms || [];
deviceStore.unsigned = unsigned;
deviceStore.signatures = signatures;
return true;
}

View File

@@ -56,6 +56,7 @@ function DeviceInfo(deviceId) {
this.verified = DeviceVerification.UNVERIFIED;
this.known = false;
this.unsigned = {};
this.signatures = {}
}
/**
@@ -88,6 +89,7 @@ DeviceInfo.prototype.toStorage = function() {
verified: this.verified,
known: this.known,
unsigned: this.unsigned,
signatures: this.signatures,
};
};

View File

@@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -31,11 +32,10 @@ const OlmDevice = require("./OlmDevice");
const olmlib = require("./olmlib");
const algorithms = require("./algorithms");
const DeviceInfo = require("./deviceinfo");
import SskInfo from './sskinfo';
const DeviceVerification = DeviceInfo.DeviceVerification;
const DeviceList = require('./DeviceList').default;
import { randomString } from '../randomstring';
import { CrossSigningInfo, CrossSigningLevel, CrossSigningVerification } from './CrossSigning';
import { CrossSigningInfo } from './CrossSigning';
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
@@ -104,7 +104,7 @@ const KEY_BACKUP_KEYS_PER_REQUEST = 200;
*/
export default function Crypto(baseApis, sessionStore, userId, deviceId,
clientStore, cryptoStore, roomList, verificationMethods) {
this._onDeviceListUserSskUpdated = this._onDeviceListUserSskUpdated.bind(this);
this._onDeviceListUserCrossSigningUpdated = this._onDeviceListUserCrossSigningUpdated.bind(this);
this._baseApis = baseApis;
this._sessionStore = sessionStore;
@@ -146,7 +146,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
);
// XXX: This isn't removed at any point, but then none of the event listeners
// this class sets seem to be removed at any point... :/
this._deviceList.on('userSskUpdated', this._onDeviceListUserSskUpdated);
this._deviceList.on('userCrossSigningUpdated', this._onDeviceListUserCrossSigningUpdated);
// the last time we did a check for the number of one-time-keys on the
// server.
@@ -269,6 +269,7 @@ Crypto.prototype.init = async function() {
*/
Crypto.prototype.resetCrossSigningKeys = async function(level) {
await this._crossSigningInfo.resetKeys(level);
this._baseApis.emit("cross-signing:keysChanged", {});
};
/**
@@ -278,6 +279,7 @@ Crypto.prototype.resetCrossSigningKeys = async function(level) {
*/
Crypto.prototype.setCrossSigningKeys = function(keys) {
this._crossSigningInfo.setKeys(keys);
this._baseApis.emit("cross-signing:keysChanged", {});
};
/**
@@ -331,34 +333,93 @@ Crypto.prototype.checkDeviceTrust = function(userId, deviceId) {
/*
* Event handler for DeviceList's userNewDevices event
*/
Crypto.prototype._onDeviceListUserSskUpdated = async function(userId) {
Crypto.prototype._onDeviceListUserCrossSigningUpdated = async function(userId) {
if (userId === this._userId) {
this.checkOwnSskTrust();
this.checkOwnCrossSigningTrust();
}
};
/*
* 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.
* Check the copy of our cross-signing key that we have in the device list and
* see if we can get the private key. If so, mark it as trusted.
*/
Crypto.prototype.checkOwnSskTrust = async function() {
Crypto.prototype.checkOwnCrossSigningTrust = async function() {
const 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
// If we see an update to our own master key, check it against the master
// key 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) {
// First, get the new cross-signing info
const newCrossSigning = this._deviceList.getStoredCrossSigningForUser(userId);
if (!newCrossSigning) {
logger.error(
"Got SSK update event for user " + userId +
" but no new SSK found!",
"Got cross-signing update event for user " + userId +
" but no new cross-signing information found!",
);
return;
}
const seenPubkey = seenSsk.getFingerprint();
const seenPubkey = newCrossSigning.getId();
const changed = this._crossSigningInfo.getId() !== seenPubkey;
let privkey;
if (changed) {
// try to get the private key if the master key changed
logger.info("Got new master key", seenPubkey);
let error;
do {
privkey = await new Promise((resolve, reject) => {
this._baseApis.emit("cross-signing:newKey", {
publicKey: seenPubkey,
type: "master",
error,
done: (key) => {
// check key matches
const signing = new global.Olm.PkSigning();
try {
const pubkey = signing.init_with_seed(key);
if (pubkey !== seenPubkey) {
error = "Key does not match";
logger.info("Key does not match: got " + pubkey
+ " expected " + seenPubkey);
return;
}
} finally {
signing.free();
}
resolve(key);
},
cancel: (error) => {
reject(error || new Error("Cancelled by user"));
},
});
});
} while (!privkey);
this._baseApis.emit("cross-signing:savePrivateKeys", {master: privkey});
logger.info("Got private key");
}
// FIXME: fetch the private key?
if (this._crossSigningInfo.getId("self_signing")
!== newCrossSigning.getId("self_signing")) {
logger.info("Got new self-signing key", newCrossSigning.getId("self_signing"));
}
if (this._crossSigningInfo.getId("user_signing")
!== newCrossSigning.getId("user_signing")) {
logger.info("Got new user-signing key", newCrossSigning.getId("user_signing"));
}
this._crossSigningInfo.setKeys(newCrossSigning.keys);
// FIXME: save it ... somewhere?
if (changed) {
this._baseApis.emit("cross-signing:keysChanged", {});
}
// FIXME:
// 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],
@@ -401,6 +462,7 @@ Crypto.prototype.checkOwnSskTrust = async function() {
localPubkey + ", published: " + seenPubkey,
);
}
*/
};
/**
@@ -986,7 +1048,6 @@ Crypto.prototype.setDeviceVerification = async function(
const xsk = this._deviceList.getStoredCrossSigningForUser(userId);
if (xsk.getId() === deviceId) {
if (verified) {
xsk.verified = CrossSigningVerification.VERIFIED;
const device = await this._crossSigningInfo.signUser(xsk);
// FIXME: mark xsk as dirty in device list
this._baseApis.uploadKeySignatures({