diff options
author | James Bottomley <James.Bottomley@HansenPartnership.com> | 2022-12-05 12:22:36 -0500 |
---|---|---|
committer | James Bottomley <James.Bottomley@HansenPartnership.com> | 2023-01-11 10:00:21 -0500 |
commit | 1efc7828438605da447147ad9672878edcd82746 (patch) | |
tree | 4263d2c8a5459d827e711d833eda1e26130c8f87 | |
parent | b85fe3b3baeef05682091f913ef1ec14f3e6f155 (diff) | |
download | openssl_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-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 11 | ||||
-rw-r--r-- | signed_tpm2_policy.1.in | 44 | ||||
-rw-r--r-- | signed_tpm2_policy.c | 244 | ||||
-rw-r--r-- | tpm2-common.c | 189 | ||||
-rw-r--r-- | tpm2-common.h | 2 |
6 files changed, 488 insertions, 3 deletions
@@ -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 |