<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

use strict;

use TLSProxy::Proxy;

package TLSProxy::Record;

my $server_encrypting = 0;
my $client_encrypting = 0;
my $etm = 0;

use constant TLS_RECORD_HEADER_LENGTH =&gt; 5;

#Record types
use constant {
    RT_APPLICATION_DATA =&gt; 23,
    RT_HANDSHAKE =&gt; 22,
    RT_ALERT =&gt; 21,
    RT_CCS =&gt; 20,
    RT_UNKNOWN =&gt; 100
};

my %record_type = (
    RT_APPLICATION_DATA, "APPLICATION DATA",
    RT_HANDSHAKE, "HANDSHAKE",
    RT_ALERT, "ALERT",
    RT_CCS, "CCS",
    RT_UNKNOWN, "UNKNOWN"
);

use constant {
    VERS_TLS_1_4 =&gt; 0x0305,
    VERS_TLS_1_3 =&gt; 0x0304,
    VERS_TLS_1_2 =&gt; 0x0303,
    VERS_TLS_1_1 =&gt; 0x0302,
    VERS_TLS_1_0 =&gt; 0x0301,
    VERS_SSL_3_0 =&gt; 0x0300,
    VERS_SSL_LT_3_0 =&gt; 0x02ff
};

my %tls_version = (
    VERS_TLS_1_3, "TLS1.3",
    VERS_TLS_1_2, "TLS1.2",
    VERS_TLS_1_1, "TLS1.1",
    VERS_TLS_1_0, "TLS1.0",
    VERS_SSL_3_0, "SSL3",
    VERS_SSL_LT_3_0, "SSL&lt;3"
);

