Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ GlobalLogger="*res://scripts/Logger.gd"
FileManager="*res://scripts/Files.gd"
Util="*res://scripts/Util.gd"
CredentialStore="*res://scripts/credential_store.gd"
AccountServers="*res://scripts/account_servers.gd"

[display]

Expand Down
69 changes: 52 additions & 17 deletions src/scenes/managers/app/network_manager.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
extends Node

const n_c = preload("res://scripts/network_compression.gd")
var n_c = preload("res://scripts/network_compression.gd").new()
var jwt = preload("res://scripts/jwt.gd").new()
var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$")

# TODO: Bandwidth toggles
@onready var scene_manager = get_tree().current_scene.get_node("SceneManager")
Expand Down Expand Up @@ -156,41 +158,55 @@ func set_networking_config(options: Dictionary) -> void:
@rpc("authority", "reliable")
func _receive_server_info(server_info: Dictionary):
GlobalLogger.log_string("Received server information.")
print(server_info)
# TODO: Do not change scene until connection is finalized.

if server_info.level:
await scene_manager.load_multiplayer_scene(server_info.level, server_info.level_node_name)

_send_player_info({"display_name": "Client"})
_send_player_info(CredentialStore.info.token)

@rpc("any_peer", "reliable")
func _receive_player_info(player_info: Dictionary):
GlobalLogger.log_string("Received '%s' player info." % multiplayer.get_remote_sender_id())

func _receive_player_info(player_info: String):
# TODO: Error checks for JWT
if multiplayer.is_server() == false:
# We are a client. We should not process any farther.
return

GlobalLogger.log_string("Received '%s' player info." % multiplayer.get_remote_sender_id())

# TODO: Preform validation to determine if the player is allowed to be here
# TODO: Preform validation to determine if the player supplied cridentials are good, where they need to be.

player_info = _sanity_check_player_info(player_info, multiplayer.get_remote_sender_id())
# Preform validation of JWT token
var player_info_dic = _sanity_check_player_info(player_info, multiplayer.get_remote_sender_id())
var player_decoded_jwt = jwt.decode_jwt(player_info_dic.jwt)

info.clients.append(player_info)
# TODO: util function to break down a url to the key parts.
var url_parts = parse_url(player_decoded_jwt.payload.issuer)
var host_pub_key = await AccountServers._request_server_pem(url_parts.host, url_parts.port)

var jwt_is_valid = jwt.verify(player_info_dic.jwt, host_pub_key)

if jwt_is_valid == false:
# TODO: Refuse connection
multiplayer.multiplayer_peer.disconnect_peer(multiplayer.get_remote_sender_id())
return

info.clients.append(player_info_dic)

# Spawn player
multiplayer_manager.spawn_player(player_info.multiplayer_id)
multiplayer_manager.rpc("spawn_player", player_info.multiplayer_id)
multiplayer_manager.spawn_player(player_info_dic.multiplayer_id)
multiplayer_manager.rpc("spawn_player", player_info_dic.multiplayer_id)

# Spawn all connected clients on the new client
for client in info.clients:
if client.multiplayer_id == player_info.multiplayer_id:
if client.multiplayer_id == player_info_dic.multiplayer_id:
continue
multiplayer_manager.rpc_id(player_info.multiplayer_id, "spawn_player", client.multiplayer_id)
multiplayer_manager.rpc_id(player_info_dic.multiplayer_id, "spawn_player", client.multiplayer_id)

send_server_session_info()

func _send_player_info(player_info: Dictionary):
func _send_player_info(player_info: String):
GlobalLogger.log_string("Starting server handshake: Sending information about ourself.")
_receive_player_info.rpc_id(1, player_info)

Expand All @@ -210,13 +226,32 @@ func received_server_session_info(received_info: Dictionary) -> void:
# TODO: Check if item exists in player inventory
# TODO: Get player inventory

func _sanity_check_player_info(player_info: Dictionary, multiplayer_id: int) -> Dictionary:
func _sanity_check_player_info(player_info: String, multiplayer_id: int) -> Dictionary:
var sane_player_info = {
"display_name": "",
"multiplayer_id": ""
"jwt": "",
"multiplayer_id": "",
"display_name": "Greetings!"
}

sane_player_info.display_name = str(player_info.display_name)
sane_player_info.jwt = str(player_info)
sane_player_info.multiplayer_id = int(multiplayer_id)

return sane_player_info


func parse_url(url: String) -> Dictionary:
var result = {
"scheme": "",
"host": "",
"port": 0,
"path": ""
}

