Firma Digital
¿Qué es la firma digital en CFE?
Section titled “¿Qué es la firma digital en CFE?”DGI exige que cada comprobante fiscal electrónico esté firmado digitalmente con una firma XML (XML Signature) enveloped: un nodo ds:Signature dentro del CFE, que referencia el documento completo (URI="") con las transformadas enveloped-signature y C14N, y que incluye el certificado en KeyInfo. Es el mismo formato que usan los ejemplos oficiales de la DGI. Además, cada mensaje SOAP se firma con WS-Security (el certificado viaja en un BinarySecurityToken y se firma el Body).
El algoritmo es RSA-SHA256 por defecto; si la DGI rechaza SHA-256 en tu ambiente podés usar AlgoritmoFirma = AlgoritmoFirma.Sha1, que es el que usan los ejemplos de la documentación.
UruFactura maneja toda la complejidad de la firma de forma automática. Solo necesitás configurar el certificado: la ruta y la contraseña del .p12, o un X509Certificate2 ya cargado.
Certificados soportados
Section titled “Certificados soportados”UruFactura acepta certificados digitales en formato:
.p12(PKCS#12).pfx(Personal Information Exchange — equivalente a.p12en Windows)
El certificado debe estar emitido por una Autoridad Certificante reconocida por AGESIC y vinculado al RUT del emisor.
Proveedores de certificados en Uruguay
Section titled “Proveedores de certificados en Uruguay”| Proveedor | Sitio |
|---|---|
| ABITAB | abitab.com.uy |
| Correo Uruguayo | correo.com.uy |
| CertiSur | certisur.com |
Cuándo se firma
Section titled “Cuándo se firma”EmitirAsync firma el CFE al emitirlo; el resultado queda en emitido.Xml. Después, EnviarAsync arma el sobre EnvioCFE (carátula con el certificado + los CFE firmados) y lo envía en un mensaje SOAP firmado con WS-Security al servicio ws_efactura. El reporte diario también se firma.
var emitido = await client.EmitirAsync(cfe); // XML firmado en emitido.Xmlawait client.EnviarAsync(emitido); // sobre + WS-SecurityEl código de seguridad que se imprime bajo el QR es el hash (DigestValue) de la firma: emitido.CodigoSeguridad.
Certificado en Azure Key Vault o en un HSM
Section titled “Certificado en Azure Key Vault o en un HSM”En lugar de una ruta, podés pasar el certificado ya cargado (con su clave privada, que puede estar en un proveedor remoto). Se usa para firmar los CFE, el reporte, WS-Security y el TLS con la DGI:
X509Certificate2 certificado = /* p. ej. CertificateClient.DownloadCertificate(...) de Azure.Security.KeyVault.Certificates */;
var config = new UruFacturaConfig{ Emisor = emisor, Certificado = certificado, // el cliente no lo libera: es tuyo};Validar el certificado antes de operar
Section titled “Validar el certificado antes de operar”El cliente carga y verifica el certificado al construirse, así que un problema aparece al iniciar la aplicación:
try{ using var client = new UruFacturaClient(config); Console.WriteLine("✅ Certificado cargado correctamente");}catch (FirmaDigitalException ex) // archivo inexistente, contraseña incorrecta o sin clave privada{ Console.Error.WriteLine($"❌ {ex.Message}");}Verificar la firma de un CFE
Section titled “Verificar la firma de un CFE”Para verificar un CFE firmado (por ejemplo, uno recibido de un proveedor):
X509Certificate2? firmante = FirmaDigital.VerificarFirma(xmlFirmado);if (firmante is null) throw new Exception("La firma no es válida.");
// La firma solo prueba que el XML no cambió desde que lo firmó el titular del certificado, que viene// en el propio XML: comprobá que el certificado sea del emisor (por ejemplo, que su sujeto contenga// el RUT de RUCEmisor) y, si corresponde, su cadena de confianza.if (!firmante.Subject.Contains(rutEmisor)) throw new Exception("El CFE no está firmado por el emisor.");Comprueba que haya exactamente una firma, hija directa de la raíz, que referencie el documento completo, que incluya el certificado y que sea criptográficamente válida con él. Devuelve ese certificado, o null si algo falla.
Gestión segura del certificado
Section titled “Gestión segura del certificado”Recomendaciones de seguridad
Section titled “Recomendaciones de seguridad”-
No subas el archivo
.p12al repositorio. Agregalo a.gitignore:*.p12*.pfx -
Usá variables de entorno para la contraseña:
PasswordCertificado = Environment.GetEnvironmentVariable("CERT_PASSWORD")! -
En producción, almacená el certificado en un gestor de secretos:
- Azure Key Vault (certificados)
- AWS Secrets Manager
- HashiCorp Vault
-
Rotá el certificado antes de su vencimiento. Los certificados generalmente vencen cada 1 a 3 años.
-
Usá permisos restrictivos en el archivo del certificado:
Terminal window chmod 600 /ruta/certificado.p12chown app:app /ruta/certificado.p12