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
Element-R: initial implementation of bootstrapCrossSigning
(#3368)
* Working `bootstrapCrossSigning` for rust * Remove unused `oldBackendOnly` * update tests * another test
This commit is contained in:
committed by
GitHub
parent
ece3ccb958
commit
bb5bccbf78
@ -38,10 +38,6 @@ const TEST_DEVICE_ID = "xzcvb";
|
|||||||
* to provide the most effective integration tests possible.
|
* to provide the most effective integration tests possible.
|
||||||
*/
|
*/
|
||||||
describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: string, initCrypto: InitCrypto) => {
|
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;
|
let aliceClient: MatrixClient;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -66,7 +62,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("bootstrapCrossSigning (before initialsync completes)", () => {
|
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
|
// have account_data requests return an empty object
|
||||||
fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {});
|
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)
|
// ... and one to upload the cross-signing keys (with UIA)
|
||||||
fetchMock.post(
|
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",
|
||||||
|
},
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
79
spec/unit/rust-crypto/CrossSigningIdentity.spec.ts
Normal file
79
spec/unit/rust-crypto/CrossSigningIdentity.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -103,9 +103,15 @@ describe("RustCrypto", () => {
|
|||||||
await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null);
|
await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bootstrapCrossSigning", async () => {
|
it("bootstrapCrossSigning delegates to CrossSigningIdentity", async () => {
|
||||||
const rustCrypto = await makeTestRustCrypto();
|
const rustCrypto = await makeTestRustCrypto();
|
||||||
|
const mockCrossSigningIdentity = {
|
||||||
|
bootstrapCrossSigning: jest.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
// @ts-ignore private property
|
||||||
|
rustCrypto.crossSigningIdentity = mockCrossSigningIdentity;
|
||||||
await rustCrypto.bootstrapCrossSigning({});
|
await rustCrypto.bootstrapCrossSigning({});
|
||||||
|
expect(mockCrossSigningIdentity.bootstrapCrossSigning).toHaveBeenCalledWith({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("isSecretStorageReady", async () => {
|
it("isSecretStorageReady", async () => {
|
||||||
|
101
src/rust-crypto/CrossSigningIdentity.ts
Normal file
101
src/rust-crypto/CrossSigningIdentity.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@ import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
|||||||
import { Device, DeviceMap } from "../models/device";
|
import { Device, DeviceMap } from "../models/device";
|
||||||
import { ServerSideSecretStorage } from "../secret-storage";
|
import { ServerSideSecretStorage } from "../secret-storage";
|
||||||
import { CrossSigningKey } from "../crypto/api";
|
import { CrossSigningKey } from "../crypto/api";
|
||||||
|
import { CrossSigningIdentity } from "./CrossSigningIdentity";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
||||||
@ -56,6 +57,7 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
private eventDecryptor: EventDecryptor;
|
private eventDecryptor: EventDecryptor;
|
||||||
private keyClaimManager: KeyClaimManager;
|
private keyClaimManager: KeyClaimManager;
|
||||||
private outgoingRequestProcessor: OutgoingRequestProcessor;
|
private outgoingRequestProcessor: OutgoingRequestProcessor;
|
||||||
|
private crossSigningIdentity: CrossSigningIdentity;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
/** The `OlmMachine` from the underlying rust crypto sdk. */
|
/** The `OlmMachine` from the underlying rust crypto sdk. */
|
||||||
@ -80,6 +82,7 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
||||||
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
|
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
|
||||||
this.eventDecryptor = new EventDecryptor(olmMachine);
|
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}
|
* Implementation of {@link CryptoApi#boostrapCrossSigning}
|
||||||
*/
|
*/
|
||||||
public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
|
public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void> {
|
||||||
logger.log("Cross-signing ready");
|
await this.crossSigningIdentity.bootstrapCrossSigning(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user