swtpm: Implement function to check whether a crypto algorithm is disabled

Implement a function that checks whether a crypto algorithm identified by
TPM algorithm identifiers is disabled.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
This commit is contained in:
Stefan Berger 2024-09-14 11:50:41 -04:00 committed by Stefan Berger
parent 8e497a6a60
commit c3de83e7fe
13 changed files with 459 additions and 8 deletions

View File

@ -335,7 +335,8 @@ may contain the following:
"rsa-keysize-2048",
"rsa-keysize-3072",
"cmdarg-profile",
"cmdarg-print-profiles"
"cmdarg-print-profiles",
"profile-opt-remove-disabled"
],
"version": "0.7.0"
}
@ -420,6 +421,11 @@ to provide a JSON-formatted profile.
The option <--print-profiles> is supported.
=item B<profile-opt-remove-disabled>
The I<--profile> option supports the I<remove-disabled> option
parameter.
=back
=item B<--print-states> (since v0.7)
@ -463,7 +469,8 @@ from swtpm. To avoid releasing the lock too early the 'permanent'
and 'volatile' state blobs must be received before the 'savestate'
blob.
=item B<--profile name=E<lt>profile-nameE<gt>|profile=E<lt>json-profileE<gt>> (since v0.10)
=item B<--profile
name=E<lt>profile-nameE<gt>|profile=E<lt>json-profileE<gt>[,remove-disabled=check|fips-host]> (since v0.10)
This option allows to set a profile for a TPM 2 using either the option parameter
I<name=> to select a built-in profile by its name or I<profile=> to provide a
@ -476,6 +483,36 @@ started and for as long as no state file exists. The profile cannot be changed
anymore afterwards and, if passed again using this option, an error will
occur.
The I<remove-disabled> option parameter tells swtpm to remove those
algorithms from the profile that are disabled when FIPS is enabled on a host
(I<fips-host> parameter; reference is RHEL 9.4+ FIPS mode) and OpenSSL's access
to crypto algorithms is restricted. The I<check> parameter can be used to
check that an algorithm is actually disabled before removing it. This option
is only supported if the I<custom> profile is chosen. In this case it will
(currently) do the following:
=over 2
=item * remove camellia, tdes, and rsaes (RSA encryption with PKCS#1 v1.5
padding)
=item * disable signature support (RSA and EC) over SHA1
=item * disable unpadded RSA encryption
=item * set the minimum size for RSA keys to 2048 bits
=item * set the minimum size for EC keys to 224 bits
=back
All other algorithms remain enabled, including those that FIPS may normally not
allow, such as ecdaa and ecschnorr and others. The list of disabled algorithms
will be extended in the future when FIPS mode on the host disables more
algorithms. Since swtpm may add attributes to the profile that require a
certain StateFormatLevel, it is recommended to omit the StateFormatLevel from
the passed custom profile.
Note that profiles may disable algorithms that are considered mandatory for
a TPM 2, such as RSA-PSS. However, FIPS-enforcement on the host disables
algorithms in the OpenSSL crypto library that the TPM 2 would normally

View File

@ -21,6 +21,7 @@ noinst_HEADERS = \
options.h \
daemonize.h \
pidfile.h \
profile.h \
seccomp_profile.h \
server.h \
swtpm_aes.h \
@ -49,6 +50,7 @@ libswtpm_libtpms_la_SOURCES = \
mainloop.c \
options.c \
pidfile.c \
profile.c \
seccomp_profile.c \
server.c \
swtpm_aes.c \

View File

@ -269,7 +269,7 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion)
"{ "
"\"type\": \"swtpm\", "
"\"features\": [ "
"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
" ], "
"\"profiles\": { %s}, "
"\"version\": \"" VERSION "\" "
@ -291,6 +291,7 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion)
keysizecaps ? keysizecaps : "",
true ? ", \"cmdarg-profile\"" : "",
true ? ", \"cmdarg-print-profiles\"" : "",
true ? ", \"profile-opt-remove-disabled\"" : "",
profiles ? profiles : ""
);

View File

