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