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 => 37 + (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# corrupt an Asymmetric cipher test 353SKIP: { 354 skip "Skipping Asymmetric RSA corruption test because of no rsa in this build", 1 355 if disabled("rsa") || disabled("fips-post"); 356 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 357 '-corrupt_desc', 'RSA_Encrypt', 358 '-corrupt_type', 'KAT_AsymmetricCipher'])), 359 "fipsinstall fails when the asymmetric cipher result is corrupted"); 360} 361 362# 'local' ensures that this change is only done in this file. 363local $ENV{OPENSSL_CONF_INCLUDE} = abs2rel(curdir()); 364 365ok(replace_parent_line_file('fips.cnf', 'fips_parent.cnf') 366 && run(app(['openssl', 'fipsinstall', '-config', 'fips_parent.cnf'])), 367 "verify fips provider loads from a configuration file"); 368 369ok(replace_parent_line_file('fips_no_module_mac.cnf', 370 'fips_parent_no_module_mac.cnf') 371 && !run(app(['openssl', 'fipsinstall', 372 '-config', 'fips_parent_no_module_mac.cnf'])), 373 "verify load config fail no module mac"); 374 375SKIP: { 376 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 377 capture => 1, statusvar => \my $exit); 378 skip "FIPS provider version doesn't support self test indicator", 3 379 if !$exit; 380 381 ok(replace_parent_line_file('fips_no_install_mac.cnf', 382 'fips_parent_no_install_mac.cnf') 383 && !run(app(['openssl', 'fipsinstall', 384 '-config', 'fips_parent_no_install_mac.cnf'])), 385 "verify load config fail no install mac"); 386 387 ok(replace_parent_line_file('fips_bad_indicator.cnf', 388 'fips_parent_bad_indicator.cnf') 389 && !run(app(['openssl', 'fipsinstall', 390 '-config', 'fips_parent_bad_indicator.cnf'])), 391 "verify load config fail bad indicator"); 392 393 394 ok(replace_parent_line_file('fips_bad_install_mac.cnf', 395 'fips_parent_bad_install_mac.cnf') 396 && !run(app(['openssl', 'fipsinstall', 397 '-config', 'fips_parent_bad_install_mac.cnf'])), 398 "verify load config fail bad install mac"); 399} 400 401ok(replace_parent_line_file('fips_bad_module_mac.cnf', 402 'fips_parent_bad_module_mac.cnf') 403 && !run(app(['openssl', 'fipsinstall', 404 '-config', 'fips_parent_bad_module_mac.cnf'])), 405 "verify load config fail bad module mac"); 406 407SKIP: { 408 run(test(["fips_version_test", "-config", $provconf, "<3.1.0"]), 409 capture => 1, statusvar => \my $exit); 410 skip "FIPS provider version doesn't support self test indicator", 3 411 if !$exit; 412 413 my $stconf = "fipsmodule_selftest.cnf"; 414 415 ok(run(app(['openssl', 'fipsinstall', '-out', $stconf, 416 '-module', $infile, '-self_test_onload'])), 417 "fipsinstall config saved without self test indicator"); 418 419 ok(!run(app(['openssl', 'fipsinstall', '-in', $stconf, 420 '-module', $infile, '-verify'])), 421 "fipsinstall config verify fails without self test indicator"); 422 423 ok(run(app(['openssl', 'fipsinstall', '-in', $stconf, 424 '-module', $infile, '-self_test_onload', '-verify'])), 425 "fipsinstall config verify passes when self test indicator is not present"); 426} 427 428SKIP: { 429 run(test(["fips_version_test", "-config", $provconf, ">=3.1.0"]), 430 capture => 1, statusvar => \my $exit); 431 skip "FIPS provider version can run self tests on install", 1 432 if !$exit; 433 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 434 '-provider_name', 'fips', '-mac_name', 'HMAC', 435 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 436 '-section_name', 'fips_sect', '-self_test_oninstall', 437 '-ems_check'])), 438 "fipsinstall fails when attempting to run self tests on install"); 439} 440 441ok(find_line_file('drbg-no-trunc-md = 0', 'fips.cnf') == 1, 442 'fipsinstall defaults to not banning truncated digests with DRBGs'); 443 444ok(run(app(['openssl', 'fipsinstall', '-out', 'fips.cnf', '-module', $infile, 445 '-provider_name', 'fips', '-mac_name', 'HMAC', 446 '-macopt', 'digest:SHA256', '-macopt', "hexkey:$fipskey", 447 '-section_name', 'fips_sect', '-no_drbg_truncated_digests'])), 448 "fipsinstall knows about allowing truncated digests in DRBGs"); 449 450ok(find_line_file('drbg-no-trunc-md = 1', 'fips.cnf') == 1, 451 'fipsinstall will allow option for truncated digests with DRBGs'); 452 453 454ok(run(app(['openssl', 'fipsinstall', '-out', 'fips-pedantic.cnf', 455 '-module', $infile, '-pedantic'])), 456 "fipsinstall accepts -pedantic option"); 457 458foreach my $o (@pedantic_okay) { 459 ok(run(app(['openssl', 'fipsinstall', '-out', "fips-${o}.cnf", 460 '-module', $infile, '-pedantic', "-${o}"])), 461 "fipsinstall accepts -${o} after -pedantic option"); 462} 463 464foreach my $o (@pedantic_fail) { 465 ok(!run(app(['openssl', 'fipsinstall', '-out', 'fips_fail.cnf', 466 '-module', $infile, '-pedantic', "-${o}"])), 467 "fipsinstall disallows -${o} after -pedantic option"); 468} 469 470foreach my $cp (@commandline) { 471 my $o = $commandline[0]; 472 my $l = $commandline[1]; 473 474 ok(find_line_file("${l} = 1", 'fips-pedantic.cnf') == 1, 475 "fipsinstall enables ${l} with -pendantic option"); 476 ok(find_line_file("${l} = 0", 'fips.cnf') == 1, 477 "fipsinstall disables ${l} without -pendantic option"); 478 479 ok(run(app(['openssl', 'fipsinstall', '-out', "fips-${o}.cnf", 480 '-module', $infile, "-${o}"])), 481 "fipsinstall accepts -${o} option"); 482 ok(find_line_file("${l} = 1", "fips-${o}.cnf") == 1, 483 "fipsinstall enables ${l} with -${o} option"); 484} 485