| /* SPDX-License-Identifier: BSD-3-Clause */ |
| /* |
| * swtpm_setup.c: Tool to simulate TPM 1.2 & TPM 2 manufacturing |
| * |
| * Author: Stefan Berger, stefanb@linux.ibm.com |
| * |
| * Copyright (c) IBM Corporation, 2021 |
| */ |
| |
| #include "config.h" |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <grp.h> |
| #include <limits.h> |
| #include <pwd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <glib.h> |
| #include <glib/gstdio.h> |
| #include <glib/gprintf.h> |
| |
| #include <glib-object.h> |
| #include <json-glib/json-glib.h> |
| |
| #include <libtpms/tpm_nvfilename.h> |
| |
| #include "swtpm.h" |
| #include "swtpm_conf.h" |
| #include "swtpm_utils.h" |
| #include "swtpm_setup_utils.h" |
| |
| #include <openssl/sha.h> |
| |
| /* default values for passwords */ |
| #define DEFAULT_OWNER_PASSWORD "ooo" |
| #define DEFAULT_SRK_PASSWORD "sss" |
| |
| #define SETUP_CREATE_EK_F (1 << 0) |
| #define SETUP_TAKEOWN_F (1 << 1) |
| #define SETUP_EK_CERT_F (1 << 2) |
| #define SETUP_PLATFORM_CERT_F (1 << 3) |
| #define SETUP_LOCK_NVRAM_F (1 << 4) |
| #define SETUP_SRKPASS_ZEROS_F (1 << 5) |
| #define SETUP_OWNERPASS_ZEROS_F (1 << 6) |
| #define SETUP_STATE_OVERWRITE_F (1 << 7) |
| #define SETUP_STATE_NOT_OVERWRITE_F (1 << 8) |
| #define SETUP_TPM2_F (1 << 9) |
| #define SETUP_ALLOW_SIGNING_F (1 << 10) |
| #define SETUP_TPM2_ECC_F (1 << 11) |
| #define SETUP_CREATE_SPK_F (1 << 12) |
| #define SETUP_DISPLAY_RESULTS_F (1 << 13) |
| #define SETUP_DECRYPTION_F (1 << 14) |
| #define SETUP_WRITE_EK_CERT_FILES_F (1 << 15) |
| #define SETUP_RECONFIGURE_F (1 << 16) |
| |
| /* default configuration file */ |
| #define SWTPM_SETUP_CONF "swtpm_setup.conf" |
| |
| /* Default logging goes to stderr */ |
| gchar *gl_LOGFILE = NULL; |
| |
| #define DEFAULT_RSA_KEYSIZE 2048 |
| |
| |
| static const struct flag_to_certfile { |
| unsigned long flag; |
| const char *filename; |
| const char *type; |
| } flags_to_certfiles[] = { |
| {.flag = SETUP_EK_CERT_F , .filename = "ek.cert", .type = "ek" }, |
| {.flag = SETUP_PLATFORM_CERT_F, .filename = "platform.cert", .type = "platform" }, |
| {.flag = 0, .filename = NULL, .type = NULL}, |
| }; |
| |
| /* initialize the path of the config_file */ |
| static int init(gchar **config_file) |
| { |
| const gchar *configdir = g_get_user_config_dir(); |
| |
| *config_file = g_build_filename(configdir, SWTPM_SETUP_CONF, NULL); |
| if (access(*config_file, R_OK) != 0) { |
| g_free(*config_file); |
| *config_file = g_build_filename(SYSCONFDIR, SWTPM_SETUP_CONF, NULL); |
| } |
| |
| return 0; |
| } |
| |
| /* Get the spec and attributes parameters from swtpm */ |
| static int tpm_get_specs_and_attributes(struct swtpm *swtpm, gchar ***params) |
| { |
| int ret; |
| g_autofree gchar *json = NULL; |
| JsonParser *jp = NULL; |
| GError *error = NULL; |
| JsonReader *jr = NULL; |
| JsonNode *root; |
| static const struct parse_rule { |
| const char *node1; |
| const char *node2; |
| gboolean is_int; |
| const char *optname; |
| } parser_rules[7] = { |
| {"TPMSpecification", "family", FALSE, "--tpm-spec-family"}, |
| {"TPMSpecification", "level", TRUE, "--tpm-spec-level"}, |
| {"TPMSpecification", "revision", TRUE, "--tpm-spec-revision"}, |
| {"TPMAttributes", "manufacturer", FALSE, "--tpm-manufacturer"}, |
| {"TPMAttributes", "model", FALSE, "--tpm-model"}, |
| {"TPMAttributes", "version", FALSE, "--tpm-version"}, |
| {NULL, NULL, FALSE, NULL}, |
| }; |
| size_t idx; |
| |
| ret = swtpm->cops->ctrl_get_tpm_specs_and_attrs(swtpm, &json); |
| if (ret != 0) { |
| logerr(gl_LOGFILE, "Could not get the TPM spec and attribute parameters.\n"); |
| return 1; |
| } |
| |
| jp = json_parser_new(); |
| |
| if (!json_parser_load_from_data(jp, json, -1, &error)) { |
| logerr(gl_LOGFILE, "JSON parser failed: %s\n", error->message); |
| g_error_free(error); |
| goto error; |
| } |
| |
| *params = NULL; |
| root = json_parser_get_root(jp); |
| |
| for (idx = 0; parser_rules[idx].node1 != NULL; idx++) { |
| jr = json_reader_new(root); |
| if (json_reader_read_member(jr, parser_rules[idx].node1) && |
| json_reader_read_member(jr, parser_rules[idx].node2)) { |
| gchar *str; |
| |
| if (parser_rules[idx].is_int) |
| str = g_strdup_printf("%ld", (long)json_reader_get_int_value(jr)); |
| else |
| str = g_strdup(json_reader_get_string_value(jr)); |
| |
| *params = concat_arrays(*params, |
| (gchar*[]){ |
| g_strdup(parser_rules[idx].optname), |
| str, |
| NULL |
| }, TRUE); |
| } else { |
| logerr(gl_LOGFILE, "Could not find [%s][%s] in '%s'\n", |
| parser_rules[idx].node1, parser_rules[idx].node2, json); |
| ret = 1; |
| break; |
| } |
| g_object_unref(jr); |
| jr = NULL; |
| } |
| |
| if (ret) { |
| g_strfreev(*params); |
| *params = NULL; |
| g_object_unref(jr); |
| } |
| error: |
| g_object_unref(jp); |
| |
| return ret; |
| } |
| |
| /* Call an external tool to create the certificates */ |
| static int call_create_certs(unsigned long flags, unsigned int cert_flags, |
| const gchar *configfile, const gchar *certsdir, |
| const gchar *ekparam, const gchar *vmid, struct swtpm *swtpm) |
| { |
| gchar **config_file_lines = NULL; /* must free */ |
| g_autofree gchar *create_certs_tool = NULL; |
| g_autofree gchar *create_certs_tool_config = NULL; |
| g_autofree gchar *create_certs_tool_options = NULL; |
| g_autofree gchar **cmd = NULL; |
| gchar **params = NULL; /* must free */ |
| g_autofree gchar *prgname = NULL; |
| gboolean success; |
| gint exit_status; |
| size_t idx, j; |
| gchar *s; |
| int ret; |
| |
| ret = tpm_get_specs_and_attributes(swtpm, ¶ms); |
| if (ret != 0) |
| goto error; |
| |
| ret = read_file_lines(configfile, &config_file_lines); |
| if (ret != 0) |
| goto error; |
| |
| create_certs_tool = get_config_value(config_file_lines, "create_certs_tool"); |
| create_certs_tool_config = get_config_value(config_file_lines, "create_certs_tool_config"); |
| create_certs_tool_options = get_config_value(config_file_lines, "create_certs_tool_options"); |
| |
| ret = 0; |
| |
| if (create_certs_tool != NULL) { |
| g_autofree gchar *create_certs_tool_path = g_find_program_in_path(create_certs_tool); |
| if (create_certs_tool_path == NULL) { |
| logerr(gl_LOGFILE, "Could not find %s in PATH.\n", create_certs_tool); |
| ret = 1; |
| goto error; |
| } |
| |
| if (flags & SETUP_TPM2_F) { |
| params = concat_arrays(params, |
| (gchar*[]){ |
| g_strdup("--tpm2"), |
| NULL |
| }, TRUE); |
| } |
| cmd = concat_arrays((gchar*[]) { |
| create_certs_tool_path, |
| "--type", "_", /* '_' must be at index '2' ! */ |
| "--ek", (gchar *)ekparam, |
| "--dir", (gchar *)certsdir, |
| NULL |
| }, NULL, FALSE); |
| if (gl_LOGFILE != NULL) |
| cmd = concat_arrays(cmd, (gchar*[]){"--logfile", (gchar *)gl_LOGFILE, NULL}, TRUE); |
| if (vmid != NULL) |
| cmd = concat_arrays(cmd, (gchar*[]){"--vmid", (gchar *)vmid, NULL}, TRUE); |
| cmd = concat_arrays(cmd, params, TRUE); |
| if (create_certs_tool_config != NULL) |
| cmd = concat_arrays(cmd, (gchar*[]){"--configfile", create_certs_tool_config, NULL}, TRUE); |
| if (create_certs_tool_options != NULL) |
| cmd = concat_arrays(cmd, (gchar*[]){"--optsfile", create_certs_tool_options, NULL}, TRUE); |
| |
| s = g_strrstr(create_certs_tool, G_DIR_SEPARATOR_S); |
| if (s) |
| prgname = strdup(&s[1]); |
| else |
| prgname = strdup(create_certs_tool); |
| |
| for (idx = 0; flags_to_certfiles[idx].filename != NULL; idx++) { |
| if (cert_flags & flags_to_certfiles[idx].flag) { |
| g_autofree gchar *standard_output = NULL; |
| g_autofree gchar *standard_error = NULL; |
| GError *error = NULL; |
| gchar **lines; |
| |
| cmd[2] = (gchar *)flags_to_certfiles[idx].type; /* replaces the "_" above */ |
| |
| s = g_strjoinv(" ", cmd); |
| logit(gl_LOGFILE, " Invoking %s\n", s); |
| g_free(s); |
| |
| success = g_spawn_sync(NULL, cmd, NULL, 0, NULL, NULL, |
| &standard_output, &standard_error, &exit_status, &error); |
| if (!success) { |
| logerr(gl_LOGFILE, "An error occurred running %s: %s\n", |
| create_certs_tool, error->message); |
| g_error_free(error); |
| ret = 1; |
| break; |
| } else if (exit_status != 0) { |
| logerr(gl_LOGFILE, "%s exit with status %d: %s\n", |
| prgname, WEXITSTATUS(exit_status), standard_error); |
| ret = 1; |
| break; |
| } |
| |
| lines = g_strsplit(standard_output, "\n", -1); |
| for (j = 0; lines[j] != NULL; j++) { |
| if (strlen(lines[j]) > 0) |
| logit(gl_LOGFILE, "%s: %s\n", prgname, lines[j]); |
| } |
| g_strfreev(lines); |
| |
| SWTPM_G_FREE(standard_output); |
| SWTPM_G_FREE(standard_error); |
| } |
| } |
| } |
| |
| error: |
| g_strfreev(config_file_lines); |
| g_strfreev(params); |
| |
| return ret; |
| } |
| |
| static char *create_certfile_name(const gchar *user_certsdir, |
| const gchar *key_type, |
| const gchar *key_description) |
| { |
| g_autofree gchar *filename = g_strdup_printf("%s-%s.crt", key_type, key_description); |
| |
| return g_strjoin(G_DIR_SEPARATOR_S, user_certsdir, filename, NULL); |
| } |
| |
| /* |
| * Remove the cert file unless the user wants a copy of it. |
| */ |
| static int certfile_move_or_delete(unsigned long flags, gboolean preserve, const gchar *certfile, |
| const gchar *user_certsdir, const gchar *key_type, |
| const gchar *key_description) |
| { |
| g_autofree gchar *content = NULL; |
| g_autofree gchar *cf = NULL; |
| gsize content_length; |
| GError *error = NULL; |
| size_t offset = 0; |
| |
| if (preserve && (flags & SETUP_WRITE_EK_CERT_FILES_F) && user_certsdir != NULL) { |
| if (!g_file_get_contents(certfile, &content, &content_length, &error)) |
| goto error; |
| |
| cf = create_certfile_name(user_certsdir, key_type, key_description); |
| if (!(flags & SETUP_TPM2_F)) { |
| /* A TPM 1.2 certificate has a 7 byte header at the beginning |
| * that we now remove */ |
| if (content_length >= 8) |
| offset = 7; |
| } |
| if (!g_file_set_contents(cf, &content[offset], content_length - offset, |
| &error)) |
| goto error; |
| if (g_chmod(cf, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { |
| logerr(gl_LOGFILE, "Failed to chmod file '%s': %s\n", cf, strerror(errno)); |
| goto error_unlink; |
| } |
| } |
| unlink(certfile); |
| |
| return 0; |
| |
| error: |
| logerr(gl_LOGFILE, "%s\n", error->message); |
| g_error_free(error); |
| |
| error_unlink: |
| unlink(certfile); |
| |
| return 1; |
| } |
| |
| static int read_certificate_file(const gchar *certsdir, const gchar *filename, |
| gchar **filecontent, size_t *filecontent_len, |
| gchar **certfile) |
| { |
| *certfile = g_strjoin(G_DIR_SEPARATOR_S, certsdir, filename, NULL); |
| |
| return read_file(*certfile, filecontent, filecontent_len); |
| } |
| |
| /* |
| * Read the certificate from the file where swtpm_cert left it. |
| * Write the file into the TPM's NVRAM and, if the user wants it, |
| * copy it into a user-provided directory. |
| */ |
| static int tpm2_persist_certificate(unsigned long flags, const gchar *certsdir, |
| const struct flag_to_certfile *ftc, |
| unsigned int rsa_keysize, struct swtpm2 *swtpm2, |
| const gchar *user_certsdir, const gchar *key_type, |
| const gchar *key_description) |
| { |
| g_autofree gchar *filecontent = NULL; |
| g_autofree gchar *certfile = NULL; |
| size_t filecontent_len; |
| int ret; |
| |
| ret = read_certificate_file(certsdir, ftc->filename, |
| &filecontent, &filecontent_len, &certfile); |
| if (ret != 0) |
| goto error_unlink; |
| |
| if (ftc->flag == SETUP_EK_CERT_F) { |
| ret = swtpm2->ops->write_ek_cert_nvram(&swtpm2->swtpm, |
| !!(flags & SETUP_TPM2_ECC_F), rsa_keysize, |
| !!(flags & SETUP_LOCK_NVRAM_F), |
| (const unsigned char*)filecontent, filecontent_len); |
| } else { |
| ret = swtpm2->ops->write_platform_cert_nvram(&swtpm2->swtpm, |
| !!(flags & SETUP_LOCK_NVRAM_F), |
| (const unsigned char *)filecontent, filecontent_len); |
| } |
| |
| if (ret != 0) |
| goto error_unlink; |
| |
| return certfile_move_or_delete(flags, !!(ftc->flag & SETUP_EK_CERT_F), |
| certfile, user_certsdir, |
| key_type, key_description); |
| |
| error_unlink: |
| unlink(certfile); |
| return 1; |
| } |
| |
| /* Create EK and certificate for a TPM 2 */ |
| static int tpm2_create_ek_and_cert(unsigned long flags, const gchar *config_file, |
| const gchar *certsdir, const gchar *vmid, |
| unsigned int rsa_keysize, struct swtpm2 *swtpm2, |
| const gchar *user_certsdir) |
| { |
| g_autofree gchar *ekparam = NULL; |
| const char *key_description; |
| unsigned long cert_flags; |
| const gchar *key_type; |
| size_t idx; |
| int ret; |
| |
| if (flags & SETUP_CREATE_EK_F) { |
| ret = swtpm2->ops->create_ek(&swtpm2->swtpm, !!(flags & SETUP_TPM2_ECC_F), rsa_keysize, |
| !!(flags & SETUP_ALLOW_SIGNING_F), |
| !!(flags & SETUP_DECRYPTION_F), |
| !!(flags & SETUP_LOCK_NVRAM_F), |
| &ekparam, &key_description); |
| if (ret != 0) |
| return 1; |
| } |
| |
| /* Only look at ek and platform certs here */ |
| cert_flags = flags & (SETUP_EK_CERT_F | SETUP_PLATFORM_CERT_F); |
| if (cert_flags) { |
| ret = call_create_certs(flags, cert_flags, config_file, certsdir, ekparam, |
| vmid, &swtpm2->swtpm); |
| if (ret != 0) |
| return 1; |
| |
| for (idx = 0; flags_to_certfiles[idx].filename; idx++) { |
| if (cert_flags & flags_to_certfiles[idx].flag) { |
| key_type = flags_to_certfiles[idx].flag & SETUP_EK_CERT_F ? "ek" : ""; |
| |
| ret = tpm2_persist_certificate(flags, certsdir, &flags_to_certfiles[idx], |
| rsa_keysize, swtpm2, user_certsdir, |
| key_type, key_description); |
| if (ret) |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Create endorsement keys and certificates for a TPM 2 */ |
| static int tpm2_create_eks_and_certs(unsigned long flags, const gchar *config_file, |
| const gchar *certsdir, const gchar *vmid, |
| unsigned int rsa_keysize, struct swtpm2 *swtpm2, |
| const gchar *user_certsdir) |
| { |
| int ret; |
| |
| /* 1st key will be RSA */ |
| flags = flags & ~SETUP_TPM2_ECC_F; |
| ret = tpm2_create_ek_and_cert(flags, config_file, certsdir, vmid, rsa_keysize, swtpm2, |
| user_certsdir); |
| if (ret != 0) |
| return 1; |
| |
| /* 2nd key will be an ECC; no more platform cert */ |
| flags = (flags & ~SETUP_PLATFORM_CERT_F) | SETUP_TPM2_ECC_F; |
| return tpm2_create_ek_and_cert(flags, config_file, certsdir, vmid, rsa_keysize, swtpm2, |
| user_certsdir); |
| } |
| |
| /* Get the default PCR banks from the config file and if nothing can |
| be found there use the DEFAULT_PCR_BANKS #define. |
| */ |
| static gchar *get_default_pcr_banks(const gchar *config_file) |
| { |
| g_auto(GStrv) config_file_lines = NULL; |
| gchar *pcr_banks; |
| int ret; |
| |
| ret = read_file_lines(config_file, &config_file_lines); |
| if (ret != 0) |
| return NULL; |
| |
| pcr_banks = get_config_value(config_file_lines, "active_pcr_banks"); |
| if (pcr_banks) |
| g_strstrip(pcr_banks); |
| if (pcr_banks == NULL || strlen(pcr_banks) == 0) { |
| g_free(pcr_banks); |
| pcr_banks = g_strdup(DEFAULT_PCR_BANKS); |
| } |
| return pcr_banks; |
| } |
| |
| /* Activate the given list of PCR banks. If pcr_banks is '-' then leave |
| * the configuration as-is. |
| */ |
| static int tpm2_activate_pcr_banks(struct swtpm2 *swtpm2, |
| const gchar *pcr_banks) |
| { |
| g_autofree gchar *active_pcr_banks_join = NULL; |
| g_autofree gchar *all_pcr_banks_join = NULL; |
| g_auto(GStrv) active_pcr_banks = NULL; |
| g_auto(GStrv) all_pcr_banks = NULL; |
| g_auto(GStrv) pcr_banks_l = NULL; |
| struct swtpm *swtpm = &swtpm2->swtpm; |
| int ret = 0; |
| |
| if (g_str_equal(pcr_banks, "-")) |
| return 0; |
| |
| ret = swtpm2->ops->get_all_pcr_banks(swtpm, &all_pcr_banks); |
| if (ret != 0) |
| return ret; |
| |
| pcr_banks_l = g_strsplit(pcr_banks, ",", -1); |
| ret = swtpm2->ops->set_active_pcr_banks(swtpm, pcr_banks_l, all_pcr_banks, |
| &active_pcr_banks); |
| if (ret != 0) |
| return ret; |
| |
| active_pcr_banks_join = g_strjoinv(",", active_pcr_banks); |
| all_pcr_banks_join = g_strjoinv(",", all_pcr_banks); |
| logit(gl_LOGFILE, "Successfully activated PCR banks %s among %s.\n", |
| active_pcr_banks_join, all_pcr_banks_join); |
| |
| return 0; |
| } |
| |
| /* Simulate manufacturing a TPM 2: create keys and certificates */ |
| static int init_tpm2(unsigned long flags, gchar **swtpm_prg_l, const gchar *config_file, |
| 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) |
| { |
| 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); |
| if (swtpm2 == NULL) |
| return 1; |
| swtpm = &swtpm2->swtpm; |
| |
| ret = swtpm->cops->start(swtpm); |
| if (ret != 0) { |
| logerr(gl_LOGFILE, "Could not start the TPM 2.\n"); |
| goto error; |
| } |
| |
| if (!(flags & SETUP_RECONFIGURE_F)) { |
| if ((flags & SETUP_CREATE_SPK_F)) { |
| ret = swtpm2->ops->create_spk(swtpm, !!(flags & SETUP_TPM2_ECC_F), rsa_keysize); |
| if (ret != 0) |
| goto destroy; |
| } |
| |
| ret = tpm2_create_eks_and_certs(flags, config_file, certsdir, vmid, rsa_keysize, swtpm2, |
| user_certsdir); |
| if (ret != 0) |
| goto destroy; |
| } |
| |
| ret = tpm2_activate_pcr_banks(swtpm2, pcr_banks); |
| if (ret != 0) |
| goto destroy; |
| |
| ret = swtpm2->ops->shutdown(swtpm); |
| |
| destroy: |
| swtpm->cops->destroy(swtpm); |
| |
| error: |
| swtpm_free(swtpm); |
| |
| return ret; |
| } |
| |
| /* Create the owner password digest */ |
| static void tpm12_get_ownerpass_digest(unsigned long flags, const gchar *ownerpass, |
| unsigned char ownerpass_digest[SHA_DIGEST_LENGTH]) |
| { |
| const gchar zeros[SHA_DIGEST_LENGTH]= {0, }; |
| size_t len; |
| |
| if (ownerpass == NULL) { |
| if (flags & SETUP_OWNERPASS_ZEROS_F) { |
| ownerpass = zeros; |
| len = sizeof(zeros); |
| } else { |
| ownerpass = DEFAULT_OWNER_PASSWORD; |
| len = strlen(ownerpass); |
| } |
| } else { |
| len = strlen(ownerpass); |
| } |
| SHA1((const unsigned char *)ownerpass, len, ownerpass_digest); |
| } |
| |
| /* Create the SRK password digest */ |
| static void tpm12_get_srkpass_digest(unsigned long flags, const gchar *srkpass, |
| unsigned char srkpass_digest[SHA_DIGEST_LENGTH]) |
| { |
| const gchar zeros[SHA_DIGEST_LENGTH]= {0, }; |
| size_t len; |
| |
| if (srkpass == NULL) { |
| if (flags & SETUP_SRKPASS_ZEROS_F) { |
| srkpass = zeros; |
| len = sizeof(zeros); |
| } else { |
| srkpass = DEFAULT_SRK_PASSWORD; |
| len = strlen(srkpass); |
| } |
| } else { |
| len = strlen(srkpass); |
| } |
| SHA1((const unsigned char *)srkpass, len, srkpass_digest); |
| } |
| |
| /* Take ownership of a TPM 1.2 */ |
| static int tpm12_take_ownership(unsigned long flags, const gchar *ownerpass, |
| const gchar *srkpass, gchar *pubek, size_t pubek_len, |
| struct swtpm12 *swtpm12) |
| { |
| unsigned char ownerpass_digest[SHA_DIGEST_LENGTH]; |
| unsigned char srkpass_digest[SHA_DIGEST_LENGTH]; |
| |
| tpm12_get_ownerpass_digest(flags, ownerpass, ownerpass_digest); |
| tpm12_get_srkpass_digest(flags, srkpass, srkpass_digest); |
| |
| return swtpm12->ops->take_ownership(&swtpm12->swtpm, ownerpass_digest, srkpass_digest, |
| (const unsigned char *)pubek, pubek_len); |
| } |
| |
| /* Create the certificates for a TPM 1.2 */ |
| static int tpm12_create_certs(unsigned long flags, const gchar *config_file, |
| const gchar *certsdir, const gchar *ekparam, |
| const gchar *vmid, struct swtpm12 *swtpm12, |
| const gchar *user_certsdir) |
| { |
| g_autofree gchar *filecontent = NULL; |
| g_autofree gchar *certfile = NULL; |
| unsigned int cert_flags; |
| const gchar *key_type; |
| gsize filecontent_len; |
| size_t idx; |
| int ret; |
| |
| /* TPM 1.2 only has ek and platform certs */ |
| cert_flags = flags & (SETUP_EK_CERT_F | SETUP_PLATFORM_CERT_F); |
| |
| ret = call_create_certs(flags, cert_flags, config_file, certsdir, ekparam, |
| vmid, &swtpm12->swtpm); |
| if (ret != 0) |
| return 1; |
| |
| for (idx = 0; flags_to_certfiles[idx].filename; idx++) { |
| if (cert_flags & flags_to_certfiles[idx].flag) { |
| SWTPM_G_FREE(filecontent); |
| SWTPM_G_FREE(certfile); |
| |
| ret = read_certificate_file(certsdir, flags_to_certfiles[idx].filename, |
| &filecontent, &filecontent_len, &certfile); |
| if (ret != 0) |
| return 1; |
| |
| if (flags_to_certfiles[idx].flag == SETUP_EK_CERT_F) { |
| ret = swtpm12->ops->write_ek_cert_nvram(&swtpm12->swtpm, |
| (const unsigned char*)filecontent, filecontent_len); |
| if (ret == 0) |
| logit(gl_LOGFILE, "Successfully created NVRAM area for EK certificate.\n"); |
| } else { |
| ret = swtpm12->ops->write_platform_cert_nvram(&swtpm12->swtpm, |
| (const unsigned char*)filecontent, filecontent_len); |
| if (ret == 0) |
| logit(gl_LOGFILE, "Successfully created NVRAM area for Platform certificate.\n"); |
| } |
| |
| if (ret != 0) { |
| unlink(certfile); |
| return 1; |
| } |
| |
| key_type = flags_to_certfiles[idx].flag & SETUP_EK_CERT_F ? "ek" : ""; |
| |
| if (certfile_move_or_delete(flags, !!(flags_to_certfiles[idx].flag & SETUP_EK_CERT_F), |
| certfile, user_certsdir, key_type, "rsa2048") != 0) |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Simulate manufacturing a TPM 1.2: create keys and certificate and possibly take ownership */ |
| static int init_tpm(unsigned long flags, gchar **swtpm_prg_l, const gchar *config_file, |
| const gchar *tpm_state_path, const gchar *ownerpass, const gchar *srkpass, |
| const gchar *vmid, const gchar *swtpm_keyopt, |
| int *fds_to_pass, size_t n_fds_to_pass, const gchar *certsdir, |
| const gchar *user_certsdir) |
| { |
| struct swtpm12 *swtpm12; |
| struct swtpm *swtpm; |
| g_autofree gchar *pubek = NULL; |
| size_t pubek_len = 0; |
| int ret = 1; |
| |
| swtpm12 = swtpm12_new(swtpm_prg_l, tpm_state_path, swtpm_keyopt, gl_LOGFILE, |
| fds_to_pass, n_fds_to_pass); |
| if (swtpm12 == NULL) |
| return 1; |
| swtpm = &swtpm12->swtpm; |
| |
| ret = swtpm->cops->start(swtpm); |
| if (ret != 0) { |
| logerr(gl_LOGFILE, "Could not start the TPM 1.2.\n"); |
| goto error; |
| } |
| |
| ret = swtpm12->ops->run_swtpm_bios(swtpm); |
| if (ret != 0) |
| goto destroy; |
| |
| if ((flags & SETUP_CREATE_EK_F)) { |
| ret = swtpm12->ops->create_endorsement_key_pair(swtpm, &pubek, &pubek_len); |
| if (ret != 0) |
| goto destroy; |
| |
| logit(gl_LOGFILE, "Successfully created EK.\n"); |
| |
| /* can only take owernship if created an EK */ |
| if ((flags & SETUP_TAKEOWN_F)) { |
| ret = tpm12_take_ownership(flags, ownerpass, srkpass, pubek, pubek_len, swtpm12); |
| if (ret != 0) |
| goto destroy; |
| |
| logit(gl_LOGFILE, "Successfully took ownership of the TPM.\n"); |
| } |
| |
| /* can only create EK cert if created an EK */ |
| if ((flags & SETUP_EK_CERT_F)) { |
| g_autofree gchar *ekparam = print_as_hex((unsigned char *)pubek, pubek_len); |
| |
| ret = tpm12_create_certs(flags, config_file, certsdir, ekparam, vmid, swtpm12, |
| user_certsdir); |
| if (ret != 0) |
| goto destroy; |
| } |
| } |
| |
| if ((flags & SETUP_LOCK_NVRAM_F)) { |
| ret = swtpm12->ops->nv_lock(swtpm); |
| if (ret == 0) |
| logit(gl_LOGFILE, "Successfully locked NVRAM access.\n"); |
| } |
| |
| destroy: |
| swtpm->cops->destroy(swtpm); |
| |
| error: |
| swtpm_free(swtpm); |
| |
| return ret; |
| } |
| |
| /* Check whether we are allowed to overwrite existing state. |
| * This function returns 2 if the state exists but flag is set to not to overwrite it, |
| * 0 in case we can overwrite it, 1 if the state exists. |
| */ |
| static int check_state_overwrite(gchar **swtpm_prg_l, unsigned int flags, |
| const char *tpm_state_path) |
| { |
| gboolean success; |
| g_autofree gchar *standard_output = NULL; |
| int exit_status = 0; |
| g_autoptr(GError) error = NULL; |
| g_autofree gchar **argv = NULL; |
| g_autofree gchar *statearg = g_strdup_printf("backend-uri=%s", tpm_state_path); |
| g_autofree gchar *logop = NULL; |
| g_autofree gchar **my_argv = NULL; |
| |
| my_argv = concat_arrays((gchar*[]) { |
| "--print-states", |
| "--tpmstate", |
| statearg, |
| NULL |
| }, NULL, FALSE); |
| |
| if (flags & SETUP_TPM2_F) |
| my_argv = concat_arrays(my_argv, (gchar*[]) { "--tpm2", NULL }, TRUE); |
| |
| if (gl_LOGFILE != NULL) { |
| logop = g_strdup_printf("file=%s", gl_LOGFILE); |
| my_argv = concat_arrays(my_argv, (gchar*[]){"--log", logop, NULL}, TRUE); |
| } |
| |
| argv = concat_arrays(swtpm_prg_l, my_argv, FALSE); |
| |
| success = g_spawn_sync(NULL, argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, |
| &standard_output, NULL, &exit_status, &error); |
| if (!success) { |
| logerr(gl_LOGFILE, "Could not start swtpm '%s': %s\n", swtpm_prg_l[0], error->message); |
| return 1; |
| } |
| |
| if (exit_status != 0) { |
| logerr(gl_LOGFILE, "%s exit with status %d: %s\n", |
| swtpm_prg_l[0], exit_status, standard_output); |
| return 1; |
| } |
| |
| if (g_strstr_len(standard_output, -1, TPM_PERMANENT_ALL_NAME) != NULL) { |
| /* State file exists */ |
| if (flags & SETUP_STATE_NOT_OVERWRITE_F) { |
| logit(gl_LOGFILE, "Not overwriting existing state file.\n"); |
| return 2; |
| } |
| if (flags & SETUP_STATE_OVERWRITE_F) |
| return 0; |
| logerr(gl_LOGFILE, "Found existing TPM state '%s'.\n", TPM_PERMANENT_ALL_NAME); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void versioninfo(void) |
| { |
| printf("TPM emulator setup tool version %d.%d.%d\n", |
| SWTPM_VER_MAJOR, SWTPM_VER_MINOR, SWTPM_VER_MICRO); |
| } |
| |
| static void usage(const char *prgname, const char *default_config_file) |
| { |
| versioninfo(); |
| printf( |
| "Usage: %s [options]\n" |
| "\n" |
| "The following options are supported:\n" |
| "\n" |
| "--runas <user> : Run this program under the given user's account.\n" |
| "\n" |
| "--tpm-state <dir>: Path where the TPM's state will be written to;\n" |
| " this is a mandatory argument. Prefix with dir:// to\n" |
| " use directory backend, or file:// to use linear file.\n" |
| "\n" |
| "--tpmstate <dir> : This is an alias for --tpm-state <dir>.\n" |
| "\n" |
| "--tpm <executable>\n" |
| " : Path to the TPM executable; this is an optional argument and\n" |
| " by default 'swtpm' in the PATH is used.\n" |
| "\n" |
| "--swtpm_ioctl <executable>\n" |
| " : Path to the swtpm_ioctl executable; this is deprecated\n" |
| " argument.\n" |
| "\n" |
| "--tpm2 : Setup a TPM 2; by default a TPM 1.2 is setup.\n" |
| "\n" |
| "--createek : Create the EK; for a TPM 2 an RSA and ECC EK will be\n" |
| " created\n" |
| "\n" |
| "--allow-signing : Create an EK that can be used for signing;\n" |
| " this option requires --tpm2.\n" |
| " Note: Careful, this option will create a non-standard EK!\n" |
| "\n" |
| "--decryption : Create an EK that can be used for key encipherment;\n" |
| " this is the default unless --allow-signing is given;\n" |
| " this option requires --tpm2.\n" |
| "\n" |
| "--ecc : This option allows to create a TPM 2's ECC key as storage\n" |
| " primary key; a TPM 2 always gets an RSA and an ECC EK key.\n" |
| "\n" |
| "--take-ownership : Take ownership; this option implies --createek\n" |
| " --ownerpass <password>\n" |
| " : Provide custom owner password; default is %s\n" |
| " --owner-well-known:\n" |
| " : Use an owner password of 20 zero bytes\n" |
| " --srkpass <password>\n" |
| " : Provide custom SRK password; default is %s\n" |
| " --srk-well-known:\n" |
| " : Use an SRK password of 20 zero bytes\n" |
| "--create-ek-cert : Create an EK certificate; this implies --createek\n" |
| "\n" |
| "--create-platform-cert\n" |
| " : Create a platform certificate; this implies --create-ek-cert\n" |
| "\n" |
| "--create-spk : Create storage primary key; this requires --tpm2\n" |
| "\n" |
| "--lock-nvram : Lock NVRAM access\n" |
| "\n" |
| "--display : At the end display as much info as possible about the\n" |
| " configuration of the TPM\n" |
| "\n" |
| "--config <config file>\n" |
| " : Path to configuration file; default is %s\n" |
| "\n" |
| "--logfile <logfile>\n" |
| " : Path to log file; default is logging to stderr\n" |
| "\n" |
| "--keyfile <keyfile>\n" |
| " : Path to a key file containing the encryption key for the\n" |
| " TPM to encrypt its persistent state with. The content\n" |
| " must be a 32 hex digit number representing a 128bit AES key.\n" |
| " This parameter will be passed to the TPM using\n" |
| " '--key file=<file>'.\n" |
| "\n" |
| "--keyfile-fd <fd>: Like --keyfile but a file descriptor is given to read the\n" |
| " encryption key from.\n" |
| "\n" |
| "--pwdfile <pwdfile>\n" |
| " : Path to a file containing a passphrase from which the\n" |
| " TPM will derive the 128bit AES key. The passphrase can be\n" |
| " 32 bytes long.\n" |
| " This parameter will be passed to the TPM using\n" |
| " '--key pwdfile=<file>'.\n" |
| "\n" |
| "--pwdfile-fd <fd>: Like --pwdfile but a file descriptor is given to to read\n" |
| " the passphrase from.\n" |
| "\n" |
| "--cipher <cipher>: The cipher to use; either aes-128-cbc or aes-256-cbc;\n" |
| " the default is aes-128-cbc; the same cipher must be\n" |
| " used on the swtpm command line\n" |
| "\n" |
| "--overwrite : Overwrite existing TPM state by re-initializing it; if this\n" |
| " option is not given, this program will return an error if\n" |
| " existing state is detected\n" |
| "\n" |
| "--not-overwrite : Do not overwrite existing TPM state but silently end\n" |
| "\n" |
| "--vmid <vm id> : Unique (VM) identifier to use as common name in certificate\n" |
| "\n" |
| "--pcr-banks <banks>\n" |
| " : Set of PCR banks to activate. Provide a comma separated list\n" |
| " like 'sha1,sha256'. '-' to skip and leave all banks active.\n" |
| " Default: %s\n" |
| "\n" |
| "--rsa-keysize <keysize>\n" |
| " : The RSA key size of the EK key; 3072 bits may be supported\n" |
| " if libtpms supports it.\n" |
| " Default: %u\n" |
| "\n" |
| "--write-ek-cert-files <directory>\n" |
| " : Write EK cert files into the given directory\n" |
| "\n" |
| "--tcsd-system-ps-file <file>\n" |
| " : This option is deprecated and has no effect.\n" |
| "\n" |
| "--print-capabilities\n" |
| " : Print JSON formatted capabilities added after v0.1 and exit.\n" |
| "\n" |
| "--create-config-files [[overwrite][,root]]\n" |
| " : Create swtpm_setup and swtpm-localca config files for a\n" |
| " user account.\n" |
| " overwrite: overwrite any existing files\n" |
| " root: allow to create files under root's home directory\n" |
| " skip-if-exist: if any file exists exit without error\n" |
| "\n" |
| "--reconfigure : Reconfigure an existing swtpm by reusing existing state.\n" |
| " The active PCR banks can be changed but no new keys will\n" |
| " be created.\n" |
| "\n" |
| "--version : Display version and exit\n" |
| "\n" |
| "--help,-h : Display this help screen\n\n", |
| prgname, |
| DEFAULT_OWNER_PASSWORD, |
| DEFAULT_SRK_PASSWORD, |
| default_config_file, |
| DEFAULT_PCR_BANKS, |
| DEFAULT_RSA_KEYSIZE |
| ); |
| } |
| |
| static int get_swtpm_capabilities(gchar **swtpm_prg_l, gboolean is_tpm2, |
| gchar **standard_output) |
| { |
| gchar *my_argv[] = { "--print-capabilities", is_tpm2 ? "--tpm2" : NULL, NULL }; |
| g_autofree gchar *logop = NULL; |
| g_autoptr(GError) error = NULL; |
| g_autofree gchar **argv = NULL; |
| int exit_status = 0; |
| gboolean success; |
| int ret = 1; |
| |
| argv = concat_arrays(swtpm_prg_l, my_argv, FALSE); |
| |
| if (gl_LOGFILE != NULL) { |
| logop = g_strdup_printf("file=%s", gl_LOGFILE); |
| argv = concat_arrays(argv, (gchar*[]){"--log", logop, NULL}, TRUE); |
| } |
| |
| success = g_spawn_sync(NULL, argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, |
| standard_output, NULL, &exit_status, &error); |
| if (!success) { |
| logerr(gl_LOGFILE, "Could not start swtpm '%s': %s\n", swtpm_prg_l[0], error->message); |
| goto error; |
| } |
| ret = 0; |
| |
| error: |
| return ret; |
| } |
| |
| static int get_supported_tpm_versions(gchar **swtpm_prg_l, gboolean *swtpm_has_tpm12, |
| gboolean *swtpm_has_tpm2) |
| { |
| g_autofree gchar *standard_output = NULL; |
| int ret; |
| |
| ret = get_swtpm_capabilities(swtpm_prg_l, FALSE, &standard_output); |
| if (ret) |
| return ret; |
| |
| *swtpm_has_tpm12 = g_strstr_len(standard_output, -1, "\"tpm-1.2\"") != NULL; |
| *swtpm_has_tpm2 = g_strstr_len(standard_output, -1, "\"tpm-2.0\"") != NULL; |
| |
| return 0; |
| } |
| |
| /* Get the support RSA key sizes. |
| * This function returns an array of ints like the following |
| * - [ 1024, 2048, 3072 ] |
| * - [] (empty array, indicating only 2048 bit RSA keys are supported) |
| */ |
| static int get_rsa_keysizes(unsigned long flags, gchar **swtpm_prg_l, |
| unsigned int **keysizes, size_t *n_keysizes) |
| { |
| g_autofree gchar *standard_output = NULL; |
| const gchar *needle = "\"rsa-keysize-"; |
| unsigned int keysize; |
| int ret = 1; |
| char *p; |
| int n; |
| |
| *n_keysizes = 0; |
| |
| if (flags & SETUP_TPM2_F) { |
| ret = get_swtpm_capabilities(swtpm_prg_l, TRUE, &standard_output); |
| if (ret) |
| goto error; |
| |
| p = standard_output; |
| /* A crude way of parsing the json output just looking for "rsa-keysize-%u" */ |
| while ((p = g_strstr_len(p, -1, needle)) != NULL) { |
| p += strlen(needle); |
| n = sscanf(p, "%u\"", &keysize); |
| if (n == 1) { |
| *keysizes = g_realloc(*keysizes, (*n_keysizes + 1) * sizeof(unsigned int)); |
| (*keysizes)[*n_keysizes] = keysize; |
| (*n_keysizes)++; |
| } |
| } |
| } |
| ret = 0; |
| |
| error: |
| return ret; |
| } |
| |
| /* Return the RSA key size capabilities in a NULL-terminated array */ |
| static int get_rsa_keysize_caps(unsigned long flags, gchar **swtpm_prg_l, |
| gchar ***keysize_strs) |
| { |
| unsigned int *keysizes = NULL; |
| size_t n_keysizes = 0; |
| size_t i, j; |
| int ret = get_rsa_keysizes(flags, swtpm_prg_l, &keysizes, &n_keysizes); |
| if (ret) |
| return ret; |
| |
| *keysize_strs = g_malloc0(sizeof(char *) * (n_keysizes + 1)); |
| for (i = 0, j = 0; i < n_keysizes; i++) { |
| if (keysizes[i] >= 2048) |
| (*keysize_strs)[j++] = g_strdup_printf("tpm2-rsa-keysize-%u", keysizes[i]); |
| } |
| |
| g_free(keysizes); |
| |
| return 0; |
| } |
| |
| /* 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) |
| { |
| g_autofree gchar *param = g_strdup(""); |
| gchar **keysize_strs = NULL; |
| gchar *tmp; |
| size_t i; |
| int ret = 0; |
| |
| ret = get_rsa_keysize_caps(SETUP_TPM2_F, swtpm_prg_l, &keysize_strs); |
| if (ret) |
| return 1; |
| |
| for (i = 0; keysize_strs[i] != NULL; i++) { |
| tmp = g_strdup_printf("%s, \"%s\"", param, keysize_strs[i]); |
| g_free(param); |
| param = 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 ], " |
| "\"version\": \"" VERSION "\" " |
| "}\n", |
| swtpm_has_tpm12 ? "\"tpm-1.2\", " : "", |
| swtpm_has_tpm2 ? "\"tpm-2.0\", " : "", |
| param); |
| |
| g_strfreev(keysize_strs); |
| |
| return 0; |
| } |
| |
| static int change_process_owner(const char *user) |
| { |
| char *endptr; |
| unsigned long long uid = strtoull(user, &endptr, 10); |
| gid_t gid; |
| struct passwd *passwd; |
| int ret = 1; |
| |
| if (*endptr != '\0') { |
| /* assuming a name */ |
| passwd = getpwnam(user); |
| if (passwd == NULL) { |
| logerr(gl_LOGFILE, "Error: User '%s' does not exist.\n", user); |
| goto error; |
| } |
| |
| if (initgroups(passwd->pw_name, passwd->pw_gid) != 0) { |
| logerr(gl_LOGFILE, "Error: initgroups() failed: %s\n", strerror(errno)); |
| goto error; |
| } |
| |
| gid = passwd->pw_gid; |
| uid = passwd->pw_uid; |
| } else { |
| if (uid > 0xffffffff) { |
| logerr(gl_LOGFILE, "Error: uid %s outside valid range.\n", user); |
| goto error; |
| } |
| gid = (gid_t)uid; |
| } |
| |
| if (setgid(gid) != 0) { |
| logerr(gl_LOGFILE, "Error: setgid(%d) failed: %s\n", gid, strerror(errno)); |
| goto error; |
| } |
| |
| if (setuid(uid) != 0) { |
| logerr(gl_LOGFILE, "Error: setuid(%d) failed: %s\n", uid, strerror(errno)); |
| goto error; |
| } |
| |
| ret = 0; |
| |
| error: |
| return ret; |
| } |
| |
| static int handle_create_config_files(const char *optarg) |
| { |
| g_auto(GStrv) tokens = NULL; |
| gboolean overwrite = FALSE; |
| gboolean root_flag = FALSE; |
| gboolean skip_if_exist = FALSE; |
| |
| if (optarg) { |
| tokens = g_strsplit_set(optarg, ", ", -1); |
| overwrite = g_strv_contains((const gchar **)tokens, "overwrite"); |
| root_flag = g_strv_contains((const gchar **)tokens, "root"); |
| skip_if_exist = g_strv_contains((const gchar **)tokens, "skip-if-exist"); |
| if (overwrite && skip_if_exist) { |
| fprintf(stderr, "Error: overwrite and skip-if-exist cannot both be used\n"); |
| return 1; |
| } |
| } |
| |
| return create_config_files(overwrite, root_flag, skip_if_exist); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int opt, option_index = 0; |
| static const struct option long_options[] = { |
| {"tpm-state", required_argument, NULL, 't'}, |
| {"tpmstate", required_argument, NULL, 't'}, /* alias for tpm-state */ |
| {"tpm", required_argument, NULL, 'T'}, |
| {"swtpm_ioctl", required_argument, NULL, '_'}, |
| {"tpm2", no_argument, NULL, '2'}, |
| {"ecc", no_argument, NULL, 'e'}, |
| {"createek", no_argument, NULL, 'c'}, |
| {"create-spk", no_argument, NULL, 'C'}, |
| {"take-ownership", no_argument, NULL, 'o'}, |
| {"ownerpass", required_argument, NULL, 'O'}, |
| {"owner-well-known", no_argument, NULL, 'w'}, |
| {"srkpass", required_argument, NULL, 'S'}, |
| {"srk-well-known", no_argument, NULL, 's'}, |
| {"create-ek-cert", no_argument, NULL, 'E'}, |
| {"create-platform-cert", no_argument, NULL, 'P'}, |
| {"lock-nvram", no_argument, NULL, 'L'}, |
| {"display", no_argument, NULL, 'i'}, |
| {"config", required_argument, NULL, 'f'}, |
| {"vmid", required_argument, NULL, 'm'}, |
| {"keyfile", required_argument, NULL, 'x'}, |
| {"keyfile-fd", required_argument, NULL, 'X'}, |
| {"pwdfile", required_argument, NULL, 'k'}, |
| {"pwdfile-fd", required_argument, NULL, 'K'}, |
| {"cipher", required_argument, NULL, 'p'}, |
| {"runas", required_argument, NULL, 'r'}, |
| {"logfile", required_argument, NULL, 'l'}, |
| {"overwrite", no_argument, NULL, 'v'}, |
| {"not-overwrite", no_argument, NULL, 'V'}, |
| {"allow-signing", no_argument, NULL, 'a'}, |
| {"decryption", no_argument, NULL, 'd'}, |
| {"pcr-banks", required_argument, NULL, 'b'}, |
| {"rsa-keysize", required_argument, NULL, 'A'}, |
| {"write-ek-cert-files", required_argument, NULL, '3'}, |
| {"create-config-files", optional_argument, NULL, 'u'}, |
| {"tcsd-system-ps-file", required_argument, NULL, 'F'}, |
| {"version", no_argument, NULL, '1'}, |
| {"print-capabilities", no_argument, NULL, 'y'}, |
| {"reconfigure", no_argument, NULL, 'R'}, |
| {"help", no_argument, NULL, 'h'}, |
| {NULL, 0, NULL, 0} |
| }; |
| unsigned long flags = 0; |
| g_autofree gchar *swtpm_prg = NULL; |
| g_autofree gchar *tpm_state_path = NULL; |
| struct swtpm_backend_ops *backend_ops = &swtpm_backend_dir; |
| void *backend_state = NULL; |
| g_autofree gchar *config_file = NULL; |
| g_autofree gchar *ownerpass = NULL; |
| gboolean got_ownerpass = FALSE; |
| g_autofree gchar *srkpass = NULL; |
| gboolean got_srkpass = FALSE; |
| g_autofree gchar *vmid = NULL; |
| g_autofree gchar *pcr_banks = NULL; |
| gboolean printcapabilities = FALSE; |
| g_autofree gchar *keyfile = NULL; |
| long int keyfile_fd = -1; |
| g_autofree gchar *pwdfile = NULL; |
| long int pwdfile_fd = -1; |
| g_autofree gchar *cipher = g_strdup("aes-128-cbc"); |
| g_autofree gchar *rsa_keysize_str = g_strdup_printf("%d", DEFAULT_RSA_KEYSIZE); |
| unsigned int rsa_keysize; |
| g_autofree gchar *swtpm_keyopt = NULL; |
| g_autofree gchar *runas = NULL; |
| g_autofree gchar *certsdir = NULL; |
| g_autofree gchar *user_certsdir = NULL; |
| gchar *tmp; |
| gchar **swtpm_prg_l = NULL; |
| gchar **tmp_l = NULL; |
| size_t i, n; |
| struct stat statbuf; |
| const struct passwd *curr_user; |
| struct group *curr_grp; |
| char *endptr; |
| gboolean swtpm_has_tpm12 = FALSE; |
| gboolean swtpm_has_tpm2 = FALSE; |
| int fds_to_pass[1] = { -1 }; |
| unsigned n_fds_to_pass = 0; |
| char tmpbuffer[200]; |
| time_t now; |
| struct tm *tm; |
| int ret = 1; |
| g_autoptr(GError) error = NULL; |
| |
| setvbuf(stdout, 0, _IONBF, 0); |
| |
| if (init(&config_file) < 0) |
| goto error; |
| |
| swtpm_prg = g_find_program_in_path("swtpm"); |
| if (swtpm_prg) { |
| tmp = g_strconcat(swtpm_prg, " socket", NULL); |
| g_free(swtpm_prg); |
| swtpm_prg = tmp; |
| } |
| |
| while ((opt = getopt_long(argc, argv, "h?", |
| long_options, &option_index)) != -1) { |
| switch (opt) { |
| case 't': /* --tpmstate, --tpm-state */ |
| g_free(tpm_state_path); |
| if (strncmp(optarg, "dir://", 6) == 0) { |
| tpm_state_path = g_strdup(optarg); |
| } else if (strncmp(optarg, "file://", 7) == 0) { |
| tpm_state_path = g_strdup(optarg); |
| backend_ops = &swtpm_backend_file; |
| } else { |
| /* always prefix with dir:// so we can pass verbatim to swtpm */ |
| tpm_state_path = g_strconcat("dir://", optarg, NULL); |
| } |
| break; |
| case 'T': /* --tpm */ |
| g_free(swtpm_prg); |
| swtpm_prg = g_strdup(optarg); |
| break; |
| case '_': /* --swtpm_ioctl */ |
| fprintf(stdout, "Warning: --swtpm_ioctl is deprecated and has no effect."); |
| break; |
| case '2': /* --tpm2 */ |
| flags |= SETUP_TPM2_F; |
| break; |
| case 'e': /* --ecc */ |
| flags |= SETUP_TPM2_ECC_F; |
| break; |
| case 'c': /* --createek */ |
| flags |= SETUP_CREATE_EK_F; |
| break; |
| case 'C': /* --create-spk */ |
| flags |= SETUP_CREATE_SPK_F; |
| break; |
| case 'o': /* --take-ownership */ |
| flags |= SETUP_CREATE_EK_F | SETUP_TAKEOWN_F; |
| break; |
| case 'O': /* --ownerpass */ |
| g_free(ownerpass); |
| ownerpass = g_strdup(optarg); |
| got_ownerpass = TRUE; |
| break; |
| case 'w': /* --owner-well-known */ |
| flags |= SETUP_OWNERPASS_ZEROS_F; |
| got_ownerpass = TRUE; |
| break; |
| case 'S': /* --srk-pass */ |
| g_free(srkpass); |
| srkpass = g_strdup(optarg); |
| got_srkpass = TRUE; |
| break; |
| case 's': /* --srk-well-known */ |
| flags |= SETUP_SRKPASS_ZEROS_F; |
| got_srkpass = TRUE; |
| break; |
| case 'E': /* --create-ek-cert */ |
| flags |= SETUP_CREATE_EK_F | SETUP_EK_CERT_F; |
| break; |
| case 'P': /* --create-platform-cert */ |
| flags |= SETUP_CREATE_EK_F | SETUP_PLATFORM_CERT_F; |
| break; |
| case 'L': /* --lock-nvram */ |
| flags |= SETUP_LOCK_NVRAM_F; |
| break; |
| case 'i': /* --display */ |
| flags |= SETUP_DISPLAY_RESULTS_F; |
| break; |
| case 'f': /* --config */ |
| g_free(config_file); |
| config_file = g_strdup(optarg); |
| break; |
| case 'm': /* --vmid */ |
| g_free(vmid); |
| vmid = g_strdup(optarg); |
| break; |
| case 'x': /* --keyfile */ |
| g_free(keyfile); |
| keyfile = g_strdup(optarg); |
| break; |
| case 'X': /* --pwdfile-fd' */ |
| keyfile_fd = strtoull(optarg, &endptr, 10); |
| if (*endptr != '\0' && keyfile_fd >= INT_MAX) { |
| fprintf(stderr, "Invalid file descriptor '%s'\n", optarg); |
| goto error; |
| } |
| break; |
| case 'k': /* --pwdfile */ |
| g_free(pwdfile); |
| pwdfile = g_strdup(optarg); |
| break; |
| case 'K': /* --pwdfile-fd' */ |
| pwdfile_fd = strtoull(optarg, &endptr, 10); |
| if (*endptr != '\0' || pwdfile_fd >= INT_MAX) { |
| fprintf(stderr, "Invalid file descriptor '%s'\n", optarg); |
| goto error; |
| } |
| break; |
| case 'p': /* --cipher */ |
| g_free(cipher); |
| cipher = g_strdup(optarg); |
| break; |
| case 'r': /* --runas */ |
| g_free(runas); |
| runas = g_strdup(optarg); |
| break; |
| case 'l': /* --logfile */ |
| g_free(gl_LOGFILE); |
| gl_LOGFILE = g_strdup(optarg); |
| break; |
| case 'v': /* --overwrite */ |
| flags |= SETUP_STATE_OVERWRITE_F; |
| break; |
| case 'V': /* --not-overwrite */ |
| flags |= SETUP_STATE_NOT_OVERWRITE_F; |
| break; |
| case 'a': /* --allow-signing */ |
| flags |= SETUP_ALLOW_SIGNING_F; |
| break; |
| case 'd': /* --decryption */ |
| flags |= SETUP_DECRYPTION_F; |
| break; |
| case 'b': /* --pcr-banks */ |
| tmp = g_strconcat(pcr_banks ? pcr_banks: "", |
| pcr_banks ? "," : "", g_strstrip(optarg), NULL); |
| g_free(pcr_banks); |
| pcr_banks = tmp; |
| break; |
| case 'A': /* --rsa-keysize */ |
| g_free(rsa_keysize_str); |
| rsa_keysize_str = strdup(optarg); |
| break; |
| case '3': /* --write-ek-cert-files */ |
| g_free(user_certsdir); |
| user_certsdir = g_strdup(optarg); |
| flags |= SETUP_WRITE_EK_CERT_FILES_F; |
| break; |
| case 'u': |
| if (optarg == NULL && optind < argc && argv[optind][0] != '0') |
| optarg = argv[optind++]; |
| ret = handle_create_config_files(optarg); |
| goto out; |
| case 'F': /* --tcsd-system-ps-file */ |
| printf("Warning: --tcsd-system-ps-file is deprecated and has no effect."); |
| break; |
| case '1': /* --version */ |
| versioninfo(); |
| ret = 0; |
| goto out; |
| case 'y': /* --print-capabilities */ |
| printcapabilities = TRUE; |
| break; |
| case 'R': /* --reconfigure */ |
| flags |= SETUP_RECONFIGURE_F; |
| break; |
| case '?': |
| case 'h': /* --help */ |
| usage(argv[0], config_file); |
| if (opt == 'h') |
| ret = 0; |
| goto out; |
| default: |
| fprintf(stderr, "Unknown option code %d\n", opt); |
| usage(argv[0], config_file); |
| goto error; |
| } |
| } |
| |
| if (swtpm_prg == NULL) { |
| logerr(gl_LOGFILE, |
| "Default TPM 'swtpm' could not be found and was not provided using --tpm.\n"); |
| goto error; |
| } |
| |
| swtpm_prg_l = split_cmdline(swtpm_prg); |
| tmp = g_find_program_in_path(swtpm_prg_l[0]); |
| if (!tmp) { |
| logerr(gl_LOGFILE, "swtpm at %s is not an executable.\n", swtpm_prg_l[0]); |
| goto error; |
| } |
| g_free(tmp); |
| |
| |
| ret = get_supported_tpm_versions(swtpm_prg_l, &swtpm_has_tpm12, &swtpm_has_tpm2); |
| if (ret != 0) |
| goto error; |
| |
| if (printcapabilities) { |
| ret = print_capabilities(swtpm_prg_l, swtpm_has_tpm12, swtpm_has_tpm2); |
| goto out; |
| } |
| |
| if ((flags & SETUP_TPM2_F) != 0 && !swtpm_has_tpm2) { |
| logerr(gl_LOGFILE, "swtpm at %s does not support TPM 2\n", swtpm_prg_l[0]); |
| goto error; |
| } else if ((flags & SETUP_TPM2_F) == 0 && !swtpm_has_tpm12){ |
| logerr(gl_LOGFILE, "swtpm at %s does not support TPM 1.2\n", swtpm_prg_l[0]); |
| goto error; |
| } |
| |
| if (runas) { |
| ret = change_process_owner(runas); |
| if (ret != 0) |
| goto error; |
| } |
| |
| if (!got_ownerpass) |
| ownerpass = g_strdup(DEFAULT_OWNER_PASSWORD); |
| if (!got_srkpass) |
| srkpass = g_strdup(DEFAULT_SRK_PASSWORD); |
| |
| if (gl_LOGFILE != NULL) { |
| FILE *tmpfile; |
| if (stat(gl_LOGFILE, &statbuf) == 0 && |
| (statbuf.st_mode & S_IFMT) == S_IFLNK) { |
| fprintf(stderr, "Logfile must not be a symlink.\n"); |
| goto error; |
| } |
| tmpfile = fopen(gl_LOGFILE, "a"); |
| if (tmpfile == NULL) { |
| fprintf(stderr, "Cannot write to logfile %s.\n", gl_LOGFILE); |
| goto error; |
| } |
| fclose(tmpfile); |
| } |
| |
| curr_user = getpwuid(getuid()); |
| |
| // Check tpm_state_path directory and access rights |
| if (tpm_state_path == NULL) { |
| logerr(gl_LOGFILE, "--tpm-state must be provided\n"); |
| goto error; |
| } |
| |
| backend_state = backend_ops->parse_backend(tpm_state_path); |
| if (!backend_state) |
| goto error; |
| |
| if (backend_ops->check_access(backend_state, R_OK|W_OK, curr_user) != 0) |
| goto error; |
| |
| if ((flags & SETUP_WRITE_EK_CERT_FILES_F)) { |
| if (check_directory_access(user_certsdir, W_OK, curr_user) != 0) |
| goto error; |
| } |
| |
| if (flags & SETUP_TPM2_F) { |
| if (flags & SETUP_TAKEOWN_F) { |
| logerr(gl_LOGFILE, "Taking ownership is not supported for TPM 2.\n"); |
| goto error; |
| } |
| } else { |
| if (flags & SETUP_TPM2_ECC_F) { |
| logerr(gl_LOGFILE, "--ecc requires --tpm2.\n"); |
| goto error; |
| } |
| if (flags & SETUP_CREATE_SPK_F) { |
| logerr(gl_LOGFILE, "--create-spk requires --tpm2.\n"); |
| goto error; |
| } |
| if (flags & SETUP_RECONFIGURE_F) { |
| logerr(gl_LOGFILE, "--reconfigure requires --tpm2.\n"); |
| goto error; |
| } |
| if (flags & SETUP_ALLOW_SIGNING_F) { |
| logerr(gl_LOGFILE, "--allow-signing requires --tpm2.\n"); |
| goto error; |
| } |
| if (flags & SETUP_DECRYPTION_F) { |
| logerr(gl_LOGFILE, "--decryption requires --tpm2.\n"); |
| goto error; |
| } |
| } |
| |
| if (!(flags & SETUP_RECONFIGURE_F)) { |
| ret = check_state_overwrite(swtpm_prg_l, flags, tpm_state_path); |
| if (ret == 1) { |
| goto error; |
| } else if (ret == 2) { |
| ret = 0; |
| goto out; |
| } |
| |
| ret = backend_ops->delete_state(backend_state); |
| if (ret != 0) |
| goto error; |
| } |
| |
| if (access(config_file, R_OK) != 0) { |
| logerr(gl_LOGFILE, "User %s cannot read config file %s.\n", |
| curr_user ? curr_user->pw_name : "<unknown>", config_file); |
| goto error; |
| } |
| |
| /* check pcr_banks; read from config file if not given */ |
| tmp_l = g_strsplit(pcr_banks ? pcr_banks : "", ",", -1); |
| for (i = 0, n = 0; tmp_l[i]; i++) { |
| g_strstrip(tmp_l[i]); |
| n += strlen(tmp_l[i]); |
| } |
| g_strfreev(tmp_l); |
| if (n == 0) { |
| g_free(pcr_banks); |
| pcr_banks = get_default_pcr_banks(config_file); |
| } |
| |
| if (cipher != NULL) { |
| if (strcmp(cipher, "aes-128-cbc") != 0 && |
| strcmp(cipher, "aes-cbc") != 0 && |
| strcmp(cipher, "aes-256-cbc") != 0) { |
| logerr(gl_LOGFILE, "Unsupported cipher %s.\n", cipher); |
| goto error; |
| } |
| tmp = g_strdup_printf(",mode=%s", cipher); |
| g_free(cipher); |
| cipher = tmp; |
| } |
| |
| if (keyfile != NULL) { |
| if (access(keyfile, R_OK) != 0) { |
| logerr(gl_LOGFILE, "User %s cannot read keyfile %s.\n", |
| curr_user ? curr_user->pw_name : "<unknown>", keyfile); |
| goto error; |
| } |
| swtpm_keyopt = g_strdup_printf("file=%s%s", keyfile, cipher); |
| logit(gl_LOGFILE, " The TPM's state will be encrypted with a provided key.\n"); |
| } else if (pwdfile != NULL) { |
| if (access(pwdfile, R_OK) != 0) { |
| logerr(gl_LOGFILE, "User %s cannot read passphrase file %s.\n", |
| curr_user ? curr_user->pw_name : "<unknown>", pwdfile); |
| goto error; |
| } |
| swtpm_keyopt = g_strdup_printf("pwdfile=%s%s", pwdfile, cipher); |
| logit(gl_LOGFILE, " The TPM's state will be encrypted using a key derived from a passphrase.\n"); |
| } else if (keyfile_fd >= 0) { |
| fds_to_pass[n_fds_to_pass++] = keyfile_fd; |
| swtpm_keyopt = g_strdup_printf("fd=%ld%s", keyfile_fd, cipher); |
| logit(gl_LOGFILE, " The TPM's state will be encrypted with a provided key (fd).\n"); |
| } else if (pwdfile_fd >= 0) { |
| fds_to_pass[n_fds_to_pass++] = pwdfile_fd; |
| swtpm_keyopt = g_strdup_printf("pwdfd=%ld%s", pwdfile_fd, cipher); |
| logit(gl_LOGFILE, " The TPM's state will be encrypted using a key derived from a passphrase (fd).\n"); |
| } |
| |
| if (strcmp(rsa_keysize_str, "max") == 0) { |
| unsigned int *keysizes = NULL; |
| size_t n_keysizes; |
| |
| ret = get_rsa_keysizes(flags, swtpm_prg_l, &keysizes, &n_keysizes); |
| if (ret) |
| goto error; |
| g_free(rsa_keysize_str); |
| if (n_keysizes > 0) { |
| /* last one is the biggest one */ |
| rsa_keysize_str = g_strdup_printf("%u", keysizes[n_keysizes - 1]); |
| } else { |
| rsa_keysize_str = g_strdup("2048"); |
| } |
| g_free(keysizes); |
| } |
| if (strcmp(rsa_keysize_str, "2048") == 0 || strcmp(rsa_keysize_str, "3072") == 0) { |
| unsigned int *keysizes = NULL; |
| size_t n_keysizes; |
| gboolean found = FALSE; |
| |
| ret = get_rsa_keysizes(flags, swtpm_prg_l, &keysizes, &n_keysizes); |
| if (ret) |
| goto error; |
| |
| rsa_keysize = strtoull(rsa_keysize_str, NULL, 10); |
| for (i = 0; i < n_keysizes && found == FALSE; i++) |
| found = (keysizes[i] == rsa_keysize); |
| if (!found && rsa_keysize != 2048) { |
| logerr(gl_LOGFILE, "%u bit RSA keys are not supported by libtpms.\n", rsa_keysize); |
| goto error; |
| } |
| g_free(keysizes); |
| } else { |
| logit(gl_LOGFILE, "Unsupported RSA key size %s.\n", rsa_keysize_str); |
| goto error; |
| } |
| |
| if (flags & SETUP_RECONFIGURE_F) { |
| if (flags & (SETUP_CREATE_EK_F | SETUP_EK_CERT_F | SETUP_PLATFORM_CERT_F)) { |
| logerr(gl_LOGFILE, "Reconfiguration is not supported with creation of EK or certificates\n"); |
| goto error; |
| } |
| } |
| |
| now = time(NULL); |
| tm = localtime(&now); |
| if (strftime(tmpbuffer, sizeof(tmpbuffer), "%a %d %h %Y %I:%M:%S %p %Z", tm) == 0) { |
| logerr(gl_LOGFILE, "Could not format time/date string.\n"); |
| goto error; |
| } |
| curr_grp = getgrgid(getgid()); |
| logit(gl_LOGFILE, "Starting vTPM %s as %s:%s @ %s\n", |
| flags & SETUP_RECONFIGURE_F ? "reconfiguration" : "manufacturing", |
| curr_user ? curr_user->pw_name : "<unknown>", |
| curr_grp ? curr_grp->gr_name : "<unknown>", |
| tmpbuffer); |
| |
| if (flags & (SETUP_EK_CERT_F | SETUP_PLATFORM_CERT_F)) { |
| certsdir = g_dir_make_tmp("swtpm_setup.certs.XXXXXX", &error); |
| if (certsdir == NULL) { |
| logerr(gl_LOGFILE, "Could not create temporary directory for certs: %s\n", |
| error->message); |
| goto error; |
| } |
| } |
| |
| if ((flags & SETUP_TPM2_F) == 0) { |
| ret = init_tpm(flags, swtpm_prg_l, config_file, tpm_state_path, ownerpass, srkpass, vmid, |
| swtpm_keyopt, fds_to_pass, n_fds_to_pass, certsdir, user_certsdir); |
| } 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); |
| } |
| |
| if (ret == 0) { |
| logit(gl_LOGFILE, "Successfully authored TPM state.\n"); |
| } else { |
| logerr(gl_LOGFILE, "An error occurred. Authoring the TPM state failed.\n"); |
| backend_ops->delete_state(backend_state); |
| } |
| |
| now = time(NULL); |
| tm = localtime(&now); |
| if (strftime(tmpbuffer, sizeof(tmpbuffer), "%a %d %h %Y %I:%M:%S %p %Z", tm) == 0) { |
| logerr(gl_LOGFILE, "Could not format time/date string.\n"); |
| goto error; |
| } |
| logit(gl_LOGFILE, "Ending vTPM manufacturing @ %s\n", |
| tmpbuffer); |
| |
| out: |
| if (certsdir && g_rmdir(certsdir) != 0) |
| logerr(gl_LOGFILE, "Could not remove temporary directory for certs: %s\n", |
| strerror(errno)); |
| |
| if (backend_ops && backend_state) |
| backend_ops->free_backend(backend_state); |
| g_strfreev(swtpm_prg_l); |
| g_free(gl_LOGFILE); |
| |
| return ret; |
| |
| error: |
| ret = 1; |
| goto out; |
| } |