@ -37,6 +37,9 @@
#include "config.h"
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "check_algos.h"
@ -312,6 +315,25 @@ static int check_rsa_sign(const char *hashname, unsigned int keysize,
return bad;
}
static int check_rsa_verify(const char *hashname, unsigned int keysize,
unsigned int padding)
{
EVP_PKEY *pkey = get_rsakey(keysize);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
const EVP_MD *md = EVP_get_digestbyname(hashname);
int bad;
bad = (!pkey || !ctx || !md ||
EVP_PKEY_verify_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 ||
EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0);
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return bad;
}
/*
* List of OpenSSL configuration-disabled and 'fips=yes'-disabled algorithms
* that TPM 2 may enable with a profile.
@ -373,6 +395,17 @@ static const struct algorithms_tests {
.padding = RSA_PKCS1_PADDING,
.testfn = check_rsa_encryption,
.fix_flags = FIX_DISABLE_FIPS,
}, {
.disabled_type = DISABLED_BY_FIPS,
/*
* Use RSA 1024 test that indicates OpenSSL minimum supported key length
* see: https://www.keylength.com/en/4/
*/
.names = (const char *[]){"ecc-nist-p192", NULL},
.algname = "SHA256",
.keysize = 1024,
.padding = RSA_PKCS1_PSS_PADDING,
.testfn = check_rsa_sign,
}, {
.disabled_type = DISABLED_SHA1_SIGNATURES,
.names = (const char *[]){"rsa", "sha1", "rsapss", NULL},
@ -414,25 +447,78 @@ static const struct algorithms_tests {
}
};
static const struct fips_disabled {
const char *name;
const char *related;
size_t related_len;
} ossl_fips_disabled_algorithms[] = {
#define ENTRY(NAME, RELATED, R_LEN) \
{ .name = NAME, .related = RELATED, .related_len = R_LEN}
/* minimum required list of algorithms to disable */
ENTRY("camellia", "camellia-min-size=", 18),
ENTRY("tdes", "tdes-min-size=", 14),
ENTRY("rsaes", NULL, 0),
ENTRY("ecc-nist-p192", NULL, 0),
ENTRY(NULL, 0, 0)
#undef ENTRY
};
/* list of minimum required key sizes for FIPS */
static const struct key_sizes {
const char **names; // all of these must be found enabled in profile
const char *keyword;
unsigned int min_size;
const char *algname; // string to use for OpenSSL
unsigned int keysize; // keysize
unsigned int padding; // padding
AlgorithmTest testfn; // function to call
} fips_key_sizes[] = {
{
.names = (const char *[]){"ecc-nist", NULL}, //keyword only matters if this is given
.keyword = "ecc-min-size=",
.min_size = 224,
/*
* Use RSA 1024 test that indicates OpenSSL minimum supported key length
* see: https://www.keylength.com/en/4/
*/
.algname = "SHA256",
.keysize = 1024, // 1024 would fail test; 2048 expected to work
.padding = RSA_PKCS1_PSS_PADDING,
.testfn = check_rsa_sign,
}, {
.names = (const char *[]){"rsa", NULL}, //keyword only matters if this is given
.keyword = "rsa-min-size=",
.min_size = 2048,
.algname = "SHA256",
.keysize = 1024, // 1024 would fail test; 2048 expected to work
.padding = RSA_PKCS1_PSS_PADDING,
.testfn = check_rsa_sign,
}, {
// keep last
}
};
/*
* Check whether the crypto algorithm described by the TPM algorithm 'verbs'
* is disabled.
*/
static bool check_ossl_algorithm_is_disabled(const gchar *const*tpm_algorithms)
{
size_t i;
for (i = 0; ossl_config_disabled[i].names; i++) {
if (!strv_contains_all(ossl_config_disabled[i].names, tpm_algorithms))
continue;
if (ossl_config_disabled[i].testfn(
ossl_config_disabled[i].algname,
ossl_config_disabled[i].keysize,
ossl_config_disabled[i].padding))
return true;
}
return false;
}
/* Determine whether any of the algorithms in the array are FIPS-disable */
static unsigned int
_check_ossl_algorithms_are_disabled(const gchar *const*algorithms,
@ -526,3 +612,179 @@ unsigned int check_ossl_algorithms_are_disabled(const gchar *const*algorithms,
disabled_filter,
stop_on_first_disabled);
}
static gchar *algorithms_gencmpstr(gchar *input, ssize_t *len)
{
char *equals = index(input, '=');
if (equals)
*len = equals - input;
else
*len = -1;
return input;
}
/*
* Remove those algorithms in the given array that are disabled by FIPS and
* set or adjust key lengths to minimum required sizes for FIPS.
*
* @algorithms: pointer to NULL-terminated array of algorithms
* @force: whether to test force adding attributes rather than test the
* algorithm
*/
int check_ossl_fips_disabled_remove_algorithms(gchar ***algorithms,
gboolean force)
{
unsigned long v;
char *end_ptr;
size_t i, l;
gchar *old;
ssize_t j;
/* remove all unsupported algorithms */
for (i = 0; ossl_fips_disabled_algorithms[i].name != NULL; i++) {
if (!force &&
!check_ossl_algorithm_is_disabled(
(const char *[]){ossl_fips_disabled_algorithms[i].name,
NULL})) {
continue;
}
strv_remove(*algorithms,
ossl_fips_disabled_algorithms[i].name, -1,
true);
if (ossl_fips_disabled_algorithms[i].related)
strv_remove(*algorithms,
ossl_fips_disabled_algorithms[i].related,
ossl_fips_disabled_algorithms[i].related_len,
true);
}
/* deduplicate items in algorithms array */
strv_dedup(*algorithms, algorithms_gencmpstr, true);
/* set/adjust min. key sizes */
for (i = 0; fips_key_sizes[i].keyword; i++) {
if (!force &&
fips_key_sizes[i].testfn(fips_key_sizes[i].algname,
fips_key_sizes[i].keysize,
fips_key_sizes[i].padding) == 0) {
continue;
}
l = strlen(fips_key_sizes[i].keyword);
j = strv_strncmp((const gchar *const*)*algorithms, fips_key_sizes[i].keyword, l);
if (j >= 0) {
/* key size large enough as indicated? */
errno = 0;
v = strtoul(&((*algorithms)[j])[l], &end_ptr, 10);
if (errno || end_ptr[0] != '\0') {
logprintf(STDERR_FILENO,
"Error: Could not parse '%s' as a number.\n",
&((*algorithms)[j])[l]);
return 1;
}
if (v >= fips_key_sizes[i].min_size)
continue;
/* need to adjust min key size */
old = (*algorithms)[j];
} else {
/* append to strv */
j = g_strv_length(*algorithms);
*algorithms = g_realloc(*algorithms,
sizeof(char *) * (j + 1 + 1));
(*algorithms)[j + 1] = NULL;
old = NULL;
}
if (asprintf(&((*algorithms)[j]), "%s%u",
fips_key_sizes[i].keyword,
fips_key_sizes[i].min_size) < 0) {
(*algorithms)[j] = old;
return 1;
}
g_free(old);
}
return 0;
}
/*
* Set attributes in the profile that are needed due to FIPS-disabled
* algorithms.
*
* @attributes: pointer to NULL-termainted array of attributes
* @force: whether to test force adding attributes rather than test the
* algorithm
*/
int check_ossl_fips_disabled_set_attributes(gchar ***attributes, gboolean force)
{
const gchar *const fips_attributes[] = {
"no-sha1-signing",
"no-sha1-verification",
"no-unpadded-encryption",
NULL
};
if (force) {
if (!(*attributes) ||
(!strv_contains_all((const gchar *const*)*attributes,
(const char*[]){"fips-host", NULL}) &&
!strv_contains_all((const gchar *const*)*attributes,
fips_attributes)))
*attributes = strv_extend(*attributes, fips_attributes);
goto exit;
}
/* need to do checks */
if ((*attributes) &&
strv_contains_all((const gchar *const*)*attributes,
(const char*[]){"fips-host", NULL})) {
/* fips-host is already set */
goto exit;
}
if (!(*attributes) ||
!g_strv_contains((const gchar *const*)*attributes,
"no-sha1-signing")) {
/* 2048 bit key will not be reason signing fails but SHA1 */
if (check_rsa_sign("SHA1", 2048, RSA_PKCS1_PSS_PADDING)) {
/* set no-sha1-signing */
*attributes = strv_extend(*attributes,
(const char *[]){
"no-sha1-signing",
NULL
});
}
}
if (!(*attributes) ||
!g_strv_contains((const gchar *const*)*attributes,
"no-sha1-verification")) {
/* 2048 bit key will not be reason signing fails but SHA1 */
if (check_rsa_verify("SHA1", 2048, RSA_PKCS1_PSS_PADDING)) {
/* set no-sha1-verification */
*attributes = strv_extend(*attributes,
(const char *[]){
"no-sha1-verification",
NULL
});
}
}
if (!(*attributes) ||
!g_strv_contains((const gchar *const*)*attributes,
"no-unpadded-encryption")) {
if (check_rsa_encryption(NULL, 2048, RSA_NO_PADDING)) {
/* set no-unpadded-encryption */
*attributes = strv_extend(*attributes,
(const char *[]){
"no-unpadded-encryption",
NULL
});
}
}
exit:
return 0;
}

