core/crypto/hash.c (288 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stdlib.h>
#include <string.h>
#include "hash.h"
/**
* Configure a hash engine to start a new hashing calculation.
*
* @param engine The hash engine to configure.
* @param type The type of hash to start.
*
* @return 0 if the hash engine was successfully configured or an error code.
*/
int hash_start_new_hash (const struct hash_engine *engine, enum hash_type type)
{
int status;
if (engine == NULL) {
return HASH_ENGINE_INVALID_ARGUMENT;
}
switch (type) {
case HASH_TYPE_SHA1:
#ifdef HASH_ENABLE_SHA1
status = engine->start_sha1 (engine);
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
case HASH_TYPE_SHA256:
status = engine->start_sha256 (engine);
break;
case HASH_TYPE_SHA384:
#ifdef HASH_ENABLE_SHA384
status = engine->start_sha384 (engine);
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
case HASH_TYPE_SHA512:
#ifdef HASH_ENABLE_SHA512
status = engine->start_sha512 (engine);
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
default:
status = HASH_ENGINE_UNKNOWN_HASH;
break;
}
return status;
}
/**
* Calculate the hash on a complete set of data.
*
* @param engine The hash engine to use to calculate the hash.
* @param type The type of hash to calculate.
* @param data The data to hash.
* @param length The length of the data.
* @param hash The buffer that will contain the generated hash.
* @param hash_length The size of the hash buffer.
*
* @return The length of the calculated hash or an error code. Use ROT_IS_ERROR to check the return
* value.
*/
int hash_calculate (const struct hash_engine *engine, enum hash_type type, const uint8_t *data,
size_t length, uint8_t *hash, size_t hash_length)
{
int status = 0;
if (engine == NULL) {
return HASH_ENGINE_INVALID_ARGUMENT;
}
switch (type) {
case HASH_TYPE_SHA1:
#ifdef HASH_ENABLE_SHA1
status = engine->calculate_sha1 (engine, data, length, hash, hash_length);
if (status == 0) {
status = SHA1_HASH_LENGTH;
}
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
case HASH_TYPE_SHA256:
status = engine->calculate_sha256 (engine, data, length, hash, hash_length);
if (status == 0) {
status = SHA256_HASH_LENGTH;
}
break;
case HASH_TYPE_SHA384:
#ifdef HASH_ENABLE_SHA384
status = engine->calculate_sha384 (engine, data, length, hash, hash_length);
if (status == 0) {
status = SHA384_HASH_LENGTH;
}
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
case HASH_TYPE_SHA512:
#ifdef HASH_ENABLE_SHA512
status = engine->calculate_sha512 (engine, data, length, hash, hash_length);
if (status == 0) {
status = SHA512_HASH_LENGTH;
}
#else
status = HASH_ENGINE_UNSUPPORTED_HASH;
#endif
break;
default:
status = HASH_ENGINE_UNKNOWN_HASH;
}
return status;
}
/**
* Get the hash algorithm used to generate a hash based on the output length.
*
* @param hash_length The length of the hash output.
*
* @return Hash algorithm used if the length is known or HASH_TYPE_INVALID.
*/
enum hash_type hash_get_type_from_length (size_t hash_length)
{
switch (hash_length) {
case SHA1_HASH_LENGTH:
return HASH_TYPE_SHA1;
case SHA256_HASH_LENGTH:
return HASH_TYPE_SHA256;
case SHA384_HASH_LENGTH:
return HASH_TYPE_SHA384;
case SHA512_HASH_LENGTH:
return HASH_TYPE_SHA512;
default:
return HASH_TYPE_INVALID;
}
}
/**
* Get the hash algorithm type for the active hash identifier.
*
* This is not something that is expected to be called generally. It's primarily a helper function
* for hash engine implementations that use the active hash enumeration.
*
* @param active The HASH_ACTIVE_* identifier for the hash algorithm.
*
* @return The HASH_TYPE_* enumeration for the identifier.
*/
enum hash_type hash_get_type_from_active (uint8_t active)
{
return (active == HASH_ACTIVE_NONE) ? HASH_TYPE_INVALID : (enum hash_type) active;
}
/**
* Get the length of the output digest for the indicated hash algorithm.
*
* @param hash_type The hashing algorithm to check.
*
* @return Digest length if the hash type is known or HASH_ENGINE_UNKNOWN_HASH.
*/
int hash_get_hash_length (enum hash_type hash_type)
{
switch (hash_type) {
case HASH_TYPE_SHA1:
return SHA1_HASH_LENGTH;
case HASH_TYPE_SHA256:
return SHA256_HASH_LENGTH;
case HASH_TYPE_SHA384:
return SHA384_HASH_LENGTH;
case HASH_TYPE_SHA512:
return SHA512_HASH_LENGTH;
default:
return HASH_ENGINE_UNKNOWN_HASH;
}
}
/**
* Get the length of the output digest for the active hash calculation on a specified hash engine.
*
* @param hash The hash engine to query.
*
* @return Length of the output digest or 0 if no hash calculation is ongoing.
*/
size_t hash_get_active_hash_length (const struct hash_engine *hash)
{
size_t length = 0;
if (hash != NULL) {
length = hash_get_hash_length (hash->get_active_algorithm (hash));
if (length == HASH_ENGINE_UNKNOWN_HASH) {
length = 0;
}
}
return length;
}
/**
* Get the block size used for the indicated hash algorithm.
*
* @param hash_type The hashing algorithm to check.
*
* @return Hash block size if the hash type is known or HASH_ENGINE_UNKNOWN_HASH.
*/
int hash_get_block_size (enum hash_type hash_type)
{
switch (hash_type) {
case HASH_TYPE_SHA1:
return SHA1_BLOCK_SIZE;
case HASH_TYPE_SHA256:
return SHA256_BLOCK_SIZE;
case HASH_TYPE_SHA384:
return SHA384_BLOCK_SIZE;
case HASH_TYPE_SHA512:
return SHA512_BLOCK_SIZE;
default:
return HASH_ENGINE_UNKNOWN_HASH;
}
}
/**
* Determine if a specific hashing algorithm is supported by the device.
*
* @param hash_type The hashing algorithm to check.
*
* @return True if algorithm is supported, False otherwise.
*/
bool hash_is_alg_supported (enum hash_type type)
{
switch (type) {
#ifdef HASH_ENABLE_SHA1
case HASH_TYPE_SHA1:
return true;
#endif
case HASH_TYPE_SHA256:
return true;
#ifdef HASH_ENABLE_SHA384
case HASH_TYPE_SHA384:
return true;
#endif
#ifdef HASH_ENABLE_SHA512
case HASH_TYPE_SHA512:
return true;
#endif
default:
return false;
}
}
/**
* Generate an HMAC for a block of data.
*
* @param engine The hashing engine to use for generating the HMAC.
* @param key The secret key for the HMAC.
* @param key_length The length of the key.
* @param data The data to generate the HMAC for.
* @param length The length of the data.
* @param hash The hashing algorithm to use.
* @param hmac The output buffer that will hold the HMAC. It must be the right size for the hashing
* algorithm being used.
* @param hmac_length The size of the HMAC buffer.
*
* @return 0 if the HMAC was successfully generated or an error code.
*/
int hash_generate_hmac (const struct hash_engine *engine, const uint8_t *key, size_t key_length,
const uint8_t *data, size_t length, enum hmac_hash hash, uint8_t *hmac, size_t hmac_length)
{
struct hmac_engine hmac_engine;
int status;
status = hash_hmac_init (&hmac_engine, engine, hash, key, key_length);
if (status != 0) {
return status;
}
if (hmac_length < hmac_engine.hash_length) {
status = HASH_ENGINE_HASH_BUFFER_TOO_SMALL;
goto fail;
}
status = hash_hmac_update (&hmac_engine, data, length);
if (status != 0) {
goto fail;
}
return hash_hmac_finish (&hmac_engine, hmac, hmac_length);
fail:
hash_hmac_cancel (&hmac_engine);
return status;
}
/**
* Initialize an engine for generating an HMAC.
*
* An initialized HMAC engine must be released by either finishing or canceling the operation.
*
* @param engine The HMAC engine to initialize.
* @param hash The hash engine to use to generate the HMAC.
* @param hash_type The type of hashing algorithm to use.
* @param key The key to use with the HMAC.
* @param key_length The length of the key.
*
* @return 0 if the HMAC engine was successfully initialized or an error code.
*/
int hash_hmac_init (struct hmac_engine *engine, const struct hash_engine *hash,
enum hmac_hash hash_type, const uint8_t *key, size_t key_length)
{
int status;
size_t i;
if ((engine == NULL) || (hash == NULL) || (key == NULL) || (key_length == 0)) {
return HASH_ENGINE_INVALID_ARGUMENT;
}
engine->block_size = hash_get_block_size ((enum hash_type) hash_type);
if (engine->block_size == HASH_ENGINE_UNKNOWN_HASH) {
return HASH_ENGINE_UNKNOWN_HASH;
}
engine->hash = hash;
engine->type = hash_type;
engine->hash_length = hash_hmac_get_hmac_length (hash_type);
if (key_length > engine->block_size) {
/* If the HMAC key is longer than the algorithm block size, it needs to be hashed so that it
* can fit within the algorithm block size. */
status = hash_calculate (hash, (enum hash_type) hash_type, key, key_length, engine->key,
sizeof (engine->key));
if (ROT_IS_ERROR (status)) {
return status;
}
key_length = status;
}
else {
memcpy (engine->key, key, key_length);
}
/* Start the inner hash. */
status = hash_start_new_hash (hash, (enum hash_type) hash_type);
if (status != 0) {
return status;
}
/* Transform the key for the inner hash. */
for (i = 0; i < engine->block_size; i++) {
if (i < key_length) {
engine->key[i] ^= 0x36;
}
else {
engine->key[i] = 0x36;
}
}
status = hash->update (hash, engine->key, engine->block_size);
if (status != 0) {
hash->cancel (hash);
return status;
}
/* We've already hashed the inner key, so transform it for use in the outer hash. */
for (i = 0; i < engine->block_size; i++) {
engine->key[i] ^= (0x5c ^ 0x36);
}
return 0;
}
/**
* Add message data to the HMAC calculation.
*
* @param engine The HMAC engine to update with new message data.
* @param data The message data to add.
* @param length The length of the message data.
*
* @return 0 if the HMAC was successfully updated or an error code.
*/
int hash_hmac_update (struct hmac_engine *engine, const uint8_t *data, size_t length)
{
if ((engine == NULL) || (data == NULL)) {
return HASH_ENGINE_INVALID_ARGUMENT;
}
return engine->hash->update (engine->hash, data, length);
}
/**
* Get the HMAC for the message and release the HMAC operation. The HMAC operation is released
* whether or not the HMAC was successfully generated, except in the case where the HMAC buffer is
* not large enough or an input argument is not valid.
*
* @param engine The engine to get the HMAC from.
* @param hmac The buffer to hold the HMAC value.
* @param hmac_length The length of the HMAC buffer.
*
* @return 0 if the HMAC was successfully generated or an error code.
* HASH_ENGINE_HASH_BUFFER_TOO_SMALL is returned if the HMAC buffer is not large enough for the
* result.
*/
int hash_hmac_finish (struct hmac_engine *engine, uint8_t *hmac, size_t hmac_length)
{
uint8_t inner_hash[SHA512_HASH_LENGTH];
int status;
if ((engine == NULL) || (hmac == NULL)) {
return HASH_ENGINE_INVALID_ARGUMENT;
}
if (hmac_length < engine->hash_length) {
return HASH_ENGINE_HASH_BUFFER_TOO_SMALL;
}
/* Finish the inner hash. */
status = engine->hash->finish (engine->hash, inner_hash, sizeof (inner_hash));
if (status != 0) {
goto fail;
}
/* Run the outer hash. The key data for this has already been set in the context buffer. */
status = hash_start_new_hash (engine->hash, (enum hash_type) engine->type);
if (status != 0) {
goto fail;
}
status = engine->hash->update (engine->hash, engine->key, engine->block_size);
if (status != 0) {
goto fail;
}
status = engine->hash->update (engine->hash, inner_hash, engine->hash_length);
if (status != 0) {
goto fail;
}
status = engine->hash->finish (engine->hash, hmac, hmac_length);
if (status != 0) {
goto fail;
}
return 0;
fail:
engine->hash->cancel (engine->hash);
return status;
}
/**
* Cancel and release the current HMAC operation without generating the HMAC.
*
* @param engine The engine to release.
*/
void hash_hmac_cancel (struct hmac_engine *engine)
{
if (engine != NULL) {
engine->hash->cancel (engine->hash);
}
}