Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:tls_alert, 'handshake failure', https://www.bbc.co.uk #164

Open
docapotamus opened this issue Aug 10, 2016 · 21 comments
Open

:tls_alert, 'handshake failure', https://www.bbc.co.uk #164

docapotamus opened this issue Aug 10, 2016 · 21 comments

Comments

@docapotamus
Copy link

Hello,

Sorry I wasn't sure if to post this here or on hackney. I don't know all the ins-and-outs of SSL and I'm using Elixir so guessed this was the right place. If you would like me to move it too or reopen on hackney please let me know.

I am trying to check https://www.bbc.co.uk from HTTPoison and receiving:

[error] SSL: :hello: ssl_alert.erl:97:Fatal error: handshake failure

With the following tuple returned:

{:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, 'handshake failure'}}}}

I can't see anything wrong with the certificate when using openssl s_client -connect www.bbc.co.uk:443:

CONNECTED(00000003)
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = *.bbc.co.uk
verify return:1

---
Certificate chain
 0 s:/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=*.bbc.co.uk
   i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA

---
Server certificate
-----BEGIN CERTIFICATE-----
MIIF0zCCBLugAwIBAgISESGmCn6XQw4M0Yr34Hxqn/0FMA0GCSqGSIb3DQEBCwUA
MGYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYD
VQQDEzNHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gU0hB
MjU2IC0gRzIwHhcNMTYwMzE0MTcwMTA2WhcNMTcwMzE1MTcwMTA2WjBwMQswCQYD
VQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xKTAnBgNV
BAoTIEJyaXRpc2ggQnJvYWRjYXN0aW5nIENvcnBvcmF0aW9uMRQwEgYDVQQDDAsq
LmJiYy5jby51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJhk1p3B
nY+PBKhGHtyVKEJWONitLVBPAqK9E0265Nv9LY4A5mhui9dgnQK31cUdCYM8mzSc
iLtGzbcOZ9Wp95nfGQalcjG2PakE5vlbeVxR5K2khJD6Uvys6X3frxJ7CWjaY+ms
VURe5YeSZp8pMdB2QPFoSG1Yu7SM21yHtz0WbEITXtNLGBVUMzlT1wdyIWa0pNYH
IxKPZZN7oMtQuIzTFq+lLiuJAUGYBmw90yZcoC9pgq9W0qCV5phy19QtJj++t5QJ
2ZOqHqyP3VL1sMHfI09EBftLwzsWY0RXbZ84ePjf5ldfeLiEjBIstWzd+rNH2yww
2BsKBmLH5Ns/CU8CAwEAAaOCAm8wggJrMA4GA1UdDwEB/wQEAwIFoDBJBgNVHSAE
QjBAMD4GBmeBDAECAjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxz
aWduLmNvbS9yZXBvc2l0b3J5LzCBtQYDVR0RBIGtMIGqggsqLmJiYy5jby51a4IK
YmJjaS5jby51a4IHYmJjLmNvbYIObGl2ZS5iYmMuY28udWuCD2xpdmUuYmJjaS5j
by51a4IMbGl2ZS5iYmMuY29tggwqLmJiY2kuY28udWuCCSouYmJjLmNvbYIQKi5s
aXZlLmJiYy5jby51a4IRKi5saXZlLmJiY2kuY28udWuCDioubGl2ZS5iYmMuY29t
ggliYmMuY28udWswCQYDVR0TBAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
BQUHAwIwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2NybC5nbG9iYWxzaWduLmNv
bS9ncy9nc29yZ2FuaXphdGlvbnZhbHNoYTJnMi5jcmwwgaAGCCsGAQUFBwEBBIGT
MIGQME0GCCsGAQUFBzAChkFodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2Nh
Y2VydC9nc29yZ2FuaXphdGlvbnZhbHNoYTJnMnIxLmNydDA/BggrBgEFBQcwAYYz
aHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFsc2hh
MmcyMB0GA1UdDgQWBBR08gvI1HXfa/ExprxIjz0ovqm9vTAfBgNVHSMEGDAWgBSW
3mHxvRwWKVMcwMx9O4MAQOYafDANBgkqhkiG9w0BAQsFAAOCAQEAXymgwHvukz+R
HEpVFWwLKQJoXL6EoQPiQb0v+bX3lOlh2B+plG+Ev0GVU7XR+okpsZXuhu6LrLvm
TBnny1RTxozy4hRqQ44pylDo7KyN90ynDYSgm8W+9V/c51ZWlhhg2KkklJubnuH5
DUL5t+G3EVawUit+XuEbNfC3yBYvbQpY4rjelr1uNukcnvRyQ6+8PjGH50ZzyAni
o+ydZvbVLpTosye/KhCmnmVv7QsKrF73BzMVcsfob/nQ8zvM5vXtDPoI6FzD+YSW
J8LOf3GJf7EuwxB9N2uoTR5ademUF2eN+SjaMB/porVkHIMrmY9QR5cd9kY1cTvn
vhIf+cM6UA==
-----END CERTIFICATE-----
subject=/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=*.bbc.co.uk
issuer=/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2

