wiki:actividades_2018_pruebas

Version 24 (modified by pbuitrago, 6 years ago) (diff)

--

Adecuación del portal de Murachí para firmar y verificar documentos firmados electrónicamente usando certificados digitales


Los archivos .p12 o .pfx es una formar de almacenar los certificados digitales juntos con la clave privada, estos archivos contiene información necesario para que el usuario pueda realizar firmas electrónicas.

El consumo del servicio de Murachí en principio se realizado usando certificados desde dispositivo criptográficos, el proceso de firma electrónica del servicio Murachí se realiza por parte, una parte se va a realizar en el cliente que es donde se encuentra el dispositivo criptográfico y otra parte se va a realizar en el servidor de Murachí

Como se puede observar en el diagrama de secuencia del lado del cliente se van a realizar 3 procesos:

  • Subir documento pdf: En este paso se carga al sistema Murachí el archivo usando el recurso “:/Murachi/0.1/archivos/” y se obtiene como respuesta el identificador único del archivo pdf (:identificador_unico_de_archivo_pdf).
  • Preparar firma del archivo: En este caso se envía el certificado firmante al sistema Murachí junto con otros parámetros necesarios para la firma electrónica. En esta ocasión se utiliza biblioteca de JavaScript? Forge y crytpto. Con esta librería podemos gestionar los archivos .p12 o .pfx
  • Completar la firma del archivo: En este paso se va a enviar al sistema Murachí el hash cifrado utilizando el recurso :Murachi/0.1/archivos/pdfs/resenas, para completar la firma electrónica. Aquí se utiliza de nuevo la librería JavaScript? Forge y Crytpto para acceder al archivo y firmar el hash que se obtuvo en la sección anterior, usando el recurso :firmar_hash(sign). Obtenemos del sistema Murachí el identificador único del archivo firmado.

Como el servicio de firma electrónica se realiza en varios pasos vamos a dividir el ejercicio en secciones.


Sección 1: Subir documento pdf

En la primera sección se envía al sistema Murachí el archivo pdf que se va a firmar. Entonces para esta sección se requiere de un formulario que permita tomar del sistema de archivo documentos pdf y luego enviarlo con el método $.ajax usando el recurso /Murachi/0.1/archivos/

Característica del formulario HTML


Control:

  • Botón de envío (Submit button)
  • Botón de reinicializando (Reset button)
  • Selección de fichero (file select)

Atributos:

  • action: función de javaScript que procesa el formulario para esta actividad la función es “SignFilePDF()”
  • enctype: este atributo específica el tipo de contenido que sera usado para enviar los datos al servidor, para esta actividad se utilizará “multipart/form-data”


Código del formulario HTML:

<form enctype="multipart/form-data" method="post" id="firmar" name="SignFormat">
    <h2>Firmar electr&oacute;nica (PDF)</h2>

    <h3>Seleccione el archivo que va a firmar electr&oacute;nicamente</h2> 
       
    <br>
    <input id="file-sign" class="file" type="file" data-min-file-count="1" name="upload" accept=".pdf" >

    <br>
</form>

<button type="button" name="buttonsubmit3" id="buttonsubmit3" onclick="SignFilePDF();" class="btn btn-primary btn-lg">Firmar</button>

<button type="reset" id="reset"  class="btn btn-default">Limpiar</button>

<br>
<br>
<div id="seccion1"> </div>
<br>
<div id="seccion2"> </div>
<br>
<div id="seccion3"> </div>
<br>
<div id="seccion4"> </div>


Los elementos <div> del formulario con id = "sección1", “sección2” y “sección3” se utiliza para mostrar los resultados correspondientes a los 3 procesos descritos para la firma electrónica (consumo del servicio a Murachí).

Como se va a utilizar certificado con soporte a software se requiere tener en la pagina una sección que me permita cargar a la pagina el archivo .p12 o .pxf


Código del formulario HTML:

