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.
 
 
 
 

662 lines
16 KiB

/*
* gfsecret - Secret sharing tools
* Copyright (C) 2016,2017,2018,2020 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 "secret.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <gcrypt.h>
#include <libgfshare.h>
#include <xmem.h>
#include "util.h"
/** @file secret.c
* Secret splitting module.
*
* How to use this module to split a secret.
*
* Create a secret object and fills it with the secret data:
* \code
* gfsec_secret_t *secret = gfsec_secret_new();
* gfsec_secret_set_secret_file(secret, filename, filename);
* \endcode
*
* Create share objects describing the shares to split the
* secret into, and add them to the secret object:
* \code
* gfsec_share_t *share = gfsec_share_new();
* gfsec_share_set_info(share, GFSEC_SHARE_NUMBER_AUTOASSIGN,
* GFSEC_SCHEME_FILE, authority, path, NULL);
* gfsec_secret_add_share(secret, share);
* \endcode
*
* Actually split the secret and write the shares' data:
* \code
* gfsec_secret_split(secret, threshold);
* for ( int i = 0; i < secret->n_shares; i++ ) {
* gfsec_share_t *share = secret->shares[i];
* write_data_somewhere(share->data, share->len);
* }
* \endcode
*
*
* How to use this module to reconstitute a secret.
*
* Create a secret object:
* \code
* gfsec_secret_t *secret = gfsec_secret_new();
* secret->threshold = threshold;
* \endcode
*
* Create share objects describing the shares, and add them
* to the secret object:
* \code
* gfsec_share_t *share = gfsec_share_new();
* gfsec_share_set_info(share, share_number, GFSEC_SCHEME_FILE,
* authority, path, hash);
* \endcode
*
* Fill the shares with data if available:
* \code
* gfsec_share_set_data(share, data, len);
* \endcode
*
* Actually reconstitute the secret:
* \code
* if ( gfsec_secret_can_recombine(secret) ) {
* gfsec_secret_recombine(secret);
* do_something_with_secret(secret->data, secret->len);
* }
* \endcode
*
*/
/**
* Fills a buffer with random data.
* This function is used internally by libgfshare.
*
* @param buffer The buffer to fill.
* @param len The buffer size.
*/
static void
gfsec_fill_rand(unsigned char *buffer, unsigned int len)
{
gcry_randomize(buffer, len, GCRY_STRONG_RANDOM);
}
/**
* Initializes a new share object.
*
* @return The share object.
*/
gfsec_share_t *
gfsec_share_new(void)
{
gfsec_share_t *s;
s = xmalloc(sizeof(gfsec_share_t));
s->number = GFSEC_SHARE_NUMBER_AUTOASSIGN;
s->scheme = GFSEC_SCHEME_FILE;
s->authority = NULL;
s->path = NULL;
s->len = 0;
s->data = NULL;
s->hash = NULL;
return s;
}
/**
* Fills the fields of a share object.
* Note that all strings are copied to newly allocated buffers.
* The gfsec_share_free function takes care of freeing those buffers.
*
* @param share The share object;
* @param number The share number; should be between 1 and 255
* (inclusive) for a normal share, 0 for a share
* which contains the whole secret, or
* GFSEC_SHARE_NUMBER_AUTOASSIGN to let the
* gfsec_secret_split function assign a share
* number automatically;
* @param scheme The scheme of the URI locating the share;
* @param authority The authority part of the URI (may be NULL);
* @param path The path part of the URI (may be NULL);
* @param hash A buffer containing a SHA-256 hash of the
* share's data (may be NULL).
*/
void
gfsec_share_set_info(gfsec_share_t *share,
unsigned short number,
gfsec_scheme_t scheme,
const char *authority,
const char *path,
const unsigned char *hash)
{
assert(share != NULL);
share->number = number;
share->scheme = scheme;
if ( authority )
share->authority = xstrdup(authority);
if ( path ) {
/* Make sure the path is absolute. */
if ( *path != '/' )
xasprintf(&(share->path), "/%s", path);
else
share->path = xstrdup(path);
}
if ( hash )
memcpy(share->hash, hash, 32);
}
/**
* Fills a share object with the corresponding data.
* If the share object specifies a hash value, the data are
* checked against this hash, and GFSEC_ERR_INVALID_SHARE is
* returned if the checksum fails.
*
* @param share The share object.
* @param data The buffer containing the data; only the pointer
* is copied, not the buffer itself, and it will be
* freed when the gfsec_share_free function is called;
* @param len The size of the data buffer.
*
* @return 0 if successfull, or GFSEC_ERR_INVALID_SHARE is the
* data does not match the share's expected hash.
*/
int
gfsec_share_set_data(gfsec_share_t *share,
unsigned char *data,
size_t len)
{
int ret;
assert(share != NULL);
assert(data != NULL);
share->data = data;
share->len = len;
ret = 0;
if ( share->hash ) {
unsigned char hash[32];
gcry_md_hash_buffer(GCRY_MD_SHA256, hash, data, len);
if ( memcmp(share->hash, hash, 32) != 0 ) {
ret = GFSEC_ERR_INVALID_SHARE;
share->data = NULL;
share->len = 0;
}
}
return ret;
}
/**
* Gets the URI representing the location of a share.
*
* @param share The share object.
* @param buffer The buffer to write the URI into.
* @param len The size of the buffer.
*
* @return The number of characters written into the buffer.
*/
int
gfsec_share_get_uri(gfsec_share_t *share,
char *buffer,
size_t len)
{
static char *schemes[] = { "file://", "uuid://", "label://", "mtp://" };
int ret;
assert(share != NULL);
assert(buffer != NULL);
ret = snprintf(buffer, len, "%s%s%s",
schemes[share->scheme],
share->authority ? share->authority : "",
share->path);
return ret;
}
/**
* Frees a share object and all of its buffers.
*
* @param share The share object.
*/
void
gfsec_share_free(gfsec_share_t *share)
{
assert(share != NULL);
if ( share->authority )
free(share->authority);
if ( share->path )
free(share->path);
if ( share->data )
free(share->data);
if ( share->hash )
free(share->hash);
free(share);
}
/**
* Initializes a new secret object.
*
* @return The secret object.
*/
gfsec_secret_t *
gfsec_secret_new(void)
{
gfsec_secret_t *s;
s = xmalloc(sizeof(*s));
s->filename = NULL;
s->restore = NULL;
s->destroy = NULL;
s->data = NULL;
s->len = 0;
s->n_shares = 0;
s->max_shares = 0;
s->threshold = 0;
s->shares = NULL;
s->full_share = NULL;
s->can_combine = 0;
return s;
}
/**
* Sets the secret to be split.
*
* @param secret The secret object.
* @param filename The location of the file to read the secret from.
* @param confname The location where the reconstructed secret will
* be written to. If NULL, this is set to the
* location of the original file.
*
* @return 0 if successful, or one of the GFSEC_ERR_SYSTEM_ERROR if
* an I/O error occured.
*/
int
gfsec_secret_set_secret_file(gfsec_secret_t *secret,
const char *filename,
const char *confname)
{
assert(secret != NULL);
assert(filename != NULL);
if ( confname )
secret->filename = *confname ? xstrdup(confname) : NULL;
else
secret->filename = xstrdup(filename);
if ( ! (secret->data = read_file(filename, &(secret->len),
GFSEC_SECRET_MAX_SIZE)) )
return GFSEC_ERR_SYSTEM_ERROR;
return 0;
}
/**
* Adds a share to a secret object.
*
* @param secret The secret object.
* @param share The share to add.
*
* @return 0 if successful, or GFSEC_ERR_TOO_MANY_SHARES if the
* secret object already has the maximal number of shares allowed.
*/
int
gfsec_secret_add_share(gfsec_secret_t *secret, gfsec_share_t *share)
{
assert(secret != NULL);
assert(share != NULL);
if ( secret->n_shares >= secret->max_shares ) {
size_t new_max;
new_max = secret->max_shares + 10;
if ( new_max > 255 ) {
new_max = 255;
if ( secret->n_shares >= new_max )
return GFSEC_ERR_TOO_MANY_SHARES;
}
secret->shares = xrealloc(secret->shares, new_max * sizeof(gfsec_share_t *));
secret->max_shares = new_max;
}
if ( share->number == GFSEC_SHARE_NUMBER_AUTOASSIGN ) {
unsigned nr, u;
char *tmp;
do {
nr = (random() & 0xFF00) >> 8;
if ( nr == 0 )
nr = 1;
for ( u = 0; u < secret->n_shares; u++ )
if ( secret->shares[u]->number == nr )
nr = 0;
} while ( nr == 0 );
share->number = nr;
xasprintf(&tmp, "%s.%03d", share->path, nr);
free(share->path);
share->path = tmp;
}
secret->shares[secret->n_shares++] = share;
return 0;
}
/**
* Checks if the secret can be reconstituted.
* Note that it is mandatory to call this function prior to calling
* the gfsec_secret_combine function.
*
* @param secret The secret object.
*
* @return
* - 0 if the secret can be reconstituted;
* - GFSEC_ERR_INVALID_LENGTH if not all shares have the same length;
* - GFSEC_ERR_NOT_ENOUGH_SHARES if not enough shares are available.
*/
int
gfsec_secret_can_combine(gfsec_secret_t *secret)
{
unsigned u, avail_shares;
gfsec_share_t *share;
assert(secret != NULL);
for ( u = 0, avail_shares = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( share->data ) {
if ( secret->len == 0 )
secret->len = share->len;
else if ( secret->len != share->len )
return GFSEC_ERR_INVALID_LENGTH;
if ( gfsec_share_is_full(share) )
secret->full_share = share;
else
avail_shares += 1;
}
}
if ( ! secret->full_share && avail_shares < secret->threshold )
return GFSEC_ERR_NOT_ENOUGH_SHARES;
secret->can_combine = 1;
return 0;
}
/**
* Reconstitutes the secret.
* The reconstituted secret will be stored in the data field of
* the secret object.
*
* @param secret The secret object.
*
* @return
* - 0 if successful;
* - GFSEC_ERR_INVALID_CALL if called improperly (either
* gfsec_secret_can_recombine has not been called previously,
* or it has been called but has found the secret could not be
* reconstituted);
* - GFSEC_ERR_SYSTEM_ERROR if an error occured in Libgfshare.
*/
int
gfsec_secret_combine(gfsec_secret_t *secret)
{
gfsec_share_t *share;
unsigned u;
assert(secret != NULL);
if ( ! secret->can_combine )
return GFSEC_ERR_INVALID_CALL;
secret->data = xmalloc(secret->len);
if ( gfshare_fill_rand == NULL )
gfshare_fill_rand = gfsec_fill_rand;
if ( secret->full_share )
memcpy(secret->data, secret->full_share->data, secret->len);
else {
gfshare_ctx *ctx;
unsigned char sharenrs[255];
unsigned v;
for ( u = v = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( share->data && ! gfsec_share_is_full(share) )
sharenrs[v++] = share->number;
}
if ( ! (ctx= gfshare_ctx_init_dec(sharenrs, v, secret->len)) )
return GFSEC_ERR_SYSTEM_ERROR;
for ( u = v = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( share->data && ! gfsec_share_is_full(share) )
gfshare_ctx_dec_giveshare(ctx, v++, share->data);
}
gfshare_ctx_dec_extract(ctx, secret->data);
gfshare_ctx_free(ctx);
}
return 0;
}
/**
* Gets the number of partial shares in the secret object.
*/
static unsigned
gfsec_secret_get_partial_share_count(gfsec_secret_t *secret)
{
unsigned u, n;
for ( u = n = 0; u < secret->n_shares; u++ )
if ( ! gfsec_share_is_full(secret->shares[u]) )
n += 1;
return n;
}
/**
* Splits the secret and fills the shares accordingly.
*
* @param secret The secret object.
* @param threshold The minimal number of shares needed to
* reconstitute the secret.
*
* @return
* - 0 if successful;
* - GFSEC_ERR_NOT_ENOUGH_SHARES if the secret object does not define
* enough shares to satisfy the desired threshold;
* - GFSEC_ERR_SYSTEM_ERROR if an error occured in Libgfshare or
* Libgcrypt.
*/
int
gfsec_secret_split(gfsec_secret_t *secret, unsigned char threshold)
{
gfshare_ctx *ctx;
gcry_md_hd_t md;
gfsec_share_t *share;
unsigned char sharenrs[255];
unsigned u, v, n;
int rc;
assert(secret != NULL);
n = gfsec_secret_get_partial_share_count(secret);
if ( threshold >= n )
return GFSEC_ERR_NOT_ENOUGH_SHARES;
secret->threshold = threshold;
if ( gfshare_fill_rand == NULL )
gfshare_fill_rand = gfsec_fill_rand;
for ( u = v = rc = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( ! gfsec_share_is_full(share) )
sharenrs[v++] = share->number;
share->data = xmalloc(secret->len);
share->hash = xmalloc(32);
share->len = secret->len;
}
if ( ! (ctx = gfshare_ctx_init_enc(sharenrs, n, threshold, secret->len)) )
rc = GFSEC_ERR_SYSTEM_ERROR;
if ( rc == 0 && gcry_md_open(&md, GCRY_MD_SHA256, 0) != 0 )
rc = GFSEC_ERR_SYSTEM_ERROR;
if ( rc == 0 ) {
gfshare_ctx_enc_setsecret(ctx, secret->data);
for ( u = v = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( ! gfsec_share_is_full(share) )
gfshare_ctx_enc_getshare(ctx, v++, share->data);
else
memcpy(share->data, secret->data, secret->len);
gcry_md_write(md, share->data, share->len);
memcpy(share->hash, gcry_md_read(md, 0), 32);
gcry_md_reset(md);
}
}
else {
for ( u = v = 0; u < secret->n_shares; u++ ) {
share = secret->shares[u];
if ( share->data ) {
free(share->data);
share->data = NULL;
}
if ( share->hash ) {
free(share->hash);
share->hash = NULL;
}
}
}
if ( ctx )
gfshare_ctx_free(ctx);
if ( md )
gcry_md_close(md);
return rc;
}
/**
* Frees a secret object and the associated shares.
*
* @param secret The secret object.
*/
void
gfsec_secret_free(gfsec_secret_t *secret)
{
unsigned u;
assert(secret != NULL);
if ( secret->filename )
free(secret->filename);
if ( secret->restore )
free(secret->restore);
if ( secret->destroy )
free(secret->destroy);
if ( secret->data )
free(secret->data);
for ( u = 0; u < secret->n_shares; u++ )
gfsec_share_free(secret->shares[u]);
if ( secret->shares )
free(secret->shares);
free(secret);
}
/**
* Translates a GFSEC_ERR_* error code into an error string.
*
* @param err The error code.
*
* @return A statically allocated string containing the error
* message.
*/
const char *
gfsec_error_string(int err)
{
const char *msg;
switch ( err ) {
#define GFSEC_ERROR(symbol, value, message) \
case GFSEC_ERR_##symbol: \
msg = message; \
break;
#include "gfsec-errors.h"
#undef GFSEC_ERROR
default: /* Should not happen. */
msg = "Unknown error";
break;
}
return msg;
}