var matches = url_regex.search(url)
if matches:
result["scheme"] = matches.get_string(1).to_lower()
result["host"] = matches.get_string(2)
result["port"] = int(matches.get_string(3)) if matches.get_string(3) != "" else (443 if result["scheme"] == "https" else 80)
result["path"] = matches.get_string(4) if matches.get_string(4) != "" else "/"

return result
23 changes: 23 additions & 0 deletions src/scripts/account_servers.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
extends Node
var http = preload("res://scripts/http.gd").new()
var database = {}
# TODO: Open metadata file, and keep it opened

# TODO: Validate RSA PEM key
func get_pem(host: String, port: int) -> String:
# TODO: Check if we have the key saved
return ""

func _request_server_pem(host: String, port: int = 443) -> String:
var key = await http.req(HTTPClient.METHOD_GET, host, "/public_key", port)
if key.ok == true:
return key.body
return ""


func _ready():
_request_server_pem("http://localhost", 40400)

func _open_or_create_database():
var dir = DirAccess.open("user://")
dir.make_dir_recursive("user://account_servers/database.json")
1 change: 1 addition & 0 deletions src/scripts/account_servers.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c6heul4elcmbk
86 changes: 86 additions & 0 deletions src/scripts/jwt.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# This provides basic JWT features
extends Node

func verify(jwt_string: String = "", signature_pem: String = "") -> bool:
# TODO: Error checks
var crypto: Crypto = Crypto.new()
var public_key: CryptoKey = _signature_pem_to_cryptokey(signature_pem)
var jwt_parts: Dictionary = _get_jwt_parts(jwt_string)
var formatted_payload: Dictionary = _format_jwt_payload(jwt_parts.head, jwt_parts.payload)

return crypto.verify(
HashingContext.HASH_SHA256,
formatted_payload.payload_bytes,
Marshalls.base64_to_raw(jwt_parts.signature),
public_key
)

func decode_jwt(jwt_string: String) -> Dictionary:
# TODO: Error checks
var return_dict = {"head": {}, "payload": {}}

var jwt_parts = _get_jwt_parts(jwt_string)

jwt_parts.head = _base64url_to_base64(jwt_parts.head)
jwt_parts.head = Marshalls.base64_to_utf8(jwt_parts.head)
return_dict.head = JSON.parse_string(jwt_parts.head)

jwt_parts.payload = _base64url_to_base64(jwt_parts.payload)
jwt_parts.payload = Marshalls.base64_to_utf8(jwt_parts.payload)
return_dict.payload = JSON.parse_string(jwt_parts.payload)

return return_dict

func _base64url_to_base64(base64url: String):
# TODO: Error checks
var fixed: String = base64url

fixed = fixed.replace("_", "/").replace("-", "+")
var padding = 4 - (fixed.length() % 4)

if padding < 4:
fixed += "=".repeat(padding)

return fixed

func _signature_pem_to_cryptokey(signature_pem: String = "") -> CryptoKey:
# TODO: Error checks
var public_key := CryptoKey.new()
if public_key.load_from_string(signature_pem, true) != OK:
GlobalLogger.log_string("Failed to load signature", 3)
return null

return public_key

func _get_jwt_parts(jwt_string: String = "") -> Dictionary:
# TODO: Error checks
var return_dict = {"ok": false, "head": "", "payload": "", "signature": ""}

var jwt_split = jwt_string.split(".")

if len(jwt_split) != 3:
GlobalLogger.log_string("JWT token is not formatted correctly.", 2)
return return_dict

return_dict.head = jwt_split[0]
return_dict.payload = jwt_split[1]
return_dict.signature = _base64url_to_base64(jwt_split[2])
return_dict.ok = true

return return_dict

func _format_jwt_payload(head: String, payload: String) -> Dictionary:
# TODO: Error checks
var return_dict = {"ok": false, "payload_bytes": []}

var formatted_payload = head + "." + payload
var payload_bytes = formatted_payload.to_utf8_buffer()

var hasher: HashingContext = HashingContext.new()
hasher.start(HashingContext.HASH_SHA256)
hasher.update(payload_bytes)

return_dict.payload_bytes = hasher.finish()
return_dict.ok = true

return return_dict
1 change: 1 addition & 0 deletions src/scripts/jwt.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bt5gufaix02sa
3 changes: 1 addition & 2 deletions src/scripts/keys.gd
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ func _write_keys_to_disk(username):
func _generate_keys():
var crypto = Crypto.new()

# keys.private = crypto.generate_rsa(2048)
var generated_keys = crypto.generate_rsa(2048)

keys.private = generated_keys.save_to_string(false)
keys.public = generated_keys.save_to_string(true)
return
return