668cb74b2a
qcrypto_secret_prop_set_loaded() forgets to reset secret->rawdata after unloading a secret, which will lead to a double free at some point. Because there is no use case for unloading an already loaded secret (apart from deleting the whole secret object) and we know that nobody could use this because it would lead to crashes, let's just forbid the operation instead of fixing the unloading. Eventually, we'll want to get rid of 'loaded' in the external interface, but for the meantime this is more consistent with rng, which has a similar property 'opened' that also can't be reset to false after it became true. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
419 lines
12 KiB
C
419 lines
12 KiB
C
/*
|
|
* 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.1 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 "qemu/osdep.h"
|
|
#include "crypto/secret_common.h"
|
|
#include "crypto/cipher.h"
|
|
#include "qapi/error.h"
|
|
#include "qom/object_interfaces.h"
|
|
#include "qemu/base64.h"
|
|
#include "qemu/module.h"
|
|
#include "trace.h"
|
|
|
|
|
|
static void qcrypto_secret_decrypt(QCryptoSecretCommon *secret,
|
|
const uint8_t *input,
|
|
size_t inputlen,
|
|
uint8_t **output,
|
|
size_t *outputlen,
|
|
Error **errp)
|
|
{
|
|
g_autofree uint8_t *iv = NULL;
|
|
g_autofree uint8_t *key = NULL;
|
|
g_autofree uint8_t *ciphertext = NULL;
|
|
size_t keylen, ciphertextlen, ivlen;
|
|
g_autoptr(QCryptoCipher) aes = NULL;
|
|
g_autofree uint8_t *plaintext = NULL;
|
|
|
|
*output = NULL;
|
|
*outputlen = 0;
|
|
|
|
if (qcrypto_secret_lookup(secret->keyid,
|
|
&key, &keylen,
|
|
errp) < 0) {
|
|
return;
|
|
}
|
|
|
|
if (keylen != 32) {
|
|
error_setg(errp, "Key should be 32 bytes in length");
|
|
return;
|
|
}
|
|
|
|
if (!secret->iv) {
|
|
error_setg(errp, "IV is required to decrypt secret");
|
|
return;
|
|
}
|
|
|
|
iv = qbase64_decode(secret->iv, -1, &ivlen, errp);
|
|
if (!iv) {
|
|
return;
|
|
}
|
|
if (ivlen != 16) {
|
|
error_setg(errp, "IV should be 16 bytes in length not %zu",
|
|
ivlen);
|
|
return;
|
|
}
|
|
|
|
aes = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_256,
|
|
QCRYPTO_CIPHER_MODE_CBC,
|
|
key, keylen,
|
|
errp);
|
|
if (!aes) {
|
|
return;
|
|
}
|
|
|
|
if (qcrypto_cipher_setiv(aes, iv, ivlen, errp) < 0) {
|
|
return;
|
|
}
|
|
|
|
if (secret->format == QCRYPTO_SECRET_FORMAT_BASE64) {
|
|
ciphertext = qbase64_decode((const gchar *)input,
|
|
inputlen,
|
|
&ciphertextlen,
|
|
errp);
|
|
if (!ciphertext) {
|
|
return;
|
|
}
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
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]);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Even though plaintext may contain arbitrary NUL
|
|
* ensure it is explicitly NUL terminated.
|
|
*/
|
|
ciphertextlen -= plaintext[ciphertextlen - 1];
|
|
plaintext[ciphertextlen] = '\0';
|
|
|
|
*output = g_steal_pointer(&plaintext);
|
|
*outputlen = ciphertextlen;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
QCryptoSecretCommonClass *sec_class
|
|
= QCRYPTO_SECRET_COMMON_GET_CLASS(obj);
|
|
|
|
if (value) {
|
|
Error *local_err = NULL;
|
|
uint8_t *input = NULL;
|
|
size_t inputlen = 0;
|
|
uint8_t *output = NULL;
|
|
size_t outputlen = 0;
|
|
|
|
if (sec_class->load_data) {
|
|
sec_class->load_data(secret, &input, &inputlen, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
} else {
|
|
error_setg(errp, "%s provides no 'load_data' method'",
|
|
object_get_typename(obj));
|
|
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_BASE64) {
|
|
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 if (secret->rawdata) {
|
|
error_setg(errp, "Cannot unload secret");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static bool
|
|
qcrypto_secret_prop_get_loaded(Object *obj,
|
|
Error **errp G_GNUC_UNUSED)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
return secret->rawdata != NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_secret_prop_set_format(Object *obj,
|
|
int value,
|
|
Error **errp G_GNUC_UNUSED)
|
|
{
|
|
QCryptoSecretCommon *creds = QCRYPTO_SECRET_COMMON(obj);
|
|
creds->format = value;
|
|
}
|
|
|
|
|
|
static int
|
|
qcrypto_secret_prop_get_format(Object *obj,
|
|
Error **errp G_GNUC_UNUSED)
|
|
{
|
|
QCryptoSecretCommon *creds = QCRYPTO_SECRET_COMMON(obj);
|
|
return creds->format;
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_secret_prop_set_iv(Object *obj,
|
|
const char *value,
|
|
Error **errp)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
|
|
g_free(secret->iv);
|
|
secret->iv = g_strdup(value);
|
|
}
|
|
|
|
|
|
static char *
|
|
qcrypto_secret_prop_get_iv(Object *obj,
|
|
Error **errp)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
return g_strdup(secret->iv);
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_secret_prop_set_keyid(Object *obj,
|
|
const char *value,
|
|
Error **errp)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
|
|
g_free(secret->keyid);
|
|
secret->keyid = g_strdup(value);
|
|
}
|
|
|
|
|
|
static char *
|
|
qcrypto_secret_prop_get_keyid(Object *obj,
|
|
Error **errp)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
return g_strdup(secret->keyid);
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_secret_complete(UserCreatable *uc, Error **errp)
|
|
{
|
|
object_property_set_bool(OBJECT(uc), "loaded", true, errp);
|
|
}
|
|
|
|
|
|
static void
|
|
qcrypto_secret_finalize(Object *obj)
|
|
{
|
|
QCryptoSecretCommon *secret = QCRYPTO_SECRET_COMMON(obj);
|
|
|
|
g_free(secret->iv);
|
|
g_free(secret->keyid);
|
|
g_free(secret->rawdata);
|
|
}
|
|
|
|
static void
|
|
qcrypto_secret_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
|
|
|
ucc->complete = qcrypto_secret_complete;
|
|
|
|
object_class_property_add_bool(oc, "loaded",
|
|
qcrypto_secret_prop_get_loaded,
|
|
qcrypto_secret_prop_set_loaded);
|
|
object_class_property_add_enum(oc, "format",
|
|
"QCryptoSecretFormat",
|
|
&QCryptoSecretFormat_lookup,
|
|
qcrypto_secret_prop_get_format,
|
|
qcrypto_secret_prop_set_format);
|
|
object_class_property_add_str(oc, "keyid",
|
|
qcrypto_secret_prop_get_keyid,
|
|
qcrypto_secret_prop_set_keyid);
|
|
object_class_property_add_str(oc, "iv",
|
|
qcrypto_secret_prop_get_iv,
|
|
qcrypto_secret_prop_set_iv);
|
|
}
|
|
|
|
|
|
int qcrypto_secret_lookup(const char *secretid,
|
|
uint8_t **data,
|
|
size_t *datalen,
|
|
Error **errp)
|
|
{
|
|
Object *obj;
|
|
QCryptoSecretCommon *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 = (QCryptoSecretCommon *)
|
|
object_dynamic_cast(obj,
|
|
TYPE_QCRYPTO_SECRET_COMMON);
|
|
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_t, 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_COMMON,
|
|
.instance_size = sizeof(QCryptoSecretCommon),
|
|
.instance_finalize = qcrypto_secret_finalize,
|
|
.class_size = sizeof(QCryptoSecretCommonClass),
|
|
.class_init = qcrypto_secret_class_init,
|
|
.abstract = true,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_USER_CREATABLE },
|
|
{ }
|
|
}
|
|
};
|
|
|
|
|
|
static void
|
|
qcrypto_secret_register_types(void)
|
|
{
|
|
type_register_static(&qcrypto_secret_info);
|
|
}
|
|
|
|
|
|
type_init(qcrypto_secret_register_types);
|