Add a test for unrecognised record types
[openssl.git] / test / recipes / 70-test_sslrecords.t
1 #! /usr/bin/env perl
2 # Copyright 2016 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 use strict;
10 use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
11 use OpenSSL::Test::Utils;
12 use TLSProxy::Proxy;
13
14 my $test_name = "test_sslrecords";
15 setup($test_name);
16
17 plan skip_all => "TLSProxy isn't usable on $^O"
18     if $^O =~ /^(VMS|MSWin32)$/;
19
20 plan skip_all => "$test_name needs the dynamic engine feature enabled"
21     if disabled("engine") || disabled("dynamic-engine");
22
23 plan skip_all => "$test_name needs the sock feature enabled"
24     if disabled("sock");
25
26 plan skip_all => "$test_name needs TLSv1.2 enabled"
27     if disabled("tls1_2");
28
29 $ENV{OPENSSL_ia32cap} = '~0x200000200000000';
30 my $proxy = TLSProxy::Proxy->new(
31     \&add_empty_recs_filter,
32     cmdstr(app(["openssl"]), display => 1),
33     srctop_file("apps", "server.pem"),
34     (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
35 );
36
37 #Test 1: Injecting out of context empty records should fail
38 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
39 my $inject_recs_num = 1;
40 $proxy->serverflags("-tls1_2");
41 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
42 my $num_tests = 10;
43 if (!disabled("tls1_1")) {
44     $num_tests++;
45 }
46 plan tests => $num_tests;
47 ok(TLSProxy::Message->fail(), "Out of context empty records test");
48
49 #Test 2: Injecting in context empty records should succeed
50 $proxy->clear();
51 $content_type = TLSProxy::Record::RT_HANDSHAKE;
52 $proxy->serverflags("-tls1_2");
53 $proxy->start();
54 ok(TLSProxy::Message->success(), "In context empty records test");
55
56 #Test 3: Injecting too many in context empty records should fail
57 $proxy->clear();
58 #We allow 32 consecutive in context empty records
59 $inject_recs_num = 33;
60 $proxy->serverflags("-tls1_2");
61 $proxy->start();
62 ok(TLSProxy::Message->fail(), "Too many in context empty records test");
63
64 #Test 4: Injecting a fragmented fatal alert should fail. We actually expect no
65 #        alerts to be sent from either side because *we* injected the fatal
66 #        alert, i.e. this will look like a disorderly close
67 $proxy->clear();
68 $proxy->filter(\&add_frag_alert_filter);
69 $proxy->serverflags("-tls1_2");
70 $proxy->start();
71 ok(!TLSProxy::Message->end(), "Fragmented alert records test");
72
73 #Run some SSLv2 ClientHello tests
74
75 use constant {
76     TLSV1_2_IN_SSLV2 => 0,
77     SSLV2_IN_SSLV2 => 1,
78     FRAGMENTED_IN_TLSV1_2 => 2,
79     FRAGMENTED_IN_SSLV2 => 3,
80     ALERT_BEFORE_SSLV2 => 4
81 };
82 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
83 my $sslv2testtype = TLSV1_2_IN_SSLV2;
84 $proxy->clear();
85 $proxy->filter(\&add_sslv2_filter);
86 $proxy->serverflags("-tls1_2");
87 $proxy->start();
88 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
89
90 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
91 #        support this so it should fail. We actually treat it as an unknown
92 #        protocol so we don't even send an alert in this case.
93 $sslv2testtype = SSLV2_IN_SSLV2;
94 $proxy->clear();
95 $proxy->serverflags("-tls1_2");
96 $proxy->start();
97 ok(!TLSProxy::Message->end(), "SSLv2 in SSLv2 ClientHello test");
98
99 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
100 #        at all, but it gives us confidence that Test 8 fails for the right
101 #        reasons
102 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
103 $proxy->clear();
104 $proxy->serverflags("-tls1_2");
105 $proxy->start();
106 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
107
108 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
109 #        record; and another TLS1.2 record. This isn't allowed so should fail
110 $sslv2testtype = FRAGMENTED_IN_SSLV2;
111 $proxy->clear();
112 $proxy->serverflags("-tls1_2");
113 $proxy->start();
114 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
115
116 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
117 #        fail because an SSLv2 ClientHello must be the first record.
118 $sslv2testtype = ALERT_BEFORE_SSLV2;
119 $proxy->clear();
120 $proxy->serverflags("-tls1_2");
121 $proxy->start();
122 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
123
124 #Unregcognised record type tests
125
126 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
127 $proxy->clear();
128 $proxy->filter(\&add_unknown_record_type);
129 $proxy->start();
130 ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.2");
131
132 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
133 if (!disabled("tls1_1")) {
134     $proxy->clear();
135     $proxy->clientflags("-tls1_1");
136     $proxy->start();
137     ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
138 }
139
140 sub add_empty_recs_filter
141 {
142     my $proxy = shift;
143
144     # We're only interested in the initial ClientHello
145     if ($proxy->flight != 0) {
146         return;
147     }
148
149     for (my $i = 0; $i < $inject_recs_num; $i++) {
150         my $record = TLSProxy::Record->new(
151             0,
152             $content_type,
153             TLSProxy::Record::VERS_TLS_1_2,
154             0,
155             0,
156             0,
157             0,
158             "",
159             ""
160         );
161
162         push @{$proxy->record_list}, $record;
163     }
164 }
165
166 sub add_frag_alert_filter
167 {
168     my $proxy = shift;
169     my $byte;
170
171     # We're only interested in the initial ClientHello
172     if ($proxy->flight != 0) {
173         return;
174     }
175
176     # Add a zero length fragment first
177     #my $record = TLSProxy::Record->new(
178     #    0,
179     #    TLSProxy::Record::RT_ALERT,
180     #    TLSProxy::Record::VERS_TLS_1_2,
181     #    0,
182     #    0,
183     #    0,
184     #    "",
185     #    ""
186     #);
187     #push @{$proxy->record_list}, $record;
188
189     # Now add the alert level (Fatal) as a separate record
190     $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
191     my $record = TLSProxy::Record->new(
192         0,
193         TLSProxy::Record::RT_ALERT,
194         TLSProxy::Record::VERS_TLS_1_2,
195         1,
196         0,
197         1,
198         1,
199         $byte,
200         $byte
201     );
202     push @{$proxy->record_list}, $record;
203
204     # And finally the description (Unexpected message) in a third record
205     $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
206     $record = TLSProxy::Record->new(
207         0,
208         TLSProxy::Record::RT_ALERT,
209         TLSProxy::Record::VERS_TLS_1_2,
210         1,
211         0,
212         1,
213         1,
214         $byte,
215         $byte
216     );
217     push @{$proxy->record_list}, $record;
218 }
219
220 sub add_sslv2_filter
221 {
222     my $proxy = shift;
223     my $clienthello;
224     my $record;
225
226     # We're only interested in the initial ClientHello
227     if ($proxy->flight != 0) {
228         return;
229     }
230
231     # Ditch the real ClientHello - we're going to replace it with our own
232     shift @{$proxy->record_list};
233
234     if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
235         my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
236                                TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
237         my $alertlen = length $alert;
238         $record = TLSProxy::Record->new(
239             0,
240             TLSProxy::Record::RT_ALERT,
241             TLSProxy::Record::VERS_TLS_1_2,
242             $alertlen,
243             0,
244             $alertlen,
245             $alertlen,
246             $alert,
247             $alert
248         );
249
250         push @{$proxy->record_list}, $record;
251     }
252
253     if ($sslv2testtype == ALERT_BEFORE_SSLV2
254             || $sslv2testtype == TLSV1_2_IN_SSLV2
255             || $sslv2testtype == SSLV2_IN_SSLV2) {
256         # This is an SSLv2 format ClientHello
257         $clienthello =
258             pack "C44",
259             0x01, # ClientHello
260             0x03, 0x03, #TLSv1.2
261             0x00, 0x03, # Ciphersuites len
262             0x00, 0x00, # Session id len
263             0x00, 0x20, # Challenge len
264             0x00, 0x00, 0x2f, #AES128-SHA
265             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
266             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
267             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
268
269         if ($sslv2testtype == SSLV2_IN_SSLV2) {
270             # Set the version to "real" SSLv2
271             vec($clienthello, 1, 8) = 0x00;
272             vec($clienthello, 2, 8) = 0x02;
273         }
274
275         my $chlen = length $clienthello;
276
277         $record = TLSProxy::Record->new(
278             0,
279             TLSProxy::Record::RT_HANDSHAKE,
280             TLSProxy::Record::VERS_TLS_1_2,
281             $chlen,
282             1, #SSLv2
283             $chlen,
284             $chlen,
285             $clienthello,
286             $clienthello
287         );
288
289         push @{$proxy->record_list}, $record;
290     } else {
291         # For this test we're using a real TLS ClientHello
292         $clienthello =
293             pack "C49",
294             0x01, # ClientHello
295             0x00, 0x00, 0x2D, # Message length
296             0x03, 0x03, # TLSv1.2
297             0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
298             0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
299             0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
300             0x00, # Session id len
301             0x00, 0x04, # Ciphersuites len
302             0x00, 0x2f, # AES128-SHA
303             0x00, 0xff, # Empty reneg info SCSV
304             0x01, # Compression methods len
305             0x00, # Null compression
306             0x00, 0x00; # Extensions len
307
308         # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
309         # We deliberately split the second record prior to the Challenge/Random
310         # and set the first byte of the random to 1. This makes the second SSLv2
311         # record look like an SSLv2 ClientHello
312         my $frag1 = substr $clienthello, 0, 6;
313         my $frag2 = substr $clienthello, 6, 32;
314         my $frag3 = substr $clienthello, 38;
315
316         my $fraglen = length $frag1;
317         $record = TLSProxy::Record->new(
318             0,
319             TLSProxy::Record::RT_HANDSHAKE,
320             TLSProxy::Record::VERS_TLS_1_2,
321             $fraglen,
322             0,
323             $fraglen,
324             $fraglen,
325             $frag1,
326             $frag1
327         );
328         push @{$proxy->record_list}, $record;
329
330         $fraglen = length $frag2;
331         my $recvers;
332         if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
333             $recvers = 1;
334         } else {
335             $recvers = 0;
336         }
337         $record = TLSProxy::Record->new(
338             0,
339             TLSProxy::Record::RT_HANDSHAKE,
340             TLSProxy::Record::VERS_TLS_1_2,
341             $fraglen,
342             $recvers,
343             $fraglen,
344             $fraglen,
345             $frag2,
346             $frag2
347         );
348         push @{$proxy->record_list}, $record;
349
350         $fraglen = length $frag3;
351         $record = TLSProxy::Record->new(
352             0,
353             TLSProxy::Record::RT_HANDSHAKE,
354             TLSProxy::Record::VERS_TLS_1_2,
355             $fraglen,
356             0,
357             $fraglen,
358             $fraglen,
359             $frag3,
360             $frag3
361         );
362         push @{$proxy->record_list}, $record;
363     }
364
365 }
366
367 sub add_unknown_record_type
368 {
369     my $proxy = shift;
370
371     # We'll change a record after the initial version neg has taken place
372     if ($proxy->flight != 2) {
373         return;
374     }
375
376     my $lastrec = ${$proxy->record_list}[-1];
377     my $record = TLSProxy::Record->new(
378         2,
379         TLSProxy::Record::RT_UNKNOWN,
380         $lastrec->version(),
381         1,
382         0,
383         1,
384         1,
385         "X",
386         "X"
387     );
388
389     unshift @{$proxy->record_list}, $record;
390 }