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