0e38308fbd1d8c2fcd17ad5286b08caf22e95ce5
[openssl.git] / test / recipes / 70-test_sslrecords.t
1 #! /usr/bin/env perl
2 # Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
3 #
4 # Licensed under the Apache License 2.0 (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 feature 'state';
11
12 use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
13 use OpenSSL::Test::Utils;
14 use TLSProxy::Proxy;
15
16 my $test_name = "test_sslrecords";
17 setup($test_name);
18
19 plan skip_all => "TLSProxy isn't usable on $^O"
20     if $^O =~ /^(VMS)$/;
21
22 plan skip_all => "$test_name needs the dynamic engine feature enabled"
23     if disabled("engine") || disabled("dynamic-engine");
24
25 plan skip_all => "$test_name needs the sock feature enabled"
26     if disabled("sock");
27
28 plan skip_all => "$test_name needs TLSv1.2 enabled"
29     if disabled("tls1_2");
30
31 $ENV{OPENSSL_ia32cap} = '~0x200000200000000';
32 my $proxy = TLSProxy::Proxy->new(
33     \&add_empty_recs_filter,
34     cmdstr(app(["openssl"]), display => 1),
35     srctop_file("apps", "server.pem"),
36     (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
37 );
38
39 my $boundary_test_type;
40 my $fatal_alert = 0;    # set by filters at expected fatal alerts
41
42 #Test 1: Injecting out of context empty records should fail
43 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
44 my $inject_recs_num = 1;
45 $proxy->serverflags("-tls1_2");
46 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
47 plan tests => 18;
48 ok($fatal_alert, "Out of context empty records test");
49
50 #Test 2: Injecting in context empty records should succeed
51 $proxy->clear();
52 $content_type = TLSProxy::Record::RT_HANDSHAKE;
53 $proxy->serverflags("-tls1_2");
54 $proxy->start();
55 ok(TLSProxy::Message->success(), "In context empty records test");
56
57 #Test 3: Injecting too many in context empty records should fail
58 $fatal_alert = 0;
59 $proxy->clear();
60 #We allow 32 consecutive in context empty records
61 $inject_recs_num = 33;
62 $proxy->serverflags("-tls1_2");
63 $proxy->start();
64 ok($fatal_alert, "Too many in context empty records test");
65
66 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
67 #        send back an alert of its own because it cannot handle fragmented
68 #        alerts
69 $fatal_alert = 0;
70 $proxy->clear();
71 $proxy->filter(\&add_frag_alert_filter);
72 $proxy->serverflags("-tls1_2");
73 $proxy->start();
74 ok($fatal_alert, "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 #Unrecognised record type tests
128
129 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
130 $fatal_alert = 0;
131 $proxy->clear();
132 $proxy->serverflags("-tls1_2");
133 $proxy->filter(\&add_unknown_record_type);
134 $proxy->start();
135 ok($fatal_alert, "Unrecognised record type in TLS1.2");
136
137 SKIP: {
138     skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
139
140     #Test 11: Sending an unrecognised record type in TLS1.1 should fail
141     $fatal_alert = 0;
142     $proxy->clear();
143     $proxy->clientflags("-tls1_1");
144     $proxy->start();
145     ok($fatal_alert, "Unrecognised record type in TLS1.1");
146 }
147
148 #Test 12: Sending a different record version in TLS1.2 should fail
149 $fatal_alert = 0;
150 $proxy->clear();
151 $proxy->clientflags("-tls1_2");
152 $proxy->filter(\&change_version);
153 $proxy->start();
154 ok($fatal_alert, "Changed record version in TLS1.2");
155
156 #TLS1.3 specific tests
157 SKIP: {
158     skip "TLSv1.3 disabled", 6 if disabled("tls1_3");
159
160     #Test 13: Sending a different record version in TLS1.3 should fail
161     $proxy->clear();
162     $proxy->filter(\&change_version);
163     $proxy->start();
164     ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3");
165
166     #Test 14: Sending an unrecognised record type in TLS1.3 should fail
167     $fatal_alert = 0;
168     $proxy->clear();
169     $proxy->filter(\&add_unknown_record_type);
170     $proxy->start();
171     ok($fatal_alert, "Unrecognised record type in TLS1.3");
172
173     #Test 15: Sending an outer record type other than app data once encrypted
174     #should fail
175     $fatal_alert = 0;
176     $proxy->clear();
177     $proxy->filter(\&change_outer_record_type);
178     $proxy->start();
179     ok($fatal_alert, "Wrong outer record type in TLS1.3");
180
181     use constant {
182         DATA_AFTER_SERVER_HELLO => 0,
183         DATA_AFTER_FINISHED => 1,
184         DATA_AFTER_KEY_UPDATE => 2
185     };
186
187     #Test 16: Sending a ServerHello which doesn't end on a record boundary
188     #         should fail
189     $fatal_alert = 0;
190     $proxy->clear();
191     $boundary_test_type = DATA_AFTER_SERVER_HELLO;
192     $proxy->filter(\&not_on_record_boundary);
193     $proxy->start();
194     ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
195
196     #Test 17: Sending a Finished which doesn't end on a record boundary
197     #         should fail
198     $fatal_alert = 0;
199     $proxy->clear();
200     $boundary_test_type = DATA_AFTER_FINISHED;
201     $proxy->filter(\&not_on_record_boundary);
202     $proxy->start();
203     ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
204
205     #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
206     #         should fail
207     $fatal_alert = 0;
208     $proxy->clear();
209     $boundary_test_type = DATA_AFTER_KEY_UPDATE;
210     $proxy->filter(\&not_on_record_boundary);
211     $proxy->start();
212     ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
213  }
214
215
216 sub add_empty_recs_filter
217 {
218     my $proxy = shift;
219     my $records = $proxy->record_list;
220
221     # We're only interested in the initial ClientHello
222     if ($proxy->flight != 0) {
223         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
224         return;
225     }
226
227     for (my $i = 0; $i < $inject_recs_num; $i++) {
228         my $record = TLSProxy::Record->new(
229             0,
230             $content_type,
231             TLSProxy::Record::VERS_TLS_1_2,
232             0,
233             0,
234             0,
235             0,
236             "",
237             ""
238         );
239         push @{$records}, $record;
240     }
241 }
242
243 sub add_frag_alert_filter
244 {
245     my $proxy = shift;
246     my $records = $proxy->record_list;
247     my $byte;
248
249     # We're only interested in the initial ClientHello
250     if ($proxy->flight != 0) {
251         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
252         return;
253     }
254
255     # Add a zero length fragment first
256     #my $record = TLSProxy::Record->new(
257     #    0,
258     #    TLSProxy::Record::RT_ALERT,
259     #    TLSProxy::Record::VERS_TLS_1_2,
260     #    0,
261     #    0,
262     #    0,
263     #    "",
264     #    ""
265     #);
266     #push @{$proxy->record_list}, $record;
267
268     # Now add the alert level (Fatal) as a separate record
269     $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
270     my $record = TLSProxy::Record->new(
271         0,
272         TLSProxy::Record::RT_ALERT,
273         TLSProxy::Record::VERS_TLS_1_2,
274         1,
275         0,
276         1,
277         1,
278         $byte,
279         $byte
280     );
281     push @{$records}, $record;
282
283     # And finally the description (Unexpected message) in a third record
284     $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
285     $record = TLSProxy::Record->new(
286         0,
287         TLSProxy::Record::RT_ALERT,
288         TLSProxy::Record::VERS_TLS_1_2,
289         1,
290         0,
291         1,
292         1,
293         $byte,
294         $byte
295     );
296     push @{$records}, $record;
297 }
298
299 sub add_sslv2_filter
300 {
301     my $proxy = shift;
302     my $clienthello;
303     my $record;
304
305     # We're only interested in the initial ClientHello
306     if ($proxy->flight != 0) {
307         return;
308     }
309
310     # Ditch the real ClientHello - we're going to replace it with our own
311     shift @{$proxy->record_list};
312
313     if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
314         my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
315                                TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
316         my $alertlen = length $alert;
317         $record = TLSProxy::Record->new(
318             0,
319             TLSProxy::Record::RT_ALERT,
320             TLSProxy::Record::VERS_TLS_1_2,
321             $alertlen,
322             0,
323             $alertlen,
324             $alertlen,
325             $alert,
326             $alert
327         );
328
329         push @{$proxy->record_list}, $record;
330     }
331
332     if ($sslv2testtype == ALERT_BEFORE_SSLV2
333             || $sslv2testtype == TLSV1_2_IN_SSLV2
334             || $sslv2testtype == SSLV2_IN_SSLV2) {
335         # This is an SSLv2 format ClientHello
336         $clienthello =
337             pack "C44",
338             0x01, # ClientHello
339             0x03, 0x03, #TLSv1.2
340             0x00, 0x03, # Ciphersuites len
341             0x00, 0x00, # Session id len
342             0x00, 0x20, # Challenge len
343             0x00, 0x00, 0x2f, #AES128-SHA
344             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
345             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
346             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
347
348         if ($sslv2testtype == SSLV2_IN_SSLV2) {
349             # Set the version to "real" SSLv2
350             vec($clienthello, 1, 8) = 0x00;
351             vec($clienthello, 2, 8) = 0x02;
352         }
353
354         my $chlen = length $clienthello;
355
356         $record = TLSProxy::Record->new(
357             0,
358             TLSProxy::Record::RT_HANDSHAKE,
359             TLSProxy::Record::VERS_TLS_1_2,
360             $chlen,
361             1, #SSLv2
362             $chlen,
363             $chlen,
364             $clienthello,
365             $clienthello
366         );
367
368         push @{$proxy->record_list}, $record;
369     } else {
370         # For this test we're using a real TLS ClientHello
371         $clienthello =
372             pack "C49",
373             0x01, # ClientHello
374             0x00, 0x00, 0x2D, # Message length
375             0x03, 0x03, # TLSv1.2
376             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
377             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
378             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
379             0x00, # Session id len
380             0x00, 0x04, # Ciphersuites len
381             0x00, 0x2f, # AES128-SHA
382             0x00, 0xff, # Empty reneg info SCSV
383             0x01, # Compression methods len
384             0x00, # Null compression
385             0x00, 0x00; # Extensions len
386
387         # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
388         # We deliberately split the second record prior to the Challenge/Random
389         # and set the first byte of the random to 1. This makes the second SSLv2
390         # record look like an SSLv2 ClientHello
391         my $frag1 = substr $clienthello, 0, 6;
392         my $frag2 = substr $clienthello, 6, 32;
393         my $frag3 = substr $clienthello, 38;
394
395         my $fraglen = length $frag1;
396         $record = TLSProxy::Record->new(
397             0,
398             TLSProxy::Record::RT_HANDSHAKE,
399             TLSProxy::Record::VERS_TLS_1_2,
400             $fraglen,
401             0,
402             $fraglen,
403             $fraglen,
404             $frag1,
405             $frag1
406         );
407         push @{$proxy->record_list}, $record;
408
409         $fraglen = length $frag2;
410         my $recvers;
411         if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
412             $recvers = 1;
413         } else {
414             $recvers = 0;
415         }
416         $record = TLSProxy::Record->new(
417             0,
418             TLSProxy::Record::RT_HANDSHAKE,
419             TLSProxy::Record::VERS_TLS_1_2,
420             $fraglen,
421             $recvers,
422             $fraglen,
423             $fraglen,
424             $frag2,
425             $frag2
426         );
427         push @{$proxy->record_list}, $record;
428
429         $fraglen = length $frag3;
430         $record = TLSProxy::Record->new(
431             0,
432             TLSProxy::Record::RT_HANDSHAKE,
433             TLSProxy::Record::VERS_TLS_1_2,
434             $fraglen,
435             0,
436             $fraglen,
437             $fraglen,
438             $frag3,
439             $frag3
440         );
441         push @{$proxy->record_list}, $record;
442     }
443
444 }
445
446 sub add_unknown_record_type
447 {
448     my $proxy = shift;
449     my $records = $proxy->record_list;
450     state $added_record;
451
452     # We'll change a record after the initial version neg has taken place
453     if ($proxy->flight == 0) {
454         $added_record = 0;
455         return;
456     } elsif ($proxy->flight != 1 || $added_record) {
457         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
458         return;
459     }
460
461     my $record = TLSProxy::Record->new(
462         1,
463         TLSProxy::Record::RT_UNKNOWN,
464         @{$records}[-1]->version(),
465         1,
466         0,
467         1,
468         1,
469         "X",
470         "X"
471     );
472
473     #Find ServerHello record and insert after that
474     my $i;
475     for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
476         next;
477     }
478     $i++;
479
480     splice @{$proxy->record_list}, $i, 0, $record;
481     $added_record = 1;
482 }
483
484 sub change_version
485 {
486     my $proxy = shift;
487     my $records = $proxy->record_list;
488
489     # We'll change a version after the initial version neg has taken place
490     if ($proxy->flight != 1) {
491         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
492         return;
493     }
494
495     if ($#{$records} > 1) {
496         # ... typically in ServerHelloDone
497         @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
498     }
499 }
500
501 sub change_outer_record_type
502 {
503     my $proxy = shift;
504     my $records = $proxy->record_list;
505
506     # We'll change a record after the initial version neg has taken place
507     if ($proxy->flight != 1) {
508         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
509         return;
510     }
511
512     # Find CCS record and change record after that
513     my $i = 0;
514     foreach my $record (@{$records}) {
515         last if $record->content_type == TLSProxy::Record::RT_CCS;
516         $i++;
517     }
518     if (defined(${$records}[++$i])) {
519         ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
520     }
521 }
522
523 sub not_on_record_boundary
524 {
525     my $proxy = shift;
526     my $records = $proxy->record_list;
527     my $data;
528
529     #Find server's first flight
530     if ($proxy->flight != 1) {
531         $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
532         return;
533     }
534
535     if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
536         #Merge the ServerHello and EncryptedExtensions records into one
537         my $i = 0;
538         foreach my $record (@{$records}) {
539             if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
540                 $record->{sent} = 1;    # pretend it's sent already
541                 last;
542             }
543             $i++;
544         }
545
546         if (defined(${$records}[$i+1])) {
547             $data = ${$records}[$i]->data();
548             $data .= ${$records}[$i+1]->decrypt_data();
549             ${$records}[$i+1]->data($data);
550             ${$records}[$i+1]->len(length $data);
551
552             #Delete the old ServerHello record
553             splice @{$records}, $i, 1;
554         }
555     } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
556         return if @{$proxy->{message_list}}[-1]->{mt}
557                   != TLSProxy::Message::MT_FINISHED;
558
559         my $last_record = @{$records}[-1];
560         $data = $last_record->decrypt_data;
561
562         #Add a KeyUpdate message onto the end of the Finished record
563         my $keyupdate = pack "C5",
564             0x18, # KeyUpdate
565             0x00, 0x00, 0x01, # Message length
566             0x00; # Update not requested
567
568         $data .= $keyupdate;
569
570         #Add content type and tag
571         $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
572
573         #Update the record
574         $last_record->data($data);
575         $last_record->len(length $data);
576     } else {
577         return if @{$proxy->{message_list}}[-1]->{mt}
578                   != TLSProxy::Message::MT_FINISHED;
579
580         #KeyUpdates must end on a record boundary
581
582         my $record = TLSProxy::Record->new(
583             1,
584             TLSProxy::Record::RT_APPLICATION_DATA,
585             TLSProxy::Record::VERS_TLS_1_2,
586             0,
587             0,
588             0,
589             0,
590             "",
591             ""
592         );
593
594         #Add two KeyUpdate messages into a single record
595         my $keyupdate = pack "C5",
596             0x18, # KeyUpdate
597             0x00, 0x00, 0x01, # Message length
598             0x00; # Update not requested
599
600         $data = $keyupdate.$keyupdate;
601
602         #Add content type and tag
603         $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
604
605         $record->data($data);
606         $record->len(length $data);
607         push @{$records}, $record;
608     }
609 }