Skip to content

Commit

Permalink
Basic implementation of SAS verification in Rust
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh committed Jun 20, 2023
1 parent ae56953 commit 11b1cf8
Show file tree
Hide file tree
Showing 6 changed files with 493 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.10",
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.11",
"another-json": "^0.2.0",
"bs58": "^5.0.0",
"content-type": "^1.0.4",
Expand Down
26 changes: 23 additions & 3 deletions spec/integ/crypto/verification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.

import fetchMock from "fetch-mock-jest";
import { MockResponse } from "fetch-mock";
import "fake-indexeddb/auto";

import { createClient, CryptoEvent, MatrixClient } from "../../../src";
import {
Expand Down Expand Up @@ -49,7 +50,7 @@ jest.useFakeTimers();

let previousCrypto: Crypto | undefined;

beforeAll(() => {
beforeAll(async () => {
// Stub out global.crypto
previousCrypto = global["crypto"];

Expand All @@ -61,6 +62,9 @@ beforeAll(() => {
},
},
});

// we use the libolm primitives in the test, so init the Olm library
await global.Olm.init();
});

// restore the original global.crypto
Expand Down Expand Up @@ -136,6 +140,10 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u

afterEach(async () => {
await aliceClient.stopClient();

// Allow in-flight things to complete before we tear down the test
await jest.runAllTimersAsync();

fetchMock.mockReset();
});

Expand All @@ -145,7 +153,18 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
e2eKeyResponder.addDeviceKeys(TEST_USER_ID, TEST_DEVICE_ID, SIGNED_TEST_DEVICE_DATA);
});

oldBackendOnly("can verify via SAS", async () => {
it("can verify another device via SAS", async () => {
// Rust crypto needs to know about the other device, so let /sync complete, which will kick off the
// outgoing request processor (which will make a keys query).
syncResponder.sendOrQueueSyncResponse({});

// Legacy DeviceList has a sleep(5) which we need to make happen
await jest.advanceTimersByTimeAsync(10);

// The device list should now be up to date
const devices = await aliceClient.getCrypto()!.getUserDeviceInfo([TEST_USER_ID]);
expect(devices.get(TEST_USER_ID)!.keys()).toContain(TEST_DEVICE_ID);

// have alice initiate a verification. She should send a m.key.verification.request
let [requestBody, request] = await Promise.all([
expectSendToDeviceMessage("m.key.verification.request"),
Expand Down Expand Up @@ -206,11 +225,12 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
expect(verifier.getShowSasCallbacks()).toBeNull();

// start off the verification process: alice will send an `accept`
const sendToDevicePromise = expectSendToDeviceMessage("m.key.verification.accept");
const verificationPromise = verifier.verify();
// advance the clock, because the devicelist likes to sleep for 5ms during key downloads
jest.advanceTimersByTime(10);

requestBody = await expectSendToDeviceMessage("m.key.verification.accept");
requestBody = await sendToDevicePromise;
toDeviceMessage = requestBody.messages[TEST_USER_ID][TEST_DEVICE_ID];
expect(toDeviceMessage.key_agreement_protocol).toEqual("curve25519-hkdf-sha256");
expect(toDeviceMessage.short_authentication_string).toEqual(["decimal", "emoji"]);
Expand Down
2 changes: 2 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2235,6 +2235,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.secretStorage,
this.cryptoCallbacks,
);
rustCrypto.supportedVerificationMethods = this.verificationMethods;

this.cryptoBackend = rustCrypto;

// attach the event listeners needed by RustCrypto
Expand Down
41 changes: 37 additions & 4 deletions src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { secretStorageContainsCrossSigningKeys } from "./secret-storage";
import { keyFromPassphrase } from "../crypto/key_passphrase";
import { encodeRecoveryKey } from "../crypto/recoverykey";
import { crypto } from "../crypto/crypto";
import { RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification";

/**
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
Expand Down Expand Up @@ -508,6 +509,13 @@ export class RustCrypto implements CryptoBackend {
return;
}

/**
* The verification methods we offer to the other side during an interactive verification.
*
* If `undefined`, we will offer all the methods supported by the Rust SDK.
*/
public supportedVerificationMethods: string[] | undefined;

/**
* Send a verification request to our other devices.
*
Expand All @@ -517,7 +525,18 @@ export class RustCrypto implements CryptoBackend {
*
* @returns a VerificationRequest when the request has been sent to the other party.
*/
public requestOwnUserVerification(): Promise<VerificationRequest> {
public async requestOwnUserVerification(): Promise<VerificationRequest> {
/* something like this, but currently untested
const user: RustSdkCryptoJs.OwnUserIdentity = await this.olmMachine.getIdentity(
new RustSdkCryptoJs.UserId(this.userId),
);
const [request, outgoingRequest]: [RustSdkCryptoJs.VerificationRequest, RustSdkCryptoJs.ToDeviceRequest] =
await user.requestVerification(
this.supportedVerificationMethods?.map(verificationMethodIdentifierToMethod),
);
await this.outgoingRequestProcessor.makeOutgoingRequest(outgoingRequest);
return new RustVerificationRequest(request, this.outgoingRequestProcessor);
*/
throw new Error("not implemented");
}

Expand All @@ -526,15 +545,29 @@ export class RustCrypto implements CryptoBackend {
*
* If a verification is already in flight, returns it. Otherwise, initiates a new one.
*
* Implementation of {@link CryptoApi#requestDeviceVerification }.
* Implementation of {@link CryptoApi#requestDeviceVerification}.
*
* @param userId - ID of the owner of the device to verify
* @param deviceId - ID of the device to verify
*
* @returns a VerificationRequest when the request has been sent to the other party.
*/
public requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest> {
throw new Error("not implemented");
public async requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest> {
const device: RustSdkCryptoJs.Device | undefined = await this.olmMachine.getDevice(
new RustSdkCryptoJs.UserId(userId),
new RustSdkCryptoJs.DeviceId(deviceId),
);

if (!device) {
throw new Error("Not a known device");
}

const [request, outgoingRequest]: [RustSdkCryptoJs.VerificationRequest, RustSdkCryptoJs.ToDeviceRequest] =
await device.requestVerification(
this.supportedVerificationMethods?.map(verificationMethodIdentifierToMethod),
);
await this.outgoingRequestProcessor.makeOutgoingRequest(outgoingRequest);
return new RustVerificationRequest(request, this.outgoingRequestProcessor);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit 11b1cf8

Please sign in to comment.