Do you know why using a Let's Encrypt cert for TLS would result in a client failing at the SSL handshake point with error 19 (self signed cert in chain)? I am developing an app in C that uses the Mosquitto libraries to open the connection (and fails at the handshake) but to simplify this question I'm going to demonstrate the problem using the mosquitto_sub command (which we know works).
I am using Centos 6.2 with Let's Encrypt certificates for my website without any issues (because they're free, automated, and open). I now want to use the same certificate issued by Let's Encrypt to secure a TLS connection between my server and any remote client. TLS is being used for connections to the Mosquitto MQTT broker in fact, on port 8881. The mosquitto.conf file on my server contains:
...
user mosquitto
listener 8883 example.com
cafile /etc/mosquitto/certs/chain-ca.pem # These 3 from Let's Encrypt
certfile /etc/mosquitto/certs/cert.pem
keyfile /etc/mosquitto/certs/privkey.pem
require_certificate false
...
I have several network interfaces on my CentOS server and my domain, "example.com" resolves with the dig command to the IP of interface eth0:1:
[root@spiff mosquitto]# dig example.com
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.47.rc1.el6_8.2 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 804
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 300 IN A 158.234.234.24
;; AUTHORITY SECTION:
example.com. 300 IN NS dns0.ns.co.uk.
example.com. 300 IN NS dns1.ns.co.uk.
;; Query time: 41 msec
;; SERVER: 151.236.220.5#535(11.226.220.5)
;; WHEN: Sun Oct 30 19:21:07 2016
;; MSG SIZE rcvd: 101
I believe this is significant because the certificate for 'example.com' that I've created with Let's Encrypt has a CN which has to resolve with DNS to the IP address of the NIC that the Mosquitto listener is bound to, otherwise from the perspective of OpenSSL I'm serving the wrong certificate to any clients.
The server doesn't have any issues on startup.
After a lot of Googling, I understand (maybe wrongly) that the client needs to be handed as a parameter the certificate of the CA (certification authority) that signed the cert for 'example.com' when it makes a connection to the server, because it will check that the CA used to sign example.com's certificate is trusted, and so we use the --cafile [certificate-authority-cert.crt] parameter to pass this. I appreciate that this is specific to TLS connections and that web clients don't use this feature.
When a client on the same Centos 6.2 server (which could be my program but to simplify things is the out-of-the-box mosquitto_sub command) connects with these parameters I see this error:
mosquitto_sub -h example.com -p 8883 -t test -u mr-user -P P@55W0rD --cafile /etc/pki/tls/certs/lets-encrypt-x3-cross-signed.pem -d
Error: A TLS error occurred
Because the Mosquitto broker is not very specific about the value of errno on a failure, I try again using the s_client feature of openSSL instead:
[root@spiff certs]# openssl s_client -connect example.com:8883 -CAfile lets-encrypt-x3-cross-signed.pem
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify error:num=19:self signed certificate in certificate chain
verify return:0
---
Certificate chain
0 s:/CN=www.example.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
2 s:/O=Digital Signature Trust Co./CN=DST Root CA X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFDjCCA/agAwIBAgISA12o6mO9oS364BF5UVgSAD7TMA0GCSqGSIb3DQEBCwUA
[... lines removed for brevity ...]
A+q6hf00nJJsEvGmhVzQG5zAn6ojcWgT3EhurPien7Y16+kIS5tdz9xbeCgLTOrJ
BXA=
-----END CERTIFICATE-----
subject=/CN=www.example.com
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
No client certificate CA names sent
Server Temp Key: ECDH, prime256v1, 256 bits
---
SSL handshake has read 3999 bytes and written 373 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: E8D32590BAB26FF5811D39D775B3F455CAC3E8747866FA251DDA2032FA88E349
Session-ID-ctx:
Master-Key: 1B45DF54D11BC44D96AEAC940291B4D3BBAE56D6431E746873DC4F15DC1219F02019F4D903CAA6E8B23AF83CE291F4A6
Key-Arg : None
Krb5 Principal: None
PSK identity: None
PSK identity hint: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - d9 fb 28 9f c9 7c ba b3-26 ff dd 75 53 d1 12 65 ..(..|..&..uS..e
0010 - 91 76 91 2b f8 a2 b4 4b-0a e2 97 eb ce 8e a1 af .v.+...K........
[ ... lines ommitted for brevity ... ]
00a0 - 71 c3 a9 f3 16 c4 04 17-d1 e8 b0 75 e8 80 e9 fb q..........u....
Start Time: 1477857075
Timeout : 300 (sec)
Verify return code: 19 (self signed certificate in certificate chain)
---
Why does Verify think that I'm using a self-signed certificate? Is there something else that I need to do, for instance does OpenSSL check the certificate store in /etc/pki/tls for a certificate for the root authority that signed Let's Encrypt's certificate and not find it perhaps when you link in the OpenSSL libs? Is it possible that the trust store under /etc/pki doesn't know about the root authority used by Let's Encrypt already? I got lets-encrypt-x3-cross-signed.pem after reading instructions on the Let's Encrypt Chain Of Trust page, is this the wrong file completely?