#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <getopt.h>
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/types.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/hmac.h>
#include <wolfssl/wolfcrypt/memory.h>
#ifndef FIPS_IN_CORE_DIGEST_SIZE
#ifndef NO_SHA256
#define FIPS_IN_CORE_DIGEST_SIZE WC_SHA256_DIGEST_SIZE
#define FIPS_IN_CORE_HASH_TYPE WC_SHA256
#elif defined(WOLFSSL_SHA384)
#define FIPS_IN_CORE_DIGEST_SIZE WC_SHA384_DIGEST_SIZE
#define FIPS_IN_CORE_HASH_TYPE WC_SHA384
#else
#error Unsupported FIPS hash alg.
#endif
#endif
#ifndef FIPS_IN_CORE_KEY_SZ
#define FIPS_IN_CORE_KEY_SZ FIPS_IN_CORE_DIGEST_SIZE
#endif
#ifndef FIPS_IN_CORE_VERIFY_SZ
#define FIPS_IN_CORE_VERIFY_SZ FIPS_IN_CORE_DIGEST_SIZE
#endif
#ifdef WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE
extern const char coreKey[FIPS_IN_CORE_KEY_SZ*2 + 1];
#endif
static int hmac_setkey_cb(Hmac *hmac, const byte *key, word32 key_len) {
return wc_HmacSetKey(hmac, FIPS_IN_CORE_HASH_TYPE, key, key_len);
}
static int hmac_update_cb(Hmac *hmac, const byte *in, word32 in_len) {
return wc_HmacUpdate_fips(hmac, in, in_len);
}
static int hmac_final_cb(Hmac *hmac, byte *out, word32 out_sz) {
int actual_size = wc_HmacSizeByType(hmac->macType);
if (actual_size < 0)
return actual_size;
if ((int)out_sz != actual_size)
return BUFFER_E;
return wc_HmacFinal(hmac, out);
}
int main(int argc, char **argv)
{
Hmac hmac;
int ret;
struct wc_reloc_table_segments seg_map = WC_RELOC_TABLE_SEGMENTS_INITIALIZER;
word32 new_verifyCore_size = FIPS_IN_CORE_DIGEST_SIZE*2 + 1;
char new_verifyCore[FIPS_IN_CORE_DIGEST_SIZE*2 + 1];
const char *progname = strchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0];
const char *mod_path = NULL;
const char *user_coreKey = NULL;
int mod_fd;
struct stat st;
byte *mod_map = MAP_FAILED;
struct wc_reloc_counts reloc_counts;
int inplace = 0;
int quiet = 0;
int verbose = 0;
static const struct option long_options[] = {
#define FENCEPOST_OPT_FLAG (1U << 30)
#define FENCEPOST_OPT(x) { .name = #x, \
.has_arg = required_argument, \
.flag = NULL, \
.val = FENCEPOST_OPT_FLAG | offsetof(typeof(seg_map), x) }
FENCEPOST_OPT(text_start),
FENCEPOST_OPT(text_end),
FENCEPOST_OPT(reloc_tab_start),
FENCEPOST_OPT(reloc_tab_end),
FENCEPOST_OPT(reloc_tab_len_start),
FENCEPOST_OPT(reloc_tab_len_end),
FENCEPOST_OPT(fips_text_start),
FENCEPOST_OPT(fips_text_end),
FENCEPOST_OPT(rodata_start),
FENCEPOST_OPT(rodata_end),
FENCEPOST_OPT(fips_rodata_start),
FENCEPOST_OPT(fips_rodata_end),
FENCEPOST_OPT(verifyCore_start),
FENCEPOST_OPT(verifyCore_end),
FENCEPOST_OPT(data_start),
FENCEPOST_OPT(data_end),
FENCEPOST_OPT(bss_start),
FENCEPOST_OPT(bss_end),
{ "core-key", required_argument, NULL, 'k' },
{ "mod-path", required_argument, NULL, 'f' },
{ "in-place", no_argument, NULL, 'i' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ }
};
for (;;) {
int option_index = 0;
int c = getopt_long(argc, argv, "f:ik:qvh", long_options, &option_index);
if (c == -1)
break;
if (c & FENCEPOST_OPT_FLAG) {
char *eptr;
c &= ~FENCEPOST_OPT_FLAG;
*((unsigned long *)((byte *)&seg_map + c)) = strtoul(optarg, &eptr, 0);
if (*eptr != '\0') {
fprintf(stderr, "%s: %s: supplied arg \"%s\" isn't a valid number.\n",
progname, long_options[option_index].name, optarg);
exit(1);
}
continue;
}
switch (c) {
case 'f':
mod_path = optarg;
break;
case 'i':
inplace = 1;
break;
case 'k':
user_coreKey = optarg;
break;
case 'q':
quiet = 1;
break;
case 'v':
verbose = 1;
break;
case 'h':
printf("usage: %s \\\n", progname);
for (int i=0; i < (int)(sizeof long_options / sizeof long_options[0]); ++i) {
const struct option *opt = &long_options[i];
if (opt->name == NULL) {
printf("\n");
continue;
}
if (i > 0)
printf(" \\\n");
if (opt->has_arg == no_argument)
printf(" --%s", opt->name);
else if (opt->val & FENCEPOST_OPT_FLAG)
printf(" --%s <offset>", opt->name);
else
printf(" --%s <arg>", opt->name);
}
exit(0);
case ':':
fprintf(stderr, "%s: Missing argument. Try --help.\n", progname);
exit(1);
__builtin_unreachable();
case '?':
fprintf(stderr, "%s: Unrecognized option. Try --help.\n", progname);
exit(1);
__builtin_unreachable();
default:
fprintf(stderr, "%s: Unexpected error. Try --help.\n", progname);
exit(1);
__builtin_unreachable();
}
}
if (optind < argc) {
fprintf(stderr, "%s: unexpected trailing argument(s).\n", progname);
exit(1);
}
if (user_coreKey == NULL) {
#ifdef WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE
user_coreKey = coreKey;
#else
fprintf(stderr, "%s: must supply --core-key.\n", progname);
exit(1);
#endif
}
#ifdef WC_USE_PIE_FENCEPOSTS_FOR_FIPS
if ((seg_map.fips_text_start != ~0UL) ||
(seg_map.fips_text_end != ~0UL) ||
(seg_map.fips_rodata_start != ~0UL) ||
(seg_map.fips_rodata_end != ~0UL))
{
fprintf(stderr, "%s: note, ignoring explicit FIPS fenceposts "
"because WC_USE_PIE_FENCEPOSTS_FOR_FIPS.\n", progname);
}
seg_map.fips_text_start = seg_map.text_start;
seg_map.fips_text_end = seg_map.text_end;
seg_map.fips_rodata_start = seg_map.rodata_start;
seg_map.fips_rodata_end = seg_map.rodata_end;
#endif
if ((seg_map.text_start == ~0UL) ||
(seg_map.text_end == ~0UL) ||
(seg_map.reloc_tab_start == ~0UL) ||
(seg_map.reloc_tab_end == ~0UL) ||
(seg_map.reloc_tab_len_start == ~0UL) ||
(seg_map.reloc_tab_len_end == ~0UL) ||
(seg_map.fips_text_start == ~0UL) ||
(seg_map.fips_text_end == ~0UL) ||
(seg_map.rodata_start == ~0UL) ||
(seg_map.rodata_end == ~0UL) ||
(seg_map.fips_rodata_start == ~0UL) ||
(seg_map.fips_rodata_end == ~0UL) ||
(seg_map.verifyCore_start == ~0UL) ||
(seg_map.verifyCore_end == ~0UL) ||
(seg_map.data_start == ~0UL) ||
(seg_map.data_end == ~0UL) ||
(seg_map.bss_start == ~0UL) ||
(seg_map.bss_end == ~0UL))
{
fprintf(stderr, "%s: segment fencepost(s) missing. Try --help.\n",
progname);
exit(1);
}
if (mod_path == NULL) {
fprintf(stderr, "%s: module path missing. Try --help.\n", progname);
exit(1);
}
mod_fd = open(mod_path, inplace ? O_RDWR : O_RDONLY);
if (mod_fd < 0) {
fprintf(stderr, "%s: open %s: %m.\n", progname, mod_path);
exit(1);
}
ret = fstat(mod_fd, &st);
if (ret < 0) {
fprintf(stderr, "%s: fstat %s: %m.\n", progname, mod_path);
exit(1);
}
if ((seg_map.reloc_tab_start >= seg_map.reloc_tab_end) ||
(seg_map.reloc_tab_end >= (unsigned long)st.st_size) ||
(seg_map.reloc_tab_len_start >= seg_map.reloc_tab_len_end) ||
(seg_map.reloc_tab_len_end >= (unsigned long)st.st_size))
{
fprintf(stderr, "%s: supplied reloc_tab fencepost(s) are out of bounds "
"for supplied module %s with length %lu.\n",
progname, mod_path, (unsigned long)st.st_size);
exit(1);
}
mod_map = (byte *)mmap(NULL, st.st_size,
inplace ? PROT_READ | PROT_WRITE : PROT_READ,
(inplace ? MAP_SHARED : MAP_PRIVATE) | MAP_POPULATE,
mod_fd, 0);
if (mod_map == MAP_FAILED) {
fprintf(stderr, "%s: mmap() of %s, length %zu: %m.\n",
progname, mod_path, st.st_size);
exit(1);
}
seg_map.start = (unsigned long)mod_map;
seg_map.end = (unsigned long)mod_map + st.st_size;
seg_map.reloc_tab_start += (unsigned long)mod_map;
seg_map.reloc_tab_end += (unsigned long)mod_map;
seg_map.reloc_tab_len_start += (unsigned long)mod_map;
seg_map.reloc_tab_len_end += (unsigned long)mod_map;
seg_map.verifyCore_start += (unsigned long)mod_map;
seg_map.verifyCore_end += (unsigned long)mod_map;
seg_map.fips_text_start += (unsigned long)mod_map;
seg_map.fips_text_end += (unsigned long)mod_map;
seg_map.fips_rodata_start += (unsigned long)mod_map;
seg_map.fips_rodata_end += (unsigned long)mod_map;
seg_map.text_start += (unsigned long)mod_map;
seg_map.text_end += (unsigned long)mod_map;
seg_map.rodata_start += (unsigned long)mod_map;
seg_map.rodata_end += (unsigned long)mod_map;
seg_map.data_start += (unsigned long)mod_map;
seg_map.data_end += (unsigned long)mod_map;
seg_map.bss_start += (unsigned long)mod_map;
seg_map.bss_end += (unsigned long)mod_map;
ret = wolfCrypt_Init();
if (ret < 0) {
fprintf(stderr, "%s: wolfCrypt_Init() failed: %s.\n",
progname, wc_GetErrorString(ret));
exit(1);
}
ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID);
if (ret != 0) {
fprintf(stderr, "%s: wc_HmacInit() failed: %s.\n",
progname, wc_GetErrorString(ret));
exit(1);
}
if (seg_map.verifyCore_end - seg_map.verifyCore_start != new_verifyCore_size) {
fprintf(stderr, "%s: unexpected verifyCore length %zu.\n",
progname, (size_t)(seg_map.verifyCore_end - seg_map.verifyCore_start));
ret = -1;
goto out;
}
XMEMSET(&reloc_counts, 0, sizeof(reloc_counts));
ret = wc_fips_generate_hash(
&seg_map,
FIPS_IN_CORE_DIGEST_SIZE,
user_coreKey,
&hmac,
(wc_fips_verifyCore_hmac_setkey_fn)hmac_setkey_cb,
(wc_fips_verifyCore_hmac_update_fn)hmac_update_cb,
(wc_fips_verifyCore_hmac_final_fn)hmac_final_cb,
new_verifyCore,
&new_verifyCore_size,
&reloc_counts);
if (ret < 0) {
fprintf(stderr, "%s: wc_fips_generate_hash() failed: %s.\n",
progname, wc_GetErrorString(ret));
goto out;
}
if (verbose)
fprintf(inplace ? stdout : stderr,
"FIPS-bounded relocation normalizations: text=%d, rodata=%d, "
"rwdata=%d, bss=%d, other=%d\n",
reloc_counts.text, reloc_counts.rodata, reloc_counts.rwdata,
reloc_counts.bss, reloc_counts.other);
if (new_verifyCore_size < sizeof new_verifyCore) {
fprintf(stderr, "%s: wc_fips_generate_hash() returned unexpected "
"verifyCore length %u.\n",
progname, new_verifyCore_size);
ret = -1;
goto out;
}
if ((! quiet) && (verbose || !inplace))
printf("%s\n", new_verifyCore);
if (strcmp((char *)seg_map.verifyCore_start, new_verifyCore) == 0) {
fprintf(stderr, "%s: note, verifyCore already matches.\n", progname);
}
else if (inplace) {
XMEMCPY((void *)seg_map.verifyCore_start, new_verifyCore,
new_verifyCore_size);
ret = munmap(mod_map, st.st_size);
if (ret < 0) {
fprintf(stderr, "%s: munmap: %m\n", progname);
exit(1);
}
mod_map = MAP_FAILED;
ret = close(mod_fd);
if (ret < 0) {
fprintf(stderr, "%s: close: %m\n", progname);
exit(1);
}
mod_fd = -1;
printf("FIPS integrity hash updated successfully.\n");
}
out:
wc_HmacFree(&hmac);
wolfCrypt_Cleanup();
if (mod_map != MAP_FAILED) {
if (munmap(mod_map, st.st_size) < 0) {
fprintf(stderr, "%s: munmap: %m\n", progname);
if (ret == 0)
ret = -1;
}
}
if (mod_fd >= 0) {
if (close(mod_fd) < 0) {
fprintf(stderr, "%s: close: %m\n", progname);
if (ret == 0)
ret = -1;
}
}
if (ret)
exit(1);
else
exit(0);
}