Validando una firma digital en documentos PDF

En este tutorial, se verá la implementación de validación de firma digital en documentos PDF con firma desde una aplicación escrita en NodeJS. Para este propósito, es necesario realizar las siguientes validaciones:

  • Verificar la autenticidad del documento.- Este paso consiste en comprobar que el documento firmado no haya sido modificado después de la firma o que el mismo sea una falsificación. Esto se logra a partir de la generación de un HASH (código que identifica al documento de manera única, al igual que una huella digital identifica a una persona). La firma digital se constituye en el HASH del documento que se está firmando encriptado con la clave privada del signatario.
    Para validar la autenticidad del documento, se debe desencriptar la firma con la clave pública y comparar el HASH obtenido con un nuevo HASH calculado a partir del documento.
    - Si no se puede desencriptar la firma, significa que no corresponde al propietario de la clave pública.
    - Si el HASH obtenido de la firma no corresponde al HASH del documento, significa que el documento fue modificado.
  • Verificar la propiedad y legalidad de la clave pública.- Una vez verificado el paso anterior podemos estar seguros de que el documento fue firmado por el propietario del par de claves y que el mismo no fue modificado después de la firma. Sin embargo, también es necesario identificar al propietario del par de claves. Esto se logra a través del certificado de firma digital, que contiene los datos de identidad y clave pública del signatario y además está firmado por una ECA (Entidad Certificadora Autorizada) que para nuestro caso es ADSIB.
    Revisando el certificado se puede obtener la identidad del signatario, pero para estar seguros de que los mismos no fueron falseados, se debe verificar la firma de la ECA en el certificado, en este caso con la clave pública de la ADSIB.
    La firma del certificado se verifica como en el paso anterior, pero utilizando certificado de firma digital de la ADSIB, que a su vez contiene la clave pública y se encuentra en: Certificado ADSIB
  • Verificar la vigencia del certificado.- Hasta este punto ya se tiene la certeza de que el documento fue firmado por el propietario del par de claves identificado a través del certificado de firma digital. Sin embargo, para evitar que por ejemplo un tercero firme documentos a nombre de un difunto, se tienen políticas que limitan la vida útil del certificado a un tiemo determinado.
    Por este motivo, es necesario además verificar que el documento fue firmado dentro del periodo de vigencia del certificado, simplemente comparando la fecha en la que se firmó el documento y el periodo de vigencia del certificado.
  • Verificar la revocación del certificado.- Adicionalmente también es posible que el propietario del par de claves pierda el Token encargado de resguardar su par de claves o el mismo sea robado, en cuyo caso lo más aconsejable es reportar este hecho a la ECA. Una vez reportado el hecho, la ECA publica el estado del certificado como REVOCADO, invalidando cualquier firma posterior a ocurrido el hecho.
    Finalmente entonces será necesario revisar la lista para verificar que el certificado no haya sido revocado.

En el caso de los archivos PDF, se maneja el contenido por secciones, ya que si comparamos el documento original con el firmado, serán diferentes por el contenido de la firma. Sin embargo el contenido propiamente del documento no cambia.

Por lo anterior, el cálculo del HASH se realiza sobre el contenido del PDF como se explica en el siguiente enlace: PDF

La manipulación de un PDF no es parte de este tutorial, por lo que se utilizará la librería publicada en: pdfsig, que está basada en poppler.

Si no tiene instalado NodeJS, puede seguir los pasos descritos en el siguiente enlace: NodeJS

Una vez instalado, ejecutar las siguientes instrucciones para preparar el proyecto y editar el primer archivo del proyecto:

  1. mkdir validar
  2. cd validar
  3. npm init
  4. npm install pdfsig
  5. nano app.js

Con los pasos anteriores en la consola de comandos, se abrirá un editor de texto donde se puede copiar el siguiente programa:

  1. const pdfsig = require('pdfsig');
  2. const firmas = new pdfsig('prueba.pdf');
  3. const nroFirmas = firmas.size();
  4. console.log(nroFirmas);
  5. if (firmas.estado(0)) {
  6. console.log('Firma válida, el documento no ha sido modificado.');
  7. } else {
  8. console.log('El documento ha sido modificado, la firma ya no es válida.');
  9. }

