From ddbb0d09661f5fce21b335ba9aea8202d189b98e Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 1 Jul 2015 18:10:29 +0100 Subject: crypto: introduce new module for computing hash digests Introduce a new crypto/ directory that will (eventually) contain all the cryptographic related code. This initially defines a wrapper for initializing gnutls and for computing hashes with gnutls. The former ensures that gnutls is guaranteed to be initialized exactly once in QEMU regardless of CLI args. The block quorum code currently fails to initialize gnutls so it only works by luck, if VNC server TLS is not requested. The hash APIs avoids the need to litter the rest of the code with preprocessor checks and simplifies callers by allocating the correct amount of memory for the requested hash. Signed-off-by: Daniel P. Berrange Message-Id: <1435770638-25715-2-git-send-email-berrange@redhat.com> Signed-off-by: Paolo Bonzini --- tests/.gitignore | 1 + tests/Makefile | 2 + tests/test-crypto-hash.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 tests/test-crypto-hash.c (limited to 'tests') diff --git a/tests/.gitignore b/tests/.gitignore index dc813c2..18f60f1 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -10,6 +10,7 @@ rcutorture test-aio test-bitops test-coroutine +test-crypto-hash test-cutils test-hbitmap test-int128 diff --git a/tests/Makefile b/tests/Makefile index eff5e11..8c7f1ac 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -74,6 +74,7 @@ check-unit-y += tests/test-qemu-opts$(EXESUF) gcov-files-test-qemu-opts-y = qom/test-qemu-opts.c check-unit-y += tests/test-write-threshold$(EXESUF) gcov-files-test-write-threshold-y = block/write-threshold.c +check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh @@ -339,6 +340,7 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a +tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o diff --git a/tests/test-crypto-hash.c b/tests/test-crypto-hash.c new file mode 100644 index 0000000..911437e --- /dev/null +++ b/tests/test-crypto-hash.c @@ -0,0 +1,209 @@ +/* + * QEMU Crypto hash algorithms + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#include + +#include "crypto/init.h" +#include "crypto/hash.h" + +#define INPUT_TEXT "Hiss hisss Hissss hiss Hiss hisss Hiss hiss" +#define INPUT_TEXT1 "Hiss hisss " +#define INPUT_TEXT2 "Hissss hiss " +#define INPUT_TEXT3 "Hiss hisss Hiss hiss" + +#define OUTPUT_MD5 "628d206371563035ab8ef62f492bdec9" +#define OUTPUT_SHA1 "b2e74f26758a3a421e509cee045244b78753cc02" +#define OUTPUT_SHA256 "bc757abb0436586f392b437e5dd24096" \ + "f7f224de6b74d4d86e2abc6121b160d0" + +#define OUTPUT_MD5_B64 "Yo0gY3FWMDWrjvYvSSveyQ==" +#define OUTPUT_SHA1_B64 "sudPJnWKOkIeUJzuBFJEt4dTzAI=" +#define OUTPUT_SHA256_B64 "vHV6uwQ2WG85K0N+XdJAlvfyJN5rdNTYbiq8YSGxYNA=" + +static const char *expected_outputs[] = { + [QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5, + [QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1, + [QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256, +}; +static const char *expected_outputs_b64[] = { + [QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5_B64, + [QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1_B64, + [QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256_B64, +}; +static const int expected_lens[] = { + [QCRYPTO_HASH_ALG_MD5] = 16, + [QCRYPTO_HASH_ALG_SHA1] = 20, + [QCRYPTO_HASH_ALG_SHA256] = 32, +}; + +static const char hex[] = "0123456789abcdef"; + +/* Test with dynamic allocation */ +static void test_hash_alloc(void) +{ + size_t i; + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) { + uint8_t *result = NULL; + size_t resultlen = 0; + int ret; + size_t j; + + ret = qcrypto_hash_bytes(i, + INPUT_TEXT, + strlen(INPUT_TEXT), + &result, + &resultlen, + NULL); + g_assert(ret == 0); + g_assert(resultlen == expected_lens[i]); + + for (j = 0; j < resultlen; j++) { + g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]); + g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]); + } + g_free(result); + } +} + +/* Test with caller preallocating */ +static void test_hash_prealloc(void) +{ + size_t i; + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) { + uint8_t *result; + size_t resultlen; + int ret; + size_t j; + + resultlen = expected_lens[i]; + result = g_new0(uint8_t, resultlen); + + ret = qcrypto_hash_bytes(i, + INPUT_TEXT, + strlen(INPUT_TEXT), + &result, + &resultlen, + NULL); + g_assert(ret == 0); + + g_assert(resultlen == expected_lens[i]); + for (j = 0; j < resultlen; j++) { + g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]); + g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]); + } + g_free(result); + } +} + + +/* Test with dynamic allocation */ +static void test_hash_iov(void) +{ + size_t i; + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) { + struct iovec iov[3] = { + { .iov_base = (char *)INPUT_TEXT1, .iov_len = strlen(INPUT_TEXT1) }, + { .iov_base = (char *)INPUT_TEXT2, .iov_len = strlen(INPUT_TEXT2) }, + { .iov_base = (char *)INPUT_TEXT3, .iov_len = strlen(INPUT_TEXT3) }, + }; + uint8_t *result = NULL; + size_t resultlen = 0; + int ret; + size_t j; + + ret = qcrypto_hash_bytesv(i, + iov, 3, + &result, + &resultlen, + NULL); + g_assert(ret == 0); + g_assert(resultlen == expected_lens[i]); + for (j = 0; j < resultlen; j++) { + g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]); + g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]); + } + g_free(result); + } +} + + +/* Test with printable hashing */ +static void test_hash_digest(void) +{ + size_t i; + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) { + int ret; + char *digest; + + ret = qcrypto_hash_digest(i, + INPUT_TEXT, + strlen(INPUT_TEXT), + &digest, + NULL); + g_assert(ret == 0); + g_assert(g_str_equal(digest, expected_outputs[i])); + g_free(digest); + } +} + +/* Test with base64 encoding */ +static void test_hash_base64(void) +{ + size_t i; + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) { + int ret; + char *digest; + + ret = qcrypto_hash_base64(i, + INPUT_TEXT, + strlen(INPUT_TEXT), + &digest, + NULL); + g_assert(ret == 0); + g_assert(g_str_equal(digest, expected_outputs_b64[i])); + g_free(digest); + } +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/crypto/hash/iov", test_hash_iov); + g_test_add_func("/crypto/hash/alloc", test_hash_alloc); + g_test_add_func("/crypto/hash/prealloc", test_hash_prealloc); + g_test_add_func("/crypto/hash/digest", test_hash_digest); + g_test_add_func("/crypto/hash/base64", test_hash_base64); + return g_test_run(); +} -- cgit v1.1 From ca38a4cc9e36647437b837b346a41981fb8880cd Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 1 Jul 2015 18:10:32 +0100 Subject: crypto: introduce generic cipher API & built-in implementation Introduce a generic cipher API and an implementation of it that supports only the built-in AES and DES-RFB algorithms. The test suite checks the supported algorithms + modes to validate that every backend implementation is actually correctly complying with the specs. Signed-off-by: Daniel P. Berrange Message-Id: <1435770638-25715-5-git-send-email-berrange@redhat.com> Signed-off-by: Paolo Bonzini --- tests/.gitignore | 1 + tests/Makefile | 2 + tests/test-crypto-cipher.c | 290 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 tests/test-crypto-cipher.c (limited to 'tests') diff --git a/tests/.gitignore b/tests/.gitignore index 18f60f1..ccc92e4 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -10,6 +10,7 @@ rcutorture test-aio test-bitops test-coroutine +test-crypto-cipher test-crypto-hash test-cutils test-hbitmap diff --git a/tests/Makefile b/tests/Makefile index 8c7f1ac..09838ac 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -75,6 +75,7 @@ gcov-files-test-qemu-opts-y = qom/test-qemu-opts.c check-unit-y += tests/test-write-threshold$(EXESUF) gcov-files-test-write-threshold-y = block/write-threshold.c check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF) +check-unit-y += tests/test-crypto-cipher$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh @@ -341,6 +342,7 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a +tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o libqemuutil.a libqemustub.a libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o diff --git a/tests/test-crypto-cipher.c b/tests/test-crypto-cipher.c new file mode 100644 index 0000000..f9b1a03 --- /dev/null +++ b/tests/test-crypto-cipher.c @@ -0,0 +1,290 @@ +/* + * QEMU Crypto cipher algorithms + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#include + +#include "crypto/init.h" +#include "crypto/cipher.h" + +typedef struct QCryptoCipherTestData QCryptoCipherTestData; +struct QCryptoCipherTestData { + const char *path; + QCryptoCipherAlgorithm alg; + QCryptoCipherMode mode; + const char *key; + const char *plaintext; + const char *ciphertext; + const char *iv; +}; + +/* AES test data comes from appendix F of: + * + * http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + */ +static QCryptoCipherTestData test_data[] = { + { + /* NIST F.1.1 ECB-AES128.Encrypt */ + .path = "/crypto/cipher/aes-ecb-128", + .alg = QCRYPTO_CIPHER_ALG_AES_128, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = "2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "3ad77bb40d7a3660a89ecaf32466ef97" + "f5d3d58503b9699de785895a96fdbaaf" + "43b1cd7f598ece23881b00e3ed030688" + "7b0c785e27e8ad3f8223207104725dd4" + }, + { + /* NIST F.1.3 ECB-AES192.Encrypt */ + .path = "/crypto/cipher/aes-ecb-192", + .alg = QCRYPTO_CIPHER_ALG_AES_192, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "bd334f1d6e45f25ff712a214571fa5cc" + "974104846d0ad3ad7734ecb3ecee4eef" + "ef7afd2270e2e60adce0ba2face6444e" + "9a4b41ba738d6c72fb16691603c18e0e" + }, + { + /* NIST F.1.5 ECB-AES256.Encrypt */ + .path = "/crypto/cipher/aes-ecb-256", + .alg = QCRYPTO_CIPHER_ALG_AES_256, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = + "603deb1015ca71be2b73aef0857d7781" + "1f352c073b6108d72d9810a30914dff4", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "f3eed1bdb5d2a03c064b5a7e3db181f8" + "591ccb10d410ed26dc5ba74a31362870" + "b6ed21b99ca6f4f9f153e7b1beafed1d" + "23304b7a39f9f3ff067d8d8f9e24ecc7", + }, + { + /* NIST F.2.1 CBC-AES128.Encrypt */ + .path = "/crypto/cipher/aes-cbc-128", + .alg = QCRYPTO_CIPHER_ALG_AES_128, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = "2b7e151628aed2a6abf7158809cf4f3c", + .iv = "000102030405060708090a0b0c0d0e0f", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "7649abac8119b246cee98e9b12e9197d" + "5086cb9b507219ee95db113a917678b2" + "73bed6b8e3c1743b7116e69e22229516" + "3ff1caa1681fac09120eca307586e1a7", + }, + { + /* NIST F.2.3 CBC-AES128.Encrypt */ + .path = "/crypto/cipher/aes-cbc-192", + .alg = QCRYPTO_CIPHER_ALG_AES_192, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .iv = "000102030405060708090a0b0c0d0e0f", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "4f021db243bc633d7178183a9fa071e8" + "b4d9ada9ad7dedf4e5e738763f69145a" + "571b242012fb7ae07fa9baac3df102e0" + "08b0e27988598881d920a9e64f5615cd", + }, + { + /* NIST F.2.5 CBC-AES128.Encrypt */ + .path = "/crypto/cipher/aes-cbc-256", + .alg = QCRYPTO_CIPHER_ALG_AES_256, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = + "603deb1015ca71be2b73aef0857d7781" + "1f352c073b6108d72d9810a30914dff4", + .iv = "000102030405060708090a0b0c0d0e0f", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "f58c4c04d6e5f1ba779eabfb5f7bfbd6" + "9cfc4e967edb808d679f777bc6702c7d" + "39f23369a9d9bacfa530e26304231461" + "b2eb05e2c39be9fcda6c19078c6a9d1b", + }, + { + .path = "/crypto/cipher/des-rfb-ecb-56", + .alg = QCRYPTO_CIPHER_ALG_DES_RFB, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = "0123456789abcdef", + .plaintext = + "6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710", + .ciphertext = + "8f346aaf64eaf24040720d80648c52e7" + "aefc616be53ab1a3d301e69d91e01838" + "ffd29f1bb5596ad94ea2d8e6196b7f09" + "30d8ed0bf2773af36dd82a6280c20926", + }, +}; + + +static inline int unhex(char c) +{ + if (c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } + if (c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } + return c - '0'; +} + +static inline char hex(int i) +{ + if (i < 10) { + return '0' + i; + } + return 'a' + (i - 10); +} + +static size_t unhex_string(const char *hexstr, + uint8_t **data) +{ + size_t len; + size_t i; + + if (!hexstr) { + *data = NULL; + return 0; + } + + len = strlen(hexstr); + *data = g_new0(uint8_t, len / 2); + + for (i = 0; i < len; i += 2) { + (*data)[i/2] = (unhex(hexstr[i]) << 4) | unhex(hexstr[i+1]); + } + return len / 2; +} + +static char *hex_string(const uint8_t *bytes, + size_t len) +{ + char *hexstr = g_new0(char, len * 2 + 1); + size_t i; + + for (i = 0; i < len; i++) { + hexstr[i*2] = hex((bytes[i] >> 4) & 0xf); + hexstr[i*2+1] = hex(bytes[i] & 0xf); + } + hexstr[len*2] = '\0'; + + return hexstr; +} + +static void test_cipher(const void *opaque) +{ + const QCryptoCipherTestData *data = opaque; + + QCryptoCipher *cipher; + Error *err = NULL; + uint8_t *key, *iv, *ciphertext, *plaintext, *outtext; + size_t nkey, niv, nciphertext, nplaintext; + char *outtexthex; + + g_test_message("foo"); + nkey = unhex_string(data->key, &key); + niv = unhex_string(data->iv, &iv); + nciphertext = unhex_string(data->ciphertext, &ciphertext); + nplaintext = unhex_string(data->plaintext, &plaintext); + + g_assert(nciphertext == nplaintext); + + outtext = g_new0(uint8_t, nciphertext); + + cipher = qcrypto_cipher_new( + data->alg, data->mode, + key, nkey, + &err); + g_assert(cipher != NULL); + g_assert(err == NULL); + + + if (iv) { + g_assert(qcrypto_cipher_setiv(cipher, + iv, niv, + &err) == 0); + g_assert(err == NULL); + } + g_assert(qcrypto_cipher_encrypt(cipher, + plaintext, + outtext, + nplaintext, + &err) == 0); + g_assert(err == NULL); + + outtexthex = hex_string(outtext, nciphertext); + + g_assert_cmpstr(outtexthex, ==, data->ciphertext); + + g_free(outtext); + g_free(outtexthex); + g_free(key); + g_free(iv); + g_free(ciphertext); + g_free(plaintext); + qcrypto_cipher_free(cipher); +} + +int main(int argc, char **argv) +{ + size_t i; + + g_test_init(&argc, &argv, NULL); + + g_assert(qcrypto_init(NULL) == 0); + + for (i = 0; i < G_N_ELEMENTS(test_data); i++) { + g_test_add_data_func(test_data[i].path, &test_data[i], test_cipher); + } + return g_test_run(); +} -- cgit v1.1