1#! /usr/bin/env perl
2# Copyright 2015-2021 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 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_sslextension";
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 TLS enabled"
29    if alldisabled(available_protocols("tls"));
30
31my $no_below_tls13 = alldisabled(("tls1", "tls1_1", "tls1_2"))
32                     || (!disabled("tls1_3") && disabled("tls1_2"));
33
34use constant {
35    UNSOLICITED_SERVER_NAME => 0,
36    UNSOLICITED_SERVER_NAME_TLS13 => 1,
37    UNSOLICITED_SCT => 2,
38    NONCOMPLIANT_SUPPORTED_GROUPS => 3
39};
40
41my $testtype;
42my $fatal_alert = 0;    # set by filter on fatal alert
43
44my $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
52sub 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
83sub 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
100sub 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
114sub 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
130sub 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
177sub 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";
193plan tests => 8;
194ok($fatal_alert, "Duplicate ClientHello extension");
195
196SKIP: {
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->ciphers("AES128-SHA:\@SECLEVEL=0");
210    $proxy->clientflags("-no_tls1_3");
211    $proxy->start();
212    ok(TLSProxy::Message->success, "Zero extension length test");
213
214    #Test 4: Inject an unsolicited extension (<= TLSv1.2)
215    $fatal_alert = 0;
216    $proxy->clear();
217    $proxy->filter(\&inject_unsolicited_extension);
218    $testtype = UNSOLICITED_SERVER_NAME;
219    $proxy->clientflags("-no_tls1_3 -noservername");
220    $proxy->start();
221    ok($fatal_alert, "Unsolicited server name extension");
222
223    #Test 5: Send the cryptopro extension in a ClientHello. Normally this is an
224    #        unsolicited extension only ever seen in the ServerHello. We should
225    #        ignore it in a ClientHello
226    $proxy->clear();
227    $proxy->filter(\&inject_cryptopro_extension);
228    $proxy->clientflags("-no_tls1_3");
229    $proxy->start();
230    ok(TLSProxy::Message->success(), "Cryptopro extension in ClientHello");
231}
232
233SKIP: {
234    skip "TLS <= 1.2 disabled or EC disabled", 1
235        if $no_below_tls13 || disabled("ec");
236    #Test 6: Inject a noncompliant supported_groups extension (<= TLSv1.2)
237    $proxy->clear();
238    $proxy->filter(\&inject_unsolicited_extension);
239    $testtype = NONCOMPLIANT_SUPPORTED_GROUPS;
240    $proxy->clientflags("-no_tls1_3");
241    $proxy->start();
242    ok(TLSProxy::Message->success(), "Noncompliant supported_groups extension");
243}
244
245SKIP: {
246    skip "TLS <= 1.2 or CT disabled", 1
247        if $no_below_tls13 || disabled("ct");
248    #Test 7: Same as above for the SCT extension which has special handling
249    $fatal_alert = 0;
250    $proxy->clear();
251    $proxy->filter(\&inject_unsolicited_extension);
252    $testtype = UNSOLICITED_SCT;
253    $proxy->clientflags("-no_tls1_3");
254    $proxy->start();
255    ok($fatal_alert, "Unsolicited sct extension");
256}
257
258SKIP: {
259    skip "TLS 1.3 disabled", 1
260        if disabled("tls1_3") || (disabled("ec") && disabled("dh"));
261    #Test 8: Inject an unsolicited extension (TLSv1.3)
262    $fatal_alert = 0;
263    $proxy->clear();
264    $proxy->filter(\&inject_unsolicited_extension);
265    $testtype = UNSOLICITED_SERVER_NAME_TLS13;
266    $proxy->clientflags("-noservername");
267    $proxy->start();
268    ok($fatal_alert, "Unsolicited server name extension (TLSv1.3)");
269}
270