]> git.ipfire.org Git - thirdparty/openssl.git/blob - test/recipes/70-test_sslrecords.t
Update copyright year
[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 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)$/;
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 boundary 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 boundary 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 boundary 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 #Skip CCS and ServerHello
489 $i += 2;
490 ${$proxy->record_list}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
491 }
492
493 sub not_on_record_boundary
494 {
495 my $proxy = shift;
496 my $data;
497
498 #Find server's first flight
499 if ($proxy->flight != 1) {
500 return;
501 }
502
503 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
504 #Merge the ServerHello and EncryptedExtensions records into one
505 my $i;
506 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
507 next;
508 }
509 $data = ${$proxy->record_list}[$i]->data();
510 $data .= ${$proxy->record_list}[$i + 1]->decrypt_data();
511 ${$proxy->record_list}[$i]->data($data);
512 ${$proxy->record_list}[$i]->len(length $data);
513
514 #Delete the old EncryptedExtensions record
515 splice @{$proxy->record_list}, $i + 1, 1;
516 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
517 $data = ${$proxy->record_list}[-1]->decrypt_data;
518
519 #Add a KeyUpdate message onto the end of the Finished record
520 my $keyupdate = pack "C5",
521 0x18, # KeyUpdate
522 0x00, 0x00, 0x01, # Message length
523 0x00; # Update not requested
524
525 $data .= $keyupdate;
526
527 #Add content type and tag
528 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
529
530 #Update the record
531 ${$proxy->record_list}[-1]->data($data);
532 ${$proxy->record_list}[-1]->len(length $data);
533 } else {
534 #KeyUpdates must end on a record boundary
535
536 my $record = TLSProxy::Record->new(
537 1,
538 TLSProxy::Record::RT_APPLICATION_DATA,
539 TLSProxy::Record::VERS_TLS_1_0,
540 0,
541 0,
542 0,
543 0,
544 "",
545 ""
546 );
547
548 #Add two KeyUpdate messages into a single record
549 my $keyupdate = pack "C5",
550 0x18, # KeyUpdate
551 0x00, 0x00, 0x01, # Message length
552 0x00; # Update not requested
553
554 $data = $keyupdate.$keyupdate;
555
556 #Add content type and tag
557 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
558
559 $record->data($data);
560 $record->len(length $data);
561 push @{$proxy->record_list}, $record;
562 }
563 }