diff --git a/apps/amethyst/lib/apps/connection_handler.ex b/apps/amethyst/lib/apps/connection_handler.ex index b9d2267..2d8d07c 100644 --- a/apps/amethyst/lib/apps/connection_handler.ex +++ b/apps/amethyst/lib/apps/connection_handler.ex @@ -52,7 +52,7 @@ defmodule Amethyst.ConnectionHandler do Logger.info("Connection #{inspect(socket)} closed.") Process.exit(self(), :normal) {:disconnect, reason} -> - disconnect(socket, reason, connstate, version) + disconnect(socket, reason, connstate, version, state) Process.exit(self(), :normal) {:set_state, newstate} -> Logger.debug("Switching to state #{newstate} from #{connstate}") @@ -60,6 +60,12 @@ defmodule Amethyst.ConnectionHandler do {:set_version, newversion} -> Logger.debug("Switching to version #{newversion} from #{version}") loop(socket, connstate, newversion, state) + {:set_encryption, secret} -> + Logger.debug("Enabling encryption with shared secret #{inspect(secret)}") + encryption_state = :crypto.crypto_init(:aes_128_cfb8, secret, secret, true) + decryption_state = :crypto.crypto_init(:aes_128_cfb8, secret, secret, false) + state = state |> Map.put(:encryption_state, encryption_state) |> Map.put(:decryption_state, decryption_state) + loop(socket, connstate, version, state) {:set_position, position} -> prev_position = Map.get(state, :position) state = Map.put(state, :position, position) @@ -97,8 +103,8 @@ defmodule Amethyst.ConnectionHandler do end loop(socket, connstate, version, state) {:send_packet, packet} -> - # Logger.debug("Sending packet #{inspect(packet)}") - send_packet(socket, connstate, packet, version) + Logger.debug("Sending packet #{inspect(packet)}") + send_packet(socket, connstate, packet, version, state) loop(socket, connstate, version, state) after 0 -> receive do @@ -210,7 +216,13 @@ defmodule Amethyst.ConnectionHandler do defp handle_packet(id, data, connstate, version, state) do try do - packet = connstate.deserialize(id, version, data) + packet = case Map.get(state, :decryption_state) do + nil -> + connstate.deserialize(id, version, data) + dstate -> + data = :crypto.crypto_update(dstate, data) + connstate.deserialize(id, version, data) + end case connstate.handle(packet, version, state) do :ok -> state {:error, reason} -> @@ -237,11 +249,17 @@ defmodule Amethyst.ConnectionHandler do end end - defp send_packet(socket, connstate, packet, version) do + defp send_packet(socket, connstate, packet, version, state) do try do data = connstate.serialize(packet, version) length = byte_size(data) |> Amethyst.Minecraft.Write.varint() - :gen_tcp.send(socket, length <> data) + case Map.get(state, :encryption_state) do + nil -> + :gen_tcp.send(socket, length <> data) + estate -> + encrypted = :crypto.crypto_update(estate, length <> data) + :gen_tcp.send(socket, encrypted) + end rescue e -> Logger.error("Error sending packet #{inspect(packet)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}") @@ -249,12 +267,12 @@ defmodule Amethyst.ConnectionHandler do end end - defp disconnect(socket, reason, connstate, version) do + defp disconnect(socket, reason, connstate, version, state) do Logger.info("Disconnecting connection #{inspect(socket)}") Logger.debug("Disconnecting connection #{inspect(socket)}: #{reason}") case connstate.disconnect(reason) do nil -> nil - packet -> send_packet(socket, connstate, packet, version) + packet -> send_packet(socket, connstate, packet, version, state) end :gen_tcp.close(socket) end diff --git a/apps/amethyst/lib/states/login.ex b/apps/amethyst/lib/states/login.ex index e50030e..cf59efc 100644 --- a/apps/amethyst/lib/states/login.ex +++ b/apps/amethyst/lib/states/login.ex @@ -60,10 +60,19 @@ defmodule Amethyst.ConnectionState.Login do Macros.defpacket_serverbound :login_acknowledged, 0x03, 767, [] Macros.defpacket_serverbound :cookie_response, 0x04, 767, [identifier: :string, payload: {:optional, :byte_array}] - def handle(%{packet_type: :login_start, name: name, player_uuid: player_uuid}, 767, _state) do + def handle(%{packet_type: :login_start, name: name, player_uuid: player_uuid}, 767, state) do Logger.debug("Received login start for #{name} with UUID #{player_uuid}") - if Application.fetch_env!(:amethyst, :encryption) do - raise RuntimeError, "Encryption is not currently supported" + if Application.get_env(:amethyst, :encryption, true) do + verify_token = :crypto.strong_rand_bytes(4) + public_key = Amethyst.Keys.get_pub() + send(self(), {:send_packet, %{ + packet_type: :encryption_request, + server_id: "", + public_key: public_key, + verify_token: verify_token, + should_authenticate: Application.get_env(:amethyst, :auth, false) + }}) + state |> Map.put(:verify_token, verify_token) |> Map.put(:name, name) |> Map.put(:uuid, player_uuid) else send(self(), {:send_packet, %{ packet_type: :login_success, @@ -75,7 +84,23 @@ defmodule Amethyst.ConnectionState.Login do :ok end end - + def handle(%{packet_type: :encryption_response, shared_secret: secret, verify_token: verify_token}, 767, state) do + secret = Amethyst.Keys.decrypt(secret) + verify_token = Amethyst.Keys.decrypt(verify_token) + if verify_token == Map.get(state, :verify_token, :never) do + send(self(), {:set_encryption, secret}) + send(self(), {:send_packet, %{ + packet_type: :login_success, + uuid: Map.get(state, :uuid), + username: Map.get(state, :name), + properties: [], + strict_error_handling: true + }}) + :ok + else + raise RuntimeError, "Invalid verify token. Broken encryption?" + end + end def handle(%{packet_type: :login_acknowledged}, 767, _state) do Logger.debug("Received login acknowledged") send(self(), {:set_state, Amethyst.ConnectionState.Configuration}) diff --git a/config/runtime.exs b/config/runtime.exs index f36f7b8..c879757 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -2,6 +2,6 @@ import Config config :amethyst, port: 25599, # Bogus port for testing, avoids unexpected conflicts - encryption: false, # Whether or not to request encryption from clients. + encryption: true, # Whether or not to request encryption from clients. auth: false, # Whether or not users should be authenticated with Mojang. default_game: Example.Game # Which game new players should be sent to