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