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