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