erlang / otp

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

`CLIENT ALERT: Fatal - Handshake Failure` for `www.gutenberg.org` #8466

Closed wojtekmach closed 5 months ago

wojtekmach commented 6 months ago

Not sure if this is a bug or the server/certificate is misconfigured.

To Reproduce

$ rtx current erlang
26.2.5
$ erl
Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Eshell V14.2.5 (press Ctrl+G to abort, type help(). for help)
1> ssl:start(), {ok, _} = ssl:connect("elixir-lang.org", 443, [{cacerts, public_key:cacerts_get()}]).
{ok,{sslsocket,{gen_tcp,#Port<0.5>,tls_connection,undefined},
               [<0.126.0>,<0.125.0>]}}
2> ssl:start(), {ok, _} = ssl:connect("www.gutenberg.org", 443, [{cacerts, public_key:cacerts_get()}]).
=NOTICE REPORT==== 8-May-2024::15:47:06.645138 ===
TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Bad Certificate

** exception error: no match of right hand side value {error,{tls_alert,{bad_certificate,"TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Bad Certificate\n"}}}

On OTP 25.3.2.11:

$ erl
Erlang/OTP 25 [erts-13.2.2.8] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Eshell V13.2.2.8  (abort with ^G)
1> ssl:start(), {ok, _} = ssl:connect("www.gutenberg.org", 443, [{cacerts, public_key:cacerts_get()}]).
=WARNING REPORT==== 8-May-2024::15:49:35.290652 ===
Description: "Server authenticity is not verified since certificate path validation is not enabled"
     Reason: "The option {verify, verify_peer} and one of the options 'cacertfile' or 'cacerts' are required to enable this."

{ok,{sslsocket,{gen_tcp,#Port<0.6>,tls_connection,undefined},
               [<0.122.0>,<0.121.0>]}}
2> ssl:start(), {ok, _} = ssl:connect("www.gutenberg.org", 443, [{cacerts, public_key:cacerts_get()}, {verify, verify_peer}]).
=NOTICE REPORT==== 8-May-2024::15:49:51.226210 ===
TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Handshake Failure
 - {bad_cert,hostname_check_failed}
** exception error: no match of right hand side value {error,{tls_alert,{handshake_failure,"TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Handshake Failure\n {bad_cert,hostname_check_failed}"}}}

Affected versions

Tested on OTP 25.3.2.11 and 26.2.5.

Additional context

Original report at: https://github.com/elixir-nx/axon/issues/570#issuecomment-2100563285

IngelaAndin commented 6 months ago

For the hostname check not to fail you want to use the option {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)} to handle wildcard certs.

From doc:

{customize_hostname_check, HostNameCheckOpts} - Customization option

Customizes the hostname verification of the peer certificate, as different protocols that use TLS such as HTTP or LDAP may want to do it differently. For example the get standard HTTPS handling provide the already implememnted fun from the public_key application for HTTPS. {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} For futher description of customize options see [public_key:pkix_verify_hostname/3](http://otp.ericsson.se/product/internal/test/daily/logs/net/artemis/ldisk/daily_build/27_master-opu_o_ensure-os-monotonic-time_docker_100029.2024-05-06_21/docs/lib/public_key-1.16/doc/html/public_key.html#pkix_verify_hostname/3)

But it seems there is something wrong with the chain as I get this result in OTP-27 with or without the customize_hostname_check.

Trace of function checking the chain:

public_key:pkix_path_validation/3 -> {error,
                                                                {bad_cert,
                                                                 invalid_signature}}
wojtekmach commented 6 months ago

Thank you for quick reply. I tried creating the minimum reproduction but I skipped that detail about hostname, thank you for point that out. FWIW a more real world reproduction is this I believe, same result and also on OTP 26.2.5:

1> inets:start(), ssl:start(), httpc:request("https://www.gutenberg.org").
=NOTICE REPORT==== 8-May-2024::16:58:46.016644 ===
TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Bad Certificate

{error,{failed_connect,[{to_address,{"www.gutenberg.org",
                                     443}},
                        {inet,[inet],
                              {tls_alert,{bad_certificate,"TLS client: In state wait_cert_cr at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Bad Certificate\n"}}}]}}
IngelaAndin commented 6 months ago

Yes and my run also ends up with Bad Certificate alert from the ssl application.

IngelaAndin commented 6 months ago

@wojtekmach Hum ... I noticed that the problem with the cert chain is that it has one cert that uses sha (sha1) in its signature algorithm and that is no longer supported by default.

So you need to add that. You can allow it just for certificate signatures like this:

> Defalut = ssl:signature_algs(default, 'tlsv1.3').
[eddsa_ed25519,eddsa_ed448,ecdsa_secp521r1_sha512,
 ecdsa_secp384r1_sha384,ecdsa_secp256r1_sha256,
 ecdsa_brainpoolP512r1tls13_sha512,
 ecdsa_brainpoolP384r1tls13_sha384,
 ecdsa_brainpoolP256r1tls13_sha256,rsa_pss_pss_sha512,
 rsa_pss_pss_sha384,rsa_pss_pss_sha256,rsa_pss_rsae_sha512,
 rsa_pss_rsae_sha384,rsa_pss_rsae_sha256,rsa_pkcs1_sha512,
 rsa_pkcs1_sha384,rsa_pkcs1_sha256,
 {sha512,ecdsa},
 {sha384,ecdsa},
 {sha256,ecdsa}]

> ssl:connect("www.gutenberg.org", 443, [{cacerts, public_key:cacerts_get()}, {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}, {signature_algs_cert, 
Default ++ [{sha, rsa}]}]).
mwhitworth commented 6 months ago

Is there scope for a more specific error return for this case?

Strictly speaking the certificate is not bad. The specific error case is that the certificate's signature algorithm will not be processed due to client configuration.

IngelaAndin commented 6 months ago

We can look into if at least the logging of the error can be more specific. On the top of my head I can not say if it would be possible to give another TLS-alert, it might be, will check when I am back at work. Will leave this open for future enhancement of error handling.

IngelaAndin commented 6 months ago

@mwhitworth I think unsupported certificate alert could be more appropriate for this case.