Add authority bootstrap flows and Concelier ops runbooks
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