50 lines
2.2 KiB
C#
50 lines
2.2 KiB
C#
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
|
|
namespace ScadaLink.ConfigurationDatabase;
|
|
|
|
/// <summary>
|
|
/// EF Core value converter that encrypts a string column at rest using ASP.NET
|
|
/// Data Protection. Plaintext is protected when written to the database and
|
|
/// transparently unprotected when read back, so secret-bearing columns
|
|
/// (SMTP credentials, external-system auth config, database connection strings)
|
|
/// are never persisted verbatim.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The protector is purpose-scoped so ciphertext from one column cannot be
|
|
/// unprotected as another. Data Protection keys are persisted to the
|
|
/// configuration database itself (see <see cref="ScadaLinkDbContext"/> implementing
|
|
/// <c>IDataProtectionKeyContext</c>), so all central nodes share the same key ring
|
|
/// and can decrypt each other's writes.
|
|
/// </remarks>
|
|
public sealed class EncryptedStringConverter : ValueConverter<string?, string?>
|
|
{
|
|
/// <summary>The Data Protection purpose string shared by all encrypted configuration columns.</summary>
|
|
public const string ProtectorPurpose = "ScadaLink.ConfigurationDatabase.EncryptedColumn";
|
|
|
|
public EncryptedStringConverter(IDataProtector protector)
|
|
: base(
|
|
plaintext => plaintext == null ? null : protector.Protect(plaintext),
|
|
ciphertext => ciphertext == null ? null : Unprotect(protector, ciphertext))
|
|
{
|
|
}
|
|
|
|
private static string Unprotect(IDataProtector protector, string ciphertext)
|
|
{
|
|
// A row that predates encryption (or test fixtures inserting raw text) is not valid
|
|
// protected payload. Unprotect throws CryptographicException in that case; surface a
|
|
// clearer message rather than a bare crypto failure.
|
|
try
|
|
{
|
|
return protector.Unprotect(ciphertext);
|
|
}
|
|
catch (System.Security.Cryptography.CryptographicException ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Failed to decrypt an encrypted configuration column. The Data Protection key " +
|
|
"ring may be unavailable, or the stored value was not written by this system.",
|
|
ex);
|
|
}
|
|
}
|
|
}
|