diff options
Diffstat (limited to 'qemu/crypto')
38 files changed, 6447 insertions, 169 deletions
diff --git a/qemu/crypto/Makefile.objs b/qemu/crypto/Makefile.objs index b05013831..0737f4811 100644 --- a/qemu/crypto/Makefile.objs +++ b/qemu/crypto/Makefile.objs @@ -1,5 +1,30 @@ -util-obj-y += init.o -util-obj-y += hash.o -util-obj-y += aes.o -util-obj-y += desrfb.o -util-obj-y += cipher.o +crypto-obj-y = init.o +crypto-obj-y += hash.o +crypto-obj-y += aes.o +crypto-obj-y += desrfb.o +crypto-obj-y += cipher.o +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 +crypto-obj-$(CONFIG_GCRYPT) += random-gcrypt.o +crypto-obj-$(if $(CONFIG_GCRYPT),n,$(CONFIG_GNUTLS_RND)) += random-gnutls.o +crypto-obj-y += pbkdf.o +crypto-obj-$(CONFIG_NETTLE_KDF) += pbkdf-nettle.o +crypto-obj-$(if $(CONFIG_NETTLE_KDF),n,$(CONFIG_GCRYPT_KDF)) += pbkdf-gcrypt.o +crypto-obj-y += ivgen.o +crypto-obj-y += ivgen-essiv.o +crypto-obj-y += ivgen-plain.o +crypto-obj-y += ivgen-plain64.o +crypto-obj-y += afsplit.o +crypto-obj-y += xts.o +crypto-obj-y += block.o +crypto-obj-y += block-qcow.o +crypto-obj-y += block-luks.o + +# Let the userspace emulators avoid linking gnutls/etc +crypto-aes-obj-y = aes.o + +stub-obj-y += random-stub.o +stub-obj-y += pbkdf-stub.o diff --git a/qemu/crypto/aes.c b/qemu/crypto/aes.c index 244a388eb..3456eacd0 100644 --- a/qemu/crypto/aes.c +++ b/qemu/crypto/aes.c @@ -27,6 +27,7 @@ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "qemu/osdep.h" #include "qemu-common.h" #include "crypto/aes.h" diff --git a/qemu/crypto/afsplit.c b/qemu/crypto/afsplit.c new file mode 100644 index 000000000..8074913cd --- /dev/null +++ b/qemu/crypto/afsplit.c @@ -0,0 +1,158 @@ +/* + * QEMU Crypto anti forensic information splitter + * + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * Derived from cryptsetup package lib/luks1/af.c + * + * Copyright (C) 2004, Clemens Fruhwirth <clemens@endorphin.org> + * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 "qemu/osdep.h" +#include "crypto/afsplit.h" +#include "crypto/random.h" + + +static void qcrypto_afsplit_xor(size_t blocklen, + const uint8_t *in1, + const uint8_t *in2, + uint8_t *out) +{ + size_t i; + for (i = 0; i < blocklen; i++) { + out[i] = in1[i] ^ in2[i]; + } +} + + +static int qcrypto_afsplit_hash(QCryptoHashAlgorithm hash, + size_t blocklen, + uint8_t *block, + Error **errp) +{ + size_t digestlen = qcrypto_hash_digest_len(hash); + + size_t hashcount = blocklen / digestlen; + size_t finallen = blocklen % digestlen; + uint32_t i; + + if (finallen) { + hashcount++; + } else { + finallen = digestlen; + } + + for (i = 0; i < hashcount; i++) { + uint8_t *out = NULL; + size_t outlen = 0; + uint32_t iv = cpu_to_be32(i); + struct iovec in[] = { + { .iov_base = &iv, + .iov_len = sizeof(iv) }, + { .iov_base = block + (i * digestlen), + .iov_len = (i == (hashcount - 1)) ? finallen : digestlen }, + }; + + if (qcrypto_hash_bytesv(hash, + in, + G_N_ELEMENTS(in), + &out, &outlen, + errp) < 0) { + return -1; + } + + assert(outlen == digestlen); + memcpy(block + (i * digestlen), out, + (i == (hashcount - 1)) ? finallen : digestlen); + g_free(out); + } + + return 0; +} + + +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash, + size_t blocklen, + uint32_t stripes, + const uint8_t *in, + uint8_t *out, + Error **errp) +{ + uint8_t *block = g_new0(uint8_t, blocklen); + size_t i; + int ret = -1; + + for (i = 0; i < (stripes - 1); i++) { + if (qcrypto_random_bytes(out + (i * blocklen), blocklen, errp) < 0) { + goto cleanup; + } + + qcrypto_afsplit_xor(blocklen, + out + (i * blocklen), + block, + block); + if (qcrypto_afsplit_hash(hash, blocklen, block, + errp) < 0) { + goto cleanup; + } + } + qcrypto_afsplit_xor(blocklen, + in, + block, + out + (i * blocklen)); + ret = 0; + + cleanup: + g_free(block); + return ret; +} + + +int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash, + size_t blocklen, + uint32_t stripes, + const uint8_t *in, + uint8_t *out, + Error **errp) +{ + uint8_t *block = g_new0(uint8_t, blocklen); + size_t i; + int ret = -1; + + for (i = 0; i < (stripes - 1); i++) { + qcrypto_afsplit_xor(blocklen, + in + (i * blocklen), + block, + block); + if (qcrypto_afsplit_hash(hash, blocklen, block, + errp) < 0) { + goto cleanup; + } + } + + qcrypto_afsplit_xor(blocklen, + in + (i * blocklen), + block, + out); + + ret = 0; + + cleanup: + g_free(block); + return ret; +} diff --git a/qemu/crypto/block-luks.c b/qemu/crypto/block-luks.c new file mode 100644 index 000000000..439f89230 --- /dev/null +++ b/qemu/crypto/block-luks.c @@ -0,0 +1,1329 @@ +/* + * QEMU Crypto block device encryption LUKS format + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" + +#include "crypto/block-luks.h" + +#include "crypto/hash.h" +#include "crypto/afsplit.h" +#include "crypto/pbkdf.h" +#include "crypto/secret.h" +#include "crypto/random.h" + +#ifdef CONFIG_UUID +#include <uuid/uuid.h> +#endif + +#include "qemu/coroutine.h" + +/* + * Reference for the LUKS format implemented here is + * + * docs/on-disk-format.pdf + * + * in 'cryptsetup' package source code + * + * This file implements the 1.2.1 specification, dated + * Oct 16, 2011. + */ + +typedef struct QCryptoBlockLUKS QCryptoBlockLUKS; +typedef struct QCryptoBlockLUKSHeader QCryptoBlockLUKSHeader; +typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot; + + +/* The following constants are all defined by the LUKS spec */ +#define QCRYPTO_BLOCK_LUKS_VERSION 1 + +#define QCRYPTO_BLOCK_LUKS_MAGIC_LEN 6 +#define QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN 32 +#define QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN 32 +#define QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN 32 +#define QCRYPTO_BLOCK_LUKS_DIGEST_LEN 20 +#define QCRYPTO_BLOCK_LUKS_SALT_LEN 32 +#define QCRYPTO_BLOCK_LUKS_UUID_LEN 40 +#define QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS 8 +#define QCRYPTO_BLOCK_LUKS_STRIPES 4000 +#define QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS 1000 +#define QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS 1000 +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET 4096 + +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED 0x0000DEAD +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED 0x00AC71F3 + +#define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL + +static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = { + 'L', 'U', 'K', 'S', 0xBA, 0xBE +}; + +typedef struct QCryptoBlockLUKSNameMap QCryptoBlockLUKSNameMap; +struct QCryptoBlockLUKSNameMap { + const char *name; + int id; +}; + +typedef struct QCryptoBlockLUKSCipherSizeMap QCryptoBlockLUKSCipherSizeMap; +struct QCryptoBlockLUKSCipherSizeMap { + uint32_t key_bytes; + int id; +}; +typedef struct QCryptoBlockLUKSCipherNameMap QCryptoBlockLUKSCipherNameMap; +struct QCryptoBlockLUKSCipherNameMap { + const char *name; + const QCryptoBlockLUKSCipherSizeMap *sizes; +}; + + +static const QCryptoBlockLUKSCipherSizeMap +qcrypto_block_luks_cipher_size_map_aes[] = { + { 16, QCRYPTO_CIPHER_ALG_AES_128 }, + { 24, QCRYPTO_CIPHER_ALG_AES_192 }, + { 32, QCRYPTO_CIPHER_ALG_AES_256 }, + { 0, 0 }, +}; + +static const QCryptoBlockLUKSCipherSizeMap +qcrypto_block_luks_cipher_size_map_cast5[] = { + { 16, QCRYPTO_CIPHER_ALG_CAST5_128 }, + { 0, 0 }, +}; + +static const QCryptoBlockLUKSCipherSizeMap +qcrypto_block_luks_cipher_size_map_serpent[] = { + { 16, QCRYPTO_CIPHER_ALG_SERPENT_128 }, + { 24, QCRYPTO_CIPHER_ALG_SERPENT_192 }, + { 32, QCRYPTO_CIPHER_ALG_SERPENT_256 }, + { 0, 0 }, +}; + +static const QCryptoBlockLUKSCipherSizeMap +qcrypto_block_luks_cipher_size_map_twofish[] = { + { 16, QCRYPTO_CIPHER_ALG_TWOFISH_128 }, + { 24, QCRYPTO_CIPHER_ALG_TWOFISH_192 }, + { 32, QCRYPTO_CIPHER_ALG_TWOFISH_256 }, + { 0, 0 }, +}; + +static const QCryptoBlockLUKSCipherNameMap +qcrypto_block_luks_cipher_name_map[] = { + { "aes", qcrypto_block_luks_cipher_size_map_aes }, + { "cast5", qcrypto_block_luks_cipher_size_map_cast5 }, + { "serpent", qcrypto_block_luks_cipher_size_map_serpent }, + { "twofish", qcrypto_block_luks_cipher_size_map_twofish }, +}; + + +/* + * This struct is written to disk in big-endian format, + * but operated upon in native-endian format. + */ +struct QCryptoBlockLUKSKeySlot { + /* state of keyslot, enabled/disable */ + uint32_t active; + /* iterations for PBKDF2 */ + uint32_t iterations; + /* salt for PBKDF2 */ + uint8_t salt[QCRYPTO_BLOCK_LUKS_SALT_LEN]; + /* start sector of key material */ + uint32_t key_offset; + /* number of anti-forensic stripes */ + uint32_t stripes; +} QEMU_PACKED; + +QEMU_BUILD_BUG_ON(sizeof(struct QCryptoBlockLUKSKeySlot) != 48); + + +/* + * This struct is written to disk in big-endian format, + * but operated upon in native-endian format. + */ +struct QCryptoBlockLUKSHeader { + /* 'L', 'U', 'K', 'S', '0xBA', '0xBE' */ + char magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN]; + + /* LUKS version, currently 1 */ + uint16_t version; + + /* cipher name specification (aes, etc) */ + char cipher_name[QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN]; + + /* cipher mode specification (cbc-plain, xts-essiv:sha256, etc) */ + char cipher_mode[QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN]; + + /* hash specification (sha256, etc) */ + char hash_spec[QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN]; + + /* start offset of the volume data (in 512 byte sectors) */ + uint32_t payload_offset; + + /* Number of key bytes */ + uint32_t key_bytes; + + /* master key checksum after PBKDF2 */ + uint8_t master_key_digest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN]; + + /* salt for master key PBKDF2 */ + uint8_t master_key_salt[QCRYPTO_BLOCK_LUKS_SALT_LEN]; + + /* iterations for master key PBKDF2 */ + uint32_t master_key_iterations; + + /* UUID of the partition in standard ASCII representation */ + uint8_t uuid[QCRYPTO_BLOCK_LUKS_UUID_LEN]; + + /* key slots */ + QCryptoBlockLUKSKeySlot key_slots[QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS]; +} QEMU_PACKED; + +QEMU_BUILD_BUG_ON(sizeof(struct QCryptoBlockLUKSHeader) != 592); + + +struct QCryptoBlockLUKS { + QCryptoBlockLUKSHeader header; +}; + + +static int qcrypto_block_luks_cipher_name_lookup(const char *name, + QCryptoCipherMode mode, + uint32_t key_bytes, + Error **errp) +{ + const QCryptoBlockLUKSCipherNameMap *map = + qcrypto_block_luks_cipher_name_map; + size_t maplen = G_N_ELEMENTS(qcrypto_block_luks_cipher_name_map); + size_t i, j; + + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + key_bytes /= 2; + } + + for (i = 0; i < maplen; i++) { + if (!g_str_equal(map[i].name, name)) { + continue; + } + for (j = 0; j < map[i].sizes[j].key_bytes; j++) { + if (map[i].sizes[j].key_bytes == key_bytes) { + return map[i].sizes[j].id; + } + } + } + + error_setg(errp, "Algorithm %s with key size %d bytes not supported", + name, key_bytes); + return 0; +} + +static const char * +qcrypto_block_luks_cipher_alg_lookup(QCryptoCipherAlgorithm alg, + Error **errp) +{ + const QCryptoBlockLUKSCipherNameMap *map = + qcrypto_block_luks_cipher_name_map; + size_t maplen = G_N_ELEMENTS(qcrypto_block_luks_cipher_name_map); + size_t i, j; + for (i = 0; i < maplen; i++) { + for (j = 0; j < map[i].sizes[j].key_bytes; j++) { + if (map[i].sizes[j].id == alg) { + return map[i].name; + } + } + } + + error_setg(errp, "Algorithm '%s' not supported", + QCryptoCipherAlgorithm_lookup[alg]); + return NULL; +} + +/* XXX replace with qapi_enum_parse() in future, when we can + * make that function emit a more friendly error message */ +static int qcrypto_block_luks_name_lookup(const char *name, + const char *const *map, + size_t maplen, + const char *type, + Error **errp) +{ + size_t i; + for (i = 0; i < maplen; i++) { + if (g_str_equal(map[i], name)) { + return i; + } + } + + error_setg(errp, "%s %s not supported", type, name); + return 0; +} + +#define qcrypto_block_luks_cipher_mode_lookup(name, errp) \ + qcrypto_block_luks_name_lookup(name, \ + QCryptoCipherMode_lookup, \ + QCRYPTO_CIPHER_MODE__MAX, \ + "Cipher mode", \ + errp) + +#define qcrypto_block_luks_hash_name_lookup(name, errp) \ + qcrypto_block_luks_name_lookup(name, \ + QCryptoHashAlgorithm_lookup, \ + QCRYPTO_HASH_ALG__MAX, \ + "Hash algorithm", \ + errp) + +#define qcrypto_block_luks_ivgen_name_lookup(name, errp) \ + qcrypto_block_luks_name_lookup(name, \ + QCryptoIVGenAlgorithm_lookup, \ + QCRYPTO_IVGEN_ALG__MAX, \ + "IV generator", \ + errp) + + +static bool +qcrypto_block_luks_has_format(const uint8_t *buf, + size_t buf_size) +{ + const QCryptoBlockLUKSHeader *luks_header = (const void *)buf; + + if (buf_size >= offsetof(QCryptoBlockLUKSHeader, cipher_name) && + memcmp(luks_header->magic, qcrypto_block_luks_magic, + QCRYPTO_BLOCK_LUKS_MAGIC_LEN) == 0 && + be16_to_cpu(luks_header->version) == QCRYPTO_BLOCK_LUKS_VERSION) { + return true; + } else { + return false; + } +} + + +/** + * Deal with a quirk of dm-crypt usage of ESSIV. + * + * When calculating ESSIV IVs, the cipher length used by ESSIV + * may be different from the cipher length used for the block + * encryption, becauses dm-crypt uses the hash digest length + * as the key size. ie, if you have AES 128 as the block cipher + * and SHA 256 as ESSIV hash, then ESSIV will use AES 256 as + * the cipher since that gets a key length matching the digest + * size, not AES 128 with truncated digest as might be imagined + */ +static QCryptoCipherAlgorithm +qcrypto_block_luks_essiv_cipher(QCryptoCipherAlgorithm cipher, + QCryptoHashAlgorithm hash, + Error **errp) +{ + size_t digestlen = qcrypto_hash_digest_len(hash); + size_t keylen = qcrypto_cipher_get_key_len(cipher); + if (digestlen == keylen) { + return cipher; + } + + switch (cipher) { + case QCRYPTO_CIPHER_ALG_AES_128: + case QCRYPTO_CIPHER_ALG_AES_192: + case QCRYPTO_CIPHER_ALG_AES_256: + if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_AES_128)) { + return QCRYPTO_CIPHER_ALG_AES_128; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_AES_192)) { + return QCRYPTO_CIPHER_ALG_AES_192; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_AES_256)) { + return QCRYPTO_CIPHER_ALG_AES_256; + } else { + error_setg(errp, "No AES cipher with key size %zu available", + digestlen); + return 0; + } + break; + case QCRYPTO_CIPHER_ALG_SERPENT_128: + case QCRYPTO_CIPHER_ALG_SERPENT_192: + case QCRYPTO_CIPHER_ALG_SERPENT_256: + if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_SERPENT_128)) { + return QCRYPTO_CIPHER_ALG_SERPENT_128; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_SERPENT_192)) { + return QCRYPTO_CIPHER_ALG_SERPENT_192; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_SERPENT_256)) { + return QCRYPTO_CIPHER_ALG_SERPENT_256; + } else { + error_setg(errp, "No Serpent cipher with key size %zu available", + digestlen); + return 0; + } + break; + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + case QCRYPTO_CIPHER_ALG_TWOFISH_192: + case QCRYPTO_CIPHER_ALG_TWOFISH_256: + if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_TWOFISH_128)) { + return QCRYPTO_CIPHER_ALG_TWOFISH_128; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_TWOFISH_192)) { + return QCRYPTO_CIPHER_ALG_TWOFISH_192; + } else if (digestlen == qcrypto_cipher_get_key_len( + QCRYPTO_CIPHER_ALG_TWOFISH_256)) { + return QCRYPTO_CIPHER_ALG_TWOFISH_256; + } else { + error_setg(errp, "No Twofish cipher with key size %zu available", + digestlen); + return 0; + } + break; + default: + error_setg(errp, "Cipher %s not supported with essiv", + QCryptoCipherAlgorithm_lookup[cipher]); + return 0; + } +} + +/* + * Given a key slot, and user password, this will attempt to unlock + * the master encryption key from the key slot. + * + * Returns: + * 0 if the key slot is disabled, or key could not be decrypted + * with the provided password + * 1 if the key slot is enabled, and key decrypted successfully + * with the provided password + * -1 if a fatal error occurred loading the key + */ +static int +qcrypto_block_luks_load_key(QCryptoBlock *block, + QCryptoBlockLUKSKeySlot *slot, + const char *password, + QCryptoCipherAlgorithm cipheralg, + QCryptoCipherMode ciphermode, + QCryptoHashAlgorithm hash, + QCryptoIVGenAlgorithm ivalg, + QCryptoCipherAlgorithm ivcipheralg, + QCryptoHashAlgorithm ivhash, + uint8_t *masterkey, + size_t masterkeylen, + QCryptoBlockReadFunc readfunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + uint8_t *splitkey; + size_t splitkeylen; + uint8_t *possiblekey; + int ret = -1; + ssize_t rv; + QCryptoCipher *cipher = NULL; + uint8_t keydigest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN]; + QCryptoIVGen *ivgen = NULL; + size_t niv; + + if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) { + return 0; + } + + splitkeylen = masterkeylen * slot->stripes; + splitkey = g_new0(uint8_t, splitkeylen); + possiblekey = g_new0(uint8_t, masterkeylen); + + /* + * The user password is used to generate a (possible) + * decryption key. This may or may not successfully + * decrypt the master key - we just blindly assume + * the key is correct and validate the results of + * decryption later. + */ + if (qcrypto_pbkdf2(hash, + (const uint8_t *)password, strlen(password), + slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN, + slot->iterations, + possiblekey, masterkeylen, + errp) < 0) { + goto cleanup; + } + + /* + * We need to read the master key material from the + * LUKS key material header. What we're reading is + * not the raw master key, but rather the data after + * it has been passed through AFSplit and the result + * then encrypted. + */ + rv = readfunc(block, + slot->key_offset * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + splitkey, splitkeylen, + errp, + opaque); + if (rv < 0) { + goto cleanup; + } + + + /* Setup the cipher/ivgen that we'll use to try to decrypt + * the split master key material */ + cipher = qcrypto_cipher_new(cipheralg, ciphermode, + possiblekey, masterkeylen, + errp); + if (!cipher) { + goto cleanup; + } + + niv = qcrypto_cipher_get_iv_len(cipheralg, + ciphermode); + ivgen = qcrypto_ivgen_new(ivalg, + ivcipheralg, + ivhash, + possiblekey, masterkeylen, + errp); + if (!ivgen) { + goto cleanup; + } + + + /* + * The master key needs to be decrypted in the same + * way that the block device payload will be decrypted + * later. In particular we'll be using the IV generator + * to reset the encryption cipher every time the master + * key crosses a sector boundary. + */ + if (qcrypto_block_decrypt_helper(cipher, + niv, + ivgen, + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + 0, + splitkey, + splitkeylen, + errp) < 0) { + goto cleanup; + } + + /* + * Now we've decrypted the split master key, join + * it back together to get the actual master key. + */ + if (qcrypto_afsplit_decode(hash, + masterkeylen, + slot->stripes, + splitkey, + masterkey, + errp) < 0) { + goto cleanup; + } + + + /* + * We still don't know that the masterkey we got is valid, + * because we just blindly assumed the user's password + * was correct. This is where we now verify it. We are + * creating a hash of the master key using PBKDF and + * then comparing that to the hash stored in the key slot + * header + */ + if (qcrypto_pbkdf2(hash, + masterkey, masterkeylen, + luks->header.master_key_salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + luks->header.master_key_iterations, + keydigest, G_N_ELEMENTS(keydigest), + errp) < 0) { + goto cleanup; + } + + if (memcmp(keydigest, luks->header.master_key_digest, + QCRYPTO_BLOCK_LUKS_DIGEST_LEN) == 0) { + /* Success, we got the right master key */ + ret = 1; + goto cleanup; + } + + /* Fail, user's password was not valid for this key slot, + * tell caller to try another slot */ + ret = 0; + + cleanup: + qcrypto_ivgen_free(ivgen); + qcrypto_cipher_free(cipher); + g_free(splitkey); + g_free(possiblekey); + return ret; +} + + +/* + * Given a user password, this will iterate over all key + * slots and try to unlock each active key slot using the + * password until it successfully obtains a master key. + * + * Returns 0 if a key was loaded, -1 if no keys could be loaded + */ +static int +qcrypto_block_luks_find_key(QCryptoBlock *block, + const char *password, + QCryptoCipherAlgorithm cipheralg, + QCryptoCipherMode ciphermode, + QCryptoHashAlgorithm hash, + QCryptoIVGenAlgorithm ivalg, + QCryptoCipherAlgorithm ivcipheralg, + QCryptoHashAlgorithm ivhash, + uint8_t **masterkey, + size_t *masterkeylen, + QCryptoBlockReadFunc readfunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + size_t i; + int rv; + + *masterkey = g_new0(uint8_t, luks->header.key_bytes); + *masterkeylen = luks->header.key_bytes; + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + rv = qcrypto_block_luks_load_key(block, + &luks->header.key_slots[i], + password, + cipheralg, + ciphermode, + hash, + ivalg, + ivcipheralg, + ivhash, + *masterkey, + *masterkeylen, + readfunc, + opaque, + errp); + if (rv < 0) { + goto error; + } + if (rv == 1) { + return 0; + } + } + + error_setg(errp, "Invalid password, cannot unlock any keyslot"); + + error: + g_free(*masterkey); + *masterkey = NULL; + *masterkeylen = 0; + return -1; +} + + +static int +qcrypto_block_luks_open(QCryptoBlock *block, + QCryptoBlockOpenOptions *options, + QCryptoBlockReadFunc readfunc, + void *opaque, + unsigned int flags, + Error **errp) +{ + QCryptoBlockLUKS *luks; + Error *local_err = NULL; + int ret = 0; + size_t i; + ssize_t rv; + uint8_t *masterkey = NULL; + size_t masterkeylen; + char *ivgen_name, *ivhash_name; + QCryptoCipherMode ciphermode; + QCryptoCipherAlgorithm cipheralg; + QCryptoIVGenAlgorithm ivalg; + QCryptoCipherAlgorithm ivcipheralg; + QCryptoHashAlgorithm hash; + QCryptoHashAlgorithm ivhash; + char *password = NULL; + + if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { + if (!options->u.luks.key_secret) { + error_setg(errp, "Parameter 'key-secret' is required for cipher"); + return -1; + } + password = qcrypto_secret_lookup_as_utf8( + options->u.luks.key_secret, errp); + if (!password) { + return -1; + } + } + + luks = g_new0(QCryptoBlockLUKS, 1); + block->opaque = luks; + + /* Read the entire LUKS header, minus the key material from + * the underlying device */ + rv = readfunc(block, 0, + (uint8_t *)&luks->header, + sizeof(luks->header), + errp, + opaque); + if (rv < 0) { + ret = rv; + goto fail; + } + + /* The header is always stored in big-endian format, so + * convert everything to native */ + be16_to_cpus(&luks->header.version); + be32_to_cpus(&luks->header.payload_offset); + be32_to_cpus(&luks->header.key_bytes); + be32_to_cpus(&luks->header.master_key_iterations); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + be32_to_cpus(&luks->header.key_slots[i].active); + be32_to_cpus(&luks->header.key_slots[i].iterations); + be32_to_cpus(&luks->header.key_slots[i].key_offset); + be32_to_cpus(&luks->header.key_slots[i].stripes); + } + + if (memcmp(luks->header.magic, qcrypto_block_luks_magic, + QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) { + error_setg(errp, "Volume is not in LUKS format"); + ret = -EINVAL; + goto fail; + } + if (luks->header.version != QCRYPTO_BLOCK_LUKS_VERSION) { + error_setg(errp, "LUKS version %" PRIu32 " is not supported", + luks->header.version); + ret = -ENOTSUP; + goto fail; + } + + /* + * The cipher_mode header contains a string that we have + * to further parse, of the format + * + * <cipher-mode>-<iv-generator>[:<iv-hash>] + * + * eg cbc-essiv:sha256, cbc-plain64 + */ + ivgen_name = strchr(luks->header.cipher_mode, '-'); + if (!ivgen_name) { + ret = -EINVAL; + error_setg(errp, "Unexpected cipher mode string format %s", + luks->header.cipher_mode); + goto fail; + } + *ivgen_name = '\0'; + ivgen_name++; + + ivhash_name = strchr(ivgen_name, ':'); + if (!ivhash_name) { + ivhash = 0; + } else { + *ivhash_name = '\0'; + ivhash_name++; + + ivhash = qcrypto_block_luks_hash_name_lookup(ivhash_name, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + } + + ciphermode = qcrypto_block_luks_cipher_mode_lookup(luks->header.cipher_mode, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + + cipheralg = qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name, + ciphermode, + luks->header.key_bytes, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + + hash = qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + + ivalg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + + if (ivalg == QCRYPTO_IVGEN_ALG_ESSIV) { + ivcipheralg = qcrypto_block_luks_essiv_cipher(cipheralg, + ivhash, + &local_err); + if (local_err) { + ret = -ENOTSUP; + error_propagate(errp, local_err); + goto fail; + } + } else { + ivcipheralg = cipheralg; + } + + if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) { + /* Try to find which key slot our password is valid for + * and unlock the master key from that slot. + */ + if (qcrypto_block_luks_find_key(block, + password, + cipheralg, ciphermode, + hash, + ivalg, + ivcipheralg, + ivhash, + &masterkey, &masterkeylen, + readfunc, opaque, + errp) < 0) { + ret = -EACCES; + goto fail; + } + + /* We have a valid master key now, so can setup the + * block device payload decryption objects + */ + block->kdfhash = hash; + block->niv = qcrypto_cipher_get_iv_len(cipheralg, + ciphermode); + block->ivgen = qcrypto_ivgen_new(ivalg, + ivcipheralg, + ivhash, + masterkey, masterkeylen, + errp); + if (!block->ivgen) { + ret = -ENOTSUP; + goto fail; + } + + block->cipher = qcrypto_cipher_new(cipheralg, + ciphermode, + masterkey, masterkeylen, + errp); + if (!block->cipher) { + ret = -ENOTSUP; + goto fail; + } + } + + block->payload_offset = luks->header.payload_offset * + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; + + g_free(masterkey); + g_free(password); + + return 0; + + fail: + g_free(masterkey); + qcrypto_cipher_free(block->cipher); + qcrypto_ivgen_free(block->ivgen); + g_free(luks); + g_free(password); + return ret; +} + + +static int +qcrypto_block_luks_uuid_gen(uint8_t *uuidstr, Error **errp) +{ +#ifdef CONFIG_UUID + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, (char *)uuidstr); + return 0; +#else + error_setg(errp, "Unable to generate uuids on this platform"); + return -1; +#endif +} + +static int +qcrypto_block_luks_create(QCryptoBlock *block, + QCryptoBlockCreateOptions *options, + QCryptoBlockInitFunc initfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks; + QCryptoBlockCreateOptionsLUKS luks_opts; + Error *local_err = NULL; + uint8_t *masterkey = NULL; + uint8_t *slotkey = NULL; + uint8_t *splitkey = NULL; + size_t splitkeylen = 0; + size_t i; + QCryptoCipher *cipher = NULL; + QCryptoIVGen *ivgen = NULL; + char *password; + const char *cipher_alg; + const char *cipher_mode; + const char *ivgen_alg; + const char *ivgen_hash_alg = NULL; + const char *hash_alg; + char *cipher_mode_spec = NULL; + QCryptoCipherAlgorithm ivcipheralg = 0; + + memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts)); + if (!luks_opts.has_cipher_alg) { + luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256; + } + if (!luks_opts.has_cipher_mode) { + luks_opts.cipher_mode = QCRYPTO_CIPHER_MODE_XTS; + } + if (!luks_opts.has_ivgen_alg) { + luks_opts.ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64; + } + if (!luks_opts.has_hash_alg) { + luks_opts.hash_alg = QCRYPTO_HASH_ALG_SHA256; + } + + if (!options->u.luks.key_secret) { + error_setg(errp, "Parameter 'key-secret' is required for cipher"); + return -1; + } + password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp); + if (!password) { + return -1; + } + + luks = g_new0(QCryptoBlockLUKS, 1); + block->opaque = luks; + + memcpy(luks->header.magic, qcrypto_block_luks_magic, + QCRYPTO_BLOCK_LUKS_MAGIC_LEN); + + /* We populate the header in native endianness initially and + * then convert everything to big endian just before writing + * it out to disk + */ + luks->header.version = QCRYPTO_BLOCK_LUKS_VERSION; + if (qcrypto_block_luks_uuid_gen(luks->header.uuid, + errp) < 0) { + goto error; + } + + cipher_alg = qcrypto_block_luks_cipher_alg_lookup(luks_opts.cipher_alg, + errp); + if (!cipher_alg) { + goto error; + } + + cipher_mode = QCryptoCipherMode_lookup[luks_opts.cipher_mode]; + ivgen_alg = QCryptoIVGenAlgorithm_lookup[luks_opts.ivgen_alg]; + if (luks_opts.has_ivgen_hash_alg) { + ivgen_hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.ivgen_hash_alg]; + cipher_mode_spec = g_strdup_printf("%s-%s:%s", cipher_mode, ivgen_alg, + ivgen_hash_alg); + } else { + cipher_mode_spec = g_strdup_printf("%s-%s", cipher_mode, ivgen_alg); + } + hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.hash_alg]; + + + if (strlen(cipher_alg) >= QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN) { + error_setg(errp, "Cipher name '%s' is too long for LUKS header", + cipher_alg); + goto error; + } + if (strlen(cipher_mode_spec) >= QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN) { + error_setg(errp, "Cipher mode '%s' is too long for LUKS header", + cipher_mode_spec); + goto error; + } + if (strlen(hash_alg) >= QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN) { + error_setg(errp, "Hash name '%s' is too long for LUKS header", + hash_alg); + goto error; + } + + if (luks_opts.ivgen_alg == QCRYPTO_IVGEN_ALG_ESSIV) { + ivcipheralg = qcrypto_block_luks_essiv_cipher(luks_opts.cipher_alg, + luks_opts.ivgen_hash_alg, + &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + } else { + ivcipheralg = luks_opts.cipher_alg; + } + + strcpy(luks->header.cipher_name, cipher_alg); + strcpy(luks->header.cipher_mode, cipher_mode_spec); + strcpy(luks->header.hash_spec, hash_alg); + + luks->header.key_bytes = qcrypto_cipher_get_key_len(luks_opts.cipher_alg); + if (luks_opts.cipher_mode == QCRYPTO_CIPHER_MODE_XTS) { + luks->header.key_bytes *= 2; + } + + /* Generate the salt used for hashing the master key + * with PBKDF later + */ + if (qcrypto_random_bytes(luks->header.master_key_salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + errp) < 0) { + goto error; + } + + /* Generate random master key */ + masterkey = g_new0(uint8_t, luks->header.key_bytes); + if (qcrypto_random_bytes(masterkey, + luks->header.key_bytes, errp) < 0) { + goto error; + } + + + /* Setup the block device payload encryption objects */ + block->cipher = qcrypto_cipher_new(luks_opts.cipher_alg, + luks_opts.cipher_mode, + masterkey, luks->header.key_bytes, + errp); + if (!block->cipher) { + goto error; + } + + block->kdfhash = luks_opts.hash_alg; + block->niv = qcrypto_cipher_get_iv_len(luks_opts.cipher_alg, + luks_opts.cipher_mode); + block->ivgen = qcrypto_ivgen_new(luks_opts.ivgen_alg, + ivcipheralg, + luks_opts.ivgen_hash_alg, + masterkey, luks->header.key_bytes, + errp); + + if (!block->ivgen) { + goto error; + } + + + /* Determine how many iterations we need to hash the master + * key, in order to have 1 second of compute time used + */ + luks->header.master_key_iterations = + qcrypto_pbkdf2_count_iters(luks_opts.hash_alg, + masterkey, luks->header.key_bytes, + luks->header.master_key_salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + + /* Why /= 8 ? That matches cryptsetup, but there's no + * explanation why they chose /= 8... Probably so that + * if all 8 keyslots are active we only spend 1 second + * in total time to check all keys */ + luks->header.master_key_iterations /= 8; + luks->header.master_key_iterations = MAX( + luks->header.master_key_iterations, + QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS); + + + /* Hash the master key, saving the result in the LUKS + * header. This hash is used when opening the encrypted + * device to verify that the user password unlocked a + * valid master key + */ + if (qcrypto_pbkdf2(luks_opts.hash_alg, + masterkey, luks->header.key_bytes, + luks->header.master_key_salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + luks->header.master_key_iterations, + luks->header.master_key_digest, + QCRYPTO_BLOCK_LUKS_DIGEST_LEN, + errp) < 0) { + goto error; + } + + + /* Although LUKS has multiple key slots, we're just going + * to use the first key slot */ + splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES; + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + luks->header.key_slots[i].active = i == 0 ? + QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED : + QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; + luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES; + + /* This calculation doesn't match that shown in the spec, + * but instead follows the cryptsetup implementation. + */ + luks->header.key_slots[i].key_offset = + (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE) + + (ROUND_UP(((splitkeylen + (QCRYPTO_BLOCK_LUKS_SECTOR_SIZE - 1)) / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE), + (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) * i); + } + + if (qcrypto_random_bytes(luks->header.key_slots[0].salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + errp) < 0) { + goto error; + } + + /* Again we determine how many iterations are required to + * hash the user password while consuming 1 second of compute + * time */ + luks->header.key_slots[0].iterations = + qcrypto_pbkdf2_count_iters(luks_opts.hash_alg, + (uint8_t *)password, strlen(password), + luks->header.key_slots[0].salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + /* Why /= 2 ? That matches cryptsetup, but there's no + * explanation why they chose /= 2... */ + luks->header.key_slots[0].iterations /= 2; + luks->header.key_slots[0].iterations = MAX( + luks->header.key_slots[0].iterations, + QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS); + + + /* Generate a key that we'll use to encrypt the master + * key, from the user's password + */ + slotkey = g_new0(uint8_t, luks->header.key_bytes); + if (qcrypto_pbkdf2(luks_opts.hash_alg, + (uint8_t *)password, strlen(password), + luks->header.key_slots[0].salt, + QCRYPTO_BLOCK_LUKS_SALT_LEN, + luks->header.key_slots[0].iterations, + slotkey, luks->header.key_bytes, + errp) < 0) { + goto error; + } + + + /* Setup the encryption objects needed to encrypt the + * master key material + */ + cipher = qcrypto_cipher_new(luks_opts.cipher_alg, + luks_opts.cipher_mode, + slotkey, luks->header.key_bytes, + errp); + if (!cipher) { + goto error; + } + + ivgen = qcrypto_ivgen_new(luks_opts.ivgen_alg, + ivcipheralg, + luks_opts.ivgen_hash_alg, + slotkey, luks->header.key_bytes, + errp); + if (!ivgen) { + goto error; + } + + /* Before storing the master key, we need to vastly + * increase its size, as protection against forensic + * disk data recovery */ + splitkey = g_new0(uint8_t, splitkeylen); + + if (qcrypto_afsplit_encode(luks_opts.hash_alg, + luks->header.key_bytes, + luks->header.key_slots[0].stripes, + masterkey, + splitkey, + errp) < 0) { + goto error; + } + + /* Now we encrypt the split master key with the key generated + * from the user's password, before storing it */ + if (qcrypto_block_encrypt_helper(cipher, block->niv, ivgen, + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + 0, + splitkey, + splitkeylen, + errp) < 0) { + goto error; + } + + + /* The total size of the LUKS headers is the partition header + key + * slot headers, rounded up to the nearest sector, combined with + * the size of each master key material region, also rounded up + * to the nearest sector */ + luks->header.payload_offset = + (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE) + + (ROUND_UP(((splitkeylen + (QCRYPTO_BLOCK_LUKS_SECTOR_SIZE - 1)) / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE), + (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) * + QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS); + + block->payload_offset = luks->header.payload_offset * + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; + + /* Reserve header space to match payload offset */ + initfunc(block, block->payload_offset, &local_err, opaque); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + + /* Everything on disk uses Big Endian, so flip header fields + * before writing them */ + cpu_to_be16s(&luks->header.version); + cpu_to_be32s(&luks->header.payload_offset); + cpu_to_be32s(&luks->header.key_bytes); + cpu_to_be32s(&luks->header.master_key_iterations); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + cpu_to_be32s(&luks->header.key_slots[i].active); + cpu_to_be32s(&luks->header.key_slots[i].iterations); + cpu_to_be32s(&luks->header.key_slots[i].key_offset); + cpu_to_be32s(&luks->header.key_slots[i].stripes); + } + + + /* Write out the partition header and key slot headers */ + writefunc(block, 0, + (const uint8_t *)&luks->header, + sizeof(luks->header), + &local_err, + opaque); + + /* Delay checking local_err until we've byte-swapped */ + + /* Byte swap the header back to native, in case we need + * to read it again later */ + be16_to_cpus(&luks->header.version); + be32_to_cpus(&luks->header.payload_offset); + be32_to_cpus(&luks->header.key_bytes); + be32_to_cpus(&luks->header.master_key_iterations); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + be32_to_cpus(&luks->header.key_slots[i].active); + be32_to_cpus(&luks->header.key_slots[i].iterations); + be32_to_cpus(&luks->header.key_slots[i].key_offset); + be32_to_cpus(&luks->header.key_slots[i].stripes); + } + + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + + /* Write out the master key material, starting at the + * sector immediately following the partition header. */ + if (writefunc(block, + luks->header.key_slots[0].key_offset * + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + splitkey, splitkeylen, + errp, + opaque) != splitkeylen) { + goto error; + } + + memset(masterkey, 0, luks->header.key_bytes); + g_free(masterkey); + memset(slotkey, 0, luks->header.key_bytes); + g_free(slotkey); + g_free(splitkey); + g_free(password); + g_free(cipher_mode_spec); + + qcrypto_ivgen_free(ivgen); + qcrypto_cipher_free(cipher); + + return 0; + + error: + if (masterkey) { + memset(masterkey, 0, luks->header.key_bytes); + } + g_free(masterkey); + if (slotkey) { + memset(slotkey, 0, luks->header.key_bytes); + } + g_free(slotkey); + g_free(splitkey); + g_free(password); + g_free(cipher_mode_spec); + + qcrypto_ivgen_free(ivgen); + qcrypto_cipher_free(cipher); + + g_free(luks); + return -1; +} + + +static void qcrypto_block_luks_cleanup(QCryptoBlock *block) +{ + g_free(block->opaque); +} + + +static int +qcrypto_block_luks_decrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return qcrypto_block_decrypt_helper(block->cipher, + block->niv, block->ivgen, + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + startsector, buf, len, errp); +} + + +static int +qcrypto_block_luks_encrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return qcrypto_block_encrypt_helper(block->cipher, + block->niv, block->ivgen, + QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + startsector, buf, len, errp); +} + + +const QCryptoBlockDriver qcrypto_block_driver_luks = { + .open = qcrypto_block_luks_open, + .create = qcrypto_block_luks_create, + .cleanup = qcrypto_block_luks_cleanup, + .decrypt = qcrypto_block_luks_decrypt, + .encrypt = qcrypto_block_luks_encrypt, + .has_format = qcrypto_block_luks_has_format, +}; diff --git a/qemu/crypto/block-luks.h b/qemu/crypto/block-luks.h new file mode 100644 index 000000000..0934138aa --- /dev/null +++ b/qemu/crypto/block-luks.h @@ -0,0 +1,28 @@ +/* + * QEMU Crypto block device encryption LUKS format + * + * Copyright (c) 2015-2016 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_BLOCK_LUKS_H__ +#define QCRYPTO_BLOCK_LUKS_H__ + +#include "crypto/blockpriv.h" + +extern const QCryptoBlockDriver qcrypto_block_driver_luks; + +#endif /* QCRYPTO_BLOCK_LUKS_H__ */ diff --git a/qemu/crypto/block-qcow.c b/qemu/crypto/block-qcow.c new file mode 100644 index 000000000..be88c6f0e --- /dev/null +++ b/qemu/crypto/block-qcow.c @@ -0,0 +1,174 @@ +/* + * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format + * + * Copyright (c) 2015-2016 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/>. + * + */ + +/* + * Note that the block encryption implemented in this file is broken + * by design. This exists only to allow data to be liberated from + * existing qcow[2] images and should not be used in any new areas. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" + +#include "crypto/block-qcow.h" +#include "crypto/secret.h" + +#define QCRYPTO_BLOCK_QCOW_SECTOR_SIZE 512 + + +static bool +qcrypto_block_qcow_has_format(const uint8_t *buf G_GNUC_UNUSED, + size_t buf_size G_GNUC_UNUSED) +{ + return false; +} + + +static int +qcrypto_block_qcow_init(QCryptoBlock *block, + const char *keysecret, + Error **errp) +{ + char *password; + int ret; + uint8_t keybuf[16]; + int len; + + memset(keybuf, 0, 16); + + password = qcrypto_secret_lookup_as_utf8(keysecret, errp); + if (!password) { + return -1; + } + + len = strlen(password); + memcpy(keybuf, password, MIN(len, sizeof(keybuf))); + g_free(password); + + block->niv = qcrypto_cipher_get_iv_len(QCRYPTO_CIPHER_ALG_AES_128, + QCRYPTO_CIPHER_MODE_CBC); + block->ivgen = qcrypto_ivgen_new(QCRYPTO_IVGEN_ALG_PLAIN64, + 0, 0, NULL, 0, errp); + if (!block->ivgen) { + ret = -ENOTSUP; + goto fail; + } + + block->cipher = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_128, + QCRYPTO_CIPHER_MODE_CBC, + keybuf, G_N_ELEMENTS(keybuf), + errp); + if (!block->cipher) { + ret = -ENOTSUP; + goto fail; + } + + block->payload_offset = 0; + + return 0; + + fail: + qcrypto_cipher_free(block->cipher); + qcrypto_ivgen_free(block->ivgen); + return ret; +} + + +static int +qcrypto_block_qcow_open(QCryptoBlock *block, + QCryptoBlockOpenOptions *options, + QCryptoBlockReadFunc readfunc G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED, + unsigned int flags, + Error **errp) +{ + if (flags & QCRYPTO_BLOCK_OPEN_NO_IO) { + return 0; + } else { + if (!options->u.qcow.key_secret) { + error_setg(errp, + "Parameter 'key-secret' is required for cipher"); + return -1; + } + return qcrypto_block_qcow_init(block, + options->u.qcow.key_secret, errp); + } +} + + +static int +qcrypto_block_qcow_create(QCryptoBlock *block, + QCryptoBlockCreateOptions *options, + QCryptoBlockInitFunc initfunc G_GNUC_UNUSED, + QCryptoBlockWriteFunc writefunc G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED, + Error **errp) +{ + if (!options->u.qcow.key_secret) { + error_setg(errp, "Parameter 'key-secret' is required for cipher"); + return -1; + } + /* QCow2 has no special header, since everything is hardwired */ + return qcrypto_block_qcow_init(block, options->u.qcow.key_secret, errp); +} + + +static void +qcrypto_block_qcow_cleanup(QCryptoBlock *block) +{ +} + + +static int +qcrypto_block_qcow_decrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return qcrypto_block_decrypt_helper(block->cipher, + block->niv, block->ivgen, + QCRYPTO_BLOCK_QCOW_SECTOR_SIZE, + startsector, buf, len, errp); +} + + +static int +qcrypto_block_qcow_encrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return qcrypto_block_encrypt_helper(block->cipher, + block->niv, block->ivgen, + QCRYPTO_BLOCK_QCOW_SECTOR_SIZE, + startsector, buf, len, errp); +} + + +const QCryptoBlockDriver qcrypto_block_driver_qcow = { + .open = qcrypto_block_qcow_open, + .create = qcrypto_block_qcow_create, + .cleanup = qcrypto_block_qcow_cleanup, + .decrypt = qcrypto_block_qcow_decrypt, + .encrypt = qcrypto_block_qcow_encrypt, + .has_format = qcrypto_block_qcow_has_format, +}; diff --git a/qemu/crypto/block-qcow.h b/qemu/crypto/block-qcow.h new file mode 100644 index 000000000..569f83610 --- /dev/null +++ b/qemu/crypto/block-qcow.h @@ -0,0 +1,28 @@ +/* + * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format + * + * Copyright (c) 2015-2016 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_BLOCK_QCOW_H__ +#define QCRYPTO_BLOCK_QCOW_H__ + +#include "crypto/blockpriv.h" + +extern const QCryptoBlockDriver qcrypto_block_driver_qcow; + +#endif /* QCRYPTO_BLOCK_QCOW_H__ */ diff --git a/qemu/crypto/block.c b/qemu/crypto/block.c new file mode 100644 index 000000000..da60eba85 --- /dev/null +++ b/qemu/crypto/block.c @@ -0,0 +1,261 @@ +/* + * QEMU Crypto block device encryption + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/blockpriv.h" +#include "crypto/block-qcow.h" +#include "crypto/block-luks.h" + +static const QCryptoBlockDriver *qcrypto_block_drivers[] = { + [Q_CRYPTO_BLOCK_FORMAT_QCOW] = &qcrypto_block_driver_qcow, + [Q_CRYPTO_BLOCK_FORMAT_LUKS] = &qcrypto_block_driver_luks, +}; + + +bool qcrypto_block_has_format(QCryptoBlockFormat format, + const uint8_t *buf, + size_t len) +{ + const QCryptoBlockDriver *driver; + + if (format >= G_N_ELEMENTS(qcrypto_block_drivers) || + !qcrypto_block_drivers[format]) { + return false; + } + + driver = qcrypto_block_drivers[format]; + + return driver->has_format(buf, len); +} + + +QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options, + QCryptoBlockReadFunc readfunc, + void *opaque, + unsigned int flags, + Error **errp) +{ + QCryptoBlock *block = g_new0(QCryptoBlock, 1); + + block->format = options->format; + + if (options->format >= G_N_ELEMENTS(qcrypto_block_drivers) || + !qcrypto_block_drivers[options->format]) { + error_setg(errp, "Unsupported block driver %d", options->format); + g_free(block); + return NULL; + } + + block->driver = qcrypto_block_drivers[options->format]; + + if (block->driver->open(block, options, + readfunc, opaque, flags, errp) < 0) { + g_free(block); + return NULL; + } + + return block; +} + + +QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, + QCryptoBlockInitFunc initfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlock *block = g_new0(QCryptoBlock, 1); + + block->format = options->format; + + if (options->format >= G_N_ELEMENTS(qcrypto_block_drivers) || + !qcrypto_block_drivers[options->format]) { + error_setg(errp, "Unsupported block driver %d", options->format); + g_free(block); + return NULL; + } + + block->driver = qcrypto_block_drivers[options->format]; + + if (block->driver->create(block, options, initfunc, + writefunc, opaque, errp) < 0) { + g_free(block); + return NULL; + } + + return block; +} + + +int qcrypto_block_decrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return block->driver->decrypt(block, startsector, buf, len, errp); +} + + +int qcrypto_block_encrypt(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + return block->driver->encrypt(block, startsector, buf, len, errp); +} + + +QCryptoCipher *qcrypto_block_get_cipher(QCryptoBlock *block) +{ + return block->cipher; +} + + +QCryptoIVGen *qcrypto_block_get_ivgen(QCryptoBlock *block) +{ + return block->ivgen; +} + + +QCryptoHashAlgorithm qcrypto_block_get_kdf_hash(QCryptoBlock *block) +{ + return block->kdfhash; +} + + +uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block) +{ + return block->payload_offset; +} + + +void qcrypto_block_free(QCryptoBlock *block) +{ + if (!block) { + return; + } + + block->driver->cleanup(block); + + qcrypto_cipher_free(block->cipher); + qcrypto_ivgen_free(block->ivgen); + g_free(block); +} + + +int qcrypto_block_decrypt_helper(QCryptoCipher *cipher, + size_t niv, + QCryptoIVGen *ivgen, + int sectorsize, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + uint8_t *iv; + int ret = -1; + + iv = niv ? g_new0(uint8_t, niv) : NULL; + + while (len > 0) { + size_t nbytes; + if (niv) { + if (qcrypto_ivgen_calculate(ivgen, + startsector, + iv, niv, + errp) < 0) { + goto cleanup; + } + + if (qcrypto_cipher_setiv(cipher, + iv, niv, + errp) < 0) { + goto cleanup; + } + } + + nbytes = len > sectorsize ? sectorsize : len; + if (qcrypto_cipher_decrypt(cipher, buf, buf, + nbytes, errp) < 0) { + goto cleanup; + } + + startsector++; + buf += nbytes; + len -= nbytes; + } + + ret = 0; + cleanup: + g_free(iv); + return ret; +} + + +int qcrypto_block_encrypt_helper(QCryptoCipher *cipher, + size_t niv, + QCryptoIVGen *ivgen, + int sectorsize, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp) +{ + uint8_t *iv; + int ret = -1; + + iv = niv ? g_new0(uint8_t, niv) : NULL; + + while (len > 0) { + size_t nbytes; + if (niv) { + if (qcrypto_ivgen_calculate(ivgen, + startsector, + iv, niv, + errp) < 0) { + goto cleanup; + } + + if (qcrypto_cipher_setiv(cipher, + iv, niv, + errp) < 0) { + goto cleanup; + } + } + + nbytes = len > sectorsize ? sectorsize : len; + if (qcrypto_cipher_encrypt(cipher, buf, buf, + nbytes, errp) < 0) { + goto cleanup; + } + + startsector++; + buf += nbytes; + len -= nbytes; + } + + ret = 0; + cleanup: + g_free(iv); + return ret; +} diff --git a/qemu/crypto/blockpriv.h b/qemu/crypto/blockpriv.h new file mode 100644 index 000000000..62970859d --- /dev/null +++ b/qemu/crypto/blockpriv.h @@ -0,0 +1,92 @@ +/* + * QEMU Crypto block device encryption + * + * Copyright (c) 2015-2016 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_BLOCK_PRIV_H__ +#define QCRYPTO_BLOCK_PRIV_H__ + +#include "crypto/block.h" + +typedef struct QCryptoBlockDriver QCryptoBlockDriver; + +struct QCryptoBlock { + QCryptoBlockFormat format; + + const QCryptoBlockDriver *driver; + void *opaque; + + QCryptoCipher *cipher; + QCryptoIVGen *ivgen; + QCryptoHashAlgorithm kdfhash; + size_t niv; + uint64_t payload_offset; /* In bytes */ +}; + +struct QCryptoBlockDriver { + int (*open)(QCryptoBlock *block, + QCryptoBlockOpenOptions *options, + QCryptoBlockReadFunc readfunc, + void *opaque, + unsigned int flags, + Error **errp); + + int (*create)(QCryptoBlock *block, + QCryptoBlockCreateOptions *options, + QCryptoBlockInitFunc initfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp); + + void (*cleanup)(QCryptoBlock *block); + + int (*encrypt)(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp); + int (*decrypt)(QCryptoBlock *block, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp); + + bool (*has_format)(const uint8_t *buf, + size_t buflen); +}; + + +int qcrypto_block_decrypt_helper(QCryptoCipher *cipher, + size_t niv, + QCryptoIVGen *ivgen, + int sectorsize, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp); + +int qcrypto_block_encrypt_helper(QCryptoCipher *cipher, + size_t niv, + QCryptoIVGen *ivgen, + int sectorsize, + uint64_t startsector, + uint8_t *buf, + size_t len, + Error **errp); + +#endif /* QCRYPTO_BLOCK_PRIV_H__ */ diff --git a/qemu/crypto/cipher-builtin.c b/qemu/crypto/cipher-builtin.c index 30f4853c8..88963f65c 100644 --- a/qemu/crypto/cipher-builtin.c +++ b/qemu/crypto/cipher-builtin.c @@ -18,15 +18,21 @@ * */ +#include "qemu/osdep.h" #include "crypto/aes.h" #include "crypto/desrfb.h" +#include "crypto/xts.h" +typedef struct QCryptoCipherBuiltinAESContext QCryptoCipherBuiltinAESContext; +struct QCryptoCipherBuiltinAESContext { + AES_KEY enc; + AES_KEY dec; +}; typedef struct QCryptoCipherBuiltinAES QCryptoCipherBuiltinAES; struct QCryptoCipherBuiltinAES { - AES_KEY encrypt_key; - AES_KEY decrypt_key; - uint8_t *iv; - size_t niv; + QCryptoCipherBuiltinAESContext key; + QCryptoCipherBuiltinAESContext key_tweak; + uint8_t iv[AES_BLOCK_SIZE]; }; typedef struct QCryptoCipherBuiltinDESRFB QCryptoCipherBuiltinDESRFB; struct QCryptoCipherBuiltinDESRFB { @@ -40,6 +46,7 @@ struct QCryptoCipherBuiltin { QCryptoCipherBuiltinAES aes; QCryptoCipherBuiltinDESRFB desrfb; } state; + size_t blocksize; void (*free)(QCryptoCipher *cipher); int (*setiv)(QCryptoCipher *cipher, const uint8_t *iv, size_t niv, @@ -61,12 +68,87 @@ static void qcrypto_cipher_free_aes(QCryptoCipher *cipher) { QCryptoCipherBuiltin *ctxt = cipher->opaque; - g_free(ctxt->state.aes.iv); g_free(ctxt); cipher->opaque = NULL; } +static void qcrypto_cipher_aes_ecb_encrypt(AES_KEY *key, + const void *in, + void *out, + size_t len) +{ + const uint8_t *inptr = in; + uint8_t *outptr = out; + while (len) { + if (len > AES_BLOCK_SIZE) { + AES_encrypt(inptr, outptr, key); + inptr += AES_BLOCK_SIZE; + outptr += AES_BLOCK_SIZE; + len -= AES_BLOCK_SIZE; + } else { + uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE]; + memcpy(tmp1, inptr, len); + /* Fill with 0 to avoid valgrind uninitialized reads */ + memset(tmp1 + len, 0, sizeof(tmp1) - len); + AES_encrypt(tmp1, tmp2, key); + memcpy(outptr, tmp2, len); + len = 0; + } + } +} + + +static void qcrypto_cipher_aes_ecb_decrypt(AES_KEY *key, + const void *in, + void *out, + size_t len) +{ + const uint8_t *inptr = in; + uint8_t *outptr = out; + while (len) { + if (len > AES_BLOCK_SIZE) { + AES_decrypt(inptr, outptr, key); + inptr += AES_BLOCK_SIZE; + outptr += AES_BLOCK_SIZE; + len -= AES_BLOCK_SIZE; + } else { + uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE]; + memcpy(tmp1, inptr, len); + /* Fill with 0 to avoid valgrind uninitialized reads */ + memset(tmp1 + len, 0, sizeof(tmp1) - len); + AES_decrypt(tmp1, tmp2, key); + memcpy(outptr, tmp2, len); + len = 0; + } + } +} + + +static void qcrypto_cipher_aes_xts_encrypt(const void *ctx, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + const QCryptoCipherBuiltinAESContext *aesctx = ctx; + + qcrypto_cipher_aes_ecb_encrypt((AES_KEY *)&aesctx->enc, + src, dst, length); +} + + +static void qcrypto_cipher_aes_xts_decrypt(const void *ctx, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + const QCryptoCipherBuiltinAESContext *aesctx = ctx; + + qcrypto_cipher_aes_ecb_decrypt((AES_KEY *)&aesctx->dec, + src, dst, length); +} + + static int qcrypto_cipher_encrypt_aes(QCryptoCipher *cipher, const void *in, void *out, @@ -75,29 +157,26 @@ static int qcrypto_cipher_encrypt_aes(QCryptoCipher *cipher, { QCryptoCipherBuiltin *ctxt = cipher->opaque; - if (cipher->mode == QCRYPTO_CIPHER_MODE_ECB) { - const uint8_t *inptr = in; - uint8_t *outptr = out; - while (len) { - if (len > AES_BLOCK_SIZE) { - AES_encrypt(inptr, outptr, &ctxt->state.aes.encrypt_key); - inptr += AES_BLOCK_SIZE; - outptr += AES_BLOCK_SIZE; - len -= AES_BLOCK_SIZE; - } else { - uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE]; - memcpy(tmp1, inptr, len); - /* Fill with 0 to avoid valgrind uninitialized reads */ - memset(tmp1 + len, 0, sizeof(tmp1) - len); - AES_encrypt(tmp1, tmp2, &ctxt->state.aes.encrypt_key); - memcpy(outptr, tmp2, len); - len = 0; - } - } - } else { + switch (cipher->mode) { + case QCRYPTO_CIPHER_MODE_ECB: + qcrypto_cipher_aes_ecb_encrypt(&ctxt->state.aes.key.enc, + in, out, len); + break; + case QCRYPTO_CIPHER_MODE_CBC: AES_cbc_encrypt(in, out, len, - &ctxt->state.aes.encrypt_key, + &ctxt->state.aes.key.enc, ctxt->state.aes.iv, 1); + break; + case QCRYPTO_CIPHER_MODE_XTS: + xts_encrypt(&ctxt->state.aes.key, + &ctxt->state.aes.key_tweak, + qcrypto_cipher_aes_xts_encrypt, + qcrypto_cipher_aes_xts_decrypt, + ctxt->state.aes.iv, + len, out, in); + break; + default: + g_assert_not_reached(); } return 0; @@ -112,29 +191,26 @@ static int qcrypto_cipher_decrypt_aes(QCryptoCipher *cipher, { QCryptoCipherBuiltin *ctxt = cipher->opaque; - if (cipher->mode == QCRYPTO_CIPHER_MODE_ECB) { - const uint8_t *inptr = in; - uint8_t *outptr = out; - while (len) { - if (len > AES_BLOCK_SIZE) { - AES_decrypt(inptr, outptr, &ctxt->state.aes.decrypt_key); - inptr += AES_BLOCK_SIZE; - outptr += AES_BLOCK_SIZE; - len -= AES_BLOCK_SIZE; - } else { - uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE]; - memcpy(tmp1, inptr, len); - /* Fill with 0 to avoid valgrind uninitialized reads */ - memset(tmp1 + len, 0, sizeof(tmp1) - len); - AES_decrypt(tmp1, tmp2, &ctxt->state.aes.decrypt_key); - memcpy(outptr, tmp2, len); - len = 0; - } - } - } else { + switch (cipher->mode) { + case QCRYPTO_CIPHER_MODE_ECB: + qcrypto_cipher_aes_ecb_decrypt(&ctxt->state.aes.key.dec, + in, out, len); + break; + case QCRYPTO_CIPHER_MODE_CBC: AES_cbc_encrypt(in, out, len, - &ctxt->state.aes.decrypt_key, + &ctxt->state.aes.key.dec, ctxt->state.aes.iv, 0); + break; + case QCRYPTO_CIPHER_MODE_XTS: + xts_decrypt(&ctxt->state.aes.key, + &ctxt->state.aes.key_tweak, + qcrypto_cipher_aes_xts_encrypt, + qcrypto_cipher_aes_xts_decrypt, + ctxt->state.aes.iv, + len, out, in); + break; + default: + g_assert_not_reached(); } return 0; @@ -145,15 +221,13 @@ static int qcrypto_cipher_setiv_aes(QCryptoCipher *cipher, Error **errp) { QCryptoCipherBuiltin *ctxt = cipher->opaque; - if (niv != 16) { - error_setg(errp, "IV must be 16 bytes not %zu", niv); + if (niv != AES_BLOCK_SIZE) { + error_setg(errp, "IV must be %d bytes not %zu", + AES_BLOCK_SIZE, niv); return -1; } - g_free(ctxt->state.aes.iv); - ctxt->state.aes.iv = g_new0(uint8_t, niv); - memcpy(ctxt->state.aes.iv, iv, niv); - ctxt->state.aes.niv = niv; + memcpy(ctxt->state.aes.iv, iv, AES_BLOCK_SIZE); return 0; } @@ -168,23 +242,49 @@ static int qcrypto_cipher_init_aes(QCryptoCipher *cipher, QCryptoCipherBuiltin *ctxt; if (cipher->mode != QCRYPTO_CIPHER_MODE_CBC && - cipher->mode != QCRYPTO_CIPHER_MODE_ECB) { + cipher->mode != QCRYPTO_CIPHER_MODE_ECB && + cipher->mode != QCRYPTO_CIPHER_MODE_XTS) { error_setg(errp, "Unsupported cipher mode %d", cipher->mode); return -1; } ctxt = g_new0(QCryptoCipherBuiltin, 1); - if (AES_set_encrypt_key(key, nkey * 8, &ctxt->state.aes.encrypt_key) != 0) { - error_setg(errp, "Failed to set encryption key"); - goto error; - } + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + if (AES_set_encrypt_key(key, nkey * 4, &ctxt->state.aes.key.enc) != 0) { + error_setg(errp, "Failed to set encryption key"); + goto error; + } - if (AES_set_decrypt_key(key, nkey * 8, &ctxt->state.aes.decrypt_key) != 0) { - error_setg(errp, "Failed to set decryption key"); - goto error; + if (AES_set_decrypt_key(key, nkey * 4, &ctxt->state.aes.key.dec) != 0) { + error_setg(errp, "Failed to set decryption key"); + goto error; + } + + if (AES_set_encrypt_key(key + (nkey / 2), nkey * 4, + &ctxt->state.aes.key_tweak.enc) != 0) { + error_setg(errp, "Failed to set encryption key"); + goto error; + } + + if (AES_set_decrypt_key(key + (nkey / 2), nkey * 4, + &ctxt->state.aes.key_tweak.dec) != 0) { + error_setg(errp, "Failed to set decryption key"); + goto error; + } + } else { + if (AES_set_encrypt_key(key, nkey * 8, &ctxt->state.aes.key.enc) != 0) { + error_setg(errp, "Failed to set encryption key"); + goto error; + } + + if (AES_set_decrypt_key(key, nkey * 8, &ctxt->state.aes.key.dec) != 0) { + error_setg(errp, "Failed to set decryption key"); + goto error; + } } + ctxt->blocksize = AES_BLOCK_SIZE; ctxt->free = qcrypto_cipher_free_aes; ctxt->setiv = qcrypto_cipher_setiv_aes; ctxt->encrypt = qcrypto_cipher_encrypt_aes; @@ -286,6 +386,7 @@ static int qcrypto_cipher_init_des_rfb(QCryptoCipher *cipher, memcpy(ctxt->state.desrfb.key, key, nkey); ctxt->state.desrfb.nkey = nkey; + ctxt->blocksize = 8; ctxt->free = qcrypto_cipher_free_des_rfb; ctxt->setiv = qcrypto_cipher_setiv_des_rfb; ctxt->encrypt = qcrypto_cipher_encrypt_des_rfb; @@ -322,7 +423,7 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, cipher->alg = alg; cipher->mode = mode; - if (!qcrypto_cipher_validate_key_length(alg, nkey, errp)) { + if (!qcrypto_cipher_validate_key_length(alg, mode, nkey, errp)) { goto error; } @@ -374,6 +475,12 @@ int qcrypto_cipher_encrypt(QCryptoCipher *cipher, { QCryptoCipherBuiltin *ctxt = cipher->opaque; + if (len % ctxt->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctxt->blocksize); + return -1; + } + return ctxt->encrypt(cipher, in, out, len, errp); } @@ -386,6 +493,12 @@ int qcrypto_cipher_decrypt(QCryptoCipher *cipher, { QCryptoCipherBuiltin *ctxt = cipher->opaque; + if (len % ctxt->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctxt->blocksize); + return -1; + } + return ctxt->decrypt(cipher, in, out, len, errp); } diff --git a/qemu/crypto/cipher-gcrypt.c b/qemu/crypto/cipher-gcrypt.c index 8cfc56250..ede2f70df 100644 --- a/qemu/crypto/cipher-gcrypt.c +++ b/qemu/crypto/cipher-gcrypt.c @@ -18,6 +18,9 @@ * */ +#include "qemu/osdep.h" +#include "crypto/xts.h" + #include <gcrypt.h> @@ -28,12 +31,25 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg) case QCRYPTO_CIPHER_ALG_AES_128: case QCRYPTO_CIPHER_ALG_AES_192: case QCRYPTO_CIPHER_ALG_AES_256: + case QCRYPTO_CIPHER_ALG_CAST5_128: + case QCRYPTO_CIPHER_ALG_SERPENT_128: + case QCRYPTO_CIPHER_ALG_SERPENT_192: + case QCRYPTO_CIPHER_ALG_SERPENT_256: + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + case QCRYPTO_CIPHER_ALG_TWOFISH_256: return true; default: return false; } } +typedef struct QCryptoCipherGcrypt QCryptoCipherGcrypt; +struct QCryptoCipherGcrypt { + gcry_cipher_hd_t handle; + gcry_cipher_hd_t tweakhandle; + size_t blocksize; + uint8_t *iv; +}; QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, QCryptoCipherMode mode, @@ -41,12 +57,13 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, Error **errp) { QCryptoCipher *cipher; - gcry_cipher_hd_t handle; + QCryptoCipherGcrypt *ctx; gcry_error_t err; int gcryalg, gcrymode; switch (mode) { case QCRYPTO_CIPHER_MODE_ECB: + case QCRYPTO_CIPHER_MODE_XTS: gcrymode = GCRY_CIPHER_MODE_ECB; break; case QCRYPTO_CIPHER_MODE_CBC: @@ -57,7 +74,7 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, return NULL; } - if (!qcrypto_cipher_validate_key_length(alg, nkey, errp)) { + if (!qcrypto_cipher_validate_key_length(alg, mode, nkey, errp)) { return NULL; } @@ -78,6 +95,30 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, gcryalg = GCRY_CIPHER_AES256; break; + case QCRYPTO_CIPHER_ALG_CAST5_128: + gcryalg = GCRY_CIPHER_CAST5; + break; + + case QCRYPTO_CIPHER_ALG_SERPENT_128: + gcryalg = GCRY_CIPHER_SERPENT128; + break; + + case QCRYPTO_CIPHER_ALG_SERPENT_192: + gcryalg = GCRY_CIPHER_SERPENT192; + break; + + case QCRYPTO_CIPHER_ALG_SERPENT_256: + gcryalg = GCRY_CIPHER_SERPENT256; + break; + + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + gcryalg = GCRY_CIPHER_TWOFISH128; + break; + + case QCRYPTO_CIPHER_ALG_TWOFISH_256: + gcryalg = GCRY_CIPHER_TWOFISH; + break; + default: error_setg(errp, "Unsupported cipher algorithm %d", alg); return NULL; @@ -87,12 +128,22 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, cipher->alg = alg; cipher->mode = mode; - err = gcry_cipher_open(&handle, gcryalg, gcrymode, 0); + ctx = g_new0(QCryptoCipherGcrypt, 1); + + err = gcry_cipher_open(&ctx->handle, gcryalg, gcrymode, 0); if (err != 0) { error_setg(errp, "Cannot initialize cipher: %s", gcry_strerror(err)); goto error; } + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + err = gcry_cipher_open(&ctx->tweakhandle, gcryalg, gcrymode, 0); + if (err != 0) { + error_setg(errp, "Cannot initialize cipher: %s", + gcry_strerror(err)); + goto error; + } + } if (cipher->alg == QCRYPTO_CIPHER_ALG_DES_RFB) { /* We're using standard DES cipher from gcrypt, so we need @@ -100,22 +151,59 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, * bizarre RFB variant of DES :-) */ uint8_t *rfbkey = qcrypto_cipher_munge_des_rfb_key(key, nkey); - err = gcry_cipher_setkey(handle, rfbkey, nkey); + err = gcry_cipher_setkey(ctx->handle, rfbkey, nkey); g_free(rfbkey); + ctx->blocksize = 8; } else { - err = gcry_cipher_setkey(handle, key, nkey); + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + nkey /= 2; + err = gcry_cipher_setkey(ctx->handle, key, nkey); + if (err != 0) { + error_setg(errp, "Cannot set key: %s", + gcry_strerror(err)); + goto error; + } + err = gcry_cipher_setkey(ctx->tweakhandle, key + nkey, nkey); + } else { + err = gcry_cipher_setkey(ctx->handle, key, nkey); + } + if (err != 0) { + error_setg(errp, "Cannot set key: %s", + gcry_strerror(err)); + goto error; + } + switch (cipher->alg) { + case QCRYPTO_CIPHER_ALG_AES_128: + case QCRYPTO_CIPHER_ALG_AES_192: + case QCRYPTO_CIPHER_ALG_AES_256: + case QCRYPTO_CIPHER_ALG_SERPENT_128: + case QCRYPTO_CIPHER_ALG_SERPENT_192: + case QCRYPTO_CIPHER_ALG_SERPENT_256: + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + case QCRYPTO_CIPHER_ALG_TWOFISH_256: + ctx->blocksize = 16; + break; + case QCRYPTO_CIPHER_ALG_CAST5_128: + ctx->blocksize = 8; + break; + default: + g_assert_not_reached(); + } } - if (err != 0) { - error_setg(errp, "Cannot set key: %s", - gcry_strerror(err)); - goto error; + + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + ctx->iv = g_new0(uint8_t, ctx->blocksize); } - cipher->opaque = handle; + cipher->opaque = ctx; return cipher; error: - gcry_cipher_close(handle); + gcry_cipher_close(ctx->handle); + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + gcry_cipher_close(ctx->tweakhandle); + } + g_free(ctx); g_free(cipher); return NULL; } @@ -123,34 +211,72 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, void qcrypto_cipher_free(QCryptoCipher *cipher) { - gcry_cipher_hd_t handle; + QCryptoCipherGcrypt *ctx; if (!cipher) { return; } - handle = cipher->opaque; - gcry_cipher_close(handle); + ctx = cipher->opaque; + gcry_cipher_close(ctx->handle); + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + gcry_cipher_close(ctx->tweakhandle); + } + g_free(ctx->iv); + g_free(ctx); g_free(cipher); } +static void qcrypto_gcrypt_xts_encrypt(const void *ctx, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + gcry_error_t err; + err = gcry_cipher_encrypt((gcry_cipher_hd_t)ctx, dst, length, src, length); + g_assert(err == 0); +} + +static void qcrypto_gcrypt_xts_decrypt(const void *ctx, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + gcry_error_t err; + err = gcry_cipher_decrypt((gcry_cipher_hd_t)ctx, dst, length, src, length); + g_assert(err == 0); +} + int qcrypto_cipher_encrypt(QCryptoCipher *cipher, const void *in, void *out, size_t len, Error **errp) { - gcry_cipher_hd_t handle = cipher->opaque; + QCryptoCipherGcrypt *ctx = cipher->opaque; gcry_error_t err; - err = gcry_cipher_encrypt(handle, - out, len, - in, len); - if (err != 0) { - error_setg(errp, "Cannot encrypt data: %s", - gcry_strerror(err)); + if (len % ctx->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctx->blocksize); return -1; } + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + xts_encrypt(ctx->handle, ctx->tweakhandle, + qcrypto_gcrypt_xts_encrypt, + qcrypto_gcrypt_xts_decrypt, + ctx->iv, len, out, in); + } else { + err = gcry_cipher_encrypt(ctx->handle, + out, len, + in, len); + if (err != 0) { + error_setg(errp, "Cannot encrypt data: %s", + gcry_strerror(err)); + return -1; + } + } + return 0; } @@ -161,18 +287,31 @@ int qcrypto_cipher_decrypt(QCryptoCipher *cipher, size_t len, Error **errp) { - gcry_cipher_hd_t handle = cipher->opaque; + QCryptoCipherGcrypt *ctx = cipher->opaque; gcry_error_t err; - err = gcry_cipher_decrypt(handle, - out, len, - in, len); - if (err != 0) { - error_setg(errp, "Cannot decrypt data: %s", - gcry_strerror(err)); + if (len % ctx->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctx->blocksize); return -1; } + if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) { + xts_decrypt(ctx->handle, ctx->tweakhandle, + qcrypto_gcrypt_xts_encrypt, + qcrypto_gcrypt_xts_decrypt, + ctx->iv, len, out, in); + } else { + err = gcry_cipher_decrypt(ctx->handle, + out, len, + in, len); + if (err != 0) { + error_setg(errp, "Cannot decrypt data: %s", + gcry_strerror(err)); + return -1; + } + } + return 0; } @@ -180,16 +319,26 @@ int qcrypto_cipher_setiv(QCryptoCipher *cipher, const uint8_t *iv, size_t niv, Error **errp) { - gcry_cipher_hd_t handle = cipher->opaque; + QCryptoCipherGcrypt *ctx = cipher->opaque; gcry_error_t err; - gcry_cipher_reset(handle); - err = gcry_cipher_setiv(handle, iv, niv); - if (err != 0) { - error_setg(errp, "Cannot set IV: %s", - gcry_strerror(err)); + if (niv != ctx->blocksize) { + error_setg(errp, "Expected IV size %zu not %zu", + ctx->blocksize, niv); return -1; } + if (ctx->iv) { + memcpy(ctx->iv, iv, niv); + } else { + gcry_cipher_reset(ctx->handle); + err = gcry_cipher_setiv(ctx->handle, iv, niv); + if (err != 0) { + error_setg(errp, "Cannot set IV: %s", + gcry_strerror(err)); + return -1; + } + } + return 0; } diff --git a/qemu/crypto/cipher-nettle.c b/qemu/crypto/cipher-nettle.c index b01cb1c85..70909fb7f 100644 --- a/qemu/crypto/cipher-nettle.c +++ b/qemu/crypto/cipher-nettle.c @@ -18,58 +18,177 @@ * */ +#include "qemu/osdep.h" +#include "crypto/xts.h" + #include <nettle/nettle-types.h> #include <nettle/aes.h> #include <nettle/des.h> #include <nettle/cbc.h> +#include <nettle/cast128.h> +#include <nettle/serpent.h> +#include <nettle/twofish.h> -#if CONFIG_NETTLE_VERSION_MAJOR < 3 -typedef nettle_crypt_func nettle_cipher_func; +typedef void (*QCryptoCipherNettleFuncWrapper)(const void *ctx, + size_t length, + uint8_t *dst, + const uint8_t *src); +#if CONFIG_NETTLE_VERSION_MAJOR < 3 +typedef nettle_crypt_func * QCryptoCipherNettleFuncNative; typedef void * cipher_ctx_t; typedef unsigned cipher_length_t; + +#define cast5_set_key cast128_set_key #else +typedef nettle_cipher_func * QCryptoCipherNettleFuncNative; typedef const void * cipher_ctx_t; typedef size_t cipher_length_t; #endif -static nettle_cipher_func aes_encrypt_wrapper; -static nettle_cipher_func aes_decrypt_wrapper; -static nettle_cipher_func des_encrypt_wrapper; -static nettle_cipher_func des_decrypt_wrapper; +typedef struct QCryptoNettleAES { + struct aes_ctx enc; + struct aes_ctx dec; +} QCryptoNettleAES; + +static void aes_encrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + const QCryptoNettleAES *aesctx = ctx; + aes_encrypt(&aesctx->enc, length, dst, src); +} + +static void aes_decrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + const QCryptoNettleAES *aesctx = ctx; + aes_decrypt(&aesctx->dec, length, dst, src); +} + +static void des_encrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + des_encrypt(ctx, length, dst, src); +} + +static void des_decrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + des_decrypt(ctx, length, dst, src); +} + +static void cast128_encrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + cast128_encrypt(ctx, length, dst, src); +} + +static void cast128_decrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + cast128_decrypt(ctx, length, dst, src); +} + +static void serpent_encrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + serpent_encrypt(ctx, length, dst, src); +} -static void aes_encrypt_wrapper(cipher_ctx_t ctx, cipher_length_t length, +static void serpent_decrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + serpent_decrypt(ctx, length, dst, src); +} + +static void twofish_encrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + twofish_encrypt(ctx, length, dst, src); +} + +static void twofish_decrypt_native(cipher_ctx_t ctx, cipher_length_t length, + uint8_t *dst, const uint8_t *src) +{ + twofish_decrypt(ctx, length, dst, src); +} + +static void aes_encrypt_wrapper(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { - aes_encrypt(ctx, length, dst, src); + const QCryptoNettleAES *aesctx = ctx; + aes_encrypt(&aesctx->enc, length, dst, src); } -static void aes_decrypt_wrapper(cipher_ctx_t ctx, cipher_length_t length, +static void aes_decrypt_wrapper(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { - aes_decrypt(ctx, length, dst, src); + const QCryptoNettleAES *aesctx = ctx; + aes_decrypt(&aesctx->dec, length, dst, src); } -static void des_encrypt_wrapper(cipher_ctx_t ctx, cipher_length_t length, +static void des_encrypt_wrapper(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { des_encrypt(ctx, length, dst, src); } -static void des_decrypt_wrapper(cipher_ctx_t ctx, cipher_length_t length, +static void des_decrypt_wrapper(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { des_decrypt(ctx, length, dst, src); } +static void cast128_encrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + cast128_encrypt(ctx, length, dst, src); +} + +static void cast128_decrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + cast128_decrypt(ctx, length, dst, src); +} + +static void serpent_encrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + serpent_encrypt(ctx, length, dst, src); +} + +static void serpent_decrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + serpent_decrypt(ctx, length, dst, src); +} + +static void twofish_encrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + twofish_encrypt(ctx, length, dst, src); +} + +static void twofish_decrypt_wrapper(const void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + twofish_decrypt(ctx, length, dst, src); +} + typedef struct QCryptoCipherNettle QCryptoCipherNettle; struct QCryptoCipherNettle { - void *ctx_encrypt; - void *ctx_decrypt; - nettle_cipher_func *alg_encrypt; - nettle_cipher_func *alg_decrypt; + /* Primary cipher context for all modes */ + void *ctx; + /* Second cipher context for XTS mode only */ + void *ctx_tweak; + /* Cipher callbacks for both contexts */ + QCryptoCipherNettleFuncNative alg_encrypt_native; + QCryptoCipherNettleFuncNative alg_decrypt_native; + QCryptoCipherNettleFuncWrapper alg_encrypt_wrapper; + QCryptoCipherNettleFuncWrapper alg_decrypt_wrapper; + uint8_t *iv; - size_t niv; + size_t blocksize; }; bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg) @@ -79,6 +198,13 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg) case QCRYPTO_CIPHER_ALG_AES_128: case QCRYPTO_CIPHER_ALG_AES_192: case QCRYPTO_CIPHER_ALG_AES_256: + case QCRYPTO_CIPHER_ALG_CAST5_128: + case QCRYPTO_CIPHER_ALG_SERPENT_128: + case QCRYPTO_CIPHER_ALG_SERPENT_192: + case QCRYPTO_CIPHER_ALG_SERPENT_256: + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + case QCRYPTO_CIPHER_ALG_TWOFISH_192: + case QCRYPTO_CIPHER_ALG_TWOFISH_256: return true; default: return false; @@ -98,13 +224,14 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, switch (mode) { case QCRYPTO_CIPHER_MODE_ECB: case QCRYPTO_CIPHER_MODE_CBC: + case QCRYPTO_CIPHER_MODE_XTS: break; default: error_setg(errp, "Unsupported cipher mode %d", mode); return NULL; } - if (!qcrypto_cipher_validate_key_length(alg, nkey, errp)) { + if (!qcrypto_cipher_validate_key_length(alg, mode, nkey, errp)) { return NULL; } @@ -116,38 +243,125 @@ QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg, switch (alg) { case QCRYPTO_CIPHER_ALG_DES_RFB: - ctx->ctx_encrypt = g_new0(struct des_ctx, 1); - ctx->ctx_decrypt = NULL; /* 1 ctx can do both */ + ctx->ctx = g_new0(struct des_ctx, 1); rfbkey = qcrypto_cipher_munge_des_rfb_key(key, nkey); - des_set_key(ctx->ctx_encrypt, rfbkey); + des_set_key(ctx->ctx, rfbkey); g_free(rfbkey); - ctx->alg_encrypt = des_encrypt_wrapper; - ctx->alg_decrypt = des_decrypt_wrapper; + ctx->alg_encrypt_native = des_encrypt_native; + ctx->alg_decrypt_native = des_decrypt_native; + ctx->alg_encrypt_wrapper = des_encrypt_wrapper; + ctx->alg_decrypt_wrapper = des_decrypt_wrapper; - ctx->niv = DES_BLOCK_SIZE; + ctx->blocksize = DES_BLOCK_SIZE; break; case QCRYPTO_CIPHER_ALG_AES_128: case QCRYPTO_CIPHER_ALG_AES_192: case QCRYPTO_CIPHER_ALG_AES_256: - ctx->ctx_encrypt = g_new0(struct aes_ctx, 1); - ctx->ctx_decrypt = g_new0(struct aes_ctx, 1); + ctx->ctx = g_new0(QCryptoNettleAES, 1); + + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + ctx->ctx_tweak = g_new0(QCryptoNettleAES, 1); + + nkey /= 2; + aes_set_encrypt_key(&((QCryptoNettleAES *)ctx->ctx)->enc, + nkey, key); + aes_set_decrypt_key(&((QCryptoNettleAES *)ctx->ctx)->dec, + nkey, key); + + aes_set_encrypt_key(&((QCryptoNettleAES *)ctx->ctx_tweak)->enc, + nkey, key + nkey); + aes_set_decrypt_key(&((QCryptoNettleAES *)ctx->ctx_tweak)->dec, + nkey, key + nkey); + } else { + aes_set_encrypt_key(&((QCryptoNettleAES *)ctx->ctx)->enc, + nkey, key); + aes_set_decrypt_key(&((QCryptoNettleAES *)ctx->ctx)->dec, + nkey, key); + } + + ctx->alg_encrypt_native = aes_encrypt_native; + ctx->alg_decrypt_native = aes_decrypt_native; + ctx->alg_encrypt_wrapper = aes_encrypt_wrapper; + ctx->alg_decrypt_wrapper = aes_decrypt_wrapper; + + ctx->blocksize = AES_BLOCK_SIZE; + break; + + case QCRYPTO_CIPHER_ALG_CAST5_128: + ctx->ctx = g_new0(struct cast128_ctx, 1); + + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + ctx->ctx_tweak = g_new0(struct cast128_ctx, 1); - aes_set_encrypt_key(ctx->ctx_encrypt, nkey, key); - aes_set_decrypt_key(ctx->ctx_decrypt, nkey, key); + nkey /= 2; + cast5_set_key(ctx->ctx, nkey, key); + cast5_set_key(ctx->ctx_tweak, nkey, key + nkey); + } else { + cast5_set_key(ctx->ctx, nkey, key); + } - ctx->alg_encrypt = aes_encrypt_wrapper; - ctx->alg_decrypt = aes_decrypt_wrapper; + ctx->alg_encrypt_native = cast128_encrypt_native; + ctx->alg_decrypt_native = cast128_decrypt_native; + ctx->alg_encrypt_wrapper = cast128_encrypt_wrapper; + ctx->alg_decrypt_wrapper = cast128_decrypt_wrapper; - ctx->niv = AES_BLOCK_SIZE; + ctx->blocksize = CAST128_BLOCK_SIZE; break; + + case QCRYPTO_CIPHER_ALG_SERPENT_128: + case QCRYPTO_CIPHER_ALG_SERPENT_192: + case QCRYPTO_CIPHER_ALG_SERPENT_256: + ctx->ctx = g_new0(struct serpent_ctx, 1); + + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + ctx->ctx_tweak = g_new0(struct serpent_ctx, 1); + + nkey /= 2; + serpent_set_key(ctx->ctx, nkey, key); + serpent_set_key(ctx->ctx_tweak, nkey, key + nkey); + } else { + serpent_set_key(ctx->ctx, nkey, key); + } + + ctx->alg_encrypt_native = serpent_encrypt_native; + ctx->alg_decrypt_native = serpent_decrypt_native; + ctx->alg_encrypt_wrapper = serpent_encrypt_wrapper; + ctx->alg_decrypt_wrapper = serpent_decrypt_wrapper; + + ctx->blocksize = SERPENT_BLOCK_SIZE; + break; + + case QCRYPTO_CIPHER_ALG_TWOFISH_128: + case QCRYPTO_CIPHER_ALG_TWOFISH_192: + case QCRYPTO_CIPHER_ALG_TWOFISH_256: + ctx->ctx = g_new0(struct twofish_ctx, 1); + + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + ctx->ctx_tweak = g_new0(struct twofish_ctx, 1); + + nkey /= 2; + twofish_set_key(ctx->ctx, nkey, key); + twofish_set_key(ctx->ctx_tweak, nkey, key + nkey); + } else { + twofish_set_key(ctx->ctx, nkey, key); + } + + ctx->alg_encrypt_native = twofish_encrypt_native; + ctx->alg_decrypt_native = twofish_decrypt_native; + ctx->alg_encrypt_wrapper = twofish_encrypt_wrapper; + ctx->alg_decrypt_wrapper = twofish_decrypt_wrapper; + + ctx->blocksize = TWOFISH_BLOCK_SIZE; + break; + default: error_setg(errp, "Unsupported cipher algorithm %d", alg); goto error; } - ctx->iv = g_new0(uint8_t, ctx->niv); + ctx->iv = g_new0(uint8_t, ctx->blocksize); cipher->opaque = ctx; return cipher; @@ -169,8 +383,8 @@ void qcrypto_cipher_free(QCryptoCipher *cipher) ctx = cipher->opaque; g_free(ctx->iv); - g_free(ctx->ctx_encrypt); - g_free(ctx->ctx_decrypt); + g_free(ctx->ctx); + g_free(ctx->ctx_tweak); g_free(ctx); g_free(cipher); } @@ -184,16 +398,29 @@ int qcrypto_cipher_encrypt(QCryptoCipher *cipher, { QCryptoCipherNettle *ctx = cipher->opaque; + if (len % ctx->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctx->blocksize); + return -1; + } + switch (cipher->mode) { case QCRYPTO_CIPHER_MODE_ECB: - ctx->alg_encrypt(ctx->ctx_encrypt, len, out, in); + ctx->alg_encrypt_wrapper(ctx->ctx, len, out, in); break; case QCRYPTO_CIPHER_MODE_CBC: - cbc_encrypt(ctx->ctx_encrypt, ctx->alg_encrypt, - ctx->niv, ctx->iv, + cbc_encrypt(ctx->ctx, ctx->alg_encrypt_native, + ctx->blocksize, ctx->iv, len, out, in); break; + + case QCRYPTO_CIPHER_MODE_XTS: + xts_encrypt(ctx->ctx, ctx->ctx_tweak, + ctx->alg_encrypt_wrapper, ctx->alg_encrypt_wrapper, + ctx->iv, len, out, in); + break; + default: error_setg(errp, "Unsupported cipher algorithm %d", cipher->alg); @@ -211,17 +438,34 @@ int qcrypto_cipher_decrypt(QCryptoCipher *cipher, { QCryptoCipherNettle *ctx = cipher->opaque; + if (len % ctx->blocksize) { + error_setg(errp, "Length %zu must be a multiple of block size %zu", + len, ctx->blocksize); + return -1; + } + switch (cipher->mode) { case QCRYPTO_CIPHER_MODE_ECB: - ctx->alg_decrypt(ctx->ctx_decrypt ? ctx->ctx_decrypt : ctx->ctx_encrypt, - len, out, in); + ctx->alg_decrypt_wrapper(ctx->ctx, len, out, in); break; case QCRYPTO_CIPHER_MODE_CBC: - cbc_decrypt(ctx->ctx_decrypt ? ctx->ctx_decrypt : ctx->ctx_encrypt, - ctx->alg_decrypt, ctx->niv, ctx->iv, + cbc_decrypt(ctx->ctx, ctx->alg_decrypt_native, + ctx->blocksize, ctx->iv, len, out, in); break; + + case QCRYPTO_CIPHER_MODE_XTS: + if (ctx->blocksize != XTS_BLOCK_SIZE) { + error_setg(errp, "Block size must be %d not %zu", + XTS_BLOCK_SIZE, ctx->blocksize); + return -1; + } + xts_decrypt(ctx->ctx, ctx->ctx_tweak, + ctx->alg_encrypt_wrapper, ctx->alg_decrypt_wrapper, + ctx->iv, len, out, in); + break; + default: error_setg(errp, "Unsupported cipher algorithm %d", cipher->alg); @@ -235,9 +479,9 @@ int qcrypto_cipher_setiv(QCryptoCipher *cipher, Error **errp) { QCryptoCipherNettle *ctx = cipher->opaque; - if (niv != ctx->niv) { + if (niv != ctx->blocksize) { error_setg(errp, "Expected IV size %zu not %zu", - ctx->niv, niv); + ctx->blocksize, niv); return -1; } memcpy(ctx->iv, iv, niv); diff --git a/qemu/crypto/cipher.c b/qemu/crypto/cipher.c index 024a00cb5..cafb45436 100644 --- a/qemu/crypto/cipher.c +++ b/qemu/crypto/cipher.c @@ -18,36 +18,119 @@ * */ +#include "qemu/osdep.h" +#include "qapi/error.h" #include "crypto/cipher.h" -static size_t alg_key_len[QCRYPTO_CIPHER_ALG_LAST] = { +static size_t alg_key_len[QCRYPTO_CIPHER_ALG__MAX] = { [QCRYPTO_CIPHER_ALG_AES_128] = 16, [QCRYPTO_CIPHER_ALG_AES_192] = 24, [QCRYPTO_CIPHER_ALG_AES_256] = 32, [QCRYPTO_CIPHER_ALG_DES_RFB] = 8, + [QCRYPTO_CIPHER_ALG_CAST5_128] = 16, + [QCRYPTO_CIPHER_ALG_SERPENT_128] = 16, + [QCRYPTO_CIPHER_ALG_SERPENT_192] = 24, + [QCRYPTO_CIPHER_ALG_SERPENT_256] = 32, + [QCRYPTO_CIPHER_ALG_TWOFISH_128] = 16, + [QCRYPTO_CIPHER_ALG_TWOFISH_192] = 24, + [QCRYPTO_CIPHER_ALG_TWOFISH_256] = 32, }; +static size_t alg_block_len[QCRYPTO_CIPHER_ALG__MAX] = { + [QCRYPTO_CIPHER_ALG_AES_128] = 16, + [QCRYPTO_CIPHER_ALG_AES_192] = 16, + [QCRYPTO_CIPHER_ALG_AES_256] = 16, + [QCRYPTO_CIPHER_ALG_DES_RFB] = 8, + [QCRYPTO_CIPHER_ALG_CAST5_128] = 8, + [QCRYPTO_CIPHER_ALG_SERPENT_128] = 16, + [QCRYPTO_CIPHER_ALG_SERPENT_192] = 16, + [QCRYPTO_CIPHER_ALG_SERPENT_256] = 16, + [QCRYPTO_CIPHER_ALG_TWOFISH_128] = 16, + [QCRYPTO_CIPHER_ALG_TWOFISH_192] = 16, + [QCRYPTO_CIPHER_ALG_TWOFISH_256] = 16, +}; + +static bool mode_need_iv[QCRYPTO_CIPHER_MODE__MAX] = { + [QCRYPTO_CIPHER_MODE_ECB] = false, + [QCRYPTO_CIPHER_MODE_CBC] = true, + [QCRYPTO_CIPHER_MODE_XTS] = true, +}; + + +size_t qcrypto_cipher_get_block_len(QCryptoCipherAlgorithm alg) +{ + if (alg >= G_N_ELEMENTS(alg_key_len)) { + return 0; + } + return alg_block_len[alg]; +} + + +size_t qcrypto_cipher_get_key_len(QCryptoCipherAlgorithm alg) +{ + if (alg >= G_N_ELEMENTS(alg_key_len)) { + return 0; + } + return alg_key_len[alg]; +} + + +size_t qcrypto_cipher_get_iv_len(QCryptoCipherAlgorithm alg, + QCryptoCipherMode mode) +{ + if (alg >= G_N_ELEMENTS(alg_block_len)) { + return 0; + } + if (mode >= G_N_ELEMENTS(mode_need_iv)) { + return 0; + } + + if (mode_need_iv[mode]) { + return alg_block_len[alg]; + } + return 0; +} + + static bool qcrypto_cipher_validate_key_length(QCryptoCipherAlgorithm alg, + QCryptoCipherMode mode, size_t nkey, Error **errp) { - if ((unsigned)alg >= QCRYPTO_CIPHER_ALG_LAST) { + if ((unsigned)alg >= QCRYPTO_CIPHER_ALG__MAX) { error_setg(errp, "Cipher algorithm %d out of range", alg); return false; } - if (alg_key_len[alg] != nkey) { - error_setg(errp, "Cipher key length %zu should be %zu", - alg_key_len[alg], nkey); - return false; + if (mode == QCRYPTO_CIPHER_MODE_XTS) { + if (alg == QCRYPTO_CIPHER_ALG_DES_RFB) { + error_setg(errp, "XTS mode not compatible with DES-RFB"); + return false; + } + if (nkey % 2) { + error_setg(errp, "XTS cipher key length should be a multiple of 2"); + return false; + } + + if (alg_key_len[alg] != (nkey / 2)) { + error_setg(errp, "Cipher key length %zu should be %zu", + nkey, alg_key_len[alg] * 2); + return false; + } + } else { + if (alg_key_len[alg] != nkey) { + error_setg(errp, "Cipher key length %zu should be %zu", + nkey, alg_key_len[alg]); + return false; + } } return true; } -#if defined(CONFIG_GNUTLS_GCRYPT) || defined(CONFIG_GNUTLS_NETTLE) +#if defined(CONFIG_GCRYPT) || defined(CONFIG_NETTLE) static uint8_t * qcrypto_cipher_munge_des_rfb_key(const uint8_t *key, size_t nkey) @@ -63,11 +146,11 @@ qcrypto_cipher_munge_des_rfb_key(const uint8_t *key, } return ret; } -#endif /* CONFIG_GNUTLS_GCRYPT || CONFIG_GNUTLS_NETTLE */ +#endif /* CONFIG_GCRYPT || CONFIG_NETTLE */ -#ifdef CONFIG_GNUTLS_GCRYPT +#ifdef CONFIG_GCRYPT #include "crypto/cipher-gcrypt.c" -#elif defined CONFIG_GNUTLS_NETTLE +#elif defined CONFIG_NETTLE #include "crypto/cipher-nettle.c" #else #include "crypto/cipher-builtin.c" diff --git a/qemu/crypto/desrfb.c b/qemu/crypto/desrfb.c index fc20a30df..ec47dea3b 100644 --- a/qemu/crypto/desrfb.c +++ b/qemu/crypto/desrfb.c @@ -26,6 +26,7 @@ * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992. */ +#include "qemu/osdep.h" #include "crypto/desrfb.h" static void scrunch(unsigned char *, unsigned long *); diff --git a/qemu/crypto/hash.c b/qemu/crypto/hash.c index 81e74de86..b90af3495 100644 --- a/qemu/crypto/hash.c +++ b/qemu/crypto/hash.c @@ -18,13 +18,33 @@ * */ +#include "qemu/osdep.h" +#include "qapi/error.h" #include "crypto/hash.h" #ifdef CONFIG_GNUTLS_HASH #include <gnutls/gnutls.h> #include <gnutls/crypto.h> +#endif -static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG_LAST] = { + +static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG__MAX] = { + [QCRYPTO_HASH_ALG_MD5] = 16, + [QCRYPTO_HASH_ALG_SHA1] = 20, + [QCRYPTO_HASH_ALG_SHA256] = 32, +}; + +size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg) +{ + if (alg >= G_N_ELEMENTS(qcrypto_hash_alg_size)) { + return 0; + } + return qcrypto_hash_alg_size[alg]; +} + + +#ifdef CONFIG_GNUTLS_HASH +static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG__MAX] = { [QCRYPTO_HASH_ALG_MD5] = GNUTLS_DIG_MD5, [QCRYPTO_HASH_ALG_SHA1] = GNUTLS_DIG_SHA1, [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256, @@ -38,6 +58,7 @@ gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg) return false; } + int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg, const struct iovec *iov, size_t niov, diff --git a/qemu/crypto/init.c b/qemu/crypto/init.c index 7447882c7..1e564d949 100644 --- a/qemu/crypto/init.c +++ b/qemu/crypto/init.c @@ -18,14 +18,17 @@ * */ +#include "qemu/osdep.h" #include "crypto/init.h" +#include "qapi/error.h" #include "qemu/thread.h" #ifdef CONFIG_GNUTLS #include <gnutls/gnutls.h> #include <gnutls/crypto.h> +#endif -#ifdef CONFIG_GNUTLS_GCRYPT +#ifdef CONFIG_GCRYPT #include <gcrypt.h> #endif @@ -37,6 +40,7 @@ * - When GNUTLS >= 2.12, we must not initialize gcrypt threading * because GNUTLS will do that itself * - When GNUTLS < 2.12 we must always initialize gcrypt threading + * - When GNUTLS is disabled we must always initialize gcrypt threading * * But.... * @@ -47,12 +51,15 @@ * * - gcrypt < 1.6.0 * AND - * - gnutls < 2.12 + * - gnutls < 2.12 + * OR + * - gnutls is disabled * */ -#if (defined(CONFIG_GNUTLS_GCRYPT) && \ - (!defined(GNUTLS_VERSION_NUMBER) || \ +#if (defined(CONFIG_GCRYPT) && \ + (!defined(CONFIG_GNUTLS) || \ + !defined(GNUTLS_VERSION_NUMBER) || \ (GNUTLS_VERSION_NUMBER < 0x020c00)) && \ (!defined(GCRYPT_VERSION_NUMBER) || \ (GCRYPT_VERSION_NUMBER < 0x010600))) @@ -113,6 +120,7 @@ static struct gcry_thread_cbs qcrypto_gcrypt_thread_impl = { int qcrypto_init(Error **errp) { +#ifdef CONFIG_GNUTLS int ret; ret = gnutls_global_init(); if (ret < 0) { @@ -125,8 +133,9 @@ int qcrypto_init(Error **errp) gnutls_global_set_log_level(10); gnutls_global_set_log_function(qcrypto_gnutls_log); #endif +#endif -#ifdef CONFIG_GNUTLS_GCRYPT +#ifdef CONFIG_GCRYPT if (!gcry_check_version(GCRYPT_VERSION)) { error_setg(errp, "Unable to initialize gcrypt"); return -1; @@ -139,12 +148,3 @@ int qcrypto_init(Error **errp) return 0; } - -#else /* ! CONFIG_GNUTLS */ - -int qcrypto_init(Error **errp G_GNUC_UNUSED) -{ - return 0; -} - -#endif /* ! CONFIG_GNUTLS */ diff --git a/qemu/crypto/ivgen-essiv.c b/qemu/crypto/ivgen-essiv.c new file mode 100644 index 000000000..634de6333 --- /dev/null +++ b/qemu/crypto/ivgen-essiv.c @@ -0,0 +1,120 @@ +/* + * QEMU Crypto block IV generator - essiv + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "crypto/ivgen-essiv.h" + +typedef struct QCryptoIVGenESSIV QCryptoIVGenESSIV; +struct QCryptoIVGenESSIV { + QCryptoCipher *cipher; +}; + +static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen, + const uint8_t *key, size_t nkey, + Error **errp) +{ + uint8_t *salt; + size_t nhash; + size_t nsalt; + QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1); + + /* Not necessarily the same as nkey */ + nsalt = qcrypto_cipher_get_key_len(ivgen->cipher); + + nhash = qcrypto_hash_digest_len(ivgen->hash); + /* Salt must be larger of hash size or key size */ + salt = g_new0(uint8_t, MAX(nhash, nsalt)); + + if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey, + &salt, &nhash, + errp) < 0) { + g_free(essiv); + return -1; + } + + /* Now potentially truncate salt to match cipher key len */ + essiv->cipher = qcrypto_cipher_new(ivgen->cipher, + QCRYPTO_CIPHER_MODE_ECB, + salt, MIN(nhash, nsalt), + errp); + if (!essiv->cipher) { + g_free(essiv); + g_free(salt); + return -1; + } + + g_free(salt); + ivgen->private = essiv; + + return 0; +} + +static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen, + uint64_t sector, + uint8_t *iv, size_t niv, + Error **errp) +{ + QCryptoIVGenESSIV *essiv = ivgen->private; + size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher); + uint8_t *data = g_new(uint8_t, ndata); + + sector = cpu_to_le64(sector); + memcpy(data, (uint8_t *)§or, ndata); + if (sizeof(sector) < ndata) { + memset(data + sizeof(sector), 0, ndata - sizeof(sector)); + } + + if (qcrypto_cipher_encrypt(essiv->cipher, + data, + data, + ndata, + errp) < 0) { + g_free(data); + return -1; + } + + if (ndata > niv) { + ndata = niv; + } + memcpy(iv, data, ndata); + if (ndata < niv) { + memset(iv + ndata, 0, niv - ndata); + } + g_free(data); + return 0; +} + +static void qcrypto_ivgen_essiv_cleanup(QCryptoIVGen *ivgen) +{ + QCryptoIVGenESSIV *essiv = ivgen->private; + + qcrypto_cipher_free(essiv->cipher); + g_free(essiv); +} + + +struct QCryptoIVGenDriver qcrypto_ivgen_essiv = { + .init = qcrypto_ivgen_essiv_init, + .calculate = qcrypto_ivgen_essiv_calculate, + .cleanup = qcrypto_ivgen_essiv_cleanup, +}; + diff --git a/qemu/crypto/ivgen-essiv.h b/qemu/crypto/ivgen-essiv.h new file mode 100644 index 000000000..4a00af849 --- /dev/null +++ b/qemu/crypto/ivgen-essiv.h @@ -0,0 +1,28 @@ +/* + * QEMU Crypto block IV generator - essiv + * + * Copyright (c) 2015-2016 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/ivgenpriv.h" + +#ifndef QCRYPTO_IVGEN_ESSIV_H__ +#define QCRYPTO_IVGEN_ESSIV_H__ + +extern struct QCryptoIVGenDriver qcrypto_ivgen_essiv; + +#endif /* QCRYPTO_IVGEN_ESSIV_H__ */ diff --git a/qemu/crypto/ivgen-plain.c b/qemu/crypto/ivgen-plain.c new file mode 100644 index 000000000..9b9b4ad0b --- /dev/null +++ b/qemu/crypto/ivgen-plain.c @@ -0,0 +1,61 @@ +/* + * QEMU Crypto block IV generator - plain + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "crypto/ivgen-plain.h" + +static int qcrypto_ivgen_plain_init(QCryptoIVGen *ivgen, + const uint8_t *key, size_t nkey, + Error **errp) +{ + return 0; +} + +static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen, + uint64_t sector, + uint8_t *iv, size_t niv, + Error **errp) +{ + size_t ivprefix; + uint32_t shortsector = cpu_to_le32((sector & 0xffffffff)); + ivprefix = sizeof(shortsector); + if (ivprefix > niv) { + ivprefix = niv; + } + memcpy(iv, &shortsector, ivprefix); + if (ivprefix < niv) { + memset(iv + ivprefix, 0, niv - ivprefix); + } + return 0; +} + +static void qcrypto_ivgen_plain_cleanup(QCryptoIVGen *ivgen) +{ +} + + +struct QCryptoIVGenDriver qcrypto_ivgen_plain = { + .init = qcrypto_ivgen_plain_init, + .calculate = qcrypto_ivgen_plain_calculate, + .cleanup = qcrypto_ivgen_plain_cleanup, +}; + diff --git a/qemu/crypto/ivgen-plain.h b/qemu/crypto/ivgen-plain.h new file mode 100644 index 000000000..0fe8835c3 --- /dev/null +++ b/qemu/crypto/ivgen-plain.h @@ -0,0 +1,28 @@ +/* + * QEMU Crypto block IV generator - plain + * + * Copyright (c) 2015-2016 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/ivgenpriv.h" + +#ifndef QCRYPTO_IVGEN_PLAIN_H__ +#define QCRYPTO_IVGEN_PLAIN_H__ + +extern struct QCryptoIVGenDriver qcrypto_ivgen_plain; + +#endif /* QCRYPTO_IVGEN_PLAIN_H__ */ diff --git a/qemu/crypto/ivgen-plain64.c b/qemu/crypto/ivgen-plain64.c new file mode 100644 index 000000000..6c6b1b44c --- /dev/null +++ b/qemu/crypto/ivgen-plain64.c @@ -0,0 +1,61 @@ +/* + * QEMU Crypto block IV generator - plain + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "crypto/ivgen-plain.h" + +static int qcrypto_ivgen_plain_init(QCryptoIVGen *ivgen, + const uint8_t *key, size_t nkey, + Error **errp) +{ + return 0; +} + +static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen, + uint64_t sector, + uint8_t *iv, size_t niv, + Error **errp) +{ + size_t ivprefix; + ivprefix = sizeof(sector); + sector = cpu_to_le64(sector); + if (ivprefix > niv) { + ivprefix = niv; + } + memcpy(iv, §or, ivprefix); + if (ivprefix < niv) { + memset(iv + ivprefix, 0, niv - ivprefix); + } + return 0; +} + +static void qcrypto_ivgen_plain_cleanup(QCryptoIVGen *ivgen) +{ +} + + +struct QCryptoIVGenDriver qcrypto_ivgen_plain64 = { + .init = qcrypto_ivgen_plain_init, + .calculate = qcrypto_ivgen_plain_calculate, + .cleanup = qcrypto_ivgen_plain_cleanup, +}; + diff --git a/qemu/crypto/ivgen-plain64.h b/qemu/crypto/ivgen-plain64.h new file mode 100644 index 000000000..c4104459b --- /dev/null +++ b/qemu/crypto/ivgen-plain64.h @@ -0,0 +1,28 @@ +/* + * QEMU Crypto block IV generator - plain64 + * + * Copyright (c) 2015-2016 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/ivgenpriv.h" + +#ifndef QCRYPTO_IVGEN_PLAIN64_H__ +#define QCRYPTO_IVGEN_PLAIN64_H__ + +extern struct QCryptoIVGenDriver qcrypto_ivgen_plain64; + +#endif /* QCRYPTO_IVGEN_PLAIN64_H__ */ diff --git a/qemu/crypto/ivgen.c b/qemu/crypto/ivgen.c new file mode 100644 index 000000000..f66435112 --- /dev/null +++ b/qemu/crypto/ivgen.c @@ -0,0 +1,101 @@ +/* + * QEMU Crypto block IV generator + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" + +#include "crypto/ivgenpriv.h" +#include "crypto/ivgen-plain.h" +#include "crypto/ivgen-plain64.h" +#include "crypto/ivgen-essiv.h" + + +QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgorithm alg, + QCryptoCipherAlgorithm cipheralg, + QCryptoHashAlgorithm hash, + const uint8_t *key, size_t nkey, + Error **errp) +{ + QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1); + + ivgen->algorithm = alg; + ivgen->cipher = cipheralg; + ivgen->hash = hash; + + switch (alg) { + case QCRYPTO_IVGEN_ALG_PLAIN: + ivgen->driver = &qcrypto_ivgen_plain; + break; + case QCRYPTO_IVGEN_ALG_PLAIN64: + ivgen->driver = &qcrypto_ivgen_plain64; + break; + case QCRYPTO_IVGEN_ALG_ESSIV: + ivgen->driver = &qcrypto_ivgen_essiv; + break; + default: + error_setg(errp, "Unknown block IV generator algorithm %d", alg); + g_free(ivgen); + return NULL; + } + + if (ivgen->driver->init(ivgen, key, nkey, errp) < 0) { + g_free(ivgen); + return NULL; + } + + return ivgen; +} + + +int qcrypto_ivgen_calculate(QCryptoIVGen *ivgen, + uint64_t sector, + uint8_t *iv, size_t niv, + Error **errp) +{ + return ivgen->driver->calculate(ivgen, sector, iv, niv, errp); +} + + +QCryptoIVGenAlgorithm qcrypto_ivgen_get_algorithm(QCryptoIVGen *ivgen) +{ + return ivgen->algorithm; +} + + +QCryptoCipherAlgorithm qcrypto_ivgen_get_cipher(QCryptoIVGen *ivgen) +{ + return ivgen->cipher; +} + + +QCryptoHashAlgorithm qcrypto_ivgen_get_hash(QCryptoIVGen *ivgen) +{ + return ivgen->hash; +} + + +void qcrypto_ivgen_free(QCryptoIVGen *ivgen) +{ + if (!ivgen) { + return; + } + ivgen->driver->cleanup(ivgen); + g_free(ivgen); +} diff --git a/qemu/crypto/ivgenpriv.h b/qemu/crypto/ivgenpriv.h new file mode 100644 index 000000000..7b87e02ea --- /dev/null +++ b/qemu/crypto/ivgenpriv.h @@ -0,0 +1,49 @@ +/* + * QEMU Crypto block IV generator + * + * Copyright (c) 2015-2016 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_IVGEN_PRIV_H__ +#define QCRYPTO_IVGEN_PRIV_H__ + +#include "crypto/ivgen.h" + +typedef struct QCryptoIVGenDriver QCryptoIVGenDriver; + +struct QCryptoIVGenDriver { + int (*init)(QCryptoIVGen *ivgen, + const uint8_t *key, size_t nkey, + Error **errp); + int (*calculate)(QCryptoIVGen *ivgen, + uint64_t sector, + uint8_t *iv, size_t niv, + Error **errp); + void (*cleanup)(QCryptoIVGen *ivgen); +}; + +struct QCryptoIVGen { + QCryptoIVGenDriver *driver; + void *private; + + QCryptoIVGenAlgorithm algorithm; + QCryptoCipherAlgorithm cipher; + QCryptoHashAlgorithm hash; +}; + + +#endif /* QCRYPTO_IVGEN_PRIV_H__ */ diff --git a/qemu/crypto/pbkdf-gcrypt.c b/qemu/crypto/pbkdf-gcrypt.c new file mode 100644 index 000000000..997b311d8 --- /dev/null +++ b/qemu/crypto/pbkdf-gcrypt.c @@ -0,0 +1,69 @@ +/* + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function) + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/pbkdf.h" +#include "gcrypt.h" + +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash) +{ + switch (hash) { + case QCRYPTO_HASH_ALG_MD5: + case QCRYPTO_HASH_ALG_SHA1: + case QCRYPTO_HASH_ALG_SHA256: + return true; + default: + return false; + } +} + +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash, + const uint8_t *key, size_t nkey, + const uint8_t *salt, size_t nsalt, + unsigned int iterations, + uint8_t *out, size_t nout, + Error **errp) +{ + static const int hash_map[QCRYPTO_HASH_ALG__MAX] = { + [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5, + [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1, + [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256, + }; + int ret; + + if (hash >= G_N_ELEMENTS(hash_map) || + hash_map[hash] == GCRY_MD_NONE) { + error_setg(errp, "Unexpected hash algorithm %d", hash); + return -1; + } + + ret = gcry_kdf_derive(key, nkey, GCRY_KDF_PBKDF2, + hash_map[hash], + salt, nsalt, iterations, + nout, out); + if (ret != 0) { + error_setg(errp, "Cannot derive password: %s", + gcry_strerror(ret)); + return -1; + } + + return 0; +} diff --git a/qemu/crypto/pbkdf-nettle.c b/qemu/crypto/pbkdf-nettle.c new file mode 100644 index 000000000..db9fc1578 --- /dev/null +++ b/qemu/crypto/pbkdf-nettle.c @@ -0,0 +1,66 @@ +/* + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function) + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/pbkdf.h" +#include "nettle/pbkdf2.h" + + +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash) +{ + switch (hash) { + case QCRYPTO_HASH_ALG_SHA1: + case QCRYPTO_HASH_ALG_SHA256: + return true; + default: + return false; + } +} + +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash, + const uint8_t *key, size_t nkey, + const uint8_t *salt, size_t nsalt, + unsigned int iterations, + uint8_t *out, size_t nout, + Error **errp) +{ + switch (hash) { + case QCRYPTO_HASH_ALG_SHA1: + pbkdf2_hmac_sha1(nkey, key, + iterations, + nsalt, salt, + nout, out); + break; + + case QCRYPTO_HASH_ALG_SHA256: + pbkdf2_hmac_sha256(nkey, key, + iterations, + nsalt, salt, + nout, out); + break; + + default: + error_setg_errno(errp, ENOSYS, + "PBKDF does not support hash algorithm %d", hash); + return -1; + } + return 0; +} diff --git a/qemu/crypto/pbkdf-stub.c b/qemu/crypto/pbkdf-stub.c new file mode 100644 index 000000000..266a5051b --- /dev/null +++ b/qemu/crypto/pbkdf-stub.c @@ -0,0 +1,43 @@ +/* + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function) + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/pbkdf.h" + +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash G_GNUC_UNUSED) +{ + return false; +} + +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash G_GNUC_UNUSED, + const uint8_t *key G_GNUC_UNUSED, + size_t nkey G_GNUC_UNUSED, + const uint8_t *salt G_GNUC_UNUSED, + size_t nsalt G_GNUC_UNUSED, + unsigned int iterations G_GNUC_UNUSED, + uint8_t *out G_GNUC_UNUSED, + size_t nout G_GNUC_UNUSED, + Error **errp) +{ + error_setg_errno(errp, ENOSYS, + "No crypto library supporting PBKDF in this build"); + return -1; +} diff --git a/qemu/crypto/pbkdf.c b/qemu/crypto/pbkdf.c new file mode 100644 index 000000000..695cc35df --- /dev/null +++ b/qemu/crypto/pbkdf.c @@ -0,0 +1,110 @@ +/* + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function) + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/pbkdf.h" +#ifndef _WIN32 +#include <sys/resource.h> +#endif + + +static int qcrypto_pbkdf2_get_thread_cpu(unsigned long long *val_ms, + Error **errp) +{ +#ifdef _WIN32 + FILETIME creation_time, exit_time, kernel_time, user_time; + ULARGE_INTEGER thread_time; + + if (!GetThreadTimes(GetCurrentThread(), &creation_time, &exit_time, + &kernel_time, &user_time)) { + error_setg(errp, "Unable to get thread CPU usage"); + return -1; + } + + thread_time.LowPart = user_time.dwLowDateTime; + thread_time.HighPart = user_time.dwHighDateTime; + + /* QuadPart is units of 100ns and we want ms as unit */ + *val_ms = thread_time.QuadPart / 10000ll; + return 0; +#elif defined(RUSAGE_THREAD) + struct rusage ru; + if (getrusage(RUSAGE_THREAD, &ru) < 0) { + error_setg_errno(errp, errno, "Unable to get thread CPU usage"); + return -1; + } + + *val_ms = ((ru.ru_utime.tv_sec * 1000ll) + + (ru.ru_utime.tv_usec / 1000)); + return 0; +#else + *val_ms = 0; + error_setg(errp, "Unable to calculate thread CPU usage on this platform"); + return -1; +#endif +} + +int qcrypto_pbkdf2_count_iters(QCryptoHashAlgorithm hash, + const uint8_t *key, size_t nkey, + const uint8_t *salt, size_t nsalt, + Error **errp) +{ + uint8_t out[32]; + long long int iterations = (1 << 15); + unsigned long long delta_ms, start_ms, end_ms; + + while (1) { + if (qcrypto_pbkdf2_get_thread_cpu(&start_ms, errp) < 0) { + return -1; + } + if (qcrypto_pbkdf2(hash, + key, nkey, + salt, nsalt, + iterations, + out, sizeof(out), + errp) < 0) { + return -1; + } + if (qcrypto_pbkdf2_get_thread_cpu(&end_ms, errp) < 0) { + return -1; + } + + delta_ms = end_ms - start_ms; + + if (delta_ms > 500) { + break; + } else if (delta_ms < 100) { + iterations = iterations * 10; + } else { + iterations = (iterations * 1000 / delta_ms); + } + } + + iterations = iterations * 1000 / delta_ms; + + if (iterations > INT32_MAX) { + error_setg(errp, "Iterations %lld too large for a 32-bit int", + iterations); + return -1; + } + + return iterations; +} diff --git a/qemu/crypto/random-gcrypt.c b/qemu/crypto/random-gcrypt.c new file mode 100644 index 000000000..0de9a096d --- /dev/null +++ b/qemu/crypto/random-gcrypt.c @@ -0,0 +1,33 @@ +/* + * QEMU Crypto random number provider + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" + +#include "crypto/random.h" + +#include <gcrypt.h> + +int qcrypto_random_bytes(uint8_t *buf, + size_t buflen, + Error **errp G_GNUC_UNUSED) +{ + gcry_randomize(buf, buflen, GCRY_STRONG_RANDOM); + return 0; +} diff --git a/qemu/crypto/random-gnutls.c b/qemu/crypto/random-gnutls.c new file mode 100644 index 000000000..04b45a8f8 --- /dev/null +++ b/qemu/crypto/random-gnutls.c @@ -0,0 +1,43 @@ +/* + * QEMU Crypto random number provider + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" + +#include "crypto/random.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +int qcrypto_random_bytes(uint8_t *buf, + size_t buflen, + Error **errp) +{ + int ret; + + ret = gnutls_rnd(GNUTLS_RND_RANDOM, buf, buflen); + + if (ret < 0) { + error_setg(errp, "Cannot get random bytes: %s", + gnutls_strerror(ret)); + return -1; + } + + return 0; +} diff --git a/qemu/crypto/random-stub.c b/qemu/crypto/random-stub.c new file mode 100644 index 000000000..63bbf4147 --- /dev/null +++ b/qemu/crypto/random-stub.c @@ -0,0 +1,31 @@ +/* + * QEMU Crypto random number provider + * + * Copyright (c) 2015-2016 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 "qemu/osdep.h" + +#include "crypto/random.h" + +int qcrypto_random_bytes(uint8_t *buf G_GNUC_UNUSED, + size_t buflen G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "No random byte source provided in this build"); + return -1; +} diff --git a/qemu/crypto/secret.c b/qemu/crypto/secret.c new file mode 100644 index 000000000..285ab7a63 --- /dev/null +++ b/qemu/crypto/secret.c @@ -0,0 +1,509 @@ +/* + * 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 "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); diff --git a/qemu/crypto/tlscreds.c b/qemu/crypto/tlscreds.c new file mode 100644 index 000000000..1620e126a --- /dev/null +++ b/qemu/crypto/tlscreds.c @@ -0,0 +1,259 @@ +/* + * QEMU crypto TLS credential 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/tlscredspriv.h" +#include "trace.h" + +#define DH_BITS 2048 + +#ifdef CONFIG_GNUTLS +int +qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp) +{ + int ret; + + trace_qcrypto_tls_creds_load_dh(creds, filename ? filename : "<generated>"); + + if (filename == NULL) { + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_generate2(*dh_params, DH_BITS); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to generate DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + } else { + GError *gerr = NULL; + gchar *contents; + gsize len; + gnutls_datum_t data; + if (!g_file_get_contents(filename, + &contents, + &len, + &gerr)) { + + error_setg(errp, "%s", gerr->message); + g_error_free(gerr); + return -1; + } + data.data = (unsigned char *)contents; + data.size = len; + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) { + g_free(contents); + error_setg(errp, "Unable to initialize DH parameters: %s", + gnutls_strerror(ret)); + return -1; + } + ret = gnutls_dh_params_import_pkcs3(*dh_params, + &data, + GNUTLS_X509_FMT_PEM); + g_free(contents); + if (ret < 0) { + gnutls_dh_params_deinit(*dh_params); + *dh_params = NULL; + error_setg(errp, "Unable to load DH parameters from %s: %s", + filename, gnutls_strerror(ret)); + return -1; + } + } + + return 0; +} + + +int +qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp) +{ + struct stat sb; + int ret = -1; + + if (!creds->dir) { + if (required) { + error_setg(errp, "Missing 'dir' property value"); + return -1; + } else { + return 0; + } + } + + *cred = g_strdup_printf("%s/%s", creds->dir, filename); + + if (stat(*cred, &sb) < 0) { + if (errno == ENOENT && !required) { + ret = 0; + } else { + error_setg_errno(errp, errno, + "Unable to access credentials %s", + *cred); + } + g_free(*cred); + *cred = NULL; + goto cleanup; + } + + ret = 0; + cleanup: + trace_qcrypto_tls_creds_get_path(creds, filename, + *cred ? *cred : "<none>"); + return ret; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_prop_set_verify(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = value; +} + + +static bool +qcrypto_tls_creds_prop_get_verify(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->verifyPeer; +} + + +static void +qcrypto_tls_creds_prop_set_dir(Object *obj, + const char *value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->dir = g_strdup(value); +} + + +static char * +qcrypto_tls_creds_prop_get_dir(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return g_strdup(creds->dir); +} + + +static void +qcrypto_tls_creds_prop_set_endpoint(Object *obj, + int value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->endpoint = value; +} + + +static int +qcrypto_tls_creds_prop_get_endpoint(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + return creds->endpoint; +} + + +static void +qcrypto_tls_creds_class_init(ObjectClass *oc, void *data) +{ + object_class_property_add_bool(oc, "verify-peer", + qcrypto_tls_creds_prop_get_verify, + qcrypto_tls_creds_prop_set_verify, + NULL); + object_class_property_add_str(oc, "dir", + qcrypto_tls_creds_prop_get_dir, + qcrypto_tls_creds_prop_set_dir, + NULL); + object_class_property_add_enum(oc, "endpoint", + "QCryptoTLSCredsEndpoint", + QCryptoTLSCredsEndpoint_lookup, + qcrypto_tls_creds_prop_get_endpoint, + qcrypto_tls_creds_prop_set_endpoint, + NULL); +} + + +static void +qcrypto_tls_creds_init(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = true; +} + + +static void +qcrypto_tls_creds_finalize(Object *obj) +{ + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + g_free(creds->dir); +} + + +static const TypeInfo qcrypto_tls_creds_info = { + .parent = TYPE_OBJECT, + .name = TYPE_QCRYPTO_TLS_CREDS, + .instance_size = sizeof(QCryptoTLSCreds), + .instance_init = qcrypto_tls_creds_init, + .instance_finalize = qcrypto_tls_creds_finalize, + .class_init = qcrypto_tls_creds_class_init, + .class_size = sizeof(QCryptoTLSCredsClass), + .abstract = true, +}; + + +static void +qcrypto_tls_creds_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_info); +} + + +type_init(qcrypto_tls_creds_register_types); diff --git a/qemu/crypto/tlscredsanon.c b/qemu/crypto/tlscredsanon.c new file mode 100644 index 000000000..146422008 --- /dev/null +++ b/qemu/crypto/tlscredsanon.c @@ -0,0 +1,219 @@ +/* + * QEMU crypto TLS anonymous credential 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 "qemu/osdep.h" +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredspriv.h" +#include "qapi/error.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + + +static int +qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds, + Error **errp) +{ + char *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_anon_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + + ret = gnutls_anon_allocate_server_credentials(&creds->data.server); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: %s", + gnutls_strerror(ret)); + goto cleanup; + } + + if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, + &creds->parent_obj.dh_params, + errp) < 0) { + goto cleanup; + } + + gnutls_anon_set_server_dh_params(creds->data.server, + creds->parent_obj.dh_params); + } else { + ret = gnutls_anon_allocate_client_credentials(&creds->data.client); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: %s", + gnutls_strerror(ret)); + goto cleanup; + } + } + + rv = 0; + cleanup: + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds) +{ + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { + if (creds->data.client) { + gnutls_anon_free_client_credentials(creds->data.client); + creds->data.client = NULL; + } + } else { + if (creds->data.server) { + gnutls_anon_free_server_credentials(creds->data.server); + creds->data.server = NULL; + } + } + if (creds->parent_obj.dh_params) { + gnutls_dh_params_deinit(creds->parent_obj.dh_params); + creds->parent_obj.dh_params = NULL; + } +} + +#else /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_load(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_anon_unload(QCryptoTLSCredsAnon *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + if (value) { + qcrypto_tls_creds_anon_load(creds, errp); + } else { + qcrypto_tls_creds_anon_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_anon_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + return creds->data.server != NULL; + } else { + return creds->data.client != NULL; + } +} + + +#else /* ! CONFIG_GNUTLS */ + + +static bool +qcrypto_tls_creds_anon_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_anon_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_anon_finalize(Object *obj) +{ + QCryptoTLSCredsAnon *creds = QCRYPTO_TLS_CREDS_ANON(obj); + + qcrypto_tls_creds_anon_unload(creds); +} + + +static void +qcrypto_tls_creds_anon_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_anon_complete; + + object_class_property_add_bool(oc, "loaded", + qcrypto_tls_creds_anon_prop_get_loaded, + qcrypto_tls_creds_anon_prop_set_loaded, + NULL); +} + + +static const TypeInfo qcrypto_tls_creds_anon_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_ANON, + .instance_size = sizeof(QCryptoTLSCredsAnon), + .instance_finalize = qcrypto_tls_creds_anon_finalize, + .class_size = sizeof(QCryptoTLSCredsAnonClass), + .class_init = qcrypto_tls_creds_anon_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_anon_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_anon_info); +} + + +type_init(qcrypto_tls_creds_anon_register_types); diff --git a/qemu/crypto/tlscredspriv.h b/qemu/crypto/tlscredspriv.h new file mode 100644 index 000000000..9222be4a9 --- /dev/null +++ b/qemu/crypto/tlscredspriv.h @@ -0,0 +1,42 @@ +/* + * QEMU crypto TLS credential support private 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 QCRYPTO_TLSCRED_PRIV_H__ +#define QCRYPTO_TLSCRED_PRIV_H__ + +#include "crypto/tlscreds.h" + +#ifdef CONFIG_GNUTLS + +int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds, + const char *filename, + bool required, + char **cred, + Error **errp); + +int qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + const char *filename, + gnutls_dh_params_t *dh_params, + Error **errp); + +#endif + +#endif /* QCRYPTO_TLSCRED_PRIV_H__ */ + diff --git a/qemu/crypto/tlscredsx509.c b/qemu/crypto/tlscredsx509.c new file mode 100644 index 000000000..6a0179c2e --- /dev/null +++ b/qemu/crypto/tlscredsx509.c @@ -0,0 +1,865 @@ +/* + * QEMU crypto TLS x509 credential 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 "qemu/osdep.h" +#include "crypto/tlscredsx509.h" +#include "crypto/tlscredspriv.h" +#include "crypto/secret.h" +#include "qapi/error.h" +#include "qom/object_interfaces.h" +#include "trace.h" + + +#ifdef CONFIG_GNUTLS + +#include <gnutls/x509.h> + + +static int +qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + time_t now = time(NULL); + + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "cannot get current time"); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, + (isCA ? + "The CA certificate %s has expired" : + (isServer ? + "The server certificate %s has expired" : + "The client certificate %s has expired")), + certFile); + return -1; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, + (isCA ? + "The CA certificate %s is not yet active" : + (isServer ? + "The server certificate %s is not yet active" : + "The client certificate %s is not yet active")), + certFile); + return -1; + } + + return 0; +} + + +#if LIBGNUTLS_VERSION_NUMBER >= 2 +/* + * The gnutls_x509_crt_get_basic_constraints function isn't + * available in GNUTLS 1.0.x branches. This isn't critical + * though, since gnutls_certificate_verify_peers2 will do + * pretty much the same check at runtime, so we can just + * disable this code + */ +static int +qcrypto_tls_creds_check_cert_basic_constraints(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + int status; + + status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL); + trace_qcrypto_tls_creds_x509_check_basic_constraints( + creds, certFile, status); + + if (status > 0) { /* It is a CA cert */ + if (!isCA) { + error_setg(errp, isServer ? + "The certificate %s basic constraints show a CA, " + "but we need one for a server" : + "The certificate %s basic constraints show a CA, " + "but we need one for a client", + certFile); + return -1; + } + } else if (status == 0) { /* It is not a CA cert */ + if (isCA) { + error_setg(errp, + "The certificate %s basic constraints do not " + "show a CA", + certFile); + return -1; + } + } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + /* Missing basicConstraints */ + if (isCA) { + error_setg(errp, + "The certificate %s is missing basic constraints " + "for a CA", + certFile); + return -1; + } + } else { /* General error */ + error_setg(errp, + "Unable to query certificate %s basic constraints: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + return 0; +} +#endif + + +static int +qcrypto_tls_creds_check_cert_key_usage(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isCA, + Error **errp) +{ + int status; + unsigned int usage = 0; + unsigned int critical = 0; + + status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical); + trace_qcrypto_tls_creds_x509_check_key_usage( + creds, certFile, status, usage, critical); + + if (status < 0) { + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN : + GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT; + } else { + error_setg(errp, + "Unable to query certificate %s key usage: %s", + certFile, gnutls_strerror(status)); + return -1; + } + } + + if (isCA) { + if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit " + "certificate signing", certFile); + return -1; + } + } + } else { + if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit digital " + "signature", certFile); + return -1; + } + } + if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) { + if (critical) { + error_setg(errp, + "Certificate %s usage does not permit key " + "encipherment", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_key_purpose(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + Error **errp) +{ + int status; + size_t i; + unsigned int purposeCritical; + unsigned int critical; + char *buffer = NULL; + size_t size; + bool allowClient = false, allowServer = false; + + critical = 0; + for (i = 0; ; i++) { + size = 0; + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, NULL); + + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + + /* If there is no data at all, then we must allow + client/server to pass */ + if (i == 0) { + allowServer = allowClient = true; + } + break; + } + if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + + buffer = g_new0(char, size); + + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, + &size, &purposeCritical); + + if (status < 0) { + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, "<none>", purposeCritical); + g_free(buffer); + error_setg(errp, + "Unable to query certificate %s key purpose: %s", + certFile, gnutls_strerror(status)); + return -1; + } + trace_qcrypto_tls_creds_x509_check_key_purpose( + creds, certFile, status, buffer, purposeCritical); + if (purposeCritical) { + critical = true; + } + + if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { + allowServer = true; + } else if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { + allowClient = true; + } else if (g_str_equal(buffer, GNUTLS_KP_ANY)) { + allowServer = allowClient = true; + } + + g_free(buffer); + buffer = NULL; + } + + if (isServer) { + if (!allowServer) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow " + "use with a TLS server", certFile); + return -1; + } + } + } else { + if (!allowClient) { + if (critical) { + error_setg(errp, + "Certificate %s purpose does not allow use " + "with a TLS client", certFile); + return -1; + } + } + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds, + gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA, + Error **errp) +{ + if (qcrypto_tls_creds_check_cert_times(cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } + +#if LIBGNUTLS_VERSION_NUMBER >= 2 + if (qcrypto_tls_creds_check_cert_basic_constraints(creds, + cert, certFile, + isServer, isCA, + errp) < 0) { + return -1; + } +#endif + + if (qcrypto_tls_creds_check_cert_key_usage(creds, + cert, certFile, + isCA, errp) < 0) { + return -1; + } + + if (!isCA && + qcrypto_tls_creds_check_cert_key_purpose(creds, + cert, certFile, + isServer, errp) < 0) { + return -1; + } + + return 0; +} + + +static int +qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t cert, + const char *certFile, + gnutls_x509_crt_t *cacerts, + size_t ncacerts, + const char *cacertFile, + bool isServer, + Error **errp) +{ + unsigned int status; + + if (gnutls_x509_crt_list_verify(&cert, 1, + cacerts, ncacerts, + NULL, 0, + 0, &status) < 0) { + error_setg(errp, isServer ? + "Unable to verify server certificate %s against " + "CA certificate %s" : + "Unable to verify client certificate %s against " + "CA certificate %s", + certFile, cacertFile); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + +#ifndef GNUTLS_1_0_COMPAT + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } +#endif + + error_setg(errp, + "Our own certificate %s failed validation against %s: %s", + certFile, cacertFile, reason); + return -1; + } + + return 0; +} + + +static gnutls_x509_crt_t +qcrypto_tls_creds_load_cert(QCryptoTLSCredsX509 *creds, + const char *certFile, + bool isServer, + Error **errp) +{ + gnutls_datum_t data; + gnutls_x509_crt_t cert = NULL; + char *buf = NULL; + gsize buflen; + GError *gerr; + int ret = -1; + + trace_qcrypto_tls_creds_x509_load_cert(creds, isServer, certFile); + + if (gnutls_x509_crt_init(&cert) < 0) { + error_setg(errp, "Unable to initialize certificate"); + goto cleanup; + } + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) { + error_setg(errp, isServer ? + "Unable to import server certificate %s" : + "Unable to import client certificate %s", + certFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret != 0) { + gnutls_x509_crt_deinit(cert); + cert = NULL; + } + g_free(buf); + return cert; +} + + +static int +qcrypto_tls_creds_load_ca_cert_list(QCryptoTLSCredsX509 *creds, + const char *certFile, + gnutls_x509_crt_t *certs, + unsigned int certMax, + size_t *ncerts, + Error **errp) +{ + gnutls_datum_t data; + char *buf = NULL; + gsize buflen; + int ret = -1; + GError *gerr = NULL; + + *ncerts = 0; + trace_qcrypto_tls_creds_x509_load_cert_list(creds, certFile); + + if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) { + error_setg(errp, "Cannot load CA cert list %s: %s", + certFile, gerr->message); + g_error_free(gerr); + goto cleanup; + } + + data.data = (unsigned char *)buf; + data.size = strlen(buf); + + if (gnutls_x509_crt_list_import(certs, &certMax, &data, + GNUTLS_X509_FMT_PEM, 0) < 0) { + error_setg(errp, + "Unable to import CA certificate list %s", + certFile); + goto cleanup; + } + *ncerts = certMax; + + ret = 0; + + cleanup: + g_free(buf); + return ret; +} + + +#define MAX_CERTS 16 +static int +qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds, + bool isServer, + const char *cacertFile, + const char *certFile, + Error **errp) +{ + gnutls_x509_crt_t cert = NULL; + gnutls_x509_crt_t cacerts[MAX_CERTS]; + size_t ncacerts = 0; + size_t i; + int ret = -1; + + memset(cacerts, 0, sizeof(cacerts)); + if (certFile && + access(certFile, R_OK) == 0) { + cert = qcrypto_tls_creds_load_cert(creds, + certFile, isServer, + errp); + if (!cert) { + goto cleanup; + } + } + if (access(cacertFile, R_OK) == 0) { + if (qcrypto_tls_creds_load_ca_cert_list(creds, + cacertFile, cacerts, + MAX_CERTS, &ncacerts, + errp) < 0) { + goto cleanup; + } + } + + if (cert && + qcrypto_tls_creds_check_cert(creds, + cert, certFile, isServer, + false, errp) < 0) { + goto cleanup; + } + + for (i = 0; i < ncacerts; i++) { + if (qcrypto_tls_creds_check_cert(creds, + cacerts[i], cacertFile, + isServer, true, errp) < 0) { + goto cleanup; + } + } + + if (cert && ncacerts && + qcrypto_tls_creds_check_cert_pair(cert, certFile, cacerts, + ncacerts, cacertFile, + isServer, errp) < 0) { + goto cleanup; + } + + ret = 0; + + cleanup: + if (cert) { + gnutls_x509_crt_deinit(cert); + } + for (i = 0; i < ncacerts; i++) { + gnutls_x509_crt_deinit(cacerts[i]); + } + return ret; +} + + +static int +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, + Error **errp) +{ + char *cacert = NULL, *cacrl = NULL, *cert = NULL, + *key = NULL, *dhparams = NULL; + int ret; + int rv = -1; + + trace_qcrypto_tls_creds_x509_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>"); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CRL, + false, &cacrl, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &key, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, + false, &dhparams, errp) < 0) { + goto cleanup; + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &key, errp) < 0) { + goto cleanup; + } + } + + if (creds->sanityCheck && + qcrypto_tls_creds_x509_sanity_check(creds, + creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + cacert, cert, errp) < 0) { + goto cleanup; + } + + ret = gnutls_certificate_allocate_credentials(&creds->data); + if (ret < 0) { + error_setg(errp, "Cannot allocate credentials: '%s'", + gnutls_strerror(ret)); + goto cleanup; + } + + ret = gnutls_certificate_set_x509_trust_file(creds->data, + cacert, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CA certificate '%s': %s", + cacert, gnutls_strerror(ret)); + goto cleanup; + } + + 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)); + goto cleanup; + } + } + + if (cacrl != NULL) { + ret = gnutls_certificate_set_x509_crl_file(creds->data, + cacrl, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + error_setg(errp, "Cannot load CRL '%s': %s", + cacrl, gnutls_strerror(ret)); + goto cleanup; + } + } + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams, + &creds->parent_obj.dh_params, + errp) < 0) { + goto cleanup; + } + gnutls_certificate_set_dh_params(creds->data, + creds->parent_obj.dh_params); + } + + rv = 0; + cleanup: + g_free(cacert); + g_free(cacrl); + g_free(cert); + g_free(key); + g_free(dhparams); + return rv; +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds) +{ + if (creds->data) { + gnutls_certificate_free_credentials(creds->data); + creds->data = NULL; + } + if (creds->parent_obj.dh_params) { + gnutls_dh_params_deinit(creds->parent_obj.dh_params); + creds->parent_obj.dh_params = NULL; + } +} + + +#else /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS credentials support requires GNUTLS"); +} + + +static void +qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED) +{ + /* nada */ +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_loaded(Object *obj, + bool value, + Error **errp) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + if (value) { + qcrypto_tls_creds_x509_load(creds, errp); + } else { + qcrypto_tls_creds_x509_unload(creds); + } +} + + +#ifdef CONFIG_GNUTLS + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->data != NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +static bool +qcrypto_tls_creds_x509_prop_get_loaded(Object *obj G_GNUC_UNUSED, + Error **errp G_GNUC_UNUSED) +{ + return false; +} + + +#endif /* ! CONFIG_GNUTLS */ + + +static void +qcrypto_tls_creds_x509_prop_set_sanity(Object *obj, + bool value, + Error **errp G_GNUC_UNUSED) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = value; +} + + +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) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + return creds->sanityCheck; +} + + +static void +qcrypto_tls_creds_x509_complete(UserCreatable *uc, Error **errp) +{ + object_property_set_bool(OBJECT(uc), true, "loaded", errp); +} + + +static void +qcrypto_tls_creds_x509_init(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + creds->sanityCheck = true; +} + + +static void +qcrypto_tls_creds_x509_finalize(Object *obj) +{ + QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj); + + g_free(creds->passwordid); + qcrypto_tls_creds_x509_unload(creds); +} + + +static void +qcrypto_tls_creds_x509_class_init(ObjectClass *oc, void *data) +{ + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); + + ucc->complete = qcrypto_tls_creds_x509_complete; + + object_class_property_add_bool(oc, "loaded", + qcrypto_tls_creds_x509_prop_get_loaded, + qcrypto_tls_creds_x509_prop_set_loaded, + NULL); + object_class_property_add_bool(oc, "sanity-check", + qcrypto_tls_creds_x509_prop_get_sanity, + qcrypto_tls_creds_x509_prop_set_sanity, + NULL); + object_class_property_add_str(oc, "passwordid", + qcrypto_tls_creds_x509_prop_get_passwordid, + qcrypto_tls_creds_x509_prop_set_passwordid, + NULL); +} + + +static const TypeInfo qcrypto_tls_creds_x509_info = { + .parent = TYPE_QCRYPTO_TLS_CREDS, + .name = TYPE_QCRYPTO_TLS_CREDS_X509, + .instance_size = sizeof(QCryptoTLSCredsX509), + .instance_init = qcrypto_tls_creds_x509_init, + .instance_finalize = qcrypto_tls_creds_x509_finalize, + .class_size = sizeof(QCryptoTLSCredsX509Class), + .class_init = qcrypto_tls_creds_x509_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + + +static void +qcrypto_tls_creds_x509_register_types(void) +{ + type_register_static(&qcrypto_tls_creds_x509_info); +} + + +type_init(qcrypto_tls_creds_x509_register_types); diff --git a/qemu/crypto/tlssession.c b/qemu/crypto/tlssession.c new file mode 100644 index 000000000..a543e5a57 --- /dev/null +++ b/qemu/crypto/tlssession.c @@ -0,0 +1,576 @@ +/* + * QEMU crypto TLS session 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 "qemu/osdep.h" +#include "crypto/tlssession.h" +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredsx509.h" +#include "qapi/error.h" +#include "qemu/acl.h" +#include "trace.h" + +#ifdef CONFIG_GNUTLS + + +#include <gnutls/x509.h> + + +struct QCryptoTLSSession { + QCryptoTLSCreds *creds; + gnutls_session_t handle; + char *hostname; + char *aclname; + bool handshakeComplete; + QCryptoTLSSessionWriteFunc writeFunc; + QCryptoTLSSessionReadFunc readFunc; + void *opaque; + char *peername; +}; + + +void +qcrypto_tls_session_free(QCryptoTLSSession *session) +{ + if (!session) { + return; + } + + gnutls_deinit(session->handle); + g_free(session->hostname); + g_free(session->peername); + g_free(session->aclname); + object_unref(OBJECT(session->creds)); + g_free(session); +} + + +static ssize_t +qcrypto_tls_session_push(void *opaque, const void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->writeFunc) { + errno = EIO; + return -1; + }; + + return session->writeFunc(buf, len, session->opaque); +} + + +static ssize_t +qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) +{ + QCryptoTLSSession *session = opaque; + + if (!session->readFunc) { + errno = EIO; + return -1; + }; + + return session->readFunc(buf, len, session->opaque); +} + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds, + const char *hostname, + const char *aclname, + QCryptoTLSCredsEndpoint endpoint, + Error **errp) +{ + QCryptoTLSSession *session; + int ret; + + session = g_new0(QCryptoTLSSession, 1); + trace_qcrypto_tls_session_new( + session, creds, hostname ? hostname : "<none>", + aclname ? aclname : "<none>", endpoint); + + if (hostname) { + session->hostname = g_strdup(hostname); + } + if (aclname) { + session->aclname = g_strdup(aclname); + } + session->creds = creds; + object_ref(OBJECT(creds)); + + if (creds->endpoint != endpoint) { + error_setg(errp, "Credentials endpoint doesn't match session"); + goto error; + } + + if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_init(&session->handle, GNUTLS_SERVER); + } else { + ret = gnutls_init(&session->handle, GNUTLS_CLIENT); + } + if (ret < 0) { + error_setg(errp, "Cannot initialize TLS session: %s", + gnutls_strerror(ret)); + goto error; + } + + if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds); + + ret = gnutls_priority_set_direct(session->handle, + "NORMAL:+ANON-DH", NULL); + if (ret < 0) { + error_setg(errp, "Unable to set TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.server); + } else { + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_ANON, + acreds->data.client); + } + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + } else if (object_dynamic_cast(OBJECT(creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds); + + ret = gnutls_set_default_priority(session->handle); + if (ret < 0) { + error_setg(errp, "Cannot set default TLS session priority: %s", + gnutls_strerror(ret)); + goto error; + } + ret = gnutls_credentials_set(session->handle, + GNUTLS_CRD_CERTIFICATE, + tcreds->data); + if (ret < 0) { + error_setg(errp, "Cannot set session credentials: %s", + gnutls_strerror(ret)); + goto error; + } + + if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + /* This requests, but does not enforce a client cert. + * The cert checking code later does enforcement */ + gnutls_certificate_server_set_request(session->handle, + GNUTLS_CERT_REQUEST); + } + } else { + error_setg(errp, "Unsupported TLS credentials type %s", + object_get_typename(OBJECT(creds))); + goto error; + } + + gnutls_transport_set_ptr(session->handle, session); + gnutls_transport_set_push_function(session->handle, + qcrypto_tls_session_push); + gnutls_transport_set_pull_function(session->handle, + qcrypto_tls_session_pull); + + return session; + + error: + qcrypto_tls_session_free(session); + return NULL; +} + +static int +qcrypto_tls_session_check_certificate(QCryptoTLSSession *session, + Error **errp) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + gnutls_x509_crt_t cert = NULL; + + now = time(NULL); + if (now == ((time_t)-1)) { + error_setg_errno(errp, errno, "Cannot get current time"); + return -1; + } + + ret = gnutls_certificate_verify_peers2(session->handle, &status); + if (ret < 0) { + error_setg(errp, "Verify failed: %s", gnutls_strerror(ret)); + return -1; + } + + if (status != 0) { + const char *reason = "Invalid certificate"; + + if (status & GNUTLS_CERT_INVALID) { + reason = "The certificate is not trusted"; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + reason = "The certificate hasn't got a known issuer"; + } + + if (status & GNUTLS_CERT_REVOKED) { + reason = "The certificate has been revoked"; + } + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { + reason = "The certificate uses an insecure algorithm"; + } + + error_setg(errp, "%s", reason); + return -1; + } + + certs = gnutls_certificate_get_peers(session->handle, &nCerts); + if (!certs) { + error_setg(errp, "No certificate peers"); + return -1; + } + + for (i = 0; i < nCerts; i++) { + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) { + error_setg(errp, "Cannot initialize certificate: %s", + gnutls_strerror(ret)); + return -1; + } + + ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER); + if (ret < 0) { + error_setg(errp, "Cannot import certificate: %s", + gnutls_strerror(ret)); + goto error; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + error_setg(errp, "The certificate has expired"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + error_setg(errp, "The certificate is not yet activated"); + goto error; + } + + if (i == 0) { + size_t dnameSize = 1024; + session->peername = g_malloc(dnameSize); + requery: + ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize); + if (ret < 0) { + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { + session->peername = g_realloc(session->peername, + dnameSize); + goto requery; + } + error_setg(errp, "Cannot get client distinguished name: %s", + gnutls_strerror(ret)); + goto error; + } + if (session->aclname) { + qemu_acl *acl = qemu_acl_find(session->aclname); + int allow; + if (!acl) { + error_setg(errp, "Cannot find ACL %s", + session->aclname); + goto error; + } + + allow = qemu_acl_party_is_allowed(acl, session->peername); + + if (!allow) { + error_setg(errp, "TLS x509 ACL check for %s is denied", + session->peername); + goto error; + } + } + if (session->hostname) { + if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) { + error_setg(errp, + "Certificate does not match the hostname %s", + session->hostname); + goto error; + } + } + } + + gnutls_x509_crt_deinit(cert); + } + + return 0; + + error: + gnutls_x509_crt_deinit(cert); + return -1; +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *session, + Error **errp) +{ + if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + return 0; + } else if (object_dynamic_cast(OBJECT(session->creds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + if (session->creds->verifyPeer) { + return qcrypto_tls_session_check_certificate(session, + errp); + } else { + return 0; + } + } else { + error_setg(errp, "Unexpected credential type %s", + object_get_typename(OBJECT(session->creds))); + return -1; + } +} + + +void +qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session, + QCryptoTLSSessionWriteFunc writeFunc, + QCryptoTLSSessionReadFunc readFunc, + void *opaque) +{ + session->writeFunc = writeFunc; + session->readFunc = readFunc; + session->opaque = opaque; +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *session, + const char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_send(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *session, + char *buf, + size_t len) +{ + ssize_t ret = gnutls_record_recv(session->handle, buf, len); + + if (ret < 0) { + switch (ret) { + case GNUTLS_E_AGAIN: + errno = EAGAIN; + break; + case GNUTLS_E_INTERRUPTED: + errno = EINTR; + break; + default: + errno = EIO; + break; + } + ret = -1; + } + + return ret; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *session, + Error **errp) +{ + int ret = gnutls_handshake(session->handle); + if (ret == 0) { + session->handshakeComplete = true; + } else { + if (ret == GNUTLS_E_INTERRUPTED || + ret == GNUTLS_E_AGAIN) { + ret = 1; + } else { + error_setg(errp, "TLS handshake failed: %s", + gnutls_strerror(ret)); + ret = -1; + } + } + + return ret; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) +{ + if (session->handshakeComplete) { + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + } else if (gnutls_record_get_direction(session->handle) == 0) { + return QCRYPTO_TLS_HANDSHAKE_RECVING; + } else { + return QCRYPTO_TLS_HANDSHAKE_SENDING; + } +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, + Error **errp) +{ + gnutls_cipher_algorithm_t cipher; + int ssf; + + cipher = gnutls_cipher_get(session->handle); + ssf = gnutls_cipher_get_key_size(cipher); + if (!ssf) { + error_setg(errp, "Cannot get TLS cipher key size"); + return -1; + } + return ssf; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session) +{ + if (session->peername) { + return g_strdup(session->peername); + } + return NULL; +} + + +#else /* ! CONFIG_GNUTLS */ + + +QCryptoTLSSession * +qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED, + const char *hostname G_GNUC_UNUSED, + const char *aclname G_GNUC_UNUSED, + QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return NULL; +} + + +void +qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED) +{ +} + + +int +qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +void +qcrypto_tls_session_set_callbacks( + QCryptoTLSSession *sess G_GNUC_UNUSED, + QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED, + QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ +} + + +ssize_t +qcrypto_tls_session_write(QCryptoTLSSession *sess, + const char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +ssize_t +qcrypto_tls_session_read(QCryptoTLSSession *sess, + char *buf, + size_t len) +{ + errno = -EIO; + return -1; +} + + +int +qcrypto_tls_session_handshake(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +QCryptoTLSSessionHandshakeStatus +qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) +{ + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; +} + + +int +qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, + Error **errp) +{ + error_setg(errp, "TLS requires GNUTLS support"); + return -1; +} + + +char * +qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess) +{ + return NULL; +} + +#endif diff --git a/qemu/crypto/xts.c b/qemu/crypto/xts.c new file mode 100644 index 000000000..95212341f --- /dev/null +++ b/qemu/crypto/xts.c @@ -0,0 +1,230 @@ +/* + * QEMU Crypto XTS cipher mode + * + * Copyright (c) 2015-2016 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/>. + * + * This code is originally derived from public domain / WTFPL code in + * LibTomCrypt crytographic library http://libtom.org. The XTS code + * was donated by Elliptic Semiconductor Inc (www.ellipticsemi.com) + * to the LibTom Projects + * + */ + +#include "qemu/osdep.h" +#include "crypto/xts.h" + +static void xts_mult_x(uint8_t *I) +{ + int x; + uint8_t t, tt; + + for (x = t = 0; x < 16; x++) { + tt = I[x] >> 7; + I[x] = ((I[x] << 1) | t) & 0xFF; + t = tt; + } + if (tt) { + I[0] ^= 0x87; + } +} + + +/** + * xts_tweak_uncrypt: + * @param ctxt: the cipher context + * @param func: the cipher function + * @src: buffer providing the cipher text of XTS_BLOCK_SIZE bytes + * @dst: buffer to output the plain text of XTS_BLOCK_SIZE bytes + * @iv: the initialization vector tweak of XTS_BLOCK_SIZE bytes + * + * Decrypt data with a tweak + */ +static void xts_tweak_decrypt(const void *ctx, + xts_cipher_func *func, + const uint8_t *src, + uint8_t *dst, + uint8_t *iv) +{ + unsigned long x; + + /* tweak encrypt block i */ + for (x = 0; x < XTS_BLOCK_SIZE; x++) { + dst[x] = src[x] ^ iv[x]; + } + + func(ctx, XTS_BLOCK_SIZE, dst, dst); + + for (x = 0; x < XTS_BLOCK_SIZE; x++) { + dst[x] = dst[x] ^ iv[x]; + } + + /* LFSR the tweak */ + xts_mult_x(iv); +} + + +void xts_decrypt(const void *datactx, + const void *tweakctx, + xts_cipher_func *encfunc, + xts_cipher_func *decfunc, + uint8_t *iv, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + uint8_t PP[XTS_BLOCK_SIZE], CC[XTS_BLOCK_SIZE], T[XTS_BLOCK_SIZE]; + unsigned long i, m, mo, lim; + + /* get number of blocks */ + m = length >> 4; + mo = length & 15; + + /* must have at least one full block */ + g_assert(m != 0); + + if (mo == 0) { + lim = m; + } else { + lim = m - 1; + } + + /* encrypt the iv */ + encfunc(tweakctx, XTS_BLOCK_SIZE, T, iv); + + for (i = 0; i < lim; i++) { + xts_tweak_decrypt(datactx, decfunc, src, dst, T); + + src += XTS_BLOCK_SIZE; + dst += XTS_BLOCK_SIZE; + } + + /* if length is not a multiple of XTS_BLOCK_SIZE then */ + if (mo > 0) { + memcpy(CC, T, XTS_BLOCK_SIZE); + xts_mult_x(CC); + + /* PP = tweak decrypt block m-1 */ + xts_tweak_decrypt(datactx, decfunc, src, PP, CC); + + /* Pm = first length % XTS_BLOCK_SIZE bytes of PP */ + for (i = 0; i < mo; i++) { + CC[i] = src[XTS_BLOCK_SIZE + i]; + dst[XTS_BLOCK_SIZE + i] = PP[i]; + } + for (; i < XTS_BLOCK_SIZE; i++) { + CC[i] = PP[i]; + } + + /* Pm-1 = Tweak uncrypt CC */ + xts_tweak_decrypt(datactx, decfunc, CC, dst, T); + } + + /* Decrypt the iv back */ + decfunc(tweakctx, XTS_BLOCK_SIZE, iv, T); +} + + +/** + * xts_tweak_crypt: + * @param ctxt: the cipher context + * @param func: the cipher function + * @src: buffer providing the plain text of XTS_BLOCK_SIZE bytes + * @dst: buffer to output the cipher text of XTS_BLOCK_SIZE bytes + * @iv: the initialization vector tweak of XTS_BLOCK_SIZE bytes + * + * Encrypt data with a tweak + */ +static void xts_tweak_encrypt(const void *ctx, + xts_cipher_func *func, + const uint8_t *src, + uint8_t *dst, + uint8_t *iv) +{ + unsigned long x; + + /* tweak encrypt block i */ + for (x = 0; x < XTS_BLOCK_SIZE; x++) { + dst[x] = src[x] ^ iv[x]; + } + + func(ctx, XTS_BLOCK_SIZE, dst, dst); + + for (x = 0; x < XTS_BLOCK_SIZE; x++) { + dst[x] = dst[x] ^ iv[x]; + } + + /* LFSR the tweak */ + xts_mult_x(iv); +} + + +void xts_encrypt(const void *datactx, + const void *tweakctx, + xts_cipher_func *encfunc, + xts_cipher_func *decfunc, + uint8_t *iv, + size_t length, + uint8_t *dst, + const uint8_t *src) +{ + uint8_t PP[XTS_BLOCK_SIZE], CC[XTS_BLOCK_SIZE], T[XTS_BLOCK_SIZE]; + unsigned long i, m, mo, lim; + + /* get number of blocks */ + m = length >> 4; + mo = length & 15; + + /* must have at least one full block */ + g_assert(m != 0); + + if (mo == 0) { + lim = m; + } else { + lim = m - 1; + } + + /* encrypt the iv */ + encfunc(tweakctx, XTS_BLOCK_SIZE, T, iv); + + for (i = 0; i < lim; i++) { + xts_tweak_encrypt(datactx, encfunc, src, dst, T); + + dst += XTS_BLOCK_SIZE; + src += XTS_BLOCK_SIZE; + } + + /* if length is not a multiple of XTS_BLOCK_SIZE then */ + if (mo > 0) { + /* CC = tweak encrypt block m-1 */ + xts_tweak_encrypt(datactx, encfunc, src, CC, T); + + /* Cm = first length % XTS_BLOCK_SIZE bytes of CC */ + for (i = 0; i < mo; i++) { + PP[i] = src[XTS_BLOCK_SIZE + i]; + dst[XTS_BLOCK_SIZE + i] = CC[i]; + } + + for (; i < XTS_BLOCK_SIZE; i++) { + PP[i] = CC[i]; + } + + /* Cm-1 = Tweak encrypt PP */ + xts_tweak_encrypt(datactx, encfunc, PP, dst, T); + } + + /* Decrypt the iv back */ + decfunc(tweakctx, XTS_BLOCK_SIZE, iv, T); +} |