Skip to content

server.internal.encryption #

OpenSSL EVP interop for the Bedrock encryption handshake. vlib's crypto.ecdsa keeps its EVP_PKEY handle private, so we re-declare the C bindings we need (ECDH derive, SPKI emit, ES384 sign) in the same C-interop style. See vlib/crypto/ecdsa/ecdsa.c.v for the pattern this mirrors.

fn new_context #

fn new_context(key []u8) !&Context

new_context builds a Context from the 32-byte AES-256 key derived during the handshake. It returns an error on a wrong-sized key rather than panicking.

fn new_server_key_pair #

fn new_server_key_pair() !&ServerKeyPair

new_server_key_pair generates a fresh secp384r1 keypair via OpenSSL EVP.

fn prepare_handshake #

fn prepare_handshake(client_public_key_b64 string) !HandshakeResult

prepare_handshake runs the full server side of the Bedrock encryption handshake for one client: generate the server keypair, ECDH against the client public key, derive the AES key from a fresh random salt, and build a signed ServerToClientHandshake JWT. The keypair is freed before returning.

struct C.BIO #

@[typedef]
struct C.BIO {}

struct C.BIO_METHOD #

@[typedef]
struct C.BIO_METHOD {}

struct C.EVP_MD #

@[typedef]
struct C.EVP_MD {}

struct C.EVP_MD_CTX #

@[typedef]
struct C.EVP_MD_CTX {}

struct C.EVP_PKEY #

@[typedef]
struct C.EVP_PKEY {}

struct C.EVP_PKEY_CTX #

@[typedef]
struct C.EVP_PKEY_CTX {}

struct Context #

@[heap]
struct Context {
mut:
	key             []u8
	encrypt_stream  CtrStream
	decrypt_stream  CtrStream
	encrypt_counter u64
	decrypt_counter u64
}

Context is the per-session Bedrock cipher. MCPE uses "GCM without the auth tag", which is just AES-256-CTR with the GCM initial counter block as IV: the first 12 bytes of the key followed by 00 00 00 02. Each direction keeps its own persistent CTR keystream and a monotonic counter that feeds a per-packet SHA-256 checksum, matching PocketMine's EncryptionContext.

fn (Context) encrypt #

fn (mut c Context) encrypt(payload []u8) []u8

encrypt appends the per-packet checksum to the plaintext, then encrypts the combined buffer with the send-direction CTR keystream.

fn (Context) decrypt #

fn (mut c Context) decrypt(encrypted []u8) ![]u8

decrypt reverses encrypt: it decrypts with the receive-direction keystream, splits off the trailing checksum, and verifies it against the monotonic receive counter. A mismatch returns an error - the caller must disconnect.

struct HandshakeResult #

struct HandshakeResult {
pub:
	key           []u8
	handshake_jwt string
}

HandshakeResult carries the two outputs of the handshake preparation: the derived AES-256 key (kept server-side to build the Context) and the signed ServerToClientHandshake JWT sent to the client.

struct ServerKeyPair #

struct ServerKeyPair {
mut:
	pkey &C.EVP_PKEY = unsafe { nil }
}

ServerKeyPair holds the ephemeral P-384 ECDH/signing keypair the server generates once per session for the encryption handshake. It owns the raw EVP_PKEY handle and must be freed when the handshake is done.

fn (ServerKeyPair) free #

fn (mut k ServerKeyPair) free()

free releases the underlying EVP_PKEY. Safe to call once.

fn (ServerKeyPair) public_key_der #

fn (k &ServerKeyPair) public_key_der() ![]u8

public_key_der returns the server's SubjectPublicKeyInfo (SPKI) DER bytes, used as the base64 x5u header of the ServerToClientHandshake JWT.

fn (ServerKeyPair) derive_shared_secret #

fn (k &ServerKeyPair) derive_shared_secret(client_public_key_b64 string) ![]u8

derive_shared_secret runs ECDH against the client public key (a base64 SPKI DER string from the login chain) and returns the 48-byte raw shared secret.

fn (ServerKeyPair) sign_es384 #

fn (k &ServerKeyPair) sign_es384(message []u8) ![]u8

sign_es384 signs the message with the server private key using ECDSA-SHA384 and returns the JWS-style fixed-width raw R||S signature (96 bytes for P-384).