feat(network): add selectable privacy-preserving discovery
This commit is contained in:
parent
9cf0c8fc9c
commit
4d31fd1517
26 changed files with 959 additions and 153 deletions
|
|
@ -3,22 +3,65 @@ extends RefCounted
|
|||
|
||||
enum Kind {
|
||||
DIRECT,
|
||||
DISCOVERY_DIRECT,
|
||||
DISCOVERY_RELAY,
|
||||
}
|
||||
|
||||
var kind: Kind = Kind.DIRECT
|
||||
var direct_endpoint: ConnectionEndpoint
|
||||
var display_description: String = ""
|
||||
var discovery_room_id: String = ""
|
||||
|
||||
|
||||
static func direct(endpoint: ConnectionEndpoint) -> ConnectionRoute:
|
||||
var route := ConnectionRoute.new()
|
||||
route.kind = Kind.DIRECT
|
||||
route.direct_endpoint = endpoint
|
||||
route.display_description = "direct server" if endpoint != null else ""
|
||||
return route
|
||||
|
||||
|
||||
static func discovery_direct(
|
||||
endpoint: ConnectionEndpoint,
|
||||
room_id: String,
|
||||
room_name: String,
|
||||
) -> ConnectionRoute:
|
||||
var route := ConnectionRoute.new()
|
||||
route.kind = Kind.DISCOVERY_DIRECT
|
||||
route.direct_endpoint = endpoint
|
||||
route.discovery_room_id = room_id
|
||||
var cleaned_name := room_name.strip_edges()
|
||||
route.display_description = (
|
||||
endpoint.normalized_display if endpoint != null else ""
|
||||
cleaned_name if not cleaned_name.is_empty() else "public room"
|
||||
)
|
||||
return route
|
||||
|
||||
|
||||
static func discovery_relay(
|
||||
endpoint: ConnectionEndpoint,
|
||||
room_id: String,
|
||||
room_name: String,
|
||||
) -> ConnectionRoute:
|
||||
var route := discovery_direct(endpoint, room_id, room_name)
|
||||
route.kind = Kind.DISCOVERY_RELAY
|
||||
return route
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return kind == Kind.DIRECT and direct_endpoint != null and direct_endpoint.is_valid()
|
||||
return (
|
||||
kind in [Kind.DIRECT, Kind.DISCOVERY_DIRECT, Kind.DISCOVERY_RELAY]
|
||||
and direct_endpoint != null
|
||||
and direct_endpoint.is_valid()
|
||||
and (
|
||||
kind == Kind.DIRECT
|
||||
or not discovery_room_id.is_empty()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func is_discovery_join() -> bool:
|
||||
return kind in [Kind.DISCOVERY_DIRECT, Kind.DISCOVERY_RELAY]
|
||||
|
||||
|
||||
func is_relay() -> bool:
|
||||
return kind == Kind.DISCOVERY_RELAY
|
||||
|
|
|
|||
|
|
@ -34,11 +34,12 @@ func connect_to_route(route: ConnectionRoute) -> Error:
|
|||
)
|
||||
if error != OK:
|
||||
transport_error.emit(
|
||||
"Unable to connect to %s." % endpoint.normalized_display
|
||||
"Unable to begin the connection to %s."
|
||||
% route.display_description
|
||||
)
|
||||
return error
|
||||
_peer = enet_peer
|
||||
_route_description = endpoint.normalized_display
|
||||
_route_description = route.display_description
|
||||
return OK
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ signal browse_status_changed(message: String, is_error: bool)
|
|||
signal host_settings_changed(room_name: String, discoverable: bool)
|
||||
signal host_status_changed(message: String, is_error: bool)
|
||||
signal host_state_changed(state: int)
|
||||
signal public_join_prepared(endpoint: String)
|
||||
signal public_join_prepared(route: ConnectionRoute)
|
||||
signal public_join_status_changed(message: String, is_error: bool)
|
||||
signal public_join_state_changed(state: int)
|
||||
signal friend_presence_updated(friends: Array[Dictionary])
|
||||
|
|
@ -28,6 +28,9 @@ const LEGACY_DEFAULT_ROOM_NAME: String = "straywild Room"
|
|||
const LEGACY_DEDICATED_DEFAULT_ROOM_NAME: String = (
|
||||
"straywild Dedicated Server"
|
||||
)
|
||||
const HOST_CONNECTION_DIRECT: String = "direct"
|
||||
const HOST_CONNECTION_RELAY: String = "relay"
|
||||
const HOST_DISCOVERY_PRIVATE: String = "private"
|
||||
const ROOM_NAME_SUFFIX: String = "'s Server"
|
||||
const MAX_ROOM_NAME_LENGTH: int = 48
|
||||
const HEARTBEAT_INTERVAL_SECONDS: float = 15.0
|
||||
|
|
@ -35,6 +38,7 @@ const REQUEST_TIMEOUT_SECONDS: float = 8.0
|
|||
const TRAVERSAL_POLL_INTERVAL_SECONDS: float = 1.0
|
||||
const JOIN_PROBE_INTERVAL_SECONDS: float = 0.35
|
||||
const TRAVERSAL_PACKET_PREFIX: String = "straywild_TRAVERSAL_V1 "
|
||||
const RELAY_AUTH_PACKET_PREFIX: String = "straywild_RELAY_AUTH_V1 "
|
||||
const UPNP_MAPPING_DURATION_SECONDS: int = 3600
|
||||
const UPNP_RENEW_INTERVAL_SECONDS: float = 2700.0
|
||||
const UPNP_RETRY_INTERVAL_SECONDS: float = 300.0
|
||||
|
|
@ -90,6 +94,7 @@ var _join_request: HTTPRequest
|
|||
var _traversal_timer: Timer
|
||||
var _join_probe_timer: Timer
|
||||
var _host_verified: bool = false
|
||||
var _host_connection_preference: String = HOST_CONNECTION_DIRECT
|
||||
var _traversal_host: String = ""
|
||||
var _traversal_port: int = 0
|
||||
var _host_verification_token: String = ""
|
||||
|
|
@ -97,6 +102,8 @@ var _join_request_in_flight: bool = false
|
|||
var _pending_join_endpoint: String = ""
|
||||
var _pending_join_token: String = ""
|
||||
var _pending_join_room_id: String = ""
|
||||
var _pending_join_room_name: String = ""
|
||||
var _pending_join_transport: String = ""
|
||||
var _preserve_pending_join_on_inactive: bool = false
|
||||
var _upnp_thread: Thread
|
||||
var _upnp_mapping_in_progress: bool = false
|
||||
|
|
@ -381,12 +388,25 @@ func get_host_state() -> HostState:
|
|||
return _host_state
|
||||
|
||||
|
||||
func get_host_connection_mode() -> String:
|
||||
return _host_connection_preference
|
||||
|
||||
|
||||
func get_host_discovery_mode() -> String:
|
||||
return (
|
||||
_host_connection_preference
|
||||
if _discoverable
|
||||
else HOST_DISCOVERY_PRIVATE
|
||||
)
|
||||
|
||||
|
||||
func get_public_join_state() -> PublicJoinState:
|
||||
return _public_join_state
|
||||
|
||||
|
||||
func configure_dedicated_runtime(room_name: String) -> bool:
|
||||
_host_settings_persistence_enabled = false
|
||||
_host_connection_preference = HOST_CONNECTION_DIRECT
|
||||
return set_room_name(room_name)
|
||||
|
||||
|
||||
|
|
@ -453,6 +473,49 @@ func set_discoverable(enabled: bool) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func set_host_connection_mode(connection_mode: String) -> bool:
|
||||
if connection_mode not in [
|
||||
HOST_CONNECTION_DIRECT,
|
||||
HOST_CONNECTION_RELAY,
|
||||
]:
|
||||
return false
|
||||
if (
|
||||
connection_mode == HOST_CONNECTION_RELAY
|
||||
and _session != null
|
||||
and _session.is_dedicated_host()
|
||||
):
|
||||
_set_host_state(HostState.ERROR)
|
||||
_set_host_status(
|
||||
"Dedicated servers must use direct discovery hosting.", true
|
||||
)
|
||||
return false
|
||||
if _host_connection_preference == connection_mode:
|
||||
return true
|
||||
_host_connection_preference = connection_mode
|
||||
_save_settings()
|
||||
host_settings_changed.emit(_room_name, _discoverable)
|
||||
if _discoverable:
|
||||
_set_host_state(HostState.REGISTERING)
|
||||
_set_host_status("Updating public connection mode…", false)
|
||||
_synchronize_host_lease()
|
||||
return true
|
||||
|
||||
|
||||
func cycle_host_discovery_mode() -> bool:
|
||||
match get_host_discovery_mode():
|
||||
HOST_DISCOVERY_PRIVATE:
|
||||
if not set_host_connection_mode(HOST_CONNECTION_DIRECT):
|
||||
return false
|
||||
return set_discoverable(true)
|
||||
HOST_CONNECTION_DIRECT:
|
||||
if _session != null and _session.is_dedicated_host():
|
||||
return set_discoverable(false)
|
||||
return set_host_connection_mode(HOST_CONNECTION_RELAY)
|
||||
HOST_CONNECTION_RELAY:
|
||||
return set_discoverable(false)
|
||||
return false
|
||||
|
||||
|
||||
func is_public_join_preparing() -> bool:
|
||||
return _join_request_in_flight
|
||||
|
||||
|
|
@ -480,13 +543,16 @@ func prepare_public_join(room: Dictionary) -> bool:
|
|||
"You are already hosting this room.", true
|
||||
)
|
||||
return false
|
||||
var endpoint: String = room_endpoint(room)
|
||||
var room_id: String = str(room.get("room_id", "")).strip_edges()
|
||||
if endpoint.is_empty() or room_id.is_empty():
|
||||
if room_id.is_empty():
|
||||
_set_public_join_state(PublicJoinState.ERROR)
|
||||
return false
|
||||
_pending_join_endpoint = endpoint
|
||||
_pending_join_endpoint = ""
|
||||
_pending_join_room_id = room_id
|
||||
_pending_join_room_name = str(
|
||||
room.get("room_name", "public room")
|
||||
).strip_edges()
|
||||
_pending_join_transport = str(room.get("connection_mode", ""))
|
||||
_pending_join_token = ""
|
||||
var url: String = "%s/v1/rooms/%s/join-attempts" % [
|
||||
_base_url,
|
||||
|
|
@ -535,16 +601,6 @@ func request_rooms() -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func room_endpoint(room: Dictionary) -> String:
|
||||
var address: String = str(room.get("address", "")).strip_edges()
|
||||
var port: int = int(room.get("port", 0))
|
||||
if address.is_empty() or port < 1 or port > 65535:
|
||||
return ""
|
||||
if ":" in address and not address.begins_with("["):
|
||||
address = "[%s]" % address
|
||||
return "%s:%d" % [address, port]
|
||||
|
||||
|
||||
func is_own_room(room: Dictionary) -> bool:
|
||||
return (
|
||||
not _lease_room_id.is_empty()
|
||||
|
|
@ -640,6 +696,14 @@ func _host_payload() -> Dictionary:
|
|||
ProjectSettings.get_setting("application/config/version", "unknown")
|
||||
),
|
||||
"protocol_version": NetworkProtocol.PROTOCOL_VERSION,
|
||||
"host_kind": (
|
||||
"dedicated" if _session.is_dedicated_host() else "player"
|
||||
),
|
||||
"connection_mode": (
|
||||
HOST_CONNECTION_DIRECT
|
||||
if _session.is_dedicated_host()
|
||||
else _host_connection_preference
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -688,7 +752,7 @@ func _on_host_request_completed(
|
|||
else HostState.VERIFYING
|
||||
)
|
||||
_set_host_status(
|
||||
"Room is listed publicly."
|
||||
_host_listing_status(room)
|
||||
if _host_verified
|
||||
else "Checking the public route…",
|
||||
false,
|
||||
|
|
@ -706,7 +770,7 @@ func _on_host_request_completed(
|
|||
else HostState.VERIFYING
|
||||
)
|
||||
_set_host_status(
|
||||
"Room is listed publicly."
|
||||
_host_listing_status(room)
|
||||
if _host_verified
|
||||
else "Checking the public route…",
|
||||
false,
|
||||
|
|
@ -755,10 +819,33 @@ func _on_join_request_completed(
|
|||
return
|
||||
_pending_join_token = str(response.get("join_token", ""))
|
||||
_apply_traversal_response(response)
|
||||
var route_transport := ""
|
||||
var route_privacy := ""
|
||||
var route_details: Variant = response.get("route", {})
|
||||
if typeof(route_details) == TYPE_DICTIONARY:
|
||||
var direct_route: Dictionary = route_details
|
||||
route_transport = str(direct_route.get("transport", ""))
|
||||
route_privacy = str(direct_route.get("ip_privacy", ""))
|
||||
if route_transport in ["direct", "relay"]:
|
||||
var address := str(direct_route.get("address", "")).strip_edges()
|
||||
var port := int(direct_route.get("port", 0))
|
||||
if not address.is_empty() and port >= 1 and port <= 65535:
|
||||
if ":" in address and not address.begins_with("["):
|
||||
address = "[%s]" % address
|
||||
_pending_join_endpoint = "%s:%d" % [address, port]
|
||||
if (
|
||||
_pending_join_token.is_empty()
|
||||
or _traversal_host.is_empty()
|
||||
or _traversal_port < 1
|
||||
or _pending_join_endpoint.is_empty()
|
||||
or route_transport != _pending_join_transport
|
||||
or (
|
||||
route_transport == "direct"
|
||||
and route_privacy != "peer_visible"
|
||||
)
|
||||
or (route_transport == "relay" and route_privacy != "relayed")
|
||||
or (
|
||||
route_transport == "direct"
|
||||
and (_traversal_host.is_empty() or _traversal_port < 1)
|
||||
)
|
||||
):
|
||||
_set_public_join_state(PublicJoinState.ERROR)
|
||||
public_join_status_changed.emit(
|
||||
|
|
@ -768,7 +855,25 @@ func _on_join_request_completed(
|
|||
return
|
||||
_set_public_join_state(PublicJoinState.CONNECTING)
|
||||
public_join_status_changed.emit("Connecting…", false)
|
||||
public_join_prepared.emit(_pending_join_endpoint)
|
||||
var endpoint := EndpointParser.parse(_pending_join_endpoint)
|
||||
if not endpoint.is_valid():
|
||||
_set_public_join_state(PublicJoinState.ERROR)
|
||||
public_join_status_changed.emit("Could not reach this room.", true)
|
||||
_clear_pending_join()
|
||||
return
|
||||
public_join_prepared.emit(
|
||||
ConnectionRoute.discovery_relay(
|
||||
endpoint,
|
||||
_pending_join_room_id,
|
||||
_pending_join_room_name,
|
||||
)
|
||||
if route_transport == "relay"
|
||||
else ConnectionRoute.discovery_direct(
|
||||
endpoint,
|
||||
_pending_join_room_id,
|
||||
_pending_join_room_name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _on_traversal_timer_timeout() -> void:
|
||||
|
|
@ -856,6 +961,15 @@ func _send_pending_join_probe() -> void:
|
|||
):
|
||||
_join_probe_timer.stop()
|
||||
return
|
||||
if _pending_join_transport == "relay":
|
||||
var relay_endpoint := EndpointParser.parse(_pending_join_endpoint)
|
||||
if relay_endpoint.is_valid():
|
||||
_session.send_traversal_packet(
|
||||
relay_endpoint.host,
|
||||
relay_endpoint.port,
|
||||
(RELAY_AUTH_PACKET_PREFIX + _pending_join_token).to_utf8_buffer(),
|
||||
)
|
||||
return
|
||||
_send_traversal_probe({
|
||||
"kind": "join",
|
||||
"room_id": _pending_join_room_id,
|
||||
|
|
@ -1013,10 +1127,24 @@ func _valid_public_room(room: Dictionary) -> bool:
|
|||
return (
|
||||
typeof(room.get("room_id")) == TYPE_STRING
|
||||
and typeof(room.get("room_name")) == TYPE_STRING
|
||||
and typeof(room.get("address")) == TYPE_STRING
|
||||
and typeof(room.get("game_version")) == TYPE_STRING
|
||||
and str(room.get("game_version")) == expected_version
|
||||
and _valid_json_integer(room.get("port"), 1, 65535)
|
||||
and str(room.get("connection_mode", "")) in ["direct", "relay"]
|
||||
and (
|
||||
(
|
||||
str(room.get("connection_mode", "")) == "direct"
|
||||
and str(room.get("ip_privacy", "")) == "peer_visible"
|
||||
)
|
||||
or (
|
||||
str(room.get("connection_mode", "")) == "relay"
|
||||
and str(room.get("ip_privacy", "")) == "relayed"
|
||||
)
|
||||
)
|
||||
and str(room.get("host_kind", "")) in ["player", "dedicated"]
|
||||
and not (
|
||||
str(room.get("host_kind", "")) == "dedicated"
|
||||
and str(room.get("connection_mode", "")) == "relay"
|
||||
)
|
||||
and _valid_json_integer(room.get("current_players"), 0, 128)
|
||||
and _valid_json_integer(room.get("max_players"), 1, 128)
|
||||
and int(room["current_players"]) <= int(room["max_players"])
|
||||
|
|
@ -1370,6 +1498,18 @@ func _request_failure(response: Dictionary) -> String:
|
|||
return "Room discovery is temporarily unavailable."
|
||||
|
||||
|
||||
func _host_listing_status(room: Dictionary) -> String:
|
||||
if str(room.get("connection_mode", "")) == "relay":
|
||||
return (
|
||||
"Room is listed through the privacy relay. Players see a relay "
|
||||
+ "address; the relay service still handles network addresses."
|
||||
)
|
||||
return (
|
||||
"Room is listed. Its address is hidden while browsing, but direct "
|
||||
+ "players can see your public IP after joining."
|
||||
)
|
||||
|
||||
|
||||
func _discovery_version_mismatch_message(required_version: String) -> String:
|
||||
var local_version: String = NetworkProtocol.game_version()
|
||||
var rejection: NetworkProtocol.RejectionCode = (
|
||||
|
|
@ -1493,6 +1633,8 @@ func _clear_pending_join() -> void:
|
|||
_pending_join_endpoint = ""
|
||||
_pending_join_token = ""
|
||||
_pending_join_room_id = ""
|
||||
_pending_join_room_name = ""
|
||||
_pending_join_transport = ""
|
||||
_join_probe_timer.stop()
|
||||
|
||||
|
||||
|
|
@ -1571,6 +1713,14 @@ func _load_settings() -> void:
|
|||
_presence_sharing = bool(config.get_value(
|
||||
"social", "share_presence", false
|
||||
))
|
||||
var saved_connection_mode := str(config.get_value(
|
||||
"host", "connection_mode", HOST_CONNECTION_DIRECT
|
||||
))
|
||||
if saved_connection_mode in [
|
||||
HOST_CONNECTION_DIRECT,
|
||||
HOST_CONNECTION_RELAY,
|
||||
]:
|
||||
_host_connection_preference = saved_connection_mode
|
||||
|
||||
|
||||
func _save_settings() -> void:
|
||||
|
|
@ -1581,6 +1731,9 @@ func _save_settings() -> void:
|
|||
config.set_value(
|
||||
"host", "room_name_uses_default", _room_name_uses_default
|
||||
)
|
||||
config.set_value(
|
||||
"host", "connection_mode", _host_connection_preference
|
||||
)
|
||||
config.set_value("social", "share_presence", _presence_sharing)
|
||||
var error: Error = config.save(SETTINGS_PATH)
|
||||
if error != OK:
|
||||
|
|
|
|||
|
|
@ -485,17 +485,30 @@ func join_direct(endpoint_text: String) -> bool:
|
|||
if not endpoint.is_valid():
|
||||
_fail(endpoint.error_message)
|
||||
return false
|
||||
return join_route(ConnectionRoute.direct(endpoint))
|
||||
|
||||
|
||||
func join_route(route: ConnectionRoute) -> bool:
|
||||
if (
|
||||
state != State.INACTIVE
|
||||
or not _profile_ready
|
||||
or _profile == null
|
||||
or _spawn_service == null
|
||||
or route == null
|
||||
or not route.is_valid()
|
||||
):
|
||||
return false
|
||||
_operation_generation += 1
|
||||
var generation: int = _operation_generation
|
||||
_current_route = ConnectionRoute.direct(endpoint)
|
||||
_current_route = route
|
||||
_set_state(
|
||||
State.CONNECTING,
|
||||
"Connecting to %s..." % endpoint.normalized_display
|
||||
"Connecting to %s..." % route.display_description
|
||||
)
|
||||
_replace_transport()
|
||||
var error: Error = _transport.connect_to_route(_current_route)
|
||||
if error != OK:
|
||||
_fail("Could not begin the direct connection.")
|
||||
_fail("Could not begin the connection.")
|
||||
return false
|
||||
multiplayer.multiplayer_peer = _transport.get_multiplayer_peer()
|
||||
_connection_deadline = (
|
||||
|
|
@ -597,11 +610,23 @@ func get_current_endpoint() -> ConnectionEndpoint:
|
|||
return (
|
||||
_current_route.direct_endpoint
|
||||
if _current_route != null
|
||||
and _current_route.kind == ConnectionRoute.Kind.DIRECT
|
||||
and _current_route.kind in [
|
||||
ConnectionRoute.Kind.DIRECT,
|
||||
ConnectionRoute.Kind.DISCOVERY_DIRECT,
|
||||
ConnectionRoute.Kind.DISCOVERY_RELAY,
|
||||
]
|
||||
else null
|
||||
)
|
||||
|
||||
|
||||
func is_current_route_discovery() -> bool:
|
||||
return _current_route != null and _current_route.is_discovery_join()
|
||||
|
||||
|
||||
func is_current_route_relay() -> bool:
|
||||
return _current_route != null and _current_route.is_relay()
|
||||
|
||||
|
||||
func get_last_server_metadata() -> Dictionary:
|
||||
return {
|
||||
"server_display_name": _last_server_display_name,
|
||||
|
|
@ -1226,11 +1251,16 @@ func receive_server_identity_proof(data: Dictionary) -> void:
|
|||
_pending_server_proof = data.duplicate(true)
|
||||
_server_identity_fingerprint = fingerprint
|
||||
_server_identity_public_key = public_pem
|
||||
var verification := _server_trust.verify(
|
||||
get_current_endpoint(), fingerprint
|
||||
var verification := (
|
||||
_server_trust.verify_identity(fingerprint)
|
||||
if is_current_route_discovery()
|
||||
else _server_trust.verify(get_current_endpoint(), fingerprint)
|
||||
)
|
||||
if verification == ServerTrustStore.Verification.MATCH:
|
||||
_server_trust.touch(get_current_endpoint())
|
||||
if is_current_route_discovery():
|
||||
_server_trust.touch_identity(fingerprint)
|
||||
else:
|
||||
_server_trust.touch(get_current_endpoint())
|
||||
_send_client_identity_proof()
|
||||
return
|
||||
_set_state(
|
||||
|
|
@ -1238,9 +1268,12 @@ func receive_server_identity_proof(data: Dictionary) -> void:
|
|||
"Confirm this server identity before continuing.",
|
||||
)
|
||||
_connection_deadline = 0.0
|
||||
var expected := str(
|
||||
_server_trust.get_record(get_current_endpoint()).get("fingerprint", "")
|
||||
var trust_record: Dictionary = (
|
||||
_server_trust.get_identity_record(fingerprint)
|
||||
if is_current_route_discovery()
|
||||
else _server_trust.get_record(get_current_endpoint())
|
||||
)
|
||||
var expected := str(trust_record.get("fingerprint", ""))
|
||||
server_trust_required.emit(
|
||||
get_current_route_display(),
|
||||
expected,
|
||||
|
|
@ -1255,11 +1288,15 @@ func resolve_server_trust(accepted: bool) -> void:
|
|||
if not accepted:
|
||||
cancel_connection()
|
||||
return
|
||||
if not _server_trust.trust(
|
||||
get_current_endpoint(),
|
||||
str(_pending_server_proof["server_fingerprint"]),
|
||||
"straywild",
|
||||
):
|
||||
var fingerprint := str(_pending_server_proof["server_fingerprint"])
|
||||
var trusted := (
|
||||
_server_trust.trust_identity(fingerprint, get_current_route_display())
|
||||
if is_current_route_discovery()
|
||||
else _server_trust.trust(
|
||||
get_current_endpoint(), fingerprint, "straywild"
|
||||
)
|
||||
)
|
||||
if not trusted:
|
||||
_fail_identity("The server identity could not be pinned.")
|
||||
return
|
||||
_set_state(State.AUTHENTICATING, "Authenticating identity...")
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ func record_successful_connection(
|
|||
recent = _make_entry(endpoint, SavedServerEntry.Kind.RECENT)
|
||||
_recent_entries.append(recent)
|
||||
recent["display_name"] = (
|
||||
server_name if not server_name.is_empty() else endpoint.normalized_display
|
||||
server_name if not server_name.is_empty() else "Recent Server"
|
||||
)
|
||||
_apply_result_metadata(
|
||||
recent, now, "SUCCESS", server_name, protocol_version,
|
||||
|
|
@ -358,7 +358,11 @@ func _make_entry(
|
|||
var now: int = int(Time.get_unix_time_from_system())
|
||||
return {
|
||||
"entry_id": Crypto.new().generate_random_bytes(16).hex_encode(),
|
||||
"display_name": endpoint.host,
|
||||
"display_name": (
|
||||
"Saved Server"
|
||||
if kind == SavedServerEntry.Kind.SAVED
|
||||
else "Recent Server"
|
||||
),
|
||||
"route_kind": "DIRECT",
|
||||
"host": endpoint.host,
|
||||
"normalized_host": endpoint.host,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ func verify(endpoint: ConnectionEndpoint, fingerprint: String) -> Verification:
|
|||
)
|
||||
|
||||
|
||||
func verify_identity(fingerprint: String) -> Verification:
|
||||
_ensure_loaded()
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
return Verification.CHANGED
|
||||
return (
|
||||
Verification.MATCH
|
||||
if _records.has(_identity_key(fingerprint))
|
||||
else Verification.FIRST_SEEN
|
||||
)
|
||||
|
||||
|
||||
func get_record(endpoint: ConnectionEndpoint) -> Dictionary:
|
||||
_ensure_loaded()
|
||||
if endpoint == null:
|
||||
|
|
@ -46,6 +57,11 @@ func get_record(endpoint: ConnectionEndpoint) -> Dictionary:
|
|||
return Dictionary(_records.get(endpoint.normalized_display, {})).duplicate(true)
|
||||
|
||||
|
||||
func get_identity_record(fingerprint: String) -> Dictionary:
|
||||
_ensure_loaded()
|
||||
return Dictionary(_records.get(_identity_key(fingerprint), {})).duplicate(true)
|
||||
|
||||
|
||||
func trust(
|
||||
endpoint: ConnectionEndpoint,
|
||||
fingerprint: String,
|
||||
|
|
@ -74,6 +90,25 @@ func trust(
|
|||
return _save()
|
||||
|
||||
|
||||
func trust_identity(fingerprint: String, server_name: String = "") -> bool:
|
||||
_ensure_loaded()
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
return false
|
||||
var key := _identity_key(fingerprint)
|
||||
var now := int(Time.get_unix_time_from_system())
|
||||
var previous: Dictionary = _records.get(key, {})
|
||||
_records[key] = {
|
||||
"route_kind": "discovery",
|
||||
"identity_key": key,
|
||||
"fingerprint": fingerprint,
|
||||
"first_seen_unix": int(previous.get("first_seen_unix", now)),
|
||||
"last_seen_unix": now,
|
||||
"last_observed_server_name": server_name.left(80),
|
||||
"trust_state": "pinned",
|
||||
}
|
||||
return _save()
|
||||
|
||||
|
||||
func touch(endpoint: ConnectionEndpoint) -> void:
|
||||
_ensure_loaded()
|
||||
if endpoint == null:
|
||||
|
|
@ -85,6 +120,15 @@ func touch(endpoint: ConnectionEndpoint) -> void:
|
|||
_save()
|
||||
|
||||
|
||||
func touch_identity(fingerprint: String) -> void:
|
||||
_ensure_loaded()
|
||||
var record: Dictionary = _records.get(_identity_key(fingerprint), {})
|
||||
if record.is_empty():
|
||||
return
|
||||
record["last_seen_unix"] = int(Time.get_unix_time_from_system())
|
||||
_save()
|
||||
|
||||
|
||||
func _ensure_loaded() -> void:
|
||||
if _loaded:
|
||||
return
|
||||
|
|
@ -108,12 +152,21 @@ func _ensure_loaded() -> void:
|
|||
continue
|
||||
var record: Dictionary = value
|
||||
var endpoint := str(record.get("normalized_endpoint", ""))
|
||||
var identity_key := str(record.get("identity_key", ""))
|
||||
var fingerprint := str(record.get("fingerprint", ""))
|
||||
if not endpoint.is_empty() and NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
continue
|
||||
if not endpoint.is_empty():
|
||||
_records[endpoint] = record.duplicate(true)
|
||||
elif identity_key == _identity_key(fingerprint):
|
||||
_records[identity_key] = record.duplicate(true)
|
||||
_expected_hash = PortableFileGuard.hash_file(_store_path)
|
||||
|
||||
|
||||
static func _identity_key(fingerprint: String) -> String:
|
||||
return "identity:%s" % fingerprint
|
||||
|
||||
|
||||
func _save() -> bool:
|
||||
if _write_blocked:
|
||||
return false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue