Don't allow fragmented alerts
[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 my $boundary_test_type;
38
39 #Test 1: Injecting out of context empty records should fail
40 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
41 my $inject_recs_num = 1;
42 $proxy->serverflags("-tls1_2");
43 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
44 plan tests => 18;
45 ok(TLSProxy::Message->fail(), "Out of context empty records test");
46
47 #Test 2: Injecting in context empty records should succeed
48 $proxy->clear();
49 $content_type = TLSProxy::Record::RT_HANDSHAKE;
50 $proxy->serverflags("-tls1_2");
51 $proxy->start();
52 ok(TLSProxy::Message->success(), "In context empty records test");
53
54 #Test 3: Injecting too many in context empty records should fail
55 $proxy->clear();
56 #We allow 32 consecutive in context empty records
57 $inject_recs_num = 33;
58 $proxy->serverflags("-tls1_2");
59 $proxy->start();
60 ok(TLSProxy::Message->fail(), "Too many in context empty records test");
61
62 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
63 #        send back an alert of its own because it cannot handle fragmented
64 #        alerts
65 $proxy->clear();
66 $proxy->filter(\&add_frag_alert_filter);
67 $proxy->serverflags("-tls1_2");
68 $proxy->start();
69 ok(TLSProxy::Message->fail(), "Fragmented alert records test");
70
71 #Run some SSLv2 ClientHello tests
72
73 use constant {
74     TLSV1_2_IN_SSLV2 => 0,
75     SSLV2_IN_SSLV2 => 1,
76     FRAGMENTED_IN_TLSV1_2 => 2,
77     FRAGMENTED_IN_SSLV2 => 3,
78     ALERT_BEFORE_SSLV2 => 4
79 };
80 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
81 my $sslv2testtype = TLSV1_2_IN_SSLV2;
82 $proxy->clear();
83 $proxy->filter(\&add_sslv2_filter);
84 $proxy->serverflags("-tls1_2");
85 $proxy->start();
86 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
87
88 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
89 #        support this so it should fail. We actually treat it as an unknown
90 #        protocol so we don't even send an alert in this case.
91 $sslv2testtype = SSLV2_IN_SSLV2;
92 $proxy->clear();
93 $proxy->serverflags("-tls1_2");
94 $proxy->start();
95 ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test");
96
97 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
98 #        at all, but it gives us confidence that Test 8 fails for the right
99 #        reasons
100 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
101 $proxy->clear();
102 $proxy->serverflags("-tls1_2");
103 $proxy->start();
104 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
105
106 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
107 #        record; and another TLS1.2 record. This isn't allowed so should fail
108 $sslv2testtype = FRAGMENTED_IN_SSLV2;
109 $proxy->clear();
110 $proxy->serverflags("-tls1_2");
111 $proxy->start();
112 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
113
114 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
115 #        fail because an SSLv2 ClientHello must be the first record.
116 $sslv2testtype = ALERT_BEFORE_SSLV2;
117 $proxy->clear();
118 $proxy->serverflags("-tls1_2");
119 $proxy->start();
120 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
121
122 #Unrecognised record type tests
123
124 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
125 $proxy->clear();
126 $proxy->serverflags("-tls1_2");
127 $proxy->filter(\&add_unknown_record_type);
128 $proxy->start();
129 ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.2");
130
131 SKIP: {
132     skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
133
134     #Test 11: Sending an unrecognised record type in TLS1.1 should fail
135     $proxy->clear();
136     $proxy->clientflags("-tls1_1");
137     $proxy->start();
138     ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
139 }
140
141 #Test 12: Sending a different record version in TLS1.2 should fail
142 $proxy->clear();
143 $proxy->clientflags("-tls1_2");
144 $proxy->filter(\&change_version);
145 $proxy->start();
146 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.2");
147
148 #TLS1.3 specific tests
149 SKIP: {
150     skip "TLSv1.3 disabled", 6 if disabled("tls1_3");
151
152     #Test 13: Sending a different record version in TLS1.3 should succeed
153     $proxy->clear();
154     $proxy->filter(\&change_version);
155     $proxy->start();
156     ok(TLSProxy::Message->success(), "Changed record version in TLS1.3");
157
158     #Test 14: Sending an unrecognised record type in TLS1.3 should fail
159     $proxy->clear();
160     $proxy->filter(\&add_unknown_record_type);
161     $proxy->start();
162     ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.3");
163
164     #Test 15: Sending an outer record type other than app data once encrypted
165     #should fail
166     $proxy->clear();
167     $proxy->filter(\&change_outer_record_type);
168     $proxy->start();
169     ok(TLSProxy::Message->fail(), "Wrong outer record type in TLS1.3");
170
171     use constant {
172         DATA_AFTER_SERVER_HELLO => 0,
173         DATA_AFTER_FINISHED => 1,
174         DATA_AFTER_KEY_UPDATE => 2
175     };
176
177     #Test 16: Sending a ServerHello which doesn't end on a record boundary
178     #         should fail
179     $proxy->clear();
180     $boundary_test_type = DATA_AFTER_SERVER_HELLO;
181     $proxy->filter(\&not_on_record_boundary);
182     $proxy->start();
183     ok(TLSProxy::Message->fail(), "Record not on bounday in TLS1.3 (ServerHello)");
184
185     #Test 17: Sending a Finished which doesn't end on a record boundary
186     #         should fail
187     $proxy->clear();
188     $boundary_test_type = DATA_AFTER_FINISHED;
189     $proxy->filter(\&not_on_record_boundary);
190     $proxy->start();
191     ok(TLSProxy::Message->fail(), "Record not on bounday in TLS1.3 (Finished)");
192
193     #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
194     #         should fail
195     $proxy->clear();
196     $boundary_test_type = DATA_AFTER_KEY_UPDATE;
197     $proxy->filter(\&not_on_record_boundary);
198     $proxy->start();
199     ok(TLSProxy::Message->fail(), "Record not on bounday in TLS1.3 (KeyUpdate)");
200  }
201
202
203 sub add_empty_recs_filter
204 {
205     my $proxy = shift;
206
207     # We're only interested in the initial ClientHello
208     if ($proxy->flight != 0) {
209         return;
210     }
211
212     for (my $i = 0; $i < $inject_recs_num; $i++) {
213         my $record = TLSProxy::Record->new(
214             0,
215             $content_type,
216             TLSProxy::Record::VERS_TLS_1_2,
217             0,
218             0,
219             0,
220             0,
221             "",
222             ""
223         );
224
225         push @{$proxy->record_list}, $record;
226     }
227 }
228
229 sub add_frag_alert_filter
230 {
231     my $proxy = shift;
232     my $byte;
233
234     # We're only interested in the initial ClientHello
235     if ($proxy->flight != 0) {
236         return;
237     }
238
239     # Add a zero length fragment first
240     #my $record = TLSProxy::Record->new(
241     #    0,
242     #    TLSProxy::Record::RT_ALERT,
243     #    TLSProxy::Record::VERS_TLS_1_2,
244     #    0,
245     #    0,
246     #    0,
247     #    "",
248     #    ""
249     #);
250     #push @{$proxy->record_list}, $record;
251
252     # Now add the alert level (Fatal) as a separate record
253     $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
254     my $record = TLSProxy::Record->new(
255         0,
256         TLSProxy::Record::RT_ALERT,
257         TLSProxy::Record::VERS_TLS_1_2,
258         1,
259         0,
260         1,
261         1,
262         $byte,
263         $byte
264     );
265     push @{$proxy->record_list}, $record;
266
267     # And finally the description (Unexpected message) in a third record
268     $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
269     $record = TLSProxy::Record->new(
270         0,
271         TLSProxy::Record::RT_ALERT,
272         TLSProxy::Record::VERS_TLS_1_2,
273         1,
274         0,
275         1,
276         1,
277         $byte,
278         $byte
279     );
280     push @{$proxy->record_list}, $record;
281 }
282
283 sub add_sslv2_filter
284 {
285     my $proxy = shift;
286     my $clienthello;
287     my $record;
288
289     # We're only interested in the initial ClientHello
290     if ($proxy->flight != 0) {
291         return;
292     }
293
294     # Ditch the real ClientHello - we're going to replace it with our own
295     shift @{$proxy->record_list};
296
297     if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
298         my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
299                                TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
300         my $alertlen = length $alert;
301         $record = TLSProxy::Record->new(
302             0,
303             TLSProxy::Record::RT_ALERT,
304             TLSProxy::Record::VERS_TLS_1_2,
305             $alertlen,
306             0,
307             $alertlen,
308             $alertlen,
309             $alert,
310             $alert
311         );
312
313         push @{$proxy->record_list}, $record;
314     }
315
316     if ($sslv2testtype == ALERT_BEFORE_SSLV2
317             || $sslv2testtype == TLSV1_2_IN_SSLV2
318             || $sslv2testtype == SSLV2_IN_SSLV2) {
319         # This is an SSLv2 format ClientHello
320         $clienthello =
321             pack "C44",
322             0x01, # ClientHello
323             0x03, 0x03, #TLSv1.2
324             0x00, 0x03, # Ciphersuites len
325             0x00, 0x00, # Session id len
326             0x00, 0x20, # Challenge len
327             0x00, 0x00, 0x2f, #AES128-SHA
328             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
329             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
330             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
331
332         if ($sslv2testtype == SSLV2_IN_SSLV2) {
333             # Set the version to "real" SSLv2
334             vec($clienthello, 1, 8) = 0x00;
335             vec($clienthello, 2, 8) = 0x02;
336         }
337
338         my $chlen = length $clienthello;
339
340         $record = TLSProxy::Record->new(
341             0,
342             TLSProxy::Record::RT_HANDSHAKE,
343             TLSProxy::Record::VERS_TLS_1_2,
344             $chlen,
345             1, #SSLv2
346             $chlen,
347             $chlen,
348             $clienthello,
349             $clienthello
350         );
351
352         push @{$proxy->record_list}, $record;
353     } else {
354         # For this test we're using a real TLS ClientHello
355         $clienthello =
356             pack "C49",
357             0x01, # ClientHello
358             0x00, 0x00, 0x2D, # Message length
359             0x03, 0x03, # TLSv1.2
360             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
361             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
362             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
363             0x00, # Session id len
364             0x00, 0x04, # Ciphersuites len
365             0x00, 0x2f, # AES128-SHA
366             0x00, 0xff, # Empty reneg info SCSV
367             0x01, # Compression methods len
368             0x00, # Null compression
369             0x00, 0x00; # Extensions len
370
371         # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
372         # We deliberately split the second record prior to the Challenge/Random
373         # and set the first byte of the random to 1. This makes the second SSLv2
374         # record look like an SSLv2 ClientHello
375         my $frag1 = substr $clienthello, 0, 6;
376         my $frag2 = substr $clienthello, 6, 32;
377         my $frag3 = substr $clienthello, 38;
378
379         my $fraglen = length $frag1;
380         $record = TLSProxy::Record->new(
381             0,
382             TLSProxy::Record::RT_HANDSHAKE,
383             TLSProxy::Record::VERS_TLS_1_2,
384             $fraglen,
385             0,
386             $fraglen,
387             $fraglen,
388             $frag1,
389             $frag1
390         );
391         push @{$proxy->record_list}, $record;
392
393         $fraglen = length $frag2;
394         my $recvers;
395         if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
396             $recvers = 1;
397         } else {
398             $recvers = 0;
399         }
400         $record = TLSProxy::Record->new(
401             0,
402             TLSProxy::Record::RT_HANDSHAKE,
403             TLSProxy::Record::VERS_TLS_1_2,
404             $fraglen,
405             $recvers,
406             $fraglen,
407             $fraglen,
408             $frag2,
409             $frag2
410         );
411         push @{$proxy->record_list}, $record;
412
413         $fraglen = length $frag3;
414         $record = TLSProxy::Record->new(
415             0,
416             TLSProxy::Record::RT_HANDSHAKE,
417             TLSProxy::Record::VERS_TLS_1_2,
418             $fraglen,
419             0,
420             $fraglen,
421             $fraglen,
422             $frag3,
423             $frag3
424         );
425         push @{$proxy->record_list}, $record;
426     }
427
428 }
429
430 sub add_unknown_record_type
431 {
432     my $proxy = shift;
433
434     # We'll change a record after the initial version neg has taken place
435     if ($proxy->flight != 1) {
436         return;
437     }
438
439     my $lastrec = ${$proxy->record_list}[-1];
440     my $record = TLSProxy::Record->new(
441         1,
442         TLSProxy::Record::RT_UNKNOWN,
443         $lastrec->version(),
444         1,
445         0,
446         1,
447         1,
448         "X",
449         "X"
450     );
451
452     #Find ServerHello record and insert after that
453     my $i;
454     for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
455         next;
456     }
457     $i++;
458
459     splice @{$proxy->record_list}, $i, 0, $record;
460 }
461
462 sub change_version
463 {
464     my $proxy = shift;
465
466     # We'll change a version after the initial version neg has taken place
467     if ($proxy->flight != 2) {
468         return;
469     }
470
471     (${$proxy->record_list}[-1])->version(TLSProxy::Record::VERS_TLS_1_1);
472 }
473
474 sub change_outer_record_type
475 {
476     my $proxy = shift;
477
478     # We'll change a record after the initial version neg has taken place
479     if ($proxy->flight != 1) {
480         return;
481     }
482
483     #Find ServerHello record and change record after that
484     my $i;
485     for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
486         next;
487     }
488     $i++;
489     ${$proxy->record_list}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
490 }
491
492 sub not_on_record_boundary
493 {
494     my $proxy = shift;
495     my $data;
496
497     #Find server's first flight
498     if ($proxy->flight != 1) {
499         return;
500     }
501
502     if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
503         #Merge the ServerHello and EncryptedExtensions records into one
504         my $i;
505         for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
506             next;
507         }
508         $data = ${$proxy->record_list}[$i]->data();
509         $data .= ${$proxy->record_list}[$i + 1]->decrypt_data();
510         ${$proxy->record_list}[$i]->data($data);
511         ${$proxy->record_list}[$i]->len(length $data);
512
513         #Delete the old EncryptedExtensions record
514         splice @{$proxy->record_list}, $i + 1, 1;
515     } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
516         $data = ${$proxy->record_list}[-1]->decrypt_data;
517
518         #Add a KeyUpdate message onto the end of the Finished record
519         my $keyupdate = pack "C5",
520             0x18, # KeyUpdate
521             0x00, 0x00, 0x01, # Message length
522             0x00; # Update not requested
523
524         $data .= $keyupdate;
525
526         #Add content type and tag
527         $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
528
529         #Update the record
530         ${$proxy->record_list}[-1]->data($data);
531         ${$proxy->record_list}[-1]->len(length $data);
532     } else {
533         #KeyUpdates must end on a record boundary
534
535         my $record = TLSProxy::Record->new(
536             1,
537             TLSProxy::Record::RT_APPLICATION_DATA,
538             TLSProxy::Record::VERS_TLS_1_0,
539             0,
540             0,
541             0,
542             0,
543             "",
544             ""
545         );
546
547         #Add two KeyUpdate messages into a single record
548         my $keyupdate = pack "C5",
549             0x18, # KeyUpdate
550             0x00, 0x00, 0x01, # Message length
551             0x00; # Update not requested
552
553         $data = $keyupdate.$keyupdate;
554
555         #Add content type and tag
556         $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
557
558         $record->data($data);
559         $record->len(length $data);
560         push @{$proxy->record_list}, $record;
561     }
562 }