Packages
fast_tls
1.1.25
1.1.26
1.1.25
1.1.24
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.16
1.1.15
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.26
1.0.25
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.16
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-beta1
TLS / SSL OpenSSL-based native driver for Erlang / Elixir
Current section
Files
Jump to
Current section
Files
src/fast_tls.erl
%%%----------------------------------------------------------------------
%%% File : fast_tls.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Interface to openssl
%%% Created : 24 Jul 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2025 ProcessOne, SARL. 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.
%%%
%%%----------------------------------------------------------------------
-module(fast_tls).
-on_load(load_nif/0).
-author('alexey@process-one.net').
-export([open_nif/10, loop_nif/4, get_peer_certificate_nif/1,
get_verify_result_nif/1, invalidate_nif/1,
get_negotiated_cipher_nif/1, set_fips_mode_nif/1,
get_fips_mode_nif/0, get_tls_cb_exporter_nif/1,
p12_to_pem_nif/2]).
-export([tcp_to_tls/2,
tls_to_tcp/1, send/2, recv/2, recv/3, recv_data/2,
setopts/2, sockname/1, peername/1,
controlling_process/2, close/1,
get_peer_certificate/1, get_peer_certificate/2,
get_verify_result/1, get_cert_verify_string/2,
add_certfile/2, get_certfile/1, delete_certfile/1,
clear_cache/0, get_negotiated_cipher/1,
get_tls_last_message/2, set_fips_mode/1, get_fips_mode/0,
get_tls_cb_exporter/1, p12_to_pem/2, finish_handshake/2]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-include_lib("public_key/include/public_key.hrl").
-define(SET_CERTIFICATE_FILE_ACCEPT, 1).
-define(SET_CERTIFICATE_FILE_CONNECT, 2).
-define(SET_ENCRYPTED_INPUT, 3).
-define(SET_DECRYPTED_OUTPUT, 4).
-define(GET_ENCRYPTED_OUTPUT, 5).
-define(GET_DECRYPTED_INPUT, 6).
-define(GET_PEER_CERTIFICATE, 7).
-define(GET_VERIFY_RESULT, 8).
-define(VERIFY_NONE, 16#10000).
-define(COMPRESSION_NONE, 16#100000).
-define(OVERRIDE_CERT_PURPOSE, 16#200000).
-define(PRINT(Format, Args), io:format(Format, Args)).
-record(tlssock, {tcpsock :: inet:socket(),
tlsport :: port()}).
-type tls_socket() :: #tlssock{}.
-type cert() :: #'Certificate'{} | #'OTPCertificate'{}.
-export_type([tls_socket/0]).
open_nif(_Flags, _CertFile, _KeyFile, _Ciphers, _ProtocolOpts, _DH, _DHFile, _CAFile, _SNI, _ALPN) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
loop_nif(_Port, _ToSend, _Received, _ReceiveBytes) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_peer_certificate_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_verify_result_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
add_certfile_nif(_Domain, _File) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_certfile_nif(_Domain) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
delete_certfile_nif(_Domain) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
invalidate_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
clear_cache_nif() ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_negotiated_cipher_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
tls_get_peer_finished_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
tls_get_finished_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_tls_cb_exporter_nif(_Port) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
set_fips_mode_nif(_Flag) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
get_fips_mode_nif() ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
p12_to_pem_nif(_P12Data, _Pass) ->
erlang:nif_error({nif_not_loaded, ?MODULE}).
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
-spec tcp_to_tls(inet:socket(), [atom() | {atom(), any()}]) ->
{ok, tls_socket()} | {'error', 'no_certfile' | binary()}.
tcp_to_tls(TCPSocket, Options) ->
Command = case lists:member(connect, Options) of
true -> ?SET_CERTIFICATE_FILE_CONNECT;
false -> ?SET_CERTIFICATE_FILE_ACCEPT
end,
CertFile = proplists:get_value(certfile, Options, ""),
if CertFile /= [] orelse Command == ?SET_CERTIFICATE_FILE_CONNECT ->
Flags1 = case lists:member(verify_none, Options) of
true -> ?VERIFY_NONE;
false -> 0
end,
Flags2 = case lists:member(compression_none, Options) of
true -> ?COMPRESSION_NONE;
false -> 0
end,
Flags3 = case lists:member(override_cert_purpose, Options) of
true -> ?OVERRIDE_CERT_PURPOSE;
false -> 0
end,
Flags = Flags1 bor Flags2 bor Flags3,
Ciphers =
case lists:keysearch(ciphers, 1, Options) of
{value, {ciphers, C}} ->
iolist_to_binary(C);
false ->
<<>>
end,
ProtocolOpts = case lists:keysearch(protocol_options, 1, Options) of
{value, {protocol_options, P}} ->
iolist_to_binary(P);
false ->
<<>>
end,
DH = case lists:keysearch(dh, 1, Options) of
{value, {dh, Der}} ->
iolist_to_binary(Der);
false ->
<<>>
end,
DHFile = case lists:keysearch(dhfile, 1, Options) of
{value, {dhfile, D}} ->
iolist_to_binary(D);
false ->
<<>>
end,
CAFile = case lists:keysearch(cafile, 1, Options) of
{value, {cafile, CA}} ->
iolist_to_binary(CA);
false ->
<<>>
end,
KeyFile = case lists:keysearch(keyfile, 1, Options) of
{value, {keyfile, KF}} when CertFile /= "" ->
iolist_to_binary(KF);
_ ->
<<>>
end,
ServerName = case lists:keysearch(sni, 1, Options) of
{value, {sni, SNI}} ->
iolist_to_binary(SNI);
false ->
<<>>
end,
ALPN = case lists:keysearch(alpn, 1, Options) of
{value, {alpn, ProtoList}} ->
encode_alpn(ProtoList);
false ->
<<>>
end,
case open_nif(Command bor Flags, CertFile, KeyFile, Ciphers,
ProtocolOpts, DH, DHFile, CAFile, ServerName, ALPN) of
{ok, Port} ->
{ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}};
Err = {error, _} ->
Err
end;
true -> {error, no_certfile}
end.
-spec tls_to_tcp(tls_socket()) -> inet:socket().
tls_to_tcp(#tlssock{tcpsock = TCPSocket, tlsport = Port}) ->
invalidate_nif(Port),
TCPSocket.
-spec recv(tls_socket(), non_neg_integer()) ->
{error, inet:posix()} | {error, binary()} | {ok, binary()}.
recv(Socket, Length) -> recv(Socket, Length, infinity).
-spec recv(tls_socket(), non_neg_integer(), timeout()) ->
{error, inet:posix()} | {error, binary()} | {ok, binary()}.
recv(TLSSock, Length, Timeout) ->
recv_and_loop(TLSSock, <<>>, <<>>, <<>>,
case Length of 0 -> -1; _ -> Length end, Timeout).
-spec recv_data(tls_socket(), binary()) ->
{error, inet:posix() | binary()} | {ok, binary()}.
recv_data(TLSSock, Packet) ->
loop(TLSSock, <<>>, Packet, <<>>, -1).
-spec loop(tls_socket(), binary(), binary(), binary(), integer()) ->
{error, inet:posix() | binary()} | {ok, binary()}.
loop(#tlssock{tcpsock = TCPSocket,
tlsport = Port} = Socket,
ToSend, Received, DecBuf, Length) ->
try loop_nif(Port, ToSend, Received, Length) of
{error, _} = Err ->
Err;
{Tag, <<>>, Decrypted} when Tag == ok; Tag == init ->
{ok, <<DecBuf/binary, Decrypted/binary>>};
{Tag, ToWrite, Decrypted} when Tag == ok; Tag == init ->
case gen_tcp:send(TCPSocket, ToWrite) of
ok ->
loop(Socket, <<>>, <<>>, <<DecBuf/binary, Decrypted/binary>>,
Length - byte_size(Decrypted));
{error, _} = Err ->
Err
end;
{{error, _} = Err, ToWrite, _} ->
_ = gen_tcp:send(TCPSocket, ToWrite),
Err
catch error:badarg ->
{error, einval}
end.
-spec recv_and_loop(tls_socket(), binary(), binary(), binary(), integer(), timeout()) ->
{error, inet:posix() | binary()} | {ok, binary()}.
recv_and_loop(#tlssock{tcpsock = TCPSocket} = Socket,
ToSend, Received, DecBuf, Length, Timeout) ->
case loop(Socket, ToSend, Received, DecBuf, Length) of
{error, _} = Err ->
Err;
{ok, Decrypted} ->
case size(Decrypted) - size(DecBuf) of
V when V == 0 ->
case gen_tcp:recv(TCPSocket, 0, Timeout) of
{ok, Received2} ->
recv_and_loop(Socket, <<>>, Received2, Decrypted,
Length - V, Timeout);
{error, _} = Err ->
Err
end;
V when Length > V ->
recv_and_loop(Socket, <<>>, <<>>, Decrypted,
Length - V, Timeout);
_ when Length < 0 ->
{ok, Decrypted};
V when Length == V ->
{ok, Decrypted};
_ ->
{error, too_much_data_received}
end
end.
-spec finish_handshake(tls_socket(), timeout()) ->
{error, inet:posix() | binary()} | ok.
finish_handshake(#tlssock{tcpsock = TCPSocket, tlsport = Port}, Timeout) ->
OurLoop = fun OurLoop(Received) ->
try loop_nif(Port, <<>>, Received, 0) of
{error, _} = Err ->
Err;
{Tag, ToWrite, <<>>} when Tag == ok; Tag == init ->
case gen_tcp:send(TCPSocket, ToWrite) of
ok when Tag == init ->
case gen_tcp:recv(TCPSocket, 0, Timeout) of
{ok, Received2} ->
OurLoop(Received2);
{error, _} = Err ->
Err
end;
ok ->
ok;
{error, _} = Err ->
Err
end;
{Tag, _ToWrite, _Data} when Tag == ok; Tag == init ->
{error, too_much_data_received};
{{error, _} = Err, ToWrite, _} ->
_ = gen_tcp:send(TCPSocket, ToWrite),
Err
catch error:badarg ->
{error, einval}
end
end,
OurLoop(<<>>).
-spec send(tls_socket(), binary()) ->
ok | {error, inet:posix() | binary() | timeout}.
send(Socket, Packet) ->
case loop(Socket, Packet, <<>>, <<>>, 0) of
{ok, <<>>} ->
ok;
{ok, Data} ->
Data;
Other ->
Other
end.
-spec setopts(tls_socket(), list()) ->
ok | {error, inet:posix()}.
setopts(#tlssock{tcpsock = TCPSocket}, Opts) ->
inet:setopts(TCPSocket, Opts).
-spec sockname(tls_socket()) ->
{ok, {inet:ip_address(), inet:port_number()}} | {error, inet:posix()}.
sockname(#tlssock{tcpsock = TCPSocket}) ->
inet:sockname(TCPSocket).
peername(#tlssock{tcpsock = TCPSocket}) ->
inet:peername(TCPSocket).
controlling_process(#tlssock{tcpsock = TCPSocket},
Pid) ->
gen_tcp:controlling_process(TCPSocket, Pid).
close(#tlssock{tcpsock = TCPSocket, tlsport = Port}) ->
invalidate_nif(Port),
gen_tcp:close(TCPSocket).
-spec get_peer_certificate(tls_socket()) -> {ok, cert()} | error.
get_peer_certificate(TLSSock) ->
get_peer_certificate(TLSSock, plain).
-spec get_peer_certificate(tls_socket(), otp|plain) -> {ok, cert()} | error;
(tls_socket(), der) -> {ok, binary()} | error.
get_peer_certificate(#tlssock{tlsport = Port}, Type) ->
case catch get_peer_certificate_nif(Port) of
{'EXIT', {badarg, _}} ->
error;
{ok, BCert} when Type == der ->
{ok, BCert};
{ok, BCert} ->
try public_key:pkix_decode_cert(BCert, Type) of
Cert -> {ok, Cert}
catch _:_ ->
error
end;
{error, _} -> error
end.
-spec get_negotiated_cipher(tls_socket()) -> error | {ok, binary()}.
get_negotiated_cipher(#tlssock{tlsport = Port}) ->
case catch get_negotiated_cipher_nif(Port) of
Val when is_binary(Val) ->
{ok, Val};
_ ->
error
end.
-spec get_tls_last_message(peer | self, tls_socket()) -> {ok, binary()} | {error, term()}.
get_tls_last_message(peer, #tlssock{tlsport = Port}) ->
tls_get_peer_finished_nif(Port);
get_tls_last_message(self, #tlssock{tlsport = Port}) ->
tls_get_finished_nif(Port).
-spec get_tls_cb_exporter(tls_socket()) -> {ok, binary()} | {error, term()}.
get_tls_cb_exporter(#tlssock{tlsport = Port}) ->
get_tls_cb_exporter_nif(Port).
-spec get_verify_result(tls_socket()) -> byte().
get_verify_result(#tlssock{tlsport = Port}) ->
{ok, Res} = get_verify_result_nif(Port),
Res.
-spec get_cert_verify_string(number(), cert() | binary()) -> binary().
get_cert_verify_string(CertVerifyRes, Cert) ->
IsSelfsigned = cert_is_self_signed(Cert),
case {CertVerifyRes, IsSelfsigned} of
{21, true} -> <<"self-signed certificate">>;
_ -> cert_verify_code(CertVerifyRes)
end.
-spec cert_is_self_signed(cert() | binary()) -> boolean().
cert_is_self_signed(#'Certificate'{} = Cert) ->
BCert = public_key:pkix_encode('Certificate', Cert, plain),
cert_is_self_signed(BCert);
cert_is_self_signed(Cert) ->
public_key:pkix_is_self_signed(Cert).
-spec add_certfile(iodata(), iodata()) -> ok.
add_certfile(Domain, File) ->
add_certfile_nif(Domain, File).
%% @doc This function is intended for tests only
-spec get_certfile(iodata()) -> {ok, binary()} | error.
get_certfile(Domain) ->
get_certfile_nif(Domain).
%% @doc Returns true if element is deleted, false otherwise
-spec delete_certfile(iodata()) -> boolean().
delete_certfile(Domain) ->
delete_certfile_nif(Domain).
%% @doc Clears cached SSL_CTX structures
%% You MUST call this function if you change content
%% of your CA, DH or certificate files
-spec clear_cache() -> ok.
clear_cache() ->
clear_cache_nif().
%% @doc Enables/disables FIPS mode
-spec set_fips_mode(boolean()) -> ok | {error, binary()}.
set_fips_mode(true) ->
set_fips_mode_nif(1);
set_fips_mode(false) ->
set_fips_mode_nif(0).
%% @doc Checks whether FIPS mode is enabled or not
-spec get_fips_mode() -> boolean().
get_fips_mode() ->
get_fips_mode_nif().
%% @doc Converts PKCS12 certificate to PEM encoded private key and certificate
-spec p12_to_pem(binary(), binary()) -> {ok, {binary(), binary()}} | {error, atom() | binary()}.
p12_to_pem(P12Data, Pass) ->
p12_to_pem_nif(P12Data, <<Pass/binary, 0>>).
cert_verify_code(0) -> <<"ok">>;
cert_verify_code(2) ->
<<"unable to get issuer certificate">>;
cert_verify_code(3) ->
<<"unable to get certificate CRL">>;
cert_verify_code(4) ->
<<"unable to decrypt certificate's signature">>;
cert_verify_code(5) ->
<<"unable to decrypt CRL's signature">>;
cert_verify_code(6) ->
<<"unable to decode issuer public key">>;
cert_verify_code(7) ->
<<"certificate signature failure">>;
cert_verify_code(8) -> <<"CRL signature failure">>;
cert_verify_code(9) ->
<<"certificate is not yet valid">>;
cert_verify_code(10) -> <<"certificate has expired">>;
cert_verify_code(11) -> <<"CRL is not yet valid">>;
cert_verify_code(12) -> <<"CRL has expired">>;
cert_verify_code(13) ->
<<"format error in certificate's notBefore "
"field">>;
cert_verify_code(14) ->
<<"format error in certificate's notAfter "
"field">>;
cert_verify_code(15) ->
<<"format error in CRL's lastUpdate field">>;
cert_verify_code(16) ->
<<"format error in CRL's nextUpdate field">>;
cert_verify_code(17) -> <<"out of memory">>;
cert_verify_code(18) -> <<"self signed certificate">>;
cert_verify_code(19) ->
<<"self signed certificate in certificate "
"chain">>;
cert_verify_code(20) ->
<<"unable to get local issuer certificate">>;
cert_verify_code(21) ->
<<"unable to verify the first certificate">>;
cert_verify_code(22) ->
<<"certificate chain too long">>;
cert_verify_code(23) -> <<"certificate revoked">>;
cert_verify_code(24) -> <<"invalid CA certificate">>;
cert_verify_code(25) ->
<<"path length constraint exceeded">>;
cert_verify_code(26) ->
<<"unsupported certificate purpose">>;
cert_verify_code(27) -> <<"certificate not trusted">>;
cert_verify_code(28) -> <<"certificate rejected">>;
cert_verify_code(29) -> <<"subject issuer mismatch">>;
cert_verify_code(30) ->
<<"authority and subject key identifier "
"mismatch">>;
cert_verify_code(31) ->
<<"authority and issuer serial number mismatch">>;
cert_verify_code(32) ->
<<"key usage does not include certificate "
"signing">>;
cert_verify_code(50) ->
<<"application verification failure">>;
cert_verify_code(X) ->
<<"Unknown OpenSSL error code: ", (integer_to_binary(X))/binary>>.
encode_alpn(ProtoList) ->
[<<(size(Proto)), Proto/binary>> || Proto <- ProtoList, Proto /= <<>>].
load_nif() ->
case os:getenv("COVERALLS") of
"true" -> ok;
_ -> load_nif2()
end.
load_nif2() ->
SOPath = p1_nif_utils:get_so_path(fast_tls, [fast_tls], "fast_tls"),
load_nif(SOPath).
load_nif(SOPath) ->
case erlang:load_nif(SOPath, 0) of
ok ->
set_fips_mode();
{error, {reload, _}} -> % We don't support upgrade in this module so let's not crash
ok;
{error, {upgrade, _}} -> % We don't support upgrade in this module so let's not crash
ok;
{error, ErrorDesc} = Err ->
error_logger:error_msg("failed to load TLS NIF: ~s~n",
[erl_ddll:format_error(ErrorDesc)]),
Err
end.
-spec set_fips_mode() -> ok | {error, term()}.
set_fips_mode() ->
case application:get_env(?MODULE, fips_mode) of
{ok, Flag} when Flag == true orelse Flag == false ->
case set_fips_mode(Flag) of
ok -> ok;
{error, Reason} = Err ->
error_logger:error_msg("~s", [Reason]),
Err
end;
_ ->
ok
end.
-ifdef(TEST).
%% DER encoded DH parameters (2048 bits)
-define(DH, <<48,130,1,8,2,130,1,1,0,146,218,14,246,
227,231,225,122,39,149,89,115,73,249,41,
239,197,168,101,114,1,121,135,30,206,
169,127,254,204,228,53,170,194,229,198,
217,154,137,237,225,70,196,242,42,25,16,
129,6,212,231,247,91,254,86,232,151,113,
176,135,120,61,177,224,162,198,69,68,
120,113,192,110,70,64,129,180,122,160,
155,34,145,186,59,199,202,64,186,246,36,
145,192,24,51,9,255,128,224,249,184,241,
108,19,170,54,148,113,249,232,106,118,
228,15,95,90,29,67,140,245,210,158,147,
244,254,16,109,40,49,179,160,209,228,
204,21,57,197,168,138,78,22,197,141,183,
50,31,96,32,138,187,94,99,132,191,8,26,
98,43,229,49,132,164,146,145,161,232,
205,44,233,41,18,207,47,11,112,31,201,
163,151,71,179,128,78,129,38,32,133,165,
189,187,144,150,186,223,215,91,85,192,
214,31,143,66,194,29,184,23,60,134,74,
143,253,33,171,29,79,136,25,197,125,66,
177,186,76,206,61,138,152,91,88,86,231,
196,100,76,1,197,196,160,88,120,161,185,
212,240,103,92,198,221,189,102,246,17,
77,78,187,121,152,68,227,2,1,2>>).
transmission_with_client_certificate_test() ->
transmission_test_with_opts([certificate()], [certificate()]).
transmission_without_client_certificate_test() ->
transmission_test_with_opts([certificate()], []).
transmission_without_server_cert_fails_test() ->
TestPid = self(),
{ok, ListenSocket} = gen_tcp:listen(0, [binary, {packet, 0}, {active, false},
{reuseaddr, true}, {nodelay, true}]),
{ok, Port} = inet:port(ListenSocket),
_ListenerPid = spawn(fun() -> {ok, Socket} = gen_tcp:accept(ListenSocket),
Res = tcp_to_tls(Socket, []),
TestPid ! {listener_tcp_to_tls, Res}
end),
{ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, Port, [binary, {packet, 0}, {active, false},
{reuseaddr, true}, {nodelay, true}]),
{ok, TLSSock} = tcp_to_tls(Socket, [connect]),
close(TLSSock),
receive
{listener_tcp_to_tls, Res} ->
?assertEqual({error, no_certfile}, Res)
end.
transmission_test_with_opts(ListenerOpts, SenderOpts) ->
{LPid, Port} = setup_listener(ListenerOpts),
SPid = setup_sender(Port, SenderOpts),
SPid ! {stop, self()},
FC = receive
{result, Res, FinishedFromClient} ->
?assertEqual(ok, Res),
FinishedFromClient
end,
LPid ! {stop, self()},
FL = receive
{received, Msg, FinishedFromListener} ->
?assertEqual(<<"abcdefghi">>, Msg),
FinishedFromListener
end,
?assertEqual(FC, FL),
receive
{certfile, Cert} ->
case lists:keymember(certfile, 1, SenderOpts) of
true -> ?assertNotEqual(error, Cert);
false -> ?assertEqual(error, Cert)
end
end.
not_compatible_protocol_options_test() ->
{LPid, Port} = setup_listener([certificate(), {protocol_options, <<"no_sslv2|no_sslv3|no_tlsv1|no_tlsv1_1|no_tlsv1_2|no_tlsv1_3">>}]),
SPid = setup_sender(Port, [{protocol_options, <<"no_sslv2|no_sslv3|no_tlsv1|no_tlsv1_1|no_tlsv1_2">>}]),
SPid ! {stop, self()},
receive
{result, _Res, _} -> ok
end,
LPid ! {stop, self()},
receive
{received, {error, _, _} = Msg, _} ->
?assertMatch({error, _, <<>>}, Msg);
{received, Msg, _} ->
?assertMatch(<<>>, Msg)
end.
setup_listener(Opts) ->
{ok, ListenSocket} = gen_tcp:listen(0,
[binary, {packet, 0}, {active, false},
{reuseaddr, true}, {nodelay, true}]),
Pid = spawn(fun() ->
{ok, Socket} = gen_tcp:accept(ListenSocket),
{ok, TLSSock} = tcp_to_tls(Socket, [{dh, ?DH}|Opts]),
listener_loop(TLSSock, <<>>)
end),
{ok, Port} = inet:port(ListenSocket),
{Pid, Port}.
listener_loop(TLSSock, Msg) ->
Finished = get_tls_last_message(peer, TLSSock),
case recv(TLSSock, 1, 1000) of
{error, timeout} ->
receive
{stop, Pid} ->
Pid ! {received, Msg, Finished},
Cert = get_peer_certificate(TLSSock),
Pid ! {certfile, Cert}
after 0 ->
listener_loop(TLSSock, Msg)
end;
{error, closed} ->
receive
{stop, Pid} ->
Pid ! {received, Msg, Finished},
Cert = get_peer_certificate(TLSSock),
Pid ! {certfile, Cert}
end;
{error, Err} ->
receive
{stop, Pid} ->
Pid ! {received, {error, Err, Msg}, Finished}
end;
{ok, Data} ->
listener_loop(TLSSock, <<Msg/binary, Data/binary>>)
end.
setup_sender(Port, Opts) ->
{ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, Port, [
binary, {packet, 0}, {active, false},
{reuseaddr, true}, {nodelay, true}]),
spawn(fun() ->
{ok, TLSSock} = tcp_to_tls(Socket, [connect | Opts]),
sender_loop(TLSSock)
end).
sender_loop(TLSSock) ->
{Res, Finished} = try
recv(TLSSock, 0, 100),
F = get_tls_last_message(self, TLSSock),
ok = send(TLSSock, <<"abc">>),
recv(TLSSock, 0, 100),
ok = send(TLSSock, <<"def">>),
recv(TLSSock, 0, 100),
ok = send(TLSSock, <<"ghi">>),
recv(TLSSock, 0, 100),
close(TLSSock),
{ok, F}
catch
_:Err ->
close(TLSSock),
{Err, <<>>}
end,
receive
{stop, Pid} ->
Pid ! {result, Res, Finished}
end.
-ifdef(REBAR3).
certificate() ->
{certfile, <<"tests/cert.pem">>}.
-else.
certificate() ->
{certfile, <<"../tests/cert.pem">>}.
-endif.
-endif.