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:
-
wget
:--2019-08-06 16:02:30-- 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. ERROR: The certificate of ‘my-hostname’ is not trusted. ERROR: The certificate of ‘my-hostname’ doesn't have a known issuer.
-
curl
:curl: (60) SSL certificate problem: self signed certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.
-
java
:javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
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:
- Conseguir el certificado autofirmado (obtain-cert.sh)
- 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>
...