using System; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace BSO.Net { /// /// 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. /// 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; /// Inhalt lesen, ohne die Signatur zu pruefen. null = kein Ticket / kaputt. 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; } } /// Signatur pruefen (Server). Gueltig und nicht abgelaufen -> Ticket, sonst null mit Grund. 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, '=')); } } }