1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Element-R: initial implementation of bootstrapCrossSigning (#3368)

* Working `bootstrapCrossSigning` for rust

* Remove unused `oldBackendOnly`

* update tests

* another test
This commit is contained in:
Richard van der Hoff
2023-05-16 21:30:32 +01:00
committed by GitHub
parent ece3ccb958
commit bb5bccbf78
5 changed files with 197 additions and 8 deletions

View File

@ -38,10 +38,6 @@ const TEST_DEVICE_ID = "xzcvb";
* 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 () => {
@ -66,7 +62,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
});
describe("bootstrapCrossSigning (before initialsync completes)", () => {
oldBackendOnly("publishes keys if none were yet published", async () => {
it("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", {});
@ -75,7 +71,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
// ... and one to upload the cross-signing keys (with UIA)
fetchMock.post(
{ url: "path:/_matrix/client/unstable/keys/device_signing/upload", name: "upload-keys" },
// legacy crypto uses /unstable/; /v3/ is correct
{
url: new RegExp("/_matrix/client/(unstable|v3)/keys/device_signing/upload"),
name: "upload-keys",
},
{},
);

View File

@ -0,0 +1,79 @@
/*
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 { Mocked } from "jest-mock";
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
import { CrossSigningIdentity } from "../../../src/rust-crypto/CrossSigningIdentity";
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
describe("CrossSigningIdentity", () => {
describe("bootstrapCrossSigning", () => {
/** the CrossSigningIdentity implementation under test */
let crossSigning: CrossSigningIdentity;
/** a mocked-up OlmMachine which crossSigning is connected to */
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
/** A mock OutgoingRequestProcessor which crossSigning is connected to */
let outgoingRequestProcessor: Mocked<OutgoingRequestProcessor>;
beforeEach(async () => {
await RustSdkCryptoJs.initAsync();
olmMachine = {
crossSigningStatus: jest.fn(),
bootstrapCrossSigning: jest.fn(),
close: jest.fn(),
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
outgoingRequestProcessor = {
makeOutgoingRequest: jest.fn(),
} as unknown as Mocked<OutgoingRequestProcessor>;
crossSigning = new CrossSigningIdentity(olmMachine, outgoingRequestProcessor);
});
it("should do nothing if keys are present on-device and in secret storage", async () => {
olmMachine.crossSigningStatus.mockResolvedValue({
hasMaster: true,
hasSelfSigning: true,
hasUserSigning: true,
});
// TODO: secret storage
await crossSigning.bootstrapCrossSigning({});
expect(olmMachine.bootstrapCrossSigning).not.toHaveBeenCalled();
expect(outgoingRequestProcessor.makeOutgoingRequest).not.toHaveBeenCalled();
});
it("should call bootstrapCrossSigning if a reset is forced", async () => {
olmMachine.bootstrapCrossSigning.mockResolvedValue([]);
await crossSigning.bootstrapCrossSigning({ setupNewCrossSigning: true });
expect(olmMachine.bootstrapCrossSigning).toHaveBeenCalledWith(true);
});
it("should call bootstrapCrossSigning if we need new keys", async () => {
olmMachine.crossSigningStatus.mockResolvedValue({
hasMaster: false,
hasSelfSigning: false,
hasUserSigning: false,
});
olmMachine.bootstrapCrossSigning.mockResolvedValue([]);
await crossSigning.bootstrapCrossSigning({});
expect(olmMachine.bootstrapCrossSigning).toHaveBeenCalledWith(true);
});
});
});

View File

@ -103,9 +103,15 @@ describe("RustCrypto", () => {
await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null);
});
it("bootstrapCrossSigning", async () => {
it("bootstrapCrossSigning delegates to CrossSigningIdentity", async () => {
const rustCrypto = await makeTestRustCrypto();
const mockCrossSigningIdentity = {
bootstrapCrossSigning: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore private property
rustCrypto.crossSigningIdentity = mockCrossSigningIdentity;
await rustCrypto.bootstrapCrossSigning({});
expect(mockCrossSigningIdentity.bootstrapCrossSigning).toHaveBeenCalledWith({});
});
it("isSecretStorageReady", async () => {

View File

@ -0,0 +1,101 @@
/*
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 { OlmMachine, CrossSigningStatus } from "@matrix-org/matrix-sdk-crypto-js";
import { BootstrapCrossSigningOpts } from "../crypto-api";
import { logger } from "../logger";
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
import { UIAuthCallback } from "../interactive-auth";
/** Manages the cross-signing keys for our own user.
*/
export class CrossSigningIdentity {
public constructor(
private readonly olmMachine: OlmMachine,
private readonly outgoingRequestProcessor: OutgoingRequestProcessor,
) {}
/**
* Initialise our cross-signing keys by creating new keys if they do not exist, and uploading to the server
*/
public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
if (opts.setupNewCrossSigning) {
await this.resetCrossSigning(opts.authUploadDeviceSigningKeys);
return;
}
const olmDeviceStatus: CrossSigningStatus = await this.olmMachine.crossSigningStatus();
const privateKeysInSecretStorage = false; // TODO
const olmDeviceHasKeys =
olmDeviceStatus.hasMaster && olmDeviceStatus.hasUserSigning && olmDeviceStatus.hasSelfSigning;
// Log all relevant state for easier parsing of debug logs.
logger.log("bootStrapCrossSigning: starting", {
setupNewCrossSigning: opts.setupNewCrossSigning,
olmDeviceHasMaster: olmDeviceStatus.hasMaster,
olmDeviceHasUserSigning: olmDeviceStatus.hasUserSigning,
olmDeviceHasSelfSigning: olmDeviceStatus.hasSelfSigning,
privateKeysInSecretStorage,
});
if (!olmDeviceHasKeys && !privateKeysInSecretStorage) {
logger.log(
"bootStrapCrossSigning: Cross-signing private keys not found locally or in secret storage, creating new keys",
);
await this.resetCrossSigning(opts.authUploadDeviceSigningKeys);
} else if (olmDeviceHasKeys) {
logger.log("bootStrapCrossSigning: Olm device has private keys: exporting to secret storage");
await this.exportCrossSigningKeysToStorage();
} else if (privateKeysInSecretStorage) {
logger.log(
"bootStrapCrossSigning: Cross-signing private keys not found locally, but they are available " +
"in secret storage, reading storage and caching locally",
);
throw new Error("TODO");
}
// TODO: we might previously have bootstrapped cross-signing but not completed uploading the keys to the
// server -- in which case we should call OlmDevice.bootstrap_cross_signing. How do we know?
logger.log("bootStrapCrossSigning: complete");
}
/** Reset our cross-signing keys
*
* This method will:
* * Tell the OlmMachine to create new keys
* * Upload the new public keys and the device signature to the server
* * Upload the private keys to SSSS, if it is set up
*/
private async resetCrossSigning(authUploadDeviceSigningKeys?: UIAuthCallback<void>): Promise<void> {
const outgoingRequests: Array<OutgoingRequest> = await this.olmMachine.bootstrapCrossSigning(true);
logger.log("bootStrapCrossSigning: publishing keys to server");
for (const req of outgoingRequests) {
await this.outgoingRequestProcessor.makeOutgoingRequest(req, authUploadDeviceSigningKeys);
}
await this.exportCrossSigningKeysToStorage();
}
/**
* Extract the cross-signing keys from the olm machine and save them to secret storage, if it is configured
*
* (If secret storage is *not* configured, we assume that the export will happen when it is set up)
*/
private async exportCrossSigningKeysToStorage(): Promise<void> {
// TODO
}
}

View File

@ -36,6 +36,7 @@ import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
import { Device, DeviceMap } from "../models/device";
import { ServerSideSecretStorage } from "../secret-storage";
import { CrossSigningKey } from "../crypto/api";
import { CrossSigningIdentity } from "./CrossSigningIdentity";
/**
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
@ -56,6 +57,7 @@ export class RustCrypto implements CryptoBackend {
private eventDecryptor: EventDecryptor;
private keyClaimManager: KeyClaimManager;
private outgoingRequestProcessor: OutgoingRequestProcessor;
private crossSigningIdentity: CrossSigningIdentity;
public constructor(
/** The `OlmMachine` from the underlying rust crypto sdk. */
@ -80,6 +82,7 @@ export class RustCrypto implements CryptoBackend {
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
this.eventDecryptor = new EventDecryptor(olmMachine);
this.crossSigningIdentity = new CrossSigningIdentity(olmMachine, this.outgoingRequestProcessor);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -337,7 +340,7 @@ export class RustCrypto implements CryptoBackend {
* Implementation of {@link CryptoApi#boostrapCrossSigning}
*/
public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
logger.log("Cross-signing ready");
await this.crossSigningIdentity.bootstrapCrossSigning(opts);
}
/**