How to sign a PDF digitally?

Asked

Viewed 8,303 times

25

I am developing a PHP system with the Laravel 5.1 framework that will generate some technical reports, and the client wants these reports to be digitally signed, IE, que conste la no PDF generated the electronic signature of the technical responsible.

I have no idea how that works... I know you have some tokens, cards and so on that are used for this, but how do I pull this signature through my system and put it there in the document? Can someone give me a direction? Is there an API or something like that?

1 answer

36


Short answer:

  1. Get a digital certificate from a certification body (Ex.: SERPRO). You have to pay and renew periodically.
  2. Use the certificate primary key in conjunction with the pdf to create a digital signature with the openssl_sign PHP function.
  3. Save this signature to a file so you or someone else can test later if the file was signed by vc with the openssl_verify function.

Long answer:

Public and private keys

There are several ways to identify the origin of a particular file. One of these ways is through the use of a "key pair". A private key and a public key. The private key is exclusively yours and should not be given to anyone. The public key should be available to the general public. The algorithms that work with this key pair, encrypt with the private key and decrypt with the public key. This way, because the private key is not known, if any file can be decrypted with the public key known to a certain user, we are sure that this file came from that user. Similarly if we encrypt a file with the user’s public key only it will be able to decrypt with the private key.

Hashes

Another concept is the integrity of the document. Ensure that the document that reaches the destination is exactly the same as the source. For this, the file "hash" is calculated, that is, a character set that represents the file and has a mathematical guarantee of being unique to a certain file. The file hash can be public. If in the destination you compare the hash of your file with the initial hash, you ensure the integrity of it. There are several hash algorithms. One of most common is the MD5.

Digital signature

Simply put, a digital signature is nothing more than a hash encrypted by a user’s private key.

Certificados Digitais

The problem is: who guarantees that that key belongs to that user ? answer: someone known to everyone. That is, a public certification authority. These entities charge for their services. They are like digital notaries. There are several with differentiated prices. They provide so-called "digital certificates" that contain the key pair you need to sign things, so you can digitally identify yourself among other functions.

Certificate hierarchy

Thus a certificate to be valid has a "hirarquia of certificates", that is, another certificate that validates its certificate, another that validates the entity that certified it, until it reaches the national root entity (in our case the ICP-Brazil). This hirarchy usually has a few steps. At most 4.

Server certificates

In addition to the user himself having a certificate for you, so you can access certificates on a certain server, you need to enable HTTPS mode. For this the server itself needs a certificate to ensure that that machine is really the machine you want to access. Vc then enables the HTTPS mode on the HTTP server of your choice, using the server certificate.

Physical medium of storage

These certificates can be provided in various ways:

  • A1 which are basically files that you yourself can write to a pen drive or something and import directly into the browser.
  • A3 which are special hardware devices to contain only certificates. They look like a flash drive, but they have the special function of containing certificates in a special memory. There are other versions that come on cards with the same function. The advantage is that being specific hardware are more secure than a certificate present in a single file, which can be erased or replaced easily. In this case beyond the cost of the certificate there is the hardware cost.

If you want you can create your own certification authority and issue your own certificates. The problem with this is that you do not have the security of transactions that guarantee the entire process and may be the victim of the infamous "Man-in-the-Middle" attack if someone manages to stay in the middle of the origin and destination of your data. That is, your certification body is not known to the general public and therefore the origin of your data cannot be guaranteed. Typically certifiers of this type are used only for development and actual certificates are purchased for production servers.


To sign and check a file

So let’s go to the steps in a more elaborate way:

  1. u need to obtain a valid digital certificate from a certification body
  2. if it is an A3 certificate, load the device driver into the "security devices in your browser".
  3. import the certificates from your certification authority in the "authorities" tab of your browser’s certified function
  4. import your certificate in the "people tab"
  5. open your certificate and export to public and private key files
  6. then in your PHP file load the PDF file into one variable and the private key into another
  7. then use the function openssl_sign with these 2 variables to create the signature (check the documentation)
  8. to verify the signature use openssl_verify with the public key.

