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