diff --git a/.travis.yml b/.travis.yml index 4b16e3c86..8eb1921bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,6 +78,7 @@ matrix: CONFIG="--with-openssl --prefix=${PREFIX}" CHECK="distcheck" RUN_TEST="1" + LIBTPMS_GIT_CHECKOUT="origin/stefanberger/runtime_disable_algorithms.v3" - dist: bionic env: PREFIX="/usr" CONFIG="--with-openssl --prefix=/usr --enable-test-coverage" @@ -85,6 +86,7 @@ matrix: CHECK="check" SWTPM_TEST_IBMTSS2="1" SWTPM_TEST_STORE_VOLATILE="1" + LIBTPMS_GIT_CHECKOUT="origin/stefanberger/runtime_disable_algorithms.v3" before_script: - sudo apt-get -y install tss2 - sudo pip install cpp-coveralls @@ -103,6 +105,7 @@ matrix: CONFIG="--with-openssl --prefix=${PREFIX} --without-seccomp" SUDO="sudo" CHECK="check" + LIBTPMS_GIT_CHECKOUT="origin/stefanberger/runtime_disable_algorithms.v3" - env: CFLAGS="-fsanitize=address -g -fno-omit-frame-pointer -fno-sanitize-recover" LIBTPMS_CFLAGS="-fsanitize=address -g -fno-omit-frame-pointer -fno-sanitize-recover" LIBTPMS_CONFIG="--disable-use-openssl-functions" @@ -112,6 +115,7 @@ matrix: CONFIG="--with-openssl --prefix=${PREFIX} --without-seccomp" SUDO="sudo" CHECK="check" + LIBTPMS_GIT_CHECKOUT="origin/stefanberger/runtime_disable_algorithms.v3" - env: CFLAGS="-fsanitize=undefined -g -fno-omit-frame-pointer -fno-sanitize-recover" LIBTPMS_CFLAGS="-fsanitize=undefined -g -fno-omit-frame-pointer -fno-sanitize-recover" LIBS="-lubsan" @@ -120,3 +124,4 @@ matrix: CONFIG="--with-openssl --prefix=${PREFIX}" SUDO="sudo" CHECK="check" + LIBTPMS_GIT_CHECKOUT="origin/stefanberger/runtime_disable_algorithms.v3" diff --git a/configure.ac b/configure.ac index 6ffabac8f..aa6b0b92b 100644 --- a/configure.ac +++ b/configure.ac @@ -186,6 +186,14 @@ AC_CHECK_LIB(tpms, TPMLIB_ChooseTPMVersion,[true], AC_MSG_ERROR("libtpms 0.6 or later is required") ) +AC_CHECK_LIB(tpms, + [TPMLIB_SetProfile], + [AC_DEFINE_UNQUOTED([HAVE_LIBTPMS_SETPROFILE_API], 1, + [whether TPMLIB_SetProfile API is available]) + libtpms_has_setprofile="yes"], + [libtpms_has_setprofile="no"] +) + AC_SUBST([LIBTPMS_LIBS]) AC_CHECK_LIB(c, clock_gettime, LIBRT_LIBS="", LIBRT_LIBS="-lrt") @@ -626,7 +634,8 @@ printf "with_chardev : %5s (no = no chardev interface)\n" $with_chardev printf "with_vtpm_proxy : %5s (no = no vtpm proxy support; Linux only)\n" $with_vtpm_proxy printf "with_seccomp : %5s (no = no seccomp profile; Linux only)\n" $with_seccomp printf "\n" -printf "active PCR banks : %s\n" $DEFAULT_PCR_BANKS +printf "active PCR banks : %s\n" $DEFAULT_PCR_BANKS +printf "TPM 2 profile support : %s\n" $libtpms_has_setprofile echo echo "Version to build : $PACKAGE_VERSION" echo "Crypto library : $cryptolib" diff --git a/man/man8/swtpm.pod b/man/man8/swtpm.pod index 98b266009..d18a9cd57 100644 --- a/man/man8/swtpm.pod +++ b/man/man8/swtpm.pod @@ -333,7 +333,8 @@ may contain the following: "flags-opt-disable-auto-shutdown", "rsa-keysize-1024", "rsa-keysize-2048", - "rsa-keysize-3072" + "rsa-keysize-3072", + "cmdarg-profile" ], "version": "0.7.0" } @@ -408,6 +409,12 @@ The I<--flags> option supports the I flag. The TPM 2 supports the shown RSA key sizes. If none of the rsa-keysize verbs is shown then only RSA 2048 bit keys are supported. +=item B (since v0.8) + +The option <--profile> is supported to set a profile for a TPM 2 using either +the option parameter I to select a profile by its name or I +to provide a JSON-formatted profile. + =back =item B<--print-states> (since v0.7) @@ -451,6 +458,76 @@ 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=Eprofile-nameE|profile=Ejson-profileE> (since v0.8) + +This option allows to set a profile for a TPM 2 using either the option parameter +I to select a built-in profile by its name or I to provide a +JSON-formatted profile that must provide the name of a built-in profile and may +also include a list of algorithms that restricts the algorithms that a TPM 2 +enables. + +The profile can only be set the very first time that a TPM 2 instance is +started and for as long as no state file exists. The profile cannot be changed +anymore afterwards and, if passed again using this option, will be silently +ignored. + +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 +provide (RSA-PSS, Camellia, TDES, ...) and therefore they cannot be made +available by the TPM 2. Because of this it may be required that a profile be used +to avoid the TPM 2 entering failure mode upon self-testing. The effect of the +disablement of algoritms may be that certain programs and test suites requiring +them may not work correctly anymore. Therefore, profiles other than the default +profile have to be applied very carefully to avoid unnecessary application +failures where the only solution would be to not run them on a host that has +FIPS-enforcement enabled. + +To see the list of algorithms that are supported and can be disabled one +may use the I tool like this: + + $ swtpm_ioctl --tcp :2322 --info 0x08 | jq + { + "RuntimeAlgorithms": { + "Implemented": "rsa,rsa-min-size=1024,tdes,tdes-min-size=128,sha1,hmac,aes,aes-min-size=128,mgf1,keyedhash,xor,sha256,sha384,sha512,null,rsassa,rsaes,rsapss,oaep,ecdsa,ecdh,ecdaa,sm2,ecschnorr,ecmqv,kdf1-sp800-56a,kdf2,kdf1-sp800-108,ecc,ecc-min-size=192,symcipher,camellia,camellia-min-size=128,cmac,ctr,ofb,cbc,cfb,ecb", + "CanBeDisabled": "tdes,sha1,sha512,rsassa,rsaes,rsapss,ecmqv,camellia,cmac,ctr,ofb,cbc,ecb", + "Enabled": "rsa,rsa-min-size=1024,tdes,tdes-min-size=128,sha1,hmac,aes,aes-min-size=128,mgf1,keyedhash,xor,sha256,sha384,sha512,null,rsassa,rsaes,rsapss,oaep,ecdsa,ecdh,ecdaa,sm2,ecschnorr,ecmqv,kdf1-sp800-56a,kdf2,kdf1-sp800-108,ecc,symcipher,camellia,camellia-min-size=128,cmac,ctr,ofb,cbc,cfb,ecb", + "Disabled": "" + } + } + + +To see the list of available profiles one may use the I tool like +this: + + $ swtpm_ioctl --tcp :2322 --info 0x20 | jq + { + "AvailableProfiles": [ + { + "name": "default", + "stateFormatLevel": 1 + }, + { + "name": "fips-2022", + "stateFormatLevel": 1, + "commands": "0x11f-0x122,0x124-0x12e,0x130-0x140,0x142-0x159,0x15b-0x15e,0x160-0x165,0x167-0x174,0x176-0x178,0x17a-0x193,0x197", + "algorithms": "rsa,rsa-min-size=2048,hmac,aes,aes-min-size=128,mgf1,keyedhash,xor,sha256,sha384,sha512,null,rsassa,rsaes,oaep,ecdsa,ecdh,ecdaa,sm2,ecschnorr,ecmqv,kdf1-sp800-56a,kdf2,kdf1-sp800-108,ecc,symcipher,cmac,ctr,ofb,cbc,cfb,ecb,ecc-min-size=256" + } + ] + } + + +To see the current profile that a TPM 2 instance is running with one may use the +I tool: + + $ swtpm_ioctl --tcp :2322 --info 0x10 | jq + { + "ActiveProfile": { + "name": "default", + "stateFormatLevel": 1 + } + } + =item B<-h|--help> Display usage info. diff --git a/man/man8/swtpm_setup.pod b/man/man8/swtpm_setup.pod index 636e35d02..c2833eccd 100644 --- a/man/man8/swtpm_setup.pod +++ b/man/man8/swtpm_setup.pod @@ -193,6 +193,31 @@ size is used. This option allows the reconfiguration of the active PCR banks of a TPM 2 using the I<--pcr-banks> option. +=item B<--profile > + +Configure a TPM 2 with the given profile. Example profiles look +like this: + + {"name": "null"} + + {"name": "default"} + + { + "name":"default", + "algorithms":"rsa,rsa-min-size=1024,tdes-min-size=128,hmac,aes,\ + aes-min-size=128,mgf1,keyedhash,xor,sha256,sha384,\ + sha512,null,rsassa,rsaes,rsapss,oaep,ecdsa,ecdh,ecdaa,\ + sm2,ecschnorr,ecmqv,kdf1-sp800-56a,kdf2,kdf1-sp800-108,\ + ecc,ecc-min-size=192,symcipher,camellia,\ + camellia-min-size=128,cmac,ctr,ofb,cbc,cfb,ecb" + } + + +The JSON profile must contain the 'name' field with a name of a profile +supported by libtpms. The profile may contain an algorithms field with a +list of algorithms to enable. Unknown fields in the JSON profile will be +ignored. + =item B<--print-capabilities> (since v0.2) Print capabilities that were added to swtpm_setup after version 0.1. diff --git a/src/swtpm/Makefile.am b/src/swtpm/Makefile.am index 2a659500a..d1bd92b9f 100644 --- a/src/swtpm/Makefile.am +++ b/src/swtpm/Makefile.am @@ -75,6 +75,7 @@ libswtpm_libtpms_la_CFLAGS = \ $(CFLAGS) \ $(HARDENING_CFLAGS) \ $(GLIB_CFLAGS) \ + $(JSON_GLIB_CFLAGS) \ $(LIBSECCOMP_CFLAGS) libswtpm_libtpms_la_LDFLAGS = \ @@ -84,6 +85,7 @@ libswtpm_libtpms_la_LDFLAGS = \ libswtpm_libtpms_la_LIBADD = \ $(LIBTPMS_LIBS) \ $(GLIB_LIBS) \ + $(JSON_GLIB_LIBS) \ $(LIBRT_LIBS) \ $(LIBSECCOMP_LIBS) \ $(LIBCRYPTO_LIBS) diff --git a/src/swtpm/capabilities.c b/src/swtpm/capabilities.c index 6e194056f..b9c1daac3 100644 --- a/src/swtpm/capabilities.c +++ b/src/swtpm/capabilities.c @@ -45,6 +45,9 @@ #include #include +#include + +#include "compiler_dependencies.h" #include "capabilities.h" #include "logging.h" #include "swtpm_nvstore.h" @@ -114,6 +117,73 @@ static int get_rsa_keysize_caps(char **keysizecaps) goto cleanup; } +#ifdef HAVE_LIBTPMS_SETPROFILE_API + +static int get_profiles(gchar **profiles) +{ + char *info_data = TPMLIB_GetInfo(32 /*TPMLIB_INFO_AVAILABLE_PROFILES*/); + JsonParser *jp = NULL; + JsonReader *jr = NULL; + g_autoptr(GError) error = NULL; + JsonNode *root; + gint i, num; + int ret = 0; + GString *gstr = g_string_new(NULL); + + jp = json_parser_new(); + + if (!json_parser_load_from_data(jp, info_data, -1, &error)) { + logprintf(STDERR_FILENO, + "Could not parse JSON data: %s\n", error->message); + goto error; + } + + root = json_parser_get_root(jp); + jr = json_reader_new(root); + + if (!json_reader_read_member(jr, "AvailableProfiles")) { + logprintf(STDERR_FILENO, + "Missing 'AvailableProfiles' field: %s\n", + info_data); + goto error_unref_jr; + } + + num = json_reader_count_elements(jr); + for (i = 0; i < num; i++) { + if (!json_reader_read_element(jr, i) || + !json_reader_read_member(jr, "name")) { + logprintf(STDERR_FILENO, + "Failed to traverse JSON list.\n"); + goto error_unref_jr; + } + g_string_append_printf(gstr, "%s\"%s\"", + i > 0 ? ", " : " ", + json_reader_get_string_value(jr)); + json_reader_end_element(jr); + json_reader_end_element(jr); + } + + +error_unref_jr: + g_object_unref(jr); + +error: + g_object_unref(jp); + *profiles = g_string_free(gstr, false); + free(info_data); + + return ret; +} + +#else + +static int get_profiles(gchar **profiles SWTPM_ATTR_UNUSED) +{ + return 0; +} + +#endif + int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion) { char *string = NULL; @@ -129,6 +199,13 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion) char *keysizecaps = NULL; const char *nvram_backend_dir = "\"nvram-backend-dir\", "; const char *nvram_backend_file = "\"nvram-backend-file\""; +#ifdef HAVE_LIBTPMS_SETPROFILE_API + const char *cmdarg_profile = "\"cmdarg-profile\""; +#else + const char *cmdarg_profile = NULL; +#endif + g_autofree gchar *profiles = NULL; + bool comma1; /* ignore errors */ TPMLIB_ChooseTPMVersion(tpmversion); @@ -137,17 +214,28 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion) if (ret < 0) goto cleanup; + if (tpmversion == TPMLIB_TPM_VERSION_2) { + ret = get_profiles(&profiles); + if (ret < 0) + goto cleanup; + } + if (TPMLIB_ChooseTPMVersion(TPMLIB_TPM_VERSION_1_2) == TPM_SUCCESS) with_tpm1 = "\"tpm-1.2\", "; if (TPMLIB_ChooseTPMVersion(TPMLIB_TPM_VERSION_2) == TPM_SUCCESS) with_tpm2 = "\"tpm-2.0\", "; + comma1 = cmdarg_profile || profiles; + n = asprintf(&string, "{ " "\"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" " ], " +#ifdef HAVE_LIBTPMS_SETPROFILE_API + "\"profiles\": [%s ], " +#endif "\"version\": \"" VERSION "\" " "}", with_tpm1, @@ -164,7 +252,13 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion) true ? "\"cmdarg-migration\", " : "", nvram_backend_dir, nvram_backend_file, - keysizecaps ? keysizecaps : "" + keysizecaps ? keysizecaps : "", + comma1 ? ", " : "", + cmdarg_profile ? cmdarg_profile : "" +#ifdef HAVE_LIBTPMS_SETPROFILE_API + , + profiles ? profiles : "" +#endif ); if (n < 0) { diff --git a/src/swtpm/common.c b/src/swtpm/common.c index bb87f3a1c..28c7b87f7 100644 --- a/src/swtpm/common.c +++ b/src/swtpm/common.c @@ -278,6 +278,17 @@ static const OptionDesc migration_opt_desc[] = { END_OPTION_DESC }; +static const OptionDesc profile_opt_desc[] = { + { + .name = "name", + .type = OPT_TYPE_STRING, + }, { + .name = "profile", + .type = OPT_TYPE_STRING, + }, + END_OPTION_DESC +}; + /* * handle_log_options: * Parse and act upon the parsed log options. Initialize the logging. @@ -1410,6 +1421,51 @@ static int parse_migration_options(char *options, bool *incoming_migration, return -1; } +static int parse_profile_options(char *options, char **json_profile) +{ + OptionValues *ovs = NULL; + char *error = NULL; + const char *profile; + const char *name; + + ovs = options_parse(options, profile_opt_desc, &error); + if (!ovs) { + logprintf(STDERR_FILENO, "Error parsing profile options: %s\n", error); + goto error; + } + + profile = option_get_string(ovs, "profile", NULL); + name = option_get_string(ovs, "name", NULL); + if (profile && name) { + logprintf(STDERR_FILENO, "Profile and name cannot be provided at the same time\n"); + goto error; + } + if (profile) { + *json_profile = strdup(profile); + if (!*json_profile) + goto oom_error; + } else if (name) { + if (asprintf(json_profile, "{\"name\":\"%s\"}", name) < 0) { + logprintf(STDERR_FILENO, "Out of memory.\n"); + goto oom_error; + } + } + + option_values_free(ovs); + + return 0; + +oom_error: + logprintf(STDERR_FILENO, + "Out of memory to create JSON profile\n"); + +error: + option_values_free(ovs); + free(error); + + return -1; +} + /* * handle_migration_options: * Parse the 'migration' options. @@ -1432,5 +1488,27 @@ int handle_migration_options(char *options, bool *incoming_migration, release_lock_outgoing) < 0) return -1; + return 0l; +} + +/* + * handle_profile_options: + * Parse the 'profile' options. + * + * @options: the porfile options to parse + * @json_profile: pointer to a buffer for the profile rules to pass to libtpms + * + * Returns 0 on success, -1 on failure. + */ +int handle_profile_options(char *options, char **json_profile) +{ + *json_profile = NULL; + + if (!options) + return 0; + + if (parse_profile_options(options, json_profile) < 0) + return -1; + return 0; } diff --git a/src/swtpm/common.h b/src/swtpm/common.h index bc9418887..bb26f15db 100644 --- a/src/swtpm/common.h +++ b/src/swtpm/common.h @@ -68,5 +68,7 @@ static inline int handle_seccomp_options(char *options SWTPM_ATTR_UNUSED, int handle_migration_options(char *options, bool *incoming_migration, bool *release_lock_outgoing); +int handle_profile_options(char *options, char **json_profile); + #endif /* _SWTPM_COMMON_H_ */ diff --git a/src/swtpm/ctrlchannel.c b/src/swtpm/ctrlchannel.c index 7b77a0420..3ad6555b7 100644 --- a/src/swtpm/ctrlchannel.c +++ b/src/swtpm/ctrlchannel.c @@ -573,7 +573,8 @@ int ctrlchannel_process_fd(int fd, mlp->storage_locked = !mlp->incoming_migration; res = tpmlib_start(be32toh(init_p->u.req.init_flags), - mlp->tpmversion, mlp->storage_locked); + mlp->tpmversion, mlp->storage_locked, + mlp->json_profile); if (res) { logprintf(STDERR_FILENO, "Error: Could not initialize the TPM\n"); diff --git a/src/swtpm/cuse_tpm.c b/src/swtpm/cuse_tpm.c index faafda39d..7cccec13e 100644 --- a/src/swtpm/cuse_tpm.c +++ b/src/swtpm/cuse_tpm.c @@ -130,6 +130,9 @@ static bool g_release_lock_outgoing; static unsigned int g_locking_retries; #define DEFAULT_LOCKING_RETRIES 300 /* 300 * 10ms */ +/* the JSON profile for the TPM 2 */ +static char *g_json_profile; + #if GLIB_MAJOR_VERSION >= 2 # if GLIB_MINOR_VERSION >= 32 @@ -159,6 +162,7 @@ struct cuse_param { char *localitydata; char *seccompdata; char *migrationdata; + char *profiledata; unsigned int seccomp_action; char *flagsdata; uint16_t startupType; @@ -273,6 +277,10 @@ static const char *usage = " releases the storage lock on outgoing migration\n" "--print-capabilites : print capabilities and terminate\n" "--print-states : print existing TPM states and terminate\n" +#ifdef HAVE_LIBTPMS_SETPROFILE_API +"--profile name=|profile=\n" +" : Set a profile on the TPM 2\n" +#endif "-h|--help : display this help screen and terminate\n" "\n"; @@ -496,7 +504,7 @@ static void worker_thread(gpointer data, gpointer user_data SWTPM_ATTR_UNUSED) * @res: the result from starting the TPM */ static int tpm_start(uint32_t flags, TPMLIB_TPMVersion l_tpmversion, - TPM_RESULT *res) + const char *json_profile, TPM_RESULT *res) { DIR *dir; const char *uri = tpmstate_get_backend_uri(); @@ -537,7 +545,7 @@ static int tpm_start(uint32_t flags, TPMLIB_TPMVersion l_tpmversion, g_storage_locked = !g_incoming_migration; - *res = tpmlib_start(flags, l_tpmversion, g_storage_locked); + *res = tpmlib_start(flags, l_tpmversion, g_storage_locked, json_profile); if (*res != TPM_SUCCESS) goto error_del_pool; @@ -1170,7 +1178,8 @@ static void ptm_ioctl(fuse_req_t req, int cmd, void *arg, TPMLIB_Terminate(); tpm_running = false; - if (tpm_start(init_p->u.req.init_flags, tpmversion, &res) < 0) { + if (tpm_start(init_p->u.req.init_flags, tpmversion, + g_json_profile, &res) < 0) { logprintf(STDERR_FILENO, "Error: Could not initialize the TPM.\n"); } else { @@ -1601,6 +1610,9 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac {"print-capabilities" , no_argument, 0, 'a'}, {"print-states" , no_argument, 0, 'e'}, +#ifdef HAVE_LIBTPMS_SETPROFILE_API + {"profile" , required_argument, 0, 'I'}, +#endif {NULL , 0 , 0, 0 }, }; struct cuse_info cinfo; @@ -1715,6 +1727,9 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac case 'i': /* --migration */ param.migrationdata = optarg; break; + case 'I': /* --profile */ + param.profiledata = optarg; + break; case 'h': /* help */ fprintf(stdout, usage, prgname, iface); goto exit; @@ -1816,7 +1831,8 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac handle_flags_options(param.flagsdata, &need_init_cmd, ¶m.startupType, &g_disable_auto_shutdown) < 0 || handle_migration_options(param.migrationdata, &g_incoming_migration, - &g_release_lock_outgoing) < 0) { + &g_release_lock_outgoing) < 0 || + handle_profile_options(param.profiledata, &g_json_profile) < 0) { ret = -3; goto exit; } @@ -1862,7 +1878,7 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac worker_thread_init(); if (!need_init_cmd) { - if (tpm_start(0, tpmversion, &res) < 0) { + if (tpm_start(0, tpmversion, g_json_profile, &res) < 0) { ret = -1; goto exit; } @@ -1885,6 +1901,7 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac ret = ptm_cuse_lowlevel_main(1, argv, &cinfo, &clops, ¶m); exit: + free(g_json_profile); ptm_cleanup(); free(cinfo_argv[0]); diff --git a/src/swtpm/fips.c b/src/swtpm/fips.c index e67adcbe0..f3ebdea59 100644 --- a/src/swtpm/fips.c +++ b/src/swtpm/fips.c @@ -37,8 +37,12 @@ #include "config.h" +#include + #include "fips.h" #include "logging.h" +#include "utils.h" +#include "swtpm_utils.h" #include @@ -111,3 +115,55 @@ int fips_mode_disable(void) return 0; } #endif + +/* list of FIPS-disabled algorithms that TPM 2 may enabled */ +static const char *fips_disabled[] = { + "camellia", + "rsapss", + "sha1", + "tdes", +}; + +/* list of minimum required key sizes for FIPS */ +static const struct key_sizes { + const char *keyword; + unsigned int min_size; +} key_sizes[] = { + { + .keyword = "rsa-min-size=", + .min_size = 2048, + }, { + .keyword = "ecc-min-size=", + .min_size = 224, + } +}; + +/* Determine whether any of the algorithms in the array are FIPS-disable */ +bool fips_algorithms_are_disabled(gchar *const*algorithms) +{ + bool found = false; + size_t i, l; + int j; + unsigned long v; + + for (i = 0; i < ARRAY_LEN(fips_disabled); i++) { + if (strv_strncmp(algorithms, fips_disabled[i], -1) >= 0) { + found = true; + break; + } + } + + for (i = 0; i < ARRAY_LEN(key_sizes); i++) { + l = strlen(key_sizes[i].keyword); + j = strv_strncmp(algorithms, key_sizes[i].keyword, l); + if (j >= 0) { + /* trusting value from libtpms is well formatted avoiding checks */ + v = strtoul(&(algorithms[j][l]), NULL, 10); + if (v < key_sizes[i].min_size) { + found = true; + break; + } + } + } + return found; +} diff --git a/src/swtpm/fips.h b/src/swtpm/fips.h index 1761def56..9c4dfbf88 100644 --- a/src/swtpm/fips.h +++ b/src/swtpm/fips.h @@ -40,7 +40,10 @@ #include +#include + bool fips_mode_enabled(void); int fips_mode_disable(void); +bool fips_algorithms_are_disabled(gchar *const*algorithms); #endif /* _SWTPM_FIPS_H_ */ diff --git a/src/swtpm/mainloop.h b/src/swtpm/mainloop.h index d58c21d20..12335c973 100644 --- a/src/swtpm/mainloop.h +++ b/src/swtpm/mainloop.h @@ -73,6 +73,8 @@ struct mainLoopParams { the lock on outgoing migration. */ unsigned int locking_retries; #define DEFAULT_LOCKING_RETRIES 300 /* 300 * 10ms */ + /* TPM 2 profile on JSON format */ + char *json_profile; }; int mainLoop(struct mainLoopParams *mlp, diff --git a/src/swtpm/options.c b/src/swtpm/options.c index f89bee4c1..476e82a44 100644 --- a/src/swtpm/options.c +++ b/src/swtpm/options.c @@ -194,6 +194,97 @@ option_value_add(OptionValues *ovs, const OptionDesc optdesc, const char *val, return ret; } +/* + * option_parse_token: + * Parse an option that may have one of the following formats + * 1) name[,n2=v2[,...]] + * 2) name=value[,n2=v2[,...]] + * 3) name={...}[,n2=v2[,...]] + * Case 3 allows to parse values containing a JSON map. + * + * @str: On first call this must be the string to break into tokens; + * on subsequent calls this should be NULL + * @saveptr: A pointer to a pointer where this function stores the address + * to continue parsing next time + * @tok: The function returns the pointer to the beginning of the token here; + * The token lies withing the given @str and @str may be modified for + * NUL-terminating the token + * @char: A pointer where to store a string pointer in case of error + * + * In case of success this function returns 0, -1 otherwise. + */ +static int +option_parse_token(char *str, char **saveptr, char **tok, char **error) +{ + char *comma, *equals; + + if (!str) + str = *saveptr; + + *tok = str; + if (!str) + return 0; + + equals = strchr(str, '='); + comma = strchr(str, ','); + /* don't care about '=' after a ',' */ + if (equals > comma) + equals = NULL; + + if (equals != NULL && equals[1] == '{') { + unsigned long c = 1; + char *eom = NULL; + size_t i = 2; + + /* find terminating "}" considering nested maps */ + eom = NULL; + while (equals[i] && !eom) { + switch (equals[i]) { + case '{': + c++; + break; + case '}': + c--; + if (c == 0) + eom = &equals[i]; + break; + } + i++; + } + + if (!eom) { + option_error_set(error, "Unterminated JSON map."); + return -1; + } + + /* what follows the "}" ? */ + switch (eom[1]) { + case ',': + eom[1] = 0; + *saveptr = &eom[2]; + break; + case '\0': + /* this was the last item */ + *saveptr = NULL; + break; + default: + option_error_set(error, "Unexpected character following JSON map."); + return -1; + } + } else { + if (!comma) { + /* this is the last item */ + *saveptr = NULL; + } else { + comma[0] = '\0'; + *saveptr = &comma[1]; + } + } + *tok = str; + + return 0; +} + /* * options_parse: * Parse the string of options following the template; return the @@ -227,7 +318,10 @@ options_parse(char *opts, const OptionDesc optdesc[], char **error) saveptr = opts_bak; /* make coverity happy */ - tok = strtok_r(opts_bak, ",", &saveptr); + if (option_parse_token(opts_bak, &saveptr, &tok, error) < 0) { + goto error; + } + while (tok) { size_t toklen = strlen(tok); @@ -257,7 +351,9 @@ options_parse(char *opts, const OptionDesc optdesc[], char **error) goto error; } - tok = strtok_r(NULL, ",", &saveptr); + if (option_parse_token(NULL, &saveptr, &tok, error) < 0) { + goto error; + } } free(opts_bak); diff --git a/src/swtpm/swtpm.c b/src/swtpm/swtpm.c index 2573404c2..2b103dd9a 100644 --- a/src/swtpm/swtpm.c +++ b/src/swtpm/swtpm.c @@ -197,15 +197,20 @@ 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" +#ifdef HAVE_LIBTPMS_SETPROFILE_API + "--profile name=|profile=\n" + " : Set a profile on the TPM 2\n" +#endif "-h|--help : display this help screen and terminate\n" "\n", prgname, iface); } -static void swtpm_cleanup(struct ctrlchannel *cc, struct server *server) +static void swtpm_cleanup(struct mainLoopParams *mlp, struct server *server) { + free(mlp->json_profile); pidfile_remove(); - ctrlchannel_free(cc); + ctrlchannel_free(mlp->cc); server_free(server); log_global_free(); tpmstate_global_free(); @@ -247,6 +252,7 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) char *migrationdata = NULL; char *runas = NULL; char *chroot = NULL; + char *profiledata = NULL; bool need_init_cmd = true; #ifdef DEBUG time_t start_time; @@ -279,6 +285,9 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) {"print-capabilities" , no_argument, 0, 'a'}, {"print-states", no_argument, 0, 'e'}, +#ifdef HAVE_LIBTPMS_SETPROFILE_API + {"profile" , required_argument, 0, 'I'}, +#endif {NULL , 0 , 0, 0 }, }; @@ -425,6 +434,10 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) migrationdata = optarg; break; + case 'I': + profiledata = optarg; + break; + default: usage(stderr, prgname, iface); exit(EXIT_FAILURE); @@ -501,7 +514,8 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) handle_flags_options(flagsdata, &need_init_cmd, &mlp.startupType, &mlp.disable_auto_shutdown) < 0 || handle_migration_options(migrationdata, &mlp.incoming_migration, - &mlp.release_lock_outgoing) < 0) { + &mlp.release_lock_outgoing) < 0 || + handle_profile_options(profiledata, &mlp.json_profile) < 0) { goto exit_failure; } @@ -540,7 +554,8 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) if (!need_init_cmd) { mlp.storage_locked = !mlp.incoming_migration; - if ((rc = tpmlib_start(0, mlp.tpmversion, mlp.storage_locked))) + if ((rc = tpmlib_start(0, mlp.tpmversion, mlp.storage_locked, + mlp.json_profile))) goto error_no_tpm; tpm_running = true; } @@ -569,7 +584,7 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) close(notify_fd[1]); notify_fd[1] = -1; - swtpm_cleanup(mlp.cc, server); + swtpm_cleanup(&mlp, server); /* Fatal initialization errors cause the program to abort */ if (rc == 0) { @@ -581,12 +596,12 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) } exit_failure: - swtpm_cleanup(mlp.cc, server); + swtpm_cleanup(&mlp, server); exit(EXIT_FAILURE); exit_success: - swtpm_cleanup(mlp.cc, server); + swtpm_cleanup(&mlp, server); exit(EXIT_SUCCESS); } diff --git a/src/swtpm/swtpm_chardev.c b/src/swtpm/swtpm_chardev.c index 89594224f..126d1cb28 100644 --- a/src/swtpm/swtpm_chardev.c +++ b/src/swtpm/swtpm_chardev.c @@ -218,15 +218,20 @@ 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" +#ifdef HAVE_LIBTPMS_SETPROFILE_API + "--profile name=|profile=\n" + " : Set a profile on the TPM 2\n" +#endif "-h|--help : display this help screen and terminate\n" "\n", prgname, iface); } -static void swtpm_cleanup(struct ctrlchannel *cc) +static void swtpm_cleanup(struct mainLoopParams *mlp) { + free(mlp->json_profile); pidfile_remove(); - ctrlchannel_free(cc); + ctrlchannel_free(mlp->cc); log_global_free(); tpmstate_global_free(); SWTPM_NVRAM_Shutdown(); @@ -302,6 +307,7 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i char *migrationdata = NULL; char *runas = NULL; char *chroot = NULL; + char *profiledata = NULL; #ifdef WITH_VTPM_PROXY bool use_vtpm_proxy = false; #endif @@ -338,6 +344,9 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i {"print-capabilities" , no_argument, 0, 'a'}, {"print-states", no_argument, 0, 'e'}, +#ifdef HAVE_LIBTPMS_SETPROFILE_API + {"profile" , required_argument, 0, 'I'}, +#endif {NULL , 0 , 0, 0 }, }; @@ -475,6 +484,10 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i migrationdata = optarg; break; + case 'I': + profiledata = optarg; + break; + default: usage(stderr, prgname, iface); exit(EXIT_FAILURE); @@ -563,7 +576,8 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i handle_flags_options(flagsdata, &need_init_cmd, &mlp.startupType, &mlp.disable_auto_shutdown) < 0 || handle_migration_options(migrationdata, &mlp.incoming_migration, - &mlp.release_lock_outgoing) < 0) { + &mlp.release_lock_outgoing) < 0 || + handle_profile_options(profiledata, &mlp.json_profile) < 0) { goto exit_failure; } @@ -588,7 +602,8 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i if (!need_init_cmd) { mlp.storage_locked = !mlp.incoming_migration; - if ((rc = tpmlib_start(0, mlp.tpmversion, mlp.storage_locked))) + if ((rc = tpmlib_start(0, mlp.tpmversion, mlp.storage_locked, + mlp.json_profile))) goto error_no_tpm; tpm_running = true; } @@ -620,7 +635,7 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i close(notify_fd[1]); notify_fd[1] = -1; - swtpm_cleanup(mlp.cc); + swtpm_cleanup(&mlp); /* Fatal initialization errors cause the program to abort */ if (rc == 0) { @@ -632,12 +647,12 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i } exit_failure: - swtpm_cleanup(mlp.cc); + swtpm_cleanup(&mlp); exit(EXIT_FAILURE); exit_success: - swtpm_cleanup(mlp.cc); + swtpm_cleanup(&mlp); exit(EXIT_SUCCESS); } diff --git a/src/swtpm/tpmlib.c b/src/swtpm/tpmlib.c index 7dfc3c24d..0ce8ba661 100644 --- a/src/swtpm/tpmlib.c +++ b/src/swtpm/tpmlib.c @@ -48,6 +48,8 @@ #include #include +#include + #include "tpmlib.h" #include "logging.h" #include "tpm_ioctl.h" @@ -105,14 +107,81 @@ TPM_RESULT tpmlib_choose_tpm_version(TPMLIB_TPMVersion tpmversion) return res; } +/* This function only applies to TPM2: If FIPS mode was enabled on the host, + * determine whether OpenSSL needs to deactivate FIPS mode. It doesn't need + * to deactivate it if a profile was chosen that has no algorithms that FIPS + * deactivates, otherwise it has to deactivate FIPS mode in the OpenSSL + * instance being used. + */ +static bool tpmlib_check_need_disable_fips_mode_tpm2(void) +{ + char *info_data = TPMLIB_GetInfo(/*TPMLIB_INFO_RUNTIME_ALGORITHMS*/ 8); + g_autofree gchar *enabled = NULL; + bool need_disable = false; + gchar **algorithms; + int ret; + + ret = json_get_submap_value(info_data, "RuntimeAlgorithms", "Enabled", + &enabled); + if (ret) { + /* libtpms <= v0.9; need to disable FIPS mode if enabled + since libtpms always used sha1, tdes etc. */ + need_disable = true; + goto error; + } + + algorithms = g_strsplit(enabled, ",", -1); + + need_disable = fips_algorithms_are_disabled(algorithms); + + g_strfreev(algorithms); +error: + free(info_data); + + return need_disable; +} + +/* Determine wheter FIPS mode is enabled in the crypto library. If FIPS mode is + * enabled check wether any of the algorithms that the TPM 2 uses would need + * FIPS mode to be disabled for the TPM 2 to work and try to disable it. + */ +static int tpmlib_maybe_disable_fips_mode(TPMLIB_TPMVersion tpmversion) +{ + bool disable_fips = false; + int ret = 0; + + if (fips_mode_enabled()) { + switch (tpmversion) { + case TPMLIB_TPM_VERSION_1_2: + disable_fips = true; + break; + case TPMLIB_TPM_VERSION_2: + disable_fips = tpmlib_check_need_disable_fips_mode_tpm2(); + break; + } + if (disable_fips && fips_mode_disable()) + ret = 1; + } + return ret; +} + TPM_RESULT tpmlib_start(uint32_t flags, TPMLIB_TPMVersion tpmversion, - bool lock_nvram) + bool lock_nvram, const char *json_profile) { TPM_RESULT res; if ((res = tpmlib_choose_tpm_version(tpmversion)) != TPM_SUCCESS) return res; +#ifdef HAVE_LIBTPMS_SETPROFILE_API + if (json_profile != NULL && tpmversion == TPMLIB_TPM_VERSION_2 && + (res = TPMLIB_SetProfile(json_profile)) != TPM_SUCCESS) { + logprintf(STDERR_FILENO, + "Error: Could not set profile for TPM2.\n"); + return res; + } +#endif + if ((res = TPMLIB_MainInit()) != TPM_SUCCESS) { logprintf(STDERR_FILENO, "Error: Could not initialize libtpms.\n"); @@ -136,8 +205,10 @@ TPM_RESULT tpmlib_start(uint32_t flags, TPMLIB_TPMVersion tpmversion, } } - if (fips_mode_enabled() && fips_mode_disable() < 0) + if (tpmlib_maybe_disable_fips_mode(tpmversion)) { + res = TPM_FAIL; goto error_terminate; + } return TPM_SUCCESS; diff --git a/src/swtpm/tpmlib.h b/src/swtpm/tpmlib.h index fb2cf014d..90f9cb869 100644 --- a/src/swtpm/tpmlib.h +++ b/src/swtpm/tpmlib.h @@ -48,7 +48,7 @@ enum TPMLIB_StateType tpmlib_blobtype_to_statetype(uint32_t blobtype); TPM_RESULT tpmlib_register_callbacks(struct libtpms_callbacks *cbs); TPM_RESULT tpmlib_choose_tpm_version(TPMLIB_TPMVersion tpmversion); TPM_RESULT tpmlib_start(uint32_t flags, TPMLIB_TPMVersion tpmversion, - bool lock_nvram); + bool lock_nvram, const char *profile); int tpmlib_get_tpm_property(enum TPMLIB_TPMProperty prop); uint32_t tpmlib_get_cmd_ordinal(const unsigned char *request, size_t req_len); bool tpmlib_is_request_cancelable(TPMLIB_TPMVersion tpmversion, diff --git a/src/swtpm/utils.c b/src/swtpm/utils.c index 14cc97622..32ca063fb 100644 --- a/src/swtpm/utils.c +++ b/src/swtpm/utils.c @@ -51,6 +51,8 @@ #include #endif +#include + #include "utils.h" #include "logging.h" #include "tpmlib.h" @@ -346,3 +348,57 @@ ssize_t read_eintr(int fd, void *buffer, size_t buflen) return n; } } + +int json_get_submap_value(const char *json_input, const char *field_name, + const char *field_name2, char **value) +{ + g_autoptr(GError) error = NULL; + JsonParser *jp = NULL; + JsonReader *jr = NULL; + JsonNode *root; + int ret = -1; + + jp = json_parser_new(); + if (!json_parser_load_from_data(jp, json_input, -1, &error)) { + logprintf(STDERR_FILENO, + "Could not parse JSON '%s': %s\n", json_input, error->message); + goto error_unref_jp; + } + + root = json_parser_get_root(jp); + jr = json_reader_new(root); + + if (!json_reader_read_member(jr, field_name)) { + logprintf(STDERR_FILENO, "Missing '%s' field in '%s'\n", + field_name, json_input); + goto error_unref_jr; + } + + if (!json_reader_read_member(jr, field_name2)) { + logprintf(STDERR_FILENO, "Missing '%s/%s' field in '%s'\n", + field_name, field_name2, json_input); + goto error_unref_jr; + } + *value = g_strdup(json_reader_get_string_value(jr)); + + ret = 0; + +error_unref_jr: + g_object_unref(jr); + +error_unref_jp: + g_object_unref(jp); + + return ret; +} + +int strv_strncmp(gchar *const*str_array, const gchar *s, size_t n) +{ + size_t i; + + for (i = 0; str_array[i]; i++) { + if (strncmp(str_array[i], s, n) == 0) + return (int)i; + } + return -1; +} diff --git a/src/swtpm/utils.h b/src/swtpm/utils.h index d3e6b3043..4ef6da3a1 100644 --- a/src/swtpm/utils.h +++ b/src/swtpm/utils.h @@ -43,6 +43,8 @@ #include #include +#include + #include #define ROUND_TO_NEXT_POWER_OF_2_32(a) \ @@ -72,4 +74,9 @@ ssize_t writev_full(int fd, const struct iovec *iov, int iovcnt); ssize_t read_eintr(int fd, void *buffer, size_t buflen); +int json_get_submap_value(const char *json_input, const char *field_name, + const char *field_name2, char **value); + +int strv_strncmp(gchar *const*str_array, const gchar *s, size_t n); + #endif /* _SWTPM_UTILS_H_ */ diff --git a/src/swtpm_setup/Makefile.am b/src/swtpm_setup/Makefile.am index bad923980..cfc49abd4 100644 --- a/src/swtpm_setup/Makefile.am +++ b/src/swtpm_setup/Makefile.am @@ -8,6 +8,7 @@ MY_CFLAGS = @MY_CFLAGS@ MY_LDFLAGS = @MY_LDFLAGS@ noinst_HEADERS = \ + profile.h \ swtpm.h \ swtpm_setup.h \ swtpm_setup_utils.h @@ -16,6 +17,7 @@ bin_PROGRAMS = \ swtpm_setup swtpm_setup_SOURCES = \ + profile.c \ swtpm.c \ swtpm_setup.c \ swtpm_setup_utils.c \ diff --git a/src/swtpm_setup/profile.c b/src/swtpm_setup/profile.c new file mode 100644 index 000000000..1c07a668c --- /dev/null +++ b/src/swtpm_setup/profile.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * profile.c: TPM 2 profile handling + * + * Author: Stefan Berger, stefanb@linux.ibm.com + * + * Copyright (c) IBM Corporation, 2022 + */ + +#include "config.h" + +#include + +#include "profile.h" +#include "swtpm_utils.h" + +#ifdef HAVE_LIBTPMS_SETPROFILE_API + +/* Return the names of the supported profiles */ +int get_profile_names(const gchar *swtpm_capabilities_json, gchar ***profile_names) +{ + g_autoptr(GError) error = NULL; + JsonParser *jp = NULL; + JsonReader *jr = NULL; + JsonNode *root; + gint i, num; + int ret = 1; + + jp = json_parser_new(); + if (!json_parser_load_from_data(jp, swtpm_capabilities_json, -1, &error)) { + logerr(gl_LOGFILE, "Could not parse capabilities JSON '%s': %s\n", + swtpm_capabilities_json, error->message); + goto error_unref_jp; + } + + root = json_parser_get_root(jp); + jr = json_reader_new(root); + + if (!json_reader_read_member(jr, "profiles")) { + logerr(gl_LOGFILE, "Missing 'profiles' field: %s\n", + swtpm_capabilities_json); + goto error_unref_jr; + } + + num = json_reader_count_elements(jr); + *profile_names = g_malloc0((num + 1) * sizeof(char *)); + for (i = 0; i < num; i++) { + if (!json_reader_read_element(jr, i)) { + logerr(gl_LOGFILE, "Could not parse JSON list: %s\n", error->message); + goto error_str_array_free; + } + (*profile_names)[i] = g_strdup(json_reader_get_string_value(jr)); + json_reader_end_element(jr); + } + ret = 0; + + +error_unref_jr: + g_object_unref(jr); + +error_unref_jp: + g_object_unref(jp); + + return ret; + +error_str_array_free: + g_strfreev(*profile_names); + + goto error_unref_jr; +} + +#else + +int get_profile_names(const gchar *swtpm_capabilities_json, gchar ***profile_names) +{ + *profile_names = g_malloc0(1 * sizeof(char *)); + + return 0; +} + +#endif + +int check_json_profile(const gchar *swtpm_capabilities_json, const char *json_profile) +{ + gchar **profile_names = NULL; + g_autofree gchar *name = NULL; + int idx; + int ret; + + ret = json_get_map_value(json_profile, "name", &name); + if (ret) + return ret; + + ret = get_profile_names(swtpm_capabilities_json, &profile_names); + if (ret) + goto error; + + idx = strv_strcmp(profile_names, name); + if (idx < 0) { + logerr(gl_LOGFILE, "swtpm does not support a profile with name '%s'\n", name); + ret = 1; + } + +error: + g_strfreev(profile_names); + + return ret; +} diff --git a/src/swtpm_setup/profile.h b/src/swtpm_setup/profile.h new file mode 100644 index 000000000..55bbe0da7 --- /dev/null +++ b/src/swtpm_setup/profile.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * profile.h: TPM 2 profile handling + * + * Author: Stefan Berger, stefanb@linux.ibm.com + * + * Copyright (c) IBM Corporation, 2022 + */ + +#ifndef SWTPM_SETUP_PROFILE_H +#define SWTPM_SETUP_PROFILE_H + +#include + +int get_profile_names(const gchar *swtpm_capabilities_json, gchar ***profile_names); +int check_json_profile(const gchar *swtpm_capabilities_json, const char *json_profile); + +#endif /* SWTPM_SETUP_PROFILE_H */ diff --git a/src/swtpm_setup/swtpm.c b/src/swtpm_setup/swtpm.c index 2643ee2d5..341d08310 100644 --- a/src/swtpm_setup/swtpm.c +++ b/src/swtpm_setup/swtpm.c @@ -57,6 +57,7 @@ struct tpm_resp_header { static int swtpm_start(struct swtpm *self) { g_autofree gchar *tpmstate = g_strdup_printf("backend-uri=%s", self->state_path); + g_autofree gchar *json_profile = NULL; g_autofree gchar *pidfile_arg = NULL; g_autofree gchar *server_fd = NULL; g_autofree gchar *ctrl_fd = NULL; @@ -99,6 +100,12 @@ static int swtpm_start(struct swtpm *self) argv = concat_arrays(argv, (gchar*[]){"--key", keyopts, NULL}, TRUE); } + if (self->json_profile != NULL) { + json_profile = g_strdup_printf("profile=%s", self->json_profile); + argv = concat_arrays(argv, (gchar*[]){"--profile", json_profile, NULL}, TRUE); + logit(self->logfile, "Apply profile: %s\n", self->json_profile); + } + if (gl_LOGFILE != NULL) { logop = g_strdup_printf("file=%s", gl_LOGFILE); argv = concat_arrays(argv, (gchar*[]){"--log", logop, NULL}, TRUE); @@ -1929,7 +1936,7 @@ static void swtpm_init(struct swtpm *swtpm, gchar **swtpm_exec_l, const gchar *state_path, const gchar *keyopts, const gchar *logfile, int *fds_to_pass, size_t n_fds_to_pass, - gboolean is_tpm2) + gboolean is_tpm2, const gchar *json_profile) { swtpm->cops = &swtpm_cops; swtpm->swtpm_exec_l = swtpm_exec_l; @@ -1939,6 +1946,7 @@ static void swtpm_init(struct swtpm *swtpm, swtpm->fds_to_pass = fds_to_pass; swtpm->n_fds_to_pass = n_fds_to_pass; swtpm->is_tpm2 = is_tpm2; + swtpm->json_profile = json_profile; swtpm->pid = -1; swtpm->ctrl_fds[0] = swtpm->ctrl_fds[1] = -1; @@ -1952,7 +1960,7 @@ struct swtpm12 *swtpm12_new(gchar **swtpm_exec_l, const gchar *state_path, struct swtpm12 *swtpm12 = g_malloc0(sizeof(struct swtpm12)); swtpm_init(&swtpm12->swtpm, swtpm_exec_l, state_path, keyopts, logfile, - fds_to_pass, n_fds_to_pass, FALSE); + fds_to_pass, n_fds_to_pass, FALSE, NULL); swtpm12->ops = &swtpm_tpm12_ops; return swtpm12; @@ -1960,12 +1968,13 @@ struct swtpm12 *swtpm12_new(gchar **swtpm_exec_l, const gchar *state_path, struct swtpm2 *swtpm2_new(gchar **swtpm_exec_l, const gchar *state_path, const gchar *keyopts, const gchar *logfile, - int *fds_to_pass, size_t n_fds_to_pass) + int *fds_to_pass, size_t n_fds_to_pass, + const gchar *json_profile) { struct swtpm2 *swtpm2 = g_malloc0(sizeof(struct swtpm2)); swtpm_init(&swtpm2->swtpm, swtpm_exec_l, state_path, keyopts, logfile, - fds_to_pass, n_fds_to_pass, TRUE); + fds_to_pass, n_fds_to_pass, TRUE, json_profile); swtpm2->ops = &swtpm_tpm2_ops; return swtpm2; diff --git a/src/swtpm_setup/swtpm.h b/src/swtpm_setup/swtpm.h index 2f81dce47..76c42fdc3 100644 --- a/src/swtpm_setup/swtpm.h +++ b/src/swtpm_setup/swtpm.h @@ -68,6 +68,7 @@ struct swtpm { const int *fds_to_pass; size_t n_fds_to_pass; gboolean is_tpm2; + const char *json_profile; GPid pid; int ctrl_fds[2]; @@ -90,7 +91,8 @@ struct swtpm12 *swtpm12_new(gchar **swtpm_prg_l, const gchar *tpm_state_path, struct swtpm2 *swtpm2_new(gchar **swtpm_prg_l, const gchar *tpm_state_path, const gchar *swtpm_keyopts, const gchar *logfile, - int *fds_to_pass, size_t n_fds_to_pass); + int *fds_to_pass, size_t n_fds_to_pass, + const gchar *profile_rules); void swtpm_free(struct swtpm *); diff --git a/src/swtpm_setup/swtpm_setup.c b/src/swtpm_setup/swtpm_setup.c index 3570235dd..89cce5c02 100644 --- a/src/swtpm_setup/swtpm_setup.c +++ b/src/swtpm_setup/swtpm_setup.c @@ -32,6 +32,7 @@ #include +#include "profile.h" #include "swtpm.h" #include "swtpm_setup_conf.h" #include "swtpm_setup_utils.h" @@ -495,14 +496,14 @@ static int init_tpm2(unsigned long flags, gchar **swtpm_prg_l, const gchar *conf const gchar *tpm2_state_path, const gchar *vmid, const gchar *pcr_banks, const gchar *swtpm_keyopt, int *fds_to_pass, size_t n_fds_to_pass, unsigned int rsa_keysize, const gchar *certsdir, - const gchar *user_certsdir) + const gchar *user_certsdir, const gchar *json_profile) { struct swtpm2 *swtpm2; struct swtpm *swtpm; int ret; swtpm2 = swtpm2_new(swtpm_prg_l, tpm2_state_path, swtpm_keyopt, gl_LOGFILE, - fds_to_pass, n_fds_to_pass); + fds_to_pass, n_fds_to_pass, json_profile); if (swtpm2 == NULL) return 1; swtpm = &swtpm2->swtpm; @@ -919,6 +920,11 @@ static void usage(const char *prgname, const char *default_config_file) " The active PCR banks can be changed but no new keys will\n" " be created.\n" "\n" +#ifdef HAVE_LIBTPMS_SETPROFILE_API + "--profile \n" + " : Configure swtpm with the given profile.\n" +#endif + "\n" "--version : Display version and exit\n" "\n" "--help,-h : Display this help screen\n\n", @@ -1039,11 +1045,26 @@ static int get_rsa_keysize_caps(unsigned long flags, gchar **swtpm_prg_l, return 0; } +static int validate_json_profile(gchar **swtpm_prg_l, const char *json_profile) +{ + g_autofree gchar *standard_output = NULL; + int ret; + + ret = get_swtpm_capabilities(swtpm_prg_l, TRUE, &standard_output); + if (ret) + return ret; + + return check_json_profile(standard_output, json_profile); +} + /* Print teh JSON object of swtpm_setup's capabilities */ static int print_capabilities(char **swtpm_prg_l, gboolean swtpm_has_tpm12, gboolean swtpm_has_tpm2) { + g_autofree gchar *standard_output = NULL; g_autofree gchar *param = g_strdup(""); + g_autofree gchar *profile_list = NULL; + gchar **profile_names = NULL; gchar **keysize_strs = NULL; gchar *tmp; size_t i; @@ -1059,20 +1080,49 @@ static int print_capabilities(char **swtpm_prg_l, gboolean swtpm_has_tpm12, param = tmp; } + if (swtpm_has_tpm2) { + ret = get_swtpm_capabilities(swtpm_prg_l, TRUE, &standard_output); + if (ret) + goto error; + ret = get_profile_names(standard_output, &profile_names); + if (ret) + goto error; + + if (g_strv_length(profile_names) > 0) { + tmp = g_strjoinv("\", \"", profile_names); + profile_list = g_strdup_printf(" \"%s\" ", tmp); + g_free(tmp); + } + } + printf("{ \"type\": \"swtpm_setup\", " "\"features\": [ %s%s\"cmdarg-keyfile-fd\", \"cmdarg-pwdfile-fd\", \"tpm12-not-need-root\"" ", \"cmdarg-write-ek-cert-files\", \"cmdarg-create-config-files\"" ", \"cmdarg-reconfigure-pcr-banks\"" - "%s ], " + "%s" +#ifdef HAVE_LIBTPMS_SETPROFILE_API + ", \"cmdarg-profile\"" +#endif + "" + " ], " +#ifdef HAVE_LIBTPMS_SETPROFILE_API + "\"profiles\": [%s], " +#endif "\"version\": \"" VERSION "\" " "}\n", swtpm_has_tpm12 ? "\"tpm-1.2\", " : "", swtpm_has_tpm2 ? "\"tpm-2.0\", " : "", - param); + param +#ifdef HAVE_LIBTPMS_SETPROFILE_API + , profile_list ? profile_list : "" +#endif + ); +error: g_strfreev(keysize_strs); + g_strfreev(profile_names); - return 0; + return ret; } static int change_process_owner(const char *user) @@ -1185,6 +1235,9 @@ int main(int argc, char *argv[]) {"version", no_argument, NULL, '1'}, {"print-capabilities", no_argument, NULL, 'y'}, {"reconfigure", no_argument, NULL, 'R'}, +#ifdef HAVE_LIBTPMS_SETPROFILE_API + {"profile", required_argument, NULL, 'I'}, +#endif {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; @@ -1212,6 +1265,7 @@ int main(int argc, char *argv[]) g_autofree gchar *runas = NULL; g_autofree gchar *certsdir = NULL; g_autofree gchar *user_certsdir = NULL; + g_autofree gchar *json_profile = NULL; gchar *tmp; gchar **swtpm_prg_l = NULL; gchar **tmp_l = NULL; @@ -1394,6 +1448,10 @@ int main(int argc, char *argv[]) case 'R': /* --reconfigure */ flags |= SETUP_RECONFIGURE_F; break; + case 'I': /* --profile */ + g_free(json_profile); + json_profile = g_strdup(optarg); + break; case '?': case 'h': /* --help */ usage(argv[0], config_file); @@ -1421,7 +1479,6 @@ int main(int argc, char *argv[]) } g_free(tmp); - ret = get_supported_tpm_versions(swtpm_prg_l, &swtpm_has_tpm12, &swtpm_has_tpm2); if (ret != 0) goto error; @@ -1439,6 +1496,14 @@ int main(int argc, char *argv[]) goto error; } + if ((flags & SETUP_TPM2_F) != 0 && json_profile) { + if (validate_json_profile(swtpm_prg_l, json_profile) != 0) + goto error; + } else if (json_profile) { + logerr(gl_LOGFILE, "There's no --profile support for TPM 1.2\n"); + goto error; + } + if (runas) { ret = change_process_owner(runas); if (ret != 0) @@ -1648,7 +1713,7 @@ int main(int argc, char *argv[]) } else { ret = init_tpm2(flags, swtpm_prg_l, config_file, tpm_state_path, vmid, pcr_banks, swtpm_keyopt, fds_to_pass, n_fds_to_pass, rsa_keysize, certsdir, - user_certsdir); + user_certsdir, json_profile); } if (ret == 0) { diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 2e8b14a99..c3bf4b89a 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -17,11 +17,13 @@ libswtpm_utils_la_CFLAGS = \ $(MY_CFLAGS) \ $(CFLAGS) \ $(HARDENING_CFLAGS) \ - $(GLIB_CFLAGS) + $(GLIB_CFLAGS) \ + $(JSON_GLIB_CFLAGS) libswtpm_utils_la_LDFLAGS = \ $(MY_LDFLAGS) \ - $(HARDENING_LDFLAGS) + $(HARDENING_LDFLAGS) \ + $(JSON_GLIB_LIBS) libswtpm_utils_la_SOURCES = \ swtpm_utils.c diff --git a/src/utils/swtpm_utils.c b/src/utils/swtpm_utils.c index 0a28c6201..312a5be27 100644 --- a/src/utils/swtpm_utils.c +++ b/src/utils/swtpm_utils.c @@ -21,6 +21,7 @@ #include #include +#include #include "swtpm_utils.h" @@ -440,3 +441,51 @@ int check_directory_access(const gchar *directory, int mode, const struct passwd } return 0; } + +int json_get_map_value(const char *json_input, const char *field_name, + gchar **value) +{ + g_autoptr(GError) error = NULL; + JsonParser *jp = NULL; + JsonReader *jr = NULL; + JsonNode *root; + int ret = -1; + + jp = json_parser_new(); + if (!json_parser_load_from_data(jp, json_input, -1, &error)) { + logerr(gl_LOGFILE, + "Could not parse JSON '%s': %s\n", json_input, error->message); + goto error_unref_jp; + } + + root = json_parser_get_root(jp); + jr = json_reader_new(root); + + if (!json_reader_read_member(jr, field_name)) { + logerr(gl_LOGFILE, "Missing '%s' field in '%s'\n", + field_name, json_input); + goto error_unref_jr; + } + *value = g_strdup(json_reader_get_string_value(jr)); + + ret = 0; + +error_unref_jr: + g_object_unref(jr); + +error_unref_jp: + g_object_unref(jp); + + return ret; +} + +int strv_strcmp(gchar *const*str_array, const gchar *s) +{ + size_t i; + + for (i = 0; str_array[i]; i++) { + if (strcmp(str_array[i], s) == 0) + return (int)i; + } + return -1; +} diff --git a/src/utils/swtpm_utils.h b/src/utils/swtpm_utils.h index 70a0e4e97..fb10fa556 100644 --- a/src/utils/swtpm_utils.h +++ b/src/utils/swtpm_utils.h @@ -43,4 +43,9 @@ gchar *str_replace(const char *in, const char *torep, const char *rep); int check_directory_access(const gchar *directory, int mode, const struct passwd *curr_user); +int json_get_map_value(const char *json_input, const char *field_name, + gchar **value); + +int strv_strcmp(gchar *const*str_array, const gchar *s); + #endif /* SWTPM_UTILS_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 4c601b848..fd9f877e5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -83,7 +83,9 @@ TESTS += \ test_tpm2_swtpm_bios \ \ test_tpm2_ibmtss2 \ - test_tpm2_swtpm_setup_overwrite + test_tpm2_swtpm_setup_overwrite \ + test_tpm2_swtpm_setup_profile \ + test_tpm2_libtpms_versions_profiles if WITH_GNUTLS TESTS += \ @@ -164,6 +166,7 @@ EXTRA_DIST = \ data/tpm2state4/tpm2-00.permall \ data/tpm2state5/signature.bin \ data/tpm2state5/tpm2-00.permall \ + data/tpm2state6/tpm2-00.permall \ patches/0001-Deactivate-test-cases-accessing-rootcerts.txt.patch \ patches/0002-Implement-powerup-for-swtpm.patch \ patches/0003-Set-CRYPTOLIBRARY-to-openssl.patch \ diff --git a/tests/_test_print_capabilities b/tests/_test_print_capabilities index 4894baaea..a5ddf90bd 100755 --- a/tests/_test_print_capabilities +++ b/tests/_test_print_capabilities @@ -24,7 +24,7 @@ if [ "${SWTPM_IFACE}" != "cuse" ]; then noncuse='"tpm-send-command-header", ' fi -exp='\{ "type": "swtpm", "features": \[ "tpm-1.2",( "tpm-2.0",)? '${noncuse}'"flags-opt-startup", "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" \], "version": "[^"]*" \}' +exp='\{ "type": "swtpm", "features": \[ "tpm-1.2",( "tpm-2.0",)? '${noncuse}'"flags-opt-startup", "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")? \],( "profiles": \[ \],)? "version": "[^"]*" \}' if ! [[ ${msg} =~ ${exp} ]]; then echo "Unexpected response from ${SWTPM_IFACE} TPM to --print-capabilities:" echo "Actual : ${msg}" @@ -41,7 +41,7 @@ if ! msg="$(${SWTPM_SETUP} --print-capabilities 2>&1)"; then fi # The are some variable parameters at the end, use regex -exp='\{ "type": "swtpm_setup", "features": \[ "tpm-1.2",( "tpm-2.0",)? "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd", "tpm12-not-need-root", "cmdarg-write-ek-cert-files", "cmdarg-create-config-files", "cmdarg-reconfigure-pcr-banks"(, "tpm2-rsa-keysize-2048")?(, "tpm2-rsa-keysize-3072")? \], "version": "[^"]*" \}' +exp='\{ "type": "swtpm_setup", "features": \[ "tpm-1.2",( "tpm-2.0",)? "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd", "tpm12-not-need-root", "cmdarg-write-ek-cert-files", "cmdarg-create-config-files", "cmdarg-reconfigure-pcr-banks"(, "tpm2-rsa-keysize-2048")?(, "tpm2-rsa-keysize-3072")?(, "cmdarg-profile")? \],( "profiles": \[ [^]]*\],)? "version": "[^"]*" \}' if ! [[ ${msg} =~ ${exp} ]]; then echo "Unexpected response from ${SWTPM_SETUP} to --print-capabilities:" echo "Actual : ${msg}" diff --git a/tests/_test_tpm2_print_capabilities b/tests/_test_tpm2_print_capabilities index 1de955c86..c50dda899 100755 --- a/tests/_test_tpm2_print_capabilities +++ b/tests/_test_tpm2_print_capabilities @@ -25,7 +25,7 @@ if [ "${SWTPM_IFACE}" != "cuse" ]; then fi # The rsa key size reporting is variable, so use a regex -exp='\{ "type": "swtpm", "features": \[( "tpm-1.2",)? "tpm-2.0", '${noncuse}'"flags-opt-startup", "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"(, "rsa-keysize-1024")?(, "rsa-keysize-2048")?(, "rsa-keysize-3072")? \], "version": "[^"]*" \}' +exp='\{ "type": "swtpm", "features": \[( "tpm-1.2",)? "tpm-2.0", '${noncuse}'"flags-opt-startup", "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"(, "rsa-keysize-1024")?(, "rsa-keysize-2048")?(, "rsa-keysize-3072")?(, "cmdarg-profile")? \],( "profiles": \[ [^]]*\],)? "version": "[^"]*" \}' if ! [[ ${msg} =~ ${exp} ]]; then echo "Unexpected response from ${SWTPM_IFACE} TPM to --print-capabilities:" echo "Actual : ${msg}" @@ -42,7 +42,7 @@ if ! msg="$(${SWTPM_SETUP} --tpm2 --print-capabilities 2>&1)"; then fi # The are some variable parameters at the end, use regex -exp='\{ "type": "swtpm_setup", "features": \[( "tpm-1.2",)? "tpm-2.0", "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd", "tpm12-not-need-root", "cmdarg-write-ek-cert-files", "cmdarg-create-config-files", "cmdarg-reconfigure-pcr-banks"(, "tpm2-rsa-keysize-2048")?(, "tpm2-rsa-keysize-3072")? \], "version": "[^"]*" \}' +exp='\{ "type": "swtpm_setup", "features": \[( "tpm-1.2",)? "tpm-2.0", "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd", "tpm12-not-need-root", "cmdarg-write-ek-cert-files", "cmdarg-create-config-files", "cmdarg-reconfigure-pcr-banks"(, "tpm2-rsa-keysize-2048")?(, "tpm2-rsa-keysize-3072")?(, "cmdarg-profile")? \],( "profiles": \[ [^]]*\],)? "version": "[^"]*" \}' if ! [[ ${msg} =~ ${exp} ]]; then echo "Unexpected response from ${SWTPM_SETUP} to --print-capabilities:" echo "Actual : ${msg}" diff --git a/tests/common b/tests/common index 8bb62ffd9..b2ff3d8a9 100644 --- a/tests/common +++ b/tests/common @@ -890,6 +890,20 @@ function skip_test_no_tpm20() fi } +# Check whether swtpm has support for the --profile option +function skip_test_no_cmdarg_profile() +{ + local swtpm_exe="$1" + + local res + + res=$(${swtpm_exe} socket --print-capabilities | grep '"cmdarg-profile"') + if [ -z "${res}" ]; then + echo "${swtpm_exe} does not support the --profile option" + exit 77 + fi +} + # Test whether swtpm has a chardev interface; Returns 0 if true, 1 otherwise function test_swtpm_has_chardev() { diff --git a/tests/data/tpm2state6/tpm2-00.permall b/tests/data/tpm2state6/tpm2-00.permall new file mode 100644 index 000000000..bc8dc0d72 Binary files /dev/null and b/tests/data/tpm2state6/tpm2-00.permall differ diff --git a/tests/test_tpm2_libtpms_versions_profiles b/tests/test_tpm2_libtpms_versions_profiles new file mode 100755 index 000000000..4fc062fbd --- /dev/null +++ b/tests/test_tpm2_libtpms_versions_profiles @@ -0,0 +1,290 @@ +#!/usr/bin/env bash + +# For the license, see the LICENSE file in the root directory. + +# FIXME +#if [ ${SWTPM_TEST_EXPENSIVE:-0} -eq 0 ]; then +# exit 77 +#fi + +ROOT=${abs_top_builddir:-$(dirname "$0")/..} +TESTDIR=${abs_top_testdir:-$(dirname "$0")} +SRCDIR=${abs_top_srcdir:-$(dirname "$0")/..} + +source "${TESTDIR}/common" + +workdir="$(mktemp -d)" || exit 1 +export TPM_PATH=${workdir} +SWTPM_SERVER_PORT=65476 +SWTPM_CTRL_PORT=65477 +SWTPM_SERVER_NAME=127.0.0.1 +SWTPM_INTERFACE="socket+socket" + +LIBTPMS_URL=https://github.com/stefanberger/libtpms +LIBTPMS_INITIAL_BRANCH=stefanberger/runtime_disable_algorithms.v3 # FIXME: should be master + + +cat <<_EOF_ > "${workdir}/swtpm-localca.options" +--tpm-manufacturer IBM +--tpm-model swtpm-libtpms +--tpm-version 2 +--platform-manufacturer "Fedora XYZ" +--platform-version 2.1 +--platform-model "QEMU A.B" +_EOF_ + +export MY_SWTPM_LOCALCA="${SWTPM_LOCALCA}" + +cat <<_EOF_ > "${workdir}/swtpm_setup.conf" +create_certs_tool=\${MY_SWTPM_LOCALCA} +create_certs_tool_config=${workdir}/swtpm-localca.conf +create_certs_tool_options=${workdir}/swtpm-localca.options +_EOF_ + +# Copy swtpm source tree to workdir +pushd "${SRCDIR}" &>/dev/null || exit 1 +mkdir -p "${workdir}/swtpm" +cp -rp . "${workdir}/swtpm" +cd "${workdir}/swtpm" || exit 1 +chmod -R 0755 . # when using 'distcheck' +git clean -xdf &>/dev/null +popd &>/dev/null || exit 1 + +function cleanup() +{ + rm -rf "${workdir}" + + if kill_quiet -0 "${SWTPM_PID}"; then + kill_quiet -9 "${SWTPM_PID}" + fi +} + +function build_swtpm() +{ + local srcdir="$1" + local cflags="$2" + local destdir="$3" + + echo -e "\nBuilding swtpm with CFLAGS=${cflags} ..." + + pushd "${srcdir}" &>/dev/null || exit 1 + + git clean -xdf &>/dev/null + CFLAGS="${cflags}" ./autogen.sh --without-cuse --without-selinux --without-seccomp + make -j "$(nproc)" || return 1 + + cp \ + src/swtpm/.libs/swtpm src/swtpm/.libs/libswtpm*.so* \ + src/swtpm_setup/swtpm_setup \ + src/swtpm_ioctl/swtpm_ioctl \ + "${destdir}" + + popd &>/dev/null || exit 1 +} + +# Copy a compiled libtpms version to its destination directory; the destination +# directory will be created +# @param1: Destination directory's parent +# @param2: The version of libtpms, e.g. '0.9' or 'master' +function copy_libtpms() +{ + local dest="$1" + local version="$2" + + local destdir="${dest}/libtpms-${version}" + + mkdir -p "${destdir}" + cp src/.libs/libtpms.so* "${destdir}" + + return 0 +} + +# Build various versions of libtpms starting with libtpms 0.9 up to 'master' +# Build swtpm for each one of these versions as well. +function build_libtpms_and_swtpm() +{ + local workdir="$1" + + local tmp major maj min + local libtpmsdir="${workdir}/libtpms" + + pushd "${workdir}" 2>&1 || return 1 + + git clone "${LIBTPMS_URL}" libtpms + + pushd libtpms 2>&1 || return 1 + + git checkout "${LIBTPMS_INITIAL_BRANCH}" + + echo -e "\nBuilding libtpms ${INITIAL_BRANCH} ..." + CFLAGS="-g -O2" ./autogen.sh --with-tpm2 --without-tpm1 + make -j "$(nproc)" || return 1 + + copy_libtpms "${workdir}" "master" + + # Determine major version of what master built + tmp=$(ls src/.libs/libtpms.so.*.*) + major=$(echo "${tmp}" | sed -n 's/.*.so\.\([^\.]\+\)\.\([^\.]\+\)\..*/\1/p') + + if ! build_swtpm \ + "${workdir}/swtpm" \ + "-I${libtpmsdir}/include -L${workdir}/libtpms-master" \ + "${workdir}/libtpms-master"; then + echo "Error: Could not build swtpm" + return 1 + fi + + for ((maj=0; maj<=major; maj++)) { + min=0 + [ "${maj}" = 0 ] && min=9 # start with v0.9 + + for ((;; min++)) { + git checkout "origin/stable-${maj}.${min}" &>/dev/null || break + + # clean directory containing libtpms.so.* + rm -rf src/.libs + echo -e "\nBuilding libtpms ${maj}.${min}..." + make -j "$(nproc)" || return 1 + + copy_libtpms "${workdir}" "${maj}.${min}" + + if ! build_swtpm \ + "${workdir}/swtpm" \ + "-I${libtpmsdir}/include -L${workdir}/libtpms-${maj}.${min}" \ + "${workdir}/libtpms-${maj}.${min}"; then + echo "Error: Could not build swtpm" + return 1 + fi + } + } + + popd &>/dev/null || return 1 + + #ls -l libtpms-* + #PATH="${workdir}/libtpms-master" LD_LIBRARY_PATH=${PATH} ./libtpms-master/swtpm_setup --help + #PATH="${workdir}/libtpms-0.9" LD_LIBRARY_PATH=${PATH} ./libtpms-0.9/swtpm_setup socket --help + + popd &>/dev/null || return 1 + + return 0 +} + +# Run swtpm_setup on all versions >= v0.10 of libtpms. The state is written +# into directories with the name patterns of state--${branch}--${profile}, +# which leads to names like state--0.10--null for null profile and libvtpm v0.10 +function swtpm_setup_create_profile_state() +{ + local workdir="$1" + + local branch profiles libtpmsdir statedir + + pushd "${workdir}" &>/dev/null || return 1 + + # Setup swtpm-0.10 and later with null profile and check that swtpm-0.9 works with it + for libtpmsdir in libtpms-master libtpms-*.*; do + branch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p') + [ "${branch}" = "0.9" ] && continue + + # get a space-separated list of profile names + profiles=$(PATH="${workdir}/${libtpmsdir}" \ + LD_LIBRARY_PATH="${workdir}/${libtpmsdir}" \ + swtpm socket --tpm2 --print-capabilities \ + | sed -n -e "s/.* \"profiles\": \[ \(.*\) \].*/\1/" -e 's/[ "]//g' -e 's/,/ /gp') + for profile in ${profiles}; do + statedir="state--${branch}--${profile}" + mkdir "${statedir}" + echo "$statedir,$libtpmsdir,$profile" + if ! PATH="${workdir}/${libtpmsdir}" \ + LD_LIBRARY_PATH="${workdir}/${libtpmsdir}" \ + "./${libtpmsdir}/swtpm_setup" \ + --tpm2 \ + --tpmstate "${statedir}" \ + --config "${workdir}/swtpm_setup.conf" \ + --log "${statedir}/swtpm_setup.log" \ + --tpm "${workdir}/${libtpmsdir}/swtpm socket" \ + --profile "{\"name\":\"${profile}\"}"; then + echo "Error: Could not run swtpm_setup with libtpms from branch ${branch} with profile ${profile}" + fi + #ls -l "${statedir}" + #cat "${statedir}/swtpm_setup.log" + done + done + + popd &>/dev/null || return 1 + + return 0 +} + +# This function tests that the NULL profile state, that was created by a certian +# version of libtpms, can be used by swtpm with libtpms v0.9 and others. +function swtpm_run_with_null_profile_state() +{ + local workdir="$1" + + local statedir branch libtpmsbranch resp exp fsize err=0 + + pushd "${workdir}" &>/dev/null || return 1 + + for statedir in state--*--null; do + branch=$(echo "${statedir}" | sed -n 's/.*--\(.*\)--.*/\1/p') + for libtpmsdir in libtpms-master libtpms-*.*; do + libtpmsbranch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p') + ls -l "${workdir}/${libtpmsdir}" + fsize=$(get_filesize "${workdir}/${statedir}/tpm2-00.permall") + if ! LD_LIBRARY_PATH="${workdir}/${libtpmsdir}" \ + SWTPM_EXE=./${libtpmsdir}/swtpm \ + run_swtpm "${SWTPM_INTERFACE}" \ + --tpm2 \ + --tpmstate "dir=${workdir}/${statedir}" \ + --flags not-need-init,startup-clear; then + echo "Error: Could not start swtpm with null profile state and libtpms version '${libtpmsbranch}'." + err=1 + break + fi + # Read PCR 10 + resp=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b\x03\x00\x04\x00') + exp=' 80 01 00 00 00 3e 00 00 00 00 00 00 00 14 00 00 00 01 00 0b 03 00 04 00 00 00 00 01 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' + if [ "${resp}" != "${exp}" ]; then + echo "Error: Did not get expected result from TPM2_PCRRead(10)" + echo "expected: ${exp}" + echo "received: ${resp}" + err=1 + break + fi + echo " PCR 10 is good: profile: null libtpms: ${libtpmsbranch} state written by: ${branch}" + + if [ "$(get_filesize "${workdir}/${statedir}/tpm2-00.permall")" != "${fsize}" ]; then + echo "Error: Size of tpm2 state file has changed" + echo "expected: ${fsize}" + echo "actual : $(get_filesize "${workdir}/${statedir}/tpm2-00.permall")" + err=1 + break + fi + + kill_quiet -9 "${SWTPM_PID}" + done + if [ ${err} -ne 0 ]; then + kill_quiet -9 "${SWTPM_PID}" + break + fi + done + + popd &>/dev/null || return 1 + + return "${err}" +} + +if ! build_libtpms_and_swtpm "${workdir}"; then + echo "Error: Building libtpms and/or swtpm failed" + exit 1 +fi + +if ! swtpm_setup_create_profile_state "${workdir}"; then + exit 1 +fi + +if ! swtpm_run_with_null_profile_state "${workdir}"; then + exit 1 +fi + +exit 0 diff --git a/tests/test_tpm2_swtpm_setup_profile b/tests/test_tpm2_swtpm_setup_profile new file mode 100755 index 000000000..bd0cf59b7 --- /dev/null +++ b/tests/test_tpm2_swtpm_setup_profile @@ -0,0 +1,302 @@ +#!/usr/bin/env bash + +# For the license, see the LICENSE file in the root directory. + +ROOT=${abs_top_builddir:-$(dirname "$0")/..} +TESTDIR=${abs_top_testdir:-$(dirname "$0")} + +source "${TESTDIR}/common" +skip_test_no_tpm20 "${SWTPM_EXE}" +skip_test_no_cmdarg_profile "${SWTPM_EXE}" + +workdir="$(mktemp -d)" || exit 1 +export TPM_PATH=${workdir} +SWTPM_SERVER_PORT=65470 +SWTPM_CTRL_PORT=65471 +SWTPM_SERVER_NAME=127.0.0.1 +SWTPM_INTERFACE="socket+socket" + +trap "cleanup" SIGTERM EXIT + + +cat <<_EOF_ > "${workdir}/swtpm-localca.options" +--tpm-manufacturer IBM +--tpm-model swtpm-libtpms +--tpm-version 2 +--platform-manufacturer "Fedora XYZ" +--platform-version 2.1 +--platform-model "QEMU A.B" +_EOF_ + +export MY_SWTPM_LOCALCA="${SWTPM_LOCALCA}" + +cat <<_EOF_ > "${workdir}/swtpm_setup.conf" +create_certs_tool=\${MY_SWTPM_LOCALCA} +create_certs_tool_config=${workdir}/swtpm-localca.conf +create_certs_tool_options=${workdir}/swtpm-localca.options +_EOF_ + +function cleanup() +{ + rm -rf "${workdir}" + + if kill_quiet -0 "${SWTPM_PID}"; then + kill_quiet -9 "${SWTPM_PID}" + fi +} + +test_swtpm_setup_profile() +{ + local workdir="${1}" + local profile="${2}" + local exp_response="${3}" # expected 'ActiveProfile' response; optional + local disabled="${4}" # disabled algorithms; optional + local tpm2request="${5}" # TPM 2 request to send; optional + local exp_tpm2response="${6}" # expected respone; optional + + local response + + if ! $SWTPM_SETUP \ + --tpm2 \ + --tpmstate "${workdir}" \ + --config "${workdir}/swtpm_setup.conf" \ + --log "${workdir}/logfile" \ + --tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \ + --overwrite \ + ${profile:+--profile ${profile}} + then + echo "Test 1 failed: Error: Could not run $SWTPM_SETUP." + echo "Setup Logfile:" + cat "${workdir}/logfile" + exit 1 + fi + + run_swtpm "${SWTPM_INTERFACE}" \ + --tpm2 \ + --flags not-need-init,startup-clear + + if ! kill_quiet -0 "${SWTPM_PID}"; then + echo "Error: ${SWTPM_INTERFACE} TPM did not start." + exit 1 + fi + + response=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x10) + if ! [[ "${response}" =~ ${exp_response} ]]; then + echo "Error: Response does not match expected response regular expression" + echo "Actual : ${response}" + echo "Expected : ${exp_response}" + exit 1 + fi + + if [ -n "${disabled}" ]; then + response=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 | + sed -n 's/.*"Disabled":"\([^"]\+\).*/\1/p') + if [ "${response}" != "${disabled}" ]; then + echo "Error: Response for disabled algorithms does not match expected response" + echo "Actual : ${response}" + echo "Expected : ${disabled}" + exit 1 + fi + fi + + if [ -n "${tpm2request}" ]; then + response=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" "${tpm2request}") + if [ "${response}" != "${exp_tpm2response}" ]; then + echo "Error: TPM 2 response does not match expected response" + echo "Actual : ${response}" + echo "Expected : ${exp_tpm2response}" + exit 1 + fi + fi + + if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then + echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM." + exit 1 + fi + + if wait_process_gone "${SWTPM_PID}" 4; then + echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore." + exit 1 + fi +} + +# Get available profiles and algorithms that are implemented and those that can be disabled +run_swtpm "${SWTPM_INTERFACE}" \ + --tpm2 \ + --flags not-need-init + +if ! kill_quiet -0 "${SWTPM_PID}"; then + echo "Error: ${SWTPM_INTERFACE} TPM did not start." + exit 1 +fi + +profiles=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x20 | + sed 's/{"name/\n{"name/g' | + sed -n 's/^{"name":"\([^"]\+\)".*/\1/p') + +canbedisabled_str=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 | + sed -n 's/.*"CanBeDisabled":"\([^"]\+\).*/\1/p') + +implemented_str=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 | + sed -n 's/.*"Implemented":"\([^"]\+\).*/\1/p') + +if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then + echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM." + exit 1 +fi + +if wait_process_gone "${SWTPM_PID}" 4; then + echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore." + exit 1 +fi + +# Test with no profile +exp_response=$(echo "^\{\"ActiveProfile\":\{" \ + "\"name\":\"default\"," \ + "\"stateFormatLevel\":[0-9]+,"\ + "\"commands\":\"[,x[:xdigit:]-]+\""\ + "(,\"algorithms\":\"[,=[:alnum:]-]+\")?"\ + "\}\}\$"| tr -d " ") +test_swtpm_setup_profile \ + "${workdir}" "" "${exp_response}" \ + "" "" "" + +echo "Test without profile passed" + +# Test-setup each profile that is implemented +for profile in ${profiles}; do + exp_response=$(echo "^\{\"ActiveProfile\":\{" \ + "\"name\":\"${profile}\"," \ + "\"stateFormatLevel\":[0-9]+,"\ + "\"commands\":\"[,x[:xdigit:]-]+\""\ + "(,\"algorithms\":\"[,=[:alnum:]-]+\")?"\ + "\}\}\$"| tr -d " ") + test_swtpm_setup_profile \ + "${workdir}" "{\"name\":\"${profile}\"}" "${exp_response}" "" "" "" + + echo "Test with profile '${profile}' passed" +done + +# Setup the default profile with an increasing set of disabled algorithms +# that swtpm_ioctl has to show then. +IFS="," read -r -a canbedisabled <<< "${canbedisabled_str}" +IFS="," read -r -a implemented <<< "${implemented_str}" + +algos="${implemented[*]}" +disabled=() +for todisable in "${canbedisabled[@]}"; do + # Remove algorithm ${todisable} from list of implemented algorithms + algos=$(echo "${algos}" | sed "s/${todisable}//" | tr -s ' ') + disabled+=("${todisable}") + + # Format profile + a=$(echo "${algos}" | sed -e 's/ /,/g' -e 's/,$//') + profile="{\"name\":\"default\",\"algorithms\":\"${a}\"}" + + d=${disabled[*]} + d=${d// /,} + + test_swtpm_setup_profile \ + "${workdir}" "${profile}" "" "${d}" "" "" + echo "Test with default profile and disabled algorithms '${d}' passed" +done + +# Setup the TPM 2 with default profile and sha1 removed +# Check that the GetCapability command returns the proper set of PCR banks + +algos="${implemented[*]}" +algos=$(echo "${algos}" | sed "s/sha1//" | tr -s ' ') +# Format profile +a=$(echo "${algos}" | sed -e 's/ /,/g' -e 's/,$//') +profile="{\"name\":\"default\",\"algorithms\":\"${a}\"}" + +# Check PCR bank sha1 is not available: tssgetcapability -cap 5 +test_swtpm_setup_profile \ + "${workdir}" \ + "${profile}" \ + "" \ + "" \ + "\x80\x01\x00\x00\x00\x16\x00\x00\x01\x7a\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x40" \ + " 80 01 00 00 00 25 00 00 00 00 00 00 00 00 05 00 00 00 03 00 0b 03 ff ff ff 00 0c 03 00 00 00 00 0d 03 00 00 00" + +echo "Test with default profile and 'sha1' disabled algorithms passed" + + +# Copy TPM 2 state written by libtpms v0.9 and have the TPM read and re-write the state. +# The size of the state file must not change since we don't want the profile to be +# written into it. + +cp "${TESTDIR}/data/tpm2state6/tpm2-00.permall" "${workdir}" +before=$(get_filesize "${workdir}/tpm2-00.permall") + +# Avoid swptm sending TPM2_Shutdown(SU_STATE) and adding savestate to the state file +run_swtpm "${SWTPM_INTERFACE}" \ + --tpm2 \ + --flags not-need-init,startup-clear,disable-auto-shutdown + +if ! kill_quiet -0 "${SWTPM_PID}"; then + echo "Error: ${SWTPM_INTERFACE} TPM did not start." + exit 1 +fi + +# Run ChangeEPS so the state is re-written: tsschangeeps +cmd='\x80\x02\x00\x00\x00\x1b\x00\x00\x01\x24\x40\x00\x00\x0c\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00' +RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" "${cmd}") +exp=' 80 02 00 00 00 13 00 00 00 00 00 00 00 00 00 00 01 00 00' +if [ "$RES" != "$exp" ]; then + echo "Error: Did not get expected result from TPM2_ChangeEPS" + echo "expected: $exp" + echo "received: $RES" + exit 1 +fi + +if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then + echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM." + exit 1 +fi + +if wait_process_gone "${SWTPM_PID}" 4; then + echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore." + exit 1 +fi + +after=$(get_filesize "${workdir}/tpm2-00.permall") +if [ "${after}" -ne "${before}" ]; then + echo "Error: The size of the TPM 2 state file has changed" + echo "Actual : ${after}" + echo "Expected : ${before}" + exit 1 +fi + +echo "Test with state written by libtpms v0.9 passed" + +# If the user passes the null profile in then libtpms has to write state +# at the level of libtpms v0.9 and the size of the state file has to be +# the same as the one created with libtpms v0.9 +rm -f "${workdir}"/tpm2-00.* + +if ! $SWTPM_SETUP \ + --tpm2 \ + --tpmstate "${workdir}" \ + --config "${workdir}/swtpm_setup.conf" \ + --log "${workdir}/logfile" \ + --tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \ + --profile '{"name":"null"}' +then + echo "Test failed: Error: Could not run $SWTPM_SETUP and set null-profile." + echo "Setup Logfile:" + cat "${workdir}/logfile" + exit 1 +fi + +after=$(get_filesize "${workdir}/tpm2-00.permall") +if [ "${after}" -ne "${before}" ]; then + echo "Error: The size of the TPM 2 state file is different than if created by libtpms v0.9" + echo "Actual : ${after}" + echo "Expected : ${before}" + exit 1 +fi + +echo "Test of writing state at the level of libtpms v0.9 passed" + +exit 0