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