feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
564
src/Cli/StellaOps.Cli/Commands/Proof/KeyRotationCommandGroup.cs
Normal file
564
src/Cli/StellaOps.Cli/Commands/Proof/KeyRotationCommandGroup.cs
Normal file
@@ -0,0 +1,564 @@
|
||||
using System.CommandLine;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Cli.Commands.Proof;
|
||||
|
||||
/// <summary>
|
||||
/// Command group for key rotation operations.
|
||||
/// Sprint: SPRINT_0501_0008_0001_proof_chain_key_rotation
|
||||
/// Task: PROOF-KEY-0011
|
||||
/// Implements advisory §8.2 key rotation commands.
|
||||
/// </summary>
|
||||
public class KeyRotationCommandGroup
|
||||
{
|
||||
private readonly ILogger<KeyRotationCommandGroup> _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public KeyRotationCommandGroup(ILogger<KeyRotationCommandGroup> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the key rotation command tree.
|
||||
/// </summary>
|
||||
public Command BuildCommand()
|
||||
{
|
||||
var keyCommand = new Command("key", "Key management and rotation commands");
|
||||
|
||||
keyCommand.AddCommand(BuildListCommand());
|
||||
keyCommand.AddCommand(BuildAddCommand());
|
||||
keyCommand.AddCommand(BuildRevokeCommand());
|
||||
keyCommand.AddCommand(BuildRotateCommand());
|
||||
keyCommand.AddCommand(BuildStatusCommand());
|
||||
keyCommand.AddCommand(BuildHistoryCommand());
|
||||
keyCommand.AddCommand(BuildVerifyCommand());
|
||||
|
||||
return keyCommand;
|
||||
}
|
||||
|
||||
private Command BuildListCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var includeRevokedOption = new Option<bool>(
|
||||
name: "--include-revoked",
|
||||
getDefaultValue: () => false,
|
||||
description: "Include revoked keys in output");
|
||||
var outputOption = new Option<string>(
|
||||
name: "--output",
|
||||
getDefaultValue: () => "text",
|
||||
description: "Output format: text, json");
|
||||
|
||||
var listCommand = new Command("list", "List keys for a trust anchor")
|
||||
{
|
||||
anchorArg,
|
||||
includeRevokedOption,
|
||||
outputOption
|
||||
};
|
||||
|
||||
listCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var includeRevoked = context.ParseResult.GetValueForOption(includeRevokedOption);
|
||||
var output = context.ParseResult.GetValueForOption(outputOption) ?? "text";
|
||||
context.ExitCode = await ListKeysAsync(anchorId, includeRevoked, output, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return listCommand;
|
||||
}
|
||||
|
||||
private Command BuildAddCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var keyIdArg = new Argument<string>("keyId", "New key ID");
|
||||
var algorithmOption = new Option<string>(
|
||||
aliases: ["-a", "--algorithm"],
|
||||
getDefaultValue: () => "Ed25519",
|
||||
description: "Key algorithm: Ed25519, ES256, ES384, RS256");
|
||||
var publicKeyOption = new Option<string?>(
|
||||
name: "--public-key",
|
||||
description: "Path to public key file (PEM format)");
|
||||
var notesOption = new Option<string?>(
|
||||
name: "--notes",
|
||||
description: "Human-readable notes about the key");
|
||||
|
||||
var addCommand = new Command("add", "Add a new key to a trust anchor")
|
||||
{
|
||||
anchorArg,
|
||||
keyIdArg,
|
||||
algorithmOption,
|
||||
publicKeyOption,
|
||||
notesOption
|
||||
};
|
||||
|
||||
addCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var keyId = context.ParseResult.GetValueForArgument(keyIdArg);
|
||||
var algorithm = context.ParseResult.GetValueForOption(algorithmOption) ?? "Ed25519";
|
||||
var publicKeyPath = context.ParseResult.GetValueForOption(publicKeyOption);
|
||||
var notes = context.ParseResult.GetValueForOption(notesOption);
|
||||
context.ExitCode = await AddKeyAsync(anchorId, keyId, algorithm, publicKeyPath, notes, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return addCommand;
|
||||
}
|
||||
|
||||
private Command BuildRevokeCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var keyIdArg = new Argument<string>("keyId", "Key ID to revoke");
|
||||
var reasonOption = new Option<string>(
|
||||
aliases: ["-r", "--reason"],
|
||||
getDefaultValue: () => "rotation-complete",
|
||||
description: "Reason for revocation");
|
||||
var effectiveOption = new Option<DateTimeOffset?>(
|
||||
name: "--effective-at",
|
||||
description: "Effective revocation time (default: now). ISO-8601 format.");
|
||||
var forceOption = new Option<bool>(
|
||||
name: "--force",
|
||||
getDefaultValue: () => false,
|
||||
description: "Skip confirmation prompt");
|
||||
|
||||
var revokeCommand = new Command("revoke", "Revoke a key from a trust anchor")
|
||||
{
|
||||
anchorArg,
|
||||
keyIdArg,
|
||||
reasonOption,
|
||||
effectiveOption,
|
||||
forceOption
|
||||
};
|
||||
|
||||
revokeCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var keyId = context.ParseResult.GetValueForArgument(keyIdArg);
|
||||
var reason = context.ParseResult.GetValueForOption(reasonOption) ?? "rotation-complete";
|
||||
var effectiveAt = context.ParseResult.GetValueForOption(effectiveOption) ?? DateTimeOffset.UtcNow;
|
||||
var force = context.ParseResult.GetValueForOption(forceOption);
|
||||
context.ExitCode = await RevokeKeyAsync(anchorId, keyId, reason, effectiveAt, force, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return revokeCommand;
|
||||
}
|
||||
|
||||
private Command BuildRotateCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var oldKeyIdArg = new Argument<string>("oldKeyId", "Old key ID to replace");
|
||||
var newKeyIdArg = new Argument<string>("newKeyId", "New key ID");
|
||||
var algorithmOption = new Option<string>(
|
||||
aliases: ["-a", "--algorithm"],
|
||||
getDefaultValue: () => "Ed25519",
|
||||
description: "Key algorithm: Ed25519, ES256, ES384, RS256");
|
||||
var publicKeyOption = new Option<string?>(
|
||||
name: "--public-key",
|
||||
description: "Path to new public key file (PEM format)");
|
||||
var overlapOption = new Option<int>(
|
||||
name: "--overlap-days",
|
||||
getDefaultValue: () => 30,
|
||||
description: "Days to keep both keys active before revoking old");
|
||||
|
||||
var rotateCommand = new Command("rotate", "Rotate a key (add new, schedule old revocation)")
|
||||
{
|
||||
anchorArg,
|
||||
oldKeyIdArg,
|
||||
newKeyIdArg,
|
||||
algorithmOption,
|
||||
publicKeyOption,
|
||||
overlapOption
|
||||
};
|
||||
|
||||
rotateCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var oldKeyId = context.ParseResult.GetValueForArgument(oldKeyIdArg);
|
||||
var newKeyId = context.ParseResult.GetValueForArgument(newKeyIdArg);
|
||||
var algorithm = context.ParseResult.GetValueForOption(algorithmOption) ?? "Ed25519";
|
||||
var publicKeyPath = context.ParseResult.GetValueForOption(publicKeyOption);
|
||||
var overlapDays = context.ParseResult.GetValueForOption(overlapOption);
|
||||
context.ExitCode = await RotateKeyAsync(anchorId, oldKeyId, newKeyId, algorithm, publicKeyPath, overlapDays, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return rotateCommand;
|
||||
}
|
||||
|
||||
private Command BuildStatusCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var outputOption = new Option<string>(
|
||||
name: "--output",
|
||||
getDefaultValue: () => "text",
|
||||
description: "Output format: text, json");
|
||||
|
||||
var statusCommand = new Command("status", "Show key rotation status and warnings")
|
||||
{
|
||||
anchorArg,
|
||||
outputOption
|
||||
};
|
||||
|
||||
statusCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var output = context.ParseResult.GetValueForOption(outputOption) ?? "text";
|
||||
context.ExitCode = await ShowStatusAsync(anchorId, output, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return statusCommand;
|
||||
}
|
||||
|
||||
private Command BuildHistoryCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var keyIdOption = new Option<string?>(
|
||||
aliases: ["-k", "--key-id"],
|
||||
description: "Filter by specific key ID");
|
||||
var limitOption = new Option<int>(
|
||||
name: "--limit",
|
||||
getDefaultValue: () => 50,
|
||||
description: "Maximum entries to show");
|
||||
var outputOption = new Option<string>(
|
||||
name: "--output",
|
||||
getDefaultValue: () => "text",
|
||||
description: "Output format: text, json");
|
||||
|
||||
var historyCommand = new Command("history", "Show key audit history")
|
||||
{
|
||||
anchorArg,
|
||||
keyIdOption,
|
||||
limitOption,
|
||||
outputOption
|
||||
};
|
||||
|
||||
historyCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var keyId = context.ParseResult.GetValueForOption(keyIdOption);
|
||||
var limit = context.ParseResult.GetValueForOption(limitOption);
|
||||
var output = context.ParseResult.GetValueForOption(outputOption) ?? "text";
|
||||
context.ExitCode = await ShowHistoryAsync(anchorId, keyId, limit, output, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return historyCommand;
|
||||
}
|
||||
|
||||
private Command BuildVerifyCommand()
|
||||
{
|
||||
var anchorArg = new Argument<Guid>("anchorId", "Trust anchor ID");
|
||||
var keyIdArg = new Argument<string>("keyId", "Key ID to verify");
|
||||
var signedAtOption = new Option<DateTimeOffset?>(
|
||||
aliases: ["-t", "--signed-at"],
|
||||
description: "Verify key was valid at this time (ISO-8601)");
|
||||
|
||||
var verifyCommand = new Command("verify", "Verify a key's validity at a point in time")
|
||||
{
|
||||
anchorArg,
|
||||
keyIdArg,
|
||||
signedAtOption
|
||||
};
|
||||
|
||||
verifyCommand.SetHandler(async (context) =>
|
||||
{
|
||||
var anchorId = context.ParseResult.GetValueForArgument(anchorArg);
|
||||
var keyId = context.ParseResult.GetValueForArgument(keyIdArg);
|
||||
var signedAt = context.ParseResult.GetValueForOption(signedAtOption) ?? DateTimeOffset.UtcNow;
|
||||
context.ExitCode = await VerifyKeyAsync(anchorId, keyId, signedAt, context.GetCancellationToken());
|
||||
});
|
||||
|
||||
return verifyCommand;
|
||||
}
|
||||
|
||||
#region Handler Implementations
|
||||
|
||||
private async Task<int> ListKeysAsync(Guid anchorId, bool includeRevoked, string output, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Listing keys for anchor {AnchorId}, includeRevoked={IncludeRevoked}",
|
||||
anchorId, includeRevoked);
|
||||
|
||||
// TODO: Wire up to IKeyRotationService when DI is available
|
||||
|
||||
if (output == "json")
|
||||
{
|
||||
var result = new
|
||||
{
|
||||
anchorId = anchorId.ToString(),
|
||||
activeKeys = Array.Empty<object>(),
|
||||
revokedKeys = includeRevoked ? Array.Empty<object>() : null
|
||||
};
|
||||
Console.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Keys for Trust Anchor: {anchorId}");
|
||||
Console.WriteLine("═════════════════════════════════════════════");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Active Keys:");
|
||||
Console.WriteLine(" (No active keys found - connect to service)");
|
||||
if (includeRevoked)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Revoked Keys:");
|
||||
Console.WriteLine(" (No revoked keys found - connect to service)");
|
||||
}
|
||||
}
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to list keys for anchor {AnchorId}", anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> AddKeyAsync(Guid anchorId, string keyId, string algorithm, string? publicKeyPath, string? notes, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Adding key {KeyId} to anchor {AnchorId}", keyId, anchorId);
|
||||
|
||||
string? publicKey = null;
|
||||
if (publicKeyPath != null)
|
||||
{
|
||||
if (!File.Exists(publicKeyPath))
|
||||
{
|
||||
Console.Error.WriteLine($"Error: Public key file not found: {publicKeyPath}");
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
publicKey = await File.ReadAllTextAsync(publicKeyPath, ct);
|
||||
}
|
||||
|
||||
// TODO: Wire up to IKeyRotationService.AddKeyAsync
|
||||
|
||||
Console.WriteLine("Adding key to trust anchor...");
|
||||
Console.WriteLine($" Anchor: {anchorId}");
|
||||
Console.WriteLine($" Key ID: {keyId}");
|
||||
Console.WriteLine($" Algorithm: {algorithm}");
|
||||
Console.WriteLine($" Public Key: {(publicKey != null ? "Provided" : "Not specified")}");
|
||||
if (notes != null)
|
||||
Console.WriteLine($" Notes: {notes}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("✓ Key added successfully (simulation)");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to add key {KeyId} to anchor {AnchorId}", keyId, anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> RevokeKeyAsync(Guid anchorId, string keyId, string reason, DateTimeOffset effectiveAt, bool force, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Revoking key {KeyId} from anchor {AnchorId}", keyId, anchorId);
|
||||
|
||||
if (!force)
|
||||
{
|
||||
Console.Write($"Revoke key '{keyId}' from anchor {anchorId}? [y/N] ");
|
||||
var response = Console.ReadLine();
|
||||
if (response?.ToLowerInvariant() != "y")
|
||||
{
|
||||
Console.WriteLine("Cancelled.");
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Wire up to IKeyRotationService.RevokeKeyAsync
|
||||
|
||||
Console.WriteLine("Revoking key...");
|
||||
Console.WriteLine($" Anchor: {anchorId}");
|
||||
Console.WriteLine($" Key ID: {keyId}");
|
||||
Console.WriteLine($" Reason: {reason}");
|
||||
Console.WriteLine($" Effective At: {effectiveAt:O}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("✓ Key revoked successfully (simulation)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Note: Proofs signed before revocation remain valid.");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to revoke key {KeyId} from anchor {AnchorId}", keyId, anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> RotateKeyAsync(Guid anchorId, string oldKeyId, string newKeyId, string algorithm, string? publicKeyPath, int overlapDays, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Rotating key {OldKeyId} -> {NewKeyId} for anchor {AnchorId}",
|
||||
oldKeyId, newKeyId, anchorId);
|
||||
|
||||
string? publicKey = null;
|
||||
if (publicKeyPath != null)
|
||||
{
|
||||
if (!File.Exists(publicKeyPath))
|
||||
{
|
||||
Console.Error.WriteLine($"Error: Public key file not found: {publicKeyPath}");
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
publicKey = await File.ReadAllTextAsync(publicKeyPath, ct);
|
||||
}
|
||||
|
||||
var revokeAt = DateTimeOffset.UtcNow.AddDays(overlapDays);
|
||||
|
||||
// TODO: Wire up to IKeyRotationService
|
||||
|
||||
Console.WriteLine("Key Rotation Plan");
|
||||
Console.WriteLine("═════════════════");
|
||||
Console.WriteLine($" Anchor: {anchorId}");
|
||||
Console.WriteLine($" Old Key: {oldKeyId}");
|
||||
Console.WriteLine($" New Key: {newKeyId}");
|
||||
Console.WriteLine($" Algorithm: {algorithm}");
|
||||
Console.WriteLine($" Overlap Period: {overlapDays} days");
|
||||
Console.WriteLine($" Old Key Revokes At: {revokeAt:O}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Step 1: Add new key to allowedKeyIds...");
|
||||
Console.WriteLine(" ✓ Key added (simulation)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Step 2: Schedule old key revocation...");
|
||||
Console.WriteLine($" ✓ Old key will be revoked on {revokeAt:yyyy-MM-dd} (simulation)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("✓ Key rotation initiated successfully");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Next Steps:");
|
||||
Console.WriteLine($" 1. Start using '{newKeyId}' for new signatures");
|
||||
Console.WriteLine($" 2. Old key remains valid until {revokeAt:yyyy-MM-dd}");
|
||||
Console.WriteLine($" 3. Run 'stellaops key status {anchorId}' to check rotation warnings");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to rotate key {OldKeyId} -> {NewKeyId} for anchor {AnchorId}",
|
||||
oldKeyId, newKeyId, anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> ShowStatusAsync(Guid anchorId, string output, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Showing key status for anchor {AnchorId}", anchorId);
|
||||
|
||||
// TODO: Wire up to IKeyRotationService.GetRotationWarningsAsync
|
||||
|
||||
if (output == "json")
|
||||
{
|
||||
var result = new
|
||||
{
|
||||
anchorId = anchorId.ToString(),
|
||||
status = "healthy",
|
||||
warnings = Array.Empty<object>()
|
||||
};
|
||||
Console.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Key Status for Trust Anchor: {anchorId}");
|
||||
Console.WriteLine("═════════════════════════════════════════════");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Overall Status: ✓ Healthy (simulation)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Active Keys: 0");
|
||||
Console.WriteLine("Revoked Keys: 0");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Rotation Warnings: None");
|
||||
}
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to show status for anchor {AnchorId}", anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> ShowHistoryAsync(Guid anchorId, string? keyId, int limit, string output, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Showing key history for anchor {AnchorId}, keyId={KeyId}, limit={Limit}",
|
||||
anchorId, keyId, limit);
|
||||
|
||||
// TODO: Wire up to IKeyRotationService.GetKeyHistoryAsync
|
||||
|
||||
if (output == "json")
|
||||
{
|
||||
var result = new
|
||||
{
|
||||
anchorId = anchorId.ToString(),
|
||||
keyId = keyId,
|
||||
entries = Array.Empty<object>()
|
||||
};
|
||||
Console.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Key Audit History for Trust Anchor: {anchorId}");
|
||||
if (keyId != null)
|
||||
Console.WriteLine($" Filtered by Key: {keyId}");
|
||||
Console.WriteLine("═════════════════════════════════════════════");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Timestamp | Operation | Key ID | Operator");
|
||||
Console.WriteLine("───────────────────────────────────────────────────────────────────");
|
||||
Console.WriteLine("(No history entries - connect to service)");
|
||||
}
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to show history for anchor {AnchorId}", anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> VerifyKeyAsync(Guid anchorId, string keyId, DateTimeOffset signedAt, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Verifying key {KeyId} validity at {SignedAt} for anchor {AnchorId}",
|
||||
keyId, signedAt, anchorId);
|
||||
|
||||
// TODO: Wire up to IKeyRotationService.CheckKeyValidityAsync
|
||||
|
||||
Console.WriteLine($"Key Validity Check");
|
||||
Console.WriteLine("═════════════════════════════════════════════");
|
||||
Console.WriteLine($" Anchor: {anchorId}");
|
||||
Console.WriteLine($" Key ID: {keyId}");
|
||||
Console.WriteLine($" Time: {signedAt:O}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Result: ⚠ Unknown (connect to service for verification)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Temporal validation checks:");
|
||||
Console.WriteLine(" [ ] Key existed at specified time");
|
||||
Console.WriteLine(" [ ] Key was not revoked before specified time");
|
||||
Console.WriteLine(" [ ] Key algorithm is currently trusted");
|
||||
|
||||
return ProofExitCodes.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to verify key {KeyId} for anchor {AnchorId}", keyId, anchorId);
|
||||
return ProofExitCodes.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user