]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
share, save, load, init, print megolm out 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     strncpy(session->roomId, roomId, ROOM_ID_SIZE);\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     for (int i = roomIdLen; i < ROOM_ID_SIZE; i++)\r
272         session->roomId[i] = '\0';\r
273 \r
274     size_t pickleBufferLen;\r
275     fread(&pickleBufferLen, sizeof(size_t), 1, f);\r
276 \r
277     void * pickleBuffer = malloc(pickleBufferLen);\r
278     fread(pickleBuffer, 1, pickleBufferLen, f);\r
279 \r
280     olm_unpickle_outbound_group_session(\r
281         session->session,\r
282         key, strlen(key),\r
283         pickleBuffer, pickleBufferLen);\r
284     \r
285     free(pickleBuffer);\r
286 \r
287     olm_outbound_group_session_id(session->session, (uint8_t *)session->id, MEGOLM_SESSION_ID_SIZE);\r
288     olm_outbound_group_session_key(session->session, (uint8_t *)session->key, MEGOLM_SESSION_KEY_SIZE);\r
289 \r
290     fclose(f);\r
291 \r
292     return true;\r
293 }\r
294 \r
295 \r
296 \r
297 bool\r
298 MatrixClientInit(\r
299     MatrixClient * client,\r
300     const char * server)\r
301 {\r
302     memset(client, 0, sizeof(MatrixClient));\r
303 \r
304     strcpy(client->server, server);\r
305 \r
306     // init olm account\r
307     MatrixOlmAccountInit(&client->olmAccount);\r
308 \r
309     // set device key\r
310     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
311     size_t res =\r
312         olm_account_identity_keys(\r
313             client->olmAccount.account,\r
314             deviceKeysJson,\r
315             OLM_IDENTITY_KEYS_JSON_SIZE);\r
316 \r
317     mjson_get_string(deviceKeysJson, res,\r
318         "$.curve25519",\r
319         client->deviceKey, DEVICE_KEY_SIZE);\r
320     mjson_get_string(deviceKeysJson, res,\r
321         "$.ed25519",\r
322         client->signingKey, SIGNING_KEY_SIZE);\r
323 \r
324     return true;\r
325 }\r
326 \r
327 bool\r
328 MatrixClientSave(\r
329     MatrixClient * client,\r
330     const char * filename)\r
331 {\r
332     FILE * f = fopen(filename, "w");\r
333     \r
334     fwrite(client->deviceKey, 1, DEVICE_KEY_SIZE, f);\r
335     fwrite(client->signingKey, 1, DEVICE_KEY_SIZE, f);\r
336     fwrite(client->userId, 1, USER_ID_SIZE, f);\r
337     fwrite(client->server, 1, SERVER_SIZE, f);\r
338     fwrite(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
339     fwrite(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
340     fwrite(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
341     fwrite(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
342 \r
343     fwrite(&client->numDevices, sizeof(int), 1, f);\r
344     for (int i = 0; i < client->numDevices; i++) {\r
345         fwrite(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
346         fwrite(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
347     }\r
348 \r
349     fclose(f);\r
350     return true;\r
351 }\r
352 \r
353 bool\r
354 MatrixClientLoad(\r
355     MatrixClient * client,\r
356     const char * filename)\r
357 {\r
358     FILE * f = fopen(filename, "r");\r
359     \r
360     fread(client->deviceKey, 1, DEVICE_KEY_SIZE, f);\r
361     fread(client->signingKey, 1, DEVICE_KEY_SIZE, f);\r
362     fread(client->userId, 1, USER_ID_SIZE, f);\r
363     fread(client->server, 1, SERVER_SIZE, f);\r
364     fread(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
365     fread(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
366     fread(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
367     fread(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
368 \r
369     fread(&client->numDevices, sizeof(int), 1, f);\r
370     for (int i = 0; i < client->numDevices; i++) {\r
371         fread(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
372         fread(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
373     }\r
374 \r
375     fclose(f);\r
376     return true;\r
377 }\r
378 \r
379 bool\r
380 MatrixClientSetAccessToken(\r
381     MatrixClient * client,\r
382     const char * accessToken)\r
383 {\r
384     for (int i = 0; i < ACCESS_TOKEN_SIZE-1; i++)\r
385         client->accessToken[i] = accessToken[i];\r
386     client->accessToken[ACCESS_TOKEN_SIZE-1] = '\0';\r
387 \r
388     return true;\r
389 }\r
390 \r
391 bool\r
392 MatrixClientSetDeviceId(\r
393     MatrixClient * client,\r
394     const char * deviceId)\r
395 {\r
396     for (int i = 0; i < DEVICE_ID_SIZE-1; i++)\r
397         client->deviceId[i] = deviceId[i];\r
398     client->deviceId[DEVICE_ID_SIZE-1] = '\0';\r
399 \r
400     return true;\r
401 }\r
402 \r
403 bool\r
404 MatrixClientSetUserId(\r
405     MatrixClient * client,\r
406     const char * userId)\r
407 {\r
408     for (int i = 0; i < USER_ID_SIZE-1; i++)\r
409         client->userId[i] = userId[i];\r
410     client->userId[USER_ID_SIZE-1] = '\0';\r
411 \r
412     return true;\r
413 }\r
414 \r
415 bool\r
416 MatrixClientGenerateOnetimeKeys(\r
417     MatrixClient * client,\r
418     int numberOfKeys)\r
419 {\r
420     static uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
421     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
422 \r
423     size_t res =\r
424         olm_account_generate_one_time_keys(client->olmAccount.account,\r
425             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
426 \r
427     return res != olm_error();\r
428 }\r
429 \r
430 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
431 bool\r
432 MatrixClientUploadOnetimeKeys(\r
433     MatrixClient * client)\r
434 {\r
435     static char requestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
436 \r
437     mjson_snprintf(requestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
438         "{\"one_time_keys\":{");\r
439 \r
440     static char onetimeKeysBuffer[1024];\r
441     olm_account_one_time_keys(client->olmAccount.account,\r
442         onetimeKeysBuffer, 1024);\r
443 \r
444     const char *keys;\r
445     int keysLen;\r
446     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
447 \r
448     int koff, klen, voff, vlen, vtype, off = 0;\r
449     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
450         static char keyJson[JSON_ONETIME_KEY_SIZE];\r
451         \r
452         snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
453             "{\"key\":\"%.*s\"}",\r
454             vlen-2, keys + voff+1);\r
455 \r
456         static char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
457 \r
458         JsonSign(client,\r
459             keyJson, JSON_ONETIME_KEY_SIZE,\r
460             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
461         \r
462         mjson_snprintf(requestBuffer+strlen(requestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
463             "\"signed_curve25519:%.*s\":%s,",\r
464             klen-2, keys + koff+1,\r
465             keyJsonSigned);\r
466     }\r
467 \r
468     mjson_snprintf(requestBuffer+strlen(requestBuffer)-1, KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
469         "}}");\r
470 \r
471     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
472     MatrixHttpPost(client,\r
473         KEYS_UPLOAD_URL,\r
474         requestBuffer,\r
475         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
476         true);\r
477 \r
478     return true;\r
479 }\r
480 \r
481 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
482 bool\r
483 MatrixClientUploadDeviceKeys(\r
484     MatrixClient * client)\r
485 {\r
486     static char deviceKeysBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
487 \r
488     mjson_snprintf(deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
489         "{\"device_keys\":{"\r
490             "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
491             "\"device_id\":\"%s\","\r
492             "\"keys\":{"\r
493                 "\"curve25519:%s\":\"%s\","\r
494                 "\"ed25519:%s\":\"%s\""\r
495             "},"\r
496             "\"user_id\":\"%s\""\r
497         "}}",\r
498         client->deviceId,\r
499         client->deviceId, client->deviceKey,\r
500         client->deviceId, client->signingKey,\r
501         client->userId);\r
502 \r
503     static char deviceKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
504     JsonSign(client,\r
505         deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
506         deviceKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
507 \r
508 \r
509     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
510     MatrixHttpPost(client,\r
511         KEYS_UPLOAD_URL,\r
512         deviceKeysSignedBuffer,\r
513         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
514         true);\r
515 \r
516     return true;\r
517 }\r
518 \r
519 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysclaim\r
520 bool\r
521 MatrixClientClaimOnetimeKey(\r
522     MatrixClient * client,\r
523     const char * userId,\r
524     const char * deviceId,\r
525     char * outOnetimeKey, int outOnetimeKeyCap)\r
526 {\r
527     static char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];\r
528     mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,\r
529     "{"\r
530       "\"one_time_keys\": {"\r
531         "\"%s\": {"\r
532           "\"%s\": \"signed_curve25519\""\r
533         "}"\r
534       "},"\r
535       "\"timeout\": 10000"\r
536     "}",\r
537     userId,\r
538     deviceId);\r
539 \r
540     static char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];\r
541     MatrixHttpPost(client,\r
542         KEYS_CLAIM_URL,\r
543         requestBuffer,\r
544         responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,\r
545         true);\r
546     \r
547     char userIdEscaped[USER_ID_SIZE];\r
548     JsonEscape(userId, strlen(userId),\r
549         userIdEscaped, USER_ID_SIZE);\r
550     \r
551     static char query[JSON_QUERY_SIZE];\r
552     snprintf(query, JSON_QUERY_SIZE,\r
553         "$.one_time_keys.%s.%s",\r
554         userIdEscaped,\r
555         deviceId);\r
556     \r
557     const char * keyObject;\r
558     int keyObjectSize;\r
559     mjson_find(responseBuffer, strlen(responseBuffer),\r
560         query,\r
561         &keyObject, &keyObjectSize);\r
562     \r
563     int koff, klen, voff, vlen, vtype;\r
564     mjson_next(keyObject, keyObjectSize, 0,\r
565         &koff, &klen, &voff, &vlen, &vtype);\r
566     \r
567     mjson_get_string(keyObject + voff, vlen,\r
568         "$.key", outOnetimeKey, outOnetimeKeyCap);\r
569     \r
570     // TODO: verify signature\r
571     \r
572     return true;\r
573 }\r
574 \r
575 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
576 bool\r
577 MatrixClientLoginPassword(\r
578     MatrixClient * client,\r
579     const char * username,\r
580     const char * password,\r
581     const char * displayName)\r
582 {\r
583     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
584 \r
585     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
586         "{"\r
587             "\"type\": \"m.login.password\","\r
588             "\"identifier\": {"\r
589                 "\"type\": \"m.id.user\","\r
590                 "\"user\": \"%s\""\r
591             "},"\r
592             "\"password\": \"%s\","\r
593             "\"initial_device_display_name\": \"%s\""\r
594         "}",\r
595         username,\r
596         password,\r
597         displayName);\r
598     \r
599     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
600     bool result =\r
601         MatrixHttpPost(client,\r
602             LOGIN_URL,\r
603             requestBuffer,\r
604             responseBuffer, LOGIN_RESPONSE_SIZE,\r
605             false);\r
606     \r
607     int responseLen = strlen(responseBuffer);\r
608     \r
609     if (!result)\r
610         return false;\r
611 \r
612     mjson_get_string(responseBuffer, responseLen,\r
613         "$.access_token",\r
614         client->accessToken, ACCESS_TOKEN_SIZE);\r
615     mjson_get_string(responseBuffer, responseLen,\r
616         "$.device_id",\r
617         client->deviceId, DEVICE_ID_SIZE);\r
618     mjson_get_string(responseBuffer, responseLen,\r
619         "$.expires_in_ms",\r
620         client->expireMs, EXPIRE_MS_SIZE);\r
621     mjson_get_string(responseBuffer, responseLen,\r
622         "$.refresh_token",\r
623         client->refreshToken, REFRESH_TOKEN_SIZE);\r
624 \r
625     return true;\r
626 }\r
627 \r
628 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
629 bool\r
630 MatrixClientSendEvent(\r
631     MatrixClient * client,\r
632     const char * roomId,\r
633     const char * msgType,\r
634     const char * msgBody)\r
635 {    \r
636     static char requestUrl[MAX_URL_LEN];\r
637     sprintf(requestUrl,\r
638         ROOMEVENT_URL, roomId, msgType, (int)time(NULL));\r
639 \r
640     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
641     bool result =\r
642         MatrixHttpPut(client,\r
643             requestUrl,\r
644             msgBody,\r
645             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
646             true);\r
647     \r
648     return result;\r
649 }\r
650 \r
651 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
652 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
653 bool\r
654 MatrixClientSendEventEncrypted(\r
655     MatrixClient * client,\r
656     const char * roomId,\r
657     const char * msgType,\r
658     const char * msgBody)\r
659 {\r
660     // event json\r
661     static char requestBuffer[ROOMEVENT_REQUEST_SIZE];\r
662     sprintf(requestBuffer,\r
663         "{"\r
664         "\"type\":\"%s\","\r
665         "\"content\":%s,"\r
666         "\"room_id\":\"%s\""\r
667         "}",\r
668         msgType,\r
669         msgBody,\r
670         roomId);\r
671 \r
672     // get megolm session\r
673     MatrixMegolmOutSession * outSession;\r
674     MatrixClientGetMegolmOutSession(client, roomId, &outSession);\r
675         \r
676     // encrypt\r
677     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
678     MatrixMegolmOutSessionEncrypt(outSession,\r
679         requestBuffer,\r
680         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
681 \r
682     // encrypted event json\r
683     const char * senderKey = client->deviceKey;\r
684     const char * sessionId = outSession->id;\r
685     const char * deviceId = client->deviceId;\r
686 \r
687     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
688     sprintf(encryptedEventBuffer,\r
689         "{"\r
690         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
691         "\"ciphertext\":\"%s\","\r
692         "\"device_id\":\"%s\","\r
693         "\"sender_key\":\"%s\","\r
694         "\"session_id\":\"%s\""\r
695         "}",\r
696         encryptedBuffer,\r
697         deviceId,\r
698         senderKey,\r
699         sessionId);\r
700 \r
701     // send\r
702     return MatrixClientSendEvent(client,\r
703         roomId,\r
704         "m.room.encrypted",\r
705         encryptedEventBuffer);\r
706 }\r
707 \r
708 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
709 bool\r
710 MatrixClientSync(\r
711     MatrixClient * client,\r
712     char * outSyncBuffer, int outSyncCap)\r
713 {\r
714     return\r
715         MatrixHttpGet(client,\r
716             "/_matrix/client/v3/sync",\r
717             outSyncBuffer, outSyncCap,\r
718             true);\r
719 }\r
720 \r
721 bool\r
722 MatrixClientShareMegolmOutSession(\r
723     MatrixClient * client,\r
724     const char * userId,\r
725     const char * deviceId,\r
726     MatrixMegolmOutSession * session)\r
727 {\r
728     // generate room key event\r
729     static char eventBuffer[KEY_SHARE_EVENT_LEN];\r
730     sprintf(eventBuffer,\r
731         "{"\r
732             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
733             "\"room_id\":\"%s\","\r
734             "\"session_id\":\"%s\","\r
735             "\"session_key\":\"%s\""\r
736         "}",\r
737         session->roomId,\r
738         session->id,\r
739         session->key\r
740     );\r
741 \r
742     // // get olm session\r
743     // MatrixOlmSession * olmSession;\r
744     // MatrixClientGetOlmSession(client, userId, deviceId, &olmSession);\r
745 \r
746     // // encrypt\r
747     // char encryptedBuffer[KEY_SHARE_EVENT_LEN];\r
748     // MatrixOlmSessionEncrypt(olmSession,\r
749     //     eventBuffer,\r
750     //     encryptedBuffer, KEY_SHARE_EVENT_LEN);\r
751 \r
752     // send\r
753     MatrixClientSendToDeviceEncrypted(client,\r
754         userId,\r
755         deviceId,\r
756         eventBuffer,\r
757         "m.room_key");\r
758 \r
759     return true;\r
760 }\r
761 \r
762 bool\r
763 MatrixClientShareMegolmOutSessionTest(\r
764     MatrixClient * client,\r
765     const char * deviceId,\r
766     MatrixMegolmOutSession * session)\r
767 {\r
768     // generate room key event\r
769     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
770     sprintf(eventBuffer,\r
771         "{"\r
772             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
773             "\"room_id\":\"%s\","\r
774             "\"session_id\":\"%s\","\r
775             "\"session_key\":\"%s\""\r
776         "}",\r
777         session->roomId,\r
778         session->id,\r
779         session->key\r
780     );\r
781 \r
782     // send\r
783     MatrixClientSendToDevice(client,\r
784         client->userId,\r
785         deviceId,\r
786         eventBuffer,\r
787         "m.room_key");\r
788 \r
789     return true;\r
790 }\r
791 \r
792 // bool\r
793 // MatrixClientSetMegolmOutSession(\r
794 //     MatrixClient * client,\r
795 //     const char * roomId,\r
796 //     MatrixMegolmOutSession session)\r
797 // {\r
798 //     if (client->numMegolmOutSessions < 10)\r
799 //     {\r
800 //         session.roomId = roomId;\r
801 //         client->megolmOutSessions[client->numMegolmOutSessions] = session;\r
802 //         client->numMegolmOutSessions++;\r
803 \r
804 //         return true;\r
805 //     }\r
806 //     return false;\r
807 // }\r
808 \r
809 bool\r
810 MatrixClientGetMegolmOutSession(\r
811     MatrixClient * client,\r
812     const char * roomId,\r
813     MatrixMegolmOutSession ** outSession)\r
814 {\r
815     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
816     {\r
817         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
818         {\r
819             *outSession = &client->megolmOutSessions[i];\r
820             return true;\r
821         }\r
822     }\r
823 \r
824     if (MatrixClientInitMegolmOutSession(client, roomId)) {\r
825         *outSession = &client->megolmOutSessions[client->numMegolmOutSessions-1];\r
826         return true;\r
827     }\r
828 \r
829     return false;\r
830 }\r
831 \r
832 bool\r
833 MatrixClientInitMegolmOutSession(\r
834     MatrixClient * client,\r
835     const char * roomId)\r
836 {\r
837     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
838     {\r
839         MatrixMegolmOutSessionInit(\r
840             &client->megolmOutSessions[client->numMegolmOutSessions],\r
841             roomId);\r
842         \r
843         client->numMegolmOutSessions++;\r
844 \r
845         return true;\r
846     }\r
847     return false;\r
848 }\r
849 \r
850 bool\r
851 MatrixClientGetOlmSession(\r
852     MatrixClient * client,\r
853     const char * userId,\r
854     const char * deviceId,\r
855     MatrixOlmSession ** outSession)\r
856 {\r
857     for (int i = 0; i < client->numOlmSessions; i++)\r
858     {\r
859         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
860         {\r
861             *outSession = &client->olmSessions[i];\r
862             return true;\r
863         }\r
864     }\r
865 \r
866     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
867     {\r
868         static char deviceKey[DEVICE_KEY_SIZE];\r
869         MatrixClientGetDeviceKey(client,\r
870             deviceId,\r
871             deviceKey, DEVICE_KEY_SIZE);\r
872 \r
873         char onetimeKey[ONETIME_KEY_SIZE];\r
874         MatrixClientClaimOnetimeKey(client,\r
875             userId,\r
876             deviceId,\r
877             onetimeKey, ONETIME_KEY_SIZE);\r
878 \r
879         MatrixOlmSessionTo(\r
880             &client->olmSessions[client->numOlmSessions],\r
881             client->olmAccount.account,\r
882             deviceId,\r
883             deviceKey,\r
884             onetimeKey);\r
885 \r
886         *outSession = &client->olmSessions[client->numOlmSessions];\r
887         \r
888         client->numOlmSessions++;\r
889 \r
890         return true;\r
891     }\r
892 \r
893     return false;\r
894 }\r
895 \r
896 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
897 bool\r
898 MatrixClientSendToDevice(\r
899     MatrixClient * client,\r
900     const char * userId,\r
901     const char * deviceId,\r
902     const char * message,\r
903     const char * msgType)\r
904 {\r
905     static char requestUrl[MAX_URL_LEN];\r
906     sprintf(requestUrl,\r
907         TODEVICE_URL, msgType, (int)time(NULL));\r
908 \r
909     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
910     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
911         "{"\r
912             "\"messages\": {"\r
913                 "\"%s\": {"\r
914                     "\"%s\":%s"\r
915                 "}"\r
916             "}"\r
917         "}",\r
918         userId,\r
919         deviceId,\r
920         message);\r
921 \r
922     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
923     bool result =\r
924         MatrixHttpPut(client,\r
925             requestUrl,\r
926             eventBuffer,\r
927             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
928             true);\r
929     \r
930     return result;\r
931 }\r
932 \r
933 bool\r
934 MatrixClientSendToDeviceEncrypted(\r
935     MatrixClient * client,\r
936     const char * userId,\r
937     const char * deviceId,\r
938     const char * message,\r
939     const char * msgType)\r
940 {\r
941     // get olm session\r
942     MatrixOlmSession * olmSession;\r
943     MatrixClientGetOlmSession(client, userId, deviceId, &olmSession);\r
944 \r
945     // create event json\r
946     char deviceKey[DEVICE_KEY_SIZE];\r
947     MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);\r
948     \r
949     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
950     sprintf(eventBuffer,\r
951         "{"\r
952         "\"type\": \"%s\","\r
953         "\"content\": \"%s\","\r
954         "\"sender\": \"%s\","\r
955         "\"recipient\": \"%s\","\r
956         "\"recipient_keys\": {"\r
957           "\"ed25519\": \"%s\""\r
958         "},"\r
959         "\"keys\": {"\r
960           "\"ed25519\": \"%s\""\r
961         "}"\r
962         "}",\r
963         msgType,\r
964         message,\r
965         client->userId,\r
966         userId, // recipient user id\r
967         deviceKey, // recipient device key\r
968         client->deviceKey);\r
969 \r
970     // encrypt\r
971     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
972     MatrixOlmSessionEncrypt(olmSession,\r
973         eventBuffer,\r
974         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
975 \r
976     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
977     sprintf(encryptedEventBuffer,\r
978         "{"\r
979         "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","\r
980         "\"ciphertext\":{"\r
981           "\"%s\":{"\r
982             "\"body\":\"%s\","\r
983             "\"type\":%d"\r
984           "}"\r
985         "},"\r
986         "\"device_id\":\"%s\","\r
987         "\"sender_key\":\"%s\""\r
988         "}",\r
989         deviceKey,\r
990         encryptedBuffer,\r
991         0, //olmSession->type,\r
992         client->deviceId,\r
993         client->deviceKey);\r
994 \r
995     // send\r
996     return MatrixClientSendToDevice(\r
997         client,\r
998         userId,\r
999         deviceId,\r
1000         encryptedEventBuffer,\r
1001         "m.room.encrypted");\r
1002 }\r
1003 \r
1004 bool\r
1005 MatrixClientFindDevice(\r
1006     MatrixClient * client,\r
1007     const char * deviceId,\r
1008     MatrixDevice ** outDevice)\r
1009 {\r
1010     MatrixClientRequestDeviceKeys(client);\r
1011 \r
1012     for (int i = 0; i < client->numDevices; i++)\r
1013     {\r
1014         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1015         {\r
1016             *outDevice = &client->devices[i];\r
1017             return true;\r
1018         }\r
1019     }\r
1020 \r
1021     *outDevice = NULL;\r
1022     return false;\r
1023 }\r
1024 \r
1025 bool\r
1026 MatrixClientGetDeviceKey(\r
1027     MatrixClient * client,\r
1028     const char * deviceId,\r
1029     char * outDeviceKey, int outDeviceKeyCap)\r
1030 {\r
1031     MatrixDevice * device;\r
1032     \r
1033     if (MatrixClientFindDevice(client, deviceId, &device))\r
1034     {\r
1035         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1036         return true;\r
1037     }\r
1038 \r
1039     MatrixClientRequestDeviceKeys(client);\r
1040     \r
1041     if (MatrixClientFindDevice(client, deviceId, &device))\r
1042     {\r
1043         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1044         return true;\r
1045     }\r
1046 \r
1047     return false;\r
1048 }\r
1049 \r
1050 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
1051 bool\r
1052 MatrixClientRequestDeviceKeys(\r
1053     MatrixClient * client)\r
1054 {\r
1055     static char userIdEscaped[USER_ID_SIZE];\r
1056     JsonEscape(client->userId, strlen(client->userId),\r
1057         userIdEscaped, USER_ID_SIZE);\r
1058 \r
1059     static char request[KEYS_QUERY_REQUEST_SIZE];\r
1060     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
1061         "{\"device_keys\":{\"%s\":[]}}", client->userId);\r
1062 \r
1063     static char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
1064     bool requestResult = MatrixHttpPost(client,\r
1065         KEYS_QUERY_URL,\r
1066         request,\r
1067         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
1068         true);\r
1069 \r
1070     if (! requestResult)\r
1071         return false;\r
1072 \r
1073     // query for retrieving device keys for user id\r
1074     static char query[JSON_QUERY_SIZE];\r
1075     snprintf(query, JSON_QUERY_SIZE,\r
1076         "$.device_keys.%s", userIdEscaped);\r
1077     \r
1078     const char * s;\r
1079     int slen;\r
1080     mjson_find(responseBuffer, strlen(responseBuffer),\r
1081         query, &s, &slen);\r
1082 \r
1083     // loop over keys\r
1084     \r
1085     int koff, klen, voff, vlen, vtype, off = 0;\r
1086     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1087                                     &voff, &vlen, &vtype)) != 0; ) {\r
1088         const char * key = s + koff;\r
1089         const char * val = s + voff;\r
1090 \r
1091         // set device id, "key" is the JSON key\r
1092         MatrixDevice d;\r
1093         snprintf(d.deviceId, DEVICE_ID_SIZE,\r
1094             "%.*s", klen-2, key+1);\r
1095 \r
1096         // look for device key in value\r
1097         static char deviceKeyQuery[JSON_QUERY_SIZE];\r
1098         snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
1099             "$.keys.curve25519:%s", d.deviceId);\r
1100         mjson_get_string(val, vlen,\r
1101             deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
1102 \r
1103         // add device\r
1104         if (client->numDevices < NUM_DEVICES)\r
1105         {\r
1106             client->devices[client->numDevices] = d;\r
1107             client->numDevices++;\r
1108         }\r
1109         else\r
1110         {\r
1111             return false;\r
1112         }\r
1113     }\r
1114 \r
1115     return true;\r
1116 }\r