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