erlang / otp

Erlang/OTP
http://erlang.org
Apache License 2.0
11.39k stars 2.96k forks source link

Erlang Critical EKU on ICA certificate: Fatal - Unsupported Certificate #8806

Closed loftkey closed 2 weeks ago

loftkey commented 2 months ago

Describe the bug When x509 Extended Key Usage (EKU) is marked as critical on a Intermediate Certificate Authority (ICA) certificate within a cert chain Erlang OTP errors out with.

{error,{tls_alert,{unsupported_certificate,"TLS client: In state wait_cert_cr at ssl_handshake.erl:2117 generated CLIENT ALERT: Fatal - Unsupported Certificate\n"}}}

OpenSSL, Postgres, Redis, JVM, Chrome, GoLang stdlib, and the .NET framework have no issues with these same certificate chains.

To Reproduce

1 Setup Download attached zip file. Extract it and navigate to the resulting directory in bash shell.

2 Create Root CA

openssl genrsa -out root.key 3072
openssl req -config root.cnf -key root.key -new -x509 -days 7300 -sha256 -extensions v3_ca -out root.crt -subj "/C=US/ST=California/L=Temecula/O=ACME Inc/OU=Labs/CN=Root CA/emailAddress=test@testing.io"

3 Good For Erlang Only extension marked as critical in the cert chain is Basic Constraints as that is necessary for valid CA certificates with x509 strict validation. 3.1 Create Intermediate CA

openssl genrsa -out good_ica.key 3072
openssl req -config good_ica.cnf -key good_ica.key -new -sha256 -out good_ica.csr -subj "/C=US/ST=California/L=Temecula/O=ACME Inc/OU=GOOD/CN=Good ICA/emailAddress=good@testing.io"
openssl x509 -req -extfile good_ica.cnf -extensions v3_intermediate_ca -CA root.crt -CAkey root.key -CAcreateserial -days 3650 -in good_ica.csr -out good_ica.crt

3.2 Create Cert

openssl genrsa -out good_test.key 4096
openssl req -config good_ica.cnf -key good_test.key -new -sha256 -out good_test.csr -subj "/C=US/ST=California/L=Temecula/O=ACME Inc/OU=GOOD_ICA_IN_USE/CN=GOOD Leaf Cert"
openssl x509 -req -extfile good_ica.cnf -extensions server_cert -CA good_ica.crt -CAkey good_ica.key -CAcreateserial -days 3650 -in good_test.csr -out good_test.crt

3.3 Create CA Chain

cat good_ica.crt root.crt > good_ca-chain.crt

3.4 Run TLS Test Server

openssl s_server -accept 8443 -cert good_test.crt -key good_test.key -CAfile good_ca-chain.crt

3.5 Test TLS with OpenSSL

openssl s_client -connect 127.0.0.1:8443  -showcerts -CAfile good_ca-chain.crt -verify_return_error -x509_strict

Happy Dance - everything looks good

---
Server certificate
subject=C=US, ST=California, L=Temecula, O=ACME Inc, OU=GOOD_ICA_IN_USE, CN=GOOD Leaf Cert
issuer=C=US, ST=California, L=Temecula, O=ACME Inc, OU=GOOD, CN=Good ICA, emailAddress=good@testing.io
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 5129 bytes and written 379 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

3.6 Test with Erlang OTP 26 (in Erlang shell) Command:

ssl:start(), ssl:connect("127.0.0.1", 8443, [{verify, verify_peer}, {versions, ['tlsv1.3']}, {cacertfile, "good_ca-chain.crt"}, {server_name_indication, disable}]).

