diff options
Diffstat (limited to 'src/tls')
-rw-r--r-- | src/tls/Makefile.in | 2 | ||||
-rw-r--r-- | src/tls/tls.erl | 495 |
2 files changed, 256 insertions, 241 deletions
diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in index ee40f93ec..203c88ac9 100644 --- a/src/tls/Makefile.in +++ b/src/tls/Makefile.in @@ -27,7 +27,7 @@ EFLAGS += -pz .. # make debug=true to compile Erlang module with debug informations. ifdef debug - EFLAGS+=+debug_info +export_all + EFLAGS+=+debug_info endif ifeq (@md2@, true) diff --git a/src/tls/tls.erl b/src/tls/tls.erl index bf896b02c..74a62709b 100644 --- a/src/tls/tls.erl +++ b/src/tls/tls.erl @@ -25,347 +25,362 @@ %%%---------------------------------------------------------------------- -module(tls). + -author('alexey@process-one.net'). -behaviour(gen_server). --export([start/0, start_link/0, - 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_verify_result/1, - get_cert_verify_string/2, - test/0]). +-export([start/0, start_link/0, 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_verify_result/1, get_cert_verify_string/2, test/0]). %% Internal exports, call-back functions. --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - code_change/3, - terminate/2]). +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, code_change/3, terminate/2]). -include("ejabberd.hrl"). -define(SET_CERTIFICATE_FILE_ACCEPT, 1). + -define(SET_CERTIFICATE_FILE_CONNECT, 2). --define(SET_ENCRYPTED_INPUT, 3). + +-define(SET_ENCRYPTED_INPUT, 3). + -define(SET_DECRYPTED_OUTPUT, 4). + -define(GET_ENCRYPTED_OUTPUT, 5). --define(GET_DECRYPTED_INPUT, 6). + +-define(GET_DECRYPTED_INPUT, 6). + -define(GET_PEER_CERTIFICATE, 7). --define(GET_VERIFY_RESULT, 8). --define(VERIFY_NONE, 16#10000). --ifdef(SSL40). --define(CERT_DECODE, {public_key, pkix_decode_cert, plain}). --define(CERT_SELFSIGNED, {public_key, pkix_is_self_signed}). --else. --define(CERT_DECODE, {ssl_pkix, decode_cert, [pkix]}). --define(CERT_SELFSIGNED, {erlang, is_atom}). %% Dummy function for old OTPs --endif. +-define(GET_VERIFY_RESULT, 8). + +-define(VERIFY_NONE, 65536). + +-record(tlssock, {tcpsock :: inet:socket(), + tlsport :: port()}). +-type tls_socket() :: #tlssock{}. --record(tlssock, {tcpsock, tlsport}). +-type cert() :: any(). %% TODO + +-export_type([tls_socket/0]). start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + gen_server:start_link({local, ?MODULE}, ?MODULE, [], + []). init([]) -> - case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of - ok -> ok; - {error, already_loaded} -> ok + case erl_ddll:load_driver(ejabberd:get_so_path(), + tls_drv) + of + ok -> ok; + {error, already_loaded} -> ok end, Port = open_port({spawn, "tls_drv"}, [binary]), - Res = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT, "./ssl.pem" ++ [0]), + Res = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT, + <<"./ssl.pem", 0>>), case Res of - <<0>> -> - %ets:new(iconv_table, [set, public, named_table]), - %ets:insert(iconv_table, {port, Port}), - {ok, Port}; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} + <<0>> -> {ok, Port}; + <<1, Error/binary>> -> {error, (Error)} end. - %%% -------------------------------------------------------- %%% The call-back functions. %%% -------------------------------------------------------- -handle_call(_, _, State) -> - {noreply, State}. +handle_call(_, _, State) -> {noreply, State}. -handle_cast(_, State) -> - {noreply, State}. +handle_cast(_, State) -> {noreply, State}. handle_info({'EXIT', Port, Reason}, Port) -> {stop, {port_died, Reason}, Port}; - handle_info({'EXIT', _Pid, _Reason}, Port) -> {noreply, Port}; +handle_info(_, State) -> {noreply, State}. -handle_info(_, State) -> - {noreply, State}. +code_change(_OldVsn, State, _Extra) -> {ok, State}. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -terminate(_Reason, Port) -> - Port ! {self, close}, - ok. +terminate(_Reason, Port) -> Port ! {self, close}, ok. +-spec tcp_to_tls(inet:socket(), + [{atom(), any()}]) -> {'error','no_certfile' | binary()} | + {ok, tls_socket()}. tcp_to_tls(TCPSocket, Options) -> case lists:keysearch(certfile, 1, Options) of - {value, {certfile, CertFile}} -> - case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of - ok -> ok; - {error, already_loaded} -> ok - end, - Port = open_port({spawn, "tls_drv"}, [binary]), - Flags = - case lists:member(verify_none, Options) of - true -> - ?VERIFY_NONE; - false -> - 0 - end, - Command = case lists:member(connect, Options) of - true -> - ?SET_CERTIFICATE_FILE_CONNECT; - false -> - ?SET_CERTIFICATE_FILE_ACCEPT - end, - case port_control(Port, Command bor Flags, CertFile ++ [0]) of - <<0>> -> - {ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}}; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} - end; - false -> - {error, no_certfile} + {value, {certfile, CertFile}} -> + case erl_ddll:load_driver(ejabberd:get_so_path(), + tls_drv) + of + ok -> ok; + {error, already_loaded} -> ok + end, + Port = open_port({spawn, "tls_drv"}, [binary]), + Flags = case lists:member(verify_none, Options) of + true -> ?VERIFY_NONE; + false -> 0 + end, + Command = case lists:member(connect, Options) of + true -> ?SET_CERTIFICATE_FILE_CONNECT; + false -> ?SET_CERTIFICATE_FILE_ACCEPT + end, + CertFile1 = iolist_to_binary(CertFile), + case port_control(Port, Command bor Flags, + <<CertFile1/binary, 0>>) + of + <<0>> -> + {ok, #tlssock{tcpsock = TCPSocket, tlsport = Port}}; + <<1, Error/binary>> -> {error, (Error)} + end; + false -> {error, no_certfile} end. - -tls_to_tcp(#tlssock{tcpsock = TCPSocket, tlsport = Port}) -> - port_close(Port), - TCPSocket. - -recv(Socket, Length) -> - recv(Socket, Length, infinity). -recv(#tlssock{tcpsock = TCPSocket, tlsport = Port} = TLSSock, + +-spec tls_to_tcp(tls_socket()) -> inet:socket(). + +tls_to_tcp(#tlssock{tcpsock = TCPSocket, + tlsport = Port}) -> + port_close(Port), TCPSocket. + +recv(Socket, Length) -> recv(Socket, Length, infinity). + +-spec recv(tls_socket(), non_neg_integer(), + timeout()) -> {error, inet:posix()} | + {error, binary()} | + {ok, binary()}. + +recv(#tlssock{tcpsock = TCPSocket, tlsport = Port} = + TLSSock, Length, Timeout) -> - case port_control(Port, ?GET_DECRYPTED_INPUT, <<Length:32>>) of - <<0>> -> - case gen_tcp:recv(TCPSocket, 0, Timeout) of - {ok, Packet} -> - recv_data(TLSSock, Packet, Length); - {error, _Reason} = Error -> - Error - end; - <<0, In/binary>> -> - {ok, In}; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} + case port_control(Port, ?GET_DECRYPTED_INPUT, + <<Length:32>>) + of + <<0>> -> + case gen_tcp:recv(TCPSocket, 0, Timeout) of + {ok, Packet} -> recv_data(TLSSock, Packet, Length); + {error, _Reason} = Error -> Error + end; + <<0, In/binary>> -> {ok, In}; + <<1, Error/binary>> -> {error, (Error)} end. recv_data(TLSSock, Packet) -> recv_data(TLSSock, Packet, 0). +-spec recv_data(tls_socket(), binary(), + non_neg_integer()) -> {error, inet:posix() | binary()} | + {ok, binary()}. + recv_data(TLSSock, Packet, Length) -> case catch recv_data1(TLSSock, Packet, Length) of - {'EXIT', Reason} -> - {error, Reason}; - Res -> - Res + {'EXIT', Reason} -> {error, Reason}; + Res -> Res end. -recv_data1(#tlssock{tcpsock = TCPSocket, tlsport = Port}, Packet, Length) -> +recv_data1(#tlssock{tcpsock = TCPSocket, + tlsport = Port}, + Packet, Length) -> case port_control(Port, ?SET_ENCRYPTED_INPUT, Packet) of - <<0>> -> - case port_control(Port, ?GET_DECRYPTED_INPUT, <<Length:32>>) of - <<0, In/binary>> -> - case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of - <<0, Out/binary>> -> - case gen_tcp:send(TCPSocket, Out) of - ok -> - %?PRINT("IN: ~p~n", [{TCPSocket, binary_to_list(In)}]), - {ok, In}; - Error -> - Error - end; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} - end; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} - end; - <<1, Error/binary>> -> - {error, binary_to_list(Error)} + <<0>> -> + case port_control(Port, ?GET_DECRYPTED_INPUT, + <<Length:32>>) + of + <<0, In/binary>> -> + case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of + <<0, Out/binary>> -> + case gen_tcp:send(TCPSocket, Out) of + ok -> {ok, In}; + Error -> Error + end; + <<1, Error/binary>> -> {error, (Error)} + end; + <<1, Error/binary>> -> {error, (Error)} + end; + <<1, Error/binary>> -> {error, (Error)} end. -send(#tlssock{tcpsock = TCPSocket, tlsport = Port} = TLSSock, Packet) -> - case port_control(Port, ?SET_DECRYPTED_OUTPUT, Packet) of - <<0>> -> - %?PRINT("OUT: ~p~n", [{TCPSocket, lists:flatten(Packet)}]), - case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of - <<0, Out/binary>> -> - gen_tcp:send(TCPSocket, Out); - <<1, Error/binary>> -> - {error, binary_to_list(Error)} - end; - <<1, Error/binary>> -> - {error, binary_to_list(Error)}; - <<2>> -> % Dirty hack - receive - {timeout, _Timer, _} -> - {error, timeout} - after 100 -> - send(TLSSock, Packet) - end +-spec send(tls_socket(), binary()) -> ok | {error, inet:posix() | + binary() | timeout}. + +send(#tlssock{tcpsock = TCPSocket, tlsport = Port} = + TLSSock, + Packet) -> + case port_control(Port, ?SET_DECRYPTED_OUTPUT, Packet) + of + <<0>> -> + case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of + <<0, Out/binary>> -> gen_tcp:send(TCPSocket, Out); + <<1, Error/binary>> -> {error, (Error)} + end; + <<1, Error/binary>> -> {error, (Error)}; + <<2>> -> % Dirty hack + receive + {timeout, _Timer, _} -> {error, timeout} + after 100 -> send(TLSSock, Packet) + end 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) -> +controlling_process(#tlssock{tcpsock = TCPSocket}, + Pid) -> gen_tcp:controlling_process(TCPSocket, Pid). close(#tlssock{tcpsock = TCPSocket, tlsport = Port}) -> - gen_tcp:close(TCPSocket), - port_close(Port). + gen_tcp:close(TCPSocket), port_close(Port). + +-spec get_peer_certificate(tls_socket()) -> error | {ok, cert()}. get_peer_certificate(#tlssock{tlsport = Port}) -> case port_control(Port, ?GET_PEER_CERTIFICATE, []) of - <<0, BCert/binary>> -> - {CertMod, CertFun, CertSecondArg} = ?CERT_DECODE, - case catch apply(CertMod, CertFun, [BCert, CertSecondArg]) of - {ok, Cert} -> %% returned by R13 and older - {ok, Cert}; - {'Certificate', _, _, _} = Cert -> - {ok, Cert}; - _ -> - error - end; - <<1>> -> - error + <<0, BCert/binary>> -> + case catch public_key:pkix_decode_cert(BCert, plain) + of + {ok, Cert} -> {ok, Cert}; + {'Certificate', _, _, _} = Cert -> {ok, Cert}; + _ -> error + end; + <<1>> -> error end. +-spec get_verify_result(tls_socket()) -> byte(). + get_verify_result(#tlssock{tlsport = Port}) -> <<Res>> = port_control(Port, ?GET_VERIFY_RESULT, []), Res. - test() -> - case erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv) of - ok -> ok; - {error, already_loaded} -> ok + case erl_ddll:load_driver(ejabberd:get_so_path(), + tls_drv) + of + ok -> ok; + {error, already_loaded} -> ok end, Port = open_port({spawn, "tls_drv"}, [binary]), ?PRINT("open_port: ~p~n", [Port]), PCRes = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT, - "./ssl.pem" ++ [0]), + <<"./ssl.pem", 0>>), ?PRINT("port_control: ~p~n", [PCRes]), - {ok, ListenSocket} = gen_tcp:listen(1234, [binary, - {packet, 0}, - {active, true}, - {reuseaddr, true}, - {nodelay, true}]), + {ok, ListenSocket} = gen_tcp:listen(1234, + [binary, {packet, 0}, {active, true}, + {reuseaddr, true}, {nodelay, true}]), ?PRINT("listen: ~p~n", [ListenSocket]), {ok, Socket} = gen_tcp:accept(ListenSocket), ?PRINT("accept: ~p~n", [Socket]), loop(Port, Socket). - loop(Port, Socket) -> receive - {tcp, Socket, Data} -> - %?PRINT("read: ~p~n", [Data]), - Res = port_control(Port, ?SET_ENCRYPTED_INPUT, Data), - ?PRINT("SET_ENCRYPTED_INPUT: ~p~n", [Res]), - - DIRes = port_control(Port, ?GET_DECRYPTED_INPUT, Data), - ?PRINT("GET_DECRYPTED_INPUT: ~p~n", [DIRes]), - case DIRes of - <<0, In/binary>> -> - ?PRINT("input: ~s~n", [binary_to_list(In)]); - <<1, DIError/binary>> -> - ?PRINT("GET_DECRYPTED_INPUT error: ~p~n", [binary_to_list(DIError)]) - end, - - EORes = port_control(Port, ?GET_ENCRYPTED_OUTPUT, Data), - ?PRINT("GET_ENCRYPTED_OUTPUT: ~p~n", [EORes]), - case EORes of - <<0, Out/binary>> -> - gen_tcp:send(Socket, Out); - <<1, EOError/binary>> -> - ?PRINT("GET_ENCRYPTED_OUTPUT error: ~p~n", [binary_to_list(EOError)]) - end, - - - loop(Port, Socket); - Msg -> - ?PRINT("receive: ~p~n", [Msg]), - loop(Port, Socket) + {tcp, Socket, Data} -> + Res = port_control(Port, ?SET_ENCRYPTED_INPUT, Data), + ?PRINT("SET_ENCRYPTED_INPUT: ~p~n", [Res]), + DIRes = port_control(Port, ?GET_DECRYPTED_INPUT, Data), + ?PRINT("GET_DECRYPTED_INPUT: ~p~n", [DIRes]), + case DIRes of + <<0, In/binary>> -> ?PRINT("input: ~s~n", [(In)]); + <<1, DIError/binary>> -> + ?PRINT("GET_DECRYPTED_INPUT error: ~p~n", [(DIError)]) + end, + EORes = port_control(Port, ?GET_ENCRYPTED_OUTPUT, Data), + ?PRINT("GET_ENCRYPTED_OUTPUT: ~p~n", [EORes]), + case EORes of + <<0, Out/binary>> -> gen_tcp:send(Socket, Out); + <<1, EOError/binary>> -> + ?PRINT("GET_ENCRYPTED_OUTPUT error: ~p~n", [(EOError)]) + end, + loop(Port, Socket); + Msg -> + ?PRINT("receive: ~p~n", [Msg]), loop(Port, Socket) end. +-spec get_cert_verify_string(number(), cert()) -> binary(). get_cert_verify_string(CertVerifyRes, Cert) -> - BCert = public_key:pkix_encode('Certificate', Cert, plain), - {CertMod, CertFun} = ?CERT_SELFSIGNED, - IsSelfsigned = apply(CertMod, CertFun, [BCert]), + BCert = public_key:pkix_encode('Certificate', Cert, + plain), + IsSelfsigned = public_key:pkix_is_self_signed(BCert), case {CertVerifyRes, IsSelfsigned} of - {21, true} -> "self-signed certificate"; - _ -> cert_verify_code(CertVerifyRes) + {21, true} -> <<"self-signed certificate">>; + _ -> cert_verify_code(CertVerifyRes) end. %% http://www.openssl.org/docs/apps/verify.html -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_list(X). +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: ", (jlib:integer_to_binary(X))/binary>>. |