diff options
author | Ingela Anderton Andin <[email protected]> | 2016-09-20 20:58:34 +0200 |
---|---|---|
committer | Ingela Anderton Andin <[email protected]> | 2016-12-05 10:59:51 +0100 |
commit | 1e6942e97339ff39a0436834c260bf50c3d3a481 (patch) | |
tree | a7b277fe925fc74dd186faf64f3d24ac066de5bd /lib/ssl/src/dtls_handshake.erl | |
parent | 39fef8eec26c2903ef11ba8829e1f625906b3e11 (diff) | |
download | otp-1e6942e97339ff39a0436834c260bf50c3d3a481.tar.gz otp-1e6942e97339ff39a0436834c260bf50c3d3a481.tar.bz2 otp-1e6942e97339ff39a0436834c260bf50c3d3a481.zip |
ssl: Implement DTLS state machine
Beta DTLS, not production ready. Only very basically tested, and
not everything in the SPEC is implemented and some things
are hard coded that should not be, so this implementation can not be consider
secure.
Refactor "TLS connection state" and socket handling, to facilitate
DTLS implementation.
Create dtls "listner" (multiplexor) process that spawns
DTLS connection process handlers.
Handle DTLS fragmentation.
Framework for handling retransmissions.
Replay Detection is not implemented yet.
Alerts currently always handled as in TLS.
Diffstat (limited to 'lib/ssl/src/dtls_handshake.erl')
-rw-r--r-- | lib/ssl/src/dtls_handshake.erl | 555 |
1 files changed, 273 insertions, 282 deletions
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index c6535d5928..af3708ddb7 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -18,15 +18,15 @@ %% %CopyrightEnd% -module(dtls_handshake). +-include("dtls_connection.hrl"). -include("dtls_handshake.hrl"). -include("dtls_record.hrl"). -include("ssl_internal.hrl"). -include("ssl_alert.hrl"). --export([client_hello/8, client_hello/9, hello/4, - hello_verify_request/1, get_dtls_handshake/2, - dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1, - encode_handshake/3]). +-export([client_hello/8, client_hello/9, cookie/4, hello/4, + hello_verify_request/2, get_dtls_handshake/3, fragment_handshake/2, + handshake_bin/2, encode_handshake/3]). -type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | ssl_handshake:ssl_handshake(). @@ -62,9 +62,10 @@ client_hello(Host, Port, Cookie, ConnectionStates, Version = dtls_record:highest_protocol_version(Versions), Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = maps:get(security_parameters, Pending), - CipherSuites = ssl_handshake:available_suites(UserSuites, Version), + TLSVersion = dtls_v1:corresponding_tls_version(Version), + CipherSuites = ssl_handshake:available_suites(UserSuites, TLSVersion), - Extensions = ssl_handshake:client_hello_extensions(Host, dtls_v1:corresponding_tls_version(Version), CipherSuites, + Extensions = ssl_handshake:client_hello_extensions(Host, TLSVersion, CipherSuites, SslOpts, ConnectionStates, Renegotiation), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), @@ -96,72 +97,51 @@ hello(#client_hello{client_version = ClientVersion} = Hello, #ssl_options{versions = Versions} = SslOpts, Info, Renegotiation) -> Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions), - %% - %% TODO: handle Cipher Fallback - %% handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation). --spec hello_verify_request(binary()) -> #hello_verify_request{}. +cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionId, + cipher_suites = CipherSuites, + compression_methods = CompressionMethods}) -> + CookieData = [address_to_bin(Address, Port), + <<?BYTE(Major), ?BYTE(Minor)>>, + Random, SessionId, CipherSuites, CompressionMethods], + crypto:hmac(sha, Key, CookieData). + +-spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}. %% %% Description: Creates a hello verify request message sent by server to %% verify client %%-------------------------------------------------------------------- -hello_verify_request(Cookie) -> - %% TODO: DTLS Versions????? - #hello_verify_request{protocol_version = {254, 255}, cookie = Cookie}. +hello_verify_request(Cookie, Version) -> + #hello_verify_request{protocol_version = Version, cookie = Cookie}. %%-------------------------------------------------------------------- -%% %%-------------------------------------------------------------------- -encode_handshake(Handshake, Version, MsgSeq) -> +encode_handshake(Handshake, Version, Seq) -> {MsgType, Bin} = enc_handshake(Handshake, Version), Len = byte_size(Bin), - Enc = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin], - Frag = {MsgType, MsgSeq, Bin}, - {Enc, Frag}. + [MsgType, ?uint24(Len), ?uint16(Seq), ?uint24(0), ?uint24(Len), Bin]. -%%-------------------------------------------------------------------- --spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | undefined) -> - {[dtls_handshake()], #dtls_hs_state{}} | {retransmit, #dtls_hs_state{}}. -%% -%% Description: Given a DTLS state and new data from ssl_record, collects -%% and returns it as a list of handshake messages, also returns a new -%% DTLS state -%%-------------------------------------------------------------------- -get_dtls_handshake(Records, undefined) -> - HsState = #dtls_hs_state{highest_record_seq = 0, - starting_read_seq = 0, - fragments = gb_trees:empty(), - completed = []}, - get_dtls_handshake(Records, HsState); -get_dtls_handshake(Records, HsState0) when is_list(Records) -> - HsState1 = lists:foldr(fun get_dtls_handshake_aux/2, HsState0, Records), - get_dtls_handshake_completed(HsState1); -get_dtls_handshake(Record, HsState0) when is_record(Record, ssl_tls) -> - HsState1 = get_dtls_handshake_aux(Record, HsState0), - get_dtls_handshake_completed(HsState1). +fragment_handshake(Bin, _) when is_binary(Bin)-> + %% This is the change_cipher_spec not a "real handshake" but part of the flight + Bin; +fragment_handshake([MsgType, Len, Seq, _, Len, Bin], Size) -> + Bins = bin_fragments(Bin, Size), + handshake_fragments(MsgType, Seq, Len, Bins, []). +handshake_bin([Type, Length, Data], Seq) -> + handshake_bin(Type, Length, Seq, Data). + %%-------------------------------------------------------------------- --spec dtls_handshake_new_epoch(#dtls_hs_state{}) -> #dtls_hs_state{}. +-spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) -> + {[{dtls_handshake(), binary()}], #protocol_buffers{}} | {more_data, #protocol_buffers{}}. %% -%% Description: Reset the DTLS decoder state for a new Epoch +%% Description: ... %%-------------------------------------------------------------------- -%% dtls_handshake_new_epoch(<<>>) -> -%% dtls_hs_state_init(); -dtls_handshake_new_epoch(HsState) -> - HsState#dtls_hs_state{highest_record_seq = 0, - starting_read_seq = HsState#dtls_hs_state.current_read_seq, - fragments = gb_trees:empty(), completed = []}. - -%-------------------------------------------------------------------- --spec dtls_handshake_new_flight(integer() | undefined) -> #dtls_hs_state{}. -% -% Description: Init the DTLS decoder state for a new Flight -dtls_handshake_new_flight(ExpectedReadReq) -> - #dtls_hs_state{current_read_seq = ExpectedReadReq, - highest_record_seq = 0, - starting_read_seq = 0, - fragments = gb_trees:empty(), completed = []}. +get_dtls_handshake(Version, Fragment, ProtocolBuffers) -> + handle_fragments(Version, Fragment, ProtocolBuffers, []). %%-------------------------------------------------------------------- %%% Internal functions @@ -170,27 +150,29 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId, cipher_suites = CipherSuites, compression_methods = Compressions, random = Random, - extensions = #hello_extensions{elliptic_curves = Curves, - signature_algs = ClientHashSigns} = HelloExt}, + extensions = + #hello_extensions{elliptic_curves = Curves, + signature_algs = ClientHashSigns} = HelloExt}, #ssl_options{versions = Versions, signature_algs = SupportedHashSigns} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) -> case dtls_record:is_acceptable_version(Version, Versions) of true -> + TLSVersion = dtls_v1:corresponding_tls_version(Version), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, Cert, - dtls_v1:corresponding_tls_version(Version)), - ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), + ClientHashSigns, SupportedHashSigns, Cert,TLSVersion), + ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(TLSVersion)), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, - Port, Session0#session{ecc = ECCCurve}, Version, + Port, Session0#session{ecc = ECCCurve}, TLSVersion, SslOpts, Cache, CacheCb, Cert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); _ -> {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), - case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, + SupportedHashSigns, TLSVersion) of #alert{} = Alert -> Alert; HashSign -> @@ -228,214 +210,15 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, {Version, SessionId, ConnectionStates, ProtoExt, Protocol} end. -get_dtls_handshake_completed(HsState = #dtls_hs_state{completed = Completed}) -> - {lists:reverse(Completed), HsState#dtls_hs_state{completed = []}}. - -get_dtls_handshake_aux(#ssl_tls{version = Version, - sequence_number = SeqNo, - fragment = Data}, HsState) -> - get_dtls_handshake_aux(Version, SeqNo, Data, HsState). - -get_dtls_handshake_aux(Version, SeqNo, - <<?BYTE(Type), ?UINT24(Length), - ?UINT16(MessageSeq), - ?UINT24(FragmentOffset), ?UINT24(FragmentLength), - Body:FragmentLength/binary, Rest/binary>>, - HsState0) -> - case reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState0) of - {HsState1, HighestSeqNo, MsgBody} -> - HsState2 = dec_dtls_fragment(Version, HighestSeqNo, Type, Length, MessageSeq, MsgBody, HsState1), - HsState3 = process_dtls_fragments(Version, HsState2), - get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3); - - HsState2 -> - HsState3 = process_dtls_fragments(Version, HsState2), - get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3) - end; - -get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) -> - HsState. - -dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody, - HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) -> - Raw = <<?BYTE(Type), ?UINT24(Length), ?UINT16(MessageSeq), ?UINT24(0), ?UINT24(Length), MsgBody/binary>>, - H = decode_handshake(Version, Type, MsgBody), - HsState#dtls_hs_state{completed = [{H,Raw}|Acc], highest_record_seq = erlang:max(HighestSeqNo, SeqNo)}. - -process_dtls_fragments(Version, - HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, - fragments = Fragments0}) -> - case gb_trees:is_empty(Fragments0) of - true -> - HsState0; - _ -> - case gb_trees:smallest(Fragments0) of - {CurrentReadSeq, {SeqNo, Type, Length, CurrentReadSeq, {Length, [{0, Length}], MsgBody}}} -> - HsState1 = dtls_hs_state_process_seq(HsState0), - HsState2 = dec_dtls_fragment(Version, SeqNo, Type, Length, CurrentReadSeq, MsgBody, HsState1), - process_dtls_fragments(Version, HsState2); - _ -> - HsState0 - end - end. - -dtls_hs_state_process_seq(HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, - fragments = Fragments0}) -> - Fragments1 = gb_trees:delete_any(CurrentReadSeq, Fragments0), - HsState0#dtls_hs_state{current_read_seq = CurrentReadSeq + 1, - fragments = Fragments1}. - -dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState0 = #dtls_hs_state{fragments = Fragments0}) -> - Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), - HsState0#dtls_hs_state{fragments = Fragments1}. - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, - Body, HsState0 = #dtls_hs_state{current_read_seq = undefined}) - when Type == ?CLIENT_HELLO; - Type == ?SERVER_HELLO; - Type == ?HELLO_VERIFY_REQUEST -> - %% First message, should be client hello - %% return the current message and set the next expected Sequence - %% - %% Note: this could (should?) be restricted further, ClientHello and - %% HelloVerifyRequest have to have message_seq = 0, ServerHello - %% can have a message_seq of 0 or 1 - %% - {HsState0#dtls_hs_state{current_read_seq = MessageSeq + 1}, SeqNo, Body}; - -reassemble_dtls_fragment(_SeqNo, _Type, Length, _MessageSeq, _, Length, - _Body, HsState = #dtls_hs_state{current_read_seq = undefined}) -> - %% not what we expected, drop it - HsState; - -reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, - Body, HsState0 = - #dtls_hs_state{starting_read_seq = StartingReadSeq}) - when MessageSeq < StartingReadSeq -> - %% this has to be the start of a new flight, let it through - %% - %% Note: this could (should?) be restricted further, the first message of a - %% new flight has to have message_seq = 0 - %% - HsState = dtls_hs_state_process_seq(HsState0), - {HsState, SeqNo, Body}; - -reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, - _Body, HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when MessageSeq < CurrentReadSeq -> - HsState; - -reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, - Body, HsState0 = #dtls_hs_state{current_read_seq = MessageSeq}) -> - %% Message fully contained and it's the current seq - HsState1 = dtls_hs_state_process_seq(HsState0), - {HsState1, SeqNo, Body}; - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, - Body, HsState) -> - %% Message fully contained and it's the NOT the current seq -> buffer - Fragment = {SeqNo, Type, Length, MessageSeq, - dtls_fragment_init(Length, 0, Length, Body)}, - dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState); - -reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, FragmentOffset, FragmentLength, - _Body, - HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when FragmentOffset + FragmentLength == Length andalso MessageSeq == (CurrentReadSeq - 1) -> - {retransmit, HsState}; - -reassemble_dtls_fragment(_SeqNo, _Type, _Length, MessageSeq, _FragmentOffset, _FragmentLength, - _Body, - HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) - when MessageSeq < CurrentReadSeq -> - HsState; - -reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - case gb_trees:lookup(MessageSeq, Fragments0) of - {value, Fragment} -> - dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, Fragment, HsState); - none -> - dtls_fragment_start(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState) - end. -dtls_fragment_start(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, HsState = #dtls_hs_state{fragments = Fragments0}) -> - Fragment = {SeqNo, Type, Length, MessageSeq, - dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body)}, - Fragments1 = gb_trees:insert(MessageSeq, Fragment, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}. - -dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, - FragmentOffset, FragmentLength, - Body, - {LastSeqNo, Type, Length, MessageSeq, FragBuffer0}, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - FragBuffer1 = dtls_fragment_add(FragBuffer0, FragmentOffset, FragmentLength, Body), - Fragment = {erlang:max(SeqNo, LastSeqNo), Type, Length, MessageSeq, FragBuffer1}, - Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}; - -%% Type, Length or Seq mismatch, drop everything... -%% Note: the RFC is not clear on how to handle this... -dtls_fragment_reassemble(_SeqNo, _Type, _Length, MessageSeq, - _FragmentOffset, _FragmentLength, _Body, _Fragment, - HsState = #dtls_hs_state{fragments = Fragments0}) -> - Fragments1 = gb_trees:delete_any(MessageSeq, Fragments0), - HsState#dtls_hs_state{fragments = Fragments1}. - -dtls_fragment_add({Length, FragmentList0, Bin0}, FragmentOffset, FragmentLength, Body) -> - Bin1 = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, Bin0), - FragmentList1 = add_fragment(FragmentList0, {FragmentOffset, FragmentLength}), - {Length, FragmentList1, Bin1}. - -dtls_fragment_init(Length, 0, Length, Body) -> - {Length, [{0, Length}], Body}; -dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body) -> - Bin = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, <<0:(Length*8)>>), - {Length, [{FragmentOffset, FragmentOffset + FragmentLength}], Bin}. - -dtls_fragment_bin_add(FragmentOffset, FragmentLength, Add, Buffer) -> - <<First:FragmentOffset/bytes, _:FragmentLength/bytes, Rest/binary>> = Buffer, - <<First/binary, Add/binary, Rest/binary>>. - -merge_fragment_list([], Fragment, Acc) -> - lists:reverse([Fragment|Acc]); - -merge_fragment_list([H = {_, HEnd}|Rest], Frag = {FStart, _}, Acc) - when FStart > HEnd -> - merge_fragment_list(Rest, Frag, [H|Acc]); - -merge_fragment_list(Rest = [{HStart, _HEnd}|_], Frag = {_FStart, FEnd}, Acc) - when FEnd < HStart -> - lists:reverse(Acc) ++ [Frag|Rest]; - -merge_fragment_list([{HStart, HEnd}|Rest], _Frag = {FStart, FEnd}, Acc) - when - FStart =< HEnd orelse FEnd >= HStart -> - Start = erlang:min(HStart, FStart), - End = erlang:max(HEnd, FEnd), - NewFrag = {Start, End}, - merge_fragment_list(Rest, NewFrag, Acc). - -add_fragment(List, {FragmentOffset, FragmentLength}) -> - merge_fragment_list(List, {FragmentOffset, FragmentOffset + FragmentLength}, []). +%%%%%%% Encodeing %%%%%%%%%%%%% enc_handshake(#hello_verify_request{protocol_version = {Major, Minor}, cookie = Cookie}, _Version) -> - CookieLength = byte_size(Cookie), + CookieLength = byte_size(Cookie), {?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), ?BYTE(CookieLength), - Cookie/binary>>}; + Cookie:CookieLength/binary>>}; enc_handshake(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; @@ -459,38 +242,246 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, ?BYTE(CookieLength), Cookie/binary, ?UINT16(CsLength), BinCipherSuites/binary, ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; + +enc_handshake(#server_hello{} = HandshakeMsg, Version) -> + {Type, <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>} = + ssl_handshake:encode_handshake(HandshakeMsg, Version), + {DTLSMajor, DTLSMinor} = dtls_v1:corresponding_dtls_version({Major, Minor}), + {Type, <<?BYTE(DTLSMajor), ?BYTE(DTLSMinor), Rest/binary>>}; + enc_handshake(HandshakeMsg, Version) -> ssl_handshake:encode_handshake(HandshakeMsg, Version). -decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, +bin_fragments(Bin, Size) -> + bin_fragments(Bin, size(Bin), Size, 0, []). + +bin_fragments(Bin, BinSize, FragSize, Offset, Fragments) -> + case (BinSize - Offset - FragSize) > 0 of + true -> + Frag = binary:part(Bin, {Offset, FragSize}), + bin_fragments(Bin, BinSize, FragSize, Offset + FragSize, [{Frag, Offset} | Fragments]); + false -> + Frag = binary:part(Bin, {Offset, BinSize-Offset}), + lists:reverse([{Frag, Offset} | Fragments]) + end. + +handshake_fragments(_, _, _, [], Acc) -> + lists:reverse(Acc); +handshake_fragments(MsgType, Seq, Len, [{Bin, Offset} | Bins], Acc) -> + FragLen = size(Bin), + handshake_fragments(MsgType, Seq, Len, Bins, + [<<?BYTE(MsgType), Len/binary, Seq/binary, ?UINT24(Offset), + ?UINT24(FragLen), Bin/binary>> | Acc]). + +address_to_bin({A,B,C,D}, Port) -> + <<0:80,16#ffff:16,A,B,C,D,Port:16>>; +address_to_bin({A,B,C,D,E,F,G,H}, Port) -> + <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>. + +%%%%%%% Decodeing %%%%%%%%%%%%% + +handle_fragments(Version, FragmentData, Buffers0, Acc) -> + Fragments = decode_handshake_fragments(FragmentData), + do_handle_fragments(Version, Fragments, Buffers0, Acc). + +do_handle_fragments(_, [], Buffers, Acc) -> + {lists:reverse(Acc), Buffers}; +do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Acc) -> + case reassemble(Version, Fragment, Buffers0) of + {more_data, _} = More when Acc == []-> + More; + {more_data, Buffers} when Fragments == [] -> + {lists:reverse(Acc), Buffers}; + {more_data, Buffers} -> + do_handle_fragments(Version, Fragments, Buffers, Acc); + {HsPacket, Buffers} -> + do_handle_fragments(Version, Fragments, Buffers, [HsPacket | Acc]) + end. + +decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) -> + decode_handshake(Version, Type, Bin). + +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; +decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), + ?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, - ?BYTE(Cookie_length), Cookie:Cookie_length/binary, + ?BYTE(CookieLength), Cookie:CookieLength/binary, ?UINT16(Cs_length), CipherSuites:Cs_length/binary, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> - DecodedExtensions = ssl_handshake:decode_hello_extensions(Extensions), - + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), + #client_hello{ client_version = {Major,Minor}, random = Random, - session_id = Session_ID, cookie = Cookie, + session_id = Session_ID, cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), compression_methods = Comp_methods, extensions = DecodedExtensions - }; + }; + +decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), + ?BYTE(Major), ?BYTE(Minor), + ?BYTE(CookieLength), + Cookie:CookieLength/binary>>) -> + #hello_verify_request{protocol_version = {Major, Minor}, + cookie = Cookie}; + +decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_), + ?UINT24(_), ?UINT24(_), Msg/binary>>) -> + %% DTLS specifics stripped + decode_tls_thandshake(Version, Tag, Msg). + +decode_tls_thandshake(Version, Tag, Msg) -> + TLSVersion = dtls_v1:corresponding_tls_version(Version), + ssl_handshake:decode_handshake(TLSVersion, Tag, Msg). + +decode_handshake_fragments(<<>>) -> + [<<>>]; +decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length), + ?UINT16(MessageSeq), + ?UINT24(FragmentOffset), ?UINT24(FragmentLength), + Fragment:FragmentLength/binary, Rest/binary>>) -> + [#handshake_fragment{type = Type, + length = Length, + message_seq = MessageSeq, + fragment_offset = FragmentOffset, + fragment_length = FragmentLength, + fragment = Fragment} | decode_handshake_fragments(Rest)]. + +reassemble(Version, #handshake_fragment{message_seq = Seq} = Fragment, + #protocol_buffers{dtls_handshake_next_seq = Seq, + dtls_handshake_next_fragments = Fragments0, + dtls_handshake_later_fragments = LaterFragments0} = + Buffers0)-> + case reassemble_fragments(Fragment, Fragments0) of + {more_data, Fragments} -> + {more_data, Buffers0#protocol_buffers{dtls_handshake_next_fragments = Fragments}}; + {raw, RawHandshake} -> + Handshake = decode_handshake(Version, RawHandshake), + {NextFragments, LaterFragments} = next_fragments(LaterFragments0), + {{Handshake, RawHandshake}, Buffers0#protocol_buffers{dtls_handshake_next_seq = Seq + 1, + dtls_handshake_next_fragments = NextFragments, + dtls_handshake_later_fragments = LaterFragments}} + end; +reassemble(_, #handshake_fragment{message_seq = FragSeq} = Fragment, + #protocol_buffers{dtls_handshake_next_seq = Seq, + dtls_handshake_later_fragments = LaterFragments} = Buffers0) when FragSeq > Seq-> + {more_data, + Buffers0#protocol_buffers{dtls_handshake_later_fragments = [Fragment | LaterFragments]}}; +reassemble(_, _, Buffers) -> + %% Disregard fragments FragSeq < Seq + {more_data, Buffers}. + +reassemble_fragments(Current, Fragments0) -> + [Frag1 | Frags] = lists:keysort(#handshake_fragment.fragment_offset, [Current | Fragments0]), + [Fragment | _] = Fragments = merge_fragment(Frag1, Frags), + case is_complete_handshake(Fragment) of + true -> + {raw, handshake_bin(Fragment)}; + false -> + {more_data, Fragments} + end. -decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), - ?BYTE(CookieLength), Cookie:CookieLength/binary>>) -> +merge_fragment(Frag0, []) -> + [Frag0]; +merge_fragment(Frag0, [Frag1 | Rest]) -> + case merge_fragments(Frag0, Frag1) of + [_|_] = Frags -> + Frags ++ Rest; + Frag -> + merge_fragment(Frag, Rest) + end. - #hello_verify_request{ - protocol_version = {Major,Minor}, - cookie = Cookie}; -decode_handshake(Version, Tag, Msg) -> - ssl_handshake:decode_handshake(Version, Tag, Msg). +is_complete_handshake(#handshake_fragment{length = Length, fragment_length = Length}) -> + true; +is_complete_handshake(_) -> + false. + +next_fragments(LaterFragments) -> + case lists:keysort(#handshake_fragment.message_seq, LaterFragments) of + [] -> + {[], []}; + [#handshake_fragment{message_seq = Seq} | _] = Fragments -> + split_frags(Fragments, Seq, []) + end. -%% address_to_bin({A,B,C,D}, Port) -> -%% <<0:80,16#ffff:16,A,B,C,D,Port:16>>; -%% address_to_bin({A,B,C,D,E,F,G,H}, Port) -> -%% <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>. +split_frags([#handshake_fragment{message_seq = Seq} = Frag | Rest], Seq, Acc) -> + split_frags(Rest, Seq, [Frag | Acc]); +split_frags(Frags, _, Acc) -> + {lists:reverse(Acc), Frags}. + + +%% Duplicate +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData}) -> + Previous; + +%% Lager fragment save new data +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = CurrentLen, + fragment = CurrentData}) when CurrentLen > PreviousLen -> + NewLength = CurrentLen - PreviousLen, + <<_:PreviousLen/binary, NewData/binary>> = CurrentData, + Previous#handshake_fragment{ + fragment_length = PreviousLen + NewLength, + fragment = <<PreviousData/binary, NewData/binary>> + }; + +%% Smaller fragment +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen + } = Previous, + #handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = CurrentLen}) when CurrentLen < PreviousLen -> + Previous; +%% Next fragment +merge_fragments(#handshake_fragment{ + fragment_offset = PreviousOffSet, + fragment_length = PreviousLen, + fragment = PreviousData + } = Previous, + #handshake_fragment{ + fragment_offset = CurrentOffSet, + fragment_length = CurrentLen, + fragment = CurrentData}) when PreviousOffSet + PreviousLen == CurrentOffSet-> + Previous#handshake_fragment{ + fragment_length = PreviousLen + CurrentLen, + fragment = <<PreviousData/binary, CurrentData/binary>>}; +%% No merge there is a gap +merge_fragments(Previous, Current) -> + [Previous, Current]. + +handshake_bin(#handshake_fragment{ + type = Type, + length = Len, + message_seq = Seq, + fragment_length = Len, + fragment_offset = 0, + fragment = Fragment}) -> + handshake_bin(Type, Len, Seq, Fragment). + +handshake_bin(Type, Length, Seq, FragmentData) -> + <<?BYTE(Type), ?UINT24(Length), + ?UINT16(Seq), ?UINT24(0), ?UINT24(Length), + FragmentData:Length/binary>>. |