You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Integration test for bootstrapCrossSigning
(#3355)
* Stub implementation of bootstrapCrossSigning * Integration test for `bootstrapCrossSigning`
This commit is contained in:
committed by
GitHub
parent
63abd00ca7
commit
7ff44d4a50
117
spec/integ/cross-signing.spec.ts
Normal file
117
spec/integ/cross-signing.spec.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 fetchMock from "fetch-mock-jest";
|
||||||
|
import "fake-indexeddb/auto";
|
||||||
|
import { IDBFactory } from "fake-indexeddb";
|
||||||
|
|
||||||
|
import { CRYPTO_BACKENDS, InitCrypto } from "../test-utils/test-utils";
|
||||||
|
import { createClient, MatrixClient, UIAuthCallback } from "../../src";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||||
|
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
|
||||||
|
// eslint-disable-next-line no-global-assign
|
||||||
|
indexedDB = new IDBFactory();
|
||||||
|
});
|
||||||
|
|
||||||
|
const TEST_USER_ID = "@alice:localhost";
|
||||||
|
const TEST_DEVICE_ID = "xzcvb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for cross-signing functionality.
|
||||||
|
*
|
||||||
|
* These tests work by intercepting HTTP requests via fetch-mock rather than mocking out bits of the client, so as
|
||||||
|
* to provide the most effective integration tests possible.
|
||||||
|
*/
|
||||||
|
describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: string, initCrypto: InitCrypto) => {
|
||||||
|
// oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the
|
||||||
|
// Rust backend. Once we have full support in the rust sdk, it will go away.
|
||||||
|
const oldBackendOnly = backend === "rust-sdk" ? test.skip : test;
|
||||||
|
|
||||||
|
let aliceClient: MatrixClient;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// anything that we don't have a specific matcher for silently returns a 404
|
||||||
|
fetchMock.catch(404);
|
||||||
|
fetchMock.config.warnOnFallback = false;
|
||||||
|
|
||||||
|
const homeserverUrl = "https://alice-server.com";
|
||||||
|
aliceClient = createClient({
|
||||||
|
baseUrl: homeserverUrl,
|
||||||
|
userId: TEST_USER_ID,
|
||||||
|
accessToken: "akjgkrgjs",
|
||||||
|
deviceId: TEST_DEVICE_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
await initCrypto(aliceClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await aliceClient.stopClient();
|
||||||
|
fetchMock.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("bootstrapCrossSigning (before initialsync completes)", () => {
|
||||||
|
oldBackendOnly("publishes keys if none were yet published", async () => {
|
||||||
|
// have account_data requests return an empty object
|
||||||
|
fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {});
|
||||||
|
|
||||||
|
// we expect a request to upload signatures for our device ...
|
||||||
|
fetchMock.post({ url: "path:/_matrix/client/v3/keys/signatures/upload", name: "upload-sigs" }, {});
|
||||||
|
|
||||||
|
// ... and one to upload the cross-signing keys (with UIA)
|
||||||
|
fetchMock.post(
|
||||||
|
{ url: "path:/_matrix/client/unstable/keys/device_signing/upload", name: "upload-keys" },
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// provide a UIA callback, so that the cross-signing keys are uploaded
|
||||||
|
const authDict = { type: "test" };
|
||||||
|
const uiaCallback: UIAuthCallback<void> = async (makeRequest) => {
|
||||||
|
await makeRequest(authDict);
|
||||||
|
};
|
||||||
|
|
||||||
|
// now bootstrap cross signing, and check it resolves successfully
|
||||||
|
await aliceClient.bootstrapCrossSigning({
|
||||||
|
authUploadDeviceSigningKeys: uiaCallback,
|
||||||
|
});
|
||||||
|
|
||||||
|
// check the cross-signing keys upload
|
||||||
|
expect(fetchMock.called("upload-keys")).toBeTruthy();
|
||||||
|
const [, keysOpts] = fetchMock.lastCall("upload-keys")!;
|
||||||
|
const keysBody = JSON.parse(keysOpts!.body as string);
|
||||||
|
expect(keysBody.auth).toEqual(authDict); // check uia dict was passed
|
||||||
|
// there should be a key of each type
|
||||||
|
// master key is signed by the device
|
||||||
|
expect(keysBody).toHaveProperty(`master_key.signatures.[${TEST_USER_ID}].[ed25519:${TEST_DEVICE_ID}]`);
|
||||||
|
const masterKeyId = Object.keys(keysBody.master_key.keys)[0];
|
||||||
|
// ssk and usk are signed by the master key
|
||||||
|
expect(keysBody).toHaveProperty(`self_signing_key.signatures.[${TEST_USER_ID}].[${masterKeyId}]`);
|
||||||
|
expect(keysBody).toHaveProperty(`user_signing_key.signatures.[${TEST_USER_ID}].[${masterKeyId}]`);
|
||||||
|
const sskId = Object.keys(keysBody.self_signing_key.keys)[0];
|
||||||
|
|
||||||
|
// check the publish call
|
||||||
|
expect(fetchMock.called("upload-sigs")).toBeTruthy();
|
||||||
|
const [, sigsOpts] = fetchMock.lastCall("upload-sigs")!;
|
||||||
|
const body = JSON.parse(sigsOpts!.body as string);
|
||||||
|
// there should be a signature for our device, by our self-signing key.
|
||||||
|
expect(body).toHaveProperty(
|
||||||
|
`[${TEST_USER_ID}].[${TEST_DEVICE_ID}].signatures.[${TEST_USER_ID}].[${sskId}]`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2758,6 +2758,10 @@ describe("MatrixClient", function () {
|
|||||||
expect(() => client.isCrossSigningReady()).toThrow("End-to-end encryption disabled");
|
expect(() => client.isCrossSigningReady()).toThrow("End-to-end encryption disabled");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("bootstrapCrossSigning", () => {
|
||||||
|
expect(() => client.bootstrapCrossSigning({})).toThrow("End-to-end encryption disabled");
|
||||||
|
});
|
||||||
|
|
||||||
it("isSecretStorageReady", () => {
|
it("isSecretStorageReady", () => {
|
||||||
expect(() => client.isSecretStorageReady()).toThrow("End-to-end encryption disabled");
|
expect(() => client.isSecretStorageReady()).toThrow("End-to-end encryption disabled");
|
||||||
});
|
});
|
||||||
@ -2769,6 +2773,7 @@ describe("MatrixClient", function () {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockCryptoBackend = {
|
mockCryptoBackend = {
|
||||||
isCrossSigningReady: jest.fn(),
|
isCrossSigningReady: jest.fn(),
|
||||||
|
bootstrapCrossSigning: jest.fn(),
|
||||||
isSecretStorageReady: jest.fn(),
|
isSecretStorageReady: jest.fn(),
|
||||||
stop: jest.fn().mockResolvedValue(undefined),
|
stop: jest.fn().mockResolvedValue(undefined),
|
||||||
} as unknown as Mocked<CryptoBackend>;
|
} as unknown as Mocked<CryptoBackend>;
|
||||||
@ -2782,6 +2787,14 @@ describe("MatrixClient", function () {
|
|||||||
expect(mockCryptoBackend.isCrossSigningReady).toHaveBeenCalledTimes(1);
|
expect(mockCryptoBackend.isCrossSigningReady).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("bootstrapCrossSigning", async () => {
|
||||||
|
const testOpts = {};
|
||||||
|
mockCryptoBackend.bootstrapCrossSigning.mockResolvedValue(undefined);
|
||||||
|
await client.bootstrapCrossSigning(testOpts);
|
||||||
|
expect(mockCryptoBackend.bootstrapCrossSigning).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockCryptoBackend.bootstrapCrossSigning).toHaveBeenCalledWith(testOpts);
|
||||||
|
});
|
||||||
|
|
||||||
it("isSecretStorageReady", async () => {
|
it("isSecretStorageReady", async () => {
|
||||||
client["cryptoBackend"] = mockCryptoBackend;
|
client["cryptoBackend"] = mockCryptoBackend;
|
||||||
const testResult = "test";
|
const testResult = "test";
|
||||||
|
@ -98,6 +98,11 @@ describe("RustCrypto", () => {
|
|||||||
await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false);
|
await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("bootstrapCrossSigning", async () => {
|
||||||
|
const rustCrypto = await makeTestRustCrypto();
|
||||||
|
await rustCrypto.bootstrapCrossSigning({});
|
||||||
|
});
|
||||||
|
|
||||||
it("isSecretStorageReady", async () => {
|
it("isSecretStorageReady", async () => {
|
||||||
const rustCrypto = await makeTestRustCrypto();
|
const rustCrypto = await makeTestRustCrypto();
|
||||||
await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false);
|
await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false);
|
||||||
|
@ -2747,15 +2747,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
*
|
*
|
||||||
* This function:
|
* This function:
|
||||||
* - creates new cross-signing keys if they are not found locally cached nor in
|
* - creates new cross-signing keys if they are not found locally cached nor in
|
||||||
* secret storage (if it has been setup)
|
* secret storage (if it has been set up)
|
||||||
*
|
*
|
||||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
* @deprecated Prefer {@link CryptoApi.bootstrapCrossSigning | `CryptoApi.bootstrapCrossSigning`}.
|
||||||
*/
|
*/
|
||||||
public bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
|
public bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
return this.crypto.bootstrapCrossSigning(opts);
|
return this.cryptoBackend.bootstrapCrossSigning(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,6 +137,22 @@ export interface CryptoApi {
|
|||||||
*/
|
*/
|
||||||
isCrossSigningReady(): Promise<boolean>;
|
isCrossSigningReady(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap cross-signing by creating keys if needed.
|
||||||
|
*
|
||||||
|
* If everything is already set up, then no changes are made, so this is safe to run to ensure
|
||||||
|
* cross-signing is ready for use.
|
||||||
|
*
|
||||||
|
* This function:
|
||||||
|
* - creates new cross-signing keys if they are not found locally cached nor in
|
||||||
|
* secret storage (if it has been set up)
|
||||||
|
* - publishes the public keys to the server if they are not already published
|
||||||
|
* - stores the private keys in secret storage if secret storage is set up.
|
||||||
|
*
|
||||||
|
* @param opts - options object
|
||||||
|
*/
|
||||||
|
bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether secret storage:
|
* Checks whether secret storage:
|
||||||
* - is enabled on this account
|
* - is enabled on this account
|
||||||
|
@ -30,7 +30,7 @@ import { RoomEncryptor } from "./RoomEncryptor";
|
|||||||
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
|
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
|
||||||
import { KeyClaimManager } from "./KeyClaimManager";
|
import { KeyClaimManager } from "./KeyClaimManager";
|
||||||
import { MapWithDefault } from "../utils";
|
import { MapWithDefault } from "../utils";
|
||||||
import { DeviceVerificationStatus } from "../crypto-api";
|
import { BootstrapCrossSigningOpts, DeviceVerificationStatus } from "../crypto-api";
|
||||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
||||||
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
||||||
import { Device, DeviceMap } from "../models/device";
|
import { Device, DeviceMap } from "../models/device";
|
||||||
@ -324,6 +324,13 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link CryptoApi#boostrapCrossSigning}
|
||||||
|
*/
|
||||||
|
public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
|
||||||
|
logger.log("Cross-signing ready");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link CryptoApi#isSecretStorageReady}
|
* Implementation of {@link CryptoApi#isSecretStorageReady}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user