thecr3w-dev/backend/TheCr3w.Shared/Crypto/Ed25519Service.cs

127 lines
4.7 KiB
C#

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;
/// <summary>
/// Serwis kryptograficzny odpowiedzialny za generowanie par kluczy, podpisywanie
/// oraz weryfikację podpisów cyfrowych Ed25519 (RFC 8032) dla metadanych wydań i manifestów wdrożeniowych.
/// </summary>
public static class Ed25519Service
{
/// <summary>
/// Generuje nową parę kluczy Ed25519.
/// Zwraca klucz prywatny (64-bajtowy w hex) oraz klucz publiczny (32-bajtowy w hex).
/// </summary>
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());
}
/// <summary>
/// Podpisuje dane binarne kluczem prywatnym Ed25519.
/// Zwraca 64-bajtowy podpis w postaci małych liter hex (128 znaków).
/// </summary>
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();
}
/// <summary>
/// Weryfikuje podpis Ed25519 dla przekazanych danych binarnych i klucza publicznego.
/// </summary>
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;
}
}
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// Wylicza skrót SHA256 dla strumienia danych binarnych.
/// </summary>
public static async Task<string> ComputeSha256Async(Stream stream, CancellationToken cancellationToken = default)
{
using var sha256 = SHA256.Create();
var hash = await sha256.ComputeHashAsync(stream, cancellationToken);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// Wylicza skrót SHA256 z tablicy bajtów.
/// </summary>
public static string ComputeSha256(byte[] data)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(data);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}