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