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