4f07ee3d3fa8b63d6dd8f223ff4b53f0d61a0da3
[openssl.git] / util / TLSProxy / Message.pm
1 # Copyright 2016 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 package TLSProxy::Message;
11
12 use constant TLS_MESSAGE_HEADER_LENGTH => 4;
13
14 #Message types
15 use constant {
16     MT_HELLO_REQUEST => 0,
17     MT_CLIENT_HELLO => 1,
18     MT_SERVER_HELLO => 2,
19     MT_NEW_SESSION_TICKET => 4,
20     MT_ENCRYPTED_EXTENSIONS => 8,
21     MT_CERTIFICATE => 11,
22     MT_SERVER_KEY_EXCHANGE => 12,
23     MT_CERTIFICATE_REQUEST => 13,
24     MT_SERVER_HELLO_DONE => 14,
25     MT_CERTIFICATE_VERIFY => 15,
26     MT_CLIENT_KEY_EXCHANGE => 16,
27     MT_FINISHED => 20,
28     MT_CERTIFICATE_STATUS => 22,
29     MT_NEXT_PROTO => 67
30 };
31
32 #Alert levels
33 use constant {
34     AL_LEVEL_WARN => 1,
35     AL_LEVEL_FATAL => 2
36 };
37
38 #Alert descriptions
39 use constant {
40     AL_DESC_CLOSE_NOTIFY => 0,
41     AL_DESC_UNEXPECTED_MESSAGE => 10,
42     AL_DESC_NO_RENEGOTIATION => 100
43 };
44
45 my %message_type = (
46     MT_HELLO_REQUEST, "HelloRequest",
47     MT_CLIENT_HELLO, "ClientHello",
48     MT_SERVER_HELLO, "ServerHello",
49     MT_NEW_SESSION_TICKET, "NewSessionTicket",
50     MT_ENCRYPTED_EXTENSIONS, "EncryptedExtensions",
51     MT_CERTIFICATE, "Certificate",
52     MT_SERVER_KEY_EXCHANGE, "ServerKeyExchange",
53     MT_CERTIFICATE_REQUEST, "CertificateRequest",
54     MT_SERVER_HELLO_DONE, "ServerHelloDone",
55     MT_CERTIFICATE_VERIFY, "CertificateVerify",
56     MT_CLIENT_KEY_EXCHANGE, "ClientKeyExchange",
57     MT_FINISHED, "Finished",
58     MT_CERTIFICATE_STATUS, "CertificateStatus",
59     MT_NEXT_PROTO, "NextProto"
60 );
61
62 use constant {
63     EXT_STATUS_REQUEST => 5,
64     EXT_SUPPORTED_GROUPS => 10,
65     EXT_ENCRYPT_THEN_MAC => 22,
66     EXT_EXTENDED_MASTER_SECRET => 23,
67     EXT_SESSION_TICKET => 35,
68     EXT_SUPPORTED_VERSIONS => 43,
69     EXT_KEY_SHARE => 40,
70     # This extension is an unofficial extension only ever written by OpenSSL
71     # (i.e. not read), and even then only when enabled. We use it to test
72     # handling of duplicate extensions.
73     EXT_DUPLICATE_EXTENSION => 0xfde8
74 };
75
76 my $payload = "";
77 my $messlen = -1;
78 my $mt;
79 my $startoffset = -1;
80 my $server = 0;
81 my $success = 0;
82 my $end = 0;
83 my @message_rec_list = ();
84 my @message_frag_lens = ();
85 my $ciphersuite = 0;
86
87 sub clear
88 {
89     $payload = "";
90     $messlen = -1;
91     $startoffset = -1;
92     $server = 0;
93     $success = 0;
94     $end = 0;
95     @message_rec_list = ();
96     @message_frag_lens = ();
97 }
98
99 #Class method to extract messages from a record
100 sub get_messages
101 {
102     my $class = shift;
103     my $serverin = shift;
104     my $record = shift;
105     my @messages = ();
106     my $message;
107
108     @message_frag_lens = ();
109
110     if ($serverin != $server && length($payload) != 0) {
111         die "Changed peer, but we still have fragment data\n";
112     }
113     $server = $serverin;
114
115     if ($record->content_type == TLSProxy::Record::RT_CCS) {
116         if ($payload ne "") {
117             #We can't handle this yet
118             die "CCS received before message data complete\n";
119         }
120         if ($server) {
121             TLSProxy::Record->server_encrypting(1);
122         } else {
123             TLSProxy::Record->client_encrypting(1);
124         }
125     } elsif ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
126         if ($record->len == 0 || $record->len_real == 0) {
127             print "  Message truncated\n";
128         } else {
129             my $recoffset = 0;
130
131             if (length $payload > 0) {
132                 #We are continuing processing a message started in a previous
133                 #record. Add this record to the list associated with this
134                 #message
135                 push @message_rec_list, $record;
136
137                 if ($messlen <= length($payload)) {
138                     #Shouldn't happen
139                     die "Internal error: invalid messlen: ".$messlen
140                         ." payload length:".length($payload)."\n";
141                 }
142                 if (length($payload) + $record->decrypt_len >= $messlen) {
143                     #We can complete the message with this record
144                     $recoffset = $messlen - length($payload);
145                     $payload .= substr($record->decrypt_data, 0, $recoffset);
146                     push @message_frag_lens, $recoffset;
147                     $message = create_message($server, $mt, $payload,
148                                               $startoffset);
149                     push @messages, $message;
150
151                     $payload = "";
152                 } else {
153                     #This is just part of the total message
154                     $payload .= $record->decrypt_data;
155                     $recoffset = $record->decrypt_len;
156                     push @message_frag_lens, $record->decrypt_len;
157                 }
158                 print "  Partial message data read: ".$recoffset." bytes\n";
159             }
160
161             while ($record->decrypt_len > $recoffset) {
162                 #We are at the start of a new message
163                 if ($record->decrypt_len - $recoffset < 4) {
164                     #Whilst technically probably valid we can't cope with this
165                     die "End of record in the middle of a message header\n";
166                 }
167                 @message_rec_list = ($record);
168                 my $lenhi;
169                 my $lenlo;
170                 ($mt, $lenhi, $lenlo) = unpack('CnC',
171                                                substr($record->decrypt_data,
172                                                       $recoffset));
173                 $messlen = ($lenhi << 8) | $lenlo;
174                 print "  Message type: $message_type{$mt}\n";
175                 print "  Message Length: $messlen\n";
176                 $startoffset = $recoffset;
177                 $recoffset += 4;
178                 $payload = "";
179                 
180                 if ($recoffset < $record->decrypt_len) {
181                     #Some payload data is present in this record
182                     if ($record->decrypt_len - $recoffset >= $messlen) {
183                         #We can complete the message with this record
184                         $payload .= substr($record->decrypt_data, $recoffset,
185                                            $messlen);
186                         $recoffset += $messlen;
187                         push @message_frag_lens, $messlen;
188                         $message = create_message($server, $mt, $payload,
189                                                   $startoffset);
190                         push @messages, $message;
191
192                         $payload = "";
193                     } else {
194                         #This is just part of the total message
195                         $payload .= substr($record->decrypt_data, $recoffset,
196                                            $record->decrypt_len - $recoffset);
197                         $recoffset = $record->decrypt_len;
198                         push @message_frag_lens, $recoffset;
199                     }
200                 }
201             }
202         }
203     } elsif ($record->content_type == TLSProxy::Record::RT_APPLICATION_DATA) {
204         print "  [ENCRYPTED APPLICATION DATA]\n";
205         print "  [".$record->decrypt_data."]\n";
206     } elsif ($record->content_type == TLSProxy::Record::RT_ALERT) {
207         my ($alertlev, $alertdesc) = unpack('CC', $record->decrypt_data);
208         #A CloseNotify from the client indicates we have finished successfully
209         #(we assume)
210         if (!$end && !$server && $alertlev == AL_LEVEL_WARN
211             && $alertdesc == AL_DESC_CLOSE_NOTIFY) {
212             $success = 1;
213         }
214         #All alerts end the test
215         $end = 1;
216     }
217
218     return @messages;
219 }
220
221 #Function to work out which sub-class we need to create and then
222 #construct it
223 sub create_message
224 {
225     my ($server, $mt, $data, $startoffset) = @_;
226     my $message;
227
228     #We only support ClientHello in this version...needs to be extended for
229     #others
230     if ($mt == MT_CLIENT_HELLO) {
231         $message = TLSProxy::ClientHello->new(
232             $server,
233             $data,
234             [@message_rec_list],
235             $startoffset,
236             [@message_frag_lens]
237         );
238         $message->parse();
239     } elsif ($mt == MT_SERVER_HELLO) {
240         $message = TLSProxy::ServerHello->new(
241             $server,
242             $data,
243             [@message_rec_list],
244             $startoffset,
245             [@message_frag_lens]
246         );
247         $message->parse();
248     } elsif ($mt == MT_SERVER_KEY_EXCHANGE) {
249         $message = TLSProxy::ServerKeyExchange->new(
250             $server,
251             $data,
252             [@message_rec_list],
253             $startoffset,
254             [@message_frag_lens]
255         );
256         $message->parse();
257     } elsif ($mt == MT_NEW_SESSION_TICKET) {
258         $message = TLSProxy::NewSessionTicket->new(
259             $server,
260             $data,
261             [@message_rec_list],
262             $startoffset,
263             [@message_frag_lens]
264         );
265         $message->parse();
266     } else {
267         #Unknown message type
268         $message = TLSProxy::Message->new(
269             $server,
270             $mt,
271             $data,
272             [@message_rec_list],
273             $startoffset,
274             [@message_frag_lens]
275         );
276     }
277
278     return $message;
279 }
280
281 sub end
282 {
283     my $class = shift;
284     return $end;
285 }
286 sub success
287 {
288     my $class = shift;
289     return $success;
290 }
291 sub fail
292 {
293     my $class = shift;
294     return !$success && $end;
295 }
296 sub new
297 {
298     my $class = shift;
299     my ($server,
300         $mt,
301         $data,
302         $records,
303         $startoffset,
304         $message_frag_lens) = @_;
305     
306     my $self = {
307         server => $server,
308         data => $data,
309         records => $records,
310         mt => $mt,
311         startoffset => $startoffset,
312         message_frag_lens => $message_frag_lens
313     };
314
315     return bless $self, $class;
316 }
317
318 sub ciphersuite
319 {
320     my $class = shift;
321     if (@_) {
322       $ciphersuite = shift;
323     }
324     return $ciphersuite;
325 }
326
327 #Update all the underlying records with the modified data from this message
328 #Note: Does not currently support re-encrypting
329 sub repack
330 {
331     my $self = shift;
332     my $msgdata;
333
334     my $numrecs = $#{$self->records};
335
336     $self->set_message_contents();
337
338     my $lenhi;
339     my $lenlo;
340
341     $lenlo = length($self->data) & 0xff;
342     $lenhi = length($self->data) >> 8;
343     $msgdata = pack('CnC', $self->mt, $lenhi, $lenlo).$self->data;
344
345     if ($numrecs == 0) {
346         #The message is fully contained within one record
347         my ($rec) = @{$self->records};
348         my $recdata = $rec->decrypt_data;
349
350         my $old_length;
351
352         # We use empty message_frag_lens to indicates that pre-repacking,
353         # the message wasn't present. The first fragment length doesn't include
354         # the TLS header, so we need to check and compute the right length.
355         if (@{$self->message_frag_lens}) {
356             $old_length = ${$self->message_frag_lens}[0] +
357               TLS_MESSAGE_HEADER_LENGTH;
358         } else {
359             $old_length = 0;
360         }
361
362         my $prefix = substr($recdata, 0, $self->startoffset);
363         my $suffix = substr($recdata, $self->startoffset + $old_length);
364
365         $rec->decrypt_data($prefix.($msgdata).($suffix));
366         # TODO(openssl-team): don't keep explicit lengths.
367         # (If a length override is ever needed to construct invalid packets,
368         #  use an explicit override field instead.)
369         $rec->decrypt_len(length($rec->decrypt_data));
370         $rec->len($rec->len + length($msgdata) - $old_length);
371         # Don't support re-encryption.
372         $rec->data($rec->decrypt_data);
373
374         #Update the fragment len in case we changed it above
375         ${$self->message_frag_lens}[0] = length($msgdata)
376                                          - TLS_MESSAGE_HEADER_LENGTH;
377         return;
378     }
379
380     #Note we don't currently support changing a fragmented message length
381     my $recctr = 0;
382     my $datadone = 0;
383     foreach my $rec (@{$self->records}) {
384         my $recdata = $rec->decrypt_data;
385         if ($recctr == 0) {
386             #This is the first record
387             my $remainlen = length($recdata) - $self->startoffset;
388             $rec->data(substr($recdata, 0, $self->startoffset)
389                        .substr(($msgdata), 0, $remainlen));
390             $datadone += $remainlen;
391         } elsif ($recctr + 1 == $numrecs) {
392             #This is the last record
393             $rec->data(substr($msgdata, $datadone));
394         } else {
395             #This is a middle record
396             $rec->data(substr($msgdata, $datadone, length($rec->data)));
397             $datadone += length($rec->data);
398         }
399         $recctr++;
400     }
401 }
402
403 #To be overridden by sub-classes
404 sub set_message_contents
405 {
406 }
407
408 #Read only accessors
409 sub server
410 {
411     my $self = shift;
412     return $self->{server};
413 }
414
415 #Read/write accessors
416 sub mt
417 {
418     my $self = shift;
419     if (@_) {
420       $self->{mt} = shift;
421     }
422     return $self->{mt};
423 }
424 sub data
425 {
426     my $self = shift;
427     if (@_) {
428       $self->{data} = shift;
429     }
430     return $self->{data};
431 }
432 sub records
433 {
434     my $self = shift;
435     if (@_) {
436       $self->{records} = shift;
437     }
438     return $self->{records};
439 }
440 sub startoffset
441 {
442     my $self = shift;
443     if (@_) {
444       $self->{startoffset} = shift;
445     }
446     return $self->{startoffset};
447 }
448 sub message_frag_lens
449 {
450     my $self = shift;
451     if (@_) {
452       $self->{message_frag_lens} = shift;
453     }
454     return $self->{message_frag_lens};
455 }
456 sub encoded_length
457 {
458     my $self = shift;
459     return TLS_MESSAGE_HEADER_LENGTH + length($self->data);
460 }
461
462 1;