erlang / otp

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

TLS Session Reuse when upgrading a TCP socket to a TLS connection #4664

Open bmteller opened 3 years ago

bmteller commented 3 years ago

Describe the bug

TLS session resumption does not work when upgrading a connection and the SNI name does not match ip address of the socket.

The resumption codes lookups up the session using the hostip,port returned from :inet.peername() on the socket. see: https://github.com/erlang/otp/blob/master/lib/ssl/src/tls_socket.erl#L121 https://github.com/erlang/otp/blob/4e1660e7ff93334ef8ce3ae740e0886e4483bf30/lib/ssl/src/ssl_gen_statem.erl#L137 https://github.com/erlang/otp/blob/4e1660e7ff93334ef8ce3ae740e0886e4483bf30/lib/ssl/src/tls_connection.erl#L148 https://github.com/erlang/otp/blob/5af4f64fcc024fc93b076721d02a814b0f599f86/lib/ssl/src/ssl_session.erl#L123

However, it saves the session id using the sni name:

https://github.com/erlang/otp/blob/5af4f64fcc024fc93b076721d02a814b0f599f86/lib/ssl/src/tls_dtls_connection.erl#L1581 https://github.com/erlang/otp/blob/5af4f64fcc024fc93b076721d02a814b0f599f86/lib/ssl/src/tls_dtls_connection.erl#L1601

This breaks the use case of using a HTTP CONNECT proxy. Because the connect proxy host, ip is used to lookup the session but the SNI name is used when saving the session.

To Reproduce

curl -O -L http://curl.haxx.se/ca/cacert.pem

:application.ensure_all_started(:ssl)

connect = fn() -> 
  {:ok, sock} = :gen_tcp.connect('www.google.com', 443, [])
  {:ok, ssl_sock} = :ssl.connect(sock, verify: :verify_peer, server_name_indication: 'www.google.com', cacertfile: "/tmp/cacert.pem")
  {:ok, info} = :ssl.connection_information(ssl_sock)
  session_id = Keyword.get(info, :session_id)
  :ssl.close(ssl_sock)
  session_id
end

id1 = connect.()
id2 = connect.()

id1 == id2
=> false

but when connecting without upgrading it works

direct_connect = fn() -> 
  {:ok, ssl_sock} = :ssl.connect('www.google.com', 443, verify: :verify_peer, server_name_indication: 'www.google.com', cacertfile: "/tmp/cacert.pem")
  {:ok, info} = :ssl.connection_information(ssl_sock)
  session_id = Keyword.get(info, :session_id)
  :ssl.close(ssl_sock)
  session_id
end

id1 = direct_connect.()
id2 = direct_connect.()
id1 == id2

=> true

Expected behavior I expect there to be some way to reuse tls sessions when upgrading a socket.

Affected versions I've tested this on 22.3.4.11 but I think it's broken on latest master code as well.

IngelaAndin commented 3 years ago

You can reuse sessions by using the save option on a connection and then extracting the session_id from that connection and later using the session id explicitly. See the ssl Users Guide. http://erlang.org/doc/apps/ssl/using_ssl.html#session-reuse-pre-tls-1.3 There are some problems with sni not being mandatory for legacy TLS versions pre TLS-1.2. In TLS-1.3 session reuse mechanism is replaced by TLS-1.3 session ticket mechanism.

IngelaAndin commented 3 years ago

I think what you are observing has to do with that automated session reuse is a best effort mechanism, and depending on if you use upgrade or not you get different timing. To be able to reuse a previous session a previous session has to have previously succeeded and be registered in the session table. So if next connection attempt happens before the register has happened the client will not try to reuse the session. Also even if the client tries to reuse the session the server can choose to not reuse the session so there is no session reuse guarantee. If you use the save option you will get a synchronization so that you get a maximum chance on session reuse. We do associate the session with the SNI value and server port if the SNI is available, for legacy reasons the variable names might not always be the best.

u3s commented 3 months ago

ping @IngelaAndin

u3s commented 6 days ago

ping @IngelaAndin