From e7c277b86b685c61f4aed83af11c274f945bf28a Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 30 Sep 2024 17:59:56 +0200 Subject: [PATCH 1/7] feat: added SHA-256 interface and MbedTLS implementation Ticket: MEN-7483 Changelog: None Signed-off-by: Lars Erik Wik --- CMakeLists.txt | 7 ++ include/mender-sha.h | 76 +++++++++++++++++++ platform/sha/generic/mbedtls/src/mender-sha.c | 63 +++++++++++++++ platform/sha/generic/weak/src/mender-sha.c | 38 ++++++++++ zephyr/CMakeLists.txt | 1 + zephyr/Kconfig | 18 +++++ 6 files changed, 203 insertions(+) create mode 100644 include/mender-sha.h create mode 100644 platform/sha/generic/mbedtls/src/mender-sha.c create mode 100644 platform/sha/generic/weak/src/mender-sha.c diff --git a/CMakeLists.txt b/CMakeLists.txt index fcd9523..3a7962b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,12 @@ if (NOT CONFIG_MENDER_PLATFORM_TLS_TYPE) else() message(STATUS "Using custom '${CONFIG_MENDER_PLATFORM_TLS_TYPE}' platform TLS implementation") endif() +if (NOT CONFIG_MENDER_PLATFORM_SHA_TYPE) + message(STATUS "Using default 'generic/weak' platform SHA implementation") + set(CONFIG_MENDER_PLATFORM_SHA_TYPE "generic/weak") +else() + message(STATUS "Using custom '${CONFIG_MENDER_PLATFORM_SHA_TYPE}' platform SHA implementation") +endif() option(MENDER_MBEDTLS_ERROR_STR "Enable mbedtls error strings" OFF) @@ -168,6 +174,7 @@ file(GLOB SOURCES_TEMP "${CMAKE_CURRENT_LIST_DIR}/platform/scheduler/${CONFIG_MENDER_PLATFORM_SCHEDULER_TYPE}/src/mender-scheduler.c" "${CMAKE_CURRENT_LIST_DIR}/platform/storage/${CONFIG_MENDER_PLATFORM_STORAGE_TYPE}/src/mender-storage.c" "${CMAKE_CURRENT_LIST_DIR}/platform/tls/${CONFIG_MENDER_PLATFORM_TLS_TYPE}/src/mender-tls.c" + "${CMAKE_CURRENT_LIST_DIR}/platform/sha/${CONFIG_MENDER_PLATFORM_SHA_TYPE}/src/mender-sha.c" ) if (CONFIG_MENDER_CLIENT_INVENTORY) list(APPEND SOURCES_TEMP diff --git a/include/mender-sha.h b/include/mender-sha.h new file mode 100644 index 0000000..36e668c --- /dev/null +++ b/include/mender-sha.h @@ -0,0 +1,76 @@ +/** + * @file mender-sha.h + * @brief Mender SHA interface + * + * Copyright Northern.tech AS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MENDER_SHA_H__ +#define __MENDER_SHA_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "mender-utils.h" /* mender_err_t */ + +/** + * @brief This type is just a pointer to whatever data structure is required by + * a specific platform implementation. This data structure will be + * allocated on the heap by mender_sha256_begin function and must be + * free'd in the mender_sha256_finish function. + */ +typedef void *mender_sha256_context_t; + +/** + * @brief Size of SHA-256 buffer in Bytes. + */ +#define MENDER_DIGEST_BUFFER_SIZE 32 + +/** + * @brief Initializes a SHA-256 context and starts a checksum calculation. + * @param context The SHA-256 context to be initialized. This must not be NULL. + * @note A call to mender_sha256_begin must be followed by a call to + * mender_sha256_finish in order to release resources. + * @return MENDER_OK on success, otherwise error code. + */ +mender_err_t mender_sha256_begin(mender_sha256_context_t *context); + +/** + * @brief Feeds an input buffer into an ongoing SHA-256 checksum calculation. + * @param context The SHA-256 context. This must have been initialized. + * @param input The buffer holding the data to be fed. + * @param length The length of the input data in Bytes. + * @return MENDER_OK on success, otherwise error code. + */ +mender_err_t mender_sha256_update(mender_sha256_context_t context, const unsigned char *input, size_t length); + +/** + * @brief Finishes the SHA-256 checksum calculation, writes the result to the + * output buffer and clears the SHA-256 context. + * @param context The SHA-256 context to be cleared. If NULL is passed, no + * operation is performed. + * @param output A writeable buffer of MENDER_DIGEST_BUFFER_SIZE Bytes for + * SHA-256 checksum. If NULL is passed, the function will only + * clear the SHA-256 context. + * @return MENDER_OK on success, otherwise error code. + */ +mender_err_t mender_sha256_finish(mender_sha256_context_t context, unsigned char *output); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENDER_SHA_H__ */ diff --git a/platform/sha/generic/mbedtls/src/mender-sha.c b/platform/sha/generic/mbedtls/src/mender-sha.c new file mode 100644 index 0000000..bda124f --- /dev/null +++ b/platform/sha/generic/mbedtls/src/mender-sha.c @@ -0,0 +1,63 @@ +/** + * @file mender-sha.c + * @brief Mender SHA interface for MbedTLS platform + * + * Copyright Northern.tech AS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mender-sha.h" +#include "mender-log.h" + +#include + +mender_err_t +mender_sha256_begin(mender_sha256_context_t *context) { + assert(NULL != context); + + mbedtls_sha256_context *ctx = malloc(sizeof(mbedtls_sha256_context)); + if (NULL == ctx) { + mender_log_error("Unable to allocate memory"); + return MENDER_FAIL; + } + + mbedtls_sha256_init(ctx); + mbedtls_sha256_starts(ctx, 0); /* Use SHA-256, not SHA-224 */ + + *context = ctx; + return MENDER_OK; +} + +mender_err_t +mender_sha256_update(mender_sha256_context_t context, const unsigned char *input, size_t length) { + assert(NULL != context); + + mbedtls_sha256_context *ctx = context; + mbedtls_sha256_update(ctx, input, length); + + return MENDER_OK; +} + +mender_err_t +mender_sha256_finish(mender_sha256_context_t context, unsigned char *output) { + mbedtls_sha256_context *ctx = context; + if (NULL != ctx) { + if (NULL != output) { + mbedtls_sha256_finish(ctx, output); + } + mbedtls_sha256_free(ctx); + free(ctx); + } + return MENDER_OK; +} diff --git a/platform/sha/generic/weak/src/mender-sha.c b/platform/sha/generic/weak/src/mender-sha.c new file mode 100644 index 0000000..d1f8d3d --- /dev/null +++ b/platform/sha/generic/weak/src/mender-sha.c @@ -0,0 +1,38 @@ +/** + * @file mender-sha.c + * @brief Mender SHA interface for weak platform + * + * Copyright Northern.tech AS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mender-sha.h" + +__attribute__((weak)) mender_err_t +mender_sha256_begin(MENDER_ARG_UNUSED mender_sha256_context_t *ctx) { + /* Nothing to do */ + return MENDER_NOT_IMPLEMENTED; +} + +__attribute__((weak)) mender_err_t +mender_sha256_update(MENDER_ARG_UNUSED mender_sha256_context_t ctx, MENDER_ARG_UNUSED const unsigned char *input, MENDER_ARG_UNUSED size_t length) { + /* Nothing to do */ + return MENDER_NOT_IMPLEMENTED; +} + +__attribute__((weak)) mender_err_t +mender_sha256_finish(MENDER_ARG_UNUSED mender_sha256_context_t ctx, MENDER_ARG_UNUSED unsigned char *output) { + /* Nothing to do */ + return MENDER_NOT_IMPLEMENTED; +} diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 97df53a..1a55734 100755 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -29,6 +29,7 @@ if(CONFIG_MENDER_MCU_CLIENT) "${CMAKE_CURRENT_LIST_DIR}/../platform/scheduler/${CONFIG_MENDER_PLATFORM_SCHEDULER_TYPE}/src/mender-scheduler.c" "${CMAKE_CURRENT_LIST_DIR}/../platform/storage/${CONFIG_MENDER_PLATFORM_STORAGE_TYPE}/src/mender-storage.c" "${CMAKE_CURRENT_LIST_DIR}/../platform/tls/${CONFIG_MENDER_PLATFORM_TLS_TYPE}/src/mender-tls.c" + "${CMAKE_CURRENT_LIST_DIR}/../platform/sha/${CONFIG_MENDER_PLATFORM_TLS_TYPE}/src/mender-sha.c" ) zephyr_library_sources_ifdef(CONFIG_MENDER_CLIENT_INVENTORY "${CMAKE_CURRENT_LIST_DIR}/../core/src/mender-inventory.c" diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 7f21cd2..29747ae 100755 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -212,6 +212,24 @@ if MENDER_MCU_CLIENT default "generic/mbedtls" if MENDER_PLATFORM_TLS_TYPE_MBEDTLS default "generic/weak" if MENDER_PLATFORM_TLS_TYPE_WEAK + choice MENDER_PLATFORM_SHA_TYPE + prompt "Mender platform SHA implementation type" + default MENDER_PLATFORM_SHA_TYPE_MBEDTLS + help + Specify platform SHA implementation type, select 'weak' to use you own implementation. + + config MENDER_PLATFORM_SHA_TYPE_MBEDTLS + bool "mbedtls" + select MBEDTLS + config MENDER_PLATFORM_SHA_TYPE_WEAK + bool "weak" + endchoice + + config MENDER_PLATFORM_SHA_TYPE + string + default "generic/mbedtls" if MENDER_PLATFORM_SHA_TYPE_MBEDTLS + default "generic/weak" if MENDER_PLATFORM_SHA_TYPE_WEAK + endmenu if MENDER_PLATFORM_NET_TYPE_DEFAULT From f38d6d311938b974cc5779003eb6ab1d2a570707 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Oct 2024 09:30:40 +0200 Subject: [PATCH 2/7] chore: fixed typos in comments Ticket: None Changelog: None Signed-off-by: Lars Erik Wik --- core/src/mender-artifact.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/mender-artifact.c b/core/src/mender-artifact.c index ceb6c0f..a79819b 100644 --- a/core/src/mender-artifact.c +++ b/core/src/mender-artifact.c @@ -532,7 +532,7 @@ mender_artifact_read_manifest(mender_artifact_ctx_t *ctx) { } *next = '\0'; - ///* Process line */ + /* Process line */ char *separator = strstr(line, " "); if (NULL == separator) { mender_log_error("Invalid manifest file"); @@ -547,7 +547,7 @@ mender_artifact_read_manifest(mender_artifact_ctx_t *ctx) { } *separator = '\0'; - /* Allocate memory and check if allocation was succesfull */ + /* Allocate memory and check if allocation was successful */ checksum->key = strdup(line); checksum->value = strdup(separator + 2); if ((NULL == checksum->key) || (NULL == checksum->value)) { From 8bfcf657f5442478530760ba47d4d192a8698c66 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Oct 2024 09:42:56 +0200 Subject: [PATCH 3/7] docs: fixed misleading doc string for mender_artifact_get_ctx Ticket: None Changelog: None Signed-off-by: Lars Erik Wik --- include/mender-artifact.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mender-artifact.h b/include/mender-artifact.h index 9cb02a8..4b8fe58 100644 --- a/include/mender-artifact.h +++ b/include/mender-artifact.h @@ -94,7 +94,7 @@ mender_err_t mender_artifact_get_device_type(mender_artifact_ctx_t *ctx, const c mender_artifact_ctx_t *mender_artifact_create_ctx(void); /** - * @brief Function used to create a new artifact context + * @brief Function used to get the artifact context * @return MENDER_OK if the function succeeds, error code otherwise */ mender_err_t mender_artifact_get_ctx(mender_artifact_ctx_t **ctx); From 2005bbf4736b565cc881f2aa38481daba4de6894 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 2 Oct 2024 10:43:57 +0200 Subject: [PATCH 4/7] feat: added integrity checks for artifact version file The client now performs integrity checks on the version file in the artifact by comparing its computed checksum to the one in the artifact manifest. Ticket: MEN-7483 Changelog: Commit Signed-off-by: Lars Erik Wik --- core/src/mender-artifact.c | 135 +++++++++++++++++++++++++++++++++---- core/src/mender-client.c | 32 ++++----- include/mender-artifact.h | 29 ++++++-- 3 files changed, 162 insertions(+), 34 deletions(-) diff --git a/core/src/mender-artifact.c b/core/src/mender-artifact.c index a79819b..24a14bf 100644 --- a/core/src/mender-artifact.c +++ b/core/src/mender-artifact.c @@ -150,6 +150,89 @@ static size_t mender_artifact_round_up(size_t length, size_t incr); */ static mender_artifact_ctx_t *mender_artifact_ctx = NULL; +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT +/** + * @brief Get checksum entry for a file in the context + * @param ctx The mender artifact context + * @param filename The name of the file in the artifact + * @return The checksum entry or NULL on error. + * @note Since other files may be parsed before the manifest file, we need to + * create these entries in a lazy fashion. + */ +static mender_artifact_checksum_t * +mender_artifact_checksum_get_or_create(mender_artifact_ctx_t *ctx, const char *filename) { + assert(NULL != ctx); + assert(NULL != filename); + + /* See if we already have an entry for this file */ + mender_artifact_checksum_t *checksum; + for (checksum = ctx->artifact_info.checksums; NULL != checksum; checksum = checksum->next) { + if (StringEqual(checksum->filename, filename)) { + break; + } + } + + if (NULL == checksum) { + /* Create new if entry not found */ + checksum = (mender_artifact_checksum_t *)calloc(1, sizeof(mender_artifact_checksum_t)); + if (NULL == checksum) { + mender_log_error("Unable to allocate memory"); + return NULL; + } + checksum->filename = strdup(filename); + if (NULL == checksum->filename) { + mender_log_error("Unable to allocate memory"); + free(checksum); + return NULL; + } + checksum->next = ctx->artifact_info.checksums; + ctx->artifact_info.checksums = checksum; + + /* Start SHA-256 checksum computation (if not already started) */ + if (MENDER_OK != mender_sha256_begin(&(checksum->context))) { + mender_log_error("Failed to start checksum"); + return NULL; + } + } + + return checksum; +} +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + +mender_err_t +mender_artifact_check_integrity(mender_artifact_ctx_t *ctx) { + assert(NULL != ctx); + +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT + for (mender_artifact_checksum_t *checksum = ctx->artifact_info.checksums; NULL != checksum; checksum = checksum->next) { + unsigned char computed[MENDER_DIGEST_BUFFER_SIZE]; + + if (!StringEqual(checksum->filename, "version")) { + /* Whenever we introduce a new file to the artifact manifest, we + * need to skip integrity checks for that file until it's + * implemented. Otherwise, the client will get stuck trying to + * install the artifact which may hold the new implementation. */ + mender_log_warning("Skipping integrity check for artifact file '%s': Not implemented", checksum->filename); + continue; + } + mender_log_debug("Checking integrity for artifact file '%s'", checksum->filename); + + if (MENDER_OK != mender_sha256_finish(checksum->context, computed)) { + mender_log_error("Failed to finish checksum for file '%s'", checksum->filename); + checksum->context = NULL; + return MENDER_FAIL; + } + checksum->context = NULL; + + if (0 != memcmp(checksum->manifest, computed, MENDER_DIGEST_BUFFER_SIZE)) { + mender_log_error("Computed checksum for file '%s' does not match manifest", checksum->filename); + return MENDER_FAIL; + } + } +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + return MENDER_OK; +} + mender_artifact_ctx_t * mender_artifact_create_ctx(void) { @@ -325,7 +408,10 @@ mender_artifact_release_ctx(mender_artifact_ctx_t *ctx) { #ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT mender_utils_free_linked_list(ctx->artifact_info.provides); mender_utils_free_linked_list(ctx->artifact_info.depends); - mender_utils_free_linked_list(ctx->artifact_info.checksums); + for (mender_artifact_checksum_t *checksum = ctx->artifact_info.checksums; NULL != checksum; checksum = checksum->next) { + free(checksum->filename); + mender_sha256_finish(checksum->context, NULL); + } #endif free(ctx); } @@ -440,6 +526,21 @@ mender_artifact_read_version(mender_artifact_ctx_t *ctx) { return MENDER_OK; } +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT + /* Get checksum entry (create one if needed) */ + mender_artifact_checksum_t *checksum; + if (NULL == (checksum = mender_artifact_checksum_get_or_create(ctx, "version"))) { + /* Error already logged */ + return MENDER_FAIL; + } + + /* Update SHA-256 checksum */ + if (MENDER_OK != mender_sha256_update(checksum->context, ctx->input.data, ctx->file.size)) { + mender_log_error("Failed to update update checksum"); + return MENDER_FAIL; + } +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + /* Check version file */ if (NULL == (object = cJSON_ParseWithLength(ctx->input.data, ctx->file.size))) { mender_log_error("Unable to allocate memory"); @@ -538,25 +639,31 @@ mender_artifact_read_manifest(mender_artifact_ctx_t *ctx) { mender_log_error("Invalid manifest file"); return MENDER_FAIL; } + *separator = '\0'; - /* Add checksum to the list */ - mender_key_value_list_t *checksum = (mender_key_value_list_t *)calloc(1, sizeof(mender_key_value_list_t)); - if (NULL == checksum) { - mender_log_error("Unable to allocate memory"); + const char *checksum_str = line; + const char *filename = separator + 2; + + /* Make sure digest is of expected length (two hex per byte) */ + if ((MENDER_DIGEST_BUFFER_SIZE * 2) != strlen(checksum_str)) { + mender_log_error("Bad checksum '%s' in manifest for file '%s'", checksum_str, filename); return MENDER_FAIL; } - *separator = '\0'; - /* Allocate memory and check if allocation was successful */ - checksum->key = strdup(line); - checksum->value = strdup(separator + 2); - if ((NULL == checksum->key) || (NULL == checksum->value)) { - mender_log_error("Unable to allocate memory"); - mender_utils_free_linked_list(checksum); + /* Get checksum entry for the file (creates one if not found) */ + mender_artifact_checksum_t *checksum; + if (NULL == (checksum = mender_artifact_checksum_get_or_create(ctx, filename))) { + /* Error already logged */ return MENDER_FAIL; } - checksum->next = ctx->artifact_info.checksums; - ctx->artifact_info.checksums = checksum; + + /* Populate with manifest checksum */ + for (int i = 0; i < MENDER_DIGEST_BUFFER_SIZE; i++) { + if (1 != sscanf(checksum_str + (2 * i), "%02hhx", checksum->manifest + i)) { + mender_log_error("Bad checksum '%s' in manifest for file '%s'", checksum_str, filename); + return MENDER_FAIL; + } + } ///* Move to the next line */ line = next + 1; diff --git a/core/src/mender-client.c b/core/src/mender-client.c index 717bdc8..1c707bb 100644 --- a/core/src/mender-client.c +++ b/core/src/mender-client.c @@ -990,24 +990,26 @@ mender_client_update_work_function(void) { /* Get artifact context if artifact download succeeded */ if ((NULL != mender_update_module) && (MENDER_OK == (ret = mender_artifact_get_ctx(&mender_artifact_ctx)))) { #ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT - if (MENDER_OK == (ret = mender_check_artifact_requirements(mender_artifact_ctx, deployment))) { + if (MENDER_OK == (ret = mender_artifact_check_integrity(mender_artifact_ctx))) { + if (MENDER_OK == (ret = mender_check_artifact_requirements(mender_artifact_ctx, deployment))) { #ifdef CONFIG_MENDER_PROVIDES_DEPENDS - /* Add the new provides to the deployment data (we need the artifact context) */ - char *new_provides = NULL; - const char *artifact_name = NULL; - if (MENDER_OK == (ret = mender_prepare_new_provides(mender_artifact_ctx, &new_provides, &artifact_name))) { - if (MENDER_OK != (ret = mender_deployment_data_set_provides(mender_client_deployment_data, new_provides))) { - mender_log_error("Failed to set deployment data provides"); + /* Add the new provides to the deployment data (we need the artifact context) */ + char *new_provides = NULL; + const char *artifact_name = NULL; + if (MENDER_OK == (ret = mender_prepare_new_provides(mender_artifact_ctx, &new_provides, &artifact_name))) { + if (MENDER_OK != (ret = mender_deployment_data_set_provides(mender_client_deployment_data, new_provides))) { + mender_log_error("Failed to set deployment data provides"); + } + /* Replace artifact_name with the one from provides */ + else if (MENDER_OK != (ret = mender_deployment_data_set_artifact_name(mender_client_deployment_data, artifact_name))) { + mender_log_error("Failed to set deployment data artifact name"); + } + free(new_provides); + } else { + mender_log_error("Unable to prepare new provides"); } - /* Replace artifact_name with the one from provides */ - else if (MENDER_OK != (ret = mender_deployment_data_set_artifact_name(mender_client_deployment_data, artifact_name))) { - mender_log_error("Failed to set deployment data artifact name"); - } - free(new_provides); - } else { - mender_log_error("Unable to prepare new provides"); - } #endif /* CONFIG_MENDER_PROVIDES_DEPENDS */ + } } #endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ } else { diff --git a/include/mender-artifact.h b/include/mender-artifact.h index 4b8fe58..72d486e 100644 --- a/include/mender-artifact.h +++ b/include/mender-artifact.h @@ -25,6 +25,7 @@ extern "C" { #endif /* __cplusplus */ #include "mender-utils.h" +#include "mender-sha.h" /** * @brief Artifact state machine used to process input data stream @@ -50,6 +51,16 @@ typedef struct { cJSON *meta_data; /**< Meta-data from the header tarball, NULL if no meta-data */ } mender_artifact_payload_t; +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT +typedef struct mender_artifact_checksum_t mender_artifact_checksum_t; +struct mender_artifact_checksum_t { + char *filename; + unsigned char manifest[MENDER_DIGEST_BUFFER_SIZE]; + mender_sha256_context_t context; + mender_artifact_checksum_t *next; +}; +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + /** * @brief Artifact context */ @@ -65,11 +76,11 @@ typedef struct { } payloads; /**< Payloads of the artifact */ #ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT struct { - mender_key_value_list_t *checksums; /**< Contains checksums of the artifact */ - mender_key_value_list_t *provides; /**< Provides of the artifact */ - mender_key_value_list_t *depends; /**< Depends of the artifact */ - } artifact_info; /**< Global information about the artifact */ -#endif + mender_artifact_checksum_t *checksums; /**< Contains checksums of the artifact */ + mender_key_value_list_t *provides; /**< Provides of the artifact */ + mender_key_value_list_t *depends; /**< Depends of the artifact */ + } artifact_info; /**< Global information about the artifact */ +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ struct { char *name; /**< Name of the file currently parsed */ size_t size; /**< Size of the file currently parsed (bytes) */ @@ -112,6 +123,14 @@ mender_err_t mender_artifact_process_data(mender_artifact_ctx_t *ctx, size_t input_length, mender_err_t (*callback)(char *, cJSON *, char *, size_t, void *, size_t, size_t)); +/** + * @brief Do integrity checks by comparing the manifest checksums to the computed ones + * @param ctx Artifact context + * @return MENDER_OK if integrity is enforced, error code otherwise + * @note Call the after the processing of data from artifact stream is complete + */ +mender_err_t mender_artifact_check_integrity(mender_artifact_ctx_t *ctx); + /** * @brief Function used to release artifact context * @param ctx Artifact context From 2b348733476ec07041d505437938a5ae46541782 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 2 Oct 2024 15:39:51 +0200 Subject: [PATCH 5/7] feat: added integrity checks for artifact header.tar file The client now performs integrity checks on the header.tar file in the artifact by comparing its computed checksum to the one in the artifact manifest. Ticket: MEN-7483 Changelog: Commit Signed-off-by: Lars Erik Wik --- core/src/mender-artifact.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/core/src/mender-artifact.c b/core/src/mender-artifact.c index 24a14bf..68bfaab 100644 --- a/core/src/mender-artifact.c +++ b/core/src/mender-artifact.c @@ -129,6 +129,10 @@ static mender_err_t mender_artifact_read_data(mender_artifact_ctx_t *ctx, mender */ static mender_err_t mender_artifact_drop_file(mender_artifact_ctx_t *ctx); +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT +static mender_err_t mender_artifact_read_header(mender_artifact_ctx_t *ctx); +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + /** * @brief Shift data after parsing * @param ctx Artifact context @@ -207,7 +211,7 @@ mender_artifact_check_integrity(mender_artifact_ctx_t *ctx) { for (mender_artifact_checksum_t *checksum = ctx->artifact_info.checksums; NULL != checksum; checksum = checksum->next) { unsigned char computed[MENDER_DIGEST_BUFFER_SIZE]; - if (!StringEqual(checksum->filename, "version")) { + if (!StringEqual(checksum->filename, "version") && !StringEqual(checksum->filename, "header.tar")) { /* Whenever we introduce a new file to the artifact manifest, we * need to skip integrity checks for that file until it's * implemented. Otherwise, the client will get stuck trying to @@ -353,7 +357,10 @@ mender_artifact_process_data(mender_artifact_ctx_t *ctx, /* Drop data, file is not relevant */ ret = mender_artifact_drop_file(ctx); - +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT + } else if (StringEqual(ctx->file.name, "header.tar")) { + ret = mender_artifact_read_header(ctx); +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ } else { /* Nothing to do */ @@ -806,6 +813,33 @@ mender_artifact_read_header_info(mender_artifact_ctx_t *ctx) { return ret; } +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT +static mender_err_t +mender_artifact_read_header(mender_artifact_ctx_t *ctx) { + assert(NULL != ctx); + + /* Check if all data have been received */ + if ((NULL == ctx->input.data) || (ctx->input.length < mender_artifact_round_up(ctx->file.size, MENDER_ARTIFACT_STREAM_BLOCK_SIZE))) { + return MENDER_OK; + } + + /* Get checksum entry (create one if needed) */ + mender_artifact_checksum_t *checksum; + if (NULL == (checksum = mender_artifact_checksum_get_or_create(ctx, "header.tar"))) { + /* Error already logged */ + return MENDER_FAIL; + } + + /* Update SHA-256 checksum */ + if (MENDER_OK != mender_sha256_update(checksum->context, ctx->input.data, ctx->file.size)) { + mender_log_error("Failed to update update checksum"); + return MENDER_FAIL; + } + + return MENDER_DONE; +} +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + #ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT static mender_err_t mender_artifact_read_type_info(mender_artifact_ctx_t *ctx) { From 9130d79e90e1dfee927e738896d8df3f3230d61d Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 2 Oct 2024 15:44:40 +0200 Subject: [PATCH 6/7] chore: added logging of mismatching checksums Added logging of mismatching checksums during artifact integrity checks in debug mode. Ticket: None Changelog: None Signed-off-by: Lars Erik Wik --- core/src/mender-artifact.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/src/mender-artifact.c b/core/src/mender-artifact.c index 68bfaab..0cc68b0 100644 --- a/core/src/mender-artifact.c +++ b/core/src/mender-artifact.c @@ -230,6 +230,24 @@ mender_artifact_check_integrity(mender_artifact_ctx_t *ctx) { if (0 != memcmp(checksum->manifest, computed, MENDER_DIGEST_BUFFER_SIZE)) { mender_log_error("Computed checksum for file '%s' does not match manifest", checksum->filename); +#if CONFIG_MENDER_LOG_LEVEL >= MENDER_LOG_LEVEL_DBG + /* Log the mismatching checksums for debugging */ + char checksum_str[(MENDER_DIGEST_BUFFER_SIZE * 2) + 1]; + + for (int i = 0; i < MENDER_DIGEST_BUFFER_SIZE; i++) { + if (2 != snprintf(checksum_str + (i * 2), 3, "%02hhx", checksum->manifest[i])) { + break; + } + } + mender_log_debug("%s: '%s' (manifest)", checksum->filename, checksum_str); + + for (int i = 0; i < MENDER_DIGEST_BUFFER_SIZE; i++) { + if (2 != snprintf(checksum_str + (i * 2), 3, "%02hhx", computed[i])) { + break; + } + } + mender_log_debug("%s: '%s' (computed)", checksum->filename, checksum_str); +#endif /* CONFIG_MENDER_LOG_LEVEL >= MENDER_LOG_LEVEL_DBG */ return MENDER_FAIL; } } @@ -651,6 +669,9 @@ mender_artifact_read_manifest(mender_artifact_ctx_t *ctx) { const char *checksum_str = line; const char *filename = separator + 2; + /* Useful when debugging artifact integrity check failures */ + mender_log_debug("%s %s", checksum_str, filename); + /* Make sure digest is of expected length (two hex per byte) */ if ((MENDER_DIGEST_BUFFER_SIZE * 2) != strlen(checksum_str)) { mender_log_error("Bad checksum '%s' in manifest for file '%s'", checksum_str, filename); From 6c1a829360e074af9914ac2270d7777ccd48e53d Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Oct 2024 14:13:08 +0200 Subject: [PATCH 7/7] feat: added integrity checks for artifact data files The client now performs integrity checks on the data files in the artifact by comparing its computed checksum to the one in the artifact manifest. Ticket: MEN-7483 Changelog: Commit Signed-off-by: Lars Erik Wik --- core/src/mender-artifact.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/core/src/mender-artifact.c b/core/src/mender-artifact.c index 0cc68b0..0e0ea60 100644 --- a/core/src/mender-artifact.c +++ b/core/src/mender-artifact.c @@ -211,7 +211,8 @@ mender_artifact_check_integrity(mender_artifact_ctx_t *ctx) { for (mender_artifact_checksum_t *checksum = ctx->artifact_info.checksums; NULL != checksum; checksum = checksum->next) { unsigned char computed[MENDER_DIGEST_BUFFER_SIZE]; - if (!StringEqual(checksum->filename, "version") && !StringEqual(checksum->filename, "header.tar")) { + if (!StringEqual(checksum->filename, "version") && !StringEqual(checksum->filename, "header.tar") + && !mender_utils_strbeginwith(checksum->filename, "data/")) { /* Whenever we introduce a new file to the artifact manifest, we * need to skip integrity checks for that file until it's * implemented. Otherwise, the client will get stuck trying to @@ -1072,6 +1073,37 @@ mender_artifact_read_data(mender_artifact_ctx_t *ctx, mender_err_t (*callback)(c size_t length = ((ctx->file.size - ctx->file.index) > MENDER_ARTIFACT_STREAM_BLOCK_SIZE) ? MENDER_ARTIFACT_STREAM_BLOCK_SIZE : (ctx->file.size - ctx->file.index); +#ifdef CONFIG_MENDER_FULL_PARSE_ARTIFACT + mender_artifact_checksum_t *checksum; + { + /* The filename will be something like + * 'data/0000.tar/zephyr.signed.bin'. But the manifest will hold + * 'data/0000/zephyr.signed.bin'. Hence, we need to remove the + * '.tar' extension from the string. + */ + char filename[strlen(ctx->file.name) + 1]; + strcpy(filename, ctx->file.name); + + for (char *ch = strstr(filename, ".tar"); (NULL != ch) && (*ch != '\0'); ch++) { + /* Don't worry! The call to strlen() on a static string should + * be optimized out by the compiler */ + *ch = ch[strlen(".tar")]; + } + + /* Get checksum entry (create one if needed) */ + if (NULL == (checksum = mender_artifact_checksum_get_or_create(ctx, filename))) { + /* Error already logged */ + return MENDER_FAIL; + } + } + + /* Update SHA-256 checksum */ + if (MENDER_OK != mender_sha256_update(checksum->context, ctx->input.data, length)) { + mender_log_error("Failed to update update checksum"); + return MENDER_FAIL; + } +#endif /* CONFIG_MENDER_FULL_PARSE_ARTIFACT */ + /* Invoke callback */ if (MENDER_OK != (ret = callback(ctx->payloads.values[index].type,