Current section
Files
Jump to
Current section
Files
src/erltls.erl
-module(erltls).
-include("erltls.hrl").
%% todo:
%% 1. implement the missing methods
%% 2. In handshake process add a timeout param (affects connect and ssl_accept methods)
-export([
start/0,
start/1,
stop/0,
cipher_suites/0,
clear_pem_cache/0,
connect/2,
connect/3,
connect/4,
controlling_process/2,
getopts/2,
setopts/2,
getstat/1,
getstat/2,
peercert/1,
connection_information/1,
peername/1,
sockname/1,
session_reused/1,
listen/2,
transport_accept/1,
transport_accept/2,
ssl_accept/1,
ssl_accept/2,
ssl_accept/3,
send/2,
recv/2,
recv/3,
close/1,
close/2,
shutdown/2,
versions/0
]).
-spec start() ->
ok | {error, reason()}.
start() ->
start(temporary).
-spec start(permanent | transient | temporary) ->
ok | {error, reason()}.
start(Type) ->
case application:ensure_all_started(erltls, Type) of
{ok, _} ->
ok;
Other ->
Other
end.
-spec stop() ->
ok.
stop() ->
application:stop(erltls).
cipher_suites() ->
case erltls_manager:get_context([], false) of
{ok, Ctx} ->
erltls_nif:ciphers(Ctx);
_ ->
{error, <<"invalid ssl context">>}
end.
clear_pem_cache() ->
case erltls_manager:clear_cache() of
true ->
ok;
Error ->
{error, Error}
end.
-spec connect(port(), [connect_option()]) ->
{ok, tlssocket()} | {error, reason()}.
connect(Socket, TlsOpt) ->
connect(Socket, TlsOpt, ?DEFAULT_TIMEOUT).
-spec connect(port() | host(), [connect_option()] | inet:port_number(), timeout() | list()) ->
{ok, tlssocket()} | {error, reason()}.
connect(Socket, TlsOpt0, Timeout) when is_port(Socket) ->
%todo: implement timeout in this case
case inet:setopts(Socket, erltls_options:default_inet_options()) of
ok ->
case erltls_options:get_options(TlsOpt0) of
{ok, [], TlsOpt, []} ->
do_connect(Socket, TlsOpt, erltls_options:emulated_for_socket(Socket), Timeout);
{ok, TcpOpt, _TlsOpt, EmulatedOpt} ->
{error, {options, TcpOpt ++ EmulatedOpt}};
Error ->
Error
end;
Error ->
Error
end;
connect(Host, Port, Options) ->
connect(Host, Port, Options, ?DEFAULT_TIMEOUT).
-spec connect(host(), inet:port_number(), [connect_option()], timeout()) ->
{ok, tlssocket()} | {error, reason()}.
connect(Host, Port, Options, Timeout) ->
case erltls_options:get_options(Options) of
{ok, TcpOpt, TlsOpt, EmulatedOpts} ->
case gen_tcp:connect(Host, Port, TcpOpt ++ erltls_options:default_inet_options(), Timeout) of
{ok, TcpSocket} ->
do_connect(TcpSocket, TlsOpt, EmulatedOpts, Timeout);
Error ->
Error
end;
Error ->
Error
end.
-spec controlling_process(tlssocket(), pid()) ->
ok | {error, reason()}.
controlling_process(#tlssocket{ssl_pid = Pid} = Socket, NewOwner) ->
erltls_ssl_process:controlling_process(Pid, Socket, NewOwner).
-spec getopts(tlssocket(), [gen_tcp:option_name()]) ->
{ok, [gen_tcp:option()]} | {error, reason()}.
getopts(#tlssocket{tcp_sock = TcpSock, ssl_pid = Pid}, OptionNames) ->
case erltls_options:get_inet_names(OptionNames) of
{ok, InetOptsNames, []} ->
inet:getopts(TcpSock, InetOptsNames);
{ok, [], EmulatedOptsNames} ->
erltls_ssl_process:get_emulated_options(Pid, EmulatedOptsNames);
{ok, InetOptsNames, EmulatedOptsNames} ->
case inet:getopts(TcpSock, InetOptsNames) of
{ok, Opts1} ->
case erltls_ssl_process:get_emulated_options(Pid, EmulatedOptsNames) of
{ok, Opts2} ->
{ok, Opts1 ++ Opts2};
Error ->
Error
end;
Error ->
Error
end;
Error ->
Error
end.
-spec setopts(tlssocket(), [gen_tcp:option()]) ->
ok | {error, reason()}.
setopts(#tlssocket{ssl_pid = Pid}, Options) ->
case erltls_options:get_inet_options(Options) of
{ok, InetOpts, EmulatedOpts} ->
erltls_ssl_process:setopts(Pid, InetOpts, EmulatedOpts);
Error ->
Error
end.
-spec getstat(tlssocket()) ->
{ok, [{inet:stat_option(), integer()}]} | {error, inet:posix()}.
getstat(#tlssocket{tcp_sock = TcpSock}) ->
inet:getstat(TcpSock).
-spec getstat(tlssocket(), [inet:stat_option()]) ->
{ok, [{inet:stat_option(), integer()}]} | {error, inet:posix()}.
getstat(#tlssocket{tcp_sock = TcpSock}, Opt) ->
inet:getstat(TcpSock, Opt).
-spec peercert(tlssocket()) ->
{ok, binary()} | {error, reason()}.
peercert(#tlssocket{ssl_pid = Pid}) ->
erltls_ssl_process:peercert(Pid).
-spec peername(tlssocket()) ->
{ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
peername(#tlssocket{tcp_sock = TcpSock}) ->
inet:peername(TcpSock).
-spec sockname(tlssocket()) ->
{ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}.
sockname(#tlssocket{tcp_sock = TcpSock}) ->
inet:sockname(TcpSock).
-spec connection_information(tlssocket()) -> {ok, list()} | {error, reason()}.
connection_information(#tlssocket{ssl_pid = Pid}) ->
erltls_ssl_process:session_info(Pid).
-spec session_reused(tlssocket()) -> boolean() | {error, reason()}.
session_reused(#tlssocket{ssl_pid = Pid}) ->
erltls_ssl_process:session_reused(Pid).
-spec listen(inet:port_number(), [listen_option()]) ->
{ok, tlssocket()} | {error, reason()}.
listen(Port, Options) ->
case erltls_options:get_options(Options) of
{ok, TcpOpt, TlsOpt, EmulatedOpt} ->
case gen_tcp:listen(Port, TcpOpt ++ erltls_options:default_inet_options()) of
{ok, TcpSocket} ->
erltls_ssl_process:new(TcpSocket, TlsOpt, EmulatedOpt, ?SSL_ROLE_SERVER);
Error ->
Error
end;
Error ->
Error
end.
-spec transport_accept(tlssocket()) ->
{ok, tlssocket()} |{error, reason()}.
transport_accept(ListenSocket) ->
transport_accept(ListenSocket, ?DEFAULT_TIMEOUT).
-spec transport_accept(tlssocket(), timeout()) ->
{ok, tlssocket()} | {error, reason()}.
transport_accept(#tlssocket{tcp_sock = TcpSock, ssl_pid = Pid}, Timeout) ->
case gen_tcp:accept(TcpSock, Timeout) of
{ok, ASocket} ->
case erltls_ssl_process:get_options(Pid) of
{ok, TlsOpts, EmulatedOpts} ->
erltls_ssl_process:new(ASocket, TlsOpts, EmulatedOpts, ?SSL_ROLE_SERVER);
Error ->
Error
end;
Error ->
Error
end.
-spec ssl_accept(tlssocket()) ->
ok | {error, reason()}.
ssl_accept(Socket) ->
ssl_accept(Socket, ?DEFAULT_TIMEOUT).
-spec ssl_accept(tlssocket() | port(), timeout()| [tls_option()]) ->
ok | {ok, tlssocket()} | {error, reason()}.
ssl_accept(#tlssocket{} = Socket, Timeout) ->
ssl_accept(Socket, [], Timeout);
ssl_accept(Socket, SslOptions) when is_port(Socket) ->
ssl_accept(Socket, SslOptions, ?DEFAULT_TIMEOUT).
-spec ssl_accept(tlssocket() | port(), [tls_option()], timeout()) ->
{ok, tlssocket()} | {error, reason()}.
ssl_accept(#tlssocket{tcp_sock = TcpSock, ssl_pid = Pid}, [], Timeout) ->
erltls_ssl_process:handshake(Pid, TcpSock, Timeout);
ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
case erltls_options:get_options(SslOptions) of
{ok, [], TlsOpt, []} ->
case erltls_ssl_process:new(Socket, TlsOpt, erltls_options:emulated_for_socket(Socket), ?SSL_ROLE_SERVER) of
{ok, SslSocket} ->
case erltls_ssl_process:handshake(SslSocket#tlssocket.ssl_pid, Socket, Timeout) of
ok ->
{ok, SslSocket};
Error ->
Error
end;
Error ->
Error
end;
{ok, TcpOpt, _TlsOpt, EmulatedOpt} ->
{error, {options, TcpOpt ++ EmulatedOpt}};
Error ->
Error
end.
-spec send(tlssocket(), iodata()) ->
ok | {error, reason()}.
send(#tlssocket{ssl_pid = Pid, tcp_sock = TcpSocket}, Data) ->
case erltls_ssl_process:encode_data(Pid, Data) of
{ok, TlsData} ->
gen_tcp:send(TcpSocket, TlsData);
Error ->
Error
end.
-spec recv(tlssocket(), integer()) ->
{ok, binary()| list()} | {error, reason()}.
recv(Socket, Length) ->
recv(Socket, Length, infinity).
-spec recv(tlssocket(), integer(), timeout()) ->
{ok, binary()| list()} | {error, reason()}.
recv(#tlssocket{tcp_sock = TcpSock, ssl_pid = Pid}, Length, Timeout) ->
case erltls_ssl_process:get_pending_buffer(Pid, Length) of
need_more ->
passive_read_more(TcpSock, Pid, Length, Timeout);
Response->
Response
end.
passive_read_more(TcpSock, TlsPid, TotalLength, Timeout) ->
case gen_tcp:recv(TcpSock, 0, Timeout) of
{ok, Packet} ->
case erltls_ssl_process:decode_data(TlsPid, Packet, TotalLength) of
need_more ->
passive_read_more(TcpSock, TlsPid, TotalLength, Timeout);
Response ->
Response
end;
Error ->
Error
end.
-spec close(tlssocket(), timeout() | {pid(), integer()}) ->
ok | {ok, port()} | {error, reason()}.
close(#tlssocket{tcp_sock = TcpSock, ssl_pid = SslPid} = Socket, {NewOwnerPid, Timeout}) when is_pid(NewOwnerPid) ->
case erltls_ssl_process:downgrade(SslPid, NewOwnerPid, Timeout) of
ok ->
{ok, TcpSock};
Error ->
close(Socket),
Error
end;
close(TlsSocket, _Timeout) ->
%todo: implement timeout parameter here.
close(TlsSocket).
-spec close(tlssocket()) -> term().
close(#tlssocket{ssl_pid = Pid, tcp_sock = TcpSocket}) ->
erltls_ssl_process:shutdown(Pid),
erltls_ssl_process:close(Pid),
gen_tcp:close(TcpSocket).
-spec shutdown(tlssocket(), read | write | read_write) ->
ok | {error, reason()}.
shutdown(#tlssocket{tcp_sock = TcpSocket, ssl_pid = Pid}, How)->
case How =:= write orelse How =:= read_write of
true ->
erltls_ssl_process:shutdown(Pid);
_ ->
ok
end,
gen_tcp:shutdown(TcpSocket, How).
-spec versions() ->
{ok, list()}.
versions() ->
erltls_nif:version().
%internals
do_connect(TcpSocket, TlsOpt, EmulatedOpts, Timeout) when is_list(EmulatedOpts) ->
UseSessionTicket = erltls_options:use_session_ticket(erltls_utils:lookup(use_session_ticket, TlsOpt)),
case get_session_ticket(UseSessionTicket, TcpSocket) of
{ok, SessionAsn1, Host, Port} ->
case erltls_ssl_process:new(TcpSocket, TlsOpt, EmulatedOpts, ?SSL_ROLE_CLIENT, SessionAsn1, Timeout) of
{ok, #tlssocket{ssl_pid = Pid} = TlsSocketRef} ->
update_session_ticket(UseSessionTicket, Host, Port, Pid),
{ok, TlsSocketRef};
Error ->
Error
end;
Error ->
Error
end;
do_connect(_TcpSocket, _TlsOpt, EmulatedOpts, _Timeout) ->
{error, EmulatedOpts}.
get_session_ticket(true, Socket) ->
case inet:peername(Socket) of
{ok, {Host, Port}} ->
case erltls_ticket_cache:get(Host, Port) of
null ->
{ok, <<>>, Host, Port};
{ok, SessionAsn1} ->
{ok, SessionAsn1, Host, Port};
Resp ->
Resp
end;
Error ->
Error
end;
get_session_ticket(_, _Socket) ->
{ok, <<>>, undefined, undefined}.
update_session_ticket(true, Host, Port, TlsRef) ->
case erltls_ssl_process:get_session_asn1(TlsRef) of
{ok, HasTicket, SessionAsn1} ->
case HasTicket of
true ->
erltls_ticket_cache:set(Host, Port, SessionAsn1);
_ ->
true
end;
Error ->
Error
end;
update_session_ticket(_, _Host, _Port, _TlsRef) ->
true.