aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssl/src/tls_connection_1_3.erl
blob: 9f7a82223e5a1f0c561248f3f232466769315f45 (plain) (tree)









































































































                                                                           

                                  
 



                              
 
























                                                                                                 
                                





                                                          

                                                                         










                                                                                              

                                                                


                                                         





                                                                         



                                                                         

                                                 
                                            










                                                                                               

                                          
                                                                     







                                                                                
                                              






                                                                                        
                                
                           
                                                                                                    
                           
                                                                                                    






                                                                                                 

                                                                                  






                                                                                        
                                                                       



                                                                          
                         
                                                                                                
 













                                                                                                      







                                           






                                             
 




                                                                                 













                                                                              





                                                            





                                                                        
        
















                                                                                  
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
%%----------------------------------------------------------------------
%% Purpose: TODO
%%----------------------------------------------------------------------

%% RFC 8446
%% A.1.  Client
%%
%%                               START <----+
%%                Send ClientHello |        | Recv HelloRetryRequest
%%           [K_send = early data] |        |
%%                                 v        |
%%            /                 WAIT_SH ----+
%%            |                    | Recv ServerHello
%%            |                    | K_recv = handshake
%%        Can |                    V
%%       send |                 WAIT_EE
%%      early |                    | Recv EncryptedExtensions
%%       data |           +--------+--------+
%%            |     Using |                 | Using certificate
%%            |       PSK |                 v
%%            |           |            WAIT_CERT_CR
%%            |           |        Recv |       | Recv CertificateRequest
%%            |           | Certificate |       v
%%            |           |             |    WAIT_CERT
%%            |           |             |       | Recv Certificate
%%            |           |             v       v
%%            |           |              WAIT_CV
%%            |           |                 | Recv CertificateVerify
%%            |           +> WAIT_FINISHED <+
%%            |                  | Recv Finished
%%            \                  | [Send EndOfEarlyData]
%%                               | K_send = handshake
%%                               | [Send Certificate [+ CertificateVerify]]
%%     Can send                  | Send Finished
%%     app data   -->            | K_send = K_recv = application
%%     after here                v
%%                           CONNECTED
%%
%% A.2.  Server
%%
%%                               START <-----+
%%                Recv ClientHello |         | Send HelloRetryRequest
%%                                 v         |
%%                              RECVD_CH ----+
%%                                 | Select parameters
%%                                 v
%%                              NEGOTIATED
%%                                 | Send ServerHello
%%                                 | K_send = handshake
%%                                 | Send EncryptedExtensions
%%                                 | [Send CertificateRequest]
%%  Can send                       | [Send Certificate + CertificateVerify]
%%  app data                       | Send Finished
%%  after   -->                    | K_send = application
%%  here                  +--------+--------+
%%               No 0-RTT |                 | 0-RTT
%%                        |                 |
%%    K_recv = handshake  |                 | K_recv = early data
%%  [Skip decrypt errors] |    +------> WAIT_EOED -+
%%                        |    |       Recv |      | Recv EndOfEarlyData
%%                        |    | early data |      | K_recv = handshake
%%                        |    +------------+      |
%%                        |                        |
%%                        +> WAIT_FLIGHT2 <--------+
%%                                 |
%%                        +--------+--------+
%%                No auth |                 | Client auth
%%                        |                 |
%%                        |                 v
%%                        |             WAIT_CERT
%%                        |        Recv |       | Recv Certificate
%%                        |       empty |       v
%%                        | Certificate |    WAIT_CV
%%                        |             |       | Recv
%%                        |             v       | CertificateVerify
%%                        +-> WAIT_FINISHED <---+
%%                                 | Recv Finished
%%                                 | K_recv = application
%%                                 v
%%                             CONNECTED

-module(tls_connection_1_3).

-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").

%% gen_statem helper functions
-export([start/4,
         negotiated/4
        ]).

start(internal,
      #client_hello{} = Hello,
      #state{connection_states = _ConnectionStates0,
	     ssl_options = #ssl_options{ciphers = _ServerCiphers,
                                        signature_algs = _ServerSignAlgs,
                                        signature_algs_cert = _SignatureSchemes, %% TODO: Check??
                                        supported_groups = _ServerGroups0,
                                        versions = _Versions} = SslOpts,
             session = #session{own_certificate = Cert}} = State0,
      _Module) ->

    Env = #{cert => Cert},
    case tls_handshake_1_3:handle_client_hello(Hello, SslOpts, Env) of
        #alert{} = Alert ->
            ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
        M ->
            %% update connection_states with cipher
            State = update_state(State0, M),
            {next_state, negotiated, State, [{next_event, internal, M}]}

    end.

%% TODO: move these functions
update_state(#state{connection_states = ConnectionStates0,
                    session = Session} = State,
             #{cipher := Cipher,
               key_share := KeyShare,
               session_id := SessionId}) ->
    #{security_parameters := SecParamsR0} = PendingRead =
        maps:get(pending_read, ConnectionStates0),
    #{security_parameters := SecParamsW0} = PendingWrite =
        maps:get(pending_write, ConnectionStates0),
    SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
    SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),
    ConnectionStates =
        ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
                           pending_write => PendingWrite#{security_parameters => SecParamsW}},
    State#state{connection_states = ConnectionStates,
                key_share = KeyShare,
                session = Session#session{session_id = SessionId}}.


