Por motivos de fuerza mayor estuve ausente unos dias...
Así que voy a intentar clarificar unas ideas que hace tiempo escribí, a fin de que sean útiles a la comunidad
Lo que aquí expondré es producto de la investigación en posibilidades y herramientas para poder firmar electronicamente documentos,
desde nuestros programas harbour/Fivewin, especialmente en formato XML enfocado a la facturación electrónica
Decir también que la firma digital es una tarea compleja, que intervienen multitud de variables y hacerlo de forma nativa en harbour,
si bien fue una de mis motivaciones iniciales a partir de la infraestructura proporcionada por Windows y sin dependencias externas, me resultó una labor titánica
que he abandonado, por falta de tiempo y seguramente de preparación
En la parte final de este post, describiré el proceso por el cual me llevó al desafío de la firma nativa en harbour
y dejaré unas directrices por si alguien quiere continuar ...
De forma práctica, voy a detallar las herramientes que utilizo o tengo cierto conocimiento, para delegar este tema de la firma, con utilidades de terceros
Cualquier nueva información, herramientas o métodos utilizados, será muy bienvenida para ampliar esta información
Por supuesto, ante cualquier error u omisión, rectifíquenme por favor !
Fijemos algunos conceptos ...
Qué es una firma electrónica ?
Es un fichero que contiene información sobre el documento original, el firmante, la fecha de la firma, algoritmos utilizados y posible caducidad de la firma
Formatos principales de firma
- CAdES (CMS Avanzado) : Es la evolución del primer formato de firma estandarizado. Es apropiado para firmar ficheros grandes, especialmente si la firma contiene el documento original porque optimiza el espacio de la información. Tras firmar, no podrás ver la información firmada, porque la información se guarda de forma binaria
- XAdES (XML Avanzado) : El resultado es un fichero de texto XML, un formato de texto muy similar al HTML que utiliza etiquetas. Los documentos obtenidos suelen ser más grandes que en el caso de CAdES, por eso no es adecuado cuando el fichero original es muy grande. Aplicaciones como eCoFirma o facturae de las Administraciones Públicas en España firman en XAdES
- PAdES (PDF Avanzado) : Este es el formato más adecuado cuando el documento original es un pdf. El destinatario de la firma puede comprobar fácilmente la firma y el documento firmado. Con los formatos anteriores esto no es posible si no se utilizan herramientas externas
- OOXML y ODF : Son los formatos de firma que utilizan Microsoft Office y Open Office, respectivamente
Subformatos (perfiles) de XAdES (XML Advanced Electronic Signatures, Firma electrónica avanzada XML)
- XAdES-BES, forma básica que simplemente cumple los requisitos legales de la Directiva para firma electrónica avanzada
- XAdES-EPES, forma básica a la que se le ha añadido información sobre la política de firma
- XAdES-T (timestamp), añade un campo de sellado de tiempo para proteger contra el repudio
- XAdES-C (complete), añade referencias a datos de verificación (certificados y *as de revocación) a los documentos firmados para permitir verificación y validación off-line en el futuro (pero no almacena los datos en sí mismos)
- XAdES-X (extended), añade sellos de tiempo a las referencias introducidas por XAdES-C para evitar que pueda verse comprometida en el futuro una cadena de certificados
- XAdES-X-L (extended long-term), añade los propios certificados y listas de revocación a los documentos firmados para permitir la verificación en el futuro incluso si las fuentes originales (de consulta de certificados o de las listas de revocación) no estuvieran ya disponibles
Qué necesito para firmar electrónicamente un documento ?
El documento a firmar (PDF, XML, ...)
Un certificado digital expedido por una Autoridad Certificadora, por ejemplo : https://www.sede.fnmt.gob.es/certificad ... resentante
Tipos de Certificados
Existen una multitud de certificados digitales para amplios propósitos en seguridad, voy a centrarme
en los certificados más comunes para la facturación electrónica, según X.509 que es un estándar para
infraestructura de claves publicas (PKI)
- Sintaxis definida en lenguaje ASN.1 (Abstract Syntax Notation One)
- Extensiones de archivos de certificado X.509 :
- .CER - Certificado codificado en CER (Canonical Encoding Rules), algunas veces es una secuencia de certificados
- .DER - Certificado codificado en DER (Distinguished Encoding Rules)
- .PEM - Certificado codificado en Base64, encerrado entre "-----BEGIN CERTIFICATE-----" y "-----END CERTIFICATE-----"
Puede contener certificados o claves privadas, encerrados entre las líneas BEGIN/END
- .P7B - Ver .p7c
- .P7C - Estructura PKCS#7 SignedData sin datos, solo certificado(s) o CRL(s)
- .PFX - Ver .p12
- .P12 - PKCS#12, puede contener certificado(s) (público) y claves privadas (protegido con clave)
- .CER - Certificado codificado en CER (Canonical Encoding Rules), algunas veces es una secuencia de certificados
- Por razones históricas (en la actualidad son lo mismo)
- .pfx: es la copia de seguridad con clave privada de un certificado (exportado desde Internet Explorer)
- .p12: es la copia de seguridad con clave privada de un certificado (exportado desde Firefox)
- .pfx: es la copia de seguridad con clave privada de un certificado (exportado desde Internet Explorer)
Sistemas criptográficos de clave pública
Los anteriores certificados, suelen utilizar alguno de estos algoritmos :
- RSA = Rivest, Shamir y Adleman
- ECC = Criptografía de curva elíptica
Algunos tipos de Firma
- PKCS = Public-Key Cryptography Standards. Grupo de estándares de criptografía de clave pública publicados por RSA
Conjunto de algoritmos de clave asimétrica, que es un sistema criptográfico que se caracteriza por utilizar dos claves, una clave pública y otra privada
- PKCS#1 : Estándar criptográfico RSA
- PKCS#3 : Estándar de intercambio de claves Diffie-Hellman
- PKCS#5 : Estándar de cifrado basado en contraseñas
- PKCS#7 : Usado para firmar y/o cifrar mensajes en PKI
- PKCS#10: Formato de los mensajes enviados a una Autoridad de certificación para solicitar la certificación de una clave pública
- PKCS#11: Define un API genérico de acceso a dispositivos criptográficos
- PKCS#15: https://es.wikipedia.org/wiki/PKCS
- PKCS#1 : Estándar criptográfico RSA
Utilidades del sistema (también desde cmd) para obtener información de certificados instalados
- Code: Select all Expand view RUN
rem Lista certificados instalados en el sistema, desde cmd o powershell
> certutil -user -store my
rem Acceso GUI almacen de certificados
> certmgr.exe
Herramientas y utilidades de terceros
- Autofirma
- Chilkat
- Microsoft signtool
- EZSignIt
- XolidoSign
- XML Signer (XAdES)
- OpenSSL
Autofirma
Es una herramienta con interfaz gráfica proporcionada por el Ministerio de Hacienda y Administraciones Públicas en España, que
permite la ejecución de operaciones de firma de ficheros locales en entornos de escritorio
(Windows, Linux y Mac OS X). Ofrece la posibilidad de realizar firmas de cualquier tipo de documento
Dispone de una api que puede ser llamada desde harbour (hb_processRun) o FWH (ShellExecute), sin intervención del usuario
Ejemplo de firma de factura electronica, con política de firma y certificado en fichero (formato pfx, con usuario/contraseña)
- Code: Select all Expand view RUN
cPolicy := '\npolicyIdentifier=https://www.facturae.gob.es/formato/Polticasfirma/A)%20Versi%C3%B3n%203.1/Politica_Firma_formato_facturae_v3_1.pdf';
cPolicy += '\npolicyQualifier=https://www.facturae.gob.es/formato/Polticasfirma/A)%20Versi%C3%B3n%203.1/Politica_Firma_formato_facturae_v3_1.pdf';
cPolicy += '\npolicyIdentifierHash="Ohixl6upD6av8N7pEvDABhEL6hM='
cPolicy += '\npolicyIdentifierHashAlgorithm="http://www.w3.org/2001/04/xmlenc#sha256"'
cPolicy += '\npolicyDescription="" '
autofirmaCommandLine.exe sign
-i request.xml -o request.xml.xsig
-config format="XAdES Enveloped"+ cPolicy
-format xades -algorithm SHA512withRSA
-store pkcs12:certificado.pfx -password 1234 -filter
Ejemplo de firma de fichero XML con certificado del almacen de certificados del usuario, mediante uso de alias
alias_certificado = nombre descriptivo que podemos ver con certmgr.exe
- Code: Select all Expand view RUN
autofirmaCommandLine.exe sign
-i test.xml -o test.xml.xsig
-config format="XAdES Enveloped"
-format xades
-store windows -alias "alias_certificado"
Verificar la firma, informar de toda la ruta del fichero
- Code: Select all Expand view RUN
autofirmaCommandLine.exe verify -i c:\path\test.xml.xsign
ChilKat
- Se distribuye en varios formatos, también en formato DLL ActiveX
- Es de pago pero se puede descargar y utilizar de forma gratuita en la mayoria de sus funciones
- Si es necesario registrar, se pueden unir 4 desarrolladores y comprar una licencia de 369 USD, vale lo que cuesta !
- Estamos ante la navaja suiza de la criptografia y firma para Windows (y un sinfin de utilidades más)
- Son los auténticos especialistas del tema, lo que no se pueda firmar con esta DLL dificilmente se podrá firmar con nada
- Implementan con ejemplos la facturación electrónica de varios paises
- Existen multitud de ejemplos en FoxPro y Visual Basic que facilmente son trasladables a harbour
- Code: Select all Expand view RUN
oXmlToSign := CreateObject('Chilkat_9_5_0.Xml')
// Se completa toda la lista de tags segun el formato de factura electronica de cada pais
...
// Se crea el objeto para la digestión del documento
oGen := CreateObject('Chilkat_9_5_0.XmlDSigGen')
...
// Se crea un objeto xml para añadir la firma y se añaden atributos
oObject := CreateObject('Chilkat_9_5_0.Xml')
...
// Se añade la firma al objeto y el resto de propiedades
oGen:AddObject("Signature869123-Object629337", oObject:GetXml(),"","")
...
// Se obtiene el certificado para firmar en formato PFX
oCert := CreateObject('Chilkat_9_5_0.Cert')
nSuccess := oCert:LoadPfxFile("certificado.pfx","mi_password")
// Podemos obtener el error con
? oCert:LastErrorText
// Cargar el objeto XML generado al principio y que va a ser firmado
oSbXml := CreateObject('Chilkat_9_5_0.StringBuilder')
oXmlToSign:GetXmlSb(oSbXml)
// Firma del XML
nSuccess = oGen:CreateXmlDSigSb(oSbXml)
// Podemos obtener el error con
? oCert:LastErrorText
// Guardar el XML firmado en un archivo
nSuccess := oSbXml:WriteFile("test.xml.sign","utf-8",0)
// Tambien podemos guardar a la harbour
hb_memowrit("test.xml.sign", oSbXml:GetAsString() )
Signtool
Es una utilidad en línea de comandos de Microsoft utilizada principalmente para firmar digitalmente archivos ejecutables,
controladores y otros tipos de archivos binarios en Windows, como parte de la seguridad del software y la distribución
Es útil para firmar ejecutables y asegurar la integridad y autenticidad de aplicaciones, pero no está diseñado
para firmar facturas electrónicas, que suelen estar en formatos estructurados XML
EZSignIt
Alternativa libre (y distribuible) a Signtool, ejecutable desde linea de comando y GUI
Sirve para la firma de codigo (exe, dll, ocx)
No sirve para la firma XADES XML
XolidoSign
Programa de escritorio GRATUITO para firmar, verificar y sellar con tiempo documentos electrónicamente
No he visto que pueda realizar firma para facturación electrónica
Me consta que es utilizado por notarías y registradores para firma digital, pero no lo he probado personalmente
XML Signer (XAdES)
Programa de pago para firma masiva de XML con certificados digitales X.509
Aunque estampa la firma, no he conseguido que validara para factura-e
Desconozco si se le puede llamar desde linea de comando
OpenSSL
Potente herramienta de línea de comandos y biblioteca criptográfica que se utiliza ampliamente para operaciones de cifrado,
firma y manejo de certificados. Sin embargo, con OpenSSL no es posible firmar facturas electrónicas.
El motivo principal es que normalmente se requiere del estándar XAdES (XML Advanced Electronic Signatures) y OpenSSL no está diseñado
específicamente para manejar firmas electrónicas
Sin embargo creo que nos puede ayudar en las operaciones criptográficas y de certificados, si vamos a construir por nuestra cuenta el formato XADES
Firmar con harbour sin dependencias de terceros ?
Bueno, esta es la gran pregunta que me hice al descubrir que los colegas de FoxPro y en particular Fernando Mora, firmaban XML de facturación electrónica en varios paises latinoamericanos,
sin necesidad de ninguna dependencia externa, directamente utilizando la criptografia propia de Windows (dejo el enlace en fuentes)
A partir de Windows Vista -creo- el propio sistema operativo provee de las siguientes api :
- wincrypt: API de criptografía de Windows (Cryptographic API), también conocida como CryptoAPI
- bcrypt : API de criptografía de Windows CNG (Cryptography Next Generation)
- ncrypt : API para la gestión de Claves Criptográficas CNG
- cryptui : API que proporciona una interfaz gráfica para gestionar y operar con certificados
wincrypt
- Manejo de Certificados, permite trabajar con certificados digitales X.509, proporcionando funciones para crear, importar, exportar, validar y administrar certificados
- Cifrado y Descifrado utilizando algoritmos como RSA, AES, y DES
- Generación de claves simétricas y asimétricas para su uso en operaciones de cifrado, firmas digitales y otros procesos criptográficos
- Generación de hashes seguros mediante algoritmos como SHA-1, SHA-256 y SHA-512
- Algunas funciones ya no están disponibles en sistemas Windows 10/11 y hay que utilizar CNG (bcrypt)
bcrypt
- Generación y Manejo de Claves Criptográficas
- Cifrado y Descifrado utilizando algoritmos como AES, DES, 3DES, entre otros
- Generación de hashes seguros mediante algoritmos como SHA-1, SHA-256 y SHA-512
- Funciones para crear y verificar firmas digitales, asegurando la autenticidad y no repudio de los datos
- Generación de Números Aleatorios Seguros (CRNG) esenciales en operaciones como la generación de claves
- Es el reemplazo a largo plazo de CryptoAPI
ncrypt
- Permite crear, almacenar, exportar e importar claves criptográficas, tanto simétricas como asimétricas.
- Las claves pueden ser almacenadas de manera segura utilizando proveedores de almacenamiento de claves (Key Storage Providers, KSP),
- como el Proveedor de Claves de Software de Microsoft o un Módulo de Seguridad de Hardware (HSM)
- Las claves pueden ser almacenadas de forma segura en dispositivos como el Trusted Platform Module (TPM) o HSMs, lo que asegura que las claves privadas nunca sean expuestas
- Proporciona funciones para realizar operaciones de cifrado y descifrado con algoritmos robustos, como AES, RSA, y otros.
- Interoperabilidad con Proveedores de Servicios Criptográficos (CSP)
cryptui
- Gestión de Certificados, permite ver, importar, exportar, y administrar certificados digitales a través de asistentes fáciles de usar
- Facilita la administración de certificados X.509, incluyendo la visualización de detalles como la autoridad de certificación, las fechas de expiración, y la cadena de confianza
Todas estas API las podemos encontrar en nuestro sistema operativo en forma de DLL (crypt32.dll, bcrypt.dll, ncrypt.dll, cryptui.dll )
Estan escritas en C y lógicamente deberemos de acceder con harbour a traves de su API C
También deberemos crear las librerias de importacion .lib o .a dependiendo de nuestro compilador de C
Como puerta de entrada a este mundo, dejo un ejemplo funcional para ilustrar el acceso a estas API y una forma de trabajo peculiar que mostró el amigo Manu Expósito -gracias por tu paciencia !-
en su fantástico manual : Lenguaje C para programadores Harbour
- Code: Select all Expand view RUN
/**
* Test de uso funcion hb_WinGetCertificateFromStore()
* v1.0 30-04-2024
* (c)2024 Joaquim Ferrer <quim_ferrer@yahoo.es>
*
* Dialogo de seleccion de certificados de Windows
* Uso :
* hb_WinGetCertificateFromStore( <@cCertName>, [hWnd, cTitle, cDisplay] )
*
* Params :
* cCertName = Variable dimensionada y pasada por referencia -requerida-
* hWnd = Handle de ventana, si es NIL o 0, es la ventana del escritorio
* cTitle = Titulo (interior) de la ventana -opcional-
* cDisplay = Texto (interior) de la ventana -opcional-
*
* Notas :
* La libreria Chilkat no dispone -hasta la fecha- de ninguna API de acceso al
* almacen de certificados de Windows. Mediante hb_WinGetCertificateFromStore
* es posible obtener el nombre de un certificado (Subject Name) y pasarlo a
* la funcion Chilkat.CertStore.FindCertBySubjectCN()
*/
function main()
local nError
local cCert := space(64)
local cTitle := "Header de la ventana, ERP Nombre, ..."
local cDisplay := "Texto informativo para seleccionar certificado"
// Titulo y texto por defecto
nError := hb_WinGetCertificateFromStore( @cCert, 0, cTitle, cDisplay )
if nError == 0
? "Nombre del certificado para pasar a Chilkat :"
? cCert
else
? "Error o no se ha seleccionado certificado :"
? nError
endif
return NIL
#pragma BEGINDUMP
#include <windows.h>
#include <wincrypt.h>
#include <cryptuiapi.h>
#include <hbapi.h>
#include <hbapiitm.h>
#include <hbapierr.h>
//--------------------------------------------------------------------------
// Accede al dialogo de seleccion de certificados del almacen de
// certificados de Windows
// Esta funcion puede ser llamada desde C o desde PRG
//--------------------------------------------------------------------------
int hb_WinGetCertificateFromStore( LPSTR pszString, HWND hWnd, LPCWSTR pwszTitle, LPCWSTR pwszDisplay )
{
HCRYPTPROV_LEGACY hCryptProv = 0;
HCERTSTORE hStore;
PCCERT_CONTEXT pCert;
DWORD dwDontUseColumn = CRYPTUI_SELECT_EXPIRATION_COLUMN;
DWORD dwFlags = 0; // Actualmente no se usa y debe establecerse en 0
hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM, 0, hCryptProv, CERT_SYSTEM_STORE_CURRENT_USER, L"MY" );
if (hStore)
{
pCert = CryptUIDlgSelectCertificateFromStore( hStore, hWnd,
pwszTitle, pwszDisplay, dwDontUseColumn, dwFlags, NULL );
if (pCert)
{
DWORD dwLen = 256;
CertGetNameString( pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, pszString, dwLen );
return 0;
}
}
return 1;
}
static wchar_t * AnsiToWide( const char * szString )
{
int iLen;
wchar_t * szWide;
iLen = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, szString, -1, NULL, 0 );
szWide = ( wchar_t* ) hb_xgrab( iLen * sizeof( wchar_t ) );
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, szString, -1, szWide, iLen );
return szWide;
}
//--------------------------------------------------------------------------
// Wrapper a PRG, ejemplo de llamada :
// hb_WinGetCertificateFromStore(<@cCertName>, [hWnd, cTitle, cDisplay] )
// devuelve 0 si todo va bien o 1 si hay error
//--------------------------------------------------------------------------
HB_FUNC( HB_WINGETCERTIFICATEFROMSTORE )
{
// Variable recibida y pasada por referencia
LPSTR pszString = ( LPSTR ) hb_parc( 1 );
if ( pszString )
{
// Handle de ventana, si es NULL es la ventana del escritorio
HWND hWnd = ( HWND ) hb_parptr( 2 );
// Header de la ventana
LPCSTR lpSrc = hb_parcx( 3 );
LPCWSTR pszTitle = ( LPCWSTR ) AnsiToWide( lpSrc );
// Texto de seleccion de certificado
lpSrc = hb_parcx( 4 );
LPCWSTR pwszDisplay = AnsiToWide( lpSrc );
DWORD dwError = hb_WinGetCertificateFromStore( pszString, hWnd, pszTitle, pwszDisplay );
hb_xfree( (LPVOID) pszTitle );
hb_xfree( (LPVOID) pwszDisplay );
hb_storc( pszString, 1 );
hb_retni( dwError );
}
else
hb_errRT_BASE( EG_ARG, 2010, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
#pragma ENDDUMP
Si existe dificultad en construirlas o si se quiere profundizar en este tema, he abierto repositorio público en :
https://github.com/QuimFerrer/wincrypt
Incluye librerias construidas para varios compiladores, el ejemplo anterior para harbour y otra alternativa desde fivewin, además de fuentes foxpro que me han servido de guía
Conforme disponga de tiempo iré ampliando el repositorio con funciones C y wrappers a PRG que aún necesito ordenar
Disfruten de la vida !
Fuentes
- Formatos de Firma
https://firmaelectronica.gob.es/Home/Ci ... Firma.html
- Parámetros de configuración de los formatos de firma
https://github-wiki-see.page/m/ctt-gob- ... s-de-firma
- Qué es un certificado x.509 y cómo funciona
https://www.ssldragon.com/es/blog/que-e ... ado-x-509/
- Qué es una PKI
https://www.entrust.com/es/resources/learn/what-is-pki
- Certificado X.509 Qué es y para qué se usa
https://protecciondatos-lopd.com/empres ... cado-x509/
- Autofirma
https://sede.serviciosmin.gob.es/ES-ES/ ... Firma.aspx
- ChilKat
https://www.example-code.com/foxpro/xades.asp
- Microsoft signtool
https://learn.microsoft.com/es-es/windo ... o/signtool
- EZSignIt
https://www.ssesetup.com/download.html
- XolidoSign desktop
https://www.xolido.com/lang/xolidosign/ ... descargar/
- XML Signer (XAdES)
https://www.signfiles.com/xml-signer/
- OpenSSL
https://www.openssl.org/
- Guía de Facturación Electrónica en Perú SUNAT (una buena guia para seguir)
https://fe-primer.greenter.dev/docs/sign/
- Firmar XML 100% VFP sin DLL
https://groups.google.com/g/publicesvfo ... fjumq2DgAJ