Compare commits

..

No commits in common. "eff1ff0a5e570fe098d7a745b2ce68dd70d1311c" and "6ad910d4b47a9a6bf1ec0846bef676e1134e5558" have entirely different histories.

6 changed files with 44 additions and 106 deletions

View File

@ -48,8 +48,11 @@ defmodule Amethyst.ConnectionHandler do
@spec loop(:gen_tcp.socket(), atom(), integer(), map()) :: no_return() @spec loop(:gen_tcp.socket(), atom(), integer(), map()) :: no_return()
defp loop(socket, connstate, version, state) do defp loop(socket, connstate, version, state) do
receive do receive do
:closed ->
Logger.info("Connection #{inspect(socket)} closed.")
Process.exit(self(), :normal)
{:disconnect, reason} -> {:disconnect, reason} ->
disconnect(socket, reason, connstate, version, state) disconnect(socket, reason, connstate, version)
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}")
@ -57,12 +60,6 @@ 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)
@ -100,18 +97,11 @@ 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, state) send_packet(socket, connstate, packet, version)
loop(socket, connstate, version, state) loop(socket, connstate, version, state)
after 0 -> after 0 ->
# Received stuff from the connection receiver is lower priority
receive do receive do
:closed ->
Logger.info("Connection #{inspect(socket)} closed.")
Process.exit(self(), :normal)
{:get_encryption, from} ->
send(from, Map.get(state, :decryption_state))
loop(socket, connstate, version, state)
{:packet, id, data} -> {:packet, id, data} ->
state = handle_packet(id, data, connstate, version, state) state = handle_packet(id, data, connstate, version, state)
loop(socket, connstate, version, state) loop(socket, connstate, version, state)
@ -247,17 +237,11 @@ defmodule Amethyst.ConnectionHandler do
end end
end end
defp send_packet(socket, connstate, packet, version, state) do defp send_packet(socket, connstate, packet, version) 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()
case Map.get(state, :encryption_state) do :gen_tcp.send(socket, length <> data)
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__)}")
@ -265,12 +249,12 @@ defmodule Amethyst.ConnectionHandler do
end end
end end
defp disconnect(socket, reason, connstate, version, state) do defp disconnect(socket, reason, connstate, version) 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, state) packet -> send_packet(socket, connstate, packet, version)
end end
:gen_tcp.close(socket) :gen_tcp.close(socket)
end end

View File

@ -35,7 +35,7 @@ defmodule Amethyst.ConnectionReceiver do
{:ok, spawn(fn -> {:ok, spawn(fn ->
Process.set_label("ConnectionReceiver for #{inspect(socket)}") Process.set_label("ConnectionReceiver for #{inspect(socket)}")
{:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0) {:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0)
receive(socket, pid, nil) receive(socket, pid)
end)} end)}
end end
@ -44,64 +44,42 @@ defmodule Amethyst.ConnectionReceiver do
{:ok, spawn_link(fn -> {:ok, spawn_link(fn ->
Process.set_label("ConnectionReceiver for #{inspect(socket)}") Process.set_label("ConnectionReceiver for #{inspect(socket)}")
{:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0) {:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0)
receive(socket, pid, nil) receive(socket, pid)
end)} end)}
end end
@spec receive(:gen_tcp.socket(), pid(), nil | :crypto.crypto_state()) :: no_return() @spec receive(:gen_tcp.socket(), pid()) :: no_return()
def receive(socket, sender, cstate) do def receive(socket, sender) do
case get_packet(socket, cstate) do case get_packet(socket) do
:closed -> send(sender, :closed) :closed -> send(sender, :closed)
Process.exit(self(), :normal) Process.exit(self(), :normal)
{:error, error} -> Logger.error("Error reading packet: #{error}") {:error, error} -> Logger.error("Error reading packet: #{error}")
{id, data} -> send(sender, {:packet, id, data}) {id, data} -> send(sender, {:packet, id, data})
end end
if cstate == nil do receive(socket, sender)
# Ask the handler if the encryption state has changed
send(sender, {:get_encryption, self()})
Logger.debug("Asking for news on encryption...")
receive do
nil -> receive(socket, sender, cstate)
some ->
Logger.debug("Enabling decryption!")
receive(socket, sender, some)
end
else
receive(socket, sender, cstate)
end
end end
def get_packet(client, cstate) do def get_packet(client) do
case get_varint(client, "", cstate) do case get_varint(client, "") do
:closed -> :closed :closed -> :closed
{:error, error} -> {:error, error} {:error, error} -> {:error, error}
{[length], ""} -> {[length], ""} ->
recv = :gen_tcp.recv(client, length) recv = :gen_tcp.recv(client, length)
case recv do case recv do
{:ok, full_packet} -> {:ok, full_packet} -> ({[id], data} = Read.start(full_packet) |> Read.varint() |> Read.stop()
full_packet = case cstate do {id, data})
nil -> full_packet
ds -> :crypto.crypto_update(ds, full_packet)
end
({[id], data} = Read.start(full_packet) |> Read.varint() |> Read.stop()
{id, data})
{:error, :closed} -> :closed {:error, :closed} -> :closed
{:error, error} -> {:error, error} {:error, error} -> {:error, error}
end end
end end
end end
defp get_varint(client, acc, cstate) do defp get_varint(client, acc) do
case :gen_tcp.recv(client, 1) do case :gen_tcp.recv(client, 1) do
{:ok, byte} -> {:ok, byte} -> case byte do
byte = case cstate do <<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
nil -> byte <<1::1, _::7>> -> get_varint(client, acc <> byte)
ds -> :crypto.crypto_update(ds, byte) end
end
case byte do
<<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
<<1::1, _::7>> -> get_varint(client, acc <> byte, cstate)
end
{:error, :closed} -> :closed {:error, :closed} -> :closed
{:error, error} -> {:error, error} {:error, error} -> {:error, error}
end end

