crypto/s2n_hash.c (304 lines of code) (raw):
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include "crypto/s2n_hash.h"
#include "crypto/s2n_fips.h"
#include "crypto/s2n_hmac.h"
#include "error/s2n_errno.h"
#include "utils/s2n_safety.h"
#if S2N_LIBCRYPTO_SUPPORTS_PROVIDERS
static EVP_MD *s2n_evp_mds[S2N_HASH_ALGS_COUNT] = { 0 };
#else
static const EVP_MD *s2n_evp_mds[S2N_HASH_ALGS_COUNT] = { 0 };
#endif
bool s2n_hash_use_custom_md5_sha1()
{
#if defined(S2N_LIBCRYPTO_SUPPORTS_EVP_MD5_SHA1_HASH)
return false;
#else
return true;
#endif
}
S2N_RESULT s2n_hash_algorithms_init()
{
#if S2N_LIBCRYPTO_SUPPORTS_PROVIDERS
/* openssl-3.0 introduced the concept of providers.
* After openssl-3.0, the old EVP_sha256()-style methods will still work,
* but may be inefficient. See
* https://docs.openssl.org/3.4/man7/ossl-guide-libcrypto-introduction/#performance
*
* Additionally, the old style methods do not support property query strings
* to guide which provider to fetch from. This is important for FIPS, where
* the default query string of "fips=yes" will need to be overridden for
* legacy algorithms.
*/
s2n_evp_mds[S2N_HASH_MD5] = EVP_MD_fetch(NULL, "MD5", "-fips");
s2n_evp_mds[S2N_HASH_MD5_SHA1] = EVP_MD_fetch(NULL, "MD5-SHA1", "-fips");
s2n_evp_mds[S2N_HASH_SHA1] = EVP_MD_fetch(NULL, "SHA1", NULL);
s2n_evp_mds[S2N_HASH_SHA224] = EVP_MD_fetch(NULL, "SHA224", NULL);
s2n_evp_mds[S2N_HASH_SHA256] = EVP_MD_fetch(NULL, "SHA256", NULL);
s2n_evp_mds[S2N_HASH_SHA384] = EVP_MD_fetch(NULL, "SHA384", NULL);
s2n_evp_mds[S2N_HASH_SHA512] = EVP_MD_fetch(NULL, "SHA512", NULL);
#else
s2n_evp_mds[S2N_HASH_MD5] = EVP_md5();
s2n_evp_mds[S2N_HASH_SHA1] = EVP_sha1();
s2n_evp_mds[S2N_HASH_SHA224] = EVP_sha224();
s2n_evp_mds[S2N_HASH_SHA256] = EVP_sha256();
s2n_evp_mds[S2N_HASH_SHA384] = EVP_sha384();
s2n_evp_mds[S2N_HASH_SHA512] = EVP_sha512();
/* Very old libcryptos like openssl-1.0.2 do not support EVP_MD_md5_sha1().
* We work around that by manually combining MD5 and SHA1, rather than
* using the composite algorithm.
*/
#if defined(S2N_LIBCRYPTO_SUPPORTS_EVP_MD5_SHA1_HASH)
s2n_evp_mds[S2N_HASH_MD5_SHA1] = EVP_md5_sha1();
#endif
#endif
return S2N_RESULT_OK;
}
S2N_RESULT s2n_hash_algorithms_cleanup()
{
#if S2N_LIBCRYPTO_SUPPORTS_PROVIDERS
for (size_t i = 0; i < S2N_HASH_ALGS_COUNT; i++) {
/* https://docs.openssl.org/3.4/man3/EVP_DigestInit/
* > Decrements the reference count for the fetched EVP_MD structure.
* > If the reference count drops to 0 then the structure is freed.
* > If the argument is NULL, nothing is done.
*/
EVP_MD_free(s2n_evp_mds[i]);
s2n_evp_mds[i] = NULL;
}
#endif
return S2N_RESULT_OK;
}
const EVP_MD *s2n_hash_alg_to_evp_md(s2n_hash_algorithm alg)
{
PTR_ENSURE_GTE(alg, 0);
PTR_ENSURE_LT(alg, S2N_HASH_ALGS_COUNT);
return s2n_evp_mds[alg];
}
int s2n_hash_digest_size(s2n_hash_algorithm alg, uint8_t *out)
{
POSIX_ENSURE(S2N_MEM_IS_WRITABLE_CHECK(out, sizeof(*out)), S2N_ERR_PRECONDITION_VIOLATION);
/* clang-format off */
switch (alg) {
case S2N_HASH_NONE: *out = 0; break;
case S2N_HASH_MD5: *out = MD5_DIGEST_LENGTH; break;
case S2N_HASH_SHA1: *out = SHA_DIGEST_LENGTH; break;
case S2N_HASH_SHA224: *out = SHA224_DIGEST_LENGTH; break;
case S2N_HASH_SHA256: *out = SHA256_DIGEST_LENGTH; break;
case S2N_HASH_SHA384: *out = SHA384_DIGEST_LENGTH; break;
case S2N_HASH_SHA512: *out = SHA512_DIGEST_LENGTH; break;
case S2N_HASH_MD5_SHA1: *out = MD5_DIGEST_LENGTH + SHA_DIGEST_LENGTH; break;
default:
POSIX_BAIL(S2N_ERR_HASH_INVALID_ALGORITHM);
}
/* clang-format on */
return S2N_SUCCESS;
}
/* Return true if hash algorithm is available, false otherwise. */
bool s2n_hash_is_available(s2n_hash_algorithm alg)
{
switch (alg) {
case S2N_HASH_MD5:
case S2N_HASH_MD5_SHA1:
case S2N_HASH_NONE:
case S2N_HASH_SHA1:
case S2N_HASH_SHA224:
case S2N_HASH_SHA256:
case S2N_HASH_SHA384:
case S2N_HASH_SHA512:
return true;
case S2N_HASH_ALGS_COUNT:
return false;
}
return false;
}
int s2n_hash_is_ready_for_input(struct s2n_hash_state *state)
{
POSIX_PRECONDITION(s2n_hash_state_validate(state));
return state->is_ready_for_input;
}
static int s2n_evp_hash_new(struct s2n_hash_state *state)
{
POSIX_ENSURE_REF(state->digest.high_level.evp.ctx = S2N_EVP_MD_CTX_NEW());
if (s2n_hash_use_custom_md5_sha1()) {
POSIX_ENSURE_REF(state->digest.high_level.evp_md5_secondary.ctx = S2N_EVP_MD_CTX_NEW());
}
return S2N_SUCCESS;
}
static int s2n_evp_hash_init(struct s2n_hash_state *state, s2n_hash_algorithm alg)
{
POSIX_ENSURE_REF(state->digest.high_level.evp.ctx);
if (alg == S2N_HASH_NONE) {
return S2N_SUCCESS;
}
if (alg == S2N_HASH_MD5_SHA1 && s2n_hash_use_custom_md5_sha1()) {
POSIX_ENSURE_REF(state->digest.high_level.evp_md5_secondary.ctx);
POSIX_GUARD_OSSL(EVP_DigestInit_ex(state->digest.high_level.evp.ctx,
s2n_hash_alg_to_evp_md(S2N_HASH_SHA1), NULL),
S2N_ERR_HASH_INIT_FAILED);
POSIX_GUARD_OSSL(EVP_DigestInit_ex(state->digest.high_level.evp_md5_secondary.ctx,
s2n_hash_alg_to_evp_md(S2N_HASH_MD5), NULL),
S2N_ERR_HASH_INIT_FAILED);
return S2N_SUCCESS;
}
const EVP_MD *md = s2n_hash_alg_to_evp_md(alg);
POSIX_ENSURE(md, S2N_ERR_HASH_INVALID_ALGORITHM);
POSIX_GUARD_OSSL(EVP_DigestInit_ex(state->digest.high_level.evp.ctx, md, NULL),
S2N_ERR_HASH_INIT_FAILED);
return S2N_SUCCESS;
}
static int s2n_evp_hash_update(struct s2n_hash_state *state, const void *data, uint32_t size)
{
if (state->alg == S2N_HASH_NONE) {
return S2N_SUCCESS;
}
POSIX_ENSURE_REF(EVP_MD_CTX_md(state->digest.high_level.evp.ctx));
POSIX_GUARD_OSSL(EVP_DigestUpdate(state->digest.high_level.evp.ctx, data, size), S2N_ERR_HASH_UPDATE_FAILED);
if (state->alg == S2N_HASH_MD5_SHA1 && s2n_hash_use_custom_md5_sha1()) {
POSIX_ENSURE_REF(EVP_MD_CTX_md(state->digest.high_level.evp_md5_secondary.ctx));
POSIX_GUARD_OSSL(EVP_DigestUpdate(state->digest.high_level.evp_md5_secondary.ctx, data, size), S2N_ERR_HASH_UPDATE_FAILED);
}
return S2N_SUCCESS;
}
static int s2n_evp_hash_digest(struct s2n_hash_state *state, void *out, uint32_t size)
{
unsigned int digest_size = size;
uint8_t expected_digest_size = 0;
POSIX_GUARD(s2n_hash_digest_size(state->alg, &expected_digest_size));
POSIX_ENSURE_EQ(digest_size, expected_digest_size);
if (state->alg == S2N_HASH_NONE) {
return S2N_SUCCESS;
}
POSIX_ENSURE_REF(EVP_MD_CTX_md(state->digest.high_level.evp.ctx));
if (state->alg == S2N_HASH_MD5_SHA1 && s2n_hash_use_custom_md5_sha1()) {
POSIX_ENSURE_REF(EVP_MD_CTX_md(state->digest.high_level.evp_md5_secondary.ctx));
uint8_t sha1_digest_size = 0;
POSIX_GUARD(s2n_hash_digest_size(S2N_HASH_SHA1, &sha1_digest_size));
unsigned int sha1_primary_digest_size = sha1_digest_size;
unsigned int md5_secondary_digest_size = digest_size - sha1_primary_digest_size;
POSIX_ENSURE(EVP_MD_CTX_size(state->digest.high_level.evp.ctx) <= sha1_digest_size, S2N_ERR_HASH_DIGEST_FAILED);
POSIX_ENSURE((size_t) EVP_MD_CTX_size(state->digest.high_level.evp_md5_secondary.ctx) <= md5_secondary_digest_size, S2N_ERR_HASH_DIGEST_FAILED);
POSIX_GUARD_OSSL(EVP_DigestFinal_ex(state->digest.high_level.evp.ctx, ((uint8_t *) out) + MD5_DIGEST_LENGTH, &sha1_primary_digest_size), S2N_ERR_HASH_DIGEST_FAILED);
POSIX_GUARD_OSSL(EVP_DigestFinal_ex(state->digest.high_level.evp_md5_secondary.ctx, out, &md5_secondary_digest_size), S2N_ERR_HASH_DIGEST_FAILED);
return S2N_SUCCESS;
}
POSIX_ENSURE((size_t) EVP_MD_CTX_size(state->digest.high_level.evp.ctx) <= digest_size, S2N_ERR_HASH_DIGEST_FAILED);
POSIX_GUARD_OSSL(EVP_DigestFinal_ex(state->digest.high_level.evp.ctx, out, &digest_size), S2N_ERR_HASH_DIGEST_FAILED);
return S2N_SUCCESS;
}
static int s2n_evp_hash_copy(struct s2n_hash_state *to, struct s2n_hash_state *from)
{
if (from->alg == S2N_HASH_NONE) {
return S2N_SUCCESS;
}
POSIX_ENSURE_REF(to->digest.high_level.evp.ctx);
POSIX_GUARD_OSSL(EVP_MD_CTX_copy_ex(to->digest.high_level.evp.ctx, from->digest.high_level.evp.ctx), S2N_ERR_HASH_COPY_FAILED);
if (from->alg == S2N_HASH_MD5_SHA1 && s2n_hash_use_custom_md5_sha1()) {
POSIX_ENSURE_REF(to->digest.high_level.evp_md5_secondary.ctx);
POSIX_GUARD_OSSL(EVP_MD_CTX_copy_ex(to->digest.high_level.evp_md5_secondary.ctx, from->digest.high_level.evp_md5_secondary.ctx), S2N_ERR_HASH_COPY_FAILED);
}
return S2N_SUCCESS;
}
static int s2n_evp_hash_reset(struct s2n_hash_state *state)
{
POSIX_GUARD_OSSL(S2N_EVP_MD_CTX_RESET(state->digest.high_level.evp.ctx), S2N_ERR_HASH_WIPE_FAILED);
if (state->alg == S2N_HASH_MD5_SHA1 && s2n_hash_use_custom_md5_sha1()) {
POSIX_GUARD_OSSL(S2N_EVP_MD_CTX_RESET(state->digest.high_level.evp_md5_secondary.ctx), S2N_ERR_HASH_WIPE_FAILED);
}
return S2N_SUCCESS;
}
static int s2n_evp_hash_free(struct s2n_hash_state *state)
{
S2N_EVP_MD_CTX_FREE(state->digest.high_level.evp.ctx);
state->digest.high_level.evp.ctx = NULL;
if (s2n_hash_use_custom_md5_sha1()) {
S2N_EVP_MD_CTX_FREE(state->digest.high_level.evp_md5_secondary.ctx);
state->digest.high_level.evp_md5_secondary.ctx = NULL;
}
return S2N_SUCCESS;
}
static const struct s2n_hash s2n_evp_hash = {
.alloc = &s2n_evp_hash_new,
.init = &s2n_evp_hash_init,
.update = &s2n_evp_hash_update,
.digest = &s2n_evp_hash_digest,
.copy = &s2n_evp_hash_copy,
.reset = &s2n_evp_hash_reset,
.free = &s2n_evp_hash_free,
};
/* This method looks unnecessary, but our CBMC proofs are
* dependent on it. Search for:
* __CPROVER_file_local_s2n_hash_c_s2n_hash_set_evp_impl
*/
static void s2n_hash_set_evp_impl(struct s2n_hash_state *state)
{
state->hash_impl = &s2n_evp_hash;
}
int s2n_hash_new(struct s2n_hash_state *state)
{
POSIX_ENSURE_REF(state);
s2n_hash_set_evp_impl(state);
POSIX_ENSURE_REF(state->hash_impl->alloc);
POSIX_GUARD(state->hash_impl->alloc(state));
state->alg = S2N_HASH_NONE;
state->is_ready_for_input = 0;
state->currently_in_hash = 0;
return S2N_SUCCESS;
}
S2N_RESULT s2n_hash_state_validate(struct s2n_hash_state *state)
{
RESULT_ENSURE_REF(state);
return S2N_RESULT_OK;
}
int s2n_hash_init(struct s2n_hash_state *state, s2n_hash_algorithm alg)
{
POSIX_ENSURE_REF(state);
POSIX_ENSURE(s2n_hash_is_available(alg), S2N_ERR_HASH_INVALID_ALGORITHM);
POSIX_ENSURE_REF(state->hash_impl);
POSIX_ENSURE_REF(state->hash_impl->init);
POSIX_GUARD(state->hash_impl->init(state, alg));
state->alg = alg;
state->is_ready_for_input = 1;
state->currently_in_hash = 0;
return S2N_SUCCESS;
}
int s2n_hash_update(struct s2n_hash_state *state, const void *data, uint32_t size)
{
POSIX_PRECONDITION(s2n_hash_state_validate(state));
POSIX_ENSURE(S2N_MEM_IS_READABLE(data, size), S2N_ERR_PRECONDITION_VIOLATION);
POSIX_ENSURE(state->is_ready_for_input, S2N_ERR_HASH_NOT_READY);
POSIX_ENSURE_REF(state->hash_impl);
POSIX_ENSURE_REF(state->hash_impl->update);
POSIX_GUARD(state->hash_impl->update(state, data, size));
POSIX_ENSURE(size <= (UINT64_MAX - state->currently_in_hash), S2N_ERR_INTEGER_OVERFLOW);
state->currently_in_hash += size;
return S2N_SUCCESS;
}
int s2n_hash_digest(struct s2n_hash_state *state, void *out, uint32_t size)
{
POSIX_PRECONDITION(s2n_hash_state_validate(state));
POSIX_ENSURE(S2N_MEM_IS_READABLE(out, size), S2N_ERR_PRECONDITION_VIOLATION);
POSIX_ENSURE(state->is_ready_for_input, S2N_ERR_HASH_NOT_READY);
POSIX_ENSURE_REF(state->hash_impl);
POSIX_ENSURE_REF(state->hash_impl->digest);
POSIX_GUARD(state->hash_impl->digest(state, out, size));
state->currently_in_hash = 0;
state->is_ready_for_input = 0;
return S2N_SUCCESS;
}
int s2n_hash_copy(struct s2n_hash_state *to, struct s2n_hash_state *from)
{
POSIX_PRECONDITION(s2n_hash_state_validate(to));
POSIX_PRECONDITION(s2n_hash_state_validate(from));
POSIX_ENSURE_REF(from->hash_impl);
POSIX_ENSURE_REF(from->hash_impl->copy);
POSIX_GUARD(from->hash_impl->copy(to, from));
to->hash_impl = from->hash_impl;
to->alg = from->alg;
to->is_ready_for_input = from->is_ready_for_input;
to->currently_in_hash = from->currently_in_hash;
return S2N_SUCCESS;
}
int s2n_hash_reset(struct s2n_hash_state *state)
{
POSIX_ENSURE_REF(state);
POSIX_ENSURE_REF(state->hash_impl);
POSIX_ENSURE_REF(state->hash_impl->reset);
POSIX_GUARD(state->hash_impl->reset(state));
POSIX_GUARD(s2n_hash_init(state, state->alg));
return S2N_SUCCESS;
}
int s2n_hash_free(struct s2n_hash_state *state)
{
if (state == NULL) {
return S2N_SUCCESS;
}
POSIX_ENSURE_REF(state->hash_impl);
POSIX_ENSURE_REF(state->hash_impl->free);
POSIX_GUARD(state->hash_impl->free(state));
state->alg = S2N_HASH_NONE;
state->is_ready_for_input = 0;
state->currently_in_hash = 0;
return S2N_SUCCESS;
}
int s2n_hash_get_currently_in_hash_total(struct s2n_hash_state *state, uint64_t *out)
{
POSIX_PRECONDITION(s2n_hash_state_validate(state));
POSIX_ENSURE(S2N_MEM_IS_WRITABLE_CHECK(out, sizeof(*out)), S2N_ERR_PRECONDITION_VIOLATION);
POSIX_ENSURE(state->is_ready_for_input, S2N_ERR_HASH_NOT_READY);
*out = state->currently_in_hash;
return S2N_SUCCESS;
}