Topics

[PATCH 3/4] Add importable keys

James Bottomley
 

Importable keys are a new type of key that can be created without
access to the receiving TPM. The design of importable keys is that
you "wrap" the key (encrypt with a derived key) in such a way that it
can only be unwrapped by the TPM local parent key. To do this
wrapping you only need to know the public key of the parent, which can
be shipped in an ordinary openssl public key file.

The key format of importable keys requires an additional encrypted
secret parameter, which basically encodes the wrapping key using ECDH
so it can be decrypted by the parent on import.

Importable keys are designed to be used everywhere the current
loadable keys are used, the only difference being that the TPM engine
converts the importable key to loadable on first load and thereafter
uses it identically to a loadable key.

The new command line option of importable keys is

create_tpm2_key --import <pubparent> --wrap <private key> tmp.key

Where <pubparent> is the parent public key and <private key> is the
key you are wrapping (obviously since you have no access to the TPM,
this option can only be used to wrap existing private keys).

Signed-off-by: James Bottomley <James.Bottomley@...>
---
create_tpm2_key.1.in | 17 +++
create_tpm2_key.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++-----
e_tpm2.c | 108 ++++++++++++++++---
tpm2-asn.h | 3 +
4 files changed, 377 insertions(+), 39 deletions(-)

diff --git a/create_tpm2_key.1.in b/create_tpm2_key.1.in
index 0e52d86..6ec5f25 100644
--- a/create_tpm2_key.1.in
+++ b/create_tpm2_key.1.in
@@ -7,6 +7,13 @@ Can be used to create a TPM loadable representation of a private key.
The key is either internal to the TPM or wrapped from an existing
private key.

+Note that this command can now create two different types of keys:
+importable and loadable (the default type being loadable). The
+difference between the two is that the creation of loadable keys
+requires the presence of the actual TPM the key will be loaded on. An
+importable key can be created without any TPM contact provided you
+have the public key of the parent the new key will be imported to.
+
[files]

