Skip to content
Snippets Groups Projects
hashlib.c 19.40 KiB
/*******************************************************************************
   hashlib.c

   Management wrappers for OpenSSL
   - Uses OpenSSL libraries to provide standard hash functions

   Author: brian.monahan@hpe.com
      
   (c) Copyright 2017 Hewlett Packard Enterprise Development LP 

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are
   met: 

   1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer. 

   2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution. 

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
   IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

*******************************************************************************/
#include <openssl/evp.h>

#include "utils.h"
#include "bytevector.h"
#include "stringbuffer.h"
#include "alloc.h"

#include "hashlib.h"


/*******************************************************************************
   Hash Digest
*******************************************************************************/
// The hashDigest object (Digest_t) encapsulates all infomation needed to coordinate
// hashing operations.
struct hashDigest {
   ByteVec_t       *result;      // Resulting hash-digest (as a bytevector)
   int              bytesHashed; // Number of bytes hashed
   HashSpec_t       hashSpec;    // Hash specification - e.g. sha256 etc.
   Hashstate_t      hashState;   // Phase of hash operations.

   // OpenSSL components
   const EVP_MD  *md;            // Internal OpenSSL message digest specifier
   EVP_MD_CTX    *mdctx;         // Internal OpenSSL message digest content
};


// Static method prototypes
static unsigned int digestBytes(const EVP_MD *md, int length, Byte_t *bytes, Byte_t *digest);
static EVP_MD_CTX *contextInit(const EVP_MD *md);
static Boolean_t processBlock(EVP_MD_CTX *mdctx, int length, Byte_t *bytes);
static unsigned int digestFinal(EVP_MD_CTX *mdctx, Byte_t *digest);

static Boolean_t isHashSpec(HashSpec_t hSpec);

static const EVP_MD *getMD(HashSpec_t hSpec);
static void ensureMemMgmt();
static void dispose_DG(void *obj);


// hashDigest Memory Management
MemMgr_t *hashDigest_MemMgr = NULL;


// Allocates a hash digest object
Digest_t *new_DG(HashSpec_t hSpec) {
   if (!isHashSpec(hSpec)) {
      diagnostic("hashlib.new_DG: Expected a HashSpec value - instead, got (%i)", hSpec);
      codeError_exit();
   }

	// Ensure that the allocation structure has been setup.
	ensureMemMgmt();

	// allocate digest object
	Digest_t *dgst = allocateObject_MM(hashDigest_MemMgr);

	dgst->result = allocate_BV(0);  // allocate byte vector (no local memory)
	dgst->bytesHashed = 0;
	dgst->hashSpec = hSpec;
	dgst->hashState = HST_READY;

	dgst->md = getMD(hSpec);  // message digest info. corresponding to hSpec
	dgst->mdctx = NULL;       // message digest context

   // return allocated digest object
   return dgst;
}


// Deallocates hash digest object
void deallocate_DG(Digest_t *dgst) {
	if (dgst == NULL) return;

	// Ensure that the allocation structure has been setup.
	ensureMemMgmt();

	// deallocate the byte vector ...
	deallocate_BV(dgst->result);

	// check for any EVP_MD_CTX object
   if (dgst->mdctx != NULL) {
      // releases any EVP_MD_CTX object
      EVP_MD_CTX_destroy(dgst->mdctx);
      dgst->md = NULL;
      dgst->mdctx = NULL;
   }

   // recycle the digest object on the allocation structure
   deallocateObject_MM(hashDigest_MemMgr, sizeof(Digest_t), dgst);
}


// Resets hash digest object
// - no allocation performed ...
// - results in a digest object in ready state.
// - if hSpec == 0, then reuse existing hSpec value.
void reset_DG(Digest_t *dgst, HashSpec_t hSpec) {
   req_NonNull(dgst);

   if (hSpec == 0) {
      hSpec = dgst->hashSpec;
   }

   if (!isHashSpec(hSpec)) {
      diagnostic("hashlib.new_DG: Expected a HashSpec value - instead, got (%i)", hSpec);
      codeError_exit();
   }

	// check for any EVP_MD_CTX object
   if (dgst->mdctx != NULL) {
      // releases EVP_MD_CTX object
      EVP_MD_CTX_destroy(dgst->mdctx);
      dgst->md = NULL;
      dgst->mdctx = NULL;
   }

   // reinitialise dgst
   reset_BV(dgst->result);

	dgst->bytesHashed = 0;
	dgst->hashSpec = hSpec;
	dgst->hashState = HST_READY;

	dgst->md = getMD(hSpec);  // gets the message digest info. corresponding to hSpec
	dgst->mdctx = NULL;
}


// Gets the number of bytes hashed so far
int getBytesHashed_DG(Digest_t *dgst) {
   req_NonNull(dgst);

   return dgst->bytesHashed;
}


// Gets the current hash spec value.
HashSpec_t getHashSpec_DG(Digest_t *dgst) {
   req_NonNull(dgst);

   return dgst->hashSpec;
}

// Extracts nominal string of the hash spec value
const char *showHashSpec_DG(HashSpec_t hSpec) {
   switch (hSpec) {
      case HSP_SHA224:      return "SHA-224";
      case HSP_SHA256:      return "SHA-256";
      case HSP_SHA384:      return "SHA-384";
      case HSP_SHA512:      return "SHA-512";
      case HSP_RIPEMD160:   return "RIPEMD-160";

      default:
         diagnostic("hashlib.showHashSpec_DG: Unrecognised hash-spec (%i)", hSpec);
         codeError_exit();
   }
}


// Digest length (in bytes)
int getDigestLength_DG(HashSpec_t hSpec) {
   return ((hSpec % 1000) / 8);
}


// Extracts the (binary) hash value from the current digest and appends to the given bytevector ...
// - The digest must have already been finalised.
// - The result is TRUE if the digest hash value was appended.
Boolean_t getHashValue_DG(Digest_t *dgst, ByteVec_t *resultBV) {
   req_NonNull(dgst);
   req_NonNull(resultBV);

   if (dgst->hashState != HST_FINAL) {
      return FALSE;
   }
   else {
      appendInto_BV(resultBV, dgst->result);

      return TRUE;
   }
}


// Sets the (binary) hash value of the digest from the given bytevector ...
// - The length of the (binary) data must exactly match the required digest size.
// - THe digest state should be Ready or Init
// - If successful, the digest state becomes Final.
// - The result is TRUE only if the digest hash value was successfully set.
Boolean_t setHashValue_DG(Digest_t *dgst, ByteVec_t *sourceBV) {
   req_NonNull(dgst);
   req_NonNull(sourceBV);

   int srcLength   =  getLength_BV(sourceBV);
   int digestSize  =  getDigestLength_DG(dgst->hashSpec);

   if (srcLength != digestSize) {
      diagnostic("hashlib.setHashValue_DG: Bytevector provided %i bytes, but expected %i bytes instead", srcLength, digestSize);
      codeError_exit();
   }

   if (dgst->hashState != HST_INIT && dgst->hashState != HST_READY) {

      return FALSE;
   }
   else {
      wipe_BV(dgst->result);
      appendInto_BV(dgst->result, sourceBV);

      dgst->hashState = HST_FINAL;

      return TRUE;
   }
}


// Checks that the digests are equal in value ...
// - Both must be as _equally_ defined i.e. both = NULL or both != NULL
Boolean_t isEqual_DG(Digest_t *dgst1, Digest_t *dgst2) {
   if (dgst1 == NULL) {
      return (dgst2 == NULL);
   }
   else if (dgst2 == NULL) {
      // i.e. dgst1 != NULL and dgst2 == NULL
      return FALSE;
   }

   // dgst1 != NULL and dgst2 != NULL
   return isEqual_BV(dgst1->result, dgst2->result);
}


// Clones a digest value from source digest to destination digest.
// - the source digest state should be final.
// - Destination digest state becomes final.
// - Result is false if no data transferred - otherwise true.
Boolean_t clone_DG(Digest_t *dest, Digest_t *source) {
   req_NonNull(dest);
   req_NonNull(source);

   if (source->hashState != HST_FINAL) {
      return FALSE;
   }
   else {
      clone_BV(dest->result, source->result);
      dest->hashState = HST_FINAL;
      return TRUE;
   }
}


