From ac1d88784907c9603b3849b2c3043259f75ed2a5 Mon Sep 17 00:00:00 2001 From: Daniel P. Berrange Date: Wed, 14 Oct 2015 09:58:38 +0100 Subject: crypto: add QCryptoSecret object class for password/key handling Introduce a new QCryptoSecret object class which will be used for providing passwords and keys to other objects which need sensitive credentials. The new object can provide secret values directly as properties, or indirectly via a file. The latter includes support for file descriptor passing syntax on UNIX platforms. Ordinarily passing secret values directly as properties is insecure, since they are visible in process listings, or in log files showing the CLI args / QMP commands. It is possible to use AES-256-CBC to encrypt the secret values though, in which case all that is visible is the ciphertext. For ad hoc developer testing though, it is fine to provide the secrets directly without encryption so this is not explicitly forbidden. The anticipated scenario is that libvirtd will create a random master key per QEMU instance (eg /var/run/libvirt/qemu/$VMNAME.key) and will use that key to encrypt all passwords it provides to QEMU via '-object secret,....'. This avoids the need for libvirt (or other mgmt apps) to worry about file descriptor passing. It also makes life easier for people who are scripting the management of QEMU, for whom FD passing is significantly more complex. Providing data inline (insecure, only for ad hoc dev testing) $QEMU -object secret,id=sec0,data=letmein Providing data indirectly in raw format printf "letmein" > mypasswd.txt $QEMU -object secret,id=sec0,file=mypasswd.txt Providing data indirectly in base64 format $QEMU -object secret,id=sec0,file=mykey.b64,format=base64 Providing data with encryption $QEMU -object secret,id=master0,file=mykey.b64,format=base64 \ -object secret,id=sec0,data=[base64 ciphertext],\ keyid=master0,iv=[base64 IV],format=base64 Note that 'format' here refers to the format of the ciphertext data. The decrypted data must always be in raw byte format. More examples are shown in the updated docs. Reviewed-by: Eric Blake Signed-off-by: Daniel P. Berrange --- include/crypto/secret.h | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 include/crypto/secret.h (limited to 'include/crypto') diff --git a/include/crypto/secret.h b/include/crypto/secret.h new file mode 100644 index 0000000000..913519ae27 --- /dev/null +++ b/include/crypto/secret.h @@ -0,0 +1,148 @@ +/* + * QEMU crypto secret support + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#ifndef QCRYPTO_SECRET_H__ +#define QCRYPTO_SECRET_H__ + +#include "qemu-common.h" +#include "qapi/error.h" +#include "qom/object.h" + +#define TYPE_QCRYPTO_SECRET "secret" +#define QCRYPTO_SECRET(obj) \ + OBJECT_CHECK(QCryptoSecret, (obj), TYPE_QCRYPTO_SECRET) + +typedef struct QCryptoSecret QCryptoSecret; +typedef struct QCryptoSecretClass QCryptoSecretClass; + +/** + * QCryptoSecret: + * + * The QCryptoSecret object provides storage of secrets, + * which may be user passwords, encryption keys or any + * other kind of sensitive data that is represented as + * a sequence of bytes. + * + * The sensitive data associated with the secret can + * be provided directly via the 'data' property, or + * indirectly via the 'file' property. In the latter + * case there is support for file descriptor passing + * via the usual /dev/fdset/NN syntax that QEMU uses. + * + * The data for a secret can be provided in two formats, + * either as a UTF-8 string (the default), or as base64 + * encoded 8-bit binary data. The latter is appropriate + * for raw encryption keys, while the former is appropriate + * for user entered passwords. + * + * The data may be optionally encrypted with AES-256-CBC, + * and the decryption key provided by another + * QCryptoSecret instance identified by the 'keyid' + * property. When passing sensitive data directly + * via the 'data' property it is strongly recommended + * to use the AES encryption facility to prevent the + * sensitive data being exposed in the process listing + * or system log files. + * + * Providing data directly, insecurely (suitable for + * ad hoc developer testing only) + * + * $QEMU -object secret,id=sec0,data=letmein + * + * Providing data indirectly: + * + * # printf "letmein" > password.txt + * # $QEMU \ + * -object secret,id=sec0,file=password.txt + * + * Using a master encryption key with data. + * + * The master key needs to be created as 32 secure + * random bytes (optionally base64 encoded) + * + * # openssl rand -base64 32 > key.b64 + * # KEY=$(base64 -d key.b64 | hexdump -v -e '/1 "%02X"') + * + * Each secret to be encrypted needs to have a random + * initialization vector generated. These do not need + * to be kept secret + * + * # openssl rand -base64 16 > iv.b64 + * # IV=$(base64 -d iv.b64 | hexdump -v -e '/1 "%02X"') + * + * A secret to be defined can now be encrypted + * + * # SECRET=$(printf "letmein" | + * openssl enc -aes-256-cbc -a -K $KEY -iv $IV) + * + * When launching QEMU, create a master secret pointing + * to key.b64 and specify that to be used to decrypt + * the user password + * + * # $QEMU \ + * -object secret,id=secmaster0,format=base64,file=key.b64 \ + * -object secret,id=sec0,keyid=secmaster0,format=base64,\ + * data=$SECRET,iv=$( mypasswd.txt # $QEMU \ -object secret,id=sec0,filename=mypasswd.txt \ -object tls-creds-x509,passwordid=sec0,id=creds0,\ dir=/home/berrange/.pki/qemu,endpoint=server \ -vnc :1,tls-creds=creds0 This requires QEMU to be linked to GNUTLS >= 3.1.11. If GNUTLS is too old an error will be reported if an attempt is made to pass a decryption password. Reviewed-by: Eric Blake Signed-off-by: Daniel P. Berrange --- crypto/tlscredsx509.c | 48 +++++++++++++++++++++++++++++++++++++++++++ include/crypto/tlscredsx509.h | 1 + qemu-options.hx | 8 +++++++- 3 files changed, 56 insertions(+), 1 deletion(-) (limited to 'include/crypto') diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c index 26f18cbb4a..d58fdea347 100644 --- a/crypto/tlscredsx509.c +++ b/crypto/tlscredsx509.c @@ -20,6 +20,7 @@ #include "crypto/tlscredsx509.h" #include "crypto/tlscredspriv.h" +#include "crypto/secret.h" #include "qom/object_interfaces.h" #include "trace.h" @@ -607,9 +608,30 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, } if (cert != NULL && key != NULL) { +#if GNUTLS_VERSION_NUMBER >= 0x030111 + char *password = NULL; + if (creds->passwordid) { + password = qcrypto_secret_lookup_as_utf8(creds->passwordid, + errp); + if (!password) { + goto cleanup; + } + } + ret = gnutls_certificate_set_x509_key_file2(creds->data, + cert, key, + GNUTLS_X509_FMT_PEM, + password, + 0); + g_free(password); +#else /* GNUTLS_VERSION_NUMBER < 0x030111 */ + if (creds->passwordid) { + error_setg(errp, "PKCS8 decryption requires GNUTLS >= 3.1.11"); + goto cleanup; + } ret = gnutls_certificate_set_x509_key_file(creds->data, cert, key, GNUTLS_X509_FMT_PEM); +#endif /* GNUTLS_VERSION_NUMBER < 0x030111 */ if (ret < 0) { error_setg(errp, "Cannot load certificate '%s' & key '%s': %s", cert, key, gnutls_strerror(ret)); @@ -737,6 +759,27 @@ qcrypto_tls_creds_x509_prop_set_sanity(Object *obj, } +static void +qcrypto_tls_creds_x509_prop_set_passwordid(Object *obj, + const char *value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->passwordid = g_strdup(value); +} + + +static char * +qcrypto_tls_creds_x509_prop_get_passwordid(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return g_strdup(creds->passwordid); +} + + static bool qcrypto_tls_creds_x509_prop_get_sanity(Object *obj, Error **errp G_GNUC_UNUSED) @@ -769,6 +812,10 @@ qcrypto_tls_creds_x509_init(Object *obj) qcrypto_tls_creds_x509_prop_get_sanity, qcrypto_tls_creds_x509_prop_set_sanity, NULL); + object_property_add_str(obj, "passwordid", + qcrypto_tls_creds_x509_prop_get_passwordid, + qcrypto_tls_creds_x509_prop_set_passwordid, + NULL); } @@ -777,6 +824,7 @@ qcrypto_tls_creds_x509_finalize(Object *obj) { QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + g_free(creds->passwordid); qcrypto_tls_creds_x509_unload(creds); } diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h index b9785fddcf..25796d7de4 100644 --- a/include/crypto/tlscredsx509.h +++ b/include/crypto/tlscredsx509.h @@ -101,6 +101,7 @@ struct QCryptoTLSCredsX509 { gnutls_certificate_credentials_t data; #endif bool sanityCheck; + char *passwordid; }; diff --git a/qemu-options.hx b/qemu-options.hx index f37a2eba02..49afe6cd3b 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3627,7 +3627,7 @@ expensive operation that consumes random pool entropy, so it is recommended that a persistent set of parameters be generated upfront and saved. -@item -object tls-creds-x509,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off} +@item -object tls-creds-x509,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off},passwordid=@var{id} Creates a TLS anonymous credentials object, which can be used to provide TLS support on network backends. The @option{id} parameter is a unique @@ -3654,6 +3654,12 @@ in PEM format, in filenames @var{ca-cert.pem}, @var{ca-crl.pem} (optional), @var{server-cert.pem} (only servers), @var{server-key.pem} (only servers), @var{client-cert.pem} (only clients), and @var{client-key.pem} (only clients). +For the @var{server-key.pem} and @var{client-key.pem} files which +contain sensitive private keys, it is possible to use an encrypted +version by providing the @var{passwordid} parameter. This provides +the ID of a previously created @code{secret} object containing the +password for decryption. + @item -object filter-buffer,id=@var{id},netdev=@var{netdevid},interval=@var{t}[,queue=@var{all|rx|tx}] Interval @var{t} can't be 0, this filter batches the packet delivery: all -- cgit v1.2.3-55-g7522