aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Bottomley <James.Bottomley@HansenPartnership.com>2022-12-05 12:22:36 -0500
committerJames Bottomley <James.Bottomley@HansenPartnership.com>2023-01-11 10:00:21 -0500
commit1efc7828438605da447147ad9672878edcd82746 (patch)
tree4263d2c8a5459d827e711d833eda1e26130c8f87
parentb85fe3b3baeef05682091f913ef1ec14f3e6f155 (diff)
downloadopenssl_tpm2_engine-1efc7828438605da447147ad9672878edcd82746.tar.gz
signed_tpm2_policy: add new command for manipulating signed policies
This adds the new command signed_tpm2_policy which can add, show and remove policies from a key. The key must first have been created with a --signed-policy <key> option, and you must possess the private part of <key> to add a new policy. Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am11
-rw-r--r--signed_tpm2_policy.1.in44
-rw-r--r--signed_tpm2_policy.c244
-rw-r--r--tpm2-common.c189
-rw-r--r--tpm2-common.h2
6 files changed, 488 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 48f9458..49ade8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ create_tpm2_key
load_tpm2_key
seal_tpm2_data
unseal_tpm2_data
+signed_tpm2_policy
test-driver
tests/*.log
tests/*.trs
diff --git a/Makefile.am b/Makefile.am
index d5a514d..58b6cb0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,9 +2,9 @@ EXTRA_DIST = README openssl.cnf.sample
if NATIVE_BUILD
EXTRA_DIST += create_tpm2_key.1 load_tpm2_key.1 seal_tpm2_data.1 \
- unseal_tpm2_data.1
+ unseal_tpm2_data.1 signed_tpm2_policy.1
man1_MANS = create_tpm2_key.1 load_tpm2_key.1 seal_tpm2_data.1 \
- unseal_tpm2_data.1
+ unseal_tpm2_data.1 signed_tpm2_policy.1
CLEANFILES = $(man1_MANS)
endif
@@ -14,7 +14,8 @@ CFLAGS+= -DOPENSSL_API_COMPAT=0x10100000L
endif
openssl_engine_LTLIBRARIES=libtpm2.la
-bin_PROGRAMS=create_tpm2_key load_tpm2_key seal_tpm2_data unseal_tpm2_data
+bin_PROGRAMS=create_tpm2_key load_tpm2_key seal_tpm2_data unseal_tpm2_data \
+ signed_tpm2_policy
openssl_enginedir=@enginesdir@
libtpm2_la_LDFLAGS= -no-undefined -avoid-version
@@ -38,6 +39,10 @@ unseal_tpm2_data_SOURCES=unseal_tpm2_data.c tpm2-common.c
unseal_tpm2_data_LDADD=${DEPS_LIBS}
unseal_tpm2_data_CFLAGS=${DEPS_CFLAGS} -Werror
+signed_tpm2_policy_SOURCES=signed_tpm2_policy.c tpm2-common.c
+signed_tpm2_policy_LDADD=${DEPS_LIBS}
+signed_tpm2_policy_CFLAGS=${DEPS_CFLAGS} -g -Werror
+
$(builddir)/%.1: $(srcdir)/%.1.in $(top_builddir)/%
$(HELP2MAN) --no-info -i $< -o $@ $(top_builddir)/$*
diff --git a/signed_tpm2_policy.1.in b/signed_tpm2_policy.1.in
new file mode 100644
index 0000000..a299d94
--- /dev/null
+++ b/signed_tpm2_policy.1.in
@@ -0,0 +1,44 @@
+[name]
+signed_tpm2_policy - add, remove and list signed policies
+
+[description]
+
+<cmd> is one of add, rm or ls and [arg] is the private key for the add
+command or the policy number for the rm command.
+
+
+
+[PCR Values]
+
+The PCR values are specified as
+
+ <bank>:<list>
+
+Where <bank> is any supported PCR hash bank and list specifies the
+PCRs to lock the key to as both comma separated individual values as
+well as comma separated ranges. So
+
+ sha256:1,3 means PCRs 1 and 3 in the sha256 bank
+
+ sha512:1,3-5 means PCRs 1,3,4 and 5 in the sha512 bank
+
+[examples]
+
+list all signed policies:
+
+ signed_tpm2_policy ls key.tpm
+
+The output is a numbered list of policies (with optional names)
+
+remove the first policy
+
+ signed_tpm2_policy rm key.tpm 1
+
+add a new policy with name 'thispolicy' locked to pcr16 using the
+private policy.key:
+
+ signed_tpm2_policy add --name thispolicy --pcr-lock sha256:16 key.tmp policy.key
+
+[see also]
+
+create_tpm2_key(1)
diff --git a/signed_tpm2_policy.c b/signed_tpm2_policy.c
new file mode 100644
index 0000000..10c8157
--- /dev/null
+++ b/signed_tpm2_policy.c
@@ -0,0 +1,244 @@
+/*
+ *
+ * Copyright (C) 2022 James Bottomley <James.Bottomley@HansenPartnership.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+
+#include "tpm2-tss.h"
+#include "tpm2-asn.h"
+#include "tpm2-common.h"
+
+#define OPT_SIGNED_POLICY 0x1fd
+
+static struct option long_options[] = {
+ {"auth", 0, 0, 'a'},
+ {"help", 0, 0, 'h'},
+ {"pcr-lock", 1, 0, 'x'},
+ {"signed-policy", 1, 0, OPT_SIGNED_POLICY },
+ {"version", 0, 0, 'v'},
+ {"key-policy", 1, 0, 'c'},
+ {"engine", 1, 0, 'e'},
+ {"policy-name", 1, 0, 'n'},
+ {0, 0, 0, 0}
+};
+
+void
+usage(char *argv0)
+{
+ fprintf(stdout, "Usage: %s <cmd> [options] <tpmkey> [<arg>]\n\n"
+ "Options:\n"
+ "\t-a, --auth require a password for the key [NO]\n"
+ "\t-h, --help print this help message\n"
+ "\t-c, --key-policy <pubkey> Specify a policy for the TPM key\n"
+ "\t-i, --import <pubkey> Create an importable key with the outer\n"
+ " wrapper encrypted to <pubkey>\n"
+ "\t-x, --pcr-lock <pcrs> Lock the created key to the specified PCRs\n"
+ " By current value. See PCR VALUES for\n"
+ " details about formatting\n"
+ "\n"
+ "\t--signed-policy <key> Add a signed policy directive that allows\n"
+ "\t policies signed by the specified public <key>\n"
+ "\t to authorize use of the key\n"
+ "\t-n, --policy-name <name> Optional name to annotate the policy with\n"
+ "\n"
+ "Report bugs to " PACKAGE_BUGREPORT "\n",
+ argv0);
+ exit(-1);
+}
+
+static TPM_ALG_ID
+tpm2_get_name_alg(const char *tpmkey)
+{
+ BIO *bf;
+ TSSPRIVKEY *tpk;
+ BYTE *buffer;
+ INT32 size;
+ TPM2B_PUBLIC pub;
+
+ bf = BIO_new_file(tpmkey, "r");
+ if (!bf) {
+ fprintf(stderr, "File %s does not exist or cannot be read\n",
+ tpmkey);
+ exit(1);
+ }
+
+ tpk = PEM_read_bio_TSSPRIVKEY(bf, NULL, NULL, NULL);
+ if (!tpk) {
+ BIO_seek(bf, 0);
+ ERR_clear_error();
+ tpk = ASN1_item_d2i_bio(ASN1_ITEM_rptr(TSSPRIVKEY), bf, NULL);
+ }
+ BIO_free(bf);
+ if (!tpk) {
+ fprintf(stderr, "Cannot parse file as TPM key\n");
+ exit(1);
+ }
+ buffer = tpk->pubkey->data;
+ size = tpk->pubkey->length;
+ TPM2B_PUBLIC_Unmarshal(&pub, &buffer, &size, FALSE);
+ return pub.publicArea.nameAlg;
+}
+
+int main(int argc, char **argv)
+{
+ char *filename, *policyFilename = NULL, *policy_name = NULL,
+ *policy_signing_key;
+ int option_index, c, auth = 0;
+ const char *reason = NULL;
+ TPM_RC rc;
+ char *engine = NULL;
+ char *signed_policy = NULL;
+ TSSAUTHPOLICY *ap = NULL;
+ TPMT_HA digest;
+ int size;
+ TPML_PCR_SELECTION pcr_lock = { 0 };
+
+ OpenSSL_add_all_digests();
+ /* may be needed to decrypt the key */
+ OpenSSL_add_all_ciphers();
+
+ while (1) {
+ option_index = 0;
+ c = getopt_long(argc, argv, "ahvc:x:e:n:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ auth = 1;
+ break;
+ case 'h':
+ usage(argv[0]);
+ break;
+ case 'v':
+ fprintf(stdout, "%s " VERSION "\n"
+ "Copyright 2017 by James Bottomley\n"
+ "License LGPL-2.1-only\n"
+ "Written by James Bottomley <James.Bottomley@HansenPartnership.com>\n",
+ argv[0]);
+ exit(0);
+ case 'c':
+ policyFilename = optarg;
+ break;
+ case 'x':
+ tpm2_get_pcr_lock(&pcr_lock, optarg);
+ break;
+ case 'e':
+ engine = optarg;
+ break;
+ case 'n':
+ policy_name = optarg;
+ break;
+ case OPT_SIGNED_POLICY:
+ signed_policy = optarg;
+ break;
+ default:
+ printf("Unknown option '%c'\n", c);
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ if (optind >= argc - 1) {
+ printf("Too few arguments: Expected file name as last argument\n");
+ usage(argv[0]);
+ }
+
+ filename = argv[argc - 2];
+ policy_signing_key = argv[argc - 1];
+
+ if (optind < argc - 2) {
+ printf("Unexpected additional arguments\n");
+ usage(argv[0]);
+ }
+
+ name_alg = tpm2_get_name_alg(filename);
+ digest.hashAlg = name_alg;
+ size = TSS_GetDigestSize(digest.hashAlg);
+ memset((uint8_t *)&digest.digest, 0, size);
+
+ ap = TSSAUTHPOLICY_new();
+ if (policy_name) {
+ ap->name = ASN1_UTF8STRING_new();
+ ASN1_STRING_set(ap->name, policy_name, strlen(policy_name));
+ }
+ ap->policy = sk_TSSOPTPOLICY_new_null();
+ if (!ap->policy) {
+ rc = NOT_TPM_ERROR;
+ reason="sk_TSSOPTPOLICY_new_null allocation";
+ goto out_err;
+ }
+
+ if (policyFilename) {
+ rc = tpm2_parse_policy_file(policyFilename, ap->policy,
+ (char *)(unsigned long)auth,
+ &digest);
+ reason = "parse_policy_file";
+ if (rc)
+ goto out_free_policy;
+ } else if (signed_policy) {
+ rc = tpm2_add_signed_policy(ap->policy, signed_policy, &digest);
+ reason = "add_signed_policy";
+ if (rc)
+ goto out_free_policy;
+ }
+
+ if (auth)
+ tpm2_add_auth_policy(ap->policy, &digest);
+
+ if (pcr_lock.count != 0) {
+ TSS_CONTEXT *tssContext = NULL;
+ const char *dir;
+
+ dir = tpm2_set_unique_tssdir();
+ rc = tpm2_create(&tssContext, dir);
+ if (rc) {
+ reason = "TSS_Create";
+ goto out_free_policy;
+ }
+ rc = tpm2_pcr_lock_policy(tssContext, &pcr_lock,
+ ap->policy, &digest);
+ TSS_Delete(tssContext);
+ tpm2_rm_tssdir(dir);
+ if (rc) {
+ reason = "create pcr policy";
+ goto out_free_policy;
+ }
+ }
+
+ rc = tpm2_new_signed_policy(filename, policy_signing_key, engine,
+ ap, &digest);
+ if (rc == 0)
+ exit(0);
+
+ out_free_policy:
+ if (ap->name)
+ ASN1_UTF8STRING_free(ap->name);
+ tpm2_free_policy(ap->policy);
+ out_err:
+ if (rc == NOT_TPM_ERROR)
+ fprintf(stderr, "%s failed\n", reason);
+ else
+ tpm2_error(rc, reason);
+
+ exit(1);
+}
diff --git a/tpm2-common.c b/tpm2-common.c
index 633acb7..e345c6a 100644
--- a/tpm2-common.c
+++ b/tpm2-common.c
@@ -1569,6 +1569,66 @@ static const EVP_MD *tpm2_md(TPM_ALG_ID alg)
}
}
+TPM_RC tpm2_sign_digest(EVP_PKEY *pkey, TPMT_HA *digest, TPMT_SIGNATURE *sig)
+{
+ EVP_PKEY_CTX *ctx;
+ const int pkey_id = EVP_PKEY_id(pkey);
+ size_t size;
+
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ return TPM_RC_MEMORY;
+
+ EVP_PKEY_sign_init(ctx);
+ EVP_PKEY_CTX_set_signature_md(ctx, tpm2_md(digest->hashAlg));
+ if (pkey_id == EVP_PKEY_RSA) {
+ sig->sigAlg = TPM_ALG_RSASSA;
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
+ sig->signature.rsassa.hash = digest->hashAlg;
+ size = MAX_RSA_KEY_BYTES;
+ EVP_PKEY_sign(ctx, VAL_2B(sig->signature.rsassa.sig, buffer),
+ &size,
+ (uint8_t *)&digest->digest,
+ TSS_GetDigestSize(digest->hashAlg));
+ VAL_2B(sig->signature.rsassa.sig, size) = size;
+ } else if (pkey_id == EVP_PKEY_EC) {
+ unsigned char sigbuf[1024];
+ const unsigned char *p;
+ ECDSA_SIG *es = ECDSA_SIG_new();
+ const BIGNUM *r, *s;
+
+ sig->sigAlg = TPM_ALG_ECDSA;
+ sig->signature.ecdsa.hash = digest->hashAlg;
+ size = sizeof(sigbuf);
+ EVP_PKEY_sign(ctx, sigbuf, &size, (uint8_t *)&digest->digest,
+ TSS_GetDigestSize(digest->hashAlg));
+ /* this is all openssl crap: it returns der form unlike RSA
+ * which returns raw form */
+ p = sigbuf;
+ d2i_ECDSA_SIG(&es, &p, size);
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+ r = es->r;
+ s = es->s;
+#else
+ r = ECDSA_SIG_get0_r(es);
+ s = ECDSA_SIG_get0_s(es);
+#endif
+ VAL_2B(sig->signature.ecdsa.signatureR, size) =
+ BN_bn2bin(r, VAL_2B(sig->signature.ecdsa.signatureR,
+ buffer));
+ VAL_2B(sig->signature.ecdsa.signatureS, size) =
+ BN_bn2bin(s, VAL_2B(sig->signature.ecdsa.signatureS,
+ buffer));
+ ECDSA_SIG_free(es);
+ } else {
+ fprintf(stderr, "pkey has unknown signing algorithm %d\n", pkey_id);
+ exit(1);
+ }
+ EVP_PKEY_CTX_free(ctx);
+
+ return TPM_RC_SUCCESS;
+}
+
int tpm2_load_engine_file(const char *filename, struct app_data **app_data,
EVP_PKEY **ppkey, UI_METHOD *ui, void *cb_data,
const char *srk_auth, int get_key_auth,
@@ -2151,6 +2211,135 @@ out:
return rc;
}
+TPM_RC tpm2_new_signed_policy(char *tpmkey, char *policykey, char *engine,
+ TSSAUTHPOLICY *ap, TPMT_HA *digest)
+{
+ BIO *bf;
+ TSSPRIVKEY *tpk;
+ EVP_PKEY *pkey;
+ TSSOPTPOLICY *policy;
+ BYTE *buffer;
+ INT32 size;
+ TPM2B_PUBLIC pub;
+ DIGEST_2B nonce;
+ TPMT_HA hash;
+ TPM_RC rc;
+ TPMT_SIGNATURE sig;
+ NAME_2B name;
+ const TPM_CC cc = TPM_CC_PolicyAuthorize;
+ BYTE buf[1024];
+ UINT16 written = 0;
+
+ bf = BIO_new_file(tpmkey, "r");
+ if (!bf) {
+ fprintf(stderr, "File %s does not exist or cannot be read\n",
+ tpmkey);
+ return 0;
+ }
+
+ tpk = PEM_read_bio_TSSPRIVKEY(bf, NULL, NULL, NULL);
+ if (!tpk) {
+ BIO_seek(bf, 0);
+ ERR_clear_error();
+ tpk = ASN1_item_d2i_bio(ASN1_ITEM_rptr(TSSPRIVKEY), bf, NULL);
+ }
+ BIO_free(bf);
+ if (!tpk) {
+ fprintf(stderr, "Cannot parse file as TPM key\n");
+ return 0;
+ }
+ if (!tpk->policy || sk_TSSOPTPOLICY_num(tpk->policy) <= 0) {
+ fprintf(stderr, "TPM Key has no policy\n");
+ goto err_free_tpmkey;
+ }
+
+ policy = sk_TSSOPTPOLICY_value(tpk->policy, 0);
+ if (ASN1_INTEGER_get(policy->CommandCode) != TPM_CC_PolicyAuthorize) {
+ fprintf(stderr, "TPM Key has no signed policy\n");
+ goto err_free_tpmkey;
+ }
+
+ buffer = policy->CommandPolicy->data;
+ size = policy->CommandPolicy->length;
+ rc = TPM2B_PUBLIC_Unmarshal(&pub, &buffer, &size, FALSE);
+ if (rc == TPM_RC_SUCCESS) {
+ rc = TPM2B_DIGEST_Unmarshal((TPM2B_DIGEST *)&nonce, &buffer, &size);
+ } else {
+ fprintf(stderr, "Unmarshal Failure on PolicyAuthorize public key\n");
+ }
+
+ if (rc != TPM_RC_SUCCESS) {
+ fprintf(stderr, "Unmarshal failure on PolicyAuthorize\n");
+ goto err_free_tpmkey;
+ }
+
+ bf = BIO_new_file(policykey, "r");
+ if (!bf) {
+ fprintf(stderr, "File %s does not exist or cannot be read\n",
+ policykey);
+ goto err_free_tpmkey;
+ }
+
+ pkey = PEM_read_bio_PrivateKey(bf, NULL, NULL, NULL);
+ BIO_free(bf);
+ if (!pkey) {
+ fprintf(stderr, "Could not get policy private key\n");
+ goto err_free_tpmkey;
+ }
+
+ /* the to be signed hash is HASH(approvedPolicy || nonce) */
+ hash.hashAlg = name_alg;
+ TSS_Hash_Generate(&hash,
+ TSS_GetDigestSize(digest->hashAlg), &digest->digest,
+ nonce.size, nonce.buffer,
+ 0, NULL);
+
+ rc = tpm2_sign_digest(pkey, &hash, &sig);
+ EVP_PKEY_free(pkey);
+ if (rc != TPM_RC_SUCCESS) {
+ fprintf(stderr, "Signing failed\n");
+ goto err_free_tpmkey;
+ }
+ tpm2_ObjectPublic_GetName(&name, &pub.publicArea);
+
+ size = sizeof(buf);
+ buffer = buf;
+ TSS_TPM_CC_Marshal(&cc, &written, &buffer, &size);
+ TSS_TPM2B_PUBLIC_Marshal(&pub, &written, &buffer, &size);
+ TSS_TPM2B_DIGEST_Marshal((TPM2B_DIGEST *)&nonce, &written, &buffer, &size);
+ TSS_TPMT_SIGNATURE_Marshal(&sig, &written, &buffer, &size);
+
+ policy = TSSOPTPOLICY_new();
+
+ ASN1_INTEGER_set(policy->CommandCode, cc);
+ ASN1_STRING_set(policy->CommandPolicy, buf + 4, written - 4);
+ sk_TSSOPTPOLICY_push(ap->policy, policy);
+
+ if (!tpk->authPolicy)
+ tpk->authPolicy = sk_TSSAUTHPOLICY_new_null();
+
+ /* insert at the beginning on the assumption we should try
+ * latest policy addition first */
+ sk_TSSAUTHPOLICY_unshift(tpk->authPolicy, ap);
+
+ bf = BIO_new_file(tpmkey, "w");
+ if (bf == NULL) {
+ fprintf(stderr, "Failed to open key file %s for writing\n",
+ tpmkey);
+ goto err_free_tpmkey;
+ }
+ PEM_write_bio_TSSPRIVKEY(bf, tpk);
+ BIO_free(bf);
+
+ TSSPRIVKEY_free(tpk);
+ EVP_PKEY_free(pkey);
+ return 0;
+
+ err_free_tpmkey:
+ TSSPRIVKEY_free(tpk);
+ return 1;
+}
+
void tpm2_free_policy(STACK_OF(TSSOPTPOLICY) *sk)
{
TSSOPTPOLICY *policy;
diff --git a/tpm2-common.h b/tpm2-common.h
index da35155..5abb8fc 100644
--- a/tpm2-common.h
+++ b/tpm2-common.h
@@ -113,4 +113,6 @@ void openssl_print_errors();
TPM_RC tpm2_ObjectPublic_GetName(NAME_2B *name, TPMT_PUBLIC *tpmtPublic);
TPM_RC tpm2_add_signed_policy(STACK_OF(TSSOPTPOLICY) *sk, char *key_file,
TPMT_HA *digest);
+TPM_RC tpm2_new_signed_policy(char *tpmkey, char *policykey, char *engine,
+ TSSAUTHPOLICY *ap, TPMT_HA *digest);
#endif