Skip to content

Commit db58a66

Browse files
authored
Add method for checking whether our other devices are cross-signed, even when this device isn't (matrix-org#2288)
1 parent 9f45986 commit db58a66

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

spec/unit/crypto/cross-signing.spec.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,4 +883,138 @@ describe("Cross Signing", function() {
883883
expect(bobTrust3.isCrossSigningVerified()).toBeTruthy();
884884
expect(bobTrust3.isTofu()).toBeTruthy();
885885
});
886+
887+
it(
888+
"should observe that our own device is cross-signed, even if this device doesn't trust the key",
889+
async function() {
890+
const { client: alice } = await makeTestClient(
891+
{ userId: "@alice:example.com", deviceId: "Osborne2" },
892+
);
893+
alice.uploadDeviceSigningKeys = async () => {};
894+
alice.uploadKeySignatures = async () => {};
895+
896+
// Generate Alice's SSK etc
897+
const aliceMasterSigning = new global.Olm.PkSigning();
898+
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
899+
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
900+
const aliceSigning = new global.Olm.PkSigning();
901+
const alicePrivkey = aliceSigning.generate_seed();
902+
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
903+
const aliceSSK = {
904+
user_id: "@alice:example.com",
905+
usage: ["self_signing"],
906+
keys: {
907+
["ed25519:" + alicePubkey]: alicePubkey,
908+
},
909+
};
910+
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
911+
aliceSSK.signatures = {
912+
"@alice:example.com": {
913+
["ed25519:" + aliceMasterPubkey]: sskSig,
914+
},
915+
};
916+
917+
// Alice's device downloads the keys, but doesn't trust them yet
918+
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
919+
keys: {
920+
master: {
921+
user_id: "@alice:example.com",
922+
usage: ["master"],
923+
keys: {
924+
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
925+
},
926+
},
927+
self_signing: aliceSSK,
928+
},
929+
firstUse: 1,
930+
unsigned: {},
931+
});
932+
933+
// Alice has a second device that's cross-signed
934+
const aliceCrossSignedDevice = {
935+
user_id: "@alice:example.com",
936+
device_id: "Dynabook",
937+
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
938+
keys: {
939+
"curve25519:Dynabook": "somePubkey",
940+
"ed25519:Dynabook": "someOtherPubkey",
941+
},
942+
};
943+
const sig = aliceSigning.sign(anotherjson.stringify(aliceCrossSignedDevice));
944+
aliceCrossSignedDevice.signatures = {
945+
"@alice:example.com": {
946+
["ed25519:" + alicePubkey]: sig,
947+
},
948+
};
949+
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
950+
Dynabook: aliceCrossSignedDevice,
951+
});
952+
953+
// We don't trust the cross-signing keys yet...
954+
expect(alice.checkDeviceTrust(aliceCrossSignedDevice.device_id).isCrossSigningVerified()).toBeFalsy();
955+
// ... but we do acknowledge that the device is signed by them
956+
expect(alice.checkIfOwnDeviceCrossSigned(aliceCrossSignedDevice.device_id)).toBeTruthy();
957+
},
958+
);
959+
960+
it("should observe that our own device isn't cross-signed", async function() {
961+
const { client: alice } = await makeTestClient(
962+
{ userId: "@alice:example.com", deviceId: "Osborne2" },
963+
);
964+
alice.uploadDeviceSigningKeys = async () => {};
965+
alice.uploadKeySignatures = async () => {};
966+
967+
// Generate Alice's SSK etc
968+
const aliceMasterSigning = new global.Olm.PkSigning();
969+
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
970+
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
971+
const aliceSigning = new global.Olm.PkSigning();
972+
const alicePrivkey = aliceSigning.generate_seed();
973+
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
974+
const aliceSSK = {
975+
user_id: "@alice:example.com",
976+
usage: ["self_signing"],
977+
keys: {
978+
["ed25519:" + alicePubkey]: alicePubkey,
979+
},
980+
};
981+
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
982+
aliceSSK.signatures = {
983+
"@alice:example.com": {
984+
["ed25519:" + aliceMasterPubkey]: sskSig,
985+
},
986+
};
987+
988+
// Alice's device downloads the keys
989+
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
990+
keys: {
991+
master: {
992+
user_id: "@alice:example.com",
993+
usage: ["master"],
994+
keys: {
995+
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
996+
},
997+
},
998+
self_signing: aliceSSK,
999+
},
1000+
firstUse: 1,
1001+
unsigned: {},
1002+
});
1003+
1004+
// Alice has a second device that's also not cross-signed
1005+
const aliceNotCrossSignedDevice = {
1006+
user_id: "@alice:example.com",
1007+
device_id: "Dynabook",
1008+
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
1009+
keys: {
1010+
"curve25519:Dynabook": "somePubkey",
1011+
"ed25519:Dynabook": "someOtherPubkey",
1012+
},
1013+
};
1014+
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
1015+
Dynabook: aliceNotCrossSignedDevice,
1016+
});
1017+
1018+
expect(alice.checkIfOwnDeviceCrossSigned(aliceNotCrossSignedDevice.device_id)).toBeFalsy();
1019+
});
8861020
});

src/client.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,6 +2072,21 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
20722072
return this.crypto.checkDeviceTrust(userId, deviceId);
20732073
}
20742074

2075+
/**
2076+
* Check whether one of our own devices is cross-signed by our
2077+
* user's stored keys, regardless of whether we trust those keys yet.
2078+
*
2079+
* @param {string} deviceId The ID of the device to check
2080+
*
2081+
* @returns {boolean} true if the device is cross-signed
2082+
*/
2083+
public checkIfOwnDeviceCrossSigned(deviceId: string): boolean {
2084+
if (!this.crypto) {
2085+
throw new Error("End-to-end encryption disabled");
2086+
}
2087+
return this.crypto.checkIfOwnDeviceCrossSigned(deviceId);
2088+
}
2089+
20752090
/**
20762091
* Check the copy of our cross-signing key that we have in the device list and
20772092
* see if we can get the private key. If so, mark it as trusted.

src/crypto/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,25 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
14231423
}
14241424
}
14251425

1426+
/**
1427+
* Check whether one of our own devices is cross-signed by our
1428+
* user's stored keys, regardless of whether we trust those keys yet.
1429+
*
1430+
* @param {string} deviceId The ID of the device to check
1431+
*
1432+
* @returns {boolean} true if the device is cross-signed
1433+
*/
1434+
public checkIfOwnDeviceCrossSigned(deviceId: string): boolean {
1435+
const device = this.deviceList.getStoredDevice(this.userId, deviceId);
1436+
const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(this.userId);
1437+
return userCrossSigning.checkDeviceTrust(
1438+
userCrossSigning,
1439+
device,
1440+
false,
1441+
true,
1442+
).isCrossSigningVerified();
1443+
}
1444+
14261445
/*
14271446
* Event handler for DeviceList's userNewDevices event
14281447
*/

0 commit comments

Comments
 (0)