Google Chrome 75.x, a self-signed certificate and ERR_SSL_KEY_USAGE_INCOMPATIBLE

Just imagine, you have a small web server running with nginx and you have not given in to Let’s Encrypt to secure your https website. For some odd reason you just never bothered to set it all up, just because.

The web server runs fine, Google Chrome is unhappy because it can not find the certificate authority to validate the certificate, gives you a warning but you can proceed.

And suddenly things break. Break horribly.

You realize, lots of projects use Chromium (yes, I am looking at you Opera) so it will not just break for  Google Chrome >=75.x users but potentially for quite a lot of other browsers which are based on Chromium.

The error message is, of course, very helpful:

ERR_SSL_KEY_USAGE_INCOMPATIBLE

Well, the first thing you try is to search for it at Google and hope you get a ton of results back, work through 80% of the noise and find a solution.

And you now things will not work out as expected when the search results to not exceed the number 2.

Yes. In words: Two.

If you ever want to take part into a Google Search contest who will find the least results, that would be a good search term. After this blog posting it may increase to three.

As there are only two results to choose from I choose the one that looks most promising to me:

ssl/openssl_ssl_util.cc – chromium/src/net – Git at Google

Searching the file for the error message gets me to this part:
case SSL_R_KEY_USAGE_BIT_INCORRECT:
return ERR_SSL_KEY_USAGE_INCOMPATIBLE;
default:
return ERR_SSL_PROTOCOL_ERROR;

Interesting, I have learned that incompatible seems to be a widely known synonym for incorrect. Good to know. Maybe it is worth a try searching for  the keyword used in the case statement.

And behold, we are actually getting more search results. Four. Better than two…

One search result we already know about and the other three reference source code in boringssl.

The most promising link is this one:

boringssl/ssl_cert.cc at master · google/boringssl · GitHub

With the source code piece:
if (!CBS_asn1_bitstring_has_bit(&bit_string, bit)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_KEY_USAGE_BIT_INCORRECT);
return false;
}

So it is checking for some bits in the bitfield of the key usage. So what could be the problem? Is it the https setup of nginx which has been unchanged for months or something with the key/certificate pair which has also not been changed for months….

Obviously, I tried the easy path and testing various options in the nginx ssl configuration. Which did not help. But at least I turned on TLSv1.3.

Looking at the old, self-signed certificate (SAN set to simple.example.com to protect the innocent):
X509v3 extensions:
X509v3 Key Usage:
Key Encipherment, Data Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:simple.example.com

Looks good. Exactly what it should be. Or should it? More information concerning the key usage can be found in RFC 5280:
id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }


KeyUsage ::= BIT STRING {
digitalSignature (0),
nonRepudiation (1), -- recent editions of X.509 have
-- renamed this bit to contentCommitment
keyEncipherment (2),
dataEncipherment (3),
keyAgreement (4),
keyCertSign (5),
cRLSign (6),
encipherOnly (7),
decipherOnly (8) }

And for the key usages which were set in the certificate:
The keyEncipherment bit is asserted when the subject public key is
used for enciphering private or secret keys, i.e., for key
transport. For example, this bit shall be set when an RSA public
key is to be used for encrypting a symmetric content-decryption
key or an asymmetric private key.


The dataEncipherment bit is asserted when the subject public key
is used for directly enciphering raw user data without the use of
an intermediate symmetric cipher. Note that the use of this bit
is extremely uncommon; almost all applications use key transport
or key agreement to establish a symmetric key.

Uncommon…

At this point, I am just very puzzled, thus I began searching for problems with self-signed certificates and keyUsage bits and found the following entry:

Self Signed Root-CA not correctly verified when keyUsage set (Certificate Sign, CRL Sign)

And then the very important entry from Camilo Viecco (:cviecco):
The absence of KeyUsages implies that any usage is valid for that partciular certificate.

Let that sink in. No keyUsage implies it is valid for everything.

As https is broken (at least for Google Chrome 75.x) I might as well create a new key/certificate pair without any keyUsage. Surely that can not be incompatible/incorrect…

So I log onto the machine, hop over to /tmp and set it up a req.conf file for openssl:
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = VA
L = SomeCity
O = MyCompany
OU = MyDivision
CN = simple.example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = simple.example.com

And the openssl command line to use the configuration file:
openssl req -x509 -nodes -days 3650 -newkey rsa:8192 -keyout cert.pem -out cert.pem -config req.conf -extensions 'v3_req'

Once created, I have split the cert.pem into key.pem and cert.pem and put where nginx expects the files to be. Restarting nginx and, surprise, Google Chrome still does not like a self-signed certificate but at least it will then proceed if the user insists.

Maybe I should really look into Let’s Encrypt…

Advertisement