9 #include <esp_random.h>
\r
12 // can be used to disable static allocation
\r
13 #define STATIC static
\r
16 #define LOGIN_REQUEST_SIZE 1024
\r
17 #define LOGIN_RESPONSE_SIZE 1024
\r
18 #define LOGIN_URL "/_matrix/client/v3/login"
\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
28 #define ROOMKEY_REQUEST_SIZE (1024*4)
\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
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
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
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
49 #define SYNC_TIMEOUT 5000
\r
51 #define JSON_QUERY_SIZE 128
\r
52 #define JSON_MAX_INDICES 100
\r
53 #define JSON_MAX_ENTRY_SIZE 1024
\r
55 #define MAX(a,b) ((a) > (b) ? (a) : (b))
\r
56 #define MIN(a,b) ((a) < (b) ? (a) : (b))
\r
67 for (int i = 0; i < randomLen; i++)
\r
69 random[i] = esp_random() % 256;
\r
74 STATIC bool first = true;
\r
75 if (first) { srand(time(0)); first = false; }
\r
77 for (int i = 0; i < randomLen; i++)
\r
79 random[i] = rand() % 256;
\r
87 const char * sIn, int sInLen,
\r
88 char * sOut, int sOutCap)
\r
92 for (int i = 0; i < sInLen; i++)
\r
97 if (sIn[i] == '.' ||
\r
101 sOut[sOutIndex++] = '\\';
\r
103 sOut[sOutIndex++] = sIn[i];
\r
106 if (sOutIndex < sOutCap)
\r
107 sOut[sOutIndex] = '\0';
\r
114 const char * sIn, int sInLen,
\r
115 char * sOut, int sOutCap)
\r
117 snprintf(sOut, sOutCap, "{}");
\r
119 int koff, klen, voff, vlen, vtype, off;
\r
126 struct Key keys[JSON_MAX_INDICES];
\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
135 for (int i = 0; i < numKeys; i++) {
\r
136 for (int j = i; j < numKeys; j++) {
\r
141 MIN(keys[i].len, keys[j].len)
\r
144 struct Key k = keys[i];
\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
155 const char * valPtr;
\r
157 mjson_find(sIn, sInLen, jp, &valPtr, &valLen);
\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
162 char * buffer = strdup(sOut);
\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
170 // TODO: recursively sort entries
\r
176 MatrixClient * client,
\r
177 const char * sIn, int sInLen,
\r
178 char * sOut, int sOutCap)
\r
180 STATIC char signature[OLM_SIGNATURE_SIZE];
\r
182 olm_account_sign(client->olmAccount.account,
\r
184 signature, OLM_SIGNATURE_SIZE);
\r
186 int signatureLen = res;
\r
188 STATIC char thisSigningKey[SIGNING_KEY_SIZE];
\r
189 MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, SIGNING_KEY_SIZE);
\r
191 STATIC char signatureJson[JSON_SIGNATURE_SIZE];
\r
192 int signatureJsonLen =
\r
193 mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,
\r
197 "\"ed25519:%s\":\"%.*s\""
\r
204 signatureLen, signature);
\r
206 struct mjson_fixedbuf result = { sOut, sOutCap, 0 };
\r
209 signatureJson, signatureJsonLen,
\r
210 mjson_print_fixed_buf,
\r
217 MatrixOlmAccountInit(
\r
218 MatrixOlmAccount * account)
\r
220 account->account = olm_account(account->memory);
\r
222 STATIC uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];
\r
223 Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);
\r
225 size_t res = olm_create_account(
\r
228 OLM_ACCOUNT_RANDOM_SIZE);
\r
230 return res != olm_error();
\r
234 MatrixOlmAccountUnpickle(
\r
235 MatrixOlmAccount * account,
\r
236 void * pickled, int pickledLen,
\r
237 const void * key, int keyLen)
\r
240 res = olm_unpickle_account(account->account,
\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
247 return res != olm_error();
\r
251 MatrixOlmAccountGetDeviceKey(
\r
252 MatrixOlmAccount * account,
\r
253 char * key, int keyCap)
\r
255 STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];
\r
257 olm_account_identity_keys(account->account,
\r
258 deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);
\r
259 mjson_get_string(deviceKeysJson, res,
\r
266 MatrixOlmAccountGetSigningKey(
\r
267 MatrixOlmAccount * account,
\r
268 char * key, int keyCap)
\r
270 STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];
\r
272 olm_account_identity_keys(account->account,
\r
273 deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);
\r
274 mjson_get_string(deviceKeysJson, res,
\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
288 memset(session, 0, sizeof(MatrixOlmSession));
\r
290 session->deviceId = deviceId;
\r
293 olm_session(session->memory);
\r
295 char * encryptedCopy = strdup(encrypted);
\r
298 olm_create_inbound_session_from(session->session, olmAccount,
\r
299 deviceKey, strlen(deviceKey),
\r
300 encryptedCopy, strlen(encryptedCopy));
\r
302 if (res == olm_error()) {
\r
303 printf("error olm:%s\n", olm_session_last_error(session->session));
\r
306 return res != olm_error();
\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
317 memset(session, 0, sizeof(MatrixOlmSession));
\r
319 session->deviceId = deviceId;
\r
322 olm_session(session->memory);
\r
324 STATIC uint8_t random[OLM_OUTBOUND_SESSION_RANDOM_SIZE];
\r
325 Randomize(random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);
\r
328 olm_create_outbound_session(session->session,
\r
330 deviceKey, strlen(deviceKey),
\r
331 deviceOnetimeKey, strlen(deviceOnetimeKey),
\r
332 random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);
\r
334 if (res == olm_error()) {
\r
335 printf("error olm:%s\n", olm_session_last_error(session->session));
\r
338 return res != olm_error();
\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
348 memset(session, 0, sizeof(MatrixOlmSession));
\r
350 session->deviceId = deviceId;
\r
353 olm_session(session->memory);
\r
356 res = olm_unpickle_session(session->session,
\r
358 pickled, pickledLen);
\r
360 if (res == olm_error()) {
\r
361 printf("error unpickling olm session:%s\n", olm_session_last_error(session->session));
\r
364 return res != olm_error();
\r
368 MatrixOlmSessionEncrypt(
\r
369 MatrixOlmSession * session,
\r
370 const char * plaintext,
\r
371 char * outBuffer, int outBufferCap)
\r
373 STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];
\r
374 Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);
\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
382 return res != olm_error();
\r
386 MatrixOlmSessionDecrypt(
\r
387 MatrixOlmSession * session,
\r
388 size_t messageType,
\r
390 char * outBuffer, int outBufferCap)
\r
392 STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];
\r
393 Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);
\r
396 olm_decrypt(session->session,
\r
398 encrypted, strlen(encrypted),
\r
399 outBuffer, outBufferCap);
\r
401 if (res != olm_error() && (int)res < outBufferCap)
\r
402 outBuffer[res] = '\0';
\r
404 return res != olm_error();
\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
414 memset(session, 0, sizeof(MatrixMegolmInSession));
\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
421 olm_inbound_group_session(session->memory);
\r
424 olm_init_inbound_group_session(
\r
425 // olm_import_inbound_group_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
432 return res != olm_error();
\r
436 MatrixMegolmInSessionDecrypt(
\r
437 MatrixMegolmInSession * session,
\r
438 const char * encrypted, int encryptedLen,
\r
439 char * outDecrypted, int outDecryptedCap)
\r
441 // uint8_t buffer[1024];
\r
442 // memcpy(buffer, encrypted, encryptedLen);
\r
444 uint32_t megolmInMessageIndex;
\r
447 olm_group_decrypt(session->session,
\r
448 (uint8_t *)encrypted, encryptedLen,
\r
449 (uint8_t *)outDecrypted, outDecryptedCap,
\r
450 &megolmInMessageIndex);
\r
452 if (res == olm_error()) {
\r
453 printf("error decrypting megolm message: %s\n", olm_inbound_group_session_last_error(session->session));
\r
459 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session
\r
461 MatrixMegolmOutSessionInit(
\r
462 MatrixMegolmOutSession * session,
\r
463 const char * roomId)
\r
465 memset(session, 0, sizeof(MatrixMegolmOutSession));
\r
467 STATIC uint8_t random[MEGOLM_INIT_RANDOM_SIZE];
\r
468 Randomize(random, MEGOLM_INIT_RANDOM_SIZE);
\r
470 strncpy(session->roomId, roomId, ROOM_ID_SIZE);
\r
473 olm_outbound_group_session(session->memory);
\r
475 olm_init_outbound_group_session(
\r
478 MEGOLM_INIT_RANDOM_SIZE);
\r
480 olm_outbound_group_session_id(session->session,
\r
481 (uint8_t *)session->id,
\r
482 MEGOLM_SESSION_ID_SIZE);
\r
484 olm_outbound_group_session_key(session->session,
\r
485 (uint8_t *)session->key,
\r
486 MEGOLM_SESSION_KEY_SIZE);
\r
492 MatrixMegolmOutSessionEncrypt(
\r
493 MatrixMegolmOutSession * session,
\r
494 const char * plaintext,
\r
495 char * outBuffer, int outBufferCap)
\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
502 return res != olm_error();
\r
508 MatrixClient * client)
\r
510 memset(client, 0, sizeof(MatrixClient));
\r
512 // init olm account
\r
513 MatrixOlmAccountInit(&client->olmAccount);
\r
519 MatrixClientSetAccessToken(
\r
520 MatrixClient * client,
\r
521 const char * accessToken)
\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
531 MatrixClientSetDeviceId(
\r
532 MatrixClient * client,
\r
533 const char * deviceId)
\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
543 MatrixClientSetUserId(
\r
544 MatrixClient * client,
\r
545 const char * userId)
\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
555 MatrixClientGenerateOnetimeKeys(
\r
556 MatrixClient * client,
\r
559 STATIC uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];
\r
560 Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);
\r
563 olm_account_generate_one_time_keys(client->olmAccount.account,
\r
564 numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);
\r
566 return res != olm_error();
\r
569 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysupload
\r
571 MatrixClientUploadOnetimeKeys(
\r
572 MatrixClient * client)
\r
574 mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,
\r
577 STATIC char onetimeKeysBuffer[1024];
\r
578 olm_account_one_time_keys(client->olmAccount.account,
\r
579 onetimeKeysBuffer, 1024);
\r
581 // olm_account_one_time_keys returns a json object
\r
582 // find curve25519 member
\r
585 mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);
\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
594 snprintf(keyJson, JSON_ONETIME_KEY_SIZE,
\r
595 "{\"key\":\"%.*s\"}",
\r
596 vlen-2, keys + voff+1);
\r
598 STATIC char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];
\r
601 keyJson, keyJsonLen,
\r
602 keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);
\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
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
614 mjson_snprintf(g_KeysUploadRequestBuffer+strlen(g_KeysUploadRequestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(g_KeysUploadRequestBuffer),
\r
617 snprintf(g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,
\r
618 "{\"one_time_keys\":%s}", g_KeysUploadRequestBuffer);
\r
620 STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];
\r
621 MatrixHttpPost(client->hc,
\r
623 g_KeysUploadRequestSignedBuffer,
\r
624 responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,
\r
630 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysupload
\r
632 MatrixClientUploadDeviceKeys(
\r
633 MatrixClient * client)
\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
640 int deviceKeysBufferLen =
\r
641 mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,
\r
643 "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"
\r
644 "\"device_id\":\"%s\","
\r
646 "\"curve25519:%s\":\"%s\","
\r
647 "\"ed25519:%s\":\"%s\""
\r
649 "\"user_id\":\"%s\""
\r
652 client->deviceId, thisDeviceKey,
\r
653 client->deviceId, thisSigningKey,
\r
657 g_KeysUploadRequestBuffer, deviceKeysBufferLen,
\r
658 g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);
\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
664 STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];
\r
665 MatrixHttpPost(client->hc,
\r
668 responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,
\r
674 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysclaim
\r
676 MatrixClientClaimOnetimeKey(
\r
677 MatrixClient * client,
\r
678 const char * userId,
\r
679 const char * deviceId,
\r
680 char * outOnetimeKey, int outOnetimeKeyCap)
\r
682 STATIC char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];
\r
683 mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,
\r
685 "\"one_time_keys\":{"
\r
687 "\"%s\":\"signed_curve25519\""
\r
690 "\"timeout\":10000"
\r
695 STATIC char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];
\r
696 MatrixHttpPost(client->hc,
\r
699 responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,
\r
702 STATIC char userIdEscaped[USER_ID_SIZE];
\r
703 JsonEscape(userId, strlen(userId),
\r
704 userIdEscaped, USER_ID_SIZE);
\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
714 // find the corresponding json object
\r
715 const char * keyObject;
\r
717 mjson_find(responseBuffer, strlen(responseBuffer),
\r
719 &keyObject, &keyObjectSize);
\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
728 mjson_get_string(keyObject + voff, vlen,
\r
729 "$.key", outOnetimeKey, outOnetimeKeyCap);
\r
731 // TODO: verify signature
\r
736 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3login
\r
738 MatrixClientLoginPassword(
\r
739 MatrixClient * client,
\r
740 const char * username,
\r
741 const char * password,
\r
742 const char * displayName)
\r
744 STATIC char requestBuffer[LOGIN_REQUEST_SIZE];
\r
746 mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,
\r
748 "\"type\":\"m.login.password\","
\r
750 "\"type\":\"m.id.user\","
\r
753 "\"password\":\"%s\","
\r
754 "\"initial_device_display_name\":\"%s\""
\r
760 STATIC char responseBuffer[LOGIN_RESPONSE_SIZE];
\r
762 MatrixHttpPost(client->hc,
\r
765 responseBuffer, LOGIN_RESPONSE_SIZE,
\r
771 int responseLen = strlen(responseBuffer);
\r
773 // store variables in MatrixClient
\r
774 mjson_get_string(responseBuffer, responseLen,
\r
776 client->accessToken, ACCESS_TOKEN_SIZE);
\r
777 mjson_get_string(responseBuffer, responseLen,
\r
779 client->deviceId, DEVICE_ID_SIZE);
\r
780 mjson_get_string(responseBuffer, responseLen,
\r
782 client->expireMs, EXPIRE_MS_SIZE);
\r
783 mjson_get_string(responseBuffer, responseLen,
\r
785 client->refreshToken, REFRESH_TOKEN_SIZE);
\r
787 MatrixHttpSetAccessToken(client->hc, client->accessToken);
\r
792 // https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
\r
794 MatrixClientSendEvent(
\r
795 MatrixClient * client,
\r
796 const char * roomId,
\r
797 const char * msgType,
\r
798 const char * msgBody)
\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
804 STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];
\r
806 MatrixHttpPut(client->hc,
\r
809 responseBuffer, ROOM_SEND_RESPONSE_SIZE,
\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
818 MatrixClientSendEventEncrypted(
\r
819 MatrixClient * client,
\r
820 const char * roomId,
\r
821 const char * msgType,
\r
822 const char * msgBody)
\r
825 STATIC char requestBuffer[ROOM_SEND_REQUEST_SIZE];
\r
826 snprintf(requestBuffer, ROOM_SEND_REQUEST_SIZE,
\r
830 "\"room_id\":\"%s\""
\r
836 // get megolm session
\r
837 MatrixMegolmOutSession * outSession;
\r
838 if (! MatrixClientGetMegolmOutSession(client, roomId, &outSession))
\r
839 MatrixClientNewMegolmOutSession(client, roomId, &outSession);
\r
842 MatrixMegolmOutSessionEncrypt(outSession,
\r
844 g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);
\r
846 char thisDeviceKey[DEVICE_KEY_SIZE];
\r
847 MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);
\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
855 snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,
\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
863 g_EncryptedRequestBuffer,
\r
869 return MatrixClientSendEvent(client,
\r
871 "m.room.encrypted",
\r
872 g_EncryptedEventBuffer);
\r
875 // this handles to_device events received from a sync
\r
876 // mainly for verification (m.key.verification.* events)
\r
878 MatrixClientHandleEvent(
\r
879 MatrixClient * client,
\r
880 const char * event, int eventLen
\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
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
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
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
905 char verificationReadyBuffer[2048];
\r
906 snprintf(verificationReadyBuffer, 2048,
\r
908 "\"from_device\":\"%s\","
\r
909 "\"methods\":[\"m.sas.v1\"],"
\r
910 "\"transaction_id\":\"%s\""
\r
915 MatrixClientSendToDevice(client,
\r
917 verifyFromDeviceId,
\r
918 verificationReadyBuffer,
\r
919 "m.key.verification.ready");
\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
926 olm_create_sas_random_length(olmSas));
\r
928 OlmUtility * olmUtil = olm_utility(malloc(olm_utility_size()));
\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
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
946 snprintf(concat, 512+64, "%.*s%s", (int)olm_sas_pubkey_length(olmSas), publicKey, keyStartJsonCanonical);
\r
948 int commitmentLen =
\r
949 olm_sha256(olmUtil, concat, concatLen, commitment, 1024);
\r
950 olm_clear_utility(olmUtil);
\r
953 STATIC char verificationAcceptBuffer[512];
\r
954 snprintf(verificationAcceptBuffer, 512,
\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
964 commitmentLen, commitment,
\r
967 MatrixClientSendToDevice(client,
\r
969 verifyFromDeviceId,
\r
970 verificationAcceptBuffer,
\r
971 "m.key.verification.accept");
\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
980 STATIC char theirPublicKey[128];
\r
981 int theirPublicKeyLen =
\r
982 mjson_get_string(event, eventLen, "$.content.key", theirPublicKey, 128);
\r
984 olm_sas_set_their_key(olmSas, theirPublicKey, theirPublicKeyLen);
\r
986 STATIC char verificationKeyBuffer[256];
\r
987 snprintf(verificationKeyBuffer, 256,
\r
989 "\"key\":\"%.*s\","
\r
990 "\"transaction_id\":\"%s\""
\r
992 (int)olm_sas_pubkey_length(olmSas), publicKey,
\r
995 MatrixClientSendToDevice(client,
\r
997 verifyFromDeviceId,
\r
998 verificationKeyBuffer,
\r
999 "m.key.verification.key");
\r
1002 STATIC char hkdfInfo[1024];
\r
1004 snprintf(hkdfInfo, 1024,
\r
1005 "MATRIX_KEY_VERIFICATION_SAS%s%s%s%s%s",
\r
1007 verifyFromDeviceId,
\r
1012 unsigned char sasBytes[5];
\r
1013 olm_sas_generate_bytes(olmSas,
\r
1014 hkdfInfo, hkdfInfoLen,
\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
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
1029 // calculate MACs for signing key, master key and the key list
\r
1030 else if (strcmp(eventType, "m.key.verification.mac") == 0) {
\r
1032 STATIC char masterKey[123];
\r
1033 MatrixClientRequestMasterKey(client, masterKey, 123);
\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
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
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
1059 // create key list
\r
1060 snprintf(keyList, 1024,
\r
1061 "%s,%s", key1Id, key2Id);
\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
1069 snprintf(macInfo, 1024,
\r
1070 "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",
\r
1074 verifyFromDeviceId,
\r
1077 olm_sas_calculate_mac_fixed_base64(olmSas, keyList, strlen(keyList), macInfo, macInfoLen, keyListMac, 1024);
\r
1081 snprintf(macInfo, 1024,
\r
1082 "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",
\r
1086 verifyFromDeviceId,
\r
1089 olm_sas_calculate_mac_fixed_base64(olmSas, key1, strlen(key1), macInfo, macInfoLen, key1Mac, 1024);
\r
1093 snprintf(macInfo, 1024,
\r
1094 "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",
\r
1098 verifyFromDeviceId,
\r
1101 olm_sas_calculate_mac_fixed_base64(olmSas, key2, strlen(key2), macInfo, macInfoLen, key2Mac, 1024);
\r
1104 // construct message and send
\r
1105 STATIC char verificationMacBuffer[1024];
\r
1106 snprintf(verificationMacBuffer, 1024,
\r
1108 "\"keys\":\"%s\","
\r
1113 "\"transaction_id\":\"%s\""
\r
1122 MatrixClientSendToDevice(client,
\r
1124 verifyFromDeviceId,
\r
1125 verificationMacBuffer,
\r
1126 "m.key.verification.mac");
\r
1128 // send 'done' message
\r
1129 STATIC char verificationDoneBuffer[128];
\r
1130 snprintf(verificationDoneBuffer, 128,
\r
1132 "\"transaction_id\":\"%s\""
\r
1136 MatrixClientSendToDevice(client,
\r
1138 verifyFromDeviceId,
\r
1139 verificationDoneBuffer,
\r
1140 "m.key.verification.done");
\r
1144 client->verified = true;
\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
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
1155 STATIC char jp[128];
\r
1156 snprintf(jp, 128, "$.content.ciphertext.%s.type", thisDeviceKey);
\r
1158 double messageType;
\r
1159 mjson_get_number(event, eventLen, jp, &messageType);
\r
1160 int messageTypeInt = (int)messageType;
\r
1162 snprintf(jp, 128, "$.content.ciphertext.%s.body", thisDeviceKey);
\r
1164 mjson_get_string(event, eventLen, jp, g_EncryptedEventBuffer, 2048);
\r
1166 MatrixOlmSession * olmSession;
\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
1173 verifyFromDeviceId,
\r
1174 g_EncryptedEventBuffer,
\r
1177 // or new outgoing, checking for known sessions first
\r
1179 if (! MatrixClientGetOlmSession(client, client->userId, verifyFromDeviceId, &olmSession))
\r
1181 MatrixClientNewOlmSessionOut(client,
\r
1183 verifyFromDeviceId,
\r
1188 STATIC char decrypted[2048];
\r
1189 MatrixOlmSessionDecrypt(olmSession,
\r
1190 messageTypeInt, g_EncryptedEventBuffer, decrypted, 2048);
\r
1192 MatrixClientHandleEvent(client, decrypted, strlen(decrypted));
\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
1205 MatrixMegolmInSession * megolmInSession;
\r
1206 MatrixClientNewMegolmInSession(client, roomId, sessionId, sessionKey, &megolmInSession);
\r
1211 MatrixClientHandleRoomEvent(
\r
1212 MatrixClient * client,
\r
1213 const char * room, int roomLen,
\r
1214 const char * event, int eventLen)
\r
1216 STATIC char eventType[128];
\r
1217 memset(eventType, 0, sizeof(eventType));
\r
1218 mjson_get_string(event, eventLen, "$.type", eventType, 128);
\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
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
1233 MatrixMegolmInSession * megolmInSession;
\r
1234 res = MatrixClientGetMegolmInSession(client,
\r
1236 sessionId, sessionIdLen,
\r
1237 &megolmInSession);
\r
1240 mjson_get_string(event, eventLen, "$.content.ciphertext", g_EncryptedEventBuffer, 2048);
\r
1242 STATIC char decrypted[2048];
\r
1243 MatrixMegolmInSessionDecrypt(megolmInSession, g_EncryptedEventBuffer, strlen(g_EncryptedEventBuffer), decrypted, 2048);
\r
1245 MatrixClientHandleEvent(client, decrypted, strlen(decrypted));
\r
1248 printf("error: megolm session not known\n");
\r
1252 MatrixClientHandleEvent(client, event, eventLen);
\r
1255 // pass the response from sync to Handle(Room)Event
\r
1257 MatrixClientHandleSync(
\r
1258 MatrixClient * client,
\r
1259 char * syncBuffer, int syncBufferLen,
\r
1260 char * nextBatch, int nextBatchCap)
\r
1264 const char * s = syncBuffer;
\r
1265 int slen = syncBufferLen;
\r
1267 // read next_batch
\r
1268 mjson_get_string(s, slen, "$.next_batch", nextBatch, nextBatchCap);
\r
1271 const char * events;
\r
1274 mjson_find(s, slen, "$.to_device.events", &events, &eventsLen);
\r
1276 // iterate event and pass to HandleEvent
\r
1277 if (res != MJSON_TOK_INVALID) {
\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
1284 MatrixClientHandleEvent(client, v, vlen);
\r
1290 const char * rooms;
\r
1293 mjson_find(s, slen, "$.rooms.join", &rooms, &roomsLen);
\r
1295 if (res != MJSON_TOK_INVALID) {
\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
1303 const char * events;
\r
1306 mjson_find(v, vlen, "$.timeline.events", &events, &eventsLen);
\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
1315 MatrixClientHandleRoomEvent(client,
\r
1324 // https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3sync
\r
1327 MatrixClient * client,
\r
1328 char * outSyncBuffer, int outSyncCap,
\r
1329 char * nextBatch, int nextBatchCap)
\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
1337 // "&filter={\"event_fields\":[\"to_device\"]}",
\r
1338 strlen(nextBatch) > 0 ? "&since=" : "");
\r
1340 int index = strlen(url);
\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
1347 url[index++] = '%';
\r
1348 url[index++] = '7';
\r
1349 url[index++] = 'E';
\r
1355 url[index] = '\0';
\r
1358 MatrixHttpGet(client->hc,
\r
1360 outSyncBuffer, outSyncCap,
\r
1363 MatrixClientHandleSync(client,
\r
1364 outSyncBuffer, strlen(outSyncBuffer),
\r
1365 nextBatch, nextBatchCap);
\r
1370 // https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3roomsroomideventeventid
\r
1372 MatrixClientGetRoomEvent(
\r
1373 MatrixClient * client,
\r
1374 const char * roomId,
\r
1375 const char * eventId,
\r
1376 char * outEvent, int outEventCap)
\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
1385 MatrixHttpGet(client->hc,
\r
1387 outEvent, outEventCap,
\r
1392 MatrixClientShareMegolmOutSession(
\r
1393 MatrixClient * client,
\r
1394 const char * userId,
\r
1395 const char * deviceId,
\r
1396 MatrixMegolmOutSession * session)
\r
1398 // generate room key event
\r
1399 STATIC char eventBuffer[KEY_SHARE_EVENT_LEN];
\r
1400 snprintf(eventBuffer, KEY_SHARE_EVENT_LEN,
\r
1402 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
1403 "\"room_id\":\"%s\","
\r
1404 "\"session_id\":\"%s\","
\r
1405 "\"session_key\":\"%s\""
\r
1413 MatrixClientSendToDeviceEncrypted(client,
\r
1423 MatrixClientGetMegolmOutSession(
\r
1424 MatrixClient * client,
\r
1425 const char * roomId,
\r
1426 MatrixMegolmOutSession ** outSession)
\r
1428 for (int i = 0; i < client->numMegolmOutSessions; i++)
\r
1430 if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)
\r
1432 *outSession = &client->megolmOutSessions[i];
\r
1441 MatrixClientNewMegolmOutSession(
\r
1442 MatrixClient * client,
\r
1443 const char * roomId,
\r
1444 MatrixMegolmOutSession ** outSession)
\r
1446 if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)
\r
1448 MatrixMegolmOutSession * result =
\r
1449 &client->megolmOutSessions[client->numMegolmOutSessions];
\r
1451 MatrixMegolmOutSessionInit(result,
\r
1454 *outSession = result;
\r
1456 client->numMegolmOutSessions++;
\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
1471 for (int i = 0; i < client->numMegolmInSessions; i++)
\r
1473 if (strncmp(client->megolmInSessions[i].roomId, roomId, roomIdLen) == 0 &&
\r
1474 strncmp(client->megolmInSessions[i].id, sessionId, sessionIdLen) == 0)
\r
1476 *outSession = &client->megolmInSessions[i];
\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
1492 if (client->numMegolmInSessions < NUM_MEGOLM_SESSIONS)
\r
1494 MatrixMegolmInSession * result =
\r
1495 &client->megolmInSessions[client->numMegolmInSessions];
\r
1497 MatrixMegolmInSessionInit(result,
\r
1500 sessionKey, strlen(sessionKey));
\r
1502 *outSession = result;
\r
1504 client->numMegolmInSessions++;
\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
1521 // TODO: cancel requests
\r
1522 MatrixClientSendDummy(client, userId, deviceId);
\r
1524 STATIC char event[ROOMKEY_REQUEST_SIZE];
\r
1525 snprintf(event, ROOMKEY_REQUEST_SIZE,
\r
1527 "\"action\":\"request\","
\r
1529 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
1530 "\"room_id\":\"%s\","
\r
1531 "\"sender_key\":\"%s\","
\r
1532 "\"session_id\":\"%s\""
\r
1534 "\"request_id\":\"%lld\","
\r
1535 "\"requesting_device_id\":\"%s\""
\r
1541 client->deviceId);
\r
1544 MatrixClientSendToDevice(client,
\r
1548 "m.room_key_request");
\r
1554 MatrixClientGetOlmSession(
\r
1555 MatrixClient * client,
\r
1556 const char * userId,
\r
1557 const char * deviceId,
\r
1558 MatrixOlmSession ** outSession)
\r
1560 (void)userId; //unused for now
\r
1562 for (int i = 0; i < client->numOlmSessions; i++)
\r
1564 if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)
\r
1566 *outSession = &client->olmSessions[i];
\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
1582 (void)userId; //unused for now
\r
1584 if (client->numOlmSessions < NUM_OLM_SESSIONS)
\r
1586 STATIC char deviceKey[DEVICE_KEY_SIZE];
\r
1587 MatrixClientRequestDeviceKey(client,
\r
1589 deviceKey, DEVICE_KEY_SIZE);
\r
1591 MatrixOlmSessionFrom(
\r
1592 &client->olmSessions[client->numOlmSessions],
\r
1593 client->olmAccount.account,
\r
1598 *outSession = &client->olmSessions[client->numOlmSessions];
\r
1600 client->numOlmSessions++;
\r
1609 MatrixClientNewOlmSessionOut(
\r
1610 MatrixClient * client,
\r
1611 const char * userId,
\r
1612 const char * deviceId,
\r
1613 MatrixOlmSession ** outSession)
\r
1615 if (client->numOlmSessions < NUM_OLM_SESSIONS)
\r
1617 STATIC char deviceKey[DEVICE_KEY_SIZE];
\r
1618 MatrixClientRequestDeviceKey(client,
\r
1620 deviceKey, DEVICE_KEY_SIZE);
\r
1622 char onetimeKey[ONETIME_KEY_SIZE];
\r
1623 MatrixClientClaimOnetimeKey(client,
\r
1626 onetimeKey, ONETIME_KEY_SIZE);
\r
1628 MatrixOlmSessionTo(
\r
1629 &client->olmSessions[client->numOlmSessions],
\r
1630 client->olmAccount.account,
\r
1635 *outSession = &client->olmSessions[client->numOlmSessions];
\r
1637 client->numOlmSessions++;
\r
1645 // https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
\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
1654 STATIC char requestUrl[MAX_URL_LEN];
\r
1655 snprintf(requestUrl, MAX_URL_LEN,
\r
1656 TODEVICE_URL, msgType, (int)time(NULL));
\r
1658 snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,
\r
1670 STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];
\r
1672 MatrixHttpPut(client->hc,
\r
1674 g_TodeviceEventBuffer,
\r
1675 responseBuffer, ROOM_SEND_RESPONSE_SIZE,
\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
1689 // get olm session
\r
1690 MatrixOlmSession * olmSession;
\r
1691 if (! MatrixClientGetOlmSession(client, userId, deviceId, &olmSession))
\r
1692 MatrixClientNewOlmSessionOut(client, userId, deviceId, &olmSession);
\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
1700 char thisSigningKey[DEVICE_KEY_SIZE];
\r
1701 MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);
\r
1703 snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,
\r
1705 "\"type\":\"%s\","
\r
1707 "\"sender\":\"%s\","
\r
1708 "\"recipient\":\"%s\","
\r
1709 "\"recipient_keys\":{"
\r
1710 "\"ed25519\":\"%s\""
\r
1713 "\"ed25519\":\"%s\""
\r
1719 userId, // recipient user id
\r
1720 targetSigningKey, // recipient device key
\r
1724 MatrixOlmSessionEncrypt(olmSession,
\r
1725 g_TodeviceEventBuffer,
\r
1726 g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);
\r
1728 char thisDeviceKey[DEVICE_KEY_SIZE];
\r
1729 MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);
\r
1731 snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,
\r
1733 "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","
\r
1734 "\"ciphertext\":{"
\r
1736 "\"body\":\"%s\","
\r
1740 "\"device_id\":\"%s\","
\r
1741 "\"sender_key\":\"%s\""
\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
1751 return MatrixClientSendToDevice(
\r
1755 g_EncryptedEventBuffer,
\r
1756 "m.room.encrypted");
\r
1760 MatrixClientSendDummy(
\r
1761 MatrixClient * client,
\r
1762 const char * userId,
\r
1763 const char * deviceId)
\r
1765 return MatrixClientSendToDeviceEncrypted(
\r
1774 MatrixClientFindDevice(
\r
1775 MatrixClient * client,
\r
1776 const char * deviceId,
\r
1777 MatrixDevice ** outDevice)
\r
1779 for (int i = 0; i < client->numDevices; i++)
\r
1781 if (strcmp(client->devices[i].deviceId, deviceId) == 0)
\r
1783 *outDevice = &client->devices[i];
\r
1788 MatrixClientRequestDeviceKeys(client);
\r
1790 for (int i = 0; i < client->numDevices; i++)
\r
1792 if (strcmp(client->devices[i].deviceId, deviceId) == 0)
\r
1794 *outDevice = &client->devices[i];
\r
1799 *outDevice = NULL;
\r
1804 MatrixClientRequestDeviceKey(
\r
1805 MatrixClient * client,
\r
1806 const char * deviceId,
\r
1807 char * outDeviceKey, int outDeviceKeyCap)
\r
1809 MatrixDevice * device;
\r
1811 if (MatrixClientFindDevice(client, deviceId, &device))
\r
1813 strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);
\r
1817 MatrixClientRequestDeviceKeys(client);
\r
1819 if (MatrixClientFindDevice(client, deviceId, &device))
\r
1821 strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);
\r
1829 MatrixClientRequestSigningKey(
\r
1830 MatrixClient * client,
\r
1831 const char * deviceId,
\r
1832 char * outSigningKey, int outSigningKeyCap)
\r
1834 MatrixDevice * device;
\r
1836 if (MatrixClientFindDevice(client, deviceId, &device))
\r
1838 strncpy(outSigningKey, device->signingKey, outSigningKeyCap);
\r
1842 MatrixClientRequestDeviceKeys(client);
\r
1844 if (MatrixClientFindDevice(client, deviceId, &device))
\r
1846 strncpy(outSigningKey, device->signingKey, outSigningKeyCap);
\r
1854 MatrixClientRequestMasterKey(
\r
1855 MatrixClient * client,
\r
1856 char * outMasterKey, int outMasterKeyCap)
\r
1858 if (strlen(client->masterKey) > 0) {
\r
1859 strncpy(outMasterKey, client->masterKey, outMasterKeyCap);
\r
1863 MatrixClientRequestDeviceKeys(client);
\r
1865 if (strlen(client->masterKey) > 0) {
\r
1866 strncpy(outMasterKey, client->masterKey, outMasterKeyCap);
\r
1873 // https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3keysquery
\r
1875 MatrixClientRequestDeviceKeys(
\r
1876 MatrixClient * client)
\r
1878 if (client->numDevices >= NUM_DEVICES) {
\r
1879 printf("error: Maximum number of devices reached\n");
\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
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
1893 STATIC char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];
\r
1894 bool requestResult = MatrixHttpPost(client->hc,
\r
1897 responseBuffer, KEYS_QUERY_RESPONSE_SIZE,
\r
1900 if (! requestResult)
\r
1903 // query for retrieving device keys for user id
\r
1904 STATIC char query[JSON_QUERY_SIZE];
\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
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
1921 // iterate over returned devices for that userId
\r
1922 snprintf(query, JSON_QUERY_SIZE,
\r
1923 "$.device_keys.%s", userIdEscaped);
\r
1925 mjson_find(responseBuffer, strlen(responseBuffer),
\r
1926 query, &s, &slen);
\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
1935 // set device id, "key" is the JSON key
\r
1937 snprintf(d.deviceId, DEVICE_ID_SIZE,
\r
1938 "%.*s", klen-2, key+1);
\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
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
1955 if (client->numDevices < NUM_DEVICES)
\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
1962 if (! foundDevice) {
\r
1963 client->devices[client->numDevices] = d;
\r
1964 client->numDevices++;
\r
1977 MatrixClientDeleteDevice(
\r
1978 MatrixClient * client)
\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