View File

@ -56,4 +56,9 @@ unsigned int check_ossl_algorithms_are_disabled(const gchar *const*algorithms,
#define FIX_ENABLE_SHA1_SIGNATURES (1 << 1) /* fix by setting OPENSSL_ENABLE_SHA1_SIGNATURES=1 */
#define FIX_DISABLE_CONFIG (1 << 2) /* fix by modifying openssl config (how?) */
int check_ossl_fips_disabled_remove_algorithms(gchar ***algorithms,
gboolean check);
int check_ossl_fips_disabled_set_attributes(gchar ***attributes,
gboolean check);
#endif /* _SWTPM_CHECK_ALGOS_H_ */

View File

@ -71,6 +71,8 @@
#include "seccomp_profile.h"
#include "tpmlib.h"
#include "mainloop.h"
#include "profile.h"
#include "swtpm_utils.h"
/* --log %s */
static const OptionDesc logging_opt_desc[] = {
@ -285,6 +287,9 @@ static const OptionDesc profile_opt_desc[] = {
}, {
.name = "profile",
.type = OPT_TYPE_STRING,
}, {
.name = "remove-disabled",
.type = OPT_TYPE_STRING,
},
END_OPTION_DESC
};
@ -1430,7 +1435,9 @@ error:
static int parse_profile_options(char *options, char **json_profile)
{
const char *remove_disabled;
OptionValues *ovs = NULL;
gboolean force = false;
char *error = NULL;
const char *profile;
const char *name;
@ -1458,6 +1465,21 @@ static int parse_profile_options(char *options, char **json_profile)
}
}
remove_disabled = option_get_string(ovs, "remove-disabled", NULL);
if (remove_disabled) {
if (!strcmp(remove_disabled, "check")) {
force = false;
} else if (!strcmp(remove_disabled, "fips-host")) {
force = true;
} else {
logprintf(STDERR_FILENO, "Invalid option parameter '%s' for 'remove-disabled'\n",
remove_disabled);
goto error;
}
if (profile_remove_fips_disabled_algorithms(json_profile, force))
goto error;
}
option_values_free(ovs);
return 0;
@ -1467,6 +1489,7 @@ oom_error:
"Out of memory to create JSON profile\n");
error:
SWTPM_G_FREE(*json_profile);
option_values_free(ovs);
free(error);

