Merge QCryptoSecret object support

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABCAAGBQJWdDmJAAoJEL6G67QVEE/fJ4EP+gNC4ErBDpbg+I4RhLHv/FsF
 i2iEYkmBfFzUmiB8iSFlJY12XJ/CPnbrWks0WNHIoarUgtGuvfqH91KGkERxJQFE
 TkD65EOhNHWlP1zaI5r5ZMizhOdO6EPa0pbS/QH/UCy5qwu5IbG5EesOw00d7nL8
 5gT39ehXAFljutRygWsa1JyOkDB04WNehVsly/l2t9v16aQUDJOC5mWVuoInrDEo
 ye1VG2Cx7Y1/FRo3fFCDwzYD+8jgxIBAu8Igwjk/95VbfBVl769PBAilQRc5zMHt
 //eMNdul6GooVKmu/K1JWkmjIZJFUiboEMgPJElWV1y8bWmhh++4J6EVb55owoDk
 VRv84cqiaYErVb+56gaImr92GSKezll0APWz6YlDsFZgPClCPnUjDSr39+23t1h4
 LprirtbkAjw73T92kuQ7kzbXElWm7rSfcx5u1/S6YPP+EDzZpW9+h62lKGGnuS2M
 bzwFOOmWHe1MhbRSh+BOzGBf1wWhMSCKgLAOmPuRQ8slS91vfE66bIlqpIKBGgfn
 42t0wZCEW8bqIe8xry5pC5UoDfm3cVDhgGHGyMLWWDMez0qDchaAkWNkIDtc8Juv
 a1WqE/0lP/sVb36yLVANvt1/Qvpg6M3JwMbTjVaJl2eTDDtwho4PK+Chxx0a8BGl
 Z6oGj1rmvCDD2Dsi/EXI
 =g+9G
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/berrange/tags/pull-qcrypto-secrets-base-2015-12-18-1' into staging

Merge QCryptoSecret object support

# gpg: Signature made Fri 18 Dec 2015 16:51:21 GMT using RSA key ID 15104FDF
# gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>"
# gpg:                 aka "Daniel P. Berrange <berrange@redhat.com>"

* remotes/berrange/tags/pull-qcrypto-secrets-base-2015-12-18-1:
  crypto: add support for loading encrypted x509 keys
  crypto: add QCryptoSecret object class for password/key handling
  qga: convert to use error checked base64 decode
  qemu-char: convert to use error checked base64 decode
  util: add base64 decoding function

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2015-12-18 17:04:15 +00:00
commit c688084506
20 changed files with 1533 additions and 11 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);

View File

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

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

@ -101,6 +101,7 @@ struct QCryptoTLSCredsX509 {
gnutls_certificate_credentials_t data;
#endif
bool sanityCheck;
char *passwordid;
};

58
include/qemu/base64.h Normal file
View File

@ -0,0 +1,58 @@
/*
* QEMU base64 helpers
*
* 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 QEMU_BASE64_H__
#define QEMU_BASE64_H__
#include "qemu-common.h"
/**
* qbase64_decode:
* @input: the (possibly) base64 encoded text
* @in_len: length of @input or -1 if NUL terminated
* @out_len: filled with length of decoded data
* @errp: pointer to a NULL-initialized error object
*
* Attempt to decode the (possibly) base64 encoded
* text provided in @input. If the @input text may
* contain embedded NUL characters, or may not be
* NUL terminated, then @in_len must be set to the
* known size of the @input buffer.
*
* Note that embedded NULs, or lack of a NUL terminator
* are considered invalid base64 data and errors
* will be reported to this effect.
*
* If decoding is successful, the decoded data will
* be returned and @out_len set to indicate the
* number of bytes in the decoded data. The caller
* must use g_free() to free the returned data when
* it is no longer required.
*
* Returns: the decoded data or NULL
*/
uint8_t *qbase64_decode(const char *input,
size_t in_len,
size_t *out_len,
Error **errp);
#endif /* QEMU_BUFFER_H__ */

View File

