unit uVerifactuCrypto;

interface

uses
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.Comp.DataSet, FireDAC.Comp.Client, uFireDacHelper,
  System.SysUtils, Data.DB;

type
  TVerifactuCrypto = class(TObject)
  private
    class function HexToBytes(const Hex: string): TBytes;
  public
    // --- FUNCIONES PBLICAS ---

    {
      Recupera la informacin del certificado de la empresa desde la BD.
      Devuelve True si se encuentra la configuracin.
    }
    class function GetEmpresaCertInfo(AConexion: TFDConnection; AIDEmpresa: Integer; out ACertThumbprint, ACertStore: string): Boolean;

    {
      Calcula el hash SHA-256 de una cadena de texto.
      Devuelve la representacin hexadecimal del hash.
    }
    class function CalcularSHA256(const AInput: string): string;

    {
      Firma un hash utilizando el certificado especificado por su huella digital y almacn.
      Devuelve la firma en formato Base64.
    }
    class function FirmarHash(const AHashHex: string; const ACertThumbprint, ACertStore: string): string;
  end;

implementation

uses
  System.Classes, System.Hash, System.NetEncoding, Winapi.Windows, Winapi.WinCrypt; // Necesario para TFDQuery

{ TVerifactuCrypto }

class function TVerifactuCrypto.HexToBytes(const Hex: string): TBytes;
var
  I: Integer;
begin
  // Convierte una cadena hexadecimal (ej: '4A3B') a un array de bytes
  SetLength(Result, Length(Hex) div 2);
  for I := 0 to Length(Result) - 1 do
  begin
    Result[I] := StrToInt('$' + Copy(Hex, I * 2 + 1, 2));
  end;
end;

class function TVerifactuCrypto.GetEmpresaCertInfo(AConexion: TFDConnection;
  AIDEmpresa: Integer; out ACertThumbprint, ACertStore: string): Boolean;
var
  ZQ: TFDQuery;
begin
  Result := False;
  ACertThumbprint := '';
  ACertStore := '';
  ZQ := TFDQuery.Create(nil);
  try
    ZQ.Connection := AConexion;
    // Asumo que la tabla se llama 'empresa' y el id 'id_empresa'
    ZQ.SQL.Text := 'SELECT verifactu_cert_thumbprint, verifactu_cert_store FROM empresa WHERE id_empresa = :pid';
    ZQ.ParamByName('pid').AsInteger := AIDEmpresa;
    ZQ.Open;
    if not ZQ.IsEmpty then
    begin
      ACertThumbprint := ZQ.FieldByName('verifactu_cert_thumbprint').AsString;
      ACertStore := ZQ.FieldByName('verifactu_cert_store').AsString;
      // La configuracin es vlida solo si ambos campos estn rellenos
      Result := (Trim(ACertThumbprint) <> '') and (Trim(ACertStore) <> '');
    end;
  finally
    ZQ.Free;
  end;
end;

class function TVerifactuCrypto.CalcularSHA256(const AInput: string): string;
begin
  // Usamos la clase de Hashing nativa de Delphi. Es simple, moderna y segura.
  Result := THashSHA256.GetHashString(AInput, TEncoding.UTF8);
end;

class function TVerifactuCrypto.FirmarHash(const AHashHex: string;
  const ACertThumbprint, ACertStore: string): string;
var
  hCertStore: HCERTSTORE;
  pCertContext: PCCERT_CONTEXT;
  CertThumbprintBytes: TBytes;
  CertHashBlob: TCryptBlob;
  hCryptProv: HCRYPTPROV_OR_NCRYPT_KEY_HANDLE;
  dwKeySpec: DWORD;
  bMustFreeProv: BOOL;
  hHash: HCRYPTHASH;
  rgbSignature: PByte;
  dwSigLen: DWORD;
  StoreLocation: DWORD;
begin
  Result := '';
  pCertContext := nil;
  hCertStore := nil;
  hCryptProv := 0;

  // 1. Determinar la ubicacin del almacn de certificados
  if SameText(ACertStore, 'LocalMachine') then
    StoreLocation := CERT_SYSTEM_STORE_LOCAL_MACHINE
  else
    StoreLocation := CERT_SYSTEM_STORE_CURRENT_USER; // Opcin por defecto

  // 2. Abrir el almacn 'MY' (Personal) de Windows
  hCertStore := CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, StoreLocation, 'MY');
  if hCertStore = nil then RaiseLastOSError;

  try
    // 3. Buscar el certificado por su huella digital (Thumbprint)
    CertThumbprintBytes := HexToBytes(ACertThumbprint);
    CertHashBlob.cbData := Length(CertThumbprintBytes);
    CertHashBlob.pbData := @CertThumbprintBytes[0];

    pCertContext := CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
      0, CERT_FIND_HASH, @CertHashBlob, nil);
    if pCertContext = nil then
      raise Exception.CreateFmt('Certificado no encontrado. Verifique la huella digital y el almacn. Huella: %s', [ACertThumbprint]);

    // 4. Adquirir el manejador de la clave privada del certificado
    if not CryptAcquireCertificatePrivateKey(pCertContext, CRYPT_ACQUIRE_CACHE_FLAG, nil,
      hCryptProv, dwKeySpec, bMustFreeProv) then RaiseLastOSError;

    try
      // 5. Crear un objeto Hash (la API de firma lo necesita)
      if not CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, hHash) then RaiseLastOSError;
      try
        // 6. Cargar el valor del hash que ya calculamos
        if not CryptSetHashParam(hHash, HP_HASHVAL, HexToBytes(AHashHex), 0) then RaiseLastOSError;

        // 7. Firmar el hash. Primero se pide el tamao del buffer...
        dwSigLen := 0;
        if not CryptSignHash(hHash, dwKeySpec, nil, 0, nil, dwSigLen) then RaiseLastOSError;
        GetMem(rgbSignature, dwSigLen);
        try
          // ... y luego se obtiene la firma
          if not CryptSignHash(hHash, dwKeySpec, nil, 0, rgbSignature, dwSigLen) then RaiseLastOSError;

          // 8. Codificar la firma en Base64 para guardarla en la BD
          Result := TNetEncoding.Base64.EncodeBytesToString(rgbSignature, dwSigLen);
        finally
          FreeMem(rgbSignature);
        end;
      finally
        CryptDestroyHash(hHash);
      end;
    finally
      if bMustFreeProv then CryptReleaseContext(hCryptProv, 0);
    end;
  finally
    // Liberar todos los recursos es CRTICO para evitar fugas de memoria
    if pCertContext <> nil then CertFreeCertificateContext(pCertContext);
    if hCertStore <> nil then CertCloseStore(hCertStore, 0);
  end;
end;

end.
