Conectandose a un certificado autofirmado en debian

2019/08/06

TL;DR: Descargar el certificado en /usr/local/share/ca-certificates/ y ejecutar update-ca-certificates.

Descripción del problema

Se intenta la conexión a un servidor web utilizando https el cual posee un certificado auto firmado (self signed certificate).

Al intentar la conexión suelen obtenerse mensajes similares a los siguientes:

Objetivos

Interesa encontrar la forma de aceptar el certificado auto firmado a nivel del sistema operativo, sin tener que estar modificando las invocaciones al utilizar wget (--no-check-certificate), curl (--insecure) o java (por ejemplo stackoverflow).

Setup de servidor con certificado auto firmado

Para replicar los mensajes anteriores se creará un certificado autofirmado para el hostname del equipo (generate-cert.sh). Este será utilizado con un servidor https (web-server.py) cuyo hostname será el del equipo y como puerto se utilizará el 8443 ya que no precisan permisos de root. Por último, se realizarán pruebas (try-connect.sh) utilizando wget, curl y una clase java que realice una conexión https (HttpsClient.java).

Certificado autofirmado

Para la creación del certificado se utilizará el comando openssl:

openssl req \
    -new -x509 \
    -keyout "$outfile" -out "$outfile" \
    -subj "/C=${country}/ST=${state}/L=${locality}/O=${organization}/OU=${organizationalunit}/CN=${commonname}/emailAddress=${email}" \
    -days 365 \
    -nodes

En el script generate-cert.sh se establecen los valores de las variables utilizadas anteriormente. Notar que el commonname es el hostname del equipo.

Servidor https

El certificado anterior se utilizará con un servidor https escrito en python. Se utiliza python ya que es sencillo crear un servidor y no se necesita instalar o configurar ningún programa extra. El servidor https se encuentra en el script web-server.py y utiliza SimpleHTTPRequestHandler para servir los archivos del directorio en que es ejecutado:

httpd = HTTPServer((socket.gethostname(), 8443), SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile='/tmp/cert-and-key.pem', server_side=True)
httpd.serve_forever()

El servidor anterior escucha en el puerto de usuario 8443.

Conexión al servidor.

El script try-connect.sh se utiliza para realizar las pruebas de conexión al servidor web con certificado autosignado establecido en los apartados anteriores:

...
wget "$URL"
curl "$URL"
...
java HttpsClient "$URL"
...

Nota: Se debe descargar el archivo HttpsClient.java si se quiere probar de realizar una conexión https desde java.

Solución en debian

La solución consta de dos partes:

  1. Conseguir el certificado autofirmado (obtain-cert.sh)
  2. Agregar el certificado como válido en el sistema.

Conseguir el certificado autofirmado

Primeramente se debe obtener el certificado autofirmado del servidor. Como ejemplo, varias formas de obtener el certificado se encuentran listadas en shellhacks. En este caso se utilizará, mediante el script [obtain-cert.sh][file obtain-cert.sh] el comando openssl cuya explicación se encuentra en stackoverflow:

echo -n \
    | openssl s_client -connect ${HOSTNAME}:8443 \
    | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/$HOSTNAME.crt

Nota: de la información devuelta por el comando s_client nos interesa solo el certificado.

Agregar el certificado como válido al sistema

En debian, el paquete ca-certificates es el que permite a las aplicaciones verificar la autenticación de las conexiones SSL (TLS). En el archivo README.Debian se indica:

If you want to install local certificate authorities to be implicitly trusted, please put the certificate files as single files ending with “.crt” into /usr/local/share/ca-certificates/ and re-run ‘update-ca-certificates’.

Por lo que alcanza copiar el archivo previamente obtenido en el directorio /usr/local/share/ca-certificates/ y ejecutar update-ca-certificates:

$ sudo install --mode 644 --owner=root --group=root \
    /tmp/$(hostname).crt /usr/local/share/ca-certificates/
$ sudo update-ca-certificates
...
Importing into legacy system store:
I already trust 128, your new list has 129
Certificate added: C=uy, S=Montevideo, L=Montevideo, O=My Home, OU=My Room, CN=my-hostname, E="My Email <my.email@my-hostname>"
1 new root certificates were added to your trust store.
Import process completed.

Importing into BTLS system store:
I already trust 128, your new list has 129
Certificate added: C=uy, S=Montevideo, L=Montevideo, O=My Home, OU=My Room, CN=my-hostname, E="My Email <my.email@my-hostname>"
1 new root certificates were added to your trust store.
Import process completed.
...

Confirmación de la solución

Para confirmar que lo anterior solucionó el problema hay que volver a ejecutar el script try-connect.sh:

$ ./try-connect.sh
+ wget https://my-hostname:8443
--2019-08-06 18:06:44--  https://my-hostname:8443/
Resolving my-hostname (my-hostname)... 127.0.1.1
Connecting to my-hostname (my-hostname)|127.0.1.1|:8443... connected.
HTTP request sent, awaiting response... 200 OK
...
+ curl https://my-hostname:8443
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
...
+ java HttpsClient https://my-hostname:8443
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
...