Post

What makes an intermediate certificate intermediate?

What makes an intermediate certificate intermediate?

When inspecting x509 certificates, it’s not always obvious which attributes denote the purpose of a certificate — whether root, intermediate, or end-entity.

Questions to explore

This post works through the following questions, with examples where applicable:

  • What are the observable differences between root, intermediate, and end-entity certificates
  • The x509v3 extensions appear to restrict certificate usage, what happened before these extensions were added?
  • Can I sign a certificate with another that’s not intended to be a root/intermediate e.g. asking openssl to ignore these v3 extensions?
  • Is enforcement ultimately a responsibility of the validating party to ensure that a non-signing certificate has not been used in a chain?
  • How do different clients behave when attempting to validate a broken certificate chain

Certificate differences

Subject and issuer certificate fields

Let’s use wikipedia.com and look at the certificate chain it presents with openssl s_client (with a bit of awk to extract the relevant lines for the presented certificate chain, up to 9 certificates, rarely see more than 3 or 4 in most scenarios):

1
2
3
$ echo "GET /" | \
openssl s_client -showcerts -connect www.wikipedia.com:443 -verify_return_error  2>&1 | \
awk '/^\s+[0-9]/,/\s+v:/ {print }'
Expand for full openssl s_client output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
$ echo "GET /" | openssl s_client -showcerts -connect www.wikipedia.com:443 -verify_return_error
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = E5
verify return:1
depth=0 CN = wikipedia.com
verify return:1
---
Certificate chain
 0 s:CN = wikipedia.com
   i:C = US, O = Let's Encrypt, CN = E5
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
   v:NotBefore: Oct 31 23:18:47 2024 GMT; NotAfter: Jan 29 23:18:46 2025 GMT
-----BEGIN CERTIFICATE-----
MIIFTTCCBNSgAwIBAgISA713TCJy7J9coRpo9VWF9QYXMAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NTAeFw0yNDEwMzEyMzE4NDdaFw0yNTAxMjkyMzE4NDZaMBgxFjAUBgNVBAMTDXdp
a2lwZWRpYS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARytAnZIrrqdbAj
YnczbBJepbPE6W6PjAJHVlJZnh8e+UwRX3EKLCLLgp44NENFK4Jcd84xxPG+mN7c
ct/tCTR6o4ID4jCCA94wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUF
BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBThg433eChTZtwP
wg2vOojHgvIkozAfBgNVHSMEGDAWgBSfK1/PPCFPnQS37SssxMZwi9LXDTBVBggr
BgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9lNS5vLmxlbmNyLm9yZzAi
BggrBgEFBQcwAoYWaHR0cDovL2U1LmkubGVuY3Iub3JnLzCCAekGA1UdEQSCAeAw
ggHcggsqLmVuLXdwLmNvbYILKi5lbi13cC5vcmeCDyoubWVkaWF3aWtpLmNvbYIQ
Ki52b3lhZ2V3aWtpLmNvbYIQKi52b3lhZ2V3aWtpLm9yZ4IQKi53aWlraXBlZGlh
LmNvbYIOKi53aWtpYm9vay5jb22CDyoud2lraWJvb2tzLmNvbYIPKi53aWtpZXBk
aWEuY29tgg8qLndpa2llcGRpYS5vcmeCECoud2lraWlwZWRpYS5vcmeCECoud2lr
aWp1bmlvci5jb22CECoud2lraWp1bmlvci5uZXSCECoud2lraWp1bmlvci5vcmeC
Dyoud2lraXBlZGlhLmNvbYIJZW4td3AuY29tggllbi13cC5vcmeCDW1lZGlhd2lr
aS5jb22CDnZveWFnZXdpa2kuY29tgg52b3lhZ2V3aWtpLm9yZ4IOd2lpa2lwZWRp
YS5jb22CDHdpa2lib29rLmNvbYINd2lraWJvb2tzLmNvbYINd2lraWVwZGlhLmNv
bYINd2lraWVwZGlhLm9yZ4IOd2lraWlwZWRpYS5vcmeCDndpa2lqdW5pb3IuY29t
gg53aWtpanVuaW9yLm5ldIIOd2lraWp1bmlvci5vcmeCDXdpa2lwZWRpYS5jb20w
EwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdgDP
EVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZLlFWOoAAAEAwBHMEUC
IQDOQfIBwT2aOdrc6rFQG83V5VIy/StPJsBkp/cwmoueIQIgGciNTDnZVQXASCoL
0ajX3BH4DtoXtHn608o3pWUkvpUAdgB9WR4S4XgqexxhZ3xe/fjQh1wUoE6VnrkD
L9kOjC55uAAAAZLlFWNYAAAEAwBHMEUCIQCm2m4aDT5gs4aDws4ImktVBg/8pt2E
uWsR6NKhwtr/6gIgZOncM/2inGNQmuA+GjXPKX7xgmfNgOL0PEYgPL3FaeEwCgYI
KoZIzj0EAwMDZwAwZAIwIBCXYaRy2WTUBfXVdvqxHkwDWOpiwkGfqzjFnSiI/PUj
XyIZ/mSqjLbRqdCKOqmYAjBvqfap85eomWIgj224K1vGbWDk/Ku3ITz968PslQaK
ptg9/G94hhVkiWqqT+n6uF8=
-----END CERTIFICATE-----
 1 s:C = US, O = Let's Encrypt, CN = E5
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   a:PKEY: id-ecPublicKey, 384 (bit); sigalg: RSA-SHA256
   v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT
-----BEGIN CERTIFICATE-----
MIIEVzCCAj+gAwIBAgIRAIOPbGPOsTmMYgZigxXJ/d4wDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCRTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNCzqK
a2GOtu/cX1jnxkJFVKtj9mZhSAouWXW0gQI3ULc/FnncmOyhKJdyIBwsz9V8UiBO
VHhbhBRrwJCuhezAUUE8Wod/Bk3U/mDR+mwt4X2VEIiiCFQPmRpM5uoKrNijgfgw
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSfK1/PPCFPnQS37SssxMZw
i9LXDTAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAH3KdNEVCQdqk0LKyuNImTKdRJY1C
2uw2SJajuhqkyGPY8C+zzsufZ+mgnhnq1A2KVQOSykOEnUbx1cy637rBAihx97r+
bcwbZM6sTDIaEriR/PLk6LKs9Be0uoVxgOKDcpG9svD33J+G9Lcfv1K9luDmSTgG
6XNFIN5vfI5gs/lMPyojEMdIzK9blcl2/1vKxO8WGCcjvsQ1nJ/Pwt8LQZBfOFyV
XP8ubAp/au3dc4EKWG9MO5zcx1qT9+NXRGdVWxGvmBFRAajciMfXME1ZuGmk3/GO
koAM7ZkjZmleyokP1LGzmfJcUd9s7eeu1/9/eg5XlXd/55GtYjAM+C4DG5i7eaNq
cm2F+yxYIPt6cbbtYVNJCGfHWqHEQ4FYStUyFnv8sjyqU8ypgZaNJ9aVcWSICLOI
E1/Qv/7oKsnZCWJ926wU6RqG1OYPGOi1zuABhLw61cuPVDT28nQS/e6z95cJXq0e
K1BcaJ6fJZsmbjRgD5p3mvEf5vdQM7MCEvU0tHbsx2I5mHHJoABHb8KVBgWp/lcX
GWiWaeOyB7RP+OfDtvi2OsapxXiV7vNVs7fMlrRjY1joKaqmmycnBvAq14AEbtyL
sVfOS66B8apkeFX2NY4XPEYV4ZSCe8VHPrdrERk2wILG3T/EGmSIkCYVUMSnjmJd
VQD9F6Na/+zmXCc=
-----END CERTIFICATE-----
---
Server certificate
subject=CN = wikipedia.com
issuer=C = US, O = Let's Encrypt, CN = E5
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2856 bytes and written 399 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
DONE

This returns:

1
2
3
4
5
6
7
8
  0 s:CN = wikipedia.com
   i:C = US, O = Let's Encrypt, CN = E5
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
   v:NotBefore: Oct 31 23:18:47 2024 GMT; NotAfter: Jan 29 23:18:46 2025 GMT
 1 s:C = US, O = Let's Encrypt, CN = E5
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   a:PKEY: id-ecPublicKey, 384 (bit); sigalg: RSA-SHA256
   v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT

Core observation is that the issuer of the end-entity/service certificate is the subject of the next certificate up in the chain (and so on). This subject -> issuer chain extends all the way to the root certificate in the trust store of the client.

From a root certificate perspective, the way openssl s_client displays this isn’t intuitive (it only displays the certificate bundle presented by the service, but not the root), additionally I have a suspicion that openssl (OpenSSL 3.0.13) is always using my machines (Ubuntu) trust store and validating the full chain, so there is no easy way to see the difference between a chain that isn’t validated against a root vs. one that is, with openssl.

To show the similar fields from the matching root;

1
2
3
$ cat /etc/ssl/certs/ISRG_Root_X1.pem | openssl x509 -text -noout | egrep 'Issuer:|Subject:'
        Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
        Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1

The hierarchy is reflected in the chain of subject and issuer fields. Joining these two outputs answers the first question — what are the observable differences between end-entity, intermediate and root certificates:

1
2
3
4
5
6
7
8
9
10
11
12
* end entity/subject
s:CN = wikipedia.com
i:C = US, O = Let's Encrypt, CN = E5

* intermediate
s:C = US, O = Let's Encrypt, CN = E5
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1

* root
s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1

One wrinkle worth noting: an intermediate can have more than one issuer. CAs sometimes have their intermediate signed by two different roots, so that clients trusting either root can validate the chain. Let’s Encrypt used this approach — see Extending Android Device Compatibility — to maintain compatibility with older Android devices that didn’t trust the ISRG Root X1. The same E5 intermediate shown above was also signed by the older DST Root CA X3. The subject/issuer chain picture holds, but path building can involve choosing between multiple valid paths to a trusted root.

Extension fields

The x509 v3 extensions also declare what a certificate is allowed to be used for.

I copied the PEM blocks from the openssl s_client output into files named end-entity and intermediate:

The end entity cert;

1
2
3
4
5
6
7
$ cat end-entity | openssl x509 -noout -ext keyUsage,extendedKeyUsage,basicConstraints
X509v3 Key Usage: critical
    Digital Signature
X509v3 Extended Key Usage:
    TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
    CA:FALSE

The intermediate cert;

1
2
3
4
5
6
7
$ cat intermediate | openssl x509 -noout -ext keyUsage,extendedKeyUsage,basicConstraints
X509v3 Key Usage: critical
    Digital Signature, Certificate Sign, CRL Sign
X509v3 Extended Key Usage:
    TLS Web Client Authentication, TLS Web Server Authentication
X509v3 Basic Constraints: critical
    CA:TRUE, pathlen:0

And the root;

1
2
3
4
5
6
$ cat /etc/ssl/certs/ISRG_Root_X1.pem | \
  openssl x509 -noout -ext keyUsage,extendedKeyUsage,basicConstraints
X509v3 Key Usage: critical
    Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
    CA:TRUE

This highlights the other observable difference between root, intermediate and end entity certificates:

  • Root and intermediate certs will have the CA boolean set to true in the basicConstraints field, and Certificate Sign set in the Key Usage field
  • Intermediate certificates may have a pathlen value set in the basicConstraints field denoting whether the certificate should be used to sign intermediates or not
  • End-entity certificates will have the CA boolean set to false for basicConstraints, and will not have Certificate Sign set in the keyUsage field

The full and authoritative details of these (and all the other) certificate fields is contained within rfc5280

CAs generally conform to these extension values, but clients must always verify the chain independently. This BlackHat presentation (PDF and video) covers issues that arose when clients failed to do so — covered further later in this post.

