277 lines
6.4 KiB
C++
277 lines
6.4 KiB
C++
/*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2015 K. Isom <coder@kyleisom.net>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
|
|
|
|
#include <emsha/emsha.h>
|
|
#include <emsha/sha256.h>
|
|
#include <emsha/hmac.h>
|
|
|
|
|
|
namespace emsha {
|
|
|
|
|
|
// These constants are used to keep track of the state of the HMAC.
|
|
|
|
// HMAC is in a clean-slate state following a call to Reset().
|
|
constexpr uint8_t HMAC_INIT = 0U;
|
|
|
|
// The ipad constants have been XOR'd into the key and written to the
|
|
// SHA-256 context.
|
|
constexpr uint8_t HMAC_IPAD = 1U;
|
|
|
|
// The opad constants have been XOR'd into the key and written to the
|
|
// SHA-256 context.
|
|
constexpr uint8_t HMAC_OPAD = 2U;
|
|
|
|
// HMAC has been finalised
|
|
constexpr uint8_t HMAC_FIN = 3U;
|
|
|
|
// HMAC is in an invalid state.
|
|
constexpr uint8_t HMAC_INVALID = 4U;
|
|
|
|
|
|
static constexpr uint8_t ipad = 0x36U;
|
|
static constexpr uint8_t opad = 0x5cU;
|
|
|
|
|
|
HMAC::HMAC(const uint8_t *ik, uint32_t ikl)
|
|
: hstate(HMAC_INIT), k{0U}, buf{0U}
|
|
{
|
|
std::fill(this->k, this->k+HMAC_KEY_LENGTH, 0);
|
|
|
|
if (ikl < HMAC_KEY_LENGTH) {
|
|
for (uint32_t i = 0U; i < ikl; i++) {
|
|
this->k[i] = ik[i];
|
|
}
|
|
while (ikl < HMAC_KEY_LENGTH) {
|
|
this->k[ikl++] = 0U;
|
|
}
|
|
} else if (ikl > HMAC_KEY_LENGTH) {
|
|
if (this->ctx.Update(ik, ikl) != EMSHAResult::OK) {
|
|
this->hstate = HMAC_INVALID;
|
|
} else if (this->ctx.Result(this->k) != EMSHAResult::OK) {
|
|
this->hstate = HMAC_INVALID;
|
|
} else if (this->ctx.Reset() != EMSHAResult::OK) {
|
|
this->hstate = HMAC_INVALID;
|
|
} else {
|
|
this->hstate = HMAC_INIT;
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0U; i < ikl; i++) {
|
|
this->k[i] = ik[i];
|
|
}
|
|
}
|
|
|
|
if (this->reset() != EMSHAResult::OK) {
|
|
this->hstate = HMAC_INVALID;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* A custom destructor is needed to ensure that the key material is wiped.
|
|
*/
|
|
HMAC::~HMAC()
|
|
{
|
|
(void)this->reset();
|
|
std::fill(this->k, this->k + HMAC_KEY_LENGTH, 0);
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
HMAC::Reset()
|
|
{
|
|
return this->reset();
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
HMAC::reset()
|
|
{
|
|
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
|
|
// accordingly.
|
|
this->ctx.Reset();
|
|
std::fill(this->buf, this->buf + SHA256_HASH_SIZE, 0);
|
|
|
|
// Set up the k0 ⊕ ipad construction, and write it into the
|
|
// SHA-256 context.
|
|
uint8_t key[HMAC_KEY_LENGTH];
|
|
for (uint32_t i = 0; i < HMAC_KEY_LENGTH; i++) {
|
|
key[i] = this->k[i] ^ ipad;
|
|
}
|
|
|
|
res = this->ctx.Update(key, HMAC_KEY_LENGTH);
|
|
if (EMSHAResult::OK != res) {
|
|
this->hstate = HMAC_INVALID;
|
|
return res;
|
|
}
|
|
|
|
// This key is considered sensitive material and should be wiped.
|
|
std::fill(key, key + HMAC_KEY_LENGTH, 0);
|
|
|
|
this->hstate = HMAC_IPAD;
|
|
return EMSHAResult::OK;
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
HMAC::Update(const std::uint8_t *message, std::uint32_t messageLength)
|
|
{
|
|
EMSHAResult res;
|
|
SHA256 &hctx = this->ctx;
|
|
|
|
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(message, messageLength);
|
|
if (EMSHAResult::OK != res) {
|
|
this->hstate = HMAC_INVALID;
|
|
return res;
|
|
}
|
|
assert(HMAC_IPAD == this->hstate);
|
|
|
|
return EMSHAResult::OK;
|
|
}
|
|
|
|
|
|
inline EMSHAResult
|
|
HMAC::finalResult(uint8_t *d)
|
|
{
|
|
if (nullptr == d) {
|
|
return EMSHAResult::NullPointer;
|
|
}
|
|
|
|
// If the HMAC has already been finalised, skip straight to
|
|
// copying the result.
|
|
if (this->hstate == HMAC_FIN) {
|
|
std::copy(this->buf, this->buf+SHA256_HASH_SIZE, d);
|
|
|
|
return EMSHAResult::OK;
|
|
}
|
|
|
|
EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHAResult::InvalidState);
|
|
|
|
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 (EMSHAResult::OK != res) {
|
|
this->hstate = HMAC_INVALID;
|
|
return EMSHAResult::InvalidState;
|
|
}
|
|
assert(HMAC_IPAD == this->hstate);
|
|
|
|
// The SHA-256 context needs to be reset so that it may be
|
|
// re-used for the outer digest.
|
|
this->ctx.Reset();
|
|
|
|
// Set up the k0 ⊕ opad construction, and write it into the
|
|
// SHA-256 context.
|
|
uint8_t key[HMAC_KEY_LENGTH];
|
|
for (uint32_t i = 0; i < HMAC_KEY_LENGTH; i++) {
|
|
key[i] = this->k[i] ^ opad;
|
|
}
|
|
|
|
res = this->ctx.Update(key, HMAC_KEY_LENGTH);
|
|
if (EMSHAResult::OK != res) {
|
|
this->hstate = HMAC_INVALID;
|
|
return res;
|
|
}
|
|
this->hstate = HMAC_OPAD;
|
|
|
|
// This key is considered sensitive material and should be wiped.
|
|
std::fill(key, key + HMAC_KEY_LENGTH, 0);
|
|
|
|
// Write the inner hash result into the outer hash.
|
|
res = this->ctx.Update(this->buf, SHA256_HASH_SIZE);
|
|
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 (EMSHAResult::OK != res) {
|
|
this->hstate = HMAC_INVALID;
|
|
return res;
|
|
}
|
|
assert(HMAC_OPAD == this->hstate);
|
|
|
|
std::copy(this->buf, this->buf + SHA256_HASH_SIZE, d);
|
|
this->hstate = HMAC_FIN;
|
|
return EMSHAResult::OK;
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
HMAC::Finalise(std::uint8_t *digest)
|
|
{
|
|
return this->finalResult(digest);
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
HMAC::Result(std::uint8_t *digest)
|
|
{
|
|
return this->finalResult(digest);
|
|
}
|
|
|
|
|
|
std::uint32_t
|
|
HMAC::Size()
|
|
{
|
|
return SHA256_HASH_SIZE;
|
|
}
|
|
|
|
|
|
EMSHAResult
|
|
ComputeHMAC(const uint8_t *k, const uint32_t kl,
|
|
const uint8_t *m, const uint32_t ml,
|
|
uint8_t *d)
|
|
{
|
|
EMSHAResult res;
|
|
HMAC h(k, kl);
|
|
|
|
res = h.Update(m, ml);
|
|
if (res == EMSHAResult::OK) {
|
|
res = h.Result(d);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
} // end of namespace emsha
|
|
|