mirror of
https://github.com/drwetter/testssl.sh.git
synced 2026-09-14 13:48:16 +02:00
167 lines
4.9 KiB
Perl
Executable File
167 lines
4.9 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#
|
|
# tls_server_client_test.pl
|
|
#
|
|
# Starts `openssl s_server` in the background (safe under GitHub Actions:
|
|
# strips RUNNER_TRACKING_ID so the runner's cleanup sweep won't reap it,
|
|
# and redirects stdin from /dev/null with -ign_eof so s_server doesn't
|
|
# quit after the first connection), waits until it's actually accepting
|
|
# connections, then drives one or more `openssl s_client` checks against
|
|
# it. Server is always killed on the way out, success or failure.
|
|
#
|
|
# Host, port, and the self-signed cert/key are all generated/fixed here
|
|
# rather than taken from the command line.
|
|
#
|
|
# Usage:
|
|
# ./tls_server_client_test.pl
|
|
#
|
|
use strict;
|
|
use warnings;
|
|
use IPC::Run3;
|
|
use File::Temp qw(tempdir);
|
|
|
|
use constant {
|
|
HOST => '127.0.0.1',
|
|
PORT => 4433,
|
|
};
|
|
|
|
my $LOG = 's_server.' . PORT . '.log';
|
|
|
|
# tempdir with CLEANUP => 1 removes itself (and the cert/key inside it)
|
|
# when the script exits, normally or via die/signal.
|
|
my $CERT_DIR = tempdir(CLEANUP => 1);
|
|
my $CERT = "$CERT_DIR/cert.pem";
|
|
my $KEY = "$CERT_DIR/key.pem";
|
|
|
|
generate_cert($CERT, $KEY);
|
|
print "generated self-signed cert/key in $CERT_DIR\n";
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Start the server
|
|
# ---------------------------------------------------------------------
|
|
my $server_pid = start_server(HOST, PORT, $CERT, $KEY, $LOG);
|
|
print "started s_server pid=$server_pid on " . HOST . ":" . PORT . " (log: $LOG)\n";
|
|
|
|
# Make sure we clean up on normal exit *and* on die()/signal.
|
|
my $cleaned_up = 0;
|
|
local $SIG{__DIE__} = sub { cleanup($server_pid) unless $cleaned_up; };
|
|
local $SIG{INT} = sub { cleanup($server_pid) unless $cleaned_up; exit 1; };
|
|
local $SIG{TERM} = sub { cleanup($server_pid) unless $cleaned_up; exit 1; };
|
|
|
|
my $exit_code = 0;
|
|
eval {
|
|
wait_for_ready(HOST, PORT, 20, 0.5);
|
|
run_client_checks(HOST, PORT);
|
|
1;
|
|
} or do {
|
|
warn "test failed: $@";
|
|
$exit_code = 1;
|
|
};
|
|
|
|
cleanup($server_pid);
|
|
exit $exit_code;
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
sub generate_cert {
|
|
my ($cert, $key) = @_;
|
|
|
|
my ($out, $err);
|
|
run3(
|
|
['openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
|
|
'-keyout', $key, '-out', $cert,
|
|
'-days', '1',
|
|
'-subj', '/CN=localhost',
|
|
'-addext', 'subjectAltName=IP:' . HOST],
|
|
\undef, \$out, \$err,
|
|
);
|
|
|
|
die "cert generation failed:\n$err\n" if $? != 0;
|
|
}
|
|
|
|
sub start_server {
|
|
my ($host, $port, $cert, $key, $log) = @_;
|
|
|
|
my $pid = fork();
|
|
die "fork failed: $!" unless defined $pid;
|
|
|
|
if ($pid == 0) {
|
|
# --- child: becomes openssl s_server ---
|
|
|
|
# Strip the runner's tracking env var so the Actions runner's
|
|
# process-cleanup sweep (which greps for it) doesn't kill us
|
|
# the moment this step/job ends.
|
|
delete local $ENV{RUNNER_TRACKING_ID};
|
|
|
|
# Detach stdin from whatever the parent had (important: s_server
|
|
# treats stdin EOF as "quit after this connection" unless told
|
|
# otherwise). -ign_eof is a second belt-and-suspenders layer.
|
|
open(STDIN, '<', '/dev/null') or die "reopen STDIN: $!";
|
|
open(STDOUT, '>', $log) or die "reopen STDOUT: $!";
|
|
open(STDERR, '>&STDOUT') or die "reopen STDERR: $!";
|
|
|
|
exec('openssl', 's_server',
|
|
'-accept', "$host:$port",
|
|
'-cert', $cert,
|
|
'-key', $key,
|
|
'-ign_eof',
|
|
'-quiet');
|
|
# exec only returns on failure
|
|
die "exec openssl s_server failed: $!";
|
|
}
|
|
|
|
return $pid;
|
|
}
|
|
|
|
sub wait_for_ready {
|
|
my ($host, $port, $tries, $delay) = @_;
|
|
|
|
for my $i (1 .. $tries) {
|
|
my ($out, $err);
|
|
# Empty stdin: just probe the handshake, don't send app data.
|
|
run3(
|
|
['openssl', 's_client', '-connect', "$host:$port"],
|
|
\'', \$out, \$err,
|
|
);
|
|
return 1 if $? == 0;
|
|
select(undef, undef, undef, $delay); # fractional sleep
|
|
}
|
|
|
|
die "server on $host:$port never became ready after $tries attempts\n";
|
|
}
|
|
|
|
sub run_client_checks {
|
|
my ($host, $port) = @_;
|
|
|
|
# Replace this with your real test(s). Shown here: a basic connect
|
|
# that feeds a line of data through and checks openssl's exit code
|
|
# plus captured output. Swap in Test::More ok()/is() calls as needed.
|
|
my ($out, $err);
|
|
run3(
|
|
['testssl.sh', '-p', "$host:$port"],
|
|
\"", \$out, \$err,
|
|
);
|
|
|
|
die "s_client exited non-zero: $?\nstderr:\n$err\n" if $? != 0;
|
|
|
|
print "client check ok, output:\n$out\n";
|
|
}
|
|
|
|
sub cleanup {
|
|
my ($pid) = @_;
|
|
return unless $pid;
|
|
$cleaned_up = 1;
|
|
|
|
if (kill(0, $pid)) { # still alive?
|
|
kill('TERM', $pid);
|
|
# give it a moment, then force it
|
|
for (1 .. 10) {
|
|
last unless kill(0, $pid);
|
|
select(undef, undef, undef, 0.2);
|
|
}
|
|
kill('KILL', $pid) if kill(0, $pid);
|
|
}
|
|
waitpid($pid, 0);
|
|
print "cleaned up s_server pid=$pid\n";
|
|
}
|