Add decentralized cryptographic identity
This commit is contained in:
parent
0b5b78a553
commit
fa996a6735
27 changed files with 1733 additions and 58 deletions
127
network/server_trust_store.gd
Normal file
127
network/server_trust_store.gd
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
class_name ServerTrustStore
|
||||
extends Node
|
||||
|
||||
const FORMAT_VERSION: int = 1
|
||||
const STORE_PATH: String = "user://server_trust.json"
|
||||
const TEMP_PATH: String = STORE_PATH + ".tmp"
|
||||
|
||||
enum Verification {
|
||||
FIRST_SEEN,
|
||||
MATCH,
|
||||
CHANGED,
|
||||
}
|
||||
|
||||
var _records: Dictionary = {}
|
||||
var _loaded: bool = false
|
||||
var _write_blocked: bool = false
|
||||
|
||||
|
||||
func verify(endpoint: ConnectionEndpoint, fingerprint: String) -> Verification:
|
||||
_ensure_loaded()
|
||||
if endpoint == null or not endpoint.is_valid():
|
||||
return Verification.CHANGED
|
||||
var record: Dictionary = _records.get(endpoint.normalized_display, {})
|
||||
if record.is_empty():
|
||||
return Verification.FIRST_SEEN
|
||||
return (
|
||||
Verification.MATCH
|
||||
if record.get("fingerprint") == fingerprint
|
||||
else Verification.CHANGED
|
||||
)
|
||||
|
||||
|
||||
func get_record(endpoint: ConnectionEndpoint) -> Dictionary:
|
||||
_ensure_loaded()
|
||||
if endpoint == null:
|
||||
return {}
|
||||
return Dictionary(_records.get(endpoint.normalized_display, {})).duplicate(true)
|
||||
|
||||
|
||||
func trust(
|
||||
endpoint: ConnectionEndpoint,
|
||||
fingerprint: String,
|
||||
server_name: String = "",
|
||||
) -> bool:
|
||||
_ensure_loaded()
|
||||
if (
|
||||
endpoint == null
|
||||
or not endpoint.is_valid()
|
||||
or not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
||||
):
|
||||
return false
|
||||
var now := int(Time.get_unix_time_from_system())
|
||||
var previous: Dictionary = _records.get(endpoint.normalized_display, {})
|
||||
_records[endpoint.normalized_display] = {
|
||||
"route_kind": "direct",
|
||||
"normalized_host": endpoint.host,
|
||||
"port": endpoint.port,
|
||||
"normalized_endpoint": endpoint.normalized_display,
|
||||
"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:
|
||||
return
|
||||
var record: Dictionary = _records.get(endpoint.normalized_display, {})
|
||||
if record.is_empty():
|
||||
return
|
||||
record["last_seen_unix"] = int(Time.get_unix_time_from_system())
|
||||
_save()
|
||||
|
||||
|
||||
func _ensure_loaded() -> void:
|
||||
if _loaded:
|
||||
return
|
||||
_loaded = true
|
||||
if not FileAccess.file_exists(STORE_PATH):
|
||||
return
|
||||
var file := FileAccess.open(STORE_PATH, FileAccess.READ)
|
||||
if file == null:
|
||||
return
|
||||
var json := JSON.new()
|
||||
if json.parse(file.get_as_text()) != OK or typeof(json.data) != TYPE_DICTIONARY:
|
||||
return
|
||||
var data: Dictionary = json.data
|
||||
if data.get("format_version") != FORMAT_VERSION:
|
||||
_write_blocked = true
|
||||
return
|
||||
if typeof(data.get("records")) != TYPE_ARRAY:
|
||||
return
|
||||
for value: Variant in data["records"]:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var record: Dictionary = value
|
||||
var endpoint := str(record.get("normalized_endpoint", ""))
|
||||
var fingerprint := str(record.get("fingerprint", ""))
|
||||
if not endpoint.is_empty() and NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
_records[endpoint] = record.duplicate(true)
|
||||
|
||||
|
||||
func _save() -> bool:
|
||||
if _write_blocked:
|
||||
return false
|
||||
var file := FileAccess.open(TEMP_PATH, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_string(JSON.stringify({
|
||||
"format_version": FORMAT_VERSION,
|
||||
"records": _records.values(),
|
||||
}, "\t"))
|
||||
file.flush()
|
||||
var ok := file.get_error() == OK
|
||||
file.close()
|
||||
if not ok:
|
||||
return false
|
||||
if FileAccess.file_exists(STORE_PATH):
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(STORE_PATH))
|
||||
return DirAccess.rename_absolute(
|
||||
ProjectSettings.globalize_path(TEMP_PATH),
|
||||
ProjectSettings.globalize_path(STORE_PATH),
|
||||
) == OK
|
||||
Loading…
Add table
Add a link
Reference in a new issue