e6f7a36c05d9bdce16203976ce5501b2511b7be8
[openssl.git] / test / recipes / 70-test_sslrecords.t
1 #! /usr/bin/env perl
2 # Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
3 #
4 # Licensed under the OpenSSL license (the "License").  You may not use
5 # this file except in compliance with the License.  You can obtain a copy
6 # in the file LICENSE in the source distribution or at
7 # https://www.openssl.org/source/license.html
8
9 use strict;
10 use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
11 use OpenSSL::Test::Utils;
12 use TLSProxy::Proxy;
13
14 my $test_name = "test_sslrecords";
15 setup($test_name);
16
17 plan skip_all => "TLSProxy isn't usable on $^O"
18     if $^O =~ /^(VMS|MSWin32)$/;
19
20 plan skip_all => "$test_name needs the dynamic engine feature enabled"
21     if disabled("engine") || disabled("dynamic-engine");
22
23 plan skip_all => "$test_name needs the sock feature enabled"
24     if disabled("sock");
25
26 plan skip_all => "$test_name needs TLSv1.2 enabled"
27     if disabled("tls1_2");
28
29 $ENV{OPENSSL_ia32cap} = '~0x200000200000000';
30 my $proxy = TLSProxy::Proxy->new(
31     \&add_empty_recs_filter,
32     cmdstr(app(["openssl"]), display => 1),
33     srctop_file("apps", "server.pem"),
34     (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
35 );
36
37 #Test 1: Injecting out of context empty records should fail
38 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
39 my $inject_recs_num = 1;
40 $proxy->serverflags("-tls1_2");
41 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
42 my $num_tests = 11;
43 if (!disabled("tls1_1")) {
44     $num_tests++;
45 }
46 if (!disabled("tls1_3")) {
47     $num_tests++;
48 }
49 plan tests => $num_tests;
50 ok(TLSProxy::Message->fail(), "Out of context empty records test");
51
52 #Test 2: Injecting in context empty records should succeed
53 $proxy->clear();
54 $content_type = TLSProxy::Record::RT_HANDSHAKE;
55 $proxy->serverflags("-tls1_2");
56 $proxy->start();
57 ok(TLSProxy::Message->success(), "In context empty records test");
58
59 #Test 3: Injecting too many in context empty records should fail
60 $proxy->clear();
61 #We allow 32 consecutive in context empty records
62 $inject_recs_num = 33;
63 $proxy->serverflags("-tls1_2");
64 $proxy->start();
65 ok(TLSProxy::Message->fail(), "Too many in context empty records test");
66
67 #Test 4: Injecting a fragmented fatal alert should fail. We actually expect no
68 #        alerts to be sent from either side because *we* injected the fatal
69 #        alert, i.e. this will look like a disorderly close
70 $proxy->clear();
71 $proxy->filter(\&add_frag_alert_filter);
72 $proxy->serverflags("-tls1_2");
73 $proxy->start();
74 ok(!TLSProxy::Message->end(), "Fragmented alert records test");
75
76 #Run some SSLv2 ClientHello tests
77
78 use constant {
79     TLSV1_2_IN_SSLV2 => 0,
80     SSLV2_IN_SSLV2 => 1,
81     FRAGMENTED_IN_TLSV1_2 => 2,
82     FRAGMENTED_IN_SSLV2 => 3,
83     ALERT_BEFORE_SSLV2 => 4
84 };
85 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
86 my $sslv2testtype = TLSV1_2_IN_SSLV2;
87 $proxy->clear();
88 $proxy->filter(\&add_sslv2_filter);
89 $proxy->serverflags("-tls1_2");
90 $proxy->start();
91 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
92
93 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
94 #        support this so it should fail. We actually treat it as an unknown
95 #        protocol so we don't even send an alert in this case.
96 $sslv2testtype = SSLV2_IN_SSLV2;
97 $proxy->clear();
98 $proxy->serverflags("-tls1_2");
99 $proxy->start();
100 ok(!TLSProxy::Message->end(), "SSLv2 in SSLv2 ClientHello test");
101
102 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
103 #        at all, but it gives us confidence that Test 8 fails for the right
104 #        reasons
105 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
106 $proxy->clear();
107 $proxy->serverflags("-tls1_2");
108 $proxy->start();
109 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
110
111 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
112 #        record; and another TLS1.2 record. This isn't allowed so should fail
113 $sslv2testtype = FRAGMENTED_IN_SSLV2;
114 $proxy->clear();
115 $proxy->serverflags("-tls1_2");
116 $proxy->start();
117 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
118
119 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
120 #        fail because an SSLv2 ClientHello must be the first record.
121 $sslv2testtype = ALERT_BEFORE_SSLV2;
122 $proxy->clear();
123 $proxy->serverflags("-tls1_2");
124 $proxy->start();
125 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
126
127 #Unregcognised record type tests
128
129 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
130 $proxy->clear();
131 $proxy->serverflags("-tls1_2");
132 $proxy->filter(\&add_unknown_record_type);
133 $proxy->start();
134 ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.2");
135
136 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
137 if (!disabled("tls1_1")) {
138     $proxy->clear();
139     $proxy->clientflags("-tls1_1");
140     $proxy->start();
141     ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
142 }
143
144 #Test 12: Sending a different record version in TLS1.2 should fail
145 $proxy->clear();
146 $proxy->clientflags("-tls1_2");
147 $proxy->filter(\&change_version);
148 $proxy->start();
149 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.2");
150
151 #Test 13: Sending a different record version in TLS1.3 should succeed
152 if (!disabled("tls1_3")) {
153     $proxy->clear();
154     $proxy->filter(\&change_version);
155     $proxy->start();
156     ok(TLSProxy::Message->success(), "Changed record version in TLS1.3");
157 }
158
159 sub add_empty_recs_filter
160 {
161     my $proxy = shift;
162
163     # We're only interested in the initial ClientHello
164     if ($proxy->flight != 0) {
165         return;
166     }
167
168     for (my $i = 0; $i < $inject_recs_num; $i++) {
169         my $record = TLSProxy::Record->new(
170             0,
171             $content_type,
172             TLSProxy::Record::VERS_TLS_1_2,
173             0,
174             0,
175             0,
176             0,
177             "",
178             ""
179         );
180
181         push @{$proxy->record_list}, $record;
182     }
183 }
184
185 sub add_frag_alert_filter
186 {
187     my $proxy = shift;
188     my $byte;
189
190     # We're only interested in the initial ClientHello
191     if ($proxy->flight != 0) {
192         return;
193     }
194
195     # Add a zero length fragment first
196     #my $record = TLSProxy::Record->new(
197     #    0,
198     #    TLSProxy::Record::RT_ALERT,
199     #    TLSProxy::Record::VERS_TLS_1_2,
200     #    0,
201     #    0,
202     #    0,
203     #    "",
204     #    ""
205     #);
206     #push @{$proxy->record_list}, $record;
207
208     # Now add the alert level (Fatal) as a separate record
209     $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
210     my $record = TLSProxy::Record->new(
211         0,
212         TLSProxy::Record::RT_ALERT,
213         TLSProxy::Record::VERS_TLS_1_2,
214         1,
215         0,
216         1,
217         1,
218         $byte,
219         $byte
220     );
221     push @{$proxy->record_list}, $record;
222
223     # And finally the description (Unexpected message) in a third record
224     $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
225     $record = TLSProxy::Record->new(
226         0,
227         TLSProxy::Record::RT_ALERT,
228         TLSProxy::Record::VERS_TLS_1_2,
229         1,
230         0,
231         1,
232         1,
233         $byte,
234         $byte
235     );
236     push @{$proxy->record_list}, $record;
237 }
238
239 sub add_sslv2_filter
240 {
241     my $proxy = shift;
242     my $clienthello;
243     my $record;
244
245     # We're only interested in the initial ClientHello
246     if ($proxy->flight != 0) {
247         return;
248     }
249
250     # Ditch the real ClientHello - we're going to replace it with our own
251     shift @{$proxy->record_list};
252
253     if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
254         my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
255                                TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
256         my $alertlen = length $alert;
257         $record = TLSProxy::Record->new(
258             0,
259             TLSProxy::Record::RT_ALERT,
260             TLSProxy::Record::VERS_TLS_1_2,
261             $alertlen,
262             0,
263             $alertlen,
264             $alertlen,
265             $alert,
266             $alert
267         );
268
269         push @{$proxy->record_list}, $record;
270     }
271
272     if ($sslv2testtype == ALERT_BEFORE_SSLV2
273             || $sslv2testtype == TLSV1_2_IN_SSLV2
274             || $sslv2testtype == SSLV2_IN_SSLV2) {
275         # This is an SSLv2 format ClientHello
276         $clienthello =
277             pack "C44",
278             0x01, # ClientHello
279             0x03, 0x03, #TLSv1.2
280             0x00, 0x03, # Ciphersuites len
281             0x00, 0x00, # Session id len
282             0x00, 0x20, # Challenge len
283             0x00, 0x00, 0x2f, #AES128-SHA
284             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
285             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
286             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
287
288         if ($sslv2testtype == SSLV2_IN_SSLV2) {
289             # Set the version to "real" SSLv2
290             vec($clienthello, 1, 8) = 0x00;
291             vec($clienthello, 2, 8) = 0x02;
292         }
293
294         my $chlen = length $clienthello;
295
296         $record = TLSProxy::Record->new(
297             0,
298             TLSProxy::Record::RT_HANDSHAKE,
299             TLSProxy::Record::VERS_TLS_1_2,
300             $chlen,
301             1, #SSLv2
302             $chlen,
303             $chlen,
304             $clienthello,
305             $clienthello
306         );
307
308         push @{$proxy->record_list}, $record;
309     } else {
310         # For this test we're using a real TLS ClientHello
311         $clienthello =
312             pack "C49",
313             0x01, # ClientHello
314             0x00, 0x00, 0x2D, # Message length
315             0x03, 0x03, # TLSv1.2
316             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
317             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
318             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
319             0x00, # Session id len
320             0x00, 0x04, # Ciphersuites len
321             0x00, 0x2f, # AES128-SHA
322             0x00, 0xff, # Empty reneg info SCSV
323             0x01, # Compression methods len
324             0x00, # Null compression
325             0x00, 0x00; # Extensions len
326
327         # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
328         # We deliberately split the second record prior to the Challenge/Random
329         # and set the first byte of the random to 1. This makes the second SSLv2
330         # record look like an SSLv2 ClientHello
331         my $frag1 = substr $clienthello, 0, 6;
332         my $frag2 = substr $clienthello, 6, 32;
333         my $frag3 = substr $clienthello, 38;
334
335         my $fraglen = length $frag1;
336         $record = TLSProxy::Record->new(
337             0,
338             TLSProxy::Record::RT_HANDSHAKE,
339             TLSProxy::Record::VERS_TLS_1_2,
340             $fraglen,
341             0,
342             $fraglen,
343             $fraglen,
344             $frag1,
345             $frag1
346         );
347         push @{$proxy->record_list}, $record;
348
349         $fraglen = length $frag2;
350         my $recvers;
351         if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
352             $recvers = 1;
353         } else {
354             $recvers = 0;
355         }
356         $record = TLSProxy::Record->new(
357             0,
358             TLSProxy::Record::RT_HANDSHAKE,
359             TLSProxy::Record::VERS_TLS_1_2,
360             $fraglen,
361             $recvers,
362             $fraglen,
363             $fraglen,
364             $frag2,
365             $frag2
366         );
367         push @{$proxy->record_list}, $record;
368
369         $fraglen = length $frag3;
370         $record = TLSProxy::Record->new(
371             0,
372             TLSProxy::Record::RT_HANDSHAKE,
373             TLSProxy::Record::VERS_TLS_1_2,
374             $fraglen,
375             0,
376             $fraglen,
377             $fraglen,
378             $frag3,
379             $frag3
380         );
381         push @{$proxy->record_list}, $record;
382     }
383
384 }
385
386 sub add_unknown_record_type
387 {
388     my $proxy = shift;
389
390     # We'll change a record after the initial version neg has taken place
391     if ($proxy->flight != 2) {
392         return;
393     }
394
395     my $lastrec = ${$proxy->record_list}[-1];
396     my $record = TLSProxy::Record->new(
397         2,
398         TLSProxy::Record::RT_UNKNOWN,
399         $lastrec->version(),
400         1,
401         0,
402         1,
403         1,
404         "X",
405         "X"
406     );
407
408     unshift @{$proxy->record_list}, $record;
409 }
410
411 sub change_version
412 {
413     my $proxy = shift;
414
415     # We'll change a version after the initial version neg has taken place
416     if ($proxy->flight != 2) {
417         return;
418     }
419
420     (${$proxy->record_list}[-1])->version(TLSProxy::Record::VERS_TLS_1_1);
421 }