netfishing/network/network_identity_crypto.gd

145 lines
4.1 KiB
GDScript3
Raw Normal View History

class_name NetworkIdentityCrypto
extends RefCounted
const ALGORITHM: String = "RSA-3072-SHA256"
const FINGERPRINT_LENGTH: int = 64
const MAX_PUBLIC_KEY_BYTES: int = 8192
const MAX_SIGNATURE_BYTES: int = 1024
const DOMAIN_PREFIX: String = "NETFISHING"
const IDENTITY_VERSION: String = "identity_v1"
static func normalize_public_pem(value: String) -> String:
var normalized := value.replace("\r\n", "\n").replace("\r", "\n").strip_edges()
return normalized + "\n" if not normalized.is_empty() else ""
static func fingerprint_public_pem(value: String) -> String:
var normalized := normalize_public_pem(value)
return normalized.sha256_text() if not normalized.is_empty() else ""
static func valid_fingerprint(value: Variant) -> bool:
if typeof(value) != TYPE_STRING:
return false
var text := str(value)
if text.length() != FINGERPRINT_LENGTH:
return false
for character: String in text:
if character not in "0123456789abcdef":
return false
return true
static func format_fingerprint(value: String, groups: int = 5) -> String:
if not valid_fingerprint(value):
return "Unavailable"
var visible := value.left(clampi(groups, 1, 8) * 4).to_upper()
var parts := PackedStringArray()
for offset: int in range(0, visible.length(), 4):
parts.append(visible.substr(offset, 4))
return "-".join(parts)
static func compact_suffix(value: String) -> String:
return value.right(6).to_upper() if valid_fingerprint(value) else "??????"
static func secure_id(byte_count: int = 16) -> String:
return Crypto.new().generate_random_bytes(byte_count).hex_encode()
static func canonical_bytes(domain: String, fields: Array) -> PackedByteArray:
var output := PackedByteArray()
_append_string(output, DOMAIN_PREFIX)
_append_string(output, IDENTITY_VERSION)
_append_string(output, domain)
for value: Variant in fields:
match typeof(value):
TYPE_STRING, TYPE_STRING_NAME:
output.append(1)
_append_string(output, str(value))
TYPE_INT:
output.append(2)
_append_i64(output, int(value))
TYPE_BOOL:
output.append(3)
output.append(1 if bool(value) else 0)
TYPE_PACKED_BYTE_ARRAY:
output.append(4)
_append_bytes(output, value)
_:
return PackedByteArray()
return output
static func digest(domain: String, fields: Array) -> PackedByteArray:
var bytes := canonical_bytes(domain, fields)
if bytes.is_empty():
return PackedByteArray()
var context := HashingContext.new()
if context.start(HashingContext.HASH_SHA256) != OK:
return PackedByteArray()
context.update(bytes)
return context.finish()
static func sign_fields(
private_key: CryptoKey, domain: String, fields: Array
) -> PackedByteArray:
if private_key == null:
return PackedByteArray()
var value := digest(domain, fields)
return (
Crypto.new().sign(HashingContext.HASH_SHA256, value, private_key)
if not value.is_empty() else PackedByteArray()
)
static func verify_fields(
public_key: CryptoKey,
domain: String,
fields: Array,
signature: PackedByteArray,
) -> bool:
if (
public_key == null
or signature.is_empty()
or signature.size() > MAX_SIGNATURE_BYTES
):
return false
var value := digest(domain, fields)
return (
not value.is_empty()
and Crypto.new().verify(
HashingContext.HASH_SHA256, value, signature, public_key
)
)
static func load_public_key(public_pem: String) -> CryptoKey:
var normalized := normalize_public_pem(public_pem)
if normalized.is_empty() or normalized.to_utf8_buffer().size() > MAX_PUBLIC_KEY_BYTES:
return null
var key := CryptoKey.new()
return key if key.load_from_string(normalized, true) == OK else null
static func _append_string(output: PackedByteArray, value: String) -> void:
_append_bytes(output, value.to_utf8_buffer())
static func _append_bytes(output: PackedByteArray, value: PackedByteArray) -> void:
_append_u32(output, value.size())
output.append_array(value)
static func _append_u32(output: PackedByteArray, value: int) -> void:
for shift: int in [24, 16, 8, 0]:
output.append((value >> shift) & 0xff)
static func _append_i64(output: PackedByteArray, value: int) -> void:
for shift: int in [56, 48, 40, 32, 24, 16, 8, 0]:
output.append((value >> shift) & 0xff)