@ -295,8 +295,6 @@
# @format: #optional data encoding (default 'utf8').
# - base64: data must be base64 encoded text. Its binary
# decoding gets written.
# Bug: invalid base64 is currently not rejected.
# Whitespace *is* invalid.
# - utf8: data's UTF-8 encoding is written
# - data itself is always Unicode regardless of format, like
# any other string.

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

@ -32,6 +32,7 @@
#include "qapi/qmp-input-visitor.h"
#include "qapi/qmp-output-visitor.h"
#include "qapi-visit.h"
#include "qemu/base64.h"
#include <unistd.h>
#include <fcntl.h>
@ -3264,7 +3265,12 @@ void qmp_ringbuf_write(const char *device, const char *data,
}
if (has_format && (format == DATA_FORMAT_BASE64)) {
write_data = g_base64_decode(data, &write_count);
write_data = qbase64_decode(data, -1,
&write_count,
errp);
if (!write_data) {
return;
}
} else {
write_data = (uint8_t *)data;
write_count = strlen(data);

View File

@ -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
@ -3678,6 +3684,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

View File

@ -29,6 +29,7 @@
#include "qemu/queue.h"
#include "qemu/host-utils.h"
#include "qemu/sockets.h"
#include "qemu/base64.h"
#ifndef CONFIG_HAS_ENVIRON
#ifdef __APPLE__
@ -525,7 +526,10 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
gfh->state = RW_STATE_NEW;
}
buf = g_base64_decode(buf_b64, &buf_len);
buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
if (!buf) {
return NULL;
}
if (!has_count) {
count = buf_len;
@ -1963,7 +1967,10 @@ void qmp_guest_set_user_password(const char *username,
char *chpasswddata = NULL;
size_t chpasswdlen;
rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
if (!rawpasswddata) {
return;
}
rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
rawpasswddata[rawpasswdlen] = '\0';

View File

@ -34,6 +34,7 @@
#include "qapi/qmp/qerror.h"
#include "qemu/queue.h"
#include "qemu/host-utils.h"
#include "qemu/base64.h"
#ifndef SHTDN_REASON_FLAG_PLANNED
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
@ -357,7 +358,10 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
return NULL;
}
fh = gfh->fh;
buf = g_base64_decode(buf_b64, &buf_len);
buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
if (!buf) {
return NULL;
}
if (!has_count) {
count = buf_len;
@ -1294,7 +1298,10 @@ void qmp_guest_set_user_password(const char *username,
return;
}
rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
if (!rawpasswddata) {
return;
}
rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
rawpasswddata[rawpasswdlen] = '\0';

View File

@ -14,6 +14,7 @@
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qapi/qmp/qerror.h"
#include "qemu/base64.h"
/* Maximum captured guest-exec out_data/err_data - 16MB */
#define GUEST_EXEC_MAX_OUTPUT (16*1024*1024)
@ -393,10 +394,19 @@ GuestExec *qmp_guest_exec(const char *path,
GIOChannel *in_ch, *out_ch, *err_ch;
GSpawnFlags flags;
bool has_output = (has_capture_output && capture_output);
uint8_t *input = NULL;
size_t ninput = 0;
arglist.value = (char *)path;
arglist.next = has_arg ? arg : NULL;
if (has_input_data) {
input = qbase64_decode(input_data, -1, &ninput, err);
if (!input) {
return NULL;
}
}
argv = guest_exec_get_args(&arglist, true);
envp = has_env ? guest_exec_get_args(env, false) : NULL;
@ -425,7 +435,8 @@ GuestExec *qmp_guest_exec(const char *path,
g_child_watch_add(pid, guest_exec_child_watch, gei);
if (has_input_data) {
gei->in.data = g_base64_decode(input_data, &gei->in.size);
gei->in.data = input;
gei->in.size = ninput;
#ifdef G_OS_WIN32
in_ch = g_io_channel_win32_new_fd(in_fd);
#else

View File

@ -512,8 +512,6 @@ Arguments:
- "data": data to write (json-string)
- "format": data format (json-string, optional)
- Possible values: "utf8" (default), "base64"
Bug: invalid base64 is currently not rejected.
Whitespace *is* invalid.
Example:

2
tests/.gitignore vendored
View File

@ -8,11 +8,13 @@ check-qom-interface
check-qom-proplist
rcutorture
test-aio
test-base64
test-bitops
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)
@ -90,6 +91,7 @@ check-unit-y += tests/test-io-channel-file$(EXESUF)
check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
check-unit-y += tests/test-io-channel-command$(EXESUF)
check-unit-y += tests/test-io-channel-buffer$(EXESUF)
check-unit-y += tests/test-base64$(EXESUF)
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@ -423,6 +425,8 @@ tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
$(test-qom-obj-y)
tests/test-timed-average$(EXESUF): tests/test-timed-average.o qemu-timer.o \
$(test-util-obj-y)
tests/test-base64$(EXESUF): tests/test-base64.o \
libqemuutil.a libqemustub.a
tests/test-qapi-types.c tests/test-qapi-types.h :\
$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
@ -464,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)

109
tests/test-base64.c Normal file
View File

@ -0,0 +1,109 @@
/*
* QEMU base64 helper test
*
* 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 "qemu/base64.h"
static void test_base64_good(void)
{
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\n"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
const char expect[] = "Because we focused on the snake, "
"we missed the scorpion.";
size_t len;
uint8_t *actual = qbase64_decode(input,
-1,
&len,
&error_abort);
g_assert(actual != NULL);
g_assert_cmpint(len, ==, strlen(expect));
g_assert_cmpstr((char *)actual, ==, expect);
g_free(actual);
}
static void test_base64_bad(const char *input,
size_t input_len)
{
size_t len;
Error *err = NULL;
uint8_t *actual = qbase64_decode(input,
input_len,
&len,
&err);
g_assert(err != NULL);
g_assert(actual == NULL);
g_assert_cmpint(len, ==, 0);
error_free(err);
}
static void test_base64_embedded_nul(void)
{
/* We put a NUL character in the middle of the base64
* text which is invalid data, given the expected length */
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\0"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
test_base64_bad(input, G_N_ELEMENTS(input) - 1);
}
static void test_base64_not_nul_terminated(void)
{
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\n"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
/* Using '-2' to make us drop the trailing NUL, thus
* creating an invalid base64 sequence for decoding */
test_base64_bad(input, G_N_ELEMENTS(input) - 2);
}
static void test_base64_invalid_chars(void)
{
/* We put a single quote character in the middle
* of the base64 text which is invalid data */
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW'"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
test_base64_bad(input, strlen(input));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/util/base64/good", test_base64_good);
g_test_add_func("/util/base64/embedded-nul", test_base64_embedded_nul);
g_test_add_func("/util/base64/not-nul-terminated",
test_base64_not_nul_terminated);
g_test_add_func("/util/base64/invalid-chars", test_base64_invalid_chars);
return g_test_run();
}

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

View File

@ -30,3 +30,4 @@ util-obj-y += qemu-coroutine-sleep.o
util-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
util-obj-y += buffer.o
util-obj-y += timed-average.o
util-obj-y += base64.o

60
util/base64.c Normal file
View File

@ -0,0 +1,60 @@
/*
* QEMU base64 helpers
*
* 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 <config-host.h>
#include "qemu/base64.h"
static const char *base64_valid_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n";
uint8_t *qbase64_decode(const char *input,
size_t in_len,
size_t *out_len,
Error **errp)
{
*out_len = 0;
if (in_len != -1) {
/* Lack of NUL terminator is an error */
if (input[in_len] != '\0') {
error_setg(errp, "Base64 data is not NUL terminated");
return NULL;
}
/* Check there's no NULs embedded since we expect
* this to be valid base64 data */
if (memchr(input, '\0', in_len) != NULL) {
error_setg(errp, "Base64 data contains embedded NUL characters");
return NULL;
}
/* Now we know its a valid nul terminated string
* strspn is safe to use... */
} else {
in_len = strlen(input);
}
if (strspn(input, base64_valid_chars) != in_len) {
error_setg(errp, "Base64 data contains invalid characters");
return NULL;
}
return g_base64_decode(input, out_len);
}