View File

@ -277,8 +277,11 @@ static const char *usage =
" releases the storage lock on outgoing migration\n"
"--print-capabilities : print capabilities and terminate\n"
"--print-states : print existing TPM states and terminate\n"
"--profile name=<name>|profile=<json-profile>\n"
"--profile name=<name>|profile=<json-profile>[,remove-disabled=check|fips-host]\n"
" : Set a profile on the TPM 2\n"
" remove-disabled: On the 'custom' profile remove algorithms\n"
" disabled by FIPS mode in OpenSSL; use 'check' to test the\n"
" algorithms first\n"
"--print-profiles\n"
" : print all profiles supported by libtpms\n"
"-h|--help : display this help screen and terminate\n"

93
src/swtpm/profile.c Normal file
View File

@ -0,0 +1,93 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* profile.c: Functions for handling profiles
*
* Author: Stefan Berger, stefanb@linux.ibm.com
*
* Copyright (c) IBM Corporation, 2024
*/
#include "config.h"
#include <stdio.h>
#include "profile.h"
#include "utils.h"
#include "swtpm_utils.h"
#include "check_algos.h"
/*
* If the given profile is the 'custom' profile then remove algorithms and key
* sizes disabled by FIPS (in OpenSSL).
*
* @json_profile: Pointer to the string with the JSON profile
* @check: Whether to check wheter the 'candidate' algorithms are actually
* disabled and only remove from profile if disabled.
*
* Return values:
* 0 : no error
* 1 : fatal error
* 2 : this is not the 'custom' profile
*/
int profile_remove_fips_disabled_algorithms(char **json_profile,
gboolean force)
{
g_autofree gchar *info_data = NULL;
g_auto(GStrv) algorithms = NULL;
g_auto(GStrv) attributes = NULL;
g_autofree gchar *value = NULL;
int ret;
ret = json_get_map_key_value(*json_profile, "Name", &value);
if (ret || !value || strcmp(value, "custom"))
return 2;
SWTPM_G_FREE(value);
ret = json_get_map_key_value(*json_profile, "Algorithms", &value);
if (ret == 1)
return 1;
if (ret == 2) {
info_data = TPMLIB_GetInfo(TPMLIB_INFO_RUNTIME_ALGORITHMS);
ret = json_get_submap_value(info_data, "RuntimeAlgorithms", "Implemented",
&value);
if (ret)
return 1;
}
algorithms = g_strsplit(value, ",", -1);
if (check_ossl_fips_disabled_remove_algorithms(&algorithms, force))
return 1;
g_free(value);
value = g_strjoinv(",", algorithms);
/* put algorithms into JSON */
ret = json_set_map_key_value(json_profile, "Algorithms", value);
if (ret)
return 1;
SWTPM_G_FREE(value);
/* disable sha1 signature and unpadded encryption using Attributes */
ret = json_get_map_key_value(*json_profile, "Attributes", &value);
if (ret == 1)
return 1;
if (value)
attributes = g_strsplit(value, ",", -1);
if (check_ossl_fips_disabled_set_attributes(&attributes, force))
return 1;
g_free(value);
if (attributes) {
value = g_strjoinv(",", attributes);
ret = json_set_map_key_value(json_profile, "Attributes", value);
if (ret)
return 1;
}
return 0;
}

