using System.Security.Cryptography; using System.Text; using System.Text.Json; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Security; using TheCr3w.Shared.Models; namespace TheCr3w.Shared.Crypto; /// /// Serwis kryptograficzny odpowiedzialny za generowanie par kluczy, podpisywanie /// oraz weryfikację podpisów cyfrowych Ed25519 (RFC 8032) dla metadanych wydań i manifestów wdrożeniowych. /// public static class Ed25519Service { /// /// Generuje nową parę kluczy Ed25519. /// Zwraca klucz prywatny (64-bajtowy w hex) oraz klucz publiczny (32-bajtowy w hex). /// public static (string PrivateKeyHex, string PublicKeyHex) GenerateKeyPair() { var random = new SecureRandom(); var keyGen = new Ed25519KeyPairGenerator(); keyGen.Init(new Ed25519KeyGenerationParameters(random)); var keyPair = keyGen.GenerateKeyPair(); var privKey = (Ed25519PrivateKeyParameters)keyPair.Private; var pubKey = (Ed25519PublicKeyParameters)keyPair.Public; var privBytes = privKey.GetEncoded(); var pubBytes = pubKey.GetEncoded(); return (Convert.ToHexString(privBytes).ToLowerInvariant(), Convert.ToHexString(pubBytes).ToLowerInvariant()); } /// /// Podpisuje dane binarne kluczem prywatnym Ed25519. /// Zwraca 64-bajtowy podpis w postaci małych liter hex (128 znaków). /// public static string Sign(byte[] data, string privateKeyHex) { var privBytes = Convert.FromHexString(privateKeyHex); var privKey = new Ed25519PrivateKeyParameters(privBytes, 0); var signer = new Ed25519Signer(); signer.Init(true, privKey); signer.BlockUpdate(data, 0, data.Length); var signature = signer.GenerateSignature(); return Convert.ToHexString(signature).ToLowerInvariant(); } /// /// Weryfikuje podpis Ed25519 dla przekazanych danych binarnych i klucza publicznego. /// public static bool Verify(byte[] data, string signatureHex, string publicKeyHex) { try { var pubBytes = Convert.FromHexString(publicKeyHex); var sigBytes = Convert.FromHexString(signatureHex); var pubKey = new Ed25519PublicKeyParameters(pubBytes, 0); var verifier = new Ed25519Signer(); verifier.Init(false, pubKey); verifier.BlockUpdate(data, 0, data.Length); return verifier.VerifySignature(sigBytes); } catch { return false; } } /// /// Tworzy kanoniczny ciąg bajtów dla metadanych wydania aplikacji natywnej. /// Kolejność właściwości jest ściśle zdefiniowana i niezmienna, a wartości są znormalizowane. /// public static byte[] GetCanonicalBytes(ReleaseMetadata metadata) { // Kanoniczny format JSON o ścisłej kolejności kluczy bez zbędnych spacji (invariant UTF-8) var canonicalDto = new { app_id = metadata.AppId, architecture = metadata.Architecture.ToLowerInvariant(), channel = metadata.Channel.ToLowerInvariant(), file_size = metadata.FileSize, mandatory = metadata.Mandatory, minimum_supported_version = metadata.MinimumSupportedVersion, platform = metadata.Platform.ToLowerInvariant(), published_at = metadata.PublishedAt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), release_id = metadata.ReleaseId.ToString().ToLowerInvariant(), sha256 = metadata.Sha256.ToLowerInvariant(), version = metadata.Version, version_code = metadata.VersionCode }; var json = JsonSerializer.Serialize(canonicalDto, new JsonSerializerOptions { WriteIndented = false }); return Encoding.UTF8.GetBytes(json); } /// /// Wylicza skrót SHA256 dla strumienia danych binarnych. /// public static async Task ComputeSha256Async(Stream stream, CancellationToken cancellationToken = default) { using var sha256 = SHA256.Create(); var hash = await sha256.ComputeHashAsync(stream, cancellationToken); return Convert.ToHexString(hash).ToLowerInvariant(); } /// /// Wylicza skrót SHA256 z tablicy bajtów. /// public static string ComputeSha256(byte[] data) { using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(data); return Convert.ToHexString(hash).ToLowerInvariant(); } }