View File

@ -216,11 +216,6 @@ defmodule Amethyst.Minecraft.Read do
{[data | acc], rest, :reversed} {[data | acc], rest, :reversed}
end end
def byte_array({acc, data, :reversed}) do
{[length], rest} = start(data) |> varint |> stop
raw({acc, rest, :reversed}, length)
end
@doc """ @doc """
Reads a varint. `read` tracks the number of bytes read and `nacc` tracks the number being read. Reads a varint. `read` tracks the number of bytes read and `nacc` tracks the number being read.
""" """

View File

@ -30,6 +30,10 @@ defmodule Amethyst.Keys do
GenServer.start_link(__MODULE__, bits, name: __MODULE__) GenServer.start_link(__MODULE__, bits, name: __MODULE__)
end end
def get_priv do
GenServer.call(__MODULE__, :get_priv)
end
def get_pub do def get_pub do
GenServer.call(__MODULE__, :get_pub) GenServer.call(__MODULE__, :get_pub)
end end
@ -48,19 +52,22 @@ defmodule Amethyst.Keys do
rsa_public_key = {:RSAPublicKey, modulus, public_exponent} rsa_public_key = {:RSAPublicKey, modulus, public_exponent}
Logger.info("Generated RSA keys") Logger.info("Generated RSA keys")
{:ok, {rsa_public_key, rsa_private_key, bits}} {:ok, {rsa_public_key, rsa_private_key}}
end end
@impl true @impl true
def handle_call(:get_pub, _from, {pubkey, privkey, bits}) do def handle_call(:get_priv, _from, {pubkey, privkey}) do
{:SubjectPublicKeyInfo, pk, :not_encrypted} = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, pubkey) {:reply, :public_key.der_encode(:RSAPrivateKey, privkey), {pubkey, privkey}}
# Logger.debug("#{inspect(pem_encoded, limit: :infinity)}")
{:reply, pk, {pubkey, privkey, bits}}
end end
@impl true @impl true
def handle_call({:decrypt, encrypted}, _from, {pubkey, privkey, bits}) do def handle_call(:get_pub, _from, {pubkey, privkey}) do
{:reply, :public_key.der_encode(:RSAPublicKey, pubkey), {pubkey, privkey}}
end
@impl true
def handle_call({:decrypt, encrypted}, _from, {pubkey, privkey}) do
plaintext = :public_key.decrypt_private(encrypted, privkey) plaintext = :public_key.decrypt_private(encrypted, privkey)
{:reply, plaintext, {pubkey, privkey, bits}} {:reply, plaintext, {pubkey, privkey}}
end end
end end

View File

@ -60,20 +60,10 @@ 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.get_env(:amethyst, :encryption, true) do if Application.fetch_env!(:amethyst, :encryption) do
verify_token = :crypto.strong_rand_bytes(4) raise RuntimeError, "Encryption is not currently supported"
public_key = Amethyst.Keys.get_pub()
Logger.debug("Public key: #{inspect(public_key, limit: :infinity)}")
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,
@ -85,23 +75,7 @@ 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: true, # Whether or not to request encryption from clients. encryption: false, # 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