19
src/swtpm/profile.h Normal file
View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* profile.h: Header for profile.c
*
* Author: Stefan Berger, stefanb@linux.ibm.com
*
* Copyright (c) IBM Corporation, 2024
*/
#ifndef _SWTPM_PROFILE_H_
#define _SWTPM_PROFILE_H_
#include <glib.h>
int profile_remove_fips_disabled_algorithms(char **json_profile,
gboolean check);
#endif /* _SWTPM_PROFILE_H_ */

View File

@ -198,8 +198,11 @@ static void usage(FILE *file, const char *prgname, const char *iface)
" : print capabilities and terminate\n"
"--print-states\n"
" : print existing TPM states and terminate\n"
"--profile name=<name>|profile=<json-profile>\n"
"--profile name=<name>|profile=<json-profile>[,remove-disabled=check|fips-host]\n"
" : Set a profile on the TPM 2\n"
" remove-disabled: On the 'custom' profile remove algorithms\n"
" disabled by FIPS mode in OpenSSL; use 'check' to test the\n"
" algorithms first\n"
"--print-profiles\n"
" : print all profiles supported by libtpms\n"
"-h|--help : display this help screen and terminate\n"

View File

@ -219,8 +219,11 @@ static void usage(FILE *file, const char *prgname, const char *iface)
" : print capabilities and terminate\n"
"--print-states\n"
" : print existing TPM states and terminate\n"
"--profile name=<name>|profile=<json-profile>\n"
"--profile name=<name>|profile=<json-profile>[,remove-disabled=check|fips-host]\n"
" : Set a profile on the TPM 2\n"
" remove-disabled: On the 'custom' profile remove algorithms\n"
" disabled by FIPS mode in OpenSSL; use 'check' to test the\n"
" algorithms first\n"
"--print-profiles\n"
" : print all profiles supported by libtpms\n"
"-h|--help : display this help screen and terminate\n"

View File

@ -29,7 +29,7 @@ exp='\{ "type": "swtpm", '\
'"flags-opt-disable-auto-shutdown", "ctrl-opt-terminate", '${seccomp}'"cmdarg-key-fd", '\
'"cmdarg-pwd-fd", "cmdarg-print-states", "cmdarg-chroot", "cmdarg-migration", '\
'"nvram-backend-dir", "nvram-backend-file", "cmdarg-profile", '\
'"cmdarg-print-profiles" \],'\
'"cmdarg-print-profiles", "profile-opt-remove-disabled" \],'\
'( "profiles": \{ \},)? '\
'"version": "[^"]*" \}'
if ! [[ ${msg} =~ ${exp} ]]; then

View File

@ -31,7 +31,7 @@ exp='\{ "type": "swtpm", '\
'"cmdarg-pwd-fd", "cmdarg-print-states", "cmdarg-chroot", "cmdarg-migration", '\
'"nvram-backend-dir", "nvram-backend-file"'\
'(, "rsa-keysize-1024")?(, "rsa-keysize-2048")?(, "rsa-keysize-3072")?, "cmdarg-profile", '\
'"cmdarg-print-profiles" \],'\
'"cmdarg-print-profiles", "profile-opt-remove-disabled" \],'\
'( "profiles": \{ "names": \[ [^]]*\], "algorithms": \{ [^\}]*\}, "commands": \{ [^\}]*\} },)? '\
'"version": "[^"]*" \}'
if ! [[ ${msg} =~ ${exp} ]]; then