Policy File Format
@@ -69,3 +76,13 @@ Create a self-signed cert using the TPM engine:
3. Test using openssl:
$ openssl s_server -cert <certfilename> -www -accept 4433 -keyform engine -engi
ne tpm -key <keyfilename>
+
+Creating an importable key:
+
+1. First get the public key of the storage primary seed (as an elliptic
+ curve key):
+ $ tsscreateprimary -ecc nistp256 -hi o -opem srk.pub
+
+2. Once you have this public key, you can export it anywhere and do the
+ key wrapping
+ $ create_tpm2_key --import srk.pub --wrap my_key.priv my_key.tpm
diff --git a/create_tpm2_key.c b/create_tpm2_key.c
index 899e434..0576c83 100644
--- a/create_tpm2_key.c
+++ b/create_tpm2_key.c
@@ -58,6 +58,7 @@ static struct option long_options[] = {
{"list-curves", 0, 0, 'l'},
{"da", 0, 0, 'd'},
{"key-policy", 1, 0, 'c'},
+ {"import", 1, 0, 'i'},
/*
* The option --deprecated allows us to create old format keys
* for the purposes of testing. It should never be used in
@@ -98,6 +99,8 @@ usage(char *argv0)
"\t Supported curves are bnp256, nisp256, nisp384\n"
"\t-l, --list-curves List all the Elliptic Curves the TPM supports\n"
"\t-c, --key-policy 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"
"\n"
"Report bugs to " PACKAGE_BUGREPORT "\n",
argv0);
@@ -270,6 +273,135 @@ TPM_RC tpm2_innerwrap(TPMT_SENSITIVE *s,
return TPM_RC_SUCCESS;
}

+TPM_RC tpm2_outerwrap(EVP_PKEY *parent,
+ TPMT_SENSITIVE *s,
+ TPMT_PUBLIC *pub,
+ TPM2B_PRIVATE *p,
+ TPM2B_ENCRYPTED_SECRET *enc_secret)
+{
+ TPM2B_PRIVATE secret, seed;
+ /* amount of room in the buffer for the integrity TPM2B */
+ const int name_alg_size = TSS_GetDigestSize(pub->nameAlg);
+ const int integrity_skip = name_alg_size + 2;
+ // BYTE *integrity = p->t.buffer;
+ BYTE *sensitive = p->t.buffer + integrity_skip;
+ BYTE *buf;
+ TPM2B *t2b;
+ INT32 size;
+ size_t ssize;
+ UINT16 bsize, written = 0;
+ EVP_PKEY *ephemeral = NULL;
+ EVP_PKEY_CTX *ctx;
+ TPM2B_ECC_POINT pub_pt, ephemeral_pt;
+ const EC_KEY *e_parent, *e_ephemeral;
+ const EC_GROUP *group;
+ unsigned char aeskey[T2_AES_KEY_BYTES];
+ /* hmac follows namealg, so set to max size */
+ TPM2B_KEY hmackey;
+ TPMT_HA hmac;
+ TPM2B_NAME name;
+ TPM2B_DIGEST digest;
+ unsigned char null_iv[AES_128_BLOCK_SIZE_BYTES];
+ TPM2B null_2b;
+
+ null_2b.size = 0;
+
+ if (EVP_PKEY_type(EVP_PKEY_id(parent)) != EVP_PKEY_EC) {
+ printf("Can only currently wrap to EC parent\n");
+ return TPM_RC_ASYMMETRIC;
+ }
+
+ e_parent = EVP_PKEY_get0_EC_KEY(parent);
+ group = EC_KEY_get0_group(e_parent);
+
+ /* marshal the sensitive into a TPM2B */
+ t2b = (TPM2B *)sensitive;
+ buf = t2b->buffer;
+ size = sizeof(p->t.buffer) - integrity_skip;
+ bsize = 0;
+ TSS_TPMT_SENSITIVE_Marshal(s, &bsize, &buf, &size);
+ buf = (BYTE *)&t2b->size;
+ size = 2;
+ TSS_UINT16_Marshal(&bsize, &written, &buf, &size);
+ /* set the total size of the private entity */
+ p->b.size = bsize + sizeof(UINT16) + integrity_skip;
+
+ /* compute the elliptic curve shared (and encrypted) secret */
+ ctx = EVP_PKEY_CTX_new(parent, NULL);
+ if (!ctx)
+ goto openssl_err;
+ if (EVP_PKEY_keygen_init(ctx) != 1)
+ goto openssl_err;
+ EVP_PKEY_keygen(ctx, &ephemeral);
+ if (!ephemeral)
+ goto openssl_err;
+ /* otherwise the ctx free will free the key */
+ EVP_PKEY_up_ref(ephemeral);
+ EVP_PKEY_CTX_free(ctx);
+
+ e_ephemeral = EVP_PKEY_get0_EC_KEY(ephemeral);
+
+ /* now begin again with the ephemeral private key because the
+ * context must be initialised with the private key */
+ ctx = EVP_PKEY_CTX_new(ephemeral, NULL);
+ if (!ctx)
+ goto openssl_err;
+ if (EVP_PKEY_derive_init(ctx) != 1)
+ goto openssl_err;
+ if (EVP_PKEY_derive_set_peer(ctx, parent) != 1)
+ goto openssl_err;
+ ssize = sizeof(secret.t.buffer);
+ if (EVP_PKEY_derive(ctx, secret.b.buffer, &ssize) != 1)
+ goto openssl_err;
+ secret.b.size = ssize;
+ EVP_PKEY_CTX_free(ctx);
+
+ tpm2_get_public_point(&pub_pt, group, EC_KEY_get0_public_key(e_parent));
+ tpm2_get_public_point(&ephemeral_pt, group,
+ EC_KEY_get0_public_key(e_ephemeral));
+
+ /* now pass the secret through KDFe to get the shared secret
+ * The size is the size of the parent name algorithm which we
+ * assume to be sha256 */
+ TSS_KDFE(seed.b.buffer, pub->nameAlg, &secret.b, "DUPLICATE",
+ &ephemeral_pt.point.x.b, &pub_pt.point.x.b,
+ SHA256_DIGEST_LENGTH*8);
+ seed.b.size = SHA256_DIGEST_LENGTH;
+
+ /* and finally through KDFa to get the aes symmetric encryption key */
+ tpm2_ObjectPublic_GetName(&name, pub);
+ TSS_KDFA(aeskey, pub->nameAlg, &seed.b, "STORAGE", &name.b, &null_2b,
+ T2_AES_KEY_BITS);
+ /* and then the outer HMAC key */
+ hmackey.b.size = name_alg_size;
+ TSS_KDFA(hmackey.b.buffer, pub->nameAlg, &seed.b, "INTEGRITY",
+ &null_2b, &null_2b, name_alg_size * 8);
+ /* OK the ephermeral public point is now the encrypted secret */
+ size = sizeof(ephemeral_pt);
+ buf = enc_secret->b.buffer;
+ TSS_TPM2B_ECC_POINT_Marshal(&ephemeral_pt, &written,
+ &buf, &size);
+ enc_secret->b.size = written;
+ memset(null_iv, 0, sizeof(null_iv));
+ TSS_AES_EncryptCFB(sensitive, T2_AES_KEY_BITS, aeskey, null_iv,
+ p->t.size - integrity_skip, sensitive);
+ hmac.hashAlg = pub->nameAlg;
+ TSS_HMAC_Generate(&hmac, &hmackey,
+ p->t.size - integrity_skip, sensitive,
+ name.b.size, name.b.buffer,
+ 0, NULL);
+ digest.b.size = name_alg_size;
+ memcpy(digest.b.buffer, &hmac.digest, digest.b.size);
+ size = integrity_skip;
+ buf = p->t.buffer;
+ TSS_TPM2B_DIGEST_Marshal(&digest, &written, &buf, &size);
+ return TPM_RC_SUCCESS;
+
+ openssl_err:
+ ERR_print_errors_fp(stderr);
+ return TPM_RC_ASYMMETRIC;
+}
+
TPM_RC
parse_policy_file(const char *policy_file, STACK_OF(TSSOPTPOLICY) *sk,
char *auth, TPMT_HA *digest)
@@ -378,7 +510,7 @@ int
openssl_write_tpmfile(const char *file, BYTE *pubkey, int pubkey_len,
BYTE *privkey, int privkey_len, int empty_auth,
TPM_HANDLE parent, STACK_OF(TSSOPTPOLICY) *sk,
- int version)
+ int version, TPM2B_ENCRYPTED_SECRET *secret)
{
union {
TSSLOADABLE tssl;
@@ -406,7 +538,14 @@ openssl_write_tpmfile(const char *file, BYTE *pubkey, int pubkey_len,

PEM_write_bio_TSSLOADABLE(outb, &k.tssl);
} else {
- k.tpk.type = OBJ_txt2obj(OID_loadableKey, 1);
+ if (secret) {
+ k.tpk.type = OBJ_txt2obj(OID_importableKey, 1);
+ k.tpk.secret = ASN1_OCTET_STRING_new();
+ ASN1_STRING_set(k.tpk.secret, secret->t.secret,
+ secret->t.size);
+ } else {
+ k.tpk.type = OBJ_txt2obj(OID_loadableKey, 1);
+ }
k.tpk.emptyAuth = empty_auth;
k.tpk.parent = ASN1_INTEGER_new();
ASN1_INTEGER_set(k.tpk.parent, parent);
@@ -445,6 +584,27 @@ openssl_read_key(char *filename)
return pkey;
}

+EVP_PKEY *
+openssl_read_public_key(char *filename)
+{
+ BIO *b = NULL;
+ EVP_PKEY *pkey;
+
+ b = BIO_new_file(filename, "r");
+ if (b == NULL) {
+ fprintf(stderr, "Error opening file for read: %s\n", filename);
+ return NULL;
+ }
+
+ if ((pkey = PEM_read_bio_PUBKEY(b, NULL, NULL, NULL)) == NULL) {
+ fprintf(stderr, "Reading key %s from disk failed.\n", filename);
+ openssl_print_errors();
+ }
+ BIO_free(b);
+
+ return pkey;
+}
+
void tpm2_public_template_rsa(TPMT_PUBLIC *pub)
{
pub->type = TPM_ALG_RSA;
@@ -784,7 +944,7 @@ int main(int argc, char **argv)
Create_Out cout;
TPM2B_PUBLIC *pub;
TPM2B_PRIVATE *priv;
- char *key = NULL, *parent_auth = NULL;
+ char *key = NULL, *parent_auth = NULL, *import = NULL;
TPMI_ECC_CURVE ecc = TPM_ECC_NONE;
int rsa = -1;
uint32_t noda = TPMA_OBJECT_NODA;
@@ -794,10 +954,11 @@ int main(int argc, char **argv)
int version = 1;
uint32_t sizeInBytes;
TPMT_HA digest;
+ TPM2B_ENCRYPTED_SECRET secret, *enc_secret = NULL;

while (1) {
option_index = 0;
- c = getopt_long(argc, argv, "n:s:ab:p:hw:vk:re:ldc:",
+ c = getopt_long(argc, argv, "n:s:ab:p:hw:vk:re:ldc:i:",
long_options, &option_index);
if (c == -1)
break;
@@ -873,6 +1034,9 @@ int main(int argc, char **argv)
case 'c':
policyFilename = optarg;
break;
+ case 'i':
+ import = optarg;
+ break;
case OPT_DEPRECATED:
version = 0;
break;
@@ -910,21 +1074,9 @@ int main(int argc, char **argv)
rsa = 0;
}

- dir = tpm2_set_unique_tssdir();
- rc = tpm2_create(&tssContext, dir);
- if (rc) {
- reason = "TSS_Create";
- goto out_err;
- }
-
- if ((parent & 0xff000000) == 0x40000000) {
- rc = tpm2_load_srk(tssContext, &phandle, parent_auth, NULL, parent, version);
- if (rc) {
- reason = "tpm2_load_srk";
- goto out_delete;
- }
- } else {
- phandle = parent;
+ if (import && !wrap) {
+ fprintf(stderr, "Can only wrap importable keys\n");
+ exit(1);
}

digest.hashAlg = name_alg;
@@ -936,13 +1088,13 @@ int main(int argc, char **argv)
if (!sk) {
rc = NOT_TPM_ERROR;
reason="sk_TSSOPTPOLICY_new_null allocation";
- goto out_flush;
+ goto out_err;
}

rc = parse_policy_file(policyFilename, sk, auth, &digest);
if (rc) {
reason = "parse_policy_file";
- goto out_flush;
+ goto out_free_policy;
}
}

@@ -955,11 +1107,90 @@ int main(int argc, char **argv)
fprintf(stderr, "Passwords do not match\n");
reason = "authorization";
rc = NOT_TPM_ERROR;
- goto out_flush;
+ goto out_free_auth;
}
}
}

+ if (import) {
+ EVP_PKEY *p_pkey = openssl_read_public_key(import);
+ EVP_PKEY *pkey = openssl_read_key(wrap);
+ TPMT_SENSITIVE s;
+
+ /* steal existing private and public areas */
+ pub = &iin.objectPublic;
+ priv = &iout.outPrivate;
+
+ rc = NOT_TPM_ERROR;
+
+ if (!p_pkey || !pkey) {
+ reason = "read openssl key";
+ goto out_err;
+ }
+
+ /* FIXME: should do RSA as well, it's just more complex */
+ if (EVP_PKEY_type(EVP_PKEY_id(p_pkey)) != EVP_PKEY_EC) {
+ reason = "parent not EC key";
+ goto out_err;
+ }
+
+ rc = openssl_to_tpm_public(pub, pkey);
+ if (rc) {
+ reason = "openssl_to_tpm_public";
+ goto out_err;
+ }
+ if (policyFilename) {
+ pub->publicArea.objectAttributes.val &=
+ ~TPMA_OBJECT_USERWITHAUTH;
+ rc = TSS_TPM2B_Create(
+ &pub->publicArea.authPolicy.b,
+ (uint8_t *)&digest.digest, sizeInBytes,
+ sizeof(TPMU_HA));
+ if (rc) {
+ reason = "set policy";
+ goto out_err;
+ }
+ }
+
+ rc = wrap_key(&s, auth, pkey);
+ if (rc) {
+ reason = "wrap_key";
+ goto out_err;
+ }
+
+ /* set the NODA flag */
+ pub->publicArea.objectAttributes.val |= noda;
+
+ rc = tpm2_outerwrap(p_pkey, &s, &pub->publicArea,
+ priv, &secret);
+ if (rc) {
+ reason = "tpm2_outerwrap";
+ goto out_err;
+ }
+
+ enc_secret = &secret;
+
+ /* skip over all the TPM connection stuff */
+ goto write_key;
+ }
+
+ dir = tpm2_set_unique_tssdir();
+ rc = tpm2_create(&tssContext, dir);
+ if (rc) {
+ reason = "TSS_Create";
+ goto out_free_auth;
+ }
+
+ if ((parent & 0xff000000) == 0x40000000) {
+ rc = tpm2_load_srk(tssContext, &phandle, parent_auth, NULL, parent, version);
+ if (rc) {
+ reason = "tpm2_load_srk";
+ goto out_delete;
+ }
+ } else {
+ phandle = parent;
+ }
+
if (wrap) {
EVP_PKEY *pkey;
TPMT_SENSITIVE s;
@@ -1113,6 +1344,10 @@ int main(int argc, char **argv)
priv = &cout.outPrivate;
}
tpm2_flush_srk(tssContext, phandle);
+ TSS_Delete(tssContext);
+ tpm2_rm_tssdir(dir, 0);
+
+ write_key:
buffer = pubkey;
pubkey_len = 0;
size = sizeof(pubkey);
@@ -1123,19 +1358,20 @@ int main(int argc, char **argv)
TSS_TPM2B_PRIVATE_Marshal(priv, &privkey_len, &buffer, &size);
openssl_write_tpmfile(filename, pubkey, pubkey_len,
privkey, privkey_len, auth == NULL, parent, sk,
- version);
+ version, enc_secret);
free_policy(sk);
- TSS_Delete(tssContext);
- tpm2_rm_tssdir(dir, 0);

