diff --git a/spec/unit/crypto/verification/sas.spec.js b/spec/unit/crypto/verification/sas.spec.js index 59df685b4..0fbea9a96 100644 --- a/spec/unit/crypto/verification/sas.spec.js +++ b/spec/unit/crypto/verification/sas.spec.js @@ -22,6 +22,7 @@ try { } import expect from 'expect'; +import olmlib from '../../../../lib/crypto/olmlib'; import sdk from '../../../..'; @@ -78,38 +79,35 @@ describe("SAS verification", function() { }, ); - alice.setDeviceVerified = expect.createSpy(); - alice.getDeviceEd25519Key = () => { - return "alice+base64+ed25519+key"; - }; - alice.getStoredDevice = () => { - return DeviceInfo.fromStorage( - { - keys: { - "ed25519:Dynabook": "bob+base64+ed25519+key", - }, + const aliceDevice = alice._crypto._olmDevice; + const bobDevice = bob._crypto._olmDevice; + + alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", { + Dynabook: { + user_id: "@bob:example.com", + device_id: "Dynabook", + algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM], + keys: { + "ed25519:Dynabook": bobDevice.deviceEd25519Key, + "curve25519:Dynabook": bobDevice.deviceCurve25519Key, }, - "Dynabook", - ); - }; + }, + }); alice.downloadKeys = () => { return Promise.resolve(); }; - bob.setDeviceVerified = expect.createSpy(); - bob.getStoredDevice = () => { - return DeviceInfo.fromStorage( - { - keys: { - "ed25519:Osborne2": "alice+base64+ed25519+key", - }, + bob._crypto._deviceList.storeDevicesForUser("@alice:example.com", { + Osborne2: { + user_id: "@alice:example.com", + device_id: "Osborne2", + algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM], + keys: { + "ed25519:Osborne2": aliceDevice.deviceEd25519Key, + "curve25519:Osborne2": aliceDevice.deviceCurve25519Key, }, - "Osborne2", - ); - }; - bob.getDeviceEd25519Key = () => { - return "bob+base64+ed25519+key"; - }; + }, + }); bob.downloadKeys = () => { return Promise.resolve(); }; @@ -180,10 +178,12 @@ describe("SAS verification", function() { expect(macMethod).toBe("hkdf-hmac-sha256"); // make sure Alice and Bob verified each other - expect(alice.setDeviceVerified) - .toHaveBeenCalledWith(bob.getUserId(), bob.deviceId); - expect(bob.setDeviceVerified) - .toHaveBeenCalledWith(alice.getUserId(), alice.deviceId); + const bobDevice + = await alice.getStoredDevice("@bob:example.com", "Dynabook"); + expect(bobDevice.isVerified()).toBeTruthy(); + const aliceDevice + = await bob.getStoredDevice("@alice:example.com", "Osborne2"); + expect(aliceDevice.isVerified()).toBeTruthy(); }); it("should be able to verify using the old MAC", async function() { @@ -218,10 +218,40 @@ describe("SAS verification", function() { expect(macMethod).toBe("hmac-sha256"); - expect(alice.setDeviceVerified) - .toHaveBeenCalledWith(bob.getUserId(), bob.deviceId); - expect(bob.setDeviceVerified) - .toHaveBeenCalledWith(alice.getUserId(), alice.deviceId); + const bobDevice + = await alice.getStoredDevice("@bob:example.com", "Dynabook"); + expect(bobDevice.isVerified()).toBeTruthy(); + const aliceDevice + = await bob.getStoredDevice("@alice:example.com", "Osborne2"); + expect(aliceDevice.isVerified()).toBeTruthy(); + }); + + it("should verify a cross-signing key", async function() { + const privateKeys = {}; + alice.on("cross-signing:savePrivateKeys", function(e) { + privateKeys.alice = e; + }); + await alice.resetCrossSigningKeys(); + bob.on("cross-signing:savePrivateKeys", function(e) { + privateKeys.bob = e; + }); + await bob.resetCrossSigningKeys(); + + bob.on("cross-signing:getKey", function(e) { + e.done(privateKeys.bob[e.type]); + }); + + bob._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", { + keys: alice._crypto._crossSigningInfo.keys, + }); + await Promise.all([ + aliceVerifier.verify(), + bobPromise.then((verifier) => verifier.verify()), + ]); + + expect(alice.checkDeviceTrust("@bob:example.com", "Dynabook")).toBe(1); + expect(bob.checkUserTrust("@alice:example.com")).toBe(6); + expect(bob.checkDeviceTrust("@alice:example.com", "Osborne2")).toBe(1); }); }); diff --git a/src/client.js b/src/client.js index c4ddd6d8c..0f64fb2fa 100644 --- a/src/client.js +++ b/src/client.js @@ -854,6 +854,9 @@ MatrixClient.prototype.resetCrossSigningKeys MatrixClient.prototype.setCrossSigningKeys = wrapCryptoFunc("setCrossSigningKeys"); +MatrixClient.prototype.getCrossSigningId + = wrapCryptoFunc("getCrossSigningId"); + /** * Cancel a room key request for this event if one is ongoing and resend the * request. diff --git a/src/crypto/index.js b/src/crypto/index.js index 3be1be3c0..6edf07787 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -286,6 +286,18 @@ Crypto.prototype.setCrossSigningKeys = function(keys) { this._baseApis.emit("cross-signing:keysChanged", {}); }; +/** + * Get the user's cross-signing key ID. + * + * @param {string} type The type of key to get the ID of. One of "master", + * "self_signing", or "user_signing". Defaults to "master". + * + * @returns {string} the key ID + */ +Crypto.prototype.getCrossSigningId = function(type) { + return this._crossSigningInfo.getId(type); +}; + /** * Check whether a given user is trusted. * diff --git a/src/crypto/verification/Base.js b/src/crypto/verification/Base.js index 5662fe348..cee051c8d 100644 --- a/src/crypto/verification/Base.js +++ b/src/crypto/verification/Base.js @@ -22,6 +22,7 @@ limitations under the License. import {MatrixEvent} from '../../models/event'; import {EventEmitter} from 'events'; import logger from '../../logger'; +import DeviceInfo from '../deviceinfo'; export default class VerificationBase extends EventEmitter { /** @@ -192,11 +193,24 @@ export default class VerificationBase extends EventEmitter { for (const [keyId, keyInfo] of Object.entries(keys)) { const deviceId = keyId.split(':', 2)[1]; const device = await this._baseApis.getStoredDevice(userId, deviceId); - if (!device) { - logger.warn(`verification: Could not find device ${deviceId} to verify`); - } else { + if (device) { await verifier(keyId, device, keyInfo); verifiedDevices.push(deviceId); + } else { + const crossSigningInfo = this._baseApis._crypto._deviceList + .getStoredCrossSigningForUser(userId); + if (crossSigningInfo && crossSigningInfo.getId() === deviceId) { + await verifier(keyId, DeviceInfo.fromStorage({ + keys: { + [keyId]: deviceId, + }, + }, deviceId), keyInfo); + verifiedDevices.push(deviceId); + } else { + logger.warn( + `verification: Could not find device ${deviceId} to verify`, + ); + } } } diff --git a/src/crypto/verification/SAS.js b/src/crypto/verification/SAS.js index 5889c56be..363597704 100644 --- a/src/crypto/verification/SAS.js +++ b/src/crypto/verification/SAS.js @@ -354,19 +354,32 @@ export default class SAS extends Base { } _sendMAC(olmSAS, method) { - const keyId = `ed25519:${this._baseApis.deviceId}`; const mac = {}; + const keyList = []; const baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + this._baseApis.getUserId() + this._baseApis.deviceId + this.userId + this.deviceId + this.transactionId; - mac[keyId] = olmSAS[macMethods[method]]( + const deviceKeyId = `ed25519:${this._baseApis.deviceId}`; + mac[deviceKeyId] = olmSAS[macMethods[method]]( this._baseApis.getDeviceEd25519Key(), - baseInfo + keyId, + baseInfo + deviceKeyId, ); + keyList.push(deviceKeyId); + + const crossSigningId = this._baseApis.getCrossSigningId(); + if (crossSigningId) { + const crossSigningKeyId = `ed25519:${crossSigningId}`; + mac[crossSigningKeyId] = olmSAS[macMethods[method]]( + crossSigningId, + baseInfo + crossSigningKeyId, + ); + keyList.push(crossSigningKeyId); + } + const keys = olmSAS[macMethods[method]]( - keyId, + keyList.sort().join(","), baseInfo + "KEY_IDS", ); this._sendToDevice("m.key.verification.mac", { mac, keys });