---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits

---
SSL handshake has read 3136 bytes and written 434 bytes

---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 77AFF54A148AEC8BD6359129C69A2B33E22B86205BDBA720CA19DAD3934A5C09
    Session-ID-ctx: 
    Master-Key: 67730137790D1F41E4E99F24511B75C64D4A4C6004485E3C89CD4F6E5FF56B6D8B3CAD3A5D87C2C45FE2EB15C3450744
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1470851488
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)

---

I can provide anymore details that are needed. Its seems to be working in all browsers and via curl. I tested this against a fresh home brew installation of OpenSSL but I am also getting the issue on up to date Debian Jessie Machine.

Not sure how the Erlang TLS/SSL module works but I'm wondering if it is an issue there.

Thanks in advance

@edgurgel
Copy link
Owner

Sorry for the late reply but I'm pretty sure this is a hackney/erlang SSL issue :/

@docapotamus
Copy link
Author

No problem @edgurgel

I will open the issue there.

Thanks for the reply.

@jondlm
Copy link

jondlm commented Sep 19, 2016

I ran into a similar issue when trying to hit a service internally at my company. I'm pretty sure something funky is up with our cert chain, but I was able to find a workaround since all my traffic is behind our VPN:

HTTPoison.get("https://example.com/", [], [ssl: [{:versions, [:'tlsv1.2']}]])

# or worst case:

HTTPoison.get("https://example.com/", [], [ssl: [{:verify, :verify_none}]])

Hopefully that helps someone.

Updated based on a better recommendation

@daniel1943
Copy link

Thanks! @jondlm Probably saved me few hours :)

@teamon
Copy link

teamon commented Nov 15, 2016

Just for reference - it is better to force TLS v1.2 with .., [ssl: [{:versions, [:'tlsv1.2']}]]

@jbrowning
Copy link

I would suggest that this issue be re-opened as it's going to become more common as HTTPS endpoints upgrade their supported TLS versions. I ran into this today with the Slack API.

👍 for @teamon's workaround as it's much better than turning off SSL verification completely.

@edgurgel edgurgel reopened this Nov 17, 2016
@edgurgel
Copy link
Owner

Cool I will leave this open so people can jump in here if the README is not enough. Let's leave it open till Erlang 19 is past

@steven-mercatante
Copy link

Neither of @jondlm's suggestions worked for me.

Erlang/OTP: 19
Elixir: 1.3.4
httpoison: 0.10.0
hackney: 1.6.3

I'm going to downgrade to Erlang 18 since that's when this last worked for me.

@adrianotadao
Copy link

@teamon hello =)

Could you share some reference about what you said?

Just for reference - it is better to force TLS v1.2 with .., [ssl: [{:versions, [:'tlsv1.2']}]]

I would like to understand more about it.

Thank you

@teamon
Copy link

teamon commented Mar 20, 2017

@adrianotadao See elixir-tesla/tesla#35.

Basically, when using verify_none you give up host verification which is kind of against the purpose of using SSL at all. The issue here is that there are many TLS versions and erlang have(had) some issues with selecting the correct version and it can be overcome with forcing the usage of TLS 1.2.

@ntalfer
Copy link

ntalfer commented May 31, 2017

Hi
I fell in that case when using otp 18 and httpotion (ibrowse) but it's the same thing with httpoison (hackney). A TCP socket is created using gen_tcp:connect(Hostname, Port), and then upgraded to a SSL one with ssl:connect(Sock,..).
After a look at inets code, I've noticed that when we set a Hostname instead of an IP and upgrading a socket, we have to set the SSL option {server_name_indication, Hostname} in order to perform a successful handshake.

$ erl
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]

Eshell V7.3  (abort with ^G)
1> ssl:start().
ok
2> Sock = fun() ->
2> {ok, S} = gen_tcp:connect("requestb.in", 443, []), 
2> S
2> end.
#Fun<erl_eval.20.50752066>
3> ssl:connect(Sock(), []).

=ERROR REPORT==== 31-May-2017::11:20:54 ===
SSL: hello: src/ssl_alert.erl:93:Fatal error: handshake failure
{error,{tls_alert,"handshake failure"}}

4> ssl:connect(Sock(), [{server_name_indication, "requestb.in"}]).
{ok,{sslsocket,{gen_tcp,#Port<0.1052>,tls_connection,
                        undefined},
               <0.52.0>}}

You will notice that doing directly ssl:connect(Hostname, Port, []) will work. This issue only occurs when upgrading a normal TCP socket.

