8 #define LOGIN_REQUEST_SIZE 1024
\r
9 #define LOGIN_RESPONSE_SIZE 1024
\r
10 #define LOGIN_URL "/_matrix/client/v3/login"
\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
18 #define TODEVICE_EVENT_SIZE 512
\r
19 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"
\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
25 #define UPLOAD_KEYS_REQUEST_SIZE 512
\r
26 #define UPLOAD_KEYS_REQUEST_SIGNED_SIZE 1024
\r
28 #define JSON_QUERY_SIZE 128
\r
37 static bool first = false;
\r
38 if (first) { srand(time(0)); first = false; }
\r
40 for (int i = 0; i < randomLen; i++)
\r
42 random[i] = rand() % 256;
\r
48 char * sIn, int sInLen,
\r
49 char * sOut, int sOutCap)
\r
53 for (int i = 0; i < sInLen; i++)
\r
58 if (sIn[i] == '.' ||
\r
62 sOut[sOutIndex++] = '\\';
\r
64 sOut[sOutIndex++] = sIn[i];
\r
67 if (sOutIndex < sOutCap)
\r
68 sOut[sOutIndex] = '\0';
\r
74 MatrixClient * client,
\r
75 char * sIn, int sInLen,
\r
76 char * sOut, int sOutCap)
\r
78 static char signature[OLM_SIGNATURE_SIZE];
\r
80 olm_account_sign(client->olmAccount.account,
\r
82 signature, OLM_SIGNATURE_SIZE);
\r
84 int signatureLen = res;
\r
86 static char signatureJson[JSON_SIGNATURE_SIZE];
\r
87 int signatureJsonLen =
\r
88 mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,
\r
92 "\"ed25519:%s\":\"%.*s\""
\r
98 signatureLen, signature);
\r
100 struct mjson_fixedbuf result = { sOut, sOutCap, 0 };
\r
103 signatureJson, signatureJsonLen,
\r
104 mjson_print_fixed_buf,
\r
112 MatrixOlmAccountInit(
\r
113 MatrixOlmAccount * account)
\r
115 account->account = olm_account(account->memory);
\r
117 static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];
\r
118 Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);
\r
120 size_t res = olm_create_account(
\r
123 OLM_ACCOUNT_RANDOM_SIZE);
\r
125 return res != olm_error();
\r
128 // TODO: in/outbound sessions
\r
130 MatrixOlmSessionInit(
\r
131 MatrixOlmSession * session,
\r
132 const char * deviceId)
\r
134 memset(session, 0, sizeof(MatrixOlmSession));
\r
136 static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];
\r
137 Randomize(random, MEGOLM_INIT_RANDOM_SIZE);
\r
139 session->deviceId = deviceId;
\r
142 olm_session(session->memory);
\r
144 return session->session != NULL;
\r
148 MatrixOlmSessionEncrypt(
\r
149 MatrixOlmSession * session,
\r
150 const char * plaintext,
\r
151 char * outBuffer, int outBufferCap)
\r
153 static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];
\r
154 Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);
\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
161 return res != olm_error();
\r
164 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session
\r
166 MatrixMegolmOutSessionInit(
\r
167 MatrixMegolmOutSession * session,
\r
168 const char * roomId)
\r
170 memset(session, 0, sizeof(MatrixMegolmOutSession));
\r
172 static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];
\r
173 Randomize(random, MEGOLM_INIT_RANDOM_SIZE);
\r
175 session->roomId = roomId;
\r
178 olm_outbound_group_session(session->memory);
\r
180 olm_init_outbound_group_session(
\r
183 MEGOLM_INIT_RANDOM_SIZE);
\r
185 olm_outbound_group_session_id(session->session,
\r
186 (uint8_t *)session->id,
\r
187 MEGOLM_SESSION_ID_SIZE);
\r
189 olm_outbound_group_session_key(session->session,
\r
190 (uint8_t *)session->key,
\r
191 MEGOLM_SESSION_KEY_SIZE);
\r
197 MatrixMegolmOutSessionEncrypt(
\r
198 MatrixMegolmOutSession * session,
\r
199 const char * plaintext,
\r
200 char * outBuffer, int outBufferCap)
\r
202 size_t res = olm_group_encrypt(session->session,
\r
203 (uint8_t *)plaintext, strlen(plaintext),
\r
204 (uint8_t *)outBuffer, outBufferCap);
\r
206 return res != olm_error();
\r
213 MatrixClient * client,
\r
214 const char * server)
\r
216 memset(client, 0, sizeof(MatrixClient));
\r
218 strcpy(client->server, server);
\r
220 // init olm account
\r
221 MatrixOlmAccountInit(&client->olmAccount);
\r
224 static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];
\r
226 olm_account_identity_keys(
\r
227 client->olmAccount.account,
\r
229 OLM_IDENTITY_KEYS_JSON_SIZE);
\r
231 mjson_get_string(deviceKeysJson, res,
\r
233 client->deviceKey, DEVICE_KEY_SIZE);
\r
234 mjson_get_string(deviceKeysJson, res,
\r
236 client->signingKey, SIGNING_KEY_SIZE);
\r
242 MatrixClientSetAccessToken(
\r
243 MatrixClient * client,
\r
244 const char * accessToken)
\r
246 int accessTokenLen = strlen(accessToken);
\r
248 if (accessTokenLen > ACCESS_TOKEN_SIZE - 1)
\r
251 for (int i = 0; i < accessTokenLen; i++)
\r
252 client->accessToken[i] = accessToken[i];
\r
258 MatrixClientSetDeviceId(
\r
259 MatrixClient * client,
\r
260 const char * deviceId)
\r
262 int deviceIdLen = strlen(deviceId);
\r
264 if (deviceIdLen > DEVICE_ID_SIZE - 1)
\r
267 for (int i = 0; i < deviceIdLen; i++)
\r
268 client->deviceId[i] = deviceId[i];
\r
274 MatrixClientSetUserId(
\r
275 MatrixClient * client,
\r
276 const char * userId)
\r
278 int userIdLen = strlen(userId);
\r
280 if (userIdLen > USER_ID_SIZE - 1)
\r
283 for (int i = 0; i < userIdLen; i++)
\r
284 client->userId[i] = userId[i];
\r
290 MatrixClientGenerateOnetimeKeys(
\r
291 MatrixClient * client,
\r
294 static uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];
\r
295 Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);
\r
298 olm_account_generate_one_time_keys(client->olmAccount.account,
\r
299 numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);
\r
301 return res != olm_error();
\r
305 MatrixClientUploadOnetimeKeys(
\r
306 MatrixClient * client)
\r
308 static char requestBuffer[UPLOAD_KEYS_REQUEST_SIZE];
\r
310 mjson_snprintf(requestBuffer, UPLOAD_KEYS_REQUEST_SIZE,
\r
311 "{\"one_time_keys\":{");
\r
313 static char onetimeKeysBuffer[1024];
\r
314 olm_account_one_time_keys(client->olmAccount.account,
\r
315 onetimeKeysBuffer, 1024);
\r
319 mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);
\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
325 snprintf(keyJson, JSON_ONETIME_KEY_SIZE,
\r
326 "{\"key\":\"%.*s\"}",
\r
327 vlen-2, keys + voff+1);
\r
329 static char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];
\r
332 keyJson, JSON_ONETIME_KEY_SIZE,
\r
333 keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);
\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
341 mjson_snprintf(requestBuffer+strlen(requestBuffer), UPLOAD_KEYS_REQUEST_SIZE-strlen(requestBuffer),
\r
344 printf("%s\n", requestBuffer);
\r
350 MatrixClientUploadDeviceKeys(
\r
351 MatrixClient * client)
\r
353 static char deviceKeysBuffer[UPLOAD_KEYS_REQUEST_SIZE];
\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
360 "\"curve25519:%s\":\"%s\","
\r
361 "\"ed25519:%s\":\"%s\""
\r
363 "\"user_id\":\"%s\""
\r
366 client->deviceId, client->deviceKey,
\r
367 client->deviceId, client->signingKey,
\r
370 static char deviceKeysSignedBuffer[UPLOAD_KEYS_REQUEST_SIGNED_SIZE];
\r
372 deviceKeysBuffer, UPLOAD_KEYS_REQUEST_SIZE,
\r
373 deviceKeysSignedBuffer, UPLOAD_KEYS_REQUEST_SIZE);
\r
375 printf("%s\n", deviceKeysSignedBuffer);
\r
380 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login
\r
382 MatrixClientLoginPassword(
\r
383 MatrixClient * client,
\r
384 const char * username,
\r
385 const char * password,
\r
386 const char * displayName)
\r
388 static char requestBuffer[LOGIN_REQUEST_SIZE];
\r
390 mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,
\r
392 "\"type\": \"m.login.password\","
\r
393 "\"identifier\": {"
\r
394 "\"type\": \"m.id.user\","
\r
397 "\"password\": \"%s\","
\r
398 "\"initial_device_display_name\": \"%s\""
\r
404 static char responseBuffer[LOGIN_RESPONSE_SIZE];
\r
406 MatrixHttpPost(client,
\r
409 responseBuffer, LOGIN_RESPONSE_SIZE,
\r
412 int responseLen = strlen(responseBuffer);
\r
417 mjson_get_string(responseBuffer, responseLen,
\r
419 client->accessToken, ACCESS_TOKEN_SIZE);
\r
420 mjson_get_string(responseBuffer, responseLen,
\r
422 client->deviceId, DEVICE_ID_SIZE);
\r
423 mjson_get_string(responseBuffer, responseLen,
\r
425 client->expireMs, EXPIRE_MS_SIZE);
\r
426 mjson_get_string(responseBuffer, responseLen,
\r
428 client->refreshToken, REFRESH_TOKEN_SIZE);
\r
433 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
\r
435 MatrixClientSendEvent(
\r
436 MatrixClient * client,
\r
437 const char * roomId,
\r
438 const char * msgType,
\r
439 const char * msgBody)
\r
441 static char requestUrl[MAX_URL_LEN];
\r
442 sprintf(requestUrl,
\r
443 ROOMEVENT_URL, roomId, msgType, (int)time(NULL));
\r
445 static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];
\r
447 MatrixHttpPut(client,
\r
450 responseBuffer, ROOMEVENT_RESPONSE_SIZE,
\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
459 MatrixClientSendEventEncrypted(
\r
460 MatrixClient * client,
\r
461 const char * roomId,
\r
462 const char * msgType,
\r
463 const char * msgBody)
\r
466 static char requestBuffer[ROOMEVENT_REQUEST_SIZE];
\r
467 sprintf(requestBuffer,
\r
471 "\"room_id\":\"%s\""
\r
477 // get megolm session
\r
478 MatrixMegolmOutSession * outSession;
\r
479 MatrixClientGetMegolmOutSession(client, roomId, &outSession);
\r
482 static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];
\r
483 MatrixMegolmOutSessionEncrypt(outSession,
\r
485 encryptedBuffer, ENCRYPTED_REQUEST_SIZE);
\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
492 static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];
\r
493 sprintf(encryptedEventBuffer,
\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
507 return MatrixClientSendEvent(client,
\r
509 "m.room.encrypted",
\r
510 encryptedEventBuffer);
\r
513 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync
\r
516 MatrixClient * client,
\r
517 char * outSyncBuffer, int outSyncCap)
\r
520 MatrixHttpGet(client,
\r
521 "/_matrix/client/v3/sync",
\r
522 outSyncBuffer, outSyncCap,
\r
527 MatrixClientShareMegolmOutSession(
\r
528 MatrixClient * client,
\r
529 const char * deviceId,
\r
530 MatrixMegolmOutSession * session)
\r
532 // generate room key event
\r
533 char eventBuffer[KEY_SHARE_EVENT_LEN];
\r
534 sprintf(eventBuffer,
\r
536 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
537 "\"room_id\":\"%s\","
\r
538 "\"session_id\":\"%s\","
\r
539 "\"session_key\":\"%s\""
\r
547 MatrixOlmSession * olmSession;
\r
548 MatrixClientGetOlmSession(client, deviceId, &olmSession);
\r
551 char encryptedBuffer[KEY_SHARE_EVENT_LEN];
\r
552 MatrixOlmSessionEncrypt(olmSession,
\r
554 encryptedBuffer, KEY_SHARE_EVENT_LEN);
\r
557 MatrixClientSendToDeviceEncrypted(client,
\r
567 MatrixClientShareMegolmOutSessionTest(
\r
568 MatrixClient * client,
\r
569 const char * deviceId,
\r
570 MatrixMegolmOutSession * session)
\r
572 // generate room key event
\r
573 char eventBuffer[KEY_SHARE_EVENT_LEN];
\r
574 sprintf(eventBuffer,
\r
576 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
577 "\"room_id\":\"%s\","
\r
578 "\"session_id\":\"%s\","
\r
579 "\"session_key\":\"%s\""
\r
587 MatrixClientSendToDevice(client,
\r
597 // MatrixClientSetMegolmOutSession(
\r
598 // MatrixClient * client,
\r
599 // const char * roomId,
\r
600 // MatrixMegolmOutSession session)
\r
602 // if (client->numMegolmOutSessions < 10)
\r
604 // session.roomId = roomId;
\r
605 // client->megolmOutSessions[client->numMegolmOutSessions] = session;
\r
606 // client->numMegolmOutSessions++;
\r
614 MatrixClientGetMegolmOutSession(
\r
615 MatrixClient * client,
\r
616 const char * roomId,
\r
617 MatrixMegolmOutSession ** outSession)
\r
619 for (int i = 0; i < client->numMegolmOutSessions; i++)
\r
621 if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)
\r
623 *outSession = &client->megolmOutSessions[i];
\r
628 if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)
\r
630 MatrixMegolmOutSessionInit(
\r
631 &client->megolmOutSessions[client->numMegolmOutSessions],
\r
634 *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];
\r
636 client->numMegolmOutSessions++;
\r
645 MatrixClientGetOlmSession(
\r
646 MatrixClient * client,
\r
647 const char * deviceId,
\r
648 MatrixOlmSession ** outSession)
\r
650 for (int i = 0; i < client->numOlmSessions; i++)
\r
652 if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)
\r
654 *outSession = &client->olmSessions[i];
\r
659 if (client->numOlmSessions < NUM_OLM_SESSIONS)
\r
661 MatrixOlmSessionInit(
\r
662 &client->olmSessions[client->numOlmSessions],
\r
665 *outSession = &client->olmSessions[client->numOlmSessions];
\r
667 client->numOlmSessions++;
\r
675 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
\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
684 static char requestUrl[MAX_URL_LEN];
\r
685 sprintf(requestUrl,
\r
686 TODEVICE_URL, msgType, (int)time(NULL));
\r
688 static char eventBuffer[TODEVICE_EVENT_SIZE];
\r
689 snprintf(eventBuffer, TODEVICE_EVENT_SIZE,
\r
701 static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];
\r
703 MatrixHttpPut(client,
\r
706 responseBuffer, ROOMEVENT_RESPONSE_SIZE,
\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
721 MatrixOlmSession * olmSession;
\r
722 MatrixClientGetOlmSession(client, deviceId, &olmSession);
\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
729 static char eventBuffer[TODEVICE_EVENT_SIZE];
\r
730 sprintf(eventBuffer,
\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
740 "\"ed25519\": \"%s\""
\r
746 userId, // recipient user id
\r
747 deviceKey, // recipient device key
\r
748 client->deviceKey);
\r
751 static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];
\r
752 MatrixOlmSessionEncrypt(olmSession,
\r
754 encryptedBuffer, ENCRYPTED_REQUEST_SIZE);
\r
756 static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];
\r
757 sprintf(encryptedEventBuffer,
\r
759 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
760 "\"sender_key\":\"%s\","
\r
774 return MatrixClientSendToDevice(
\r
778 encryptedEventBuffer,
\r
779 "m.room.encrypted");
\r
783 MatrixClientFindDevice(
\r
784 MatrixClient * client,
\r
785 const char * deviceId,
\r
786 MatrixDevice ** outDevice)
\r
788 MatrixClientRequestDeviceKeys(client);
\r
790 for (int i = 0; i < client->numDevices; i++)
\r
792 if (strcmp(client->devices[i].deviceId, deviceId) == 0)
\r
794 *outDevice = &client->devices[i];
\r
804 MatrixClientGetDeviceKey(
\r
805 MatrixClient * client,
\r
806 const char * deviceId,
\r
807 char * outDeviceKey, int outDeviceKeyCap)
\r
809 MatrixClientRequestDeviceKeys(client);
\r
811 MatrixDevice * device;
\r
812 if (MatrixClientFindDevice(client, deviceId, &device))
\r
814 strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);
\r
821 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery
\r
823 MatrixClientRequestDeviceKeys(
\r
824 MatrixClient * client)
\r
826 char userIdEscaped[USER_ID_SIZE];
\r
827 JsonEscape(client->userId, strlen(client->userId),
\r
828 userIdEscaped, USER_ID_SIZE);
\r
830 char request[KEYS_QUERY_REQUEST_SIZE];
\r
831 snprintf(request, KEYS_QUERY_REQUEST_SIZE,
\r
832 "{\"device_keys\":{\"%s\":[]}}", userIdEscaped);
\r
834 char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];
\r
835 bool requestResult = MatrixHttpPost(client,
\r
838 responseBuffer, KEYS_QUERY_RESPONSE_SIZE,
\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
850 mjson_find(responseBuffer, strlen(responseBuffer),
\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
861 // set device id, "key" is the JSON key
\r
863 strncpy(d.deviceId, key, klen);
\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
873 if (client->numDevices < NUM_DEVICES)
\r
875 client->devices[client->numDevices] = d;
\r
876 client->numDevices++;
\r