
On Tue, Feb 13, 2024 at 05:16:05PM +0100, Michal Privoznik wrote:
The systemd-cred offers a convenient way to talk to host's TPM. While its original intent is to be used in systemd unit files, it offers two additional commands: encrypt and decrypt that can be used independent of the rest of systemd. And these are the ones we need. They offer a convenient way to encrypt/decrypt plaintext using a key that's stored in host's TPM (when --with-key=tpm is passed).
This is one way we could do it, but what I don't like about this is that it ties the secret driver to system. I think it would be better to have an intermediate key, that is protected by credentials and use that for encryption of the individual secrets. Roughly speaking.... * Make virtsecretd accept a '--encryption-key /some/path' option * In ExecPreStart in virtsecretd.service, if no key already exists, we dd from /dev/random enough bytes for a AES256 key, and then use systemd-creds encrypt to store it * In ExecStart we set the --encryption-key arg to point to the credentials that systemd will decrypt * The secret driver can now use this master key to AES encrypt/decrypt individual secrets The benefit of this is that only encryption of the intermediate key is tied to the TPM. If someone needs to move everything to a new host, then can copy over all the secret data files, and then just re-seal the intermediate key against the new machine's TPM. The also lets people setup the --encryption-key for virtsecretd with some arbitrary non-systemd mechanism. eg could use clevis/tang, or $whatever. The secret driver does not need to know about systemd at all, all the credentials logic is done with a minimal tweak to the .service file.
In addition, there's 'has-tpm2' command which allows us to detect whether host and the OS have everything needed to utilize the TPM.
FWIW, systemd-creds does not require the use of a TPM2, it is quite happy running without one, so libvirt should not really describe this as TPM based, nor require a TPM. Without a TPM systemd just generates a random key and stores it in /var. That is less secure by default of course, but still reasonable, as in theory you could way to inject the master key to /var in some manner.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/libvirt_private.syms | 3 + src/util/virsecret.c | 170 +++++++++++++++++++++++++++++++++++++++ src/util/virsecret.h | 10 +++ 3 files changed, 183 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 396bd80844..1d1bba915a 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3362,6 +3362,9 @@ virSecretLookupDefClear; virSecretLookupDefCopy; virSecretLookupFormatSecret; virSecretLookupParseSecret; +virSecretTPMAvailable; +virSecretTPMDecrypt; +virSecretTPMEncrypt;
# util/virsecureerase.h diff --git a/src/util/virsecret.c b/src/util/virsecret.c index 8a220a37ec..11aa146b6d 100644 --- a/src/util/virsecret.c +++ b/src/util/virsecret.c @@ -23,10 +23,13 @@
#include "datatypes.h" #include "viralloc.h" +#include "vircommand.h" #include "virerror.h" #include "virlog.h" #include "virsecret.h" +#include "virutil.h" #include "viruuid.h" +#include "virfile.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -176,3 +179,170 @@ virSecretGetSecretString(virConnectPtr conn,
return 0; } + + +/** + * virSecretTPMAvailable: + * + * Checks whether systemd-creds is available and whether host supports + * TPM. Use this prior calling other virSecretTPM*() APIs. + * + * Returns: 1 in case TPM is available, + * 0 in case TPM is NOT available, + * -1 otherwise. + */ +int +virSecretTPMAvailable(void) +{ + g_autoptr(virCommand) cmd = NULL; + g_autofree char *out = NULL; + int exitstatus; + int rc; + + cmd = virCommandNewArgList("systemd-creds", "has-tpm2", NULL); + + virCommandSetOutputBuffer(cmd, &out); + + rc = virCommandRun(cmd, &exitstatus); + + if (rc < 0) { + /* systemd-creds not available */ + return -1; + } + + if (!out || !*out) { + /* systemd-creds reported nothing. Ouch. */ + return 0; + } + + /* systemd-creds can report one of these: + * + * yes - TPM is available and recognized by FW, kernel, etc. + * no - TPM is not available or not recognized + * partial - otherwise + */ + + if (STRPREFIX(out, "yes\n")) + return 1; + + return 0; +} + + +/** + * virSecretTPMEncrypt: + * @name: credential name + * @value: credential value + * @value_size: size of @value + * @base64: encrypted @value, base64 encoded + * + * Encrypts given plaintext (@value) with a secret key automatically + * derived from the system's TPM2 chip. Ciphertext is base64 encoded and + * stored into @base64. Pass the same @name to virSecretTPMDecrypt(). + * + * Returns: 0 on success, + * -1 otherwise (with error reported). + */ +int +virSecretTPMEncrypt(const char *name, + unsigned const char *value, + size_t value_size, + char **base64) +{ + g_autoptr(virCommand) cmd = NULL; + g_autofree char *errBuf = NULL; + int pipeFD[2] = { -1, -1 }; + int ret = -1; + + if (virPipe(pipeFD) < 0) + return -1; + + if (virSetCloseExec(pipeFD[1]) < 0) { + virReportSystemError(errno, "%s", + _("Unable to set cloexec flag")); + goto cleanup; + } + + cmd = virCommandNewArgList("systemd-creds", "encrypt", + "--with-key=tpm2", NULL); + virCommandAddArgFormat(cmd, "--name=%s", name); + virCommandAddArgList(cmd, "-", "-", NULL); + + virCommandSetInputFD(cmd, pipeFD[0]); + virCommandSetOutputBuffer(cmd, base64); + virCommandSetErrorBuffer(cmd, &errBuf); + virCommandDoAsyncIO(cmd); + + if (virCommandRunAsync(cmd, NULL) < 0) + goto cleanup; + + if (safewrite(pipeFD[1], value, value_size) != value_size) { + virReportSystemError(errno, "%s", + _("Unable to pass secret value to systemd-cred")); + goto cleanup; + } + + if (VIR_CLOSE(pipeFD[1]) < 0) { + virReportSystemError(errno, "%s", _("unable to close pipe")); + goto cleanup; + } + + if (virCommandWait(cmd, NULL) < 0) { + if (errBuf && *errBuf) { + VIR_WARN("systemd-creds reported: %s", errBuf); + } + goto cleanup; + } + + ret = 0; + cleanup: + virCommandAbort(cmd); + VIR_FORCE_CLOSE(pipeFD[0]); + VIR_FORCE_CLOSE(pipeFD[1]); + return ret; +} + + +/** + * virSecretTPMDecrypt: + * @name: credential name + * @base64: encrypted @value, base64 encoded + * @value: credential value, + * @value_size: size of @value + * + * Decrypts given ciphertext, base64 encoded (@base64) and stores + * plaintext into @value and its size into @value_size. + * + * Returns: 0 on success, + * -1 otherwise (with error reported). + */ +int +virSecretTPMDecrypt(const char *name, + const char *base64, + unsigned char **value, + size_t *value_size) +{ + g_autoptr(virCommand) cmd = NULL; + g_autofree char *out = NULL; + g_autofree char *errBuf = NULL; + + cmd = virCommandNewArgList("systemd-creds", "decrypt", + "--transcode=base64", NULL); + virCommandAddArgFormat(cmd, "--name=%s", name); + virCommandAddArgList(cmd, "-", "-", NULL); + + virCommandSetInputBuffer(cmd, base64); + virCommandSetOutputBuffer(cmd, &out); + virCommandSetErrorBuffer(cmd, &errBuf); + + if (virCommandRun(cmd, NULL) < 0) { + if (errBuf && *errBuf) { + VIR_WARN("systemd-creds reported: %s", errBuf); + } + return -1; + } + + *value = g_base64_decode(out, value_size); + + return 0; +} diff --git a/src/util/virsecret.h b/src/util/virsecret.h index c803f0fe33..a5ca764bb7 100644 --- a/src/util/virsecret.h +++ b/src/util/virsecret.h @@ -62,3 +62,13 @@ int virSecretGetSecretString(virConnectPtr conn, size_t *ret_secret_size) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) G_GNUC_WARN_UNUSED_RESULT; + +int virSecretTPMAvailable(void); +int virSecretTPMEncrypt(const char *name, + unsigned const char *value, + size_t value_size, + char **base64); +int virSecretTPMDecrypt(const char *name, + const char *base64, + unsigned char **value, + size_t *value_size); -- 2.43.0 _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|