/*
* Copyright (C) 2017 Aaron M. D. Jones <aaronmdjones@gmail.com>
*
* 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.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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 "acopm-common.h"
#include <assert.h> /* assert() */
#include <errno.h> /* errno */
#include <fcntl.h> /* open() */
#include <string.h> /* strerror() */
#include <unistd.h> /* close() */
#ifdef BUILD_WITH_MBEDTLS
# ifdef MBEDTLS_CONFIG_FILE
# include MBEDTLS_CONFIG_FILE
# else
# include <mbedtls/config.h> /* MBEDTLS_*_C */
# endif
# ifdef MBEDTLS_CTR_DRBG_C
# include <mbedtls/ctr_drbg.h> /* mbedtls_ctr_drbg_context, mbedtls_ctr_drbg_*() */
# endif
# ifdef MBEDTLS_HMAC_DRBG_C
# include <mbedtls/hmac_drbg.h> /* mbedtls_hmac_drbg_context, mbedtls_hmac_drbg_*() */
# endif
# include <mbedtls/entropy.h> /* mbedtls_entropy_context, mbedtls_entropy_*() */
# include <mbedtls/error.h> /* mbedtls_strerror() */
#endif /* BUILD_WITH_MBEDTLS */
#include "utils/log.h" /* acopm_log_*() */
#include "utils/misc.h" /* decls for own functions */
static const char acopm_rng_randchars[62] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static bool acopm_rng_init_done = false;
#ifdef BUILD_WITH_MBEDTLS
#if !defined(MBEDTLS_CTR_DRBG_C) && !defined(MBEDTLS_HMAC_DRBG_C)
# error "When using MbedTLS, at least one of CTR-DRBG or HMAC-DRBG must be enabled"
#endif
#if !defined(MBEDTLS_CTR_DRBG_C) && !defined(MBEDTLS_SHA256_C) && !defined(MBEDTLS_SHA512_C)
# error "When using MbedTLS without CTR-DRBG, at least one of SHA-2/256 or SHA-2/512 must be enabled"
#endif
#ifdef MBEDTLS_CTR_DRBG_C
# define ACOPM_RNG_STR_MAX MBEDTLS_CTR_DRBG_MAX_REQUEST
#else
# define ACOPM_RNG_STR_MAX MBEDTLS_HMAC_DRBG_MAX_REQUEST
#endif
static const unsigned char acopm_drbg_pstring[] = "ACOPM/RNG DRBG Personalisation String";
#ifdef MBEDTLS_CTR_DRBG_C
static mbedtls_ctr_drbg_context acopm_rng_drbg_ctx;
#else
static mbedtls_hmac_drbg_context acopm_rng_drbg_ctx;
#endif
static mbedtls_entropy_context acopm_rng_ent_ctx;
bool
acopm_rng_init(void)
{
assert(acopm_rng_init_done == false);
(void) acopm_log_debug("%s: initialising random number generator", __func__);
(void) mbedtls_entropy_init(&acopm_rng_ent_ctx);
#ifdef MBEDTLS_CTR_DRBG_C
(void) mbedtls_ctr_drbg_init(&acopm_rng_drbg_ctx);
const int ret = mbedtls_ctr_drbg_seed(
&acopm_rng_drbg_ctx, mbedtls_entropy_func, &acopm_rng_ent_ctx,
acopm_drbg_pstring, sizeof acopm_drbg_pstring
);
#else
# ifdef MBEDTLS_SHA512_C
const mbedtls_md_type_t md_type = MBEDTLS_MD_SHA512;
# else
const mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
# endif
const mbedtls_md_info_t *const md_info = mbedtls_md_info_from_type(md_type);
if (! md_info)
{
(void) acopm_log_error("%s: md_info_from_type(%u) failed", __func__, (unsigned int) md_type);
return false;
}
(void) mbedtls_hmac_drbg_init(&acopm_rng_drbg_ctx);
const int ret = mbedtls_hmac_drbg_seed(
&acopm_rng_drbg_ctx, md_info, mbedtls_entropy_func, &acopm_rng_ent_ctx,
acopm_drbg_pstring, sizeof acopm_drbg_pstring
);
#endif
if (ret != 0)
{
(void) acopm_log_error("%s: drbg_seed: %s", __func__, acopm_mbed_strerror(ret));
return false;
}
acopm_rng_init_done = true;
return true;
}
void
acopm_rng_free(void)
{
assert(acopm_rng_init_done == true);
(void) acopm_log_debug("%s: destroying random number generator", __func__);
#ifdef MBEDTLS_CTR_DRBG_C
(void) mbedtls_ctr_drbg_free(&acopm_rng_drbg_ctx);
#else
(void) mbedtls_hmac_drbg_free(&acopm_rng_drbg_ctx);
#endif
(void) mbedtls_entropy_free(&acopm_rng_ent_ctx);
acopm_rng_init_done = false;
}
bool
acopm_rng_buf(void *const restrict buf, const size_t len)
{
assert(acopm_rng_init_done == true);
assert(buf != NULL);
assert(len > 0);
assert(len <= ACOPM_RNG_STR_MAX);
#ifdef MBEDTLS_CTR_DRBG_C
const int ret = mbedtls_ctr_drbg_random(&acopm_rng_drbg_ctx, buf, len);
#else
const int ret = mbedtls_hmac_drbg_random(&acopm_rng_drbg_ctx, buf, len);
#endif
if (ret != 0)
{
(void) acopm_log_error("%s: ctr_drbg_random: %s", __func__, acopm_mbed_strerror(ret));
return false;
}
return true;
}
bool
acopm_rng_str(char *const restrict buf, const size_t len)
{
assert(acopm_rng_init_done == true);
assert(buf != NULL);
assert(len > 0);
assert(len <= ACOPM_RNG_STR_MAX);
unsigned char tmp[ACOPM_RNG_STR_MAX];
if (! acopm_rng_buf(tmp, len))
return false;
for (size_t x = 0; x < len; x++)
buf[x] = acopm_rng_randchars[tmp[x] % sizeof acopm_rng_randchars];
buf[len] = 0x00;
return true;
}
int
acopm_rng_tls(void *const restrict context, unsigned char *const restrict buf, const size_t len)
{
(void) context;
assert(acopm_rng_init_done == true);
assert(context == NULL);
assert(buf != NULL);
assert(len > 0);
assert(len <= ACOPM_RNG_STR_MAX);
#ifdef MBEDTLS_CTR_DRBG_C
const int ret = mbedtls_ctr_drbg_random(&acopm_rng_drbg_ctx, buf, len);
#else
const int ret = mbedtls_hmac_drbg_random(&acopm_rng_drbg_ctx, buf, len);
#endif
if (ret != 0)
(void) acopm_log_error("%s: ctr_drbg_random: %s", __func__, acopm_mbed_strerror(ret));
return ret;
}
const char *
acopm_mbed_strerror(const int err)
{
const unsigned int err_pos = ((err < 0) ? (unsigned int) -err : (unsigned int) err);
static char buf[4096];
#ifdef MBEDTLS_ERROR_C
char tmp[1024];
(void) mbedtls_strerror(err, tmp, sizeof tmp);
(void) snprintf(buf, sizeof buf, "-0x%X (%s)", err_pos, tmp);
#else
(void) snprintf(buf, sizeof buf, "-0x%X", err_pos);
#endif
return buf;
}
#else /* BUILD_WITH_MBEDTLS */
#define ACOPM_RNG_STR_MAX 64
static int random_fd = -1;
bool
acopm_rng_init(void)
{
assert(acopm_rng_init_done == false);
(void) acopm_log_debug("%s: initialising random number generator", __func__);
if (random_fd == -1 && (random_fd = open("/dev/urandom", O_RDONLY)) == -1)
{
(void) acopm_log_error("%s: open: %s", __func__, strerror(errno));
return false;
}
acopm_rng_init_done = true;
return true;
}
void
acopm_rng_free(void)
{
assert(acopm_rng_init_done == true);
(void) acopm_log_debug("%s: destroying random number generator", __func__);
(void) close(random_fd);
random_fd = -1;
acopm_rng_init_done = false;
}
bool
acopm_rng_buf(void *const restrict buf, const size_t len)
{
assert(acopm_rng_init_done == true);
assert(buf != NULL);
assert(len > 0);
unsigned char *dest = (unsigned char *) buf;
size_t done = 0;
ssize_t result = 0;
while ((done < len) && (result = read(random_fd, dest, (len - done))) > 0)
{
dest += (size_t) result;
done += (size_t) result;
}
if (result == -1)
{
(void) acopm_log_error("%s: read: %s", __func__, strerror(errno));
return false;
}
if (result == 0)
{
(void) acopm_log_error("%s: read: EOF", __func__);
return false;
}
return true;
}
bool
acopm_rng_str(char *const restrict buf, const size_t len)
{
assert(acopm_rng_init_done == true);
assert(buf != NULL);
assert(len > 0);
assert(len <= ACOPM_RNG_STR_MAX);
unsigned char tmp[ACOPM_RNG_STR_MAX];
if (! acopm_rng_buf(tmp, len))
return false;
for (size_t x = 0; x < len; x++)
buf[x] = acopm_rng_randchars[tmp[x] % sizeof acopm_rng_randchars];
buf[len] = 0x00;
return true;
}
#endif /* BUILD_WITH_MBEDTLS */