Validando una firma digital en documentos PDF desde una aplicación en PHP y firmando pdfs

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 PHP desde sistemas operativos Centos. 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: php-pdfsig, que está basada en PoDoFo.

Si no tiene instalado PHP y el compilador de c, puede utilizar la siguiente instrucción:

  1. sudo yum install epel-release
  2. sudo yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
  3. sudo yum-config-manager --enable remi-php74
  4. sudo yum -y update
  5. sudo yum groupinstall 'Development Tools'
  6. sudo yum install git wget cmake httpd php-fpm php-devel php-json openssl-devel freetype-devel fontconfig-devel

En caso de utilizar Centos 7 se puede descargar los binarios de aquí: php-pdfsig

Para instalar desde el código fuente es requerida la librería de podofo que se puede instalar de la siguiente manera:

  1. cd ~
  2. wget https://ufpr.dl.sourceforge.net/project/podofo/podofo/0.9.6/podofo-0.9.6.tar.gz
  3. tar -zxvf podofo-0.9.6.tar.gz
  4. mkdir podofo-build
  5. cd podofo-build
  6. cmake -G "Unix Makefiles" -DWANT_LIB64:BOOL=TRUE -DCMAKE_INSTALL_PREFIX="/usr" ../podofo-0.9.6 -DPODOFO_BUILD_SHARED:BOOL=TRUE -DPODOFO_BUILD_STATIC:BOOL=TRUE
  7. make
  8. sudo make install

Una vez instalado, ejecutar las siguientes instrucciones para descargar la librería, compilarla y activarla en PHP:

  1. git clone --branch redhat https://gitlab.softwarelibre.gob.bo/adsib/php-pdfsig.git
  2. cd php-pdfsig
  3. cd pdfsig
  4. ./make
  5. cd ..
  6. ./make
  7. sudo cp lib/php_pdfsig.so /usr/lib64/php/modules/
  8. echo 'extension=php_pdfsig' | sudo tee /etc/php.d/20-pdfsig.ini

Con este último paso queda configurada la librería libpdfsig que será utilizada para obtener las firmas del documento PDF, a continuación se reinicia el servidor apache2 y se crea el proyecto de validación:

  1. sudo systemctl enable --now httpd
  2. sudo systemctl enable --now php-fpm
  3. sudo mkdir /var/www/html/validar
  4. sudo nano /var/www/html/validar/index.php

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

  1. $json = json_decode(pdfsig_php('./prueba.pdf'));
  2. echo "<b>Nro de firmas: ".count($json)."</b></br></br>";
  3. for ($i = 0; $i < count($json); $i++) {
  4. echo "<b>Estado:</b> ".$json[$i]->estadoFirma."</br>";
  5. echo "<b>Fecha:</b> ".$json[$i]->fechaFirma."</br>";
  6. }

La primera línea recupera el pdf y la información inicial del documento. Una vez hecho esto ya se puede verificar las firmas, es así que la línea 3 obtienen el número de firmas en el documento y muestran este valor en la página. Finalmente se verifica la autenticidad del documento en la línea 6 con el estado de la firma y se muestra el mensaje correspondiente, la línea 7 muestra la fecha de la firma.

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 index.php editado en el paso anterior, se puede ingresar desde un navegador a la URL:

  1. http://localhost/validar/

La librería utilizada retorna admás de algunos datos el certificado del cual se podrá extraer la información adicional requerida:

  1. ...
  2. echo "Usuario: ".$json[$i]->certificados[0]->nombre."</br>";
  3. $str = $json[$i]->certificados[0]->certificado;
  4. $ssl = openssl_x509_parse($str);
  5. echo "CI: ".getOID('1.3.6.1.1.1.1.0', $ssl['name'])."</br>";
  6. ...

El código completo se muestra a continuación:

  1. <?php
  2. function format($fecha) {
  3. $year = substr($fecha, 2, 4);
  4. $month = substr($fecha, 6, 2);
  5. $day = substr($fecha, 8, 2);
  6. $hour = substr($fecha, 10, 2);
  7. $min = substr($fecha, 12, 2);
  8. $seg = substr($fecha, 14, 2);
  9. return $year.'-'.$month.'-'.$day.' '.$hour.':'.$min.':'.$seg;
  10. }
  11. function getOID($OID, $ssl) {
  12. preg_match('/\/' . $OID . '=([^\/]+)/', $ssl, $matches);
  13. return $matches[1];
  14. }
  15. $json = json_decode(pdfsig_php('./prueba.pdf'));
  16. echo "<b>Nro de firmas: ".count($json)."</b></br></br>";
  17. for ($i = 0; $i < count($json); $i++) {
  18. echo "<b>Estado:</b> ".$json[$i]->estadoFirma."</br>";
  19. echo "<b>Fecha:</b> ".format($json[$i]->fechaFirma)."</br>";
  20. echo "<b>Usuario:</b> ".$json[$i]->certificados[0]->nombre."</br>";
  21. $str = $json[$i]->certificados[0]->certificado;
  22. $ssl = openssl_x509_parse($str);
  23. echo "<b>CI:</b> ".getOID('1.3.6.1.1.1.1.0', $ssl['name'])."</br>";
  24. }

Para firmar un pdf a través de un certificado por software simplemente hay que utilizar la función:

  1. echo podofosign_php('prueba.pdf', 'prueba.firmado.pdf', 'token.p12', '12345678');
  2. Donde token.p12 es el archivo que contiene el par de claves y 12345678 es el pin de seguridad para acceder al par de claves.

    Para firmar un pdf haciendo uso de un token criptográfico, se debe modificar el archivo de configuración de selinux

    1. sudo nano /etc/selinux/config
    2. Y cambiar SELINUX a permisive o disbled para permitir la carga de librerías de forma dinámica (requerido para cargar el driver)

      1. # This file controls the state of SELinux on the system.
      2. # SELINUX= can take one of these three values:
      3. # enforcing - SELinux security policy is enforced.
      4. # permissive - SELinux prints warnings instead of enforcing.
      5. # disabled - SELinux is fully disabled.
      6. SELINUX=enforcing
      7. # SELINUXTYPE= type of policy in use. Possible values are:
      8. # targeted - Only targeted network daemons are protected.
      9. # strict - Full SELinux protection.
      10. SELINUXTYPE=targeted
      11. Finalmente usar la siguiente función:

        1. echo podofotoken_php('prueba.pdf', 'prueba.firmado.pdf', './libcastle.so.1.0.0', '12345678', 0);
        2. Donde libcastle.so.1.0.0 es la ruta al controlador del token, 12345678 es el pin de seguridad del token y 0 es el número de certificado a utilar del token comenzando en 0.

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

          Si desea crear un contenedor Docker puede hacerlo a través del siguiente link: Dockerfile

          © ADSIB 2019 Bolivia