Skip to content

Commit e3e13b3

Browse files
committed
tests: Cross-signing keys support in E2EKeyReceiver
Have `E2EKeyReceiver` collect uploaded cross-signing keys, so that they can be returned by `E2EKeyResponder`.
1 parent 316475d commit e3e13b3

File tree

6 files changed

+64
-50
lines changed

6 files changed

+64
-50
lines changed

spec/integ/crypto/cross-signing.spec.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ describe("cross-signing", () => {
137137
const authDict = { type: "test" };
138138
await bootstrapCrossSigning(authDict);
139139

140-
// check the cross-signing keys upload
141-
expect(fetchMock.called("upload-keys")).toBeTruthy();
142-
const [, keysOpts] = fetchMock.lastCall("upload-keys")!;
140+
// check the cross-signing keys have been uploaded
141+
expect(fetchMock.called("upload-cross-signing-keys")).toBeTruthy();
142+
const [, keysOpts] = fetchMock.lastCall("upload-cross-signing-keys")!;
143143
const keysBody = JSON.parse(keysOpts!.body as string);
144144
expect(keysBody.auth).toEqual(authDict); // check uia dict was passed
145145
// there should be a key of each type
@@ -420,15 +420,18 @@ describe("cross-signing", () => {
420420
return new Promise<any>((resolve) => {
421421
fetchMock.post(
422422
{
423-
url: new RegExp("/_matrix/client/v3/keys/device_signing/upload"),
424-
name: "upload-keys",
423+
url: new URL(
424+
"/_matrix/client/v3/keys/device_signing/upload",
425+
aliceClient.getHomeserverUrl(),
426+
).toString(),
427+
name: "upload-cross-signing-keys",
425428
},
426429
(url, options) => {
427430
const content = JSON.parse(options.body as string);
428431
resolve(content);
429432
return {};
430433
},
431-
// Override the routes define in `mockSetupCrossSigningRequests`
434+
// Override the route defined in E2EKeyReceiver
432435
{ overwriteRoutes: true },
433436
);
434437
});

spec/integ/crypto/device-dehydration.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ async function initializeSecretStorage(
181181
const e2eKeyReceiver = new E2EKeyReceiver(homeserverUrl);
182182
const e2eKeyResponder = new E2EKeyResponder(homeserverUrl);
183183
e2eKeyResponder.addKeyReceiver(userId, e2eKeyReceiver);
184-
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {});
185184
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {});
186185
const accountData: Map<string, object> = new Map();
187186
fetchMock.get("glob:http://*/_matrix/client/v3/user/*/account_data/*", (url, opts) => {

spec/test-utils/E2EKeyReceiver.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import debugFunc from "debug";
18-
import { type Debugger } from "debug";
17+
import debugFunc, { type Debugger } from "debug";
1918
import fetchMock from "fetch-mock-jest";
2019

2120
import type { IDeviceKeys, IOneTimeKey } from "../../src/@types/crypto";
21+
import type { CrossSigningKeys } from "../../src";
2222

2323
/** Interface implemented by classes that intercept `/keys/upload` requests from test clients to catch the uploaded keys
2424
*
@@ -55,14 +55,15 @@ export class E2EKeyReceiver implements IE2EKeyReceiver {
5555
private readonly debug: Debugger;
5656

5757
private deviceKeys: IDeviceKeys | null = null;
58+
private crossSigningKeys: CrossSigningKeys | null = null;
5859
private oneTimeKeys: Record<string, IOneTimeKey> = {};
5960
private readonly oneTimeKeysPromise: Promise<void>;
6061

6162
/**
6263
* Construct a new E2EKeyReceiver.
6364
*
64-
* It will immediately register an intercept of `/keys/uploads` requests for the given homeserverUrl.
65-
* Only /upload requests made to this server will be intercepted: this allows a single test to use more than one
65+
* It will immediately register an intercept of `/keys/uploads` and `/keys/device_signing/upload` requests for the given homeserverUrl.
66+
* Only requests made to this server will be intercepted: this allows a single test to use more than one
6667
* client and have the keys collected separately.
6768
*
6869
* @param homeserverUrl - the Homeserver Url of the client under test.
@@ -77,6 +78,14 @@ export class E2EKeyReceiver implements IE2EKeyReceiver {
7778

7879
fetchMock.post(new URL("/_matrix/client/v3/keys/upload", homeserverUrl).toString(), listener);
7980
});
81+
82+
fetchMock.post(
83+
{
84+
url: new URL("/_matrix/client/v3/keys/device_signing/upload", homeserverUrl).toString(),
85+
name: "upload-cross-signing-keys",
86+
},
87+
(url, options) => this.onSigningKeyUploadRequest(options),
88+
);
8089
}
8190

8291
private async onKeyUploadRequest(onOnTimeKeysUploaded: () => void, options: RequestInit): Promise<object> {
@@ -113,6 +122,18 @@ export class E2EKeyReceiver implements IE2EKeyReceiver {
113122
};
114123
}
115124

125+
private async onSigningKeyUploadRequest(request: RequestInit): Promise<object> {
126+
const content = JSON.parse(request.body as string);
127+
if (this.crossSigningKeys) {
128+
throw new Error("Application attempted to upload E2E cross-signing keys multiple times");
129+
}
130+
this.debug(`received cross-signing keys`);
131+
// Remove UIA data
132+
delete content["auth"];
133+
this.crossSigningKeys = content;
134+
return {};
135+
}
136+
116137
/** Get the uploaded Ed25519 key
117138
*
118139
* If device keys have not yet been uploaded, throws an error
@@ -150,6 +171,13 @@ export class E2EKeyReceiver implements IE2EKeyReceiver {
150171
return this.deviceKeys;
151172
}
152173

174+
/**
175+
* If cross-signing keys have been uploaded, return them. Else return null.
176+
*/
177+
public getUploadedCrossSigningKeys(): CrossSigningKeys | null {
178+
return this.crossSigningKeys;
179+
}
180+
153181
/**
154182
* If one-time keys have already been uploaded, return them. Otherwise,
155183
* set up an expectation that the keys will be uploaded, and wait for

spec/test-utils/E2EKeyResponder.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
import fetchMock from "fetch-mock-jest";
1818

1919
import { MapWithDefault } from "../../src/utils";
20-
import { type IDownloadKeyResult } from "../../src";
20+
import { type IDownloadKeyResult, type SigningKeys } from "../../src";
2121
import { type IDeviceKeys } from "../../src/@types/crypto";
2222
import { type E2EKeyReceiver } from "./E2EKeyReceiver";
2323

@@ -50,35 +50,42 @@ export class E2EKeyResponder {
5050
const content = JSON.parse(options.body as string);
5151
const usersToReturn = Object.keys(content["device_keys"]);
5252
const response = {
53-
device_keys: {} as { [userId: string]: any },
54-
master_keys: {} as { [userId: string]: any },
55-
self_signing_keys: {} as { [userId: string]: any },
56-
user_signing_keys: {} as { [userId: string]: any },
57-
failures: {} as { [serverName: string]: any },
58-
};
53+
device_keys: {},
54+
master_keys: {},
55+
self_signing_keys: {},
56+
user_signing_keys: {},
57+
failures: {},
58+
} as IDownloadKeyResult;
5959
for (const user of usersToReturn) {
60-
const userKeys = this.deviceKeysByUserByDevice.get(user);
61-
if (userKeys !== undefined) {
62-
response.device_keys[user] = Object.fromEntries(userKeys.entries());
63-
}
64-
60+
// First see if we have an E2EKeyReceiver for this user, and if so, return any keys that have been uploaded
6561
const e2eKeyReceiver = this.e2eKeyReceiversByUser.get(user);
6662
if (e2eKeyReceiver !== undefined) {
6763
const deviceKeys = e2eKeyReceiver.getUploadedDeviceKeys();
6864
if (deviceKeys !== null) {
6965
response.device_keys[user] ??= {};
7066
response.device_keys[user][deviceKeys.device_id] = deviceKeys;
7167
}
68+
const crossSigningKeys = e2eKeyReceiver.getUploadedCrossSigningKeys();
69+
if (crossSigningKeys !== null) {
70+
response.master_keys![user] = crossSigningKeys["master_key"];
71+
response.self_signing_keys![user] = crossSigningKeys["self_signing_key"] as SigningKeys;
72+
}
7273
}
7374

75+
// Mix in any keys that have been added explicitly to this E2EKeyResponder.
76+
const userKeys = this.deviceKeysByUserByDevice.get(user);
77+
if (userKeys !== undefined) {
78+
response.device_keys[user] ??= {};
79+
Object.assign(response.device_keys[user], Object.fromEntries(userKeys.entries()));
80+
}
7481
if (this.masterKeysByUser.hasOwnProperty(user)) {
75-
response.master_keys[user] = this.masterKeysByUser[user];
82+
response.master_keys![user] = this.masterKeysByUser[user];
7683
}
7784
if (this.selfSigningKeysByUser.hasOwnProperty(user)) {
78-
response.self_signing_keys[user] = this.selfSigningKeysByUser[user];
85+
response.self_signing_keys![user] = this.selfSigningKeysByUser[user];
7986
}
8087
if (this.userSigningKeysByUser.hasOwnProperty(user)) {
81-
response.user_signing_keys[user] = this.userSigningKeysByUser[user];
88+
response.user_signing_keys![user] = this.userSigningKeysByUser[user];
8289
}
8390
}
8491
return response;

spec/test-utils/mockEndpoints.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ export function mockInitialApiRequests(homeserverUrl: string, userId: string = "
4343
}
4444

4545
/**
46-
* Mock the requests needed to set up cross signing
46+
* Mock the requests needed to set up cross signing, besides those provided by {@link E2EKeyReceiver}.
4747
*
4848
* Return 404 error for `GET _matrix/client/v3/user/:userId/account_data/:type` request
4949
* Return `{}` for `POST _matrix/client/v3/keys/signatures/upload` request (named `upload-sigs` for fetchMock check)
50-
* Return `{}` for `POST /_matrix/client/(unstable|v3)/keys/device_signing/upload` request (named `upload-keys` for fetchMock check)
5150
*/
5251
export function mockSetupCrossSigningRequests(): void {
5352
// have account_data requests return an empty object
@@ -58,16 +57,6 @@ export function mockSetupCrossSigningRequests(): void {
5857

5958
// we expect a request to upload signatures for our device ...
6059
fetchMock.post({ url: "path:/_matrix/client/v3/keys/signatures/upload", name: "upload-sigs" }, {});
61-
62-
// ... and one to upload the cross-signing keys (with UIA)
63-
fetchMock.post(
64-
// legacy crypto uses /unstable/; /v3/ is correct
65-
{
66-
url: new RegExp("/_matrix/client/(unstable|v3)/keys/device_signing/upload"),
67-
name: "upload-keys",
68-
},
69-
{},
70-
);
7160
}
7261

7362
/**

spec/unit/rust-crypto/rust-crypto.spec.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,10 +1548,6 @@ describe("RustCrypto", () => {
15481548
const e2eKeyReceiver = new E2EKeyReceiver("http://server");
15491549
const e2eKeyResponder = new E2EKeyResponder("http://server");
15501550
e2eKeyResponder.addKeyReceiver(TEST_USER, e2eKeyReceiver);
1551-
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {
1552-
status: 200,
1553-
body: {},
1554-
});
15551551
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {
15561552
status: 200,
15571553
body: {},
@@ -1793,10 +1789,6 @@ describe("RustCrypto", () => {
17931789
error: "Not found",
17941790
},
17951791
});
1796-
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {
1797-
status: 200,
1798-
body: {},
1799-
});
18001792
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {
18011793
status: 200,
18021794
body: {},
@@ -1934,10 +1926,6 @@ describe("RustCrypto", () => {
19341926
error: "Not found",
19351927
},
19361928
});
1937-
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {
1938-
status: 200,
1939-
body: {},
1940-
});
19411929
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {
19421930
status: 200,
19431931
body: {},

0 commit comments

Comments
 (0)