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 <eblake@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2015-10-14 09:58:38 +01:00
parent 920639cab0
commit ac1d887849
8 changed files with 1208 additions and 0 deletions

View File

@ -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

513
crypto/secret.c Normal file
View File

@ -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);

148
include/crypto/secret.h Normal file
View File

@ -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__ */

View File

@ -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']}

View File

@ -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

1
tests/.gitignore vendored
View File

@ -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/

View File

@ -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)

452
tests/test-crypto-secret.c Normal file
View File

@ -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();
}