/* * Copyright (C) 2025 Denys Vlasenko * * Licensed under GPLv2, see file LICENSE in this source tree. */ //kbuild:lib-$(CONFIG_TLS) += hash_hmac.o //kbuild:lib-$(CONFIG_USE_BB_CRYPT_YES) += hash_hmac.o #include "libbb.h" // RFC 2104: // HMAC(key, text) based on a hash H (say, sha256) is: // ipad = [0x36 x INSIZE] // opad = [0x5c x INSIZE] // HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text)) // // H(key XOR opad) and H(key XOR ipad) can be precomputed // if we often need HMAC hmac with the same key. // // text is often given in disjoint pieces. #if !ENABLE_FEATURE_USE_CNG_API void FAST_FUNC hmac_begin(hmac_ctx_t *ctx, const uint8_t *key, unsigned key_size, md5sha_begin_func *begin) { #if HMAC_ONLY_SHA256 #define begin sha256_begin #endif uint8_t key_xor_ipad[SHA2_INSIZE]; uint8_t key_xor_opad[SHA2_INSIZE]; unsigned i; // "The authentication key can be of any length up to INSIZE, the // block length of the hash function. Applications that use keys longer // than INSIZE bytes will first hash the key using H and then use the // resultant OUTSIZE byte string as the actual key to HMAC." if (key_size > SHA2_INSIZE) { uint8_t tempkey[SHA1_OUTSIZE < SHA256_OUTSIZE ? SHA256_OUTSIZE : SHA1_OUTSIZE]; /* use ctx->hashed_key_xor_ipad as scratch ctx */ begin(&ctx->hashed_key_xor_ipad); md5sha_hash(&ctx->hashed_key_xor_ipad, key, key_size); key_size = sha_end(&ctx->hashed_key_xor_ipad, tempkey); key = tempkey; } for (i = 0; i < key_size; i++) { key_xor_ipad[i] = key[i] ^ 0x36; key_xor_opad[i] = key[i] ^ 0x5c; } for (; i < SHA2_INSIZE; i++) { key_xor_ipad[i] = 0x36; key_xor_opad[i] = 0x5c; } begin(&ctx->hashed_key_xor_ipad); begin(&ctx->hashed_key_xor_opad); md5sha_hash(&ctx->hashed_key_xor_ipad, key_xor_ipad, SHA2_INSIZE); md5sha_hash(&ctx->hashed_key_xor_opad, key_xor_opad, SHA2_INSIZE); } #undef begin unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out) { unsigned len = sha_end(&ctx->hashed_key_xor_ipad, out); /* out = H((key XOR opad) + out) */ md5sha_hash(&ctx->hashed_key_xor_opad, out, len); return sha_end(&ctx->hashed_key_xor_opad, out); } unsigned FAST_FUNC hmac_block(const uint8_t *key, unsigned key_size, md5sha_begin_func *begin, const void *in, unsigned sz, uint8_t *out) { hmac_ctx_t ctx; hmac_begin(&ctx, key, key_size, begin); hmac_hash(&ctx, in, sz); return hmac_end(&ctx, out); } /* TLS helpers */ void FAST_FUNC hmac_hash_v( hmac_ctx_t *ctx, va_list va) { uint8_t *in; /* ctx->hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */ /* ctx->hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */ /* calculate out = H((key XOR ipad) + text) */ while ((in = va_arg(va, uint8_t*)) != NULL) { unsigned size = va_arg(va, unsigned); md5sha_hash(&ctx->hashed_key_xor_ipad, in, size); } } #else void _hmac_begin(hmac_ctx_t *ctx, uint8_t *key, unsigned key_size, BCRYPT_ALG_HANDLE alg_handle) { DWORD hash_object_length = 0; ULONG _unused; NTSTATUS status; status = BCryptGetProperty(alg_handle, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hash_object_length, sizeof(DWORD), &_unused, 0); mingw_die_if_error(status, "BCryptGetProperty"); status = BCryptGetProperty(alg_handle, BCRYPT_HASH_LENGTH, (PUCHAR)&ctx->output_size, sizeof(DWORD), &_unused, 0); mingw_die_if_error(status, "BCryptGetProperty"); ctx->hash_obj = xmalloc(hash_object_length); status = BCryptCreateHash(alg_handle, &ctx->handle, ctx->hash_obj, hash_object_length, key, key_size, BCRYPT_HASH_REUSABLE_FLAG); mingw_die_if_error(status, "BCryptCreateHash"); } unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out) { NTSTATUS status; status = BCryptFinishHash(ctx->handle, out, ctx->output_size, 0); mingw_die_if_error(status, "BCryptFinishHash"); return ctx->output_size; } void FAST_FUNC hmac_hash_v(hmac_ctx_t *ctx, va_list va) { uint8_t *in; while ((in = va_arg(va, uint8_t*)) != NULL) { unsigned size = va_arg(va, unsigned); BCryptHashData(ctx->handle, in, size, 0); } } void hmac_uninit(hmac_ctx_t *ctx) { BCryptDestroyHash(ctx->handle); free(ctx->hash_obj); } #endif /* Using HMAC state, make a copy of it (IOW: without affecting this state!) * hash in the list of (ptr,size) blocks, and finish the HMAC to out[] buffer. * This function is useful for TLS PRF. */ unsigned hmac_peek_hash(hmac_ctx_t *ctx, uint8_t *out, ...) { hmac_ctx_t tmpctx = *ctx; /* struct copy */ va_list va; va_start(va, out); hmac_hash_v(&tmpctx, va); va_end(va); return hmac_end(&tmpctx, out); }