xref: /openssl/test/recipes/70-test_tls13hrr.t (revision 2ccd57b2)
1#! /usr/bin/env perl
2# Copyright 2017-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
9use strict;
10use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
11use OpenSSL::Test::Utils;
12use TLSProxy::Proxy;
13use TLSProxy::Message;
14
15my $test_name = "test_tls13hrr";
16setup($test_name);
17
18plan skip_all => "TLSProxy isn't usable on $^O"
19    if $^O =~ /^(VMS)$/;
20
21plan skip_all => "$test_name needs the dynamic engine feature enabled"
22    if disabled("engine") || disabled("dynamic-engine");
23
24plan skip_all => "$test_name needs the sock feature enabled"
25    if disabled("sock");
26
27plan skip_all => "$test_name needs TLS1.3 enabled"
28    if disabled("tls1_3") || (disabled("ec") && disabled("dh"));
29
30my $proxy = TLSProxy::Proxy->new(
31    undef,
32    cmdstr(app(["openssl"]), display => 1),
33    srctop_file("apps", "server.pem"),
34    (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
35);
36
37use constant {
38    CHANGE_HRR_CIPHERSUITE => 0,
39    CHANGE_CH1_CIPHERSUITE => 1,
40    DUPLICATE_HRR => 2,
41    INVALID_GROUP => 3,
42    NO_SUPPORTED_VERSIONS => 4
43};
44
45#Test 1: A client should fail if the server changes the ciphersuite between the
46#        HRR and the SH
47$proxy->filter(\&hrr_filter);
48if (disabled("ec")) {
49    $proxy->serverflags("-curves ffdhe3072");
50} else {
51    $proxy->serverflags("-curves P-256");
52}
53my $testtype = CHANGE_HRR_CIPHERSUITE;
54$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
55plan tests => 5;
56ok(TLSProxy::Message->fail(), "Server ciphersuite changes");
57
58#Test 2: It is an error if the client changes the offered ciphersuites so that
59#        we end up selecting a different ciphersuite between HRR and the SH
60$proxy->clear();
61if (disabled("ec")) {
62    $proxy->serverflags("-curves ffdhe3072");
63} else {
64    $proxy->serverflags("-curves P-384");
65}
66$proxy->ciphersuitess("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384");
67$testtype = CHANGE_CH1_CIPHERSUITE;
68$proxy->start();
69ok(TLSProxy::Message->fail(), "Client ciphersuite changes");
70
71#Test 3: A client should fail with unexpected_message alert if the server
72#        sends more than 1 HRR
73my $fatal_alert = 0;
74$proxy->clear();
75if (disabled("ec")) {
76    $proxy->serverflags("-curves ffdhe3072");
77} else {
78    $proxy->serverflags("-curves P-384");
79}
80$testtype = DUPLICATE_HRR;
81$proxy->start();
82ok($fatal_alert, "Server duplicated HRR");
83
84#Test 4: If the client sends a group that is in the supported_groups list but
85#        otherwise not valid (e.g. not suitable for TLSv1.3) we should reject it
86#        and not consider it when sending the HRR. We send brainpoolP512r1 in
87#        the ClientHello, which is acceptable to the server but is not valid in
88#        TLSv1.3. We expect the server to select P-521 in the HRR and the
89#        handshake to complete successfully
90SKIP: {
91    skip "EC/TLSv1.2 is disabled in this build", 1
92        if disabled("ec") || disabled("tls1_2");
93
94    $proxy->clear();
95    $proxy->clientflags("-groups P-256:brainpoolP512r1:P-521");
96    $proxy->serverflags("-groups brainpoolP512r1:P-521");
97    $testtype = INVALID_GROUP;
98    $proxy->start();
99    ok(TLSProxy::Message->success(), "Invalid group with HRR");
100}
101
102#Test 5: A failure should occur if an HRR is sent without the supported_versions
103#        extension
104$fatal_alert = 0;
105$proxy->clear();
106if (disabled("ec")) {
107    $proxy->serverflags("-curves ffdhe3072");
108} else {
109    $proxy->serverflags("-curves P-384");
110}
111$testtype = NO_SUPPORTED_VERSIONS;
112$proxy->start();
113ok($fatal_alert, "supported_versions missing from HRR");
114
115sub hrr_filter
116{
117    my $proxy = shift;
118
119    if ($testtype == CHANGE_HRR_CIPHERSUITE) {
120        # We're only interested in the HRR
121        if ($proxy->flight != 1) {
122            return;
123        }
124
125        my $hrr = ${$proxy->message_list}[1];
126
127        # We will normally only ever select CIPHER_TLS13_AES_128_GCM_SHA256
128        # because that's what Proxy tells s_server to do. Setting as below means
129        # the ciphersuite will change will we get the ServerHello
130        $hrr->ciphersuite(TLSProxy::Message::CIPHER_TLS13_AES_256_GCM_SHA384);
131        $hrr->repack();
132        return;
133    }
134
135    if ($testtype == NO_SUPPORTED_VERSIONS) {
136        # Check if we have the expected fatal alert
137        if ($proxy->flight == 2) {
138            $fatal_alert = 1
139                if @{$proxy->record_list}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_MISSING_EXTENSION;
140            return;
141        }
142
143        # Otherwise we're only interested in the HRR
144        if ($proxy->flight != 1) {
145            return;
146        }
147
148        my $hrr = ${$proxy->message_list}[1];
149        $hrr->delete_extension(TLSProxy::Message::EXT_SUPPORTED_VERSIONS);
150        $hrr->repack();
151        return;
152    }
153
154    if ($testtype == DUPLICATE_HRR) {
155        # We're only interested in the HRR
156        # and the unexpected_message alert from client
157        if ($proxy->flight == 4) {
158            $fatal_alert = 1
159                if @{$proxy->record_list}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE;
160            return;
161        }
162        if ($proxy->flight != 3) {
163            return;
164        }
165
166        # Find ServerHello record (HRR actually) and insert after that
167        my $i;
168        for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
169            next;
170        }
171        my $hrr_record = ${$proxy->record_list}[$i];
172        my $dup_hrr = TLSProxy::Record->new(3,
173            $hrr_record->content_type(),
174            $hrr_record->version(),
175            $hrr_record->len(),
176            $hrr_record->sslv2(),
177            $hrr_record->len_real(),
178            $hrr_record->decrypt_len(),
179            $hrr_record->data(),
180            $hrr_record->decrypt_data());
181
182        $i++;
183        splice @{$proxy->record_list}, $i, 0, $dup_hrr;
184        return;
185    }
186
187    if ($proxy->flight != 0) {
188        return;
189    }
190
191    my $ch1 = ${$proxy->message_list}[0];
192
193    if ($testtype == CHANGE_CH1_CIPHERSUITE) {
194        # The server will always pick TLS_AES_256_GCM_SHA384
195        my @ciphersuites = (TLSProxy::Message::CIPHER_TLS13_AES_128_GCM_SHA256);
196        $ch1->ciphersuite_len(2 * scalar @ciphersuites);
197        $ch1->ciphersuites(\@ciphersuites);
198    } elsif ($testtype == INVALID_GROUP) {
199        # INVALID_GROUP
200        my $ext = pack "C7",
201            0x00, 0x05, #List Length
202            0x00, 0x1c, #brainpoolP512r1 (not compatible with TLSv1.3)
203            0x00, 0x01, 0xff; #key_exchange data
204        $ch1->set_extension(
205            TLSProxy::Message::EXT_KEY_SHARE, $ext);
206    }
207    $ch1->repack();
208}
209