2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 07:08:12 +00:00
Files
xcat-core/xCAT-test/unit/fips_shell_runtime.t
2026-10-05 22:56:00 -03:00

178 lines
7.5 KiB
Perl

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use File::Spec;
use File::Slurper qw(read_text write_text);
use File::Temp qw(tempdir);
use Test::More;
use XCAT::Test::File qw(repo_path);
my $bash = qx(command -v bash 2>/dev/null);
chomp($bash);
my $sh = qx(command -v sh 2>/dev/null);
chomp($sh);
my $openssl = qx(command -v openssl 2>/dev/null);
chomp($openssl);
my $host_fips = 0;
if (-r '/proc/sys/crypto/fips_enabled') {
$host_fips = read_text('/proc/sys/crypto/fips_enabled') =~ /^1\s*$/;
}
SKIP: {
skip 'sh, bash, and openssl are required', 40
unless $sh && -x $sh && $bash && -x $bash && $openssl && -x $openssl;
foreach my $runtime (
['xCAT-genesis-scripts/usr/lib/xcat/fips.sh', $sh],
['xCAT/postscripts/xcatlib.sh', $bash],
) {
my ($relative, $shell) = @{$runtime};
my $library = repo_path($relative);
my $tmpdir = tempdir(CLEANUP => 1);
my $status = File::Spec->catfile($tmpdir, 'fips_enabled');
my $missing = File::Spec->catfile($tmpdir, 'missing');
my $packet = File::Spec->catfile($tmpdir, 'discovery-packet');
write_text($packet, "<xcatrequest><command>findme</command></xcatrequest>\n");
write_text($status, "1\n");
is(shell_output($shell, $library, 'xcat_fips_state "$2"', $status), '1',
"$relative recognizes enabled FIPS state");
write_text($status, "0\n");
is(shell_output($shell, $library, 'xcat_fips_state "$2"', $status), '0',
"$relative recognizes disabled FIPS state");
write_text($status, "1 0\n");
is(shell_output($shell, $library, 'xcat_fips_state "$2"', $status), '0',
"$relative rejects malformed FIPS state");
is(shell_output($shell, $library, 'xcat_fips_state "$2"', $missing), '0',
"$relative treats missing FIPS state as disabled");
is(shell_output($shell, $library, 'set -u; xcat_fips_state'),
$host_fips ? '1' : '0',
"$relative accepts its default status path with nounset");
my $invalid_key = File::Spec->catfile($tmpdir, 'invalid-key.pem');
isnt(shell_status($shell, $library,
'xcat_generate_discovery_private_key invalid "$2"', $invalid_key), 0,
"$relative rejects invalid private-key policy");
isnt(shell_status($shell, $library,
'xcat_discovery_public_key invalid "$2"', $invalid_key), 0,
"$relative rejects invalid public-key policy");
my $fips_key = File::Spec->catfile($tmpdir, 'fips-key.pem');
my $fips_public = File::Spec->catfile($tmpdir, 'fips-key.pub');
my $fips_signature = File::Spec->catfile($tmpdir, 'fips-signature');
write_text($status, "1\n");
is(run_key_cycle($shell, $library, $status, 0, $fips_key, $fips_public), 0,
"$relative reuses enabled state for key generation and extraction");
my $fips_key_text = command_output(
$openssl, 'ec', '-in', $fips_key, '-text', '-noout'
);
like($fips_key_text, qr/Private-Key: \(256 bit\)/,
"$relative generates a P-256 FIPS key");
is(shell_output($shell, $library, 'xcat_discovery_public_key 0 "$2"', $fips_key),
read_text($fips_public), "$relative exports an EC key after FIPS is disabled");
my $public_body = read_text($fips_public);
$public_body =~ s/-----[^-]+-----//g;
$public_body =~ s/\s+//g;
# lldpad reads the system description into a 256-byte buffer.
cmp_ok(length($public_body), '<=', 255,
"$relative FIPS public key fits the lldpad description buffer");
is(system($openssl, 'dgst', '-sha512', '-sign', $fips_key,
'-out', $fips_signature, $packet), 0,
"$relative FIPS key signs discovery data");
is(system($openssl, 'dgst', '-sha512', '-verify', $fips_public,
'-signature', $fips_signature, $packet), 0,
"$relative FIPS public key verifies discovery data");
SKIP: {
skip 'legacy RSA is unavailable on a FIPS-enabled test host', 4
if $host_fips;
my $legacy_key = File::Spec->catfile($tmpdir, 'legacy-key.pem');
my $legacy_public = File::Spec->catfile($tmpdir, 'legacy-key.pub');
my $legacy_signature = File::Spec->catfile($tmpdir, 'legacy-signature');
write_text($status, "0\n");
is(run_key_cycle($shell, $library, $status, 1, $legacy_key, $legacy_public), 0,
"$relative reuses disabled state for key generation and extraction");
my $legacy_key_text = command_output(
$openssl, 'rsa', '-in', $legacy_key, '-text', '-noout'
);
like($legacy_key_text, qr/Private-Key: \(1024 bit/,
"$relative preserves the legacy RSA key size");
is(system($openssl, 'dgst', '-sha512', '-sign', $legacy_key,
'-out', $legacy_signature, $packet), 0,
"$relative legacy key signs discovery data");
is(system($openssl, 'dgst', '-sha512', '-verify', $legacy_public,
'-signature', $legacy_signature, $packet), 0,
"$relative legacy public key verifies discovery data");
}
}
my $genesis_library = repo_path('xCAT-genesis-scripts/usr/lib/xcat/fips.sh');
is(shell_status($sh, $genesis_library, 'xcat_dsa_allowed 0'), 0,
'Genesis permits DSA outside FIPS mode');
isnt(shell_status($sh, $genesis_library, 'xcat_dsa_allowed 1'), 0,
'Genesis rejects DSA in FIPS mode');
my $cumulus_library = repo_path('xCAT/postscripts/xcatlib.sh');
my $key_dir = tempdir(CLEANUP => 1);
my $existing_key = "$key_dir/existing.pem";
system($openssl, 'genrsa', '-out', $existing_key, 2048) == 0
or die 'prepare existing RSA key';
my $original_key = read_text($existing_key);
is(shell_status($bash, $cumulus_library,
'xcat_prepare_discovery_key 0 "$2"', $existing_key), 0,
'non-FIPS Cumulus discovery accepts an existing key');
is(read_text($existing_key), $original_key, 'non-FIPS discovery keeps the existing key');
is(shell_status($bash, $cumulus_library,
'xcat_prepare_discovery_key 1 "$2"', $existing_key), 0,
'FIPS Cumulus discovery replaces an existing bootstrap key');
like(command_output($openssl, 'ec', '-in', $existing_key, '-text', '-noout'),
qr/Private-Key: \(256 bit\)/, 'the replacement key uses P-256');
}
done_testing();
sub shell_output {
my ($shell, $library, $command, @arguments) = @_;
return command_output(
$shell, '-c', '. "$1"; ' . $command,
'xcat-fips-test', $library, @arguments,
);
}
sub shell_status {
my ($shell, $library, $command, @arguments) = @_;
system(
$shell, '-c', '. "$1"; ' . $command,
'xcat-fips-test', $library, @arguments,
);
return $? >> 8;
}
sub run_key_cycle {
my ($shell, $library, $status, $next_state, $key, $public) = @_;
my $command = <<'EOF';
. "$1"
fips_state=$(xcat_fips_state "$2")
printf '%s\n' "$3" > "$2"
xcat_generate_discovery_private_key "$fips_state" "$4" 2>/dev/null || exit
xcat_discovery_public_key "$fips_state" "$4" > "$5" 2>/dev/null
EOF
system(
$shell, '-c', $command,
'xcat-fips-test', $library, $status, $next_state, $key, $public,
);
return $? >> 8;
}
sub command_output {
my @command = @_;
open(my $fh, '-|', @command) or die "run @command: $!";
my $output = do { local $/; <$fh> };
close($fh) or die "close @command: $!";
return $output;
}