Add decentralized cryptographic identity
This commit is contained in:
parent
0b5b78a553
commit
fa996a6735
27 changed files with 1733 additions and 58 deletions
144
network/network_identity_crypto.gd
Normal file
144
network/network_identity_crypto.gd
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue