diff --git a/editor.c b/editor.c index 988a730..a484422 100644 --- a/editor.c +++ b/editor.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "editor.h" #include "util.h" #include "keydb.h" @@ -58,7 +59,10 @@ static enum cmd_returncode_t cmd_help(struct editor_context_t *ctx, const char * static enum cmd_returncode_t cmd_new(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_list(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_add_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); +static enum cmd_returncode_t cmd_del_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); +static enum cmd_returncode_t cmd_rekey_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); +static enum cmd_returncode_t cmd_del_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_rekey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_showkey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); static enum cmd_returncode_t cmd_open(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params); @@ -90,24 +94,40 @@ static const struct editor_command_t commands[] = { }, { .cmdnames = { "del_host" }, - .callback = cmd_add_host, + .callback = cmd_del_host, .param_names = "[hostname]", .min_params = 1, .max_params = 1, .description = "Removes a host from the database file", }, + { + .cmdnames = { "rekey_host" }, + .callback = cmd_rekey_host, + .param_names = "[hostname]", + .min_params = 1, + .max_params = 1, + .description = "Re-keys the TLS PSK of a given host", + }, { .cmdnames = { "add_volume" }, .callback = cmd_add_volume, - .param_names = "[hostname] [volumename] [volume-UUID]", + .param_names = "[hostname] [devmappername] [volume-UUID]", .min_params = 3, .max_params = 3, .description = "Add a new volume to the hostname", }, + { + .cmdnames = { "del_volume" }, + .callback = cmd_del_volume, + .param_names = "[hostname] [devmappername]", + .min_params = 2, + .max_params = 2, + .description = "Removes a volume from the given host", + }, { .cmdnames = { "rekey_volume" }, .callback = cmd_rekey_volume, - .param_names = "[hostname] [volumename]", + .param_names = "[hostname] [devmappername]", .min_params = 2, .max_params = 2, .description = "Re-keys the LUKS passphrase of a volume of a given hostname", @@ -115,7 +135,7 @@ static const struct editor_command_t commands[] = { { .cmdnames = { "showkey_volume" }, .callback = cmd_showkey_volume, - .param_names = "[hostname] [volumename]", + .param_names = "[hostname] [devmappername]", .min_params = 2, .max_params = 2, .description = "Shows the LUKS passphrase of a volume of a hostname", @@ -195,24 +215,78 @@ static enum cmd_returncode_t cmd_add_host(struct editor_context_t *ctx, const ch return COMMAND_FAILURE; } } - bool success = keydb_add_host(&ctx->keydb, params[0]); + const char *host_name = params[0]; + bool success = keydb_add_host(&ctx->keydb, host_name); return success ? COMMAND_SUCCESS : COMMAND_FAILURE; } -static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { +static enum cmd_returncode_t cmd_del_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { if (!ctx->keydb) { fprintf(stderr, "No key database loaded.\n"); return COMMAND_FAILURE; } const char *host_name = params[0]; + bool success = keydb_del_host_by_name(&ctx->keydb, host_name); + return success ? COMMAND_SUCCESS : COMMAND_FAILURE; +} + +static struct host_entry_t* cmd_gethost(struct editor_context_t *ctx, const char *host_name) { + if (!ctx->keydb) { + fprintf(stderr, "No key database loaded.\n"); + return NULL; + } struct host_entry_t *host = keydb_get_host_by_name(ctx->keydb, host_name); if (!host) { fprintf(stderr, "No such host: %s\n", host_name); + return NULL; + } + return host; +} + +static struct volume_entry_t* cmd_getvolume(struct editor_context_t *ctx, const char *host_name, const char *devmapper_name) { + struct host_entry_t *host = cmd_gethost(ctx, host_name); + if (!host) { + return NULL; + } + + struct volume_entry_t *volume = keydb_get_volume_by_name(host, devmapper_name); + if (!volume) { + fprintf(stderr, "No such volume \"%s\" for host \"%s\"\n", devmapper_name, host_name); + return NULL; + } + return volume; +} + +static enum cmd_returncode_t cmd_rekey_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { + const char *host_name = params[0]; + struct host_entry_t *host = cmd_gethost(ctx, host_name); + return host && keydb_rekey_host(host) ? COMMAND_SUCCESS : COMMAND_FAILURE; +} + +static enum cmd_returncode_t cmd_do_showkey_volume(struct volume_entry_t *volume) { + char luks_passphrase[PASSPHRASE_TEXT_SIZE_BYTES]; + if (!keydb_get_volume_luks_passphrase(volume, luks_passphrase, sizeof(luks_passphrase))) { + OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase)); + fprintf(stderr, "Could not determine LUKS passphrase.\n"); + return COMMAND_FAILURE; + } + char uuid[ASCII_UUID_BUFSIZE]; + sprintf_uuid(uuid, volume->volume_uuid); + printf("LUKS passphrase of %s / %s: %s\n", volume->devmapper_name, uuid, luks_passphrase); + + OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase)); + return COMMAND_SUCCESS; +} + +static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { + const char *host_name = params[0]; + struct host_entry_t *host = cmd_gethost(ctx, host_name); + if (!host) { return COMMAND_FAILURE; } - const char *volume_name = params[1]; + const char *devmapper_name = params[1]; const char *volume_uuid_str = params[2]; if (!is_valid_uuid(volume_uuid_str)) { fprintf(stderr, "Not a valid UUID: %s\n", volume_uuid_str); @@ -220,29 +294,52 @@ static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const } uint8_t volume_uuid[16]; parse_uuid(volume_uuid, volume_uuid_str); - return keydb_add_volume(host, volume_name, volume_uuid) ? COMMAND_SUCCESS : COMMAND_FAILURE; + if (keydb_add_volume(host, devmapper_name, volume_uuid)) { + struct volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name); + return cmd_do_showkey_volume(volume); + } else { + return COMMAND_FAILURE; + } +} + +static enum cmd_returncode_t cmd_del_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { + const char *host_name = params[0]; + const char *devmapper_name = params[1]; + + struct host_entry_t *host = cmd_gethost(ctx, host_name); + if (!host) { + return COMMAND_FAILURE; + } + if (!keydb_del_volume(host, devmapper_name)) { + return COMMAND_FAILURE; + } + return COMMAND_SUCCESS; } static enum cmd_returncode_t cmd_rekey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { - return COMMAND_SUCCESS; + const char *host_name = params[0]; + const char *devmapper_name = params[1]; + + struct volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name); + if (!volume) { + return COMMAND_FAILURE; + } + if (!keydb_rekey_volume(volume)) { + return COMMAND_FAILURE; + } + return cmd_do_showkey_volume(volume); } static enum cmd_returncode_t cmd_showkey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { const char *host_name = params[0]; - struct host_entry_t *host = keydb_get_host_by_name(ctx->keydb, host_name); - if (!host) { - fprintf(stderr, "No such host: %s\n", host_name); - return COMMAND_FAILURE; - } + const char *devmapper_name = params[1]; - const char *volume_name = params[1]; - struct volume_entry_t *volume = keydb_get_volume_by_name(host, volume_name); + struct volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name); if (!volume) { - fprintf(stderr, "No such volume: %s\n", volume_name); return COMMAND_FAILURE; } - return COMMAND_SUCCESS; + return cmd_do_showkey_volume(volume); } static enum cmd_returncode_t cmd_open(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) { @@ -355,7 +452,7 @@ void editor_start(void) { } #ifndef __TEST_EDITOR__ -// gcc -D_POSIX_SOURCE -Wall -std=c11 -Wmissing-prototypes -Wstrict-prototypes -Werror=implicit-function-declaration -Wimplicit-fallthrough -Wshadow -pie -fPIE -fsanitize=address -fsanitize=undefined -fsanitize=leak -o editor editor.c util.c log.c keydb.c file_encryption.c -lasan -lubsan -lcrypto && ./editor +// gcc -O3 -ggdb3 -D_POSIX_SOURCE -Wall -std=c11 -Wmissing-prototypes -Wstrict-prototypes -Werror=implicit-function-declaration -Wimplicit-fallthrough -Wshadow -pie -fPIE -fsanitize=address -fsanitize=undefined -fsanitize=leak -o editor editor.c util.c log.c keydb.c file_encryption.c uuid.c -lcrypto && ./editor int main(int argc, char **argv) { diff --git a/global.h b/global.h index 4ff074d..09eb82c 100644 --- a/global.h +++ b/global.h @@ -39,6 +39,9 @@ /* How long a passphrase is (this is raw binary, not text) */ #define PASSPHRASE_SIZE_BYTES 32 +/* How long a passphrase is in it's encoded form, storing it as a character array */ +#define PASSPHRASE_TEXT_SIZE_BYTES ((((PASSPHRASE_SIZE_BYTES + 2) / 3) * 4) + 1) + /* How long in characters a cryptsetup device name mapping may be */ #define MAX_DEVMAPPER_NAME_LENGTH 63 diff --git a/keydb.c b/keydb.c index 63a4a9b..9d205ff 100644 --- a/keydb.c +++ b/keydb.c @@ -53,6 +53,36 @@ void keydb_free(struct keydb_t *keydb) { free(keydb); } +static int keydb_get_volume_index_by_name(struct host_entry_t *host, const char *devmapper_name) { + for (unsigned int i = 0; i < host->volume_count; i++) { + struct volume_entry_t *volume = &host->volumes[i]; + if (!strcasecmp(volume->devmapper_name, devmapper_name)) { + return i; + } + } + return -1; +} + +static int keydb_get_host_index_by_name(struct keydb_t *keydb, const char *host_name) { + for (unsigned int i = 0; i < keydb->host_count; i++) { + struct host_entry_t *host = &keydb->hosts[i]; + if (!strcasecmp(host->host_name, host_name)) { + return i; + } + } + return -1; +} + +struct volume_entry_t *keydb_get_volume_by_name(struct host_entry_t *host, const char *devmapper_name) { + const int index = keydb_get_volume_index_by_name(host, devmapper_name); + return (index >= 0) ? &host->volumes[index] : NULL; +} + +struct host_entry_t *keydb_get_host_by_name(struct keydb_t *keydb, const char *host_name) { + const int index = keydb_get_host_index_by_name(keydb, host_name); + return (index >= 0) ? &keydb->hosts[index] : NULL; +} + bool keydb_add_host(struct keydb_t **keydb, const char *host_name) { struct keydb_t *old_keydb = *keydb; if (keydb_get_host_by_name(old_keydb, host_name)) { @@ -73,7 +103,7 @@ bool keydb_add_host(struct keydb_t **keydb, const char *host_name) { return false; } strncpy(host->host_name, host_name, sizeof(host->host_name) - 1); - if (!buffer_randomize(host->tls_psk, sizeof(host->tls_psk))) { + if (!keydb_rekey_host(host)) { /* We keep the reallocation but do not increase the host count */ return false; } @@ -82,6 +112,24 @@ bool keydb_add_host(struct keydb_t **keydb, const char *host_name) { return true; } +bool keydb_del_host_by_name(struct keydb_t **keydb, const char *host_name) { + struct keydb_t *old_keydb = *keydb; + int host_index = keydb_get_host_index_by_name(old_keydb, host_name); + if (host_index == -1) { + log_msg(LLVL_ERROR, "No such host: \"%s\"", host_name); + return false; + } + + /* We keep the memory for now and do not realloc */ + array_remove(old_keydb->hosts, sizeof(struct host_entry_t), old_keydb->host_count, host_index); + old_keydb->host_count--; + return true; +} + +bool keydb_rekey_host(struct host_entry_t *host) { + return buffer_randomize(host->tls_psk, sizeof(host->tls_psk)); +} + bool keydb_add_volume(struct host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]) { if (host->volume_count == MAX_VOLUMES_PER_HOST) { log_msg(LLVL_ERROR, "Host \"%s\" already has maximum number of volumes (%d).", host->host_name, MAX_VOLUMES_PER_HOST); @@ -103,38 +151,26 @@ bool keydb_add_volume(struct host_entry_t *host, const char *devmapper_name, con return true; } -int keydb_get_volume_index_by_name(struct host_entry_t *host, const char *devmapper_name) { - for (unsigned int i = 0; i < host->volume_count; i++) { - struct volume_entry_t *volume = &host->volumes[i]; - if (!strcasecmp(volume->devmapper_name, devmapper_name)) { - return i; - } +bool keydb_del_volume(struct host_entry_t *host, const char *devmapper_name) { + int index = keydb_get_volume_index_by_name(host, devmapper_name); + if (index == -1) { + log_msg(LLVL_ERROR, "No such volume \"%s\" for host \"%s\".", devmapper_name, host->host_name); + return false; } - return -1; -} - -struct volume_entry_t *keydb_get_volume_by_name(struct host_entry_t *host, const char *devmapper_name) { - const int index = keydb_get_volume_index_by_name(host, devmapper_name); - return (index >= 0) ? &host->volumes[index] : NULL; -} - -int keydb_get_host_index_by_name(struct keydb_t *keydb, const char *host_name) { - for (unsigned int i = 0; i < keydb->host_count; i++) { - struct host_entry_t *host = &keydb->hosts[i]; - if (!strcasecmp(host->host_name, host_name)) { - return i; - } + if (!array_remove(host->volumes, sizeof(struct volume_entry_t), host->volume_count, index)) { + log_msg(LLVL_ERROR, "Failed to remove \"%s\" of host \"%s\".", devmapper_name, host->host_name); + return false; } - return -1; + host->volume_count--; + return true; } -bool keydb_get_volume_luks_passphrase(const struct volume_entry_t *volume, char *dest) { - return ascii_encode(dest, volume->luks_passphrase, sizeof(volume->luks_passphrase)); +bool keydb_rekey_volume(struct volume_entry_t *volume) { + return buffer_randomize(volume->luks_passphrase, sizeof(volume->luks_passphrase)); } -struct host_entry_t *keydb_get_host_by_name(struct keydb_t *keydb, const char *host_name) { - const int index = keydb_get_host_index_by_name(keydb, host_name); - return (index >= 0) ? &keydb->hosts[index] : NULL; +bool keydb_get_volume_luks_passphrase(const struct volume_entry_t *volume, char *dest, unsigned int dest_buffer_size) { + return ascii_encode(dest, dest_buffer_size, volume->luks_passphrase, sizeof(volume->luks_passphrase)); } bool keydb_write(const struct keydb_t *keydb, const char *filename, const char *passphrase) { diff --git a/keydb.h b/keydb.h index 249da6b..b0c3435 100644 --- a/keydb.h +++ b/keydb.h @@ -56,13 +56,15 @@ struct keydb_t { /*************** AUTO GENERATED SECTION FOLLOWS ***************/ struct keydb_t* keydb_new(void); void keydb_free(struct keydb_t *keydb); -bool keydb_add_host(struct keydb_t **keydb, const char *host_name); -bool keydb_add_volume(struct host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]); -int keydb_get_volume_index_by_name(struct host_entry_t *host, const char *devmapper_name); struct volume_entry_t *keydb_get_volume_by_name(struct host_entry_t *host, const char *devmapper_name); -int keydb_get_host_index_by_name(struct keydb_t *keydb, const char *host_name); -bool keydb_get_volume_luks_passphrase(const struct volume_entry_t *volume, char *dest); struct host_entry_t *keydb_get_host_by_name(struct keydb_t *keydb, const char *host_name); +bool keydb_add_host(struct keydb_t **keydb, const char *host_name); +bool keydb_del_host_by_name(struct keydb_t **keydb, const char *host_name); +bool keydb_rekey_host(struct host_entry_t *host); +bool keydb_add_volume(struct host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]); +bool keydb_del_volume(struct host_entry_t *host, const char *devmapper_name); +bool keydb_rekey_volume(struct volume_entry_t *volume); +bool keydb_get_volume_luks_passphrase(const struct volume_entry_t *volume, char *dest, unsigned int dest_buffer_size); bool keydb_write(const struct keydb_t *keydb, const char *filename, const char *passphrase); struct keydb_t* keydb_read(const char *filename); /*************** AUTO GENERATED SECTION ENDS ***************/ diff --git a/util.c b/util.c index 3327930..24bf8a0 100644 --- a/util.c +++ b/util.c @@ -142,32 +142,43 @@ bool buffer_randomize(uint8_t *buffer, unsigned int length) { return true; } -bool array_remove(void *base, unsigned int element_length, unsigned int element_count, unsigned int remove_element_index) { +bool array_remove(void *base, unsigned int element_size, unsigned int element_count, unsigned int remove_element_index) { if (remove_element_index >= element_count) { return false; } uint8_t *bytebase = (uint8_t*)base; - const unsigned int destination_offset = remove_element_index * element_length; - const unsigned int source_offset = (remove_element_index + 1) * element_length; - const unsigned int copy_length = ((element_count - 1) - remove_element_index) * element_length; + const unsigned int destination_offset = remove_element_index * element_size; + const unsigned int source_offset = (remove_element_index + 1) * element_size; + const unsigned int copy_length = ((element_count - 1) - remove_element_index) * element_size; if (copy_length) { memcpy(bytebase + destination_offset, bytebase + source_offset, copy_length); } + + /* Then, wipe the last element */ + const unsigned int last_element_offset = element_size * (element_count - 1); + memset(base + last_element_offset, 0, element_size); return true; } -bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length) { +static uint8_t get_array_value(const uint8_t *array, unsigned int array_length, unsigned int array_index) { + if (array_index < array_length) { + return array[array_index]; + } else { + return 0; + } +} - const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+"; - if ((source_data_length % 4) != 0) { - log_libc(LLVL_FATAL, "Can only encode binary data of which length is divisible by four, %d is not.", source_data_length); +bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length) { + const unsigned int require_dest_size = ((source_data_length + 2) / 3) * 4 + 1; + if (dest_buffer_size < require_dest_size) { + log_msg(LLVL_FATAL, "Encoding of %d bytes takes a %d byte buffer, but only %d bytes provided.", source_data_length, require_dest_size, dest_buffer_size); return false; } - const unsigned int require_dest_size = source_data_length / 4 + const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (unsigned int i = 0; i < source_data_length; i += 3) { - uint32_t word = (source_data[i + 0] << 0) | (source_data[i + 1] << 8) | (source_data[i + 2] << 16); - for (unsigned int shift = 0; shift < 24; shift += 6) { + uint32_t word = ((get_array_value(source_data, source_data_length, i + 0) << 16) | (get_array_value(source_data, source_data_length, i + 1) << 8) | (get_array_value(source_data, source_data_length, i + 2) << 0)); + for (int shift = 18; shift >= 0; shift -= 6) { *dest++ = alphabet[(word >> shift) & 0x3f]; } } diff --git a/util.h b/util.h index 078d287..e7fa826 100644 --- a/util.h +++ b/util.h @@ -39,7 +39,7 @@ int parse_hexstr(const char *hexstr, uint8_t *data, int maxlen); bool truncate_crlf(char *string); bool buffer_randomize(uint8_t *buffer, unsigned int length); bool array_remove(void *base, unsigned int element_length, unsigned int element_count, unsigned int remove_element_index); -bool ascii_encode(char *dest, const uint8_t *source_data, unsigned int source_data_length); +bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length); /*************** AUTO GENERATED SECTION ENDS ***************/ #endif diff --git a/uuid.h b/uuid.h index 70f5437..29826e6 100644 --- a/uuid.h +++ b/uuid.h @@ -27,6 +27,9 @@ #include #include +/* Already includes zero termination */ +#define ASCII_UUID_BUFSIZE 37 + /*************** AUTO GENERATED SECTION FOLLOWS ***************/ bool is_valid_uuid(const char *ascii_uuid); bool parse_uuid(uint8_t *uuid, const char *ascii_uuid);