Handle partial messages in TLSProxy
[openssl.git] / util / perl / TLSProxy / Record.pm
1 # Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
2 #
3 # Licensed under the OpenSSL license (the "License").  You may not use
4 # this file except in compliance with the License.  You can obtain a copy
5 # in the file LICENSE in the source distribution or at
6 # https://www.openssl.org/source/license.html
7
8 use strict;
9
10 use TLSProxy::Proxy;
11
12 package TLSProxy::Record;
13
14 my $server_encrypting = 0;
15 my $client_encrypting = 0;
16 my $etm = 0;
17
18 use constant TLS_RECORD_HEADER_LENGTH => 5;
19
20 #Record types
21 use constant {
22     RT_APPLICATION_DATA => 23,
23     RT_HANDSHAKE => 22,
24     RT_ALERT => 21,
25     RT_CCS => 20,
26     RT_UNKNOWN => 100
27 };
28
29 my %record_type = (
30     RT_APPLICATION_DATA, "APPLICATION DATA",
31     RT_HANDSHAKE, "HANDSHAKE",
32     RT_ALERT, "ALERT",
33     RT_CCS, "CCS",
34     RT_UNKNOWN, "UNKNOWN"
35 );
36
37 use constant {
38     VERS_TLS_1_4 => 0x0305,
39     VERS_TLS_1_3_DRAFT => 0x7f1a,
40     VERS_TLS_1_3 => 0x0304,
41     VERS_TLS_1_2 => 0x0303,
42     VERS_TLS_1_1 => 0x0302,
43     VERS_TLS_1_0 => 0x0301,
44     VERS_SSL_3_0 => 0x0300,
45     VERS_SSL_LT_3_0 => 0x02ff
46 };
47
48 my %tls_version = (
49     VERS_TLS_1_3, "TLS1.3",
50     VERS_TLS_1_2, "TLS1.2",
51     VERS_TLS_1_1, "TLS1.1",
52     VERS_TLS_1_0, "TLS1.0",
53     VERS_SSL_3_0, "SSL3",
54     VERS_SSL_LT_3_0, "SSL<3"
55 );
56
57 #Class method to extract records from a packet of data
58 sub get_records
59 {
60     my $class = shift;
61     my $server = shift;
62     my $flight = shift;
63     my $packet = shift;
64     my $partial = "";
65     my @record_list = ();
66     my @message_list = ();
67     my $data;
68     my $content_type;
69     my $version;
70     my $len;
71     my $len_real;
72     my $decrypt_len;
73
74     my $recnum = 1;
75     while (length ($packet) > 0) {
76         print " Record $recnum";
77         if ($server) {
78             print " (server -> client)\n";
79         } else {
80             print " (client -> server)\n";
81         }
82         #Get the record header
83         if (length($packet) < TLS_RECORD_HEADER_LENGTH
84                 || length($packet) < 5 + unpack("n", substr($packet, 3, 2))) {
85             print "Partial data : ".length($packet)." bytes\n";
86             $partial = $packet;
87             $packet = "";
88         } else {
89             ($content_type, $version, $len) = unpack('CnnC*', $packet);
90             $data = substr($packet, 5, $len);
91
92             print "  Content type: ".$record_type{$content_type}."\n";
93             print "  Version: $tls_version{$version}\n";
94             print "  Length: $len";
95             if ($len == length($data)) {
96                 print "\n";
97                 $decrypt_len = $len_real = $len;
98             } else {
99                 print " (expected), ".length($data)." (actual)\n";
100                 $decrypt_len = $len_real = length($data);
101             }
102
103             my $record = TLSProxy::Record->new(
104                 $flight,
105                 $content_type,
106                 $version,
107                 $len,
108                 0,
109                 $len_real,
110                 $decrypt_len,
111                 substr($packet, TLS_RECORD_HEADER_LENGTH, $len_real),
112                 substr($packet, TLS_RECORD_HEADER_LENGTH, $len_real)
113             );
114
115             if ($content_type != RT_CCS) {
116                 if (($server && $server_encrypting)
117                          || (!$server && $client_encrypting)) {
118                     if (!TLSProxy::Proxy->is_tls13() && $etm) {
119                         $record->decryptETM();
120                     } else {
121                         $record->decrypt();
122                     }
123                     $record->encrypted(1);
124
125                     if (TLSProxy::Proxy->is_tls13()) {
126                         print "  Inner content type: "
127                               .$record_type{$record->content_type()}."\n";
128                     }
129                 }
130             }
131
132             push @record_list, $record;
133
134             #Now figure out what messages are contained within this record
135             my @messages = TLSProxy::Message->get_messages($server, $record);
136             push @message_list, @messages;
137
138             $packet = substr($packet, TLS_RECORD_HEADER_LENGTH + $len_real);
139             $recnum++;
140         }
141     }
142
143     return (\@record_list, \@message_list, $partial);
144 }
145
146 sub clear
147 {
148     $server_encrypting = 0;
149     $client_encrypting = 0;
150 }
151
152 #Class level accessors
153 sub server_encrypting
154 {
155     my $class = shift;
156     if (@_) {
157       $server_encrypting = shift;
158     }
159     return $server_encrypting;
160 }
161 sub client_encrypting
162 {
163     my $class = shift;
164     if (@_) {
165       $client_encrypting= shift;
166     }
167     return $client_encrypting;
168 }
169 #Enable/Disable Encrypt-then-MAC
170 sub etm
171 {
172     my $class = shift;
173     if (@_) {
174       $etm = shift;
175     }
176     return $etm;
177 }
178
179 sub new
180 {
181     my $class = shift;
182     my ($flight,
183         $content_type,
184         $version,
185         $len,
186         $sslv2,
187         $len_real,
188         $decrypt_len,
189         $data,
190         $decrypt_data) = @_;
191     
192     my $self = {
193         flight => $flight,
194         content_type => $content_type,
195         version => $version,
196         len => $len,
197         sslv2 => $sslv2,
198         len_real => $len_real,
199         decrypt_len => $decrypt_len,
200         data => $data,
201         decrypt_data => $decrypt_data,
202         orig_decrypt_data => $decrypt_data,
203         sent => 0,
204         encrypted => 0,
205         outer_content_type => RT_APPLICATION_DATA
206     };
207
208     return bless $self, $class;
209 }
210
211 #Decrypt using encrypt-then-MAC
212 sub decryptETM
213 {
214     my ($self) = shift;
215
216     my $data = $self->data;
217
218     if($self->version >= VERS_TLS_1_1()) {
219         #TLS1.1+ has an explicit IV. Throw it away
220         $data = substr($data, 16);
221     }
222
223     #Throw away the MAC (assumes MAC is 20 bytes for now. FIXME)
224     $data = substr($data, 0, length($data) - 20);
225
226     #Find out what the padding byte is
227     my $padval = unpack("C", substr($data, length($data) - 1));
228
229     #Throw away the padding
230     $data = substr($data, 0, length($data) - ($padval + 1));
231
232     $self->decrypt_data($data);
233     $self->decrypt_len(length($data));
234
235     return $data;
236 }
237
238 #Standard decrypt
239 sub decrypt()
240 {
241     my ($self) = shift;
242     my $mactaglen = 20;
243     my $data = $self->data;
244
245     #Throw away any IVs
246     if (TLSProxy::Proxy->is_tls13()) {
247         #A TLS1.3 client, when processing the server's initial flight, could
248         #respond with either an encrypted or an unencrypted alert.
249         if ($self->content_type() == RT_ALERT) {
250             #TODO(TLS1.3): Eventually it is sufficient just to check the record
251             #content type. If an alert is encrypted it will have a record
252             #content type of application data. However we haven't done the
253             #record layer changes yet, so it's a bit more complicated. For now
254             #we will additionally check if the data length is 2 (1 byte for
255             #alert level, 1 byte for alert description). If it is, then this is
256             #an unencrypted alert, so don't try to decrypt
257             return $data if (length($data) == 2);
258         }
259         $mactaglen = 16;
260     } elsif ($self->version >= VERS_TLS_1_1()) {
261         #16 bytes for a standard IV
262         $data = substr($data, 16);
263
264         #Find out what the padding byte is
265         my $padval = unpack("C", substr($data, length($data) - 1));
266
267         #Throw away the padding
268         $data = substr($data, 0, length($data) - ($padval + 1));
269     }
270
271     #Throw away the MAC or TAG
272     $data = substr($data, 0, length($data) - $mactaglen);
273
274     if (TLSProxy::Proxy->is_tls13()) {
275         #Get the content type
276         my $content_type = unpack("C", substr($data, length($data) - 1));
277         $self->content_type($content_type);
278         $data = substr($data, 0, length($data) - 1);
279     }
280
281     $self->decrypt_data($data);
282     $self->decrypt_len(length($data));
283
284     return $data;
285 }
286
287 #Reconstruct the on-the-wire record representation
288 sub reconstruct_record
289 {
290     my $self = shift;
291     my $server = shift;
292     my $data;
293
294     if ($self->{sent}) {
295         return "";
296     }
297     $self->{sent} = 1;
298
299     if ($self->sslv2) {
300         $data = pack('n', $self->len | 0x8000);
301     } else {
302         if (TLSProxy::Proxy->is_tls13() && $self->encrypted) {
303             $data = pack('Cnn', $self->outer_content_type, $self->version,
304                          $self->len);
305         } else {
306             $data = pack('Cnn', $self->content_type, $self->version,
307                          $self->len);
308         }
309
310     }
311     $data .= $self->data;
312
313     return $data;
314 }
315
316 #Read only accessors
317 sub flight
318 {
319     my $self = shift;
320     return $self->{flight};
321 }
322 sub sslv2
323 {
324     my $self = shift;
325     return $self->{sslv2};
326 }
327 sub len_real
328 {
329     my $self = shift;
330     return $self->{len_real};
331 }
332 sub orig_decrypt_data
333 {
334     my $self = shift;
335     return $self->{orig_decrypt_data};
336 }
337
338 #Read/write accessors
339 sub decrypt_len
340 {
341     my $self = shift;
342     if (@_) {
343       $self->{decrypt_len} = shift;
344     }
345     return $self->{decrypt_len};
346 }
347 sub data
348 {
349     my $self = shift;
350     if (@_) {
351       $self->{data} = shift;
352     }
353     return $self->{data};
354 }
355 sub decrypt_data
356 {
357     my $self = shift;
358     if (@_) {
359       $self->{decrypt_data} = shift;
360     }
361     return $self->{decrypt_data};
362 }
363 sub len
364 {
365     my $self = shift;
366     if (@_) {
367       $self->{len} = shift;
368     }
369     return $self->{len};
370 }
371 sub version
372 {
373     my $self = shift;
374     if (@_) {
375       $self->{version} = shift;
376     }
377     return $self->{version};
378 }
379 sub content_type
380 {
381     my $self = shift;
382     if (@_) {
383       $self->{content_type} = shift;
384     }
385     return $self->{content_type};
386 }
387 sub encrypted
388 {
389     my $self = shift;
390     if (@_) {
391       $self->{encrypted} = shift;
392     }
393     return $self->{encrypted};
394 }
395 sub outer_content_type
396 {
397     my $self = shift;
398     if (@_) {
399       $self->{outer_content_type} = shift;
400     }
401     return $self->{outer_content_type};
402 }
403 1;