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 handshake failure when ciphers and versions are returned from sni_fun. #5985

Closed
eseres opened this issue May 12, 2022 · 5 comments · Fixed by #5998
Closed

TLS handshake failure when ciphers and versions are returned from sni_fun. #5985

eseres opened this issue May 12, 2022 · 5 comments · Fixed by #5998
Labels
bug Issue is reported as a bug in progress team:PS Assigned to OTP team PS
Milestone

Comments

@eseres
Copy link

eseres commented May 12, 2022

Describe the bug

Specifying the ciphers and versions SSL options for ssl:handshake/2 succeeds. Returning the same exact options from the sni_fun, fails.

To Reproduce

Build and run the following code and observe the output. With the first ssl:handshake(...) line included and the second one commented out, the openssl command will succeed.

% git clone https://github.com/michaelklishin/tls-gen.git
% make -C tls-gen/basic
% cp tls-gen/basic/result/server_$(hostname)_certificate.pem server_certificate.pem
% cp tls-gen/basic/result/server_$(hostname)_key.pem server_key.pem
% cp tls-gen/basic/result/ca_certificate.pem .
% erlc signature_issue.erl && erl -noshell -s signature_issue start -s init stop
% openssl version
% OpenSSL 3.0.3 3 May 2022 (Library: OpenSSL 3.0.3 3 May 2022)
% openssl s_client -connect localhost:9999 -CAfile ca_certificate.pem -servername $(hostname) -cipher DEFAULT@SECLEVEL=2

-module(signature_issue).
-export([start/0]).

start() ->
    ssl:start(),
    Versions = ['tlsv1.2' ,'tlsv1.1' ,'tlsv1'],
    Ciphers = lists:usort(ssl:filter_cipher_suites(lists:flatten([ssl:cipher_suites(all ,V) || V <- Versions]), [])),
    CiphersAndVersions = [{versions, Versions}, {ciphers, Ciphers}],
    SslOpts = [
      {active, true},
      {verify, verify_peer},
      {cacertfile, "./ca_certificate.pem"},
      {certfile,   "./server_certificate.pem"},
      {keyfile,    "./server_key.pem"},
      {log_level, error}
    ],
    {ok, ListenSocket} = gen_tcp:listen(9999, [{active, false}, binary]),
    {ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
    case ssl:handshake(AcceptSocket, [{sni_fun, fun (_SNI) -> SslOpts end}] ++ CiphersAndVersions) of % works
  % case ssl:handshake(AcceptSocket, [{sni_fun, fun (_SNI) -> SslOpts ++ CiphersAndVersions end}]) of % fails
      {ok ,Socket} -> 
        {ok, [Protocol]} = ssl:connection_information(Socket, [protocol]),
        io:format("Success: ~p~n", [Protocol]),
        ssl:close(Socket);
      {error ,Error} -> io:format("Error: ~p~n", [Error])
    end,
    ok.

Now comment out the first ssl:handshake(...) line and include the second one instead. Observe in the openssl command output how the handshake now fails and no cipher will be indicated as selected for the SSL-Session:

SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID: 16EFE7A8788E7DB12AACB7DDD026044F0339E3D262F74376854FA29337C06371
    Session-ID-ctx: 
    Master-Key: 

The error message from openssl is:

0016210901000000:error:0A000172:SSL routines:tls12_check_peer_sigalg:wrong signature type:ssl/t1_lib.c:1567:

Expected behavior

It should not make a difference whether the ciphers and versions are specifying directly to ssl:handshake/2 or returned from the sni_fun option. The SSL handshake should succeed in both cases.

Affected versions

Erlang/OTP 24 [erts-12.3.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit] [dtrace]

Additional context

It seems that ciphers option can be returned from the sni_fun as long as the versions options is given at the top level directly to ssl:handshake/2. That configuration works.

Also, specifying {versions, ['tlsv1.2']} at the top level and returning {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]} from the sni_fun and enforcing openssl to use 'tlsv1.1', will also fail - meaning that the versions returned from sni_fun will not override the options given at the top level. See corresponding ssl:handshake(...) line and openssl command below:

  case ssl:handshake(AcceptSocket, [{sni_fun, fun (_SNI) -> SslOpts ++ CiphersAndVersions end}] ++ [{versions, ['tlsv1.2']}]) of % fails
$ tls_signature_issue % openssl s_client -connect localhost:9999 -CAfile ca_certificate.pem -servername $(hostname) -no_tls1_2

Going in the opposite direction and providing all supported TLS versions at the top level and reducing them in the sni_fun appears to work.

@eseres eseres added the bug Issue is reported as a bug label May 12, 2022
@eseres eseres changed the title TLS handshake failure when ciphers and version are returned from sni_fun. TLS handshake failure when ciphers and versions are returned from sni_fun. May 12, 2022
@IngelaAndin IngelaAndin added the team:PS Assigned to OTP team PS label May 13, 2022
@IngelaAndin
Copy link
Contributor

I have reproduced the reported behaviour and will look into it next week

IngelaAndin added a commit to IngelaAndin/otp that referenced this issue May 18, 2022
If upgrading a TCP socket to a TLS socket and in the same
time changing the supported protocol version in the
sni_fun callback, so that it does not match the default, care must
be applied to make sure correct decoding of hello information is used.

Closes erlang#5985
@IngelaAndin
Copy link
Contributor

Please try my PR.

@IngelaAndin IngelaAndin added this to the OTP-25.1 milestone May 18, 2022
@IngelaAndin
Copy link
Contributor

By the way, you would get the same set of ciphers as in your example doing only

Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all ,HighestVersion), []).

All is an inclusive option (there is also the possibility to do an exclusive listing).
And the call to lists:usort will probably change the order in a way that you would not want if you for instance used it in the client or with the option of honoring the server order.

IngelaAndin added a commit that referenced this issue May 25, 2022
…ions-opt/GH-5985/OTP-18100

ssl: Handle protocol version change in sni_fun
@csrl
Copy link

csrl commented May 27, 2022

Thank you @IngelaAndin . Can this be included in a 24.x release as well?

@IngelaAndin
Copy link
Contributor

I already prepared it to be piggybacked on the next OTP-24 patch, that is it will be included the next time an OTP-24 patch is made but it will not be the reason for making the patch.

IngelaAndin pushed a commit that referenced this issue Jun 8, 2022
…into maint-24

* ingela/ssl/sni-fun-new-versions-opt/GH-5985/OTP-18100:
  ssl: Handle protocol version change in sni_fun
IngelaAndin pushed a commit that referenced this issue Jun 9, 2022
…into maint-25

* ingela/ssl/sni-fun-new-versions-opt/GH-5985/OTP-18100:
  ssl: Handle protocol version change in sni_fun
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue is reported as a bug in progress team:PS Assigned to OTP team PS
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants