mirror of
https://github.com/openssl/openssl.git
synced 2025-01-18 13:44:20 +08:00
37a954e5c3
output to stderr is unbuffered bypassing the normal output, which does not happen at line boundaries and is therefore confusing the TAP parser. This is known to cause random test failures like this one: 80-test_cmp_http.t (Wstat: 0 Tests: 5 Failed: 0) Parse errors: Tests out of sequence. Found (6) but expected (5) Bad plan. You planned 6 tests but ran 5. Fixes #23992 Reviewed-by: Tom Cosgrove <tom.cosgrove@arm.com> Reviewed-by: Saša Nedvědický <sashan@openssl.org> (Merged from https://github.com/openssl/openssl/pull/26363)
340 lines
14 KiB
Perl
340 lines
14 KiB
Perl
#! /usr/bin/env perl
|
|
# Copyright 2007-2023 The OpenSSL Project Authors. All Rights Reserved.
|
|
# Copyright Nokia 2007-2019
|
|
# Copyright Siemens AG 2015-2019
|
|
#
|
|
# 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 warnings;
|
|
|
|
use POSIX;
|
|
use OpenSSL::Test qw/:DEFAULT cmdstr data_file data_dir srctop_dir bldtop_dir result_dir/;
|
|
use OpenSSL::Test::Utils;
|
|
|
|
BEGIN {
|
|
setup("test_cmp_http");
|
|
}
|
|
use lib srctop_dir('Configurations');
|
|
use lib bldtop_dir('.');
|
|
|
|
plan skip_all => "These tests are not supported in a fuzz build"
|
|
if config('options') =~ /-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION|enable-fuzz-afl/;
|
|
|
|
plan skip_all => "These tests are not supported in a no-cmp build"
|
|
if disabled("cmp");
|
|
plan skip_all => "These tests are not supported in a no-ecx build"
|
|
if disabled("ecx"); # EC and EDDSA test certs, e.g., in Mock/newWithNew.pem
|
|
plan skip_all => "These tests are not supported in a no-sock build"
|
|
if disabled("sock");
|
|
plan skip_all => "These tests are not supported in a no-http build"
|
|
if disabled("http");
|
|
|
|
plan skip_all => "Tests involving local HTTP server not available on Windows or VMS"
|
|
if $^O =~ /^(VMS|MSWin32|msys)$/;
|
|
plan skip_all => "Tests involving local HTTP server not available in cross-compile builds"
|
|
if defined $ENV{EXE_SHELL};
|
|
|
|
sub chop_dblquot { # chop any leading and trailing '"' (needed for Windows)
|
|
my $str = shift;
|
|
$str =~ s/^\"(.*?)\"$/$1/;
|
|
return $str;
|
|
}
|
|
|
|
my $proxy = chop_dblquot($ENV{http_proxy} // $ENV{HTTP_PROXY} // "");
|
|
$proxy = "<EMPTY>" if $proxy eq "";
|
|
$proxy =~ s{^https?://}{}i;
|
|
my $no_proxy = $ENV{no_proxy} // $ENV{NO_PROXY};
|
|
|
|
my @app = qw(openssl cmp);
|
|
|
|
# the server-dependent client configuration consists of:
|
|
my $ca_dn; # The CA's Distinguished Name
|
|
my $server_dn; # The server's Distinguished Name
|
|
my $server_host;# The server's hostname or IP address
|
|
my $server_port;# The server's port
|
|
my $server_tls; # The server's TLS port, if any, or 0
|
|
my $server_path;# The server's CMP alias
|
|
my $server_cert;# The server's cert
|
|
my $kur_port; # The server's port for kur (cert update)
|
|
my $pbm_port; # The server port to be used for PBM
|
|
my $pbm_ref; # The reference for PBM
|
|
my $pbm_secret; # The secret for PBM
|
|
my $column; # The column number of the expected result
|
|
my $sleep = 0; # The time to sleep between two requests
|
|
|
|
# the dynamic server info:
|
|
my $server_fh; # Server file handle
|
|
|
|
sub subst_env {
|
|
my $val = shift;
|
|
return '""""' if $val eq "";
|
|
return $ENV{$1} if $val =~ /^\$\{ENV::(\w+)}$/;
|
|
return $val;
|
|
}
|
|
|
|
# The local $server_name variables in the subroutines below are used
|
|
# both as the name of a sub-directory with server-specific credentials
|
|
# and as the name of a server-dependent client config section.
|
|
|
|
sub load_config {
|
|
my $server_name = shift;
|
|
my $section = shift;
|
|
my $test_config = $ENV{OPENSSL_CMP_CONFIG} // "$server_name/test.cnf";
|
|
open (CH, $test_config) or die "Cannot open $test_config: $!";
|
|
my $active = 0;
|
|
while (<CH>) {
|
|
if (m/\[\s*$section\s*\]/) {
|
|
$active = 1;
|
|
} elsif (m/\[\s*.*?\s*\]/) {
|
|
$active = 0;
|
|
} elsif ($active) {
|
|
# if there are multiple entries with same key, the last one prevails
|
|
$ca_dn = subst_env($1) if m/^\s*ca_dn\s*=\s*(.*)?\s*$/;
|
|
$server_dn = subst_env($1) if m/^\s*server_dn\s*=\s*(.*)?\s*$/;
|
|
$server_host = subst_env($1) if m/^\s*server_host\s*=\s*(\S*)?\s*(\#.*)?$/;
|
|
$server_port = subst_env($1) if m/^\s*server_port\s*=\s*(\S*)?\s*(\#.*)?$/;
|
|
$server_tls = subst_env($1) if m/^\s*server_tls\s*=\s*(.*)?\s*$/;
|
|
$server_path = subst_env($1) if m/^\s*server_path\s*=\s*(.*)?\s*$/;
|
|
$server_cert = subst_env($1) if m/^\s*server_cert\s*=\s*(.*)?\s*$/;
|
|
$kur_port = subst_env($1) if m/^\s*kur_port\s*=\s*(.*)?\s*$/;
|
|
$pbm_port = subst_env($1) if m/^\s*pbm_port\s*=\s*(.*)?\s*$/;
|
|
$pbm_ref = subst_env($1) if m/^\s*pbm_ref\s*=\s*(.*)?\s*$/;
|
|
$pbm_secret = subst_env($1) if m/^\s*pbm_secret\s*=\s*(.*)?\s*$/;
|
|
$column = subst_env($1) if m/^\s*column\s*=\s*(.*)?\s*$/;
|
|
$sleep = subst_env($1) if m/^\s*sleep\s*=\s*(.*)?\s*$/;
|
|
}
|
|
}
|
|
close CH;
|
|
die "Cannot find all server-dependent config values in $test_config section [$section]\n"
|
|
if !defined $ca_dn
|
|
|| !defined $server_dn || !defined $server_host
|
|
|| !defined $server_port || !defined $server_tls
|
|
|| !defined $server_path || !defined $server_cert
|
|
|| !defined $kur_port || !defined $pbm_port
|
|
|| !defined $pbm_ref || !defined $pbm_secret
|
|
|| !defined $column || !defined $sleep;
|
|
die "Invalid server_port number in $test_config section [$section]: $server_port"
|
|
unless $server_port =~ m/^\d+$/;
|
|
$server_dn = $server_dn // $ca_dn;
|
|
}
|
|
|
|
my @server_configurations = ("Mock");
|
|
@server_configurations = split /\s+/, $ENV{OPENSSL_CMP_SERVER} if $ENV{OPENSSL_CMP_SERVER};
|
|
# set env variable, e.g., OPENSSL_CMP_SERVER="Mock Insta" to include further CMP servers
|
|
|
|
my @all_aspects = ("connection", "verification", "credentials", "commands", "enrollment");
|
|
@all_aspects = split /\s+/, $ENV{OPENSSL_CMP_ASPECTS} if $ENV{OPENSSL_CMP_ASPECTS};
|
|
# set env variable, e.g., OPENSSL_CMP_ASPECTS="commands enrollment" to select specific aspects
|
|
|
|
my $faillog;
|
|
my $file = $ENV{HARNESS_FAILLOG}; # pathname relative to result_dir
|
|
if ($file) {
|
|
open($faillog, ">", $file) or die "Cannot open '$file' for writing: $!";
|
|
}
|
|
|
|
sub test_cmp_http {
|
|
my $server_name = shift;
|
|
my $aspect = shift;
|
|
my $n = shift;
|
|
my $i = shift;
|
|
my $title = shift;
|
|
my $params = shift;
|
|
my $expected_result = shift;
|
|
$params = [ '-server', "$server_host:$server_port", @$params ]
|
|
if ($server_name eq "Mock" && !(grep { $_ eq '-server' } @$params));
|
|
my $cmd = app([@app, @$params]);
|
|
|
|
unless (is(my $actual_result = run($cmd), $expected_result, $title)) {
|
|
if ($faillog) {
|
|
my $quote_spc_empty = sub { $_ eq "" ? '""' : $_ =~ m/ / ? '"'.$_.'"' : $_ };
|
|
my $invocation = cmdstr($cmd, display => 1);
|
|
print $faillog "$server_name $aspect \"$title\" ($i/$n)".
|
|
" expected=$expected_result (".
|
|
($expected_result ? "success" : "failure").")".
|
|
" actual=$actual_result\n";
|
|
print $faillog "$invocation\n\n";
|
|
}
|
|
sleep($sleep) if $expected_result == 1;
|
|
}
|
|
}
|
|
|
|
sub test_cmp_http_aspect {
|
|
my $server_name = shift;
|
|
my $aspect = shift;
|
|
my $tests = shift;
|
|
subtest "CMP app CLI $server_name $aspect\n" => sub {
|
|
my $n = scalar @$tests;
|
|
plan tests => $n;
|
|
my $i = 1;
|
|
foreach (@$tests) {
|
|
test_cmp_http($server_name, $aspect, $n, $i++, $$_[0], $$_[1], $$_[2]);
|
|
}
|
|
};
|
|
# not unlinking test.cert.pem, test.cacerts.pem, and test.extracerts.pem
|
|
}
|
|
|
|
# The input files for the tests done here dynamically depend on the test server
|
|
# selected (where the mock server used by default is just one possibility).
|
|
# On the other hand the main test configuration file test.cnf, which references
|
|
# several server-dependent input files by relative file names, is static.
|
|
# Moreover the tests use much greater variety of input files than output files.
|
|
# Therefore we chose the current directory as a subdirectory of $SRCTOP and it
|
|
# was simpler to prepend the output file names by BLDTOP than doing the tests
|
|
# from $BLDTOP/test-runs/test_cmp_http and prepending the input files by SRCTOP.
|
|
|
|
indir data_dir() => sub {
|
|
plan tests => 1 + @server_configurations * @all_aspects
|
|
- (grep(/^Mock$/, @server_configurations)
|
|
&& grep(/^certstatus$/, @all_aspects));
|
|
|
|
foreach my $server_name (@server_configurations) {
|
|
$server_name = chop_dblquot($server_name);
|
|
load_config($server_name, $server_name);
|
|
{
|
|
SKIP: {
|
|
my $pid;
|
|
if ($server_name eq "Mock") {
|
|
indir "Mock" => sub {
|
|
$pid = start_server($server_name, "");
|
|
next unless $pid;
|
|
}
|
|
}
|
|
foreach my $aspect (@all_aspects) {
|
|
$aspect = chop_dblquot($aspect);
|
|
if ($server_name eq "Mock" && $aspect eq "certstatus") {
|
|
print "Skipping certstatus check as not supported by $server_name server\n";
|
|
next;
|
|
}
|
|
load_config($server_name, $aspect); # update with any aspect-specific settings
|
|
indir $server_name => sub {
|
|
my $tests = load_tests($server_name, $aspect);
|
|
test_cmp_http_aspect($server_name, $aspect, $tests);
|
|
};
|
|
};
|
|
stop_server($server_name, $pid) if $pid;
|
|
ok(1, "$server_name server has terminated");
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
close($faillog) if $faillog;
|
|
|
|
sub load_tests {
|
|
my $server_name = shift;
|
|
my $aspect = shift;
|
|
my $test_config = $ENV{OPENSSL_CMP_CONFIG} // "$server_name/test.cnf";
|
|
my $file = data_file("test_$aspect.csv");
|
|
my $result_dir = result_dir();
|
|
my @result;
|
|
|
|
open(my $data, '<', $file) || die "Cannot open '$file' for reading: $!";
|
|
LOOP:
|
|
while (my $line = <$data>) {
|
|
chomp $line;
|
|
$line =~ s{\r\n}{\n}g; # adjust line endings
|
|
$line =~ s{_CA_DN}{$ca_dn}g;
|
|
$line =~ s{_SERVER_DN}{$server_dn}g;
|
|
$line =~ s{_SERVER_HOST}{$server_host}g;
|
|
$line =~ s{_SERVER_PORT}{$server_port}g;
|
|
$line =~ s{_SERVER_TLS}{$server_tls}g;
|
|
$line =~ s{_SERVER_PATH}{$server_path}g;
|
|
$line =~ s{_SERVER_CERT}{$server_cert}g;
|
|
$line =~ s{_KUR_PORT}{$kur_port}g;
|
|
$line =~ s{_PBM_PORT}{$pbm_port}g;
|
|
$line =~ s{_PBM_REF}{$pbm_ref}g;
|
|
$line =~ s{_PBM_SECRET}{$pbm_secret}g;
|
|
$line =~ s{_RESULT_DIR}{$result_dir}g;
|
|
|
|
next LOOP if $server_tls == 0 && $line =~ m/,\s*-tls_used\s*,/;
|
|
my $noproxy = $no_proxy;
|
|
my $server_plain = $server_host =~ m/^\[(.*)\]$/ ? $1 : $server_host;
|
|
if ($line =~ m/,\s*-no_proxy\s*,(.*?)(,|$)/) {
|
|
$noproxy = $1;
|
|
} elsif ($server_plain eq "127.0.0.1" || $server_plain eq "::1") {
|
|
# do connections to localhost (e.g., mock server) without proxy
|
|
$line =~ s{-section,,}{-section,,-no_proxy,$server_plain,} ;
|
|
}
|
|
if ($line =~ m/,\s*-proxy\s*,/) {
|
|
next LOOP if $no_proxy && ($noproxy =~ $server_plain);
|
|
} else {
|
|
$line =~ s{-section,,}{-section,,-proxy,$proxy,};
|
|
}
|
|
$line =~ s{-section,,}{-section,,-certout,$result_dir/test.cert.pem,}
|
|
if $aspect ne "commands" || $line =~ m/,\s*-cmd\s*,\s*(ir|cr|p10cr|kur)\s*,/;
|
|
$line =~ s{-section,,}{-config,../$test_config,-section,$server_name $aspect,};
|
|
|
|
my @fields = grep /\S/, split ",", $line;
|
|
s/^<EMPTY>$// for (@fields); # used for proxy=""
|
|
s/^\s+// for (@fields); # remove leading whitespace from elements
|
|
s/\s+$// for (@fields); # remove trailing whitespace from elements
|
|
s/^\"(\".*?\")\"$/$1/ for (@fields); # remove escaping from quotation marks from elements
|
|
my $expected_result = $fields[$column];
|
|
my $description = 1;
|
|
my $title = $fields[$description];
|
|
next LOOP if (!defined($expected_result)
|
|
|| ($expected_result ne 0 && $expected_result ne 1));
|
|
next LOOP if ($line =~ m/-server,\[.*:.*\]/ && !have_IPv6());
|
|
@fields = grep {$_ ne 'BLANK'} @fields[$description + 1 .. @fields - 1];
|
|
push @result, [$title, \@fields, $expected_result];
|
|
}
|
|
close($data);
|
|
return \@result;
|
|
}
|
|
|
|
sub start_server {
|
|
my $server_name = shift;
|
|
my $args = shift; # optional further CLI arguments
|
|
my $cmd = cmdstr(app([@app, '-config', 'server.cnf',
|
|
$args ? $args : ()]), display => 1);
|
|
print "Current directory is ".getcwd()."\n";
|
|
print "Launching $server_name server: $cmd\n";
|
|
my $pid = open($server_fh, "$cmd 2>".result_dir()."/error.txt |");
|
|
unless ($pid) {
|
|
print "Error launching $cmd, cannot obtain $server_name server PID";
|
|
return 0;
|
|
}
|
|
print "$server_name server PID=$pid\n";
|
|
|
|
if ($server_host eq '*' || $server_port == 0) {
|
|
# Find out the actual server host and port and possibly different PID
|
|
my ($host, $port);
|
|
$pid = 0;
|
|
while (<$server_fh>) {
|
|
print "$server_name server output: $_";
|
|
next if m/using section/;
|
|
s/\R$//; # Better chomp
|
|
($host, $port, $pid) = ($1, $2, $3)
|
|
if /^ACCEPT\s(.*?):(\d+) PID=(\d+)$/;
|
|
last; # Do not loop further to prevent hangs on server misbehavior
|
|
}
|
|
if ($server_host eq '*' && defined $host) {
|
|
$server_host = "[::1]" if $host eq "[::]";
|
|
$server_host = "127.0.0.1" if $host eq "0.0.0.0";
|
|
}
|
|
$server_port = $port if $server_port == 0 && defined $port;
|
|
}
|
|
if ($server_host eq '*' || $server_port == 0) {
|
|
stop_server($server_name, $pid) if $pid;
|
|
print "Cannot get expected output from the $server_name server\n";
|
|
return 0;
|
|
}
|
|
$kur_port = $server_port if $kur_port eq "\$server_port";
|
|
$pbm_port = $server_port if $pbm_port eq "\$server_port";
|
|
$server_tls = $server_port if $server_tls;
|
|
return $pid;
|
|
|
|
}
|
|
|
|
sub stop_server {
|
|
my $server_name = shift;
|
|
my $pid = shift;
|
|
print "Killing $server_name server with PID=$pid\n";
|
|
kill('KILL', $pid);
|
|
waitpid($pid, 0);
|
|
}
|