swtpm_setup: Add support for --profile parameter

Add support for the --profile parameter that allows a user to select
a profile for the TPM 2 instance. The profile parameter must be a
string-formatted JSON map describing the profile to use.

Resolves: https://github.com/stefanberger/libtpms/issues/284
Resolves: https://github.com/stefanberger/swtpm/issues/710
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
This commit is contained in:
Stefan Berger 2022-06-15 13:42:15 -04:00 committed by Stefan Berger
parent 3a49ce1302
commit df11aeb6b9
11 changed files with 312 additions and 16 deletions

View File

@ -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 <json-profile>>
Configure a TPM 2 with the given profile. Example profiles look
like this:
{"Name": "null"}
{"Name": "default-v1"}
{
"Name": "custom",
"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,ecc-nist,ecc-bn,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.

View File

@ -117,16 +117,54 @@ oom:
goto cleanup;
}
static int get_profile_data(const char *info_data, JsonReader *jr,
GString *gstr, const char *member,
const char *prefix, const char *suffix)
{
g_string_append_printf(gstr, "%s: { \"canbedisabled\": ", prefix);
if (!json_reader_read_member(jr, member)) {
logprintf(STDERR_FILENO,
"Missing '%s' field: %s\n",
member, info_data);
return -1;
}
if (!json_reader_read_member(jr, "CanBeDisabled")) {
logprintf(STDERR_FILENO,
"Missing 'CanBeDisabled' field under '%s': %s\n",
member, info_data);
return -1;
}
g_string_append_printf(gstr, "\"%s\", \"implemented\": ",
json_reader_get_string_value(jr));
json_reader_end_member(jr);
if (!json_reader_read_member(jr, "Implemented")) {
logprintf(STDERR_FILENO,
"Missing 'Implemented' field under '%s': %s\n",
member, info_data);
return -1;
}
g_string_append_printf(gstr, "\"%s\" }%s",
json_reader_get_string_value(jr),
suffix);
json_reader_end_member(jr);
json_reader_end_member(jr);
return 0;
}
static int get_profiles(gchar **profiles)
{
char *info_data = TPMLIB_GetInfo(TPMLIB_INFO_AVAILABLE_PROFILES);
char *info_data = TPMLIB_GetInfo(TPMLIB_INFO_AVAILABLE_PROFILES |
TPMLIB_INFO_RUNTIME_ALGORITHMS |
TPMLIB_INFO_RUNTIME_COMMANDS);
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);
GString *gstr = g_string_new("\"names\": [ ");
jp = json_parser_new();
@ -155,11 +193,18 @@ static int get_profiles(gchar **profiles)
goto error_unref_jr;
}
g_string_append_printf(gstr, "%s\"%s\"",
i > 0 ? ", " : " ",
i > 0 ? ", " : "",
json_reader_get_string_value(jr));
json_reader_end_element(jr);
json_reader_end_element(jr);
}
json_reader_end_member(jr);
if (get_profile_data(info_data, jr, gstr,
"RuntimeAlgorithms", " ], \"algorithms\"", "") ||
get_profile_data(info_data, jr, gstr,
"RuntimeCommands", ", \"commands\"", " "))
goto error_unref_jr;
error_unref_jr:
@ -218,7 +263,7 @@ int capabilities_print_json(bool cusetpm, TPMLIB_TPMVersion tpmversion)
"\"features\": [ "
"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
" ], "
"\"profiles\": [%s ], "
"\"profiles\": { %s}, "
"\"version\": \"" VERSION "\" "
"}",
with_tpm1,

View File

@ -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 \

108
src/swtpm_setup/profile.c Normal file
View File

@ -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 <stdio.h>
#include "config.h"
#include <json-glib/json-glib.h>
#include "profile.h"
#include "swtpm_utils.h"
/* Return the names of the supported profiles */
static
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;
}
if (!json_reader_read_member(jr, "names")) {
logerr(gl_LOGFILE, "Missing 'names' field under 'profiles': %s\n",
swtpm_capabilities_json);
goto error_unref_jr;
}
num = json_reader_count_elements(jr);
if (num < 0) {
logerr(gl_LOGFILE, "Number of profile names is bad (%d)\n",
num);
goto error_unref_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;
}
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;
}

17
src/swtpm_setup/profile.h Normal file
View File

@ -0,0 +1,17 @@
/* 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 <glib.h>
int check_json_profile(const gchar *swtpm_capabilities_json, const char *json_profile);
#endif /* SWTPM_SETUP_PROFILE_H */

View File

@ -76,6 +76,7 @@ static void swtpm_close_comm(struct swtpm *self, bool all)
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;
@ -118,6 +119,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);
@ -2009,7 +2016,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;
@ -2019,6 +2026,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;
@ -2032,7 +2040,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;
@ -2040,12 +2048,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;

View File

@ -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 *);

View File

@ -32,6 +32,7 @@
#include <libtpms/tpm_nvfilename.h>
#include "profile.h"
#include "swtpm.h"
#include "swtpm_conf.h"
#include "swtpm_utils.h"
@ -548,14 +549,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;
@ -978,6 +979,9 @@ 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"
"--profile <json-profile>\n"
" : Configure swtpm with the given profile.\n"
"\n"
"--version : Display version and exit\n"
"\n"
"--help,-h : Display this help screen\n\n",
@ -1098,6 +1102,18 @@ 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 the JSON object of swtpm_setup's capabilities */
static int print_capabilities(char **swtpm_prg_l, gboolean swtpm_has_tpm12,
gboolean swtpm_has_tpm2)
@ -1122,7 +1138,10 @@ static int print_capabilities(char **swtpm_prg_l, gboolean swtpm_has_tpm12,
"\"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"
", \"cmdarg-profile\""
""
" ], "
"\"version\": \"" VERSION "\" "
"}\n",
swtpm_has_tpm12 ? "\"tpm-1.2\", " : "",
@ -1244,6 +1263,7 @@ int main(int argc, char *argv[])
{"version", no_argument, NULL, '1'},
{"print-capabilities", no_argument, NULL, 'y'},
{"reconfigure", no_argument, NULL, 'R'},
{"profile", required_argument, NULL, 'I'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
@ -1272,6 +1292,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;
@ -1457,6 +1478,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);
@ -1484,7 +1509,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;
@ -1615,6 +1639,14 @@ int main(int argc, char *argv[])
pcr_banks = get_default_pcr_banks(config_file_lines);
}
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 (cipher != NULL) {
if (strcmp(cipher, "aes-128-cbc") != 0 &&
strcmp(cipher, "aes-cbc") != 0 &&
@ -1729,7 +1761,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) {

View File

@ -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

View File

@ -21,6 +21,7 @@
#include <unistd.h>
#include <glib.h>
#include <json-glib/json-glib.h>
#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;
}

View File

@ -55,4 +55,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 */