/*
This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA
TALER 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, or (at your option) any later version.
TALER 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
TALER; see the file COPYING. If not, see
*/
/**
* @file util/taler-exchange-secmod-eddsa.c
* @brief Standalone process to perform private key EDDSA operations
* @author Christian Grothoff
*
* Key design points:
* - EVERY thread of the exchange will have its own pair of connections to the
* crypto helpers. This way, every threat will also have its own /keys state
* and avoid the need to synchronize on those.
* - auditor signatures and master signatures are to be kept in the exchange DB,
* and merged with the public keys of the helper by the exchange HTTPD!
* - the main loop of the helper is SINGLE-THREADED, but there are
* threads for crypto-workers which (only) do the signing in parallel,
* one per client.
* - thread-safety: signing happens in parallel, thus when REMOVING private keys,
* we must ensure that all signers are done before we fully free() the
* private key. This is done by reference counting (as work is always
* assigned and collected by the main thread).
*/
#include "platform.h"
#include "taler_util.h"
#include "taler-exchange-secmod-eddsa.h"
#include
#include
#include "taler_error_codes.h"
#include "taler_signatures.h"
#include "secmod_common.h"
#include
/**
* One particular key.
*/
struct Key
{
/**
* Kept in a DLL. Sorted by anchor time.
*/
struct Key *next;
/**
* Kept in a DLL. Sorted by anchor time.
*/
struct Key *prev;
/**
* Name of the file this key is stored under.
*/
char *filename;
/**
* The private key.
*/
struct TALER_ExchangePrivateKeyP exchange_priv;
/**
* The public key.
*/
struct TALER_ExchangePublicKeyP exchange_pub;
/**
* Time at which this key is supposed to become valid.
*/
struct GNUNET_TIME_Timestamp anchor;
/**
* Generation when this key was created or revoked.
*/
uint64_t key_gen;
/**
* Reference counter. Counts the number of threads that are
* using this key at this time.
*/
unsigned int rc;
/**
* Flag set to true if this key has been purged and the memory
* must be freed as soon as @e rc hits zero.
*/
bool purge;
};
/**
* Head of DLL of actual keys, sorted by anchor.
*/
static struct Key *keys_head;
/**
* Tail of DLL of actual keys.
*/
static struct Key *keys_tail;
/**
* How long can a key be used?
*/
static struct GNUNET_TIME_Relative duration;
/**
* Return value from main().
*/
static int global_ret;
/**
* Time when the key update is executed.
* Either the actual current time, or a pretended time.
*/
static struct GNUNET_TIME_Timestamp now;
/**
* The time for the key update, as passed by the user
* on the command line.
*/
static struct GNUNET_TIME_Timestamp now_tmp;
/**
* Where do we store the keys?
*/
static char *keydir;
/**
* How much should coin creation duration overlap
* with the next key? Basically, the starting time of two
* keys is always #duration - #overlap_duration apart.
*/
static struct GNUNET_TIME_Relative overlap_duration;
/**
* How long into the future do we pre-generate keys?
*/
static struct GNUNET_TIME_Relative lookahead_sign;
/**
* Task run to generate new keys.
*/
static struct GNUNET_SCHEDULER_Task *keygen_task;
/**
* Lock for the keys queue.
*/
static pthread_mutex_t keys_lock;
/**
* Current key generation.
*/
static uint64_t key_gen;
/**
* Notify @a client about @a key becoming available.
*
* @param[in,out] client the client to notify; possible freed if transmission fails
* @param key the key to notify @a client about
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
notify_client_key_add (struct TES_Client *client,
const struct Key *key)
{
struct TALER_CRYPTO_EddsaKeyAvailableNotification an = {
.header.size = htons (sizeof (an)),
.header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL),
.anchor_time = GNUNET_TIME_timestamp_hton (key->anchor),
.duration = GNUNET_TIME_relative_hton (duration),
.exchange_pub = key->exchange_pub,
.secm_pub = TES_smpub
};
TALER_exchange_secmod_eddsa_sign (&key->exchange_pub,
key->anchor,
duration,
&TES_smpriv,
&an.secm_sig);
if (GNUNET_OK !=
TES_transmit (client->csock,
&an.header))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Client %p must have disconnected\n",
client);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Notify @a client about @a key being purged.
*
* @param[in,out] client the client to notify; possible freed if transmission fails
* @param key the key to notify @a client about
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
notify_client_key_del (struct TES_Client *client,
const struct Key *key)
{
struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = {
.header.type = htons (TALER_HELPER_EDDSA_MT_PURGE),
.header.size = htons (sizeof (pn)),
.exchange_pub = key->exchange_pub
};
if (GNUNET_OK !=
TES_transmit (client->csock,
&pn.header))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Client %p must have disconnected\n",
client);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Handle @a client request @a sr to create signature. Create the
* signature using the respective key and return the result to
* the client.
*
* @param client the client making the request
* @param sr the request details
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
handle_sign_request (struct TES_Client *client,
const struct TALER_CRYPTO_EddsaSignRequest *sr)
{
const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose;
size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr)
+ sizeof (*purpose);
struct Key *key;
struct TALER_CRYPTO_EddsaSignResponse sres = {
.header.size = htons (sizeof (sres)),
.header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE)
};
enum TALER_ErrorCode ec;
if (purpose_size != htonl (purpose->size))
{
struct TALER_CRYPTO_EddsaSignFailure sf = {
.header.size = htons (sizeof (sr)),
.header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
.ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED)
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Signing request failed, request malformed\n");
return TES_transmit (client->csock,
&sf.header);
}
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
key = keys_head;
while ( (NULL != key) &&
(GNUNET_TIME_absolute_is_past (
GNUNET_TIME_absolute_add (key->anchor.abs_time,
duration))) )
{
struct Key *nxt = key->next;
if (0 != key->rc)
break; /* do later */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Deleting past key %s (expired %s ago)\n",
TALER_B2S (&nxt->exchange_pub),
GNUNET_TIME_relative2s (
GNUNET_TIME_absolute_get_duration (
GNUNET_TIME_absolute_add (key->anchor.abs_time,
duration)),
GNUNET_YES));
GNUNET_CONTAINER_DLL_remove (keys_head,
keys_tail,
key);
if ( (! key->purge) &&
(0 != unlink (key->filename)) )
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"unlink",
key->filename);
GNUNET_free (key->filename);
GNUNET_free (key);
key = nxt;
}
if (NULL == key)
{
GNUNET_break (0);
ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
}
else
{
GNUNET_assert (key->rc < UINT_MAX);
key->rc++;
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv,
purpose,
&sres.exchange_sig.eddsa_signature))
ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
else
ec = TALER_EC_NONE;
sres.exchange_pub = key->exchange_pub;
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
GNUNET_assert (key->rc > 0);
key->rc--;
}
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
if (TALER_EC_NONE != ec)
{
struct TALER_CRYPTO_EddsaSignFailure sf = {
.header.size = htons (sizeof (sf)),
.header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
.ec = htonl ((uint32_t) ec)
};
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Signing request %p failed, worker failed to produce signature\n",
client);
return TES_transmit (client->csock,
&sf.header);
}
return TES_transmit (client->csock,
&sres.header);
}
/**
* Initialize key material for key @a key (also on disk).
*
* @param[in,out] key to compute key material for
* @param position where in the DLL will the @a key go
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
setup_key (struct Key *key,
struct Key *position)
{
struct GNUNET_CRYPTO_EddsaPrivateKey priv;
struct GNUNET_CRYPTO_EddsaPublicKey pub;
GNUNET_CRYPTO_eddsa_key_create (&priv);
GNUNET_CRYPTO_eddsa_key_get_public (&priv,
&pub);
GNUNET_asprintf (&key->filename,
"%s/%llu",
keydir,
(unsigned long long) (key->anchor.abs_time.abs_value_us
/ GNUNET_TIME_UNIT_SECONDS.rel_value_us));
if (GNUNET_OK !=
GNUNET_DISK_fn_write (key->filename,
&priv,
sizeof (priv),
GNUNET_DISK_PERM_USER_READ))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"write",
key->filename);
return GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Setup fresh private key in `%s'\n",
key->filename);
key->key_gen = key_gen;
key->exchange_priv.eddsa_priv = priv;
key->exchange_pub.eddsa_pub = pub;
GNUNET_CONTAINER_DLL_insert_after (keys_head,
keys_tail,
position,
key);
return GNUNET_OK;
}
/**
* The validity period of a key @a key has expired. Purge it.
*
* @param[in] key expired or revoked key to purge
*/
static void
purge_key (struct Key *key)
{
if (key->purge)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Key %s already purged, skipping\n",
TALER_B2S (&key->exchange_pub));
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Purging key %s\n",
TALER_B2S (&key->exchange_pub));
if (0 != unlink (key->filename))
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"unlink",
key->filename);
key->purge = true;
key->key_gen = key_gen;
GNUNET_free (key->filename);
}
/**
* A @a client informs us that a key has been revoked.
* Check if the key is still in use, and if so replace (!)
* it with a fresh key.
*
* @param client the client making the request
* @param rr the revocation request
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
handle_revoke_request (struct TES_Client *client,
const struct TALER_CRYPTO_EddsaRevokeRequest *rr)
{
struct Key *key;
struct Key *nkey;
(void) client;
key = NULL;
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
for (struct Key *pos = keys_head;
NULL != pos;
pos = pos->next)
if (0 == GNUNET_memcmp (&pos->exchange_pub,
&rr->exchange_pub))
{
key = pos;
break;
}
if (NULL == key)
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Revocation request ignored, key unknown\n");
return GNUNET_OK;
}
if (key->purge)
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Revocation request ignored, key %s already revoked\n",
TALER_B2S (&key->exchange_pub));
return GNUNET_OK;
}
key_gen++;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Revoking key %s, bumping generation to %llu\n",
TALER_B2S (&key->exchange_pub),
(unsigned long long) key_gen);
purge_key (key);
/* Setup replacement key */
nkey = GNUNET_new (struct Key);
nkey->anchor = key->anchor;
if (GNUNET_OK !=
setup_key (nkey,
key))
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
TES_wake_clients ();
return GNUNET_OK;
}
/**
* Handle @a hdr message received from @a client.
*
* @param client the client that received the message
* @param hdr message that was received
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
eddsa_work_dispatch (struct TES_Client *client,
const struct GNUNET_MessageHeader *hdr)
{
uint16_t msize = ntohs (hdr->size);
switch (ntohs (hdr->type))
{
case TALER_HELPER_EDDSA_MT_REQ_SIGN:
if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return handle_sign_request (
client,
(const struct TALER_CRYPTO_EddsaSignRequest *) hdr);
case TALER_HELPER_EDDSA_MT_REQ_REVOKE:
if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return handle_revoke_request (
client,
(const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr);
default:
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
/**
* Send our initial key set to @a client together with the
* "sync" terminator.
*
* @param client the client to inform
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
eddsa_client_init (struct TES_Client *client)
{
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
for (struct Key *key = keys_head;
NULL != key;
key = key->next)
{
if (GNUNET_OK !=
notify_client_key_add (client,
key))
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
client->key_gen = key_gen;
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
{
struct GNUNET_MessageHeader synced = {
.type = htons (TALER_HELPER_EDDSA_SYNCED),
.size = htons (sizeof (synced))
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Client %p synced\n",
client);
if (GNUNET_OK !=
TES_transmit (client->csock,
&synced))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
return GNUNET_OK;
}
/**
* Notify @a client about all changes to the keys since
* the last generation known to the @a client.
*
* @param client the client to notify
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
eddsa_update_client_keys (struct TES_Client *client)
{
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Updating client %p to generation %llu\n",
client,
(unsigned long long) key_gen);
for (struct Key *key = keys_head;
NULL != key;
key = key->next)
{
if (key->key_gen <= client->key_gen)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Skipping key %s, no change since generation %llu\n",
TALER_B2S (&key->exchange_pub),
(unsigned long long) client->key_gen);
continue;
}
if (key->purge)
{
if (GNUNET_OK !=
notify_client_key_del (client,
key))
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
return GNUNET_SYSERR;
}
}
else
{
if (GNUNET_OK !=
notify_client_key_add (client,
key))
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
return GNUNET_SYSERR;
}
}
}
client->key_gen = key_gen;
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
return GNUNET_OK;
}
/**
* Create a new key (we do not have enough).
*
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
create_key (void)
{
struct Key *key;
struct GNUNET_TIME_Timestamp anchor;
anchor = GNUNET_TIME_timestamp_get ();
if (NULL != keys_tail)
{
struct GNUNET_TIME_Absolute abs;
abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
GNUNET_TIME_relative_subtract (
duration,
overlap_duration));
if (GNUNET_TIME_absolute_cmp (anchor.abs_time,
<,
abs))
anchor = GNUNET_TIME_absolute_to_timestamp (abs);
}
key = GNUNET_new (struct Key);
key->anchor = anchor;
if (GNUNET_OK !=
setup_key (key,
keys_tail))
{
GNUNET_break (0);
GNUNET_free (key);
GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* At what time does the current key set require its next action? Basically,
* the minimum of the expiration time of the oldest key, and the expiration
* time of the newest key minus the #lookahead_sign time.
*/
static struct GNUNET_TIME_Absolute
key_action_time (void)
{
struct Key *nxt;
nxt = keys_head;
while ( (NULL != nxt) &&
(nxt->purge) )
nxt = nxt->next;
if (NULL == nxt)
return GNUNET_TIME_UNIT_ZERO_ABS;
return GNUNET_TIME_absolute_min (
GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
duration),
GNUNET_TIME_absolute_subtract (
GNUNET_TIME_absolute_subtract (
GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
duration),
lookahead_sign),
overlap_duration));
}
/**
* Create new keys and expire ancient keys.
*
* @param cls NULL
*/
static void
update_keys (void *cls)
{
bool wake = false;
struct Key *nxt;
(void) cls;
keygen_task = NULL;
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
/* create new keys */
while ( (NULL == keys_tail) ||
GNUNET_TIME_absolute_is_past (
GNUNET_TIME_absolute_subtract (
GNUNET_TIME_absolute_subtract (
GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
duration),
lookahead_sign),
overlap_duration)) )
{
if (! wake)
{
key_gen++;
wake = true;
}
if (GNUNET_OK !=
create_key ())
{
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
}
nxt = keys_head;
/* purge expired keys */
while ( (NULL != nxt) &&
GNUNET_TIME_absolute_is_past (
GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
duration)))
{
if (! wake)
{
key_gen++;
wake = true;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Purging past key %s (expired %s ago)\n",
TALER_B2S (&nxt->exchange_pub),
GNUNET_TIME_relative2s (
GNUNET_TIME_absolute_get_duration (
GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
duration)),
GNUNET_YES));
purge_key (nxt);
nxt = nxt->next;
}
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
if (wake)
TES_wake_clients ();
keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (),
&update_keys,
NULL);
}
/**
* Parse private key from @a filename in @a buf.
*
* @param filename name of the file we are parsing, for logging
* @param buf key material
* @param buf_size number of bytes in @a buf
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
parse_key (const char *filename,
const void *buf,
size_t buf_size)
{
struct GNUNET_CRYPTO_EddsaPrivateKey priv;
char *anchor_s;
char dummy;
unsigned long long anchor_ll;
struct GNUNET_TIME_Timestamp anchor;
anchor_s = strrchr (filename,
'/');
if (NULL == anchor_s)
{
/* File in a directory without '/' in the name, this makes no sense. */
GNUNET_break (0);
return GNUNET_SYSERR;
}
anchor_s++;
if (1 != sscanf (anchor_s,
"%llu%c",
&anchor_ll,
&dummy))
{
/* Filenames in KEYDIR must ONLY be the anchor time in seconds! */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Filename `%s' invalid for key file, skipping\n",
filename);
return GNUNET_SYSERR;
}
anchor.abs_time.abs_value_us = anchor_ll
* GNUNET_TIME_UNIT_SECONDS.rel_value_us;
if (anchor_ll != anchor.abs_time.abs_value_us
/ GNUNET_TIME_UNIT_SECONDS.rel_value_us)
{
/* Integer overflow. Bad, invalid filename. */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Filename `%s' invalid for key file, skipping\n",
filename);
return GNUNET_SYSERR;
}
if (buf_size != sizeof (priv))
{
/* Parser failure. */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"File `%s' is malformed, skipping\n",
filename);
return GNUNET_SYSERR;
}
GNUNET_memcpy (&priv,
buf,
buf_size);
{
struct GNUNET_CRYPTO_EddsaPublicKey pub;
struct Key *key;
struct Key *before;
GNUNET_CRYPTO_eddsa_key_get_public (&priv,
&pub);
GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
key = GNUNET_new (struct Key);
key->exchange_priv.eddsa_priv = priv;
key->exchange_pub.eddsa_pub = pub;
key->anchor = anchor;
key->filename = GNUNET_strdup (filename);
key->key_gen = key_gen;
before = NULL;
for (struct Key *pos = keys_head;
NULL != pos;
pos = pos->next)
{
if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
break;
before = pos;
}
GNUNET_CONTAINER_DLL_insert_after (keys_head,
keys_tail,
before,
key);
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Imported key from `%s'\n",
filename);
}
return GNUNET_OK;
}
/**
* Import a private key from @a filename.
*
* @param cls NULL
* @param filename name of a file in the directory
*/
static enum GNUNET_GenericReturnValue
import_key (void *cls,
const char *filename)
{
struct GNUNET_DISK_FileHandle *fh;
struct GNUNET_DISK_MapHandle *map;
void *ptr;
int fd;
struct stat sbuf;
(void) cls;
{
struct stat lsbuf;
if (0 != lstat (filename,
&lsbuf))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"lstat",
filename);
return GNUNET_OK;
}
if (! S_ISREG (lsbuf.st_mode))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"File `%s' is not a regular file, which is not allowed for private keys!\n",
filename);
return GNUNET_OK;
}
}
fd = open (filename,
O_RDONLY | O_CLOEXEC);
if (-1 == fd)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"open",
filename);
return GNUNET_OK;
}
if (0 != fstat (fd,
&sbuf))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"stat",
filename);
GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (! S_ISREG (sbuf.st_mode))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"File `%s' is not a regular file, which is not allowed for private keys!\n",
filename);
GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO)))
{
/* permission are NOT tight, try to patch them up! */
if (0 !=
fchmod (fd,
S_IRUSR))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fchmod",
filename);
/* refuse to use key if file has wrong permissions */
GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
}
fh = GNUNET_DISK_get_handle_from_int_fd (fd);
if (NULL == fh)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"open",
filename);
GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (sbuf.st_size > 2048)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"File `%s' to big to be a private key\n",
filename);
GNUNET_DISK_file_close (fh);
return GNUNET_OK;
}
ptr = GNUNET_DISK_file_map (fh,
&map,
GNUNET_DISK_MAP_TYPE_READ,
(size_t) sbuf.st_size);
if (NULL == ptr)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"mmap",
filename);
GNUNET_DISK_file_close (fh);
return GNUNET_OK;
}
(void) parse_key (filename,
ptr,
(size_t) sbuf.st_size);
GNUNET_DISK_file_unmap (map);
GNUNET_DISK_file_close (fh);
return GNUNET_OK;
}
/**
* Load the various duration values from @a kcfg.
*
* @param cfg configuration to use
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"taler-exchange-secmod-eddsa",
"OVERLAP_DURATION",
&overlap_duration))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"taler-exchange-secmod-eddsa",
"OVERLAP_DURATION");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"taler-exchange-secmod-eddsa",
"DURATION",
&duration))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"taler-exchange-secmod-eddsa",
"DURATION");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"taler-exchange-secmod-eddsa",
"LOOKAHEAD_SIGN",
&lookahead_sign))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"taler-exchange-secmod-eddsa",
"LOOKAHEAD_SIGN");
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Function run on shutdown. Stops the various jobs (nicely).
*
* @param cls NULL
*/
static void
do_shutdown (void *cls)
{
(void) cls;
TES_listen_stop ();
if (NULL != keygen_task)
{
GNUNET_SCHEDULER_cancel (keygen_task);
keygen_task = NULL;
}
}
/**
* Main function that will be run under the GNUnet scheduler.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param cfg configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
static struct TES_Callbacks cb = {
.dispatch = eddsa_work_dispatch,
.updater = eddsa_update_client_keys,
.init = eddsa_client_init
};
(void) cls;
(void) args;
(void) cfgfile;
if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
{
/* The user gave "--now", use it! */
now = now_tmp;
}
else
{
/* get current time again, we may be timetraveling! */
now = GNUNET_TIME_timestamp_get ();
}
if (GNUNET_OK !=
load_durations (cfg))
{
global_ret = EXIT_NOTCONFIGURED;
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
"taler-exchange-secmod-eddsa",
"KEY_DIR",
&keydir))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"taler-exchange-secmod-eddsa",
"KEY_DIR");
global_ret = EXIT_NOTCONFIGURED;
return;
}
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
global_ret = TES_listen_start (cfg,
"taler-exchange-secmod-eddsa",
&cb);
if (0 != global_ret)
return;
/* Load keys */
GNUNET_break (GNUNET_OK ==
GNUNET_DISK_directory_create (keydir));
GNUNET_DISK_directory_scan (keydir,
&import_key,
NULL);
if ( (NULL != keys_head) &&
(GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Existing anchor is in %s the future. Refusing to start\n",
GNUNET_TIME_relative2s (
GNUNET_TIME_absolute_get_remaining (
keys_head->anchor.abs_time),
GNUNET_YES));
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
/* start job to keep keys up-to-date; MUST be run before the #listen_task,
hence with priority. */
keygen_task = GNUNET_SCHEDULER_add_with_priority (
GNUNET_SCHEDULER_PRIORITY_URGENT,
&update_keys,
NULL);
}
/**
* The entry point.
*
* @param argc number of arguments in @a argv
* @param argv command-line arguments
* @return 0 on normal termination
*/
int
main (int argc,
char **argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_option_timestamp ('t',
"time",
"TIMESTAMP",
"pretend it is a different time for the update",
&now_tmp),
GNUNET_GETOPT_OPTION_END
};
enum GNUNET_GenericReturnValue ret;
/* Restrict permissions for the key files that we create. */
(void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
/* force linker to link against libtalerutil; if we do
not do this, the linker may "optimize" libtalerutil
away and skip #TALER_OS_init(), which we do need */
TALER_OS_init ();
now_tmp = now = GNUNET_TIME_timestamp_get ();
ret = GNUNET_PROGRAM_run (argc,
argv,
"taler-exchange-secmod-eddsa",
"Handle private EDDSA key operations for a Taler exchange",
options,
&run,
NULL);
if (GNUNET_NO == ret)
return EXIT_SUCCESS;
if (GNUNET_SYSERR == ret)
return EXIT_INVALIDARGUMENT;
return global_ret;
}