Integración Jacobitus - FIDO con aplicaciones WEB

En este tutorial, se verá como integrar Jacobitus FIDO para firmar documentos desde una aplicación WEB.

Se asume que ya se tiene instalado el aplicativo. De no ser así acceder a la siguiente página: FIDO descargar e instalar. El único requisito adicional es contar con un navegador moderno que soporte AJAX.

El ejemplo utiliza HTML y JavaScript pero podrá ser aplicado a frameworks más avanzados como Angular, simplemente consumiendo los servicios que se exponen en ApiDoc mientras Jacobitus - FIDO se encuentra en ejecución.

Para comenzar se debe crear un archivo index.html con cualquier editor de textos y escribir el siguiente código:

  1. <!DOCTYPE html>
  2. <html lang="es">
  3. <head class="page-header header container-fluid">
  4. <title>Jacobitus - Tutorial</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  8. <style>
  9. body {
  10. padding: 0;
  11. margin: 0;
  12. background: #b2b6c9;
  13. }
  14. .navbar {
  15. background:#6ab446;
  16. }
  17. .nav-link,
  18. .navbar-brand {
  19. color: #fff;
  20. cursor: pointer;
  21. }
  22. .nav-link {
  23. margin-right: 1em !important;
  24. }
  25. .nav-link:hover {
  26. color: #000;
  27. }
  28. .navbar-collapse {
  29. justify-content: flex-end;
  30. }
  31. .description {
  32. left: 50%;
  33. position: absolute;
  34. top: 45%;
  35. transform: translate(-50%, -55%);
  36. text-align: center;
  37. }
  38. .description h1 {
  39. color: #6ab446;
  40. }
  41. .description p {
  42. color: #fff;
  43. font-size: 1.3rem;
  44. line-height: 1.5;
  45. }
  46. .features {
  47. margin: 4em auto;
  48. padding: 1em;
  49. position: relative;
  50. }
  51. .feature-title {
  52. color: #333;
  53. font-size: 1.3rem;
  54. font-weight: 700;
  55. margin-bottom: 20px;
  56. text-transform: uppercase;
  57. }
  58. </style>
  59. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  60. </head>
  61. <body>
  62. <nav class="navbar navbar-expand-md">
  63. <a class="navbar-brand" href="#" onClick="listar()">Verificar Token</a>
  64. <div class="collapse navbar-collapse" id="main-navigation">
  65. <ul class="navbar-nav">
  66. <li class="nav-item">
  67. <a class="nav-link" href="#" onClick="certificados()">Listar Certificados</a>
  68. </li>
  69. <li class="nav-item">
  70. <a class="nav-link" href="#" onClick="firmar()">Firmar</a>
  71. </li>
  72. </ul>
  73. </div>
  74. </nav>
  75. <div class="description">
  76. <h1>Jacobitus - FIDO!</h1>
  77. <p>Para verificar si existe algún token conectado pulse sobre "Verficicar Token".</p>
  78. <p>Para listar los certificados pulse sobre "Listar Certificados".</p>
  79. </div>
  80. <div class="container features">
  81. <div class="row"> <div class="col-lg-4 col-md-4 col-sm-12">
  82. <h3 class="feature-title">Tokens</h3>
  83. <input value="Verificar" type="button" onClick="listar()"></button>
  84. <p id="tokens"></p>
  85. </div>
  86. <div class="col-lg-4 col-md-4 col-sm-12">
  87. <h3 class="feature-title">Certificados</h3>
  88. <div>Pin : <input id="pin" type="password"/></div>
  89. <div>Slot: <input id="slot" type="text"/></div>
  90. <input value="Listar" type="button" onClick="certificados()"></button>
  91. <p id="certificados"></p>
  92. </div>
  93. <div class="col-lg-4 col-md-4 col-sm-12">
  94. <h3 class="feature-title">Firma</h3>
  95. <div>Pin   : <input id="pin2" type="password"/></div>
  96. <div>Alias: <input id="alias" type="text"/></div>
  97. <div>Slot  : <input id="slot2" type="text"/></div>
  98. <div><input id="archivo" change="handleFileSelect(evt)" type="file"/></div>
  99. <input value="Firmar" type="button" onClick="firmar()"></button>
  100. </div>
  101. </div>
  102. </div>
  103. <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  104. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  105. </body>
  106. </html>