exit(0);

out_flush:
tpm2_flush_srk(tssContext, phandle);
- free_policy(sk);
out_delete:
TSS_Delete(tssContext);
rmdir(dir);
+ out_free_auth:
+ free(auth);
+ out_free_policy:
+ free_policy(sk);
out_err:
if (rc == NOT_TPM_ERROR)
fprintf(stderr, "%s failed\n", reason);
diff --git a/e_tpm2.c b/e_tpm2.c
index 39026e9..84af5e5 100644
--- a/e_tpm2.c
+++ b/e_tpm2.c
@@ -316,7 +316,6 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
struct tpm_ui *ui, void *cb_data)
{
EVP_PKEY *pkey;
- TPM2B_PUBLIC p;
BIO *bf;
TSSLOADABLE *tssl = NULL;
TSSPRIVKEY *tpk = NULL;
@@ -331,6 +330,8 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
ASN1_OCTET_STRING *pubkey;
STACK_OF(TSSOPTPOLICY) *policy;
ASN1_OCTET_STRING *privkey;
+ ASN1_OCTET_STRING *secret = NULL;
+ Import_In iin;

if (!key_id && !bio) {
fprintf(stderr, "key_id or bio is NULL\n");
@@ -367,6 +368,7 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
pubkey = tpk->pubkey;
privkey = tpk->privkey;
policy = tpk->policy;
+ secret = tpk->secret;
} else {
BIO_seek(bf, 0);
tssl = PEM_read_bio_TSSLOADABLE(bf, NULL, NULL, NULL);
@@ -411,8 +413,10 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
goto err;
}
} else if (strcmp(OID_importableKey, oid) == 0) {
- fprintf(stderr, "Importable keys currently unsupported\n");
- goto err;
+ if (!secret) {
+ fprintf(stderr, "Importable keys require an encrypted secret\n");
+ goto err;
+ }
} else {
fprintf(stderr, "Unrecognised object type\n");
goto err;
@@ -435,23 +439,100 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
/* older keys have absent parent */
app_data->parent = TPM_RH_OWNER;

- app_data->priv = OPENSSL_malloc(privkey->length);
- if (!app_data->priv)
- goto err_free;
- app_data->priv_len = privkey->length;
- memcpy(app_data->priv, privkey->data, app_data->priv_len);
-
app_data->pub = OPENSSL_malloc(pubkey->length);
if (!app_data->pub)
goto err_free;
app_data->pub_len = pubkey->length;
memcpy(app_data->pub, pubkey->data, app_data->pub_len);
+
buffer = app_data->pub;
size = app_data->pub_len;
- TPM2B_PUBLIC_Unmarshal(&p, &buffer, &size, FALSE);
- app_data->name_alg = p.publicArea.nameAlg;
+ TPM2B_PUBLIC_Unmarshal(&iin.objectPublic, &buffer, &size, FALSE);
+ app_data->name_alg = iin.objectPublic.publicArea.nameAlg;
+
+ if (strcmp(OID_importableKey, oid) == 0) {
+ TPM_HANDLE session;
+ TSS_CONTEXT *tssContext;
+ TPM_RC rc;
+ const char *reason;
+ TPM2B_PRIVATE priv_2b;
+ BYTE *buf;
+ UINT16 written;
+ INT32 size;
+ Import_Out iout;
+
+ rc = tpm2_create(&tssContext, app_data->dir);
+ if (rc) {
+ reason="tpm2_create";
+ goto import_err;
+ }
+
+ if ((app_data->parent & 0xff000000) == 0x40000000) {
+ tpm2_load_srk(tssContext, &iin.parentHandle,
+ srk_auth, NULL, app_data->parent, 1);
+ } else {
+ iin.parentHandle = app_data->parent;
+ }
+ rc = tpm2_get_session_handle(tssContext, &session,
+ iin.parentHandle,
+ TPM_SE_HMAC,
+ iin.objectPublic.publicArea.nameAlg);
+ if (rc) {
+ reason="tpm2_get_session_handle";
+ goto import_err;
+ }
+
+ /* no inner encryption */
+ iin.encryptionKey.t.size = 0;
+ iin.symmetricAlg.algorithm = TPM_ALG_NULL;
+
+ /* for importable keys the private key is actually the
+ * outer wrapped duplicate structure */
+ buffer = privkey->data;
+ size = privkey->length;
+ TPM2B_PRIVATE_Unmarshal(&iin.duplicate, &buffer, &size);
+
+ buffer = secret->data;
+ size = secret->length;
+ TPM2B_ENCRYPTED_SECRET_Unmarshal(&iin.inSymSeed, &buffer, &size);
+ rc = TSS_Execute(tssContext,
+ (RESPONSE_PARAMETERS *)&iout,
+ (COMMAND_PARAMETERS *)&iin,
+ NULL,
+ TPM_CC_Import,
+ session, srk_auth, 0,
+ TPM_RH_NULL, NULL, 0);
+ if (rc)
+ tpm2_flush_handle(tssContext, session);
+ reason = "TPM2_Import";
+
+ import_err:
+ tpm2_flush_srk(tssContext, iin.parentHandle);
+ TSS_Delete(tssContext);
+ if (rc) {
+ tpm2_error(rc, reason);
+ goto err_free;
+ }
+ buf = priv_2b.t.buffer;
+ size = sizeof(priv_2b.t.buffer);
+ TSS_TPM2B_PRIVATE_Marshal(&iout.outPrivate, &written,
+ &buf, &size);
+ app_data->priv = OPENSSL_malloc(written);
+ if (!app_data->priv)
+ goto err_free;
+ app_data->priv_len = written;
+ memcpy(app_data->priv, priv_2b.t.buffer, written);
+ } else {
+ app_data->priv = OPENSSL_malloc(privkey->length);
+ if (!app_data->priv)
+ goto err_free;
+
+ app_data->priv_len = privkey->length;
+ memcpy(app_data->priv, privkey->data, app_data->priv_len);
+ }
+
/* create the new objects to return */
- pkey = tpm2_to_openssl_public(&p.publicArea);
+ pkey = tpm2_to_openssl_public(&iin.objectPublic.publicArea);
if (!pkey) {
fprintf(stderr, "Failed to allocate a new EVP_KEY\n");
goto err_free;
@@ -463,7 +544,8 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
goto err_free_key;
}

- if (!(p.publicArea.objectAttributes.val & TPMA_OBJECT_USERWITHAUTH))
+ if (!(iin.objectPublic.publicArea.objectAttributes.val &
+ TPMA_OBJECT_USERWITHAUTH))
app_data->req_policy_session = 1;

if (!tpm2_engine_load_key_policy(app_data, policy))
diff --git a/tpm2-asn.h b/tpm2-asn.h
index 15e0f84..82241be 100644
--- a/tpm2-asn.h
+++ b/tpm2-asn.h
@@ -65,6 +65,7 @@ DEFINE_STACK_OF(TSSOPTPOLICY);
* type OBJECT IDENTIFIER
* emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL
* policy [1] EXPLICIT SEQUENCE OF TPMPolicy OPTIONAL
+ * secret [2] EXPLICIT OCTET STRING OPTIONAL
* parent INTEGER
* pubkey OCTET STRING
* privkey OCTET STRING
@@ -84,6 +85,7 @@ typedef struct {
ASN1_OBJECT *type;
ASN1_BOOLEAN emptyAuth;
STACK_OF(TSSOPTPOLICY) *policy;
+ ASN1_OCTET_STRING *secret;
ASN1_INTEGER *parent;
ASN1_OCTET_STRING *pubkey;
ASN1_OCTET_STRING *privkey;
@@ -120,6 +122,7 @@ ASN1_SEQUENCE(TSSPRIVKEY) = {
ASN1_SIMPLE(TSSPRIVKEY, type, ASN1_OBJECT),
ASN1_EXP_OPT(TSSPRIVKEY, emptyAuth, ASN1_BOOLEAN, 0),
ASN1_EXP_SEQUENCE_OF_OPT(TSSPRIVKEY, policy, TSSOPTPOLICY, 1),
+ ASN1_EXP_OPT(TSSPRIVKEY, secret, ASN1_OCTET_STRING, 2),
ASN1_SIMPLE(TSSPRIVKEY, parent, ASN1_INTEGER),
ASN1_SIMPLE(TSSPRIVKEY, pubkey, ASN1_OCTET_STRING),
ASN1_SIMPLE(TSSPRIVKEY, privkey, ASN1_OCTET_STRING)
--
2.16.4

Fredrik Ternerot
 

On Wed, Nov 14, 2018 at 4:56 PM James Bottomley
<James.Bottomley@...> wrote:

Importable keys are a new type of key that can be created without
access to the receiving TPM. The design of importable keys is that
you "wrap" the key (encrypt with a derived key) in such a way that it
can only be unwrapped by the TPM local parent key. To do this
wrapping you only need to know the public key of the parent, which can
be shipped in an ordinary openssl public key file.

The key format of importable keys requires an additional encrypted
secret parameter, which basically encodes the wrapping key using ECDH
so it can be decrypted by the parent on import.

Importable keys are designed to be used everywhere the current
loadable keys are used, the only difference being that the TPM engine
converts the importable key to loadable on first load and thereafter
uses it identically to a loadable key.

The new command line option of importable keys is

create_tpm2_key --import <pubparent> --wrap <private key> tmp.key

Where <pubparent> is the parent public key and <private key> is the
key you are wrapping (obviously since you have no access to the TPM,
this option can only be used to wrap existing private keys).

Signed-off-by: James Bottomley <James.Bottomley@...>
---
create_tpm2_key.1.in | 17 +++
create_tpm2_key.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++-----
e_tpm2.c | 108 ++++++++++++++++---
tpm2-asn.h | 3 +
4 files changed, 377 insertions(+), 39 deletions(-)

diff --git a/create_tpm2_key.1.in b/create_tpm2_key.1.in
index 0e52d86..6ec5f25 100644
--- a/create_tpm2_key.1.in
+++ b/create_tpm2_key.1.in
@@ -7,6 +7,13 @@ Can be used to create a TPM loadable representation of a private key.
The key is either internal to the TPM or wrapped from an existing
private key.

+Note that this command can now create two different types of keys:
+importable and loadable (the default type being loadable). The
+difference between the two is that the creation of loadable keys
+requires the presence of the actual TPM the key will be loaded on. An
+importable key can be created without any TPM contact provided you
+have the public key of the parent the new key will be imported to.
+
Consider to add that the tool currently requires the parent to be of
EC type and use sha256 as name alg.

[files]

Policy File Format
@@ -69,3 +76,13 @@ Create a self-signed cert using the TPM engine:
3. Test using openssl:
$ openssl s_server -cert <certfilename> -www -accept 4433 -keyform engine -engi
ne tpm -key <keyfilename>
+
+Creating an importable key:
+
+1. First get the public key of the storage primary seed (as an elliptic
+ curve key):
+ $ tsscreateprimary -ecc nistp256 -hi o -opem srk.pub
+
+2. Once you have this public key, you can export it anywhere and do the
+ key wrapping
+ $ create_tpm2_key --import srk.pub --wrap my_key.priv my_key.tpm
diff --git a/create_tpm2_key.c b/create_tpm2_key.c
index 899e434..0576c83 100644
--- a/create_tpm2_key.c
+++ b/create_tpm2_key.c
@@ -58,6 +58,7 @@ static struct option long_options[] = {
{"list-curves", 0, 0, 'l'},
{"da", 0, 0, 'd'},
{"key-policy", 1, 0, 'c'},
+ {"import", 1, 0, 'i'},
/*
* The option --deprecated allows us to create old format keys
* for the purposes of testing. It should never be used in
@@ -98,6 +99,8 @@ usage(char *argv0)
"\t Supported curves are bnp256, nisp256, nisp384\n"
"\t-l, --list-curves List all the Elliptic Curves the TPM supports\n"
"\t-c, --key-policy 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"
"\n"
"Report bugs to " PACKAGE_BUGREPORT "\n",
argv0);
@@ -270,6 +273,135 @@ TPM_RC tpm2_innerwrap(TPMT_SENSITIVE *s,
return TPM_RC_SUCCESS;
}

+TPM_RC tpm2_outerwrap(EVP_PKEY *parent,
+ TPMT_SENSITIVE *s,
+ TPMT_PUBLIC *pub,
+ TPM2B_PRIVATE *p,
+ TPM2B_ENCRYPTED_SECRET *enc_secret)
+{
+ TPM2B_PRIVATE secret, seed;
+ /* amount of room in the buffer for the integrity TPM2B */
+ const int name_alg_size = TSS_GetDigestSize(pub->nameAlg);
+ const int integrity_skip = name_alg_size + 2;
+ // BYTE *integrity = p->t.buffer;
+ BYTE *sensitive = p->t.buffer + integrity_skip;
+ BYTE *buf;
+ TPM2B *t2b;
+ INT32 size;
+ size_t ssize;
+ UINT16 bsize, written = 0;
+ EVP_PKEY *ephemeral = NULL;
+ EVP_PKEY_CTX *ctx;
+ TPM2B_ECC_POINT pub_pt, ephemeral_pt;
+ const EC_KEY *e_parent, *e_ephemeral;
+ const EC_GROUP *group;
+ unsigned char aeskey[T2_AES_KEY_BYTES];
+ /* hmac follows namealg, so set to max size */
+ TPM2B_KEY hmackey;
+ TPMT_HA hmac;
+ TPM2B_NAME name;
+ TPM2B_DIGEST digest;
+ unsigned char null_iv[AES_128_BLOCK_SIZE_BYTES];
+ TPM2B null_2b;
+
+ null_2b.size = 0;
+
+ if (EVP_PKEY_type(EVP_PKEY_id(parent)) != EVP_PKEY_EC) {
+ printf("Can only currently wrap to EC parent\n");
+ return TPM_RC_ASYMMETRIC;
+ }
+
+ e_parent = EVP_PKEY_get0_EC_KEY(parent);
+ group = EC_KEY_get0_group(e_parent);
+
+ /* marshal the sensitive into a TPM2B */
+ t2b = (TPM2B *)sensitive;
+ buf = t2b->buffer;
+ size = sizeof(p->t.buffer) - integrity_skip;
+ bsize = 0;
+ TSS_TPMT_SENSITIVE_Marshal(s, &bsize, &buf, &size);
+ buf = (BYTE *)&t2b->size;
+ size = 2;
+ TSS_UINT16_Marshal(&bsize, &written, &buf, &size);
+ /* set the total size of the private entity */
+ p->b.size = bsize + sizeof(UINT16) + integrity_skip;
+
+ /* compute the elliptic curve shared (and encrypted) secret */
+ ctx = EVP_PKEY_CTX_new(parent, NULL);
+ if (!ctx)
+ goto openssl_err;
+ if (EVP_PKEY_keygen_init(ctx) != 1)
+ goto openssl_err;
+ EVP_PKEY_keygen(ctx, &ephemeral);
+ if (!ephemeral)
+ goto openssl_err;
+ /* otherwise the ctx free will free the key */
+ EVP_PKEY_up_ref(ephemeral);
+ EVP_PKEY_CTX_free(ctx);
+
+ e_ephemeral = EVP_PKEY_get0_EC_KEY(ephemeral);
+
+ /* now begin again with the ephemeral private key because the
+ * context must be initialised with the private key */
+ ctx = EVP_PKEY_CTX_new(ephemeral, NULL);
+ if (!ctx)
+ goto openssl_err;
+ if (EVP_PKEY_derive_init(ctx) != 1)
+ goto openssl_err;
+ if (EVP_PKEY_derive_set_peer(ctx, parent) != 1)
+ goto openssl_err;
+ ssize = sizeof(secret.t.buffer);
+ if (EVP_PKEY_derive(ctx, secret.b.buffer, &ssize) != 1)
+ goto openssl_err;
+ secret.b.size = ssize;
+ EVP_PKEY_CTX_free(ctx);
+
+ tpm2_get_public_point(&pub_pt, group, EC_KEY_get0_public_key(e_parent));
+ tpm2_get_public_point(&ephemeral_pt, group,
+ EC_KEY_get0_public_key(e_ephemeral));
+
+ /* now pass the secret through KDFe to get the shared secret
+ * The size is the size of the parent name algorithm which we
+ * assume to be sha256 */
+ TSS_KDFE(seed.b.buffer, pub->nameAlg, &secret.b, "DUPLICATE",
+ &ephemeral_pt.point.x.b, &pub_pt.point.x.b,
+ SHA256_DIGEST_LENGTH*8);
+ seed.b.size = SHA256_DIGEST_LENGTH;
+
+ /* and finally through KDFa to get the aes symmetric encryption key */
+ tpm2_ObjectPublic_GetName(&name, pub);
+ TSS_KDFA(aeskey, pub->nameAlg, &seed.b, "STORAGE", &name.b, &null_2b,
+ T2_AES_KEY_BITS);
+ /* and then the outer HMAC key */
+ hmackey.b.size = name_alg_size;
+ TSS_KDFA(hmackey.b.buffer, pub->nameAlg, &seed.b, "INTEGRITY",
+ &null_2b, &null_2b, name_alg_size * 8);
+ /* OK the ephermeral public point is now the encrypted secret */
+ size = sizeof(ephemeral_pt);
+ buf = enc_secret->b.buffer;
+ TSS_TPM2B_ECC_POINT_Marshal(&ephemeral_pt, &written,
+ &buf, &size);
+ enc_secret->b.size = written;
+ memset(null_iv, 0, sizeof(null_iv));
+ TSS_AES_EncryptCFB(sensitive, T2_AES_KEY_BITS, aeskey, null_iv,
+ p->t.size - integrity_skip, sensitive);
+ hmac.hashAlg = pub->nameAlg;
+ TSS_HMAC_Generate(&hmac, &hmackey,
+ p->t.size - integrity_skip, sensitive,
+ name.b.size, name.b.buffer,
+ 0, NULL);
+ digest.b.size = name_alg_size;
+ memcpy(digest.b.buffer, &hmac.digest, digest.b.size);
+ size = integrity_skip;
+ buf = p->t.buffer;
+ TSS_TPM2B_DIGEST_Marshal(&digest, &written, &buf, &size);
+ return TPM_RC_SUCCESS;
+
+ openssl_err:
+ ERR_print_errors_fp(stderr);
+ return TPM_RC_ASYMMETRIC;
+}
+
TPM_RC
parse_policy_file(const char *policy_file, STACK_OF(TSSOPTPOLICY) *sk,
char *auth, TPMT_HA *digest)
@@ -378,7 +510,7 @@ int
openssl_write_tpmfile(const char *file, BYTE *pubkey, int pubkey_len,
BYTE *privkey, int privkey_len, int empty_auth,
TPM_HANDLE parent, STACK_OF(TSSOPTPOLICY) *sk,
- int version)
+ int version, TPM2B_ENCRYPTED_SECRET *secret)
{
union {
TSSLOADABLE tssl;
@@ -406,7 +538,14 @@ openssl_write_tpmfile(const char *file, BYTE *pubkey, int pubkey_len,

PEM_write_bio_TSSLOADABLE(outb, &k.tssl);
} else {
- k.tpk.type = OBJ_txt2obj(OID_loadableKey, 1);
+ if (secret) {
+ k.tpk.type = OBJ_txt2obj(OID_importableKey, 1);
+ k.tpk.secret = ASN1_OCTET_STRING_new();
+ ASN1_STRING_set(k.tpk.secret, secret->t.secret,
+ secret->t.size);
+ } else {
+ k.tpk.type = OBJ_txt2obj(OID_loadableKey, 1);
+ }
k.tpk.emptyAuth = empty_auth;
k.tpk.parent = ASN1_INTEGER_new();
ASN1_INTEGER_set(k.tpk.parent, parent);
@@ -445,6 +584,27 @@ openssl_read_key(char *filename)
return pkey;
}

+EVP_PKEY *
+openssl_read_public_key(char *filename)
+{
+ BIO *b = NULL;
+ EVP_PKEY *pkey;
+
+ b = BIO_new_file(filename, "r");
+ if (b == NULL) {
+ fprintf(stderr, "Error opening file for read: %s\n", filename);
+ return NULL;
+ }
+
+ if ((pkey = PEM_read_bio_PUBKEY(b, NULL, NULL, NULL)) == NULL) {
+ fprintf(stderr, "Reading key %s from disk failed.\n", filename);
+ openssl_print_errors();
+ }
+ BIO_free(b);
+
+ return pkey;
+}
Here you are using spaces instead of tabs for indentation. Same in
openssl_read_key().

+
void tpm2_public_template_rsa(TPMT_PUBLIC *pub)
{
pub->type = TPM_ALG_RSA;
@@ -784,7 +944,7 @@ int main(int argc, char **argv)
Create_Out cout;
TPM2B_PUBLIC *pub;
TPM2B_PRIVATE *priv;
- char *key = NULL, *parent_auth = NULL;
+ char *key = NULL, *parent_auth = NULL, *import = NULL;
TPMI_ECC_CURVE ecc = TPM_ECC_NONE;
int rsa = -1;
uint32_t noda = TPMA_OBJECT_NODA;
@@ -794,10 +954,11 @@ int main(int argc, char **argv)
int version = 1;
uint32_t sizeInBytes;
TPMT_HA digest;
+ TPM2B_ENCRYPTED_SECRET secret, *enc_secret = NULL;

while (1) {
option_index = 0;
- c = getopt_long(argc, argv, "n:s:ab:p:hw:vk:re:ldc:",
+ c = getopt_long(argc, argv, "n:s:ab:p:hw:vk:re:ldc:i:",
long_options, &option_index);
if (c == -1)
break;
@@ -873,6 +1034,9 @@ int main(int argc, char **argv)
case 'c':
policyFilename = optarg;
break;
+ case 'i':
+ import = optarg;
+ break;
case OPT_DEPRECATED:
version = 0;
break;
@@ -910,21 +1074,9 @@ int main(int argc, char **argv)
rsa = 0;
}

- dir = tpm2_set_unique_tssdir();
- rc = tpm2_create(&tssContext, dir);
- if (rc) {
- reason = "TSS_Create";
- goto out_err;
- }
-
- if ((parent & 0xff000000) == 0x40000000) {
- rc = tpm2_load_srk(tssContext, &phandle, parent_auth, NULL, parent, version);
- if (rc) {
- reason = "tpm2_load_srk";
- goto out_delete;
- }
- } else {
- phandle = parent;
+ if (import && !wrap) {
+ fprintf(stderr, "Can only wrap importable keys\n");
This print is a bit misleading. You can actually wrap a loadable key.
I guess this should be "Importable keys must be wrapped" or similar.

+ exit(1);
}

digest.hashAlg = name_alg;
@@ -936,13 +1088,13 @@ int main(int argc, char **argv)
if (!sk) {
rc = NOT_TPM_ERROR;
reason="sk_TSSOPTPOLICY_new_null allocation";
- goto out_flush;
+ goto out_err;
}

rc = parse_policy_file(policyFilename, sk, auth, &digest);
if (rc) {
reason = "parse_policy_file";
- goto out_flush;
+ goto out_free_policy;
}
}

@@ -955,11 +1107,90 @@ int main(int argc, char **argv)
fprintf(stderr, "Passwords do not match\n");
reason = "authorization";
rc = NOT_TPM_ERROR;
- goto out_flush;
+ goto out_free_auth;
}
}
}

+ if (import) {
+ EVP_PKEY *p_pkey = openssl_read_public_key(import);
+ EVP_PKEY *pkey = openssl_read_key(wrap);
+ TPMT_SENSITIVE s;
+
+ /* steal existing private and public areas */
+ pub = &iin.objectPublic;
+ priv = &iout.outPrivate;
+
+ rc = NOT_TPM_ERROR;
+
+ if (!p_pkey || !pkey) {
+ reason = "read openssl key";
+ goto out_err;
+ }
+
+ /* FIXME: should do RSA as well, it's just more complex */
+ if (EVP_PKEY_type(EVP_PKEY_id(p_pkey)) != EVP_PKEY_EC) {
+ reason = "parent not EC key";
+ goto out_err;
+ }
+
+ rc = openssl_to_tpm_public(pub, pkey);
+ if (rc) {
+ reason = "openssl_to_tpm_public";
+ goto out_err;
+ }
+ if (policyFilename) {
+ pub->publicArea.objectAttributes.val &=
+ ~TPMA_OBJECT_USERWITHAUTH;
+ rc = TSS_TPM2B_Create(
+ &pub->publicArea.authPolicy.b,
+ (uint8_t *)&digest.digest, sizeInBytes,
+ sizeof(TPMU_HA));
+ if (rc) {
+ reason = "set policy";
+ goto out_err;
+ }
+ }
+
+ rc = wrap_key(&s, auth, pkey);
+ if (rc) {
+ reason = "wrap_key";
+ goto out_err;
+ }
+
+ /* set the NODA flag */
+ pub->publicArea.objectAttributes.val |= noda;
+
+ rc = tpm2_outerwrap(p_pkey, &s, &pub->publicArea,
+ priv, &secret);
+ if (rc) {
+ reason = "tpm2_outerwrap";
+ goto out_err;
+ }
+
+ enc_secret = &secret;
+
+ /* skip over all the TPM connection stuff */
+ goto write_key;
+ }
+
+ dir = tpm2_set_unique_tssdir();
+ rc = tpm2_create(&tssContext, dir);
+ if (rc) {
+ reason = "TSS_Create";
+ goto out_free_auth;
+ }
+
+ if ((parent & 0xff000000) == 0x40000000) {
+ rc = tpm2_load_srk(tssContext, &phandle, parent_auth, NULL, parent, version);
+ if (rc) {
+ reason = "tpm2_load_srk";
+ goto out_delete;
+ }
+ } else {
+ phandle = parent;
+ }
+
if (wrap) {
EVP_PKEY *pkey;
TPMT_SENSITIVE s;
@@ -1113,6 +1344,10 @@ int main(int argc, char **argv)
priv = &cout.outPrivate;
}
tpm2_flush_srk(tssContext, phandle);
+ TSS_Delete(tssContext);
+ tpm2_rm_tssdir(dir, 0);
+
+ write_key:
buffer = pubkey;
pubkey_len = 0;
size = sizeof(pubkey);
@@ -1123,19 +1358,20 @@ int main(int argc, char **argv)
TSS_TPM2B_PRIVATE_Marshal(priv, &privkey_len, &buffer, &size);
openssl_write_tpmfile(filename, pubkey, pubkey_len,
privkey, privkey_len, auth == NULL, parent, sk,
- version);
+ version, enc_secret);
free_policy(sk);
- TSS_Delete(tssContext);
- tpm2_rm_tssdir(dir, 0);

exit(0);

out_flush:
tpm2_flush_srk(tssContext, phandle);
- free_policy(sk);
out_delete:
TSS_Delete(tssContext);
rmdir(dir);
+ out_free_auth:
+ free(auth);
+ out_free_policy:
+ free_policy(sk);
out_err:
if (rc == NOT_TPM_ERROR)
fprintf(stderr, "%s failed\n", reason);
diff --git a/e_tpm2.c b/e_tpm2.c
index 39026e9..84af5e5 100644
--- a/e_tpm2.c
+++ b/e_tpm2.c
@@ -316,7 +316,6 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
struct tpm_ui *ui, void *cb_data)
{
EVP_PKEY *pkey;
- TPM2B_PUBLIC p;
BIO *bf;
TSSLOADABLE *tssl = NULL;
TSSPRIVKEY *tpk = NULL;
@@ -331,6 +330,8 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
ASN1_OCTET_STRING *pubkey;
STACK_OF(TSSOPTPOLICY) *policy;
ASN1_OCTET_STRING *privkey;
+ ASN1_OCTET_STRING *secret = NULL;
+ Import_In iin;

if (!key_id && !bio) {
fprintf(stderr, "key_id or bio is NULL\n");
@@ -367,6 +368,7 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
pubkey = tpk->pubkey;
privkey = tpk->privkey;
policy = tpk->policy;
+ secret = tpk->secret;
} else {
BIO_seek(bf, 0);
tssl = PEM_read_bio_TSSLOADABLE(bf, NULL, NULL, NULL);
@@ -411,8 +413,10 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
goto err;
}
} else if (strcmp(OID_importableKey, oid) == 0) {
- fprintf(stderr, "Importable keys currently unsupported\n");
- goto err;
+ if (!secret) {
+ fprintf(stderr, "Importable keys require an encrypted secret\n");
+ goto err;
+ }
} else {
fprintf(stderr, "Unrecognised object type\n");
goto err;
@@ -435,23 +439,100 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
/* older keys have absent parent */
app_data->parent = TPM_RH_OWNER;

- app_data->priv = OPENSSL_malloc(privkey->length);
- if (!app_data->priv)
- goto err_free;
- app_data->priv_len = privkey->length;
- memcpy(app_data->priv, privkey->data, app_data->priv_len);
-
app_data->pub = OPENSSL_malloc(pubkey->length);
if (!app_data->pub)
goto err_free;
app_data->pub_len = pubkey->length;
memcpy(app_data->pub, pubkey->data, app_data->pub_len);
+
buffer = app_data->pub;
size = app_data->pub_len;
- TPM2B_PUBLIC_Unmarshal(&p, &buffer, &size, FALSE);
- app_data->name_alg = p.publicArea.nameAlg;
+ TPM2B_PUBLIC_Unmarshal(&iin.objectPublic, &buffer, &size, FALSE);
+ app_data->name_alg = iin.objectPublic.publicArea.nameAlg;
+
+ if (strcmp(OID_importableKey, oid) == 0) {
+ TPM_HANDLE session;
+ TSS_CONTEXT *tssContext;
+ TPM_RC rc;
+ const char *reason;
+ TPM2B_PRIVATE priv_2b;
+ BYTE *buf;
+ UINT16 written;
+ INT32 size;
+ Import_Out iout;
+
+ rc = tpm2_create(&tssContext, app_data->dir);
+ if (rc) {
+ reason="tpm2_create";
+ goto import_err;
+ }
+
+ if ((app_data->parent & 0xff000000) == 0x40000000) {
+ tpm2_load_srk(tssContext, &iin.parentHandle,
+ srk_auth, NULL, app_data->parent, 1);
+ } else {
+ iin.parentHandle = app_data->parent;
+ }
+ rc = tpm2_get_session_handle(tssContext, &session,
+ iin.parentHandle,
+ TPM_SE_HMAC,
+ iin.objectPublic.publicArea.nameAlg);
+ if (rc) {
+ reason="tpm2_get_session_handle";
+ goto import_err;
+ }
+
+ /* no inner encryption */
+ iin.encryptionKey.t.size = 0;
+ iin.symmetricAlg.algorithm = TPM_ALG_NULL;
+
+ /* for importable keys the private key is actually the
+ * outer wrapped duplicate structure */
+ buffer = privkey->data;
+ size = privkey->length;
+ TPM2B_PRIVATE_Unmarshal(&iin.duplicate, &buffer, &size);
+
+ buffer = secret->data;
+ size = secret->length;
+ TPM2B_ENCRYPTED_SECRET_Unmarshal(&iin.inSymSeed, &buffer, &size);
+ rc = TSS_Execute(tssContext,
+ (RESPONSE_PARAMETERS *)&iout,
+ (COMMAND_PARAMETERS *)&iin,
+ NULL,
+ TPM_CC_Import,
+ session, srk_auth, 0,
+ TPM_RH_NULL, NULL, 0);
+ if (rc)
+ tpm2_flush_handle(tssContext, session);
+ reason = "TPM2_Import";
+
+ import_err:
+ tpm2_flush_srk(tssContext, iin.parentHandle);
+ TSS_Delete(tssContext);
+ if (rc) {
+ tpm2_error(rc, reason);
+ goto err_free;
+ }
+ buf = priv_2b.t.buffer;
+ size = sizeof(priv_2b.t.buffer);
+ TSS_TPM2B_PRIVATE_Marshal(&iout.outPrivate, &written,
+ &buf, &size);
+ app_data->priv = OPENSSL_malloc(written);
+ if (!app_data->priv)
+ goto err_free;
+ app_data->priv_len = written;
+ memcpy(app_data->priv, priv_2b.t.buffer, written);
+ } else {
+ app_data->priv = OPENSSL_malloc(privkey->length);
+ if (!app_data->priv)
+ goto err_free;
+
+ app_data->priv_len = privkey->length;
+ memcpy(app_data->priv, privkey->data, app_data->priv_len);
+ }
+
/* create the new objects to return */
- pkey = tpm2_to_openssl_public(&p.publicArea);
+ pkey = tpm2_to_openssl_public(&iin.objectPublic.publicArea);
if (!pkey) {
fprintf(stderr, "Failed to allocate a new EVP_KEY\n");
goto err_free;
@@ -463,7 +544,8 @@ static int tpm2_engine_load_key_core(ENGINE *e, EVP_PKEY **ppkey,
goto err_free_key;
}

- if (!(p.publicArea.objectAttributes.val & TPMA_OBJECT_USERWITHAUTH))
+ if (!(iin.objectPublic.publicArea.objectAttributes.val &
+ TPMA_OBJECT_USERWITHAUTH))
app_data->req_policy_session = 1;

if (!tpm2_engine_load_key_policy(app_data, policy))
diff --git a/tpm2-asn.h b/tpm2-asn.h
index 15e0f84..82241be 100644
--- a/tpm2-asn.h
+++ b/tpm2-asn.h
@@ -65,6 +65,7 @@ DEFINE_STACK_OF(TSSOPTPOLICY);
* type OBJECT IDENTIFIER
* emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL
* policy [1] EXPLICIT SEQUENCE OF TPMPolicy OPTIONAL
+ * secret [2] EXPLICIT OCTET STRING OPTIONAL
* parent INTEGER
* pubkey OCTET STRING
* privkey OCTET STRING
@@ -84,6 +85,7 @@ typedef struct {
ASN1_OBJECT *type;
ASN1_BOOLEAN emptyAuth;
STACK_OF(TSSOPTPOLICY) *policy;
+ ASN1_OCTET_STRING *secret;
ASN1_INTEGER *parent;
ASN1_OCTET_STRING *pubkey;
ASN1_OCTET_STRING *privkey;
@@ -120,6 +122,7 @@ ASN1_SEQUENCE(TSSPRIVKEY) = {
ASN1_SIMPLE(TSSPRIVKEY, type, ASN1_OBJECT),
ASN1_EXP_OPT(TSSPRIVKEY, emptyAuth, ASN1_BOOLEAN, 0),
ASN1_EXP_SEQUENCE_OF_OPT(TSSPRIVKEY, policy, TSSOPTPOLICY, 1),
+ ASN1_EXP_OPT(TSSPRIVKEY, secret, ASN1_OCTET_STRING, 2),
ASN1_SIMPLE(TSSPRIVKEY, parent, ASN1_INTEGER),
ASN1_SIMPLE(TSSPRIVKEY, pubkey, ASN1_OCTET_STRING),
ASN1_SIMPLE(TSSPRIVKEY, privkey, ASN1_OCTET_STRING)
--
2.16.4



James Bottomley
 

On Thu, 2018-11-15 at 10:57 +0100, Fredrik Ternerot wrote:
On Wed, Nov 14, 2018 at 4:56 PM James Bottomley
<James.Bottomley@...> wrote:
[...]
diff --git a/create_tpm2_key.1.in b/create_tpm2_key.1.in
index 0e52d86..6ec5f25 100644
--- a/create_tpm2_key.1.in
+++ b/create_tpm2_key.1.in
@@ -7,6 +7,13 @@ Can be used to create a TPM loadable
representation of a private key.
The key is either internal to the TPM or wrapped from an existing
private key.

+Note that this command can now create two different types of keys:
+importable and loadable (the default type being loadable). The
+difference between the two is that the creation of loadable keys
+requires the presence of the actual TPM the key will be loaded
on. An
+importable key can be created without any TPM contact provided you
+have the public key of the parent the new key will be imported to.
+
Consider to add that the tool currently requires the parent to be of
EC type and use sha256 as name alg.
That should be temporary: I just ran out of steam because openssl has
useful elliptic curve primitives. Apparently it has none for RSA-OAEP
so I'd have to write them from scratch.

[..]
+EVP_PKEY *
+openssl_read_public_key(char *filename)
+{
+ BIO *b = NULL;
+ EVP_PKEY *pkey;
+
+ b = BIO_new_file(filename, "r");
+ if (b == NULL) {
+ fprintf(stderr, "Error opening file for read:
%s\n", filename);
+ return NULL;
+ }
+
+ if ((pkey = PEM_read_bio_PUBKEY(b, NULL, NULL, NULL)) ==
NULL) {
+ fprintf(stderr, "Reading key %s from disk
failed.\n", filename);
+ openssl_print_errors();
+ }
+ BIO_free(b);
+
+ return pkey;
+}
Here you are using spaces instead of tabs for indentation. Same in
openssl_read_key().
Heh, well, it was obviously just copied over in the cut and paste.

[...]
@@ -910,21 +1074,9 @@ int main(int argc, char **argv)
rsa = 0;
}

- dir = tpm2_set_unique_tssdir();
- rc = tpm2_create(&tssContext, dir);
- if (rc) {
- reason = "TSS_Create";
- goto out_err;
- }
-
- if ((parent & 0xff000000) == 0x40000000) {
- rc = tpm2_load_srk(tssContext, &phandle,
parent_auth, NULL, parent, version);
- if (rc) {
- reason = "tpm2_load_srk";
- goto out_delete;
- }
- } else {
- phandle = parent;
+ if (import && !wrap) {
+ fprintf(stderr, "Can only wrap importable keys\n");
This print is a bit misleading. You can actually wrap a loadable key.
I guess this should be "Importable keys must be wrapped" or similar.
Yes, that sounds better, care to submit a patch?

James

Fredrik Ternerot
 

On Sat, Nov 17, 2018 at 6:54 AM James Bottomley
<James.Bottomley@...> wrote:

On Thu, 2018-11-15 at 10:57 +0100, Fredrik Ternerot wrote:
On Wed, Nov 14, 2018 at 4:56 PM James Bottomley
<James.Bottomley@...> wrote:
[...]
diff --git a/create_tpm2_key.1.in b/create_tpm2_key.1.in
index 0e52d86..6ec5f25 100644
--- a/create_tpm2_key.1.in
+++ b/create_tpm2_key.1.in
@@ -7,6 +7,13 @@ Can be used to create a TPM loadable
representation of a private key.
The key is either internal to the TPM or wrapped from an existing
private key.

+Note that this command can now create two different types of keys:
+importable and loadable (the default type being loadable). The
+difference between the two is that the creation of loadable keys
+requires the presence of the actual TPM the key will be loaded
on. An
+importable key can be created without any TPM contact provided you
+have the public key of the parent the new key will be imported to.
+
Consider to add that the tool currently requires the parent to be of
EC type and use sha256 as name alg.
That should be temporary: I just ran out of steam because openssl has
useful elliptic curve primitives. Apparently it has none for RSA-OAEP
so I'd have to write them from scratch.

[..]
+EVP_PKEY *
+openssl_read_public_key(char *filename)
+{
+ BIO *b = NULL;
+ EVP_PKEY *pkey;
+
+ b = BIO_new_file(filename, "r");
+ if (b == NULL) {
+ fprintf(stderr, "Error opening file for read:
%s\n", filename);
+ return NULL;
+ }
+
+ if ((pkey = PEM_read_bio_PUBKEY(b, NULL, NULL, NULL)) ==
NULL) {
+ fprintf(stderr, "Reading key %s from disk
failed.\n", filename);
+ openssl_print_errors();
+ }
+ BIO_free(b);
+
+ return pkey;
+}
Here you are using spaces instead of tabs for indentation. Same in
openssl_read_key().
Heh, well, it was obviously just copied over in the cut and paste.

[...]
@@ -910,21 +1074,9 @@ int main(int argc, char **argv)
rsa = 0;
}

- dir = tpm2_set_unique_tssdir();
- rc = tpm2_create(&tssContext, dir);
- if (rc) {
- reason = "TSS_Create";
- goto out_err;
- }
-
- if ((parent & 0xff000000) == 0x40000000) {
- rc = tpm2_load_srk(tssContext, &phandle,
parent_auth, NULL, parent, version);
- if (rc) {
- reason = "tpm2_load_srk";
- goto out_delete;
- }
- } else {
- phandle = parent;
+ if (import && !wrap) {
+ fprintf(stderr, "Can only wrap importable keys\n");
This print is a bit misleading. You can actually wrap a loadable key.
I guess this should be "Importable keys must be wrapped" or similar.
Yes, that sounds better, care to submit a patch?
Not really. My comments were just minor things I noticed when going
through the change, and I posted them just in case you wanted to fix
some of them before pushing the change.

--
Fredrik Ternerot