This patch adds support for encrypted block storage for the TPM's block storage. Specifically it - extends the XML parser and generator to handle the extended TPM XML referring to a 'secret'. It can handle the following XML <tpm type='built-in'> <storage> <encryption format='qcow'> <secret type='passphrase' uuid='13ea49f7-2553-7308-5168-e337ade36232'/> </encryption> </storage> </tpm> For this it relies on existing functions originally written for storage encryption. - extends the code creating the block storage file to create an encrypted QCoW2 - extends the montior to pass the encryption key to Qemu so it can access the encrypted data; - handles the secret associated with the TPM storage; much of the code around the handling of the secret has been recycled from existing storage handling code Signed-off-by: Stefan Berger <stefanb@xxxxxxxxxxxxxxxxxx> --- docs/schemas/domain.rng | 3 src/conf/domain_conf.c | 53 ++++++++++++ src/conf/domain_conf.h | 1 src/qemu/qemu_command.c | 24 ++++- src/qemu/qemu_driver.c | 2 src/qemu/qemu_monitor.c | 16 +++ src/qemu/qemu_monitor.h | 13 +++ src/qemu/qemu_monitor_text.c | 6 + src/qemu/qemu_process.c | 176 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 285 insertions(+), 9 deletions(-) Index: libvirt-acl/src/qemu/qemu_monitor.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor.c +++ libvirt-acl/src/qemu/qemu_monitor.c @@ -797,6 +797,22 @@ int qemuMonitorGetDiskSecret(qemuMonitor } +int qemuMonitorGetTPMSecret(qemuMonitorPtr mon, + virConnectPtr conn, + const char *path, + char **secret, + size_t *secretLen) +{ + int ret = -1; + *secret = NULL; + *secretLen = 0; + + QEMU_MONITOR_CALLBACK(mon, ret, tpmSecretLookup, conn, mon->vm, + path, secret, secretLen); + return ret; +} + + int qemuMonitorEmitShutdown(qemuMonitorPtr mon) { int ret = -1; Index: libvirt-acl/src/qemu/qemu_monitor.h =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor.h +++ libvirt-acl/src/qemu/qemu_monitor.h @@ -81,6 +81,13 @@ struct _qemuMonitorCallbacks { char **secret, size_t *secretLen); + int (*tpmSecretLookup)(qemuMonitorPtr mon, + virConnectPtr conn, + virDomainObjPtr vm, + const char *path, + char **secret, + size_t *secretLen); + int (*domainShutdown)(qemuMonitorPtr mon, virDomainObjPtr vm); int (*domainReset)(qemuMonitorPtr mon, @@ -152,6 +159,12 @@ int qemuMonitorGetDiskSecret(qemuMonitor char **secret, size_t *secretLen); +int qemuMonitorGetTPMSecret(qemuMonitorPtr mon, + virConnectPtr conn, + const char *path, + char **secret, + size_t *secretLen); + int qemuMonitorEmitShutdown(qemuMonitorPtr mon); int qemuMonitorEmitReset(qemuMonitorPtr mon); int qemuMonitorEmitPowerdown(qemuMonitorPtr mon); Index: libvirt-acl/src/qemu/qemu_monitor_text.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor_text.c +++ libvirt-acl/src/qemu/qemu_monitor_text.c @@ -315,6 +315,12 @@ qemuMonitorSendDiskPassphrase(qemuMonito path, &passphrase, &passphrase_len); + if (res < 0) + res = qemuMonitorGetTPMSecret(mon, + conn, + path, + &passphrase, + &passphrase_len); VIR_FREE(path); if (res < 0) return -1; Index: libvirt-acl/src/qemu/qemu_driver.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_driver.c +++ libvirt-acl/src/qemu/qemu_driver.c @@ -187,6 +187,8 @@ qemuAutostartDomains(struct qemud_driver virConnectClose(conn); } + + static int qemuSecurityInit(struct qemud_driver *driver) { Index: libvirt-acl/src/conf/domain_conf.h =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.h +++ libvirt-acl/src/conf/domain_conf.h @@ -1024,6 +1024,7 @@ struct _virDomainTPMDef { union { struct { char *storage; + virStorageEncryptionPtr encryption; } builtin; } data; }; Index: libvirt-acl/src/conf/domain_conf.c =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.c +++ libvirt-acl/src/conf/domain_conf.c @@ -790,6 +790,7 @@ void virDomainTPMDefFree(virDomainTPMDef switch (def->type) { case VIR_DOMAIN_TPM_TYPE_BUILTIN: VIR_FREE(def->data.builtin.storage); + virStorageEncryptionFree(def->data.builtin.encryption); break; default: break; @@ -3513,7 +3514,12 @@ virDomainTPMGetStorageFilename(virDomain * The XML we're dealing with looks like * * <tpm type="built-in"> - * <storage file='path/to/QCoW2/state/file' /> + * <storage file='path_to_state_file' > + * <encryption format='qcow'> + * <secret type='passphrase' + * uuid='13ea49f7-2553-7308-5168-e337ade36232'/> + * </encryption> + * </storage> * </tpm> * The 'storage' node is optional. If none is provided, * libvirt is going to create the necessary storage using @@ -3522,12 +3528,13 @@ virDomainTPMGetStorageFilename(virDomain */ static virDomainTPMDefPtr virDomainTPMDefParseXML(xmlNodePtr node) { - xmlNodePtr cur; + xmlNodePtr cur, stor; char *type = NULL; char *path = NULL; virDomainTPMDefPtr def; char *tpmStateDir = NULL; int err; + virStorageEncryptionPtr encryption = NULL; if (VIR_ALLOC(def) < 0) { virReportOOMError(); @@ -3553,6 +3560,30 @@ virDomainTPMDefParseXML(xmlNodePtr node) default: break; } + stor = cur->children; + while (stor != NULL) { + if (stor->type == XML_ELEMENT_NODE) { + if (encryption == NULL && + xmlStrEqual(stor->name, BAD_CAST "encryption")) { + encryption = virStorageEncryptionParseNode( + cur->doc, + stor); + if (encryption == NULL) + goto error; + + if (encryption->format != + VIR_STORAGE_ENCRYPTION_FORMAT_QCOW) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("encryption format type must be " + "'qcow'")); + virStorageEncryptionFree(encryption); + encryption = NULL; + goto error; + } + } + } + stor = stor->next; + } } } cur = cur->next; @@ -3577,6 +3608,8 @@ virDomainTPMDefParseXML(xmlNodePtr node) case VIR_DOMAIN_TPM_TYPE_BUILTIN: def->data.builtin.storage = path; path = NULL; + def->data.builtin.encryption = encryption; + encryption = NULL; break; default: @@ -3587,6 +3620,7 @@ cleanup: VIR_FREE(type); VIR_FREE(path); VIR_FREE(tpmStateDir); + virStorageEncryptionFree(encryption); return def; @@ -7546,6 +7580,7 @@ virDomainTPMDefFormat(virBufferPtr buf, virDomainTPMDefPtr def, const char *name) { + virStorageEncryptionPtr enc; const char *type = virDomainTPMTypeToString(def->type); if (!type) { @@ -7558,9 +7593,21 @@ virDomainTPMDefFormat(virBufferPtr buf, name, type); switch (def->type) { case VIR_DOMAIN_TPM_TYPE_BUILTIN: + enc = def->data.builtin.encryption; + + virBufferVSprintf(buf, " <storage"); + if (def->data.builtin.storage) - virBufferEscapeString(buf, " <storage file='%s'/>\n", + virBufferEscapeString(buf, " file='%s'", def->data.builtin.storage); + + virBufferVSprintf(buf, "%s>\n", enc ? "" : "/"); + + if (enc) { + if (virStorageEncryptionFormat(buf, enc, 8) < 0) + return -1; + virBufferVSprintf( buf, " </storage>\n"); + } break; default: Index: libvirt-acl/src/qemu/qemu_command.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_command.c +++ libvirt-acl/src/qemu/qemu_command.c @@ -912,7 +912,8 @@ int qemuDomainPCIAddressSetNextAddr(qemu static int qemudTPMCreateBlockStore(const char *tpmstorefile, - const char *qemu_cmd) + const char *qemu_cmd, + bool encrypted) { int rc = 0; struct stat statbuf; @@ -920,12 +921,13 @@ static int qemudTPMCreateBlockStore(cons int status; char filesize[10]; unsigned int bssize; - const char *argv[] = { qemuimg, "create", "-f", "qcow2", tpmstorefile, + const char *argv[] = { qemuimg, "create", "-f", "qcow2", + "-o", "encryption", tpmstorefile, filesize, NULL}; if (stat(tpmstorefile, &statbuf) == -1 || statbuf.st_size == 0) { if (errno == ENOENT || statbuf.st_size == 0) { - /* determine size of qcow2 */ + /* determine necessary size; qemu -tpm ? tells us */ if (qemuCapsGetTPMBuiltinBSSize(qemu_cmd, &bssize) < 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, @@ -944,9 +946,16 @@ static int qemudTPMCreateBlockStore(cons argv[0] = qemuimg; snprintf(filesize, sizeof(filesize),"%dk", bssize); - VIR_DEBUG("Creating BS file %s of size %dkb\n", + VIR_DEBUG("Creating BS file %s of size %dkb; encrypted: %d\n", tpmstorefile, - bssize); + bssize, + encrypted); + + if (!encrypted) { + argv[4] = argv[6]; + argv[5] = argv[7]; + argv[6] = NULL; + } if (virRun(argv, &status) != 0 || status != 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, @@ -985,7 +994,10 @@ static char * qemudBuildCommandLineTPMDe rc = -1; goto err_exit; } - if (qemudTPMCreateBlockStore(tmp, qemu_cmd)) { + if (qemudTPMCreateBlockStore(tmp, qemu_cmd, + tpm->data.builtin.encryption + ? true + : false)) { rc = -1; goto err_exit; } Index: libvirt-acl/src/qemu/qemu_process.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_process.c +++ libvirt-acl/src/qemu/qemu_process.c @@ -297,6 +297,144 @@ cleanup: } +static virDomainTPMDefPtr +findDomainTPMByPath(virDomainObjPtr vm, + const char *path) +{ + virDomainTPMDefPtr res = vm->def->tpm; +#if 0 + char *tmp; + + if (res) { + tmp = virDomainTPMGetStorageFilename(res, vm->def->uuid); + if (!STREQ(tmp, path)) + res = NULL; + + VIR_FREE(tmp); + } +#else + (void)path; +#endif + + return res; +} + +static int +getTPMPassphrase(virConnectPtr conn, + virDomainTPMDefPtr tpm, + char **secretRet, + size_t *secretLen) +{ + virSecretPtr secret; + char *passphrase; + unsigned char *data; + size_t size; + int ret = -1; + virStorageEncryptionPtr enc; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + if (!tpm->data.builtin.encryption) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("tpm does not have any encryption information")); + return -1; + } + enc = tpm->data.builtin.encryption; + break; + + default: + return -1; + } + + if (!conn) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot find secrets without a connection")); + goto cleanup; + } + + if (conn->secretDriver == NULL || + conn->secretDriver->lookupByUUID == NULL || + conn->secretDriver->getValue == NULL) { + qemuReportError(VIR_ERR_NO_SUPPORT, "%s", + _("secret storage not supported")); + goto cleanup; + } + + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW || + enc->nsecrets != 1 || + enc->secrets[0]->type != + VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) { + qemuReportError(VIR_ERR_XML_ERROR, + _("invalid <encryption>")); + goto cleanup; + } + + secret = conn->secretDriver->lookupByUUID(conn, + enc->secrets[0]->uuid); + if (secret == NULL) + goto cleanup; + data = conn->secretDriver->getValue(secret, &size, + VIR_SECRET_GET_VALUE_INTERNAL_CALL); + virUnrefSecret(secret); + if (data == NULL) + goto cleanup; + + if (memchr(data, '\0', size) != NULL) { + memset(data, 0, size); + VIR_FREE(data); + qemuReportError(VIR_ERR_XML_ERROR, + _("format='qcow' passphrase for %s must not contain a " + "'\\0'"), "TPM"); + goto cleanup; + } + + if (VIR_ALLOC_N(passphrase, size + 1) < 0) { + memset(data, 0, size); + VIR_FREE(data); + virReportOOMError(); + goto cleanup; + } + memcpy(passphrase, data, size); + passphrase[size] = '\0'; + + memset(data, 0, size); + VIR_FREE(data); + + *secretRet = passphrase; + *secretLen = size; + + ret = 0; + +cleanup: + return ret; +} + +static int +qemuProcessFindTPMPassphrase(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + virConnectPtr conn, + virDomainObjPtr vm, + const char *path, + char **secretRet, + size_t *secretLen) +{ + virDomainTPMDefPtr tpm; + int ret = -1; + + virDomainObjLock(vm); + tpm = findDomainTPMByPath(vm, path); + + if (!tpm) + goto cleanup; + + ret = getTPMPassphrase(conn, tpm, secretRet, secretLen); + +cleanup: + virDomainObjUnlock(vm); + return ret; +} + + + static int qemuProcessHandleReset(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm) @@ -613,6 +751,7 @@ static qemuMonitorCallbacks monitorCallb .destroy = qemuProcessHandleMonitorDestroy, .eofNotify = qemuProcessHandleMonitorEOF, .diskSecretLookup = qemuProcessFindVolumeQcowPassphrase, + .tpmSecretLookup = qemuProcessFindTPMPassphrase, .domainShutdown = qemuProcessHandleShutdown, .domainStop = qemuProcessHandleStop, .domainReset = qemuProcessHandleReset, @@ -1268,6 +1407,43 @@ qemuProcessInitPasswords(virConnectPtr c } } + while (qemuCapsGet(qemuCaps, QEMU_CAPS_TPM)) { + virDomainTPMDefPtr tpm = vm->def->tpm; + virStorageEncryptionPtr enc = NULL; + char *secret; + size_t secretLen; + + if (!tpm) + break; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + enc = tpm->data.builtin.encryption; + break; + + default: + break; + } + + if (!enc) + break; + + if (getTPMPassphrase(conn, tpm, + &secret, &secretLen) < 0) + goto cleanup; + + qemuDomainObjEnterMonitorWithDriver(driver, vm); + ret = qemuMonitorSetDrivePassphrase(priv->mon, + "vtpm-nvram", + secret); + VIR_FREE(secret); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (ret < 0) + goto cleanup; + + break; + } + cleanup: return ret; } Index: libvirt-acl/docs/schemas/domain.rng =================================================================== --- libvirt-acl.orig/docs/schemas/domain.rng +++ libvirt-acl/docs/schemas/domain.rng @@ -1731,6 +1731,9 @@ <ref name="filePath"/> </attribute> </optional> + <optional> + <ref name="encryption"/> + </optional> </element> </define> <define name="tpm"> -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list