Cut a release.

This commit is contained in:
Kyle Isom 2023-10-18 03:39:27 -07:00
parent ad07da5a39
commit 4a2c18751a
16 changed files with 593 additions and 543 deletions

View File

@ -1,7 +1,8 @@
HeaderFilterRegex: \./.+
Checks: >-
bugprone-*,
cppcoreguidelines-*,
google-*,
misc-*,
modernize-*,
performance-*,
@ -15,6 +16,7 @@ Checks: >-
-cppcoreguidelines-pro-type-vararg,
-google-readability-braces-around-statements,
-google-readability-function-size,
-google-readability-namespace-comments,
-misc-no-recursion,
-modernize-return-braced-init-list,
-modernize-use-nodiscard,

View File

@ -2,8 +2,8 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ClangTidy" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Misra" enabled="false" level="WARNING" enabled_by_default="false">
<scope name="ProjectSources" level="WARNING" enabled="false" />
<inspection_tool class="Misra" enabled="true" level="WARNING" enabled_by_default="false">
<scope name="ProjectSources" level="WARNING" enabled="true" />
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.22)
project(emsha
VERSION 1.0.3
VERSION 1.1.0
LANGUAGES CXX
DESCRIPTION "A compact HMAC-SHA-256 C++11 library.")
set(CMAKE_CXX_STANDARD 11)
@ -9,8 +9,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(EMSHA_NO_HEXSTRING OFF CACHE BOOL
"Don't include support for hex strings.")
set(EMSHA_NO_HEXLUT OFF CACHE BOOL
if (EMSHA_NO_HEXSTRING)
add_definitions(EMSHA_NO_HEXSTRING)
endif ()
set(SET_EMSHA_NO_HEXLUT OFF CACHE BOOL
"Don't use a LUT for hex strings (saves ~256B of memory).")
if (SET_EMSHA_NO_HEXLUT)
add_definitions("-DEMSHA_NO_HEXLUT")
endif ()
include(CTest)
enable_testing()

View File

@ -24,13 +24,13 @@ if (${DOXYGEN_FOUND})
set(DOXYGEN_GENERATE_LATEX YES)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/docs/mainpage.md")
set(DOXYGEN_EXCLUDE_PATTERNS "test_*" "*.cc" )
message(STATUS "Doxygen found, building docs.")
doxygen_add_docs(${PROJECT_NAME}_docs
${HEADER_FILES}
${SOURCE_FILES}
ALL
USE_STAMP_FILE)
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_docs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
${CMAKE_CURRENT_BINARY_DIR}/latex
DESTINATION share/doc/${PROJECT_NAME}/doxygen)

View File

