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