Skip to content

Firma Digital

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.


UruFactura acepta certificados digitales en formato:

  • .p12 (PKCS#12)
  • .pfx (Personal Information Exchange — equivalente a .p12 en Windows)

El certificado debe estar emitido por una Autoridad Certificante reconocida por AGESIC y vinculado al RUT del emisor.

Proveedor Sitio
ABITAB abitab.com.uy
Correo Uruguayo correo.com.uy
CertiSur certisur.com

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.Xml
await client.EnviarAsync(emitido); // sobre + WS-Security

El 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
};

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}");
}

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.


  1. No subas el archivo .p12 al repositorio. Agregalo a .gitignore:

    *.p12
    *.pfx
  2. Usá variables de entorno para la contraseña:

    PasswordCertificado = Environment.GetEnvironmentVariable("CERT_PASSWORD")!
  3. En producción, almacená el certificado en un gestor de secretos:

    • Azure Key Vault (certificados)
    • AWS Secrets Manager
    • HashiCorp Vault
  4. Rotá el certificado antes de su vencimiento. Los certificados generalmente vencen cada 1 a 3 años.

  5. Usá permisos restrictivos en el archivo del certificado:

    Terminal window
    chmod 600 /ruta/certificado.p12
    chown app:app /ruta/certificado.p12