Skip to content

Commit

Permalink
Support MSC3983: Keys Claim request
Browse files Browse the repository at this point in the history
  • Loading branch information
turt2live committed Mar 23, 2023
1 parent e009ae7 commit f4a15ac
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 1 deletion.
7 changes: 6 additions & 1 deletion examples/encryption_appservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const worksImage = fs.readFileSync("./examples/static/it-works.png");
const registration: IAppserviceRegistration = {
"as_token": creds?.['asToken'] ?? "change_me",
"hs_token": creds?.['hsToken'] ?? "change_me",
"sender_localpart": "crypto_main_bot_user",
"sender_localpart": "crypto_main_bot_user2",
"namespaces": {
users: [{
regex: "@crypto.*:localhost",
Expand Down Expand Up @@ -95,6 +95,11 @@ const bot = appservice.botIntent;
});
}

appservice.on("query.key_claim", (req, done) => {
LogService.info("index", "Key claim request:", req);
done({});
});

appservice.on("room.failed_decryption", async (roomId: string, event: any, e: Error) => {
LogService.error("index", `Failed to decrypt ${roomId} ${event['event_id']} because `, e);
});
Expand Down
34 changes: 34 additions & 0 deletions src/appservice/Appservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
MatrixClient,
MemoryStorageProvider,
Metrics,
MSC3983KeyClaimResponse,
OTKAlgorithm,
redactObjectForLogging,
UserID,
Expand Down Expand Up @@ -291,6 +292,11 @@ export class Appservice extends EventEmitter {
this.app.get("/_matrix/app/v1/thirdparty/user", this.onThirdpartyUser.bind(this));
this.app.get("/_matrix/app/v1/thirdparty/location/:protocol", this.onThirdpartyLocation.bind(this));
this.app.get("/_matrix/app/v1/thirdparty/location", this.onThirdpartyLocation.bind(this));
this.app.post("/_matrix/app/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));

// Work around for https://github.com/matrix-org/synapse/issues/3780
this.app.post("/_matrix/app/v1/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
this.app.post("/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));

// Everything else can 404

Expand Down Expand Up @@ -931,6 +937,34 @@ export class Appservice extends EventEmitter {
});
}

private async onKeysClaim(req: express.Request, res: express.Response): Promise<any> {
if (!this.isAuthed(req)) {
res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
return;
}

if (typeof (req.body) !== "object") {
res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" });
return;
}

let responded = false;
this.emit("query.key_claim", req.body, async (result: MSC3983KeyClaimResponse | undefined | null) => {
if (result?.then) result = await result;
if (!result) {
res.status(405).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented"});
responded = true;
return;
}

res.status(200).json(result);
responded = true;
});
if (!responded) {
res.status(405).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented"});
}
}

private onThirdpartyProtocol(req: express.Request, res: express.Response) {
if (!this.isAuthed(req)) {
res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
Expand Down
22 changes: 22 additions & 0 deletions src/appservice/http_responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,25 @@ interface IProtocolInstance {
fields: { [field: string]: string };
network_id: string;
}

/**
* This is the response format for an MSC3983 `/keys/claim` request.
* See https://github.com/matrix-org/matrix-spec-proposals/pull/3983
* @deprecated This can be removed at any time without notice as it is unstable functionality.
* @category Application services
*/
export interface MSC3983KeyClaimResponse {
[userId: string]: {
[deviceId: string]: {
[keyId: string]: {
// for signed_curve25519 keys
key: string,
signatures: {
[userId: string]: {
[keyId: string]: string;
};
};
};
};
};
}
112 changes: 112 additions & 0 deletions test/appservice/AppserviceTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ describe('Appservice', () => {
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/user");
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/location/protocolId");
await verifyAuth("GET", "/_matrix/app/v1/thirdparty/location");
await verifyAuth("POST", "/_matrix/app/unstable/org.matrix.msc3983/keys/claim");
} finally {
appservice.stop();
}
Expand Down Expand Up @@ -1953,6 +1954,117 @@ describe('Appservice', () => {

});

it('should emit during MSC3983 key claim requests', async () => {
const port = await getPort();
const hsToken = "s3cret_token";
const appservice = new Appservice({
port: port,
bindAddress: '',
homeserverName: 'example.org',
homeserverUrl: 'https://localhost',
registration: {
as_token: "",
hs_token: hsToken,
sender_localpart: "_bot_",
namespaces: {
users: [{ exclusive: true, regex: "@_prefix_.*:.+" }],
rooms: [],
aliases: [],
},
},
});
appservice.botIntent.ensureRegistered = () => {
return null;
};

await appservice.begin();

try {
const query = {
"@alice:example.org": {
"DEVICEID": ["signed_curve25519"],
},
};
const response = {
"@alice:example.org": {
"DEVICEID": {
"signed_curve25519:AAAAHg": {
"key": "...",
"signatures": {
"@alice:example.org": {
"ed25519:DEVICEID": "..."
}
}
},
}
},
};

const claimSpy = simple.stub().callFn((q, fn) => {
expect(q).toStrictEqual(query);
fn(response);
});
appservice.on("query.key_claim", claimSpy);

const res = await requestPromise({
uri: `http://localhost:${port}/_matrix/app/unstable/org.matrix.msc3983/keys/claim`,
method: "POST",
qs: { access_token: hsToken },
json: query,
});
expect(res).toStrictEqual(response);
expect(claimSpy.callCount).toBe(1);
} finally {
appservice.stop();
}
});

it('should return a 405 for MSC3983 if not used by consumer', async () => {
const port = await getPort();
const hsToken = "s3cret_token";
const appservice = new Appservice({
port: port,
bindAddress: '',
homeserverName: 'example.org',
homeserverUrl: 'https://localhost',
registration: {
as_token: "",
hs_token: hsToken,
sender_localpart: "_bot_",
namespaces: {
users: [{ exclusive: true, regex: "@_prefix_.*:.+" }],
rooms: [],
aliases: [],
},
},
});
appservice.botIntent.ensureRegistered = () => {
return null;
};

await appservice.begin();

try {
const query = {
"@alice:example.org": {
"DEVICEID": ["signed_curve25519"],
},
};

// Note how we're not registering anything with the EventEmitter

const res = await requestPromise({
uri: `http://localhost:${port}/_matrix/app/unstable/org.matrix.msc3983/keys/claim`,
method: "POST",
qs: { access_token: hsToken },
json: query,
}).catch(e => ({body: e.response.body, statusCode: e.statusCode}));
expect(res).toStrictEqual({statusCode: 405, body: {errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented"}});
} finally {
appservice.stop();
}
});

it('should emit while querying users', async () => {
const port = await getPort();
const hsToken = "s3cret_token";
Expand Down

0 comments on commit f4a15ac

Please sign in to comment.