From 60b8d14bade46ed0925a84737b5acef6c84bfe12 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Thu, 3 Jul 2025 10:34:00 -0700 Subject: [PATCH 1/2] Implement KRB5KDF for wolfProvider --- include/wolfprovider/alg_funcs.h | 2 + include/wolfprovider/settings.h | 1 + src/include.am | 1 + src/wp_krb5kdf.c | 578 +++++++++++++++++++++++++++++++ src/wp_wolfprov.c | 4 + test/include.am | 1 + test/test_krb5kdf.c | 214 ++++++++++++ test/unit.c | 3 + test/unit.h | 4 + 9 files changed, 808 insertions(+) create mode 100644 src/wp_krb5kdf.c create mode 100644 test/test_krb5kdf.c diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h index 3133044a..c709ebe1 100644 --- a/include/wolfprovider/alg_funcs.h +++ b/include/wolfprovider/alg_funcs.h @@ -146,6 +146,7 @@ typedef void (*DFUNC)(void); #define WP_NAMES_PKCS12KDF "PKCS12KDF" #define WP_NAMES_TLS1_3_KDF "TLS13-KDF" #define WP_NAMES_TLS1_PRF "TLS1-PRF" +#define WP_NAMES_KRB5KDF "KRB5KDF" /* Signature names. */ #define WP_NAMES_RSA "RSA:rsaEncryption:1.2.840.113549.1.1.1" @@ -309,6 +310,7 @@ extern const OSSL_DISPATCH wp_kdf_pbkdf2_functions[]; extern const OSSL_DISPATCH wp_kdf_pkcs12_functions[]; extern const OSSL_DISPATCH wp_kdf_tls1_3_kdf_functions[]; extern const OSSL_DISPATCH wp_kdf_tls1_prf_functions[]; +extern const OSSL_DISPATCH wp_kdf_krb5kdf_functions[]; /* Signature implementations. */ extern const OSSL_DISPATCH wp_rsa_signature_functions[]; diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index b5ab88c7..9b5d3894 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -82,6 +82,7 @@ #ifndef NO_AES_CBC #define WP_HAVE_AESCBC #define WP_HAVE_AESCTS + #define WP_HAVE_KRB5KDF #endif #ifndef NO_DES3 #define WP_HAVE_DES3CBC diff --git a/src/include.am b/src/include.am index e4729015..49d1dd99 100644 --- a/src/include.am +++ b/src/include.am @@ -21,6 +21,7 @@ libwolfprov_la_SOURCES += src/wp_tls1_prf.c libwolfprov_la_SOURCES += src/wp_kdf_kmgmt.c libwolfprov_la_SOURCES += src/wp_kdf_exch.c libwolfprov_la_SOURCES += src/wp_pbkdf2.c +libwolfprov_la_SOURCES += src/wp_krb5kdf.c libwolfprov_la_SOURCES += src/wp_rsa_kmgmt.c libwolfprov_la_SOURCES += src/wp_rsa_sig.c libwolfprov_la_SOURCES += src/wp_rsa_asym.c diff --git a/src/wp_krb5kdf.c b/src/wp_krb5kdf.c new file mode 100644 index 00000000..d9e7a266 --- /dev/null +++ b/src/wp_krb5kdf.c @@ -0,0 +1,578 @@ +/* wp_krb5kdf.c + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * This file is part of wolfProvider. + * + * wolfProvider 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 3 of the License, or + * (at your option) any later version. + * + * wolfProvider 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfProvider. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** Base set of parameters settable against context for KRB5KDF. */ +#define WP_KRB5KDF_BASE_SETTABLES \ + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_PROPERTIES, NULL, 0), \ + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, NULL, 0), \ + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, NULL, 0), \ + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_CONSTANT, NULL, 0) + +/** + * The KRB5KDF context structure. + */ +typedef struct wp_Krb5kdfCtx { + /** wolfSSL provider context. */ + WOLFPROV_CTX* provCtx; + + /** Cipher type for KRB5KDF. */ + int cipherType; + + /** Key for KDF. */ + unsigned char* key; + /** Size of key in bytes. */ + size_t keySz; + + /** Constant for KRB5KDF. */ + unsigned char* constant; + /** Size of constant in bytes. */ + size_t constantSz; +} wp_Krb5kdfCtx; + +#define WP_KRB5KDF_CIPHER_NONE 0 +#define WP_KRB5KDF_CIPHER_AES_128_CBC 1 +#define WP_KRB5KDF_CIPHER_AES_256_CBC 2 + +/** + * Create a new KRB5KDF context object. + * + * @param [in] provCtx wolfProvider context object. + * @return NULL on failure. + * @return KRB5KDF context object on success. + */ +static wp_Krb5kdfCtx* wp_kdf_krb5kdf_new(WOLFPROV_CTX* provCtx) +{ + wp_Krb5kdfCtx* ctx = NULL; + + if (wolfssl_prov_is_running()) { + ctx = OPENSSL_zalloc(sizeof(*ctx)); + } + if (ctx != NULL) { + ctx->provCtx = provCtx; + ctx->cipherType = WP_KRB5KDF_CIPHER_NONE; + } + + return ctx; +} + +/** + * Clear KRB5KDF context object. + * + * @param [in, out] ctx KRB5KDF context object. + */ +static void wp_kdf_krb5kdf_clear(wp_Krb5kdfCtx* ctx) +{ + if (ctx != NULL) { + OPENSSL_clear_free(ctx->key, ctx->keySz); + if (ctx->constant != NULL) { + OPENSSL_free(ctx->constant); + } + } +} + +/** + * Free the KRB5KDF context object. + * + * @param [in, out] ctx KRB5KDF context object. + */ +static void wp_kdf_krb5kdf_free(wp_Krb5kdfCtx* ctx) +{ + if (ctx != NULL) { + wp_kdf_krb5kdf_clear(ctx); + OPENSSL_free(ctx); + } +} + +/** + * Reset KRB5KDF context object. + * + * Disposes of allocated data. + * + * @param [in, out] ctx KRB5KDF context object. + */ +static void wp_kdf_krb5kdf_reset(wp_Krb5kdfCtx* ctx) +{ + if (ctx != NULL) { + WOLFPROV_CTX* provCtx = ctx->provCtx; + wp_kdf_krb5kdf_clear(ctx); + XMEMSET(ctx, 0, sizeof(*ctx)); + ctx->provCtx = provCtx; + } +} + +#define WP_MAX_CIPHER_NAME_LEN 12 + +/** + * Set the KRB5KDF context parameters. + * + * @param [in, out] ctx KRB5KDF context object. + * @param [in] params Array of parameters with values. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_kdf_krb5kdf_set_ctx_params(wp_Krb5kdfCtx* ctx, + const OSSL_PARAM params[]) +{ + int ok = 1; + OSSL_PARAM* p; + + if (params != NULL) { + if (ok) { + p = OSSL_PARAM_locate((OSSL_PARAM*)params, OSSL_KDF_PARAM_CIPHER); + if ((p != NULL) && (p->data != NULL)) { + char cipher[WP_MAX_CIPHER_NAME_LEN]; + char* pCipher = cipher; + XMEMSET(cipher, 0, sizeof(cipher)); + + if (!OSSL_PARAM_get_utf8_string(p, &pCipher, sizeof(cipher))) { + ok = 0; + } + if (ok) { + /* Only allow AES-128-CBC or AES-256-CBC. */ + if (XSTRCMP(cipher, "AES-128-CBC") == 0) { + ctx->cipherType = WP_KRB5KDF_CIPHER_AES_128_CBC; + } + else if (XSTRCMP(cipher, "AES-256-CBC") == 0) { + ctx->cipherType = WP_KRB5KDF_CIPHER_AES_256_CBC; + } + else { + ok = 0; + } + } + } + } + + if (ok) { + p = OSSL_PARAM_locate((OSSL_PARAM*)params, OSSL_KDF_PARAM_KEY); + if ((p != NULL) && (p->data != NULL)) { + OPENSSL_clear_free(ctx->key, ctx->keySz); + ctx->key = NULL; + if (!OSSL_PARAM_get_octet_string(p, (void**)&ctx->key, 0, + &ctx->keySz)) { + ok = 0; + } + } + } + + if (ok) { + p = OSSL_PARAM_locate((OSSL_PARAM*)params, OSSL_KDF_PARAM_CONSTANT); + if ((p != NULL) && (p->data != NULL)) { + OPENSSL_free(ctx->constant); + ctx->constant = NULL; + if (!OSSL_PARAM_get_octet_string(p, (void**)&ctx->constant, 0, + &ctx->constantSz)) { + ok = 0; + } + } + } + } + + return ok; +} + +/** + * Get the KRB5KDF context parameters. + * + * @param [in] ctx KRB5KDF context object. + * @param [in, out] params Array of parameters with values. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_kdf_krb5kdf_get_ctx_params(wp_Krb5kdfCtx* ctx, + OSSL_PARAM params[]) +{ + int ok = 1; + OSSL_PARAM* p; + + (void)ctx; + + p = OSSL_PARAM_locate(params, OSSL_KDF_PARAM_SIZE); + if (p != NULL) { + if (!OSSL_PARAM_set_size_t(p, ctx->keySz)) { + ok = 0; + } + } + + return ok; +} + +/** + * Returns the parameters that can be set in the KRB5KDF context. + * + * @param [in] ctx KRB5KDF context object. Unused. + * @param [in] provCtx Provider context object. Unused. + * @return Array of parameters. + */ +static const OSSL_PARAM* wp_kdf_krb5kdf_settable_ctx_params(wp_Krb5kdfCtx* ctx, + WOLFPROV_CTX* provCtx) +{ + static const OSSL_PARAM wp_krb5kdf_supported_settable_ctx_params[] = { + WP_KRB5KDF_BASE_SETTABLES, + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_CIPHER, NULL, 0), + OSSL_PARAM_END + }; + (void)ctx; + (void)provCtx; + return wp_krb5kdf_supported_settable_ctx_params; +} + +/** + * Returns the parameters that can be retrieved from the KRB5KDF context. + * + * @param [in] ctx KRB5KDF context object. Unused. + * @param [in] provCtx Provider context object. Unused. + * @return Array of parameters. + */ +static const OSSL_PARAM* wp_kdf_krb5kdf_gettable_ctx_params(wp_Krb5kdfCtx* ctx, + WOLFPROV_CTX* provCtx) +{ + static const OSSL_PARAM wp_krb5kdf_supported_gettable_ctx_params[] = { + OSSL_PARAM_size_t(OSSL_KDF_PARAM_SIZE, NULL), + OSSL_PARAM_END + }; + (void)ctx; + (void)provCtx; + return wp_krb5kdf_supported_gettable_ctx_params; +} + +static int wp_kdf_krb5kdf_expected_key_size(wp_Krb5kdfCtx* ctx) +{ + switch (ctx->cipherType) { + case WP_KRB5KDF_CIPHER_AES_128_CBC: + return 16; + case WP_KRB5KDF_CIPHER_AES_256_CBC: + return 32; + default: + return 0; + } +} + +/* Calculate the greatest common divisor using Euclidean algorithm */ +static unsigned int gcd(unsigned int a, unsigned int b) +{ + while (b != 0) { + unsigned int temp = b; + b = a % b; + a = temp; + } + return a; +} + +/* Calculate the least common multiple */ +static unsigned int lcm(unsigned int a, unsigned int b) +{ + unsigned int g = gcd(a, b); + if (g == 0) { + return 0; + } + /* Check for potential overflow before multiplication */ + if (a > 0xFFFFFFFFU / (b / g)) { + return 0; + } + return (a / g) * b; +} + +/* Add two byte arrays using 1's complement addition (end-around carry) */ +static void ones_complement_add(unsigned char *result, const unsigned char *a, + const unsigned char *b, unsigned int len) +{ + unsigned int carry = 0; + int i; + + /* Add from right to left (MSB at index 0) */ + for (i = (int)len - 1; i >= 0; i--) { + unsigned int sum = (unsigned int)a[i] + (unsigned int)b[i] + carry; + result[i] = (unsigned char)(sum & 0xFF); + carry = (sum >> 8) & 1; + } + + /* Handle end-around carry for 1's complement */ + while (carry) { + unsigned int new_carry = 0; + for (i = (int)len - 1; i >= 0; i--) { + unsigned int sum = (unsigned int)result[i] + carry; + result[i] = (unsigned char)(sum & 0xFF); + new_carry = (sum >> 8) & 1; + carry = 0; + } + carry = new_carry; + } +} + +/* Rotate a byte array to the right by specified number of bits */ +static void rotate_right(unsigned char *data, unsigned int data_len, + unsigned int bits) +{ + unsigned int total_bits = data_len * 8; + unsigned char *temp; + unsigned int i; + + if (data_len == 0 || bits == 0) { + return; + } + + bits = bits % total_bits; + if (bits == 0) { + return; + } + + temp = (unsigned char*)OPENSSL_malloc(data_len); + if (temp == NULL) { + return; + } + + XMEMSET(temp, 0, data_len); + + /* Perform bit-level rotation */ + for (i = 0; i < total_bits; i++) { + unsigned int src_byte = i / 8; + unsigned int src_bit = i % 8; + unsigned int dst_pos = (i + bits) % total_bits; + unsigned int dst_byte = dst_pos / 8; + unsigned int dst_bit = dst_pos % 8; + + /* Extract source bit (MSB = bit 0) */ + unsigned char bit = (data[src_byte] >> (7 - src_bit)) & 1; + + /* Set destination bit (MSB = bit 0) */ + if (bit) { + temp[dst_byte] |= (1U << (7 - dst_bit)); + } + } + + XMEMCPY(data, temp, data_len); + OPENSSL_clear_free(temp, data_len); +} + +/* N-fold(K) where blocksize is N, and constant_len is K + * Note: Here |= denotes concatenation + * + * L = lcm(N,K) + * R = L/K + * + * for r: 1 -> R + * s |= constant rot 13*(r-1)) + * + * block = 0 + * for k: 1 -> K + * block += s[N(k-1)..(N-1)k] (ones'-complement addition) + * + * Optimizing for space we compute: + * for each l in L-1 -> 0: + * s[l] = (constant rot 13*(l/K))[l%k] + * block[l % N] += s[l] (with carry) + * finally add carry if any + */ +static void n_fold(unsigned char *block, unsigned int blocksize, + const unsigned char *constant, size_t constant_len) +{ + unsigned int input_bits = (unsigned int)(constant_len * 8); + unsigned int output_bits = blocksize * 8; + unsigned int expanded_bits; + unsigned int expanded_bytes; + unsigned int replications; + unsigned int i; + unsigned char *expanded = NULL; + unsigned char *temp = NULL; + + /* Clear output block */ + XMEMSET(block, 0, blocksize); + + /* Handle edge cases */ + if (blocksize == 0 || constant_len == 0) { + return; + } + + /* Calculate LCM of input and output bit lengths */ + expanded_bits = lcm(input_bits, output_bits); + if (expanded_bits == 0) { + return; + } + + expanded_bytes = (expanded_bits + 7) / 8; + expanded = (unsigned char*)OPENSSL_zalloc(expanded_bytes); + temp = (unsigned char*)OPENSSL_malloc(constant_len); + if (expanded == NULL || temp == NULL) { + goto cleanup; + } + + /* Calculate number of replications */ + replications = expanded_bits / input_bits; + + /* Initialize temp with constant */ + XMEMCPY(temp, constant, constant_len); + + /* Replicate input data with rotation */ + for (i = 0; i < replications; i++) { + unsigned int bit_offset = i * input_bits; + unsigned int bit; + + /* Copy current input to expanded buffer at bit offset */ + for (bit = 0; bit < input_bits; bit++) { + unsigned int src_byte = bit / 8; + unsigned int src_bit = bit % 8; + unsigned int dst_pos = bit_offset + bit; + unsigned int dst_byte = dst_pos / 8; + unsigned int dst_bit = dst_pos % 8; + + if (dst_byte >= expanded_bytes) { + break; + } + + /* Extract bit from source (MSB = bit 0) */ + unsigned char bit_val = (temp[src_byte] >> (7 - src_bit)) & 1; + + /* Set bit in destination (MSB = bit 0) */ + if (bit_val) { + expanded[dst_byte] |= (1U << (7 - dst_bit)); + } + } + + /* Rotate input for next iteration */ + if (i + 1 < replications) { + rotate_right(temp, (unsigned int)constant_len, 13); + } + } + + /* Fold the expanded buffer into the output block */ + for (i = 0; i < expanded_bytes; i += blocksize) { + ones_complement_add(block, block, expanded + i, + (i + blocksize <= expanded_bytes) ? blocksize : + (expanded_bytes - i)); + } + +cleanup: + if (expanded != NULL) { + OPENSSL_clear_free(expanded, expanded_bytes); + } + if (temp != NULL) { + OPENSSL_clear_free(temp, constant_len); + } +} + +/** + * Derive a key using KRB5KDF. + * + * @param [in, out] ctx KRB5KDF context object. + * @param [out] key Buffer to hold derived key. + * @param [in] keyLen Size of buffer in bytes. + * @param [in] params Array of parameters to set before deriving. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_kdf_krb5kdf_derive(wp_Krb5kdfCtx* ctx, unsigned char* key, + size_t keyLen, const OSSL_PARAM params[]) +{ + int ok = 1; + size_t osize = 0; + size_t cipherLen = 0; + int rc; + Aes aes; + byte block[AES_BLOCK_SIZE]; + byte cipherBlock[AES_BLOCK_SIZE]; + byte *plain = NULL; + byte *cipher = NULL; + + if (!wolfssl_prov_is_running()) { + ok = 0; + } + if (ok && (!wp_kdf_krb5kdf_set_ctx_params(ctx, params))) { + ok = 0; + } + if (ok && (ctx->key == NULL)) { + ok = 0; + } + if (ok && (ctx->keySz != keyLen)) { + ok = 0; + } + if (ok && (wp_kdf_krb5kdf_expected_key_size(ctx) != (int)ctx->keySz)) { + ok = 0; + } + if (ok) { + XMEMSET(key, 0, keyLen); + + rc = wc_AesSetKey(&aes, ctx->key, (word32)ctx->keySz, NULL, + AES_ENCRYPTION); + if (rc != 0) { + ok = 0; + } + } + if (ok) { + n_fold(block, AES_BLOCK_SIZE, ctx->constant, ctx->constantSz); + plain = block; + cipher = cipherBlock; + for (osize = 0; ok && osize < keyLen; osize += cipherLen) { + rc = wc_AesCbcEncrypt(&aes, cipher, plain, AES_BLOCK_SIZE); + if (rc != 0) { + ok = 0; + } + + cipherLen = AES_BLOCK_SIZE; + if (cipherLen > (keyLen - osize)) + cipherLen = (keyLen - osize); + + XMEMCPY(key + osize, cipher, cipherLen); + if (keyLen > (osize + cipherLen)) { + rc = wc_AesSetKey(&aes, ctx->key, (word32)ctx->keySz, NULL, + AES_ENCRYPTION); + if (rc != 0) { + ok = 0; + } + + if (ok) { + /* swap blocks */ + plain = cipher; + if (cipher == block) { + cipher = cipherBlock; + } else { + cipher = block; + } + } + } + } + } + + wc_AesFree(&aes); + + return ok; +} + +/** Dispatch table for KRB5KDF functions implemented using wolfSSL. */ +const OSSL_DISPATCH wp_kdf_krb5kdf_functions[] = { + { OSSL_FUNC_KDF_NEWCTX, (DFUNC)wp_kdf_krb5kdf_new }, + { OSSL_FUNC_KDF_FREECTX, (DFUNC)wp_kdf_krb5kdf_free }, + { OSSL_FUNC_KDF_RESET, (DFUNC)wp_kdf_krb5kdf_reset }, + { OSSL_FUNC_KDF_DERIVE, (DFUNC)wp_kdf_krb5kdf_derive }, + { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS, (DFUNC)wp_kdf_krb5kdf_settable_ctx_params }, + { OSSL_FUNC_KDF_SET_CTX_PARAMS, (DFUNC)wp_kdf_krb5kdf_set_ctx_params }, + { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS, (DFUNC)wp_kdf_krb5kdf_gettable_ctx_params }, + { OSSL_FUNC_KDF_GET_CTX_PARAMS, (DFUNC)wp_kdf_krb5kdf_get_ctx_params }, + { 0, NULL } +}; diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c index 444964e5..a0f789b3 100644 --- a/src/wp_wolfprov.c +++ b/src/wp_wolfprov.c @@ -546,6 +546,10 @@ static const OSSL_ALGORITHM wolfprov_kdfs[] = { { WP_NAMES_TLS1_PRF, WOLFPROV_PROPERTIES, wp_kdf_tls1_prf_functions, "" }, #endif +#ifdef WP_HAVE_KRB5KDF + { WP_NAMES_KRB5KDF, WOLFPROV_PROPERTIES, wp_kdf_krb5kdf_functions, + "" }, +#endif { NULL, NULL, NULL, NULL } }; diff --git a/test/include.am b/test/include.am index f4f55329..07f2fc6e 100644 --- a/test/include.am +++ b/test/include.am @@ -20,6 +20,7 @@ test_unit_test_SOURCES = \ test/test_ecc.c \ test/test_ecx.c \ test/test_gmac.c \ + test/test_krb5kdf.c \ test/test_hkdf.c \ test/test_hmac.c \ test/test_logging.c \ diff --git a/test/test_krb5kdf.c b/test/test_krb5kdf.c new file mode 100644 index 00000000..022b18d0 --- /dev/null +++ b/test/test_krb5kdf.c @@ -0,0 +1,214 @@ +/* test_krb5kdf.c + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * This file is part of wolfProvider. + * + * wolfProvider 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 3 of the License, or + * (at your option) any later version. + * + * wolfProvider 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfProvider. If not, see . + */ + +#include "unit.h" + +#ifdef WP_HAVE_KRB5KDF + +/* Test KRB5KDF using OpenSSL implementation */ +static int test_krb5kdf_calc(OSSL_LIB_CTX* libCtx, unsigned char *key, + size_t keyLen, const char* cipher, const unsigned char* inKey, + size_t inKeyLen, const unsigned char* constant, size_t constantLen) +{ + int err = 0; + EVP_KDF *kdf = NULL; + EVP_KDF_CTX *kctx = NULL; + OSSL_PARAM params[4], *p = params; + + /* Create KDF */ + kdf = EVP_KDF_fetch(libCtx, "KRB5KDF", NULL); + if (kdf == NULL) { + PRINT_MSG("Failed to fetch KRB5KDF"); + err = 1; + } + + if (err == 0) { + /* Create KDF context */ + kctx = EVP_KDF_CTX_new(kdf); + if (kctx == NULL) { + PRINT_MSG("Failed to create KDF context"); + err = 1; + } + } + + if (err == 0) { + /* Set parameters */ + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_CIPHER, + (char*)cipher, 0); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, + (unsigned char*)inKey, inKeyLen); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_CONSTANT, + (unsigned char*)constant, constantLen); + *p = OSSL_PARAM_construct_end(); + + /* Derive key */ + if (EVP_KDF_derive(kctx, key, keyLen, params) <= 0) { + PRINT_MSG("Failed to derive key"); + err = 1; + } + } + + EVP_KDF_CTX_free(kctx); + EVP_KDF_free(kdf); + return err; +} + +/* Test error cases */ +static int test_krb5kdf_error_cases(OSSL_LIB_CTX* libCtx) +{ + int err; + unsigned char key[32]; + /* 32-byte key for AES-128 test (wrong size) */ + unsigned char inKey32[] = { + 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, 0x20 + }; + /* 16-byte key for AES-256 test (wrong size) */ + unsigned char inKey16[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 + }; + unsigned char constant[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 + }; + + PRINT_MSG("Testing KRB5KDF error case - AES-128-CBC with 32-byte key"); + /* This should fail since AES-128 key size is 16 bytes */ + err = test_krb5kdf_calc(libCtx, key, sizeof(key), "AES-128-CBC", + inKey32, sizeof(inKey32), constant, sizeof(constant)); + if (err == 0) { + /* If we get here, the test failed because it should have errored */ + PRINT_MSG("FAILED: KRB5KDF accepted wrong key size for AES-128-CBC"); + return 1; + } + PRINT_MSG("Negative test passed - KRB5KDF correctly rejected wrong key size for AES-128-CBC"); + + PRINT_MSG("Testing KRB5KDF error case - AES-256-CBC with 16-byte key"); + /* This should fail since AES-256 key size is 32 bytes */ + err = test_krb5kdf_calc(libCtx, key, sizeof(key), "AES-256-CBC", + inKey16, sizeof(inKey16), constant, sizeof(constant)); + if (err == 0) { + /* If we get here, the test failed because it should have errored */ + PRINT_MSG("FAILED: KRB5KDF accepted wrong key size for AES-256-CBC"); + return 1; + } + PRINT_MSG("Negative test passed - KRB5KDF correctly rejected wrong key size for AES-256-CBC"); + + return 0; +} + +/* Test vectors */ +static int test_krb5kdf_vector(void) +{ + int err = 0; + unsigned char oKey[32]; + unsigned char wKey[32]; + /* Test vector - AES-128-CBC */ + unsigned char inKey128[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 + }; + /* Test vector - AES-256-CBC */ + unsigned char inKey256[] = { + 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, 0x20 + }; + unsigned char constant[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 + }; + + /* Test AES-128-CBC */ + PRINT_MSG("Testing KRB5KDF with OpenSSL - AES-128-CBC"); + err = test_krb5kdf_calc(osslLibCtx, oKey, 16, "AES-128-CBC", + inKey128, sizeof(inKey128), constant, sizeof(constant)); + if (err == 1) { + PRINT_MSG("FAILED OpenSSL - AES-128-CBC"); + return err; + } + PRINT_MSG("Testing KRB5KDF with wolfSSL - AES-128-CBC"); + err = test_krb5kdf_calc(wpLibCtx, wKey, 16, "AES-128-CBC", + inKey128, sizeof(inKey128), constant, sizeof(constant)); + if (err == 1) { + PRINT_MSG("FAILED wolfSSL - AES-128-CBC"); + return err; + } + if (memcmp(oKey, wKey, 16) != 0) { + PRINT_MSG("FAILED, wolfSSL and OpenSSL derived different keys"); + PRINT_BUFFER("OpenSSL key", oKey, 16); + PRINT_BUFFER("wolfSSL key", wKey, 16); + return 1; + } + + /* Test AES-256-CBC */ + PRINT_MSG("Testing KRB5KDF with OpenSSL - AES-256-CBC"); + err = test_krb5kdf_calc(osslLibCtx, oKey, 32, "AES-256-CBC", + inKey256, sizeof(inKey256), constant, sizeof(constant)); + if (err == 1) { + PRINT_MSG("FAILED OpenSSL - AES-256-CBC"); + return err; + } + PRINT_MSG("Testing KRB5KDF with wolfSSL - AES-256-CBC"); + err = test_krb5kdf_calc(wpLibCtx, wKey, 32, "AES-256-CBC", + inKey256, sizeof(inKey256), constant, sizeof(constant)); + if (err == 1) { + PRINT_MSG("FAILED wolfSSL - AES-256-CBC"); + return err; + } + if (memcmp(oKey, wKey, 32) != 0) { + PRINT_MSG("FAILED, wolfSSL and OpenSSL derived different keys"); + PRINT_BUFFER("OpenSSL key", oKey, 32); + PRINT_BUFFER("wolfSSL key", wKey, 32); + return 1; + } + + return err; +} + +int test_krb5kdf(void *data) +{ + int err = 0; + + (void)data; + + err = test_krb5kdf_vector(); + if (err != 0) { + return err; + } + + /* Test error cases with OpenSSL first */ + err = test_krb5kdf_error_cases(osslLibCtx); + if (err != 0) { + return err; + } + + /* Test error cases with wolfSSL */ + err = test_krb5kdf_error_cases(wpLibCtx); + if (err != 0) { + return err; + } + + return err; +} + +#endif /* WP_HAVE_KRB5KDF */ diff --git a/test/unit.c b/test/unit.c index ad370310..dbfbf4ec 100644 --- a/test/unit.c +++ b/test/unit.c @@ -99,6 +99,9 @@ TEST_CASE test_case[] = { #ifdef WP_HAVE_HKDF TEST_DECL(test_hkdf, NULL), #endif +#ifdef WP_HAVE_KRB5KDF + TEST_DECL(test_krb5kdf, NULL), +#endif #ifdef WP_HAVE_DES3CBC TEST_DECL(test_des3_cbc, NULL), TEST_DECL(test_des3_cbc_stream, NULL), diff --git a/test/unit.h b/test/unit.h index 0499533b..918dfb72 100644 --- a/test/unit.h +++ b/test/unit.h @@ -132,6 +132,10 @@ int test_tls1_prf(void *data); int test_hkdf(void *data); #endif +#ifdef WP_HAVE_KRB5KDF +int test_krb5kdf(void *data); +#endif + #ifdef WP_HAVE_DES3CBC int test_des3_cbc(void *data); int test_des3_cbc_stream(void *data); From 055085babdd1e88e022bfff56e046508487f21e1 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Mon, 7 Jul 2025 13:04:35 -0700 Subject: [PATCH 2/2] Update to use more optimized nfold implementation --- src/wp_krb5kdf.c | 237 +++++++++++++---------------------------------- 1 file changed, 66 insertions(+), 171 deletions(-) diff --git a/src/wp_krb5kdf.c b/src/wp_krb5kdf.c index d9e7a266..68ab8a75 100644 --- a/src/wp_krb5kdf.c +++ b/src/wp_krb5kdf.c @@ -274,103 +274,6 @@ static int wp_kdf_krb5kdf_expected_key_size(wp_Krb5kdfCtx* ctx) } } -/* Calculate the greatest common divisor using Euclidean algorithm */ -static unsigned int gcd(unsigned int a, unsigned int b) -{ - while (b != 0) { - unsigned int temp = b; - b = a % b; - a = temp; - } - return a; -} - -/* Calculate the least common multiple */ -static unsigned int lcm(unsigned int a, unsigned int b) -{ - unsigned int g = gcd(a, b); - if (g == 0) { - return 0; - } - /* Check for potential overflow before multiplication */ - if (a > 0xFFFFFFFFU / (b / g)) { - return 0; - } - return (a / g) * b; -} - -/* Add two byte arrays using 1's complement addition (end-around carry) */ -static void ones_complement_add(unsigned char *result, const unsigned char *a, - const unsigned char *b, unsigned int len) -{ - unsigned int carry = 0; - int i; - - /* Add from right to left (MSB at index 0) */ - for (i = (int)len - 1; i >= 0; i--) { - unsigned int sum = (unsigned int)a[i] + (unsigned int)b[i] + carry; - result[i] = (unsigned char)(sum & 0xFF); - carry = (sum >> 8) & 1; - } - - /* Handle end-around carry for 1's complement */ - while (carry) { - unsigned int new_carry = 0; - for (i = (int)len - 1; i >= 0; i--) { - unsigned int sum = (unsigned int)result[i] + carry; - result[i] = (unsigned char)(sum & 0xFF); - new_carry = (sum >> 8) & 1; - carry = 0; - } - carry = new_carry; - } -} - -/* Rotate a byte array to the right by specified number of bits */ -static void rotate_right(unsigned char *data, unsigned int data_len, - unsigned int bits) -{ - unsigned int total_bits = data_len * 8; - unsigned char *temp; - unsigned int i; - - if (data_len == 0 || bits == 0) { - return; - } - - bits = bits % total_bits; - if (bits == 0) { - return; - } - - temp = (unsigned char*)OPENSSL_malloc(data_len); - if (temp == NULL) { - return; - } - - XMEMSET(temp, 0, data_len); - - /* Perform bit-level rotation */ - for (i = 0; i < total_bits; i++) { - unsigned int src_byte = i / 8; - unsigned int src_bit = i % 8; - unsigned int dst_pos = (i + bits) % total_bits; - unsigned int dst_byte = dst_pos / 8; - unsigned int dst_bit = dst_pos % 8; - - /* Extract source bit (MSB = bit 0) */ - unsigned char bit = (data[src_byte] >> (7 - src_bit)) & 1; - - /* Set destination bit (MSB = bit 0) */ - if (bit) { - temp[dst_byte] |= (1U << (7 - dst_bit)); - } - } - - XMEMCPY(data, temp, data_len); - OPENSSL_clear_free(temp, data_len); -} - /* N-fold(K) where blocksize is N, and constant_len is K * Note: Here |= denotes concatenation * @@ -393,87 +296,79 @@ static void rotate_right(unsigned char *data, unsigned int data_len, static void n_fold(unsigned char *block, unsigned int blocksize, const unsigned char *constant, size_t constant_len) { - unsigned int input_bits = (unsigned int)(constant_len * 8); - unsigned int output_bits = blocksize * 8; - unsigned int expanded_bits; - unsigned int expanded_bytes; - unsigned int replications; + unsigned int cnt; unsigned int i; - unsigned char *expanded = NULL; - unsigned char *temp = NULL; - - /* Clear output block */ - XMEMSET(block, 0, blocksize); - - /* Handle edge cases */ - if (blocksize == 0 || constant_len == 0) { + unsigned int a; + unsigned int b; + unsigned int carry; + unsigned int rot; + unsigned int bi; + unsigned int const_len = (unsigned int)constant_len; + + /* If equal size then only one unrotated copy of constant needed. */ + if (blocksize == const_len) { + XMEMCPY(block, constant, constant_len); return; } - /* Calculate LCM of input and output bit lengths */ - expanded_bits = lcm(input_bits, output_bits); - if (expanded_bits == 0) { - return; - } - - expanded_bytes = (expanded_bits + 7) / 8; - expanded = (unsigned char*)OPENSSL_zalloc(expanded_bytes); - temp = (unsigned char*)OPENSSL_malloc(constant_len); - if (expanded == NULL || temp == NULL) { - goto cleanup; - } - - /* Calculate number of replications */ - replications = expanded_bits / input_bits; - - /* Initialize temp with constant */ - XMEMCPY(temp, constant, constant_len); - - /* Replicate input data with rotation */ - for (i = 0; i < replications; i++) { - unsigned int bit_offset = i * input_bits; - unsigned int bit; - - /* Copy current input to expanded buffer at bit offset */ - for (bit = 0; bit < input_bits; bit++) { - unsigned int src_byte = bit / 8; - unsigned int src_bit = bit % 8; - unsigned int dst_pos = bit_offset + bit; - unsigned int dst_byte = dst_pos / 8; - unsigned int dst_bit = dst_pos % 8; - - if (dst_byte >= expanded_bytes) { - break; - } - - /* Extract bit from source (MSB = bit 0) */ - unsigned char bit_val = (temp[src_byte] >> (7 - src_bit)) & 1; - - /* Set bit in destination (MSB = bit 0) */ - if (bit_val) { - expanded[dst_byte] |= (1U << (7 - dst_bit)); - } - } - - /* Rotate input for next iteration */ - if (i + 1 < replications) { - rotate_right(temp, (unsigned int)constant_len, 13); - } + /* Compute GCD of constant_len and blocksize. */ + a = blocksize; + b = const_len; + while (b != 0) { + unsigned int t = b; + b = a % b; + a = t; } - - /* Fold the expanded buffer into the output block */ - for (i = 0; i < expanded_bytes; i += blocksize) { - ones_complement_add(block, block, expanded + i, - (i + blocksize <= expanded_bytes) ? blocksize : - (expanded_bytes - i)); + /* Calculate LCM of constant_len and blocksize. */ + cnt = (const_len * blocksize) / a; + + /* Start with constant un-rotated and then add to zero for the rest. */ + XMEMCPY(block, constant, constant_len); + XMEMSET(block + constant_len, 0, blocksize - constant_len); + + /* No initial carry. */ + carry = 0; + /* First rotation is 13 bits. */ + rot = 13; + /* Starting block index - constant_len <= blocksize. */ + bi = const_len; + /* Do one constant at a time - cnt is a multiple of constant_len. */ + for (i = const_len; i < cnt; i += const_len) { + unsigned int j; + /* Calculate first index into constant to rotate. */ + unsigned int ci = ((const_len - (rot >> 3)) - 1) % const_len; + /* Calculate amount to rotate right and left. */ + unsigned char rr = rot & 0x7; + unsigned char rl = 8 - rr; + + /* Add in constant buffer to block. */ + for (j = 0; j < const_len; j++) { + /* Rotated constant value. */ + unsigned char rcv; + + /* Get rotated constant buffer value. */ + rcv = (unsigned char)(constant[ci] << rl); + ci = (ci + 1) % const_len; + rcv |= (unsigned char)(constant[ci] >> rr); + + /* Add block value and rotated constant value to previous carry. */ + carry += block[bi] + rcv; + /* Store new block value. */ + block[bi] = (unsigned char)(carry & 0xff); + /* Get carry. */ + carry >>= 8; + + /* Next block index. */ + bi = (bi + 1) % blocksize; + } + rot += 13; } -cleanup: - if (expanded != NULL) { - OPENSSL_clear_free(expanded, expanded_bytes); - } - if (temp != NULL) { - OPENSSL_clear_free(temp, constant_len); + /* Final carry pass. */ + for (i = 0; (i < blocksize) && (carry > 0); i++) { + carry += block[i]; + block[i] = (unsigned char)(carry & 0xff); + carry >>= 8; } }