What happened before these extensions were added?

I couldn’t find a clear answer on what prevented this before v3 extensions — specifically, what stopped a holder of a CA-signed cert from using their private key to sign a subordinate cert and pass it off as an intermediate.

x509 V1 & V2 largely pre-date widespread SSL/TLS use, so the issue may simply not have been considered. V3 extensions arrived as SSL/TLS became prevalent, and this appears to be an issue they resolved.

Can I sign a certificate with another that’s not intended to be a root/intermediate

First, create a dummy CA using the openssl default config:

1
2
3
4
5
6
$ cd ~/dummyCA/
$ openssl req -x509 -nodes -newkey rsa:2048 -keyout dummyCA.key \
  -sha256 -days 365 -out dummyCA.pem \
  -subj "/CN=dummyCA/"
$ ls
dummyCA.key  dummyCA.pem
Expand for full `-text` openssl output of this cert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$ openssl x509 -in dummyCA.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            3e:44:4c:e5:37:d0:56:4b:2a:ac:4b:4e:77:e5:f2:e2:69:64:27:14
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = dummyCA
        Validity
            Not Before: Dec  8 17:16:03 2024 GMT
            Not After : Dec  8 17:16:03 2025 GMT
        Subject: CN = dummyCA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:a2:79:0d:9e:1a:a9:82:4d:cc:33:3f:db:bc:43:
                    ab:64:e1:9b:3e:f3:92:d0:da:18:e0:8c:8e:73:e8:
                    b0:93:a7:d7:ae:df:d8:92:29:d4:a0:50:ca:5e:2e:
                    c5:6e:6f:c8:35:89:80:b0:a7:9d:53:2d:64:24:35:
                    08:91:e0:99:77:48:2e:da:93:f0:ea:25:3c:5c:de:
                    a8:a2:d5:e6:b2:f3:a1:15:68:ce:a4:37:26:cf:bb:
                    ba:43:02:d5:ac:7f:a2:43:3c:8e:fb:19:89:05:49:
                    55:07:59:5c:79:b1:b4:51:f8:d1:d4:b3:87:73:bd:
                    b3:9a:9c:e2:8e:b5:76:d5:0b:b5:3f:df:4b:cf:4b:
                    2f:2d:ad:fc:7e:0a:e7:b9:79:ea:20:ec:ca:b6:0b:
                    83:ed:20:e4:1a:18:83:19:f5:73:12:00:5f:a8:7b:
                    b6:f7:18:86:3a:31:d3:39:37:d3:ac:37:0d:ea:f0:
                    33:1e:dc:e1:74:61:8b:50:70:7c:09:60:d4:8f:54:
                    68:c5:a6:4a:1d:d9:c6:05:a7:95:ac:e2:5c:f4:59:
                    a7:61:86:26:63:0a:4f:6e:39:76:d4:a1:34:e7:5c:
                    83:03:d0:a6:fd:27:65:78:a4:27:91:0e:bb:75:00:
                    f9:40:23:32:d1:48:c7:6d:31:9c:0e:9a:98:88:e2:
                    68:9f
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                3A:1A:83:4F:F4:57:F3:E4:39:9C:33:00:48:76:8F:64:67:92:78:40
            X509v3 Authority Key Identifier:
                3A:1A:83:4F:F4:57:F3:E4:39:9C:33:00:48:76:8F:64:67:92:78:40
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        74:10:4c:69:6d:25:47:2f:96:0f:75:79:9b:cc:41:a8:47:7d:
        69:33:5f:9a:94:c0:44:b9:60:b9:30:fe:d5:5d:0e:1e:40:bd:
        13:5b:0a:d3:ae:38:21:8c:2b:30:5f:59:51:f9:04:57:4b:48:
        4f:e7:1f:6b:a6:85:44:5b:32:29:20:9a:2e:97:17:81:68:6f:
        7b:a2:48:54:05:2d:74:12:b0:f0:72:fd:39:07:a7:b4:b9:bd:
        c5:61:cd:21:3f:9e:5c:c9:58:f1:a5:a2:91:97:a9:44:36:2c:
        6c:67:50:e6:ac:d2:dd:45:96:77:a7:62:91:2e:cf:cd:6a:b1:
        b4:71:8b:a2:32:45:0e:9c:aa:bc:e7:e6:09:84:04:43:7e:3f:
        46:43:0f:4b:99:f8:80:d1:4f:d1:b4:b7:f0:1a:a2:d8:76:c5:
        e2:57:75:eb:82:a1:f3:73:d3:77:40:e4:a3:74:99:8c:5f:ec:
        e0:d4:52:d7:b4:a5:89:bf:ed:5c:13:b4:8e:f9:a6:02:c7:f9:
        5a:f4:4a:6f:e5:5b:8e:d0:50:27:3a:98:f3:eb:07:a5:ef:9c:
        16:0d:a8:40:d0:91:3e:1a:4e:c7:a1:27:66:42:12:11:11:ec:
        6d:4c:74:d1:02:57:d2:a6:04:98:51:6a:f1:66:13:a5:0b:8c:
        21:fd:12:38

Next, create a CSR for the end-entity (skipping an intermediate for now):

1
2
$ openssl req -nodes -newkey rsa:2048 -keyout end-entity.key \
   -out end-entity.csr -subj "/CN=end-entity/"
Expand for full `-text` openssl output of this CSR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ openssl req -in end-entity.csr -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = end-entity
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b9:3a:98:e4:73:d8:4b:be:66:9a:d5:15:de:bd:
                    41:aa:76:cd:2e:4f:b9:e2:5d:1e:6b:f0:de:7c:9f:
                    38:14:4b:52:56:3b:15:ac:aa:59:74:68:f8:d5:0b:
                    d4:36:7e:84:e8:b1:0d:16:9a:3d:10:6b:84:5b:ec:
                    13:56:46:06:1b:2f:54:9f:a0:d0:43:96:a3:d3:40:
                    9f:de:7d:6d:c9:46:c5:56:28:b1:d0:1c:2d:a1:74:
                    40:4a:6b:f1:b8:62:7a:05:d3:c9:b3:67:4e:09:84:
                    bf:24:a7:57:97:d0:10:09:20:38:f6:7a:10:6c:19:
                    de:5e:25:0a:cf:00:76:7d:b2:e8:79:6e:62:94:99:
                    ec:15:7b:b6:7d:39:4e:cd:ed:68:06:93:db:7f:b1:
                    36:5d:c2:b1:d6:71:b7:15:65:9d:51:cb:be:43:0c:
                    cd:c7:77:bb:d9:72:2b:4a:ab:c4:0b:fd:21:a9:49:
                    d6:53:a3:1d:40:39:a5:0c:05:c5:a9:d9:36:d7:17:
                    05:5d:9d:72:32:14:fc:66:3d:b5:09:42:fb:49:49:
                    8f:8e:89:c0:2b:60:f1:bc:79:b5:62:80:79:35:f2:
                    2c:11:f4:03:41:6a:f8:65:8c:63:53:c7:b2:b8:df:
                    2f:f9:d4:14:c0:e2:a9:b1:6c:8a:ce:61:72:04:d4:
                    b9:27
                Exponent: 65537 (0x10001)
        Attributes:
            (none)
            Requested Extensions:
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        19:e4:ea:13:69:1e:cb:51:9a:e5:a4:9c:b4:dc:2b:bd:c7:1b:
        f6:5e:9c:f9:b9:75:65:9b:53:d2:c7:6f:96:d7:3b:b7:93:09:
        91:d7:50:20:3e:32:38:3c:f8:36:96:73:b0:64:a6:9a:ae:f4:
        15:4c:9d:e5:df:7a:98:d3:65:85:e2:ba:77:06:08:a9:c6:fa:
        25:7b:0e:95:76:39:fc:76:aa:be:c6:82:b3:c9:a5:63:bc:3a:
        38:48:d2:c6:42:8f:be:3a:c7:7b:03:a1:3c:b5:24:90:99:20:
        7a:10:26:05:26:b3:09:be:31:74:1d:1a:8c:23:f5:b6:ee:55:
        87:24:a8:87:c9:02:84:7e:62:6a:e7:d2:95:80:06:30:fc:f8:
        cd:14:ca:d5:11:47:ca:68:3a:ad:51:13:74:9d:53:43:b1:ef:
        98:af:6e:c9:1f:5b:88:c5:41:97:94:e8:36:45:fe:ef:d5:9b:
        8b:40:30:c8:09:9b:a8:e6:b6:6a:83:2d:24:ac:08:69:99:d5:
        2f:22:3d:f8:d1:6a:45:ad:41:f6:6d:7d:3d:fd:f0:ca:ee:31:
        28:1a:ea:86:3b:93:1d:33:fc:66:d0:6e:76:d2:24:e5:76:57:
        5c:fc:5b:1d:50:67:9e:8d:99:b9:88:e0:39:90:59:34:ab:7e:
        83:b7:8a:c0

Next, sign with the dummyCA using an extension file to produce a v3 certificate with CA:FALSE basicConstraint set:

1
2
3
4
5
6
7
8
$ cat v3.ext
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical, CA:FALSE
keyUsage = digitalSignature
$ openssl x509 -req -in end-entity.csr -CA dummyCA.pem -CAkey dummyCA.key \
  -CAcreateserial -out end-entity.pem -days 364 -sha256 -extfile v3.ext
Certificate request self-signature ok
subject=CN = end-entity
Expand for full `-text` openssl output of this signed certificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
$ openssl x509 -in end-entity.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            12:c8:b2:0b:cb:e3:4e:a7:3e:a7:12:8f:f2:79:98:e2:df:6f:72:83
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = dummyCA
        Validity
            Not Before: Dec  8 18:10:54 2024 GMT
            Not After : Dec  7 18:10:54 2025 GMT
        Subject: CN = end-entity
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b9:3a:98:e4:73:d8:4b:be:66:9a:d5:15:de:bd:
                    41:aa:76:cd:2e:4f:b9:e2:5d:1e:6b:f0:de:7c:9f:
                    38:14:4b:52:56:3b:15:ac:aa:59:74:68:f8:d5:0b:
                    d4:36:7e:84:e8:b1:0d:16:9a:3d:10:6b:84:5b:ec:
                    13:56:46:06:1b:2f:54:9f:a0:d0:43:96:a3:d3:40:
                    9f:de:7d:6d:c9:46:c5:56:28:b1:d0:1c:2d:a1:74:
                    40:4a:6b:f1:b8:62:7a:05:d3:c9:b3:67:4e:09:84:
                    bf:24:a7:57:97:d0:10:09:20:38:f6:7a:10:6c:19:
                    de:5e:25:0a:cf:00:76:7d:b2:e8:79:6e:62:94:99:
                    ec:15:7b:b6:7d:39:4e:cd:ed:68:06:93:db:7f:b1:
                    36:5d:c2:b1:d6:71:b7:15:65:9d:51:cb:be:43:0c:
                    cd:c7:77:bb:d9:72:2b:4a:ab:c4:0b:fd:21:a9:49:
                    d6:53:a3:1d:40:39:a5:0c:05:c5:a9:d9:36:d7:17:
                    05:5d:9d:72:32:14:fc:66:3d:b5:09:42:fb:49:49:
                    8f:8e:89:c0:2b:60:f1:bc:79:b5:62:80:79:35:f2:
                    2c:11:f4:03:41:6a:f8:65:8c:63:53:c7:b2:b8:df:
                    2f:f9:d4:14:c0:e2:a9:b1:6c:8a:ce:61:72:04:d4:
                    b9:27
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                3A:1A:83:4F:F4:57:F3:E4:39:9C:33:00:48:76:8F:64:67:92:78:40
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature
            X509v3 Subject Key Identifier:
                48:4F:E9:2C:38:CF:01:EC:3C:EB:57:4E:CC:65:91:91:EF:F8:56:CA
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        9d:54:ba:25:bd:c3:4a:55:6d:5c:7d:d4:d5:e5:9e:5d:10:55:
        bf:55:25:37:99:49:f6:ed:5b:ef:53:f7:ec:a3:bb:a8:d3:a8:
        5e:55:e9:3a:a2:5d:92:d1:5b:c1:69:8b:9b:26:e5:89:99:ae:
        35:43:fd:ea:72:ef:d2:45:da:e7:9e:a0:44:85:4f:55:73:fb:
        e9:7f:ec:75:47:c4:ba:de:55:d2:6c:65:51:ef:72:62:2a:2f:
        5e:b5:7f:a4:99:28:c2:66:28:55:d5:77:19:ca:24:7e:7d:22:
        c2:0c:64:d8:c4:fa:79:4b:bb:63:5c:7e:bc:35:46:7c:a7:32:
        3d:38:35:e6:18:9d:d6:9a:0e:98:7a:59:14:fb:fc:23:8a:25:
        6c:cd:91:18:05:89:92:62:82:39:f0:9a:33:c2:44:1b:bd:3e:
        b7:4d:91:3a:3d:a2:67:9c:91:66:8e:e8:e1:14:52:2b:ea:d7:
        df:f9:e2:4e:07:c3:b5:20:3a:7a:ae:c5:76:2c:06:67:a9:5c:
        2d:8f:95:bf:00:9d:be:71:8c:9a:e6:c7:40:a6:88:8b:5e:e4:
        d1:9e:24:df:60:02:89:ae:c6:f2:1c:9c:8e:ab:27:76:37:77:
        68:91:56:8d:46:ab:79:e8:18:25:26:ac:ca:58:a9:53:01:de:
        2f:ca:d9:59

