using System; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using static StellaOps.Localization.T; namespace StellaOps.Cryptography.Kms; public sealed partial class FileKmsClient { public async Task RotateAsync(string keyId, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(keyId); await _mutex.WaitAsync(cancellationToken).ConfigureAwait(false); try { var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: true).ConfigureAwait(false) ?? throw new InvalidOperationException(_t("crypto.kms.metadata_failed")); if (record.State == KmsKeyState.Revoked) { throw new InvalidOperationException(_t("crypto.kms.key_revoked", keyId)); } var timestamp = _timeProvider.GetUtcNow(); var versionId = $"{timestamp:yyyyMMddTHHmmssfffZ}"; var keyData = CreateKeyMaterial(record.Algorithm); try { var envelope = EncryptPrivateKey(keyData.PrivateBlob); var fileName = $"{versionId}.key.json"; var keyPath = Path.Combine(GetKeyDirectory(keyId), fileName); await WriteJsonAsync(keyPath, envelope, cancellationToken).ConfigureAwait(false); foreach (var existing in record.Versions.Where(v => v.State == KmsKeyState.Active)) { existing.State = KmsKeyState.PendingRotation; } record.Versions.Add(new KeyVersionRecord { VersionId = versionId, State = KmsKeyState.Active, CreatedAt = timestamp, PublicKey = keyData.PublicKey, CurveName = keyData.Curve, FileName = fileName, }); record.CreatedAt ??= timestamp; record.State = KmsKeyState.Active; record.ActiveVersion = versionId; await SaveMetadataAsync(record, cancellationToken).ConfigureAwait(false); return ToMetadata(record); } finally { CryptographicOperations.ZeroMemory(keyData.PrivateBlob); } } finally { _mutex.Release(); } } public async Task RevokeAsync(string keyId, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(keyId); await _mutex.WaitAsync(cancellationToken).ConfigureAwait(false); try { var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false) ?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId)); var timestamp = _timeProvider.GetUtcNow(); record.State = KmsKeyState.Revoked; foreach (var version in record.Versions) { if (version.State != KmsKeyState.Revoked) { version.State = KmsKeyState.Revoked; version.DeactivatedAt = timestamp; } } await SaveMetadataAsync(record, cancellationToken).ConfigureAwait(false); } finally { _mutex.Release(); } } }