mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
feat: verify allocated join authorisations with hmac
This commit is contained in:
@@ -56,6 +56,7 @@ var require_join_authorisation := false
|
||||
var _allowed_join_authorisations: Dictionary = {}
|
||||
var _active_join_peers: Dictionary = {} # opaque authorisation -> peer_id
|
||||
var _join_authorisation_context: Dictionary = {}
|
||||
var _join_signing_key := PackedByteArray()
|
||||
|
||||
# Test hook (tests/match_net_smoke.gd): set false before connecting to
|
||||
# suppress the automatic real hello, so a test can send a deliberately
|
||||
@@ -92,10 +93,11 @@ func _on_shutting_down() -> void:
|
||||
_allowed_join_authorisations.clear()
|
||||
_active_join_peers.clear()
|
||||
_join_authorisation_context.clear()
|
||||
_join_signing_key = PackedByteArray()
|
||||
require_join_authorisation = false
|
||||
|
||||
|
||||
func configure_join_authorisations(tokens: Array, context: Dictionary) -> bool:
|
||||
func configure_join_authorisations(tokens: Array, context: Dictionary, signing_key: PackedByteArray = PackedByteArray()) -> bool:
|
||||
var allowed := {}
|
||||
for token in tokens:
|
||||
if not token is String or String(token).is_empty():
|
||||
@@ -105,6 +107,7 @@ func configure_join_authorisations(tokens: Array, context: Dictionary) -> bool:
|
||||
return false
|
||||
_allowed_join_authorisations = allowed
|
||||
_join_authorisation_context = context.duplicate(true)
|
||||
_join_signing_key = signing_key.duplicate()
|
||||
require_join_authorisation = true
|
||||
return true
|
||||
|
||||
@@ -233,6 +236,27 @@ func _valid_join_authorisation(token: String) -> bool:
|
||||
var protocol := str(claims.get("Protocol", ""))
|
||||
var expires_at := str(claims.get("ExpiresAt", ""))
|
||||
var expiry := Time.get_unix_time_from_datetime_string(expires_at)
|
||||
if not _join_signing_key.is_empty():
|
||||
var signature_token := str(envelope["Signature"])
|
||||
var signature := Marshalls.base64_to_raw(signature_token)
|
||||
if signature.size() != 32:
|
||||
return false
|
||||
var canonical := PackedByteArray()
|
||||
var fields := [
|
||||
str(claims.get("MatchID", "")), str(claims.get("ServerID", "")),
|
||||
str(claims.get("PlayerID", "")), str(claims.get("SteamID", "")),
|
||||
str(int(claims.get("Slot", -1))), str(int(claims.get("Team", -1))), protocol,
|
||||
str(int(claims.get("Generation", 0))), expires_at,
|
||||
]
|
||||
for index in fields.size():
|
||||
canonical.append_array(String(fields[index]).to_utf8_buffer())
|
||||
if index < fields.size() - 1:
|
||||
canonical.append(0)
|
||||
var hmac := HMACContext.new()
|
||||
hmac.start(HashingContext.HASH_SHA256, _join_signing_key)
|
||||
hmac.update(canonical)
|
||||
if hmac.finish() != signature:
|
||||
return false
|
||||
return str(claims.get("MatchID", "")) == str(_join_authorisation_context.get("match_id", "")) \
|
||||
and str(claims.get("ServerID", "")) == str(_join_authorisation_context.get("server_id", "")) \
|
||||
and protocol == str(_join_authorisation_context.get("protocol", "")) \
|
||||
|
||||
@@ -64,14 +64,16 @@ func _ready() -> void:
|
||||
return
|
||||
if allocated_mode:
|
||||
var roster_file := String(config.get_value("join-authorisations-file"))
|
||||
var key_file := String(config.get_value("join-authorisations-key-file"))
|
||||
var roster_json := FileAccess.get_file_as_string(roster_file)
|
||||
var signing_key := FileAccess.get_file_as_bytes(key_file)
|
||||
var roster_tokens = JSON.parse_string(roster_json)
|
||||
if not roster_tokens is Array or roster_tokens.is_empty() or not MatchNet.configure_join_authorisations(roster_tokens, {
|
||||
if not roster_tokens is Array or roster_tokens.is_empty() or signing_key.is_empty() or not MatchNet.configure_join_authorisations(roster_tokens, {
|
||||
"match_id": String(config.get_value("match-id")),
|
||||
"server_id": String(config.get_value("server-id")),
|
||||
"protocol": str(NetCodec.PROTOCOL_VERSION),
|
||||
"protocol_version": NetCodec.PROTOCOL_VERSION,
|
||||
}):
|
||||
}, signing_key):
|
||||
printerr("cosmic-clash-server: refusing to start with invalid join-authorisations-file")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
@@ -76,6 +76,7 @@ static func specs() -> Array[Spec]:
|
||||
out.append(Spec.new("transport", Kind.STRING, "", "allocation", "Assigned transport: steam_sdr or enet"))
|
||||
out.append(Spec.new("region", Kind.STRING, "", "allocation", "Assigned region: EU or NA"))
|
||||
out.append(Spec.new("join-authorisations-file", Kind.STRING, "", "allocation", "JSON array of control-plane signed join envelopes mounted for this match"))
|
||||
out.append(Spec.new("join-authorisations-key-file", Kind.STRING, "", "allocation", "HMAC-SHA256 key file for verifying mounted join envelopes"))
|
||||
return out
|
||||
|
||||
|
||||
@@ -269,6 +270,8 @@ func _validate() -> void:
|
||||
errors.append("--assignment-expiry-unix must be in the future")
|
||||
if String(values["join-authorisations-file"]).is_empty():
|
||||
errors.append("--join-authorisations-file is required in allocated mode")
|
||||
if String(values["join-authorisations-key-file"]).is_empty():
|
||||
errors.append("--join-authorisations-key-file is required in allocated mode")
|
||||
var digest := String(values["server-image-digest"])
|
||||
if not _is_sha256_digest(digest):
|
||||
errors.append("--server-image-digest must be sha256:<64 hex characters>")
|
||||
|
||||
Reference in New Issue
Block a user