// Extracts a hex string of the hash value
// - the output string is volatile (i.e. could change ...)
static char hashBuffer[2 * MAX_DIGEST_SIZE + 1];
char *showHexHashValue_DG(Digest_t *dgst) {
   if (dgst == NULL) return NULL_STR;

   char *sPtr = hashBuffer;

   int len       = getLength_BV(dgst->result);
   Byte_t *bytes = getContent_BV(dgst->result);

   // check for null content ...
   if (bytes == NULL) {
      return NULL_STR;
   }

   // pack content as hex into hashBuffer
   for(int i = 0; i < len; i++) {
      sprintf(sPtr, "%02x", bytes[i]);
      sPtr += 2;
   }

   return hashBuffer;
}


// Shows a short fingerprint of the hash value
// - the output string is volatile
// - 4 <= fpLength <= 16, with default value: 7
// - the output string is volatile
char *showFingerprint_DG(Digest_t *dgst, int fpLength) {
   if (dgst == NULL) return NULL_STR;

   return showFingerprint_BV(dgst->result, fpLength);
}


// Extracts the current state
// - getHashState_DG produces current state value
// - showHashState_DG produces a print string
Hashstate_t getHashState_DG(Digest_t *dgst) {
   req_NonNull(dgst);

   return dgst->hashState;
}

char *showHashState_DG(Digest_t *dgst) {
   switch (dgst->hashState) {
      case HST_READY:    return "Ready";
      case HST_INIT:     return "Initialised";
      case HST_PROCESS:  return "Processing";
      case HST_FINAL:    return "Finalised";

      default:
         return "<Unknown hash-state>";
   }
}


// Hash an entire byte vector in a single step.
// - The digest state should be ready.
// - Hashes the input data and computes the final hashvalue
// - The hashValue is placed into the Digest_t object.
// - The result indicates if the hash operation was successful.
//   + If successful, the hash state becomes HST_FINAL.
//   + Otherwise, remains unchanged.
Boolean_t hashBV_DG(Digest_t *dgst, ByteVec_t *dataInput) {

   req_NonNull(dataInput);

   Hashstate_t hState = dgst->hashState;
   if (hState != HST_READY) {
      return FALSE;
   }

   int  dataLen = getLength_BV(dataInput);
   Byte_t *data = getContent_BV(dataInput);

   // Ensure that the dgst result has sufficient space for digest result
   ensureCapacity_BV(dgst->result, MAX_DIGEST_SIZE+1);

   // calculate the hash value from given bytes ...
   int mdLen = digestBytes(dgst->md, dataLen, data, getContent_BV(dgst->result));

   if (mdLen > 0) {
      // set the length of the bytevector ...
      setLength_BV(dgst->result, mdLen);

      // set the bytesHashed to dataLen
      dgst->bytesHashed = dataLen;

      // set hashState
      dgst->hashState = HST_FINAL;
      return TRUE;
   }
   else {
      // An error occurred ...

      // Reset the digest result vector ...
      reset_BV(dgst->result);
      return FALSE;
   }
}


/*******************************************************************************
   Hashing sequences of data blocks ...
   - The data blocks are themselves wrapped within bytevectors.
   - These do not "fail"/"abort" - instead, status is reported by return value.
*******************************************************************************/

// Initialise the Digest_t object ready to hash a sequence of data blocks.
// - the hashstate starts in HST_READY and ends in HST_INIT
Boolean_t hashInit_DG(Digest_t *dgst) {
   req_NonNull(dgst);

   // check hash state  ...
   if (dgst->hashState != HST_READY) {
      return FALSE;
   }

   // create and initialise the context ...
   dgst->mdctx = contextInit(dgst->md);

   // set hash state
   dgst->hashState = HST_INIT;

   return TRUE;
}


