up
This commit is contained in:
		@@ -641,11 +641,12 @@ internal static class CommandHandlers
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.LogInformation(
 | 
			
		||||
                "Revocation bundle exported to {Directory} (sequence {Sequence}, issued {Issued:u}, signing key {KeyId}).",
 | 
			
		||||
                "Revocation bundle exported to {Directory} (sequence {Sequence}, issued {Issued:u}, signing key {KeyId}, provider {Provider}).",
 | 
			
		||||
                directory,
 | 
			
		||||
                result.Sequence,
 | 
			
		||||
                result.IssuedAt,
 | 
			
		||||
                string.IsNullOrWhiteSpace(result.SigningKeyId) ? "<unknown>" : result.SigningKeyId);
 | 
			
		||||
                string.IsNullOrWhiteSpace(result.SigningKeyId) ? "<unknown>" : result.SigningKeyId,
 | 
			
		||||
                string.IsNullOrWhiteSpace(result.SigningProvider) ? "default" : result.SigningProvider);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -709,22 +710,62 @@ internal static class CommandHandlers
 | 
			
		||||
                algorithm = SignatureAlgorithms.Es256;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var hashAlgorithm = ResolveHashAlgorithm(algorithm);
 | 
			
		||||
            if (hashAlgorithm is null)
 | 
			
		||||
            var providerHint = header.TryGetProperty("provider", out var providerElement)
 | 
			
		||||
                ? providerElement.GetString()
 | 
			
		||||
                : null;
 | 
			
		||||
 | 
			
		||||
            var keyId = header.TryGetProperty("kid", out var kidElement) ? kidElement.GetString() : null;
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(keyId))
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogError("Unsupported signing algorithm '{Algorithm}'.", algorithm);
 | 
			
		||||
                keyId = Path.GetFileNameWithoutExtension(keyPath);
 | 
			
		||||
                logger.LogWarning("JWS header missing 'kid'; using fallback key id {KeyId}.", keyId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CryptoSigningKey signingKey;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                signingKey = CreateVerificationSigningKey(keyId!, algorithm!, providerHint, keyPem, keyPath);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex) when (ex is InvalidOperationException or CryptographicException)
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogError(ex, "Failed to load verification key material.");
 | 
			
		||||
                Environment.ExitCode = 1;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var ecdsa = ECDsa.Create();
 | 
			
		||||
            var providers = new List<ICryptoProvider>
 | 
			
		||||
            {
 | 
			
		||||
                new DefaultCryptoProvider()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
#if STELLAOPS_CRYPTO_SODIUM
 | 
			
		||||
            providers.Add(new LibsodiumCryptoProvider());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
            foreach (var provider in providers)
 | 
			
		||||
            {
 | 
			
		||||
                if (provider.Supports(CryptoCapability.Verification, algorithm!))
 | 
			
		||||
                {
 | 
			
		||||
                    provider.UpsertSigningKey(signingKey);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var preferredOrder = !string.IsNullOrWhiteSpace(providerHint)
 | 
			
		||||
                ? new[] { providerHint! }
 | 
			
		||||
                : Array.Empty<string>();
 | 
			
		||||
            var registry = new CryptoProviderRegistry(providers, preferredOrder);
 | 
			
		||||
            CryptoSignerResolution resolution;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ecdsa.ImportFromPem(keyPem);
 | 
			
		||||
                resolution = registry.ResolveSigner(
 | 
			
		||||
                    CryptoCapability.Verification,
 | 
			
		||||
                    algorithm!,
 | 
			
		||||
                    signingKey.Reference,
 | 
			
		||||
                    providerHint);
 | 
			
		||||
            }
 | 
			
		||||
            catch (CryptographicException ex)
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogError(ex, "Failed to import signing key.");
 | 
			
		||||
                logger.LogError(ex, "No crypto provider available for verification (algorithm {Algorithm}).", algorithm);
 | 
			
		||||
                Environment.ExitCode = 1;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -739,7 +780,10 @@ internal static class CommandHandlers
 | 
			
		||||
                Buffer.BlockCopy(bundleBytes, 0, buffer, headerBytes.Length + 1, bundleBytes.Length);
 | 
			
		||||
 | 
			
		||||
                var signatureBytes = Base64UrlDecode(encodedSignature);
 | 
			
		||||
                var verified = ecdsa.VerifyData(new ReadOnlySpan<byte>(buffer, 0, signingInputLength), signatureBytes, hashAlgorithm.Value);
 | 
			
		||||
                var verified = await resolution.Signer.VerifyAsync(
 | 
			
		||||
                    new ReadOnlyMemory<byte>(buffer, 0, signingInputLength),
 | 
			
		||||
                    signatureBytes,
 | 
			
		||||
                    cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (!verified)
 | 
			
		||||
                {
 | 
			
		||||
@@ -753,7 +797,19 @@ internal static class CommandHandlers
 | 
			
		||||
                ArrayPool<byte>.Shared.Return(buffer);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.LogInformation("Signature verified using algorithm {Algorithm}.", algorithm);
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(providerHint) && !string.Equals(providerHint, resolution.ProviderName, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogWarning(
 | 
			
		||||
                    "Preferred provider '{Preferred}' unavailable; verification used '{Provider}'.",
 | 
			
		||||
                    providerHint,
 | 
			
		||||
                    resolution.ProviderName);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.LogInformation(
 | 
			
		||||
                "Signature verified using algorithm {Algorithm} via provider {Provider} (kid {KeyId}).",
 | 
			
		||||
                algorithm,
 | 
			
		||||
                resolution.ProviderName,
 | 
			
		||||
                signingKey.Reference.KeyId);
 | 
			
		||||
 | 
			
		||||
            if (verbose)
 | 
			
		||||
            {
 | 
			
		||||
@@ -812,24 +868,39 @@ internal static class CommandHandlers
 | 
			
		||||
        return Convert.FromBase64String(normalized);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static HashAlgorithmName? ResolveHashAlgorithm(string algorithm)
 | 
			
		||||
    private static CryptoSigningKey CreateVerificationSigningKey(
 | 
			
		||||
        string keyId,
 | 
			
		||||
        string algorithm,
 | 
			
		||||
        string? providerHint,
 | 
			
		||||
        string keyPem,
 | 
			
		||||
        string keyPath)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.Equals(algorithm, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(keyPem))
 | 
			
		||||
        {
 | 
			
		||||
            return HashAlgorithmName.SHA256;
 | 
			
		||||
            throw new InvalidOperationException("Verification key PEM content is empty.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (string.Equals(algorithm, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
        using var ecdsa = ECDsa.Create();
 | 
			
		||||
        ecdsa.ImportFromPem(keyPem);
 | 
			
		||||
 | 
			
		||||
        var parameters = ecdsa.ExportParameters(includePrivateParameters: false);
 | 
			
		||||
        if (parameters.D is null || parameters.D.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            return HashAlgorithmName.SHA384;
 | 
			
		||||
            parameters.D = new byte[] { 0x01 };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (string.Equals(algorithm, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
        var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
 | 
			
		||||
        {
 | 
			
		||||
            return HashAlgorithmName.SHA512;
 | 
			
		||||
        }
 | 
			
		||||
            ["source"] = Path.GetFullPath(keyPath),
 | 
			
		||||
            ["verificationOnly"] = "true"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
        return new CryptoSigningKey(
 | 
			
		||||
            new CryptoKeyReference(keyId, providerHint),
 | 
			
		||||
            algorithm,
 | 
			
		||||
            in parameters,
 | 
			
		||||
            DateTimeOffset.UtcNow,
 | 
			
		||||
            metadata: metadata);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static string FormatDuration(TimeSpan duration)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user