El código anterior crea una interfaz web que ejecutará las siguientes funciones:

  • listar.- Esta función llama al servicio GET de la URL https://localhost:9000/api/token/connected para verificar si existen dispositivos criptográficos conectados y devuelve el slot en el que se encuentra contectado.
  • certificados.- Esta función llama al servicio POST de la URL https://localhost:9000/api/token/data que recibe como parámetros el slot en el cual se encuentra conectado el dispositivo criptográfico a utilizarse y el pin de seguridad. Retorna como resultado los certificados disponibles en el dispositivo criptográfico.
  • firmar.- Esta función llama al servcio POST de la URL https://localhost:9000/api/token/firmar_lote_pdfs que recibe como parámetros el slot donde se encuentra conectado el dispositivo criptográfico, el alias que hace referencia al certificado a utilizarse para firmar, el pin de seguridad y el pdf a ser firmado en formato base64. Finalmente retorna el PDF firmado en formato base64.

A continuación se detalla el código de la función listar().

  1. function listar() {
  2. axios.get('https://localhost:9000/api/token/connected')
  3. .then(response => {
  4. if (response.data.finalizado) {
  5. document.getElementById('tokens').innerHTML = '';
  6. const tokens = response.data.datos.tokens;
  7. for (let i = 0; i < tokens.length; i++) {
  8. document.getElementById('tokens').innerHTML += tokens[i].serial + ` - (${tokens[i].slot})`;
  9. if (i === 0) {
  10. document.getElementById('slot').value = tokens[i].slot;
  11. document.getElementById('slot2').value = tokens[i].slot;
  12. }
  13. }
  14. }
  15. })
  16. .catch(e => {
  17. alert(e);
  18. });
  19. }

Como se puede observar la función anterior utiliza axios para realizar una petición GET y muestra el resultado de los tokens conectados en el componente HTML con identificador "tokens".

A continuación se detalla el código de la función certificados().

  1. function certificados() {
  2. axios.post('https://localhost:9000/api/token/data', {
  3. pin: document.getElementById('pin').value,
  4. slot: document.getElementById('slot').value
  5. })
  6. .then(response => {
  7. if (response.data.finalizado) {
  8. document.getElementById('certificados').innerHTML = '';
  9. const elements = response.data.datos.data_token.data;
  10. for (let i = 0; i < elements.length; i++) {
  11. if (elements[i].tipo === 'X509_CERTIFICATE') {
  12. if (document.getElementById('certificados').innerHTML === '') {
  13. document.getElementById('alias').value = elements[i].alias;
  14. }
  15. document.getElementById('certificados').innerHTML += elements[i].common_name + ` - (${elements[i].alias})`;
  16. }
  17. }
  18. }
  19. })
  20. .catch(e => {
  21. alert(e);
  22. });
  23. }

La función permite seleccionar los certificados disponibles en el dispositivo criptográfico para posteriormente poder seleccionar el certificado que se desea utilizar para firmar el documento.

Con el alias del certificado se puede realizar la firma con la siguiente función:

  1. function firmar() {
  2. const files = document.getElementById('archivo').files;
  3. if (files.length) {
  4. const reader = new FileReader();
  5. reader.readAsDataURL(files[0]);
  6. reader.onload = function () {
  7. const data = reader.result.split('base64,')[1];
  8. axios.post('https://localhost:9000/api/token/firmar_lote_pdfs', {
  9. pin: document.getElementById('pin2').value,
  10. alias: document.getElementById('alias').value,
  11. slot: document.getElementById('slot2').value,
  12. pdfs: [
  13. {
  14. id: files[0].name,
  15. pdf: data
  16. }
  17. ]
  18. })
  19. .then(response => {
  20. if (response.data.finalizado) {
  21. const pom = document.createElement('a');
  22. const pdfs = response.data.datos.pdfs_firmados;
  23. for (let i = 0; i < pdfs.length; i++) {
  24. pom.setAttribute('href', 'data:application/pdf;base64,' + pdfs[i].pdf_firmado);
  25. pom.setAttribute('download', files[0].name);
  26. const event = document.createEvent('MouseEvents');
  27. event.initEvent('click', true, true);
  28. pom.dispatchEvent(event);
  29. }
  30. }
  31. })
  32. .catch(e => {
  33. alert(e);
  34. });
  35. };
  36. } else {
  37. alert('Debe seleccionar un archivo.');
  38. }
  39. reader.onerror = function (error) {
  40. alert(error);
  41. };
  42. }

La función anterior utilizar el componente HTML de tipo file para cargar un archivo, lo convierte a base64 y lo envía al servicio con los parámetros adicionales para que el PDF sea firmado. Finalmente descarga el PDF firmado como resultado.

Para ejecutar el programa simplemente hacer clic sobre el siguiente enlace firmar.html o descargarlo y abrirlo con un navegador del siguiente enlace firmar.html

© ADSIB 2019 Bolivia