Add a test for the wrong version number in a record
[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->filter(\&add_unknown_record_type);
132 $proxy->start();
133 ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.2");
134
135 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
136 if (!disabled("tls1_1")) {
137     $proxy->clear();
138     $proxy->clientflags("-tls1_1");
139     $proxy->start();
140     ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
141 }
142
143 #Test 12: Sending a different record version in TLS1.2 should fail
144 $proxy->clear();
145 $proxy->clientflags("-tls1_2");
146 $proxy->filter(\&change_version);
147 $proxy->start();
148 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.2");
149
150 #Test 13: Sending a different record version in TLS1.3 should succeed
151 if (!disabled("tls1_3")) {
152     $proxy->clear();
153     $proxy->filter(\&change_version);
154     $proxy->start();
155     ok(TLSProxy::Message->success(), "Changed record version in TLS1.3");
156 }
157
158 sub add_empty_recs_filter
159 {
160     my $proxy = shift;
161
162     # We're only interested in the initial ClientHello
163     if ($proxy->flight != 0) {
164         return;
165     }
166
167     for (my $i = 0; $i < $inject_recs_num; $i++) {
168         my $record = TLSProxy::Record->new(
169             0,
170             $content_type,
171             TLSProxy::Record::VERS_TLS_1_2,
172             0,
173             0,
174             0,
175             0,
176             "",
177             ""
178         );
179
180         push @{$proxy->record_list}, $record;
181     }
182 }
183
184 sub add_frag_alert_filter
185 {
186     my $proxy = shift;
187     my $byte;
188
189     # We're only interested in the initial ClientHello
190     if ($proxy->flight != 0) {
191         return;
192     }
193
194     # Add a zero length fragment first
195     #my $record = TLSProxy::Record->new(
196     #    0,
197     #    TLSProxy::Record::RT_ALERT,
198     #    TLSProxy::Record::VERS_TLS_1_2,
199     #    0,
200     #    0,
201     #    0,
202     #    "",
203     #    ""
204     #);
205     #push @{$proxy->record_list}, $record;
206
207     # Now add the alert level (Fatal) as a separate record
208     $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
209     my $record = TLSProxy::Record->new(
210         0,
211         TLSProxy::Record::RT_ALERT,
212         TLSProxy::Record::VERS_TLS_1_2,
213         1,
214         0,
215         1,
216         1,
217         $byte,
218         $byte
219     );
220     push @{$proxy->record_list}, $record;
221
222     # And finally the description (Unexpected message) in a third record
223     $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
224     $record = TLSProxy::Record->new(
225         0,
226         TLSProxy::Record::RT_ALERT,
227         TLSProxy::Record::VERS_TLS_1_2,
228         1,
229         0,
230         1,
231         1,
232         $byte,
233         $byte
234     );
235     push @{$proxy->record_list}, $record;
236 }
237
238 sub add_sslv2_filter
239 {
240     my $proxy = shift;
241     my $clienthello;
242     my $record;
243
244     # We're only interested in the initial ClientHello
245     if ($proxy->flight != 0) {
246         return;
247     }
248
249     # Ditch the real ClientHello - we're going to replace it with our own
250     shift @{$proxy->record_list};
251
252     if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
253         my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
254                                TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
255         my $alertlen = length $alert;
256         $record = TLSProxy::Record->new(
257             0,
258             TLSProxy::Record::RT_ALERT,
259             TLSProxy::Record::VERS_TLS_1_2,
260             $alertlen,
261             0,
262             $alertlen,
263             $alertlen,
264             $alert,
265             $alert
266         );
267
268         push @{$proxy->record_list}, $record;
269     }
270
271     if ($sslv2testtype == ALERT_BEFORE_SSLV2
272             || $sslv2testtype == TLSV1_2_IN_SSLV2
273             || $sslv2testtype == SSLV2_IN_SSLV2) {
274         # This is an SSLv2 format ClientHello
275         $clienthello =
276             pack "C44",
277             0x01, # ClientHello
278             0x03, 0x03, #TLSv1.2
279             0x00, 0x03, # Ciphersuites len
280             0x00, 0x00, # Session id len
281             0x00, 0x20, # Challenge len
282             0x00, 0x00, 0x2f, #AES128-SHA
283             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
284             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
285             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
286
287         if ($sslv2testtype == SSLV2_IN_SSLV2) {
288             # Set the version to "real" SSLv2
289             vec($clienthello, 1, 8) = 0x00;
290             vec($clienthello, 2, 8) = 0x02;
291         }
292
293         my $chlen = length $clienthello;
294
295         $record = TLSProxy::Record->new(
296             0,
297             TLSProxy::Record::RT_HANDSHAKE,
298             TLSProxy::Record::VERS_TLS_1_2,
299             $chlen,
300             1, #SSLv2
301             $chlen,
302             $chlen,
303             $clienthello,
304             $clienthello
305         );
306
307         push @{$proxy->record_list}, $record;
308     } else {
309         # For this test we're using a real TLS ClientHello
310         $clienthello =
311             pack "C49",
312             0x01, # ClientHello
313             0x00, 0x00, 0x2D, # Message length
314             0x03, 0x03, # TLSv1.2
315             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
316             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
317             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
318             0x00, # Session id len
319             0x00, 0x04, # Ciphersuites len
320             0x00, 0x2f, # AES128-SHA
321             0x00, 0xff, # Empty reneg info SCSV
322             0x01, # Compression methods len
323             0x00, # Null compression
324             0x00, 0x00; # Extensions len
325
326         # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
327         # We deliberately split the second record prior to the Challenge/Random
328         # and set the first byte of the random to 1. This makes the second SSLv2
329         # record look like an SSLv2 ClientHello
330         my $frag1 = substr $clienthello, 0, 6;
331         my $frag2 = substr $clienthello, 6, 32;
332         my $frag3 = substr $clienthello, 38;
333
334         my $fraglen = length $frag1;
335         $record = TLSProxy::Record->new(
336             0,
337             TLSProxy::Record::RT_HANDSHAKE,
338             TLSProxy::Record::VERS_TLS_1_2,
339             $fraglen,
340             0,
341             $fraglen,
342             $fraglen,
343             $frag1,
344             $frag1
345         );
346         push @{$proxy->record_list}, $record;
347
348         $fraglen = length $frag2;
349         my $recvers;
350         if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
351             $recvers = 1;
352         } else {
353             $recvers = 0;
354         }
355         $record = TLSProxy::Record->new(
356             0,
357             TLSProxy::Record::RT_HANDSHAKE,
358             TLSProxy::Record::VERS_TLS_1_2,
359             $fraglen,
360             $recvers,
361             $fraglen,
362             $fraglen,
363             $frag2,
364             $frag2
365         );
366         push @{$proxy->record_list}, $record;
367
368         $fraglen = length $frag3;
369         $record = TLSProxy::Record->new(
370             0,
371             TLSProxy::Record::RT_HANDSHAKE,
372             TLSProxy::Record::VERS_TLS_1_2,
373             $fraglen,
374             0,
375             $fraglen,
376             $fraglen,
377             $frag3,
378             $frag3
379         );
380         push @{$proxy->record_list}, $record;
381     }
382
383 }
384
385 sub add_unknown_record_type
386 {
387     my $proxy = shift;
388
389     # We'll change a record after the initial version neg has taken place
390     if ($proxy->flight != 2) {
391         return;
392     }
393
394     my $lastrec = ${$proxy->record_list}[-1];
395     my $record = TLSProxy::Record->new(
396         2,
397         TLSProxy::Record::RT_UNKNOWN,
398         $lastrec->version(),
399         1,
400         0,
401         1,
402         1,
403         "X",
404         "X"
405     );
406
407     unshift @{$proxy->record_list}, $record;
408 }
409
410 sub change_version
411 {
412     my $proxy = shift;
413
414     # We'll change a version after the initial version neg has taken place
415     if ($proxy->flight != 2) {
416         return;
417     }
418
419     (${$proxy->record_list}[-1])->version(TLSProxy::Record::VERS_TLS_1_1);
420 }