]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
remove newline
[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 #include <olm/sas.h>\r
7 \r
8 #ifdef ESP_PLATFORM\r
9 #include <esp_random.h>\r
10 #endif\r
11 \r
12 // can be used to disable static allocation\r
13 #define STATIC static\r
14 \r
15 // DEFINES\r
16 #define LOGIN_REQUEST_SIZE 1024\r
17 #define LOGIN_RESPONSE_SIZE 1024\r
18 #define LOGIN_URL "/_matrix/client/v3/login"\r
19 \r
20 #define ENCRYPTED_REQUEST_SIZE (1024*5)\r
21 STATIC char g_EncryptedRequestBuffer[ENCRYPTED_REQUEST_SIZE];\r
22 #define ENCRYPTED_EVENT_SIZE (1024*10)\r
23 STATIC char g_EncryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
24 #define ROOM_SEND_REQUEST_SIZE 256\r
25 #define ROOM_SEND_RESPONSE_SIZE 1024\r
26 #define ROOM_SEND_URL "/_matrix/client/v3/rooms/%s/send/%s/%d"\r
27 \r
28 #define ROOMKEY_REQUEST_SIZE (1024*4)\r
29 \r
30 #define TODEVICE_EVENT_SIZE (1024*5)\r
31 STATIC char g_TodeviceEventBuffer[TODEVICE_EVENT_SIZE];\r
32 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"\r
33 \r
34 #define KEYS_QUERY_URL "/_matrix/client/v3/keys/query"\r
35 #define KEYS_QUERY_REQUEST_SIZE 256\r
36 #define KEYS_QUERY_RESPONSE_SIZE (1024*5)\r
37 \r
38 #define KEYS_UPLOAD_URL "/_matrix/client/v3/keys/upload"\r
39 #define KEYS_UPLOAD_REQUEST_SIZE 1024*4\r
40 STATIC char g_KeysUploadRequestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
41 #define KEYS_UPLOAD_REQUEST_SIGNED_SIZE 2048*4\r
42 STATIC char g_KeysUploadRequestSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
43 #define KEYS_UPLOAD_RESPONSE_SIZE 2048\r
44 \r
45 #define KEYS_CLAIM_URL "/_matrix/client/v3/keys/claim"\r
46 #define KEYS_CLAIM_REQUEST_SIZE 1024\r
47 #define KEYS_CLAIM_RESPONSE_SIZE 1024\r
48 \r
49 #define SYNC_TIMEOUT 5000\r
50 \r
51 #define JSON_QUERY_SIZE 128\r
52 #define JSON_MAX_INDICES 100\r
53 #define JSON_MAX_ENTRY_SIZE 1024\r
54 \r
55 #define MAX(a,b) ((a) > (b) ? (a) : (b))\r
56 #define MIN(a,b) ((a) < (b) ? (a) : (b))\r
57 \r
58 // Util\r
59 \r
60 void\r
61 Randomize(\r
62     uint8_t * random,\r
63     int randomLen)\r
64 {\r
65     #ifdef ESP_PLATFORM\r
66 \r
67     for (int i = 0; i < randomLen; i++)\r
68     {\r
69         random[i] = esp_random() % 256;\r
70     }\r
71 \r
72     #else\r
73 \r
74     STATIC bool first = true;\r
75     if (first) { srand(time(0)); first = false; }\r
76 \r
77     for (int i = 0; i < randomLen; i++)\r
78     {\r
79         random[i] = rand() % 256;\r
80     }\r
81 \r
82     #endif\r
83 }\r
84 \r
85 bool\r
86 JsonEscape(\r
87     const char * sIn, int sInLen,\r
88     char * sOut, int sOutCap)\r
89 {\r
90     int sOutIndex = 0;\r
91 \r
92     for (int i = 0; i < sInLen; i++)\r
93     {\r
94         if (i >= sOutCap)\r
95             return false;\r
96         \r
97         if (sIn[i] == '.' ||\r
98             sIn[i] == '[' ||\r
99             sIn[i] == ']'\r
100         ) {\r
101             sOut[sOutIndex++] = '\\';\r
102         }\r
103         sOut[sOutIndex++] = sIn[i];\r
104     }\r
105 \r
106     if (sOutIndex < sOutCap)\r
107         sOut[sOutIndex] = '\0';\r
108 \r
109     return true;\r
110 }\r
111 \r
112 bool\r
113 JsonCanonicalize(\r
114     const char * sIn, int sInLen,\r
115     char * sOut, int sOutCap)\r
116 {\r
117     snprintf(sOut, sOutCap, "{}");\r
118 \r
119     int koff, klen, voff, vlen, vtype, off;\r
120 \r
121     struct Key {\r
122         const char * ptr;\r
123         int len;\r
124     };\r
125 \r
126     struct Key keys[JSON_MAX_INDICES];\r
127     int numKeys = 0;\r
128 \r
129     for (off = 0; (off = mjson_next(sIn, sInLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0; ) {\r
130         keys[numKeys].ptr = sIn + koff;\r
131         keys[numKeys].len = klen;\r
132         numKeys++;\r
133     }\r
134 \r
135     for (int i = 0; i < numKeys; i++) {\r
136         for (int j = i; j < numKeys; j++) {\r
137             if (\r
138                 strncmp(\r
139                     keys[i].ptr,\r
140                     keys[j].ptr,\r
141                     MIN(keys[i].len, keys[j].len)\r
142                 ) > 0\r
143             ) {\r
144                 struct Key k = keys[i];\r
145                 keys[i] = keys[j];\r
146                 keys[j] = k;\r
147             }\r
148         }\r
149     }\r
150 \r
151     for (int i = 0; i < numKeys; i++) {\r
152         char jp[JSON_QUERY_SIZE];\r
153         snprintf(jp, JSON_QUERY_SIZE, "$.%.*s", keys[i].len-2, keys[i].ptr+1);\r
154 \r
155         const char * valPtr;\r
156         int valLen;\r
157         mjson_find(sIn, sInLen, jp, &valPtr, &valLen);\r
158         \r
159         STATIC char newEntry[JSON_MAX_ENTRY_SIZE];\r
160         snprintf(newEntry, JSON_MAX_ENTRY_SIZE, "{%.*s:%.*s}", keys[i].len, keys[i].ptr, valLen, valPtr);\r
161 \r
162         char * buffer = strdup(sOut);\r
163 \r
164         struct mjson_fixedbuf fb = { sOut, sOutCap, 0 };\r
165         mjson_merge(buffer, strlen(buffer), newEntry, strlen(newEntry), mjson_print_fixed_buf, &fb);\r
166 \r
167         free(buffer);\r
168     }\r
169 \r
170     // TODO: recursively sort entries\r
171 \r
172     return true;\r
173 }\r
174 \r
175 bool JsonSign(\r
176     MatrixClient * client,\r
177     const char * sIn, int sInLen,\r
178     char * sOut, int sOutCap)\r
179 {\r
180     STATIC char signature[OLM_SIGNATURE_SIZE];\r
181     size_t res =\r
182         olm_account_sign(client->olmAccount.account,\r
183             sIn, sInLen,\r
184             signature, OLM_SIGNATURE_SIZE);\r
185     \r
186     int signatureLen = res;\r
187     \r
188     STATIC char thisSigningKey[SIGNING_KEY_SIZE];\r
189     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, SIGNING_KEY_SIZE);\r
190 \r
191     STATIC char signatureJson[JSON_SIGNATURE_SIZE];\r
192     int signatureJsonLen =\r
193         mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,\r
194             "{"\r
195                 "\"signatures\":{"\r
196                     "\"%s\":{"\r
197                         "\"ed25519:%s\":\"%.*s\""\r
198                     "}"\r
199                 "}"\r
200             "}",\r
201             client->userId,\r
202             //"1",\r
203             client->deviceId,\r
204             signatureLen, signature);\r
205 \r
206     struct mjson_fixedbuf result = { sOut, sOutCap, 0 };\r
207     mjson_merge(\r
208         sIn, sInLen,\r
209         signatureJson, signatureJsonLen,\r
210         mjson_print_fixed_buf,\r
211         &result);\r
212 \r
213     return true;\r
214 }\r
215 \r
216 bool\r
217 MatrixOlmAccountInit(\r
218     MatrixOlmAccount * account)\r
219 {\r
220     account->account = olm_account(account->memory);\r
221 \r
222     STATIC uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
223     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
224 \r
225     size_t res = olm_create_account(\r
226         account->account,\r
227         random,\r
228         OLM_ACCOUNT_RANDOM_SIZE);\r
229 \r
230     return res != olm_error();\r
231 }\r
232 \r
233 bool\r
234 MatrixOlmAccountUnpickle(\r
235     MatrixOlmAccount * account,\r
236     void * pickled, int pickledLen,\r
237     const void * key, int keyLen)\r
238 {\r
239     size_t res;\r
240     res = olm_unpickle_account(account->account,\r
241         key, keyLen,\r
242         pickled, pickledLen);\r
243     if (res == olm_error()) {\r
244         printf("error unpickling olm account:%s\n",\r
245             olm_account_last_error(account->account));\r
246     }\r
247     return res != olm_error();\r
248 }\r
249 \r
250 bool\r
251 MatrixOlmAccountGetDeviceKey(\r
252     MatrixOlmAccount * account,\r
253     char * key, int keyCap)\r
254 {\r
255     STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
256     size_t res =\r
257         olm_account_identity_keys(account->account,\r
258             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
259     mjson_get_string(deviceKeysJson, res,\r
260         "$.curve25519",\r
261         key, keyCap);\r
262     return true;\r
263 }\r
264 \r
265 bool\r
266 MatrixOlmAccountGetSigningKey(\r
267     MatrixOlmAccount * account,\r
268     char * key, int keyCap)\r
269 {\r
270     STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
271     size_t res =\r
272         olm_account_identity_keys(account->account,\r
273             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
274     mjson_get_string(deviceKeysJson, res,\r
275         "$.ed25519",\r
276         key, keyCap);\r
277     return true;\r
278 }\r
279 \r
280 bool\r
281 MatrixOlmSessionFrom(\r
282     MatrixOlmSession * session,\r
283     OlmAccount * olmAccount,\r
284     const char * deviceId,\r
285     const char * deviceKey,\r
286     const char * encrypted)\r
287 {\r
288     memset(session, 0, sizeof(MatrixOlmSession));\r
289 \r
290     session->deviceId = deviceId;\r
291 \r
292     session->session =\r
293         olm_session(session->memory);\r
294     \r
295     char * encryptedCopy = strdup(encrypted);\r
296     \r
297     size_t res =\r
298         olm_create_inbound_session_from(session->session, olmAccount,\r
299             deviceKey, strlen(deviceKey),\r
300             encryptedCopy, strlen(encryptedCopy));\r
301     \r
302     if (res == olm_error()) {\r
303         printf("error olm:%s\n", olm_session_last_error(session->session));\r
304     }\r
305 \r
306     return res != olm_error();\r
307 }\r
308 \r
309 bool\r
310 MatrixOlmSessionTo(\r
311     MatrixOlmSession * session,\r
312     OlmAccount * olmAccount,\r
313     const char * deviceId,\r
314     const char * deviceKey,\r
315     const char * deviceOnetimeKey)\r
316 {\r
317     memset(session, 0, sizeof(MatrixOlmSession));\r
318 \r
319     session->deviceId = deviceId;\r
320 \r
321     session->session =\r
322         olm_session(session->memory);\r
323 \r
324     STATIC uint8_t random[OLM_OUTBOUND_SESSION_RANDOM_SIZE];\r
325     Randomize(random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
326 \r
327     size_t res =\r
328         olm_create_outbound_session(session->session,\r
329             olmAccount,\r
330             deviceKey, strlen(deviceKey),\r
331             deviceOnetimeKey, strlen(deviceOnetimeKey),\r
332             random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
333     \r
334     if (res == olm_error()) {\r
335         printf("error olm:%s\n", olm_session_last_error(session->session));\r
336     }\r
337 \r
338     return res != olm_error();\r
339 }\r
340 \r
341 bool\r
342 MatrixOlmSessionUnpickle(\r
343     MatrixOlmSession * session,\r
344     const char * deviceId,\r
345     void * pickled, int pickledLen,\r
346     const void * key, int keyLen)\r
347 {\r
348     memset(session, 0, sizeof(MatrixOlmSession));\r
349 \r
350     session->deviceId = deviceId;\r
351 \r
352     session->session =\r
353         olm_session(session->memory);\r
354     \r
355     size_t res;\r
356     res = olm_unpickle_session(session->session,\r
357         key, keyLen,\r
358         pickled, pickledLen);\r
359     \r
360     if (res == olm_error()) {\r
361         printf("error unpickling olm session:%s\n", olm_session_last_error(session->session));\r
362     }\r
363 \r
364     return res != olm_error();\r
365 }\r
366 \r
367 bool\r
368 MatrixOlmSessionEncrypt(\r
369     MatrixOlmSession * session,\r
370     const char * plaintext,\r
371     char * outBuffer, int outBufferCap)\r
372 {\r
373     STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
374     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
375 \r
376     memset(outBuffer, 0, outBufferCap);\r
377     size_t res = olm_encrypt(session->session,\r
378         plaintext, strlen(plaintext),\r
379         random, OLM_ENCRYPT_RANDOM_SIZE,\r
380         outBuffer, outBufferCap);\r
381 \r
382     return res != olm_error();\r
383 }\r
384 \r
385 bool\r
386 MatrixOlmSessionDecrypt(\r
387     MatrixOlmSession * session,\r
388     size_t messageType,\r
389     char * encrypted,\r
390     char * outBuffer, int outBufferCap)\r
391 {\r
392     STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
393     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
394 \r
395     size_t res =\r
396         olm_decrypt(session->session,\r
397             messageType,\r
398             encrypted, strlen(encrypted),\r
399             outBuffer, outBufferCap);\r
400     \r
401     if (res != olm_error() && (int)res < outBufferCap)\r
402         outBuffer[res] = '\0';\r
403 \r
404     return res != olm_error();\r
405 }\r
406 \r
407 bool\r
408 MatrixMegolmInSessionInit(\r
409     MatrixMegolmInSession * session,\r
410     const char * roomId,\r
411     const char * sessionId,\r
412     const char * sessionKey, int sessionKeyLen)\r
413 {\r
414     memset(session, 0, sizeof(MatrixMegolmInSession));\r
415     \r
416     strncpy(session->roomId, roomId, sizeof(session->roomId));\r
417     strncpy(session->id, sessionId, sizeof(session->id));\r
418     strncpy(session->key, sessionKey, sizeof(session->key));\r
419 \r
420     session->session =\r
421         olm_inbound_group_session(session->memory);\r
422 \r
423     size_t res =\r
424         olm_init_inbound_group_session(\r
425         // olm_import_inbound_group_session(\r
426             session->session,\r
427             (const uint8_t *)sessionKey, sessionKeyLen);\r
428     if (res == olm_error()) {\r
429         printf("Error initializing Megolm session: %s\n", olm_inbound_group_session_last_error(session->session));\r
430     }\r
431 \r
432     return res != olm_error();\r
433 }\r
434 \r
435 bool\r
436 MatrixMegolmInSessionDecrypt(\r
437     MatrixMegolmInSession * session,\r
438     const char * encrypted, int encryptedLen,\r
439     char * outDecrypted, int outDecryptedCap)\r
440 {\r
441     // uint8_t buffer[1024];\r
442     // memcpy(buffer, encrypted, encryptedLen);\r
443 \r
444     uint32_t megolmInMessageIndex;\r
445 \r
446     size_t res =\r
447         olm_group_decrypt(session->session,\r
448             (uint8_t *)encrypted, encryptedLen,\r
449             (uint8_t *)outDecrypted, outDecryptedCap,\r
450             &megolmInMessageIndex);\r
451     \r
452     if (res == olm_error()) {\r
453         printf("error decrypting megolm message: %s\n", olm_inbound_group_session_last_error(session->session));\r
454     }\r
455     \r
456     return true;\r
457 }\r
458 \r
459 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
460 bool\r
461 MatrixMegolmOutSessionInit(\r
462     MatrixMegolmOutSession * session,\r
463     const char * roomId)\r
464 {\r
465     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
466 \r
467     STATIC uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
468     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
469 \r
470     strncpy(session->roomId, roomId, ROOM_ID_SIZE);\r
471 \r
472     session->session =\r
473         olm_outbound_group_session(session->memory);\r
474 \r
475     olm_init_outbound_group_session(\r
476         session->session,\r
477         random,\r
478         MEGOLM_INIT_RANDOM_SIZE);\r
479 \r
480     olm_outbound_group_session_id(session->session,\r
481         (uint8_t *)session->id,\r
482         MEGOLM_SESSION_ID_SIZE);\r
483         \r
484     olm_outbound_group_session_key(session->session,\r
485         (uint8_t *)session->key,\r
486         MEGOLM_SESSION_KEY_SIZE);\r
487     \r
488     return true;\r
489 }\r
490 \r
491 bool\r
492 MatrixMegolmOutSessionEncrypt(\r
493     MatrixMegolmOutSession * session,\r
494     const char * plaintext,\r
495     char * outBuffer, int outBufferCap)\r
496 {\r
497     memset(outBuffer, 0, outBufferCap);\r
498     size_t res = olm_group_encrypt(session->session,\r
499         (uint8_t *)plaintext, strlen(plaintext),\r
500         (uint8_t *)outBuffer, outBufferCap);\r
501 \r
502     return res != olm_error();\r
503 }\r
504 \r
505 \r
506 bool\r
507 MatrixClientInit(\r
508     MatrixClient * client)\r
509 {\r
510     memset(client, 0, sizeof(MatrixClient));\r
511 \r
512     // init olm account\r
513     MatrixOlmAccountInit(&client->olmAccount);\r
514 \r
515     return true;\r
516 }\r
517 \r
518 bool\r
519 MatrixClientSetAccessToken(\r
520     MatrixClient * client,\r
521     const char * accessToken)\r
522 {\r
523     for (int i = 0; i < ACCESS_TOKEN_SIZE-1; i++)\r
524         client->accessToken[i] = accessToken[i];\r
525     client->accessToken[ACCESS_TOKEN_SIZE-1] = '\0';\r
526 \r
527     return true;\r
528 }\r
529 \r
530 bool\r
531 MatrixClientSetDeviceId(\r
532     MatrixClient * client,\r
533     const char * deviceId)\r
534 {\r
535     for (int i = 0; i < DEVICE_ID_SIZE-1; i++)\r
536         client->deviceId[i] = deviceId[i];\r
537     client->deviceId[DEVICE_ID_SIZE-1] = '\0';\r
538 \r
539     return true;\r
540 }\r
541 \r
542 bool\r
543 MatrixClientSetUserId(\r
544     MatrixClient * client,\r
545     const char * userId)\r
546 {\r
547     for (int i = 0; i < USER_ID_SIZE-1; i++)\r
548         client->userId[i] = userId[i];\r
549     client->userId[USER_ID_SIZE-1] = '\0';\r
550 \r
551     return true;\r
552 }\r
553 \r
554 bool\r
555 MatrixClientGenerateOnetimeKeys(\r
556     MatrixClient * client,\r
557     int numberOfKeys)\r
558 {\r
559     STATIC uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
560     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
561 \r
562     size_t res =\r
563         olm_account_generate_one_time_keys(client->olmAccount.account,\r
564             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
565 \r
566     return res != olm_error();\r
567 }\r
568 \r
569 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysupload\r
570 bool\r
571 MatrixClientUploadOnetimeKeys(\r
572     MatrixClient * client)\r
573 {\r
574     mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
575         "{");\r
576 \r
577     STATIC char onetimeKeysBuffer[1024];\r
578     olm_account_one_time_keys(client->olmAccount.account,\r
579         onetimeKeysBuffer, 1024);\r
580 \r
581     // olm_account_one_time_keys returns a json object\r
582     // find curve25519 member\r
583     const char *keys;\r
584     int keysLen;\r
585     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
586 \r
587     // iterate over onetime keys, create key object\r
588     // sign it and append to request\r
589     int koff, klen, voff, vlen, vtype, off = 0;\r
590     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
591         STATIC char keyJson[JSON_ONETIME_KEY_SIZE];\r
592         \r
593         int keyJsonLen =\r
594             snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
595                 "{\"key\":\"%.*s\"}",\r
596                 vlen-2, keys + voff+1);\r
597 \r
598         STATIC char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
599 \r
600         JsonSign(client,\r
601             keyJson, keyJsonLen,\r
602             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
603         \r
604         mjson_snprintf(g_KeysUploadRequestBuffer+strlen(g_KeysUploadRequestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(g_KeysUploadRequestBuffer),\r
605             "\"signed_curve25519:%.*s\":%s,",\r
606             klen-2, keys + koff+1,\r
607             keyJsonSigned);\r
608     }\r
609 \r
610     // delete last ',' since the loop always appends it\r
611     if (g_KeysUploadRequestBuffer[strlen(g_KeysUploadRequestBuffer)-1] == ',')\r
612         g_KeysUploadRequestBuffer[strlen(g_KeysUploadRequestBuffer)-1] = '\0';\r
613 \r
614     mjson_snprintf(g_KeysUploadRequestBuffer+strlen(g_KeysUploadRequestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(g_KeysUploadRequestBuffer),\r
615         "}");\r
616 \r
617     snprintf(g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
618     "{\"one_time_keys\":%s}", g_KeysUploadRequestBuffer);\r
619 \r
620     STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
621     MatrixHttpPost(client->hc,\r
622         KEYS_UPLOAD_URL,\r
623         g_KeysUploadRequestSignedBuffer,\r
624         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
625         true);\r
626 \r
627     return true;\r
628 }\r
629 \r
630 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysupload\r
631 bool\r
632 MatrixClientUploadDeviceKeys(\r
633     MatrixClient * client)\r
634 {\r
635     char thisDeviceKey[DEVICE_KEY_SIZE];\r
636     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
637     char thisSigningKey[DEVICE_KEY_SIZE];\r
638     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
639 \r
640     int deviceKeysBufferLen =\r
641         mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
642             "{"\r
643                 "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
644                 "\"device_id\":\"%s\","\r
645                 "\"keys\":{"\r
646                     "\"curve25519:%s\":\"%s\","\r
647                     "\"ed25519:%s\":\"%s\""\r
648                 "},"\r
649                 "\"user_id\":\"%s\""\r
650             "}",\r
651             client->deviceId,\r
652             client->deviceId, thisDeviceKey,\r
653             client->deviceId, thisSigningKey,\r
654             client->userId);\r
655 \r
656     JsonSign(client,\r
657         g_KeysUploadRequestBuffer, deviceKeysBufferLen,\r
658         g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
659     \r
660     STATIC char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE+30];\r
661     snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE+30,\r
662     "{\"device_keys\":%s}", g_KeysUploadRequestSignedBuffer);\r
663 \r
664     STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
665     MatrixHttpPost(client->hc,\r
666         KEYS_UPLOAD_URL,\r
667         finalEvent,\r
668         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
669         true);\r
670 \r
671     return true;\r
672 }\r
673 \r
674 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysclaim\r
675 bool\r
676 MatrixClientClaimOnetimeKey(\r
677     MatrixClient * client,\r
678     const char * userId,\r
679     const char * deviceId,\r
680     char * outOnetimeKey, int outOnetimeKeyCap)\r
681 {\r
682     STATIC char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];\r
683     mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,\r
684     "{"\r
685       "\"one_time_keys\":{"\r
686         "\"%s\":{"\r
687           "\"%s\":\"signed_curve25519\""\r
688         "}"\r
689       "},"\r
690       "\"timeout\":10000"\r
691     "}",\r
692     userId,\r
693     deviceId);\r
694 \r
695     STATIC char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];\r
696     MatrixHttpPost(client->hc,\r
697         KEYS_CLAIM_URL,\r
698         requestBuffer,\r
699         responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,\r
700         true);\r
701     \r
702     STATIC char userIdEscaped[USER_ID_SIZE];\r
703     JsonEscape(userId, strlen(userId),\r
704         userIdEscaped, USER_ID_SIZE);\r
705     \r
706     // create the json query according to\r
707     // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysclaim\r
708     STATIC char query[JSON_QUERY_SIZE];\r
709     snprintf(query, JSON_QUERY_SIZE,\r
710         "$.one_time_keys.%s.%s",\r
711         userIdEscaped,\r
712         deviceId);\r
713     \r
714     // find the corresponding json object\r
715     const char * keyObject;\r
716     int keyObjectSize;\r
717     mjson_find(responseBuffer, strlen(responseBuffer),\r
718         query,\r
719         &keyObject, &keyObjectSize);\r
720     \r
721     // use mjson_next (which iterates over all key/value pairs) once\r
722     // because we only request one key\r
723     // (see https://github.com/cesanta/mjson#mjson_next for details)\r
724     int koff, klen, voff, vlen, vtype;\r
725     mjson_next(keyObject, keyObjectSize, 0,\r
726         &koff, &klen, &voff, &vlen, &vtype);\r
727     \r
728     mjson_get_string(keyObject + voff, vlen,\r
729         "$.key", outOnetimeKey, outOnetimeKeyCap);\r
730     \r
731     // TODO: verify signature\r
732     \r
733     return true;\r
734 }\r
735 \r
736 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3login\r
737 bool\r
738 MatrixClientLoginPassword(\r
739     MatrixClient * client,\r
740     const char * username,\r
741     const char * password,\r
742     const char * displayName)\r
743 {\r
744     STATIC char requestBuffer[LOGIN_REQUEST_SIZE];\r
745 \r
746     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
747         "{"\r
748             "\"type\":\"m.login.password\","\r
749             "\"identifier\":{"\r
750                 "\"type\":\"m.id.user\","\r
751                 "\"user\":\"%s\""\r
752             "},"\r
753             "\"password\":\"%s\","\r
754             "\"initial_device_display_name\":\"%s\""\r
755         "}",\r
756         username,\r
757         password,\r
758         displayName);\r
759     \r
760     STATIC char responseBuffer[LOGIN_RESPONSE_SIZE];\r
761     bool result =\r
762         MatrixHttpPost(client->hc,\r
763             LOGIN_URL,\r
764             requestBuffer,\r
765             responseBuffer, LOGIN_RESPONSE_SIZE,\r
766             false);\r
767     \r
768     if (!result)\r
769         return false;\r
770     \r
771     int responseLen = strlen(responseBuffer);\r
772 \r
773     // store variables in MatrixClient\r
774     mjson_get_string(responseBuffer, responseLen,\r
775         "$.access_token",\r
776         client->accessToken, ACCESS_TOKEN_SIZE);\r
777     mjson_get_string(responseBuffer, responseLen,\r
778         "$.device_id",\r
779         client->deviceId, DEVICE_ID_SIZE);\r
780     mjson_get_string(responseBuffer, responseLen,\r
781         "$.expires_in_ms",\r
782         client->expireMs, EXPIRE_MS_SIZE);\r
783     mjson_get_string(responseBuffer, responseLen,\r
784         "$.refresh_token",\r
785         client->refreshToken, REFRESH_TOKEN_SIZE);\r
786         \r
787     MatrixHttpSetAccessToken(client->hc, client->accessToken);\r
788 \r
789     return true;\r
790 }\r
791 \r
792 // https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
793 bool\r
794 MatrixClientSendEvent(\r
795     MatrixClient * client,\r
796     const char * roomId,\r
797     const char * msgType,\r
798     const char * msgBody)\r
799 {    \r
800     STATIC char requestUrl[MAX_URL_LEN];\r
801     snprintf(requestUrl, MAX_URL_LEN,\r
802         ROOM_SEND_URL, roomId, msgType, (int)time(NULL));\r
803 \r
804     STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
805     bool result =\r
806         MatrixHttpPut(client->hc,\r
807             requestUrl,\r
808             msgBody,\r
809             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
810             true);\r
811     \r
812     return result;\r
813 }\r
814 \r
815 // https://spec.matrix.org/v1.8/client-server-api/#mroomencrypted\r
816 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
817 bool\r
818 MatrixClientSendEventEncrypted(\r
819     MatrixClient * client,\r
820     const char * roomId,\r
821     const char * msgType,\r
822     const char * msgBody)\r
823 {\r
824     // event json\r
825     STATIC char requestBuffer[ROOM_SEND_REQUEST_SIZE];\r
826     snprintf(requestBuffer, ROOM_SEND_REQUEST_SIZE,\r
827         "{"\r
828         "\"type\":\"%s\","\r
829         "\"content\":%s,"\r
830         "\"room_id\":\"%s\""\r
831         "}",\r
832         msgType,\r
833         msgBody,\r
834         roomId);\r
835 \r
836     // get megolm session\r
837     MatrixMegolmOutSession * outSession;\r
838     if (! MatrixClientGetMegolmOutSession(client, roomId, &outSession))\r
839         MatrixClientNewMegolmOutSession(client, roomId, &outSession);\r
840         \r
841     // encrypt\r
842     MatrixMegolmOutSessionEncrypt(outSession,\r
843         requestBuffer,\r
844         g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);\r
845 \r
846     char thisDeviceKey[DEVICE_KEY_SIZE];\r
847     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
848     \r
849 \r
850     // encrypted event json\r
851     const char * senderKey = thisDeviceKey;\r
852     const char * sessionId = outSession->id;\r
853     const char * deviceId = client->deviceId;\r
854 \r
855     snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,\r
856         "{"\r
857         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
858         "\"ciphertext\":\"%s\","\r
859         "\"device_id\":\"%s\","\r
860         "\"sender_key\":\"%s\","\r
861         "\"session_id\":\"%s\""\r
862         "}",\r
863         g_EncryptedRequestBuffer,\r
864         deviceId,\r
865         senderKey,\r
866         sessionId);\r
867 \r
868     // send\r
869     return MatrixClientSendEvent(client,\r
870         roomId,\r
871         "m.room.encrypted",\r
872         g_EncryptedEventBuffer);\r
873 }\r
874 \r
875 // this handles to_device events received from a sync\r
876 // mainly for verification (m.key.verification.* events)\r
877 void\r
878 MatrixClientHandleEvent(\r
879     MatrixClient * client,\r
880     const char * event, int eventLen\r
881 ) {\r
882     // get the event type\r
883     STATIC char eventType[128];\r
884     memset(eventType, 0, sizeof(eventType));\r
885     mjson_get_string(event, eventLen, "$.type", eventType, 128);\r
886 \r
887     // static variables for verification\r
888     // since verification takes multiple requests\r
889     // data is cleared when verification is finished or started\r
890     static char transactionId[64];\r
891     static char verifyFromDeviceId[DEVICE_ID_SIZE];\r
892     static OlmSAS * olmSas = NULL;\r
893 \r
894     // initial verification request, reply that we are ready to verify\r
895     if (strcmp(eventType, "m.key.verification.request") == 0) {\r
896         // reset static data\r
897         memset(transactionId, 0, 64);\r
898         memset(verifyFromDeviceId, 0, DEVICE_ID_SIZE);\r
899         if (olmSas != NULL)\r
900             free(olmSas);\r
901         \r
902         mjson_get_string(event, eventLen, "$.content.transaction_id", transactionId, 64);\r
903         mjson_get_string(event, eventLen, "$.content.from_device", verifyFromDeviceId, DEVICE_ID_SIZE);\r
904         \r
905         char verificationReadyBuffer[2048];\r
906         snprintf(verificationReadyBuffer, 2048,\r
907             "{"\r
908             "\"from_device\":\"%s\","\r
909             "\"methods\":[\"m.sas.v1\"],"\r
910             "\"transaction_id\":\"%s\""\r
911             "}",\r
912             client->deviceId,\r
913             transactionId);\r
914         \r
915         MatrixClientSendToDevice(client,\r
916             client->userId,\r
917             verifyFromDeviceId,\r
918             verificationReadyBuffer,\r
919             "m.key.verification.ready");\r
920     }\r
921     else if (strcmp(eventType, "m.key.verification.start") == 0) {\r
922         olmSas = olm_sas(malloc(olm_sas_size()));\r
923         void * sasRandomBytes = malloc(olm_create_sas_random_length(olmSas));\r
924         olm_create_sas(olmSas,\r
925             sasRandomBytes,\r
926             olm_create_sas_random_length(olmSas));\r
927         \r
928         OlmUtility * olmUtil = olm_utility(malloc(olm_utility_size()));\r
929         \r
930         // calculate commitment according to \r
931         // https://spec.matrix.org/v1.8/client-server-api/#mkeyverificationaccept\r
932         STATIC char publicKey[64];\r
933         STATIC char keyStartJsonCanonical[512];\r
934         STATIC char concat[512+64];\r
935         STATIC char commitment[1024];\r
936         olm_sas_get_pubkey(olmSas,\r
937             publicKey,\r
938             64);\r
939 \r
940         const char * keyStartJson;\r
941         int keyStartJsonLen;\r
942         mjson_find(event, eventLen, "$.content", &keyStartJson, &keyStartJsonLen);\r
943         JsonCanonicalize(keyStartJson, keyStartJsonLen, keyStartJsonCanonical, 512);\r
944 \r
945         int concatLen =\r
946             snprintf(concat, 512+64, "%.*s%s", (int)olm_sas_pubkey_length(olmSas), publicKey, keyStartJsonCanonical);\r
947 \r
948         int commitmentLen =\r
949             olm_sha256(olmUtil, concat, concatLen, commitment, 1024);\r
950         olm_clear_utility(olmUtil);\r
951         free(olmUtil);\r
952         \r
953         STATIC char verificationAcceptBuffer[512];\r
954         snprintf(verificationAcceptBuffer, 512,\r
955             "{"\r
956             "\"commitment\":\"%.*s\","\r
957             "\"hash\":\"sha256\","\r
958             "\"key_agreement_protocol\":\"curve25519\","\r
959             "\"message_authentication_code\":\"hkdf-hmac-sha256.v2\","\r
960             "\"method\":\"m.sas.v1\","\r
961             "\"short_authentication_string\":[\"decimal\"],"\r
962             "\"transaction_id\":\"%s\""\r
963             "}",\r
964             commitmentLen, commitment,\r
965             transactionId);\r
966         \r
967         MatrixClientSendToDevice(client,\r
968             client->userId,\r
969             verifyFromDeviceId,\r
970             verificationAcceptBuffer,\r
971             "m.key.verification.accept");\r
972     }\r
973     // send our sas key and calculate sas using their received key\r
974     else if (strcmp(eventType, "m.key.verification.key") == 0) {\r
975         STATIC char publicKey[128];\r
976         olm_sas_get_pubkey(olmSas,\r
977             publicKey,\r
978             128);\r
979 \r
980         STATIC char theirPublicKey[128];\r
981         int theirPublicKeyLen =\r
982             mjson_get_string(event, eventLen, "$.content.key", theirPublicKey, 128);\r
983 \r
984         olm_sas_set_their_key(olmSas, theirPublicKey, theirPublicKeyLen);\r
985         \r
986         STATIC char verificationKeyBuffer[256];\r
987         snprintf(verificationKeyBuffer, 256,\r
988             "{"\r
989             "\"key\":\"%.*s\","\r
990             "\"transaction_id\":\"%s\""\r
991             "}",\r
992             (int)olm_sas_pubkey_length(olmSas), publicKey,\r
993             transactionId);\r
994         \r
995         MatrixClientSendToDevice(client,\r
996             client->userId,\r
997             verifyFromDeviceId,\r
998             verificationKeyBuffer,\r
999             "m.key.verification.key");\r
1000         \r
1001         // sas\r
1002         STATIC char hkdfInfo[1024];\r
1003         int hkdfInfoLen =\r
1004             snprintf(hkdfInfo, 1024,\r
1005                 "MATRIX_KEY_VERIFICATION_SAS%s%s%s%s%s",\r
1006                 client->userId,\r
1007                 verifyFromDeviceId,\r
1008                 client->userId,\r
1009                 client->deviceId,\r
1010                 transactionId);\r
1011 \r
1012         unsigned char sasBytes[5];\r
1013         olm_sas_generate_bytes(olmSas,\r
1014             hkdfInfo, hkdfInfoLen,\r
1015             sasBytes, 5);\r
1016         int b0 = sasBytes[0];\r
1017         int b1 = sasBytes[1];\r
1018         int b2 = sasBytes[2];\r
1019         int b3 = sasBytes[3];\r
1020         int b4 = sasBytes[4];\r
1021 \r
1022         // for now just printf SAS numbers\r
1023         // https://spec.matrix.org/v1.8/client-server-api/#sas-method-decimal\r
1024         printf("%d | %d | %d\n",\r
1025             ((b0 << 5) | (b1 >> 3)) + 1000,\r
1026             (((b1 & 0x7) << 10) | (b2 << 2) | (b3 >> 6)) + 1000,\r
1027             (((b3 & 0x3F) << 7) | (b4 >> 1)) + 1000);\r
1028     }\r
1029     // calculate MACs for signing key, master key and the key list\r
1030     else if (strcmp(eventType, "m.key.verification.mac") == 0) {        \r
1031         // mac\r
1032         STATIC char masterKey[123];\r
1033         MatrixClientRequestMasterKey(client, masterKey, 123);\r
1034 \r
1035         STATIC char keyList[256];\r
1036         STATIC char keyListMac[256];\r
1037         STATIC char key1Id[128];\r
1038         STATIC char key1[128];\r
1039         STATIC char key1Mac[128];\r
1040         STATIC char key2Id[128];\r
1041         STATIC char key2[128];\r
1042         STATIC char key2Mac[128];\r
1043 \r
1044         // keys have to be sorted so write keys/key IDs into key1/key2\r
1045         // depending on lexicographical order\r
1046         if (strcmp(masterKey, client->deviceId) < 0) {\r
1047             snprintf(key1Id, 1024, "ed25519:%s", masterKey);\r
1048             strcpy(key1, masterKey);\r
1049             snprintf(key2Id, 1024, "ed25519:%s", client->deviceId);\r
1050             MatrixOlmAccountGetSigningKey(&client->olmAccount, key2, 1024);\r
1051         }\r
1052         else {\r
1053             snprintf(key1Id, 1024, "ed25519:%s", client->deviceId);\r
1054             MatrixOlmAccountGetSigningKey(&client->olmAccount, key1, 1024);\r
1055             snprintf(key2Id, 1024, "ed25519:%s", masterKey);\r
1056             strcpy(key2, masterKey);\r
1057         }\r
1058 \r
1059         // create key list\r
1060         snprintf(keyList, 1024,\r
1061             "%s,%s", key1Id, key2Id);\r
1062         \r
1063         // generate MAC info for both keys and key list\r
1064         // https://spec.matrix.org/v1.8/client-server-api/#mac-calculation\r
1065         STATIC char macInfo[1024];\r
1066         int macInfoLen;\r
1067         {\r
1068             macInfoLen =\r
1069                 snprintf(macInfo, 1024,\r
1070                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1071                     client->userId,\r
1072                     client->deviceId,\r
1073                     client->userId,\r
1074                     verifyFromDeviceId,\r
1075                     transactionId,\r
1076                     "KEY_IDS");\r
1077             olm_sas_calculate_mac_fixed_base64(olmSas, keyList, strlen(keyList), macInfo, macInfoLen, keyListMac, 1024);\r
1078         }\r
1079         {\r
1080             macInfoLen =\r
1081                 snprintf(macInfo, 1024,\r
1082                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1083                     client->userId,\r
1084                     client->deviceId,\r
1085                     client->userId,\r
1086                     verifyFromDeviceId,\r
1087                     transactionId,\r
1088                     key1Id);\r
1089             olm_sas_calculate_mac_fixed_base64(olmSas, key1, strlen(key1), macInfo, macInfoLen, key1Mac, 1024);\r
1090         }\r
1091         {\r
1092             macInfoLen =\r
1093                 snprintf(macInfo, 1024,\r
1094                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1095                     client->userId,\r
1096                     client->deviceId,\r
1097                     client->userId,\r
1098                     verifyFromDeviceId,\r
1099                     transactionId,\r
1100                     key2Id);\r
1101             olm_sas_calculate_mac_fixed_base64(olmSas, key2, strlen(key2), macInfo, macInfoLen, key2Mac, 1024);\r
1102         }\r
1103 \r
1104         // construct message and send\r
1105         STATIC char verificationMacBuffer[1024];\r
1106         snprintf(verificationMacBuffer, 1024,\r
1107             "{"\r
1108             "\"keys\":\"%s\","\r
1109             "\"mac\":{"\r
1110             "\"%s\":\"%s\","\r
1111             "\"%s\":\"%s\""\r
1112             "},"\r
1113             "\"transaction_id\":\"%s\""\r
1114             "}",\r
1115             keyListMac,\r
1116             key1Id,\r
1117             key1Mac,\r
1118             key2Id,\r
1119             key2Mac,\r
1120             transactionId);\r
1121         \r
1122         MatrixClientSendToDevice(client,\r
1123             client->userId,\r
1124             verifyFromDeviceId,\r
1125             verificationMacBuffer,\r
1126             "m.key.verification.mac");\r
1127 \r
1128         // send 'done' message\r
1129         STATIC char verificationDoneBuffer[128];\r
1130         snprintf(verificationDoneBuffer, 128,\r
1131             "{"\r
1132             "\"transaction_id\":\"%s\""\r
1133             "}",\r
1134             transactionId);\r
1135         \r
1136         MatrixClientSendToDevice(client,\r
1137             client->userId,\r
1138             verifyFromDeviceId,\r
1139             verificationDoneBuffer,\r
1140             "m.key.verification.done");\r
1141         \r
1142         free(olmSas);\r
1143 \r
1144         client->verified = true;\r
1145     }\r
1146     else if (strcmp(eventType, "m.room.encrypted") == 0) {\r
1147         STATIC char algorithm[128];\r
1148         mjson_get_string(event, eventLen, "$.content.algorithm", algorithm, 128);\r
1149 \r
1150         // since this only handles to_device messages algorithm should always be olm\r
1151         if (strcmp(algorithm, "m.olm.v1.curve25519-aes-sha2") == 0) {\r
1152             STATIC char thisDeviceKey[DEVICE_KEY_SIZE];\r
1153             MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
1154 \r
1155             STATIC char jp[128];\r
1156             snprintf(jp, 128, "$.content.ciphertext.%s.type", thisDeviceKey);\r
1157 \r
1158             double messageType;\r
1159             mjson_get_number(event, eventLen, jp, &messageType);\r
1160             int messageTypeInt = (int)messageType;\r
1161 \r
1162             snprintf(jp, 128, "$.content.ciphertext.%s.body", thisDeviceKey);\r
1163 \r
1164             mjson_get_string(event, eventLen, jp, g_EncryptedEventBuffer, 2048);\r
1165 \r
1166             MatrixOlmSession * olmSession;\r
1167             \r
1168             // depending on message type create new incoming\r
1169             // (type 0 indicates a new session so we dont check locally)\r
1170             if (messageTypeInt == 0) {\r
1171                 MatrixClientNewOlmSessionIn(client,\r
1172                     client->userId,\r
1173                     verifyFromDeviceId,\r
1174                     g_EncryptedEventBuffer,\r
1175                     &olmSession);\r
1176             }\r
1177             // or new outgoing, checking for known sessions first\r
1178             else {\r
1179                 if (! MatrixClientGetOlmSession(client, client->userId, verifyFromDeviceId, &olmSession))\r
1180                 {\r
1181                     MatrixClientNewOlmSessionOut(client,\r
1182                         client->userId,\r
1183                         verifyFromDeviceId,\r
1184                         &olmSession);\r
1185                 }\r
1186             }\r
1187             \r
1188             STATIC char decrypted[2048];\r
1189             MatrixOlmSessionDecrypt(olmSession,\r
1190                 messageTypeInt, g_EncryptedEventBuffer, decrypted, 2048);\r
1191             \r
1192             MatrixClientHandleEvent(client, decrypted, strlen(decrypted));\r
1193         }\r
1194     }\r
1195     else if (strcmp(eventType, "m.room_key") == 0 ||\r
1196              strcmp(eventType, "m.forwarded_room_key") == 0) {\r
1197         // store session information\r
1198         STATIC char roomId[128];\r
1199         STATIC char sessionId[128];\r
1200         STATIC char sessionKey[1024];\r
1201         mjson_get_string(event, eventLen, "$.content.room_id", roomId, 128);\r
1202         mjson_get_string(event, eventLen, "$.content.session_id", sessionId, 128);\r
1203         mjson_get_string(event, eventLen, "$.content.session_key", sessionKey, 1024);\r
1204 \r
1205         MatrixMegolmInSession * megolmInSession;\r
1206         MatrixClientNewMegolmInSession(client, roomId, sessionId, sessionKey, &megolmInSession);\r
1207     }\r
1208 }\r
1209 \r
1210 void\r
1211 MatrixClientHandleRoomEvent(\r
1212     MatrixClient * client,\r
1213     const char * room, int roomLen,\r
1214     const char * event, int eventLen)\r
1215 {\r
1216     STATIC char eventType[128];\r
1217     memset(eventType, 0, sizeof(eventType));\r
1218     mjson_get_string(event, eventLen, "$.type", eventType, 128);\r
1219 \r
1220     if (strcmp(eventType, "m.room.encrypted") == 0) {\r
1221         STATIC char algorithm[128];\r
1222         mjson_get_string(event, eventLen, "$.content.algorithm", algorithm, 128);\r
1223 \r
1224         // only room specific message type is encrypted\r
1225         // since this is only room messages, algorithm should always be megolm\r
1226         if (strcmp(algorithm, "m.megolm.v1.aes-sha2") == 0) {\r
1227             STATIC char sessionId[128];\r
1228             int sessionIdLen =\r
1229                 mjson_get_string(event, eventLen, "$.content.session_id", sessionId, 128);\r
1230 \r
1231             bool res;\r
1232 \r
1233             MatrixMegolmInSession * megolmInSession;\r
1234             res = MatrixClientGetMegolmInSession(client,\r
1235                 room, roomLen,\r
1236                 sessionId, sessionIdLen,\r
1237                 &megolmInSession);\r
1238 \r
1239             if (res) {\r
1240                 mjson_get_string(event, eventLen, "$.content.ciphertext", g_EncryptedEventBuffer, 2048);\r
1241 \r
1242                 STATIC char decrypted[2048];\r
1243                 MatrixMegolmInSessionDecrypt(megolmInSession, g_EncryptedEventBuffer, strlen(g_EncryptedEventBuffer), decrypted, 2048);\r
1244 \r
1245                 MatrixClientHandleEvent(client, decrypted, strlen(decrypted));\r
1246             }\r
1247             else {\r
1248                 printf("error: megolm session not known\n");\r
1249             }\r
1250         }\r
1251     }\r
1252     MatrixClientHandleEvent(client, event, eventLen);\r
1253 }\r
1254 \r
1255 // pass the response from sync to Handle(Room)Event\r
1256 void\r
1257 MatrixClientHandleSync(\r
1258     MatrixClient * client,\r
1259     char * syncBuffer, int syncBufferLen,\r
1260     char * nextBatch, int nextBatchCap)\r
1261 {    \r
1262     int res;\r
1263 \r
1264     const char * s = syncBuffer;\r
1265     int slen = syncBufferLen;\r
1266 \r
1267     // read next_batch\r
1268     mjson_get_string(s, slen, "$.next_batch", nextBatch, nextBatchCap);\r
1269 \r
1270     // to_device\r
1271     const char * events;\r
1272     int eventsLen;\r
1273     res =\r
1274         mjson_find(s, slen, "$.to_device.events", &events, &eventsLen);\r
1275     \r
1276     // iterate event and pass to HandleEvent\r
1277     if (res != MJSON_TOK_INVALID) {\r
1278         {\r
1279         int koff, klen, voff, vlen, vtype, off = 0;\r
1280         for (off = 0; (off = mjson_next(events, eventsLen, off, &koff, &klen,\r
1281                                         &voff, &vlen, &vtype)) != 0; ) {\r
1282             const char * v = events + voff;\r
1283 \r
1284             MatrixClientHandleEvent(client, v, vlen);\r
1285         }\r
1286         }\r
1287     }\r
1288 \r
1289     // rooms\r
1290     const char * rooms;\r
1291     int roomsLen;\r
1292     res =\r
1293         mjson_find(s, slen, "$.rooms.join", &rooms, &roomsLen);\r
1294     \r
1295     if (res != MJSON_TOK_INVALID) {\r
1296         // iterate rooms\r
1297         int koff, klen, voff, vlen, vtype, off = 0;\r
1298         for (off = 0; (off = mjson_next(rooms, roomsLen, off, &koff, &klen,\r
1299                                         &voff, &vlen, &vtype)) != 0; ) {\r
1300             const char * k = rooms + koff;\r
1301             const char * v = rooms + voff;\r
1302 \r
1303             const char * events;\r
1304             int eventsLen;\r
1305             res =\r
1306                 mjson_find(v, vlen, "$.timeline.events", &events, &eventsLen);\r
1307             \r
1308             if (res != MJSON_TOK_INVALID) {\r
1309                 // iterate messages in that room\r
1310                 int koff2, klen2, voff2, vlen2, vtype2, off2 = 0;\r
1311                 for (off2 = 0; (off2 = mjson_next(events, eventsLen, off2, &koff2, &klen2,\r
1312                                                 &voff2, &vlen2, &vtype2)) != 0; ) {\r
1313                     const char * v2 = events + voff2;\r
1314 \r
1315                     MatrixClientHandleRoomEvent(client,\r
1316                         k+1, klen-2,\r
1317                         v2, vlen2);\r
1318                 }\r
1319             }\r
1320         }\r
1321     }\r
1322 }\r
1323 \r
1324 // https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3sync\r
1325 bool\r
1326 MatrixClientSync(\r
1327     MatrixClient * client,\r
1328     char * outSyncBuffer, int outSyncCap,\r
1329     char * nextBatch, int nextBatchCap)\r
1330 {\r
1331     // filter={\"event_fields\":[\"to_device\"]}\r
1332     STATIC char url[MAX_URL_LEN];\r
1333     snprintf(url, MAX_URL_LEN,\r
1334         "/_matrix/client/v3/sync?timeout=%d" "%s" "%s",\r
1335         SYNC_TIMEOUT,\r
1336         "",\r
1337         // "&filter={\"event_fields\":[\"to_device\"]}",\r
1338         strlen(nextBatch) > 0 ? "&since=" : "");\r
1339     \r
1340     int index = strlen(url);\r
1341 \r
1342     // URL encode next_batch parameter since it can include ~\r
1343     for (size_t i = 0; i < strlen(nextBatch); i++) {\r
1344         char c = nextBatch[i];\r
1345 \r
1346         if (c == '~') {\r
1347             url[index++] = '%';\r
1348             url[index++] = '7';\r
1349             url[index++] = 'E';\r
1350         }\r
1351         else {\r
1352             url[index++] = c;\r
1353         }\r
1354     }\r
1355     url[index] = '\0';\r
1356 \r
1357     bool result =\r
1358         MatrixHttpGet(client->hc,\r
1359             url,\r
1360             outSyncBuffer, outSyncCap,\r
1361             true);\r
1362     \r
1363     MatrixClientHandleSync(client,\r
1364         outSyncBuffer, strlen(outSyncBuffer),\r
1365         nextBatch, nextBatchCap);\r
1366     \r
1367     return result;\r
1368 }\r
1369 \r
1370 // https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3roomsroomideventeventid\r
1371 bool\r
1372 MatrixClientGetRoomEvent(\r
1373     MatrixClient * client,\r
1374     const char * roomId,\r
1375     const char * eventId,\r
1376     char * outEvent, int outEventCap)\r
1377 {\r
1378     STATIC char url[MAX_URL_LEN];\r
1379     snprintf(url, MAX_URL_LEN,\r
1380         "/_matrix/client/v3/rooms/%s/event/%s",\r
1381             roomId,\r
1382             eventId);\r
1383 \r
1384     return\r
1385         MatrixHttpGet(client->hc,\r
1386             url,\r
1387             outEvent, outEventCap,\r
1388             true);\r
1389 }\r
1390 \r
1391 bool\r
1392 MatrixClientShareMegolmOutSession(\r
1393     MatrixClient * client,\r
1394     const char * userId,\r
1395     const char * deviceId,\r
1396     MatrixMegolmOutSession * session)\r
1397 {\r
1398     // generate room key event\r
1399     STATIC char eventBuffer[KEY_SHARE_EVENT_LEN];\r
1400     snprintf(eventBuffer, KEY_SHARE_EVENT_LEN,\r
1401         "{"\r
1402             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1403             "\"room_id\":\"%s\","\r
1404             "\"session_id\":\"%s\","\r
1405             "\"session_key\":\"%s\""\r
1406         "}",\r
1407         session->roomId,\r
1408         session->id,\r
1409         session->key\r
1410     );\r
1411 \r
1412     // send\r
1413     MatrixClientSendToDeviceEncrypted(client,\r
1414         userId,\r
1415         deviceId,\r
1416         eventBuffer,\r
1417         "m.room_key");\r
1418 \r
1419     return true;\r
1420 }\r
1421 \r
1422 bool\r
1423 MatrixClientGetMegolmOutSession(\r
1424     MatrixClient * client,\r
1425     const char * roomId,\r
1426     MatrixMegolmOutSession ** outSession)\r
1427 {\r
1428     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
1429     {\r
1430         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
1431         {\r
1432             *outSession = &client->megolmOutSessions[i];\r
1433             return true;\r
1434         }\r
1435     }\r
1436 \r
1437     return false;\r
1438 }\r
1439 \r
1440 bool\r
1441 MatrixClientNewMegolmOutSession(\r
1442     MatrixClient * client,\r
1443     const char * roomId,\r
1444     MatrixMegolmOutSession ** outSession)\r
1445 {\r
1446     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
1447     {\r
1448         MatrixMegolmOutSession * result =\r
1449             &client->megolmOutSessions[client->numMegolmOutSessions];\r
1450         \r
1451         MatrixMegolmOutSessionInit(result,\r
1452             roomId);\r
1453 \r
1454         *outSession = result;\r
1455 \r
1456         client->numMegolmOutSessions++;\r
1457 \r
1458         return true;\r
1459     }\r
1460 \r
1461     return false;\r
1462 }\r
1463 \r
1464 bool\r
1465 MatrixClientGetMegolmInSession(\r
1466     MatrixClient * client,\r
1467     const char * roomId, int roomIdLen,\r
1468     const char * sessionId, int sessionIdLen,\r
1469     MatrixMegolmInSession ** outSession)\r
1470 {\r
1471     for (int i = 0; i < client->numMegolmInSessions; i++)\r
1472     {\r
1473         if (strncmp(client->megolmInSessions[i].roomId, roomId, roomIdLen) == 0 &&\r
1474             strncmp(client->megolmInSessions[i].id, sessionId, sessionIdLen) == 0)\r
1475         {\r
1476             *outSession = &client->megolmInSessions[i];\r
1477             return true;\r
1478         }\r
1479     }\r
1480 \r
1481     return false;\r
1482 }\r
1483 \r
1484 bool\r
1485 MatrixClientNewMegolmInSession(\r
1486     MatrixClient * client,\r
1487     const char * roomId,\r
1488     const char * sessionId,\r
1489     const char * sessionKey,\r
1490     MatrixMegolmInSession ** outSession)\r
1491 {\r
1492     if (client->numMegolmInSessions < NUM_MEGOLM_SESSIONS)\r
1493     {\r
1494         MatrixMegolmInSession * result =\r
1495             &client->megolmInSessions[client->numMegolmInSessions];\r
1496         \r
1497         MatrixMegolmInSessionInit(result,\r
1498             roomId,\r
1499             sessionId,\r
1500             sessionKey, strlen(sessionKey));\r
1501         \r
1502         *outSession = result;\r
1503 \r
1504         client->numMegolmInSessions++;\r
1505 \r
1506         return true;\r
1507     }\r
1508 \r
1509     return false;\r
1510 }\r
1511 \r
1512 bool\r
1513 MatrixClientRequestMegolmInSession(\r
1514     MatrixClient * client,\r
1515     const char * roomId,\r
1516     const char * sessionId,\r
1517     const char * senderKey,\r
1518     const char * userId,\r
1519     const char * deviceId)\r
1520 {\r
1521     // TODO: cancel requests\r
1522     MatrixClientSendDummy(client, userId, deviceId);\r
1523 \r
1524     STATIC char event[ROOMKEY_REQUEST_SIZE];\r
1525     snprintf(event, ROOMKEY_REQUEST_SIZE,\r
1526         "{"\r
1527             "\"action\":\"request\","\r
1528             "\"body\":{"\r
1529                 "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1530                 "\"room_id\":\"%s\","\r
1531                 "\"sender_key\":\"%s\","\r
1532                 "\"session_id\":\"%s\""\r
1533             "},"\r
1534             "\"request_id\":\"%lld\","\r
1535             "\"requesting_device_id\":\"%s\""\r
1536         "}",\r
1537         roomId,\r
1538         senderKey,\r
1539         sessionId,\r
1540         time(NULL),\r
1541         client->deviceId);\r
1542 \r
1543     \r
1544     MatrixClientSendToDevice(client,\r
1545         userId,\r
1546         deviceId,\r
1547         event,\r
1548         "m.room_key_request");\r
1549 \r
1550     return true;\r
1551 }\r
1552 \r
1553 bool\r
1554 MatrixClientGetOlmSession(\r
1555     MatrixClient * client,\r
1556     const char * userId,\r
1557     const char * deviceId,\r
1558     MatrixOlmSession ** outSession)\r
1559 {\r
1560     (void)userId; //unused for now\r
1561 \r
1562     for (int i = 0; i < client->numOlmSessions; i++)\r
1563     {\r
1564         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
1565         {\r
1566             *outSession = &client->olmSessions[i];\r
1567             return true;\r
1568         }\r
1569     }\r
1570 \r
1571     return false;\r
1572 }\r
1573 \r
1574 bool\r
1575 MatrixClientNewOlmSessionIn(\r
1576     MatrixClient * client,\r
1577     const char * userId,\r
1578     const char * deviceId,\r
1579     const char * encrypted,\r
1580     MatrixOlmSession ** outSession)\r
1581 {\r
1582     (void)userId; //unused for now\r
1583     \r
1584     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1585     {\r
1586         STATIC char deviceKey[DEVICE_KEY_SIZE];\r
1587         MatrixClientRequestDeviceKey(client,\r
1588             deviceId,\r
1589             deviceKey, DEVICE_KEY_SIZE);\r
1590 \r
1591         MatrixOlmSessionFrom(\r
1592             &client->olmSessions[client->numOlmSessions],\r
1593             client->olmAccount.account,\r
1594             deviceId,\r
1595             deviceKey,\r
1596             encrypted);\r
1597 \r
1598         *outSession = &client->olmSessions[client->numOlmSessions];\r
1599         \r
1600         client->numOlmSessions++;\r
1601 \r
1602         return true;\r
1603     }\r
1604 \r
1605     return false;\r
1606 }\r
1607 \r
1608 bool\r
1609 MatrixClientNewOlmSessionOut(\r
1610     MatrixClient * client,\r
1611     const char * userId,\r
1612     const char * deviceId,\r
1613     MatrixOlmSession ** outSession)\r
1614 {\r
1615     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1616     {\r
1617         STATIC char deviceKey[DEVICE_KEY_SIZE];\r
1618         MatrixClientRequestDeviceKey(client,\r
1619             deviceId,\r
1620             deviceKey, DEVICE_KEY_SIZE);\r
1621 \r
1622         char onetimeKey[ONETIME_KEY_SIZE];\r
1623         MatrixClientClaimOnetimeKey(client,\r
1624             userId,\r
1625             deviceId,\r
1626             onetimeKey, ONETIME_KEY_SIZE);\r
1627 \r
1628         MatrixOlmSessionTo(\r
1629             &client->olmSessions[client->numOlmSessions],\r
1630             client->olmAccount.account,\r
1631             deviceId,\r
1632             deviceKey,\r
1633             onetimeKey);\r
1634 \r
1635         *outSession = &client->olmSessions[client->numOlmSessions];\r
1636         \r
1637         client->numOlmSessions++;\r
1638 \r
1639         return true;\r
1640     }\r
1641 \r
1642     return false;\r
1643 }\r
1644 \r
1645 // https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
1646 bool\r
1647 MatrixClientSendToDevice(\r
1648     MatrixClient * client,\r
1649     const char * userId,\r
1650     const char * deviceId,\r
1651     const char * message,\r
1652     const char * msgType)\r
1653 {\r
1654     STATIC char requestUrl[MAX_URL_LEN];\r
1655     snprintf(requestUrl, MAX_URL_LEN,\r
1656         TODEVICE_URL, msgType, (int)time(NULL));\r
1657 \r
1658     snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,\r
1659         "{"\r
1660             "\"messages\":{"\r
1661                 "\"%s\":{"\r
1662                     "\"%s\":%s"\r
1663                 "}"\r
1664             "}"\r
1665         "}",\r
1666         userId,\r
1667         deviceId,\r
1668         message);\r
1669 \r
1670     STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
1671     bool result =\r
1672         MatrixHttpPut(client->hc,\r
1673             requestUrl,\r
1674             g_TodeviceEventBuffer,\r
1675             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
1676             true);\r
1677     \r
1678     return result;\r
1679 }\r
1680 \r
1681 bool\r
1682 MatrixClientSendToDeviceEncrypted(\r
1683     MatrixClient * client,\r
1684     const char * userId,\r
1685     const char * deviceId,\r
1686     const char * message,\r
1687     const char * msgType)\r
1688 {\r
1689     // get olm session\r
1690     MatrixOlmSession * olmSession;\r
1691     if (! MatrixClientGetOlmSession(client, userId, deviceId, &olmSession))\r
1692         MatrixClientNewOlmSessionOut(client, userId, deviceId, &olmSession);\r
1693 \r
1694     // create event json\r
1695     char targetDeviceKey[DEVICE_KEY_SIZE];\r
1696     MatrixClientRequestDeviceKey(client, deviceId, targetDeviceKey, DEVICE_KEY_SIZE);\r
1697     char targetSigningKey[SIGNING_KEY_SIZE];\r
1698     MatrixClientRequestSigningKey(client, deviceId, targetSigningKey, SIGNING_KEY_SIZE);\r
1699     \r
1700     char thisSigningKey[DEVICE_KEY_SIZE];\r
1701     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
1702 \r
1703     snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,\r
1704         "{"\r
1705         "\"type\":\"%s\","\r
1706         "\"content\":%s,"\r
1707         "\"sender\":\"%s\","\r
1708         "\"recipient\":\"%s\","\r
1709         "\"recipient_keys\":{"\r
1710         "\"ed25519\":\"%s\""\r
1711         "},"\r
1712         "\"keys\":{"\r
1713         "\"ed25519\":\"%s\""\r
1714         "}"\r
1715         "}",\r
1716         msgType,\r
1717         message,\r
1718         client->userId,\r
1719         userId, // recipient user id\r
1720         targetSigningKey, // recipient device key\r
1721         thisSigningKey);\r
1722 \r
1723     // encrypt\r
1724     MatrixOlmSessionEncrypt(olmSession,\r
1725         g_TodeviceEventBuffer,\r
1726         g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);\r
1727 \r
1728     char thisDeviceKey[DEVICE_KEY_SIZE];\r
1729     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
1730 \r
1731     snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,\r
1732         "{"\r
1733         "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","\r
1734         "\"ciphertext\":{"\r
1735           "\"%s\":{"\r
1736             "\"body\":\"%s\","\r
1737             "\"type\":%d"\r
1738           "}"\r
1739         "},"\r
1740         "\"device_id\":\"%s\","\r
1741         "\"sender_key\":\"%s\""\r
1742         "}",\r
1743         targetDeviceKey,\r
1744         // olm_encrypt_message_length(olmSession->session, strlen(g_TodeviceEventBuffer)), g_EncryptedRequestBuffer,\r
1745         g_EncryptedRequestBuffer,\r
1746         olm_session_has_received_message(olmSession->session),\r
1747         client->deviceId,\r
1748         thisDeviceKey);\r
1749 \r
1750     // send\r
1751     return MatrixClientSendToDevice(\r
1752         client,\r
1753         userId,\r
1754         deviceId,\r
1755         g_EncryptedEventBuffer,\r
1756         "m.room.encrypted");\r
1757 }\r
1758 \r
1759 bool\r
1760 MatrixClientSendDummy(\r
1761     MatrixClient * client,\r
1762     const char * userId,\r
1763     const char * deviceId)\r
1764 {\r
1765     return MatrixClientSendToDeviceEncrypted(\r
1766         client,\r
1767         userId,\r
1768         deviceId,\r
1769         "{}",\r
1770         "m.dummy");\r
1771 }\r
1772 \r
1773 bool\r
1774 MatrixClientFindDevice(\r
1775     MatrixClient * client,\r
1776     const char * deviceId,\r
1777     MatrixDevice ** outDevice)\r
1778 {\r
1779     for (int i = 0; i < client->numDevices; i++)\r
1780     {\r
1781         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1782         {\r
1783             *outDevice = &client->devices[i];\r
1784             return true;\r
1785         }\r
1786     }\r
1787 \r
1788     MatrixClientRequestDeviceKeys(client);\r
1789 \r
1790     for (int i = 0; i < client->numDevices; i++)\r
1791     {\r
1792         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1793         {\r
1794             *outDevice = &client->devices[i];\r
1795             return true;\r
1796         }\r
1797     }\r
1798 \r
1799     *outDevice = NULL;\r
1800     return false;\r
1801 }\r
1802 \r
1803 bool\r
1804 MatrixClientRequestDeviceKey(\r
1805     MatrixClient * client,\r
1806     const char * deviceId,\r
1807     char * outDeviceKey, int outDeviceKeyCap)\r
1808 {\r
1809     MatrixDevice * device;\r
1810     \r
1811     if (MatrixClientFindDevice(client, deviceId, &device))\r
1812     {\r
1813         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1814         return true;\r
1815     }\r
1816 \r
1817     MatrixClientRequestDeviceKeys(client);\r
1818     \r
1819     if (MatrixClientFindDevice(client, deviceId, &device))\r
1820     {\r
1821         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1822         return true;\r
1823     }\r
1824 \r
1825     return false;\r
1826 }\r
1827 \r
1828 bool\r
1829 MatrixClientRequestSigningKey(\r
1830     MatrixClient * client,\r
1831     const char * deviceId,\r
1832     char * outSigningKey, int outSigningKeyCap)\r
1833 {\r
1834     MatrixDevice * device;\r
1835     \r
1836     if (MatrixClientFindDevice(client, deviceId, &device))\r
1837     {\r
1838         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1839         return true;\r
1840     }\r
1841 \r
1842     MatrixClientRequestDeviceKeys(client);\r
1843     \r
1844     if (MatrixClientFindDevice(client, deviceId, &device))\r
1845     {\r
1846         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1847         return true;\r
1848     }\r
1849 \r
1850     return false;\r
1851 }\r
1852 \r
1853 bool\r
1854 MatrixClientRequestMasterKey(\r
1855     MatrixClient * client,\r
1856     char * outMasterKey, int outMasterKeyCap)\r
1857 {\r
1858     if (strlen(client->masterKey) > 0) {\r
1859         strncpy(outMasterKey, client->masterKey, outMasterKeyCap);\r
1860         return true;\r
1861     }\r
1862 \r
1863     MatrixClientRequestDeviceKeys(client);\r
1864     \r
1865     if (strlen(client->masterKey) > 0) {\r
1866         strncpy(outMasterKey, client->masterKey, outMasterKeyCap);\r
1867         return true;\r
1868     }\r
1869 \r
1870     return false;\r
1871 }\r
1872 \r
1873 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysquery\r
1874 bool\r
1875 MatrixClientRequestDeviceKeys(\r
1876     MatrixClient * client)\r
1877 {\r
1878     if (client->numDevices >= NUM_DEVICES) {\r
1879         printf("error: Maximum number of devices reached\n");\r
1880         return false;\r
1881     }\r
1882 \r
1883     // escape userId so we can use it in json queries\r
1884     STATIC char userIdEscaped[USER_ID_SIZE];\r
1885     JsonEscape(client->userId, strlen(client->userId),\r
1886         userIdEscaped, USER_ID_SIZE);\r
1887 \r
1888     // construct and send request\r
1889     STATIC char request[KEYS_QUERY_REQUEST_SIZE];\r
1890     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
1891         "{\"device_keys\":{\"%s\":[]}}", client->userId);\r
1892 \r
1893     STATIC char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
1894     bool requestResult = MatrixHttpPost(client->hc,\r
1895         KEYS_QUERY_URL,\r
1896         request,\r
1897         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
1898         true);\r
1899 \r
1900     if (! requestResult)\r
1901         return false;\r
1902 \r
1903     // query for retrieving device keys for user id\r
1904     STATIC char query[JSON_QUERY_SIZE];\r
1905     const char * s;\r
1906     int slen;\r
1907 \r
1908     // look for master key\r
1909     snprintf(query, JSON_QUERY_SIZE,\r
1910         "$.master_keys.%s.keys", userIdEscaped);\r
1911     mjson_find(responseBuffer, strlen(responseBuffer),\r
1912         query, &s, &slen);\r
1913     \r
1914     int koff, klen, voff, vlen, vtype, off = 0;\r
1915     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1916                                     &voff, &vlen, &vtype)) != 0; ) {\r
1917         snprintf(client->masterKey, MASTER_KEY_SIZE,\r
1918             "%.*s", vlen-2, s+voff+1);\r
1919     }\r
1920 \r
1921     // iterate over returned devices for that userId\r
1922     snprintf(query, JSON_QUERY_SIZE,\r
1923         "$.device_keys.%s", userIdEscaped);\r
1924     \r
1925     mjson_find(responseBuffer, strlen(responseBuffer),\r
1926         query, &s, &slen);\r
1927     \r
1928     // loop over keys\r
1929     // creating a new device if possible\r
1930     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1931                                     &voff, &vlen, &vtype)) != 0; ) {\r
1932         const char * key = s + koff;\r
1933         const char * val = s + voff;\r
1934 \r
1935         // set device id, "key" is the JSON key\r
1936         MatrixDevice d;\r
1937         snprintf(d.deviceId, DEVICE_ID_SIZE,\r
1938             "%.*s", klen-2, key+1);\r
1939 \r
1940         // look for device key in value\r
1941         STATIC char deviceKeyQuery[JSON_QUERY_SIZE];\r
1942         snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
1943             "$.keys.curve25519:%s", d.deviceId);\r
1944         mjson_get_string(val, vlen,\r
1945             deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
1946 \r
1947         // look for signing key in value\r
1948         STATIC char signingKeyQuery[JSON_QUERY_SIZE];\r
1949         snprintf(signingKeyQuery, JSON_QUERY_SIZE,\r
1950             "$.keys.ed25519:%s", d.deviceId);\r
1951         mjson_get_string(val, vlen,\r
1952             signingKeyQuery, d.signingKey, SIGNING_KEY_SIZE);\r
1953 \r
1954         // add device\r
1955         if (client->numDevices < NUM_DEVICES)\r
1956         {\r
1957             bool foundDevice = false;\r
1958             for (int i = 0; i < client->numDevices; i++)\r
1959                 if (strcmp(client->devices[i].deviceId, d.deviceId) == 0)\r
1960                     foundDevice = true;\r
1961 \r
1962             if (! foundDevice) {\r
1963                 client->devices[client->numDevices] = d;\r
1964                 client->numDevices++;\r
1965             }\r
1966         }\r
1967         else\r
1968         {\r
1969             return false;\r
1970         }\r
1971     }\r
1972 \r
1973     return true;\r
1974 }\r
1975 \r
1976 bool\r
1977 MatrixClientDeleteDevice(\r
1978     MatrixClient * client)\r
1979 {\r
1980     STATIC char deleteRequest[1024];\r
1981     snprintf(deleteRequest, 1024,\r
1982         "{\"devices\":[\"%s\"]}",\r
1983         client->deviceId);\r
1984     STATIC char deleteResponse[1024];\r
1985     bool res = MatrixHttpPost(client->hc, "/_matrix/client/v3/delete_devices",\r
1986         deleteRequest, deleteResponse, 1024, true);\r
1987     return res;\r
1988 }\r