Advertencia importante

 

Advertencia importante

No es recomendable manipular llaves privadas FIEL/PFX en JavaScript del lado del cliente por razones de seguridad:

  • Las llaves privadas nunca deberían exponerse en el navegador

  • JavaScript no es el entorno más seguro para operaciones criptográficas

  • El SAT tiene protocolos específicos para firma

🛡️ Alternativa Segura: Usando Web Crypto API

Para contextos NO relacionados con FIEL/SAT, puedes usar la Web Crypto API:

html
<!DOCTYPE html>
<html>
<head>
    <title>Firma Digital Básica</title>
</head>
<body>
    <textarea id="documento" rows="10" cols="50" placeholder="Escribe tu documento aquí"></textarea>
    <br>
    <button onclick="generarLlaves()">Generar Llaves</button>
    <button onclick="firmarDocumento()">Firmar Documento</button>
    <button onclick="verificarFirma()">Verificar Firma</button>
    
    <div id="resultado"></div>

    <script>
        let clavePrivada;
        let clavePublica;
        let firma;

        async function generarLlaves() {
            try {
                const keyPair = await window.crypto.subtle.generateKey(
                    {
                        name: "RSASSA-PKCS1-v1_5",
                        modulusLength: 2048,
                        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                        hash: {name: "SHA-256"}
                    },
                    true,
                    ["sign", "verify"]
                );

                clavePrivada = keyPair.privateKey;
                clavePublica = keyPair.publicKey;
                
                document.getElementById('resultado').innerHTML = 
                    '<p style="color: green;">Llaves generadas correctamente</p>';
            } catch (error) {
                console.error('Error:', error);
            }
        }

        async function firmarDocumento() {
            if (!clavePrivada) {
                alert('Primero genera las llaves');
                return;
            }

            const documento = document.getElementById('documento').value;
            const encoder = new TextEncoder();
            const data = encoder.encode(documento);

            try {
                firma = await window.crypto.subtle.sign(
                    "RSASSA-PKCS1-v1_5",
                    clavePrivada,
                    data
                );

                // Convertir a base64 para mostrar
                const firmaBase64 = btoa(String.fromCharCode(...new Uint8Array(firma)));
                document.getElementById('resultado').innerHTML = 
                    `<p>Firma generada: ${firmaBase64.substring(0, 100)}...</p>`;
            } catch (error) {
                console.error('Error firmando:', error);
            }
        }

        async function verificarFirma() {
            if (!clavePublica || !firma) {
                alert('Primero genera llaves y firma un documento');
                return;
            }

            const documento = document.getElementById('documento').value;
            const encoder = new TextEncoder();
            const data = encoder.encode(documento);

            try {
                const esValida = await window.crypto.subtle.verify(
                    "RSASSA-PKCS1-v1_5",
                    clavePublica,
                    firma,
                    data
                );

                document.getElementById('resultado').innerHTML = 
                    `<p style="color: ${esValida ? 'green' : 'red'};">
                        Firma ${esValida ? 'VÁLIDA' : 'INVÁLIDA'}
                    </p>`;
            } catch (error) {
                console.error('Error verificando:', error);
            }
        }
    </script>
</body>
</html>

📝 Para Firma FIEL/SAT (Recomendado)

Opción 1: Integración con servicios del SAT

javascript
// Ejemplo conceptual - NO funcional directo
class FirmaSAT {
    constructor() {
        this.apiUrl = 'https://api.sat.gob.mx';
    }

    async firmarCFDI(datosCFDI, certificadoPFX, password) {
        // En la práctica, esto se hace del lado del servidor
        const response = await fetch('/api/firmar-sat', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                cfdi: datosCFDI,
                pfx: certificadoPFX,
                password: password
            })
        });
        
        return await response.json();
    }
}

Opción 2: Usando librerías especializadas (Backend)

javascript
// backend/nodejs/firmador.js
const forge = require('node-forge');
const fs = require('fs');

class FirmadorSAT {
    firmarXML(xml, pfxPath, password) {
        try {
            // Leer certificado PFX
            const pfxData = fs.readFileSync(pfxPath, 'binary');
            const pfxAsn1 = forge.asn1.fromDer(pfxData);
            const pfx = forge.pkcs12.pkcs12FromAsn1(pfxAsn1, password);
            
            // Obtener llave privada y certificado
            const keyBag = pfx.getBags({bagType: forge.pki.oids.pkcs8ShroudedKeyBag});
            const certBag = pfx.getBags({bagType: forge.pki.oids.certBag});
            
            const privateKey = keyBag[forge.pki.oids.pkcs8ShroudedKeyBag][0].key;
            const certificate = certBag[forge.pki.oids.certBag][0].cert;
            
            // Firmar el XML (simplificado)
            const signature = this.generarFirmaXML(xml, privateKey);
            
            return {
                success: true,
                xmlFirmado: this.insertarFirmaXML(xml, signature, certificate),
                cadenaOriginal: this.generarCadenaOriginal(xml)
            };
        } catch (error) {
            return {success: false, error: error.message};
        }
    }

    generarCadenaOriginal(xml) {
        // Implementar según especificaciones del SAT
        return "cadena_original_generada";
    }
    
    generarFirmaXML(xml, privateKey) {
        // Lógica de firma según SAT
        return "firma_generada";
    }
    
    insertarFirmaXML(xml, firma, certificado) {
        // Insertar firma en el XML
        return xml;
    }
}

module.exports = FirmadorSAT;

🌐 Arquitectura Recomendada

text
Cliente (Navegador) → API Backend → Servicios SAT
       (JavaScript)    (Node.js/Java)   (WS/WCF)

Frontend seguro:

javascript
// frontend.js
class ClienteFirma {
    async subirArchivoFirmar(archivo) {
        const formData = new FormData();
        formData.append('archivo', archivo);
        
        const response = await fetch('/api/firmar', {
            method: 'POST',
            body: formData
        });
        
        return await response.blob();
    }
    
    async descargarArchivoFirmado(contenido, nombre) {
        const blob = new Blob([contenido], {type: 'application/xml'});
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = nombre;
        a.click();
        window.URL.revokeObjectURL(url);
    }
}

🔐 Consideraciones de Seguridad Críticas

  1. Nunca proceses PFX en el frontend

  2. Usa HTTPS siempre

  3. Valida y sanitiza todas las entradas

  4. Maneja errores adecuadamente

  5. Audita y registra todas las operaciones

📦 Librerías Útiles

Backend:

  • node-forge: Para operaciones criptográficas

  • xml-crypto: Para firmar XML

  • node-xmldom: Para manipulación XML

Frontend:

  • Web Crypto API: Para operaciones básicas

  • jsrsasign: Librería JavaScript para RSA

🚀 Ejemplo de Implementación Segura

javascript
// Ejemplo de flujo seguro
document.getElementById('btnFirmar').addEventListener('click', async () => {
    const archivo = document.getElementById('archivo').files[0];
    const firmador = new ClienteFirma();
    
    try {
        const archivoFirmado = await firmador.subirArchivoFirmar(archivo);
        firmador.descargarArchivoFirmado(archivoFirmado, 'documento_firmado.xml');
    } catch (error) {
        console.error('Error:', error);
        alert('Error al firmar el documento');
    }
});

¿Qué tipo de documentos necesitas firmar específicamente? ¿Facturas CFDI, contratos, o otro tipo de documentos? Esto me ayudaría a darte una guía más específica.

Comentarios

Entradas más populares de este blog

firma digitales PKI utilizando solo javascript y node,

convertir un certificado .key a formato PEM utilizando solo la Web Crypto API

Pilares de la firma electronica-pki-RSA