• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env perl
2# Copyright 2016-2019 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
9use strict;
10use feature 'state';
11
12use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
13use OpenSSL::Test::Utils;
14use TLSProxy::Proxy;
15
16my $test_name = "test_sslrecords";
17setup($test_name);
18
19plan skip_all => "TLSProxy isn't usable on $^O"
20    if $^O =~ /^(VMS)$/;
21
22plan skip_all => "$test_name needs the dynamic engine feature enabled"
23    if disabled("engine") || disabled("dynamic-engine");
24
25plan skip_all => "$test_name needs the sock feature enabled"
26    if disabled("sock");
27
28plan skip_all => "$test_name needs TLSv1.2 enabled"
29    if disabled("tls1_2");
30
31$ENV{OPENSSL_ia32cap} = '~0x200000200000000';
32my $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
39my $boundary_test_type;
40my $fatal_alert = 0;    # set by filters at expected fatal alerts
41
42#Test 1: Injecting out of context empty records should fail
43my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
44my $inject_recs_num = 1;
45$proxy->serverflags("-tls1_2");
46$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
47plan tests => 20;
48ok($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();
55ok(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();
64ok($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();
74ok($fatal_alert, "Fragmented alert records test");
75
76#Run some SSLv2 ClientHello tests
77
78use 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
86my $sslv2testtype = TLSV1_2_IN_SSLV2;
87$proxy->clear();
88$proxy->filter(\&add_sslv2_filter);
89$proxy->serverflags("-tls1_2");
90$proxy->start();
91ok(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();
100ok(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();
109ok(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();
117ok(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();
125ok(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();
135ok($fatal_alert, "Unrecognised record type in TLS1.2");
136
137SKIP: {
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();
154ok($fatal_alert, "Changed record version in TLS1.2");
155
156#TLS1.3 specific tests
157SKIP: {
158    skip "TLSv1.3 disabled", 8 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        DATA_BETWEEN_KEY_UPDATE => 3,
186        NO_DATA_BETWEEN_KEY_UPDATE => 4,
187    };
188
189    #Test 16: Sending a ServerHello which doesn't end on a record boundary
190    #         should fail
191    $fatal_alert = 0;
192    $proxy->clear();
193    $boundary_test_type = DATA_AFTER_SERVER_HELLO;
194    $proxy->filter(\&not_on_record_boundary);
195    $proxy->start();
196    ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
197
198    #Test 17: Sending a Finished which doesn't end on a record boundary
199    #         should fail
200    $fatal_alert = 0;
201    $proxy->clear();
202    $boundary_test_type = DATA_AFTER_FINISHED;
203    $proxy->start();
204    ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
205
206    #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
207    #         should fail
208    $fatal_alert = 0;
209    $proxy->clear();
210    $boundary_test_type = DATA_AFTER_KEY_UPDATE;
211    $proxy->start();
212    ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
213
214    #Test 19: Sending application data in the middle of a fragmented KeyUpdate
215    #         should fail. Strictly speaking this is not a record boundary test
216    #         but we use the same filter.
217    $fatal_alert = 0;
218    $proxy->clear();
219    $boundary_test_type = DATA_BETWEEN_KEY_UPDATE;
220    $proxy->start();
221    ok($fatal_alert, "Data between KeyUpdate");
222
223    #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this
224    #         is not a record boundary test but we use the same filter.
225    $proxy->clear();
226    $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE;
227    $proxy->start();
228    ok(TLSProxy::Message->success(), "No data between KeyUpdate");
229 }
230
231
232sub add_empty_recs_filter
233{
234    my $proxy = shift;
235    my $records = $proxy->record_list;
236
237    # We're only interested in the initial ClientHello
238    if ($proxy->flight != 0) {
239        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
240        return;
241    }
242
243    for (my $i = 0; $i < $inject_recs_num; $i++) {
244        my $record = TLSProxy::Record->new(
245            0,
246            $content_type,
247            TLSProxy::Record::VERS_TLS_1_2,
248            0,
249            0,
250            0,
251            0,
252            "",
253            ""
254        );
255        push @{$records}, $record;
256    }
257}
258
259sub add_frag_alert_filter
260{
261    my $proxy = shift;
262    my $records = $proxy->record_list;
263    my $byte;
264
265    # We're only interested in the initial ClientHello
266    if ($proxy->flight != 0) {
267        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
268        return;
269    }
270
271    # Add a zero length fragment first
272    #my $record = TLSProxy::Record->new(
273    #    0,
274    #    TLSProxy::Record::RT_ALERT,
275    #    TLSProxy::Record::VERS_TLS_1_2,
276    #    0,
277    #    0,
278    #    0,
279    #    "",
280    #    ""
281    #);
282    #push @{$proxy->record_list}, $record;
283
284    # Now add the alert level (Fatal) as a separate record
285    $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
286    my $record = TLSProxy::Record->new(
287        0,
288        TLSProxy::Record::RT_ALERT,
289        TLSProxy::Record::VERS_TLS_1_2,
290        1,
291        0,
292        1,
293        1,
294        $byte,
295        $byte
296    );
297    push @{$records}, $record;
298
299    # And finally the description (Unexpected message) in a third record
300    $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
301    $record = TLSProxy::Record->new(
302        0,
303        TLSProxy::Record::RT_ALERT,
304        TLSProxy::Record::VERS_TLS_1_2,
305        1,
306        0,
307        1,
308        1,
309        $byte,
310        $byte
311    );
312    push @{$records}, $record;
313}
314
315sub add_sslv2_filter
316{
317    my $proxy = shift;
318    my $clienthello;
319    my $record;
320
321    # We're only interested in the initial ClientHello
322    if ($proxy->flight != 0) {
323        return;
324    }
325
326    # Ditch the real ClientHello - we're going to replace it with our own
327    shift @{$proxy->record_list};
328
329    if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
330        my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
331                               TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
332        my $alertlen = length $alert;
333        $record = TLSProxy::Record->new(
334            0,
335            TLSProxy::Record::RT_ALERT,
336            TLSProxy::Record::VERS_TLS_1_2,
337            $alertlen,
338            0,
339            $alertlen,
340            $alertlen,
341            $alert,
342            $alert
343        );
344
345        push @{$proxy->record_list}, $record;
346    }
347
348    if ($sslv2testtype == ALERT_BEFORE_SSLV2
349            || $sslv2testtype == TLSV1_2_IN_SSLV2
350            || $sslv2testtype == SSLV2_IN_SSLV2) {
351        # This is an SSLv2 format ClientHello
352        $clienthello =
353            pack "C44",
354            0x01, # ClientHello
355            0x03, 0x03, #TLSv1.2
356            0x00, 0x03, # Ciphersuites len
357            0x00, 0x00, # Session id len
358            0x00, 0x20, # Challenge len
359            0x00, 0x00, 0x2f, #AES128-SHA
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; # Challenge
363
364        if ($sslv2testtype == SSLV2_IN_SSLV2) {
365            # Set the version to "real" SSLv2
366            vec($clienthello, 1, 8) = 0x00;
367            vec($clienthello, 2, 8) = 0x02;
368        }
369
370        my $chlen = length $clienthello;
371
372        $record = TLSProxy::Record->new(
373            0,
374            TLSProxy::Record::RT_HANDSHAKE,
375            TLSProxy::Record::VERS_TLS_1_2,
376            $chlen,
377            1, #SSLv2
378            $chlen,
379            $chlen,
380            $clienthello,
381            $clienthello
382        );
383
384        push @{$proxy->record_list}, $record;
385    } else {
386        # For this test we're using a real TLS ClientHello
387        $clienthello =
388            pack "C49",
389            0x01, # ClientHello
390            0x00, 0x00, 0x2D, # Message length
391            0x03, 0x03, # TLSv1.2
392            0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
393            0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
394            0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
395            0x00, # Session id len
396            0x00, 0x04, # Ciphersuites len
397            0x00, 0x2f, # AES128-SHA
398            0x00, 0xff, # Empty reneg info SCSV
399            0x01, # Compression methods len
400            0x00, # Null compression
401            0x00, 0x00; # Extensions len
402
403        # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
404        # We deliberately split the second record prior to the Challenge/Random
405        # and set the first byte of the random to 1. This makes the second SSLv2
406        # record look like an SSLv2 ClientHello
407        my $frag1 = substr $clienthello, 0, 6;
408        my $frag2 = substr $clienthello, 6, 32;
409        my $frag3 = substr $clienthello, 38;
410
411        my $fraglen = length $frag1;
412        $record = TLSProxy::Record->new(
413            0,
414            TLSProxy::Record::RT_HANDSHAKE,
415            TLSProxy::Record::VERS_TLS_1_2,
416            $fraglen,
417            0,
418            $fraglen,
419            $fraglen,
420            $frag1,
421            $frag1
422        );
423        push @{$proxy->record_list}, $record;
424
425        $fraglen = length $frag2;
426        my $recvers;
427        if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
428            $recvers = 1;
429        } else {
430            $recvers = 0;
431        }
432        $record = TLSProxy::Record->new(
433            0,
434            TLSProxy::Record::RT_HANDSHAKE,
435            TLSProxy::Record::VERS_TLS_1_2,
436            $fraglen,
437            $recvers,
438            $fraglen,
439            $fraglen,
440            $frag2,
441            $frag2
442        );
443        push @{$proxy->record_list}, $record;
444
445        $fraglen = length $frag3;
446        $record = TLSProxy::Record->new(
447            0,
448            TLSProxy::Record::RT_HANDSHAKE,
449            TLSProxy::Record::VERS_TLS_1_2,
450            $fraglen,
451            0,
452            $fraglen,
453            $fraglen,
454            $frag3,
455            $frag3
456        );
457        push @{$proxy->record_list}, $record;
458    }
459
460}
461
462sub add_unknown_record_type
463{
464    my $proxy = shift;
465    my $records = $proxy->record_list;
466    state $added_record;
467
468    # We'll change a record after the initial version neg has taken place
469    if ($proxy->flight == 0) {
470        $added_record = 0;
471        return;
472    } elsif ($proxy->flight != 1 || $added_record) {
473        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
474        return;
475    }
476
477    my $record = TLSProxy::Record->new(
478        1,
479        TLSProxy::Record::RT_UNKNOWN,
480        @{$records}[-1]->version(),
481        1,
482        0,
483        1,
484        1,
485        "X",
486        "X"
487    );
488
489    #Find ServerHello record and insert after that
490    my $i;
491    for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
492        next;
493    }
494    $i++;
495
496    splice @{$proxy->record_list}, $i, 0, $record;
497    $added_record = 1;
498}
499
500sub change_version
501{
502    my $proxy = shift;
503    my $records = $proxy->record_list;
504
505    # We'll change a version after the initial version neg has taken place
506    if ($proxy->flight != 1) {
507        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
508        return;
509    }
510
511    if ($#{$records} > 1) {
512        # ... typically in ServerHelloDone
513        @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
514    }
515}
516
517sub change_outer_record_type
518{
519    my $proxy = shift;
520    my $records = $proxy->record_list;
521
522    # We'll change a record after the initial version neg has taken place
523    if ($proxy->flight != 1) {
524        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
525        return;
526    }
527
528    # Find CCS record and change record after that
529    my $i = 0;
530    foreach my $record (@{$records}) {
531        last if $record->content_type == TLSProxy::Record::RT_CCS;
532        $i++;
533    }
534    if (defined(${$records}[++$i])) {
535        ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
536    }
537}
538
539sub not_on_record_boundary
540{
541    my $proxy = shift;
542    my $records = $proxy->record_list;
543    my $data;
544
545    #Find server's first flight
546    if ($proxy->flight != 1) {
547        $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
548        return;
549    }
550
551    if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
552        #Merge the ServerHello and EncryptedExtensions records into one
553        my $i = 0;
554        foreach my $record (@{$records}) {
555            if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
556                $record->{sent} = 1;    # pretend it's sent already
557                last;
558            }
559            $i++;
560        }
561
562        if (defined(${$records}[$i+1])) {
563            $data = ${$records}[$i]->data();
564            $data .= ${$records}[$i+1]->decrypt_data();
565            ${$records}[$i+1]->data($data);
566            ${$records}[$i+1]->len(length $data);
567
568            #Delete the old ServerHello record
569            splice @{$records}, $i, 1;
570        }
571    } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
572        return if @{$proxy->{message_list}}[-1]->{mt}
573                  != TLSProxy::Message::MT_FINISHED;
574
575        my $last_record = @{$records}[-1];
576        $data = $last_record->decrypt_data;
577
578        #Add a KeyUpdate message onto the end of the Finished record
579        my $keyupdate = pack "C5",
580            0x18, # KeyUpdate
581            0x00, 0x00, 0x01, # Message length
582            0x00; # Update not requested
583
584        $data .= $keyupdate;
585
586        #Add content type and tag
587        $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
588
589        #Update the record
590        $last_record->data($data);
591        $last_record->len(length $data);
592    } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) {
593        return if @{$proxy->{message_list}}[-1]->{mt}
594                  != TLSProxy::Message::MT_FINISHED;
595
596        #KeyUpdates must end on a record boundary
597
598        my $record = TLSProxy::Record->new(
599            1,
600            TLSProxy::Record::RT_APPLICATION_DATA,
601            TLSProxy::Record::VERS_TLS_1_2,
602            0,
603            0,
604            0,
605            0,
606            "",
607            ""
608        );
609
610        #Add two KeyUpdate messages into a single record
611        my $keyupdate = pack "C5",
612            0x18, # KeyUpdate
613            0x00, 0x00, 0x01, # Message length
614            0x00; # Update not requested
615
616        $data = $keyupdate.$keyupdate;
617
618        #Add content type and tag
619        $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
620
621        $record->data($data);
622        $record->len(length $data);
623        push @{$records}, $record;
624    } else {
625        return if @{$proxy->{message_list}}[-1]->{mt}
626                  != TLSProxy::Message::MT_FINISHED;
627
628        my $record = TLSProxy::Record->new(
629            1,
630            TLSProxy::Record::RT_APPLICATION_DATA,
631            TLSProxy::Record::VERS_TLS_1_2,
632            0,
633            0,
634            0,
635            0,
636            "",
637            ""
638        );
639
640        #Add a partial KeyUpdate message into the record
641        $data = pack "C1",
642            0x18; # KeyUpdate message type. Omit the rest of the message header
643
644        #Add content type and tag
645        $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
646
647        $record->data($data);
648        $record->len(length $data);
649        push @{$records}, $record;
650
651        if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) {
652            #Now add an app data record
653            $record = TLSProxy::Record->new(
654                1,
655                TLSProxy::Record::RT_APPLICATION_DATA,
656                TLSProxy::Record::VERS_TLS_1_2,
657                0,
658                0,
659                0,
660                0,
661                "",
662                ""
663            );
664
665            #Add an empty app data record (just content type and tag)
666            $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16);
667
668            $record->data($data);
669            $record->len(length $data);
670            push @{$records}, $record;
671        }
672
673        #Now add the rest of the KeyUpdate message
674        $record = TLSProxy::Record->new(
675            1,
676            TLSProxy::Record::RT_APPLICATION_DATA,
677            TLSProxy::Record::VERS_TLS_1_2,
678            0,
679            0,
680            0,
681            0,
682            "",
683            ""
684        );
685
686        #Add the last 4 bytes of the KeyUpdate record
687        $data = pack "C4",
688            0x00, 0x00, 0x01, # Message length
689            0x00; # Update not requested
690
691        #Add content type and tag
692        $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
693
694        $record->data($data);
695        $record->len(length $data);
696        push @{$records}, $record;
697
698    }
699}
700