Full Result (Happy Dance):

Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Eshell V14.2.5 (press Ctrl+G to abort, type help(). for help)
1> ssl:start(), ssl:connect("127.0.0.1", 8443, [{verify, verify_peer}, {versions, ['tlsv1.3']}, {cacertfile, "bad_ca-chain.crt"}, {server_name_indication, disable}]).
{ok,{sslsocket,{gen_tcp,#Port<0.4>,tls_connection,undefined},
               [<0.120.0>,<0.119.0>]}}

4 Bad For Erlang Critical is specified for Basic Constraints and EKU extensions for this test. Only change in the ICA cert extensions from #3 is that EKU is now marked as critical. 4.1 Create Intermediate CA

openssl genrsa -out bad_ica.key 3072
openssl req -config bad_ica.cnf -key bad_ica.key -new -sha256 -out bad_ica.csr -subj "/C=US/ST=California/L=Temecula/O=ACME Inc/OU=BAD/CN=BAD ICA/emailAddress=bad@testing.io"
openssl x509 -req -extfile bad_ica.cnf -extensions v3_intermediate_ca -CA root.crt -CAkey root.key -CAcreateserial -days 3650 -in bad_ica.csr -out bad_ica.crt

4.2 Create Cert

openssl genrsa -out bad_test.key 4096
openssl req -config bad_ica.cnf -key bad_test.key -new -sha256 -out bad_test.csr -subj "/C=US/ST=California/L=Temecula/O=ACME Inc/OU=BAD_ICA_IN_USE/CN=BAD Leaf Cert"
openssl x509 -req -extfile bad_ica.cnf -extensions server_cert -CA bad_ica.crt -CAkey bad_ica.key -CAcreateserial -days 3650 -in bad_test.csr -out bad_test.crt

4.3 Create CA Chain

cat bad_ica.crt root.crt > bad_ca-chain.crt

4.4 Run TLS Test Server

openssl s_server -accept 8443 -cert bad_test.crt -key bad_test.key -CAfile bad_ca-chain.crt

4.5 Test TLS with OpenSSL

openssl s_client -connect 127.0.0.1:8443  -showcerts -CAfile bad_ca-chain.crt -verify_return_error -x509_strict

Still Happy Dance!!

Server certificate
subject=C=US, ST=California, L=Temecula, O=ACME Inc, OU=BAD_ICA_IN_USE, CN=BAD Leaf Cert
issuer=C=US, ST=California, L=Temecula, O=ACME Inc, OU=BAD, CN=BAD ICA, emailAddress=bad@testing.io
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 5124 bytes and written 379 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

4.6 Test with Erlang OTP 26 (in Erlang shell) Command:

ssl:start(), ssl:connect("127.0.0.1", 8443, [{verify, verify_peer}, {versions, ['tlsv1.3']}, {cacertfile, "bad_ca-chain.crt"}, {server_name_indication, disable}]).

Full Result:

Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Eshell V14.2.5 (press Ctrl+G to abort, type help(). for help)
1> ssl:start(), ssl:connect("127.0.0.1", 8443, [{verify, verify_peer}, {versions, ['tlsv1.3']}, {cacertfile, "bad_ca-chain.crt"}, {server_name_indication, disable}]).
=NOTICE REPORT==== 12-Sep-2024::11:10:04.724373 ===
TLS client: In state wait_cert_cr at ssl_handshake.erl:2117 generated CLIENT ALERT: Fatal - Unsupported Certificate

{error,{tls_alert,{unsupported_certificate,"TLS client: In state wait_cert_cr at ssl_handshake.erl:2117 generated CLIENT ALERT: Fatal - Unsupported Certificate\n"}}}

Expected behavior Certificate chains with an ICA that contain a critical EKU extension are seen as having a supported extension.

Affected versions Seen in both 25 and 26 major versions of Erlang OTP.

Additional context See attached ZIP file for files produced in the test steps above. minimalRepo.zip

IngelaAndin commented 2 months ago

Well to make a long story short. Extensions marked critical must be verified. If they are not marked critical and they do not have to be verified but could be. if they are "known". Users can use the verify_fun to themselves verify extensions not verified by default or to make further or application specific checks this works both with pyblic_key directly or via TLS (ssl application).

When it comes to Extended Key Usage, we previously always checked it to include TLS-client/server use, as most commonly this extension is used in the peer cert. However it is allowed for CA certs so we changed it to only verify the peer-cert for TLS-clint/server use which makes more sense. And then it became unhandled for CAs, however it seems reasonable to check it for certificate signing if it is a CA so we could add that, as it seems other implementations do.

loftkey commented 2 months ago

Awesome, thank you for your time and help. If you need any information about this issue let us know!

IngelaAndin commented 1 month ago

@loftkey So this was a little more involved then anticipated, but now finally I have shaped up my PR #8840 so that you can try it out. I still need to work some on a new test case.

loftkey commented 1 month ago

@IngelaAndin We have tested your fix, and it is working! Thank you so much!

$ erl
Erlang/OTP 27 [erts-15.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Eshell V15.1 (press Ctrl+G to abort, type help(). for help)
1> ssl:start(), ssl:connect("127.0.0.1", 8443, [{verify, verify_peer}, {versions, ['tlsv1.3']}, {cacertfile, "bad_ca-chain.crt"}, {server_name_indication, disable}]).
{ok,{sslsocket,{gen_tcp,#Port<0.5>,tls_connection,undefined},
               [<0.116.0>,<0.115.0>]}}