// Add a data block byte vector to current digest.
// - The hashstate starts in either state HST_INIT or HST_PROCESS and transitions to HST_PROCESS
Boolean_t addOneBlock_DG(Digest_t *dgst, ByteVec_t *dataInput) {
   req_NonNull(dgst);
   req_NonNull(dataInput);

   // check hash state  ..
   if (dgst->hashState != HST_INIT && dgst->hashState != HST_PROCESS) {
      return FALSE;
   }

   int  dataLen = getLength_BV(dataInput);
   Byte_t *data = getContent_BV(dataInput);

   // Ensure that the dgst result has sufficient space for digest result
   ensureCapacity_BV(dgst->result, MAX_DIGEST_SIZE+1);

   // calculate the hash value ...
   int mdLen = digestBytes(dgst->md, dataLen, data, getContent_BV(dgst->result));

   if (mdLen > 0) {
      // set the length of the bytevector ...
      setLength_BV(dgst->result, mdLen);

      // add dataLen to the bytesHashed
      dgst->bytesHashed += dataLen;

      // set hashState
      dgst->hashState = HST_PROCESS;
      return TRUE;
   }
   else {
      // An error occurred ...

      // wipe the result ...
      reset_BV(dgst->result);

      return FALSE;
   }
}


// Hashes the given sequence of data blocks, where each data block is
// contained in a bytevector.
// - The hashstate starts in either state HST_INIT or HST_PROCESS and transitions to HST_PROCESS
Boolean_t hashBlocks_DG(Digest_t *dgst, int length, ByteVec_t *blocks[]) {
   req_NonNull(dgst);

   // check hash state  ...
   if (dgst->hashState != HST_INIT && dgst->hashState != HST_PROCESS) {
      return FALSE;
   }

   ByteVec_t *curVec = NULL;
   int dataLen = 0;

   for (int i = 0; i<length; i++) {
      curVec = blocks[i];

      if (curVec != NULL) {
         dataLen = getLength_BV(curVec);

         if (dataLen > 0) {
            if (processBlock(dgst->mdctx, dataLen, getContent_BV(curVec))) {
               dgst->bytesHashed += dataLen;
            }
            else {

            }
         }
      }
   }

   // set hash state
   dgst->hashState = HST_PROCESS;

   return TRUE;
}

// Finalise the current sequence ...
// - The final hash-value is computed.
// - The hashstate should be HST_PROCESS and transitions to HST_FINAL.
Boolean_t hashFinal_DG(Digest_t *dgst) {
   req_NonNull(dgst);

   if (dgst->hashState == HST_FINAL) {
      return TRUE;
   }

   // check hash state  ...
   if (dgst->hashState != HST_PROCESS) {
      return FALSE;
   }

   // extract the hash value ...
   int mdLen = digestFinal(dgst->mdctx, getContent_BV(dgst->result));

   if (mdLen > 0) {
      // set the length of the bytevector ...
      setLength_BV(dgst->result, mdLen);

      // set hashState
      dgst->hashState = HST_FINAL;
      return TRUE;
   }
   else {
      // An error occurred ...

      // wipe the result ...
      reset_BV(dgst->result);

      // set hashState
      dgst->hashState = HST_FINAL;

      return FALSE;
   }
}


/*******************************************************************************l
   Wrappers for standard hash digest calc. using OpenSSL digest algorithms
*******************************************************************************/

// Single-shot hashing of a single block of data
static unsigned int digestBytes(const EVP_MD *md, int length, Byte_t *bytes, Byte_t *digest) {
   req_NonNull(digest);

   EVP_MD_CTX *mdctx = contextInit(md);

   // Hash the data ...
   if (1 != EVP_DigestUpdate(mdctx, bytes, length)) {
      EVP_MD_CTX_destroy(mdctx);

      // ensure safe digest value
      digest[0] = 0;
      return 0;
   }

   return digestFinal(mdctx, digest);
}


// Generate and initialise the digest
static EVP_MD_CTX *contextInit(const EVP_MD *md) {
   EVP_MD_CTX *mdctx;

   // Set up digest context
   mdctx = EVP_MD_CTX_create();

   // Bind message digest to context
   EVP_DigestInit_ex(mdctx, md, NULL);

   // return the context
   return mdctx;
}


static Boolean_t processBlock(EVP_MD_CTX *mdctx, int length, Byte_t *bytes) {
   return asBoolean( EVP_DigestUpdate(mdctx, bytes, length) );
}


static unsigned int digestFinal(EVP_MD_CTX *mdctx, Byte_t *digest) {
   unsigned int md_len = 0;

   // Finalise
   EVP_DigestFinal_ex(mdctx, digest, &md_len);
   EVP_MD_CTX_destroy(mdctx);

   // return the length
   return md_len;
}


