On Wed, Aug 31, 2011 at 10:36:05AM -0400, Stefan Berger wrote: > This patch adds a -tpm ...,initstate=...,... command line option to the > TPM's existing options and enables the TPM to be initialized with an > existing state blob. This in turn allows us to simulate TPM manufacturing > and equip the TPM with an endorsement key, certificates and initialize its > NVRAM areas etc.. This step is typically done during manufacturng of the TPM > and/or the (physical) machine. > > The initial state can be passed either as file or via a file descriptor. The > encoding of the state can either be binary or in form of a base64-encoded > blob surrounded by tags indicating the start and end. > > The intial state can be produced through a yet-to-be-published tpm-authoring > tool. > > Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com>
I am guessing we get the base64 format from tpmlib? > --- > hw/tpm_builtin.c | 123 > ++++++++++++++++++++++++++++++++++++++++++++++++++++++- > qemu-config.c | 12 +++++ > qemu-options.hx | 40 ++++++++++++++++- > 3 files changed, 172 insertions(+), 3 deletions(-) > > Index: qemu-git/hw/tpm_builtin.c > =================================================================== > --- qemu-git.orig/hw/tpm_builtin.c > +++ qemu-git/hw/tpm_builtin.c > @@ -170,9 +170,16 @@ static struct enckey { > AES_KEY tpm_dec_key; > } enckey; > > +static int tpm_initstatefd = -1; > +static bool tpm_initstate_bin; > + This hardcodes assumption of a single backend. > static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs, > enum BSEntryType be, > TPMSizedBuffer *tsb); > +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data, > + uint32_t *length, > + size_t tpm_number, > + const char *name); > > > > @@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load > *length = permanent_state.size; > > if (*length == 0) { > - rc = TPM_RETRY; > + rc = tpm_builtin_get_initial_state(data, length, tpm_number, > name); > } else { > /* keep a copy of the last permanent state */ > rc = TPM_Malloc(data, *length); > @@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys > } > > > +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data, > + uint32_t *length, > + size_t tpm_number, > + const char *name) > +{ > + TPM_RESULT rc = TPM_RETRY; > + uint32_t allocated = 0; > + int len, flags; > + unsigned char buf[1024]; > + unsigned char *result = NULL; > + size_t result_len; > + > + if (tpm_initstatefd >= 0) { > + *data = NULL; > + *length = 0; > + > + flags = fcntl(tpm_initstatefd, F_GETFL); > + if (flags < 0 || > + fcntl(tpm_initstatefd, F_SETFL, flags & ~O_NONBLOCK) < 0) { > + return TPM_FAIL; > + } > + > + while (TRUE) { > + len = read(tpm_initstatefd, buf, sizeof(buf)); > + > + if (len > 0) { > + if (len > allocated - *length) { > + allocated = *length + len + 1024; > + if (TPM_Realloc(data, allocated) != TPM_SUCCESS) { > + goto err_exit; > + } > + } > + memcpy(&(*data)[*length], buf, len); > + *length += len; > + (*data)[*length] = 0; > + } else if (len == 0) { > + rc = TPM_SUCCESS; > + break; > + } else if (len < 0) { > + if (errno == EINTR) { > + continue; > + } > + goto err_exit; > + } > + } > + > + if (*data == NULL) { > + /* nothing read */ > + rc = TPM_FAIL; > + goto err_exit; > + } > + > + if (!tpm_initstate_bin) { > + if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE, > + &result, &result_len) != TPM_SUCCESS) { > + goto err_exit; > + } > + TPM_Free(*data); > + *data = result; > + *length = result_len; > + result = NULL; > + } > + /* sanity check for the size of the blob */ > + if (*length > tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) { > + goto err_exit; > + } Do we really have to hand-craft file reading? How large is TPMPROP_TPM_MAX_NV_SPACE? If not too large, we can just allocate that and do a single fread call? Or, we rely on glib now - can we use g_io_channel_read_to_end () or something like that? > + /* have it written into the BlockStorage */ > + rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name); What if that backend is compiled-out? link will fail? > + if (rc != TPM_SUCCESS) { > + goto err_exit; > + } > + } > + > +norm_exit: > + close(tpm_initstatefd); > + tpm_initstatefd = -1; > + > + return rc; > + > +err_exit: > + TPM_Free(*data); > + *data = NULL; > + *length = 0; > + TPM_Free(result); > + > + goto norm_exit; > +} > + > /*****************************************************************/ > > > @@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe > const char *value; > unsigned char keyvalue[256/8]; > int keysize = sizeof(keyvalue); > + unsigned int offset; > > driver = g_malloc(sizeof(TPMBackend)); > if (!driver) { > @@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe > enckey.enctype = BS_DIR_ENCTYPE_NONE; > } > > + value = qemu_opt_get(opts, "initstate"); > + if (value) { > + offset = 0; > + > + if (!strncmp(value, "bin:", 4)) { > + tpm_initstate_bin = true; > + offset = 4; > + } else if (!strncmp(value, "base64:", 7)) { > + tpm_initstate_bin = false; > + offset = 7; > + } > + > + if (sscanf(&value[offset], "fd:%d", &tpm_initstatefd) != 1) { > + tpm_initstatefd = open(&value[offset], O_RDONLY); > + if (tpm_initstatefd < 0) { > + fprintf(stderr, "tpm: could not open file '%s' for > reading.\n", > + value); > + goto err_exit; > + } > + } > + } > + Separate options for fd and for file mode would be better. > return driver; > > err_exit: > @@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke > g_free(driver->id); > g_free(driver->model); > g_free(driver); > + > + close(tpm_initstatefd); > + tpm_initstatefd = -1; > } > > > Index: qemu-git/qemu-config.c > =================================================================== > --- qemu-git.orig/qemu-config.c > +++ qemu-git/qemu-config.c > @@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = { > .type = QEMU_OPT_STRING, > .help = "Data encryption key", > }, > + { > + .name = "initstate", > + .type = QEMU_OPT_STRING, > + .help = "File or file descriptor for reading initial TPM state " > + "from", > + }, > { /* end of list */ } > }, > }; > @@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = { > .type = QEMU_OPT_STRING, > .help = "Data encryption key", > }, > + { > + .name = "initstate", > + .type = QEMU_OPT_STRING, > + .help = "File or file descriptor for reading initial TPM state " > + "from", > + }, > { /* end of list */ } > }, > }; I think description should document the magic bin:/base64: etc strings, or better get rid of them. > Index: qemu-git/qemu-options.hx > =================================================================== > --- qemu-git.orig/qemu-options.hx > +++ qemu-git/qemu-options.hx > @@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:) > DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \ > "" \ > "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \ > + " [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \ > " enable a builtin TPM with state in file in path\n" \ > " and encrypt the TPM's state with the given AES key\n" \ > + " initstate= path to initial state of TPM; default is > base64\n" \ > "-tpm null enable a TPM null driver that responds with a fault\n" \ > " message to every TPM request\n" \ > "-tpm model=? to list available TPM device models\n" \ > @@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend > qemu -tpmdev ? > @end example > > -@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] > +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] > [,initstate=@var{path}] > > Creates an instance of the built-in TPM. > > @@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t > -tpmdev > builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef > -device tpm-tis,tpmdev=tpm0 > @end example > > +@option{initstate} specifies the path to a file containing the initial > +state of the TPM. It can be used to provide the TPM with an EK and > certificates > +for the EK, TPM and Platform. Since the file contains binary data that > +have to conform to the TPM's layout of data, it must have been created using > +an approriate authoring tool. > + > +The initstate option allows to provide a binary state blob or one that is > +encode in base 64. The base64-encode state blob must have the format > + > +@example > +-----BEGIN INITSTATE----- > +<base 64 encoded state> > +-----END INITSTATE----- > +@end example > + > +The initstate option is only effective when Qemu is started with blank > +state. > + > +The initstate option supports several formats: > + > +@table @option > + @item [base64:]<path_to_blob> > + Provide the path to the TPM's initial state blob in base64 format. > + @item bin:<path to blob> > + Provide the path to the TPM's initial state blob in binary format. > + @item [base64:]fd:<fd> > + Provide the base64 formatted initial state via a file descriptor to read > from. > + @item bin:fd:<fd> > + Provide the binary initial state via a file descriptor to read from. The command line is non standard. E.g. what if the path starts with fd? > +@end table > + > +@option{initstate} is optional. > + > + > @item -tpmdev null > > Creates an instance of a TPM null driver that responds to every command > @@ -1840,7 +1876,7 @@ with a fault message. > The short form of a TPM device option is: > @table @option > > -@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] > [,key=@var{key}] > +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] > [,key=@var{key}] [,initstate=@var{path}] > @findex -tpm > > @option{model} specifies the device model. The default device model is a >