We can double check that this root and end entity chain verifies…

1
2
3
4
5
$ openssl verify -show_chain -CAfile dummyCA.pem end-entity.pem
end-entity.pem: OK
Chain:
depth=0: CN = end-entity (untrusted)
depth=1: CN = dummyCA

In a real scenario, we’d have our end-entity private key and signed end-entity certificate, but not the CA’s private key (or any intermediate private keys). To simulate this, delete it:

1
$ rm dummyCA.key

Next, create a fake end-entity certificate — one we’ll attempt to sign with our CA-signed end-entity certificate:

1
2
$ openssl req -nodes -newkey rsa:2048 -keyout next-end-entity.key \
   -out next-end-entity.csr -subj "/CN=next-end-entity/"
Expand for full `-text` openssl output of this CSR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ openssl req -in next-end-entity.csr -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = next-end-entity
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:9d:c8:d1:d4:4c:9a:e8:ce:39:60:0e:9f:5c:db:
                    1a:82:c7:15:9d:93:2b:05:e4:9c:a5:7f:1f:7d:a7:
                    c2:8c:ed:a4:74:da:7d:45:ef:a1:38:6e:a9:bb:52:
                    2a:35:18:97:53:c9:30:b9:ea:8e:72:be:ac:c0:29:
                    48:57:33:fe:6a:63:03:9b:fd:71:1b:98:d4:8c:ef:
                    92:43:a9:38:4f:54:b6:29:0c:2b:a9:3d:d1:44:e1:
                    c4:dd:d5:97:a8:cb:ac:77:3e:e1:ab:7f:ff:17:9c:
                    f8:46:87:4c:2b:94:bc:d7:10:09:e1:7c:48:2e:53:
                    d1:39:25:1c:19:a4:30:ac:76:46:7e:45:33:7c:f2:
                    6a:e9:db:8b:1a:57:bb:cb:6c:be:7f:f5:a2:4d:6c:
                    e4:35:1b:f7:81:c8:50:3d:ec:ea:50:ee:d1:6f:90:
                    b9:e9:43:37:22:5c:22:88:44:68:46:e1:76:ad:09:
                    ff:bf:06:bd:d6:f9:cd:06:c7:cf:10:b1:ff:96:da:
                    a3:db:45:21:9d:bc:2e:ae:f8:48:62:92:19:13:33:
                    d5:84:92:85:05:b4:08:79:a5:8e:b0:78:c2:a1:6d:
                    6d:a3:7c:17:1f:f0:b9:54:66:b2:3c:02:70:9c:e3:
                    25:ff:f6:ba:df:6d:57:11:46:7f:47:2f:ed:04:c5:
                    c1:c3
                Exponent: 65537 (0x10001)
        Attributes:
            (none)
            Requested Extensions:
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        5d:74:69:da:46:83:65:4a:ae:c0:25:ba:a9:8f:d2:ea:aa:9d:
        2e:bb:2f:37:87:02:38:c1:e6:ef:d4:aa:7a:cb:2a:21:aa:c1:
        fe:d1:75:7c:29:64:d4:23:31:d1:b1:01:d9:bb:bb:a3:3a:a6:
        ff:15:48:2f:42:b8:c6:10:be:11:cd:ef:b1:92:00:fa:23:c5:
        5b:33:40:a3:d5:5f:90:45:29:87:d9:b6:72:a5:77:89:33:03:
        c4:b1:95:29:65:88:a5:99:18:b9:f7:de:25:7a:60:ff:57:44:
        b7:43:0c:8f:bc:8d:8f:9b:c0:03:75:37:26:d7:d2:bb:cb:f9:
        dc:a7:69:95:bc:c9:39:ca:7c:88:71:98:db:b1:66:c4:4b:f5:
        22:5b:25:60:59:c2:ad:4a:41:2f:64:b0:9a:f9:3a:89:24:07:
        61:72:ac:19:47:e2:2c:b1:71:4d:12:14:be:d9:a6:a0:f4:05:
        1b:8b:e1:7e:4e:47:e6:8b:f3:a4:0e:bd:af:1f:cc:ba:e6:1a:
        e2:89:ab:16:f9:31:9a:c6:e8:78:1a:62:80:e9:dd:de:26:33:
        42:48:7e:15:81:de:1e:98:95:6e:fc:98:5a:e5:9e:63:0d:1a:
        e4:db:de:67:66:02:32:45:a6:3d:1a:73:03:46:31:2d:24:4d:
        f8:e6:7a:d0