To use digital certificates in a web application

  1. Follow steps 1 to 4 above.
  2. Get a digital certificate for the server
  3. Configure your HTTP server to use that certificate and enable HTTPS mode
  4. With your certificate installed on your machine and the server configured for HTTPS, your certificate password should be requested when accessing the site. This password solitation process happens before any page of your site appears, because the HTTPS protocol is negotiated before the transmission of the HTTP pages.
  5. Once done the above steps, in the system vc will be able to access the client’s certificate through the global variable $_SERVER['SSL_CLIENT_CERT']. Vc can convert the contents of this variable into an array containing the data present in the certificate (personal data) with the command openssl_x509_parse($_SERVER['SSL_CLIENT_CERT']).
  6. Public and private keys can be obtained with commands openssl_get_publickey() and openssl_get_privatekey() respectively. Remember that you can only get the private key from your own certificates. The private key is reserved to the certificate owner.
  7. Then use steps 6 to 8 of the above procedure to sign and verify a document.

Configure HTTPS in Apache 2

Put in a file (let’s say 'cadeiadecertificados.crt') the whole certificate file of the server certificate. Just copy and paste one underneath the other in a same text file. Leave the server certificate in a separate file ('server.crt') and the server certificate private key in another ('server.key'').

Below is an HTTPS configuration model in Apache 2

<VirtualHost *:443>
    ServerAdmin webmaster@localhost

    DocumentRoot /var/www
    Alias /sistema /opt/sistema
    <Directory /opt/sistema>
        Options Indexes FollowSymLinks MultiViews 
        AllowOverride All
        AcceptPathInfo On
        Order allow,deny
        allow from all
    </Directory>

    SSLEngine on
    SSLOptions +StdEnvVars +ExportCertData
    SSLCertificateFile      /opt/sistema/certs/servidor.crt
    SSLCertificateKeyFile   /opt/sistema/certs/servidor.key
    SSLCACertificateFile    /opt/sistema/certs/cadeiadecertificados.crt
    SSLVerifyClient require
    SSLVerifyDepth  10

    ErrorLog ${APACHE_LOG_DIR}/error.log
    LogLevel warn
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Creating a local certification authority and a locally signed server certificate

The Bash (Linux) script below creates a certification authority for local certificate issuance.

  • make sure that the openssl package is installed. If not install with sudo apt-get install openssl (debian distributions), or corresponding command in your distribution.
  • put the code below in a file named "openssl.cnf" and keep this file in the same folder as the scripts below.

openssl.cnf file:

#
# OpenSSL example configuration file.
# This is mostly being used for generation of certificate requests.
#

# This definition stops the following lines choking if HOME isn't
# defined.
HOME            = .
RANDFILE        = $ENV::HOME/.rnd

# Extra OBJECT IDENTIFIER info:
#oid_file       = $ENV::HOME/.oid
oid_section     = new_oids

# To use this configuration file with the "-extfile" option of the
# "openssl x509" utility, name here the section containing the
# X.509v3 extensions to use:
# extensions        = 
# (Alternatively, use a configuration file that has only
# X.509v3 extensions in its main [= default] section.)

[ new_oids ]

# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
# Add a simple OID like this:
# testoid1=1.2.3.4
# Or use config file substitution like this:
# testoid2=${testoid1}.5.6

# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7

####################################################################
[ ca ]
default_ca  = CA_default        # The default ca section

####################################################################
[ CA_default ]

dir     = .         # Where everything is kept
certs       = $dir/certs        # Where the issued certs are kept
crl_dir     = $dir/crl      # Where the issued crl are kept
database    = $dir/index.txt    # database index file.
#unique_subject = no            # Set to 'no' to allow creation of
                    # several ctificates with same subject.
new_certs_dir   = $dir          # default place for new certs.

certificate = $dir/cacert.pem   # The CA certificate
serial      = $dir/serial       # The current serial number
crlnumber   = $dir/crlnumber    # the current crl number
                    # must be commented out to leave a V1 CRL
crl     = $dir/crl.pem      # The current CRL
private_key = $dir/private/cakey.pem# The private key
RANDFILE    = $dir/private/.rand    # private random number file

x509_extensions = usr_cert      # The extentions to add to the cert

# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt    = ca_default        # Subject Name options
cert_opt    = ca_default        # Certificate field options

# Extension copying option: use with caution.
# copy_extensions = copy

# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions    = crl_ext

default_days    = 365           # how long to certify for
default_crl_days= 30            # how long before next CRL
default_md  = default       # use public key default MD
preserve    = no            # keep passed DN ordering

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy      = policy_match

# For the CA policy
[ policy_match ]
countryName     = match
stateOrProvinceName = match
organizationName    = match
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName     = optional
stateOrProvinceName = optional
localityName        = optional
organizationName    = optional
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

####################################################################
[ req ]
default_bits        = 1024
default_keyfile     = privkey.pem
distinguished_name  = req_distinguished_name
attributes      = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert

# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret

# This sets a mask for permitted string types. There are several options. 
# default: PrintableString, T61String, BMPString.
# pkix   : PrintableString, BMPString (PKIX recommendation before 2004)
# utf8only: only UTF8Strings (PKIX recommendation after 2004).
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
string_mask = utf8only

# req_extensions = v3_req # The extensions to add to a certificate request

[ req_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default     = AU
countryName_min         = 2
countryName_max         = 2

stateOrProvinceName     = State or Province Name (full name)
stateOrProvinceName_default = Some-State

localityName            = Locality Name (eg, city)

0.organizationName      = Organization Name (eg, company)
0.organizationName_default  = Internet Widgits Pty Ltd

# we can do this but it is not needed normally :-)
#1.organizationName     = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd

organizationalUnitName      = Organizational Unit Name (eg, section)
#organizationalUnitName_default =

commonName          = Common Name (e.g. server FQDN or YOUR name)
commonName_max          = 64

emailAddress            = Email Address
emailAddress_max        = 64

# SET-ex3           = SET extension number 3

[ req_attributes ]
challengePassword       = A challenge password
challengePassword_min       = 4
challengePassword_max       = 20

unstructuredName        = An optional company name

[ usr_cert ]

# These extensions are added when 'ca' signs a request.

# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.

basicConstraints=CA:FALSE

# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.

# This is OK for an SSL server.
# nsCertType            = server

# For an object signing certificate this would be used.
# nsCertType = objsign

# For normal client use this is typical
# nsCertType = client, email

# and for everything including object signing:
# nsCertType = client, email, objsign

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# This will be displayed in Netscape's comment listbox.
nsComment           = "OpenSSL Generated Certificate"

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move

# Copy subject details
# issuerAltName=issuer:copy

#nsCaRevocationUrl      = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName

# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping

[ v3_req ]

# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]


# Extensions for a typical CA


# PKIX recommendation.

subjectKeyIdentifier=hash

authorityKeyIdentifier=keyid:always,issuer

# This is what PKIX recommends but some broken software chokes on critical
# extensions.
#basicConstraints = critical,CA:true
# So we do this instead.
basicConstraints = CA:true

# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign

# Some might want this also
# nsCertType = sslCA, emailCA

# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
# issuerAltName=issuer:copy

# DER hex encoding of an extension: beware experts only!
# obj=DER:02:03
# Where 'obj' is a standard or added object
# You can even override a supported extension:
# basicConstraints= critical, DER:30:03:01:01:FF

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always

[ proxy_cert_ext ]
# These extensions should be added when creating a proxy certificate

# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.

basicConstraints=CA:FALSE

# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.

# This is OK for an SSL server.
# nsCertType            = server

# For an object signing certificate this would be used.
# nsCertType = objsign

# For normal client use this is typical
# nsCertType = client, email

# and for everything including object signing:
# nsCertType = client, email, objsign

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# This will be displayed in Netscape's comment listbox.
nsComment           = "OpenSSL Generated Certificate"

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move

# Copy subject details
# issuerAltName=issuer:copy

#nsCaRevocationUrl      = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName

# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

####################################################################
[ tsa ]

default_tsa = tsa_config1   # the default TSA section

[ tsa_config1 ]

# These are used by the TSA reply generation only.
dir     = ./demoCA      # TSA root directory
serial      = $dir/tsaserial    # The current serial number (mandatory)
crypto_device   = builtin       # OpenSSL engine to use for signing
signer_cert = $dir/tsacert.pem  # The TSA signing certificate
                    # (optional)
certs       = $dir/cacert.pem   # Certificate chain to include in reply
                    # (optional)
signer_key  = $dir/private/tsakey.pem # The TSA private key (optional)

default_policy  = tsa_policy1       # Policy if request did not specify it
                    # (optional)
other_policies  = tsa_policy2, tsa_policy3  # acceptable policies (optional)
digests     = md5, sha1     # Acceptable message digests (mandatory)
accuracy    = secs:1, millisecs:500, microsecs:100  # (optional)
clock_precision_digits  = 0 # number of digits after dot. (optional)
ordering        = yes   # Is ordering defined for timestamps?
                # (optional, default: no)
tsa_name        = yes   # Must the TSA name be included in the reply?
                # (optional, default: no)
ess_cert_id_chain   = no    # Must the ESS cert id chain be included?
                # (optional, default: no)

Just put the openssl.cnf file and the script below in a folder and run the script.

#/bin/bash

# Este script cria uma autoridade certificadora (CA) local para ser usada 
# em ambientes desenvolvimento e servir como base para emissão de 
# certificados digitais e um certificado de servidor emitido por esta CA. 
#
# São criados os seguintes arquivos:
# rootCA.key => Chave privada da autoridade certificadora local
# rootCA.crt => Certificado da autoridade certificadora local auto-assinado
# server.csr => Solicitação de emissão de certificado de servidor 
# server.key => Chave privada do certificado de servidor solicitado
# server.crt => Certificado de servidor emitido pela autoridade certificadora local
# index.txt e serial são arquivos necessários apenas ao processo de 
# emissão de certificados pelo openssl e não são relevantes para o 
# usuário. Não devem ser apagados mas podem ser ignorados.

echo 00 > serial
touch index.txt

echo "Criar chave privada da Autoridade Ceritficadora (CA)"
openssl genrsa -des3 -passout pass:123 -out  ./rootCA.key 2048

echo "Remover a senha da chave privada"
openssl rsa -passin pass:123 -in ./rootCA.key -out ./rootCA.key

echo "Criar certificado auto-assinado da CA"
openssl req -config openssl.cnf -new -x509 -subj '/C=BR/L=Dev/O=COMPANHIA/CN=CA' -days 99999 -key ./rootCA.key -out ./rootCA.crt

echo "Criar chave privada do servidor"
openssl genrsa -des3 -passout pass:123 -out ./server.key 2048

echo "Remover senha da chave privada do servidor"
openssl rsa -passin pass:123 -in ./server.key -out ./server.key

echo "Criar requisição de certificado para o servidor"
openssl req -config ./openssl.cnf -new -subj '/C=BR/L=Dev/O=COMPANHIA/CN=server' -key ./server.key -out ./server.csr

echo "Criar certificado para o servidor a partir da requisição de certificado"
openssl ca -batch -config ./openssl.cnf -days 999 -in ./server.csr -out ./server.crt -keyfile ./rootCA.key -cert ./rootCA.crt -policy policy_anything

echo "Apagando arquivos temporários"
rm -f index.txt*
touch index.txt
rm serial.old
rm *.pem

echo "Finalizado."

Issuing certificates from the local certification authority

The script below issues certificates from the certification authority created with the above script.

#!/bin/bash

# Este script emite certificados com base na autoridade certificadora 
# local. É criada uma pasta cujo nome é o nome do dono do certificado, 
# contendo os seguintes arquivos:
# <NOME>.key => Chave privada do usuário
# <NOME>.csr => Requisição de certificado à CA local
# <NOME>.crt => Certificado emitido pela CA local
# <NOME>.p12 => Certificado em formato PKCS12

if [[ -z "$1" || -z "$2" ]]; then
    echo "Uso: emitircertificado <NOME> <CPF>"
    exit
fi

NOME=$1
CPF=$2
DN="/C=BR/L=Dev/O=COMPANHIA/CN=$1:$2"
ARQ="${NOME//[[:space:]]/}"

echo -e "\nCriando pasta... " 
mkdir $ARQ
echo "feito."

echo -e "\nCriando chave privada... "
openssl genrsa -des3 -passout pass:123 -out ./$ARQ/$ARQ.key 2048
echo "feito."

echo -e "\nRemovendo senha..."
openssl rsa -passin pass:123 -in ./$ARQ/$ARQ.key -out ./$ARQ/$ARQ.key
echo "feito."

echo -e "\nCriando CSR... "
openssl req -config ./openssl.cnf -new -subj "$DN" -key ./$ARQ/$ARQ.key -out ./$ARQ/$ARQ.csr
echo "feito."

echo -e "\nCriando certificado do cliente"
openssl ca -batch -config ./openssl.cnf -days 999 -in ./$ARQ/$ARQ.csr -out ./$ARQ/$ARQ.crt -keyfile ./rootCA.key -cert ./rootCA.crt -policy policy_anything
echo "feito."

echo -e "\nExportar o cliente para pkcs12 para importar no navegador"
openssl pkcs12 -export -passout pass:123 -in ./$ARQ/$ARQ.crt -inkey ./$ARQ/$ARQ.key -certfile ./rootCA.crt -out ./$ARQ/$ARQ.p12

echo -e "\nApagar arquivos temporários"
rm -f index.txt*
touch index.txt
rm serial.old
rm *.pem

echo "Finalizado."

I hope it will be useful. Feel free to ask more.

  • clarified @Nelsonteixeira, when entering this part of the development I will probably ask more yes rs, hugs and many thanks!

  • Be at ease :)

  • 6

    Excellent response, +1!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.