]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
cli send encrypted & manage megolm session, save/load megolm sessions
[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 (1024*5)\r
13 #define ENCRYPTED_EVENT_SIZE (1024*10)\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 (1024*5)\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*10)\r
24 \r
25 #define KEYS_UPLOAD_URL "/_matrix/client/v3/keys/upload"\r
26 #define KEYS_UPLOAD_REQUEST_SIZE 1024\r
27 #define KEYS_UPLOAD_REQUEST_SIGNED_SIZE 2048\r
28 #define KEYS_UPLOAD_RESPONSE_SIZE 2048\r
29 \r
30 #define KEYS_CLAIM_URL "/_matrix/client/v3/keys/claim"\r
31 #define KEYS_CLAIM_REQUEST_SIZE 1024\r
32 #define KEYS_CLAIM_RESPONSE_SIZE 1024\r
33 \r
34 #define JSON_QUERY_SIZE 128\r
35 \r
36 \r
37 \r
38 void\r
39 Randomize(\r
40     uint8_t * random,\r
41     int randomLen)\r
42 {\r
43     static bool first = true;\r
44     if (first) { srand(time(0)); first = false; }\r
45 \r
46     for (int i = 0; i < randomLen; i++)\r
47     {\r
48         random[i] = rand() % 256;\r
49     }\r
50 }\r
51 \r
52 bool\r
53 JsonEscape(\r
54     const char * sIn, int sInLen,\r
55     char * sOut, int sOutCap)\r
56 {\r
57     int sOutIndex = 0;\r
58 \r
59     for (int i = 0; i < sInLen; i++)\r
60     {\r
61         if (i >= sOutCap)\r
62             return false;\r
63         \r
64         if (sIn[i] == '.' ||\r
65             sIn[i] == '[' ||\r
66             sIn[i] == ']'\r
67         ) {\r
68             sOut[sOutIndex++] = '\\';\r
69         }\r
70         sOut[sOutIndex++] = sIn[i];\r
71     }\r
72 \r
73     if (sOutIndex < sOutCap)\r
74         sOut[sOutIndex] = '\0';\r
75 \r
76     return true;\r
77 }\r
78 \r
79 bool JsonSign(\r
80     MatrixClient * client,\r
81     const char * sIn, int sInLen,\r
82     char * sOut, int sOutCap)\r
83 {\r
84     static char signature[OLM_SIGNATURE_SIZE];\r
85     size_t res =\r
86         olm_account_sign(client->olmAccount.account,\r
87             sIn, sInLen,\r
88             signature, OLM_SIGNATURE_SIZE);\r
89     \r
90     int signatureLen = res;\r
91 \r
92     static char signatureJson[JSON_SIGNATURE_SIZE];\r
93     int signatureJsonLen =\r
94         mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,\r
95             "{"\r
96                 "\"signatures\":{"\r
97                     "\"%s\":{"\r
98                         "\"ed25519:%s\":\"%.*s\""\r
99                     "}"\r
100                 "}"\r
101             "}",\r
102             client->userId,\r
103             client->deviceId,\r
104             signatureLen, signature);\r
105 \r
106     struct mjson_fixedbuf result = { sOut, sOutCap, 0 };\r
107     mjson_merge(\r
108         sIn, sInLen,\r
109         signatureJson, signatureJsonLen,\r
110         mjson_print_fixed_buf,\r
111         &result);\r
112 \r
113     return true;\r
114 }\r
115 \r
116 \r
117 bool\r
118 MatrixOlmAccountInit(\r
119     MatrixOlmAccount * account)\r
120 {\r
121     account->account = olm_account(account->memory);\r
122 \r
123     static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
124     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
125 \r
126     size_t res = olm_create_account(\r
127         account->account,\r
128         random,\r
129         OLM_ACCOUNT_RANDOM_SIZE);\r
130 \r
131     return res != olm_error();\r
132 }\r
133 \r
134 // TODO: in/outbound sessions\r
135 bool\r
136 MatrixOlmSessionTo(\r
137     MatrixOlmSession * session,\r
138     OlmAccount * olmAccount,\r
139     const char * deviceId,\r
140     const char * deviceKey,\r
141     const char * deviceOnetimeKey)\r
142 {\r
143     memset(session, 0, sizeof(MatrixOlmSession));\r
144 \r
145     session->deviceId = deviceId;\r
146 \r
147     session->session =\r
148         olm_session(session->memory);\r
149 \r
150     static uint8_t random[OLM_OUTBOUND_SESSION_RANDOM_SIZE];\r
151     Randomize(random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
152 \r
153     size_t res =\r
154         olm_create_outbound_session(session->session,\r
155             olmAccount,\r
156             deviceKey, strlen(deviceKey),\r
157             deviceOnetimeKey, strlen(deviceOnetimeKey),\r
158             random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
159     \r
160     if (res == olm_error()) {\r
161         printf("error olm: %s\n", olm_account_last_error(olmAccount));\r
162     }\r
163 \r
164     return session->session != NULL;\r
165 }\r
166 \r
167 bool\r
168 MatrixOlmSessionEncrypt(\r
169     MatrixOlmSession * session,\r
170     const char * plaintext,\r
171     char * outBuffer, int outBufferCap)\r
172 {\r
173     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
174     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
175 \r
176     size_t res = olm_encrypt(session->session,\r
177         plaintext, strlen(plaintext),\r
178         random, OLM_ENCRYPT_RANDOM_SIZE,\r
179         outBuffer, outBufferCap);\r
180 \r
181     return res != olm_error();\r
182 }\r
183 \r
184 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
185 bool\r
186 MatrixMegolmOutSessionInit(\r
187     MatrixMegolmOutSession * session,\r
188     const char * roomId)\r
189 {\r
190     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
191 \r
192     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
193     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
194 \r
195     session->roomId = roomId;\r
196 \r
197     session->session =\r
198         olm_outbound_group_session(session->memory);\r
199 \r
200     olm_init_outbound_group_session(\r
201         session->session,\r
202         random,\r
203         MEGOLM_INIT_RANDOM_SIZE);\r
204 \r
205     olm_outbound_group_session_id(session->session,\r
206         (uint8_t *)session->id,\r
207         MEGOLM_SESSION_ID_SIZE);\r
208         \r
209     olm_outbound_group_session_key(session->session,\r
210         (uint8_t *)session->key,\r
211         MEGOLM_SESSION_KEY_SIZE);\r
212     \r
213     return true;\r
214 }\r
215 \r
216 bool\r
217 MatrixMegolmOutSessionEncrypt(\r
218     MatrixMegolmOutSession * session,\r
219     const char * plaintext,\r
220     char * outBuffer, int outBufferCap)\r
221 {\r
222     size_t res = olm_group_encrypt(session->session,\r
223         (uint8_t *)plaintext, strlen(plaintext),\r
224         (uint8_t *)outBuffer, outBufferCap);\r
225 \r
226     return res != olm_error();\r
227 }\r
228 \r
229 bool\r
230 MatrixMegolmOutSessionSave(\r
231     MatrixMegolmOutSession * session,\r
232     const char * filename,\r
233     const char * key)\r
234 {\r
235     FILE * f = fopen(filename, "w");\r
236 \r
237     size_t roomIdLen = strlen(session->roomId);\r
238     fwrite(&roomIdLen, sizeof(size_t), 1, f);\r
239     fwrite(session->roomId, 1, roomIdLen, f);\r
240 \r
241     size_t pickleBufferLen =\r
242         olm_pickle_outbound_group_session_length(\r
243             session->session);\r
244     void * pickleBuffer = malloc(pickleBufferLen);\r
245 \r
246     olm_pickle_outbound_group_session(\r
247         session->session,\r
248         key, strlen(key),\r
249         pickleBuffer, pickleBufferLen);\r
250     \r
251     fwrite(&pickleBufferLen, sizeof(size_t), 1, f);\r
252     fwrite(pickleBuffer, 1, pickleBufferLen, f);\r
253     free(pickleBuffer);\r
254 \r
255     fclose(f);\r
256 \r
257     return true;\r
258 }\r
259 \r
260 bool\r
261 MatrixMegolmOutSessionLoad(\r
262     MatrixMegolmOutSession * session,\r
263     const char * filename,\r
264     const char * key)\r
265 {\r
266     FILE * f = fopen(filename, "r");\r
267 \r
268     size_t roomIdLen;\r
269     fread(&roomIdLen, sizeof(size_t), 1, f);\r
270     fread(session->roomId, 1, roomIdLen, f);\r
271 \r
272     size_t pickleBufferLen;\r
273     fread(&pickleBufferLen, sizeof(size_t), 1, f);\r
274 \r
275     void * pickleBuffer = malloc(pickleBufferLen);\r
276     fread(pickleBuffer, 1, pickleBufferLen, f);\r
277 \r
278     olm_unpickle_outbound_group_session(\r
279         session->session,\r
280         key, strlen(key),\r
281         pickleBuffer, pickleBufferLen);\r
282     \r
283     free(pickleBuffer);\r
284 \r
285     fclose(f);\r
286 \r
287     return true;\r
288 }\r
289 \r
290 \r
291 \r
292 bool\r
293 MatrixClientInit(\r
294     MatrixClient * client,\r
295     const char * server)\r
296 {\r
297     memset(client, 0, sizeof(MatrixClient));\r
298 \r
299     strcpy(client->server, server);\r
300 \r
301     // init olm account\r
302     MatrixOlmAccountInit(&client->olmAccount);\r
303 \r
304     // set device key\r
305     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
306     size_t res =\r
307         olm_account_identity_keys(\r
308             client->olmAccount.account,\r
309             deviceKeysJson,\r
310             OLM_IDENTITY_KEYS_JSON_SIZE);\r
311 \r
312     mjson_get_string(deviceKeysJson, res,\r
313         "$.curve25519",\r
314         client->deviceKey, DEVICE_KEY_SIZE);\r
315     mjson_get_string(deviceKeysJson, res,\r
316         "$.ed25519",\r
317         client->signingKey, SIGNING_KEY_SIZE);\r
318 \r
319     return true;\r
320 }\r
321 \r
322 bool\r
323 MatrixClientSave(\r
324     MatrixClient * client,\r
325     const char * filename)\r
326 {\r
327     FILE * f = fopen(filename, "w");\r
328     \r
329     fwrite(client->deviceKey, 1, DEVICE_KEY_SIZE, f);\r
330     fwrite(client->signingKey, 1, DEVICE_KEY_SIZE, f);\r
331     fwrite(client->userId, 1, USER_ID_SIZE, f);\r
332     fwrite(client->server, 1, SERVER_SIZE, f);\r
333     fwrite(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
334     fwrite(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
335     fwrite(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
336     fwrite(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
337 \r
338     fwrite(&client->numDevices, sizeof(int), 1, f);\r
339     for (int i = 0; i < client->numDevices; i++) {\r
340         fwrite(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
341         fwrite(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
342     }\r
343 \r
344     fclose(f);\r
345     return true;\r
346 }\r
347 \r
348 bool\r
349 MatrixClientLoad(\r
350     MatrixClient * client,\r
351     const char * filename)\r
352 {\r
353     FILE * f = fopen(filename, "r");\r
354     \r
355     fread(client->deviceKey, 1, DEVICE_KEY_SIZE, f);\r
356     fread(client->signingKey, 1, DEVICE_KEY_SIZE, f);\r
357     fread(client->userId, 1, USER_ID_SIZE, f);\r
358     fread(client->server, 1, SERVER_SIZE, f);\r
359     fread(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
360     fread(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
361     fread(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
362     fread(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
363 \r
364     fread(&client->numDevices, sizeof(int), 1, f);\r
365     for (int i = 0; i < client->numDevices; i++) {\r
366         fread(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
367         fread(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
368     }\r
369 \r
370     fclose(f);\r
371     return true;\r
372 }\r
373 \r
374 bool\r
375 MatrixClientSetAccessToken(\r
376     MatrixClient * client,\r
377     const char * accessToken)\r
378 {\r
379     for (int i = 0; i < ACCESS_TOKEN_SIZE-1; i++)\r
380         client->accessToken[i] = accessToken[i];\r
381     client->accessToken[ACCESS_TOKEN_SIZE-1] = '\0';\r
382 \r
383     return true;\r
384 }\r
385 \r
386 bool\r
387 MatrixClientSetDeviceId(\r
388     MatrixClient * client,\r
389     const char * deviceId)\r
390 {\r
391     for (int i = 0; i < DEVICE_ID_SIZE-1; i++)\r
392         client->deviceId[i] = deviceId[i];\r
393     client->deviceId[DEVICE_ID_SIZE-1] = '\0';\r
394 \r
395     return true;\r
396 }\r
397 \r
398 bool\r
399 MatrixClientSetUserId(\r
400     MatrixClient * client,\r
401     const char * userId)\r
402 {\r
403     for (int i = 0; i < USER_ID_SIZE-1; i++)\r
404         client->userId[i] = userId[i];\r
405     client->userId[USER_ID_SIZE-1] = '\0';\r
406 \r
407     return true;\r
408 }\r
409 \r
410 bool\r
411 MatrixClientGenerateOnetimeKeys(\r
412     MatrixClient * client,\r
413     int numberOfKeys)\r
414 {\r
415     static uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
416     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
417 \r
418     size_t res =\r
419         olm_account_generate_one_time_keys(client->olmAccount.account,\r
420             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
421 \r
422     return res != olm_error();\r
423 }\r
424 \r
425 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
426 bool\r
427 MatrixClientUploadOnetimeKeys(\r
428     MatrixClient * client)\r
429 {\r
430     static char requestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
431 \r
432     mjson_snprintf(requestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
433         "{\"one_time_keys\":{");\r
434 \r
435     static char onetimeKeysBuffer[1024];\r
436     olm_account_one_time_keys(client->olmAccount.account,\r
437         onetimeKeysBuffer, 1024);\r
438 \r
439     const char *keys;\r
440     int keysLen;\r
441     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
442 \r
443     int koff, klen, voff, vlen, vtype, off = 0;\r
444     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
445         static char keyJson[JSON_ONETIME_KEY_SIZE];\r
446         \r
447         snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
448             "{\"key\":\"%.*s\"}",\r
449             vlen-2, keys + voff+1);\r
450 \r
451         static char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
452 \r
453         JsonSign(client,\r
454             keyJson, JSON_ONETIME_KEY_SIZE,\r
455             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
456         \r
457         mjson_snprintf(requestBuffer+strlen(requestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
458             "\"signed_curve25519:%.*s\":%s,",\r
459             klen-2, keys + koff+1,\r
460             keyJsonSigned);\r
461     }\r
462 \r
463     mjson_snprintf(requestBuffer+strlen(requestBuffer)-1, KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
464         "}}");\r
465 \r
466     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
467     MatrixHttpPost(client,\r
468         KEYS_UPLOAD_URL,\r
469         requestBuffer,\r
470         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
471         true);\r
472 \r
473     return true;\r
474 }\r
475 \r
476 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
477 bool\r
478 MatrixClientUploadDeviceKeys(\r
479     MatrixClient * client)\r
480 {\r
481     static char deviceKeysBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
482 \r
483     mjson_snprintf(deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
484         "{\"device_keys\":{"\r
485             "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
486             "\"device_id\":\"%s\","\r
487             "\"keys\":{"\r
488                 "\"curve25519:%s\":\"%s\","\r
489                 "\"ed25519:%s\":\"%s\""\r
490             "},"\r
491             "\"user_id\":\"%s\""\r
492         "}}",\r
493         client->deviceId,\r
494         client->deviceId, client->deviceKey,\r
495         client->deviceId, client->signingKey,\r
496         client->userId);\r
497 \r
498     static char deviceKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
499     JsonSign(client,\r
500         deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
501         deviceKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
502 \r
503 \r
504     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
505     MatrixHttpPost(client,\r
506         KEYS_UPLOAD_URL,\r
507         deviceKeysSignedBuffer,\r
508         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
509         true);\r
510 \r
511     return true;\r
512 }\r
513 \r
514 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysclaim\r
515 bool\r
516 MatrixClientClaimOnetimeKey(\r
517     MatrixClient * client,\r
518     const char * userId,\r
519     const char * deviceId,\r
520     char * outOnetimeKey, int outOnetimeKeyCap)\r
521 {\r
522     static char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];\r
523     mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,\r
524     "{"\r
525       "\"one_time_keys\": {"\r
526         "\"%s\": {"\r
527           "\"%s\": \"signed_curve25519\""\r
528         "}"\r
529       "},"\r
530       "\"timeout\": 10000"\r
531     "}",\r
532     userId,\r
533     deviceId);\r
534 \r
535     static char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];\r
536     MatrixHttpPost(client,\r
537         KEYS_CLAIM_URL,\r
538         requestBuffer,\r
539         responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,\r
540         true);\r
541     \r
542     char userIdEscaped[USER_ID_SIZE];\r
543     JsonEscape(userId, strlen(userId),\r
544         userIdEscaped, USER_ID_SIZE);\r
545     \r
546     static char query[JSON_QUERY_SIZE];\r
547     snprintf(query, JSON_QUERY_SIZE,\r
548         "$.one_time_keys.%s.%s",\r
549         userIdEscaped,\r
550         deviceId);\r
551     \r
552     const char * keyObject;\r
553     int keyObjectSize;\r
554     mjson_find(responseBuffer, strlen(responseBuffer),\r
555         query,\r
556         &keyObject, &keyObjectSize);\r
557     \r
558     int koff, klen, voff, vlen, vtype;\r
559     mjson_next(keyObject, keyObjectSize, 0,\r
560         &koff, &klen, &voff, &vlen, &vtype);\r
561     \r
562     mjson_get_string(keyObject + voff, vlen,\r
563         "$.key", outOnetimeKey, outOnetimeKeyCap);\r
564     \r
565     // TODO: verify signature\r
566     \r
567     return true;\r
568 }\r
569 \r
570 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
571 bool\r
572 MatrixClientLoginPassword(\r
573     MatrixClient * client,\r
574     const char * username,\r
575     const char * password,\r
576     const char * displayName)\r
577 {\r
578     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
579 \r
580     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
581         "{"\r
582             "\"type\": \"m.login.password\","\r
583             "\"identifier\": {"\r
584                 "\"type\": \"m.id.user\","\r
585                 "\"user\": \"%s\""\r
586             "},"\r
587             "\"password\": \"%s\","\r
588             "\"initial_device_display_name\": \"%s\""\r
589         "}",\r
590         username,\r
591         password,\r
592         displayName);\r
593     \r
594     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
595     bool result =\r
596         MatrixHttpPost(client,\r
597             LOGIN_URL,\r
598             requestBuffer,\r
599             responseBuffer, LOGIN_RESPONSE_SIZE,\r
600             false);\r
601     \r
602     int responseLen = strlen(responseBuffer);\r
603     \r
604     if (!result)\r
605         return false;\r
606 \r
607     mjson_get_string(responseBuffer, responseLen,\r
608         "$.access_token",\r
609         client->accessToken, ACCESS_TOKEN_SIZE);\r
610     mjson_get_string(responseBuffer, responseLen,\r
611         "$.device_id",\r
612         client->deviceId, DEVICE_ID_SIZE);\r
613     mjson_get_string(responseBuffer, responseLen,\r
614         "$.expires_in_ms",\r
615         client->expireMs, EXPIRE_MS_SIZE);\r
616     mjson_get_string(responseBuffer, responseLen,\r
617         "$.refresh_token",\r
618         client->refreshToken, REFRESH_TOKEN_SIZE);\r
619 \r
620     return true;\r
621 }\r
622 \r
623 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
624 bool\r
625 MatrixClientSendEvent(\r
626     MatrixClient * client,\r
627     const char * roomId,\r
628     const char * msgType,\r
629     const char * msgBody)\r
630 {    \r
631     static char requestUrl[MAX_URL_LEN];\r
632     sprintf(requestUrl,\r
633         ROOMEVENT_URL, roomId, msgType, (int)time(NULL));\r
634 \r
635     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
636     bool result =\r
637         MatrixHttpPut(client,\r
638             requestUrl,\r
639             msgBody,\r
640             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
641             true);\r
642     \r
643     return result;\r
644 }\r
645 \r
646 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
647 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
648 bool\r
649 MatrixClientSendEventEncrypted(\r
650     MatrixClient * client,\r
651     const char * roomId,\r
652     const char * msgType,\r
653     const char * msgBody)\r
654 {\r
655     // event json\r
656     static char requestBuffer[ROOMEVENT_REQUEST_SIZE];\r
657     sprintf(requestBuffer,\r
658         "{"\r
659         "\"type\":\"%s\","\r
660         "\"content\":%s,"\r
661         "\"room_id\":\"%s\""\r
662         "}",\r
663         msgType,\r
664         msgBody,\r
665         roomId);\r
666 \r
667     // get megolm session\r
668     MatrixMegolmOutSession * outSession;\r
669     MatrixClientGetMegolmOutSession(client, roomId, &outSession);\r
670         \r
671     // encrypt\r
672     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
673     MatrixMegolmOutSessionEncrypt(outSession,\r
674         requestBuffer,\r
675         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
676 \r
677     // encrypted event json\r
678     const char * senderKey = client->deviceKey;\r
679     const char * sessionId = outSession->id;\r
680     const char * deviceId = client->deviceId;\r
681 \r
682     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
683     sprintf(encryptedEventBuffer,\r
684         "{"\r
685         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
686         "\"ciphertext\":\"%s\","\r
687         "\"device_id\":\"%s\","\r
688         "\"sender_key\":\"%s\","\r
689         "\"session_id\":\"%s\""\r
690         "}",\r
691         encryptedBuffer,\r
692         deviceId,\r
693         senderKey,\r
694         sessionId);\r
695 \r
696     // send\r
697     return MatrixClientSendEvent(client,\r
698         roomId,\r
699         "m.room.encrypted",\r
700         encryptedEventBuffer);\r
701 }\r
702 \r
703 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
704 bool\r
705 MatrixClientSync(\r
706     MatrixClient * client,\r
707     char * outSyncBuffer, int outSyncCap)\r
708 {\r
709     return\r
710         MatrixHttpGet(client,\r
711             "/_matrix/client/v3/sync",\r
712             outSyncBuffer, outSyncCap,\r
713             true);\r
714 }\r
715 \r
716 bool\r
717 MatrixClientShareMegolmOutSession(\r
718     MatrixClient * client,\r
719     const char * userId,\r
720     const char * deviceId,\r
721     MatrixMegolmOutSession * session)\r
722 {\r
723     // generate room key event\r
724     static char eventBuffer[KEY_SHARE_EVENT_LEN];\r
725     sprintf(eventBuffer,\r
726         "{"\r
727             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
728             "\"room_id\":\"%s\","\r
729             "\"session_id\":\"%s\","\r
730             "\"session_key\":\"%s\""\r
731         "}",\r
732         session->roomId,\r
733         session->id,\r
734         session->key\r
735     );\r
736 \r
737     // // get olm session\r
738     // MatrixOlmSession * olmSession;\r
739     // MatrixClientGetOlmSession(client, userId, deviceId, &olmSession);\r
740 \r
741     // // encrypt\r
742     // char encryptedBuffer[KEY_SHARE_EVENT_LEN];\r
743     // MatrixOlmSessionEncrypt(olmSession,\r
744     //     eventBuffer,\r
745     //     encryptedBuffer, KEY_SHARE_EVENT_LEN);\r
746 \r
747     // send\r
748     MatrixClientSendToDeviceEncrypted(client,\r
749         userId,\r
750         deviceId,\r
751         eventBuffer,\r
752         "m.room_key");\r
753 \r
754     return true;\r
755 }\r
756 \r
757 bool\r
758 MatrixClientShareMegolmOutSessionTest(\r
759     MatrixClient * client,\r
760     const char * deviceId,\r
761     MatrixMegolmOutSession * session)\r
762 {\r
763     // generate room key event\r
764     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
765     sprintf(eventBuffer,\r
766         "{"\r
767             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
768             "\"room_id\":\"%s\","\r
769             "\"session_id\":\"%s\","\r
770             "\"session_key\":\"%s\""\r
771         "}",\r
772         session->roomId,\r
773         session->id,\r
774         session->key\r
775     );\r
776 \r
777     // send\r
778     MatrixClientSendToDevice(client,\r
779         client->userId,\r
780         deviceId,\r
781         eventBuffer,\r
782         "m.room_key");\r
783 \r
784     return true;\r
785 }\r
786 \r
787 // bool\r
788 // MatrixClientSetMegolmOutSession(\r
789 //     MatrixClient * client,\r
790 //     const char * roomId,\r
791 //     MatrixMegolmOutSession session)\r
792 // {\r
793 //     if (client->numMegolmOutSessions < 10)\r
794 //     {\r
795 //         session.roomId = roomId;\r
796 //         client->megolmOutSessions[client->numMegolmOutSessions] = session;\r
797 //         client->numMegolmOutSessions++;\r
798 \r
799 //         return true;\r
800 //     }\r
801 //     return false;\r
802 // }\r
803 \r
804 bool\r
805 MatrixClientGetMegolmOutSession(\r
806     MatrixClient * client,\r
807     const char * roomId,\r
808     MatrixMegolmOutSession ** outSession)\r
809 {\r
810     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
811     {\r
812         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
813         {\r
814             *outSession = &client->megolmOutSessions[i];\r
815             return true;\r
816         }\r
817     }\r
818 \r
819     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
820     {\r
821         MatrixMegolmOutSessionInit(\r
822             &client->megolmOutSessions[client->numMegolmOutSessions],\r
823             roomId);\r
824 \r
825         *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];\r
826         \r
827         client->numMegolmOutSessions++;\r
828 \r
829         return true;\r
830     }\r
831 \r
832     return false;\r
833 }\r
834 \r
835 bool\r
836 MatrixClientGetOlmSession(\r
837     MatrixClient * client,\r
838     const char * userId,\r
839     const char * deviceId,\r
840     MatrixOlmSession ** outSession)\r
841 {\r
842     for (int i = 0; i < client->numOlmSessions; i++)\r
843     {\r
844         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
845         {\r
846             *outSession = &client->olmSessions[i];\r
847             return true;\r
848         }\r
849     }\r
850 \r
851     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
852     {\r
853         static char deviceKey[DEVICE_KEY_SIZE];\r
854         MatrixClientGetDeviceKey(client,\r
855             deviceId,\r
856             deviceKey, DEVICE_KEY_SIZE);\r
857 \r
858         char onetimeKey[ONETIME_KEY_SIZE];\r
859         MatrixClientClaimOnetimeKey(client,\r
860             userId,\r
861             deviceId,\r
862             onetimeKey, ONETIME_KEY_SIZE);\r
863 \r
864         MatrixOlmSessionTo(\r
865             &client->olmSessions[client->numOlmSessions],\r
866             client->olmAccount.account,\r
867             deviceId,\r
868             deviceKey,\r
869             onetimeKey);\r
870 \r
871         *outSession = &client->olmSessions[client->numOlmSessions];\r
872         \r
873         client->numOlmSessions++;\r
874 \r
875         return true;\r
876     }\r
877 \r
878     return false;\r
879 }\r
880 \r
881 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
882 bool\r
883 MatrixClientSendToDevice(\r
884     MatrixClient * client,\r
885     const char * userId,\r
886     const char * deviceId,\r
887     const char * message,\r
888     const char * msgType)\r
889 {\r
890     static char requestUrl[MAX_URL_LEN];\r
891     sprintf(requestUrl,\r
892         TODEVICE_URL, msgType, (int)time(NULL));\r
893 \r
894     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
895     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
896         "{"\r
897             "\"messages\": {"\r
898                 "\"%s\": {"\r
899                     "\"%s\":%s"\r
900                 "}"\r
901             "}"\r
902         "}",\r
903         userId,\r
904         deviceId,\r
905         message);\r
906 \r
907     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
908     bool result =\r
909         MatrixHttpPut(client,\r
910             requestUrl,\r
911             eventBuffer,\r
912             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
913             true);\r
914     \r
915     return result;\r
916 }\r
917 \r
918 bool\r
919 MatrixClientSendToDeviceEncrypted(\r
920     MatrixClient * client,\r
921     const char * userId,\r
922     const char * deviceId,\r
923     const char * message,\r
924     const char * msgType)\r
925 {\r
926     // get olm session\r
927     MatrixOlmSession * olmSession;\r
928     MatrixClientGetOlmSession(client, userId, deviceId, &olmSession);\r
929 \r
930     // create event json\r
931     char deviceKey[DEVICE_KEY_SIZE];\r
932     MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);\r
933     \r
934     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
935     sprintf(eventBuffer,\r
936         "{"\r
937         "\"type\": \"%s\","\r
938         "\"content\": \"%s\","\r
939         "\"sender\": \"%s\","\r
940         "\"recipient\": \"%s\","\r
941         "\"recipient_keys\": {"\r
942           "\"ed25519\": \"%s\""\r
943         "},"\r
944         "\"keys\": {"\r
945           "\"ed25519\": \"%s\""\r
946         "}"\r
947         "}",\r
948         msgType,\r
949         message,\r
950         client->userId,\r
951         userId, // recipient user id\r
952         deviceKey, // recipient device key\r
953         client->deviceKey);\r
954 \r
955     // encrypt\r
956     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
957     MatrixOlmSessionEncrypt(olmSession,\r
958         eventBuffer,\r
959         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
960 \r
961     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
962     sprintf(encryptedEventBuffer,\r
963         "{"\r
964         "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","\r
965         "\"ciphertext\":{"\r
966           "\"%s\":{"\r
967             "\"body\":\"%s\","\r
968             "\"type\":%d"\r
969           "}"\r
970         "},"\r
971         "\"device_id\":\"%s\","\r
972         "\"sender_key\":\"%s\""\r
973         "}",\r
974         deviceKey,\r
975         encryptedBuffer,\r
976         0, //olmSession->type,\r
977         client->deviceId,\r
978         client->deviceKey);\r
979 \r
980     // send\r
981     return MatrixClientSendToDevice(\r
982         client,\r
983         userId,\r
984         deviceId,\r
985         encryptedEventBuffer,\r
986         "m.room.encrypted");\r
987 }\r
988 \r
989 bool\r
990 MatrixClientFindDevice(\r
991     MatrixClient * client,\r
992     const char * deviceId,\r
993     MatrixDevice ** outDevice)\r
994 {\r
995     MatrixClientRequestDeviceKeys(client);\r
996 \r
997     for (int i = 0; i < client->numDevices; i++)\r
998     {\r
999         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1000         {\r
1001             *outDevice = &client->devices[i];\r
1002             return true;\r
1003         }\r
1004     }\r
1005 \r
1006     *outDevice = NULL;\r
1007     return false;\r
1008 }\r
1009 \r
1010 bool\r
1011 MatrixClientGetDeviceKey(\r
1012     MatrixClient * client,\r
1013     const char * deviceId,\r
1014     char * outDeviceKey, int outDeviceKeyCap)\r
1015 {\r
1016     MatrixDevice * device;\r
1017     \r
1018     if (MatrixClientFindDevice(client, deviceId, &device))\r
1019     {\r
1020         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1021         return true;\r
1022     }\r
1023 \r
1024     MatrixClientRequestDeviceKeys(client);\r
1025     \r
1026     if (MatrixClientFindDevice(client, deviceId, &device))\r
1027     {\r
1028         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1029         return true;\r
1030     }\r
1031 \r
1032     return false;\r
1033 }\r
1034 \r
1035 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
1036 bool\r
1037 MatrixClientRequestDeviceKeys(\r
1038     MatrixClient * client)\r
1039 {\r
1040     static char userIdEscaped[USER_ID_SIZE];\r
1041     JsonEscape(client->userId, strlen(client->userId),\r
1042         userIdEscaped, USER_ID_SIZE);\r
1043 \r
1044     static char request[KEYS_QUERY_REQUEST_SIZE];\r
1045     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
1046         "{\"device_keys\":{\"%s\":[]}}", client->userId);\r
1047 \r
1048     static char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
1049     bool requestResult = MatrixHttpPost(client,\r
1050         KEYS_QUERY_URL,\r
1051         request,\r
1052         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
1053         true);\r
1054 \r
1055     if (! requestResult)\r
1056         return false;\r
1057 \r
1058     // query for retrieving device keys for user id\r
1059     static char query[JSON_QUERY_SIZE];\r
1060     snprintf(query, JSON_QUERY_SIZE,\r
1061         "$.device_keys.%s", userIdEscaped);\r
1062     \r
1063     const char * s;\r
1064     int slen;\r
1065     mjson_find(responseBuffer, strlen(responseBuffer),\r
1066         query, &s, &slen);\r
1067 \r
1068     // loop over keys\r
1069     \r
1070     int koff, klen, voff, vlen, vtype, off = 0;\r
1071     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1072                                     &voff, &vlen, &vtype)) != 0; ) {\r
1073         const char * key = s + koff;\r
1074         const char * val = s + voff;\r
1075 \r
1076         // set device id, "key" is the JSON key\r
1077         MatrixDevice d;\r
1078         snprintf(d.deviceId, DEVICE_ID_SIZE,\r
1079             "%.*s", klen-2, key+1);\r
1080 \r
1081         // look for device key in value\r
1082         static char deviceKeyQuery[JSON_QUERY_SIZE];\r
1083         snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
1084             "$.keys.curve25519:%s", d.deviceId);\r
1085         mjson_get_string(val, vlen,\r
1086             deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
1087 \r
1088         // add device\r
1089         if (client->numDevices < NUM_DEVICES)\r
1090         {\r
1091             client->devices[client->numDevices] = d;\r
1092             client->numDevices++;\r
1093         }\r
1094         else\r
1095         {\r
1096             return false;\r
1097         }\r
1098     }\r
1099 \r
1100     return true;\r
1101 }\r