8
votes

There is a self-signed root CA certificate, rootcert.pem, with the following V3 extensions:

X509v3 extensions:
    X509v3 Key Usage: 
       Certificate Sign
    X509v3 Basic Constraints: 
       CA:TRUE

The rootcert.pem has CA flag set to true & its key usage permits certificate signing. The rootcert.pem signs a certificate foocert.pem with the following V3 extensions:

X509v3 extensions:
    X509v3 Basic Constraints: 
        CA:FALSE
    X509v3 Key Usage: 
        Digital Signature, Non Repudiation, Key Encipherment

The foocert.pem has CA flag set to false & its key usage does not permit certificate signing. But lets say, the foocert.pem signs a rougecert.pem.

Form a certificate chain of foocert & roguecert:

cat foocert.pem roguecert.pem > roguechain.pem

Verify using openssl verify. The verification is successful:

$ openssl verify  -CAfile rootcert.pem roguechain.pem 
roguechain.pem: OK

Verify with x509_strict, still its successful:

$ openssl verify -x509_strict -CAfile rootcert.pem badchain.pem 
badchain.pem: OK

Lets say a system trusts just the root CA certificate. A peer gives its certificate chain, where one of the intermediate certificate is not supposed to be a CA (CA set to false & key usage does not permit certificate signing), still the openssl flags the chain is good?

How do I make openssl not trust such chains?

3
Where does badchain.pem come from? Was it supposed to be rougechain.pem again?Greg Dubicki
It's possible this was a bug in openssl that was fixed in version 1.1.0: github.com/openssl/openssl/issues/5236 Although in that GitHub issue it looks like they're using openssl s_client, but below @paulus said he was using openssl s_client and didn't see the problem. So I don't know what's going on.Mark Doliner

3 Answers

6
votes

The original answer proved to be wrong, so here's another one :)

It looks like openssl verify does only the certificate chain verifications and it doesn't check any flags (even with correct -purpose set). The library however does check the flags when you actually do an ssl/tls connection:

$ openssl s_client -verify 100 -CAfile cacert.pem -connect servername:443
verify depth is 100
CONNECTED(00000003)
depth=1 /CN=middle
verify error:num=24:invalid CA certificate
verify return:1
depth=1 /CN=middle
verify error:num=26:unsupported certificate purpose
verify return:1
depth=2 /CN=root
verify return:1
depth=1 /CN=middle
verify return:1
depth=0 /CN=leaf
verify return:1
---
Certificate chain
0 s:/CN=leaf
  i:/CN=middle
1 s:/CN=middle
  i:/CN=root
---
...
SSL-Session:
    Protocol  : TLSv1
    ...
    Verify return code: 26 (unsupported certificate purpose)
5
votes

(edit: just noticed my answer is a longer and more detailed version of Chris Lesner's, I'd vote him up but I don't have the rep here yet :>)

I think you're still on the wrong track here. I think the actual issue is that 'verify' does not verify chains in a single file in this way.

The final argument to 'verify' is listed as [certificates] and documented thus:

   certificates
       One or more certificates to verify. If no certificates are given, verify will attempt to read a
       certificate from standard input. Certificates must be in PEM format.

You can pass multiple files - but each will be verified as a single leaf certificate, independent of the others. And it's not absolutely explicit, but it implies (and I can tell you that it is in fact the case; see code locations below for details) that one certificate will be read from each file. If you feed it a file containing multiple certificates concatenated together, it will in fact just verify the first certificate from the file as a leaf certificate, and entirely ignore the others.

Thus in your example, what you actually did was verify foocert.pem as a leaf certificate, because it's the first certificate in your roguechain.pem. foocert.pem is valid as a leaf certificate, even with -purpose sslserver. man x509 section CERTIFICATE EXTENSIONS documents the conditions for the 'SSL Server' purpose:

   SSL Server
       The extended key usage extension must be absent or include the "web server authentication" and/or one
       of the SGC OIDs.  keyUsage must be absent or it must have the digitalSignature, the keyEncipherment set
       or both bits set.  Netscape certificate type must be absent or have the SSL server bit set.

and you can see that your foocert.pem has no extended key usage and is not a Netscape type certificate; it does have key usage, with both Digital Signature and Key Encipherment bits set. Therefore, it passed the test.

To do the check you really wanted to do, you have to do this:

openssl verify -CAfile rootcert.pem -untrusted foocert.pem -purpose sslserver roguecert.pem

using the filenames you gave in your initial question. Basically, it's a reasonable simplification to say that you provide root CA certs as -CAfile, -CApath, or -trusted, intermediate CA certs as -untrusted, and the leaf certificate(s) to be validated as the final argument(s).

Note that the file passed as -untrusted can contain multiple certificates concatenated together:

   -untrusted file
       A file of untrusted certificates. The file should contain multiple certificates in PEM format
       concatenated together.

If you want to follow the code to confirm this, each leaf certificate file is loaded by load_cert() in apps/apps.c. The return for that function is X509; X509 objects are single certificates. Compare with load_certs() in the same file, which returns STACK_OF(X509) - that's what OpenSSL typically uses for raw certificate chains.

4
votes

I suspect openssl is looking only at the first thing it finds in the roguecert.pem file which is the valid contents of foocert.pem. (To prove it to yourself swap the args to cat.) To verify a certificate chain supply the certificates in the chain that are not trusted using:

   -untrusted file
       A file of untrusted certificates. The file should contain multiple 
       certificates in PEM format concatenated together.