blob: a4090899a660b1fe20f80142c9119a93c40760b1 [file]
/* 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 <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <json-glib/json-glib.h>
#include "profile.h"
#include "swtpm_conf.h"
#include "swtpm_utils.h"
#include "swtpm_setup_utils.h"
#include "compiler_dependencies.h"
#define DISTRO_PROFILES_DIR DATAROOTDIR "/swtpm/profiles"
/* 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;
}
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);
/* { "Name": null } does not lead to parser failure but return name = NULL */
if (ret || name == NULL) {
ret = 1;
logerr(gl_LOGFILE, "Failed to get 'Name' from profile '%s'.\n",
json_profile);
return ret;
}
if (strlen(name) > 32) {
logerr(gl_LOGFILE, "Profile name must not exceed 32 characters.\n");
return 1;
}
/* 'custom:' prefix is accepted */
if (!strncmp(name, "custom:", 7))
return 0;
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;
}
/* Create a path to the profile and check whether the file is accessible */
static int profile_path_from_dir(const gchar *dir,
const gchar *profile_name,
gchar **json_profile_file)
{
*json_profile_file = g_strdup_printf("%s/%s.json",
dir, profile_name);
if (g_access(*json_profile_file, R_OK) != 0) {
SWTPM_G_FREE(*json_profile_file);
return -1;
}
return 0;
}
static int profile_path_local(gchar *const *config_file_lines,
const gchar *profile_name,
gchar **json_profile_file)
{
g_autofree gchar *dir = NULL;
dir = get_config_value(config_file_lines, "local_profiles_dir");
if (dir == NULL || strlen(dir) == 0 )
return -1;
return profile_path_from_dir(dir, profile_name, json_profile_file);
}
static int profile_path_distro(gchar *const *config_file_lines SWTPM_ATTR_UNUSED,
const gchar *profile_name,
gchar **json_profile_file)
{
return profile_path_from_dir(DISTRO_PROFILES_DIR,
profile_name,
json_profile_file);
}
static int profile_build_json(gchar *const *config_file_lines SWTPM_ATTR_UNUSED,
const gchar *profile_name,
gchar **json_profile)
{
*json_profile = g_strdup_printf("{\"Name\":\"%s\"}", profile_name);
return 0;
}
/*
* Check whether the profile name is valid; especially avoid names with '/'
* that would enable '../../etc/foo'.
*/
int profile_name_check(const gchar *profile_name)
{
GMatchInfo *match_info;
GRegex *regex;
int ret = 0;
regex = g_regex_new("^[A-Za-z0-9.\\-:]+$",
0 /* G_REGEX_DEFAULT */,
0 /* G_REGEX_MATCH_DEFAULT */,
NULL);
g_regex_match(regex, profile_name, 0, &match_info);
if (!g_match_info_matches(match_info)) {
logerr(gl_LOGFILE,
"Profile name '%s' contains unacceptable characters.\n",
profile_name);
ret = -1;
}
g_match_info_free(match_info);
g_regex_unref(regex);
return ret;
}
/*
* Try to find the profile with the given name as a file (with added .json
* suffix) in the local or distro files directories. If not found in either,
* create the JSON for selecting a built-in profile.
*
* @config_file_lines: lines of the configuration file
* @json_profile_name: the name of the profile to search for
* @json_profile_file: pointer to set an available profile file's full path
* @json_profile: pointer to set to the JSON string for built-in profile
*/
int profile_get_by_name(gchar *const *config_file_lines,
const gchar *json_profile_name,
gchar **json_profile_file,
gchar **json_profile)
{
typedef int (*getter_t)(gchar *const *config_file_lines,
const gchar *name,
gchar **result);
const struct {
const char *prefix;
gboolean filename; /* true: returns a filename */
getter_t getter;
} prefixes[] = {
{ "local:", TRUE, profile_path_local},
{ "distro:", TRUE, profile_path_distro},
{ "builtin:", FALSE, profile_build_json},
{ NULL, },
};
size_t i, len;
for (i = 0; prefixes[i].prefix; i++) {
len = strlen(prefixes[i].prefix);
if (!strncmp(prefixes[i].prefix, json_profile_name, len))
return prefixes[i].getter(config_file_lines,
&json_profile_name[len],
prefixes[i].filename
? json_profile_file
: json_profile);
}
/* no prefixes matched */
for (i = 0; prefixes[i].prefix; i++) {
if (prefixes[i].getter(config_file_lines,
json_profile_name,
prefixes[i].filename
? json_profile_file
: json_profile) == 0)
return 0;
}
return -1;
}
static int profile_replace_name(JsonObject *jo, const char *name)
{
JsonNode *node;
node = json_object_get_member(jo, "Name");
if (!node || json_node_get_node_type(node) != JSON_NODE_VALUE)
return -1;
json_object_set_string_member(jo, "Name", name);
return 0;
}
static JsonArray *profile_gather_dir(const char *dir)
{
g_autofree gchar *fullpath = NULL;
g_autoptr(JsonParser) jp = NULL;
const gchar *filename;
JsonNode *root;
JsonObject *jo;
JsonArray *ja;
GDir *gdir;
gdir = g_dir_open(dir, 0, NULL);
if (!gdir)
return NULL;
jp = json_parser_new();
ja = json_array_new();
while ((filename = g_dir_read_name(gdir))) {
size_t len = strlen(filename);
if (len <= 5 || !g_str_has_suffix(filename, ".json"))
continue;
fullpath = g_strdup_printf("%s/%s", dir, filename);
if (json_parser_load_from_file(jp, fullpath, NULL)) {
root = json_parser_get_root(jp);
if (json_node_get_node_type(root) == JSON_NODE_OBJECT) {
/* remove suffix .json */
g_autofree gchar *name = g_strndup(filename, len - 5);
jo = json_node_dup_object(root);
if (profile_replace_name(jo, name) == 0)
json_array_add_object_element(ja, jo);
else
json_object_unref(jo);
}
}
SWTPM_G_FREE(fullpath);
}
g_dir_close(gdir);
return ja;
}
static JsonArray *profile_gather_local(gchar *const *config_file_lines)
{
g_autofree gchar *dir = NULL;
dir = get_config_value(config_file_lines, "local_profiles_dir");
if (dir == NULL || strlen(dir) == 0 )
return NULL;
return profile_gather_dir(dir);
}
static JsonArray *profile_gather_builtin(const gchar **swtpm_prg_l)
{
g_autofree gchar *standard_output = NULL;
g_autofree const gchar **cmd = NULL;
g_autoptr(JsonParser) jp = NULL;
JsonNode *root, *arr;
JsonArray *ja = NULL;
JsonObject *jo;
cmd = concat_arrays(swtpm_prg_l,
(const gchar*[]) {"--tpm2", "--print-profiles", NULL},
FALSE);
if (!spawn_sync(NULL, cmd, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL,
&standard_output, NULL, NULL, NULL))
return NULL;
jp = json_parser_new();
if (json_parser_load_from_data(jp, standard_output, -1, NULL)) {
root = json_parser_get_root(jp);
if (json_node_get_node_type(root) == JSON_NODE_OBJECT) {
jo = json_node_get_object(root);
arr = json_object_get_member(jo, "AvailableProfiles");
if (arr && json_node_get_node_type(arr) == JSON_NODE_ARRAY)
ja = json_node_dup_array(arr);
}
}
return ja;
}
/*
* Gather all local, distro, and built-in profiles in a JSON map and print
* to stdout.
*
* @swtpm_prg_l: Array of strings to invoke swtpm (e.g., 'swtpm socket')
* @config_file_line: Lines of the configuratin file
*
* Returns the number of bytes printed.
*/
int profile_printall(const gchar **swtpm_prg_l,
gchar *const *config_file_lines)
{
g_autoptr(JsonGenerator) jg = NULL;
g_autoptr(JsonObject) jo = NULL;
g_autoptr(JsonNode) root = NULL;
g_autofree gchar *out = NULL;
JsonArray *ja;
jo = json_object_new();
ja = profile_gather_local(config_file_lines);
if (ja)
json_object_set_array_member(jo, "local", ja);
ja = profile_gather_dir(DISTRO_PROFILES_DIR);
if (ja)
json_object_set_array_member(jo, "distro", ja);
ja = profile_gather_builtin(swtpm_prg_l);
if (ja)
json_object_set_array_member(jo, "builtin", ja);
root = json_node_new(JSON_NODE_OBJECT);
json_node_set_object(root, jo);
jg = json_generator_new();
json_generator_set_root(jg, root);
out = json_generator_to_data(jg, NULL);
return printf("%s\n", out);
}