]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
get send encrypted to send :)
[matrix_esp_thesis] / src / matrix.c
1 #include "matrix.h"\r
2 \r
3 #include <time.h>\r
4 #include <stdio.h>\r
5 #include <mjson.h>\r
6 \r
7 \r
8 #define LOGIN_REQUEST_SIZE 1024\r
9 #define LOGIN_RESPONSE_SIZE 1024\r
10 #define LOGIN_URL "/_matrix/client/v3/login"\r
11 \r
12 #define ENCRYPTED_REQUEST_SIZE 512\r
13 #define ENCRYPTED_EVENT_SIZE 1024\r
14 #define ROOMEVENT_REQUEST_SIZE 256\r
15 #define ROOMEVENT_RESPONSE_SIZE 1024\r
16 #define ROOMEVENT_URL "/_matrix/client/v3/rooms/%s/send/%s/%d"\r
17 \r
18 #define TODEVICE_EVENT_SIZE 512\r
19 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"\r
20 \r
21 #define KEYS_QUERY_URL "/_matrix/client/v3/keys/query"\r
22 #define KEYS_QUERY_REQUEST_SIZE 256\r
23 #define KEYS_QUERY_RESPONSE_SIZE 1024\r
24 \r
25 #define JSON_QUERY_SIZE 128\r
26 \r
27 \r
28 \r
29 void\r
30 Randomize(uint8_t * random, int randomLen)\r
31 {\r
32     static bool first = false;\r
33     if (first) { srand(time(0)); first = false; }\r
34 \r
35     for (int i = 0; i < randomLen; i++)\r
36     {\r
37         random[i] = rand() % 256;\r
38     }\r
39 }\r
40 \r
41 bool\r
42 JsonEscape(\r
43     char * sIn, int sInLen,\r
44     char * sOut, int sOutCap)\r
45 {\r
46     int sOutIndex = 0;\r
47 \r
48     for (int i = 0; i < sInLen; i++)\r
49     {\r
50         if (i >= sOutCap)\r
51             return false;\r
52         \r
53         if (sIn[i] == '.' ||\r
54             sIn[i] == '[' ||\r
55             sIn[i] == ']'\r
56         ) {\r
57             sOut[sOutIndex++] = '\\';\r
58         }\r
59         sOut[sOutIndex++] = sIn[i];\r
60     }\r
61 \r
62     if (sOutIndex < sOutCap)\r
63         sOut[sOutIndex] = '\0';\r
64 \r
65     return true;\r
66 }\r
67 \r
68 // TODO: in/outbound sessions\r
69 bool\r
70 MatrixOlmSessionInit(\r
71     MatrixOlmSession * session,\r
72     const char * deviceId)\r
73 {\r
74     memset(session, 0, sizeof(MatrixOlmSession));\r
75 \r
76     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
77     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
78 \r
79     session->deviceId = deviceId;\r
80 \r
81     session->session =\r
82         olm_session(session->memory);\r
83 \r
84     return session->session != NULL;\r
85 }\r
86 \r
87 bool\r
88 MatrixOlmSessionEncrypt(\r
89     MatrixOlmSession * session,\r
90     const char * plaintext,\r
91     char * outBuffer, int outBufferCap)\r
92 {\r
93     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
94     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
95 \r
96     size_t res = olm_encrypt(session->session,\r
97         plaintext, strlen(plaintext),\r
98         random, OLM_ENCRYPT_RANDOM_SIZE,\r
99         outBuffer, outBufferCap);\r
100 \r
101     return res != olm_error();\r
102 }\r
103 \r
104 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
105 bool\r
106 MatrixMegolmOutSessionInit(\r
107     MatrixMegolmOutSession * session,\r
108     const char * roomId)\r
109 {\r
110     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
111 \r
112     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
113     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
114 \r
115     session->roomId = roomId;\r
116 \r
117     session->session =\r
118         olm_outbound_group_session(session->memory);\r
119 \r
120     olm_init_outbound_group_session(\r
121         session->session,\r
122         random,\r
123         MEGOLM_INIT_RANDOM_SIZE);\r
124 \r
125     olm_outbound_group_session_id(session->session,\r
126         (uint8_t *)session->id,\r
127         MEGOLM_SESSION_ID_SIZE);\r
128         \r
129     olm_outbound_group_session_key(session->session,\r
130         (uint8_t *)session->key,\r
131         MEGOLM_SESSION_KEY_SIZE);\r
132     \r
133     return true;\r
134 }\r
135 \r
136 bool\r
137 MatrixMegolmOutSessionEncrypt(\r
138     MatrixMegolmOutSession * session,\r
139     const char * plaintext,\r
140     char * outBuffer, int outBufferCap)\r
141 {\r
142     size_t res = olm_group_encrypt(session->session,\r
143         (uint8_t *)plaintext, strlen(plaintext),\r
144         (uint8_t *)outBuffer, outBufferCap);\r
145 \r
146     return res != olm_error();\r
147 }\r
148 \r
149 \r
150 \r
151 bool\r
152 MatrixClientInit(\r
153     MatrixClient * client,\r
154     const char * server)\r
155 {\r
156     memset(client, 0, sizeof(MatrixClient));\r
157 \r
158     strcpy(client->server, server);\r
159 \r
160     return true;\r
161 }\r
162 \r
163 bool\r
164 MatrixClientSetAccessToken(\r
165     MatrixClient * client,\r
166     const char * accessToken)\r
167 {\r
168     int accessTokenLen = strlen(accessToken);\r
169 \r
170     if (accessTokenLen < ACCESS_TOKEN_SIZE - 1)\r
171         return false;\r
172 \r
173     for (int i = 0; i < accessTokenLen; i++)\r
174         client->accessToken[i] = accessToken[i];\r
175 \r
176     return true;\r
177 }\r
178 \r
179 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
180 bool\r
181 MatrixClientLoginPassword(\r
182     MatrixClient * client,\r
183     const char * username,\r
184     const char * password,\r
185     const char * displayName)\r
186 {\r
187     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
188 \r
189     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
190         "{"\r
191             "\"type\": \"m.login.password\","\r
192             "\"identifier\": {"\r
193                 "\"type\": \"m.id.user\","\r
194                 "\"user\": \"%s\""\r
195             "},"\r
196             "\"password\": \"%s\","\r
197             "\"initial_device_display_name\": \"%s\""\r
198         "}",\r
199         username,\r
200         password,\r
201         displayName);\r
202     \r
203     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
204     bool result =\r
205         MatrixHttpPost(client,\r
206             LOGIN_URL,\r
207             requestBuffer,\r
208             responseBuffer, LOGIN_RESPONSE_SIZE,\r
209             false);\r
210     \r
211     int responseLen = strlen(responseBuffer);\r
212     \r
213     if (!result)\r
214         return false;\r
215 \r
216     mjson_get_string(responseBuffer, responseLen,\r
217         "$.access_token",\r
218         client->accessToken, ACCESS_TOKEN_SIZE);\r
219     mjson_get_string(responseBuffer, responseLen,\r
220         "$.device_id",\r
221         client->deviceId, DEVICE_ID_SIZE);\r
222     mjson_get_string(responseBuffer, responseLen,\r
223         "$.expires_in_ms",\r
224         client->expireMs, EXPIRE_MS_SIZE);\r
225     mjson_get_string(responseBuffer, responseLen,\r
226         "$.refresh_token",\r
227         client->refreshToken, REFRESH_TOKEN_SIZE);\r
228 \r
229     return true;\r
230 }\r
231 \r
232 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
233 bool\r
234 MatrixClientSendEvent(\r
235     MatrixClient * client,\r
236     const char * roomId,\r
237     const char * msgType,\r
238     const char * msgBody)\r
239 {    \r
240     static char requestUrl[MAX_URL_LEN];\r
241     sprintf(requestUrl,\r
242         ROOMEVENT_URL, roomId, msgType, (int)time(NULL));\r
243 \r
244     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
245     bool result =\r
246         MatrixHttpPut(client,\r
247             requestUrl,\r
248             msgBody,\r
249             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
250             true);\r
251     \r
252     return result;\r
253 }\r
254 \r
255 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
256 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
257 bool\r
258 MatrixClientSendEventEncrypted(\r
259     MatrixClient * client,\r
260     const char * roomId,\r
261     const char * msgType,\r
262     const char * msgBody)\r
263 {\r
264     // event json\r
265     static char requestBuffer[ROOMEVENT_REQUEST_SIZE];\r
266     sprintf(requestBuffer,\r
267         "{"\r
268         "\"type\":\"%s\","\r
269         "\"content\":%s,"\r
270         "\"room_id\":\"%s\""\r
271         "}",\r
272         msgType,\r
273         msgBody,\r
274         roomId);\r
275 \r
276     // get megolm session\r
277     MatrixMegolmOutSession * outSession;\r
278     MatrixClientGetMegolmOutSession(client, roomId, &outSession);\r
279         \r
280     // encrypt\r
281     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
282     MatrixMegolmOutSessionEncrypt(outSession,\r
283         requestBuffer,\r
284         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
285 \r
286     // encrypted event json\r
287     const char * senderKey = client->deviceKey;\r
288     const char * sessionId = outSession->id;\r
289     const char * deviceId = client->deviceId;\r
290 \r
291     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
292     sprintf(encryptedEventBuffer,\r
293         "{"\r
294         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
295         "\"sender_key\":\"%s\","\r
296         "\"ciphertext\":\"%s\","\r
297         "\"session_id\":\"%s\","\r
298         "\"device_id\":\"%s\""\r
299         "}",\r
300         senderKey,\r
301         encryptedBuffer,\r
302         sessionId,\r
303         deviceId);\r
304 \r
305     // send\r
306     return MatrixClientSendEvent(client,\r
307         roomId,\r
308         "m.room.encrypted",\r
309         encryptedEventBuffer);\r
310 }\r
311 \r
312 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
313 bool\r
314 MatrixClientSync(\r
315     MatrixClient * client,\r
316     char * outSyncBuffer, int outSyncCap)\r
317 {\r
318     return\r
319         MatrixHttpGet(client,\r
320             "/_matrix/client/v3/sync",\r
321             outSyncBuffer, outSyncCap,\r
322             true);\r
323 }\r
324 \r
325 bool\r
326 MatrixClientShareMegolmOutSession(\r
327     MatrixClient * client,\r
328     const char * deviceId,\r
329     MatrixMegolmOutSession * session)\r
330 {\r
331     // generate room key event\r
332     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
333     sprintf(eventBuffer,\r
334         "{"\r
335             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
336             "\"room_id\":\"%s\","\r
337             "\"session_id\":\"%s\","\r
338             "\"session_key\":\"%s\""\r
339         "}",\r
340         session->roomId,\r
341         session->id,\r
342         session->key\r
343     );\r
344 \r
345     // get olm session\r
346     MatrixOlmSession * olmSession;\r
347     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
348 \r
349     // encrypt\r
350     char encryptedBuffer[KEY_SHARE_EVENT_LEN];\r
351     MatrixOlmSessionEncrypt(olmSession,\r
352         eventBuffer,\r
353         encryptedBuffer, KEY_SHARE_EVENT_LEN);\r
354 \r
355     // send\r
356     MatrixClientSendToDeviceEncrypted(client,\r
357         client->userId,\r
358         deviceId,\r
359         encryptedBuffer,\r
360         "m.room_key");\r
361 \r
362     return true;\r
363 }\r
364 \r
365 // bool\r
366 // MatrixClientSetMegolmOutSession(\r
367 //     MatrixClient * client,\r
368 //     const char * roomId,\r
369 //     MatrixMegolmOutSession session)\r
370 // {\r
371 //     if (client->numMegolmOutSessions < 10)\r
372 //     {\r
373 //         session.roomId = roomId;\r
374 //         client->megolmOutSessions[client->numMegolmOutSessions] = session;\r
375 //         client->numMegolmOutSessions++;\r
376 \r
377 //         return true;\r
378 //     }\r
379 //     return false;\r
380 // }\r
381 \r
382 bool\r
383 MatrixClientGetMegolmOutSession(\r
384     MatrixClient * client,\r
385     const char * roomId,\r
386     MatrixMegolmOutSession ** outSession)\r
387 {\r
388     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
389     {\r
390         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
391         {\r
392             *outSession = &client->megolmOutSessions[i];\r
393             return true;\r
394         }\r
395     }\r
396 \r
397     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
398     {\r
399         MatrixMegolmOutSessionInit(\r
400             &client->megolmOutSessions[client->numMegolmOutSessions],\r
401             roomId);\r
402 \r
403         *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];\r
404         \r
405         client->numMegolmOutSessions++;\r
406 \r
407         return true;\r
408     }\r
409 \r
410     return false;\r
411 }\r
412 \r
413 bool\r
414 MatrixClientGetOlmSession(\r
415     MatrixClient * client,\r
416     const char * deviceId,\r
417     MatrixOlmSession ** outSession)\r
418 {\r
419     for (int i = 0; i < client->numOlmSessions; i++)\r
420     {\r
421         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
422         {\r
423             *outSession = &client->olmSessions[i];\r
424             return true;\r
425         }\r
426     }\r
427 \r
428     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
429     {\r
430         MatrixOlmSessionInit(\r
431             &client->olmSessions[client->numOlmSessions],\r
432             deviceId);\r
433 \r
434         *outSession = &client->olmSessions[client->numOlmSessions];\r
435         \r
436         client->numOlmSessions++;\r
437 \r
438         return true;\r
439     }\r
440 \r
441     return false;\r
442 }\r
443 \r
444 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
445 bool\r
446 MatrixClientSendToDevice(\r
447     MatrixClient * client,\r
448     const char * userId,\r
449     const char * deviceId,\r
450     const char * message,\r
451     const char * msgType)\r
452 {\r
453     static char requestUrl[MAX_URL_LEN];\r
454     sprintf(requestUrl,\r
455         TODEVICE_URL, msgType, (int)time(NULL));\r
456 \r
457     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
458     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
459         "{"\r
460             "\"messages\": {"\r
461                 "\"%s\": {"\r
462                     "\"%s\":%s"\r
463                 "}"\r
464             "}"\r
465         "}",\r
466         userId,\r
467         deviceId,\r
468         message);\r
469 \r
470     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
471     bool result =\r
472         MatrixHttpPut(client,\r
473             requestUrl,\r
474             eventBuffer,\r
475             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
476             true);\r
477     \r
478     return result;\r
479 }\r
480 \r
481 bool\r
482 MatrixClientSendToDeviceEncrypted(\r
483     MatrixClient * client,\r
484     const char * userId,\r
485     const char * deviceId,\r
486     const char * message,\r
487     const char * msgType)\r
488 {\r
489     // get olm session\r
490     MatrixOlmSession * olmSession;\r
491     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
492 \r
493     // create event json\r
494     char deviceKey[DEVICE_KEY_SIZE];\r
495     MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);\r
496     const char * senderKey = client->deviceKey;\r
497     \r
498     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
499     sprintf(eventBuffer,\r
500         "{"\r
501         "\"type\": \"%s\","\r
502         "\"content\": \"%s\","\r
503         "\"sender\": \"%s\","\r
504         "\"recipient\": \"%s\","\r
505         "\"recipient_keys\": {"\r
506           "\"ed25519\": \"%s\""\r
507         "},"\r
508         "\"keys\": {"\r
509           "\"ed25519\": \"%s\""\r
510         "}"\r
511         "}",\r
512         msgType,\r
513         message,\r
514         client->userId,\r
515         userId, // recipient user id\r
516         deviceKey, // recipient device key\r
517         client->deviceKey);\r
518 \r
519     // encrypt\r
520     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
521     MatrixOlmSessionEncrypt(olmSession,\r
522         eventBuffer,\r
523         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
524 \r
525     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
526     sprintf(encryptedEventBuffer,\r
527         "{"\r
528         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
529         "\"sender_key\":\"%s\","\r
530         "\"ciphertext\":{"\r
531           "\"%s\":{"\r
532             "\"body\":\"%s\","\r
533             "\"type\":\"%d\""\r
534           "}"\r
535         "}"\r
536         "}",\r
537         senderKey,\r
538         deviceKey,\r
539         encryptedBuffer,\r
540         olmSession->type);\r
541 \r
542     // send\r
543     return MatrixClientSendToDevice(\r
544         client,\r
545         userId,\r
546         deviceId,\r
547         encryptedEventBuffer,\r
548         "m.room.encrypted");\r
549 }\r
550 \r
551 bool\r
552 MatrixClientFindDevice(\r
553     MatrixClient * client,\r
554     const char * deviceId,\r
555     MatrixDevice ** outDevice)\r
556 {\r
557     MatrixClientRequestDeviceKeys(client);\r
558 \r
559     for (int i = 0; i < client->numDevices; i++)\r
560     {\r
561         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
562         {\r
563             *outDevice = &client->devices[i];\r
564             return true;\r
565         }\r
566     }\r
567 \r
568     *outDevice = NULL;\r
569     return false;\r
570 }\r
571 \r
572 bool\r
573 MatrixClientGetDeviceKey(\r
574     MatrixClient * client,\r
575     const char * deviceId,\r
576     char * outDeviceKey, int outDeviceKeyCap)\r
577 {\r
578     MatrixClientRequestDeviceKeys(client);\r
579     \r
580     MatrixDevice * device;\r
581     if (MatrixClientFindDevice(client, deviceId, &device))\r
582     {\r
583         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
584         return true;\r
585     }\r
586 \r
587     return false;\r
588 }\r
589 \r
590 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
591 bool\r
592 MatrixClientRequestDeviceKeys(\r
593     MatrixClient * client)\r
594 {\r
595     char userIdEscaped[USER_ID_SIZE];\r
596     JsonEscape(client->userId, strlen(client->userId),\r
597         userIdEscaped, USER_ID_SIZE);\r
598 \r
599     char request[KEYS_QUERY_REQUEST_SIZE];\r
600     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
601         "{\"device_keys\":{\"%s\":[]}}", userIdEscaped);\r
602 \r
603     char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
604     bool requestResult = MatrixHttpPost(client,\r
605         KEYS_QUERY_URL,\r
606         request,\r
607         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
608         true);\r
609 \r
610     if (requestResult)\r
611     {\r
612         // query for retrieving device keys for user id\r
613         char query[JSON_QUERY_SIZE];\r
614         snprintf(query, JSON_QUERY_SIZE,\r
615             "$.device_keys.%s", userIdEscaped);\r
616         \r
617         const char * s;\r
618         int slen;\r
619         mjson_find(responseBuffer, strlen(responseBuffer),\r
620             query, &s, &slen);\r
621 \r
622         // loop over keys\r
623         \r
624         int koff, klen, voff, vlen, vtype, off;\r
625         for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
626                                         &voff, &vlen, &vtype)) != 0; ) {\r
627             const char * key = s + koff;\r
628             const char * val = s + voff;\r
629 \r
630             // set device id, "key" is the JSON key\r
631             MatrixDevice d;\r
632             strncpy(d.deviceId, key, klen);\r
633 \r
634             // look for device key in value\r
635             char deviceKeyQuery[JSON_QUERY_SIZE];\r
636             snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
637                 "$.keys.curve25519:%.*s", klen, key);\r
638             mjson_get_string(val, vlen,\r
639                 deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
640 \r
641             // add device\r
642             if (client->numDevices < NUM_DEVICES)\r
643             {\r
644                 client->devices[client->numDevices] = d;\r
645                 client->numDevices++;\r
646             }\r
647             else\r
648             {\r
649                 return false;\r
650             }\r
651         }\r
652 \r
653         return true;\r
654     }\r
655     else\r
656     {\r
657         return false;\r
658     }\r
659 }\r