La primera línea importa la librería pdfsig, la tercera recupera el pdf y la información inicial del documento. Una vez hecho esto ya se puede verificar las firmas, es así que las líneas 5 y 6 obtienen el número de firmas en el documento y muestran este valor por consola. Finalmente se verifica la autenticidad del documento en la línea 8 con la función estado y se muestra el mensaje correspondiente.

Para ejecutar el ejemplo anterior se requiere un archivo pdf firmado que se puede descargar de la siguiente url: PDF para ejecutar el ejemplo, una vez guardado el archivo app.js editado en el paso anterior, se puede ejecutar:

  1. node app.js

Pasando al siguiente paso "Verificar la propiedad y legalidad de la clave pública", se puede obtener los atributos del certificado como se meustra en las siguientes líneas que obtienen el certificado asociado a la firma y finalmente muestra el nombre del signatario y su documento de identidad:

  1. const certificado = firmas.getFirmas()[0].certificados[0];
  2. console.log(certificado.subject.getField('CN').value);
  3. for (let i = 0; i < certificado.subject.attributes.length; i++) {
  4. if (certificado.subject.attributes[i].type === '1.3.6.1.1.1.1.0') {
  5. console.log(certificado.subject.attributes[i].value);
  6. }
  7. }

Existen atributos en el certificado como el nombre del signatario que son reconocidos mundialmente, es así que el nombre se lo puede obtener con la abreviatura CN, sin embargo otro tipo de atributos se deben acceder a través de su OID como ocurre en este caso con el CI, por lo que se los bebe buscar como se muestra en las líneas 4 a 8 del ejemplo.

Adicionalmente estaría la validación de si el certificado tiene valor legar, es decir si fue emitido por una Entidad Certificadora Autorizada ECA que para el ejemplo es ADSIB, para lo cual se debe descargar el certificado de la ADSIB, disponible en el siguiente enlace: Certificado ADSIB y guardarlo en el directorio del programa.

  1. const cafile = require('fs').readFileSync('firmadigital_bo.pem');
  2. const ca = require('node-forge').pki.certificateFromPem(cafile);
  3. if (ca.verify(certificado)) {
  4. console.log('Certificado válido (Emitido por la PKI del Estado Plurinacional de Bolivia).');
  5. } else {
  6. console.log('Certificado inválido.');
  7. }

Del ejemplo anterior, la primera línea recupera el certificado de la ECA descargado de: Certificado ADSIB y la segunda línea utiliza la librería node-forge recuperar y manipular el certificado. Finalmente se utiliza la función verify de node-forge que se encarga de verificar que la firma del certificado corresponda al certificado de la ECA.

El tercer paso es muy fácil de verificar, simplemente sería validar el rango de la firma con la validez del certificado.

  1. if (firmas.fecha(0).getTime() >= certificado.validity.notBefore.getTime() && firmas.fecha(0).getTime() <= certificado.validity.notAfter.getTime()) {
  2. console.log('La firma del documento fue realizada dentro de la vigencia del certificado.');
  3. } else {
  4. console.log('La firma del documento fue realizada fuera de la vigencia del certificado (No válido).');
  5. }

El último paso consiste en verificar que el certificado no se encuentre en la lista de revocación de certificados, para lo cual se utilizará la librería ocsp que debe ser instalada con el siguiente comando en la consola:

  1. npm install ocsp

Y en el archivo app.js se agregarían las siguientes líneas de código:

  1. require('ocsp').check({
  2. cert: require('node-forge').pki.certificateToPem(certificado),
  3. issuer: cafile
  4. }, (err, res) => {
  5. if (res) {
  6. if (res.type === 'good') {
  7. console.log('Certificado no revocado.');
  8. } else {
  9. console.log(res.value);
  10. }
  11. } else {
  12. console.log(err.message);
  13. }
  14. });

Si desea descargar el ejemplo puede hacerlo a través del siguiente link: Código de ejemplo

© ADSIB 2019 Bolivia