5> ssl:connect("requestb.in", 443, []).                           
{ok,{sslsocket,{gen_tcp,#Port<0.1183>,tls_connection,
                        undefined},
               <0.56.0>}}

Hope it helps.
FYI here is the piece of code of inets (httpc_handler.erl) that helped me fix the issue:

maybe_add_sni(Host, Options) ->
    case http_util:is_hostname(Host) andalso
	  not lists:keymember(server_name_indication, 1, Options) of
	true ->
	    [{server_name_indication, Host} | Options];
	false ->
	    Options
    end.

@wstucco
Copy link

wstucco commented Jun 30, 2017

Same problem with HTTPoison 0.12, Elixir 1.4.5, Erlang 20

The only difference is that I'm connecting through a proxy server, not directly

I applied the fix explained in this post and it worked flawlessly

Basically you have to pass the server_name_indication to the underlying ssl subsystem

HTTPoison.get("https://hostname", [], [proxy: "http://proxy:port",  
  ssl: [server_name_indication: 'hostname']])

Note that server_name_indication is passed as char list, not as binary string.

@ntalfer
Copy link

ntalfer commented Jun 30, 2017

I think the issue can be closed now but that sni option tip really deserves to be documented somewhere. A lot of people may experience the same issue in the future.

@mustafaturan
Copy link

mustafaturan commented Jul 17, 2017

Not recommended but:
You can disable it with the disable option: {server_name_indication, disable}

dnet added a commit to silentsignal/sslproxy that referenced this issue Sep 7, 2017
failing to do so results in a cryptic error result
{options, {{server_name_indication, "127.0.0.1"}}}

See also (thanks to @ntalfer for pointing me in the right direction)
edgurgel/httpoison#164 (comment)
@collegeimprovements
Copy link

Can it be configured via some option in config file ?
Since so many libraries are depending on HTTPoison, it would be useful if we can do that via config.

aliaksandr-martsinovich added a commit to aliaksandr-martsinovich/ex_jenkins that referenced this issue Feb 1, 2018
@greentornado
Copy link

I got this error too, with Erlang 20, elixir 1.6,1

@ludwikbukowski
Copy link

The same, Erlang 20, elixir 1.5.2

@ntalfer
Copy link

ntalfer commented Mar 30, 2018

As I said earlier, the only way to fix this issue for now is to add in your code the equivalent in Elixir of the maybe_add_sni erlang function. It would look like this:

def get(url) do
  options = [...] ++ ssl_options(url)
  HTTPoison.get(url, [], options)
end

defp ssl_options(url) do
 {:ok, {scheme, _, host, _, _, _}} = :http_uri.parse(url |> to_charlist)
   is_ssl = (scheme == :https)
   is_hostname =  :http_util.is_hostname(host)
    cond do
        is_ssl and is_hostname -> [ssl: true, server_name_indication: host]
	is_ssl -> [ssl: true]
	true -> []
    end.
end 

@kyihtooag
Copy link

I ran into a similar issue when trying to hit a service internally at my company. I'm pretty sure something funky is up with our cert chain, but I was able to find a workaround since all my traffic is behind our VPN:

HTTPoison.get("https://example.com/", [], [ssl: [{:versions, [:'tlsv1.2']}]])

# or worst case:

HTTPoison.get("https://example.com/", [], [ssl: [{:verify, :verify_none}]])

Hopefully that helps someone.

Updated based on a better recommendation

Thanks! @jondlm

@fsword
Copy link

fsword commented Nov 26, 2019

I ran into a similar issue when trying to hit a service internally at my company. I'm pretty sure something funky is up with our cert chain, but I was able to find a workaround since all my traffic is behind our VPN:

HTTPoison.get("https://example.com/", [], [ssl: [{:versions, [:'tlsv1.2']}]])

# or worst case:

HTTPoison.get("https://example.com/", [], [ssl: [{:verify, :verify_none}]])

Hopefully that helps someone.

Updated based on a better recommendation

thanks, It's helpful.

@kgrvamsi
Copy link

kgrvamsi commented Sep 9, 2021

This code is golden and worked for me with no issues

url ="https://myexample.come/auth"

body = "{\"username\": \"robin\", \"password\": \"password\"}"

headers = [
      {"Content-Type", "application/json"}
    ]

defaults = :ssl.cipher_suites(:default, :"tlsv1.2")
    rsa_kx =
      :ssl.cipher_suites(:all, :"tlsv1.2")
      |> :ssl.filter_cipher_suites(
         key_exchange: &(&1 == :rsa),
         cipher: &(&1 in [:aes_128_cbc, :aes_128_gcm, :aes_256_cbc, :aes_256_gcm]),
      )

    options = [ssl: [ciphers: defaults ++ rsa_kx]]

    HTTPoison.post(url, body, headers, options)

Reference URL: https://elixirforum.com/t/https-handshake-error-fatal-handshake-failure/36877/10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests