Home | History | Annotate | Line # | Download | only in recipes
      1 #! /usr/bin/env perl
      2 # Copyright 2015-2024 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_sslextension";
     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 TLS enabled"
     29     if alldisabled(available_protocols("tls"));
     30 
     31 my $no_below_tls13 = alldisabled(("tls1", "tls1_1", "tls1_2"))
     32                      || (!disabled("tls1_3") && disabled("tls1_2"));
     33 
     34 use constant {
     35     UNSOLICITED_SERVER_NAME => 0,
     36     UNSOLICITED_SERVER_NAME_TLS13 => 1,
     37     UNSOLICITED_SCT => 2,
     38     NONCOMPLIANT_SUPPORTED_GROUPS => 3
     39 };
     40 
     41 my $testtype;
     42 my $fatal_alert = 0;    # set by filter on fatal alert
     43 
     44 my $proxy = TLSProxy::Proxy->new(
     45     \&inject_duplicate_extension_clienthello,
     46     cmdstr(app(["openssl"]), display => 1),
     47     srctop_file("apps", "server.pem"),
     48     (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
     49 );
     50 
     51 
     52 sub extension_filter
     53 {
     54     my $proxy = shift;
     55 
     56     if ($proxy->flight == 1) {
     57         # Change the ServerRandom so that the downgrade sentinel doesn't cause
     58         # the connection to fail
     59         my $message = ${$proxy->message_list}[1];
     60         $message->random("\0"x32);
     61         $message->repack();
     62         return;
     63     }
     64 
     65     # We're only interested in the initial ClientHello
     66     if ($proxy->flight != 0) {
     67         return;
     68     }
     69 
     70     foreach my $message (@{$proxy->message_list}) {
     71         if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) {
     72             # Remove all extensions and set the extension len to zero
     73             $message->extension_data({});
     74             $message->extensions_len(0);
     75             # Extensions have been removed so make sure we don't try to use them
     76             $message->process_extensions();
     77 
     78             $message->repack();
     79         }
     80     }
     81 }
     82 
     83 sub inject_duplicate_extension
     84 {
     85   my ($proxy, $message_type) = @_;
     86 
     87     foreach my $message (@{$proxy->message_list}) {
     88         if ($message->mt == $message_type) {
     89           my %extensions = %{$message->extension_data};
     90             # Add a duplicate extension. We use cryptopro_bug since we never
     91             # normally write that one, and it is allowed as unsolicited in the
     92             # ServerHello
     93             $message->set_extension(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION, "");
     94             $message->dupext(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION);
     95             $message->repack();
     96         }
     97     }
     98 }
     99 
    100 sub inject_duplicate_extension_clienthello
    101 {
    102     my $proxy = shift;
    103 
    104     # We're only interested in the initial ClientHello
    105     if ($proxy->flight == 0) {
    106         inject_duplicate_extension($proxy, TLSProxy::Message::MT_CLIENT_HELLO);
    107         return;
    108     }
    109 
    110     my $last_record = @{$proxy->{record_list}}[-1];
    111     $fatal_alert = 1 if $last_record->is_fatal_alert(1);
    112 }
    113 
    114 sub inject_duplicate_extension_serverhello
    115 {
    116     my $proxy = shift;
    117 
    118     # We're only interested in the initial ServerHello
    119     if ($proxy->flight == 0) {
    120         return;
    121     } elsif ($proxy->flight == 1) {
    122         inject_duplicate_extension($proxy, TLSProxy::Message::MT_SERVER_HELLO);
    123         return;
    124     }
    125 
    126     my $last_record = @{$proxy->{record_list}}[-1];
    127     $fatal_alert = 1 if $last_record->is_fatal_alert(0);
    128 }
    129 
    130 sub inject_unsolicited_extension
    131 {
    132     my $proxy = shift;
    133     my $message;
    134     state $sent_unsolisited_extension;
    135 
    136     if ($proxy->flight == 0) {
    137         $sent_unsolisited_extension = 0;
    138         return;
    139     }
    140 
    141     # We're only interested in the initial ServerHello/EncryptedExtensions
    142     if ($proxy->flight != 1) {
    143         if ($sent_unsolisited_extension) {
    144             my $last_record = @{$proxy->record_list}[-1];
    145             $fatal_alert = 1 if $last_record->is_fatal_alert(0);
    146         }
    147         return;
    148     }
    149 
    150     if ($testtype == UNSOLICITED_SERVER_NAME_TLS13) {
    151         return if (!defined($message = ${$proxy->message_list}[2]));
    152         die "Expecting EE message ".($message->mt).","
    153                                    .${$proxy->message_list}[1]->mt.", "
    154                                    .${$proxy->message_list}[3]->mt
    155             if $message->mt != TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS;
    156     } else {
    157         $message = ${$proxy->message_list}[1];
    158     }
    159 
    160     my $ext = pack "C2",
    161         0x00, 0x00; #Extension length
    162 
    163     my $type;
    164     if ($testtype == UNSOLICITED_SERVER_NAME
    165             || $testtype == UNSOLICITED_SERVER_NAME_TLS13) {
    166         $type = TLSProxy::Message::EXT_SERVER_NAME;
    167     } elsif ($testtype == UNSOLICITED_SCT) {
    168         $type = TLSProxy::Message::EXT_SCT;
    169     } elsif ($testtype == NONCOMPLIANT_SUPPORTED_GROUPS) {
    170         $type = TLSProxy::Message::EXT_SUPPORTED_GROUPS;
    171     }
    172     $message->set_extension($type, $ext);
    173     $message->repack();
    174     $sent_unsolisited_extension = 1;
    175 }
    176 
    177 sub inject_cryptopro_extension
    178 {
    179     my $proxy = shift;
    180 
    181     # We're only interested in the initial ClientHello
    182     if ($proxy->flight != 0) {
    183         return;
    184     }
    185 
    186     my $message = ${$proxy->message_list}[0];
    187     $message->set_extension(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION, "");
    188     $message->repack();
    189 }
    190 
    191 # Test 1-2: Sending a duplicate extension should fail.
    192 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
    193 plan tests => 8;
    194 ok($fatal_alert, "Duplicate ClientHello extension");
    195 
    196 SKIP: {
    197     skip "TLS <= 1.2 disabled", 4 if $no_below_tls13;
    198 
    199     $fatal_alert = 0;
    200     $proxy->clear();
    201     $proxy->filter(\&inject_duplicate_extension_serverhello);
    202     $proxy->clientflags("-no_tls1_3");
    203     $proxy->start();
    204     ok($fatal_alert, "Duplicate ServerHello extension");
    205 
    206     #Test 3: Sending a zero length extension block should pass
    207     $proxy->clear();
    208     $proxy->filter(\&extension_filter);
    209     $proxy->cipherc("DEFAULT:\@SECLEVEL=0");
    210     $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
    211     $proxy->clientflags("-no_tls1_3");
    212     $proxy->start();
    213     ok(TLSProxy::Message->success, "Zero extension length test");
    214 
    215     #Test 4: Inject an unsolicited extension (<= TLSv1.2)
    216     $fatal_alert = 0;
    217     $proxy->clear();
    218     $proxy->filter(\&inject_unsolicited_extension);
    219     $testtype = UNSOLICITED_SERVER_NAME;
    220     $proxy->clientflags("-no_tls1_3 -noservername");
    221     $proxy->start();
    222     ok($fatal_alert, "Unsolicited server name extension");
    223 
    224     #Test 5: Send the cryptopro extension in a ClientHello. Normally this is an
    225     #        unsolicited extension only ever seen in the ServerHello. We should
    226     #        ignore it in a ClientHello
    227     $proxy->clear();
    228     $proxy->filter(\&inject_cryptopro_extension);
    229     $proxy->clientflags("-no_tls1_3");
    230     $proxy->start();
    231     ok(TLSProxy::Message->success(), "Cryptopro extension in ClientHello");
    232 }
    233 
    234 SKIP: {
    235     skip "TLS <= 1.2 disabled or EC disabled", 1
    236         if $no_below_tls13 || disabled("ec");
    237     #Test 6: Inject a noncompliant supported_groups extension (<= TLSv1.2)
    238     $proxy->clear();
    239     $proxy->filter(\&inject_unsolicited_extension);
    240     $testtype = NONCOMPLIANT_SUPPORTED_GROUPS;
    241     $proxy->clientflags("-no_tls1_3");
    242     $proxy->start();
    243     ok(TLSProxy::Message->success(), "Noncompliant supported_groups extension");
    244 }
    245 
    246 SKIP: {
    247     skip "TLS <= 1.2 or CT disabled", 1
    248         if $no_below_tls13 || disabled("ct");
    249     #Test 7: Same as above for the SCT extension which has special handling
    250     $fatal_alert = 0;
    251     $proxy->clear();
    252     $proxy->filter(\&inject_unsolicited_extension);
    253     $testtype = UNSOLICITED_SCT;
    254     $proxy->clientflags("-no_tls1_3");
    255     $proxy->start();
    256     ok($fatal_alert, "Unsolicited sct extension");
    257 }
    258 
    259 SKIP: {
    260     skip "TLS 1.3 disabled", 1
    261         if disabled("tls1_3") || (disabled("ec") && disabled("dh"));
    262     #Test 8: Inject an unsolicited extension (TLSv1.3)
    263     $fatal_alert = 0;
    264     $proxy->clear();
    265     $proxy->filter(\&inject_unsolicited_extension);
    266     $testtype = UNSOLICITED_SERVER_NAME_TLS13;
    267     $proxy->clientflags("-noservername");
    268     $proxy->start();
    269     ok($fatal_alert, "Unsolicited server name extension (TLSv1.3)");
    270 }
    271