Implement mTLS authentication between client and server

This commit is contained in:
2025-02-23 01:32:59 +00:00
parent 83ab6f7a20
commit ebf9afb4e1
9 changed files with 166 additions and 43 deletions

View File

@@ -4,15 +4,15 @@
-module(proto_sup).
-behaviour(supervisor).
-export([start_link/1]).
-export([start_link/2]).
-export([init/1]).
start_link(Port) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [Port]).
start_link(Port, CertDir) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, CertDir]).
init([Port]) ->
init([Port, CertDir]) ->
SupFlags = #{stragegy => one_for_all,
intensity => 1,
period => 5},
ChildSpecs = [tcp_server:child_spec(Port)],
ChildSpecs = [tcp_server:child_spec(Port, CertDir)],
{ok, {SupFlags, ChildSpecs}}.

View File

@@ -3,8 +3,8 @@
{vsn, "0.1.0"},
{registered, []},
{mod, {study_system_server_app, []}},
{applications, [kernel, stdlib]},
{env, []},
{applications, [kernel, stdlib, ssl]},
{env, [{cert_dir, "../test"}]},
{modules, []},
{licenses, ["AGPL-3.0-only"]},
{links, []}]}.

View File

@@ -8,7 +8,8 @@
start(_StartType, _StartArgs) ->
Port = application:get_env(study_system_server, port, 12888),
proto_sup:start_link(Port).
{ok, CertDir} = application:get_env(study_system_server, cert_dir),
proto_sup:start_link(Port, CertDir).
stop(_State) ->
ok.

View File

@@ -4,25 +4,31 @@
-module(tcp_server).
-behaviour(gen_server).
-export([start_link/1, child_spec/1]).
-export([start_link/2, child_spec/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {socket, acceptor}).
start_link(Port) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Port], []).
start_link(Port, CertDir) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Port, CertDir], []).
child_spec(Port) ->
child_spec(Port, CertDir) ->
#{id => ?MODULE,
start => {?MODULE, start_link, [Port]},
start => {?MODULE, start_link, [Port, CertDir]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [?MODULE]}.
init([Port]) ->
{ok, Socket} = gen_tcp:listen(Port, [binary, inet6, {active, true}]),
init([Port, CertDir]) ->
io:format("cert dir: ~p~n", [CertDir]),
SslOpts = [{certfile, filename:join([CertDir, "server.pem"])},
{cacertfile, filename:join([CertDir, "ca.pem"])},
{verify, verify_peer},
{fail_if_no_peer_cert, true}],
{ok, Socket} =
ssl:listen(Port, [binary, inet6, {active, true} | SslOpts]),
Pid = spawn_link(fun() -> acceptor_loop(Socket) end),
{ok, #state{socket = Socket, acceptor = Pid}}.
@@ -45,21 +51,27 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
acceptor_loop(Socket) ->
case gen_tcp:accept(Socket) of
{ok, ClientSocket} ->
gen_tcp:controlling_process(
ClientSocket,
spawn(fun() -> handle_connection(ClientSocket) end)),
acceptor_loop(Socket);
case ssl:transport_accept(Socket) of
{ok, TlsSocket} ->
case ssl:handshake(TlsSocket) of
{ok, ClientSocket} ->
Pid = spawn(
fun() -> handle_connection(ClientSocket) end),
ok = ssl:controlling_process(ClientSocket, Pid),
ssl:setopts(ClientSocket, [{active, true}]),
acceptor_loop(Socket);
{error, _Reason} ->
acceptor_loop(Socket)
end;
{error, closed} ->
ok
end.
handle_connection(Socket) ->
receive
{tcp, Socket, Data} ->
gen_tcp:send(Socket, Data),
{ssl, Socket, Data} ->
ssl:send(Socket, Data),
handle_connection(Socket);
{tcp_closed, Socket} ->
{ssl_closed, Socket} ->
ok
end.