If you are working with self-signed SSL/TLS certificates, e.g. in your internal test or lab environment, the communication is well encrypted but you have another problem: The certificate cannot be verified.
In this tutorial we show how to add the self-signed certificate to your local list of trusted CA (Certificate Authorities) certificates.
Signed by unknown authority / Unable to get local issuer
This may lead to application issues when the application is communicating with your service with a self-signed certificate installed. For example when an application runs against an API with a self-signed certificate, the application might bail out with an error because the underlying operating system is not able to verify the certificate:
2024/04/15 02:08:02 ERROR : : error reading destination directory: RequestError: send request failed
caused by: Get "https://minio.lab.local:9000/backup?delimiter=%2F&encoding-type=url&max-keys=1000&prefix=bucket%2F": x509: certificate signed by unknown authority
In this example, an application wanted to access a locally installed Minio with a self-signed certificate – and the application refused to talk to the Minio server.
Depending on the application, the error may show a different error, for example:
unable to get local issuer certificate
The reason for these errors is that there was no local certificate found inside /etc/ssl/certs which would be at the top of the certificate chain and therefore used as certificate authority.
Manual verification with openssl
A manual verification can be done with the openssl command. The first few lines already reveal that the certificate runs into a verify error because a self-signed certificate was detected:
ck@linux:~$ openssl s_client -connect minio.lab.local:9000
CONNECTED(00000003)
depth=0 CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
verify return:1
---
Certificate chain
0 s:CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
i:CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
---
Server certificate
-----BEGIN CERTIFICATE-----
[...]
Further down in the output (after the certificate), a verification error is also showing up:
-----END CERTIFICATE-----
subject=CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
issuer=CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1887 bytes and written 373 bytes
Verification error: self signed certificate
---
[...]
Add certificate to local CA certificates
If the certificate was created by a CA (even your own CA), the CA's certificate could be used and placed under /usr/local/share/ca-certificates with a .crt suffix. In this situation we have a self-signed certificate without a CA, meaning a standalone server certificate.
We can use the openssl command from before again to retrieve the server certificate:
ck@linux:~$ openssl s_client -connect minio.lab.local:9000
[...]
Server certificate
-----BEGIN CERTIFICATE-----
MIIEvzCCAyegAwIBAgIUPw06iGutptld4uLkf1GpkIL3yMYwDQYJKoZIhvcNAQEL
[...]
O/V6qxj2gECEuP4dcUiaWuioiA==
-----END CERTIFICATE-----
[...]
Copy the certificate (including the lines —–BEGIN CERTIFICATE—– and —–END CERTIFICATE—–) and create a new crt file in /usr/local/share/ca-certificates/, for example /usr/local/share/ca-certificates/minio.lab.local.crt.
Then run the update-ca-certificates command. This command should detect your newly added crt file and add it to the global list of CA certificates (in /etc/ssl/certs/ca-certificates.crt):
root@linux:~# update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
Now that you added the self-signed server certificate to the ca-certificates, any TLS connection to the server using this certificate should now be verified.
Did it work?
You can verify the difference with openssl, once again.
root@owiland:~# openssl s_client -connect minio.lab.local:9000
CONNECTED(00000003)
depth=0 CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
verify return:1
---
Certificate chain
0 s:CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
i:CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEvzCCAyegAwIBAgIUPw06iGutptld4uLkf1GpkIL3yMYwDQYJKoZIhvcNAQEL
[...]
O/V6qxj2gECEuP4dcUiaWuioiA==
-----END CERTIFICATE-----
subject=CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
issuer=CN = *.lab.local, OU = Minio, O = Minio, L = Zurich, ST = Zurich, C = CH
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1887 bytes and written 373 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
Server public key is 3072 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
[...]
As we can see from the output, the certificate verification for the self-signed certificate is now working.
Other use cases
Of course the same procedure can also be applied to other kinds of certificates. For example if you are running a very outdated Linux machine (which we hope you don't) or your Linux machine runs in an air-gapped (= no Internet access) environment, you might need to manually add (new) Root CA certificates to the list of ca-certificates.