mirror of https://github.com/openssl/openssl.git
229 lines
7.5 KiB
Perl
229 lines
7.5 KiB
Perl
#! /usr/bin/env perl
|
||
# Copyright 2024 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 feature 'state';
|
||
|
||
use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
|
||
use OpenSSL::Test::Utils;
|
||
use TLSProxy::Proxy;
|
||
use TLSProxy::Message;
|
||
use Cwd qw(abs_path);
|
||
|
||
my $test_name = "test_dtls13ack";
|
||
setup($test_name);
|
||
|
||
# TODO(DTLSv1.3): The test currently does not work as changes to ´engines/e_ossltest.c´
|
||
# in #25119 should be ported to the ossltest provider.
|
||
plan skip_all => "This doesn't work properly currently";
|
||
plan skip_all => "TLSProxy isn't usable on $^O"
|
||
if $^O =~ /^(VMS)$/ || $^O =~ /^(MSWin32)$/;
|
||
|
||
plan skip_all => "$test_name needs the module feature enabled"
|
||
if disabled("module");
|
||
|
||
plan skip_all => "$test_name needs the sock feature enabled"
|
||
if disabled("sock");
|
||
|
||
plan skip_all => "DTLSProxy does not support partial messages"
|
||
if disabled("ec");
|
||
|
||
plan skip_all => "$test_name needs DTLSv1.3 enabled"
|
||
if disabled("dtls1_3");
|
||
|
||
$ENV{OPENSSL_MODULES} = abs_path(bldtop_dir("test"));
|
||
|
||
my $proxy = TLSProxy::Proxy->new_dtls(
|
||
undef,
|
||
cmdstr(app(["openssl"]), display => 1),
|
||
srctop_file("apps", "server.pem"),
|
||
(!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
|
||
);
|
||
|
||
my $testcount = 3;
|
||
|
||
plan tests => $testcount;
|
||
|
||
#Test 1: Check that records are acked during an uninterrupted handshake
|
||
$proxy->serverflags("-min_protocol DTLSv1.3 -max_protocol DTLSv1.3");
|
||
$proxy->clientflags("-min_protocol DTLSv1.3 -max_protocol DTLSv1.3");
|
||
TLSProxy::Message->successondata(1);
|
||
skip "TLSProxy could not start", $testcount if !$proxy->start();
|
||
|
||
my @expected = get_expected_ack_record_numbers();
|
||
my @actual = get_actual_acked_record_numbers();
|
||
my @missing = record_numbers_missing(\@expected, \@actual);
|
||
my $expected_count = @expected;
|
||
my $missing_count = @missing;
|
||
|
||
ok($missing_count == 0 && $expected_count == 1,
|
||
"Check that all record numbers are acked");
|
||
|
||
# Test 2: Check that records that are missing are not acked during a handshake
|
||
$proxy->clear();
|
||
my $found_first_client_finish_msg = 0;
|
||
$proxy->serverflags("-min_protocol DTLSv1.3 -max_protocol DTLSv1.3");
|
||
$proxy->clientflags("-min_protocol DTLSv1.3 -max_protocol DTLSv1.3");
|
||
$proxy->filter(\&drop_first_client_finish_filter);
|
||
TLSProxy::Message->successondata(1);
|
||
$proxy->start();
|
||
|
||
@expected = get_expected_ack_record_numbers();
|
||
@actual = get_actual_acked_record_numbers();
|
||
@missing = record_numbers_missing(\@expected, \@actual);
|
||
$expected_count = @expected;
|
||
$missing_count = @missing;
|
||
|
||
ok($missing_count == 1 && $expected_count == 2,
|
||
"Check that all record numbers except one are acked");
|
||
|
||
SKIP: {
|
||
skip "TODO(DTLSv1.3): This test fails because the client does not properly
|
||
handle when the last flight is dropped when it includes a
|
||
CompressedCertificate.", 1
|
||
if !disabled("zlib") || !disabled("zstd") || !disabled("brotli");
|
||
# Test 3: Check that client cert and verify messages are also acked
|
||
$proxy->clear();
|
||
$proxy->filter(undef);
|
||
$found_first_client_finish_msg = 0;
|
||
$proxy->serverflags("-min_protocol DTLSv1.3 -max_protocol DTLSv1.3 -Verify 1");
|
||
$proxy->clientflags("-mtu 2000 -min_protocol DTLSv1.3 -max_protocol DTLSv1.3"
|
||
." -cert ".srctop_file("apps", "server.pem"));
|
||
TLSProxy::Message->successondata(1);
|
||
$proxy->start();
|
||
|
||
@expected = get_expected_ack_record_numbers();
|
||
@actual = get_actual_acked_record_numbers();
|
||
@missing = record_numbers_missing(\@expected, \@actual);
|
||
$expected_count = @expected;
|
||
$missing_count = @missing;
|
||
|
||
ok($missing_count == 0 && $expected_count == 3,
|
||
"Check that all record numbers are acked");
|
||
}
|
||
|
||
sub get_expected_ack_record_numbers
|
||
{
|
||
my $records = $proxy->record_list;
|
||
my @record_numbers = ();
|
||
|
||
foreach (@{$records}) {
|
||
my $record = $_;
|
||
|
||
if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE
|
||
&& $record->{sent}) {
|
||
my $epoch = $record->epoch;
|
||
my $seqnum = $record->seq;
|
||
my $serverissender = $record->serverissender;
|
||
my $recnum = TLSProxy::RecordNumber->new($epoch, $seqnum);
|
||
|
||
my @messages = TLSProxy::Message->get_messages($record);
|
||
|
||
my $record_should_be_acked = 0;
|
||
|
||
foreach (@messages) {
|
||
my $message = $_;
|
||
if (!$serverissender
|
||
&& ($message->mt == TLSProxy::Message::MT_FINISHED
|
||
|| $message->mt == TLSProxy::Message::MT_CERTIFICATE
|
||
|| $message->mt == TLSProxy::Message::MT_COMPRESSED_CERTIFICATE
|
||
|| $message->mt == TLSProxy::Message::MT_CERTIFICATE_VERIFY)
|
||
# TODO(DTLSv1.3): The ACK of the following messages are never processed
|
||
# by the proxy because s_client is closed too early send it:
|
||
# || $message->mt == TLSProxy::Message::MT_KEY_UPDATE
|
||
# || $message->mt == TLSProxy::Message::MT_NEW_SESSION_TICKET
|
||
) {
|
||
$record_should_be_acked = 1;
|
||
}
|
||
}
|
||
|
||
push(@record_numbers, $recnum) if ($record_should_be_acked == 1);
|
||
}
|
||
}
|
||
|
||
return @record_numbers;
|
||
}
|
||
|
||
sub get_actual_acked_record_numbers
|
||
{
|
||
my @records = @{$proxy->record_list};
|
||
my @record_numbers = ();
|
||
|
||
foreach (@records) {
|
||
my $record = $_;
|
||
|
||
if ($record->content_type == TLSProxy::Record::RT_ACK) {
|
||
my $recnum_count = unpack('n', $record->decrypt_data) / 16;
|
||
my $ptr = 2;
|
||
|
||
for (my $idx = 0; $idx < $recnum_count; $idx++) {
|
||
my $epoch_lo;
|
||
my $epoch_hi;
|
||
my $msgseq_lo;
|
||
my $msgseq_hi;
|
||
|
||
($epoch_hi, $epoch_lo, $msgseq_hi, $msgseq_lo)
|
||
= unpack('NNNN', substr($record->decrypt_data, $ptr));
|
||
$ptr = $ptr + 16;
|
||
|
||
my $epoch = ($epoch_hi << 32) | $epoch_lo;
|
||
my $msgseq = ($msgseq_hi << 32) | $msgseq_lo;
|
||
my $recnum = TLSProxy::RecordNumber->new($epoch, $msgseq);
|
||
|
||
push(@record_numbers, $recnum);
|
||
}
|
||
}
|
||
}
|
||
return @record_numbers;
|
||
}
|
||
|
||
sub record_numbers_missing
|
||
{
|
||
my @expected_record_numbers = @{$_[0]};
|
||
my @actual_record_numbers = @{$_[1]};
|
||
my @missing_record_numbers = ();
|
||
|
||
foreach (@expected_record_numbers)
|
||
{
|
||
my $found = 0;
|
||
my $expected = $_;
|
||
|
||
foreach (@actual_record_numbers) {
|
||
my $actual = $_;
|
||
if ($actual->epoch() == $expected->epoch()
|
||
&& $actual->seqnum() == $expected->seqnum()) {
|
||
$found = 1
|
||
}
|
||
}
|
||
|
||
if ($found == 0) {
|
||
push(@missing_record_numbers, $expected);
|
||
}
|
||
}
|
||
|
||
return @missing_record_numbers;
|
||
}
|
||
|
||
sub drop_first_client_finish_filter
|
||
{
|
||
my $inproxy = shift;
|
||
|
||
foreach my $record (@{$inproxy->record_list}) {
|
||
next if ($record->{sent} == 1 || $record->serverissender || $found_first_client_finish_msg == 1);
|
||
|
||
my @messages = TLSProxy::Message->get_messages($record);
|
||
foreach my $message (@messages) {
|
||
if ($message->mt == TLSProxy::Message::MT_FINISHED) {
|
||
$record->{sent} = 1;
|
||
$found_first_client_finish_msg = 1;
|
||
last;
|
||
}
|
||
}
|
||
}
|
||
}
|