This commit is contained in:
master
2025-10-15 10:03:56 +03:00
parent 0ddc014864
commit 79823d3319
276 changed files with 21674 additions and 934 deletions

View File

@@ -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)

View File

@@ -78,7 +78,12 @@ internal sealed class AuthorityRevocationClient : IAuthorityRevocationClient
if (verbose)
{
logger.LogInformation("Received revocation export sequence {Sequence} (sha256:{Digest}, signing key {KeyId}).", payload.Sequence, digest, payload.SigningKeyId ?? "<unspecified>");
logger.LogInformation(
"Received revocation export sequence {Sequence} (sha256:{Digest}, signing key {KeyId}, provider {Provider}).",
payload.Sequence,
digest,
payload.SigningKeyId ?? "<unspecified>",
string.IsNullOrWhiteSpace(payload.Signature?.Provider) ? "default" : payload.Signature!.Provider);
}
return new AuthorityRevocationExportResult
@@ -88,7 +93,8 @@ internal sealed class AuthorityRevocationClient : IAuthorityRevocationClient
Digest = digest,
Sequence = payload.Sequence,
IssuedAt = payload.IssuedAt,
SigningKeyId = payload.SigningKeyId
SigningKeyId = payload.SigningKeyId,
SigningProvider = payload.Signature?.Provider
};
}
@@ -201,6 +207,9 @@ internal sealed class AuthorityRevocationClient : IAuthorityRevocationClient
[JsonPropertyName("keyId")]
public string KeyId { get; set; } = string.Empty;
[JsonPropertyName("provider")]
public string Provider { get; set; } = string.Empty;
[JsonPropertyName("value")]
public string Value { get; set; } = string.Empty;
}

View File

@@ -15,4 +15,6 @@ internal sealed class AuthorityRevocationExportResult
public required DateTimeOffset IssuedAt { get; init; }
public string? SigningKeyId { get; init; }
public string? SigningProvider { get; init; }
}