cambios denuncias
This commit is contained in:
@@ -1,27 +1,37 @@
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using GestionaDenunciasAN.Models;
|
||||
using GestionaDenunciasAN.Services;
|
||||
using ApiDenuncias.Helpers;
|
||||
using GestionaDenuncias.Shared.Models;
|
||||
using ApiDenuncias.Services;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace ApiDenuncias.Services;
|
||||
|
||||
public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
{
|
||||
private const string ProtectedStringPrefix = "enc:v1:";
|
||||
private static readonly byte[] ProtectedBytesPrefix = Encoding.ASCII.GetBytes("enc:v1:");
|
||||
private const string DataProtectionStringPrefix = "enc:v1:";
|
||||
private const string KeyVaultStringPrefix = "enc:v2:";
|
||||
private static readonly byte[] DataProtectionBytesPrefix = Encoding.ASCII.GetBytes("enc:v1:");
|
||||
private static readonly byte[] KeyVaultBytesPrefix = Encoding.ASCII.GetBytes("enc:v2:");
|
||||
private const int AesGcmNonceSize = 12;
|
||||
private const int AesGcmTagSize = 16;
|
||||
private static readonly PropertyInfo[] ComplaintProperties = typeof(DenunciasGestiona)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(property => property.CanRead && property.CanWrite)
|
||||
.ToArray();
|
||||
|
||||
private readonly MySqlDenunciaStore _inner;
|
||||
private readonly IEncryptionKeyProvider _encryptionKeyProvider;
|
||||
private readonly IDataProtector _protector;
|
||||
|
||||
public EncryptedDenunciaStore(MySqlDenunciaStore inner, IDataProtectionProvider dataProtectionProvider)
|
||||
public EncryptedDenunciaStore(
|
||||
MySqlDenunciaStore inner,
|
||||
IEncryptionKeyProvider encryptionKeyProvider,
|
||||
IDataProtectionProvider dataProtectionProvider)
|
||||
{
|
||||
_inner = inner;
|
||||
_encryptionKeyProvider = encryptionKeyProvider;
|
||||
_protector = dataProtectionProvider.CreateProtector("ApiDenuncias.DatabaseSensitiveData.v1");
|
||||
}
|
||||
|
||||
@@ -29,31 +39,47 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
=> _inner.EnsureSchemaAsync(cancellationToken);
|
||||
|
||||
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
||||
=> (await _inner.GetAllDenunciasAsync(cancellationToken))
|
||||
.Select(UnprotectComplaint)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
return (await _inner.GetAllDenunciasAsync(cancellationToken))
|
||||
.Select(denuncia => UnprotectComplaint(denuncia, key))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
||||
=> (await _inner.GetAllFicherosAsync(cancellationToken))
|
||||
.Select(UnprotectAttachment)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
return (await _inner.GetAllFicherosAsync(cancellationToken))
|
||||
.Select(fichero => UnprotectAttachment(fichero, key))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<List<FicherosDenuncias>> GetFicherosByDenunciaAsync(int denunciaId, CancellationToken cancellationToken = default)
|
||||
=> (await _inner.GetFicherosByDenunciaAsync(denunciaId, cancellationToken))
|
||||
.Select(UnprotectAttachment)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
return (await _inner.GetFicherosByDenunciaAsync(denunciaId, cancellationToken))
|
||||
.Select(fichero => UnprotectAttachment(fichero, key))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<DenunciasGestiona?> GetDenunciaByIdAsync(int denunciaId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
var denuncia = await _inner.GetDenunciaByIdAsync(denunciaId, cancellationToken);
|
||||
return denuncia is null ? null : UnprotectComplaint(denuncia);
|
||||
return denuncia is null ? null : UnprotectComplaint(denuncia, key);
|
||||
}
|
||||
|
||||
public Task UpsertDenunciaAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken = default)
|
||||
=> _inner.UpsertDenunciaAsync(ProtectComplaint(denuncia), cancellationToken);
|
||||
public async Task UpsertDenunciaAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
await _inner.UpsertDenunciaAsync(ProtectComplaint(denuncia, key), cancellationToken);
|
||||
}
|
||||
|
||||
public Task UpsertFicherosAsync(IEnumerable<FicherosDenuncias> ficheros, CancellationToken cancellationToken = default)
|
||||
=> _inner.UpsertFicherosAsync(ficheros.Select(ProtectAttachment).ToArray(), cancellationToken);
|
||||
public async Task UpsertFicherosAsync(IEnumerable<FicherosDenuncias> ficheros, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||
await _inner.UpsertFicherosAsync(ficheros.Select(fichero => ProtectAttachment(fichero, key)).ToArray(), cancellationToken);
|
||||
}
|
||||
|
||||
public Task MarkFicherosAsUploadedAsync(
|
||||
int denunciaId,
|
||||
@@ -62,11 +88,106 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
CancellationToken cancellationToken = default)
|
||||
=> _inner.MarkFicherosAsUploadedAsync(denunciaId, fileNames, uploadedAtUtc, cancellationToken);
|
||||
|
||||
private DenunciasGestiona ProtectComplaint(DenunciasGestiona source)
|
||||
=> TransformComplaint(source, ProtectString);
|
||||
private DenunciasGestiona ProtectComplaint(DenunciasGestiona source, byte[] key)
|
||||
=> TransformComplaint(ToPersistentComplaint(source), value => ProtectString(value, key));
|
||||
|
||||
private DenunciasGestiona UnprotectComplaint(DenunciasGestiona source)
|
||||
=> TransformComplaint(source, UnprotectString);
|
||||
private DenunciasGestiona UnprotectComplaint(DenunciasGestiona source, byte[] key)
|
||||
{
|
||||
var decrypted = TransformComplaint(source, value => UnprotectString(value, key));
|
||||
return RebuildComplaintFromPayload(decrypted);
|
||||
}
|
||||
|
||||
private static DenunciasGestiona ToPersistentComplaint(DenunciasGestiona source)
|
||||
{
|
||||
return new DenunciasGestiona
|
||||
{
|
||||
// Permanentes tecnicos y de trazabilidad.
|
||||
Id_RegistroDenuncia = source.Id_RegistroDenuncia,
|
||||
Id_Denuncia = source.Id_Denuncia,
|
||||
Fecha = source.Fecha,
|
||||
Expediente_Gestiona = source.Expediente_Gestiona,
|
||||
CodigoExpedienteGestiona = source.CodigoExpedienteGestiona,
|
||||
Id_Persona_Gestiona = source.Id_Persona_Gestiona,
|
||||
Etiqueta = source.Etiqueta,
|
||||
Estado = source.Estado,
|
||||
Confidencial = source.Confidencial,
|
||||
EsActualizacion = source.EsActualizacion,
|
||||
ProcedureId = source.ProcedureId,
|
||||
GroupId = source.GroupId,
|
||||
NombreDenuncia = source.NombreDenuncia,
|
||||
EstadoDenuncia = source.EstadoDenuncia,
|
||||
ArchivoElegido = source.ArchivoElegido,
|
||||
FechaSubidaAGestiona = source.FechaSubidaAGestiona,
|
||||
EnGestiona = source.EnGestiona,
|
||||
EnRechazada = source.EnRechazada,
|
||||
|
||||
// Payload temporal cifrado. Los campos funcionales se derivan de aqui al leer.
|
||||
CamposFormularioJson = source.CamposFormularioJson,
|
||||
TextoOriginalReport = source.TextoOriginalReport
|
||||
};
|
||||
}
|
||||
|
||||
private static DenunciasGestiona RebuildComplaintFromPayload(DenunciasGestiona stored)
|
||||
{
|
||||
var rebuilt = TryParseStoredReport(stored) ?? new DenunciasGestiona();
|
||||
|
||||
if (rebuilt.Id_Denuncia == 0)
|
||||
{
|
||||
rebuilt.Id_Denuncia = stored.Id_Denuncia;
|
||||
}
|
||||
|
||||
if (rebuilt.Fecha == DateTime.MinValue)
|
||||
{
|
||||
rebuilt.Fecha = stored.Fecha;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rebuilt.CamposFormularioJson))
|
||||
{
|
||||
rebuilt.CamposFormularioJson = stored.CamposFormularioJson;
|
||||
}
|
||||
|
||||
rebuilt.TextoOriginalReport = stored.TextoOriginalReport;
|
||||
ApplyPersistentTechnicalFields(rebuilt, stored);
|
||||
return rebuilt;
|
||||
}
|
||||
|
||||
private static DenunciasGestiona? TryParseStoredReport(DenunciasGestiona stored)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(stored.TextoOriginalReport))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return ReportParser.ParseReport(stored.TextoOriginalReport);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyPersistentTechnicalFields(DenunciasGestiona target, DenunciasGestiona stored)
|
||||
{
|
||||
target.Id_RegistroDenuncia = stored.Id_RegistroDenuncia;
|
||||
target.Id_Denuncia = stored.Id_Denuncia == 0 ? target.Id_Denuncia : stored.Id_Denuncia;
|
||||
target.Expediente_Gestiona = stored.Expediente_Gestiona;
|
||||
target.CodigoExpedienteGestiona = stored.CodigoExpedienteGestiona;
|
||||
target.Id_Persona_Gestiona = stored.Id_Persona_Gestiona;
|
||||
target.Etiqueta = stored.Etiqueta;
|
||||
target.Estado = stored.Estado;
|
||||
target.Confidencial = stored.Confidencial || target.Confidencial;
|
||||
target.EsActualizacion = stored.EsActualizacion;
|
||||
target.ProcedureId = stored.ProcedureId;
|
||||
target.GroupId = stored.GroupId;
|
||||
target.NombreDenuncia = stored.NombreDenuncia;
|
||||
target.EstadoDenuncia = stored.EstadoDenuncia;
|
||||
target.ArchivoElegido = stored.ArchivoElegido;
|
||||
target.FechaSubidaAGestiona = stored.FechaSubidaAGestiona;
|
||||
target.EnGestiona = stored.EnGestiona;
|
||||
target.EnRechazada = stored.EnRechazada;
|
||||
}
|
||||
|
||||
private static DenunciasGestiona TransformComplaint(DenunciasGestiona source, Func<string, string> transformString)
|
||||
{
|
||||
@@ -88,7 +209,7 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
return target;
|
||||
}
|
||||
|
||||
private FicherosDenuncias ProtectAttachment(FicherosDenuncias source)
|
||||
private FicherosDenuncias ProtectAttachment(FicherosDenuncias source, byte[] key)
|
||||
{
|
||||
var content = source.Fichero ?? [];
|
||||
var hash = string.IsNullOrWhiteSpace(source.ContentSha256)
|
||||
@@ -99,56 +220,77 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
{
|
||||
Id_Fichero = source.Id_Fichero,
|
||||
Id_Tipo = source.Id_Tipo,
|
||||
Descripcion = ProtectString(source.Descripcion ?? string.Empty),
|
||||
Descripcion = ProtectString(source.Descripcion ?? string.Empty, key),
|
||||
Fecha = source.Fecha,
|
||||
Observaciones = ProtectString(source.Observaciones ?? string.Empty),
|
||||
Observaciones = ProtectString(source.Observaciones ?? string.Empty, key),
|
||||
Id_Denuncia = source.Id_Denuncia,
|
||||
NombreFichero = source.NombreFichero,
|
||||
Fichero = ProtectBytes(content),
|
||||
Fichero = ProtectBytes(content, key),
|
||||
Subido = source.Subido,
|
||||
FechaSubida = source.FechaSubida,
|
||||
ContentSha256 = hash
|
||||
};
|
||||
}
|
||||
|
||||
private FicherosDenuncias UnprotectAttachment(FicherosDenuncias source)
|
||||
private FicherosDenuncias UnprotectAttachment(FicherosDenuncias source, byte[] key)
|
||||
{
|
||||
return new FicherosDenuncias
|
||||
{
|
||||
Id_Fichero = source.Id_Fichero,
|
||||
Id_Tipo = source.Id_Tipo,
|
||||
Descripcion = UnprotectString(source.Descripcion ?? string.Empty),
|
||||
Descripcion = UnprotectString(source.Descripcion ?? string.Empty, key),
|
||||
Fecha = source.Fecha,
|
||||
Observaciones = UnprotectString(source.Observaciones ?? string.Empty),
|
||||
Observaciones = UnprotectString(source.Observaciones ?? string.Empty, key),
|
||||
Id_Denuncia = source.Id_Denuncia,
|
||||
NombreFichero = source.NombreFichero,
|
||||
Fichero = UnprotectBytes(source.Fichero ?? []),
|
||||
Fichero = UnprotectBytes(source.Fichero ?? [], key),
|
||||
Subido = source.Subido,
|
||||
FechaSubida = source.FechaSubida,
|
||||
ContentSha256 = source.ContentSha256
|
||||
};
|
||||
}
|
||||
|
||||
private string ProtectString(string value)
|
||||
private string ProtectString(string value, byte[] key)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
||||
if (string.IsNullOrWhiteSpace(value) ||
|
||||
value.StartsWith(KeyVaultStringPrefix, StringComparison.Ordinal) ||
|
||||
value.StartsWith(DataProtectionStringPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return ProtectedStringPrefix + _protector.Protect(value);
|
||||
var encrypted = EncryptBytes(Encoding.UTF8.GetBytes(value), key);
|
||||
return KeyVaultStringPrefix + Convert.ToBase64String(encrypted);
|
||||
}
|
||||
|
||||
private string UnprotectString(string value)
|
||||
private string UnprotectString(string value, byte[] key)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || !value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.StartsWith(KeyVaultStringPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
try
|
||||
{
|
||||
var encrypted = Convert.FromBase64String(value[KeyVaultStringPrefix.Length..]);
|
||||
return Encoding.UTF8.GetString(DecryptBytes(encrypted, key));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!value.StartsWith(DataProtectionStringPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _protector.Unprotect(value[ProtectedStringPrefix.Length..]);
|
||||
return _protector.Unprotect(value[DataProtectionStringPrefix.Length..]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -156,28 +298,48 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] ProtectBytes(byte[] value)
|
||||
private byte[] ProtectBytes(byte[] value, byte[] key)
|
||||
{
|
||||
if (value.Length == 0 || StartsWith(value, ProtectedBytesPrefix))
|
||||
if (value.Length == 0 ||
|
||||
StartsWith(value, KeyVaultBytesPrefix) ||
|
||||
StartsWith(value, DataProtectionBytesPrefix))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var protectedBytes = _protector.Protect(value);
|
||||
var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(protectedBytes));
|
||||
return [.. ProtectedBytesPrefix, .. base64Bytes];
|
||||
var encrypted = EncryptBytes(value, key);
|
||||
var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(encrypted));
|
||||
return [.. KeyVaultBytesPrefix, .. base64Bytes];
|
||||
}
|
||||
|
||||
private byte[] UnprotectBytes(byte[] value)
|
||||
private byte[] UnprotectBytes(byte[] value, byte[] key)
|
||||
{
|
||||
if (value.Length == 0 || !StartsWith(value, ProtectedBytesPrefix))
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (StartsWith(value, KeyVaultBytesPrefix))
|
||||
{
|
||||
try
|
||||
{
|
||||
var base64 = Encoding.ASCII.GetString(value, KeyVaultBytesPrefix.Length, value.Length - KeyVaultBytesPrefix.Length);
|
||||
return DecryptBytes(Convert.FromBase64String(base64), key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StartsWith(value, DataProtectionBytesPrefix))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var base64 = Encoding.ASCII.GetString(value, ProtectedBytesPrefix.Length, value.Length - ProtectedBytesPrefix.Length);
|
||||
var base64 = Encoding.ASCII.GetString(value, DataProtectionBytesPrefix.Length, value.Length - DataProtectionBytesPrefix.Length);
|
||||
return _protector.Unprotect(Convert.FromBase64String(base64));
|
||||
}
|
||||
catch
|
||||
@@ -186,6 +348,36 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] EncryptBytes(byte[] plainBytes, byte[] key)
|
||||
{
|
||||
var nonce = RandomNumberGenerator.GetBytes(AesGcmNonceSize);
|
||||
var cipherBytes = new byte[plainBytes.Length];
|
||||
var tag = new byte[AesGcmTagSize];
|
||||
|
||||
using var aes = new AesGcm(key, AesGcmTagSize);
|
||||
aes.Encrypt(nonce, plainBytes, cipherBytes, tag);
|
||||
|
||||
return [.. nonce, .. tag, .. cipherBytes];
|
||||
}
|
||||
|
||||
private static byte[] DecryptBytes(byte[] encryptedBytes, byte[] key)
|
||||
{
|
||||
if (encryptedBytes.Length < AesGcmNonceSize + AesGcmTagSize)
|
||||
{
|
||||
throw new CryptographicException("Payload cifrado invalido.");
|
||||
}
|
||||
|
||||
var nonce = encryptedBytes.AsSpan(0, AesGcmNonceSize);
|
||||
var tag = encryptedBytes.AsSpan(AesGcmNonceSize, AesGcmTagSize);
|
||||
var cipherBytes = encryptedBytes.AsSpan(AesGcmNonceSize + AesGcmTagSize);
|
||||
var plainBytes = new byte[cipherBytes.Length];
|
||||
|
||||
using var aes = new AesGcm(key, AesGcmTagSize);
|
||||
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
|
||||
|
||||
return plainBytes;
|
||||
}
|
||||
|
||||
private static bool StartsWith(byte[] value, byte[] prefix)
|
||||
{
|
||||
if (value.Length < prefix.Length)
|
||||
|
||||
Reference in New Issue
Block a user