consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Crypto.Signers;
|
||||
using Org.BouncyCastle.Security;
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -133,24 +136,21 @@ public sealed class DsseVerifier : IDsseVerifier
|
||||
try
|
||||
{
|
||||
var signatureBytes = Convert.FromBase64String(sig.Sig);
|
||||
if (VerifySignature(pae, signatureBytes, publicKeyPem))
|
||||
var verification = VerifySignature(pae, signatureBytes, publicKeyPem);
|
||||
if (verification.IsValid)
|
||||
{
|
||||
verifiedKeyIds.Add(sig.KeyId ?? "unknown");
|
||||
_logger.LogDebug("DSSE signature verified for keyId: {KeyId}", sig.KeyId ?? "unknown");
|
||||
}
|
||||
else
|
||||
{
|
||||
issues.Add($"signature_invalid_{sig.KeyId ?? "unknown"}");
|
||||
issues.Add($"signature_invalid_{sig.KeyId ?? "unknown"}:{verification.ReasonCode}");
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
issues.Add($"signature_invalid_base64_{sig.KeyId ?? "unknown"}");
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
issues.Add($"signature_crypto_error_{sig.KeyId ?? "unknown"}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute payload hash for result
|
||||
@@ -236,49 +236,164 @@ public sealed class DsseVerifier : IDsseVerifier
|
||||
|
||||
/// <summary>
|
||||
/// Verifies a signature against PAE using the provided public key.
|
||||
/// Supports ECDSA P-256 and RSA keys.
|
||||
/// Supports ECDSA, RSA, and Ed25519 keys.
|
||||
/// </summary>
|
||||
private bool VerifySignature(byte[] pae, byte[] signature, string publicKeyPem)
|
||||
private SignatureVerificationResult VerifySignature(byte[] pae, byte[] signature, string publicKeyPem)
|
||||
{
|
||||
if (!TryExtractPublicKeyDer(publicKeyPem, out var publicKeyDer))
|
||||
{
|
||||
return SignatureVerificationResult.Invalid("invalid_public_key_material");
|
||||
}
|
||||
|
||||
if (TryVerifyWithEcdsa(pae, signature, publicKeyDer, out var ecdsaResult))
|
||||
{
|
||||
return ecdsaResult;
|
||||
}
|
||||
|
||||
if (TryVerifyWithRsa(pae, signature, publicKeyDer, out var rsaResult))
|
||||
{
|
||||
return rsaResult;
|
||||
}
|
||||
|
||||
if (TryVerifyWithEd25519(pae, signature, publicKeyDer, out var ed25519Result))
|
||||
{
|
||||
return ed25519Result;
|
||||
}
|
||||
|
||||
return SignatureVerificationResult.Invalid("unsupported_public_key_type");
|
||||
}
|
||||
|
||||
private static bool TryVerifyWithEcdsa(
|
||||
byte[] pae,
|
||||
byte[] signature,
|
||||
byte[] publicKeyDer,
|
||||
out SignatureVerificationResult result)
|
||||
{
|
||||
// Try ECDSA first (most common for Sigstore/Fulcio)
|
||||
try
|
||||
{
|
||||
using var ecdsa = ECDsa.Create();
|
||||
ecdsa.ImportFromPem(publicKeyPem);
|
||||
return ecdsa.VerifyData(pae, signature, HashAlgorithmName.SHA256);
|
||||
ecdsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);
|
||||
var isValid = ecdsa.VerifyData(pae, signature, HashAlgorithmName.SHA256);
|
||||
result = isValid
|
||||
? SignatureVerificationResult.Valid
|
||||
: SignatureVerificationResult.Invalid("signature_mismatch");
|
||||
return true;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
// Not an ECDSA key, try RSA
|
||||
result = SignatureVerificationResult.NotApplicable;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try RSA
|
||||
private static bool TryVerifyWithRsa(
|
||||
byte[] pae,
|
||||
byte[] signature,
|
||||
byte[] publicKeyDer,
|
||||
out SignatureVerificationResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(publicKeyPem);
|
||||
return rsa.VerifyData(pae, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
rsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);
|
||||
var isValid = rsa.VerifyData(pae, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
result = isValid
|
||||
? SignatureVerificationResult.Valid
|
||||
: SignatureVerificationResult.Invalid("signature_mismatch");
|
||||
return true;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
// Not an RSA key either
|
||||
}
|
||||
|
||||
// Try Ed25519 if available (.NET 9+)
|
||||
try
|
||||
{
|
||||
// Ed25519 support via System.Security.Cryptography
|
||||
// Note: Ed25519 verification requires different handling
|
||||
// For now, we log and return false - can be extended later
|
||||
_logger.LogDebug("Ed25519 signature verification not yet implemented");
|
||||
result = SignatureVerificationResult.NotApplicable;
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
}
|
||||
|
||||
private static bool TryVerifyWithEd25519(
|
||||
byte[] pae,
|
||||
byte[] signature,
|
||||
byte[] publicKeyDer,
|
||||
out SignatureVerificationResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Ed25519 not available
|
||||
var key = PublicKeyFactory.CreateKey(publicKeyDer);
|
||||
if (key is not Ed25519PublicKeyParameters ed25519PublicKey)
|
||||
{
|
||||
result = SignatureVerificationResult.NotApplicable;
|
||||
return false;
|
||||
}
|
||||
|
||||
var verifier = new Ed25519Signer();
|
||||
verifier.Init(false, ed25519PublicKey);
|
||||
verifier.BlockUpdate(pae, 0, pae.Length);
|
||||
|
||||
var isValid = verifier.VerifySignature(signature);
|
||||
result = isValid
|
||||
? SignatureVerificationResult.Valid
|
||||
: SignatureVerificationResult.Invalid("signature_mismatch");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
|
||||
{
|
||||
result = SignatureVerificationResult.Invalid("invalid_public_key_material");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryExtractPublicKeyDer(string publicKeyPem, out byte[] publicKeyDer)
|
||||
{
|
||||
publicKeyDer = Array.Empty<byte>();
|
||||
if (string.IsNullOrWhiteSpace(publicKeyPem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
var beginMarker = "-----BEGIN PUBLIC KEY-----";
|
||||
var endMarker = "-----END PUBLIC KEY-----";
|
||||
var beginIndex = publicKeyPem.IndexOf(beginMarker, StringComparison.Ordinal);
|
||||
var endIndex = publicKeyPem.IndexOf(endMarker, StringComparison.Ordinal);
|
||||
if (beginIndex < 0 || endIndex <= beginIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodyStart = beginIndex + beginMarker.Length;
|
||||
var body = publicKeyPem[bodyStart..endIndex];
|
||||
var normalized = new string(body.Where(static ch => !char.IsWhiteSpace(ch)).ToArray());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
publicKeyDer = Convert.FromBase64String(normalized);
|
||||
return publicKeyDer.Length > 0;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct SignatureVerificationResult
|
||||
{
|
||||
public static SignatureVerificationResult Valid => new(true, "none");
|
||||
public static SignatureVerificationResult NotApplicable => new(false, "not_applicable");
|
||||
|
||||
public bool IsValid { get; }
|
||||
|
||||
public string ReasonCode { get; }
|
||||
|
||||
private SignatureVerificationResult(bool isValid, string reasonCode)
|
||||
{
|
||||
IsValid = isValid;
|
||||
ReasonCode = reasonCode;
|
||||
}
|
||||
|
||||
public static SignatureVerificationResult Invalid(string reasonCode) => new(false, reasonCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| ATTESTOR-225-001 | DOING | Sprint 225: implement Ed25519 DSSE verification with deterministic failure reasons and vectors. |
|
||||
| AUDIT-0043-M | DONE | Revalidated maintainability for StellaOps.Attestation (2026-01-06). |
|
||||
| AUDIT-0043-T | DONE | Revalidated test coverage for StellaOps.Attestation (2026-01-06). |
|
||||
| AUDIT-0043-A | TODO | Open findings from revalidation (canonical JSON for DSSE payloads). |
|
||||
|
||||
Reference in New Issue
Block a user