diff options
-rw-r--r-- | .gitignore | 12 | ||||
-rw-r--r-- | INSTALL | 10 | ||||
-rw-r--r-- | Makefile.in | 41 | ||||
-rw-r--r-- | README | 13 | ||||
-rwxr-xr-x | autogen.sh | 8 | ||||
-rw-r--r-- | configure.ac | 24 | ||||
-rw-r--r-- | doc/file-format-and-process.md | 30 | ||||
-rw-r--r-- | musl-libgen-c.h | 53 | ||||
-rw-r--r-- | random-seed.c | 561 | ||||
-rw-r--r-- | readall.c | 82 | ||||
-rw-r--r-- | readall.h | 14 | ||||
-rw-r--r-- | sha2.c | 204 | ||||
-rw-r--r-- | sha2.h | 69 | ||||
-rw-r--r-- | util.c | 93 | ||||
-rw-r--r-- | util.h | 41 |
15 files changed, 1255 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0734561 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/aclocal.m4 +/autom4te.cache +/config.h.in +/config.log +/config.status +/configure +/Makefile + +/random-seed +/random-seed.test +*.o +*.d @@ -0,0 +1,10 @@ +Use ./configure; make; sudo make test; sudo make install. + +If building from git, run ./autogen.sh first. + +Tests must be run as root in order to access RNDADDENTROPY. + +On systemd, to set random-seed as your random seed provider, execute: + + systemctl enable random-seed + systemctl mask systemd-random-seed diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..a86c2f9 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,41 @@ +# @configure_input@ + +datarootdir = @datarootdir@ +sbindir = @sbindir@ +sysconfdir = @sysconfdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +CPPFLAGS = @CPPFLAGS@ +CFLAGS = -Wall -Wextra -pedantic @SYSTEMD_CFLAGS@ @CFLAGS@ -MD -MP -UNDEBUG -include @abs_top_builddir@/config.h +LDFLAGS = @LDFLAGS@ +LDLIBS = @SYSTEMD_LIBS@ + +SRC ::= random-seed.c sha2.c util.c readall.c +OBJ ::= $(SRC:.c=.o) +DEP ::= $(SRC:.c=.d) +TEST_FILE ::= random-seed.test + +all: random-seed + +random-seed: $(OBJ) + +install: random-seed + install -D -m755 random-seed $(sbindir)/random-seed + install -D -m644 random-seed.8 $(mandir)/man8/random-seed.8 +ifneq (@systemdsystemunitdir@,) + install -D -m644 random-seed.service @systemdsystemunitdir@/random-seed.service +endif + +test: $(TEST_FILE) + +$(TEST_FILE): random-seed + ./random-seed save $(TEST_FILE) + ./random-seed load $(TEST_FILE) + +clean: + $(RM) random-seed $(OBJ) $(DEP) $(TEST_FILE) + +-include $(DEP) + +.PHONY: all install test clean +.INTERMEDIATE: $(TEST_FILE) @@ -0,0 +1,13 @@ +random-seed is a random seed management program. In contrast with other random +seed implementations, random-seed will credit the random seed to the kernel +entropy count. It attempts to prevent inadvertent random seed sharing by +checking that certain system identifiers, such as the machine ID and filesystem +ID have not changed between a save and load. If these identifiers do not +match, random-seed will still load the random seed, but will not credit the +entropy. + +It is my understanding that other operating systems are either not commonly imaged (e.g. BSDs) or have official tools for system image preparation (e.g. sysprep for Windows). Therefore, random-seed is Linux specific. However, it should be reasonably easy to port by simply adjusting the paths and changing getrandom to /dev/random. + +random-seed requires GNU make to compile. + +random-seed requires Linux 3.11 or higher supporting the getrandom(2) system call. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..81f2919 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +autoheader & +aclocal +autoconf +wait diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..386fa5e --- /dev/null +++ b/configure.ac @@ -0,0 +1,24 @@ +AC_INIT(random-seed, 0.1) +AC_LANG(C) +AC_CONFIG_HEADERS(config.h) +AC_PROG_CC + +CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L" +CFLAGS="$CFLAGS -std=c99" + +PKG_HAVE_DEFINE_WITH_MODULES(SYSTEMD, libsystemd, , , [use libsystemd to get machine id]) +AS_IF([test "$with_systemd" = yes], + [PKG_CHECK_VAR(systemdsystemunitdir, systemd, systemdsystemunitdir)]) + +AC_ARG_ENABLE(debug, + AC_HELP_STRING(--enable-debug, [enable debug output [default=no]]), + [enable_debug=$enableval], + [enable_debug=no] + ) +AS_CASE([$enable_debug], + [yes], [AC_DEFINE(DEBUG, [], [enable debug output])], + [no], [], + [AC_MSG_ERROR([invalid argument to --enable-debug])] +) + +AC_OUTPUT(Makefile) diff --git a/doc/file-format-and-process.md b/doc/file-format-and-process.md new file mode 100644 index 0000000..6ffc067 --- /dev/null +++ b/doc/file-format-and-process.md @@ -0,0 +1,30 @@ +The random-seed format consists of: + +1. 512 bytes of random seed data for compatibility with other random seed + implementations +2. The magic string "RANDOM SEED FILE VERSION 1". +3. A series of line delimited commands with space delimited arguments. + +Comments are not supported. + +# Hashing +In an attempt to improve privacy, device IDs are hashed with SHA256(random-data +|| ID) where || denotes concatenation and random-data is the 512 bytes of +random data at the start of the file. + +# Commands + +## salt +Set the salt for the following commands to the argument. This must be the +first command. + +## machine-id +Check that the contents of `/etc/machine-id`, when hashed, matches the +argument. + +## fs-id +Check that calling statfs(2) on the random seed file returns a `f_fsid` that +when hashed, matches the argument. + +## done +End of mandatory commands. diff --git a/musl-libgen-c.h b/musl-libgen-c.h new file mode 100644 index 0000000..99c90ee --- /dev/null +++ b/musl-libgen-c.h @@ -0,0 +1,53 @@ +/* + * Copied from musl. + * + * Copyright © 2005-2014 Rich Felker, et al. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a 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. + */ + +#ifndef MUSL_LIBGEN_H +#define MUSL_LIBGEN_H + +#include <string.h> + +static inline char *mybasename(char *s) +{ + size_t i; + if (!s || !*s) return "."; + i = strlen(s)-1; + for (; i&&s[i]=='/'; i--) s[i] = 0; + for (; i&&s[i-1]!='/'; i--); + return s+i; +} + +static inline char *mydirname(char *s) +{ + size_t i; + if (!s || !*s) return "."; + i = strlen(s)-1; + for (; s[i]=='/'; i--) if (!i) return "/"; + for (; s[i]!='/'; i--) if (!i) return "."; + for (; s[i]=='/'; i--) if (!i) return "/"; + s[i+1] = 0; + return s; +} + +#endif diff --git a/random-seed.c b/random-seed.c new file mode 100644 index 0000000..7a0527a --- /dev/null +++ b/random-seed.c @@ -0,0 +1,561 @@ +/* Copyright 2018 Alex Xu (aka Hello71, alxu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + */ + +/* we don't use enough CPU time, better be safe than sorry */ +#ifdef NDEBUG +#warning "NDEBUG is always disabled for security" +#undef NDEBUG +#endif + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/syscall.h> +#include <unistd.h> + +#ifndef WITHOUT_SYSTEMD +#include <systemd/sd-id128.h> +#endif + +#include "musl-libgen-c.h" +#include "readall.h" +#include "util.h" + +// musl forbids include/linux +#define RNDADDENTROPY _IOW( 'R', 0x03, int [2] ) + +/* 3 hours */ +#define DAEMONIZE_SLEEP_TIME (3*60*60) + +struct rand_pool_info_ { + int entropy_count; + int buf_size; + uint32_t buf[RAND_POOL_SIZE / sizeof(uint32_t)]; +}; + +static const char MAGIC[] = "RANDOM SEED FILE VERSION 1\n"; + +static sig_atomic_t sigint = 0; +static sig_atomic_t sigterm = 0; + +static inline void usage() { + puts("usage: random-seed MODE FILE"); + puts("see random-seed(8) for more information."); +} + +static size_t get_machine_id(unsigned char **machine_id) { +#ifdef HAVE_SYSTEMD + sd_id128_t raw_machine_id; + int r = sd_id128_get_machine(&raw_machine_id); + if (r) { + fprintf(stderr, "error getting machine id: %s\n", strerror(-r)); + *machine_id = NULL; + return 0; + } + *machine_id = malloc(33); + /* convert to string to be compatible with non-hex machine IDs */ + sd_id128_to_string(raw_machine_id, (char *)*machine_id); + /* match /etc/machine-id format */ + (*machine_id)[32] = '\n'; + + return 33; +#else + char *machine_id; + ssize_t machine_id_len; + + const char *etc_machine_id = SYSCONFDIR "/machine-id"; + const char *var_lib_dbus_machine_id = LOCALSTATEDIR "/lib/dbus/machine-id"; + FILE *machine_id_file = fopen(etc_machine_id, "r"); + if (!machine_id_file) { + if (errno != ENOENT) + fprintf(stderr, "error opening %s: %s, trying %s\n", + etc_machine_id, strerror(errno), var_lib_dbus_machine_id); + machine_id_file = fopen(var_lib_dbus_machine_id, "r"); + if (!machine_id_file) { + fprintf(stderr, "couldn't open any machine-id file, last error: " + "%s; tried: %s, %s\n", strerror(errno), etc_machine_id, + var_lib_dbus_machine_id); + *machine_id = NULL; + return 0; + } + } + + if (readall(machine_id_file, machine_id, &machine_id_len) != READALL_OK) { + fputs("error reading machine id file\n", stderr); + *machine_id = NULL; + return 0; + } + + return machine_id_len; +#endif +} + +static bool get_machine_id_hash(const unsigned char salt[static SALT_LEN], unsigned char machine_id_digest[static HASH_LEN]) { + static unsigned char *c_machine_id; + static size_t c_machine_id_len; + if (!c_machine_id) + c_machine_id_len = get_machine_id(&c_machine_id); + if (!c_machine_id_len) { + free(c_machine_id); + c_machine_id = NULL; + return false; + } + unsigned char c_machine_id_digest[HASH_LEN]; + hash(salt, c_machine_id_digest, c_machine_id, c_machine_id_len); + memcpy(machine_id_digest, c_machine_id_digest, HASH_LEN); + return true; +} + +static inline bool get_fs_id_hash(const unsigned char salt[static SALT_LEN], unsigned char fsid_digest[static HASH_LEN], int seed_fd) { + struct statfs statfs_buf; + if (fstatfs(seed_fd, &statfs_buf) == -1) { + fprintf(stderr, "error statfs seed file: %s, " + "disabling entropy credit\n", strerror(errno)); + return false; + } + hash(salt, fsid_digest, &statfs_buf.f_fsid, sizeof(statfs_buf.f_fsid)); + return true; +} + +static bool run_seed_file_cmd(const char *cmd, const unsigned char *salt, FILE *seed_file) { + char *arg; + if (streq(cmd, "machine-id")) { + arg = strtok(NULL, " \t"); + if (!arg) { + fputs("error parsing seed file: expected argument " + "to 'machine-id'\n", stderr); + return false; + } + unsigned char machine_id_hash[HASH_LEN]; + if (!get_machine_id_hash(salt, machine_id_hash)) { + fputs("error getting machine id hash, disabling entropy credit\n", + stderr); + return false; + } + + if (!hash_match(machine_id_hash, arg)) { + fputs("machine-id does not match, disabling entropy credit\n", + stderr); + return false; + } + return true; + } + + if (streq(cmd, "fs-id")) { + arg = strtok(NULL, " \t"); + if (!arg) { + fputs("error parsing seed file: expected argument to 'fs-id'\n", stderr); + return false; + } + unsigned char fs_id_hash[HASH_LEN]; + if (!get_fs_id_hash(salt, fs_id_hash, fileno(seed_file))) { + fputs("error getting fs id hash, disabling entropy credit\n", stderr); + return false; + } + + if (!hash_match(fs_id_hash, arg)) { + fputs("fs id does not match, disabling entropy credit\n", stderr); + return false; + } + return true; + } + + fprintf(stderr, "error parsing seed file: unsupported command: %s\n", cmd); + return false; +} + +static bool load(FILE *seed_file) { + bool credit_entropy = true; + + struct rand_pool_info_ rpi = { + .entropy_count = RAND_POOL_SIZE * 8, + .buf_size = RAND_POOL_SIZE, + .buf = { 0 } + }; + + uint64_t linenum = 0; + char *line = NULL; + size_t n = 0; + bool done = false; + + if (fread(&rpi.buf, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE) { + if (feof(seed_file)) { + fputs("premature EOF on seed file\n", stderr); + } else if (ferror(seed_file)) { + fputs("error reading from seed file\n", stderr); + } else { + fputs("short read from seed file\n", stderr); + } + return false; + } + + unsigned char *salt = (unsigned char *)rpi.buf; + + while (1) { + errno = 0; + ssize_t line_length = getline(&line, &n, seed_file); + if (line_length == -1) { + if (errno) { + perror("error reading from seed file"); + credit_entropy = false; + } + break; + } + + linenum++; + + if (linenum == 1) { + if (streq(line, MAGIC)) { + continue; + } else { + fputs("error parsing seed file: invalid magic\n", stderr); + credit_entropy = false; + break; + } + } + + char *nul = memchr(line, '\0', line_length); + if (nul) { + fprintf(stderr, "error parsing seed file: encountered NUL byte in commands line %zu char %zu\n", linenum, nul - line + 1); + credit_entropy = false; + break; + } + + char *cmd = strtok(line, " \t\n"); + if (!cmd) + continue; + +#ifdef DEBUG + fprintf(stderr, "executing command: %s\n", cmd); +#endif + + if (streq(cmd, "done")) { + done = true; + continue; + } + + if (!run_seed_file_cmd(cmd, salt, seed_file)) { + credit_entropy = false; + break; + } + } + + if (credit_entropy && !done) { + fputs("missing done command, random seed file probably truncated. disabling entropy credit\n", stderr); + credit_entropy = false; + } + + int urandom_fd = open("/dev/urandom", O_RDWR, 0); + if (urandom_fd == -1) { + perror("error opening /dev/urandom"); + exit(5); + } + + if (credit_entropy) { + if (ioctl(urandom_fd, RNDADDENTROPY, &rpi) == -1) { + perror("ioctl(RNDADDENTROPY)"); + if (errno == EPERM) { + fputs("Continuing without crediting entropy.\n", stderr); + } + credit_entropy = false; + } + } + + if (!credit_entropy) { + if (write(urandom_fd, &rpi.buf, RAND_POOL_SIZE) != RAND_POOL_SIZE) { + fputs("error writing entropy to /dev/urandom\n", stderr); + exit(5); + } + } + + return credit_entropy; +} + +static bool get_rand_pool(unsigned char *buf) { + size_t rand_bits_gotten = 0; + while (rand_bits_gotten < RAND_POOL_SIZE) { + long this_rand_bits_gotten = random_get(buf + rand_bits_gotten, RAND_POOL_SIZE - rand_bits_gotten, 0); + if (this_rand_bits_gotten < 0) { + switch (errno) { + case EAGAIN: continue; + case EINTR: return false; + default: + perror("getrandom"); + exit(1); + } + } else { + rand_bits_gotten += (size_t)this_rand_bits_gotten; + } + } + return true; +} + +/** + * Save entropy to disk. + * + * \param seed_path the seed file path + * \param random_buf the random buffer. if NULL, get our own entropy. + * \return true means saved successfully, false means received EINTR + */ +static bool save(const char *seed_path, unsigned char *random_buf) { + assert(seed_path); + + bool rv = false; + char *seed_path_tmp = NULL; + char *seed_name_new = NULL; + + unsigned char my_random_buf[RAND_POOL_SIZE]; + if (!random_buf) { + if (!get_rand_pool(my_random_buf)) + return false; + random_buf = my_random_buf; + } + + unsigned char machine_id_digest[HASH_LEN]; + if (!get_machine_id_hash(random_buf, machine_id_digest)) { + fputs("cannot obtain machine id, aborting save\n", stderr); + return false; + } + char machine_id_hash[HASH_LEN*2+1]; + memset(machine_id_hash, 0, HASH_LEN*2); + mem2hex(machine_id_hash, machine_id_digest, HASH_LEN); + machine_id_hash[sizeof(machine_id_hash)-1] = '\0'; + + int seed_dir_fd = -1; + int seed_fd = -1; + FILE *seed_file = NULL; + + seed_path_tmp = strdup(seed_path); + + // TODO: rewrite when AT_REPLACE gets added... eventually... + seed_dir_fd = open(mydirname(seed_path_tmp), O_RDONLY | O_DIRECTORY); + if (seed_dir_fd == -1) { + perror("error opening seed directory"); + goto out; + } + + unsigned char fs_id_digest[HASH_LEN]; + if (!get_fs_id_hash(random_buf, fs_id_digest, seed_dir_fd)) { + fputs("cannot obtain machine id, aborting save\n", stderr); + free(seed_path_tmp); + return false; + } + char fs_id_hash[HASH_LEN*2+1]; + mem2hex(fs_id_hash, fs_id_digest, HASH_LEN); + fs_id_hash[sizeof(fs_id_hash)-1] = '\0'; + + strcpy(seed_path_tmp, seed_path); + char *seed_name = mybasename(seed_path_tmp); + size_t seed_name_new_len = strlen(seed_name) + 6; + seed_name_new = malloc(seed_name_new_len); + if (!seed_name_new) { + fputs("out of memory\n", stderr); + free(seed_path_tmp); + return false; + } + assert(seed_name_new_len < INT_MAX); + if ((size_t)snprintf(seed_name_new, seed_name_new_len, ".%s.new", seed_name) >= seed_name_new_len) + abort(); + + seed_fd = openat(seed_dir_fd, seed_name_new, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (seed_fd == -1) { + perror("error opening new seed file"); + goto err; + } + seed_file = fdopen(seed_fd, "w"); + if (!seed_file) { + perror("error converting seed file fd to stream"); + goto err; + } + + if (fwrite(random_buf, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE + || fputs(MAGIC, seed_file) == EOF + || fputs("machine-id ", seed_file) == EOF + || fputs(machine_id_hash, seed_file) == EOF + || fputs("\nfs-id ", seed_file) == EOF + || fputs(fs_id_hash, seed_file) == EOF + || fputs("\ndone\n", seed_file) == EOF) { + fputs("error writing new seed file\n", stderr); + goto err; + } + + if (fflush(seed_file) == EOF) { + perror("error flushing new seed file"); + goto err; + } + if (fsync(seed_fd) == -1) { + perror("error syncing new seed file"); + goto err; + } + if (renameat(seed_dir_fd, seed_name_new, seed_dir_fd, seed_name) == -1) { + perror("error installing new seed file"); + goto err; + } + if (fclose(seed_file) == EOF) { + perror("error closing new seed file"); + goto err; + } + if (fsync(seed_dir_fd) == -1) { + perror("error syncing seed directory"); + goto out; + } + + rv = true; + goto out; + +err: + if (seed_file) { + fclose(seed_file); + if (unlinkat(seed_dir_fd, seed_name_new, 0) == -1) { + perror("error removing temporary seed file"); + rv = false; + } + } + +out: + if (seed_dir_fd != -1) { + if (close(seed_dir_fd) == -1) { + perror("error closing seed directory"); + rv = false; + } + } + + free(seed_path_tmp); + free(seed_name_new); + + return rv; +} + +static void sighandler(int signum) { + switch (signum) { + case SIGHUP: + // do nothing; we just needed to interrupt sleep + break; + case SIGINT: + sigint = 1; + break; + case SIGTERM: + sigterm = 1; + break; + default: + abort(); + } +} + +int main(int argc, char *argv[]) { + const char *seed_path = argv[2]; + FILE *seed_file; + unsigned char random_buf[RAND_POOL_SIZE]; + int exit_status = 0; + + if (argc == 2 && (streq(argv[1], "-h") || streq(argv[1], "--help"))) { + usage(); + exit(0); + } + + if (argc != 3) { + fprintf(stderr, "invalid argument count, expected 2\n"); + usage(); + exit(2); + } + + umask(0077); + + if (streq(argv[1], "load")) { + bool refresh_seed = true; + + if (streq(seed_path, "-")) { + fputs("warning: cannot refresh stdin seed\n", stderr); + seed_file = stdin; + refresh_seed = false; + } else { + seed_file = fopen(seed_path, "r"); + if (!seed_file) { + perror("error opening seed file"); + exit(3); + } + } + if (!load(seed_file)) + exit_status = 1; + if (refresh_seed) { + if (random_get(random_buf, sizeof(random_buf), GRND_NONBLOCK) == -1) { + if (errno != EAGAIN) { + perror("getrandom"); + exit(1); + } + daemon(0, 1); + close(0); + close(1); + save(seed_path, NULL); + } else { + save(seed_path, random_buf); + } + } + exit(exit_status); + } else if (streq(argv[1], "save")) { + exit(!save(seed_path, NULL)); + } else if (streq(argv[1], "daemonize")) { + if (streq(seed_path, "-")) { + fputs("error: seed_path cannot be - for daemonize\n", stderr); + exit(2); + } + seed_file = fopen(seed_path, "r"); + if (!seed_file) { + perror("error opening seed file"); + exit(3); + } + load(seed_file); + + struct sigaction sa; + sa.sa_handler = sighandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + daemon(seed_path[0] != '/', 1); + close(0); + close(1); + // don't close stderr because we log there + while (true) { + if (sigint) + exit(exit_status); + if (!save(seed_path, NULL)) { + exit_status = 1; + fputs("an error occurred while saving, trying again later\n", stderr); + } + if (sigterm) + exit(exit_status); + sleep(DAEMONIZE_SLEEP_TIME); + } + } else if (streq(argv[1], "daemonise")) { + fputs("invalid mode (did you mean `daemonize'?)\n", stderr); + exit(2); + } else { + fputs("invalid mode, expected load, save, or daemonize\n", stderr); + exit(2); + } +} diff --git a/readall.c b/readall.c new file mode 100644 index 0000000..9f55697 --- /dev/null +++ b/readall.c @@ -0,0 +1,82 @@ +/* by Nominal Animal, 2017: https://stackoverflow.com/a/44894946 */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#include "readall.h" + +/* Size of each input chunk to be + read and allocate for. */ +#ifndef READALL_CHUNK +#define READALL_CHUNK 262144 +#endif + +/* This function returns one of the READALL_ constants above. + If the return value is zero == READALL_OK, then: + (*dataptr) points to a dynamically allocated buffer, with + (*sizeptr) chars read from the file. + The buffer is allocated for one extra char, which is NUL, + and automatically appended after the data. + Initial values of (*dataptr) and (*sizeptr) are ignored. +*/ +int readall(FILE *in, char **dataptr, size_t *sizeptr) +{ + char *data = NULL, *temp; + size_t size = 0; + size_t used = 0; + size_t n; + + /* None of the parameters can be NULL. */ + if (in == NULL || dataptr == NULL || sizeptr == NULL) + return READALL_INVALID; + + /* A read error already occurred? */ + if (ferror(in)) + return READALL_ERROR; + + while (1) { + + if (used + READALL_CHUNK + 1 > size) { + size = used + READALL_CHUNK + 1; + + /* Overflow check. Some ANSI C compilers + may optimize this away, though. */ + if (size <= used) { + free(data); + return READALL_TOOMUCH; + } + + temp = realloc(data, size); + if (temp == NULL) { + free(data); + return READALL_NOMEM; + } + data = temp; + } + + n = fread(data + used, 1, READALL_CHUNK, in); + if (n == 0) + break; + + used += n; + } + + if (ferror(in)) { + free(data); + return READALL_ERROR; + } + + temp = realloc(data, used + 1); + if (temp == NULL) { + free(data); + return READALL_NOMEM; + } + data = temp; + data[used] = '\0'; + + *dataptr = data; + *sizeptr = used; + + return READALL_OK; +} diff --git a/readall.h b/readall.h new file mode 100644 index 0000000..94c2bfe --- /dev/null +++ b/readall.h @@ -0,0 +1,14 @@ +#ifndef READALL_H +#define READALL_H + +#include <stdio.h> + +#define READALL_OK 0 /* Success */ +#define READALL_INVALID -1 /* Invalid parameters */ +#define READALL_ERROR -2 /* Stream error */ +#define READALL_TOOMUCH -3 /* Too much input */ +#define READALL_NOMEM -4 /* Out of memory */ + +int readall(FILE *in, char **dataptr, size_t *sizeptr); + +#endif @@ -0,0 +1,204 @@ +/* + * FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 02/02/2007 + * Issue date: 04/30/2005 + * + * Copyright (C) 2013, Con Kolivas <kernel@kolivas.org> + * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch> + * All rights reserved. + * + * 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 project 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 PROJECT 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 PROJECT 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 <string.h> + +#include "sha2.h" + +#define UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8_t) ((x) ); \ + *((str) + 2) = (uint8_t) ((x) >> 8); \ + *((str) + 1) = (uint8_t) ((x) >> 16); \ + *((str) + 0) = (uint8_t) ((x) >> 24); \ +} + +#define PACK32(str, x) \ +{ \ + *(x) = ((uint32_t) *((str) + 3) ) \ + | ((uint32_t) *((str) + 2) << 8) \ + | ((uint32_t) *((str) + 1) << 16) \ + | ((uint32_t) *((str) + 0) << 24); \ +} + +#define SHA256_SCR(i) \ +{ \ + w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \ + + SHA256_F3(w[i - 15]) + w[i - 16]; \ +} + +static uint32_t sha256_h0[8] = + {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + +uint32_t sha256_k[64] = + {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +/* SHA-256 functions */ + +void sha256_transf(sha256_ctx *ctx, const unsigned char *message, + unsigned int block_nb) +{ + uint32_t w[64]; + uint32_t wv[8]; + uint32_t t1, t2; + const unsigned char *sub_block; + int i; + + int j; + + for (i = 0; i < (int) block_nb; i++) { + sub_block = message + (i << 6); + + for (j = 0; j < 16; j++) { + PACK32(&sub_block[j << 2], &w[j]); + } + + for (j = 16; j < 64; j++) { + SHA256_SCR(j); + } + + for (j = 0; j < 8; j++) { + wv[j] = ctx->h[j]; + } + + for (j = 0; j < 64; j++) { + t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + + sha256_k[j] + w[j]; + t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + + for (j = 0; j < 8; j++) { + ctx->h[j] += wv[j]; + } + } +} + +void sha256(const unsigned char *message, unsigned int len, unsigned char *digest) +{ + sha256_ctx ctx; + + sha256_init(&ctx); + sha256_update(&ctx, message, len); + sha256_final(&ctx, digest); +} + +void sha256_init(sha256_ctx *ctx) +{ + memcpy(ctx->h, sha256_h0, sizeof(uint32_t)*8); + ctx->len = 0; + ctx->tot_len = 0; +} + +void sha256_update(sha256_ctx *ctx, const unsigned char *message, + unsigned int len) +{ + unsigned int block_nb; + unsigned int new_len, rem_len, tmp_len; + const unsigned char *shifted_message; + + tmp_len = SHA256_BLOCK_SIZE - ctx->len; + rem_len = len < tmp_len ? len : tmp_len; + + memcpy(&ctx->block[ctx->len], message, rem_len); + + if (ctx->len + len < SHA256_BLOCK_SIZE) { + ctx->len += len; + return; + } + + new_len = len - rem_len; + block_nb = new_len / SHA256_BLOCK_SIZE; + + shifted_message = message + rem_len; + + sha256_transf(ctx, ctx->block, 1); + sha256_transf(ctx, shifted_message, block_nb); + + rem_len = new_len % SHA256_BLOCK_SIZE; + + memcpy(ctx->block, &shifted_message[block_nb << 6], + rem_len); + + ctx->len = rem_len; + ctx->tot_len += (block_nb + 1) << 6; +} + +void sha256_final(sha256_ctx *ctx, unsigned char *digest) +{ + unsigned int block_nb; + unsigned int pm_len; + unsigned int len_b; + + int i; + + block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) + < (ctx->len % SHA256_BLOCK_SIZE))); + + len_b = (ctx->tot_len + ctx->len) << 3; + pm_len = block_nb << 6; + + memset(ctx->block + ctx->len, 0, pm_len - ctx->len); + ctx->block[ctx->len] = 0x80; + UNPACK32(len_b, ctx->block + pm_len - 4); + + sha256_transf(ctx, ctx->block, block_nb); + + for (i = 0 ; i < 8; i++) { + UNPACK32(ctx->h[i], &digest[i << 2]); + } +} @@ -0,0 +1,69 @@ +/* + * FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 02/02/2007 + * Issue date: 04/30/2005 + * + * Copyright (C) 2013, Con Kolivas <kernel@kolivas.org> + * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch> + * All rights reserved. + * + * 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 project 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 PROJECT 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 PROJECT 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. + */ + +#ifndef SHA2_H +#define SHA2_H + +#include <stdint.h> + +#define SHA256_DIGEST_SIZE ( 256 / 8) +#define SHA256_BLOCK_SIZE ( 512 / 8) + +#define SHFR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define CH(x, y, z) ((x & y) ^ (~x & z)) +#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) + +#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3)) +#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10)) + +typedef struct { + unsigned int tot_len; + unsigned int len; + unsigned char block[2 * SHA256_BLOCK_SIZE]; + uint32_t h[8]; +} sha256_ctx; + +extern uint32_t sha256_k[64]; + +void sha256_init(sha256_ctx * ctx); +void sha256_update(sha256_ctx *ctx, const unsigned char *message, + unsigned int len); +void sha256_final(sha256_ctx *ctx, unsigned char *digest); +void sha256(const unsigned char *message, unsigned int len, + unsigned char *digest); + +#endif /* !SHA2_H */ @@ -0,0 +1,93 @@ +#include <assert.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static inline signed char hexchr2int(char c) { + /* No, nobody uses EBCDIC anymore. */ + return (c >= '0' && c <= '9') ? (c - '0') : + (c >= 'a' && c <= 'f') ? (c - 'a' + 10) : + (c >= 'A' && c <= 'F') ? (c - 'A' + 10) : + -1; +} + +/** + * Decode hex. + * + * \param dest where to store the decoded data + * \param size the maximum number of bytes to decode + * \param src the hex-encoded data + * + * \return 0 if an error occurred, otherwise the number of bytes written to + * dest + */ +size_t hex2mem(unsigned char *dest, size_t size, const char *src) { +#ifdef DEBUG + fprintf(stderr, "hex decoding %zu bytes\n", size); +#endif + size_t i; + for (i = 0; i < size; i++) { + int n1 = hexchr2int(src[2*i]); + if (n1 < 0) return 0; + int n2 = hexchr2int(src[2*i+1]); + if (n2 < 0) return 0; + dest[i] = (unsigned char)(n1 << 4 | n2); + } + return i; +} + +static const char *HEX_CHARS = "0123456789abcdef"; + +/** Encode hex. + * + * \param dest where to store the encoded data (must have at least size*2+1 bytes) + * \param src the data to encode + * \param size the number of bytes to encode + */ +void mem2hex(char *dest, const void *src, size_t size) { + for (size_t i = 0; i < size; i++) { + unsigned char c = *((const unsigned char *)src + i); + dest[2*i] = HEX_CHARS[c >> 4]; + dest[2*i+1] = HEX_CHARS[c & 0xf]; + } + dest[size*2] = '\0'; +} + +void hash(const unsigned char salt[static SALT_LEN], unsigned char *out, const void *in, size_t size) { + assert(size < INT_MAX - SALT_LEN - 100); +#ifdef DEBUG + fprintf(stderr, "hashing %zu bytes starting with 0x%x ending with 0x%x\n", size, (int)((unsigned char*)in)[0], (int)((unsigned char*)in)[size-1]); +#endif + sha256_ctx ctx; + sha256_init(&ctx); + //sha256_update(&ctx, salt, SALT_LEN); + sha256_update(&ctx, in, (unsigned int)size); + sha256_final(&ctx, out); +} + +void print_hash(const unsigned char digest[static HASH_LEN]) { +#ifdef DEBUG + char hash[HASH_LEN*2+1]; + mem2hex(hash, digest, HASH_LEN); + fprintf(stderr, "hash: %s\n", hash); +#endif +} + +bool hash_match(const unsigned char digest[static HASH_LEN], const char *arg) { + unsigned char theirdigest[HASH_LEN]; + if (hex2mem(theirdigest, sizeof(theirdigest), arg) == 0) { + fputs("error decoding hex hash\n", stderr); + exit(1); + } +#ifdef DEBUG + fprintf(stderr, "comparing hash, theirs: %s = 0x%02x..0x%02x, ours: 0x%02x..0x%02x\n", arg, (int)theirdigest[0], (int)theirdigest[HASH_LEN-1], (int)digest[0], (int)theirdigest[HASH_LEN-1]); + fputs(" our ", stderr); + print_hash(digest); + fputs("their ", stderr); + print_hash(theirdigest); +#endif + return !memcmp(digest, theirdigest, HASH_LEN); +} @@ -0,0 +1,41 @@ +#ifndef UTIL_H +#define UTIL_H + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "sha2.h" + +#define GRND_NONBLOCK 0x01 +#define GRND_RANDOM 0x02 + +/* The pool size is fixed at 4096 bits since Linux 2.6. */ +#define RAND_POOL_SIZE 512 +/* SHA-256 */ +#define HASH_LEN 32 +/* The salt is the random data */ +#define SALT_LEN RAND_POOL_SIZE + +static inline bool streq(const char *s1, const char *s2) { + return !strcmp(s1, s2); +} + +static inline ssize_t random_get(void *buf, size_t buflen, unsigned int flags) { + long rv = syscall(SYS_getrandom, buf, buflen, flags); + if (rv == -1 && errno == ENOSYS) { + fputs("getrandom returned ENOSYS. random-seed requires Linux 3.17", stderr); + exit(1); + } + return rv; +} + +size_t hex2mem(unsigned char *dest, size_t size, const char *src); +void mem2hex(char *dest, const void *src, size_t size); +void hash(const unsigned char salt[static SALT_LEN], unsigned char *out, const void *in, size_t size); +bool hash_match(const unsigned char digest[static HASH_LEN], const char *arg); + +#endif |