Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic encryption support for simple bots #142

Merged
merged 26 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b85ab6
Add early support for encryption: Bootstrap process
turt2live Jul 24, 2021
cc2f5ea
Link encryption up to sync
turt2live Jul 27, 2021
aab5610
First round of tests
turt2live Jul 28, 2021
1ab0779
Add another round of tests
turt2live Jul 28, 2021
8ce133d
Docs + ability to query device lists
turt2live Jul 28, 2021
c69d703
Set up a proper crypto store and use it to flag e2ee enablement
turt2live Jul 29, 2021
bc51f92
Initial device list tracking
turt2live Jul 29, 2021
e42a44b
WIP for sending encrypted events
turt2live Aug 4, 2021
bb79d52
Finish primary send path
turt2live Aug 7, 2021
075f053
Add tests for recent work + fix related bugs
turt2live Aug 9, 2021
343ced3
Organize imports
turt2live Aug 9, 2021
68911aa
Initial untested code for receiving decryption keys
turt2live Aug 9, 2021
e277656
Appease the linter
turt2live Aug 9, 2021
028635c
Support decryption
turt2live Aug 10, 2021
d96a873
Encrypt and decrypt by default when possible
turt2live Aug 11, 2021
2569dd6
Fix immediate tests
turt2live Aug 12, 2021
a0b3c7c
Add tests for missed code
turt2live Aug 14, 2021
59e2627
Organize imports
turt2live Aug 14, 2021
f4fecf9
Add support for media encryption/decryption
turt2live Aug 15, 2021
f58d7ea
Don't spam logs with buffer contents
turt2live Aug 16, 2021
9cfd907
Actually use sent_outbound_sessions table
turt2live Aug 16, 2021
5798099
Store outbound group session as soon as possible
turt2live Aug 16, 2021
161829b
Fix first message being undecryptable after 9cfd907
turt2live Aug 16, 2021
8e2188d
Add support for fallback keys
turt2live Aug 17, 2021
19b09bf
Protect against double device reuse
turt2live Aug 17, 2021
e07b0f4
Test to ensure CryptoClient stores outbound group sessions as inbound…
turt2live Aug 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.idea/
lib/
examples/storage/

# Logs
logs
Expand Down
104 changes: 104 additions & 0 deletions examples/encryption_bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
EncryptionAlgorithm,
FileMessageEventContent,
LogLevel,
LogService,
MatrixClient, MessageEvent,
RichConsoleLogger,
SimpleFsStorageProvider,
} from "../src";
import { SqliteCryptoStorageProvider } from "../src/storage/SqliteCryptoStorageProvider";
import * as fs from "fs";

LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogLevel.TRACE);
LogService.muteModule("Metrics");
LogService.trace = LogService.debug;

let creds = null;
try {
creds = require("../../examples/storage/encryption_bot.creds.json");
} catch (e) {
// ignore
}

const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost";
const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008";
const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN';
const storage = new SimpleFsStorageProvider("./examples/storage/encryption_bot.json");
const crypto = new SqliteCryptoStorageProvider("./examples/storage/encryption_bot.db");
const worksImage = fs.readFileSync("./examples/static/it-works.png");

const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto);

(async function() {
let encryptedRoomId: string;
const joinedRooms = await client.getJoinedRooms();
await client.crypto.prepare(joinedRooms); // init crypto because we're doing things before the client is started
for (const roomId of joinedRooms) {
if (await client.crypto.isRoomEncrypted(roomId)) {
encryptedRoomId = roomId;
break;
}
}
if (!encryptedRoomId) {
encryptedRoomId = await client.createRoom({
invite: [dmTarget],
is_direct: true,
visibility: "private",
preset: "trusted_private_chat",
initial_state: [
{type: "m.room.encryption", state_key: "", content: {algorithm: EncryptionAlgorithm.MegolmV1AesSha2}},
{type: "m.room.guest_access", state_key: "", content: {guest_access: "can_join"}},
],
});
}

client.on("room.failed_decryption", async (roomId: string, event: any, e: Error) => {
LogService.error("index", `Failed to decrypt ${roomId} ${event['event_id']} because `, e);
});

client.on("room.message", async (roomId: string, event: any) => {
if (roomId !== encryptedRoomId) return;

const message = new MessageEvent(event);

if (message.sender === (await client.getUserId()) && message.messageType === "m.notice") {
// yay, we decrypted our own message. Communicate that back for testing purposes.
const encrypted = await client.crypto.encryptMedia(Buffer.from(worksImage));
const mxc = await client.uploadContent(encrypted.buffer);
await client.sendMessage(roomId, {
msgtype: "m.image",
body: "it-works.png",
info: {
// XXX: We know these details, so have hardcoded them.
w: 256,
h: 256,
mimetype: "image/png",
size: worksImage.length,
},
file: {
url: mxc,
...encrypted.file,
},
});
return;
}

if (message.messageType === "m.image") {
const fileEvent = new MessageEvent<FileMessageEventContent>(message.raw);
const decrypted = await client.crypto.decryptMedia(fileEvent.content.file);
fs.writeFileSync('./examples/storage/decrypted.png', decrypted);
await client.unstableApis.addReactionToEvent(roomId, fileEvent.eventId, 'Decrypted');
return;
}

if (message.messageType !== "m.text") return;
if (message.textBody.startsWith("!ping")) {
await client.replyNotice(roomId, event, "Pong");
}
});

LogService.info("index", "Starting bot...");
await client.start();
})();
Binary file added examples/static/it-works.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"test": "ts-mocha --project ./tsconfig.json test/*Test.ts test/**/*.ts",
"build:examples": "tsc -p tsconfig-examples.json",
"example:appservice": "yarn build:examples && node lib/examples/appservice.js",
"example:login_register": "yarn build:examples && node lib/examples/login_register.js"
"example:login_register": "yarn build:examples && node lib/examples/login_register.js",
"example:encryption_bot": "yarn build:examples && node lib/examples/encryption_bot.js"
},
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
Expand All @@ -47,8 +48,13 @@
"scripts/*",
"tsconfig.json"
],
"optionalDependencies": {
"better-sqlite3": "^7.4.3"
},
"dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.4.tgz",
"@types/express": "^4.17.7",
"another-json": "^0.2.0",
"chalk": "^4.1.0",
"express": "^4.17.1",
"glob-to-regexp": "^0.4.1",
Expand All @@ -64,9 +70,11 @@
"sanitize-html": "^2.3.2"
},
"devDependencies": {
"@types/better-sqlite3": "^5.4.3",
"@types/expect": "^24.3.0",
"@types/mocha": "^8.0.1",
"@types/node": "10",
"@types/simple-mock": "^0.8.2",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/eslint-plugin-tslint": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
Expand All @@ -81,6 +89,6 @@
"tmp": "^0.2.1",
"ts-mocha": "^7.0.0",
"tslint": "^6.1.3",
"typescript": "^3.9.7"
"typescript": "^4.3.5"
}
}
Loading