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
1021 lines
39 KiB
JavaScript
1021 lines
39 KiB
JavaScript
/*
|
|
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.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
import '../../olm-loader';
|
|
import anotherjson from 'another-json';
|
|
|
|
import * as olmlib from "../../../src/crypto/olmlib";
|
|
import { TestClient } from '../../TestClient';
|
|
import { resetCrossSigningKeys } from "./crypto-utils";
|
|
import { MatrixError } from '../../../src/http-api';
|
|
import { logger } from '../../../src/logger';
|
|
|
|
const PUSH_RULES_RESPONSE = {
|
|
method: "GET",
|
|
path: "/pushrules/",
|
|
data: {},
|
|
};
|
|
|
|
const filterResponse = function(userId) {
|
|
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
|
|
return {
|
|
method: "POST",
|
|
path: filterPath,
|
|
data: { filter_id: "f1lt3r" },
|
|
};
|
|
};
|
|
|
|
function setHttpResponses(httpBackend, responses) {
|
|
responses.forEach(response => {
|
|
httpBackend
|
|
.when(response.method, response.path)
|
|
.respond(200, response.data);
|
|
});
|
|
}
|
|
|
|
async function makeTestClient(userInfo, options, keys) {
|
|
if (!keys) keys = {};
|
|
|
|
function getCrossSigningKey(type) {
|
|
return keys[type];
|
|
}
|
|
|
|
function saveCrossSigningKeys(k) {
|
|
Object.assign(keys, k);
|
|
}
|
|
|
|
if (!options) options = {};
|
|
options.cryptoCallbacks = Object.assign(
|
|
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
|
|
);
|
|
const testClient = new TestClient(
|
|
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
|
|
);
|
|
const client = testClient.client;
|
|
|
|
await client.initCrypto();
|
|
|
|
return { client, httpBackend: testClient.httpBackend };
|
|
}
|
|
|
|
describe("Cross Signing", function() {
|
|
if (!global.Olm) {
|
|
logger.warn('Not running megolm backup unit tests: libolm not present');
|
|
return;
|
|
}
|
|
|
|
beforeAll(function() {
|
|
return global.Olm.init();
|
|
});
|
|
|
|
it("should sign the master key with the device key", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
|
|
await olmlib.verifySignature(
|
|
alice.crypto.olmDevice, keys.master_key, "@alice:example.com",
|
|
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
|
);
|
|
});
|
|
alice.uploadKeySignatures = async () => {};
|
|
alice.setAccountData = async () => {};
|
|
alice.getAccountDataFromServer = async () => {};
|
|
// set Alice's cross-signing key
|
|
await alice.bootstrapCrossSigning({
|
|
authUploadDeviceSigningKeys: async func => await func({}),
|
|
});
|
|
expect(alice.uploadDeviceSigningKeys).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should abort bootstrap if device signing auth fails", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async (auth, keys) => {
|
|
const errorResponse = {
|
|
session: "sessionId",
|
|
flows: [
|
|
{
|
|
stages: [
|
|
"m.login.password",
|
|
],
|
|
},
|
|
],
|
|
params: {},
|
|
};
|
|
|
|
// If we're not just polling for flows, add on error rejecting the
|
|
// auth attempt.
|
|
if (auth) {
|
|
Object.assign(errorResponse, {
|
|
completed: [],
|
|
error: "Invalid password",
|
|
errcode: "M_FORBIDDEN",
|
|
});
|
|
}
|
|
|
|
const error = new MatrixError(errorResponse);
|
|
error.httpStatus == 401;
|
|
throw error;
|
|
};
|
|
alice.uploadKeySignatures = async () => {};
|
|
alice.setAccountData = async () => {};
|
|
alice.getAccountDataFromServer = async () => { };
|
|
const authUploadDeviceSigningKeys = async func => await func({});
|
|
|
|
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
|
|
// through failure, stopping before actually applying changes.
|
|
let bootstrapDidThrow = false;
|
|
try {
|
|
await alice.bootstrapCrossSigning({
|
|
authUploadDeviceSigningKeys,
|
|
});
|
|
} catch (e) {
|
|
if (e.errcode === "M_FORBIDDEN") {
|
|
bootstrapDidThrow = true;
|
|
}
|
|
}
|
|
expect(bootstrapDidThrow).toBeTruthy();
|
|
});
|
|
|
|
it("should upload a signature when a user is verified", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
// set Alice's cross-signing key
|
|
await resetCrossSigningKeys(alice);
|
|
// Alice downloads Bob's device key
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
"ed25519:bobs+master+pubkey": "bobs+master+pubkey",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
// Alice verifies Bob's key
|
|
const promise = new Promise((resolve, reject) => {
|
|
alice.uploadKeySignatures = (...args) => {
|
|
resolve(...args);
|
|
};
|
|
});
|
|
await alice.setDeviceVerified("@bob:example.com", "bobs+master+pubkey", true);
|
|
// Alice should send a signature of Bob's key to the server
|
|
await promise;
|
|
});
|
|
|
|
it.skip("should get cross-signing keys from sync", async function() {
|
|
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 { client: alice, httpBackend } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
{
|
|
cryptoCallbacks: {
|
|
// will be called to sign our own device
|
|
getCrossSigningKey: type => {
|
|
if (type === 'master') {
|
|
return masterKey;
|
|
} else {
|
|
return selfSigningKey;
|
|
}
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
const keyChangePromise = new Promise((resolve, reject) => {
|
|
alice.once("crossSigning.keysChanged", async (e) => {
|
|
resolve(e);
|
|
await alice.checkOwnCrossSigningTrust({
|
|
allowPrivateKeyRequests: true,
|
|
});
|
|
});
|
|
});
|
|
|
|
const uploadSigsPromise = new Promise((resolve, reject) => {
|
|
alice.uploadKeySignatures = jest.fn(async (content) => {
|
|
try {
|
|
await olmlib.verifySignature(
|
|
alice.crypto.olmDevice,
|
|
content["@alice:example.com"][
|
|
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
|
|
],
|
|
"@alice:example.com",
|
|
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
|
);
|
|
olmlib.pkVerify(
|
|
content["@alice:example.com"]["Osborne2"],
|
|
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
|
|
"@alice:example.com",
|
|
);
|
|
resolve();
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
});
|
|
|
|
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 master key, ssk, device key
|
|
const responses = [
|
|
PUSH_RULES_RESPONSE,
|
|
{
|
|
method: "POST",
|
|
path: "/keys/upload",
|
|
data: {
|
|
one_time_key_counts: {
|
|
curve25519: 100,
|
|
signed_curve25519: 100,
|
|
},
|
|
},
|
|
},
|
|
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",
|
|
data: {
|
|
one_time_key_counts: {
|
|
curve25519: 100,
|
|
signed_curve25519: 100,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
setHttpResponses(httpBackend, responses);
|
|
|
|
alice.startClient();
|
|
httpBackend.flushAllExpected();
|
|
|
|
// once ssk is confirmed, device key should be trusted
|
|
await keyChangePromise;
|
|
await uploadSigsPromise;
|
|
|
|
const aliceTrust = alice.checkUserTrust("@alice:example.com");
|
|
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
|
|
expect(aliceTrust.isTofu()).toBeTruthy();
|
|
expect(aliceTrust.isVerified()).toBeTruthy();
|
|
|
|
const aliceDeviceTrust = alice.checkDeviceTrust("@alice:example.com", "Osborne2");
|
|
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeTruthy();
|
|
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
|
|
expect(aliceDeviceTrust.isTofu()).toBeTruthy();
|
|
expect(aliceDeviceTrust.isVerified()).toBeTruthy();
|
|
});
|
|
|
|
it("should use trust chain to determine device verification", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
// set Alice's cross-signing key
|
|
await resetCrossSigningKeys(alice);
|
|
// Alice downloads Bob's ssk and device key
|
|
const bobMasterSigning = new global.Olm.PkSigning();
|
|
const bobMasterPrivkey = bobMasterSigning.generate_seed();
|
|
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
|
|
const bobSigning = new global.Olm.PkSigning();
|
|
const bobPrivkey = bobSigning.generate_seed();
|
|
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
|
|
const bobSSK = {
|
|
user_id: "@bob:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + bobPubkey]: bobPubkey,
|
|
},
|
|
};
|
|
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
|
|
bobSSK.signatures = {
|
|
"@bob:example.com": {
|
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
|
|
},
|
|
},
|
|
self_signing: bobSSK,
|
|
},
|
|
firstUse: 1,
|
|
unsigned: {},
|
|
});
|
|
const bobDevice = {
|
|
user_id: "@bob:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": "somePubkey",
|
|
"ed25519:Dynabook": "someOtherPubkey",
|
|
},
|
|
};
|
|
const sig = bobSigning.sign(anotherjson.stringify(bobDevice));
|
|
bobDevice.signatures = {
|
|
"@bob:example.com": {
|
|
["ed25519:" + bobPubkey]: sig,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
|
Dynabook: bobDevice,
|
|
});
|
|
// Bob's device key should be TOFU
|
|
const bobTrust = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust.isVerified()).toBeFalsy();
|
|
expect(bobTrust.isTofu()).toBeTruthy();
|
|
|
|
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust.isVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust.isTofu()).toBeTruthy();
|
|
|
|
// Alice verifies Bob's SSK
|
|
alice.uploadKeySignatures = () => {};
|
|
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
|
|
|
|
// Bob's device key should be trusted
|
|
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust2.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobTrust2.isTofu()).toBeTruthy();
|
|
|
|
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust2.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobDeviceTrust2.isLocallyVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust2.isTofu()).toBeTruthy();
|
|
});
|
|
|
|
it.skip("should trust signatures received from other devices", async function() {
|
|
const aliceKeys = {};
|
|
const { client: alice, httpBackend } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
null,
|
|
aliceKeys,
|
|
);
|
|
alice.crypto.deviceList.startTrackingDeviceList("@bob:example.com");
|
|
alice.crypto.deviceList.stopTrackingAllDeviceLists = () => {};
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
|
|
// set Alice's cross-signing key
|
|
await resetCrossSigningKeys(alice);
|
|
|
|
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.crypto.deviceList.once("userCrossSigningUpdated", (userId) => {
|
|
if (userId === "@bob:example.com") {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
|
|
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);
|
|
|
|
const bobOlmAccount = new global.Olm.Account();
|
|
bobOlmAccount.create();
|
|
const bobKeys = JSON.parse(bobOlmAccount.identity_keys());
|
|
const bobDevice = {
|
|
user_id: "@bob:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
|
keys: {
|
|
"ed25519:Dynabook": bobKeys.ed25519,
|
|
"curve25519:Dynabook": bobKeys.curve25519,
|
|
},
|
|
};
|
|
const deviceStr = anotherjson.stringify(bobDevice);
|
|
bobDevice.signatures = {
|
|
"@bob:example.com": {
|
|
"ed25519:Dynabook": bobOlmAccount.sign(deviceStr),
|
|
},
|
|
};
|
|
olmlib.pkSign(bobDevice, selfSigningKey, "@bob:example.com");
|
|
|
|
const bobMaster = {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
|
|
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
|
|
},
|
|
};
|
|
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com");
|
|
|
|
// Alice downloads Bob's keys
|
|
// - device key
|
|
// - ssk
|
|
// - master key signed by her usk (pretend that it was signed by another
|
|
// of Alice's devices)
|
|
const responses = [
|
|
PUSH_RULES_RESPONSE,
|
|
{
|
|
method: "POST",
|
|
path: "/keys/upload",
|
|
data: {
|
|
one_time_key_counts: {
|
|
curve25519: 100,
|
|
signed_curve25519: 100,
|
|
},
|
|
},
|
|
},
|
|
filterResponse("@alice:example.com"),
|
|
{
|
|
method: "GET",
|
|
path: "/sync",
|
|
data: {
|
|
next_batch: "abcdefg",
|
|
device_lists: {
|
|
changed: [
|
|
"@bob:example.com",
|
|
],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
method: "POST",
|
|
path: "/keys/query",
|
|
data: {
|
|
"failures": {},
|
|
"device_keys": {
|
|
"@alice:example.com": {
|
|
"Osborne2": aliceDevice,
|
|
},
|
|
"@bob:example.com": {
|
|
"Dynabook": bobDevice,
|
|
},
|
|
},
|
|
"master_keys": {
|
|
"@bob:example.com": bobMaster,
|
|
},
|
|
"self_signing_keys": {
|
|
"@bob:example.com": {
|
|
user_id: "@bob:example.com",
|
|
usage: ["self-signing"],
|
|
keys: {
|
|
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
|
|
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
|
|
},
|
|
signatures: {
|
|
"@bob:example.com": {
|
|
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
|
|
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB"
|
|
+ "LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
method: "POST",
|
|
path: "/keys/upload",
|
|
data: {
|
|
one_time_key_counts: {
|
|
curve25519: 100,
|
|
signed_curve25519: 100,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
setHttpResponses(httpBackend, responses);
|
|
|
|
alice.startClient();
|
|
httpBackend.flushAllExpected();
|
|
await keyChangePromise;
|
|
|
|
// Bob's device key should be trusted
|
|
const bobTrust = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobTrust.isTofu()).toBeTruthy();
|
|
|
|
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobDeviceTrust.isLocallyVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust.isTofu()).toBeTruthy();
|
|
});
|
|
|
|
it("should dis-trust an unsigned device", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
// set Alice's cross-signing key
|
|
await resetCrossSigningKeys(alice);
|
|
// Alice downloads Bob's ssk and device key
|
|
// (NOTE: device key is not signed by ssk)
|
|
const bobMasterSigning = new global.Olm.PkSigning();
|
|
const bobMasterPrivkey = bobMasterSigning.generate_seed();
|
|
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
|
|
const bobSigning = new global.Olm.PkSigning();
|
|
const bobPrivkey = bobSigning.generate_seed();
|
|
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
|
|
const bobSSK = {
|
|
user_id: "@bob:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + bobPubkey]: bobPubkey,
|
|
},
|
|
};
|
|
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
|
|
bobSSK.signatures = {
|
|
"@bob:example.com": {
|
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
|
|
},
|
|
},
|
|
self_signing: bobSSK,
|
|
},
|
|
firstUse: 1,
|
|
unsigned: {},
|
|
});
|
|
const bobDevice = {
|
|
user_id: "@bob:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": "somePubkey",
|
|
"ed25519:Dynabook": "someOtherPubkey",
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
|
Dynabook: bobDevice,
|
|
});
|
|
// Bob's device key should be untrusted
|
|
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust.isVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust.isTofu()).toBeFalsy();
|
|
|
|
// Alice verifies Bob's SSK
|
|
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
|
|
|
|
// Bob's device key should be untrusted
|
|
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
|
|
});
|
|
|
|
it("should dis-trust a user when their ssk changes", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
await resetCrossSigningKeys(alice);
|
|
// Alice downloads Bob's keys
|
|
const bobMasterSigning = new global.Olm.PkSigning();
|
|
const bobMasterPrivkey = bobMasterSigning.generate_seed();
|
|
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
|
|
const bobSigning = new global.Olm.PkSigning();
|
|
const bobPrivkey = bobSigning.generate_seed();
|
|
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
|
|
const bobSSK = {
|
|
user_id: "@bob:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + bobPubkey]: bobPubkey,
|
|
},
|
|
};
|
|
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
|
|
bobSSK.signatures = {
|
|
"@bob:example.com": {
|
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
|
|
},
|
|
},
|
|
self_signing: bobSSK,
|
|
},
|
|
firstUse: 1,
|
|
unsigned: {},
|
|
});
|
|
const bobDevice = {
|
|
user_id: "@bob:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": "somePubkey",
|
|
"ed25519:Dynabook": "someOtherPubkey",
|
|
},
|
|
};
|
|
const bobDeviceString = anotherjson.stringify(bobDevice);
|
|
const sig = bobSigning.sign(bobDeviceString);
|
|
bobDevice.signatures = {};
|
|
bobDevice.signatures["@bob:example.com"] = {};
|
|
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
|
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
|
Dynabook: bobDevice,
|
|
});
|
|
// Alice verifies Bob's SSK
|
|
alice.uploadKeySignatures = () => {};
|
|
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
|
|
|
|
// Bob's device key should be trusted
|
|
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust.isVerified()).toBeTruthy();
|
|
expect(bobDeviceTrust.isTofu()).toBeTruthy();
|
|
|
|
// Alice downloads new SSK for Bob
|
|
const bobMasterSigning2 = new global.Olm.PkSigning();
|
|
const bobMasterPrivkey2 = bobMasterSigning2.generate_seed();
|
|
const bobMasterPubkey2 = bobMasterSigning2.init_with_seed(bobMasterPrivkey2);
|
|
const bobSigning2 = new global.Olm.PkSigning();
|
|
const bobPrivkey2 = bobSigning2.generate_seed();
|
|
const bobPubkey2 = bobSigning2.init_with_seed(bobPrivkey2);
|
|
const bobSSK2 = {
|
|
user_id: "@bob:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + bobPubkey2]: bobPubkey2,
|
|
},
|
|
};
|
|
const sskSig2 = bobMasterSigning2.sign(anotherjson.stringify(bobSSK2));
|
|
bobSSK2.signatures = {
|
|
"@bob:example.com": {
|
|
["ed25519:" + bobMasterPubkey2]: sskSig2,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@bob:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + bobMasterPubkey2]: bobMasterPubkey2,
|
|
},
|
|
},
|
|
self_signing: bobSSK2,
|
|
},
|
|
firstUse: 0,
|
|
unsigned: {},
|
|
});
|
|
// Bob's and his device should be untrusted
|
|
const bobTrust = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust.isVerified()).toBeFalsy();
|
|
expect(bobTrust.isTofu()).toBeFalsy();
|
|
|
|
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
|
|
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
|
|
|
|
// Alice verifies Bob's SSK
|
|
alice.uploadKeySignatures = () => {};
|
|
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey2, true);
|
|
|
|
// Bob should be trusted but not his device
|
|
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust2.isVerified()).toBeTruthy();
|
|
|
|
const bobDeviceTrust3 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust3.isVerified()).toBeFalsy();
|
|
|
|
// Alice gets new signature for device
|
|
const sig2 = bobSigning2.sign(bobDeviceString);
|
|
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
|
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
|
Dynabook: bobDevice,
|
|
});
|
|
|
|
// Bob's device should be trusted again (but not TOFU)
|
|
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust3.isVerified()).toBeTruthy();
|
|
|
|
const bobDeviceTrust4 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
|
|
expect(bobDeviceTrust4.isCrossSigningVerified()).toBeTruthy();
|
|
});
|
|
|
|
it("should offer to upgrade device verifications to cross-signing", async function() {
|
|
let upgradeResolveFunc;
|
|
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
{
|
|
cryptoCallbacks: {
|
|
shouldUpgradeDeviceVerifications: (verifs) => {
|
|
expect(verifs.users["@bob:example.com"]).toBeDefined();
|
|
upgradeResolveFunc();
|
|
return ["@bob:example.com"];
|
|
},
|
|
},
|
|
},
|
|
);
|
|
const { client: bob } = await makeTestClient(
|
|
{ userId: "@bob:example.com", deviceId: "Dynabook" },
|
|
);
|
|
|
|
bob.uploadDeviceSigningKeys = async () => {};
|
|
bob.uploadKeySignatures = async () => {};
|
|
// set Bob's cross-signing key
|
|
await resetCrossSigningKeys(bob);
|
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
|
Dynabook: {
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": bob.crypto.olmDevice.deviceCurve25519Key,
|
|
"ed25519:Dynabook": bob.crypto.olmDevice.deviceEd25519Key,
|
|
},
|
|
verified: 1,
|
|
known: true,
|
|
},
|
|
});
|
|
alice.crypto.deviceList.storeCrossSigningForUser(
|
|
"@bob:example.com",
|
|
bob.crypto.crossSigningInfo.toStorage(),
|
|
);
|
|
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
// when alice sets up cross-signing, she should notice that bob's
|
|
// cross-signing key is signed by his Dynabook, which alice has
|
|
// verified, and ask if the device verification should be upgraded to a
|
|
// cross-signing verification
|
|
let upgradePromise = new Promise((resolve) => {
|
|
upgradeResolveFunc = resolve;
|
|
});
|
|
await resetCrossSigningKeys(alice);
|
|
await upgradePromise;
|
|
|
|
const bobTrust = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobTrust.isTofu()).toBeTruthy();
|
|
|
|
// "forget" that Bob is trusted
|
|
delete alice.crypto.deviceList.crossSigningInfo["@bob:example.com"]
|
|
.keys.master.signatures["@alice:example.com"];
|
|
|
|
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
|
|
expect(bobTrust2.isTofu()).toBeTruthy();
|
|
|
|
upgradePromise = new Promise((resolve) => {
|
|
upgradeResolveFunc = resolve;
|
|
});
|
|
alice.crypto.deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
|
await new Promise((resolve) => {
|
|
alice.crypto.on("userTrustStatusChanged", resolve);
|
|
});
|
|
await upgradePromise;
|
|
|
|
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
|
|
expect(bobTrust3.isCrossSigningVerified()).toBeTruthy();
|
|
expect(bobTrust3.isTofu()).toBeTruthy();
|
|
});
|
|
|
|
it(
|
|
"should observe that our own device is cross-signed, even if this device doesn't trust the key",
|
|
async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
|
|
// Generate Alice's SSK etc
|
|
const aliceMasterSigning = new global.Olm.PkSigning();
|
|
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
|
|
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
|
|
const aliceSigning = new global.Olm.PkSigning();
|
|
const alicePrivkey = aliceSigning.generate_seed();
|
|
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
|
|
const aliceSSK = {
|
|
user_id: "@alice:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + alicePubkey]: alicePubkey,
|
|
},
|
|
};
|
|
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
|
|
aliceSSK.signatures = {
|
|
"@alice:example.com": {
|
|
["ed25519:" + aliceMasterPubkey]: sskSig,
|
|
},
|
|
};
|
|
|
|
// Alice's device downloads the keys, but doesn't trust them yet
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@alice:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
|
|
},
|
|
},
|
|
self_signing: aliceSSK,
|
|
},
|
|
firstUse: 1,
|
|
unsigned: {},
|
|
});
|
|
|
|
// Alice has a second device that's cross-signed
|
|
const aliceCrossSignedDevice = {
|
|
user_id: "@alice:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": "somePubkey",
|
|
"ed25519:Dynabook": "someOtherPubkey",
|
|
},
|
|
};
|
|
const sig = aliceSigning.sign(anotherjson.stringify(aliceCrossSignedDevice));
|
|
aliceCrossSignedDevice.signatures = {
|
|
"@alice:example.com": {
|
|
["ed25519:" + alicePubkey]: sig,
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
|
Dynabook: aliceCrossSignedDevice,
|
|
});
|
|
|
|
// We don't trust the cross-signing keys yet...
|
|
expect(alice.checkDeviceTrust(aliceCrossSignedDevice.device_id).isCrossSigningVerified()).toBeFalsy();
|
|
// ... but we do acknowledge that the device is signed by them
|
|
expect(alice.checkIfOwnDeviceCrossSigned(aliceCrossSignedDevice.device_id)).toBeTruthy();
|
|
},
|
|
);
|
|
|
|
it("should observe that our own device isn't cross-signed", async function() {
|
|
const { client: alice } = await makeTestClient(
|
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
|
);
|
|
alice.uploadDeviceSigningKeys = async () => {};
|
|
alice.uploadKeySignatures = async () => {};
|
|
|
|
// Generate Alice's SSK etc
|
|
const aliceMasterSigning = new global.Olm.PkSigning();
|
|
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
|
|
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
|
|
const aliceSigning = new global.Olm.PkSigning();
|
|
const alicePrivkey = aliceSigning.generate_seed();
|
|
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
|
|
const aliceSSK = {
|
|
user_id: "@alice:example.com",
|
|
usage: ["self_signing"],
|
|
keys: {
|
|
["ed25519:" + alicePubkey]: alicePubkey,
|
|
},
|
|
};
|
|
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
|
|
aliceSSK.signatures = {
|
|
"@alice:example.com": {
|
|
["ed25519:" + aliceMasterPubkey]: sskSig,
|
|
},
|
|
};
|
|
|
|
// Alice's device downloads the keys
|
|
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
|
keys: {
|
|
master: {
|
|
user_id: "@alice:example.com",
|
|
usage: ["master"],
|
|
keys: {
|
|
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
|
|
},
|
|
},
|
|
self_signing: aliceSSK,
|
|
},
|
|
firstUse: 1,
|
|
unsigned: {},
|
|
});
|
|
|
|
// Alice has a second device that's also not cross-signed
|
|
const aliceNotCrossSignedDevice = {
|
|
user_id: "@alice:example.com",
|
|
device_id: "Dynabook",
|
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
|
keys: {
|
|
"curve25519:Dynabook": "somePubkey",
|
|
"ed25519:Dynabook": "someOtherPubkey",
|
|
},
|
|
};
|
|
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
|
Dynabook: aliceNotCrossSignedDevice,
|
|
});
|
|
|
|
expect(alice.checkIfOwnDeviceCrossSigned(aliceNotCrossSignedDevice.device_id)).toBeFalsy();
|
|
});
|
|
});
|