- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint. - Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately. - Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly. - Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
		
			
				
	
	
		
			129 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| #if STELLAOPS_CRYPTO_SODIUM
 | |
| using System;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Threading;
 | |
| using System.Threading.Tasks;
 | |
| using Microsoft.IdentityModel.Tokens;
 | |
| 
 | |
| namespace StellaOps.Cryptography;
 | |
| 
 | |
| /// <summary>
 | |
| /// Libsodium-backed crypto provider (ES256) registered when <c>STELLAOPS_CRYPTO_SODIUM</c> is defined.
 | |
| /// </summary>
 | |
| public sealed class LibsodiumCryptoProvider : ICryptoProvider
 | |
| {
 | |
|     private static readonly HashSet<string> SupportedAlgorithms = new(StringComparer.OrdinalIgnoreCase)
 | |
|     {
 | |
|         SignatureAlgorithms.Es256
 | |
|     };
 | |
| 
 | |
|     private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys = new(StringComparer.Ordinal);
 | |
| 
 | |
|     public string Name => "libsodium";
 | |
| 
 | |
|     public bool Supports(CryptoCapability capability, string algorithmId)
 | |
|     {
 | |
|         if (string.IsNullOrWhiteSpace(algorithmId))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return capability switch
 | |
|         {
 | |
|             CryptoCapability.Signing or CryptoCapability.Verification => SupportedAlgorithms.Contains(algorithmId),
 | |
|             _ => false
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     public IPasswordHasher GetPasswordHasher(string algorithmId)
 | |
|         => throw new NotSupportedException("Libsodium provider does not expose password hashing capabilities.");
 | |
| 
 | |
|     public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
 | |
|     {
 | |
|         ArgumentNullException.ThrowIfNull(keyReference);
 | |
| 
 | |
|         EnsureAlgorithmSupported(algorithmId);
 | |
| 
 | |
|         if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
 | |
|         {
 | |
|             throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
 | |
|         }
 | |
| 
 | |
|         if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
 | |
|         {
 | |
|             throw new InvalidOperationException(
 | |
|                 $"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
 | |
|         }
 | |
| 
 | |
|         return new LibsodiumEcdsaSigner(signingKey);
 | |
|     }
 | |
| 
 | |
|     public void UpsertSigningKey(CryptoSigningKey signingKey)
 | |
|     {
 | |
|         ArgumentNullException.ThrowIfNull(signingKey);
 | |
|         EnsureAlgorithmSupported(signingKey.AlgorithmId);
 | |
|         if (signingKey.Kind != CryptoSigningKeyKind.Ec)
 | |
|         {
 | |
|             throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
 | |
|         }
 | |
| 
 | |
|         signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
 | |
|     }
 | |
| 
 | |
|     public bool RemoveSigningKey(string keyId)
 | |
|     {
 | |
|         if (string.IsNullOrWhiteSpace(keyId))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return signingKeys.TryRemove(keyId, out _);
 | |
|     }
 | |
| 
 | |
|     public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
 | |
|         => signingKeys.Values.ToArray();
 | |
| 
 | |
|     private static void EnsureAlgorithmSupported(string algorithmId)
 | |
|     {
 | |
|         if (!SupportedAlgorithms.Contains(algorithmId))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'libsodium'.");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private sealed class LibsodiumEcdsaSigner : ICryptoSigner
 | |
|     {
 | |
|         private readonly CryptoSigningKey signingKey;
 | |
|         private readonly ICryptoSigner fallbackSigner;
 | |
| 
 | |
|         public LibsodiumEcdsaSigner(CryptoSigningKey signingKey)
 | |
|         {
 | |
|             this.signingKey = signingKey ?? throw new ArgumentNullException(nameof(signingKey));
 | |
|             fallbackSigner = EcdsaSigner.Create(signingKey);
 | |
|         }
 | |
| 
 | |
|         public string KeyId => signingKey.Reference.KeyId;
 | |
| 
 | |
|         public string AlgorithmId => signingKey.AlgorithmId;
 | |
| 
 | |
|         public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
 | |
|         {
 | |
|             cancellationToken.ThrowIfCancellationRequested();
 | |
|             // TODO(SEC5.B1): replace fallback with libsodium bindings once native interop lands.
 | |
|             return fallbackSigner.SignAsync(data, cancellationToken);
 | |
|         }
 | |
| 
 | |
|         public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
 | |
|         {
 | |
|             cancellationToken.ThrowIfCancellationRequested();
 | |
|             return fallbackSigner.VerifyAsync(data, signature, cancellationToken);
 | |
|         }
 | |
| 
 | |
|         public JsonWebKey ExportPublicJsonWebKey()
 | |
|             => fallbackSigner.ExportPublicJsonWebKey();
 | |
|     }
 | |
| }
 | |
| #endif
 |