From 0f59aaef597ff3b17509d0892d6e1392eadfb761 Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Mon, 8 Jul 2024 22:45:57 +0200 Subject: [PATCH] Progress on implementing encryption (I think) --- config/runtime.exs | 3 +- lib/amethyst.ex | 1 + lib/encryption.ex | 65 ++++++++++++++++++++++++++++++++++++++++++++ lib/servers/login.ex | 13 +++++++-- test/data_test.exs | 1 + 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 lib/encryption.ex diff --git a/config/runtime.exs b/config/runtime.exs index 7ef1357..65c6018 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -2,4 +2,5 @@ import Config config :amethyst, port: 25599, # Bogus port for testing, avoids unexpected conflicts - encryption: false + encryption: true, # Whether or not to enable encryption, this should almost always be 'true' for security reasons + auth: true # Whether or not users should be authenticated. diff --git a/lib/amethyst.ex b/lib/amethyst.ex index b112a50..2066baf 100644 --- a/lib/amethyst.ex +++ b/lib/amethyst.ex @@ -26,6 +26,7 @@ defmodule Amethyst.Application do children = [ Supervisor.child_spec({Task, fn -> Amethyst.TCPListener.accept(Application.fetch_env!(:amethyst, :port)) end}, restart: :permanent), {Task.Supervisor, name: Amethyst.ConnectionSupervisor}, + {Amethyst.Keys, 1024}, ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/encryption.ex b/lib/encryption.ex new file mode 100644 index 0000000..89ab7b2 --- /dev/null +++ b/lib/encryption.ex @@ -0,0 +1,65 @@ +# Amethyst - An experimental Minecraft server written in Elixir. +# Copyright (C) 2024 KodiCraft +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +defmodule Amethyst.Keys do + @moduledoc """ + This module generates and manages the keys used for encryption. + + Minecraft uses RSA encryption, keys are stored in ASN.1 format and should be + generated at runtime. The vanilla server uses 1024-bit keys, but we can use + a larger key size for added security. + """ + + use GenServer + require Logger + + def start_link(bits) do + GenServer.start_link(__MODULE__, bits, name: __MODULE__) + end + + def get_priv do + GenServer.call(__MODULE__, :get_priv) + end + + def get_pub do + GenServer.call(__MODULE__, :get_pub) + end + + @impl true + def init(bits) do + Logger.info("Generating RSA keys with #{bits} bits") + # https://elixirforum.com/t/how-to-generate-rsa-public-key-using-crypto-provided-exponent-and-modulus/38487 + {:RSAPrivateKey, _, modulus, public_exponent, _, _, _, _exponent1, _, _, _other_prime_infos} = + rsa_private_key = :public_key.generate_key({:rsa, bits, 65_537}) + + rsa_public_key = {:RSAPublicKey, modulus, public_exponent} + privkey = :public_key.der_encode(:RSAPrivateKey, rsa_private_key) + pubkey = :public_key.der_encode(:RSAPublicKey, rsa_public_key) + + Logger.info("Generated RSA keys") + {:ok, {pubkey, privkey}} + end + + @impl true + def handle_call(:get_priv, _from, {pubkey, privkey}) do + {:reply, privkey, {pubkey, privkey}} + end + + @impl true + def handle_call(:get_pub, _from, {pubkey, privkey}) do + {:reply, pubkey, {pubkey, privkey}} + end +end diff --git a/lib/servers/login.ex b/lib/servers/login.ex index 0cff919..46a520e 100644 --- a/lib/servers/login.ex +++ b/lib/servers/login.ex @@ -71,8 +71,12 @@ defmodule Amethyst.Server.Login do Write.varint(0x00) <> Write.string(reason) end # Encryption Request https://wiki.vg/Protocol#Encryption_Request - def serialize({:encryption_request, id, pubkey, verify_token, auth}) do - Write.varint(0x01) <> Write.string(id) <> Write.varint(byte_size(pubkey)) <> pubkey <> Write.varint(byte_size(verify_token)) <> verify_token <> Write.bool(auth) + def serialize({:encryption_request, server_id, pubkey, verify_token, auth}) do + Write.varint(0x01) <> + Write.string(server_id) <> + Write.varint(byte_size(pubkey)) <> pubkey <> + Write.varint(byte_size(verify_token)) <> verify_token <> + Write.bool(auth) end # Login Success https://wiki.vg/Protocol#Login_Success def serialize({:login_success, uuid, username, props, strict}) do @@ -103,7 +107,10 @@ defmodule Amethyst.Server.Login do def handle({:login_start, name, uuid}, client) do Logger.info("Logging in #{name} (#{uuid})") if Application.fetch_env!(:amethyst, :encryption) do - raise RuntimeError, "Encryption is not currently supported!" + verify_token = :crypto.strong_rand_bytes(4) + pubkey = Amethyst.Keys.get_pub() + auth = Application.fetch_env!(:amethyst, :auth) + transmit({:encryption_request, "amethyst", pubkey, verify_token, auth}, client) else transmit({:login_success, uuid, name, [], false}, client) end diff --git a/test/data_test.exs b/test/data_test.exs index 9961aa7..7372491 100644 --- a/test/data_test.exs +++ b/test/data_test.exs @@ -12,6 +12,7 @@ defmodule WriteTest do assert Amethyst.Minecraft.Write.varint(127) == <<0x7F>> assert Amethyst.Minecraft.Write.varint(128) == <<0x80, 0x01>> assert Amethyst.Minecraft.Write.varint(255) == <<0xFF, 0x01>> + assert Amethyst.Minecraft.Write.varint(25_565) == <<0xDD, 0xC7, 0x01>> assert Amethyst.Minecraft.Write.varint(2_147_483_647) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x07>> assert Amethyst.Minecraft.Write.varint(-1) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x0F>> assert Amethyst.Minecraft.Write.varint(-2_147_483_648) == <<0x80, 0x80, 0x80, 0x80, 0x08>>