What is a self-signed certificate in the certificate chain? Why is my OpenSSL complaining about it?

1.3K    Asked by AlexanderCoxon in SQL Server , Asked on Dec 2, 2021

I am trying to set up a certificate chain for a lab server. I have created my own root CA, an intermediate CA, and a server certificate. I supplied these certificates along with the server key to the OpenSSL s_server command. When I run OpenSSL s_client and connect to that server, OpenSSL complains that there is a self-signed certificate in the certificate chain.


When I connect to a public web server using s_client, however, not only does the server not send all of the certificates in the chain (just the intermediate parent certificate of the server certificate) but OpenSSL doesn't complain about a self-signed certificate, let alone an incomplete certificate chain.

If I use s_server with a CA file containing just the server's parent intermediate certificate, s_client complains that it can't get the local issuer certificate. I never see this error with public web servers even though they don't send the entire certificate chain.


In none of these tests (using my own certificates or public web servers) I am using the -CApath, -CAfile or -verify options with the s_client command.

I don't know what I'm doing wrong. Why does s_client complain about my self-signed certificate in the certificate chain even though I don't use -verify? Why is it complaining about my (I assume) root certificate being self-signed when all root certificates are self-signed? For example, this is what I get with a public web server:


openssl s_client -showcerts -servername security.stackexchange.com -connect security.stackexchange.com:443 CONNECTED(00000004) depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 verify return:1 depth=0 CN = *.stackexchange.com verify return:1 ---

But using s_server with my full certificate chain, I get this:


openssl s_client -showcerts -servername server.domain.com -connect server.domain.com:443 CONNECTED(00000004) depth=2 C = US, ST = State, L = City, O = Company, OU = Company CA verify error:num=19:self signed certificate in certificate chain


Here are my certificates. And yes, I have the constraints CA=TRUE, Digital Signature and Certificate Sign set in my root CA.


root CA:

intermediate CA:

server certificate:


Answered by Anisha Dalal

First, it is quite correct for a TLS/SSL server to send the intermediate aka chain cert(s) but not the root cert; see rfc 5246 sec 7.4.2 or the slightly more verbose version in rfc 8446 sec 4.4.2.

As Z.T. mostly correctly commented, -verify is the default for s_client (you don't need to specify it) and if you don't specify -CAfile and/or -CApath by default (except for bugs in some obsolete versions) it uses a default trust store, configured at compile-time, which can include either a single file with concatenated PEM certs or a directory containing separate PEM files with names or links using truncated subject hashes usually created by c_rehash (add) or, as commented, manually determined with x509 -hash; see the man page for verify(1ssl) on your system or on the web. OpenSSL itself doesn't provide the root certs that go in such a truststore; if you are using a distro or other packaged version, the builder usually configures OpenSSL to use a set of root certs provided by the distro or package. (On distros I know, this distro-provided truststore is also used for other software including NSS, GNUtls, and Java; see below.) If you (or someone) built OpenSSL by hand, you have to do this yourself. Since you are not getting a verify error on public servers, your build presumably is using a truststore that has (at least some) public CA's preinstalled; you can see where this is with openssl version -d (plus the hardcoded names cert.pem and/or certs).

You can add your root or other anchor (see below) to this default truststore, or use -CAfile and/or -CApath to specify a custom one containing your own root(s) or anchor(s). On the distros, I know, and probably others, you should NOT hand-modify the default files because they are automatically generated by a process that also sets the truststores used by other software such as NSS, GNUtls, and Java; these need to contain the same data (certs) but in different formats. Instead, see e.g. man update-ca-trust for RedHat family or man update-ca-certificates for Debian family.

OpenSSL will use an intermediate (aka chain) cert or certs in the truststore to build the cert chain if needed, i.e. if not sent by the server (in violation of the RFC, but many do that), but historically it will only accept a chain -- either fully received from the server or (partly) built from the local truststore -- if it ends at a root that is in the local truststore. For recent versions, namely 1.0.2 up but only documented since 1.1.0, there is an option -partial_chain which does accept a chain ending in an intermediate (non-root) that is in the local truststore. How to fix a self-signed certificate in the certificate chain?

For Node.js

You can insert an environment variable to allow untrusted certificates using the following command at the beginning of the code:

  process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;

This is risky and it’s not recommended to be used in production. Alternatively, use npm config set strict-ssl=false if you have to do this for many applications and you want to save repeating the process. Users also suggest upgrading your version of Node, to fixes any existing bugs and vulnerabilities.

For npm

The recommended solution is, again, to upgrade your version of npm running one of the following:

npm install npm -g --ca=null 
npm update npm -g

Or, tell your current version of npm to use known registrars, and after installing, stop using them:

npm config set ca ""

npm install npm -g

npm config delete ca

Some users mentioned that they only switched the registry URL from https to http:

npm config set registry="http://registry.npmjs.org/"



Your Answer

Interviews

Parent Categories