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