/* * 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 . * */ #include "qemu/osdep.h" #include "crypto/secret.h" #include "crypto/cipher.h" #include "qapi/error.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_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; object_class_property_add_bool(oc, "loaded", qcrypto_secret_prop_get_loaded, qcrypto_secret_prop_set_loaded, NULL); object_class_property_add_enum(oc, "format", "QCryptoSecretFormat", QCryptoSecretFormat_lookup, qcrypto_secret_prop_get_format, qcrypto_secret_prop_set_format, NULL); object_class_property_add_str(oc, "data", qcrypto_secret_prop_get_data, qcrypto_secret_prop_set_data, NULL); object_class_property_add_str(oc, "file", qcrypto_secret_prop_get_file, qcrypto_secret_prop_set_file, NULL); object_class_property_add_str(oc, "keyid", qcrypto_secret_prop_get_keyid, qcrypto_secret_prop_set_keyid, NULL); object_class_property_add_str(oc, "iv", qcrypto_secret_prop_get_iv, qcrypto_secret_prop_set_iv, NULL); } 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_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, .instance_size = sizeof(QCryptoSecret), .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);