Next, attempt to sign using the CA-signed end-entity cert as the CA — it has CA:FALSE set, so it will be interesting to see how openssl responds. Using the same v3.ext as above:

1
2
3
4
$ openssl x509 -req -in next-end-entity.csr -CA end-entity.pem -CAkey end-entity.key \
  -CAcreateserial -out next-end-entity.pem -days 364 -sha256 -extfile v3.ext
Certificate request self-signature ok
subject=CN = end-entity

openssl signs without complaint, warning or error - disregarding the CA:FALSE value.

1
2
3
$ openssl x509 -in next-end-entity.pem -noout -text | egrep '^\s+Issuer:|^\s+Subject:'
        Issuer: CN = end-entity
        Subject: CN = next-end-entity
Expand for full `-text` openssl output of this signed certificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
$ openssl x509 -in next-end-entity.pem -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            38:49:c3:4b:cc:2c:b3:6e:03:7a:91:a9:96:45:11:6d:8d:bd:44:75
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = end-entity
        Validity
            Not Before: Dec  8 20:03:02 2024 GMT
            Not After : Dec  7 20:03:02 2025 GMT
        Subject: CN = next-end-entity
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:9d:c8:d1:d4:4c:9a:e8:ce:39:60:0e:9f:5c:db:
                    1a:82:c7:15:9d:93:2b:05:e4:9c:a5:7f:1f:7d:a7:
                    c2:8c:ed:a4:74:da:7d:45:ef:a1:38:6e:a9:bb:52:
                    2a:35:18:97:53:c9:30:b9:ea:8e:72:be:ac:c0:29:
                    48:57:33:fe:6a:63:03:9b:fd:71:1b:98:d4:8c:ef:
                    92:43:a9:38:4f:54:b6:29:0c:2b:a9:3d:d1:44:e1:
                    c4:dd:d5:97:a8:cb:ac:77:3e:e1:ab:7f:ff:17:9c:
                    f8:46:87:4c:2b:94:bc:d7:10:09:e1:7c:48:2e:53:
                    d1:39:25:1c:19:a4:30:ac:76:46:7e:45:33:7c:f2:
                    6a:e9:db:8b:1a:57:bb:cb:6c:be:7f:f5:a2:4d:6c:
                    e4:35:1b:f7:81:c8:50:3d:ec:ea:50:ee:d1:6f:90:
                    b9:e9:43:37:22:5c:22:88:44:68:46:e1:76:ad:09:
                    ff:bf:06:bd:d6:f9:cd:06:c7:cf:10:b1:ff:96:da:
                    a3:db:45:21:9d:bc:2e:ae:f8:48:62:92:19:13:33:
                    d5:84:92:85:05:b4:08:79:a5:8e:b0:78:c2:a1:6d:
                    6d:a3:7c:17:1f:f0:b9:54:66:b2:3c:02:70:9c:e3:
                    25:ff:f6:ba:df:6d:57:11:46:7f:47:2f:ed:04:c5:
                    c1:c3
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                48:4F:E9:2C:38:CF:01:EC:3C:EB:57:4E:CC:65:91:91:EF:F8:56:CA
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature
            X509v3 Subject Key Identifier:
                B2:E8:5E:19:E8:F7:AD:54:ED:B6:3F:76:4D:44:B7:4C:30:A1:C5:07
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        18:ec:8e:03:ab:1e:f7:b1:fd:89:73:7d:1c:4a:2d:c1:e4:0d:
        24:8c:67:73:b1:8d:a0:db:17:16:6e:b2:36:c0:9c:a0:2d:46:
        75:83:17:b0:5e:15:4d:1d:60:d9:3e:a0:2c:8e:ef:62:f3:8d:
        cc:fc:2b:c0:27:9e:9a:65:d1:a4:af:47:82:d4:01:49:7a:a9:
        d6:65:bf:ee:4d:2c:61:72:7c:28:1b:28:e8:03:25:c5:28:f3:
        64:64:d4:62:b9:cd:eb:d7:0f:4f:9a:2c:95:da:95:6f:ce:56:
        1e:63:c0:b5:88:16:c9:03:ff:b8:8f:85:e6:22:1c:c1:4a:d1:
        31:08:04:fe:ce:08:4c:a2:f7:e2:9a:e2:be:b9:fd:0d:74:f1:
        12:93:a9:86:46:f2:94:5b:e0:bb:15:1f:f0:85:b9:73:e7:f9:
        7b:69:71:31:48:57:a4:22:7a:bf:31:77:80:f0:45:cd:91:76:
        b6:aa:07:f0:d0:8d:bf:67:0a:7f:40:a3:cd:29:e3:48:e5:11:
        7a:55:6a:9b:02:52:0d:bc:29:09:65:8c:1c:48:63:8a:7a:dc:
        ef:9b:d6:ff:d9:b1:83:3c:c7:4b:5d:4e:c3:f0:6e:ca:96:9e:
        52:50:4b:2c:5e:b3:97:35:8a:9a:ea:bb:38:8f:09:eb:83:fc:
        85:58:74:cd

But, attempting to use openssl verify throws an error due to the key usage field of the (fake) intermediate

1
2
3
4
5
6
$ openssl verify -show_chain -CAfile dummyCA.pem -untrusted end-entity.pem next-end-entity.pem
CN = end-entity
error 79 at 1 depth lookup: invalid CA certificate
CN = end-entity
error 32 at 1 depth lookup: key usage does not include certificate signing
error next-end-entity.pem: verification failed

