]> git.ipfire.org Git - thirdparty/openssl.git/blob - test/recipes/70-test_sslrecords.t
Following the license change, modify the boilerplates in test/
[thirdparty/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 }