Publicado 2019-08-06.
Palabras clave: debian tls
TL;DR: Descargar el certificado en /usr/local/share/ca-certificates/
y
ejecutar update-ca-certificates
.
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
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).
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).
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.
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.
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
.
La solución consta de dos partes:
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.
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.
...
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>
...