diff options
-rw-r--r-- | crypto/Makefile.objs | 1 | ||||
-rw-r--r-- | crypto/secret.c | 513 | ||||
-rw-r--r-- | include/crypto/secret.h | 148 | ||||
-rw-r--r-- | qapi/crypto.json | 14 | ||||
-rw-r--r-- | qemu-options.hx | 77 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefile | 2 | ||||
-rw-r--r-- | tests/test-crypto-secret.c | 452 |
8 files changed, 1208 insertions, 0 deletions
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs index b2a0e0b..a3135f1 100644 --- a/crypto/Makefile.objs +++ b/crypto/Makefile.objs @@ -7,6 +7,7 @@ crypto-obj-y += tlscreds.o crypto-obj-y += tlscredsanon.o crypto-obj-y += tlscredsx509.o crypto-obj-y += tlssession.o +crypto-obj-y += secret.o # Let the userspace emulators avoid linking gnutls/etc crypto-aes-obj-y = aes.o diff --git a/crypto/secret.c b/crypto/secret.c new file mode 100644 index 0000000..9a9257a --- /dev/null +++ b/crypto/secret.c @@ -0,0 +1,513 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "crypto/secret.h" +#include "crypto/cipher.h" +#include "qom/object_interfaces.h" +#include "qemu/base64.h" +#include "trace.h" + + +static void +qcrypto_secret_load_data(QCryptoSecret *secret, + uint8_t **output, + size_t *outputlen, + Error **errp) +{ + char *data = NULL; + size_t length = 0; + GError *gerr = NULL; + + *output = NULL; + *outputlen = 0; + + if (secret->file) { + if (secret->data) { + error_setg(errp, + "'file' and 'data' are mutually exclusive"); + return; + } + if (!g_file_get_contents(secret->file, &data, &length, &gerr)) { + error_setg(errp, + "Unable to read %s: %s", + secret->file, gerr->message); + g_error_free(gerr); + return; + } + *output = (uint8_t *)data; + *outputlen = length; + } else if (secret->data) { + *outputlen = strlen(secret->data); + *output = (uint8_t *)g_strdup(secret->data); + } else { + error_setg(errp, "Either 'file' or 'data' must be provided"); + } +} + + +static void qcrypto_secret_decrypt(QCryptoSecret *secret, + const uint8_t *input, + size_t inputlen, + uint8_t **output, + size_t *outputlen, + Error **errp) +{ + uint8_t *key = NULL, *ciphertext = NULL, *iv = NULL; + size_t keylen, ciphertextlen, ivlen; + QCryptoCipher *aes = NULL; + uint8_t *plaintext = NULL; + + *output = NULL; + *outputlen = 0; + + if (qcrypto_secret_lookup(secret->keyid, + &key, &keylen, + errp) < 0) { + goto cleanup; + } + + if (keylen != 32) { + error_setg(errp, "Key should be 32 bytes in length"); + goto cleanup; + } + + if (!secret->iv) { + error_setg(errp, "IV is required to decrypt secret"); + goto cleanup; + } + + iv = qbase64_decode(secret->iv, -1, &ivlen, errp); + if (!iv) { + goto cleanup; + } + if (ivlen != 16) { + error_setg(errp, "IV should be 16 bytes in length not %zu", + ivlen); + goto cleanup; + } + + aes = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_256, + QCRYPTO_CIPHER_MODE_CBC, + key, keylen, + errp); + if (!aes) { + goto cleanup; + } + + if (qcrypto_cipher_setiv(aes, iv, ivlen, errp) < 0) { + goto cleanup; + } + + if (secret->format == QCRYPTO_SECRET_FORMAT_BASE64) { + ciphertext = qbase64_decode((const gchar*)input, + inputlen, + &ciphertextlen, + errp); + if (!ciphertext) { + goto cleanup; + } + plaintext = g_new0(uint8_t, ciphertextlen + 1); + } else { + ciphertextlen = inputlen; + plaintext = g_new0(uint8_t, inputlen + 1); + } + if (qcrypto_cipher_decrypt(aes, + ciphertext ? ciphertext : input, + plaintext, + ciphertextlen, + errp) < 0) { + plaintext = NULL; + goto cleanup; + } + + if (plaintext[ciphertextlen - 1] > 16 || + plaintext[ciphertextlen - 1] > ciphertextlen) { + error_setg(errp, "Incorrect number of padding bytes (%d) " + "found on decrypted data", + (int)plaintext[ciphertextlen - 1]); + g_free(plaintext); + plaintext = NULL; + goto cleanup; + } + + /* Even though plaintext may contain arbitrary NUL + * ensure it is explicitly NUL terminated. + */ + ciphertextlen -= plaintext[ciphertextlen - 1]; + plaintext[ciphertextlen] = '\0'; + + *output = plaintext; + *outputlen = ciphertextlen; + + cleanup: + g_free(ciphertext); + g_free(iv); + g_free(key); + qcrypto_cipher_free(aes); +} + + +static void qcrypto_secret_decode(const uint8_t *input, + size_t inputlen, + uint8_t **output, + size_t *outputlen, + Error **errp) +{ + *output = qbase64_decode((const gchar*)input, + inputlen, + outputlen, + errp); +} + + +static void +qcrypto_secret_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + if (value) { + Error *local_err = NULL; + uint8_t *input = NULL; + size_t inputlen = 0; + uint8_t *output = NULL; + size_t outputlen = 0; + + qcrypto_secret_load_data(secret, &input, &inputlen, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + if (secret->keyid) { + qcrypto_secret_decrypt(secret, input, inputlen, + &output, &outputlen, &local_err); + g_free(input); + if (local_err) { + error_propagate(errp, local_err); + return; + } + input = output; + inputlen = outputlen; + } else { + if (secret->format != QCRYPTO_SECRET_FORMAT_RAW) { + qcrypto_secret_decode(input, inputlen, + &output, &outputlen, &local_err); + g_free(input); + if (local_err) { + error_propagate(errp, local_err); + return; + } + input = output; + inputlen = outputlen; + } + } + + secret->rawdata = input; + secret->rawlen = inputlen; + } else { + g_free(secret->rawdata); + secret->rawlen = 0; + } +} + + +static bool +qcrypto_secret_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + return secret->data != NULL; +} + + +static void +qcrypto_secret_prop_set_format(Object *obj, + int value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoSecret *creds = QCRYPTO_SECRET(obj); + + creds->format = value; +} + + +static int +qcrypto_secret_prop_get_format(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoSecret *creds = QCRYPTO_SECRET(obj); + + return creds->format; +} + + +static void +qcrypto_secret_prop_set_data(Object *obj, + const char *value, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + g_free(secret->data); + secret->data = g_strdup(value); +} + + +static char * +qcrypto_secret_prop_get_data(Object *obj, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + return g_strdup(secret->data); +} + + +static void +qcrypto_secret_prop_set_file(Object *obj, + const char *value, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + g_free(secret->file); + secret->file = g_strdup(value); +} + + +static char * +qcrypto_secret_prop_get_file(Object *obj, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + return g_strdup(secret->file); +} + + +static void +qcrypto_secret_prop_set_iv(Object *obj, + const char *value, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + g_free(secret->iv); + secret->iv = g_strdup(value); +} + + +static char * +qcrypto_secret_prop_get_iv(Object *obj, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + return g_strdup(secret->iv); +} + + +static void +qcrypto_secret_prop_set_keyid(Object *obj, + const char *value, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + g_free(secret->keyid); + secret->keyid = g_strdup(value); +} + + +static char * +qcrypto_secret_prop_get_keyid(Object *obj, + Error **errp) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + return g_strdup(secret->keyid); +} + + +static void +qcrypto_secret_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_secret_init(Object *obj) +{ + object_property_add_bool(obj, "loaded", + qcrypto_secret_prop_get_loaded, + qcrypto_secret_prop_set_loaded, + NULL); + object_property_add_enum(obj, "format", + "QCryptoSecretFormat", + QCryptoSecretFormat_lookup, + qcrypto_secret_prop_get_format, + qcrypto_secret_prop_set_format, + NULL); + object_property_add_str(obj, "data", + qcrypto_secret_prop_get_data, + qcrypto_secret_prop_set_data, + NULL); + object_property_add_str(obj, "file", + qcrypto_secret_prop_get_file, + qcrypto_secret_prop_set_file, + NULL); + object_property_add_str(obj, "keyid", + qcrypto_secret_prop_get_keyid, + qcrypto_secret_prop_set_keyid, + NULL); + object_property_add_str(obj, "iv", + qcrypto_secret_prop_get_iv, + qcrypto_secret_prop_set_iv, + NULL); +} + + +static void +qcrypto_secret_finalize(Object *obj) +{ + QCryptoSecret *secret = QCRYPTO_SECRET(obj); + + g_free(secret->iv); + g_free(secret->file); + g_free(secret->keyid); + g_free(secret->rawdata); + g_free(secret->data); +} + +static void +qcrypto_secret_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_secret_complete; +} + + +int qcrypto_secret_lookup(const char *secretid, + uint8_t **data, + size_t *datalen, + Error **errp) +{ + Object *obj; + QCryptoSecret *secret; + + obj = object_resolve_path_component( + object_get_objects_root(), secretid); + if (!obj) { + error_setg(errp, "No secret with id '%s'", secretid); + return -1; + } + + secret = (QCryptoSecret *) + object_dynamic_cast(obj, + TYPE_QCRYPTO_SECRET); + if (!secret) { + error_setg(errp, "Object with id '%s' is not a secret", + secretid); + return -1; + } + + if (!secret->rawdata) { + error_setg(errp, "Secret with id '%s' has no data", + secretid); + return -1; + } + + *data = g_new0(uint8, secret->rawlen + 1); + memcpy(*data, secret->rawdata, secret->rawlen); + (*data)[secret->rawlen] = '\0'; + *datalen = secret->rawlen; + + return 0; +} + + +char *qcrypto_secret_lookup_as_utf8(const char *secretid, + Error **errp) +{ + uint8_t *data; + size_t datalen; + + if (qcrypto_secret_lookup(secretid, + &data, + &datalen, + errp) < 0) { + return NULL; + } + + if (!g_utf8_validate((const gchar*)data, datalen, NULL)) { + error_setg(errp, + "Data from secret %s is not valid UTF-8", + secretid); + g_free(data); + return NULL; + } + + return (char *)data; +} + + +char *qcrypto_secret_lookup_as_base64(const char *secretid, + Error **errp) +{ + uint8_t *data; + size_t datalen; + char *ret; + + if (qcrypto_secret_lookup(secretid, + &data, + &datalen, + errp) < 0) { + return NULL; + } + + ret = g_base64_encode(data, datalen); + g_free(data); + return ret; +} + + +static const TypeInfo qcrypto_secret_info = { + .parent = TYPE_OBJECT, + .name = TYPE_QCRYPTO_SECRET, + .instance_size = sizeof(QCryptoSecret), + .instance_init = qcrypto_secret_init, + .instance_finalize = qcrypto_secret_finalize, + .class_size = sizeof(QCryptoSecretClass), + .class_init = qcrypto_secret_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_secret_register_types(void) +{ + type_register_static(&qcrypto_secret_info); +} + + +type_init(qcrypto_secret_register_types); diff --git a/include/crypto/secret.h b/include/crypto/secret.h new file mode 100644 index 0000000..913519a --- /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 <http://www.gnu.org/licenses/>. + * + */ + +#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=$(<iv.b64) + * + * When encrypting, the data can still be provided via an + * external file, in which case it is possible to use either + * raw binary data, or base64 encoded. This example uses + * raw format + * + * # printf "letmein" | + * openssl enc -aes-256-cbc -K $KEY -iv $IV -o pw.aes + * # $QEMU \ + * -object secret,id=secmaster0,format=base64,file=key.b64 \ + * -object secret,id=sec0,keyid=secmaster0,\ + * file=pw.aes,iv=$(<iv.b64) + * + * Note that the ciphertext can be in either raw or base64 + * format, as indicated by the 'format' parameter, but the + * plaintext resulting from decryption is expected to always + * be in raw format. + */ + +struct QCryptoSecret { + Object parent_obj; + uint8_t *rawdata; + size_t rawlen; + QCryptoSecretFormat format; + char *data; + char *file; + char *keyid; + char *iv; +}; + + +struct QCryptoSecretClass { + ObjectClass parent_class; +}; + + +extern int qcrypto_secret_lookup(const char *secretid, + uint8_t **data, + size_t *datalen, + Error **errp); +extern char *qcrypto_secret_lookup_as_utf8(const char *secretid, + Error **errp); +extern char *qcrypto_secret_lookup_as_base64(const char *secretid, + Error **errp); + +#endif /* QCRYPTO_SECRET_H__ */ diff --git a/qapi/crypto.json b/qapi/crypto.json index b058b14..4012659 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -19,3 +19,17 @@ { 'enum': 'QCryptoTLSCredsEndpoint', 'prefix': 'QCRYPTO_TLS_CREDS_ENDPOINT', 'data': ['client', 'server']} + + +## +# QCryptoSecretFormat: +# +# The data format that the secret is provided in +# +# @raw: raw bytes. When encoded in JSON only valid UTF-8 sequences can be used +# @base64: arbitrary base64 encoded binary data +# Since: 2.6 +## +{ 'enum': 'QCryptoSecretFormat', + 'prefix': 'QCRYPTO_SECRET_FORMAT', + 'data': ['raw', 'base64']} diff --git a/qemu-options.hx b/qemu-options.hx index 5affc82e..f37a2eb 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3678,6 +3678,83 @@ Dump the network traffic on netdev @var{dev} to the file specified by The file format is libpcap, so it can be analyzed with tools such as tcpdump or Wireshark. +@item -object secret,id=@var{id},data=@var{string},format=@var{raw|base64}[,keyid=@var{secretid},iv=@var{string}] +@item -object secret,id=@var{id},file=@var{filename},format=@var{raw|base64}[,keyid=@var{secretid},iv=@var{string}] + +Defines a secret to store a password, encryption key, or some other sensitive +data. The sensitive data can either be passed directly via the @var{data} +parameter, or indirectly via the @var{file} parameter. Using the @var{data} +parameter is insecure unless the sensitive data is encrypted. + +The sensitive data can be provided in raw format (the default), or base64. +When encoded as JSON, the raw format only supports valid UTF-8 characters, +so base64 is recommended for sending binary data. QEMU will convert from +which ever format is provided to the format it needs internally. eg, an +RBD password can be provided in raw format, even though it will be base64 +encoded when passed onto the RBD sever. + +For added protection, it is possible to encrypt the data associated with +a secret using the AES-256-CBC cipher. Use of encryption is indicated +by providing the @var{keyid} and @var{iv} parameters. The @var{keyid} +parameter provides the ID of a previously defined secret that contains +the AES-256 decryption key. This key should be 32-bytes long and be +base64 encoded. The @var{iv} parameter provides the random initialization +vector used for encryption of this particular secret and should be a +base64 encrypted string of the 32-byte IV. + +The simplest (insecure) usage is to provide the secret inline + +@example + + # $QEMU -object secret,id=sec0,data=letmein,format=raw + +@end example + +The simplest secure usage is to provide the secret via a file + + # echo -n "letmein" > mypasswd.txt + # $QEMU -object secret,id=sec0,file=mypasswd.txt,format=raw + +For greater security, AES-256-CBC should be used. To illustrate usage, +consider the openssl command line tool which can encrypt the data. Note +that when encrypting, the plaintext must be padded to the cipher block +size (32 bytes) using the standard PKCS#5/6 compatible padding algorithm. + +First a master key needs to be created in base64 encoding: + +@example + # openssl rand -base64 32 > key.b64 + # KEY=$(base64 -d key.b64 | hexdump -v -e '/1 "%02X"') +@end example + +Each secret to be encrypted needs to have a random initialization vector +generated. These do not need to be kept secret + +@example + # openssl rand -base64 16 > iv.b64 + # IV=$(base64 -d iv.b64 | hexdump -v -e '/1 "%02X"') +@end example + +The secret to be defined can now be encrypted, in this case we're +telling openssl to base64 encode the result, but it could be left +as raw bytes if desired. + +@example + # SECRET=$(echo -n "letmein" | + openssl enc -aes-256-cbc -a -K $KEY -iv $IV) +@end example + +When launching QEMU, create a master secret pointing to @code{key.b64} +and specify that to be used to decrypt the user password. Pass the +contents of @code{iv.b64} to the second secret + +@example + # $QEMU \ + -object secret,id=secmaster0,format=base64,file=key.b64 \ + -object secret,id=sec0,keyid=secmaster0,format=base64,\ + data=$SECRET,iv=$(<iv.b64) +@end example + @end table ETEXI diff --git a/tests/.gitignore b/tests/.gitignore index 596fef3..787c95c 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -14,6 +14,7 @@ test-blockjob-txn test-coroutine test-crypto-cipher test-crypto-hash +test-crypto-secret test-crypto-tlscredsx509 test-crypto-tlscredsx509-work/ test-crypto-tlscredsx509-certs/ diff --git a/tests/Makefile b/tests/Makefile index 3ae57b8..7d2a0d3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -80,6 +80,7 @@ check-unit-y += tests/test-write-threshold$(EXESUF) gcov-files-test-write-threshold-y = block/write-threshold.c check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF) check-unit-y += tests/test-crypto-cipher$(EXESUF) +check-unit-y += tests/test-crypto-secret$(EXESUF) check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF) check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF) check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF) @@ -467,6 +468,7 @@ tests/test-mul64$(EXESUF): tests/test-mul64.o $(test-util-obj-y) tests/test-bitops$(EXESUF): tests/test-bitops.o $(test-util-obj-y) tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(test-crypto-obj-y) tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(test-crypto-obj-y) +tests/test-crypto-secret$(EXESUF): tests/test-crypto-secret.o $(test-crypto-obj-y) tests/crypto-tls-x509-helpers.o-cflags := $(TASN1_CFLAGS) tests/crypto-tls-x509-helpers.o-libs := $(TASN1_LIBS) diff --git a/tests/test-crypto-secret.c b/tests/test-crypto-secret.c new file mode 100644 index 0000000..a49c8e3 --- /dev/null +++ b/tests/test-crypto-secret.c @@ -0,0 +1,452 @@ +/* + * QEMU Crypto secret handling + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <glib.h> + +#include "crypto/init.h" +#include "crypto/secret.h" + +static void test_secret_direct(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "123456", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + g_free(pw); +} + + +static void test_secret_indirect_good(void) +{ + Object *sec; + char *fname = NULL; + int fd = g_file_open_tmp("secretXXXXXX", + &fname, + NULL); + + g_assert(fd >= 0); + g_assert_nonnull(fname); + + g_assert(write(fd, "123456", 6) == 6); + + sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "file", fname, + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + g_free(pw); + close(fd); + g_free(fname); +} + + +static void test_secret_indirect_badfile(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "file", "does-not-exist", + NULL); + + g_assert(sec == NULL); +} + + +static void test_secret_indirect_emptyfile(void) +{ + Object *sec; + char *fname = NULL; + int fd = g_file_open_tmp("secretXXXXXX", + &fname, + NULL); + + g_assert(fd >= 0); + g_assert_nonnull(fname); + + sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "file", fname, + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, ""); + + object_unparent(sec); + g_free(pw); + close(fd); + g_free(fname); +} + + +static void test_secret_noconv_base64_good(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "MTIzNDU2", + "format", "base64", + NULL); + + char *pw = qcrypto_secret_lookup_as_base64("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "MTIzNDU2"); + + object_unparent(sec); + g_free(pw); +} + + +static void test_secret_noconv_base64_bad(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "data", "MTI$NDU2", + "format", "base64", + NULL); + + g_assert(sec == NULL); +} + + +static void test_secret_noconv_utf8(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "123456", + "format", "raw", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + g_free(pw); +} + + +static void test_secret_conv_base64_utf8valid(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "MTIzNDU2", + "format", "base64", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + g_free(pw); +} + + +static void test_secret_conv_base64_utf8invalid(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "f0VMRgIBAQAAAA==", + "format", "base64", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + NULL); + g_assert(pw == NULL); + + object_unparent(sec); +} + + +static void test_secret_conv_utf8_base64(void) +{ + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "123456", + NULL); + + char *pw = qcrypto_secret_lookup_as_base64("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "MTIzNDU2"); + + object_unparent(sec); + g_free(pw); +} + + +static void test_secret_crypt_raw(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", + "\xCC\xBF\xF7\x09\x46\x19\x0B\x52\x2A\x3A\xB4\x6B\xCD\x7A\xB0\xB0", + "format", "raw", + "keyid", "master", + "iv", "0I7Gw/TKuA+Old2W2apQ3g==", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + object_unparent(master); + g_free(pw); +} + + +static void test_secret_crypt_base64(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + &error_abort, + "data", "zL/3CUYZC1IqOrRrzXqwsA==", + "format", "base64", + "keyid", "master", + "iv", "0I7Gw/TKuA+Old2W2apQ3g==", + NULL); + + char *pw = qcrypto_secret_lookup_as_utf8("sec0", + &error_abort); + + g_assert_cmpstr(pw, ==, "123456"); + + object_unparent(sec); + object_unparent(master); + g_free(pw); +} + + +static void test_secret_crypt_short_key(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVc", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "data", "zL/3CUYZC1IqOrRrzXqwsA==", + "format", "raw", + "keyid", "master", + "iv", "0I7Gw/TKuA+Old2W2apQ3g==", + NULL); + + g_assert(sec == NULL); + object_unparent(master); +} + + +static void test_secret_crypt_short_iv(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "data", "zL/3CUYZC1IqOrRrzXqwsA==", + "format", "raw", + "keyid", "master", + "iv", "0I7Gw/TKuA+Old2W2a", + NULL); + + g_assert(sec == NULL); + object_unparent(master); +} + + +static void test_secret_crypt_missing_iv(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "data", "zL/3CUYZC1IqOrRrzXqwsA==", + "format", "raw", + "keyid", "master", + NULL); + + g_assert(sec == NULL); + object_unparent(master); +} + + +static void test_secret_crypt_bad_iv(void) +{ + Object *master = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "master", + &error_abort, + "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=", + "format", "base64", + NULL); + Object *sec = object_new_with_props( + TYPE_QCRYPTO_SECRET, + object_get_objects_root(), + "sec0", + NULL, + "data", "zL/3CUYZC1IqOrRrzXqwsA==", + "format", "raw", + "keyid", "master", + "iv", "0I7Gw/TK$$uA+Old2W2a", + NULL); + + g_assert(sec == NULL); + object_unparent(master); +} + + +int main(int argc, char **argv) +{ + module_call_init(MODULE_INIT_QOM); + g_test_init(&argc, &argv, NULL); + + g_assert(qcrypto_init(NULL) == 0); + + g_test_add_func("/crypto/secret/direct", + test_secret_direct); + g_test_add_func("/crypto/secret/indirect/good", + test_secret_indirect_good); + g_test_add_func("/crypto/secret/indirect/badfile", + test_secret_indirect_badfile); + g_test_add_func("/crypto/secret/indirect/emptyfile", + test_secret_indirect_emptyfile); + + g_test_add_func("/crypto/secret/noconv/base64/good", + test_secret_noconv_base64_good); + g_test_add_func("/crypto/secret/noconv/base64/bad", + test_secret_noconv_base64_bad); + g_test_add_func("/crypto/secret/noconv/utf8", + test_secret_noconv_utf8); + g_test_add_func("/crypto/secret/conv/base64/utf8valid", + test_secret_conv_base64_utf8valid); + g_test_add_func("/crypto/secret/conv/base64/utf8invalid", + test_secret_conv_base64_utf8invalid); + g_test_add_func("/crypto/secret/conv/utf8/base64", + test_secret_conv_utf8_base64); + + g_test_add_func("/crypto/secret/crypt/raw", + test_secret_crypt_raw); + g_test_add_func("/crypto/secret/crypt/base64", + test_secret_crypt_base64); + g_test_add_func("/crypto/secret/crypt/shortkey", + test_secret_crypt_short_key); + g_test_add_func("/crypto/secret/crypt/shortiv", + test_secret_crypt_short_iv); + g_test_add_func("/crypto/secret/crypt/missingiv", + test_secret_crypt_missing_iv); + g_test_add_func("/crypto/secret/crypt/badiv", + test_secret_crypt_bad_iv); + + return g_test_run(); +} |