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