@ -77,50 +77,50 @@ satisfy a common interface :cpp`Hash`{.interpreted-text role="class"}.
All functionality provided by this library is found under the `emsha`
namespace.
### `EMSHA_RESULT`
### `EMSHAResult`
The `EMSHA_RESULT` enum is used to convey the result of an operation.
The `EMSHAResult` enum is used to convey the result of an operation.
The possible values are:
// All operations have completed successfully so far.
EMSHA_ROK = 0,
EMSHAResult::OK = 0,
// A self test or unit test failed.
EMSHA_TEST_FAILURE = 1,
EMSHAResult::TestFailure = 1,
// A null pointer was passed in as a buffer where it
// shouldn't have been.
EMSHA_NULLPTR = 2,
EMSHAResult::NullPointer = 2,
// The Hash is in an invalid state.
EMSHA_INVALID_STATE = 3,
EMSHAResult::InvalidState = 3,
// The input to SHA256::update is too large.
SHA256_INPUT_TOO_LONG = 4,
EMSHAResult::InputTooLong = 4,
// The self tests have been disabled, but a self test
// function was called.
EMSHA_SELFTEST_DISABLED = 5
EMSHAResult::SelfTestDisabled = 5
As a convenience, the following `typedef` is also provided.
> `typedef enum _EMSHA_RESULT_` :cpp`EMSHA_RESULT`{.interpreted-text
> `typedef enum _EMSHA_RESULT_` :cpp`EMSHAResult`{.interpreted-text
> role="type"}
## The Hash interface
In general, a [Hash]{.title-ref} is used along the lines of: :
emsha::EMSHA_RESULT
emsha::EMSHAResult
hash_single_pass(uint8_t *m, uint32_t ml, uint8_t *digest)
{
// Depending on the implementation, the constructor may need
// arguments.
emsha::Hash h;
emsha::EMSHA_RESULT res;
emsha::EMSHAResult res;
res = h.write(m, ml);
if (emsha::EMSHA_ROK != res) {
if (emsha::EMSHAResult::OK != res) {
return res;
}

View File

@ -55,10 +55,13 @@ HashEqual(const uint8_t *a, const uint8_t *b)
#ifndef EMSHA_NO_HEXSTRING
namespace {
#ifndef EMSHA_NO_HEXLUT
// If using a lookup table is permitted, then the faster way to do this
// is to use one.
static void
void
writeHexChar(uint8_t *dest, uint8_t src)
{
static constexpr uint8_t lut[256][3] = {
@ -106,7 +109,7 @@ writeHexChar(uint8_t *dest, uint8_t src)
// memory constraints, we'll work around this using a small (16-byte)
// lookup table and some bit shifting. On platforms where even this is
// too much, the HexString functionality will just be disabled.
static void
void
writeHexChar(uint8_t *dest, uint8_t src)
{
static constexpr uint8_t lut[] = {
@ -118,6 +121,7 @@ writeHexChar(uint8_t *dest, uint8_t src)
*(dest + 1) = lut[(src & 0xF)];
}
#endif // #ifndef EMSHA_NO_HEXLUT
} // anonymous namespace for writeHexChar
void

View File

@ -35,11 +35,10 @@
#include <cstdint>
// Emsha is an EMbedded Secure HAshing interface.
// emsha is an EMbedded Secure HAshing interface.
namespace emsha {
/// EMSHA_CHECK is used for sanity checks in certain parts of the code.
#ifdef NDEBUG
/// EMSHA_CHECK is used for sanity checks in certain parts of
/// the code. If asserts are turned off, expand the check to an
@ -58,43 +57,52 @@ namespace emsha {
const std::uint32_t SHA256_HASH_SIZE = 32;
/// The EMSHA_RESULT type is used to indicate whether an
/// operation succeeded, and if not, what the general fault type
/// was.
typedef enum _EMSHA_RESULT_ : std::uint8_t {
/// All operations have completed successfully so far.
EMSHA_ROK = 0,
/// \brief Describe the result of an EMSHA operation.
///
/// The EMSHAResult type is used to indicate whether an operation
/// succeeded, and if not, what the general fault type was.
typedef enum class EMSHAResult : std::uint8_t {
/// An unknown fault occurred. This is a serious bug in the
/// program.
Unknown = 0,
/// A self test or unit test failed.
EMSHA_TEST_FAILURE = 1,
/// All operations have completed successfully so far.
OK = 1,
/// The self-test failed.
TestFailure = 2,
/// A null pointer was passed in as a buffer where it shouldn't
/// have been.
EMSHA_NULLPTR = 2,
NullPointer = 3,
/// The Hash is in an invalid state.
EMSHA_INVALID_STATE = 3,
InvalidState = 4,
/// The input to SHA256::update is too large.
SHA256_INPUT_TOO_LONG = 4,
InputTooLong = 5,
/// The self tests have been disabled, but a self-test function
/// was called.
EMSHA_SELFTEST_DISABLED = 5
} EMSHA_RESULT;
SelfTestDisabled = 6
} ;
// A Hash is generalised superclass supporting concrete classes
// that produce digests of data.
/// A Hash is an abstract base class supporting concrete classes
/// that produce digests of data.
class Hash {
public:
virtual ~Hash() = default;
/// Reset should bring the Hash back into its
/// initial state. That is, the idea is that
/// \brief Bring the Hash back to its initial state.
///
/// hash->reset(); hash->update(...)... ;
/// hash->result(...) ;
/// That is, the idea is that
///
/// ```
/// hash->reset();
/// hash->update(...);
/// hash->result(...);
/// ```
///
/// is idempotent, assuming the inputs to update
/// and result are constant. The implications of
@ -103,34 +111,54 @@ public:
/// in general, it has the effect of preserving
/// any initial state while removing any data
/// written to the Hash via the update method.
virtual EMSHA_RESULT Reset() = 0;
///
/// \return An ::EMSHAResult describing the status of the
/// operation.
virtual EMSHAResult Reset() = 0;
// Update is writes message data into the Hash.
virtual EMSHA_RESULT Update(const std::uint8_t *m,
std::uint32_t ml) = 0;
/// \brief Write message data into the Hash.
///
/// \param message The message data to write into the Hash.
/// \param messageLength The length of the message data.
/// \return An ::EMSHAResult describing the status of the
/// operation.
virtual EMSHAResult Update(const std::uint8_t *message,
std::uint32_t messageLength) = 0;
/// Finalise carries out any final operations
/// on the Hash; after a call to finalize, no
/// more data can be written. Additionally, it
/// transfers out the resulting hash into its
/// \brief Carry out any final operations on the Hash.
///
/// After a call to finalize, no more data can be written.
/// Additionally, it transfers out the resulting hash into its
/// argument.
virtual EMSHA_RESULT Finalise(std::uint8_t *d) = 0;
///
/// \param digest The buffer to store the hash in.
/// \return An ::EMSHAResult describing the status of the
/// operation.
virtual EMSHAResult Finalise(std::uint8_t *digest) = 0;
/// Result transfers out the hash to the
/// argument. The Hash must keep enough state
/// for repeated calls to result to work.
virtual EMSHA_RESULT Result(std::uint8_t *d) = 0;
/// \brief Result transfers out the hash to the argument.
///
/// The Hash must keep enough state for repeated calls to
/// result to work.
///
/// \param digest The buffer to store the hash in.
/// \return An ::EMSHAResult describing the status of the
/// operation.
virtual EMSHAResult Result(std::uint8_t *digest) = 0;
/// Size should return the output size of the
/// Hash; this is, how large the buffers written
/// to by result should be.
/// \brief Return the output size of the Hash.
///
/// This is how large the buffers written to by result should
/// be.
virtual std::uint32_t Size() = 0;
};
/// HashEqual provides a constant time function for comparing
/// two hashes. The caller *must* ensure that both a and b are
/// the same size. The recommended approach is to use fixed-size
/// buffers of emsha::SHA256_HASH_SIZE length:
/// \brief Constant-time function for comparing two digests.
///
/// HashEqual provides a constant time function for comparing two
/// digests. The caller *must* ensure that both a and b are the same
/// size. The recommended approach is to use fixed-size buffers of
/// emsha::SHA256_HASH_SIZE length:
///
/// ```c++
/// uint8_t expected[emsha::SHA256_HASH_SIZE];
@ -146,21 +174,21 @@ public:
/// \param a A byte buffer of size Hash::Size().
/// \param b A byte buffer of size Hash::Size().
/// \return True if both byte arrays match.
bool
HashEqual(const std::uint8_t *a, const std::uint8_t *b);
bool HashEqual(const std::uint8_t *a, const std::uint8_t *b);
#ifndef EMSHA_NO_HEXSTRING
/// HexString writes a hex-encoded version of the src byte
/// array into dest. The caller *must* ensure that dest is
/// srclen * 2 bytes or longer.
/// \brief Write a hex-encoded version of a byte string.
///
/// HexString writes a hex-encoded version of the src byte array into
/// dest. The caller **must** ensure that dest is `srclen * 2` bytes
/// or longer.
///
/// \param dest The destination byte array at least (2*srclen)
/// \param dest The destination byte array at least (`2*srclen`)
/// bytes in length.
/// \param src A byte array containing the data to hexify..
/// \param src A byte array containing the data to hexify.
/// \param srclen The size in bytes of src.
void
HexString(std::uint8_t *dest, std::uint8_t *src, std::uint32_t srclen);
void HexString(std::uint8_t *dest, std::uint8_t *src, std::uint32_t srclen);
#endif // EMSHA_NO_HEXSTRING

View File

@ -43,151 +43,137 @@ const uint32_t HMAC_KEY_LENGTH = SHA256_MB_SIZE;
/// HMAC is a keyed hash that can be used to produce an
/// authenticated hash of some data. The HMAC is built on
/// (and uses internally) the SHA-256 class; it's helpful to
/// (and uses internally) the SHA256 class; it's helpful to
/// note that faults that occur in the SHA-256 code will be
/// propagated up as the return value from many of the HMAC
/// functions.
class HMAC : Hash {
public:
/// An HMAC is constructed with a key and the
/// length of the key. This key is stored in
/// the HMAC context, and is wiped by the HMAC
/// destructor.
/// \brief Construct an HMAC with its initial key.
///
/// An HMAC is constructed with a key and the length of the
/// key. This key is stored in the HMAC context, and is wiped
/// by the HMAC destructor.
///
/// \param k The HMAC key.
/// \param kl THe length of the HMAC key.
HMAC(const uint8_t *k, uint32_t kl);
/// Reset clears any data written to the HMAC;
/// this is equivalent to constructing a new HMAC,
/// but it preserves the keys.
/// \brief Clear any data written to the HMAC.
///
/// \return EMSHA_ROK is returned if the reset
/// occurred without (detected) fault.
/// If a fault occurs with the under-
/// lying SHA-256 context, the error
/// code is returned.
EMSHA_RESULT Reset() override;
/// This is equivalent to constructing a new HMAC, but it
/// preserves the keys.
///
/// \return EMSHAResult::OK is returned if the reset occurred
/// without (detected) fault. If a fault occurs with
/// the underlying SHA256 context, the error code is
/// returned.
EMSHAResult Reset() override;
/// Update writes data into the context. While
/// there is an upper limit on the size of data
/// that the underlying hash can operate on,
/// this package is designed for small systems
/// that will not approach that level of data
/// (which is on the order of 2 exabytes), so it
/// is not thought to be a concern.
/// \brief Write data into the context.
///
/// \param m A byte array containing the message
/// While there is an upper limit on the size of data that the
/// underlying hash can operate on, this package is designed
/// for small systems that will not approach that level of data
/// (which is on the order of 2 exabytes), so it is not a
/// concern for this library.
///
/// \param message A byte array containing the message
/// to be written.
/// \param ml The message length, in bytes.
/// \return
/// - EMSHA_NULLPTR is returned if m is NULL and ml is
/// nonzero.
/// - EMSHA_INVALID_STATE is returned if the update
/// is called after a call to finalize.
/// - SHA256_INPUT_TOO_LONG is returned if too much
/// data has been written to the context.
/// - EMSHA_ROK is returned if the data was
/// successfully written into the HMAC context.
/// \param messageLength The message length, in bytes.
/// \return An ::EMSHAResult describing the result of the
/// operation.
///
EMSHA_RESULT Update(const uint8_t *, uint32_t) override;
/// - EMSHAResult::NullPointer is returned if m is NULL
/// and ml is nonzero.
/// - EMSHAResult::InvalidState is returned if the
/// update is called after a call to finalize.
/// - EMSHAResult::InputTooLong is returned if too much
/// data has been written to the context.
/// - EMSHAResult::OK is returned if the data was
/// successfully written into the HMAC context.
EMSHAResult Update(const std::uint8_t *message, std::uint32_t messageLength) override;
// Finalise completes the HMAC computation. Once this
// method is called, the context cannot be updated
// unless the context is reset.
//
// Inputs:
// d: a byte buffer that must be at least
// HMAC.size() in length.
//
// Outputs:
// EMSHA_NULLPTR is returned if d is the null
// pointer.
//
// EMSHA_INVALID_STATE is returned if the HMAC
// context is in an invalid state, such as if there
// were errors in previous updates.
//
// EMSHA_ROK is returned if the context was
// successfully finalised and the digest copied to
// d.
//
EMSHA_RESULT Finalise(uint8_t *) override;
/// \brief Complete the HMAC computation.
///
/// \note Once #Finalise is called, the context cannot be
/// updated unless the context is reset.
///
/// \param digest A byte buffer that must be at least #HMAC.Size()
/// in length.
/// \return An EMSHAResult describing the result of this
/// method:
///
/// - EMSHAResult::NullPointer is returned if d is a
/// null pointer.
/// - EMSHAResult::InvalidState is returned if the HMAC
/// context is in an invalid state, such as if there
/// were errors in previous updates.
/// - EMSHAResult::OK is returned if the context was
/// successfully finalised and the digest copied to d.
///
EMSHAResult Finalise(std::uint8_t *digest) override;
// Result copies the result from the HMAC context into
// the buffer pointed to by d, running finalize if
// needed. Once called, the context cannot be updated
// until the context is reset.
//
// Inputs:
// d: a byte buffer that must be at least
// HMAC.size() in length.
//
// Outputs:
// EMSHA_NULLPTR is returned if d is the null
// pointer.
//
// EMSHA_INVALID_STATE is returned if the HMAC
// context is in an invalid state, such as if there
// were errors in previous updates.
//
// EMSHA_ROK is returned if the context was
// successfully finalised and the digest copied to
// d.
//
EMSHA_RESULT Result(uint8_t *) override;
/// \brief Copy the current digest into a destination buffer.
///
/// Copy the current digest from the HMAC context into
/// `digest`, running #Finalise if needed. Once called, the
/// context cannot be updated until the context is reset.
///
/// \param digest A byte buffer that must be at least #HMAC.size()
/// in length.
/// \return An ::EMSHAResult describing the result of this
/// method:
///
/// - EMSHAResult::NullPointer is returned if d is a
/// null pointer.
/// - EMSHAResult::InvalidState is returned if the HMAC
/// context is in an invalid state, such as if there
/// were errors in previous updates.
/// - EMSHAResult::OK is returned if the context was
/// successfully finalised and the digest copied to d.
EMSHAResult Result(std::uint8_t *digest) override;
// size returns the output size of HMAC-SHA-256, e.g.
// the size that the buffers passed to finalize and
// result should be.
//
// Outputs:
// A uint32_t representing the expected size
// of buffers passed to result and finalize.
/// \brief Returns the output size of HMAC-SHA-256.
///
/// The buffers passed to #Update and #Finalise should be at
/// least this size.
///
/// \return The expected size of buffers passed to result and
/// finalize.
std::uint32_t Size() override;
// When an HMAC context is destroyed, it is reset and
// the key material is zeroised using the STL fill
// function.
~HMAC(void);
/// When an HMAC context is destroyed, it is reset and
/// the key material is zeroised using the STL `fill`
/// function.
~HMAC();
private:
uint8_t hstate;
SHA256 ctx;
uint8_t k[HMAC_KEY_LENGTH];
uint8_t buf[SHA256_HASH_SIZE];
uint8_t buf[SHA256_HASH_SIZE];
EMSHA_RESULT reset();
inline EMSHA_RESULT
finalResult(uint8_t *d);
EMSHAResult reset();
inline EMSHAResult finalResult(uint8_t *d);
};
// compute_hmac performs a single-pass HMAC computation over
// a message.
//
// Inputs:
// k: a byte buffer containing the HMAC key.
//
// kl: the length of the HMAC key.
//
// m: the message data over which the HMAC is to be computed.
//
// ml: the length of the message.
//
// d: a byte buffer that will be used to store the resulting
// HMAC. It should be SHA256_HASH_SIZE bytes in size.
//
// Outputs:
// This function handles setting up the HMAC context with
// the given key, calling update with the message data, and
// then calling finalize to place the result in the output
// buffer. Any of the faults that can occur in these functions
// can be returned here, or EMSHA_ROK if the HMAC was
// successfully computed.
EMSHA_RESULT
ComputeHMAC(const uint8_t *k, uint32_t kl,
const uint8_t *m, uint32_t ml,
/// \brief Perform a single-pass HMAC computation over a message.
///
/// \param k A byte buffer containing the HMAC key.
/// \param kl The length of the HMAC key.
/// \param m The message data over which the HMAC is to be computed.
/// \param ml The length of the message.
/// \param d Byte buffer that will be used to store the resulting
/// HMAC. It should be emsha::SHA256_HASH_SIZE bytes in size.
/// \return An ::EMSHAResult describing the result of the HMAC operation.
EMSHAResult
ComputeHMAC(const uint8_t *k, const uint32_t kl,
const uint8_t *m, const uint32_t ml,
uint8_t *d);
} // end of namespace emsha

View File

@ -34,184 +34,160 @@
#include <cstdint>
#include <emsha/emsha.h>
#include <array>
namespace emsha {
// SHA256_MB_SIZE is the size of a message block.
/// SHA256_MB_SIZE is the size of a message block.
const uint32_t SHA256_MB_SIZE = 64;
class SHA256 : Hash {
public:
// A SHA256 context does not need any special
// construction. It can be declared and
// immediately start being used.
/// \brief A SHA256 context does not need any special
/// construction.
///
/// It can be declared and immediately start being used.
SHA256();
// The SHA256 destructor will clear out its internal
// message buffer; all the members are local and
// not resource handles, so cleanup is minimal.
/// The SHA256 destructor will clear out its internal
/// message buffer; all the members are local and
/// not resource handles, so cleanup is minimal.
~SHA256();
// reset clears the internal state of the SHA256
// context and returns it to its initial state.
// It should always return EMSHA_ROK.
EMSHA_RESULT Reset() override;
/// \brief Clear the internal state of the SHA256 context,
/// returning it to its initial state.
///
/// \return This should always return EMSHAResult::OK.
EMSHAResult Reset() override;
// update writes data into the context. While
// there is an upper limit on the size of data
// that SHA-256 can operate on, this package is
// designed for small systems that will not
// approach that level of data (which is on the
// order of 2 exabytes), so it is not thought
// to be a concern.
//
// Inputs:
// m: a byte array containing the message to
// be written. It must not be NULL (unless
// the message length is zero).
//
// ml: the message length, in bytes.
//
// Outputs:
// EMSHA_NULLPTR is returned if m is NULL
// and ml is nonzero.
//
// EMSHA_INVALID_STATE is returned if the
// update is called after a call to
// finalize.
//
// SHA256_INPUT_TOO_LONG is returned if too
// much data has been written to the
// context.
//
// EMSHA_ROK is returned if the data was
// successfully added to the SHA-256
// context.
//
EMSHA_RESULT Update(const uint8_t *m, uint32_t ml) override;
/// \brief Writes data into the SHA256.
///
/// While there is an upper limit on the size of data that
/// SHA-256 can operate on, this package is designed for small
/// systems that will not approach that level of data (which is
/// on the order of 2 exabytes), so it is not thought to be a
/// concern.
///
/// \param message A byte array containing the message to be
/// written. It must not be NULL (unless the
/// message length is zero).
/// \param messageLength The message length, in bytes.
/// \return An ::EMSHAResult describing the result of the
/// operation.
///
/// - EMSHAResult::NullPointer is returned if m is a
/// nullptr and ml is nonzero.
/// - EMSHAResult::InvalidState is returned if the
/// update is called after a call to finalize.
/// - EMSHAResult::InputTooLong is returned if too much
/// data has been written to the context.
/// - EMSHAResult::OK is returned if the data was
/// successfully added to the SHA-256 context.
EMSHAResult Update(const std::uint8_t *message, std::uint32_t messageLength) override;
// Finalise completes the digest. Once this
// method is called, the context cannot be
// updated unless the context is reset.
//
// Inputs:
// d: a byte buffer that must be at least
// SHA256.size() in length.
//
// Outputs:
// EMSHA_NULLPTR is returned if d is the
// null pointer.
//
// EMSHA_INVALID_STATE is returned if the
// SHA-256 context is in an invalid state,
// such as if there were errors in previous
// updates.
//
// EMSHA_ROK is returned if the context was
// successfully finalised and the digest
// copied to d.
//
EMSHA_RESULT Finalise(uint8_t *d) override;
/// \brief Complete the digest.
///
/// Once this method is called, the context cannot be updated
/// unless the context is reset.
///
/// \param digest byte buffer that must be at least
/// SHA256.size() in length.
/// \return An ::EMSHAResult describing the result of the
/// operation.
///
/// - EMSHAResult::NullPointer is returned if a nullptr
/// is passed in.
/// - EMSHAResult::InvalidState is returned if the
/// SHA-256 context is in an invalid state, such as
/// if there were errors in previous updates.
/// - EMSHAResult::OK is returned if the context was
/// successfully finalised and the digest copied to
/// digest.
EMSHAResult Finalise(std::uint8_t *digest) override;
// result copies the result from the SHA-256
// context into the buffer pointed to by d,
// running Finalise if needed. Once called,
// the context cannot be updated until the
// context is reset.
//
// Inputs:
// d: a byte buffer that must be at least
// SHA256.size() in length.
//
// Outputs:
// EMSHA_NULLPTR is returned if d is the
// null pointer.
//
// EMSHA_INVALID_STATE is returned if the
// SHA-256 context is in an invalid state,
// such as if there were errors in previous
// updates.
//
// EMSHA_ROK is returned if the context was
// successfully finalised and the digest
// copied to d.
//
EMSHA_RESULT Result(uint8_t *d) override;
/// \brief Copy the result from the SHA-256
/// context into the buffer pointed to by d,
/// running #Finalise if needed. Once called,
/// the context cannot be updated until the
/// context is reset.
///
/// \param digest A byte buffer that must be at least
/// SHA256.size() in length.
/// \return An ::EMSHAResult describing the result of the
/// operation.
///
/// - EMSHAResult::NullPointer is returned if a nullptr
/// is passed in.
/// - EMSHAResult::InvalidState is returned if the
/// SHA-256 context is in an invalid state, such as
/// if there were errors in previous updates.
/// - EMSHAResult::OK is returned if the context was
/// successfully finalised and the digest copied to
/// digest.
EMSHAResult Result(std::uint8_t *digest) override;
// size returns the output size of SHA256, e.g.
// the size that the buffers passed to finalize
// and result should be.
//
// Outputs:
// a uint32_t representing the expected size
// of buffers passed to result and finalize.
/// \brief Returns the output size of SHA-256.
///
/// The buffers passed to #Update and #Finalise should be at
/// least this size.
///
/// \return The expected size of buffers passed to result and
/// finalize.
std::uint32_t Size() override;
private:
// mlen stores the current message length.
uint64_t mlen;
// The intermediate hash is 8x 32-bit blocks.
uint32_t i_hash[8];
uint64_t mlen; // Current message length.
uint32_t i_hash[8]; // The intermediate hash is 8x 32-bit blocks.
// hStatus is the hash status, and hComplete indicates
// whether the hash has been finalised.
EMSHA_RESULT hStatus;
uint8_t hComplete;
EMSHAResult hStatus;
uint8_t hComplete;
// mb is the message block, and mbi is the message
// block index.
uint8_t mbi;
uint8_t mb[SHA256_MB_SIZE];
std::array<uint8_t, SHA256_MB_SIZE> mb;
inline EMSHA_RESULT addLength(const uint32_t);
inline void updateMessageBlock(void);
inline void padMessage(uint8_t pc);
EMSHA_RESULT reset();
inline EMSHAResult addLength(const uint32_t);
inline void updateMessageBlock(void);
inline void padMessage(uint8_t pc);
uint32_t chunkToUint32(uint32_t offset);
uint32_t uint32ToChunk(uint32_t offset);
EMSHAResult reset();
}; // end class SHA256
// sha256Digest performs a single pass hashing of the message
// passed in.
//
// Inputs:
// m: byte buffer containing the message to hash.
//
// ml: the length of m.
//
// d: byte buffer that will be used to store the resulting
// hash; it should have at least emsha::SHA256_HASH_SIZE
// bytes available.
//
// Outputs:
// This function handles setting up a SHA256 context, calling
// update using the message data, and then calling finalize. Any
// of the errors that can occur in those functions can be
// returned here, or EMSHA_ROK if the digest was computed
// successfully.
//
EMSHA_RESULT
sha256Digest(const uint8_t *m, uint32_t ml, uint8_t *d);
// sha256SelfTest runs through two test cases to ensure that the
// SHA-256 functions are working correctly.
//
// Outputs:
// EMSHA_ROK is returned if the self tests pass.
//
// EMSHA_SELFTEST_DISABLED is returned if the self tests
// have been disabled (e.g., libemsha was compiled with the
// EMSHA_NO_SELFTEST #define).
//
// If a fault occurred inside the SHA-256 code, the error
// code from one of the update, finalize, result, or reset
// methods is returned.
//
// If the fault is that the output does not match the test
// vector, EMSHA_TEST_FAILURE is returned.
EMSHA_RESULT
sha256SelfTest(void);
/// \brief SHA256Digest performs a single pass hashing of the message
/// passed in.
///
/// \param m Byte buffer containing the message to hash.
/// \param ml The length of m.
/// \param d Byte buffer that will be used to store the resulting hash;
/// it should have at least emsha::SHA256_HASH_SIZE bytes
/// available.
/// \return An ::EMSHAResult describing the result of the operation.
EMSHAResult SHA256Digest(const uint8_t *m, uint32_t ml, uint8_t *d);
/// \brief SHA256SelfTest runs through two test cases to ensure that the
/// SHA-256 functions are working correctly.
///
/// \return The result of the self-test.
///
/// - EMSHAResult::OK is returned if the self tests pass.
/// - EMSHAResult::SelfTestDisabled is returned if the self
/// tests have been disabled (e.g., libemsha was compiled
/// with the EMSHA_NO_SELFTEST #define).
/// - If a fault occurred inside the SHA-256 code, the error
/// code from one of the update, finalize, result, or reset
/// methods is returned.
/// - If the fault is that the output does not match the test
/// vector, EMSHAResult::TestFailure is returned.
EMSHAResult SHA256SelfTest();
} // end of namespace emsha

96
hmac.cc
View File

@ -63,11 +63,12 @@ static constexpr uint8_t opad = 0x5c;
HMAC::HMAC(const uint8_t *ik, uint32_t ikl)
: hstate(HMAC_INIT), k{0}, buf{0}
{
std::fill(this->k, this->k + emsha::HMAC_KEY_LENGTH, 0);
std::fill(this->k, this->k+HMAC_KEY_LENGTH, 0);
if (ikl < HMAC_KEY_LENGTH) {
std::copy(ik, ik + ikl, this->k);
for (uint32_t i = 0; i < ikl; i++) {
this->k[i] = ik[i];
}
while (ikl < HMAC_KEY_LENGTH) {
this->k[ikl++] = 0;
}
@ -76,7 +77,9 @@ HMAC::HMAC(const uint8_t *ik, uint32_t ikl)
this->ctx.Result(this->k);
this->ctx.Reset();
} else {
std::copy(ik, ik + ikl, this->k);
for (uint32_t i = 0; i < ikl; i++) {
this->k[i] = ik[i];
}
}
this->reset();
@ -93,17 +96,17 @@ HMAC::~HMAC()
}
EMSHA_RESULT
EMSHAResult
HMAC::Reset()
{
return this->reset();
}
EMSHA_RESULT
EMSHAResult
HMAC::reset()
{
EMSHA_RESULT res;
EMSHAResult res;
// Following a reset, both SHA-256 contexts and result buffer should be
// zero'd out for a clean slate. The HMAC state should be reset
@ -119,7 +122,7 @@ HMAC::reset()
}
res = this->ctx.Update(key, HMAC_KEY_LENGTH);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return res;
}
@ -128,55 +131,56 @@ HMAC::reset()
std::fill(key, key + HMAC_KEY_LENGTH, 0);
this->hstate = HMAC_IPAD;
return EMSHA_ROK;
return EMSHAResult::OK;
}
EMSHA_RESULT
HMAC::Update(const uint8_t *m, uint32_t ml)
EMSHAResult
HMAC::Update(const std::uint8_t *message, std::uint32_t messageLength)
{
EMSHA_RESULT res;
SHA256 &hctx = this->ctx;
EMSHAResult res;
SHA256 &hctx = this->ctx;
EMSHA_CHECK(m != nullptr, EMSHA_NULLPTR);
EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHA_INVALID_STATE);
EMSHA_CHECK(message != nullptr, EMSHAResult::NullPointer);
EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHAResult::InvalidState);
// Write the message to the SHA-256 context.
res = hctx.Update(m, ml);
if (EMSHA_ROK != res) {
res = hctx.Update(message, messageLength);
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return res;
}
assert(HMAC_IPAD == this->hstate);
return EMSHA_ROK;
return EMSHAResult::OK;
}
inline EMSHA_RESULT
inline EMSHAResult
HMAC::finalResult(uint8_t *d)
{
if (nullptr == d) {
return EMSHA_NULLPTR;
return EMSHAResult::NullPointer;
}
// If the HMAC has already been finalised, skip straight to
// copying the result.
if (HMAC_FIN == this->hstate) {
std::copy(this->buf, this->buf + SHA256_HASH_SIZE, d);
return EMSHA_ROK;
if (this->hstate == HMAC_FIN) {
std::copy(this->buf, this->buf+SHA256_HASH_SIZE, d);
return EMSHAResult::OK;
}
EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHA_INVALID_STATE);
EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHAResult::InvalidState);
EMSHA_RESULT res;
EMSHAResult res;
// Use the result buffer as an intermediate buffer to store the result
// of the inner hash.
res = this->ctx.Result(this->buf);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return EMSHA_INVALID_STATE;
return EMSHAResult::InvalidState;
}
assert(HMAC_IPAD == this->hstate);
@ -192,7 +196,7 @@ HMAC::finalResult(uint8_t *d)
}
res = this->ctx.Update(key, HMAC_KEY_LENGTH);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return res;
}
@ -203,14 +207,14 @@ HMAC::finalResult(uint8_t *d)
// Write the inner hash result into the outer hash.
res = this->ctx.Update(this->buf, SHA256_HASH_SIZE);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return res;
}
// Write the outer hash result into the working buffer.
res = this->ctx.Finalise(this->buf);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
this->hstate = HMAC_INVALID;
return res;
}
@ -218,21 +222,21 @@ HMAC::finalResult(uint8_t *d)
std::copy(this->buf, this->buf + SHA256_HASH_SIZE, d);
this->hstate = HMAC_FIN;
return EMSHA_ROK;
return EMSHAResult::OK;
}
EMSHA_RESULT
HMAC::Finalise(uint8_t *d)
EMSHAResult
HMAC::Finalise(std::uint8_t *digest)
{
return this->finalResult(d);
return this->finalResult(digest);
}
EMSHA_RESULT
HMAC::Result(uint8_t *d)
EMSHAResult
HMAC::Result(std::uint8_t *digest)
{
return this->finalResult(d);
return this->finalResult(digest);
}
@ -243,21 +247,17 @@ HMAC::Size()
}
EMSHA_RESULT
ComputeHMAC(const uint8_t *k, uint32_t kl, const uint8_t *m, uint32_t ml,
EMSHAResult
ComputeHMAC(const uint8_t *k, const uint32_t kl,
const uint8_t *m, const uint32_t ml,
uint8_t *d)
{
EMSHA_RESULT res;
HMAC h(k, kl);
EMSHAResult res;
HMAC h(k, kl);
res = h.Update(m, ml);
if (EMSHA_ROK != res) {
return res;
}
res = h.Result(d);
if (EMSHA_ROK != res) {
return res;
if (res == EMSHAResult::OK) {
res = h.Result(d);
}
return res;

194
sha256.cc
View File

@ -69,13 +69,13 @@ static constexpr uint32_t emsha256H0[] = {
};
EMSHA_RESULT
sha256Digest(const uint8_t *m, uint32_t ml, uint8_t *d)
EMSHAResult
SHA256Digest(const uint8_t *m, uint32_t ml, uint8_t *d)
{
SHA256 h;
EMSHA_RESULT ret;
SHA256 h;
EMSHAResult ret = EMSHAResult::Unknown;
if (EMSHA_ROK != (ret = h.Update(m, ml))) {
if (EMSHAResult::OK != (ret = h.Update(m, ml))) {
return ret;
}
@ -92,33 +92,35 @@ SHA256::SHA256()
SHA256::~SHA256()
{
memset(this->mb, 0, SHA256_MB_SIZE);
for (auto i = static_cast<uint32_t>(0); i < SHA256_MB_SIZE; i++) {
this->mb[i] = static_cast<uint8_t>(0);
}
}
EMSHA_RESULT
EMSHAResult
SHA256::addLength(const uint32_t l)
{
uint32_t tmp = this->mlen + l;
EMSHAResult res = EMSHAResult::InputTooLong;;
if (tmp < this->mlen) {
return SHA256_INPUT_TOO_LONG;
uint32_t const tmp = static_cast<uint32_t>(this->mlen) + l;
if (tmp >= this->mlen) {
this->mlen = tmp;
assert(this->mlen > 0);
res = EMSHAResult::OK;
}
this->mlen = tmp;
assert(this->mlen > 0);
return EMSHA_ROK;
return res;
}
EMSHA_RESULT
EMSHAResult
SHA256::Reset()
{
return this->reset();
}
EMSHA_RESULT
EMSHAResult
SHA256::reset()
{
// The message block is set to the initial hash vector.
@ -132,29 +134,47 @@ SHA256::reset()
this->i_hash[7] = emsha256H0[7];
this->mbi = 0;
this->hStatus = EMSHA_ROK;
this->hStatus = EMSHAResult::OK;
this->hComplete = 0;
this->mlen = 0;
memset(this->mb, 0, SHA256_MB_SIZE);
std::fill(this->mb.begin(), this->mb.end(), 0);
return this->hStatus;
}
// Read 32 bits from the byte buffer chunk as an unsigned 32-bit integer.
static uint32_t
chunkToUint32(const uint8_t *chunk)
uint32_t
SHA256::chunkToUint32(uint32_t offset)
{
return ((*chunk) << 24) |
((*(chunk + 1)) << 16) |
((*(chunk + 2)) << 8) |
(*(chunk + 3));
uint32_t chunk = 0;
for (uint32_t i = offset; i < offset+4; i++) {
chunk <<= 8;
chunk += static_cast<uint32_t>(this->mb[i]);
}
return chunk;
}
uint32_t
SHA256::uint32ToChunk(uint32_t offset)
{
uint32_t chunk = 0;
for (uint32_t i = offset; i < offset+4; i++) {
chunk <<= 8;
chunk += static_cast<uint32_t>(this->mb[i]);
}
return chunk;
}
// Copy an unsigned 32-bit integer into the start of the byte buffer chunk.
static void
uint32ToChunk(uint32_t x, uint8_t *chunk)
uint32ToChunkInPlace(uint32_t x, uint8_t *chunk)
{
chunk[0] = (x & 0xff000000) >> 24;
chunk[1] = (x & 0x00ff0000) >> 16;
@ -180,7 +200,7 @@ SHA256::updateMessageBlock()
uint32_t h = 0;
while (i < 16) {
w[i++] = chunkToUint32(this->mb + chunk);
w[i++] = this->chunkToUint32(chunk);
chunk += 4;
}
this->mbi = 0;
@ -225,36 +245,36 @@ SHA256::updateMessageBlock()
}
EMSHA_RESULT
SHA256::Update(const uint8_t *m, uint32_t ml)
EMSHAResult
SHA256::Update(const std::uint8_t *message, std::uint32_t messageLength)
{
// Checking invariants:
// If the message length is zero, there's nothing to be done.
if (0 == ml) { return EMSHA_ROK; }
if (0 == messageLength) { return EMSHAResult::OK; }
// The message passed in cannot be the null pointer if the
// message length is greater than 0.
if (nullptr == m) { return EMSHA_NULLPTR; }
if (message == nullptr) { return EMSHAResult::NullPointer; }
// If the SHA256 object is in a bad state, don't proceed.
if (EMSHA_ROK != this->hStatus) { return this->hStatus; }
if (this->hStatus != EMSHAResult::OK) { return this->hStatus; }
// If the hash has been finalised, don't proceed.
if (0 != this->hComplete) { return EMSHA_INVALID_STATE; }
if (this->hComplete != static_cast<uint8_t>(0)) { return EMSHAResult::InvalidState; }
// Invariants satisfied by here.
for (uint32_t i = 0; i < ml; i++) {
this->mb[this->mbi] = *(m + i);
for (uint32_t i = 0; i < messageLength; i++) {
this->mb[this->mbi] = *(message + i);
mbi++;
if (EMSHA_ROK == this->addLength(8)) {
if (EMSHAResult::OK == this->addLength(8)) {
if (SHA256_MB_SIZE == this->mbi) {
this->updateMessageBlock();
// Assumption: following the message block
// write, the context should still be in a good
// state.
assert(EMSHA_ROK == this->hStatus);
assert(EMSHAResult::OK == this->hStatus);
}
}
}
@ -267,7 +287,7 @@ inline void
SHA256::padMessage(uint8_t pc)
{
// Assumption: the context is not in a corrupted state.
assert(EMSHA_ROK == this->hStatus);
assert(EMSHAResult::OK == this->hStatus);
if (this->mbi < (SHA256_MB_SIZE - 8)) {
this->mb[this->mbi++] = pc;
@ -290,7 +310,7 @@ SHA256::padMessage(uint8_t pc)
// Assumption: updating the message block has not left the
// context in a corrupted state.
assert(EMSHA_ROK == this->hStatus);
assert(EMSHAResult::OK == this->hStatus);
}
while (this->mbi < (SHA256_MB_SIZE - 8)) {
@ -320,76 +340,76 @@ SHA256::padMessage(uint8_t pc)
// Assumption: updating the message block has not left the context in a
// corrupted state.
assert(EMSHA_ROK == this->hStatus);
assert(EMSHAResult::OK == this->hStatus);
}
EMSHA_RESULT
SHA256::Finalise(uint8_t *d)
EMSHAResult
SHA256::Finalise(std::uint8_t *digest)
{
// Check invariants.
// The digest cannot be a null pointer; this library allocates
// no memory of its own.
if (nullptr == d) { return EMSHA_NULLPTR; }
if (nullptr == digest) { return EMSHAResult::NullPointer; }
// If the SHA256 object is in a bad state, don't proceed.
if (EMSHA_ROK != this->hStatus) { return this->hStatus; }
if (EMSHAResult::OK != this->hStatus) { return this->hStatus; }
// If the hash has been finalised, don't proceed.
if (0 != this->hComplete) { return EMSHA_INVALID_STATE; }
if (0 != this->hComplete) { return EMSHAResult::InvalidState; }
// Invariants satisfied by here.
this->padMessage(0x80);
// Assumption: padding the message block has not left the context in a
// corrupted state.
assert(EMSHA_ROK == this->hStatus);
std::fill(this->mb, this->mb + SHA256_MB_SIZE, 0);
assert(EMSHAResult::OK == this->hStatus);
std::fill(this->mb.begin(), this->mb.end(), 0);
this->hComplete = 1;
this->mlen = 0;
uint32ToChunk(this->i_hash[0], d);
uint32ToChunk(this->i_hash[1], d + 4);
uint32ToChunk(this->i_hash[2], d + 8);
uint32ToChunk(this->i_hash[3], d + 12);
uint32ToChunk(this->i_hash[4], d + 16);
uint32ToChunk(this->i_hash[5], d + 20);
uint32ToChunk(this->i_hash[6], d + 24);
uint32ToChunk(this->i_hash[7], d + 28);
uint32ToChunkInPlace(this->i_hash[0], digest);
uint32ToChunkInPlace(this->i_hash[1], digest + 4);
uint32ToChunkInPlace(this->i_hash[2], digest + 8);
uint32ToChunkInPlace(this->i_hash[3], digest + 12);
uint32ToChunkInPlace(this->i_hash[4], digest + 16);
uint32ToChunkInPlace(this->i_hash[5], digest + 20);
uint32ToChunkInPlace(this->i_hash[6], digest + 24);
uint32ToChunkInPlace(this->i_hash[7], digest + 28);
return EMSHA_ROK;
return EMSHAResult::OK;
}
EMSHA_RESULT
SHA256::Result(uint8_t *d)
EMSHAResult
SHA256::Result(std::uint8_t *digest)
{
// Check invariants.
// The digest cannot be a null pointer; this library allocates
// no memory of its own.
if (nullptr == d) { return EMSHA_NULLPTR; }
if (nullptr == digest) { return EMSHAResult::NullPointer; }
// If the SHA256 object is in a bad state, don't proceed.
if (EMSHA_ROK != this->hStatus) { return this->hStatus; }
if (EMSHAResult::OK != this->hStatus) { return this->hStatus; }
// Invariants satisfied by here.
if (this->hComplete == 0U) {
return this->Finalise(d);
return this->Finalise(digest);
}
uint32ToChunk(this->i_hash[0], d);
uint32ToChunk(this->i_hash[1], d + 4);
uint32ToChunk(this->i_hash[2], d + 8);
uint32ToChunk(this->i_hash[3], d + 12);
uint32ToChunk(this->i_hash[4], d + 16);
uint32ToChunk(this->i_hash[5], d + 20);
uint32ToChunk(this->i_hash[6], d + 24);
uint32ToChunk(this->i_hash[7], d + 28);
uint32ToChunkInPlace(this->i_hash[0], digest);
uint32ToChunkInPlace(this->i_hash[1], digest + 4);
uint32ToChunkInPlace(this->i_hash[2], digest + 8);
uint32ToChunkInPlace(this->i_hash[3], digest + 12);
uint32ToChunkInPlace(this->i_hash[4], digest + 16);
uint32ToChunkInPlace(this->i_hash[5], digest + 20);
uint32ToChunkInPlace(this->i_hash[6], digest + 24);
uint32ToChunkInPlace(this->i_hash[7], digest + 28);
return EMSHA_ROK;
return EMSHAResult::OK;
}
@ -426,22 +446,22 @@ static const uint8_t helloWorld[] = {
constexpr uint32_t EMSHA_SELF_TEST_ITERS = 4;
static EMSHA_RESULT
static EMSHAResult
runTest(const uint8_t *input, uint32_t input_len, const uint8_t *expected)
{
uint8_t hexString[65]{0};
uint8_t d[SHA256_HASH_SIZE]{0};
emsha::SHA256 ctx;
emsha::EMSHA_RESULT res;
uint8_t hexString[65]{0};
uint8_t d[SHA256_HASH_SIZE]{0};
emsha::SHA256 ctx;
emsha::EMSHAResult res;
res = ctx.Update(input, input_len);
if (EMSHA_ROK != res) {
if (EMSHAResult::OK != res) {
return res;
}
for (uint32_t n = 0; n < EMSHA_SELF_TEST_ITERS; n++) {
res = ctx.Result(d);