#!/usr/bin/env perl # As the name indicates: Check for TLS 1.3 only hosts. It just runs the protocol section, there # it checks for TLS 1.2 (disabled) and TLS 1.3 (enabled) use strict; use warnings; use Test::More; use IO::Socket::INET; use File::Temp qw( tempdir ); use File::Basename; use POSIX qw(WNOHANG); my $port = 1443; my $temp_dir = tempdir(CLEANUP => 1); my $server_script = "$temp_dir/start_server.sh"; my $os="$^O"; # Non-intrusive check: is $ip:$port in LISTEN state in our network namespace? # Reads /proc/net/tcp, so it does NOT consume a connection (unlike a probe). sub port_listening { my ($ip, $port) = @_; return undef unless -r '/proc/net/tcp'; my $hex_port = sprintf("%04X", $port); my $hex_ip = join '', map { sprintf("%02X", $_) } reverse split(/\./, $ip); open my $fh, '<', '/proc/net/tcp' or return undef; while (<$fh>) { next if /^sl/; my @f = split; return 1 if $f[1] eq "$hex_ip:$hex_port" && $f[3] eq '0A'; # 0A == LISTEN } close $fh; return 0; } # Shell script as HEREDOC - aim is reusability my $shell_code = <<'HEREDOC'; #!/bin/bash # Configuration PORT=1443 IP=127.0.0.1 CERT="server.pem" KEY="server.key" # This OpenSSL version will support TLS 1.3 OPENSSL=/usr/bin/openssl if [[ $(openssl version) =~ LibreSSL ]]; then # MacOS. LibreSSL doesn't know "-naccept" if [[ -x /opt/homebrew/bin/openssl.NOPE ]]; then OPENSSL=/opt/homebrew/bin/openssl.NOPE # We hid that during GHA CI checks elif [[ -x /opt/homebrew/bin/openssl ]]; then OPENSSL=/opt/homebrew/bin/openssl # If you intend this to run fi fi # Force a specific TLS 1.3 cipher suite when needed # CIPHER_SUITE="TLS_AES_256_GCM_SHA384" # Generate self-signed cert and key if they don't exist if [ ! -f "$CERT" ] || [ ! -f "$KEY" ]; then echo "Generating self-signed certificate and key..." $OPENSSL req -x509 -newkey rsa:2048 -keyout "$KEY" -out "$CERT" -days 42 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" >/dev/null 2>&1 fi # Start OpenSSL server echo "Starting server on port $PORT..." # $OPENSSL s_server -accept "$IP:$PORT" -cert "$CERT" -key "$KEY" -tls1_3 -ciphersuites "$CIPHER_SUITE" -naccept 4242 $OPENSSL s_server -accept "$IP:$PORT" -cert "$CERT" -key "$KEY" -tls1_3 -naccept 4242 HEREDOC # Write the script to the temp directory open(my $fh, '>', $server_script) or die "Cannot write script: $!"; print $fh $shell_code; close($fh); chmod 0755, $server_script; # Start the server in the background using fork/exec my $pid = fork(); if ($pid == 0) { chdir($temp_dir) or exit 1; open(STDOUT, '>', "$temp_dir/server.log") or exit 1; open(STDERR, '>&', STDOUT) or exit 1; exec($server_script); exit 1; } elsif ($pid > 0) { # Parent process: Wait for server to be ready # Wait for the server to be listening on the port my $socket; my $ready = 0; my $listenip = '127.0.0.1'; for my $i (1..30) { $socket = IO::Socket::INET->new( PeerAddr => $listenip, PeerPort => $port, Proto => 'tcp', Timeout => 2, ); if ($socket) { $ready = 1; close($socket); last; } } ok($ready, "Server is listening on $listenip:$port"); if ($ready) { sleep(2); # ---- DEBUG stuff, taken right before testssl runs ---- my $reaped = waitpid($pid, WNOHANG); # 0 => still running my $state = ($reaped == 0) ? 'ALIVE' : "DEAD (waitpid=$reaped)"; diag("DEBUG pre-testssl: pid=$pid state=$state"); my $listening = port_listening($listenip, $port); diag("DEBUG pre-testssl: $listenip:$port LISTEN=" . ($listening // 'n/a')); if ( $os eq "linux" ){ diag("DEBUG netns: " . (`readlink /proc/self/ns/net 2>&1`)); diag("DEBUG cgroup: " . (`cat /proc/self/cgroup 2>&1`)); diag("DEBUG ss: " . (`ss -ltnp 2>&1 | grep ":$port " || echo "(no listener)"`)); } diag("DEBUG direct bash connect: " . (`timeout 2 bash -c "echo > /dev/tcp/$listenip/$port" 2>&1 && echo OK || echo REFUSED`)); my $log_pre = ''; if (open my $lfh, '<', "$temp_dir/server.log") { local $/; $log_pre = <$lfh> // ''; close $lfh; } diag("DEBUG pre-testssl server.log:\n$log_pre"); # ---- end DEBUG ---- my $testssl_output = `./testssl.sh --protocols $listenip:$port 2>&1`; like($testssl_output, qr/TLS 1\.3/, "TLS 1.3 is supported"); unlike($testssl_output, qr/OFFERED\s+TLS 1\.2/, "TLS 1.2 is NOT offered"); my $log = ''; if (open my $lfh, '<', "$temp_dir/server.log") { local $/; $log = <$lfh> // ''; close $lfh; } diag("Server Log:\n$log"); } # Cleanup: Kill the server process kill 9, $pid; waitpid($pid, 0); } else { die "Fork failed: $!"; } done_testing(); # vim:ts=5:sw=5:expandtab