diff --git a/src/basic/hmac.c b/src/basic/hmac.c new file mode 100644 index 00000000000..1e4e380aaa9 --- /dev/null +++ b/src/basic/hmac.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "hmac.h" +#include "sha256.h" + +#define HMAC_BLOCK_SIZE 64 +#define INNER_PADDING_BYTE 0x36 +#define OUTER_PADDING_BYTE 0x5c + +void hmac_sha256(const void *key, + size_t key_size, + const void *input, + size_t input_size, + uint8_t res[static SHA256_DIGEST_SIZE]) { + + uint8_t inner_padding[HMAC_BLOCK_SIZE] = { }; + uint8_t outer_padding[HMAC_BLOCK_SIZE] = { }; + uint8_t replacement_key[SHA256_DIGEST_SIZE]; + struct sha256_ctx hash; + + assert(key); + assert(key_size > 0); + assert(res); + + /* Implement algorithm as described by FIPS 198. */ + + /* The key needs to be block size length or less, hash it if it's longer. */ + if (key_size > HMAC_BLOCK_SIZE) { + sha256_init_ctx(&hash); + sha256_process_bytes(key, key_size, &hash); + sha256_finish_ctx(&hash, replacement_key); + key = replacement_key; + key_size = SHA256_DIGEST_SIZE; + } + + /* First, copy the key into the padding arrays. If it's shorter than + * the block size, the arrays are already initialized to 0. */ + memcpy(inner_padding, key, key_size); + memcpy(outer_padding, key, key_size); + + /* Then, XOR the provided key and any padding leftovers with the fixed + * padding bytes as defined in FIPS 198. */ + for (size_t i = 0; i < HMAC_BLOCK_SIZE; i++) { + inner_padding[i] ^= INNER_PADDING_BYTE; + outer_padding[i] ^= OUTER_PADDING_BYTE; + } + + /* First pass: hash the inner padding array and the input. */ + sha256_init_ctx(&hash); + sha256_process_bytes(inner_padding, HMAC_BLOCK_SIZE, &hash); + sha256_process_bytes(input, input_size, &hash); + sha256_finish_ctx(&hash, res); + + /* Second pass: hash the outer padding array and the result of the first pass. */ + sha256_init_ctx(&hash); + sha256_process_bytes(outer_padding, HMAC_BLOCK_SIZE, &hash); + sha256_process_bytes(res, SHA256_DIGEST_SIZE, &hash); + sha256_finish_ctx(&hash, res); +} diff --git a/src/basic/hmac.h b/src/basic/hmac.h new file mode 100644 index 00000000000..12b594c09b2 --- /dev/null +++ b/src/basic/hmac.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#define SHA256_DIGEST_SIZE 32 + +/* Unoptimized implementation based on FIPS 198. 'res' has to be allocated by + * the caller. Prefer external OpenSSL functions, and use this only when + * linking to OpenSSL is not desireable (eg: libsystemd.so). */ +void hmac_sha256(const void *key, size_t key_size, const void *input, size_t input_size, uint8_t res[static SHA256_DIGEST_SIZE]); diff --git a/src/basic/meson.build b/src/basic/meson.build index adb7b666c6f..a4a74698569 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -72,6 +72,8 @@ basic_sources = files(''' hashmap.h hexdecoct.c hexdecoct.h + hmac.c + hmac.h hostname-util.c hostname-util.h in-addr-util.c diff --git a/src/test/meson.build b/src/test/meson.build index fea7f107fd5..c24641fc7e8 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -629,6 +629,8 @@ tests += [ [['src/test/test-nscd-flush.c'], [], [], [], 'ENABLE_NSCD', 'manual'], + + [['src/test/test-hmac.c']], ] ############################################################ diff --git a/src/test/test-hmac.c b/src/test/test-hmac.c new file mode 100644 index 00000000000..5c73eca110b --- /dev/null +++ b/src/test/test-hmac.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "hmac.h" +#include "string-util.h" +#include "tests.h" + +static void hmac_sha256_by_string(const char *key, const char *value, uint8_t res[static SHA256_DIGEST_SIZE]) { + hmac_sha256(key, strlen(key), value, strlen(value), res); +} + +static void test_hmac(void) { + uint8_t result[SHA256_DIGEST_SIZE]; + char *hex_result = NULL; + + log_info("/* %s */", __func__); + + /* Results compared with output of 'echo -n "" | openssl dgst -sha256 -hmac ""' */ + + hmac_sha256_by_string("waldo", + "", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldohaldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldo haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldo 4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69 haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "039f3df430b19753ffb493e5b90708f75c5210b63c6bcbef3374eb3f0a3f97f7")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", + "baldo haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "c4cfaf48077cbb0bbd177a09e59ec4c248f4ca771503410f5b54b98d88d2f47b")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", + "supercalifragilisticexpialidocious", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "2c059e7a63c4c3b23f47966a65fd2f8a2f5d7161e2e90d78ff68866b5c375cb7")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda", + "supercalifragilisticexpialidocious", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "1dd1d1d45b9d9f9673dc9983c968c46ff3168e03cfeb4156a219eba1af4cff5f")); + hex_result = mfree(hex_result); +} + +int main(int argc, char **argv) { + test_setup_logging(LOG_INFO); + + test_hmac(); + + return 0; +}