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
44$ENV{OPENSSL_ia32cap} = '~0x200000200000000';
45my $proxy = TLSProxy::Proxy->new(
46    \&inject_duplicate_extension_clienthello,
47    cmdstr(app(["openssl"]), display => 1),
48    srctop_file("apps", "server.pem"),
49    (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
50);
51
52
53sub extension_filter
54{
55    my $proxy = shift;
56
57    if ($proxy->flight == 1) {
58        # Change the ServerRandom so that the downgrade sentinel doesn't cause
59        # the connection to fail
60        my $message = ${$proxy->message_list}[1];
61        $message->random("\0"x32);
62        $message->repack();
63        return;
64    }
65
66    # We're only interested in the initial ClientHello
67    if ($proxy->flight != 0) {
68        return;
69    }
70
71    foreach my $message (@{$proxy->message_list}) {
72        if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) {
73            # Remove all extensions and set the extension len to zero
74            $message->extension_data({});
75            $message->extensions_len(0);
76            # Extensions have been removed so make sure we don't try to use them
77            $message->process_extensions();
78
79            $message->repack();
80        }
81    }
82}
83
84sub inject_duplicate_extension
85{
86  my ($proxy, $message_type) = @_;
87
88    foreach my $message (@{$proxy->message_list}) {
89        if ($message->mt == $message_type) {
90          my %extensions = %{$message->extension_data};
91            # Add a duplicate extension. We use cryptopro_bug since we never
92            # normally write that one, and it is allowed as unsolicited in the
93            # ServerHello
94            $message->set_extension(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION, "");
95            $message->dupext(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION);
96            $message->repack();
97        }
98    }
99}
100
101sub inject_duplicate_extension_clienthello
102{
103    my $proxy = shift;
104
105    # We're only interested in the initial ClientHello
106    if ($proxy->flight == 0) {
107        inject_duplicate_extension($proxy, TLSProxy::Message::MT_CLIENT_HELLO);
108        return;
109    }
110
111    my $last_record = @{$proxy->{record_list}}[-1];
112    $fatal_alert = 1 if $last_record->is_fatal_alert(1);
113}
114
115sub inject_duplicate_extension_serverhello
116{
117    my $proxy = shift;
118
119    # We're only interested in the initial ServerHello
120    if ($proxy->flight == 0) {
121        return;
122    } elsif ($proxy->flight == 1) {
123        inject_duplicate_extension($proxy, TLSProxy::Message::MT_SERVER_HELLO);
124        return;
125    }
126
127    my $last_record = @{$proxy->{record_list}}[-1];
128    $fatal_alert = 1 if $last_record->is_fatal_alert(0);
129}
130
131sub inject_unsolicited_extension
132{
133    my $proxy = shift;
134    my $message;
135    state $sent_unsolisited_extension;
136
137    if ($proxy->flight == 0) {
138        $sent_unsolisited_extension = 0;
139        return;
140    }
141
142    # We're only interested in the initial ServerHello/EncryptedExtensions
143    if ($proxy->flight != 1) {
144        if ($sent_unsolisited_extension) {
145            my $last_record = @{$proxy->record_list}[-1];
146            $fatal_alert = 1 if $last_record->is_fatal_alert(0);
147        }
148        return;
149    }
150
151    if ($testtype == UNSOLICITED_SERVER_NAME_TLS13) {
152        return if (!defined($message = ${$proxy->message_list}[2]));
153        die "Expecting EE message ".($message->mt).","
154                                   .${$proxy->message_list}[1]->mt.", "
155                                   .${$proxy->message_list}[3]->mt
156            if $message->mt != TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS;
157    } else {
158        $message = ${$proxy->message_list}[1];
159    }
160
161    my $ext = pack "C2",
162        0x00, 0x00; #Extension length
163
164    my $type;
165    if ($testtype == UNSOLICITED_SERVER_NAME
166            || $testtype == UNSOLICITED_SERVER_NAME_TLS13) {
167        $type = TLSProxy::Message::EXT_SERVER_NAME;
168    } elsif ($testtype == UNSOLICITED_SCT) {
169        $type = TLSProxy::Message::EXT_SCT;
170    } elsif ($testtype == NONCOMPLIANT_SUPPORTED_GROUPS) {
171        $type = TLSProxy::Message::EXT_SUPPORTED_GROUPS;
172    }
173    $message->set_extension($type, $ext);
174    $message->repack();
175    $sent_unsolisited_extension = 1;
176}
177
178sub inject_cryptopro_extension
179{
180    my $proxy = shift;
181
182    # We're only interested in the initial ClientHello
183    if ($proxy->flight != 0) {
184        return;
185    }
186
187    my $message = ${$proxy->message_list}[0];
188    $message->set_extension(TLSProxy::Message::EXT_CRYPTOPRO_BUG_EXTENSION, "");
189    $message->repack();
190}
191
192# Test 1-2: Sending a duplicate extension should fail.
193$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
194plan tests => 8;
195ok($fatal_alert, "Duplicate ClientHello extension");
196
197SKIP: {
198    skip "TLS <= 1.2 disabled", 4 if $no_below_tls13;
199
200    $fatal_alert = 0;
201    $proxy->clear();
202    $proxy->filter(\&inject_duplicate_extension_serverhello);
203    $proxy->clientflags("-no_tls1_3");
204    $proxy->start();
205    ok($fatal_alert, "Duplicate ServerHello extension");
206
207    #Test 3: Sending a zero length extension block should pass
208    $proxy->clear();
209    $proxy->filter(\&extension_filter);
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
234SKIP: {
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
246SKIP: {
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
259SKIP: {
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