blob: 81a5c58f72b3b2d8679d6310d520c765e195d62a [file] [log] [blame]
/*
* Copyright (c) 2016, Freescale Semiconductor, Inc.
* Copyright 2016-2018 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fsl_sha.h"
/*******************************************************************************
* Definitions
*******************************************************************************/
/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.sha"
#endif
/*!< SHA-1 and SHA-256 block size */
#define SHA_BLOCK_SIZE 64
/*!< Use standard C library memcpy */
#define sha_memcpy memcpy
/*! Internal states of the HASH creation process */
typedef enum _sha_algo_state
{
kSHA_HashInit = 1u, /*!< Init state, the NEW bit in SHA Control register has not been written yet. */
kSHA_HashUpdate, /*!< Update state, DIGEST registers contain running hash, NEW bit in SHA control register has been
written. */
} sha_algo_state_t;
/*! 64-byte block represented as byte array of 16 32-bit words */
typedef union _sha_hash_block
{
uint32_t w[SHA_BLOCK_SIZE / 4]; /*!< array of 32-bit words */
uint8_t b[SHA_BLOCK_SIZE]; /*!< byte array */
} sha_block_t;
/*! internal sha context structure */
typedef struct _sha_ctx_internal
{
sha_block_t blk; /*!< memory buffer. only full 64-byte blocks are written to SHA during hash updates */
size_t blksz; /*!< number of valid bytes in memory buffer */
sha_algo_t algo; /*!< selected algorithm from the set of supported algorithms */
sha_algo_state_t state; /*!< finite machine state of the hash software process */
size_t fullMessageSize; /*!< track message size during SHA_Update(). The value is used for padding. */
} sha_ctx_internal_t;
/*!< SHA-1 and SHA-256 digest length in bytes */
enum _sha_digest_len
{
kSHA_OutLenSha1 = 20u,
kSHA_OutLenSha256 = 32u,
};
/*!< macro for checking build time condition. It is used to assure the sha_ctx_internal_t can fit into sha_ctx_t */
#define BUILD_ASSERT(condition, msg) extern int msg[1 - 2 * (!(condition))] __attribute__((unused))
/*******************************************************************************
* Code
******************************************************************************/
/*!
* @brief LDM to SHA engine INDATA and ALIAS registers.
*
* This function writes 16 words starting from the src address (must be word aligned)
* to the dst address. Dst address does not increment (destination is peripheral module register INDATA).
* Src address increments to load 16 consecutive words.
*
* @param dst peripheral register address (word aligned)
* @param src address of the input 512-bit block (16 words) (word aligned)
*
*/
#if defined(SHA_ALIAS_DATA_MASK)
__STATIC_INLINE void sha_ldm_stm_16_words(SHA_Type *base, const uint32_t *src)
{
base->INDATA = src[0];
for (int i = 0; i < 7; i++)
{
base->ALIAS[i] = src[i + 1];
}
src += 8u;
base->INDATA = src[0];
for (int i = 0; i < 7; i++)
{
base->ALIAS[i] = src[i + 1];
}
}
#else
__STATIC_INLINE void sha_ldm_stm_16_words(volatile uint32_t *dst, const uint32_t *src)
{
for (int i = 0; i < 8; i++)
{
dst[i] = src[i];
}
src += 8u;
for (int i = 0; i < 8; i++)
{
dst[i] = src[i];
}
}
#endif
/*!
* @brief Swap bytes withing 32-bit word.
*
* This function changes endianess of a 32-bit word.
*
* @param in 32-bit unsigned integer
* @return 32-bit unsigned integer with different endianess (big endian to little endian and vice versa).
*/
static uint32_t swap_bytes(uint32_t in)
{
return (((in & 0x000000ffu) << 24) | ((in & 0x0000ff00u) << 8) | ((in & 0x00ff0000u) >> 8) |
((in & 0xff000000u) >> 24));
}
/*!
* @brief Check validity of algoritm.
*
* This function checks the validity of input argument.
*
* @param algo Tested algorithm value.
* @return kStatus_Success if valid, kStatus_InvalidArgument otherwise.
*/
static status_t sha_check_input_alg(sha_algo_t algo)
{
if ((algo != kSHA_Sha1) && (algo != kSHA_Sha256))
{
return kStatus_InvalidArgument;
}
return kStatus_Success;
}
/*!
* @brief Check validity of input arguments.
*
* This function checks the validity of input arguments.
*
* @param base SHA peripheral base address.
* @param ctx Memory buffer given by user application where the SHA_Init/SHA_Update/SHA_Finish store context.
* @param algo Tested algorithm value.
* @return kStatus_Success if valid, kStatus_InvalidArgument otherwise.
*/
static status_t sha_check_input_args(SHA_Type *base, sha_ctx_t *ctx, sha_algo_t algo)
{
/* Check validity of input algorithm */
if (kStatus_Success != sha_check_input_alg(algo))
{
return kStatus_InvalidArgument;
}
if ((NULL == ctx) || (NULL == base))
{
return kStatus_InvalidArgument;
}
return kStatus_Success;
}
/*!
* @brief Check validity of internal software context.
*
* This function checks if the internal context structure looks correct.
*
* @param ctxInternal Internal context.
* @param message Input message address.
* @return kStatus_Success if valid, kStatus_InvalidArgument otherwise.
*/
static status_t sha_check_context(sha_ctx_internal_t *ctxInternal, const uint8_t *message)
{
if ((NULL == message) || (NULL == ctxInternal) || (kStatus_Success != sha_check_input_alg(ctxInternal->algo)))
{
return kStatus_InvalidArgument;
}
return kStatus_Success;
}
/*!
* @brief Initialize the SHA engine for new hash.
*
* This function sets NEW and MODE fields in SHA Control register to start new hash.
*
* @param base SHA peripheral base address.
* @param ctxInternal Internal context.
*/
static void sha_engine_init(SHA_Type *base, sha_ctx_internal_t *ctxInternal)
{
uint32_t shaCtrl;
if (kSHA_Sha1 == ctxInternal->algo)
{
shaCtrl = SHA_CTRL_MODE(1) | SHA_CTRL_NEW(1);
}
else
{
shaCtrl = SHA_CTRL_MODE(2) | SHA_CTRL_NEW(1);
}
base->CTRL = shaCtrl;
}
/*!
* @brief Load 512-bit block (16 words) into SHA engine.
*
* This function aligns the input block and moves it into SHA engine INDATA.
* CPU polls the WAITING bit and then moves data by using LDM and STM instructions.
*
* @param base SHA peripheral base address.
* @param blk 512-bit block
*/
static void sha_one_block(SHA_Type *base, const uint8_t *blk)
{
uint32_t temp[SHA_BLOCK_SIZE / sizeof(uint32_t)];
const uint32_t *actBlk;
/* make sure the 512-bit block is word aligned */
if ((uintptr_t)blk & 0x3u)
{
sha_memcpy(temp, blk, SHA_BLOCK_SIZE);
actBlk = (const uint32_t *)(uintptr_t)temp;
}
else
{
actBlk = (const uint32_t *)(uintptr_t)blk;
}
/* poll waiting. */
while (0 == (base->STATUS & SHA_STATUS_WAITING_MASK))
{
}
/* feed INDATA (and ALIASes). use STM instruction. */
#if defined(SHA_ALIAS_DATA_MASK)
sha_ldm_stm_16_words(base, actBlk);
#else
sha_ldm_stm_16_words(&base->INDATA[0], actBlk);
#endif
}
/*!
* @brief Adds message to current hash.
*
* This function merges the message to fill the internal buffer, empties the internal buffer if
* it becomes full, then process all remaining message data.
*
*
* @param base SHA peripheral base address.
* @param ctxInternal Internal context.
* @param message Input message.
* @param messageSize Size of input message in bytes.
* @return kStatus_Success.
*/
static status_t sha_process_message_data(SHA_Type *base,
sha_ctx_internal_t *ctxInternal,
const uint8_t *message,
size_t messageSize)
{
/* first fill the internal buffer to full block */
size_t toCopy = SHA_BLOCK_SIZE - ctxInternal->blksz;
sha_memcpy(&ctxInternal->blk.b[ctxInternal->blksz], message, toCopy);
message += toCopy;
messageSize -= toCopy;
/* process full internal block */
sha_one_block(base, &ctxInternal->blk.b[0]);
/* process all full blocks in message[] */
while (messageSize >= SHA_BLOCK_SIZE)
{
sha_one_block(base, message);
message += SHA_BLOCK_SIZE;
messageSize -= SHA_BLOCK_SIZE;
}
/* copy last incomplete message bytes into internal block */
sha_memcpy(&ctxInternal->blk.b[0], message, messageSize);
ctxInternal->blksz = messageSize;
return kStatus_Success;
}
/*!
* @brief Finalize the running hash to make digest.
*
* This function empties the internal buffer, adds padding bits, and generates final digest.
*
* @param base SHA peripheral base address.
* @param ctxInternal Internal context.
* @return kStatus_Success.
*/
static status_t sha_finalize(SHA_Type *base, sha_ctx_internal_t *ctxInternal)
{
sha_block_t lastBlock;
memset(&lastBlock, 0, sizeof(sha_block_t));
/* this is last call, so need to flush buffered message bytes along with padding */
if (ctxInternal->blksz <= 55u)
{
/* last data is 440 bits or less. */
sha_memcpy(&lastBlock.b[0], &ctxInternal->blk.b[0], ctxInternal->blksz);
lastBlock.b[ctxInternal->blksz] = (uint8_t)0x80U;
lastBlock.w[SHA_BLOCK_SIZE / 4 - 1] = swap_bytes(8u * ctxInternal->fullMessageSize);
sha_one_block(base, &lastBlock.b[0]);
}
else
{
if (ctxInternal->blksz < SHA_BLOCK_SIZE)
{
ctxInternal->blk.b[ctxInternal->blksz] = (uint8_t)0x80U;
for (uint32_t i = ctxInternal->blksz + 1u; i < SHA_BLOCK_SIZE; i++)
{
ctxInternal->blk.b[i] = 0;
}
}
else
{
lastBlock.b[0] = (uint8_t)0x80U;
}
sha_one_block(base, &ctxInternal->blk.b[0]);
lastBlock.w[SHA_BLOCK_SIZE / 4 - 1] = swap_bytes(8u * ctxInternal->fullMessageSize);
sha_one_block(base, &lastBlock.b[0]);
}
/* poll wait for final digest */
while (0 == (base->STATUS & SHA_STATUS_DIGEST_MASK))
{
}
return kStatus_Success;
}
/*!
* @brief Read DIGEST registers.
*
* This function copies DIGEST to output buffer.
*
* @param base SHA peripheral base address.
* @param[out] output Output buffer.
* @param Number of bytes to copy.
* @return kStatus_Success.
*/
static void sha_get_digest(SHA_Type *base, uint8_t *output, size_t outputSize)
{
uint32_t digest[8];
for (int i = 0; i < 8; i++)
{
digest[i] = swap_bytes(base->DIGEST[i]);
}
if (outputSize > sizeof(digest))
{
outputSize = sizeof(digest);
}
sha_memcpy(output, digest, outputSize);
}
/*!
* brief Initialize HASH context
*
* This function initializes new hash context.
*
* param base SHA peripheral base address
* param[out] ctx Output hash context
* param algo Underlaying algorithm to use for hash computation. Either SHA-1 or SHA-256.
* return Status of initialization
*/
status_t SHA_Init(SHA_Type *base, sha_ctx_t *ctx, sha_algo_t algo)
{
status_t status;
sha_ctx_internal_t *ctxInternal;
/* compile time check for the correct structure size */
BUILD_ASSERT(sizeof(sha_ctx_t) >= sizeof(sha_ctx_internal_t), sha_ctx_t_size);
uint32_t i;
status = sha_check_input_args(base, ctx, algo);
if (status != kStatus_Success)
{
return status;
}
/* set algorithm in context struct for later use */
ctxInternal = (sha_ctx_internal_t *)ctx;
ctxInternal->algo = algo;
ctxInternal->blksz = 0u;
for (i = 0; i < sizeof(ctxInternal->blk.w) / sizeof(ctxInternal->blk.w[0]); i++)
{
ctxInternal->blk.w[0] = 0u;
}
ctxInternal->state = kSHA_HashInit;
ctxInternal->fullMessageSize = 0;
return status;
}
/*!
* brief Add data to current HASH
*
* Add data to current HASH. This can be called repeatedly with an arbitrary amount of data to be
* hashed.
*
* param base SHA peripheral base address
* param[in,out] ctx HASH context
* param message Input message
* param messageSize Size of input message in bytes
* return Status of the hash update operation
*/
status_t SHA_Update(SHA_Type *base, sha_ctx_t *ctx, const uint8_t *message, size_t messageSize)
{
bool isUpdateState;
status_t status;
sha_ctx_internal_t *ctxInternal;
size_t blockSize;
if (messageSize == 0)
{
return kStatus_Success;
}
ctxInternal = (sha_ctx_internal_t *)ctx;
status = sha_check_context(ctxInternal, message);
if (kStatus_Success != status)
{
return status;
}
ctxInternal->fullMessageSize += messageSize;
blockSize = SHA_BLOCK_SIZE;
/* if we are still less than 64 bytes, keep only in context */
if ((ctxInternal->blksz + messageSize) <= blockSize)
{
sha_memcpy((&ctxInternal->blk.b[0]) + ctxInternal->blksz, message, messageSize);
ctxInternal->blksz += messageSize;
return status;
}
else
{
isUpdateState = ctxInternal->state == kSHA_HashUpdate;
if (!isUpdateState)
{
/* start NEW hash */
sha_engine_init(base, ctxInternal);
ctxInternal->state = kSHA_HashUpdate;
}
}
/* process message data */
status = sha_process_message_data(base, ctxInternal, message, messageSize);
return status;
}
/*!
* brief Finalize hashing
*
* Outputs the final hash and erases the context. SHA-1 or SHA-256 padding bits are automatically added by this
* function.
*
* param base SHA peripheral base address
* param[in,out] ctx HASH context
* param[out] output Output hash data
* param[in,out] outputSize On input, determines the size of bytes of the output array. On output, tells how many bytes
* have been written to output.
* return Status of the hash finish operation
*/
status_t SHA_Finish(SHA_Type *base, sha_ctx_t *ctx, uint8_t *output, size_t *outputSize)
{
size_t algOutSize = 0;
status_t status;
sha_ctx_internal_t *ctxInternal;
uint32_t *ctxW;
uint32_t i;
ctxInternal = (sha_ctx_internal_t *)ctx;
status = sha_check_context(ctxInternal, output);
if (kStatus_Success != status)
{
return status;
}
if (ctxInternal->state == kSHA_HashInit)
{
sha_engine_init(base, ctxInternal);
}
size_t outSize = 0u;
/* compute algorithm output length */
switch (ctxInternal->algo)
{
case kSHA_Sha1:
outSize = kSHA_OutLenSha1;
break;
case kSHA_Sha256:
outSize = kSHA_OutLenSha256;
break;
default:
break;
}
algOutSize = outSize;
/* flush message last incomplete block, if there is any, and add padding bits */
status = sha_finalize(base, ctxInternal);
if (outputSize)
{
if (algOutSize < *outputSize)
{
*outputSize = algOutSize;
}
else
{
algOutSize = *outputSize;
}
}
sha_get_digest(base, &output[0], algOutSize);
ctxW = (uint32_t *)ctx;
for (i = 0; i < SHA_CTX_SIZE; i++)
{
ctxW[i] = 0u;
}
return status;
}
void SHA_ClkInit(SHA_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* ungate clock */
CLOCK_EnableClock(kCLOCK_Sha0);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}
void SHA_ClkDeinit(SHA_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* gate clock */
CLOCK_DisableClock(kCLOCK_Sha0);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}