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.
 
 
 
 

428 lines
11 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>
#include <assert.h>
#include <xmem.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 one of the GFSEC_ERR_*
* error codes.
*/
static ssize_t
get_line(FILE *f, char *buffer, size_t len)
{
int c;
size_t n = 0;
while ( (c = fgetc(f)) != '\n' ) {
if ( c == EOF )
return GFSEC_ERR_SYSTEM_ERROR;
if ( n >= len - 1 ) { /* Line too long */
while ( (c = fgetc(f)) != '\n' && c != EOF ) ; /* Discard line */
return GFSEC_ERR_CONFIG_LINE_TOO_LONG;
}
buffer[n++] = (char) c;
}
buffer[n] = '\0';
return n;
}
/**
* Extracts the authority portion from a URI string.
* Reads the provided string up to first '/' character.
*
* @param uri The address of the URI string. The pointer is
* advanced to the '/' character separating the
* authority from the path.
*
* @return A newly-allocated string containing the authority, or
* NULL if the uri did not contain a '/' character.
*/
static char *
parse_authority(const char **uri)
{
char *slash, *authority = NULL;
if ( (slash = strchr(*uri, '/')) ) {
authority = xstrndup(*uri, slash - *uri);
*uri = slash;
}
return authority;
}
/**
* Extracts the path portion from a URI string.
* Reads the provided string up to its end or up to the first
* '?' character (marking the end of the 'path' part of an URI).
*
* @param uri The address of the URI string. The pointer is advanced
* to the first '?' character, or to the terminating NULL
* byte.
*
* @return A newly-allocated buffer containing the path.
*/
static char *
parse_path(const char **uri)
{
char *qm, *path;
qm = strchrnul(*uri, '?');
path = xstrndup(*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 hex buffer.
* @param sha256 The address of a newly allocated buffer to store
* the hash value.
*
* @return 0 if the hash value was successfully parsed,
* or GFSEC_ERR_CONFIG_INVALID_HASH if the string did not contain
* a valid SHA-256 hash value.
*/
static int
parse_sha256(const char *hex, size_t len, unsigned char **sha256)
{
unsigned n;
if ( len != 64 )
return GFSEC_ERR_CONFIG_INVALID_HASH;
*sha256 = xmalloc(32);
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 {
/* Invalid character in the hash. */
free(*sha256);
*sha256 = NULL;
return GFSEC_ERR_CONFIG_INVALID_HASH;
}
if ( n % 2 == 0 )
(*sha256)[n / 2] = val << 4;
else
(*sha256)[n / 2] += val;
}
return 0;
}
/**
* Parses a name=value parameter in the provided string.
*
* @param uri The address of the string containing the URI to
* parse. The pointer is advanced to the character
* located after the value.
* @param share The share object to which the parameter will be
* assigned.
*
* @return 0 if a correctly formed name=value pair was
* found, or one of the GFSEC_ERR_* error codes.
*/
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, '=')) )
return GFSEC_ERR_CONFIG_INVALID_URI;
if ( strncmp(*uri, "share", eq - *uri) == 0 ) {
amp = strchrnul(++eq, '&');
if ( strncmp(eq, "no", amp - eq) == 0 )
share->number = 0;
*uri = amp;
}
else if ( strncmp(*uri, "sha256", eq - *uri) == 0 ) {
amp = strchrnul(++eq, '&');
rc = parse_sha256(eq, amp - eq, &(share->hash));
*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.
*
* @param path The path to extract the share number from.
*
* @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.
*
* @param uri The URI to parse.
* @param secret The gfsec_secret_t object to which a share
* will be added if the URI is correctly parsed.
* @param assign If non-zero, the share number will be randomly
* assigned and not extracted from the URI.
*
* @return 0 if the URI was successfully parsed, or one of the
* GFSEC_ERR_* error codes.
*/
int
gfsec_parse_uri(const char *uri, gfsec_secret_t *secret, int assign)
{
gfsec_share_t *share;
const char *p = uri;
int rc = 0;
assert(uri != NULL);
assert(secret != NULL);
share = gfsec_share_new();
if ( strncmp(p, "file://", 7) == 0 && (p += 7) )
share->scheme = GFSEC_SCHEME_FILE;
else if ( strncmp(p, "uuid://", 7) == 0 && (p += 7) )
share->scheme = GFSEC_SCHEME_UUID;
else if ( strncmp(p, "label://", 8) == 0 && (p += 8) )
share->scheme = GFSEC_SCHEME_LABEL;
else if ( strncmp(p, "mtp://", 6) == 0 && (p += 6) )
share->scheme = GFSEC_SCHEME_MTP;
else
rc = GFSEC_ERR_CONFIG_UNKNOWN_SCHEME;
if ( rc == 0 && ! (share->authority = parse_authority(&p)) )
rc = GFSEC_ERR_CONFIG_INVALID_URI;
if ( rc == 0 )
share->path = parse_path(&p);
while ( rc == 0 && *p )
rc = parse_parameter(&p, share);
if ( rc == 0 && ! gfsec_share_is_full(share) ) {
if ( assign )
share->number = GFSEC_SHARE_NUMBER_AUTOASSIGN;
else if ( (share->number = get_share_number_from_path(share->path)) == 0 )
rc = GFSEC_ERR_CONFIG_INVALID_URI;
}
if ( rc == 0 )
rc = gfsec_secret_add_share(secret, share);
if ( rc != 0 )
gfsec_share_free(share);
return rc;
}
/**
* Parses the specified configuration file.
*
* @param cfg The address of a pointer to a gfsec_secret_t
* structure which will be automatically allocated
* in this function.
* @param filename The location of the configuration file.
* @param line The address of an integer which will be set to the
* number of the last line read (may be NULL).
*
* @return
* - 0 if successful;
* - GFSEC_ERR_SYSTEM_ERROR if an error occured when reading
* the file;
* - GFSEC_ERR_TOO_MANY_SHARES if the configuration describes
* a secret with too many shares (more than 255);
* - one of the GFSEC_ERR_CONFIG_* error codes if the syntax
* of the configuration file is invalid.
*/
int
gfsec_read_config(gfsec_secret_t **cfg,
const char *filename,
unsigned int *line)
{
FILE *f;
char buffer[MAX_LINE_LENGTH];
int rc;
assert(cfg != NULL);
assert(filename != NULL);
if ( ! (f = fopen(filename, "r")) )
return GFSEC_ERR_SYSTEM_ERROR;
*cfg = gfsec_secret_new();
(*cfg)->threshold = 2;
if ( line )
*line = 0;
rc = 0;
while ( rc >= 0 ) {
ssize_t n;
if ( line )
*line += 1;
n = get_line(f, buffer, sizeof(buffer));
if ( n == GFSEC_ERR_SYSTEM_ERROR && feof(f) )
break; /* End of file. */
else if ( n < 0 )
rc = n; /* Abort on any other error. */
else if ( n == 0 || buffer[0] == '#' )
; /* Comment or empty line */
else if ( strncmp("OUTFILE=", buffer, 8) == 0 )
(*cfg)->filename = xstrdup(buffer + 8);
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 = GFSEC_ERR_CONFIG_INVALID_THRESHOLD;
else
(*cfg)->threshold = (unsigned char) l;
}
else if ( strncmp("URI=", buffer, 4) == 0 )
rc = gfsec_parse_uri(&(buffer[4]), *cfg, 0);
}
fclose(f);
if ( rc != 0 ) {
gfsec_secret_free(*cfg);
*cfg = NULL;
}
return rc;
}
/**
* Write a configuration file for the specified secret object.
*
* @param cfg The secret object to write a configuration for.
* @param filename The location of the file to write.
*
* @return
* - 0 if successful;
* - GFSEC_ERR_SYSTEM_ERROR if an I/O error occured.
*/
int
gfsec_write_config(gfsec_secret_t *cfg, const char *filename)
{
FILE *f;
char buffer[65];
unsigned n, m;
static char *schemes[] = { "file://", "uuid://", "label://", "mtp://" };
assert(cfg != NULL);
assert(filename != NULL);
if ( ! (f = fopen(filename, "w")) )
return GFSEC_ERR_SYSTEM_ERROR;
fprintf(f, "OUTFILE=%s\n", cfg->filename);
fprintf(f, "MINSHARES=%u\n", cfg->threshold);
for ( n = 0; n < cfg->n_shares; n++ ) {
gfsec_share_t *share = cfg->shares[n];
for ( m = 0; m < 32; m++ )
sprintf(buffer + (m*2), "%02x", share->hash[m]);
fprintf(f, "URI=%s%s%s?sha256=%s%s\n",
schemes[share->scheme],
share->authority ? share->authority : "",
share->path,
buffer,
gfsec_share_is_full(share) ? "&share=no" : "");
}
fclose(f);
return 0;
}