#Class method to extract records from a packet of data
sub get_records
{
    my $class = shift;
    my $server = shift;
    my $flight = shift;
    my $packet = shift;
    my $partial = "";
    my @record_list = ();
    my @message_list = ();

    my $recnum = 1;
    while (length ($packet) &gt; 0) {
        print " Record $recnum ", $server ? "(server -&gt; client)\n"
                                          : "(client -&gt; server)\n";

        #Get the record header (unpack can't fail if $packet is too short)
        my ($content_type, $version, $len) = unpack('Cnn', $packet);

        if (length($packet) &lt; TLS_RECORD_HEADER_LENGTH + ($len // 0)) {
            print "Partial data : ".length($packet)." bytes\n";
            $partial = $packet;
            last;
        }

        my $data = substr($packet, TLS_RECORD_HEADER_LENGTH, $len);

        print "  Content type: ".$record_type{$content_type}."\n";
        print "  Version: $tls_version{$version}\n";
        print "  Length: $len\n";

        my $record = TLSProxy::Record-&gt;new(
            $flight,
            $content_type,
            $version,
            $len,
            0,
            $len,       # len_real
            $len,       # decrypt_len
            $data,      # data
            $data       # decrypt_data
        );

        if ($content_type != RT_CCS
                &amp;&amp; (!TLSProxy::Proxy-&gt;is_tls13()
                    || $content_type != RT_ALERT)) {
            if (($server &amp;&amp; $server_encrypting)
                     || (!$server &amp;&amp; $client_encrypting)) {
                if (!TLSProxy::Proxy-&gt;is_tls13() &amp;&amp; $etm) {
                    $record-&gt;decryptETM();
                } else {
                    $record-&gt;decrypt();
                }
                $record-&gt;encrypted(1);

                if (TLSProxy::Proxy-&gt;is_tls13()) {
                    print "  Inner content type: "
                          .$record_type{$record-&gt;content_type()}."\n";
                }
            }
        }

        push @record_list, $record;

        #Now figure out what messages are contained within this record
        my @messages = TLSProxy::Message-&gt;get_messages($server, $record);
        push @message_list, @messages;

        $packet = substr($packet, TLS_RECORD_HEADER_LENGTH + $len);
        $recnum++;
    }

    return (\@record_list, \@message_list, $partial);
}

sub clear
{
    $server_encrypting = 0;
    $client_encrypting = 0;
}

#Class level accessors
sub server_encrypting
{
    my $class = shift;
    if (@_) {
      $server_encrypting = shift;
    }
    return $server_encrypting;
}
sub client_encrypting
{
    my $class = shift;
    if (@_) {
      $client_encrypting= shift;
    }
    return $client_encrypting;
}
#Enable/Disable Encrypt-then-MAC
sub etm
{
    my $class = shift;
    if (@_) {
      $etm = shift;
    }
    return $etm;
}

sub new
{
    my $class = shift;
    my ($flight,
        $content_type,
        $version,
        $len,
        $sslv2,
        $len_real,
        $decrypt_len,
        $data,
        $decrypt_data) = @_;

    my $self = {
        flight =&gt; $flight,
        content_type =&gt; $content_type,
        version =&gt; $version,
        len =&gt; $len,
        sslv2 =&gt; $sslv2,
        len_real =&gt; $len_real,
        decrypt_len =&gt; $decrypt_len,
        data =&gt; $data,
        decrypt_data =&gt; $decrypt_data,
        orig_decrypt_data =&gt; $decrypt_data,
        sent =&gt; 0,
        encrypted =&gt; 0,
        outer_content_type =&gt; RT_APPLICATION_DATA
    };

    return bless $self, $class;
}

#Decrypt using encrypt-then-MAC
sub decryptETM
{
    my ($self) = shift;

    my $data = $self-&gt;data;

    if($self-&gt;version &gt;= VERS_TLS_1_1()) {
        #TLS1.1+ has an explicit IV. Throw it away
        $data = substr($data, 16);
    }

    #Throw away the MAC (assumes MAC is 20 bytes for now. FIXME)
    $data = substr($data, 0, length($data) - 20);

    #Find out what the padding byte is
    my $padval = unpack("C", substr($data, length($data) - 1));

    #Throw away the padding
    $data = substr($data, 0, length($data) - ($padval + 1));

    $self-&gt;decrypt_data($data);
    $self-&gt;decrypt_len(length($data));

    return $data;
}

#Standard decrypt
sub decrypt()
{
    my ($self) = shift;
    my $mactaglen = 20;
    my $data = $self-&gt;data;

    #Throw away any IVs
    if (TLSProxy::Proxy-&gt;is_tls13()) {
        #A TLS1.3 client, when processing the server's initial flight, could
        #respond with either an encrypted or an unencrypted alert.
        if ($self-&gt;content_type() == RT_ALERT) {
            #TODO(TLS1.3): Eventually it is sufficient just to check the record
            #content type. If an alert is encrypted it will have a record
            #content type of application data. However we haven't done the
            #record layer changes yet, so it's a bit more complicated. For now
            #we will additionally check if the data length is 2 (1 byte for
            #alert level, 1 byte for alert description). If it is, then this is
            #an unencrypted alert, so don't try to decrypt
            return $data if (length($data) == 2);
        }
        $mactaglen = 16;
    } elsif ($self-&gt;version &gt;= VERS_TLS_1_1()) {
        #16 bytes for a standard IV
        $data = substr($data, 16);

        #Find out what the padding byte is
        my $padval = unpack("C", substr($data, length($data) - 1));

        #Throw away the padding
        $data = substr($data, 0, length($data) - ($padval + 1));
    }

    #Throw away the MAC or TAG
    $data = substr($data, 0, length($data) - $mactaglen);

    if (TLSProxy::Proxy-&gt;is_tls13()) {
        #Get the content type
        my $content_type = unpack("C", substr($data, length($data) - 1));
        $self-&gt;content_type($content_type);
        $data = substr($data, 0, length($data) - 1);
    }

    $self-&gt;decrypt_data($data);
    $self-&gt;decrypt_len(length($data));

    return $data;
}

#Reconstruct the on-the-wire record representation
sub reconstruct_record
{
    my $self = shift;
    my $server = shift;
    my $data;

    #We only replay the records in the same direction
    if ($self-&gt;{sent} || ($self-&gt;flight &amp; 1) != $server) {
        return "";
    }
    $self-&gt;{sent} = 1;

    if ($self-&gt;sslv2) {
        $data = pack('n', $self-&gt;len | 0x8000);
    } else {
        if (TLSProxy::Proxy-&gt;is_tls13() &amp;&amp; $self-&gt;encrypted) {
            $data = pack('Cnn', $self-&gt;outer_content_type, $self-&gt;version,
                         $self-&gt;len);
        } else {
            $data = pack('Cnn', $self-&gt;content_type, $self-&gt;version,
                         $self-&gt;len);
        }

    }
    $data .= $self-&gt;data;

    return $data;
}

#Read only accessors
sub flight
{
    my $self = shift;
    return $self-&gt;{flight};
}
sub sslv2
{
    my $self = shift;
    return $self-&gt;{sslv2};
}
sub len_real
{
    my $self = shift;
    return $self-&gt;{len_real};
}
sub orig_decrypt_data
{
    my $self = shift;
    return $self-&gt;{orig_decrypt_data};
}

#Read/write accessors
sub decrypt_len
{
    my $self = shift;
    if (@_) {
      $self-&gt;{decrypt_len} = shift;
    }
    return $self-&gt;{decrypt_len};
}
sub data
{
    my $self = shift;
    if (@_) {
      $self-&gt;{data} = shift;
    }
    return $self-&gt;{data};
}
sub decrypt_data
{
    my $self = shift;
    if (@_) {
      $self-&gt;{decrypt_data} = shift;
    }
    return $self-&gt;{decrypt_data};
}
sub len
{
    my $self = shift;
    if (@_) {
      $self-&gt;{len} = shift;
    }
    return $self-&gt;{len};
}
sub version
{
    my $self = shift;
    if (@_) {
      $self-&gt;{version} = shift;
    }
    return $self-&gt;{version};
}
sub content_type
{
    my $self = shift;
    if (@_) {
      $self-&gt;{content_type} = shift;
    }
    return $self-&gt;{content_type};
}
sub encrypted
{
    my $self = shift;
    if (@_) {
      $self-&gt;{encrypted} = shift;
    }
    return $self-&gt;{encrypted};
}
sub outer_content_type
{
    my $self = shift;
    if (@_) {
      $self-&gt;{outer_content_type} = shift;
    }
    return $self-&gt;{outer_content_type};
}
sub is_fatal_alert
{
    my $self = shift;
    my $server = shift;

    if (($self-&gt;{flight} &amp; 1) == $server
        &amp;&amp; $self-&gt;{content_type} == TLSProxy::Record::RT_ALERT) {
        my ($level, $alert) = unpack('CC', $self-&gt;decrypt_data);
        return $alert if ($level == 2);
    }
    return 0;
}
1;
</pre></body></html>