- Implemented TelemetryClient to handle event queuing and flushing to the telemetry endpoint. - Created TtfsTelemetryService for emitting specific telemetry events related to TTFS. - Added tests for TelemetryClient to ensure event queuing and flushing functionality. - Introduced models for reachability drift detection, including DriftResult and DriftedSink. - Developed DriftApiService for interacting with the drift detection API. - Updated FirstSignalCardComponent to emit telemetry events on signal appearance. - Enhanced localization support for first signal component with i18n strings.
629 lines
23 KiB
C#
629 lines
23 KiB
C#
using System.CommandLine;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Cli.Extensions;
|
|
|
|
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.Add(BuildListCommand());
|
|
keyCommand.Add(BuildAddCommand());
|
|
keyCommand.Add(BuildRevokeCommand());
|
|
keyCommand.Add(BuildRotateCommand());
|
|
keyCommand.Add(BuildStatusCommand());
|
|
keyCommand.Add(BuildHistoryCommand());
|
|
keyCommand.Add(BuildVerifyCommand());
|
|
|
|
return keyCommand;
|
|
}
|
|
|
|
private Command BuildListCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var includeRevokedOption = new Option<bool>("--include-revoked")
|
|
{
|
|
Description = "Include revoked keys in output"
|
|
}.SetDefaultValue(false);
|
|
|
|
var outputOption = new Option<string>("--output")
|
|
{
|
|
Description = "Output format: text, json"
|
|
}.SetDefaultValue("text").FromAmong("text", "json");
|
|
|
|
var listCommand = new Command("list", "List keys for a trust anchor")
|
|
{
|
|
anchorArg,
|
|
includeRevokedOption,
|
|
outputOption
|
|
};
|
|
|
|
listCommand.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var includeRevoked = parseResult.GetValue(includeRevokedOption);
|
|
var output = parseResult.GetValue(outputOption) ?? "text";
|
|
Environment.ExitCode = await ListKeysAsync(anchorId, includeRevoked, output, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return listCommand;
|
|
}
|
|
|
|
private Command BuildAddCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var keyIdArg = new Argument<string>("keyId")
|
|
{
|
|
Description = "New key ID"
|
|
};
|
|
|
|
var algorithmOption = new Option<string>("--algorithm", new[] { "-a" })
|
|
{
|
|
Description = "Key algorithm: Ed25519, ES256, ES384, RS256"
|
|
}.SetDefaultValue("Ed25519").FromAmong("Ed25519", "ES256", "ES384", "RS256");
|
|
|
|
var publicKeyOption = new Option<string?>("--public-key")
|
|
{
|
|
Description = "Path to public key file (PEM format)"
|
|
};
|
|
|
|
var notesOption = new Option<string?>("--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.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var keyId = parseResult.GetValue(keyIdArg);
|
|
var algorithm = parseResult.GetValue(algorithmOption) ?? "Ed25519";
|
|
var publicKeyPath = parseResult.GetValue(publicKeyOption);
|
|
var notes = parseResult.GetValue(notesOption);
|
|
Environment.ExitCode = await AddKeyAsync(anchorId, keyId, algorithm, publicKeyPath, notes, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return addCommand;
|
|
}
|
|
|
|
private Command BuildRevokeCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var keyIdArg = new Argument<string>("keyId")
|
|
{
|
|
Description = "Key ID to revoke"
|
|
};
|
|
|
|
var reasonOption = new Option<string>("--reason", new[] { "-r" })
|
|
{
|
|
Description = "Reason for revocation"
|
|
}.SetDefaultValue("rotation-complete");
|
|
|
|
var effectiveOption = new Option<DateTimeOffset?>("--effective-at")
|
|
{
|
|
Description = "Effective revocation time (default: now). ISO-8601 format."
|
|
};
|
|
|
|
var forceOption = new Option<bool>("--force")
|
|
{
|
|
Description = "Skip confirmation prompt"
|
|
}.SetDefaultValue(false);
|
|
|
|
var revokeCommand = new Command("revoke", "Revoke a key from a trust anchor")
|
|
{
|
|
anchorArg,
|
|
keyIdArg,
|
|
reasonOption,
|
|
effectiveOption,
|
|
forceOption
|
|
};
|
|
|
|
revokeCommand.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var keyId = parseResult.GetValue(keyIdArg);
|
|
var reason = parseResult.GetValue(reasonOption) ?? "rotation-complete";
|
|
var effectiveAt = parseResult.GetValue(effectiveOption) ?? DateTimeOffset.UtcNow;
|
|
var force = parseResult.GetValue(forceOption);
|
|
Environment.ExitCode = await RevokeKeyAsync(anchorId, keyId, reason, effectiveAt, force, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return revokeCommand;
|
|
}
|
|
|
|
private Command BuildRotateCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var oldKeyIdArg = new Argument<string>("oldKeyId")
|
|
{
|
|
Description = "Old key ID to replace"
|
|
};
|
|
|
|
var newKeyIdArg = new Argument<string>("newKeyId")
|
|
{
|
|
Description = "New key ID"
|
|
};
|
|
|
|
var algorithmOption = new Option<string>("--algorithm", new[] { "-a" })
|
|
{
|
|
Description = "Key algorithm: Ed25519, ES256, ES384, RS256"
|
|
}.SetDefaultValue("Ed25519").FromAmong("Ed25519", "ES256", "ES384", "RS256");
|
|
|
|
var publicKeyOption = new Option<string?>("--public-key")
|
|
{
|
|
Description = "Path to new public key file (PEM format)"
|
|
};
|
|
|
|
var overlapOption = new Option<int>("--overlap-days")
|
|
{
|
|
Description = "Days to keep both keys active before revoking old"
|
|
}.SetDefaultValue(30);
|
|
|
|
var rotateCommand = new Command("rotate", "Rotate a key (add new, schedule old revocation)")
|
|
{
|
|
anchorArg,
|
|
oldKeyIdArg,
|
|
newKeyIdArg,
|
|
algorithmOption,
|
|
publicKeyOption,
|
|
overlapOption
|
|
};
|
|
|
|
rotateCommand.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var oldKeyId = parseResult.GetValue(oldKeyIdArg);
|
|
var newKeyId = parseResult.GetValue(newKeyIdArg);
|
|
var algorithm = parseResult.GetValue(algorithmOption) ?? "Ed25519";
|
|
var publicKeyPath = parseResult.GetValue(publicKeyOption);
|
|
var overlapDays = parseResult.GetValue(overlapOption);
|
|
Environment.ExitCode = await RotateKeyAsync(anchorId, oldKeyId, newKeyId, algorithm, publicKeyPath, overlapDays, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return rotateCommand;
|
|
}
|
|
|
|
private Command BuildStatusCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var outputOption = new Option<string>("--output")
|
|
{
|
|
Description = "Output format: text, json"
|
|
}.SetDefaultValue("text").FromAmong("text", "json");
|
|
|
|
var statusCommand = new Command("status", "Show key rotation status and warnings")
|
|
{
|
|
anchorArg,
|
|
outputOption
|
|
};
|
|
|
|
statusCommand.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var output = parseResult.GetValue(outputOption) ?? "text";
|
|
Environment.ExitCode = await ShowStatusAsync(anchorId, output, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return statusCommand;
|
|
}
|
|
|
|
private Command BuildHistoryCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var keyIdOption = new Option<string?>("--key-id", new[] { "-k" })
|
|
{
|
|
Description = "Filter by specific key ID"
|
|
};
|
|
|
|
var limitOption = new Option<int>("--limit")
|
|
{
|
|
Description = "Maximum entries to show"
|
|
}.SetDefaultValue(50);
|
|
|
|
var outputOption = new Option<string>("--output")
|
|
{
|
|
Description = "Output format: text, json"
|
|
}.SetDefaultValue("text").FromAmong("text", "json");
|
|
|
|
var historyCommand = new Command("history", "Show key audit history")
|
|
{
|
|
anchorArg,
|
|
keyIdOption,
|
|
limitOption,
|
|
outputOption
|
|
};
|
|
|
|
historyCommand.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var keyId = parseResult.GetValue(keyIdOption);
|
|
var limit = parseResult.GetValue(limitOption);
|
|
var output = parseResult.GetValue(outputOption) ?? "text";
|
|
Environment.ExitCode = await ShowHistoryAsync(anchorId, keyId, limit, output, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
return historyCommand;
|
|
}
|
|
|
|
private Command BuildVerifyCommand()
|
|
{
|
|
var anchorArg = new Argument<Guid>("anchorId")
|
|
{
|
|
Description = "Trust anchor ID"
|
|
};
|
|
|
|
var keyIdArg = new Argument<string>("keyId")
|
|
{
|
|
Description = "Key ID to verify"
|
|
};
|
|
|
|
var signedAtOption = new Option<DateTimeOffset?>("--signed-at", new[] { "-t" })
|
|
{
|
|
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.SetAction(async (parseResult, ct) =>
|
|
{
|
|
var anchorId = parseResult.GetValue(anchorArg);
|
|
var keyId = parseResult.GetValue(keyIdArg);
|
|
var signedAt = parseResult.GetValue(signedAtOption) ?? DateTimeOffset.UtcNow;
|
|
Environment.ExitCode = await VerifyKeyAsync(anchorId, keyId, signedAt, ct).ConfigureAwait(false);
|
|
});
|
|
|
|
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
|
|
}
|