Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

port: add sha256 API #664

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

port: add sha256 API #664

wants to merge 6 commits into from

Conversation

szczys
Copy link
Contributor

@szczys szczys commented Oct 19, 2024

Add an API for generating a sha256 hash that can be used to validate an OTA component.

Resolves https://github.com/golioth/firmware-issue-tracker/issues/717

@szczys szczys force-pushed the szczys/sha256-api branch 2 times, most recently from fcdf4db to cae526c Compare October 19, 2024 22:09
Copy link

github-actions bot commented Oct 19, 2024

Visit the preview URL for this PR (updated for commit 9c4dd63):

https://golioth-firmware-sdk-doxygen-dev--pr664-szczys-sha256-z40limll.web.app

(expires Fri, 22 Nov 2024 20:47:49 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: a9993e61697a3983f3479e468bcb0b616f9a0578

@szczys szczys force-pushed the szczys/sha256-api branch 3 times, most recently from 4a25ae3 to 5417aff Compare October 19, 2024 22:34
@szczys
Copy link
Contributor Author

szczys commented Oct 19, 2024

clang-format seems to want to indent everything after after #define <whatever> line until the next in golioth_sys.h. I think this failure is incorrect. This does not fail locally for me. I have clang-format 18.1.5 installed via pip so I tried updating the workflow to v18 but the failure persists.

@@ -121,6 +121,70 @@ void golioth_sys_thread_destroy(golioth_sys_thread_t thread);
#define golioth_sys_rand() rand()
#endif

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When #666 merges I think we should guard the following function declarations so they are only available when CONFIG_GOLIOTH_OTA is selected.

The same goes for the definitions in each platform file.

return 0;
}

size_t hex2bin(const char *hex, size_t hexlen, uint8_t *buf, size_t buflen)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really unfortunate that the manifest gives us a text string instead of a binary string for the SHA. In a manifest v2, I'd like to see this be binary, to reduce the payload size as well as the amount of computation that needs to be done on device.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this some more, I think it would make sense to convert the hash to binary as we download it, and store it as binary in the manifest struct. A user of the SDK would have no reason to expect a hash in ASCII format, it's just an artifact of the cloud API, and we shouldn't let that implementation detail dictate the Firmware API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I've added a commit to store the hash as a binary array so it's ready for comparison.

port/utils/hex.h Outdated
*
* @return Zero on success or (negative) error code otherwise.
*/
int char2hex(char c, uint8_t *x);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need this function to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is an error. Thanks for catching.

port/utils/hex.c Outdated
}
else
{
return GOLIOTH_ERR_INVALID_FORMAT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a positive value, which will cause the check on lines 47, 59, and 65 to fail. We should just leave this as -EINVAL, there's no reason to use golioth_status.h here, errno.h is part of the standard library and should be available on all platforms.

Comment on lines 294 to 297
struct golioth_hash
{
mbedtls_sha256_context sha256_ctx;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wrap this in another struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I thought I would need more than just the context and didn't update this when that wasn't the case. I've changed to directly assign the struct point to the golioth typedef.

/// Call on an existing context to begin generating a new sha256 hash.
///
/// @param sha_ctx A sha256 context handle
void golioth_sys_sha256_init(golioth_sys_sha256_t sha_ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need both create()and init(). We don't need to provide a super-flexible, general API. We just need to provide hooks into the functionality that our SDK needs, ideally while minimizing the amount of work that a user of the SDK would need to do in order to port the SDK to another platform.

/// Frees memory allocated to a sha256 context.
///
/// @param sha_ctx A sha256 context handle
void golioth_sys_sha256_free(golioth_sys_sha256_t sha_ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIs should match semantically:

  • *_create() should be paired with *_destroy()
  • *_alloc() should be paired with *_free()

/// @return GOLIOTH_OK On success
/// @return GOLIOTH_ERR_FAIL On failure
enum golioth_status golioth_sys_sha256_update(golioth_sys_sha256_t sha_ctx,
uint8_t *input,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make input const.

mbedtls_sha256_context sha256_ctx;
};

golioth_sys_sha256_t golioth_sys_sha256_create(void)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm back and forth on whether to use a typedef here, a straight void *, or a struct golioth_hash * with the incomplete definition in the header and the concrete definition in the source file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also went back and forth when considering your feedback. I decided to keep with the typedef. It uses the same memory as a void* but does a better job off indicating intent.

Add an API to manage sha256. This hash calculation is needed to verify OTA
component integrity based on the hash supplied by the server in the OTA
manifest.

Signed-off-by: Mike Szczys <[email protected]>
Add two functions of hex.c from the Zephyr tree for use in converting OTA
hex string sha256 hash to a binary array. Expose the hex2bin() function
needed to compare against locally-calculated sha256 of downloaded assets.

Signed-off-by: Mike Szczys <[email protected]>
Copy link

Code Coverage

Code Coverage

Package Line Rate Branch Rate Health
include.golioth 75% 50%
port.linux 44% 21%
port.utils 58% 46%
port.zephyr 46% 18%
src 69% 32%
Summary 66% (2518 / 3814) 31% (1085 / 3507)

Copy link

codecov bot commented Nov 15, 2024

Codecov Report

Attention: Patch coverage is 11.49425% with 77 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
port/linux/golioth_sys_linux.c 0.00% 30 Missing ⚠️
port/zephyr/golioth_sys_zephyr.c 0.00% 30 Missing ⚠️
port/utils/hex.c 34.61% 11 Missing and 6 partials ⚠️
Files with missing lines Coverage Δ
src/ota.c 57.60% <100.00%> (ø)
port/utils/hex.c 34.61% <34.61%> (ø)
port/linux/golioth_sys_linux.c 45.81% <0.00%> (-11.91%) ⬇️
port/zephyr/golioth_sys_zephyr.c 53.47% <0.00%> (-14.08%) ⬇️

... and 3 files with indirect coverage changes

Component hash is received from the server as a hex string. Convert it to a
binary array when decoding the manifest so that it's ready to compare to a
locally generated hash.

Signed-off-by: Mike Szczys <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants