Tools to make secret sharing easier.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

420 lines
9.7 KiB

/*
* gfsec - Secret sharing tools
* Copyright (C) 2016 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "secretcfg.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define MAX_LINE_LENGTH 256
/**
* Reads a single line from the specified stream.
* This function differs from standard fgets(3) in two aspects: the
* newline character is not stored, and if the line to read is too long
* to fit into the provided buffer, the whole line is discarded.
*
* @param f The stream to read from.
* @param buffer A character buffer to store the line into.
* @param len Size of the \a buffer array.
*
* @return The number of characters read, or -1 if an error occured.
* If the line was too long, -1 is returned and errno is set to
* EMSGSIZE.
*/
static ssize_t
get_line(FILE *f, char *buffer, size_t len)
{
int c;
size_t n = 0;
if ( ! f || ! buffer ) {
errno = EINVAL;
return -1;
}
while ( (c = fgetc(f)) != '\n' ) {
if ( c == EOF )
return -1;
if ( n >= len - 1 ) { /* Line too long */
errno = EMSGSIZE;
while ( (c = fgetc(f)) != '\n' && c != EOF ) ; /* Discard line */
return -1;
}
buffer[n++] = (char) c;
}
buffer[n] = '\0';
return n;
}
/**
* Reads the provided string up to the first '/' character.
* The pointer is advanced to that character.
*
* @return A newly allocated copy of the part of the string
* before the '/' character. If no '/' character was found,
* NULL is returned and errno is set to EBADMSG.
*/
static char *
parse_authority(const char **uri)
{
char *slash, *authority = NULL;
if ( ! uri ) {
errno = EBADMSG;
return NULL;
}
if ( (slash = strchr(*uri, '/')) ) {
authority = strndup(*uri, slash - *uri);
*uri = slash;
}
else
errno = EBADMSG;
return authority;
}
/**
* Reads the provided string up to its end or up to the first
* '?' character (marking the end of the 'path' part of an URI).
* The pointer is advanced to that character, or to the
* terminating NULL byte.
*
* @return A newly allocated copy of the part of the string
* before the '?' character (or the whole string if no '?'
* was found).
*/
static char *
parse_path(const char **uri)
{
char *qm, *path = NULL;
if ( ! uri ) {
errno = EBADMSG;
return NULL;
}
qm = strchrnul(*uri, '?');
path = strndup(*uri, qm - *uri);
*uri = qm;
return path;
}
/**
* Parses a hexadecimal-encoded SHA-256 hash value.
*
* @param hex The encoded hash to parse.
* @param len The length of the \a hex buffer.
*
* @return A newly allocated buffer containing the
* hash value, or NULL if an error occured.
*/
static unsigned char *
parse_sha256(const char *hex, size_t len)
{
unsigned char *sha256 = NULL;
unsigned n;
if ( len != 64 ) {
errno = EBADMSG;
return NULL;
}
if ( ! (sha256 = malloc(32)) )
return NULL;
for ( n = 0; n < len; n++ ) {
unsigned char h, val;
h = hex[n];
if ( h >= '0' && h <= '9' )
val = h - '0';
else if ( h >= 'A' && h <= 'F' )
val = h - 'A' + 10;
else if ( h >= 'a' && h <= 'f' )
val = h - 'a' + 10;
else
n = 99;
if ( n % 2 == 0 )
sha256[n / 2] = val << 4;
else
sha256[n / 2] += val;
}
if ( n == 100 ) {
free(sha256);
sha256 = NULL;
}
return sha256;
}
/**
* Parses a name=value parameter in the provided string.
* Advances the pointer to the next character after the
* value.
*
* @return 0 if a correctly formed name=value pair was
* found. Otherwise, -1 is returned and errno is set to
* EBADMSG.
*/
static int
parse_parameter(const char **uri, gfsec_share_t *share)
{
char *eq, *amp;
int rc = 0;
if ( **uri == '?' || **uri == '&' )
*uri += 1; /* Skip initial delimiter. */
if ( **uri == '\0' )
return 0; /* Silently ignore terminal delimiter. */
if ( ! (eq = strchr(*uri, '=')) ) {
errno = EBADMSG;
return -1;
}
if ( strncmp(*uri, "share", eq - *uri) == 0 ) {
amp = strchrnul(++eq, '&');
if ( strncmp(eq, "no", amp - eq) == 0 )
share->flags |= GFSEC_SHARE_FLAGS_FULL;
*uri = amp;
}
else if ( strncmp(*uri, "sha256", eq - *uri) == 0 ) {
amp = strchrnul(++eq, '&');
if ( ! (share->sha256 = parse_sha256(eq, amp - eq)) )
rc = -1;
*uri = amp;
}
else { /* Skip unknown parameter. */
while ( **uri != '\0' && **uri != '&' )
(*uri) += 1;
}
return rc;
}
/**
* Extract the share number from the last 3 characters of the
* specified path.
*
* @return The share number, or 0 if the path does not end
* with a correctly formatted share number.
*/
static unsigned char
get_share_number_from_path(const char *path)
{
size_t len;
unsigned long sharenr;
char *endptr;
len = strlen(path);
if ( len < 4 )
return 0;
if ( path[len - 4] != '.' )
return 0;
sharenr = strtoul(&(path[len - 3]), &endptr, 10);
if ( *endptr != '\0' )
return 0;
if ( sharenr > 255 )
return 0;
return (unsigned char) sharenr;
}
/**
* Parses the specified URI into a share object.
*
* @return 0 if the URI was successfully parsed; -1 if an error
* occured or if the URI is malformed (in that last case, errno
* is set to EBADMSG).
*/
static int
parse_uri(const char *uri, gfsec_secret_config_t *cfg)
{
gfsec_share_t *share;
const char *p = uri;
if ( ! uri || ! cfg ) {
errno = EINVAL;
return -1;
}
if ( ! (share = malloc(sizeof(*share))) )
return -1;
share->next = NULL;
share->authority = NULL;
share->path = NULL;
share->share_nr = 0;
share->flags = GFSEC_SHARE_FLAGS_NONE;
share->length = 0;
share->data = NULL;
share->sha256 = NULL;
if ( strncmp(p, "file://", 7) == 0 && (p += 7) )
share->scheme = SCHEME_FILE;
else if ( strncmp(p, "uuid://", 7) == 0 && (p += 7) )
share->scheme = SCHEME_UUID;
else if ( strncmp(p, "label://", 8) == 0 && (p += 8) )
share->scheme = SCHEME_LABEL;
else if ( strncmp(p, "mtp://", 6) == 0 && (p += 6) )
share->scheme = SCHEME_MTP;
else {
errno = EBADMSG;
goto bad_uri;
}
if ( ! (share->authority = parse_authority(&p)) )
goto bad_uri;
if ( ! (share->path = parse_path(&p)) )
goto bad_uri;
while ( *p ) {
if ( parse_parameter(&p, share) == -1 )
goto bad_uri;
}
if ( (share->flags & GFSEC_SHARE_FLAGS_FULL) == 0 ) {
if ( (share->share_nr = get_share_number_from_path(share->path)) == 0 ) {
errno = EBADMSG;
goto bad_uri;
}
}
if ( cfg->shares ) {
gfsec_share_t *cursor = cfg->shares;
while ( cursor->next )
cursor = cursor->next;
cursor->next = share;
}
else
cfg->shares = share;
cfg->n_shares += 1;
return 0;
bad_uri:
gfsec_destroy_share(share, 0);
return -1;
}
/**
* Parses the specified configuration file.
*
* @return A newly allocated configuration object, or NULL if
* an error occured. If the configuration file contains an
* error, errno is set to EBADMSG.
*/
gfsec_secret_config_t *
gfsec_read_config(const char *filename)
{
FILE *f;
char buffer[MAX_LINE_LENGTH];
gfsec_secret_config_t *cfg;
int rc;
if ( ! filename ) {
errno = EINVAL;
return NULL;
}
if ( ! (f = fopen(filename, "r")) )
return NULL;
if ( ! (cfg = malloc(sizeof(*cfg))) ) {
fclose(f);
return NULL;
}
cfg->output_file = NULL;
cfg->shares = NULL;
cfg->threshold = 2;
rc = 0;
while ( rc >= 0 ) {
ssize_t n;
n = get_line(f, buffer, sizeof(buffer));
if ( n < 0 ) {
rc = feof(f) ? 0 : -1;
break;
}
else if ( n == 0 || buffer[0] == '#' )
; /* Comment or empty line */
else if ( strncmp("OUTFILE=", buffer, 8) == 0 ) {
if ( ! (cfg->output_file = strdup(buffer + 8)) )
rc = -1;
}
else if ( strncmp("MINSHARES=", buffer, 10) == 0 ) {
char *endptr;
unsigned long l;
l = strtoul(buffer + 10, &endptr, 10);
if ( *endptr != '\0' || l < 2 || l > 255 )
rc = -1, errno = EBADMSG;
else
cfg->threshold = (unsigned char) l;
}
else if ( strncmp("URI=", buffer, 4) == 0 ) {
if ( parse_uri(&(buffer[4]), cfg) == -1 )
rc = -1;
}
}
fclose(f);
if ( rc == -1 ) {
gfsec_destroy_config(cfg);
cfg = NULL;
}
return cfg;
}
/**
* Frees the specified configuration object.
*/
void
gfsec_destroy_config(gfsec_secret_config_t *cfg)
{
if ( cfg ) {
if ( cfg->output_file )
free(cfg->output_file);
gfsec_destroy_share(cfg->shares, 1);
free(cfg);
}
}