1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(openssl_ocsp_SUITE).
22
23-include_lib("common_test/include/ct.hrl").
24-include_lib("public_key/include/public_key.hrl").
25
26%% Callback functions
27-export([all/0,
28         groups/0,
29         init_per_suite/1,
30         end_per_suite/1,
31         init_per_group/2,
32         end_per_group/2,
33         init_per_testcase/2,
34         end_per_testcase/2]).
35
36%% Testcases
37-export([ocsp_stapling_basic/0,ocsp_stapling_basic/1,
38         ocsp_stapling_with_nonce/0, ocsp_stapling_with_nonce/1,
39         ocsp_stapling_with_responder_cert/0,ocsp_stapling_with_responder_cert/1,
40         ocsp_stapling_revoked/0, ocsp_stapling_revoked/1,
41         ocsp_stapling_undetermined/0, ocsp_stapling_undetermined/1,
42         ocsp_stapling_no_staple/0, ocsp_stapling_no_staple/1
43        ]).
44
45%% spawn export
46-export([ocsp_responder_init/3]).
47
48
49%%--------------------------------------------------------------------
50%% Common Test interface functions -----------------------------------
51%%--------------------------------------------------------------------
52all() ->
53    [{group, 'tlsv1.3'},
54     {group, 'tlsv1.2'},
55     {group, 'dtlsv1.2'}].
56
57groups() ->
58    [{'tlsv1.3', [], ocsp_tests()},
59     {'tlsv1.2', [], ocsp_tests()},
60     {'dtlsv1.2', [], ocsp_tests()}].
61
62ocsp_tests() ->
63    [ocsp_stapling_basic,
64     ocsp_stapling_with_nonce,
65     ocsp_stapling_with_responder_cert,
66     ocsp_stapling_revoked,
67     ocsp_stapling_undetermined,
68     ocsp_stapling_no_staple
69    ].
70
71%%--------------------------------------------------------------------
72init_per_suite(Config) ->
73    case ssl_test_lib:openssl_ocsp_support() of
74        true ->
75            do_init_per_suite(Config);
76        false ->
77            {skip, "OCSP not well supported in openSSL"}
78    end.
79
80do_init_per_suite(Config) ->
81    catch crypto:stop(),
82    try crypto:start() of
83	ok ->
84        ssl_test_lib:clean_start(),
85        DataDir = proplists:get_value(data_dir, Config),
86        PrivDir = proplists:get_value(priv_dir, Config),
87
88        %% Prepare certs
89        {ok, _} = make_certs:all(DataDir, PrivDir),
90
91        ResponderPort = get_free_port(),
92        Pid = start_ocsp_responder(ResponderPort, PrivDir),
93
94        NewConfig =
95        lists:merge(
96            [{responder_port, ResponderPort},
97             {responder_pid, Pid}
98            ], Config),
99
100	    ssl_test_lib:cert_options(NewConfig)
101    catch _:_ ->
102	    {skip, "Crypto did not start"}
103    end.
104
105
106end_per_suite(Config) ->
107    ResponderPid = proplists:get_value(responder_pid, Config),
108    ssl_test_lib:close(ResponderPid),
109    ok = ssl:stop(),
110    application:stop(crypto).
111
112%%--------------------------------------------------------------------
113init_per_group(GroupName, Config) ->
114    ssl_test_lib:init_per_group_openssl(GroupName, Config).
115
116end_per_group(GroupName, Config) ->
117    ssl_test_lib:end_per_group(GroupName, Config).
118
119%%--------------------------------------------------------------------
120init_per_testcase(_TestCase, Config) ->
121    ssl_test_lib:ct_log_supported_protocol_versions(Config),
122    ct:timetrap({seconds, 10}),
123    Config.
124
125end_per_testcase(_TestCase, Config) ->
126    Config.
127
128%%--------------------------------------------------------------------
129%% Test Cases --------------------------------------------------------
130%%--------------------------------------------------------------------
131
132ocsp_stapling_basic() ->
133    [{doc, "Verify OCSP stapling works without nonce "
134           "and responder certs."}].
135ocsp_stapling_basic(Config)
136  when is_list(Config) ->
137    PrivDir = proplists:get_value(priv_dir, Config),
138    CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
139
140    Data = "ping",  %% 4 bytes
141    GroupName = proplists:get_value(group, Config),
142    ServerOpts = [{log_level, debug},
143                  {group, GroupName}],
144    Server = ssl_test_lib:start_server(openssl_ocsp,
145                                       [{options, ServerOpts}], Config),
146    Port = ssl_test_lib:inet_port(Server),
147
148    ClientOpts = [{log_level, debug},
149                  {verify, verify_peer},
150                  {cacertfile, CACertsFile},
151                  {server_name_indication, disable},
152                  {ocsp_stapling, true},
153                  {ocsp_nonce, false}] ++ dtls_client_opt(GroupName),
154    Client = ssl_test_lib:start_client(erlang,
155                                       [{port, Port},
156                                        {options, ClientOpts}], Config),
157    ssl_test_lib:send(Server, Data),
158    Data = ssl_test_lib:check_active_receive(Client, Data),
159
160    ssl_test_lib:close(Server),
161    ssl_test_lib:close(Client).
162%%--------------------------------------------------------------------
163ocsp_stapling_with_nonce() ->
164    [{doc, "Verify OCSP stapling works with nonce."}].
165ocsp_stapling_with_nonce(Config)
166  when is_list(Config) ->
167    PrivDir = proplists:get_value(priv_dir, Config),
168    CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
169
170    Data = "ping",  %% 4 bytes
171    GroupName = proplists:get_value(group, Config),
172    ServerOpts = [{log_level, debug},
173                  {group, GroupName}],
174    Server = ssl_test_lib:start_server(openssl_ocsp,
175                                       [{options, ServerOpts}], Config),
176    Port = ssl_test_lib:inet_port(Server),
177
178    ClientOpts = [{log_level, debug},
179                  {verify, verify_peer},
180                  {cacertfile, CACertsFile},
181                  {server_name_indication, disable},
182                  {ocsp_stapling, true},
183                  {ocsp_nonce, true}] ++ dtls_client_opt(GroupName),
184    Client = ssl_test_lib:start_client(erlang,
185                                       [{port, Port},
186                                        {options, ClientOpts}], Config),
187    ssl_test_lib:send(Server, Data),
188    Data = ssl_test_lib:check_active_receive(Client, Data),
189
190    ssl_test_lib:close(Server),
191    ssl_test_lib:close(Client).
192
193ocsp_stapling_with_responder_cert() ->
194    [{doc, "Verify OCSP stapling works with nonce "
195           "and responder certs."}].
196ocsp_stapling_with_responder_cert(Config)
197  when is_list(Config) ->
198    PrivDir = proplists:get_value(priv_dir, Config),
199    CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
200
201    Data = "ping",  %% 4 bytes
202    GroupName = proplists:get_value(group, Config),
203    ServerOpts = [{log_level, debug},
204                  {group, GroupName}],
205    Server = ssl_test_lib:start_server(openssl_ocsp,
206                                       [{options, ServerOpts}], Config),
207    Port = ssl_test_lib:inet_port(Server),
208
209    PrivDir = proplists:get_value(priv_dir, Config),
210    {ok, ResponderCert} =
211        file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
212    [{'Certificate', Der, _IsEncrypted}] =
213        public_key:pem_decode(ResponderCert),
214
215    ClientOpts = [{log_level, debug},
216                  {verify, verify_peer},
217                  {cacertfile, CACertsFile},
218                  {server_name_indication, disable},
219                  {ocsp_stapling, true},
220                  {ocsp_nonce, true},
221                  {ocsp_responder_certs, [Der]}] ++ dtls_client_opt(GroupName),
222    Client = ssl_test_lib:start_client(erlang,
223                                       [{port, Port},
224                                        {options, ClientOpts}], Config),
225    ssl_test_lib:send(Server, Data),
226    Data = ssl_test_lib:check_active_receive(Client, Data),
227
228    ssl_test_lib:close(Server),
229    ssl_test_lib:close(Client).
230%%--------------------------------------------------------------------
231ocsp_stapling_revoked() ->
232    [{doc, "Verify OCSP stapling works with revoked certificate."}].
233ocsp_stapling_revoked(Config)
234  when is_list(Config) ->
235    PrivDir = proplists:get_value(priv_dir, Config),
236    CACertsFile = filename:join(PrivDir, "revoked/cacerts.pem"),
237
238    GroupName = proplists:get_value(group, Config),
239    ServerOpts = [{log_level, debug},
240                  {group, GroupName}],
241    {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
242
243    Server = ssl_test_lib:start_server(openssl_ocsp_revoked,
244                                       [{options, ServerOpts}], Config),
245    Port = ssl_test_lib:inet_port(Server),
246
247    ClientOpts = [{log_level, debug},
248                  {verify, verify_peer},
249                  {server_name_indication, disable},
250                  {cacertfile, CACertsFile},
251                  {ocsp_stapling, true},
252                  {ocsp_nonce, true}
253                 ] ++ dtls_client_opt(GroupName),
254
255    Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
256                                              {host, Hostname}, {from, self()},
257                                              {options, ClientOpts}]),
258
259    ssl_test_lib:check_client_alert(Client, certificate_revoked).
260
261%%--------------------------------------------------------------------
262ocsp_stapling_undetermined() ->
263    [{doc, "Verify OCSP stapling works with certificate with undetermined status."}].
264ocsp_stapling_undetermined(Config)
265  when is_list(Config) ->
266    PrivDir = proplists:get_value(priv_dir, Config),
267    CACertsFile = filename:join(PrivDir, "undetermined/cacerts.pem"),
268
269    GroupName = proplists:get_value(group, Config),
270    ServerOpts = [{log_level, debug},
271                  {group, GroupName}],
272    {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
273
274    Server = ssl_test_lib:start_server(openssl_ocsp_undetermined,
275                                       [{options, ServerOpts}], Config),
276    Port = ssl_test_lib:inet_port(Server),
277
278    ClientOpts = [{log_level, debug},
279                  {verify, verify_peer},
280                  {server_name_indication, disable},
281                  {cacertfile, CACertsFile},
282                  {ocsp_stapling, true},
283                  {ocsp_nonce, true}
284                 ] ++ dtls_client_opt(GroupName),
285
286    Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
287                                              {host, Hostname}, {from, self()},
288                                              {options, ClientOpts}]),
289
290    ssl_test_lib:check_client_alert(Client, bad_certificate).
291
292%%--------------------------------------------------------------------
293ocsp_stapling_no_staple() ->
294    [{doc, "Verify OCSP stapling works with a missing OCSP response."}].
295ocsp_stapling_no_staple(Config)
296  when is_list(Config) ->
297    PrivDir = proplists:get_value(priv_dir, Config),
298    CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
299
300    GroupName = proplists:get_value(group, Config),
301    ServerOpts = [{log_level, debug},
302                  {group, GroupName}],
303    {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
304
305    %% Start a server that will not include an OCSP response.
306    Server = ssl_test_lib:start_server(openssl,
307                                       [{options, ServerOpts}], Config),
308    Port = ssl_test_lib:inet_port(Server),
309
310    ClientOpts = [{log_level, debug},
311                  {verify, verify_peer},
312                  {server_name_indication, disable},
313                  {cacertfile, CACertsFile},
314                  {ocsp_stapling, true},
315                  {ocsp_nonce, true}
316                 ] ++ dtls_client_opt(GroupName),
317
318    Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
319                                              {host, Hostname}, {from, self()},
320                                              {options, ClientOpts}]),
321
322    ssl_test_lib:check_client_alert(Client, bad_certificate).
323
324%%--------------------------------------------------------------------
325%% Intrernal functions -----------------------------------------------
326%%--------------------------------------------------------------------
327start_ocsp_responder(ResponderPort, PrivDir) ->
328    Starter = self(),
329    Pid = erlang:spawn_link(
330            ?MODULE, ocsp_responder_init, [ResponderPort, PrivDir, Starter]),
331    receive
332        {started, Pid} ->
333            Pid;
334        {'EXIT', Pid, Reason} ->
335            throw({unable_to_start_ocsp_service, Reason})
336    end.
337
338ocsp_responder_init(ResponderPort, PrivDir, Starter) ->
339    Index = filename:join(PrivDir, "otpCA/index.txt"),
340    CACerts = filename:join(PrivDir, "b.server/cacerts.pem"),
341    Cert = filename:join(PrivDir, "b.server/cert.pem"),
342    Key = filename:join(PrivDir, "b.server/key.pem"),
343
344    Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
345            "-rkey", Key, "-port",  erlang:integer_to_list(ResponderPort)],
346    process_flag(trap_exit, true),
347    Port = ssl_test_lib:portable_open_port("openssl", Args),
348    ocsp_responder_loop(Port, {new, Starter}).
349
350ocsp_responder_loop(Port, {Status, Starter} = State) ->
351    receive
352	stop_ocsp_responder ->
353	    ct:log("Shut down OCSP responder!~n"),
354            ok = ssl_test_lib:close_port(Port);
355	{_Port, closed} ->
356	    ct:log("Port Closed~n"),
357	    ok;
358	{'EXIT', _Port, Reason} ->
359	    ct:log("Port Closed ~p~n",[Reason]),
360	    ok;
361	{Port, {data, _Msg}} when Status == new ->
362            Starter ! {started, self()},
363	    ocsp_responder_loop(Port, {started, undefined});
364        {Port, {data, Msg}} ->
365	    ct:pal("Responder Msg ~p~n",[Msg]),
366            ocsp_responder_loop(Port, State)
367    after 1000 ->
368            case Status of
369                new ->
370                    exit(no_ocsp_server);
371                _  ->
372                    ocsp_responder_loop(Port, State)
373            end
374    end.
375
376stop_ocsp_responder(Pid) ->
377    Pid ! stop_ocsp_responder.
378
379get_free_port() ->
380    {ok, Listen} = gen_tcp:listen(0, [{reuseaddr, true}]),
381    {ok, Port} = inet:port(Listen),
382    ok = gen_tcp:close(Listen),
383    Port.
384
385dtls_client_opt('dtlsv1.2') ->
386    [{protocol, dtls}];
387dtls_client_opt(_Other) ->
388    [].