openssl/util/perl/TLSProxy/Record.pm

402 lines
9.3 KiB
Perl
Raw Normal View History

# Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the OpenSSL license (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 => 5;
#Record types
use constant {
RT_APPLICATION_DATA => 23,
RT_HANDSHAKE => 22,
RT_ALERT => 21,
RT_CCS => 20,
RT_UNKNOWN => 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 => 0x0305,
VERS_TLS_1_3 => 0x0304,
VERS_TLS_1_2 => 0x0303,
VERS_TLS_1_1 => 0x0302,
VERS_TLS_1_0 => 0x0301,
VERS_SSL_3_0 => 0x0300,
VERS_SSL_LT_3_0 => 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<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) > 0) {
print " Record $recnum ", $server ? "(server -> client)\n"
: "(client -> 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) < 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->new(
$flight,
$content_type,
$version,
$len,
0,
$len, # len_real
$len, # decrypt_len
$data, # data
$data # decrypt_data
);
if ($content_type != RT_CCS
&& (!TLSProxy::Proxy->is_tls13()
|| $content_type != RT_ALERT)) {
if (($server && $server_encrypting)
|| (!$server && $client_encrypting)) {
if (!TLSProxy::Proxy->is_tls13() && $etm) {
$record->decryptETM();
} else {
$record->decrypt();
}
$record->encrypted(1);
if (TLSProxy::Proxy->is_tls13()) {
print " Inner content type: "
.$record_type{$record->content_type()}."\n";
}
}
}
push @record_list, $record;
#Now figure out what messages are contained within this record
my @messages = TLSProxy::Message->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 => $flight,
content_type => $content_type,
version => $version,
len => $len,
sslv2 => $sslv2,
len_real => $len_real,
decrypt_len => $decrypt_len,
data => $data,
decrypt_data => $decrypt_data,
orig_decrypt_data => $decrypt_data,
sent => 0,
encrypted => 0,
outer_content_type => RT_APPLICATION_DATA
};
return bless $self, $class;
}
#Decrypt using encrypt-then-MAC
sub decryptETM
{
my ($self) = shift;
my $data = $self->data;
if($self->version >= 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->decrypt_data($data);
$self->decrypt_len(length($data));
return $data;
}
#Standard decrypt
sub decrypt()
{
my ($self) = shift;
my $mactaglen = 20;
my $data = $self->data;
#Throw away any IVs
if (TLSProxy::Proxy->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->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->version >= 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->is_tls13()) {
#Get the content type
my $content_type = unpack("C", substr($data, length($data) - 1));
$self->content_type($content_type);
$data = substr($data, 0, length($data) - 1);
}
$self->decrypt_data($data);
$self->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->{sent} || ($self->flight & 1) != $server) {
return "";
}
$self->{sent} = 1;
if ($self->sslv2) {
$data = pack('n', $self->len | 0x8000);
} else {
if (TLSProxy::Proxy->is_tls13() && $self->encrypted) {
$data = pack('Cnn', $self->outer_content_type, $self->version,
Remove a spurious TLSProxy byte in TLSv1.3 When the proxy re-encrypted a TLSv1.3 record it was adding a spurious byte onto the end. This commit removes that. The "extra" byte was intended to be the inner content type of the record. However, TLSProxy was actually adding the original encrypted data into the record (which already has the inner content type in it) and then adding the spurious additional content type byte on the end (and adjusting the record length accordingly). It is interesting to look at why this didn't cause a failure: The receiving peer first attempts to decrypt the data. Because this is TLSProxy we always use a GCM based ciphersuite with a 16 byte tag. When we decrypt this it actually gets diverted to the ossltest engine. All this does is go through the motions of encrypting/decrypting but just passes back the original data. Crucially it will never fail because of a bad tag! The receiving party thinks the spurious additional byte is part of the tag and the ossltest engine ignores it. This means the data that gets passed back to the record layer still has an additional spurious byte on it - but because the 16 byte tag has been removed, this is actually the first byte of the original tag. Again because we are using ossltest engine we aren't actually creating "real" tags - we only ever emit 16, 0 bytes for the tag. So the spurious additional byte always has the value 0. The TLSv1.3 spec says that records can have additional 0 bytes on the end of them - this is "padding". So the record layer interprets this 0 byte as padding and strips it off to end up with the originally transmitted record data - which it can now process successfully. Reviewed-by: Bernd Edlinger <bernd.edlinger@hotmail.de> (Merged from https://github.com/openssl/openssl/pull/5370)
2018-02-14 17:29:32 +00:00
$self->len);
} else {
$data = pack('Cnn', $self->content_type, $self->version,
$self->len);
}
}
$data .= $self->data;
return $data;
}
#Read only accessors
sub flight
{
my $self = shift;
return $self->{flight};
}
sub sslv2
{
my $self = shift;
return $self->{sslv2};
}
sub len_real
{
my $self = shift;
return $self->{len_real};
}
sub orig_decrypt_data
{
my $self = shift;
return $self->{orig_decrypt_data};
}
#Read/write accessors
sub decrypt_len
{
my $self = shift;
if (@_) {
$self->{decrypt_len} = shift;
}
return $self->{decrypt_len};
}
sub data
{
my $self = shift;
if (@_) {
$self->{data} = shift;
}
return $self->{data};
}
sub decrypt_data
{
my $self = shift;
if (@_) {
$self->{decrypt_data} = shift;
}
return $self->{decrypt_data};
}
sub len
{
my $self = shift;
if (@_) {
$self->{len} = shift;
}
return $self->{len};
}
sub version
{
my $self = shift;
if (@_) {
$self->{version} = shift;
}
return $self->{version};
}
sub content_type
{
my $self = shift;
if (@_) {
$self->{content_type} = shift;
}
return $self->{content_type};
}
sub encrypted
{
my $self = shift;
if (@_) {
$self->{encrypted} = shift;
}
return $self->{encrypted};
}
sub outer_content_type
{
my $self = shift;
if (@_) {
$self->{outer_content_type} = shift;
}
return $self->{outer_content_type};
}
sub is_fatal_alert
{
my $self = shift;
my $server = shift;
if (($self->{flight} & 1) == $server
&& $self->{content_type} == TLSProxy::Record::RT_ALERT) {
my ($level, $alert) = unpack('CC', $self->decrypt_data);
return $alert if ($level == 2);
}
return 0;
}
1;