Skip to content

Commit fb121c2

Browse files
authored
Support MSC3983: Keys Claim request (#307)
* Support MSC3983: Keys Claim request See matrix-org/matrix-spec-proposals#3983 * Include test code * Appease the linter
1 parent e009ae7 commit fb121c2

File tree

5 files changed

+177
-2
lines changed

5 files changed

+177
-2
lines changed

examples/encryption_appservice.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const worksImage = fs.readFileSync("./examples/static/it-works.png");
3636
const registration: IAppserviceRegistration = {
3737
"as_token": creds?.['asToken'] ?? "change_me",
3838
"hs_token": creds?.['hsToken'] ?? "change_me",
39-
"sender_localpart": "crypto_main_bot_user",
39+
"sender_localpart": "crypto_main_bot_user2",
4040
"namespaces": {
4141
users: [{
4242
regex: "@crypto.*:localhost",
@@ -95,6 +95,11 @@ const bot = appservice.botIntent;
9595
});
9696
}
9797

98+
appservice.on("query.key_claim", (req, done) => {
99+
LogService.info("index", "Key claim request:", req);
100+
done({});
101+
});
102+
98103
appservice.on("room.failed_decryption", async (roomId: string, event: any, e: Error) => {
99104
LogService.error("index", `Failed to decrypt ${roomId} ${event['event_id']} because `, e);
100105
});

src/appservice/Appservice.ts

+34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
MatrixClient,
1818
MemoryStorageProvider,
1919
Metrics,
20+
MSC3983KeyClaimResponse,
2021
OTKAlgorithm,
2122
redactObjectForLogging,
2223
UserID,
@@ -291,6 +292,11 @@ export class Appservice extends EventEmitter {
291292
this.app.get("/_matrix/app/v1/thirdparty/user", this.onThirdpartyUser.bind(this));
292293
this.app.get("/_matrix/app/v1/thirdparty/location/:protocol", this.onThirdpartyLocation.bind(this));
293294
this.app.get("/_matrix/app/v1/thirdparty/location", this.onThirdpartyLocation.bind(this));
295+
this.app.post("/_matrix/app/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
296+
297+
// Workaround for https://github.com/matrix-org/synapse/issues/3780
298+
this.app.post("/_matrix/app/v1/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
299+
this.app.post("/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
294300

295301
// Everything else can 404
296302

@@ -931,6 +937,34 @@ export class Appservice extends EventEmitter {
931937
});
932938
}
933939

940+
private async onKeysClaim(req: express.Request, res: express.Response): Promise<any> {
941+
if (!this.isAuthed(req)) {
942+
res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
943+
return;
944+
}
945+
946+
if (typeof (req.body) !== "object") {
947+
res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" });
948+
return;
949+
}
950+
951+
let responded = false;
952+
this.emit("query.key_claim", req.body, async (result: MSC3983KeyClaimResponse | undefined | null) => {
953+
if (result?.then) result = await result;
954+
if (!result) {
955+
res.status(405).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
956+
responded = true;
957+
return;
958+
}
959+
960+
res.status(200).json(result);
961+
responded = true;
962+
});
963+
if (!responded) {
964+
res.status(405).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
965+
}
966+
}
967+
934968
private onThirdpartyProtocol(req: express.Request, res: express.Response) {
935969
if (!this.isAuthed(req)) {
936970
res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });

src/appservice/http_responses.ts

+22
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,25 @@ interface IProtocolInstance {
2222
fields: { [field: string]: string };
2323
network_id: string;
2424
}
25+
26+
/**
27+
* This is the response format for an MSC3983 `/keys/claim` request.
28+
* See https://github.com/matrix-org/matrix-spec-proposals/pull/3983
29+
* @deprecated This can be removed at any time without notice as it is unstable functionality.
30+
* @category Application services
31+
*/
32+
export interface MSC3983KeyClaimResponse {
33+
[userId: string]: {
34+
[deviceId: string]: {
35+
[keyId: string]: {
36+
// for signed_curve25519 keys
37+
key: string;
38+
signatures: {
39+
[userId: string]: {
40+
[keyId: string]: string;
41+
};
42+
};
43+
};
44+
};
45+
};
46+
}

src/e2ee/RustEngine.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ export class RustEngine {
134134
}
135135

136136
private async processKeysUploadRequest(request: KeysUploadRequest) {
137-
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/upload", null, JSON.parse(request.body));
137+
const body = JSON.parse(request.body);
138+
// delete body["one_time_keys"]; // use this to test MSC3983
139+
const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/upload", null, body);
138140
await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp));
139141
}
140142

test/appservice/AppserviceTest.ts

+112
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ describe('Appservice', () => {
803803
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/user");
804804
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/location/protocolId");
805805
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/location");
806+
await verifyAuth("POST", "/_matrix/app/unstable/org.matrix.msc3983/keys/claim");
806807
} finally {
807808
appservice.stop();
808809
}
@@ -1953,6 +1954,117 @@ describe('Appservice', () => {
19531954

19541955
});
19551956

1957+
it('should emit during MSC3983 key claim requests', async () => {
1958+
const port = await getPort();
1959+
const hsToken = "s3cret_token";
1960+
const appservice = new Appservice({
1961+
port: port,
1962+
bindAddress: '',
1963+
homeserverName: 'example.org',
1964+
homeserverUrl: 'https://localhost',
1965+
registration: {
1966+
as_token: "",
1967+
hs_token: hsToken,
1968+
sender_localpart: "_bot_",
1969+
namespaces: {
1970+
users: [{ exclusive: true, regex: "@_prefix_.*:.+" }],
1971+
rooms: [],
1972+
aliases: [],
1973+
},
1974+
},
1975+
});
1976+
appservice.botIntent.ensureRegistered = () => {
1977+
return null;
1978+
};
1979+
1980+
await appservice.begin();
1981+
1982+
try {
1983+
const query = {
1984+
"@alice:example.org": {
1985+
"DEVICEID": ["signed_curve25519"],
1986+
},
1987+
};
1988+
const response = {
1989+
"@alice:example.org": {
1990+
"DEVICEID": {
1991+
"signed_curve25519:AAAAHg": {
1992+
"key": "...",
1993+
"signatures": {
1994+
"@alice:example.org": {
1995+
"ed25519:DEVICEID": "...",
1996+
},
1997+
},
1998+
},
1999+
},
2000+
},
2001+
};
2002+
2003+
const claimSpy = simple.stub().callFn((q, fn) => {
2004+
expect(q).toStrictEqual(query);
2005+
fn(response);
2006+
});
2007+
appservice.on("query.key_claim", claimSpy);
2008+
2009+
const res = await requestPromise({
2010+
uri: `http://localhost:${port}/_matrix/app/unstable/org.matrix.msc3983/keys/claim`,
2011+
method: "POST",
2012+
qs: { access_token: hsToken },
2013+
json: query,
2014+
});
2015+
expect(res).toStrictEqual(response);
2016+
expect(claimSpy.callCount).toBe(1);
2017+
} finally {
2018+
appservice.stop();
2019+
}
2020+
});
2021+
2022+
it('should return a 405 for MSC3983 if not used by consumer', async () => {
2023+
const port = await getPort();
2024+
const hsToken = "s3cret_token";
2025+
const appservice = new Appservice({
2026+
port: port,
2027+
bindAddress: '',
2028+
homeserverName: 'example.org',
2029+
homeserverUrl: 'https://localhost',
2030+
registration: {
2031+
as_token: "",
2032+
hs_token: hsToken,
2033+
sender_localpart: "_bot_",
2034+
namespaces: {
2035+
users: [{ exclusive: true, regex: "@_prefix_.*:.+" }],
2036+
rooms: [],
2037+
aliases: [],
2038+
},
2039+
},
2040+
});
2041+
appservice.botIntent.ensureRegistered = () => {
2042+
return null;
2043+
};
2044+
2045+
await appservice.begin();
2046+
2047+
try {
2048+
const query = {
2049+
"@alice:example.org": {
2050+
"DEVICEID": ["signed_curve25519"],
2051+
},
2052+
};
2053+
2054+
// Note how we're not registering anything with the EventEmitter
2055+
2056+
const res = await requestPromise({
2057+
uri: `http://localhost:${port}/_matrix/app/unstable/org.matrix.msc3983/keys/claim`,
2058+
method: "POST",
2059+
qs: { access_token: hsToken },
2060+
json: query,
2061+
}).catch(e => ({ body: e.response.body, statusCode: e.statusCode }));
2062+
expect(res).toStrictEqual({ statusCode: 405, body: { errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" } });
2063+
} finally {
2064+
appservice.stop();
2065+
}
2066+
});
2067+
19562068
it('should emit while querying users', async () => {
19572069
const port = await getPort();
19582070
const hsToken = "s3cret_token";

0 commit comments

Comments
 (0)