/*******************************************************************************
   Auxilliaries
*******************************************************************************/
static Boolean_t isHashSpec(HashSpec_t hSpec) {
   switch (hSpec) {
      case HSP_SHA224:      // FALLTHROUGH
      case HSP_SHA256:      // FALLTHROUGH
      case HSP_SHA384:      // FALLTHROUGH
      case HSP_SHA512:      // FALLTHROUGH
      case HSP_RIPEMD160:   return TRUE;

      default:
         return FALSE;
   }
}


static const EVP_MD *getMD(HashSpec_t hSpec) {
   switch (hSpec) {
      case HSP_SHA224:      return EVP_sha224();
      case HSP_SHA256:      return EVP_sha256();
      case HSP_SHA384:      return EVP_sha384();
      case HSP_SHA512:      return EVP_sha512();
      case HSP_RIPEMD160:   return EVP_ripemd160();

      default:
         diagnostic("hashlib.getMD: Unrecognised hash-spec (%i)", hSpec);
         codeError_exit();
   }
}

// setup allocation structure
static void ensureMemMgmt() {
   if (hashDigest_MemMgr == NULL) {
      hashDigest_MemMgr = new_MM(sizeof(Digest_t));
      setFinaliser_MM(hashDigest_MemMgr, dispose_DG);
   }
}

// disposal of digest memory ...
static void dispose_DG(void *obj) {
   if (obj == NULL) return;

   Digest_t *dgst = (Digest_t *)obj;

   if (dgst->mdctx != NULL) {
      // releases the EVP_MD_CTX objects
      EVP_MD_CTX_destroy(dgst->mdctx);
      dgst->mdctx = NULL;
   }

   // free the Digest_t object itself.
   free(dgst);
}


/*

//unsigned int digestBlocks(const EVP_MD *md, Byte_t *blocks[], int maxBlocks, int totalChars, Byte_t *digest);
//static int calcTotalBlocks(Byte_t *blocks[], int maxBlocks);

// Single-shot hash processing
unsigned int digestBytes(const EVP_MD *md, ByteVec_t *bytes, Byte_t *md_value) {
   EVP_MD_CTX *mdctx;
   unsigned int md_len = 0;

   // Set up context
   mdctx = EVP_MD_CTX_create();

   // Bind message digest to context
   EVP_DigestInit_ex(mdctx, md, NULL);

   // Hash the data
   EVP_DigestUpdate(mdctx, getContent_BV(bytes), getLength_BV(bytes));

   // Finalise
   EVP_DigestFinal_ex(mdctx, md_value, &md_len);
   EVP_MD_CTX_destroy(mdctx);

   return md_len;
}


// Processes a seqeunce of blocks (same size = BLOCKSIZE, except possibly the last one)
unsigned int digestBlocks(const EVP_MD *md, Byte_t *blocks[], int maxBlocks, int totalChars, Byte_t *md_value) {
   EVP_MD_CTX *mdctx;
   unsigned int md_len = 0;

   int totalBlocks = calcTotalBlocks(blocks, maxBlocks);

   size_t charsRemaining = totalChars;
   size_t thisBlkSz = 0;
   void *blockPtr  = NULL;  // current ptr to data block

   // Set up context
   mdctx = EVP_MD_CTX_create();

   // Bind message digest to context
   EVP_DigestInit_ex(mdctx, md, NULL);

   // hash all blocks
   // - assumes that all but the last block has size BLOCKSIZE.
   for (int posn = 0; posn < totalBlocks && charsRemaining > 0; posn++) {
      blockPtr = (void *)blocks[posn];
      thisBlkSz = (charsRemaining <= BLOCKSIZE ? charsRemaining : BLOCKSIZE);
      EVP_DigestUpdate(mdctx, blockPtr, thisBlkSz);
      charsRemaining -= thisBlkSz;
   }

   // Finalise
   EVP_DigestFinal_ex(mdctx, md_value, &md_len);
   EVP_MD_CTX_destroy(mdctx);

   return md_len;
}


static int calcTotalBlocks(Byte_t *blocks[], int maxBlocks) {
// count the number of non-null blocks
   int totalBlocks = 0;
   for (int i = 0; i < maxBlocks; i++) {
      if (blocks[i] != NULL) {
         totalBlocks++;
      }
   }

   return totalBlocks;
}
*/