0.4.0: Mitspieler in der Welt, BSO-Konto (Spiel-Ticket)
- Mitspieler als Bus/Spielfigur mit Namensschild, weich bewegt (RemotePlayers, Models) - Ohne gueltiges Spiel-Ticket vom Launcher kein Multiplayer; Name kommt aus dem Konto - Protokoll 2 (Hello mit Ticket), IPv6-Adressen - Nach Szenenwechsel erst nach 3 s neu anlegen (Absturz im Test 0.3.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BSO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Spiel-Ticket der BSO-Website: base64url(JSON {u, id, iat, exp}) + "." + base64url(RSA-SHA256-Signatur). Der Launcher
|
||||
/// holt es beim Spielen und schreibt es in BSO.json, die Mod schickt es im Hello mit. Die Mod liest es nur (Name, Ablauf),
|
||||
/// pruefen kann es nur der Server mit dem oeffentlichen Schluessel der Website (Verify) - wer die Mod veraendert, kommt so
|
||||
/// trotzdem nicht ohne gueltiges Konto auf einen Server.
|
||||
/// </summary>
|
||||
public sealed class Ticket
|
||||
{
|
||||
public string Username { get; private set; }
|
||||
public string AccountId { get; private set; }
|
||||
public DateTime Expires { get; private set; }
|
||||
public bool Expired => DateTime.UtcNow >= Expires;
|
||||
|
||||
/// <summary>Inhalt lesen, ohne die Signatur zu pruefen. null = kein Ticket / kaputt.</summary>
|
||||
public static Ticket Read(string ticket)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(ticket) || ticket.Length > 4096) return null;
|
||||
string[] parts = ticket.Split('.');
|
||||
if (parts.Length != 2) return null;
|
||||
using var doc = JsonDocument.Parse(FromB64(parts[0]));
|
||||
var r = doc.RootElement;
|
||||
return new Ticket
|
||||
{
|
||||
Username = r.GetProperty("u").GetString(),
|
||||
AccountId = r.GetProperty("id").GetString(),
|
||||
Expires = DateTimeOffset.FromUnixTimeSeconds(r.GetProperty("exp").GetInt64()).UtcDateTime
|
||||
};
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/// <summary>Signatur pruefen (Server). Gueltig und nicht abgelaufen -> Ticket, sonst null mit Grund.</summary>
|
||||
public static Ticket Verify(string ticket, RSA publicKey, out string error)
|
||||
{
|
||||
error = null;
|
||||
Ticket t = Read(ticket);
|
||||
if (t == null) { error = "Kein gueltiges BSO-Konto - bitte ueber den BSO Launcher starten"; return null; }
|
||||
string[] parts = ticket.Split('.');
|
||||
bool ok;
|
||||
try { ok = publicKey.VerifyData(Encoding.ASCII.GetBytes(parts[0]), FromB64(parts[1]), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); }
|
||||
catch { ok = false; }
|
||||
if (!ok) { error = "Ungueltiges Spiel-Ticket - bitte ueber den BSO Launcher starten"; return null; }
|
||||
if (t.Expired) { error = "Spiel-Ticket abgelaufen - bitte im BSO Launcher neu auf SPIELEN druecken"; return null; }
|
||||
return t;
|
||||
}
|
||||
|
||||
static byte[] FromB64(string s)
|
||||
{
|
||||
s = s.Replace('-', '+').Replace('_', '/');
|
||||
return Convert.FromBase64String(s.PadRight(s.Length + (4 - s.Length % 4) % 4, '='));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user