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 JSON_QUERY_SIZE 128
\r
30 Randomize(uint8_t * random, int randomLen)
\r
32 static bool first = false;
\r
33 if (first) { srand(time(0)); first = false; }
\r
35 for (int i = 0; i < randomLen; i++)
\r
37 random[i] = rand() % 256;
\r
43 char * sIn, int sInLen,
\r
44 char * sOut, int sOutCap)
\r
48 for (int i = 0; i < sInLen; i++)
\r
53 if (sIn[i] == '.' ||
\r
57 sOut[sOutIndex++] = '\\';
\r
59 sOut[sOutIndex++] = sIn[i];
\r
62 if (sOutIndex < sOutCap)
\r
63 sOut[sOutIndex] = '\0';
\r
68 // TODO: in/outbound sessions
\r
70 MatrixOlmSessionInit(
\r
71 MatrixOlmSession * session,
\r
72 const char * deviceId)
\r
74 memset(session, 0, sizeof(MatrixOlmSession));
\r
76 static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];
\r
77 Randomize(random, MEGOLM_INIT_RANDOM_SIZE);
\r
79 session->deviceId = deviceId;
\r
82 olm_session(session->memory);
\r
84 return session->session != NULL;
\r
88 MatrixOlmSessionEncrypt(
\r
89 MatrixOlmSession * session,
\r
90 const char * plaintext,
\r
91 char * outBuffer, int outBufferCap)
\r
93 static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];
\r
94 Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);
\r
96 size_t res = olm_encrypt(session->session,
\r
97 plaintext, strlen(plaintext),
\r
98 random, OLM_ENCRYPT_RANDOM_SIZE,
\r
99 outBuffer, outBufferCap);
\r
101 return res != olm_error();
\r
104 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session
\r
106 MatrixMegolmOutSessionInit(
\r
107 MatrixMegolmOutSession * session,
\r
108 const char * roomId)
\r
110 memset(session, 0, sizeof(MatrixMegolmOutSession));
\r
112 static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];
\r
113 Randomize(random, MEGOLM_INIT_RANDOM_SIZE);
\r
115 session->roomId = roomId;
\r
118 olm_outbound_group_session(session->memory);
\r
120 olm_init_outbound_group_session(
\r
123 MEGOLM_INIT_RANDOM_SIZE);
\r
125 olm_outbound_group_session_id(session->session,
\r
126 (uint8_t *)session->id,
\r
127 MEGOLM_SESSION_ID_SIZE);
\r
129 olm_outbound_group_session_key(session->session,
\r
130 (uint8_t *)session->key,
\r
131 MEGOLM_SESSION_KEY_SIZE);
\r
137 MatrixMegolmOutSessionEncrypt(
\r
138 MatrixMegolmOutSession * session,
\r
139 const char * plaintext,
\r
140 char * outBuffer, int outBufferCap)
\r
142 size_t res = olm_group_encrypt(session->session,
\r
143 (uint8_t *)plaintext, strlen(plaintext),
\r
144 (uint8_t *)outBuffer, outBufferCap);
\r
146 return res != olm_error();
\r
153 MatrixClient * client,
\r
154 const char * server)
\r
156 memset(client, 0, sizeof(MatrixClient));
\r
158 strcpy(client->server, server);
\r
160 // init olm account
\r
161 client->olmAccount = olm_account(client->olmAccountMemory);
\r
163 static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];
\r
164 Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);
\r
167 res = olm_create_account(
\r
168 client->olmAccount,
\r
170 OLM_ACCOUNT_RANDOM_SIZE);
\r
173 static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];
\r
175 olm_account_identity_keys(
\r
176 client->olmAccount,
\r
178 OLM_IDENTITY_KEYS_JSON_SIZE);
\r
180 mjson_get_string(deviceKeysJson, res,
\r
182 client->deviceKey, DEVICE_KEY_SIZE);
\r
183 mjson_get_string(deviceKeysJson, res,
\r
185 client->signingKey, SIGNING_KEY_SIZE);
\r
191 MatrixClientSetAccessToken(
\r
192 MatrixClient * client,
\r
193 const char * accessToken)
\r
195 int accessTokenLen = strlen(accessToken);
\r
197 if (accessTokenLen > ACCESS_TOKEN_SIZE - 1)
\r
200 for (int i = 0; i < accessTokenLen; i++)
\r
201 client->accessToken[i] = accessToken[i];
\r
207 MatrixClientSetDeviceId(
\r
208 MatrixClient * client,
\r
209 const char * deviceId)
\r
211 int deviceIdLen = strlen(deviceId);
\r
213 if (deviceIdLen > DEVICE_ID_SIZE - 1)
\r
216 for (int i = 0; i < deviceIdLen; i++)
\r
217 client->deviceId[i] = deviceId[i];
\r
222 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login
\r
224 MatrixClientLoginPassword(
\r
225 MatrixClient * client,
\r
226 const char * username,
\r
227 const char * password,
\r
228 const char * displayName)
\r
230 static char requestBuffer[LOGIN_REQUEST_SIZE];
\r
232 mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,
\r
234 "\"type\": \"m.login.password\","
\r
235 "\"identifier\": {"
\r
236 "\"type\": \"m.id.user\","
\r
239 "\"password\": \"%s\","
\r
240 "\"initial_device_display_name\": \"%s\""
\r
246 static char responseBuffer[LOGIN_RESPONSE_SIZE];
\r
248 MatrixHttpPost(client,
\r
251 responseBuffer, LOGIN_RESPONSE_SIZE,
\r
254 int responseLen = strlen(responseBuffer);
\r
259 mjson_get_string(responseBuffer, responseLen,
\r
261 client->accessToken, ACCESS_TOKEN_SIZE);
\r
262 mjson_get_string(responseBuffer, responseLen,
\r
264 client->deviceId, DEVICE_ID_SIZE);
\r
265 mjson_get_string(responseBuffer, responseLen,
\r
267 client->expireMs, EXPIRE_MS_SIZE);
\r
268 mjson_get_string(responseBuffer, responseLen,
\r
270 client->refreshToken, REFRESH_TOKEN_SIZE);
\r
275 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
\r
277 MatrixClientSendEvent(
\r
278 MatrixClient * client,
\r
279 const char * roomId,
\r
280 const char * msgType,
\r
281 const char * msgBody)
\r
283 static char requestUrl[MAX_URL_LEN];
\r
284 sprintf(requestUrl,
\r
285 ROOMEVENT_URL, roomId, msgType, (int)time(NULL));
\r
287 static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];
\r
289 MatrixHttpPut(client,
\r
292 responseBuffer, ROOMEVENT_RESPONSE_SIZE,
\r
298 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted
\r
299 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event
\r
301 MatrixClientSendEventEncrypted(
\r
302 MatrixClient * client,
\r
303 const char * roomId,
\r
304 const char * msgType,
\r
305 const char * msgBody)
\r
308 static char requestBuffer[ROOMEVENT_REQUEST_SIZE];
\r
309 sprintf(requestBuffer,
\r
313 "\"room_id\":\"%s\""
\r
319 // get megolm session
\r
320 MatrixMegolmOutSession * outSession;
\r
321 MatrixClientGetMegolmOutSession(client, roomId, &outSession);
\r
324 static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];
\r
325 MatrixMegolmOutSessionEncrypt(outSession,
\r
327 encryptedBuffer, ENCRYPTED_REQUEST_SIZE);
\r
329 // encrypted event json
\r
330 const char * senderKey = client->deviceKey;
\r
331 const char * sessionId = outSession->id;
\r
332 const char * deviceId = client->deviceId;
\r
334 static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];
\r
335 sprintf(encryptedEventBuffer,
\r
337 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
338 "\"sender_key\":\"%s\","
\r
339 "\"ciphertext\":\"%s\","
\r
340 "\"session_id\":\"%s\","
\r
341 "\"device_id\":\"%s\""
\r
349 return MatrixClientSendEvent(client,
\r
351 "m.room.encrypted",
\r
352 encryptedEventBuffer);
\r
355 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync
\r
358 MatrixClient * client,
\r
359 char * outSyncBuffer, int outSyncCap)
\r
362 MatrixHttpGet(client,
\r
363 "/_matrix/client/v3/sync",
\r
364 outSyncBuffer, outSyncCap,
\r
369 MatrixClientShareMegolmOutSession(
\r
370 MatrixClient * client,
\r
371 const char * deviceId,
\r
372 MatrixMegolmOutSession * session)
\r
374 // generate room key event
\r
375 char eventBuffer[KEY_SHARE_EVENT_LEN];
\r
376 sprintf(eventBuffer,
\r
378 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
379 "\"room_id\":\"%s\","
\r
380 "\"session_id\":\"%s\","
\r
381 "\"session_key\":\"%s\""
\r
389 MatrixOlmSession * olmSession;
\r
390 MatrixClientGetOlmSession(client, deviceId, &olmSession);
\r
393 char encryptedBuffer[KEY_SHARE_EVENT_LEN];
\r
394 MatrixOlmSessionEncrypt(olmSession,
\r
396 encryptedBuffer, KEY_SHARE_EVENT_LEN);
\r
399 MatrixClientSendToDeviceEncrypted(client,
\r
409 // MatrixClientSetMegolmOutSession(
\r
410 // MatrixClient * client,
\r
411 // const char * roomId,
\r
412 // MatrixMegolmOutSession session)
\r
414 // if (client->numMegolmOutSessions < 10)
\r
416 // session.roomId = roomId;
\r
417 // client->megolmOutSessions[client->numMegolmOutSessions] = session;
\r
418 // client->numMegolmOutSessions++;
\r
426 MatrixClientGetMegolmOutSession(
\r
427 MatrixClient * client,
\r
428 const char * roomId,
\r
429 MatrixMegolmOutSession ** outSession)
\r
431 for (int i = 0; i < client->numMegolmOutSessions; i++)
\r
433 if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)
\r
435 *outSession = &client->megolmOutSessions[i];
\r
440 if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)
\r
442 MatrixMegolmOutSessionInit(
\r
443 &client->megolmOutSessions[client->numMegolmOutSessions],
\r
446 *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];
\r
448 client->numMegolmOutSessions++;
\r
457 MatrixClientGetOlmSession(
\r
458 MatrixClient * client,
\r
459 const char * deviceId,
\r
460 MatrixOlmSession ** outSession)
\r
462 for (int i = 0; i < client->numOlmSessions; i++)
\r
464 if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)
\r
466 *outSession = &client->olmSessions[i];
\r
471 if (client->numOlmSessions < NUM_OLM_SESSIONS)
\r
473 MatrixOlmSessionInit(
\r
474 &client->olmSessions[client->numOlmSessions],
\r
477 *outSession = &client->olmSessions[client->numOlmSessions];
\r
479 client->numOlmSessions++;
\r
487 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
\r
489 MatrixClientSendToDevice(
\r
490 MatrixClient * client,
\r
491 const char * userId,
\r
492 const char * deviceId,
\r
493 const char * message,
\r
494 const char * msgType)
\r
496 static char requestUrl[MAX_URL_LEN];
\r
497 sprintf(requestUrl,
\r
498 TODEVICE_URL, msgType, (int)time(NULL));
\r
500 static char eventBuffer[TODEVICE_EVENT_SIZE];
\r
501 snprintf(eventBuffer, TODEVICE_EVENT_SIZE,
\r
513 static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];
\r
515 MatrixHttpPut(client,
\r
518 responseBuffer, ROOMEVENT_RESPONSE_SIZE,
\r
525 MatrixClientSendToDeviceEncrypted(
\r
526 MatrixClient * client,
\r
527 const char * userId,
\r
528 const char * deviceId,
\r
529 const char * message,
\r
530 const char * msgType)
\r
533 MatrixOlmSession * olmSession;
\r
534 MatrixClientGetOlmSession(client, deviceId, &olmSession);
\r
536 // create event json
\r
537 char deviceKey[DEVICE_KEY_SIZE];
\r
538 MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);
\r
539 const char * senderKey = client->deviceKey;
\r
541 static char eventBuffer[TODEVICE_EVENT_SIZE];
\r
542 sprintf(eventBuffer,
\r
544 "\"type\": \"%s\","
\r
545 "\"content\": \"%s\","
\r
546 "\"sender\": \"%s\","
\r
547 "\"recipient\": \"%s\","
\r
548 "\"recipient_keys\": {"
\r
549 "\"ed25519\": \"%s\""
\r
552 "\"ed25519\": \"%s\""
\r
558 userId, // recipient user id
\r
559 deviceKey, // recipient device key
\r
560 client->deviceKey);
\r
563 static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];
\r
564 MatrixOlmSessionEncrypt(olmSession,
\r
566 encryptedBuffer, ENCRYPTED_REQUEST_SIZE);
\r
568 static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];
\r
569 sprintf(encryptedEventBuffer,
\r
571 "\"algorithm\":\"m.megolm.v1.aes-sha2\","
\r
572 "\"sender_key\":\"%s\","
\r
586 return MatrixClientSendToDevice(
\r
590 encryptedEventBuffer,
\r
591 "m.room.encrypted");
\r
595 MatrixClientFindDevice(
\r
596 MatrixClient * client,
\r
597 const char * deviceId,
\r
598 MatrixDevice ** outDevice)
\r
600 MatrixClientRequestDeviceKeys(client);
\r
602 for (int i = 0; i < client->numDevices; i++)
\r
604 if (strcmp(client->devices[i].deviceId, deviceId) == 0)
\r
606 *outDevice = &client->devices[i];
\r
616 MatrixClientGetDeviceKey(
\r
617 MatrixClient * client,
\r
618 const char * deviceId,
\r
619 char * outDeviceKey, int outDeviceKeyCap)
\r
621 MatrixClientRequestDeviceKeys(client);
\r
623 MatrixDevice * device;
\r
624 if (MatrixClientFindDevice(client, deviceId, &device))
\r
626 strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);
\r
633 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery
\r
635 MatrixClientRequestDeviceKeys(
\r
636 MatrixClient * client)
\r
638 char userIdEscaped[USER_ID_SIZE];
\r
639 JsonEscape(client->userId, strlen(client->userId),
\r
640 userIdEscaped, USER_ID_SIZE);
\r
642 char request[KEYS_QUERY_REQUEST_SIZE];
\r
643 snprintf(request, KEYS_QUERY_REQUEST_SIZE,
\r
644 "{\"device_keys\":{\"%s\":[]}}", userIdEscaped);
\r
646 char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];
\r
647 bool requestResult = MatrixHttpPost(client,
\r
650 responseBuffer, KEYS_QUERY_RESPONSE_SIZE,
\r
655 // query for retrieving device keys for user id
\r
656 char query[JSON_QUERY_SIZE];
\r
657 snprintf(query, JSON_QUERY_SIZE,
\r
658 "$.device_keys.%s", userIdEscaped);
\r
662 mjson_find(responseBuffer, strlen(responseBuffer),
\r
667 int koff, klen, voff, vlen, vtype, off;
\r
668 for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,
\r
669 &voff, &vlen, &vtype)) != 0; ) {
\r
670 const char * key = s + koff;
\r
671 const char * val = s + voff;
\r
673 // set device id, "key" is the JSON key
\r
675 strncpy(d.deviceId, key, klen);
\r
677 // look for device key in value
\r
678 char deviceKeyQuery[JSON_QUERY_SIZE];
\r
679 snprintf(deviceKeyQuery, JSON_QUERY_SIZE,
\r
680 "$.keys.curve25519:%.*s", klen, key);
\r
681 mjson_get_string(val, vlen,
\r
682 deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);
\r
685 if (client->numDevices < NUM_DEVICES)
\r
687 client->devices[client->numDevices] = d;
\r
688 client->numDevices++;
\r