1#! /usr/bin/env perl 2# Copyright 2019-2024 The OpenSSL Project Authors. All Rights Reserved. 3# 4# Licensed under the Apache License 2.0 (the "License"). You may not use 5# this file except in compliance with the License. You can obtain a copy 6# in the file LICENSE in the source distribution or at 7# https://www.openssl.org/source/license.html 8 9use strict; 10use warnings; 11 12use File::Spec::Functions qw(:DEFAULT abs2rel); 13use File::Copy; 14use OpenSSL::Glob; 15use OpenSSL::Test qw/:DEFAULT srctop_dir srctop_file bldtop_dir bldtop_file/; 16use OpenSSL::Test::Utils; 17 18BEGIN { 19 setup("test_fipsinstall"); 20} 21use lib srctop_dir('Configurations'); 22use lib bldtop_dir('.'); 23use platform; 24 25plan skip_all => "Test only supported in a fips build" if disabled("fips"); 26 27# Compatible options for pedantic FIPS compliance 28my @pedantic_okay = 29 ( 'ems_check', 'no_drbg_truncated_digests', 'self_test_onload', 30 'signature_digest_check' 31 ); 32 33# Incompatible options for pedantic FIPS compliance 34my @pedantic_fail = 35 ( 'no_conditional_errors', 'no_security_checks', 'self_test_oninstall', 36 'no_pbkdf2_lower_bound_check' ); 37 38# Command line options 39my @commandline = 40 ( 41 ( 'ems_check', 'tls1-prf-ems-check' ), 42 ( 'no_short_mac', 'no-short-mac' ), 43 ( 'no_drbg_truncated_digests', 'drbg-no-trunc-md' ), 44 ( 'signature_digest_check', 'signature-digest-check' ), 45 ( 'hkdf_digest_check', 'hkdf-digest-check' ), 46 ( 'tls13_kdf_digest_check', 'tls13-kdf-digest-check' ), 47 ( 'tls1_prf_digest_check', 'tls1-prf-digest-check' ), 48 ( 'sshkdf_digest_check', 'sshkdf-digest-check' ), 49 ( 'sskdf_digest_check', 'sskdf-digest-check' ), 50 ( 'x963kdf_digest_check', 'x963kdf-digest-check' ), 51 ( 'dsa_sign_disabled', 'dsa-sign-disabled' ), 52 ( 'tdes_encrypt_disabled', 'tdes-encrypt-disabled' ), 53 ( 'rsa_pkcs15_pad_disabled', 'rsa-pkcs15-pad-disabled' ), 54 ( 'rsa_pss_saltlen_check', 'rsa-pss-saltlen-check' ), 55 ( 'rsa_sign_x931_disabled', 'rsa-sign-x931-pad-disabled' ), 56 ( 'hkdf_key_check', 'hkdf-key-check' ), 57 ( 'kbkdf_key_check', 'kbkdf-key-check' ), 58 ( 'tls13_kdf_key_check', 'tls13-kdf-key-check' ), 59 ( 'tls1_prf_key_check', 'tls1-prf-key-check' ), 60 ( 'sshkdf_key_check', 'sshkdf-key-check' ), 61 ( 'sskdf_key_check', 'sskdf-key-check' ), 62 ( 'x963kdf_key_check', 'x963kdf-key-check' ), 63 ( 'x942kdf_key_check', 'x942kdf-key-check' ) 64 ); 65 66plan tests => 36 + (scalar @pedantic_okay) + (scalar @pedantic_fail) 67 + 4 * (scalar @commandline); 68 69my $infile = bldtop_file('providers', platform->dso('fips')); 70my $fipskey = $ENV{FIPSKEY} // config('FIPSKEY') // '00'; 71my $provconf = srctop_file("test", "fips-and-base.cnf"); 72 73# Read in a text $infile and replace the regular expression in $srch with the 74# value in $repl and output to a new file $outfile. 75sub replace_line_file_internal { 76 77 my ($infile, $srch, $repl, $outfile) = @_; 78 my $msg; 79 80 open(my $in, "<", $infile) or return 0; 81 read($in, $msg, 1024); 82 close $in; 83 84 $msg =~ s/$srch/$repl/; 85 86 open(my $fh, ">", $outfile) or return 0; 87 print $fh $msg; 88 close $fh; 89 return 1; 90} 91 92# Read in the text input file 'fips.cnf' 93# and replace a single Key = Value line with a new value in $value. 94# OR remove the Key = Value line if the passed in $value is empty. 95# and then output a new file $outfile. 96# $key is the Key to find 97sub replace_line_file { 98 my ($key, $value, $outfile) = @_; 99 100 my $srch = qr/$key\s*=\s*\S*\n/; 101 my $rep; 102 if ($value eq "") { 103 $rep = ""; 104 } else { 105 $rep = "$key = $value\n"; 106 } 107 return replace_line_file_internal('fips.cnf', $srch, $rep, $outfile); 108} 109 110# Read in the text input file 'test/fips.cnf' 111# and replace the .cnf file used in 112# .include fipsmodule.cnf with a new value in $value. 113# and then output a new file $outfile. 114# $key is the Key to find 115sub replace_parent_line_file { 116 my ($value, $outfile) = @_; 117 my $srch = qr/fipsmodule.cnf/; 118 my $rep = "$value"; 119 return replace_line_file_internal(srctop_file("test", 'fips.cnf'), 120 $srch, $rep, $outfile); 121} 122 123# Check if the specified pattern occurs in the given file 124# Returns 1 if the pattern is found and 0 if not 125sub find_line_file { 126 my ($key, $file) = @_; 127 128 open(my $in, $file) or return -1; 129 while (my $line = <$in>) { 130 if ($line =~ /$key/) { 131 close($in); 132 return 1; 133 } 134 } 135 close($in); 136 return 0; 137} 138 139# fail if no module name 140ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', 141 '-provider_name', 'fips', 142 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 143 '-section_name', 'fips_sect'])), 144 "fipsinstall fail"); 145 146# fail to verify if the configuration file is missing 147ok(!run(app(['openssl', 'fipsinstall', '-in', 'dummy.tmp', '-module', $infile, 148 '-provider_name', 'fips', '-mac_name', 'HMAC', 149 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 150 '-section_name', 'fips_sect', '-verify'])), 151 "fipsinstall verify fail"); 152 153# output a fips.cnf file containing mac data 154ok(run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 155 '-provider_name', 'fips', '-mac_name', 'HMAC', 156 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 157 '-section_name', 'fips_sect'])), 158 "fipsinstall"); 159 160# verify the fips.cnf file 161ok(run(app(['openssl', 'fipsinstall', '-in', 'fips.cnf', '-module', $infile, 162 '-provider_name', 'fips', '-mac_name', 'HMAC', 163 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 164 '-section_name', 'fips_sect', '-verify'])), 165 "fipsinstall verify"); 166 167# Test that default options for fipsinstall output the 'install-status' for 168# FIPS 140-2 providers. 169SKIP: { 170 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 171 capture => 1, statusvar => \my $exit); 172 173 skip "Skipping FIPS 140-3 provider", 2 174 if !$exit; 175 176 ok(find_line_file('install-mac = ', 'fips.cnf') == 1, 177 'FIPS 140-2 should output install-mac'); 178 179 ok(find_line_file('install-status = INSTALL_SELF_TEST_KATS_RUN', 180 'fips.cnf') == 1, 181 'FIPS 140-2 should output install-status'); 182} 183 184# Skip Tests if POST is disabled 185SKIP: { 186 skip "Skipping POST checks", 13 187 if disabled("fips-post"); 188 189 ok(replace_line_file('module-mac', '', 'fips_no_module_mac.cnf') 190 && !run(app(['openssl', 'fipsinstall', 191 '-in', 'fips_no_module_mac.cnf', 192 '-module', $infile, 193 '-provider_name', 'fips', '-mac_name', 'HMAC', 194 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 195 '-section_name', 'fips_sect', '-verify'])), 196 "fipsinstall verify fail no module mac"); 197 198 ok(replace_line_file('install-mac', '', 'fips_no_install_mac.cnf') 199 && !run(app(['openssl', 'fipsinstall', 200 '-in', 'fips_no_install_mac.cnf', 201 '-module', $infile, 202 '-provider_name', 'fips', '-mac_name', 'HMAC', 203 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 204 '-section_name', 'fips_sect', '-verify'])), 205 "fipsinstall verify fail no install indicator mac"); 206 207 ok(replace_line_file('module-mac', '00:00:00:00:00:00', 208 'fips_bad_module_mac.cnf') 209 && !run(app(['openssl', 'fipsinstall', 210 '-in', 'fips_bad_module_mac.cnf', 211 '-module', $infile, 212 '-provider_name', 'fips', '-mac_name', 'HMAC', 213 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 214 '-section_name', 'fips_sect', '-verify'])), 215 "fipsinstall verify fail if invalid module integrity value"); 216 217 ok(replace_line_file('install-mac', '00:00:00:00:00:00', 218 'fips_bad_install_mac.cnf') 219 && !run(app(['openssl', 'fipsinstall', 220 '-in', 'fips_bad_install_mac.cnf', 221 '-module', $infile, 222 '-provider_name', 'fips', '-mac_name', 'HMAC', 223 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 224 '-section_name', 'fips_sect', '-verify'])), 225 "fipsinstall verify fail if invalid install indicator integrity value"); 226 227 ok(replace_line_file('install-status', 'INCORRECT_STATUS_STRING', 228 'fips_bad_indicator.cnf') 229 && !run(app(['openssl', 'fipsinstall', 230 '-in', 'fips_bad_indicator.cnf', 231 '-module', $infile, 232 '-provider_name', 'fips', '-mac_name', 'HMAC', 233 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 234 '-section_name', 'fips_sect', '-verify'])), 235 "fipsinstall verify fail if invalid install indicator status"); 236 237 # fail to verify the fips.cnf file if a different key is used 238 ok(!run(app(['openssl', 'fipsinstall', '-in', 'fips.cnf', 239 '-module', $infile, 240 '-provider_name', 'fips', '-mac_name', 'HMAC', 241 '-macopt', 'digest:SHA256', '-macopt', "hexkey:01", 242 '-section_name', 'fips_sect', '-verify'])), 243 "fipsinstall verify fail bad key"); 244 245 # fail to verify the fips.cnf file if a different mac digest is used 246 ok(!run(app(['openssl', 'fipsinstall', '-in', 'fips.cnf', 247 '-module', $infile, 248 '-provider_name', 'fips', '-mac_name', 'HMAC', 249 '-macopt', 'digest:SHA512', '-macopt', "hexkey:$fipskey", 250 '-section_name', 'fips_sect', '-verify'])), 251 "fipsinstall verify fail incorrect digest"); 252 253 # corrupt the module hmac 254 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', 255 '-module', $infile, 256 '-provider_name', 'fips', '-mac_name', 'HMAC', 257 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 258 '-section_name', 'fips_sect', '-corrupt_desc', 'HMAC'])), 259 "fipsinstall fails when the module integrity is corrupted"); 260 261 # corrupt the first digest 262 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 263 '-module', $infile, 264 '-provider_name', 'fips', '-mac_name', 'HMAC', 265 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 266 '-section_name', 'fips_sect', '-corrupt_desc', 'SHA2'])), 267 "fipsinstall fails when the digest result is corrupted"); 268 269 # corrupt another digest 270 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 271 '-module', $infile, 272 '-provider_name', 'fips', '-mac_name', 'HMAC', 273 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 274 '-section_name', 'fips_sect', '-corrupt_desc', 'SHA3'])), 275 "fipsinstall fails when the digest result is corrupted"); 276 277 # corrupt cipher encrypt test 278 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 279 '-module', $infile, 280 '-provider_name', 'fips', '-mac_name', 'HMAC', 281 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 282 '-section_name', 'fips_sect', '-corrupt_desc', 'AES_GCM'])), 283 "fipsinstall fails when the AES_GCM result is corrupted"); 284 285 # corrupt cipher decrypt test 286 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 287 '-module', $infile, 288 '-provider_name', 'fips', '-mac_name', 'HMAC', 289 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 290 '-section_name', 'fips_sect', '-corrupt_desc', 'AES_ECB_Decrypt'])), 291 "fipsinstall fails when the AES_ECB result is corrupted"); 292 293 # corrupt DRBG 294 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 295 '-module', $infile, 296 '-provider_name', 'fips', '-mac_name', 'HMAC', 297 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 298 '-section_name', 'fips_sect', '-corrupt_desc', 'CTR'])), 299 "fipsinstall fails when the DRBG CTR result is corrupted"); 300} 301 302# corrupt a KAS test 303SKIP: { 304 skip "Skipping KAS DH corruption test because of no dh in this build", 1 305 if disabled("dh") || disabled("fips-post"); 306 307 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 308 '-provider_name', 'fips', '-mac_name', 'HMAC', 309 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 310 '-section_name', 'fips_sect', 311 '-corrupt_desc', 'DH', 312 '-corrupt_type', 'KAT_KA'])), 313 "fipsinstall fails when the kas result is corrupted"); 314} 315 316# corrupt a Signature test - 140-3 requires a known answer test 317SKIP: { 318 skip "Skipping Signature DSA corruption test because of no dsa in this build", 1 319 if disabled("dsa") || disabled("fips-post"); 320 321 run(test(["fips_version_test", "-config", $provconf, ">=3.1.0"]), 322 capture => 1, statusvar => \my $exit); 323 skip "FIPS provider version is too old for KAT DSA signature test", 1 324 if !$exit; 325 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 326 '-provider_name', 'fips', '-mac_name', 'HMAC', 327 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 328 '-section_name', 'fips_sect', '-self_test_oninstall', 329 '-corrupt_desc', 'DSA', 330 '-corrupt_type', 'KAT_Signature'])), 331 "fipsinstall fails when the signature result is corrupted"); 332} 333 334# corrupt a Signature test - 140-2 allows a pairwise consistency test 335SKIP: { 336 skip "Skipping Signature DSA corruption test because of no dsa in this build", 1 337 if disabled("dsa") || disabled("fips-post"); 338 339 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 340 capture => 1, statusvar => \my $exit); 341 skip "FIPS provider version is too new for PCT DSA signature test", 1 342 if !$exit; 343 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 344 '-provider_name', 'fips', '-mac_name', 'HMAC', 345 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 346 '-section_name', 'fips_sect', 347 '-corrupt_desc', 'DSA', 348 '-corrupt_type', 'PCT_Signature'])), 349 "fipsinstall fails when the signature result is corrupted"); 350} 351 352# 'local' ensures that this change is only done in this file. 353local $ENV{OPENSSL_CONF_INCLUDE} = abs2rel(curdir()); 354 355ok(replace_parent_line_file('fips.cnf', 'fips_parent.cnf') 356 && run(app(['openssl', 'fipsinstall', '-config', 'fips_parent.cnf'])), 357 "verify fips provider loads from a configuration file"); 358 359ok(replace_parent_line_file('fips_no_module_mac.cnf', 360 'fips_parent_no_module_mac.cnf') 361 && !run(app(['openssl', 'fipsinstall', 362 '-config', 'fips_parent_no_module_mac.cnf'])), 363 "verify load config fail no module mac"); 364 365SKIP: { 366 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 367 capture => 1, statusvar => \my $exit); 368 skip "FIPS provider version doesn't support self test indicator", 3 369 if !$exit; 370 371 ok(replace_parent_line_file('fips_no_install_mac.cnf', 372 'fips_parent_no_install_mac.cnf') 373 && !run(app(['openssl', 'fipsinstall', 374 '-config', 'fips_parent_no_install_mac.cnf'])), 375 "verify load config fail no install mac"); 376 377 ok(replace_parent_line_file('fips_bad_indicator.cnf', 378 'fips_parent_bad_indicator.cnf') 379 && !run(app(['openssl', 'fipsinstall', 380 '-config', 'fips_parent_bad_indicator.cnf'])), 381 "verify load config fail bad indicator"); 382 383 384 ok(replace_parent_line_file('fips_bad_install_mac.cnf', 385 'fips_parent_bad_install_mac.cnf') 386 && !run(app(['openssl', 'fipsinstall', 387 '-config', 'fips_parent_bad_install_mac.cnf'])), 388 "verify load config fail bad install mac"); 389} 390 391ok(replace_parent_line_file('fips_bad_module_mac.cnf', 392 'fips_parent_bad_module_mac.cnf') 393 && !run(app(['openssl', 'fipsinstall', 394 '-config', 'fips_parent_bad_module_mac.cnf'])), 395 "verify load config fail bad module mac"); 396 397SKIP: { 398 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 399 capture => 1, statusvar => \my $exit); 400 skip "FIPS provider version doesn't support self test indicator", 3 401 if !$exit; 402 403 my $stconf = "fipsmodule_selftest.cnf"; 404 405 ok(run(app(['openssl', 'fipsinstall', '-out', $stconf, 406 '-module', $infile, '-self_test_onload'])), 407 "fipsinstall config saved without self test indicator"); 408 409 ok(!run(app(['openssl', 'fipsinstall', '-in', $stconf, 410 '-module', $infile, '-verify'])), 411 "fipsinstall config verify fails without self test indicator"); 412 413 ok(run(app(['openssl', 'fipsinstall', '-in', $stconf, 414 '-module', $infile, '-self_test_onload', '-verify'])), 415 "fipsinstall config verify passes when self test indicator is not present"); 416} 417 418SKIP: { 419 run(test(["fips_version_test", "-config", $provconf, ">=3.1.0"]), 420 capture => 1, statusvar => \my $exit); 421 skip "FIPS provider version can run self tests on install", 1 422 if !$exit; 423 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 424 '-provider_name', 'fips', '-mac_name', 'HMAC', 425 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 426 '-section_name', 'fips_sect', '-self_test_oninstall', 427 '-ems_check'])), 428 "fipsinstall fails when attempting to run self tests on install"); 429} 430 431ok(find_line_file('drbg-no-trunc-md = 0', 'fips.cnf') == 1, 432 'fipsinstall defaults to not banning truncated digests with DRBGs'); 433 434ok(run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 435 '-provider_name', 'fips', '-mac_name', 'HMAC', 436 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 437 '-section_name', 'fips_sect', '-no_drbg_truncated_digests'])), 438 "fipsinstall knows about allowing truncated digests in DRBGs"); 439 440ok(find_line_file('drbg-no-trunc-md = 1', 'fips.cnf') == 1, 441 'fipsinstall will allow option for truncated digests with DRBGs'); 442 443 444ok(run(app(['openssl', 'fipsinstall', '-out', 'fips-pedantic.cnf', 445 '-module', $infile, '-pedantic'])), 446 "fipsinstall accepts -pedantic option"); 447 448foreach my $o (@pedantic_okay) { 449 ok(run(app(['openssl', 'fipsinstall', '-out', "fips-${o}.cnf", 450 '-module', $infile, '-pedantic', "-${o}"])), 451 "fipsinstall accepts -${o} after -pedantic option"); 452} 453 454foreach my $o (@pedantic_fail) { 455 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 456 '-module', $infile, '-pedantic', "-${o}"])), 457 "fipsinstall disallows -${o} after -pedantic option"); 458} 459 460foreach my $cp (@commandline) { 461 my $o = $commandline[0]; 462 my $l = $commandline[1]; 463 464 ok(find_line_file("${l} = 1", 'fips-pedantic.cnf') == 1, 465 "fipsinstall enables ${l} with -pendantic option"); 466 ok(find_line_file("${l} = 0", 'fips.cnf') == 1, 467 "fipsinstall disables ${l} without -pendantic option"); 468 469 ok(run(app(['openssl', 'fipsinstall', '-out', "fips-${o}.cnf", 470 '-module', $infile, "-${o}"])), 471 "fipsinstall accepts -${o} option"); 472 ok(find_line_file("${l} = 1", "fips-${o}.cnf") == 1, 473 "fipsinstall enables ${l} with -${o} option"); 474} 475