From 54cdf2cb28817a04419d9d10d4d6bdbdbfe993f0 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Thu, 1 Mar 2018 12:57:39 +0100 Subject: [PATCH] tpm2-loadpkcs12: Load private key from PKCS#12 to TPM 2.0 This command loads a password-protected PKCS#12 container, extracts the private key, end entity and CA certificates and stores the private key in non-volatile storage under either the endorsement or owner hierarchy of a TPM 2.0. --- configure.ac | 1 + src/Makefile.am | 1 + src/libtpmtss/tpm_tss.h | 13 + src/libtpmtss/tpm_tss_trousers.c | 8 + src/libtpmtss/tpm_tss_tss2.c | 181 +++++++++++- src/tpm_loadpkcs12/.gitignore | 1 + src/tpm_loadpkcs12/Makefile.am | 14 + src/tpm_loadpkcs12/tpm_loadpkcs12.c | 414 ++++++++++++++++++++++++++++ 8 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 src/tpm_loadpkcs12/.gitignore create mode 100644 src/tpm_loadpkcs12/Makefile.am create mode 100644 src/tpm_loadpkcs12/tpm_loadpkcs12.c diff --git a/configure.ac b/configure.ac index ae04fc87c7..698c00ce59 100644 --- a/configure.ac +++ b/configure.ac @@ -1993,6 +1993,7 @@ AC_CONFIG_FILES([ src/scepclient/Makefile src/aikgen/Makefile src/tpm_extendpcr/Makefile + src/tpm_loadpkcs12/Makefile src/pki/Makefile src/pki/man/Makefile src/pool/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index e2747c300a..c873e93eb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -146,4 +146,5 @@ endif if USE_TPM SUBDIRS += tpm_extendpcr + SUBDIRS += tpm_loadpkcs12 endif diff --git a/src/libtpmtss/tpm_tss.h b/src/libtpmtss/tpm_tss.h index bcb7ab949f..5211c52295 100644 --- a/src/libtpmtss/tpm_tss.h +++ b/src/libtpmtss/tpm_tss.h @@ -155,6 +155,19 @@ struct tpm_tss_t { bool (*get_data)(tpm_tss_t *this, uint32_t hierarchy, uint32_t handle, chunk_t pin, chunk_t *data); + /** + * Permanently load a private key into TPM NV storage (TPM 2.0 only) + * + * @param handle object handle to be assigned to TPM key + * @param hierarchy hierarchy the TPM key object is attached to + * @param pin PIN code or empty chunk + * @param type private key type + * @param encoding private key encoding + * @return TRUE if load succeeded + */ + bool (*load_key)(tpm_tss_t *this, uint32_t hierarchy, uint32_t handle, + chunk_t pin, key_type_t type, chunk_t encoding); + /** * Destroy a tpm_tss_t. */ diff --git a/src/libtpmtss/tpm_tss_trousers.c b/src/libtpmtss/tpm_tss_trousers.c index 6ed57af9d0..2a62eee99a 100644 --- a/src/libtpmtss/tpm_tss_trousers.c +++ b/src/libtpmtss/tpm_tss_trousers.c @@ -602,6 +602,13 @@ METHOD(tpm_tss_t, get_data, bool, return FALSE; } +METHOD(tpm_tss_t, load_key, bool, + private_tpm_tss_trousers_t *this, uint32_t hierarchy, uint32_t handle, + chunk_t pin, key_type_t type, chunk_t encoding) +{ + return FALSE; +} + METHOD(tpm_tss_t, destroy, void, private_tpm_tss_trousers_t *this) { @@ -647,6 +654,7 @@ tpm_tss_t *tpm_tss_trousers_create() .sign = _sign, .get_random = _get_random, .get_data = _get_data, + .load_key = _load_key, .destroy = _destroy, }, .load_aik = _load_aik, diff --git a/src/libtpmtss/tpm_tss_tss2.c b/src/libtpmtss/tpm_tss_tss2.c index 90a16c103e..ece3f4a034 100644 --- a/src/libtpmtss/tpm_tss_tss2.c +++ b/src/libtpmtss/tpm_tss_tss2.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -1019,7 +1020,7 @@ METHOD(tpm_tss_t, sign, bool, METHOD(tpm_tss_t, get_random, bool, private_tpm_tss_tss2_t *this, size_t bytes, uint8_t *buffer) { - size_t len, random_len= sizeof(TPM2B_DIGEST)-2; + size_t len, random_len = sizeof(TPM2B_DIGEST)-2; TPM2B_DIGEST random = { { random_len, } }; uint8_t *pos = buffer; uint32_t rval; @@ -1114,6 +1115,183 @@ METHOD(tpm_tss_t, get_data, bool, return TRUE; } +/** + * ASN.1 definition of a PKCS#1 RSA private key + */ +static const asn1Object_t privkeyObjects[] = { + { 0, "RSAPrivateKey", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */ + { 1, "modulus", ASN1_INTEGER, ASN1_BODY }, /* 2 */ + { 1, "publicExponent", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 1, "privateExponent", ASN1_INTEGER, ASN1_BODY }, /* 4 */ + { 1, "prime1", ASN1_INTEGER, ASN1_BODY }, /* 5 */ + { 1, "prime2", ASN1_INTEGER, ASN1_BODY }, /* 6 */ + { 1, "exponent1", ASN1_INTEGER, ASN1_BODY }, /* 7 */ + { 1, "exponent2", ASN1_INTEGER, ASN1_BODY }, /* 8 */ + { 1, "coefficient", ASN1_INTEGER, ASN1_BODY }, /* 9 */ + { 1, "otherPrimeInfos", ASN1_SEQUENCE, ASN1_OPT | + ASN1_LOOP }, /* 10 */ + { 2, "otherPrimeInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 11 */ + { 3, "prime", ASN1_INTEGER, ASN1_BODY }, /* 12 */ + { 3, "exponent", ASN1_INTEGER, ASN1_BODY }, /* 13 */ + { 3, "coefficient", ASN1_INTEGER, ASN1_BODY }, /* 14 */ + { 1, "end opt or loop", ASN1_EOC, ASN1_END }, /* 15 */ + { 0, "exit", ASN1_EOC, ASN1_EXIT } +}; + +#define PRIV_KEY_VERSION 1 +#define PRIV_KEY_MODULUS 2 +#define PRIV_KEY_PUB_EXP 3 +#define PRIV_KEY_PRIME1 5 + +/** + * Build a TPM 2.0 RSA key from an ASN.1 encoded private key blob. + */ +static bool build_rsa_key(chunk_t blob, TPMT_SENSITIVE *priv, TPMT_PUBLIC *pub) +{ + chunk_t n, e, p; + asn1_parser_t *parser; + chunk_t object; + int objectID ; + bool success = FALSE; + + TPM2B_PRIVATE_KEY_RSA *priv_rsa = &priv->sensitive.rsa; + TPM2B_PUBLIC_KEY_RSA *pub_rsa = &pub->unique.rsa; + + priv->sensitiveType = TPM_ALG_RSA; + pub->type = TPM_ALG_RSA; + pub->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL; + pub->parameters.rsaDetail.scheme.scheme = TPM_ALG_RSASSA; + pub->parameters.rsaDetail.scheme.details.anySig.hashAlg = TPM_ALG_SHA256; + + parser = asn1_parser_create(privkeyObjects, blob); + parser->set_flags(parser, FALSE, TRUE); + + while (parser->iterate(parser, &objectID, &object)) + { + switch (objectID) + { + case PRIV_KEY_VERSION: + if (object.len > 0 && *object.ptr != 0) + { + goto end; + } + break; + case PRIV_KEY_MODULUS: + n = object; + if (n.len > 0 && *n.ptr == 0x00) + { + n = chunk_skip(n, 1); + } + if (n.len > MAX_RSA_KEY_BYTES) + { + goto end; + } + memcpy(pub_rsa->t.buffer, n.ptr, n.len); + pub_rsa->t.size = n.len; + pub->parameters.rsaDetail.keyBits = 8 * n.len; + break; + case PRIV_KEY_PUB_EXP: + e = object; + /* we only accept the standard public exponent 2'16+1 */ + if (chunk_equals(e, chunk_from_str("\x01\x00\x01"))) + { + goto end; + } + break; + case PRIV_KEY_PRIME1: + p = object; + if (p.len > 0 && *p.ptr == 0x00) + { + p = chunk_skip(p, 1); + } + if (p.len > MAX_RSA_KEY_BYTES / 2) + { + goto end; + } + memcpy(priv_rsa->t.buffer, p.ptr, p.len); + priv_rsa->t.size = p.len; + break; + } + } + success = parser->success(parser); + +end: + parser->destroy(parser); + + return success; +} + +/** + * Build a TPM 2.0 ECC key from an ASN.1 encoded private key blob. + */ +static bool build_ecc_key(chunk_t blob, TPMT_SENSITIVE *priv, TPMT_PUBLIC *pub) +{ + priv->sensitiveType = TPM_ALG_RSA; + pub->type = TPM_ALG_RSA; + + return TRUE; +} + +METHOD(tpm_tss_t, load_key, bool, + private_tpm_tss_tss2_t *this, uint32_t hierarchy, uint32_t handle, + chunk_t pin, key_type_t type, chunk_t encoding) +{ + bool success = FALSE; + uint32_t obj_handle, rval; + + TPM2B_SENSITIVE sensitive = { { sizeof(TPM2B_SENSITIVE)-2, } }; + TPM2B_PUBLIC public = { { sizeof(TPM2B_PUBLIC)-2, } }; + TPMT_SENSITIVE *priv = &sensitive.t.sensitiveArea; + TPMT_PUBLIC *pub = &public.t.publicArea; + + chunk_t priv_chunk = { (uint8_t*)priv, (size_t)sensitive.t.size }; + chunk_t pub_chunk = { (uint8_t*)pub, (size_t)public.t.size}; + + TPM2B_NAME name = { { sizeof(TPM2B_NAME)-2, } }; + + TPMS_AUTH_RESPONSE session_data; + TSS2_SYS_RSP_AUTHS sessions_data; + TPMS_AUTH_RESPONSE *session_data_array[1]; + + session_data_array[0] = &session_data; + sessions_data.rspAuths = &session_data_array[0]; + sessions_data.rspAuthsCount = 1; + + pub->nameAlg = TPM_ALG_SHA256; /* TODO make nameAlg configurable */ + pub->objectAttributes.val = 0x00040060; + + switch (type) + { + case KEY_RSA: + success = build_rsa_key(encoding, priv, pub); + break; + case KEY_ECDSA: + success = build_ecc_key(encoding, priv, pub); + break; + default: + return FALSE; + } + + if (!success) + { + return FALSE; + } + DBG1(DBG_PTS, "TPM2B_SENSITIVE: %B", &priv_chunk); + DBG1(DBG_PTS, "TPM2B_PUBLIC: %B", &pub_chunk); + + rval = Tss2_Sys_LoadExternal(this->sys_context, 0, &sensitive, &public, + hierarchy, &obj_handle, &name, &sessions_data); + if (rval != TPM_RC_SUCCESS) + { + DBG1(DBG_PTS,"%s Tss2_Sys_LoadExternal failed: 0x%06x", LABEL, rval); + return FALSE; + } + DBG1(DBG_PTS, "handle = 0x%08x", obj_handle); + + return success; +} + METHOD(tpm_tss_t, destroy, void, private_tpm_tss_tss2_t *this) { @@ -1141,6 +1319,7 @@ tpm_tss_t *tpm_tss_tss2_create() .sign = _sign, .get_random = _get_random, .get_data = _get_data, + .load_key = _load_key, .destroy = _destroy, }, ); diff --git a/src/tpm_loadpkcs12/.gitignore b/src/tpm_loadpkcs12/.gitignore new file mode 100644 index 0000000000..a834a348bc --- /dev/null +++ b/src/tpm_loadpkcs12/.gitignore @@ -0,0 +1 @@ +tpm_loadpkcs12 diff --git a/src/tpm_loadpkcs12/Makefile.am b/src/tpm_loadpkcs12/Makefile.am new file mode 100644 index 0000000000..6aee030d50 --- /dev/null +++ b/src/tpm_loadpkcs12/Makefile.am @@ -0,0 +1,14 @@ +bin_PROGRAMS = tpm_loadpkcs12 + +tpm_loadpkcs12_SOURCES = tpm_loadpkcs12.c + +tpm_loadpkcs12_LDADD = \ + $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libtpmtss/libtpmtss.la + +tpm_loadpkcs12.o : $(top_builddir)/config.status + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libtpmtss \ + -DIPSEC_CONFDIR=\"${sysconfdir}\" diff --git a/src/tpm_loadpkcs12/tpm_loadpkcs12.c b/src/tpm_loadpkcs12/tpm_loadpkcs12.c new file mode 100644 index 0000000000..1f0ad3d80a --- /dev/null +++ b/src/tpm_loadpkcs12/tpm_loadpkcs12.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2017 Andreas Steffen + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* logging */ +static bool log_to_stderr = TRUE; +static bool log_to_syslog = TRUE; +static level_t default_loglevel = 1; + +/** + * Global TPM 2.0 instance + */ +static tpm_tss_t *tpm; + +/** + * Global PKCS#12 object + */ +static pkcs12_t *p12; + +/** + * Callback credential set pki uses + */ +static callback_cred_t *cb_set; + +/** + * Credential set to cache entered secrets + */ +static mem_cred_t *cb_creds; + +static shared_key_type_t prompted; + +/** + * Callback function to receive credentials + */ +static shared_key_t* cb(void *data, shared_key_type_t type, + identification_t *me, identification_t *other, + id_match_t *match_me, id_match_t *match_other) +{ + char buf[64], *label, *secret = NULL; + shared_key_t *shared; + + if (prompted == type) + { + return NULL; + } + switch (type) + { + case SHARED_PIN: + label = "Smartcard PIN"; + break; + case SHARED_PRIVATE_KEY_PASS: + label = "Private key passphrase"; + break; + default: + return NULL; + } + snprintf(buf, sizeof(buf), "%s: ", label); +#ifdef HAVE_GETPASS + secret = getpass(buf); +#endif + if (secret && strlen(secret)) + { + prompted = type; + if (match_me) + { + *match_me = ID_MATCH_PERFECT; + } + if (match_other) + { + *match_other = ID_MATCH_NONE; + } + shared = shared_key_create(type, chunk_clone(chunk_from_str(secret))); + /* cache password in case it is required more than once */ + cb_creds->add_shared(cb_creds, shared, NULL); + return shared->get_ref(shared); + } + return NULL; +} + +/** + * Register PIN/Passphrase callback function + */ +static void add_callback() +{ + cb_set = callback_cred_create_shared(cb, NULL); + lib->credmgr->add_set(lib->credmgr, &cb_set->set); + cb_creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &cb_creds->set); +} + +/** + * Unregister PIN/Passphrase callback function + */ +static void remove_callback() +{ + lib->credmgr->remove_set(lib->credmgr, &cb_creds->set); + cb_creds->destroy(cb_creds); + lib->credmgr->remove_set(lib->credmgr, &cb_set->set); + cb_set->destroy(cb_set); +} + +/** + * logging function for tpm_loadpkcs12 + */ +static void tpm_loadpkcs12_dbg(debug_t group, level_t level, char *fmt, ...) +{ + char buffer[8192]; + char *current = buffer, *next; + va_list args; + + if (level <= default_loglevel) + { + if (log_to_stderr) + { + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + } + if (log_to_syslog) + { + /* write in memory buffer first */ + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + /* do a syslog with every line */ + while (current) + { + next = strchr(current, '\n'); + if (next) + { + *(next++) = '\0'; + } + syslog(LOG_INFO, "%s\n", current); + current = next; + } + } + } +} + +/** + * Initialize logging to stderr/syslog + */ +static void init_log(const char *program) +{ + dbg = tpm_loadpkcs12_dbg; + + if (log_to_stderr) + { + setbuf(stderr, NULL); + } + if (log_to_syslog) + { + openlog(program, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_AUTHPRIV); + } +} + +/** + * @brief exit tpm_loadpkcs12 + * + * @param status 0 = OK, -1 = general discomfort + */ +static void exit_tpm_loadpkcs12(err_t message, ...) +{ + int status = 0; + + DESTROY_IF(tpm); + if (p12) + { + container_t *container = &p12->container; + + container->destroy(container); + } + + /* print any error message to stderr */ + if (message != NULL && *message != '\0') + { + va_list args; + char m[8192]; + + va_start(args, message); + vsnprintf(m, sizeof(m), message, args); + va_end(args); + + fprintf(stderr, "tpm_loadpkcs12 error: %s\n", m); + status = -1; + } + exit(status); +} + +/** + * @brief prints the usage of the program to the stderr output + * + * If message is set, program is exited with 1 (error) + * @param message message in case of an error + */ +static void usage(const char *message) +{ + fprintf(stderr, + "Usage: tpm_loadpkcs12 --in [--debug ] [--quiet]\n" + " tpm_loadpkcs12 --help\n" + "\n" + "Options:\n" + " --in (-i) binary input file with digest to be extended\n" + " --help (-h) show usage and exit\n" + "\n" + "Debugging output:\n" + " --debug (-l) changes the log level (-1..4, default: 1)\n" + " --quiet (-q) do not write log output to stderr\n" + ); + exit_tpm_loadpkcs12(message); +} + +/** + * @brief main of tpm_loadpkcs12 which loads a PKCS#12 container and stores + * the key and certificates in a TPM 2.0 + * + * @param argc number of arguments + * @param argv pointer to the argument values + */ +int main(int argc, char *argv[]) +{ + uint32_t hierarchy = 0x40000007; /* TPM_RH_NULL */ + uint32_t handle = 0; + char *infile = NULL; + chunk_t id, encoding, pin = chunk_empty; + enumerator_t *enumerator; + public_key_t *pubkey; + private_key_t *key; + key_type_t type; + certificate_t *cert; + x509_flag_t flags; + x509_t *x509; + bool found, success; + + atexit(library_deinit); + if (!library_init(NULL, "tpm_loadpkcs12")) + { + exit(SS_RC_LIBSTRONGSWAN_INTEGRITY); + } + if (lib->integrity && + !lib->integrity->check_file(lib->integrity, "tpm_loadpkcs12", argv[0])) + { + fprintf(stderr, "integrity check of tpm_loadpkcs12 failed\n"); + exit(SS_RC_DAEMON_INTEGRITY); + } + + for (;;) + { + static const struct option long_opts[] = { + /* name, has_arg, flag, val */ + { "help", no_argument, NULL, 'h' }, + { "in", required_argument, NULL, 'i' }, + { "pin", required_argument, NULL, 'p' }, + { "handle", required_argument, NULL, 'H' }, + { "quiet", no_argument, NULL, 'q' }, + { "debug", required_argument, NULL, 'l' }, + { 0,0,0,0 } + }; + + /* parse next option */ + int c = getopt_long(argc, argv, "hi:ql:", long_opts, NULL); + + switch (c) + { + case EOF: /* end of flags */ + break; + + case 'h': /* --help */ + usage(NULL); + + case 'i': /* --in */ + infile = optarg; + continue; + + case 'H': + continue; + + case 'p': + pin = chunk_from_str(optarg); + continue; + + case 'q': /* --quiet */ + log_to_stderr = FALSE; + continue; + + case 'l': /* --debug */ + default_loglevel = atoi(optarg); + continue; + + default: + usage("unknown option"); + } + /* break from loop */ + break; + } + + init_log("tpm_loadpkcs12"); + + if (!lib->plugins->load(lib->plugins, + lib->settings->get_str(lib->settings, "tpm_loadpkcs12.load", + "random pem openssl tpm"))) + { + exit_tpm_loadpkcs12("plugin loading failed"); + } + + /* try to find a TPM */ + tpm = tpm_tss_probe(TPM_VERSION_2_0); + if (!tpm) + { + exit_tpm_loadpkcs12("no TPM 2.0 found"); + } + + if (!infile) + { + exit_tpm_loadpkcs12("mandatory --in argument missing"); + } + + /* add callback prompting for PKCS#12 password */ + add_callback(); + atexit(remove_callback); + + p12 = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS12, + BUILD_FROM_FILE, infile, BUILD_END); + + if (!p12) + { + exit_tpm_loadpkcs12("reading PKCS#12 file failed"); + } + printf("loaded PKCS#12 file from '%s'\n", infile); + + enumerator = p12->create_cert_enumerator(p12); + while (enumerator->enumerate(enumerator, &cert)) + { + x509 = (x509_t*)cert; + flags = x509->get_flags(x509); + printf("%scertificate:\n", (flags & X509_CA) ? "ca " : ""); + + pubkey = cert->get_public_key(cert); + if (pubkey->get_fingerprint(pubkey, KEYID_PUBKEY_SHA1, &id)) + { + printf(" subjectKeyIdentifier: %#B\n", &id); + } + if (pubkey->get_fingerprint(pubkey, KEYID_PUBKEY_INFO_SHA1, &id)) + { + printf(" subjectPublicKeyInfo hash: %#B\n", &id); + } + pubkey->destroy(pubkey); + } + enumerator->destroy(enumerator); + + enumerator = p12->create_key_enumerator(p12); + found = enumerator->enumerate(enumerator, &key); + enumerator->destroy(enumerator); + + if (!found) + { + exit_tpm_loadpkcs12("no private key found in PKCS#12 container"); + } + type = key->get_type(key); + + /* print some private key information */ + printf("%N private key:\n", key_type_names, type); + if (key->get_fingerprint(key, KEYID_PUBKEY_SHA1, &id)) + { + printf(" subjectKeyIdentifier: %#B\n", &id); + } + if (key->get_fingerprint(key, KEYID_PUBKEY_INFO_SHA1, &id)) + { + printf(" subjectPublicKeyInfo hash: %#B\n", &id); + } + if (!key->get_encoding(key, PRIVKEY_ASN1_DER, &encoding)) + { + exit_tpm_loadpkcs12("private key encoding failed"); + } + printf("%B\n", &encoding); + + /* load private key into TPM */ + success = tpm->load_key(tpm, hierarchy, handle, pin, type, encoding); + + /* cleanup */ + chunk_clear(&encoding); + exit_tpm_loadpkcs12(success ? NULL : "loading into TPM 2.0 failed"); + + return -1; /* should never be reached */ +} -- 2.47.2