You've already forked matrix-js-sdk
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:
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
const logger = require("../logger");
|
||||||
|
|
||||||
// load olm before the sdk if possible
|
// load olm before the sdk if possible
|
||||||
import './olm-loader';
|
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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 TestClient from '../../TestClient';
|
||||||
|
|
||||||
|
import {HttpResponse, setHttpResponses} from '../../test-utils';
|
||||||
|
|
||||||
async function makeTestClient(userInfo, options) {
|
async function makeTestClient(userInfo, options) {
|
||||||
const client = (new TestClient(
|
const client = (new TestClient(
|
||||||
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
||||||
@@ -86,12 +89,126 @@ describe("Cross Signing", function() {
|
|||||||
const alice = await makeTestClient(
|
const alice = await makeTestClient(
|
||||||
{userId: "@alice:example.com", deviceId: "Osborne2"},
|
{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
|
// 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
|
// 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() {
|
it("should use trust chain to determine device verification", async function() {
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ limitations under the License.
|
|||||||
|
|
||||||
import {pkSign, pkVerify} from './olmlib';
|
import {pkSign, pkVerify} from './olmlib';
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
function getPublicKey(keyInfo) {
|
function getPublicKey(keyInfo) {
|
||||||
return Object.entries(keyInfo.keys)[0];
|
return Object.entries(keyInfo.keys)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrivateKey(self, type, check) {
|
async function getPrivateKey(self, type, check) {
|
||||||
return new Promise((resolve, reject) => {
|
let error;
|
||||||
const askForKey = (error) => {
|
let pubkey;
|
||||||
|
let signing;
|
||||||
|
do {
|
||||||
|
[pubkey, signing] = await new Promise((resolve, reject) => {
|
||||||
self.emit("cross-signing:getKey", {
|
self.emit("cross-signing:getKey", {
|
||||||
type: type,
|
type: type,
|
||||||
error,
|
error,
|
||||||
@@ -36,9 +40,11 @@ function getPrivateKey(self, type, check) {
|
|||||||
// FIXME: the key needs to be interpreted?
|
// FIXME: the key needs to be interpreted?
|
||||||
const signing = new global.Olm.PkSigning();
|
const signing = new global.Olm.PkSigning();
|
||||||
const pubkey = signing.init_with_seed(key);
|
const pubkey = signing.init_with_seed(key);
|
||||||
const error = check(pubkey, signing);
|
error = check(pubkey, signing);
|
||||||
if (error) {
|
if (error) {
|
||||||
return askForKey(error);
|
logger.error(error);
|
||||||
|
signing.free();
|
||||||
|
resolve([null, null]);
|
||||||
}
|
}
|
||||||
resolve([pubkey, signing]);
|
resolve([pubkey, signing]);
|
||||||
},
|
},
|
||||||
@@ -46,9 +52,9 @@ function getPrivateKey(self, type, check) {
|
|||||||
reject(error || new Error("Cancelled"));
|
reject(error || new Error("Cancelled"));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
askForKey();
|
} while (!pubkey);
|
||||||
});
|
return [pubkey, signing];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CrossSigningInfo extends EventEmitter {
|
export class CrossSigningInfo extends EventEmitter {
|
||||||
@@ -64,12 +70,11 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
|
|
||||||
// you can't change the userId
|
// you can't change the userId
|
||||||
Object.defineProperty(this, 'userId', {
|
Object.defineProperty(this, 'userId', {
|
||||||
enumerabel: true,
|
enumerable: true,
|
||||||
value: userId,
|
value: userId,
|
||||||
});
|
});
|
||||||
this.keys = {};
|
this.keys = {};
|
||||||
this.fu = true;
|
this.fu = true;
|
||||||
// FIXME: add chain of ssks?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromStorage(obj, userId) {
|
static fromStorage(obj, userId) {
|
||||||
@@ -85,20 +90,24 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
toStorage() {
|
toStorage() {
|
||||||
return {
|
return {
|
||||||
keys: this.keys,
|
keys: this.keys,
|
||||||
verified: this.verified,
|
fu: this.fu,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the ID used to identify the user
|
/** 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
|
* @return {string} the ID
|
||||||
*/
|
*/
|
||||||
getId() {
|
getId(type) {
|
||||||
return getPublicKey(this.keys.master)[1];
|
type = type || "master";
|
||||||
|
return this.keys[type] && getPublicKey(this.keys[type])[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetKeys(level) {
|
async resetKeys(level) {
|
||||||
if (level === undefined || level & 4) {
|
if (level === undefined || level & 4 || !this.keys.master) {
|
||||||
level = CrossSigningLevel.MASTER;
|
level = CrossSigningLevel.MASTER;
|
||||||
} else if (level === 0) {
|
} else if (level === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -109,87 +118,120 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
let masterSigning;
|
let masterSigning;
|
||||||
let masterPub;
|
let masterPub;
|
||||||
|
|
||||||
if (level & 4) {
|
try {
|
||||||
masterSigning = new global.Olm.PkSigning();
|
if (level & 4) {
|
||||||
privateKeys.master = masterSigning.generate_seed();
|
masterSigning = new global.Olm.PkSigning();
|
||||||
masterPub = masterSigning.init_with_seed(privateKeys.master);
|
privateKeys.master = masterSigning.generate_seed();
|
||||||
keys.master = {
|
masterPub = masterSigning.init_with_seed(privateKeys.master);
|
||||||
user_id: this.userId,
|
keys.master = {
|
||||||
usage: ['master'],
|
user_id: this.userId,
|
||||||
keys: {
|
usage: ['master'],
|
||||||
['ed25519:' + masterPub]: masterPub,
|
keys: {
|
||||||
},
|
['ed25519:' + masterPub]: masterPub,
|
||||||
};
|
},
|
||||||
} else {
|
};
|
||||||
[masterPub, masterSigning] = await getPrivateKey(this, "master", (pubkey) => {
|
} else {
|
||||||
// make sure it agrees with the pubkey that we have
|
[masterPub, masterSigning] = await getPrivateKey(
|
||||||
if (pubkey !== getPublicKey(this.keys.master)[1]) {
|
this, "master", (pubkey) => {
|
||||||
return "Key does not match";
|
// make sure it agrees with the pubkey that we have
|
||||||
|
if (pubkey !== getPublicKey(this.keys.master)[1]) {
|
||||||
|
return "Key does not match";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
user_id: this.userId,
|
||||||
|
usage: ['self_signing'],
|
||||||
|
keys: {
|
||||||
|
['ed25519:' + sskPub]: sskPub,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
|
||||||
|
} finally {
|
||||||
|
sskSigning.free();
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (level & CrossSigningLevel.SELF_SIGNING) {
|
if (level & CrossSigningLevel.USER_SIGNING) {
|
||||||
const sskSigning = new global.Olm.PkSigning();
|
const uskSigning = new global.Olm.PkSigning();
|
||||||
privateKeys.self_signing = sskSigning.generate_seed();
|
try {
|
||||||
const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
|
privateKeys.user_signing = uskSigning.generate_seed();
|
||||||
keys.self_signing = {
|
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
|
||||||
user_id: this.userId,
|
keys.user_signing = {
|
||||||
usage: ['self_signing'],
|
user_id: this.userId,
|
||||||
keys: {
|
usage: ['user_signing'],
|
||||||
['ed25519:' + sskPub]: sskPub,
|
keys: {
|
||||||
},
|
['ed25519:' + uskPub]: uskPub,
|
||||||
};
|
},
|
||||||
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
|
};
|
||||||
}
|
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
|
||||||
|
} finally {
|
||||||
|
uskSigning.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (level & CrossSigningLevel.USER_SIGNING) {
|
Object.assign(this.keys, keys);
|
||||||
const uskSigning = new global.Olm.PkSigning();
|
this.emit("cross-signing:savePrivateKeys", privateKeys);
|
||||||
privateKeys.user_signing = uskSigning.generate_seed();
|
} finally {
|
||||||
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
|
if (masterSigning) {
|
||||||
keys.user_signing = {
|
masterSigning.free();
|
||||||
user_id: this.userId,
|
}
|
||||||
usage: ['user_signing'],
|
|
||||||
keys: {
|
|
||||||
['ed25519:' + uskPub]: uskPub,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.keys, keys);
|
|
||||||
this.emit("cross-signing:savePrivateKeys", privateKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeys(keys) {
|
setKeys(keys) {
|
||||||
const signingKeys = {};
|
const signingKeys = {};
|
||||||
if (keys.master) {
|
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
|
// First-Use is true if and only if we had no previous key for the user
|
||||||
this.fu = !(this.keys.self_signing);
|
this.fu = !(this.keys.self_signing);
|
||||||
signingKeys.master = keys.master;
|
signingKeys.master = keys.master;
|
||||||
if (!keys.user_signing || !keys.self_signing) {
|
} else if (this.keys.master) {
|
||||||
throw new Error("Must have new self-signing and user-signing"
|
|
||||||
+ "keys when new master key is set");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
signingKeys.master = 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];
|
const masterKey = getPublicKey(signingKeys.master)[1];
|
||||||
|
|
||||||
// verify signatures
|
// verify signatures
|
||||||
if (keys.user_signing) {
|
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 {
|
try {
|
||||||
pkVerify(keys.user_signing, masterKey, this.userId);
|
pkVerify(keys.user_signing, masterKey, this.userId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error("invalid signature on user-signing key");
|
||||||
// FIXME: what do we want to do here?
|
// FIXME: what do we want to do here?
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.self_signing) {
|
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 {
|
try {
|
||||||
pkVerify(keys.self_signing, masterKey, this.userId);
|
pkVerify(keys.self_signing, masterKey, this.userId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error("invalid signature on self-signing key");
|
||||||
// FIXME: what do we want to do here?
|
// FIXME: what do we want to do here?
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -198,6 +240,10 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
// if everything checks out, then save the keys
|
// if everything checks out, then save the keys
|
||||||
if (keys.master) {
|
if (keys.master) {
|
||||||
this.keys.master = 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) {
|
if (keys.self_signing) {
|
||||||
this.keys.self_signing = 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) => {
|
const [pubkey, usk] = await getPrivateKey(this, "user_signing", (key) => {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
const otherMaster = key.keys.master;
|
try {
|
||||||
pkSign(otherMaster, usk, this.userId, pubkey);
|
const otherMaster = key.keys.master;
|
||||||
return otherMaster;
|
pkSign(otherMaster, usk, this.userId, pubkey);
|
||||||
|
return otherMaster;
|
||||||
|
} finally {
|
||||||
|
usk.free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signDevice(userId, device) {
|
async signDevice(userId, device) {
|
||||||
@@ -223,17 +273,34 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
const [pubkey, ssk] = await getPrivateKey(this, "self_signing", (key) => {
|
const [pubkey, ssk] = await getPrivateKey(this, "self_signing", (key) => {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
const keyObj = {
|
try {
|
||||||
algorithms: device.algorithms,
|
const keyObj = {
|
||||||
keys: device.keys,
|
algorithms: device.algorithms,
|
||||||
device_id: device.deviceId,
|
keys: device.keys,
|
||||||
user_id: userId,
|
device_id: device.deviceId,
|
||||||
};
|
user_id: userId,
|
||||||
pkSign(keyObj, ssk, this.userId, pubkey);
|
};
|
||||||
return keyObj;
|
pkSign(keyObj, ssk, this.userId, pubkey);
|
||||||
|
return keyObj;
|
||||||
|
} finally {
|
||||||
|
ssk.free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserTrust(userCrossSigning) {
|
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;
|
let userTrusted;
|
||||||
const userMaster = userCrossSigning.keys.master;
|
const userMaster = userCrossSigning.keys.master;
|
||||||
const uskId = getPublicKey(this.keys.user_signing)[1];
|
const uskId = getPublicKey(this.keys.user_signing)[1];
|
||||||
@@ -253,6 +320,9 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
const userTrust = this.checkUserTrust(userCrossSigning);
|
const userTrust = this.checkUserTrust(userCrossSigning);
|
||||||
|
|
||||||
const userSSK = userCrossSigning.keys.self_signing;
|
const userSSK = userCrossSigning.keys.self_signing;
|
||||||
|
if (!userSSK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
const deviceObj = deviceToObject(device, userCrossSigning.userId);
|
const deviceObj = deviceToObject(device, userCrossSigning.userId);
|
||||||
try {
|
try {
|
||||||
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2018, 2019 New Vector 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 logger from '../logger';
|
||||||
import DeviceInfo from './deviceinfo';
|
import DeviceInfo from './deviceinfo';
|
||||||
import {CrossSigningInfo, CrossSigningVerification} from './CrossSigning';
|
import {CrossSigningInfo} from './CrossSigning';
|
||||||
import olmlib from './olmlib';
|
import olmlib from './olmlib';
|
||||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
|
|
||||||
@@ -754,7 +755,9 @@ class DeviceListUpdateSerialiser {
|
|||||||
downloadUsers, opts,
|
downloadUsers, opts,
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
const dk = res.device_keys || {};
|
const dk = res.device_keys || {};
|
||||||
|
const master_keys = res.master_keys || {};
|
||||||
const ssks = res.self_signing_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
|
// do each user in a separate promise, to avoid wedging the CPU
|
||||||
// (https://github.com/vector-im/riot-web/issues/3158)
|
// (https://github.com/vector-im/riot-web/issues/3158)
|
||||||
@@ -765,7 +768,11 @@ class DeviceListUpdateSerialiser {
|
|||||||
for (const userId of downloadUsers) {
|
for (const userId of downloadUsers) {
|
||||||
prom = prom.delay(5).then(() => {
|
prom = prom.delay(5).then(() => {
|
||||||
return this._processQueryResponseForUser(
|
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;
|
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 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
|
// map from deviceid -> deviceinfo for this user
|
||||||
@@ -818,19 +827,23 @@ class DeviceListUpdateSerialiser {
|
|||||||
this._deviceList._setRawStoredDevicesForUser(userId, storage);
|
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(
|
crossSigning.setKeys(crossSigningResponse);
|
||||||
this._olmDevice, userId, ssk, sskResponse || {},
|
|
||||||
);
|
|
||||||
|
|
||||||
this._deviceList.setRawStoredSskForUser(userId, ssk);
|
this._deviceList.setRawStoredCrossSigningForUser(
|
||||||
|
userId, crossSigning.toStorage(),
|
||||||
|
);
|
||||||
|
|
||||||
// NB. Unlike most events in the js-sdk, this one is internal to the
|
// NB. Unlike most events in the js-sdk, this one is internal to the
|
||||||
// js-sdk and is not re-emitted
|
// 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;
|
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
|
* 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 unsigned = deviceResult.unsigned || {};
|
||||||
|
const signatures = deviceResult.signatures || {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
|
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
|
||||||
@@ -987,5 +952,6 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
|
|||||||
deviceStore.keys = deviceResult.keys || {};
|
deviceStore.keys = deviceResult.keys || {};
|
||||||
deviceStore.algorithms = deviceResult.algorithms || [];
|
deviceStore.algorithms = deviceResult.algorithms || [];
|
||||||
deviceStore.unsigned = unsigned;
|
deviceStore.unsigned = unsigned;
|
||||||
|
deviceStore.signatures = signatures;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ function DeviceInfo(deviceId) {
|
|||||||
this.verified = DeviceVerification.UNVERIFIED;
|
this.verified = DeviceVerification.UNVERIFIED;
|
||||||
this.known = false;
|
this.known = false;
|
||||||
this.unsigned = {};
|
this.unsigned = {};
|
||||||
|
this.signatures = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +89,7 @@ DeviceInfo.prototype.toStorage = function() {
|
|||||||
verified: this.verified,
|
verified: this.verified,
|
||||||
known: this.known,
|
known: this.known,
|
||||||
unsigned: this.unsigned,
|
unsigned: this.unsigned,
|
||||||
|
signatures: this.signatures,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2018-2019 New Vector 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 olmlib = require("./olmlib");
|
||||||
const algorithms = require("./algorithms");
|
const algorithms = require("./algorithms");
|
||||||
const DeviceInfo = require("./deviceinfo");
|
const DeviceInfo = require("./deviceinfo");
|
||||||
import SskInfo from './sskinfo';
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
const DeviceList = require('./DeviceList').default;
|
const DeviceList = require('./DeviceList').default;
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
import { CrossSigningInfo, CrossSigningLevel, CrossSigningVerification } from './CrossSigning';
|
import { CrossSigningInfo } from './CrossSigning';
|
||||||
|
|
||||||
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
|
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
|
||||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
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,
|
export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||||
clientStore, cryptoStore, roomList, verificationMethods) {
|
clientStore, cryptoStore, roomList, verificationMethods) {
|
||||||
this._onDeviceListUserSskUpdated = this._onDeviceListUserSskUpdated.bind(this);
|
this._onDeviceListUserCrossSigningUpdated = this._onDeviceListUserCrossSigningUpdated.bind(this);
|
||||||
|
|
||||||
this._baseApis = baseApis;
|
this._baseApis = baseApis;
|
||||||
this._sessionStore = sessionStore;
|
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
|
// 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 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
|
// the last time we did a check for the number of one-time-keys on the
|
||||||
// server.
|
// server.
|
||||||
@@ -269,6 +269,7 @@ Crypto.prototype.init = async function() {
|
|||||||
*/
|
*/
|
||||||
Crypto.prototype.resetCrossSigningKeys = async function(level) {
|
Crypto.prototype.resetCrossSigningKeys = async function(level) {
|
||||||
await this._crossSigningInfo.resetKeys(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) {
|
Crypto.prototype.setCrossSigningKeys = function(keys) {
|
||||||
this._crossSigningInfo.setKeys(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
|
* Event handler for DeviceList's userNewDevices event
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._onDeviceListUserSskUpdated = async function(userId) {
|
Crypto.prototype._onDeviceListUserCrossSigningUpdated = async function(userId) {
|
||||||
if (userId === this._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
|
* Check the copy of our cross-signing key that we have in the device list and
|
||||||
* matches our private part. If it does, mark it as trusted.
|
* 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;
|
const userId = this._userId;
|
||||||
|
|
||||||
// If we see an update to our own SSK, check it against the SSK we have and,
|
// If we see an update to our own master key, check it against the master
|
||||||
// if it matches, mark it as verified
|
// key we have and, if it matches, mark it as verified
|
||||||
|
|
||||||
// First, get the pubkey of the one we can see
|
// First, get the new cross-signing info
|
||||||
const seenSsk = this._deviceList.getStoredSskForUser(userId);
|
const newCrossSigning = this._deviceList.getStoredCrossSigningForUser(userId);
|
||||||
if (!seenSsk) {
|
if (!newCrossSigning) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Got SSK update event for user " + userId +
|
"Got cross-signing update event for user " + userId +
|
||||||
" but no new SSK found!",
|
" but no new cross-signing information found!",
|
||||||
);
|
);
|
||||||
return;
|
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
|
// Now dig out the account keys and get the pubkey of the one in there
|
||||||
|
/*
|
||||||
let accountKeys = null;
|
let accountKeys = null;
|
||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
@@ -401,6 +462,7 @@ Crypto.prototype.checkOwnSskTrust = async function() {
|
|||||||
localPubkey + ", published: " + seenPubkey,
|
localPubkey + ", published: " + seenPubkey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -986,7 +1048,6 @@ Crypto.prototype.setDeviceVerification = async function(
|
|||||||
const xsk = this._deviceList.getStoredCrossSigningForUser(userId);
|
const xsk = this._deviceList.getStoredCrossSigningForUser(userId);
|
||||||
if (xsk.getId() === deviceId) {
|
if (xsk.getId() === deviceId) {
|
||||||
if (verified) {
|
if (verified) {
|
||||||
xsk.verified = CrossSigningVerification.VERIFIED;
|
|
||||||
const device = await this._crossSigningInfo.signUser(xsk);
|
const device = await this._crossSigningInfo.signUser(xsk);
|
||||||
// FIXME: mark xsk as dirty in device list
|
// FIXME: mark xsk as dirty in device list
|
||||||
this._baseApis.uploadKeySignatures({
|
this._baseApis.uploadKeySignatures({
|
||||||
|
|||||||
Reference in New Issue
Block a user