commit e66893f

shrub  ·  2026-07-31 15:03:58 +0000 UTC
parent 2e29696
do auth with bearssl and libtls
8 files changed,  +642, -17
+10, -0
 1@@ -0,0 +1,10 @@
 2+#ifndef MG_RANDOM_H
 3+#define MG_RANDOM_H
 4+
 5+#include <stddef.h>
 6+
 7+#include "types.h"
 8+
 9+int mg_random_bytes(u8* data, size_t size);
10+
11+#endif /* MG_RANDOM_H */
+7, -0
 1@@ -11,6 +11,8 @@ cc = meson.get_compiler('c')
 2 math_dep = cc.find_library('m', required: false)
 3 sdl_dep = dependency('sdl2')
 4 zlib_dep = dependency('zlib')
 5+tls_dep = dependency('libtls')
 6+bearssl_dep = cc.find_library('bearssl')
 7 renderer_backend = get_option('renderer')
 8 
 9 sources = [
10@@ -18,8 +20,10 @@ sources = [
11 	'source/util.c',
12 	'source/world/chunk.c',
13 	'source/client/camera.c',
14+	'source/mcproto/auth.c',
15 	'source/mcproto/buffer.c',
16 	'source/mcproto/client.c',
17+	'source/mcproto/crypto.c',
18 	'source/platform/platform.c',
19         'vendor/stb_image_c89.c',
20 ]
21@@ -99,6 +103,8 @@ renderer_dep = declare_dependency(
22 deps = [
23 	sdl_dep,
24 	zlib_dep,
25+	tls_dep,
26+	bearssl_dep,
27 	mgmath_dep,
28 	renderer_dep,
29 ]
30@@ -109,4 +115,5 @@ executable(
31 	include_directories: include_dirs,
32 	dependencies: deps,
33 	c_args: renderer_c_args,
34+	override_options: [ 'c_std=c99' ],
35 )
+3, -2
 1@@ -1,5 +1,6 @@
 2 #include <SDL2/SDL.h>
 3 #include <stdio.h>
 4+#include <stdlib.h>
 5 
 6 #include "SDL_keyboard.h"
 7 #include "SDL_timer.h"
 8@@ -65,8 +66,8 @@ static int protocol_poc(const char* address, const char* username)
 9 	char error[1024];
10 
11 	account.username = username;
12-	account.profile_uuid = NULL;
13-	account.access_token = NULL;
14+	account.profile_uuid = getenv("MANGOLIA_PROFILE_UUID");
15+	account.access_token = getenv("MANGOLIA_ACCESS_TOKEN");
16 	if (!mc_client_login(&client, address, &account, error, sizeof(error))) {
17 		fprintf(stderr, "%s\n", error);
18 		return MG_ERR_EXIT_FAILURE;
+201, -0
  1@@ -0,0 +1,201 @@
  2+#define _POSIX_C_SOURCE 200112L
  3+
  4+#include <stdio.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+
  8+#include <tls.h>
  9+
 10+#include "internal.h"
 11+
 12+#define MC_JOIN_HOST "sessionserver.mojang.com"
 13+#define MC_JOIN_PORT "443"
 14+#define MC_JOIN_PATH "/session/minecraft/join"
 15+
 16+static int json_string(MCBuffer* buffer, const char* value)
 17+{
 18+	const unsigned char* p = (const unsigned char*)value;
 19+	if (!bufwrite_u8(buffer, '"'))
 20+		return 0;
 21+	while (*p) {
 22+		char escaped[7];
 23+		size_t size = 1;
 24+		switch (*p) {
 25+		case '"': escaped[0] = '\\'; escaped[1] = '"'; size = 2; break;
 26+		case '\\': escaped[0] = '\\'; escaped[1] = '\\'; size = 2; break;
 27+		case '\b': escaped[0] = '\\'; escaped[1] = 'b'; size = 2; break;
 28+		case '\f': escaped[0] = '\\'; escaped[1] = 'f'; size = 2; break;
 29+		case '\n': escaped[0] = '\\'; escaped[1] = 'n'; size = 2; break;
 30+		case '\r': escaped[0] = '\\'; escaped[1] = 'r'; size = 2; break;
 31+		case '\t': escaped[0] = '\\'; escaped[1] = 't'; size = 2; break;
 32+		default:
 33+			if (*p < 0x20) {
 34+				snprintf(escaped, sizeof(escaped), "\\u%04x", *p);
 35+				size = 6;
 36+			} else {
 37+				escaped[0] = (char)*p;
 38+			}
 39+		}
 40+		if (!bufwrite(buffer, escaped, size))
 41+			return 0;
 42+		++p;
 43+	}
 44+	return bufwrite_u8(buffer, '"');
 45+}
 46+
 47+static int tls_write_all(struct tls* tls, const void* data, size_t size)
 48+{
 49+	const u8* bytes = (const u8*)data;
 50+	size_t sent = 0;
 51+
 52+	while (sent < size) {
 53+		ssize_t count = tls_write(tls, bytes + sent, size - sent);
 54+		if (count == TLS_WANT_POLLIN || count == TLS_WANT_POLLOUT)
 55+			continue;
 56+		if (count <= 0)
 57+			return 0;
 58+		sent += (size_t)count;
 59+	}
 60+	return 1;
 61+}
 62+
 63+static int tls_read_response(struct tls* tls, MCBuffer* response)
 64+{
 65+	u8 data[2048];
 66+
 67+	for (;;) {
 68+		ssize_t count = tls_read(tls, data, sizeof(data));
 69+		if (count == TLS_WANT_POLLIN || count == TLS_WANT_POLLOUT)
 70+			continue;
 71+		if (count == 0)
 72+			return 1;
 73+		if (count < 0)
 74+			return 0;
 75+		if (!bufwrite(response, data, (size_t)count))
 76+			return 0;
 77+		if (response->size > 64 * 1024)
 78+			return 0;
 79+	}
 80+}
 81+
 82+static long http_status(const MCBuffer* response)
 83+{
 84+	long status;
 85+
 86+	if (response->size < 12 || memcmp(response->data, "HTTP/", 5) != 0)
 87+		return 0;
 88+	status = strtol((const char*)response->data + 9, NULL, 10);
 89+	return status;
 90+}
 91+
 92+int mc_join_server(
 93+    const MCAccount* account,
 94+    const char* server_id,
 95+    const u8 shared_secret[16],
 96+    const u8* public_key,
 97+    size_t public_key_size,
 98+    char* error,
 99+    size_t error_size)
100+{
101+	struct tls_config* config = NULL;
102+	struct tls* tls = NULL;
103+	MCBuffer json;
104+	MCBuffer request;
105+	MCBuffer response;
106+	char hash[42];
107+	char headers[512];
108+	long status;
109+	int ok = 0;
110+
111+	if (!account->access_token || !account->access_token[0] ||
112+	    !account->profile_uuid || !account->profile_uuid[0]) {
113+		mc_set_error(error, error_size,
114+		    "online-mode server requires MANGOLIA_ACCESS_TOKEN and MANGOLIA_PROFILE_UUID");
115+		return 0;
116+	}
117+
118+	bufinit(&json);
119+	bufinit(&request);
120+	bufinit(&response);
121+	mc_server_hash(server_id, shared_secret, public_key, public_key_size, hash);
122+	if (!bufwrite(&json, "{\"accessToken\":", 15) ||
123+	    !json_string(&json, account->access_token) ||
124+	    !bufwrite(&json, ",\"selectedProfile\":", 19) ||
125+	    !json_string(&json, account->profile_uuid) ||
126+	    !bufwrite(&json, ",\"serverId\":", 12) ||
127+	    !json_string(&json, hash) ||
128+	    !bufwrite_u8(&json, '}')) {
129+		mc_set_error(error, error_size, "out of memory building session request");
130+		goto cleanup;
131+	}
132+
133+	if (tls_init() != 0) {
134+		mc_set_error(error, error_size, "libtls initialization failed");
135+		goto cleanup;
136+	}
137+	config = tls_config_new();
138+	tls = tls_client();
139+	if (!config || !tls) {
140+		mc_set_error(error, error_size, "libtls request initialization failed");
141+		goto cleanup;
142+	}
143+	if (tls_configure(tls, config) != 0) {
144+		mc_set_error(error, error_size, "libtls configure failed: %s",
145+		    tls_error(tls));
146+		goto cleanup;
147+	}
148+	if (tls_connect(tls, MC_JOIN_HOST, MC_JOIN_PORT) != 0) {
149+		mc_set_error(error, error_size, "session TLS connect failed: %s",
150+		    tls_error(tls));
151+		goto cleanup;
152+	}
153+	snprintf(headers, sizeof(headers),
154+	    "POST " MC_JOIN_PATH " HTTP/1.1\r\n"
155+	    "Host: " MC_JOIN_HOST "\r\n"
156+	    "User-Agent: Magnolia/1.8.9\r\n"
157+	    "Content-Type: application/json\r\n"
158+	    "Content-Length: %lu\r\n"
159+	    "Connection: close\r\n"
160+	    "\r\n",
161+	    (unsigned long)json.size);
162+	if (!bufwrite(&request, headers, strlen(headers)) ||
163+	    !bufwrite(&request, json.data, json.size)) {
164+		mc_set_error(error, error_size, "out of memory building session HTTP request");
165+		goto cleanup;
166+	}
167+	if (!tls_write_all(tls, request.data, request.size)) {
168+		mc_set_error(error, error_size, "session join request write failed: %s",
169+		    tls_error(tls));
170+		goto cleanup;
171+	}
172+	if (!tls_read_response(tls, &response)) {
173+		mc_set_error(error, error_size, "session join response read failed: %s",
174+		    tls_error(tls));
175+		goto cleanup;
176+	}
177+	status = http_status(&response);
178+	if (status != 204) {
179+		size_t show = response.size < 400 ? response.size : 400;
180+		mc_set_error(error, error_size,
181+		    "session server rejected join (HTTP %ld): %.*s",
182+		    status, (int)show, response.data ? (char*)response.data : "");
183+		goto cleanup;
184+	}
185+	ok = 1;
186+
187+cleanup:
188+	if (json.data)
189+		memset(json.data, 0, json.capacity);
190+	if (request.data)
191+		memset(request.data, 0, request.capacity);
192+	if (tls) {
193+		tls_close(tls);
194+		tls_free(tls);
195+	}
196+	if (config)
197+		tls_config_free(config);
198+	buffree(&response);
199+	buffree(&request);
200+	buffree(&json);
201+	return ok;
202+}
+98, -15
  1@@ -11,6 +11,7 @@
  2 #include <zlib.h>
  3 
  4 #include "internal.h"
  5+#include "mg_random.h"
  6 
  7 #ifndef MSG_NOSIGNAL
  8 #define MSG_NOSIGNAL 0
  9@@ -100,16 +101,28 @@ static int tcp_connect(MCClient* client, char* error, size_t error_size)
 10 
 11 static int socket_write(MCClient* client, const u8* data, size_t size)
 12 {
 13-	size_t sent = 0;
 14-	while (sent < size) {
 15-		ssize_t count = send(client->socket_fd, data + sent,
 16-		    size - sent, MSG_NOSIGNAL);
 17-		if (count > 0)
 18-			sent += (size_t)count;
 19-		else if (count < 0 && errno == EINTR)
 20-			continue;
 21-		else
 22-			return 0;
 23+	u8 encrypted[4096];
 24+	size_t offset = 0;
 25+
 26+	while (offset < size) {
 27+		size_t chunk = size - offset;
 28+		size_t sent = 0;
 29+		if (chunk > sizeof(encrypted))
 30+			chunk = sizeof(encrypted);
 31+		memcpy(encrypted, data + offset, chunk);
 32+		if (client->encrypted)
 33+			mc_cipher_encrypt(&client->write_cipher, encrypted, chunk);
 34+		while (sent < chunk) {
 35+			ssize_t count = send(client->socket_fd, encrypted + sent,
 36+			    chunk - sent, MSG_NOSIGNAL);
 37+			if (count > 0)
 38+				sent += (size_t)count;
 39+			else if (count < 0 && errno == EINTR)
 40+				continue;
 41+			else
 42+				return 0;
 43+		}
 44+		offset += chunk;
 45 	}
 46 	return 1;
 47 }
 48@@ -126,6 +139,8 @@ static int socket_read(MCClient* client, u8* data, size_t size)
 49 		else
 50 			return 0;
 51 	}
 52+	if (client->encrypted)
 53+		mc_cipher_decrypt(&client->read_cipher, data, size);
 54 	return 1;
 55 }
 56 
 57@@ -325,7 +340,74 @@ static int send_login_start(MCClient* client)
 58 	return ok;
 59 }
 60 
 61-static int login_loop(MCClient* client, char* error, size_t error_size)
 62+static int handle_encryption(
 63+    MCClient* client,
 64+    const MCAccount* account,
 65+    MCPacket* packet,
 66+    char* error,
 67+    size_t error_size)
 68+{
 69+	MCBuffer input = { packet->data, packet->size, packet->size, 0 };
 70+	MCBuffer response;
 71+	char server_id[256];
 72+	const u8* public_key;
 73+	const u8* verify_token;
 74+	size_t public_key_size;
 75+	size_t verify_token_size;
 76+	u8 shared_secret[16];
 77+	u8* encrypted_secret = NULL;
 78+	u8* encrypted_token = NULL;
 79+	size_t encrypted_secret_size = 0;
 80+	size_t encrypted_token_size = 0;
 81+	int ok = 0;
 82+
 83+	if (!bufread_string(&input, server_id, sizeof(server_id)) ||
 84+	    !bufread_bytes(&input, &public_key, &public_key_size) ||
 85+	    !bufread_bytes(&input, &verify_token, &verify_token_size)) {
 86+		mc_set_error(error, error_size, "malformed encryption request");
 87+		return 0;
 88+	}
 89+	if (!mg_random_bytes(shared_secret, sizeof(shared_secret))) {
 90+		mc_set_error(error, error_size, "could not obtain secure random bytes");
 91+		return 0;
 92+	}
 93+	if (!mc_join_server(account, server_id, shared_secret,
 94+	        public_key, public_key_size, error, error_size) ||
 95+	    !mc_rsa_encrypt(public_key, public_key_size,
 96+	        shared_secret, sizeof(shared_secret),
 97+	        &encrypted_secret, &encrypted_secret_size, error, error_size) ||
 98+	    !mc_rsa_encrypt(public_key, public_key_size,
 99+	        verify_token, verify_token_size,
100+	        &encrypted_token, &encrypted_token_size, error, error_size))
101+		goto cleanup;
102+
103+	bufinit(&response);
104+	if (bufwrite_varint(&response, (i32)encrypted_secret_size) &&
105+	    bufwrite(&response, encrypted_secret, encrypted_secret_size) &&
106+	    bufwrite_varint(&response, (i32)encrypted_token_size) &&
107+	    bufwrite(&response, encrypted_token, encrypted_token_size) &&
108+	    mc_client_write(client, 0x01, response.data, response.size)) {
109+		mc_cipher_init(&client->read_cipher, shared_secret);
110+		mc_cipher_init(&client->write_cipher, shared_secret);
111+		client->encrypted = 1;
112+		ok = 1;
113+	} else {
114+		mc_set_error(error, error_size, "could not send encryption response");
115+	}
116+	buffree(&response);
117+
118+cleanup:
119+	memset(shared_secret, 0, sizeof(shared_secret));
120+	free(encrypted_secret);
121+	free(encrypted_token);
122+	return ok;
123+}
124+
125+static int login_loop(
126+    MCClient* client,
127+    const MCAccount* account,
128+    char* error,
129+    size_t error_size)
130 {
131 	/*
132 	 * login state packet ids for protocol 47:
133@@ -354,10 +436,11 @@ static int login_loop(MCClient* client, char* error, size_t error_size)
134 			return 0;
135 		}
136 		if (packet.id == 0x01) {
137-			mc_set_error(error, error_size,
138-			    "server requested online-mode encryption, idfk know what to do");
139+			int ok = handle_encryption(client, account, &packet, error, error_size);
140 			mc_packet_free(&packet);
141-			return 0;
142+			if (!ok)
143+				return 0;
144+			continue;
145 		}
146 		if (packet.id == 0x02) {
147 			char username[64];
148@@ -431,7 +514,7 @@ int mc_client_login(
149 		return 0;
150 	}
151 	printf("sent handshake and login start\n");
152-	if (!login_loop(client, error, error_size)) {
153+	if (!login_loop(client, account, error, error_size)) {
154 		mc_client_destroy(client);
155 		return 0;
156 	}
+250, -0
  1@@ -0,0 +1,250 @@
  2+#include <stdlib.h>
  3+#include <string.h>
  4+
  5+#include "internal.h"
  6+#include "mg_random.h"
  7+
  8+typedef struct {
  9+	const u8* data;
 10+	size_t size;
 11+	size_t cursor;
 12+} DERReader;
 13+
 14+static int der_length(DERReader* reader, size_t* length)
 15+{
 16+	u8 first;
 17+	size_t value = 0;
 18+	unsigned count;
 19+	unsigned i;
 20+
 21+	if (reader->cursor >= reader->size)
 22+		return 0;
 23+	first = reader->data[reader->cursor++];
 24+	if (!(first & 0x80)) {
 25+		*length = first;
 26+		return *length <= reader->size - reader->cursor;
 27+	}
 28+	count = first & 0x7f;
 29+	if (count == 0 || count > sizeof(size_t) ||
 30+	    count > reader->size - reader->cursor)
 31+		return 0;
 32+	for (i = 0; i < count; ++i)
 33+		value = (value << 8) | reader->data[reader->cursor++];
 34+	if (value > reader->size - reader->cursor)
 35+		return 0;
 36+	*length = value;
 37+	return 1;
 38+}
 39+
 40+static int der_value(DERReader* reader, u8 tag, DERReader* value)
 41+{
 42+	size_t length;
 43+	if (reader->cursor >= reader->size || reader->data[reader->cursor++] != tag ||
 44+	    !der_length(reader, &length))
 45+		return 0;
 46+	value->data = reader->data + reader->cursor;
 47+	value->size = length;
 48+	value->cursor = 0;
 49+	reader->cursor += length;
 50+	return 1;
 51+}
 52+
 53+static int rsa_public_key(
 54+    const u8* der,
 55+    size_t der_size,
 56+    br_rsa_public_key* key,
 57+    char* error,
 58+    size_t error_size)
 59+{
 60+	DERReader root = { der, der_size, 0 };
 61+	DERReader spki;
 62+	DERReader algorithm;
 63+	DERReader bits;
 64+	DERReader rsa;
 65+	DERReader modulus;
 66+	DERReader exponent;
 67+
 68+	if (!der_value(&root, 0x30, &spki) ||
 69+	    !der_value(&spki, 0x30, &algorithm) ||
 70+	    !der_value(&spki, 0x03, &bits) ||
 71+	    bits.size < 1 || bits.data[0] != 0) {
 72+		mc_set_error(error, error_size, "invalid X.509 RSA public key");
 73+		return 0;
 74+	}
 75+	bits.cursor = 1;
 76+	if (!der_value(&bits, 0x30, &rsa) ||
 77+	    !der_value(&rsa, 0x02, &modulus) ||
 78+	    !der_value(&rsa, 0x02, &exponent)) {
 79+		mc_set_error(error, error_size, "invalid RSA public key integers");
 80+		return 0;
 81+	}
 82+	while (modulus.size > 1 && modulus.data[0] == 0) {
 83+		++modulus.data;
 84+		--modulus.size;
 85+	}
 86+	while (exponent.size > 1 && exponent.data[0] == 0) {
 87+		++exponent.data;
 88+		--exponent.size;
 89+	}
 90+	key->n = (unsigned char*)modulus.data;
 91+	key->nlen = modulus.size;
 92+	key->e = (unsigned char*)exponent.data;
 93+	key->elen = exponent.size;
 94+	return key->nlen >= 64 && key->elen > 0;
 95+}
 96+
 97+void mc_cipher_init(MCCipher* cipher, const u8 key[16])
 98+{
 99+	br_aes_ct_cbcenc_init(&cipher->key, key, 16);
100+	memcpy(cipher->state, key, 16);
101+}
102+
103+static u8 cipher_byte(MCCipher* cipher)
104+{
105+	u8 zero_iv[16] = { 0 };
106+	u8 block[16];
107+	memcpy(block, cipher->state, 16);
108+	br_aes_ct_cbcenc_run(&cipher->key, zero_iv, block, sizeof(block));
109+	return block[0];
110+}
111+
112+void mc_cipher_encrypt(MCCipher* cipher, u8* data, size_t size)
113+{
114+	size_t i;
115+	for (i = 0; i < size; ++i) {
116+		u8 encrypted = data[i] ^ cipher_byte(cipher);
117+		memmove(cipher->state, cipher->state + 1, 15);
118+		cipher->state[15] = encrypted;
119+		data[i] = encrypted;
120+	}
121+}
122+
123+void mc_cipher_decrypt(MCCipher* cipher, u8* data, size_t size)
124+{
125+	size_t i;
126+	for (i = 0; i < size; ++i) {
127+		u8 encrypted = data[i];
128+		data[i] ^= cipher_byte(cipher);
129+		memmove(cipher->state, cipher->state + 1, 15);
130+		cipher->state[15] = encrypted;
131+	}
132+}
133+
134+int mc_rsa_encrypt(
135+    const u8* public_key,
136+    size_t public_key_size,
137+    const u8* message,
138+    size_t message_size,
139+    u8** encrypted,
140+    size_t* encrypted_size,
141+    char* error,
142+    size_t error_size)
143+{
144+	br_rsa_public_key key;
145+	br_rsa_public rsa;
146+	u8* output;
147+	size_t padding_size;
148+	size_t i;
149+
150+	if (!rsa_public_key(public_key, public_key_size, &key, error, error_size))
151+		return 0;
152+	if (message_size + 11 > key.nlen) {
153+		mc_set_error(error, error_size, "message is too large for server RSA key");
154+		return 0;
155+	}
156+	output = (u8*)malloc(key.nlen);
157+	if (!output) {
158+		mc_set_error(error, error_size, "out of memory encrypting login response");
159+		return 0;
160+	}
161+	padding_size = key.nlen - message_size - 3;
162+	output[0] = 0;
163+	output[1] = 2;
164+	/* PKCS#1 v1.5 encryption padding requires every padding byte to be nonzero */
165+	if (!mg_random_bytes(output + 2, padding_size)) {
166+		free(output);
167+		mc_set_error(error, error_size, "could not obtain secure random bytes");
168+		return 0;
169+	}
170+	for (i = 0; i < padding_size; ++i) {
171+		while (output[2 + i] == 0) {
172+			if (!mg_random_bytes(output + 2 + i, 1)) {
173+				free(output);
174+				mc_set_error(error, error_size, "could not obtain secure random bytes");
175+				return 0;
176+			}
177+		}
178+	}
179+	output[2 + padding_size] = 0;
180+	memcpy(output + 3 + padding_size, message, message_size);
181+
182+	rsa = br_rsa_public_get_default();
183+	if (!rsa || !rsa(output, key.nlen, &key)) {
184+		free(output);
185+		mc_set_error(error, error_size, "BearSSL RSA encryption failed");
186+		return 0;
187+	}
188+	*encrypted = output;
189+	*encrypted_size = key.nlen;
190+	return 1;
191+}
192+
193+static void twos_complement(u8* data, size_t size)
194+{
195+	size_t i = size;
196+	unsigned carry = 1;
197+	while (i > 0) {
198+		unsigned value;
199+		--i;
200+		value = (unsigned)(data[i] ^ 0xff) + carry;
201+		data[i] = (u8)value;
202+		carry = value >> 8;
203+	}
204+}
205+
206+void mc_server_hash(
207+    const char* server_id,
208+    const u8 shared_secret[16],
209+    const u8* public_key,
210+    size_t public_key_size,
211+    char output[42])
212+{
213+	static const char hex[] = "0123456789abcdef";
214+	br_sha1_context sha;
215+	u8 digest[20];
216+	size_t first = 0;
217+	size_t i;
218+	size_t out = 0;
219+	int negative;
220+
221+	br_sha1_init(&sha);
222+	br_sha1_update(&sha, server_id, strlen(server_id));
223+	br_sha1_update(&sha, shared_secret, 16);
224+	br_sha1_update(&sha, public_key, public_key_size);
225+	br_sha1_out(&sha, digest);
226+	/* mc puts sha-1 digest as a signed twos-complement int */
227+	negative = (digest[0] & 0x80) != 0;
228+	if (negative)
229+		twos_complement(digest, sizeof(digest));
230+	while (first < sizeof(digest) && digest[first] == 0)
231+		++first;
232+	if (negative)
233+		output[out++] = '-';
234+	if (first == sizeof(digest)) {
235+		output[out++] = '0';
236+	} else {
237+		output[out++] = hex[digest[first] >> 4];
238+		output[out++] = hex[digest[first] & 15];
239+		for (i = first + 1; i < sizeof(digest); ++i) {
240+			output[out++] = hex[digest[i] >> 4];
241+			output[out++] = hex[digest[i] & 15];
242+		}
243+		if (output[negative ? 1 : 0] == '0') {
244+			memmove(output + (negative ? 1 : 0),
245+			    output + (negative ? 2 : 1),
246+			    out - (negative ? 2 : 1));
247+			--out;
248+		}
249+	}
250+	output[out] = '\0';
251+}
+37, -0
 1@@ -3,6 +3,8 @@
 2 
 3 #include <stddef.h>
 4 
 5+#include <bearssl.h>
 6+
 7 #include "mcproto/mcproto.h"
 8 
 9 #define MC_MAX_PACKET_SIZE (16u * 1024u * 1024u)
10@@ -16,6 +18,11 @@ typedef struct {
11 	size_t cursor;
12 } MCBuffer;
13 
14+typedef struct {
15+	br_aes_ct_cbcenc_keys key;
16+	u8 state[16];
17+} MCCipher;
18+
19 struct MCClient {
20 	int socket_fd;
21 	char host[256];
22@@ -23,6 +30,9 @@ struct MCClient {
23 	char username[17];
24 	char uuid[37];
25 	i32 compression_threshold;
26+	int encrypted;
27+	MCCipher read_cipher;
28+	MCCipher write_cipher;
29 };
30 
31 void mc_set_error(char* error, size_t error_size, const char* fmt, ...);
32@@ -51,4 +61,31 @@ int bufread_varint(MCBuffer* buffer, i32* value);
33 int bufread_string(MCBuffer* buffer, char* value, size_t value_size);
34 int bufread_bytes(MCBuffer* buffer, const u8** value, size_t* size);
35 
36+void mc_cipher_init(MCCipher* cipher, const u8 key[16]);
37+void mc_cipher_encrypt(MCCipher* cipher, u8* data, size_t size);
38+void mc_cipher_decrypt(MCCipher* cipher, u8* data, size_t size);
39+int mc_rsa_encrypt(
40+    const u8* public_key,
41+    size_t public_key_size,
42+    const u8* message,
43+    size_t message_size,
44+    u8** encrypted,
45+    size_t* encrypted_size,
46+    char* error,
47+    size_t error_size);
48+void mc_server_hash(
49+    const char* server_id,
50+    const u8 shared_secret[16],
51+    const u8* public_key,
52+    size_t public_key_size,
53+    char output[42]);
54+int mc_join_server(
55+    const MCAccount* account,
56+    const char* server_id,
57+    const u8 shared_secret[16],
58+    const u8* public_key,
59+    size_t public_key_size,
60+    char* error,
61+    size_t error_size);
62+
63 #endif /* MCPROTO_INTERNAL_H */
+36, -0
 1@@ -0,0 +1,36 @@
 2+#include "mg_random.h"
 3+
 4+#include <errno.h>
 5+#include <stddef.h>
 6+
 7+#if defined(__linux__)
 8+#include <sys/random.h>
 9+#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || \
10+    defined(__NetBSD__) || defined(__DragonFly__)
11+#include <stdlib.h>
12+#elif defined(_WIN32)
13+/* TODO: Windows shi*/
14+#else
15+#error "mg_random_bytes needs a platform CSPRNG implementation"
16+#endif
17+
18+int mg_random_bytes(u8* data, size_t size)
19+{
20+#if defined(__linux__)
21+	while (size > 0) {
22+		ssize_t count = getrandom(data, size, 0);
23+		if (count > 0) {
24+			data += (size_t)count;
25+			size -= (size_t)count;
26+			continue;
27+		}
28+		if (count < 0 && errno == EINTR)
29+			continue;
30+		return 0;
31+	}
32+	return 1;
33+#else
34+	arc4random_buf(data, size);
35+	return 1;
36+#endif
37+}