Compare commits
7 Commits
6ad910d4b4
...
eff1ff0a5e
Author | SHA1 | Date | |
---|---|---|---|
eff1ff0a5e | |||
93f8432bd1 | |||
7f3bb357db | |||
3bb8c5286a | |||
80060b1708 | |||
3f0e0ae22f | |||
c26fc23a33 |
@ -48,11 +48,8 @@ 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)
|
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 +57,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,11 +100,18 @@ 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 ->
|
||||||
|
# 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)
|
||||||
@ -237,11 +247,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 +265,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
|
||||||
|
@ -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)
|
receive(socket, pid, nil)
|
||||||
end)}
|
end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -44,42 +44,64 @@ 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)
|
receive(socket, pid, nil)
|
||||||
end)}
|
end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec receive(:gen_tcp.socket(), pid()) :: no_return()
|
@spec receive(:gen_tcp.socket(), pid(), nil | :crypto.crypto_state()) :: no_return()
|
||||||
def receive(socket, sender) do
|
def receive(socket, sender, cstate) do
|
||||||
case get_packet(socket) do
|
case get_packet(socket, cstate) 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
|
||||||
receive(socket, sender)
|
if cstate == nil do
|
||||||
|
# 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) do
|
def get_packet(client, cstate) do
|
||||||
case get_varint(client, "") do
|
case get_varint(client, "", cstate) 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} -> ({[id], data} = Read.start(full_packet) |> Read.varint() |> Read.stop()
|
{:ok, full_packet} ->
|
||||||
{id, data})
|
full_packet = case cstate do
|
||||||
|
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) do
|
defp get_varint(client, acc, cstate) do
|
||||||
case :gen_tcp.recv(client, 1) do
|
case :gen_tcp.recv(client, 1) do
|
||||||
{:ok, byte} -> case byte do
|
{:ok, byte} ->
|
||||||
<<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
|
byte = case cstate do
|
||||||
<<1::1, _::7>> -> get_varint(client, acc <> byte)
|
nil -> byte
|
||||||
end
|
ds -> :crypto.crypto_update(ds, byte)
|
||||||
|
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
|
||||||
|
@ -216,6 +216,11 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
@ -30,10 +30,6 @@ 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
|
||||||
@ -52,22 +48,19 @@ 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}}
|
{:ok, {rsa_public_key, rsa_private_key, bits}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call(:get_priv, _from, {pubkey, privkey}) do
|
def handle_call(:get_pub, _from, {pubkey, privkey, bits}) do
|
||||||
{:reply, :public_key.der_encode(:RSAPrivateKey, privkey), {pubkey, privkey}}
|
{:SubjectPublicKeyInfo, pk, :not_encrypted} = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, pubkey)
|
||||||
|
# Logger.debug("#{inspect(pem_encoded, limit: :infinity)}")
|
||||||
|
{:reply, pk, {pubkey, privkey, bits}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call(:get_pub, _from, {pubkey, privkey}) do
|
def handle_call({:decrypt, encrypted}, _from, {pubkey, privkey, bits}) 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}}
|
{:reply, plaintext, {pubkey, privkey, bits}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -60,10 +60,20 @@ 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()
|
||||||
|
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,
|
||||||
@ -75,7 +85,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})
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user