Home | History | Annotate | Line # | Download | only in ssl-tests
      1 # -*- mode: perl; -*-
      2 # Copyright 2016-2022 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 
     10 ## Test version negotiation
     11 
     12 package ssltests;
     13 
     14 use strict;
     15 use warnings;
     16 
     17 use List::Util qw/max min/;
     18 
     19 use OpenSSL::Test;
     20 use OpenSSL::Test::Utils qw/anydisabled alldisabled disabled/;
     21 setup("no_test_here");
     22 
     23 my @tls_protocols = ("SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3");
     24 # undef stands for "no limit".
     25 my @min_tls_protocols = (undef, "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3");
     26 my @max_tls_protocols = ("SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", undef);
     27 
     28 my @is_tls_disabled = anydisabled("ssl3", "tls1", "tls1_1", "tls1_2", "tls1_3");
     29 
     30 my $min_tls_enabled; my $max_tls_enabled;
     31 
     32 # Protocol configuration works in cascades, i.e.,
     33 # $no_tls1_1 disables TLSv1.1 and below.
     34 #
     35 # $min_enabled and $max_enabled will be correct if there is at least one
     36 # protocol enabled.
     37 foreach my $i (0..$#tls_protocols) {
     38     if (!$is_tls_disabled[$i]) {
     39         $min_tls_enabled = $i;
     40         last;
     41     }
     42 }
     43 
     44 foreach my $i (0..$#tls_protocols) {
     45     if (!$is_tls_disabled[$i]) {
     46         $max_tls_enabled = $i;
     47     }
     48 }
     49 
     50 my @dtls_protocols = ("DTLSv1", "DTLSv1.2");
     51 # undef stands for "no limit".
     52 my @min_dtls_protocols = (undef, "DTLSv1", "DTLSv1.2");
     53 my @max_dtls_protocols = ("DTLSv1", "DTLSv1.2", undef);
     54 
     55 my @is_dtls_disabled = anydisabled("dtls1", "dtls1_2");
     56 
     57 my $min_dtls_enabled; my $max_dtls_enabled;
     58 
     59 # $min_enabled and $max_enabled will be correct if there is at least one
     60 # protocol enabled.
     61 foreach my $i (0..$#dtls_protocols) {
     62     if (!$is_dtls_disabled[$i]) {
     63         $min_dtls_enabled = $i;
     64         last;
     65     }
     66 }
     67 
     68 foreach my $i (0..$#dtls_protocols) {
     69     if (!$is_dtls_disabled[$i]) {
     70         $max_dtls_enabled = $i;
     71     }
     72 }
     73 
     74 sub no_tests {
     75     my ($dtls) = @_;
     76     return $dtls ? alldisabled("dtls1", "dtls1_2") :
     77       alldisabled("ssl3", "tls1", "tls1_1", "tls1_2", "tls1_3");
     78 }
     79 
     80 sub generate_version_tests {
     81     my ($method) = @_;
     82 
     83     my $dtls = $method eq "DTLS";
     84     # Don't write the redundant "Method = TLS" into the configuration.
     85     undef $method if !$dtls;
     86 
     87     my @protocols = $dtls ? @dtls_protocols : @tls_protocols;
     88     my @min_protocols = $dtls ? @min_dtls_protocols : @min_tls_protocols;
     89     my @max_protocols = $dtls ? @max_dtls_protocols : @max_tls_protocols;
     90     my $min_enabled  = $dtls ? $min_dtls_enabled : $min_tls_enabled;
     91     my $max_enabled  = $dtls ? $max_dtls_enabled : $max_tls_enabled;
     92 
     93     if (no_tests($dtls)) {
     94         return;
     95     }
     96 
     97     my @tests = ();
     98 
     99     for (my $sctp = 0; $sctp < ($dtls && !disabled("sctp") ? 2 : 1); $sctp++) {
    100         foreach my $c_min (0..$#min_protocols) {
    101             my $c_max_min = $c_min == 0 ? 0 : $c_min - 1;
    102             foreach my $c_max ($c_max_min..$#max_protocols) {
    103                 foreach my $s_min (0..$#min_protocols) {
    104                     my $s_max_min = $s_min == 0 ? 0 : $s_min - 1;
    105                     foreach my $s_max ($s_max_min..$#max_protocols) {
    106                         my ($result, $protocol) =
    107                             expected_result($c_min, $c_max, $s_min, $s_max,
    108                                             $min_enabled, $max_enabled,
    109                                             \@protocols);
    110                         push @tests, {
    111                             "name" => "version-negotiation",
    112                             "client" => {
    113                                 "MinProtocol" => $min_protocols[$c_min],
    114                                 "MaxProtocol" => $max_protocols[$c_max],
    115                             },
    116                             "server" => {
    117                                 "MinProtocol" => $min_protocols[$s_min],
    118                                 "MaxProtocol" => $max_protocols[$s_max],
    119                             },
    120                             "test" => {
    121                                 "ExpectedResult" => $result,
    122                                 "ExpectedProtocol" => $protocol,
    123                                 "Method" => $method,
    124                             }
    125                         };
    126                         $tests[-1]{"test"}{"UseSCTP"} = "Yes" if $sctp;
    127                     }
    128                 }
    129             }
    130         }
    131     }
    132     return @tests if disabled("tls1_3") || disabled("tls1_2") || $dtls;
    133 
    134     #Add some version/ciphersuite sanity check tests
    135     push @tests, {
    136         "name" => "ciphersuite-sanity-check-client",
    137         "client" => {
    138             #Offering only <=TLSv1.2 ciphersuites with TLSv1.3 should fail
    139             "CipherString" => "AES128-SHA",
    140             "Ciphersuites" => "",
    141         },
    142         "server" => {
    143             "MaxProtocol" => "TLSv1.2"
    144         },
    145         "test" => {
    146             "ExpectedResult" => "ClientFail",
    147         }
    148     };
    149     push @tests, {
    150         "name" => "ciphersuite-sanity-check-server",
    151         "client" => {
    152             "CipherString" => "AES128-SHA",
    153             "MaxProtocol" => "TLSv1.2"
    154         },
    155         "server" => {
    156             #Allowing only <=TLSv1.2 ciphersuites with TLSv1.3 should fail
    157             "CipherString" => "AES128-SHA",
    158             "Ciphersuites" => "",
    159         },
    160         "test" => {
    161             "ExpectedResult" => "ServerFail",
    162         }
    163     };
    164 
    165     return @tests;
    166 }
    167 
    168 sub generate_resumption_tests {
    169     my ($method) = @_;
    170 
    171     my $dtls = $method eq "DTLS";
    172     # Don't write the redundant "Method = TLS" into the configuration.
    173     undef $method if !$dtls;
    174 
    175     my @protocols = $dtls ? @dtls_protocols : @tls_protocols;
    176     my $min_enabled  = $dtls ? $min_dtls_enabled : $min_tls_enabled;
    177     my $max_enabled = $dtls ? $max_dtls_enabled : $max_tls_enabled;
    178 
    179     if (no_tests($dtls)) {
    180         return;
    181     }
    182 
    183     my @server_tests = ();
    184     my @client_tests = ();
    185 
    186     # Obtain the first session against a fixed-version server/client.
    187     foreach my $original_protocol($min_enabled..$max_enabled) {
    188         # Upgrade or downgrade the server/client max version support and test
    189         # that it upgrades, downgrades or resumes the session as well.
    190         foreach my $resume_protocol($min_enabled..$max_enabled) {
    191             my $resumption_expected;
    192             # We should only resume on exact version match.
    193             if ($original_protocol eq $resume_protocol) {
    194                 $resumption_expected = "Yes";
    195             } else {
    196                 $resumption_expected = "No";
    197             }
    198 
    199             for (my $sctp = 0; $sctp < ($dtls && !disabled("sctp") ? 2 : 1);
    200                  $sctp++) {
    201                 foreach my $ticket ("SessionTicket", "-SessionTicket") {
    202                     # Client is flexible, server upgrades/downgrades.
    203                     push @server_tests, {
    204                         "name" => "resumption",
    205                         "client" => { },
    206                         "server" => {
    207                             "MinProtocol" => $protocols[$original_protocol],
    208                             "MaxProtocol" => $protocols[$original_protocol],
    209                             "Options" => $ticket,
    210                         },
    211                         "resume_server" => {
    212                             "MaxProtocol" => $protocols[$resume_protocol],
    213                             "Options" => $ticket,
    214                         },
    215                         "test" => {
    216                             "ExpectedProtocol" => $protocols[$resume_protocol],
    217                             "Method" => $method,
    218                             "HandshakeMode" => "Resume",
    219                             "ResumptionExpected" => $resumption_expected,
    220                         }
    221                     };
    222                     $server_tests[-1]{"test"}{"UseSCTP"} = "Yes" if $sctp;
    223                     # Server is flexible, client upgrades/downgrades.
    224                     push @client_tests, {
    225                         "name" => "resumption",
    226                         "client" => {
    227                             "MinProtocol" => $protocols[$original_protocol],
    228                             "MaxProtocol" => $protocols[$original_protocol],
    229                         },
    230                         "server" => {
    231                             "Options" => $ticket,
    232                         },
    233                         "resume_client" => {
    234                             "MaxProtocol" => $protocols[$resume_protocol],
    235                         },
    236                         "test" => {
    237                             "ExpectedProtocol" => $protocols[$resume_protocol],
    238                             "Method" => $method,
    239                             "HandshakeMode" => "Resume",
    240                             "ResumptionExpected" => $resumption_expected,
    241                         }
    242                     };
    243                     $client_tests[-1]{"test"}{"UseSCTP"} = "Yes" if $sctp;
    244                 }
    245             }
    246         }
    247     }
    248 
    249     if (!disabled("tls1_3") && !$dtls) {
    250         push @client_tests, {
    251             "name" => "resumption-with-hrr",
    252             "client" => {
    253             },
    254             "server" => {
    255                 "Curves" => "P-256"
    256             },
    257             "resume_client" => {
    258             },
    259             "test" => {
    260                 "ExpectedProtocol" => "TLSv1.3",
    261                 "Method" => "TLS",
    262                 "HandshakeMode" => "Resume",
    263                 "ResumptionExpected" => "Yes",
    264             }
    265         };
    266     }
    267 
    268     push @client_tests, {
    269         "name" => "resumption-when-mfl-ext-is-missing",
    270         "server" => {
    271         },
    272         "client" => {
    273             "extra" => {
    274                 "MaxFragmentLenExt" => 512,
    275             },
    276         },
    277         "resume_client" => {
    278         },
    279         "test" => {
    280             "Method" => $method,
    281             "HandshakeMode" => "Resume",
    282             "ResumptionExpected" => "No",
    283             "ExpectedResult" => "ServerFail",
    284         }
    285     };
    286 
    287     push @client_tests, {
    288         "name" => "resumption-when-mfl-ext-is-different",
    289         "server" => {
    290         },
    291         "client" => {
    292             "extra" => {
    293                 "MaxFragmentLenExt" => 512,
    294             },
    295         },
    296         "resume_client" => {
    297             "extra" => {
    298                 "MaxFragmentLenExt" => 1024,
    299             },
    300         },
    301         "test" => {
    302             "Method" => $method,
    303             "HandshakeMode" => "Resume",
    304             "ResumptionExpected" => "No",
    305             "ExpectedResult" => "ServerFail",
    306         }
    307     };
    308 
    309     push @client_tests, {
    310         "name" => "resumption-when-mfl-ext-is-correct",
    311         "server" => {
    312         },
    313         "client" => {
    314             "extra" => {
    315                 "MaxFragmentLenExt" => 512,
    316             },
    317         },
    318         "resume_client" => {
    319             "extra" => {
    320                 "MaxFragmentLenExt" => 512,
    321             },
    322         },
    323         "test" => {
    324             "Method" => $method,
    325             "HandshakeMode" => "Resume",
    326             "ResumptionExpected" => "Yes",
    327             "ExpectedResult" => "Success",
    328         }
    329     };
    330 
    331     return (@server_tests, @client_tests);
    332 }
    333 
    334 sub expected_result {
    335     my ($c_min, $c_max, $s_min, $s_max, $min_enabled, $max_enabled,
    336         $protocols) = @_;
    337 
    338     # Adjust for "undef" (no limit).
    339     $c_min = $c_min == 0 ? 0 : $c_min - 1;
    340     $c_max = $c_max == scalar @$protocols ? $c_max - 1 : $c_max;
    341     $s_min = $s_min == 0 ? 0 : $s_min - 1;
    342     $s_max = $s_max == scalar @$protocols ? $s_max - 1 : $s_max;
    343 
    344     # We now have at least one protocol enabled, so $min_enabled and
    345     # $max_enabled are well-defined.
    346     $c_min = max $c_min, $min_enabled;
    347     $s_min = max $s_min, $min_enabled;
    348     $c_max = min $c_max, $max_enabled;
    349     $s_max = min $s_max, $max_enabled;
    350 
    351     if ($c_min > $c_max) {
    352         # Client should fail to even send a hello.
    353         return ("ClientFail", undef);
    354     } elsif ($s_min > $s_max) {
    355         # Server has no protocols, should always fail.
    356         return ("ServerFail", undef);
    357     } elsif ($s_min > $c_max) {
    358         # Server doesn't support the client range.
    359         return ("ServerFail", undef);
    360     } elsif ($c_min > $s_max) {
    361         my @prots = @$protocols;
    362         if ($prots[$c_max] eq "TLSv1.3") {
    363             # Client will have sent supported_versions, so server will know
    364             # that there are no overlapping versions.
    365             return ("ServerFail", undef);
    366         } else {
    367             # Server will try with a version that is lower than the lowest
    368             # supported client version.
    369             return ("ClientFail", undef);
    370         }
    371     } else {
    372         # Server and client ranges overlap.
    373         my $max_common = $s_max < $c_max ? $s_max : $c_max;
    374         return ("Success", $protocols->[$max_common]);
    375     }
    376 }
    377 
    378 1;
    379