part #2
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Cryptography.Kms;
|
||||
|
||||
public sealed partial class FileKmsClient
|
||||
{
|
||||
public async Task<KmsKeyMetadata> 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("Failed to create or load key metadata.");
|
||||
|
||||
if (record.State == KmsKeyState.Revoked)
|
||||
{
|
||||
throw new InvalidOperationException($"Key '{keyId}' has been revoked and cannot be rotated.");
|
||||
}
|
||||
|
||||
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($"Key '{keyId}' does not exist.");
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user