forked from gongxuegit/tiku-backend.net
feat: establish dotnet engineering foundation
This commit is contained in:
116
Tiku.Infrastructure/Commerce/TenantSecretEncryption.cs
Normal file
116
Tiku.Infrastructure/Commerce/TenantSecretEncryption.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
public sealed class TenantSecretEncryptionOptions
|
||||
{
|
||||
public const string SectionName = "Security:TenantSecrets";
|
||||
public const string DevelopmentMasterKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
|
||||
public string KeyId { get; set; } = string.Empty;
|
||||
|
||||
public string MasterKey { get; set; } = string.Empty;
|
||||
|
||||
public static bool BeValid(TenantSecretEncryptionOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.KeyId) || string.IsNullOrWhiteSpace(options.MasterKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.FromBase64String(options.MasterKey).Length == 32;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDevelopmentDefault(TenantSecretEncryptionOptions options) =>
|
||||
string.Equals(options.MasterKey, DevelopmentMasterKey, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
internal interface ITenantSecretProtector
|
||||
{
|
||||
ProtectedTenantSecret Protect(Guid tenantId, string secretRef, JsonElement payload);
|
||||
|
||||
JsonElement Unprotect(
|
||||
Guid tenantId,
|
||||
string secretRef,
|
||||
string keyId,
|
||||
byte[] ciphertext,
|
||||
byte[] nonce,
|
||||
byte[] tag);
|
||||
}
|
||||
|
||||
internal sealed record ProtectedTenantSecret(
|
||||
string KeyId,
|
||||
byte[] Ciphertext,
|
||||
byte[] Nonce,
|
||||
byte[] Tag);
|
||||
|
||||
internal sealed class TenantSecretProtector(
|
||||
Microsoft.Extensions.Options.IOptions<TenantSecretEncryptionOptions> options) : ITenantSecretProtector
|
||||
{
|
||||
private readonly TenantSecretEncryptionOptions options = options.Value;
|
||||
|
||||
public ProtectedTenantSecret Protect(Guid tenantId, string secretRef, JsonElement payload)
|
||||
{
|
||||
var key = Convert.FromBase64String(options.MasterKey);
|
||||
var plaintext = JsonSerializer.SerializeToUtf8Bytes(payload);
|
||||
var nonce = RandomNumberGenerator.GetBytes(12);
|
||||
var ciphertext = new byte[plaintext.Length];
|
||||
var tag = new byte[16];
|
||||
var associatedData = GetAssociatedData(tenantId, secretRef, options.KeyId);
|
||||
|
||||
try
|
||||
{
|
||||
using var aes = new AesGcm(key, tag.Length);
|
||||
aes.Encrypt(nonce, plaintext, ciphertext, tag, associatedData);
|
||||
return new ProtectedTenantSecret(options.KeyId, ciphertext, nonce, tag);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(key);
|
||||
CryptographicOperations.ZeroMemory(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonElement Unprotect(
|
||||
Guid tenantId,
|
||||
string secretRef,
|
||||
string keyId,
|
||||
byte[] ciphertext,
|
||||
byte[] nonce,
|
||||
byte[] tag)
|
||||
{
|
||||
if (!string.Equals(keyId, options.KeyId, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Tenant secret uses unknown encryption key '{keyId}'.");
|
||||
}
|
||||
|
||||
var key = Convert.FromBase64String(options.MasterKey);
|
||||
var plaintext = new byte[ciphertext.Length];
|
||||
var associatedData = GetAssociatedData(tenantId, secretRef, keyId);
|
||||
try
|
||||
{
|
||||
using var aes = new AesGcm(key, tag.Length);
|
||||
aes.Decrypt(nonce, ciphertext, tag, plaintext, associatedData);
|
||||
using var document = JsonDocument.Parse(plaintext);
|
||||
return document.RootElement.Clone();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(key);
|
||||
CryptographicOperations.ZeroMemory(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetAssociatedData(Guid tenantId, string secretRef, string keyId) =>
|
||||
Encoding.UTF8.GetBytes($"{tenantId:N}\n{secretRef}\n{keyId}");
|
||||
}
|
||||
Reference in New Issue
Block a user