From 42cbf67ebe35b933a58edc0dd736e31aee5d9a65 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 15 Jul 2024 18:01:37 -0400 Subject: [PATCH] WIP: tests: Add test case for KDFa replacement by OpenSSL 'KBKDF' Implement a KDFa replacement using OpenSSL's KBKDF function. The resulting implementation has a few restrictions compared to the original one. For example, it cannot accept a counter value to be passed in to resume the KDFa as the reference implementation could -- OSSL does not support providing the counter in this becomes problematic with DRBG_Generate where it seems to want to resume with a counter over the long-term. Signed-off-by: Stefan Berger --- .gitignore | 1 + src/tpm2/crypto/openssl/Helpers.c | 144 +++++++++++++++++++++++++++ src/tpm2/crypto/openssl/Helpers_fp.h | 19 ++++ tests/Makefile.am | 18 +++- tests/kdfa_openssl.c | 76 ++++++++++++++ 5 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 tests/kdfa_openssl.c diff --git a/.gitignore b/.gitignore index 56a43746..926f3ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ Makefile /tests/base64decode /tests/fuzz /tests/freebl_sha1flattensize +/tests/kdfa_openssl /tests/kdfe_openssl /tests/nvram_offsets /tests/object_size diff --git a/src/tpm2/crypto/openssl/Helpers.c b/src/tpm2/crypto/openssl/Helpers.c index 2bd1f333..aa9d6962 100644 --- a/src/tpm2/crypto/openssl/Helpers.c +++ b/src/tpm2/crypto/openssl/Helpers.c @@ -1204,3 +1204,147 @@ out: } #endif // USE_OPENSSL_FUNCTIONS_SSKDF + +#if USE_OPENSSL_FUNCTIONS_KBKDF + +UINT16 OSSLCryptKDFa( + TPM_ALG_ID hashAlg, // IN: hash algorithm used in HMAC + const TPM2B* key, // IN: HMAC key + const TPM2B* label, // IN: a label for the KDF + const TPM2B* contextU, // IN: context U + const TPM2B* contextV, // IN: context V + UINT32 sizeInBits, // IN: size of generated key in bits + BYTE* keyStream, // OUT: key buffer + UINT32* counterInOut, // IN/OUT: caller may provide the iteration + // counter for incremental operations to + // avoid large intermediate buffers. + UINT16 blocks // IN: If non-zero, this is the maximum number + // of blocks to be returned, regardless + // of sizeInBits + ) +{ + UINT16 digestSize = CryptHashGetDigestSize(hashAlg); + char digestname[16]; + OSSL_PARAM params[8]; + OSSL_PARAM *p = params; + int use_separator = 0; + UINT16 generated = 0; + size_t contexts_size; + size_t buffer_size; + UINT32 counter = 0; // counter value + char *buffer; // for contextU+V and key and label + size_t offset; + EVP_KDF_CTX *ctx; + EVP_KDF *kdf; + INT16 bytes; // number of bytes to generate + const char *name; + + pAssert(key != NULL && keyStream != NULL); + + TPM_DO_SELF_TEST(TPM_ALG_KDF1_SP800_108); + + if(digestSize == 0) + return 0; + + if(counterInOut != NULL) + counter = *counterInOut; + + /* The TPM2 reference implementation can iterate by calling CryptKDFe multiple + * times and allows providing a counter for the next iteration. The + * OpenSSL function does not allow this. Therefore: + * - This implementation MUST NOT be used with counter != 0. Adjust caller! + */ + pAssert(counter == 0); + /* - This implementation MUST NOT be called blocks != 0 and + * blocks * digestSize * 8 != sizeInBits. Better call with blocks = 0! + */ + pAssert(blocks == 0 || blocks * digestSize * 8 == sizeInBits); + /* - This implementation MUST NOT be called for sizeInBits & 7 != 0 since + * OpenSSL implementation only allows for multiples of 8. + */ + pAssert((sizeInBits & 7) == 0); + + // If the size of the request is larger than the numbers will handle, + // it is a fatal error. + pAssert(((sizeInBits + 7) / 8) <= INT16_MAX); + + // The number of bytes to be generated is the smaller of the sizeInBits bytes or + // the number of requested blocks. The number of blocks is the smaller of the + // number requested or the number allowed by sizeInBits. A partial block is + // a full block. + bytes = (blocks > 0) ? blocks * digestSize : (UINT16)BITS_TO_BYTES(sizeInBits); + + name = GetDigestNameByHashAlg(hashAlg); + if (!name) + return 0; + if (strlen(name) >= sizeof(digestname)) + FAIL(FATAL_ERROR_INTERNAL); + strcpy(digestname, name); + + buffer_size = 0; + if (contextU) + buffer_size += contextU->size; + if (contextV) + buffer_size += contextV->size; + buffer_size += key->size; + if (label) + buffer_size += label->size; + + buffer = malloc(buffer_size); + if (!buffer) + return 0; + + kdf = EVP_KDF_fetch(NULL, OSSL_KDF_NAME_KBKDF, NULL); + if (!kdf) + goto out; + + ctx = EVP_KDF_CTX_new(kdf); + if (!ctx) + goto out; + + /* fill buffer: 1st contexts; 2nd key; 3rd label */ + offset = 0; + if (contextU) { + memcpy(&buffer[offset], contextU->buffer, contextU->size); + offset += contextU->size; + } + if (contextV) { + memcpy(&buffer[offset], contextV->buffer, contextV->size); + offset += contextV->size; + } + contexts_size = offset; + memcpy(&buffer[contexts_size], key->buffer, key->size); + if (label) + memcpy(&buffer[contexts_size + key->size], label->buffer, label->size); + + if ((label == NULL) || (label->size == 0) + || (label->buffer[label->size - 1] != 0)) + use_separator = 1; + + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, + digestname, 0); + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MAC, + "HMAC", 0); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, + buffer, contexts_size); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, + &buffer[contexts_size], key->size); + if (label) + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, + &buffer[contexts_size + key->size], + label->size); + *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_KBKDF_USE_SEPARATOR, + &use_separator); + *p = OSSL_PARAM_construct_end(); + if (EVP_KDF_derive(ctx, keyStream, bytes, params) <= 0) + goto out; + + generated = bytes; + +out: + EVP_KDF_free(kdf); + + return generated; +} + +#endif // USE_OPENSSL_FUNCTIONS_KBKDF diff --git a/src/tpm2/crypto/openssl/Helpers_fp.h b/src/tpm2/crypto/openssl/Helpers_fp.h index 446f638a..af45bb8e 100644 --- a/src/tpm2/crypto/openssl/Helpers_fp.h +++ b/src/tpm2/crypto/openssl/Helpers_fp.h @@ -131,4 +131,23 @@ OSSLCryptKDFe(TPM_ALG_ID hashAlg, // IN: hash algorithm used in HMAC ); #endif // USE_OPENSSL_FUNCTIONS_SSKDF +#if USE_OPENSSL_FUNCTIONS_KBKDF +LIB_EXPORT UINT16 +OSSLCryptKDFa( + TPM_ALG_ID hashAlg, // IN: hash algorithm used in HMAC + const TPM2B* key, // IN: HMAC key + const TPM2B* label, // IN: a label for the KDF + const TPM2B* contextU, // IN: context U + const TPM2B* contextV, // IN: context V + UINT32 sizeInBits, // IN: size of generated key in bits + BYTE* keyStream, // OUT: key buffer + UINT32* counterInOut, // IN/OUT: caller may provide the iteration + // counter for incremental operations to + // avoid large intermediate buffers. + UINT16 blocks // IN: If non-zero, this is the maximum number + // of blocks to be returned, regardless + // of sizeInBits + ); +#endif // USE_OPENSSL_FUNCTIONS_KBKDF + #endif /* HELPERS_FP_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 34e21421..920000dc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -57,10 +57,12 @@ if ENABLE_STATIC_TESTS # object_size needs ANY_OBJECT_Marshal which only is accessible with '-static' check_PROGRAMS += \ object_size \ - kdfe_openssl + kdfe_openssl \ + kdfa_openssl TESTS += \ object_size \ - kdfe_openssl + kdfe_openssl \ + kdfa_openssl object_size_SOURCES = object_size.c object_size_CFLAGS = $(AM_CFLAGS) \ @@ -85,6 +87,18 @@ kdfe_openssl_CFLAGS = $(AM_CFLAGS) \ -DTPM_POSIX kdfe_openssl_LDFLAGS = $(AM_LDFLAGS) +kdfa_openssl_SOURCES = kdfa_openssl.c +kdfa_openssl_CFLAGS = $(AM_CFLAGS) \ + -static \ + -g -ggdb \ + -I$(top_srcdir)/include/libtpms \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/tpm2 \ + -I$(top_srcdir)/src/tpm2/crypto \ + -I$(top_srcdir)/src/tpm2/crypto/openssl \ + -DTPM_POSIX +kdfa_openssl_LDFLAGS = $(AM_LDFLAGS) + endif # ENABLE_STATIC_TESTS endif # WITH_TPM2 diff --git a/tests/kdfa_openssl.c b/tests/kdfa_openssl.c new file mode 100644 index 00000000..00c380f1 --- /dev/null +++ b/tests/kdfa_openssl.c @@ -0,0 +1,76 @@ +#if USE_OPENSSL_FUNCTIONS_SSKDF + +#include "Tpm.h" +#include "Helpers_fp.h" + +int main(void) +{ + UINT16 gen1, gen2; + TPM2B_LABEL key = { + .t.size = 0x20, + .t.buffer = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}, + }; + TPM2B_LABEL label = { + .t.size = 5, + .t.buffer = "label", + }; + TPM2B_LABEL contextU = { + .t.size = 6, + .t.buffer = "test12", + }; + TPM2B_LABEL contextV = { + .t.size = 7, + .t.buffer = "test123", + }; + BYTE keyStream1[256] = {0, }, keyStream2[256] = {0, }; + UINT32 counter1, counter2; + const TPM_ALG_ID hashAlgs[] = { + TPM_ALG_SHA1, + TPM_ALG_SHA256, + TPM_ALG_SHA384, + TPM_ALG_SHA512 + }; + UINT32 sizeInBits; + UINT16 blocks; + size_t i; + + for (sizeInBits = 0; sizeInBits < 8 * sizeof(keyStream1); sizeInBits += 8) { + for (i = 0; i < ARRAY_SIZE(hashAlgs); i++) { + counter1 = 0; + memset(keyStream1, 0, sizeof(keyStream1)); + gen1 = CryptKDFa(hashAlgs[i], &key.b, &label.b, + &contextU.b, &contextV.b, sizeInBits, keyStream1, + &counter1, blocks); + + counter2 = 0; + memset(keyStream2, 0, sizeof(keyStream2)); + gen2 = OSSLCryptKDFa(hashAlgs[i], &key.b, &label.b, + &contextU.b, &contextV.b, sizeInBits, keyStream2, + &counter2, blocks); + + if (gen1 != gen2 || memcmp(keyStream1, keyStream2, gen1)) { + fprintf(stderr, "results are not equal: gen1: %d gen2: %d hash: %d sizeInBits: %d\n", + gen1, gen2, hashAlgs[i], sizeInBits); + fprintf(stderr, "%02x %02x %02x ... %02x\n", + keyStream1[0], keyStream1[1], keyStream1[2], keyStream1[gen1 - 1]); + fprintf(stderr, "%02x %02x %02x ... %02x\n", + keyStream2[0], keyStream2[1], keyStream2[2], keyStream2[gen2 - 1]); + return 1; + } + fprintf(stdout, "Success with hash %d, sizeInBits %d\n", + hashAlgs[i], sizeInBits); + } + } +} + +#else + +int main(void) +{ + return 0; +} + +#endif