I have two extremely similar self signed certificates, generated via two different methods.
To test them I have:
- Added an entry in my hosts file for local.mydomain.com
- Set up an nginx server to listen on that domain on port 443 with the certificate under test plus associated private key (I then switch the cert and restart nginx to compare)
- Connected to nginx with
openssl s_client -connect local.mydomain.com -CAfile /path/to/the/ca/cert.pem
One certificate fails:
CONNECTED(00000003)
depth=0 CN = local.mydomain.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = local.mydomain.com
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/CN=local.mydomain.com
i:/CN=local.mydomain.com
---
One certificate succeeds:
CONNECTED(00000003)
depth=0 CN = local.mydomain.com
verify return:1
---
Certificate chain
0 s:/CN = local.mydomain.com
i:/CN = local.mydomain.com
---
I compare the details of the certificates with openssl x509 -in /path/to/the/ca/cert.pem -text -noout
The failing cert:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
47:dc:02:c7:11:fc:8e:96:45:22:aa:6b:23:79:32:ca
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=local.mydomain.com
Validity
Not Before: Nov 18 11:55:31 2016 GMT
Not After : Nov 18 12:15:31 2017 GMT
Subject: CN=local.mydomain.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
<stuff>
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication, TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:local.mydomain.com
X509v3 Subject Key Identifier:
6D:4F:AF:E4:60:23:72:E5:83:27:91:7D:1D:5F:E9:7C:D9:B6:00:2A
Signature Algorithm: sha256WithRSAEncryption
<stuff>
The working cert:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
9b:6b:3d:a3:b9:a3:a4:b4
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=local.mydomain.com
Validity
Not Before: Nov 19 13:27:30 2016 GMT
Not After : Nov 19 13:27:30 2017 GMT
Subject: CN=local.mydomain.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
<stuff>
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
03:E7:DA:AA:2E:CC:23:ED:C5:07:3D:E1:33:86:F5:22:D4:76:EB:CB
X509v3 Authority Key Identifier:
keyid:03:E7:DA:AA:2E:CC:23:ED:C5:07:3D:E1:33:86:F5:22:D4:76:EB:CB
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
57<stuff>
Looking at this the most obvious difference is that the working cert has CA:TRUE
under X509v3 Basic Constraints
. However, from reading around the web I was under the impression that self signed certs weren't meant to be CAs, in particular this says they normally won't be:
Basic self-signed certificate questions
The answer there says that being self-signed there is no CA involved. But maybe openssl requires self signed certs to have that set anyway? Or maybe the certs differ in some other pertinent way?
UPDATE:
I spent some time trying to printf debug the internals of openssl, not that I understand any of it. In the file v3_purp.c is the following macro:
#define ku_reject(x, usage) \
(((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage)))
It is used in this bit of code that checks for self-signed certificates:
/* Does subject name match issuer ? */
if (X509_check_akid(x, x->akid) == X509_V_OK &&
!ku_reject(x, KU_KEY_CERT_SIGN))
x->ex_flags |= EXFLAG_SS;
In the case of the failing certificate (x)->ex_flags & EXFLAG_KUSAGE
is equal to 2
EXFLAG_KUSAGE
gets set for the failing certificate here, earlier on in the same file:
static void x509v3_cache_extensions(X509 *x)
{
......
if ((usage = X509_get_ext_d2i(x, NID_key_usage, NULL, NULL))) {
if (usage->length > 0) {
x->ex_kusage = usage->data[0];
if (usage->length > 1)
x->ex_kusage |= usage->data[1] << 8;
} else
x->ex_kusage = 0;
x->ex_flags |= EXFLAG_KUSAGE;
ASN1_BIT_STRING_free(usage);
}
....
So it looks like the issue is the failing cert has the X509v3 Key Usage
extension, but doesn't specify KU_KEY_CERT_SIGN in that extension?
UPDATE 2:
According to https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3:
"The keyCertSign bit is asserted when the subject public key is used for verifying signatures on public key certificates. If the keyCertSign bit is asserted, then the cA bit in the basic constraints extension (Section 4.2.1.9) MUST also be asserted."
So the CA bit in basic constraints needn't be present, but if you include a X509v3 Key Usage section in the cert then according to the openssl codebase you must specify keyCertSign, and according to the RFC if you do specify keyCertSign then you must also include the CA bit basic constraints?