To answer the original question:

  • Can I sign a certificate with another that’s not intended to be a root/intermediate e.g. asking openssl to ignore these v3 extensions?

Yes — openssl signs a certificate using another with CA:FALSE set, without needing any flag to override it. It may be worth testing whether other extension combinations produce different behaviour, but I assume not.

Is enforcement ultimately a responsibility of the validating party

As no other party is involved when using a signed certificate to sign a subordinate, the client bears ultimate responsibility for verifying that a certificate chain meets the required characteristics and rejecting it if not.

Other features of the public PKI architecture may also help identify certificates signed by unexpected parties — certificate pinning and CT logs being likely candidates — to be explored in a later post.

How do different clients behave when attempting to validate a broken certificate chain

Trying openssl s_server/s_client first… Starting a service with s_server:

1
2
3
4
5
$ openssl s_server -key next-end-entity.key -cert next-end-entity.pem \
  -cert_chain end-entity.pem -accept 4433 -www
Using default temp DH parameters
ACCEPT

Then connecting with s_client and finding the verification errors…

1
2
3
4
5
6
$ echo "GET /" | openssl s_client -connect localhost:4433 -CAfile dummyCA.pem 2>&1 | \
  egrep '^verify error:|^Verification error'
verify error:num=79:invalid CA certificate
verify error:num=26:unsuitable certificate purpose
verify error:num=32:key usage does not include certificate signing
Verification error: key usage does not include certificate signing

Clearly, verification errors are identified.

Testing with Chrome requires two setup steps to isolate errors caused by the invalid chain, rather than other unrelated issues:

  1. Create a new next-end-entity certificate with localhost in the subjectAltName field
  2. Install the dummyCA root in the local truststore

For the first step, create a new CSR for next-end-entity2 with the subjectAltName field:

1
2
3
$ openssl req -nodes -newkey rsa:2048 -keyout next-end-entity2.key \
   -out next-end-entity2.csr -subj "/CN=next-end-entity2/" \
   -addext "subjectAltName = DNS:localhost"
Expand for full `-text` openssl output of this CSR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ openssl req -in next-end-entity2.csr -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = next-end-entity2
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c5:1c:c6:70:ec:20:7c:cf:0c:93:01:d3:2a:67:
                    ce:24:f7:5c:07:20:95:48:6e:df:1d:2b:ff:85:c8:
                    d2:12:e8:3a:13:71:02:38:e5:93:a5:06:5b:44:74:
                    0b:f1:9c:5c:e7:2e:34:8e:1f:bd:75:ef:d2:3a:01:
                    1d:15:38:32:db:72:5b:dd:17:f0:a3:bc:1b:47:97:
                    6e:80:38:76:0e:08:0a:3a:80:93:35:70:97:57:20:
                    49:26:15:5f:58:58:e6:0a:64:77:8e:ac:fb:0d:8c:
                    55:7e:57:dd:70:83:d1:aa:21:fa:bd:43:f6:a5:80:
                    bd:e9:d3:c0:2b:35:48:29:0d:84:3c:7c:33:a9:e3:
                    f1:b3:b9:6b:f7:e8:a8:5c:00:da:da:bf:97:e2:c8:
                    77:cc:bf:b9:bf:86:64:be:36:81:4a:88:03:e0:ca:
                    e9:3a:49:bb:1e:e8:10:0e:71:ab:75:be:64:b3:cb:
                    cf:22:ac:3a:f3:d6:bb:25:6c:5a:f6:1a:51:16:39:
                    7e:d9:68:6c:e1:ec:25:31:7d:4b:9b:eb:7a:cd:ba:
                    6b:9e:78:89:4b:2c:dc:e8:eb:4d:b7:01:ed:39:e5:
                    09:43:c2:95:bf:23:8a:aa:91:74:15:eb:d9:5e:89:
                    38:40:b1:eb:11:5b:d8:fc:c1:cb:6c:48:39:b9:86:
                    2b:13
                Exponent: 65537 (0x10001)
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name:
                    DNS:localhost
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        69:70:91:c0:71:c6:2d:e3:a7:ce:3c:a5:fb:1d:db:33:f6:ab:
        46:c0:ee:ed:29:a8:ee:25:75:97:14:34:c9:ef:99:47:95:44:
        26:89:5a:f2:e4:fb:e6:f7:86:66:ae:0d:21:13:4d:72:9f:d1:
        11:e6:6a:3a:ff:5f:d7:33:5c:70:91:d7:f6:6d:79:32:0e:c2:
        ac:02:e7:e7:11:5e:6f:2a:7f:ad:b1:8e:e3:74:01:f9:f7:c4:
        7a:12:bf:cf:63:c2:8d:39:0c:3a:0d:1c:72:d8:8c:5a:d6:9b:
        11:e9:64:4c:67:c7:cb:77:d9:d1:12:4e:e2:28:5f:cb:f2:4b:
        dc:26:20:12:30:07:c2:f5:17:bd:21:59:a2:48:4f:6d:bf:88:
        93:ea:94:f5:ff:74:de:b9:a4:72:59:92:60:eb:a5:55:6e:23:
        3b:82:3a:e5:7f:bd:25:30:25:e3:59:dd:88:03:43:a7:1a:1b:
        fe:7d:e4:71:f4:46:47:90:ec:2d:20:10:1f:a0:7e:72:ae:e9:
        da:ad:95:07:a5:e8:a6:31:c3:79:5c:78:64:d1:44:77:c9:d3:
        53:4a:c9:e3:63:fe:73:1a:1d:1c:4e:26:58:cd:9e:b8:b5:39:
        1d:0c:9b:9b:b4:d6:00:7b:7c:9a:01:26:f9:47:46:c5:f0:95:
        07:d5:a7:98

Next, sign using the CA-signed cert as the CA, with the same v3.ext as above and -copy_extensions copy to bring in the subjectAltName field:

