Implement protocol encryption #2

Merged
kodi merged 6 commits from encryption into main 2024-10-05 12:07:04 +02:00
3 changed files with 56 additions and 13 deletions
Showing only changes of commit c26fc23a33 - Show all commits

View File

@ -52,7 +52,7 @@ defmodule Amethyst.ConnectionHandler do
Logger.info("Connection #{inspect(socket)} closed.") Logger.info("Connection #{inspect(socket)} closed.")
Process.exit(self(), :normal) Process.exit(self(), :normal)
{:disconnect, reason} -> {:disconnect, reason} ->
disconnect(socket, reason, connstate, version) disconnect(socket, reason, connstate, version, state)
Process.exit(self(), :normal) Process.exit(self(), :normal)
{:set_state, newstate} -> {:set_state, newstate} ->
Logger.debug("Switching to state #{newstate} from #{connstate}") Logger.debug("Switching to state #{newstate} from #{connstate}")
@ -60,6 +60,12 @@ defmodule Amethyst.ConnectionHandler do
{:set_version, newversion} -> {:set_version, newversion} ->
Logger.debug("Switching to version #{newversion} from #{version}") Logger.debug("Switching to version #{newversion} from #{version}")
loop(socket, connstate, newversion, state) 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} -> {:set_position, position} ->
prev_position = Map.get(state, :position) prev_position = Map.get(state, :position)
state = Map.put(state, :position, position) state = Map.put(state, :position, position)
@ -97,8 +103,8 @@ defmodule Amethyst.ConnectionHandler do
end end
loop(socket, connstate, version, state) loop(socket, connstate, version, state)
{:send_packet, packet} -> {:send_packet, packet} ->
# Logger.debug("Sending packet #{inspect(packet)}") Logger.debug("Sending packet #{inspect(packet)}")
send_packet(socket, connstate, packet, version) send_packet(socket, connstate, packet, version, state)
loop(socket, connstate, version, state) loop(socket, connstate, version, state)
after 0 -> after 0 ->
receive do receive do
@ -210,7 +216,13 @@ defmodule Amethyst.ConnectionHandler do
defp handle_packet(id, data, connstate, version, state) do defp handle_packet(id, data, connstate, version, state) do
try 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 case connstate.handle(packet, version, state) do
:ok -> state :ok -> state
{:error, reason} -> {:error, reason} ->
@ -237,11 +249,17 @@ defmodule Amethyst.ConnectionHandler do
end end
end end
defp send_packet(socket, connstate, packet, version) do defp send_packet(socket, connstate, packet, version, state) do
try do try do
data = connstate.serialize(packet, version) data = connstate.serialize(packet, version)
length = byte_size(data) |> Amethyst.Minecraft.Write.varint() 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 rescue
e -> e ->
Logger.error("Error sending packet #{inspect(packet)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}") 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
end end
defp disconnect(socket, reason, connstate, version) do defp disconnect(socket, reason, connstate, version, state) do
Logger.info("Disconnecting connection #{inspect(socket)}") Logger.info("Disconnecting connection #{inspect(socket)}")
Logger.debug("Disconnecting connection #{inspect(socket)}: #{reason}") Logger.debug("Disconnecting connection #{inspect(socket)}: #{reason}")
case connstate.disconnect(reason) do case connstate.disconnect(reason) do
nil -> nil nil -> nil
packet -> send_packet(socket, connstate, packet, version) packet -> send_packet(socket, connstate, packet, version, state)
end end
:gen_tcp.close(socket) :gen_tcp.close(socket)
end end

View File

@ -60,10 +60,19 @@ defmodule Amethyst.ConnectionState.Login do
Macros.defpacket_serverbound :login_acknowledged, 0x03, 767, [] Macros.defpacket_serverbound :login_acknowledged, 0x03, 767, []
Macros.defpacket_serverbound :cookie_response, 0x04, 767, [identifier: :string, payload: {:optional, :byte_array}] 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}") Logger.debug("Received login start for #{name} with UUID #{player_uuid}")
if Application.fetch_env!(:amethyst, :encryption) do if Application.get_env(:amethyst, :encryption, true) do
raise RuntimeError, "Encryption is not currently supported" 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 else
send(self(), {:send_packet, %{ send(self(), {:send_packet, %{
packet_type: :login_success, packet_type: :login_success,
@ -75,7 +84,23 @@ defmodule Amethyst.ConnectionState.Login do
:ok :ok
end end
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 def handle(%{packet_type: :login_acknowledged}, 767, _state) do
Logger.debug("Received login acknowledged") Logger.debug("Received login acknowledged")
send(self(), {:set_state, Amethyst.ConnectionState.Configuration}) send(self(), {:set_state, Amethyst.ConnectionState.Configuration})

View File

@ -2,6 +2,6 @@ import Config
config :amethyst, config :amethyst,
port: 25599, # Bogus port for testing, avoids unexpected conflicts 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. auth: false, # Whether or not users should be authenticated with Mojang.
default_game: Example.Game # Which game new players should be sent to default_game: Example.Game # Which game new players should be sent to