negotiated(internal,
           Map,
           #state{connection_states = ConnectionStates0,
                  session = #session{session_id = SessionId,
                                     own_certificate = OwnCert},
                  ssl_options = #ssl_options{} = SslOpts,
                  key_share = KeyShare,
                  tls_handshake_history = HHistory0,
                  private_key = CertPrivateKey,
                  static_env = #static_env{
                                  cert_db = CertDbHandle,
                                  cert_db_ref = CertDbRef,
                                  socket = Socket,
                                  transport_cb = Transport}}, _Module) ->
    %% Create server_hello
    %% Extensions: supported_versions, key_share, (pre_shared_key)
    ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare,
                                                 ConnectionStates0, Map),
    %% Update handshake_history (done in encode!)
    %% Encode handshake
    {BinMsg, ConnectionStates1, HHistory1} =
        tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
    %% Send server_hello
    tls_connection:send(Transport, Socket, BinMsg),
    Report = #{direction => outbound,
               protocol => 'tls_record',
               message => BinMsg},
    Msg = #{direction => outbound,
            protocol => 'handshake',
            message => ServerHello},
    ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}),
    ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),

    #{security_parameters := SecParamsR} =
        ssl_record:pending_connection_state(ConnectionStates1, read),
    #security_parameters{prf_algorithm = HKDFAlgo,
                         cipher_suite = CipherSuite} = SecParamsR,

    %% Calculate handshake_secret
    EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, <<>>}),

    ClientKey = maps:get(client_share, Map),        %% Raw data?! What about EC?
    SelectedGroup = maps:get(group, Map),
    SignatureScheme = maps:get(sign_alg, Map),

    PrivateKey = get_server_private_key(KeyShare),  %% #'ECPrivateKey'{}

    IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
    HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),

    %% Calculate [sender]_handshake_traffic_secret
    {Messages1, _} =  HHistory1,
    ClientHSTrafficSecret =
        tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),
    ServerHSTrafficSecret =
        tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),

    %% Calculate traffic keys
    #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
    {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
    {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),

    %% Update pending connection state
    PendingRead0 = ssl_record:pending_connection_state(ConnectionStates1, read),
    PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates1, write),

    PendingRead = update_conn_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
    PendingWrite = update_conn_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),

    %% Update pending and copy to current (activate)
    %% All subsequent handshake messages are encrypted
    %% ([sender]_handshake_traffic_secret)
    ConnectionStates2 = ConnectionStates1#{current_read => PendingRead,
                                           current_write => PendingWrite,
                                           pending_read => PendingRead,
                                           pending_write => PendingWrite},

    %% Create Certificate
    Certificate = tls_handshake_1_3:certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),

    %% Encode Certificate
    {_CertificateBin, _ConnectionStates2, HHistory2} =
        tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates1, HHistory1),

    %% Create CertificateVerify

    {Messages2, _} =  HHistory2,

    %% Use selected signature_alg from here, HKDF only used for key_schedule
    CertificateVerify = tls_handshake_1_3:certificate_verify(OwnCert, CertPrivateKey, SignatureScheme,
                                                             Messages2, server),
    io:format("### CertificateVerify: ~p~n", [CertificateVerify]),

    %% Encode CertificateVerify

    %% Send Certificate, CertifricateVerify

    %% Send finished

    %% Next record/Next event

    ConnectionStates1.

    %% K_send = handshake ???
    %% (Send EncryptedExtensions)
    %% ([Send CertificateRequest])
    %% [Send Certificate + CertificateVerify]
    %% Send Finished
    %% K_send = application ???

    %% Will be called implicitly
    %% {Record, State} = Connection:next_record(State2#state{session = Session}),
    %% Connection:next_event(wait_flight2, Record, State, Actions),
    %% OR
    %% Connection:next_event(WAIT_EOED, Record, State, Actions)


get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
    get_private_key(ServerShare).

get_private_key(#key_share_entry{
                   key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
    PrivateKey;
get_private_key(#key_share_entry{
                      key_exchange =
                          {_, PrivateKey}}) ->
    PrivateKey.


%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
  when is_binary(OthersKey) andalso is_binary(MyKey) andalso
       (Group =:= x25519 orelse Group =:= x448)->
    crypto:compute_key(ecdh, OthersKey, MyKey, Group);
%% FFDHE
calculate_shared_secret(OthersKey, MyKey, Group)
  when is_binary(OthersKey) andalso is_binary(MyKey) ->
    Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group),
    S = public_key:compute_key(OthersKey, MyKey, Params),
    Size = byte_size(binary:encode_unsigned(P)),
    ssl_cipher:add_zero_padding(S, Size);
%% ECDHE
calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
  when is_binary(OthersKey) ->
    Point = #'ECPoint'{point = OthersKey},
    public_key:compute_key(Point, MyKey).


update_conn_state(ConnectionState = #{security_parameters := SecurityParameters0},
                  HandshakeSecret, Key, IV) ->
    %% Store secret
    SecurityParameters = SecurityParameters0#security_parameters{
                           master_secret = HandshakeSecret},
    ConnectionState#{security_parameters => SecurityParameters,
                     cipher_state => cipher_init(Key, IV)}.


cipher_init(Key, IV) ->
    #cipher_state{key = Key, iv = IV, tag_len = 16}.