1
2
3
4
$ openssl x509 -req -in next-end-entity2.csr -CA end-entity.pem -CAkey end-entity.key \
  -CAcreateserial -out next-end-entity2.pem -days 364 -sha256 \
  -extfile v3.ext -copy_extensions copy

Expand for full `-text` openssl output of this signed certificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
$ openssl x509 -in next-end-entity2.pem -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            38:49:c3:4b:cc:2c:b3:6e:03:7a:91:a9:96:45:11:6d:8d:bd:44:77
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = end-entity
        Validity
            Not Before: Dec  8 21:24:13 2024 GMT
            Not After : Dec  7 21:24:13 2025 GMT
        Subject: CN = next-end-entity2
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c5:1c:c6:70:ec:20:7c:cf:0c:93:01:d3:2a:67:
                    ce:24:f7:5c:07:20:95:48:6e:df:1d:2b:ff:85:c8:
                    d2:12:e8:3a:13:71:02:38:e5:93:a5:06:5b:44:74:
                    0b:f1:9c:5c:e7:2e:34:8e:1f:bd:75:ef:d2:3a:01:
                    1d:15:38:32:db:72:5b:dd:17:f0:a3:bc:1b:47:97:
                    6e:80:38:76:0e:08:0a:3a:80:93:35:70:97:57:20:
                    49:26:15:5f:58:58:e6:0a:64:77:8e:ac:fb:0d:8c:
                    55:7e:57:dd:70:83:d1:aa:21:fa:bd:43:f6:a5:80:
                    bd:e9:d3:c0:2b:35:48:29:0d:84:3c:7c:33:a9:e3:
                    f1:b3:b9:6b:f7:e8:a8:5c:00:da:da:bf:97:e2:c8:
                    77:cc:bf:b9:bf:86:64:be:36:81:4a:88:03:e0:ca:
                    e9:3a:49:bb:1e:e8:10:0e:71:ab:75:be:64:b3:cb:
                    cf:22:ac:3a:f3:d6:bb:25:6c:5a:f6:1a:51:16:39:
                    7e:d9:68:6c:e1:ec:25:31:7d:4b:9b:eb:7a:cd:ba:
                    6b:9e:78:89:4b:2c:dc:e8:eb:4d:b7:01:ed:39:e5:
                    09:43:c2:95:bf:23:8a:aa:91:74:15:eb:d9:5e:89:
                    38:40:b1:eb:11:5b:d8:fc:c1:cb:6c:48:39:b9:86:
                    2b:13
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:localhost
            X509v3 Authority Key Identifier:
                48:4F:E9:2C:38:CF:01:EC:3C:EB:57:4E:CC:65:91:91:EF:F8:56:CA
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature
            X509v3 Subject Key Identifier:
                BD:5F:C1:84:2B:B3:27:AE:5E:5B:D9:A7:B1:3E:6B:6B:6E:38:68:80
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        13:78:a6:9c:49:3e:56:a3:a2:30:31:16:fa:4a:80:af:33:e3:
        eb:6f:ae:be:96:89:6b:b1:66:2e:c0:be:93:83:56:9e:39:32:
        39:84:a7:6f:70:e1:1d:fa:26:11:90:a5:75:97:40:37:0e:e4:
        ad:82:3f:ca:28:a9:b6:da:64:e9:fc:e3:6d:47:f9:bb:93:80:
        d8:f5:3e:3d:fa:2c:cf:d4:02:5e:63:84:b0:37:94:23:c3:f0:
        b7:6e:e3:0a:bb:9c:50:1c:96:d8:15:a8:1c:a0:0b:f9:7a:c9:
        50:12:4b:9d:a3:b0:6a:0c:4b:ef:ed:e5:93:31:a5:02:5c:57:
        6f:4b:57:4e:0f:20:17:c1:c5:cb:ed:0e:0b:50:7e:a7:a3:9c:
        42:c0:3d:78:ab:97:1f:0b:85:b6:42:ba:4b:f1:25:d0:f1:ec:
        bf:f4:a4:1c:c8:41:5a:f5:ba:c0:f2:41:0d:18:da:60:62:0b:
        0c:36:d7:1a:4e:6e:10:2f:91:f9:d8:59:f2:5c:f7:21:16:f8:
        e0:46:c7:85:b2:33:23:97:64:aa:f7:b1:38:1c:73:1b:8a:ec:
        f1:cd:8a:3a:45:89:7f:1a:fd:25:c6:40:e0:26:aa:2a:a5:4c:
        a1:6c:ea:14:05:fc:d7:5f:6f:ad:b0:c2:3f:e3:99:13:ed:8a:
        47:9b:99:40

This gives an end-entity certificate signed by the invalid intermediate CA-signed cert, with localhost in the subjectAltName field.

Start s_server with the new end-entity certificate next-end-entity2.pem:

1
2
3
4
$ openssl s_server -key next-end-entity2.key -cert next-end-entity2.pem \
  -cert_chain end-entity.pem -accept 4433 -www
Using default temp DH parameters
ACCEPT

With the dummyCA root added to the OS truststore (for Chrome) and the Firefox truststore, connecting from both browsers gives:

Chrome

Chrome error Chrome error

Firefox

Firefox error Firefox error

Neither browser offers the option to ignore the error and proceed — unlike some certificate errors, there is no bypass for this one.

To answer the original question:

  • How do different clients behave when attempting to validate a broken certificate chain

openssl s_client, Chrome and Firefox all throw errors, and neither browser offers a way to bypass them.

Summary

The observable differences between certificate types come down to two things: the subject/issuer chain (roots are self-signed, intermediates bridge root to end-entity) and the v3 extension fields (CA:TRUE/FALSE, Certificate Sign, pathlen). The extensions are the mechanism that should enforce these roles, but as the signing experiment shows, openssl doesn’t enforce them at signing time — enforcement is the client’s responsibility at validation time, which the verify and browser tests confirm. What came before v3 extensions isn’t well documented, but the problem appears to have simply not been addressed until SSL/TLS made it relevant.

This post is licensed under CC BY 4.0 by the author.