<div>
  <label for="pfx">Seleccione el archivo PFX / P12::</label><br>

  <input name="pfx" class="form-control" type="file" id="pfx" accept=".pfx,.p12" required /><br>

  <label for="pfxp">Ingrese la contraseña de clave privada:</label><br>

  <input name="pfxp" class="form-control" type="password" id="pfxp" /><br>
</div>


Ahora vamos a necesitar una función en javascript que me permita usar la librería Forge para cargar y acceder al archivo .p12 o .pfx y obtener la información necesaria para realizar el proceso de firma electrónica.


Función getCertificate(e)

function getCertificate(e)
  {
  console.log("*... getCertificate ...*");

  // Get PFX
  var fileInput = document.getElementById('pfx');

  var file = fileInput.files[0];

  // Read it
  var reader = new FileReader();

  reader.onload = function(e)  {
    console.log("*.... cargo el archivo .p12 ....*");

    var contents = e.target.result;

    pkcs12Der = arrayBufferToString(contents);

    pkcs12B64 = forge.util.encode64(pkcs12Der);

    var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);

    var password = $('#pfxp').val();

    pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);

    // load keys
    for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) 
      {

      var safeContents = pkcs12.safeContents[sci];

      for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) 
        {
        var safeBag = safeContents.safeBags[sbi];

        if(safeBag.type === forge.pki.oids.keyBag) 

          {
          //Found plain private key
          privateKey = safeBag.key;

          } 
        else 
        if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) 

          {
          // found encrypted private key
          privateKey = safeBag.key;

          } 
        else 
        if(safeBag.type === forge.pki.oids.certBag) 

          {
          // this bag has a certificate...
          certCount = certCount +1; 


          if(certP12bag == null) 

            {
            certP12bag = safeBag.cert;

            certpem = "" +  forge.pki.certificateToPem(certP12bag).toString();

            certBytesHex = forge.util.bytesToHex(certpem);

            }

          } 

        }

      }

    }

    reader.readAsArrayBuffer(file);

  }


Con esta función obtenemos la clave privada almacenada en la variable privateKey y el certificado almacenado en la variable certBytesHex.

La clave privada es un conjunto de bytes que debemos convertir a string, para esto se tiene la siguiente función


function arrayBufferToString( buffer )

  {
  var binary = ''

  var bytes = new Uint8Array( buffer )

  var len = bytes.byteLength

  for (var i = 0; i < len; i++)

    {
    binary += String.fromCharCode( bytes[ i ] )

    }
  return binary

  }


Función SignFilePDF() Sección 1. Subir documento pdf

En esta sección la función procesa los datos del formulario. Vamos a cargar el archivo .p12 o .pfx y usando la función getCertificate(e), y se obtiene el archivo pdf seleccionado en el formulario a través del método document.getElementById("file-sign") donde “file-sign” corresponde al id del elemento input de tipo “file” del formulario. Luego se almacena en un objeto de tipo FormData? que permite el envío del archivo al servidor usando el método $.ajax

Opciones de configuración de la petición $.ajax:

  • ulr: Establece la URL en donde se realiza la petición, para esta sección es "https://murachi.cenditel.gob.ve/Murachi/0.1/archivos".
  • type: Establece el tipo de petición, para esta actividad vamos a utilizar "POST".
  • dataType: Establece el formato de la respuesta que es permitido, si el servidor devuelve información con un formato diferente al especificado el código fallará. Para este proceso se establece “json”.
  • data: Establece la información que se enviará al servidor. Para este proceso se envía el archivo almacenado en una variable de tipo FormaData?.
  • contentType: Establece el tipo de codificación que se va a utilizar, para esta actividad es "application/json".
  • processData: Establece si la información que se envía al servidor debe ser procesada a una cadena de caracteres. Para evitar esto se debe utilizar el valor “false”. En esta sección sólo se quiere enviar el archivo sin ser procesado.
  • xhrFields: {withCredentials: true} esta característica puede usarse para establecer la propiedad de withCredentials. Si se establece TRUE permite que se pase los cookies al servidor y permitir solicitudes de dominios cruzados.
  • headers: {"Authorization":"Basic YWRtaW46YWRtaW4="} autenticación básica HTTP


Código de la función javaScript "SignFilePDF()" sección 1

function SignFilePDF() {

  //cargar el archivo .p12

      getCertificate();

      var fileInput = document.getElementById("file-sign");

      var list = fileInput.files;

      var form = $('firmar')[0];

      var data = new FormData();

      data.append('upload', $("#file-sign")[0].files[0]);

      $.ajax({   
           
        url: "https://murachi.cenditel.gob.ve/Murachi/0.1/archivos",

        type: "post",

        dataType: "json",

        data: data,

        cache: false,

        contentType: false,

        processData: false,

        xhrFields: {withCredentials: true},

        headers: {"Authorization":"Basic YWRtaW46YWRtaW4="},

        success: function(response) {

            var responseString = JSON.stringify(response); 
                                     
            document.getElementById("seccion1").innerHTML = responseString;

        }

)}


La repuesta de esta sección se procesa por el método .done a través del argumento response donde podemos acceder al valor del JSON con la función JSON.stringify.


Código completo de la sección 1:

<!doctype html>

<html>

<head>

<meta charset="utf-8">

    <title>Javascript firma p12 Demo</title>

    <script type="text/javascript" src="forge.min.js"></script>
</head>

<script>

//Funciones de conversión

function arrayBufferToString( buffer ) 
  {
  var binary = '';
  var bytes = new Uint8Array( buffer );
  var len = bytes.byteLength;
  for (var i = 0; i < len; i++) 
    {
    binary += String.fromCharCode( bytes[ i ] );
    }
  return binary;
  }

//function que obtengo la clave privada y el certificado del archivo .p12
function getCertificate(e)
  {
  // Get PFX
  var fileInput = document.getElementById('pfx');
  var file = fileInput.files[0];
  // Read it
  var reader = new FileReader();
  reader.onload = function(e)  {
    var contents = e.target.result;
    pkcs12Der = arrayBufferToString(contents);
    pkcs12B64 = forge.util.encode64(pkcs12Der);
    var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
    var password = $('#pfxp').val();
    
    pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
    // load keys
    for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) 
      {
      var safeContents = pkcs12.safeContents[sci];
      for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) 
        {
        var safeBag = safeContents.safeBags[sbi];
        if(safeBag.type === forge.pki.oids.keyBag) 
          {
          //Found plain private key
          privateKey = safeBag.key;
          } 
        else 
        if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) 
          {
          // found encrypted private key
          privateKey = safeBag.key;
          } 
        else 
        if(safeBag.type === forge.pki.oids.certBag) 
          {
          // this bag has a certificate...
          certCount = certCount +1;           
          console.log("GET_CERTIFICATE___"+ certCount + "...certBag Ok");  

          if(certP12bag == null) 
            { 
            certP12bag = safeBag.cert; 
            certpem = "" +  forge.pki.certificateToPem(certP12bag).toString(); 
            certBytesHex = forge.util.bytesToHex(certpem); 
            } 
          } 
        }  
      } 
    } 
    reader.readAsArrayBuffer(file); 
  } 

//funcion que ejecuta el proceso de firma electrónica usando el servicio Murachí 
function SignFilePDF() {  
  //cargar el archivo .p12 
  getCertificate(); 
    var fileInput = document.getElementById("file-sign"); 
    var list = fileInput.files; 
    var form = $('firmar')[0]; 
    var data = new FormData(); 
    data.append('upload', $("#file-sign")[0].files[0]); 
    $.ajax({               
        url: "https://murachi.cenditel.gob.ve/Murachi/0.1/archivos", 
        type: "post", 
        dataType: "json", 
        data: data, 
        cache: false, 
        contentType: false, 
        processData: false,
        xhrFields: {withCredentials: true},
        headers: {"Authorization":"Basic YWRtaW46YWRtaW4="},
        success: function(response) { 
            var responseString = JSON.stringify(response);                                      
            document.getElementById("seccion1").innerHTML = responseString;
           }
    )}


La respuesta del servicio Murachí es el identificador único del archivo pdf, la respuesta es un json.

Ejemplo: {"containerId":"b43ea1f0-2c1a-463e-a0b8-1804e2041f33"}

Sección 2: Preparar firma del archivo

En esta sección se va a calcular el valor del hash del documento que se envió en la sección 1, en tal sentido antes del cálculo del hash se debe enviar al servidor Murachí toda la información que se agregará al documento tales como el certificado del firmante como información adicional necesaria para la firma electrónica:

  • fileId: corresponde al identificador del archivo que se encuentra en el servidor y se desea firmar (identificador único del archivo pdf).
  • certificate: corresponde al certificado del firmante en formato hexadecimal.
  • reason: corresponde a la razón de la firma (cadena descriptiva del por qué de la firma).
  • location: corresponde a la ubicación donde se realiza la firma.
  • contact: corresponde a información de contacto del firmante.
  • signatureVisible: true para mostrar un indicador visible de firma en la primera página del documento pdf y false para no mostrar un indicador visible de firma en la primera página del documento pdf.

El certificado lo obtuvimos en la sección anterior, en la función getCertificate(). Luego enviarlo al sistema Murachí con el metodo $.ajax usando el recurso :Murachi/0.1/archivos/pdfs

document.getElementById("seccion1").innerHTML = responseString; 
            var fileId = response.fileId.toString();
            alert("antes de leer el certificado");
            if(certBytesHex != null) {
              console.log("tengo certificado");
            var cert = certBytesHex;
            console.log("certGet:"+ cert);
            var parameters = JSON.stringify({
              "fileId":fileId,
              "certificate":cert,
              "reason":"Certificado",
              "location":"CENDITEL",
              "contact":"582746574336",
              "signatureVisible":"true"
            });
            console.log("cert...3");
            $.ajax({
                type: 'POST',
                contentType: 'application/json',                
                url:"https://murachi.cenditel.gob.ve/Murachi/0.1/archivos/pdfs",
                dataType: "json",
                data: parameters,
                xhrFields: {withCredentials: true},        
                headers: {"Authorization":"Basic YWRtaW46YWRtaW4="},
                success: function(data, textStatus, jqXHR) {
                  var responseString = JSON.stringify(data);
                  document.getElementById("seccion2").innerHTML = responseString;
                        }
    )}

Si la petición tiene éxito la repuesta de esta sección se procesa a través de la función success: donde se le pase tres parámetros, data: que corresponde al resultado de la petición formateado según el valor del parámetro dataType (respuesta del servidor), textStatus: que corresponde a una cadena que describe el estado y jpXHR: que es un objeto que permite ejecutar varias funciones.


Código completo de la sección 2

<!doctype html> 
<html>
<head>
<meta charset="utf-8">
    <title>Javascript firma p12 Demo</title>
    <script type="text/javascript" src="forge.min.js"></script>
</head>

<script>

//Funciones de conversión

function arrayBufferToString( buffer ) 
{
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) 
    {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return binary;
}

//function que obtengo la clave privada y el certificado del archivo .p12
function getCertificate(e)
{
    // Get PFX
    var fileInput = document.getElementById('pfx');
    var file = fileInput.files[0];
    // Read it
    var reader = new FileReader();
    reader.onload = function(e)  {
  console.log("*.... cargo el archivo .p12 ....*");
  var contents = e.target.result;
  pkcs12Der = arrayBufferToString(contents);
  pkcs12B64 = forge.util.encode64(pkcs12Der);
  var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
  var password = $('#pfxp').val();
  pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
  // load keys
  for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) 
  {
      var safeContents = pkcs12.safeContents[sci];
      for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) 
    {
    var safeBag = safeContents.safeBags[sbi];
    if(safeBag.type === forge.pki.oids.keyBag) 
    {
        //Found plain private key
              privateKey = safeBag.key;
    } 
    else 
    if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) 
    {
            // found encrypted private key
        privateKey = safeBag.key;
    } 
    else 
        if(safeBag.type === forge.pki.oids.certBag) 
        {
      // this bag has a certificate...
      certCount = certCount +1;           
      console.log("GET_CERTIFICATE___"+ certCount + "...certBag Ok");  

      if(certP12bag == null) 
      {
          certP12bag = safeBag.cert;
          certpem = "" +  forge.pki.certificateToPem(certP12bag).toString();
          certBytesHex = forge.util.bytesToHex(certpem);
      }
        } 
    }
      }
  }
  reader.readAsArrayBuffer(file);
    }

//funcion que ejecuta el proceso de firma electrónica usando el servicio Murachí
function SignFilePDF() {
    //cargar el archivo .p12
    getCertificate();
    var fileInput = document.getElementById("file-sign");
    var list = fileInput.files;
    var form = $('firmar')[0];
    var data = new FormData();
    data.append('upload', $("#file-sign")[0].files[0]);
    $.ajax({              
        url: "https://murachi.cenditel.gob.ve/Murachi/0.1/archivos",
        type: "post",
        dataType: "json",
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        xhrFields: {withCredentials: true},
        headers: {"Authorization":"Basic YWRtaW46YWRtaW4="},
        success: function(response) {
            var responseString = JSON.stringify(response);                                      
            document.getElementById("seccion1").innerHTML = responseString;
      var fileId = response.fileId.toString();
            alert("antes de leer el certificado");
            if(certBytesHex != null) {
                var cert = certBytesHex;
    var parameters = JSON.stringify({
        "fileId":fileId,
        "certificate":cert,
        "reason":"Certificado",
        "location":"CENDITEL",
        "contact":"582746574336",
        "signatureVisible":"true"
    });
    
    $.ajax({
        type: 'POST',
        contentType: 'application/json',               
        url:"https://murachi.cenditel.gob.ve/Murachi/0.1/archivos/pdfs",
        dataType: "json",
        data: parameters,
        xhrFields: {withCredentials: true},        
        headers: {"Authorization":"Basic YWRtaW46YWRtaW4="},
        success: function(data, textStatus, jqXHR) {
          var responseString = JSON.stringify(data);
          document.getElementById("seccion2").innerHTML = responseString;
                    }
        )}
           }
    )}

La respuesta del sistema Murachí es el valor del hash del archivo pdf que se subió al servidor en la sección 1.

Ejemplo : {"hash":"a3fbfc857e0796b5e5e9fdb8e3183d8e532afe0fe662f3a24eb459f0c22bd472","error":""}


Sección 3: Completar la firma del archivo


En esta sección se firma el hash del documento a firmar con el certificado firmante almacenado en el archivo .p12 o .pfx. En tal sentido partimos desde el resultado que obtuvimos en la sección 2 (el valor del hash del archivo pdf). En este proceso de la firma del hash, con el certificado del firmante almacenado en el archivo .p12 o .pfx se han presentando diferentes inconvenientes. se ha probado las siguiente tres librerias de javaScripts: forge, hwcrypto y webcrypto. El resultado que se obtiene de este proceso no es el esperado por el servicio Murachí. Aunque el proceso de firma se termina de generar indicando que se firma el documento, pero en el proceso de verificación de firma falla, indicando que la firma no es valida, ya que el hash cifrado no corresponde al documento que se esta firmando y por lo tanto no hay integridad de dicho documento.

Prueba para el proceso de firma de hash usando diferente librerías

Attachments (2)

Download all attachments as: .zip