diff options
| author | Christian Grothoff <christian@grothoff.org> | 2020-12-06 16:53:29 +0100 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2020-12-06 16:53:29 +0100 | 
| commit | d9ac8e7975d813f1a868ab4d0cd062408f5aaf00 (patch) | |
| tree | fa8d0b988afa1352aa2cb3a0ec629b81db339ba2 | |
| parent | b90293a92f07aea92b6651048b9557d4f7fb6666 (diff) | |
incomplete work on forthcoming /keys implementation
| -rw-r--r-- | src/exchange/taler-exchange-httpd_keys.c | 1134 | ||||
| -rw-r--r-- | src/exchange/taler-exchange-httpd_keys.h | 216 | ||||
| -rw-r--r-- | src/exchangedb/exchange-0002.sql | 3 | ||||
| -rw-r--r-- | src/include/taler_exchangedb_plugin.h | 3 | 
4 files changed, 1354 insertions, 2 deletions
| diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c new file mode 100644 index 00000000..7aade68f --- /dev/null +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -0,0 +1,1134 @@ +/* +  This file is part of TALER +  Copyright (C) 2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU Affero 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_keys.c + * @brief management of our various keys + * @author Christian Grothoff + */ +#include "platform.h" +#include <pthread.h> +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_responses.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Taler protocol version in the format CURRENT:REVISION:AGE + * as used by GNU libtool.  See + * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html + * + * Please be very careful when updating and follow + * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info + * precisely.  Note that this version has NOTHING to do with the + * release version, and the format is NOT the same that semantic + * versioning uses either. + * + * When changing this version, you likely want to also update + * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in + * exchange_api_handle.c! + */ +#define EXCHANGE_PROTOCOL_VERSION "8:0:0" + + +/** + * Information about a denomination on offer by the denomination helper. + */ +struct HelperDenomination +{ + +  /** +   * When will the helper start to use this key for signing? +   */ +  struct GNUNET_TIME_Absolute start_time; + +  /** +   * For how long will the helper allow signing? 0 if +   * the key was revoked or purged. +   */ +  struct GNUNET_TIME_Relative validity_duration; + +  /** +   * Hash of the denomination key. +   */ +  struct GNUNET_HashCode h_denom_pub; + +  /** +   * Signature over this key from the security module's key. +   */ +  struct TALER_SecurityModuleSignatureP sm_sig; + +  /** +   * The (full) public key. +   */ +  struct TALER_DenominationPublicKey denom_pub; + +  /** +   * Name in configuration section for this denomination type. +   */ +  char *section_name; + +}; + + +/** + * Information about a signing key on offer by the esign helper. + */ +struct HelperSignkey +{ +  /** +   * When will the helper start to use this key for signing? +   */ +  struct GNUNET_TIME_Absolute start_time; + +  /** +   * For how long will the helper allow signing? 0 if +   * the key was revoked or purged. +   */ +  struct GNUNET_TIME_Relative validity_duration; + +  /** +   * The public key. +   */ +  struct TALER_ExchangePublicKeyP exchange_pub; + +  /** +   * Signature over this key from the security module's key. +   */ +  struct TALER_SecurityModuleSignatureP sm_sig; + +}; + + +/** + * State associated with the crypto helpers / security modules. + * Created per-thread, but NOT updated when the #key_generation + * is updated (instead constantly kept in sync whenever + * #TEH_get_key_state() is called). + */ +struct HelperState +{ + +  /** +   * Handle for the esign/EdDSA helper. +   */ +  struct TALER_CRYPTO_ExchangeSignHelper *esh; + +  /** +   * Handle for the denom/RSA helper. +   */ +  struct TALER_CRYPTO_DenominationHelper *dh; + +  /** +   * Map from H(denom_pub) to `struct HelperDenomination` entries. +   */ +  struct GNUNET_CONTAINER_MultiHashMap *denom_keys; + +  /** +   * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey` +   * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also +   * an EdDSA public key. +   */ +  struct GNUNET_CONTAINER_MultiPeerMap *esign_keys; + +}; + + +/** + * Entry in (sorted) array with possible pre-build responses for /keys. + * We keep pre-build responses for the various (valid) cherry-picking + * values around. + */ +struct KeysResponseData +{ + +  /** +   * Response to return if the client supports (deflate) compression. +   */ +  struct MHD_Response *response_compressed; + +  /** +   * Response to return if the client does not support compression. +   */ +  struct MHD_Response *response_uncompressed; + +  /** +   * Cherry-picking timestamp the client must have set for this +   * response to be valid.  0 if this is the "full" response. +   * The client's request must include this date or a higher one +   * for this response to be applicable. +   */ +  struct GNUNET_TIME_Absolute cherry_pick_date; + +}; + + +/** + * Snapshot of the (coin and signing) keys (including private keys) of + * the exchange.  There can be multiple instances of this struct, as it is + * reference counted and only destroyed once the last user is done + * with it.  The current instance is acquired using + * #TEH_KS_acquire().  Using this function increases the + * reference count.  The contents of this structure (except for the + * reference counter) should be considered READ-ONLY until it is + * ultimately destroyed (as there can be many concurrent users). + */ +struct TEH_KeyStateHandle +{ + +  /** +   * Mapping from denomination keys to denomination key issue struct. +   * Used to lookup the key by hash. +   */ +  struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; + +  /** +   * Map from `struct TALER_ExchangePublicKey` to `TBD` +   * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also +   * an EdDSA public key. +   */ +  // FIXME: never initialized, never cleaned up! +  struct GNUNET_CONTAINER_MultiPeerMap *signkey_map; + +  /** +   * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of +   * length @e krd_array_length; +   */ +  struct KeysResponseData *krd_array; + +  /** +   * Length of the @e krd_array. +   */ +  unsigned int krd_array_length; + +  /** +   * Information we track for thecrypto helpers.  Preserved +   * when the @e key_generation changes, thus kept separate. +   */ +  struct HelperState helpers; + +  /** +   * For which (global) key_generation was this data structure created? +   * Used to check when we are outdated and need to be re-generated. +   */ +  uint64_t key_generation; + +}; + + +/** + * Thread-local.  Contains a pointer to `struct TEH_KeyStateHandle` or NULL. + * Stores the per-thread latest generation of our key state. + */ +static pthread_key_t key_state; + +/** + * Counter incremented whenever we have a reason to re-build the keys because + * something external changed (in another thread).  The counter is manipulated + * using an atomic update, and thus to ensure that threads notice when it + * changes, the variable MUST be volatile.  See #TEH_get_key_state() and + * #TEH_update_key_state() for uses of this variable. + */ +static volatile uint64_t key_generation; + +/** + * RSA security module public key, all zero if not known. + */ +static struct TALER_SecurityModulePublicKeyP denom_sm_pub; + +/** + * EdDSA security module public key, all zero if not known. + */ +static struct TALER_SecurityModulePublicKeyP esign_sm_pub; + +/** + * Mutex protecting access to #denom_sm_pub and #esign_sm_pub. + * (Could be split into two locks if ever needed.) + */ +static pthread_mutex_t sm_pub_mutex = PTHREAD_MUTEX_INITIALIZER; + + +/** + * Clear memory for responses to "/keys" in @a ksh. + * + * @param[in,out] ksh key state to update + */ +static void +clear_response_cache (struct TEH_KeyStateHandle *ksh) +{ +  for (unsigned int i = 0; i<ksh->krd_array_length; i++) +  { +    struct KeysResponseData *krd = &ksh->krd_array[i]; + +    MHD_destroy_response (kdr->response_compressed); +    MHD_destroy_response (kdr->response_uncompressed); +  } +  GNUNET_array_grow (ksh->krd_array, +                     ksh->krd_array_length); +} + + +/** + * Check that the given RSA security module's public key is the one + * we have pinned.  If it does not match, we die hard. + * + * @param sm_pub RSA security module public key to check + */ +static void +check_denom_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +{ +  GNUNET_assert (0 == pthread_mutex_lock (&sm_pub_mutex)); +  if (0 != +      GNUNET_memcmp (sm_pub, +                     &denom_sm_pub)) +  { +    if (! GNUNET_is_zero (&denom_sm_pub)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Our RSA security module changed its key. This must not happen.\n"); +      GNUNET_assert (0); +    } +    denom_sm_pub = *sm_pub; /* TOFU ;-) */ +  } +  GNUNET_assert (0 == pthread_mutex_unlock (&sm_pub_mutex)); +} + + +/** + * Check that the given EdDSA security module's public key is the one + * we have pinned.  If it does not match, we die hard. + * + * @param sm_pub EdDSA security module public key to check + */ +static void +check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +{ +  GNUNET_assert (0 == pthread_mutex_lock (&sm_pub_mutex)); +  if (0 != +      GNUNET_memcmp (sm_pub, +                     &esign_sm_pub)) +  { +    if (! GNUNET_is_zero (&esign_sm_pub)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Our EdDSA security module changed its key. This must not happen.\n"); +      GNUNET_assert (0); +    } +    esign_sm_pub = *sm_pub; /* TOFU ;-) */ +  } +  GNUNET_assert (0 == pthread_mutex_unlock (&sm_pub_mutex)); +} + + +/** + * Helper function for #destroy_key_helpers to free all entries + * in the `denom_keys` map. + * + * @param cls the `struct HelperState` + * @param h_denom_pub hash of the denomination public key + * @param value the `struct HelperDenomination` to release + * @return #GNUNET_OK (continue to iterate) + */ +static int +free_denom_cb (void *cls, +               const struct GNUNET_HashCode *h_denom_pub, +               void *value) +{ +  struct HelperDenomination *hd = value; + +  (void) cls; +  (void) h_denom_pub; +  GNUNET_CRYPTO_rsa_public_key_free (hd->denom_pub.rsa_public_key); +  GNUNET_free (hd->section_name); +  GNUNET_free (hd); +  return GNUNET_OK; +} + + +/** + * Helper function for #destroy_key_helpers to free all entries + * in the `esign_keys` map. + * + * @param cls the `struct HelperState` + * @param pid unused, matches the exchange public key + * @param value the `struct HelperSignkey` to release + * @return #GNUNET_OK (continue to iterate) + */ +static int +free_esign_cb (void *cls, +               const struct GNUNET_PeerIdentity *pid, +               void *value) +{ +  struct HelperSignkey *sk = value; + +  (void) cls; +  (void) pid; +  GNUNET_free (sk); +  return GNUNET_OK; +} + + +/** + * Destroy helper state. Does NOT call free() on @a hs, as that + * state is not separately allocated!  Dual to #setup_key_helpers(). + * + * @param[in] hs helper state to free, but NOT the @a hs pointer itself! + */ +static void +destroy_key_helpers (struct HelperState *hs) +{ +  GNUNET_CONTIANER_multihashmap_iterate (hs->denom_keys, +                                         &free_denom_cb, +                                         hs); +  GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys); +  hs->denom_keys = NULL; +  GNUNET_CONTIANER_multipeermap_iterate (hs->denom_keys, +                                         &free_esign_cb, +                                         hs); +  GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys); +  hs->esign_keys = NULL; +  if (NULL != hs->dh) +  { +    TALER_CRYPTO_helper_denom_disconnect (hs->dh); +    hs->dh = NULL; +  } +  if (NULL != hs->esh) +  { +    TALER_CRYPTO_helper_esign_disconnect (hs->esh); +    hs->esh = NULL; +  } +} + + +/** + * Function called with information about available keys for signing.  Usually + * only called once per key upon connect. Also called again in case a key is + * being revoked, in that case with an @a end_time of zero. + * + * @param cls closure with the `struct HelperState *` + * @param section_name name of the denomination type in the configuration; + *                 NULL if the key has been revoked or purged + * @param start_time when does the key become available for signing; + *                 zero if the key has been revoked or purged + * @param validity_duration how long does the key remain available for signing; + *                 zero if the key has been revoked or purged + * @param h_denom_pub hash of the @a denom_pub that is available (or was purged) + * @param denom_pub the public key itself, NULL if the key was revoked or purged + * @param sm_pub public key of the security module, NULL if the key was revoked or purged + * @param sm_sig signature from the security module, NULL if the key was revoked or purged + *               The signature was already verified against @a sm_pub. + */ +static void +helper_denom_cb ( +  void *cls, +  const char *section_name, +  struct GNUNET_TIME_Absolute start_time, +  struct GNUNET_TIME_Relative validity_duration, +  const struct GNUNET_HashCode *h_denom_pub, +  const struct TALER_DenominationPublicKey *denom_pub, +  const struct TALER_SecurityModulePublicKeyP *sm_pub, +  const struct TALER_SecurityModuleSignatureP *sm_sig) +{ +  struct HelperState *hs = cls; +  struct HelperDenomination *hd; + +  check_denom_sm_pub (sm_pub); +  hd = GNUNET_CONTAINER_multihashmap_get (hs->denom_keys, +                                          h_denom_pub); +  if (NULL != hd) +  { +    /* should be just an update (revocation!), so update existing entry */ +    hd->validity_duration = validity_duration; +    GNUNET_break (0 == +                  GNUNET_memcmp (sm_sig, +                                 &hd->sm_sig)); +    GNUNET_break (start_time.abs_value_us == +                  hd->start_time.abs_value_us); +    GNUNET_break (0 == +                  strcasecmp (section_name, +                              hd->section_name)); +    return; +  } + +  hd = GNUNET_new (struct HelperDenomination); +  hd->start_time = start_time; +  hd->validity_duration = validity_duration; +  hd->h_denom_pub = *h_denom_pub; +  hd->sm_sig = *sm_sig; +  hd->denom_pub.rsa_public_key +    = GNUNET_CRYPTO_rsa_public_key_dup (denom_pub->rsa_public_key); +  hd->section_name = GNUNET_strdup (section_name); +  GNUNET_assert ( +    GNUNET_OK == +    GNUNET_CONTAINER_multihashmap_put ( +      hs->denom_keys, +      &hd->h_denom_pub, +      hd, +      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); +} + + +/** + * Function called with information about available keys for signing.  Usually + * only called once per key upon connect. Also called again in case a key is + * being revoked, in that case with an @a end_time of zero. + * + * @param cls closure with the `struct HelperState *` + * @param start_time when does the key become available for signing; + *                 zero if the key has been revoked or purged + * @param validity_duration how long does the key remain available for signing; + *                 zero if the key has been revoked or purged + * @param exchange_pub the public key itself, NULL if the key was revoked or purged + * @param sm_pub public key of the security module, NULL if the key was revoked or purged + * @param sm_sig signature from the security module, NULL if the key was revoked or purged + *               The signature was already verified against @a sm_pub. + */ +static void +helper_esign_cb ( +  void *cls, +  struct GNUNET_TIME_Absolute start_time, +  struct GNUNET_TIME_Relative validity_duration, +  const struct TALER_ExchangePublicKeyP *exchange_pub, +  const struct TALER_SecurityModulePublicKeyP *sm_pub, +  const struct TALER_SecurityModuleSignatureP *sm_sig) +{ +  struct HelperState *hs = cls; +  struct HelperSignkey *sk; +  struct GNUNET_PeerIdentity pid; + +  check_esign_sm_pub (sm_pub); +  pid.public_key = exchange_pub->eddsa_pub; +  sk = GNUNET_CONTAINER_multipeermap_get (hs->denom_keys, +                                          &pid); +  if (NULL != sk) +  { +    /* should be just an update (revocation!), so update existing entry */ +    sk->validity_duration = validity_duration; +    GNUNET_break (0 == +                  GNUNET_memcmp (sm_sig, +                                 &sk->sm_sig)); +    GNUNET_break (start_time.abs_value_us == +                  sk->start_time.abs_value_us); +    return; +  } + +  sk = GNUNET_new (struct HelperSignkey); +  sk->start_time = start_time; +  sk->validity_duration = validity_duration; +  sk->exchange_pub = *exchange_pub; +  sk->sm_sig = *sm_sig; +  GNUNET_assert ( +    GNUNET_OK == +    GNUNET_CONTAINER_multihashmap_put ( +      hs->esign_keys, +      &pid, +      sk, +      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); +} + + +/** + * Setup helper state. + * + * @param[out] hs helper state to initialize + * @return #GNUNET_OK on success + */ +static int +setup_key_helpers (struct HelperState *hs) +{ +  hs->denom_keys +    = GNUNET_CONTAINER_multihashmap_create (1024, +                                            GNUNET_YES); +  hs->esign_keys +    = GNUNET_CONTAINER_multipeermap_create (32, +                                            GNUNET_NO /* MUST BE NO! */); +  hs->dh = TALER_CRYPTO_helper_denom_connect (cfg, +                                              &helper_denom_cb, +                                              hs); +  if (NULL == hs->dh) +  { +    destroy_key_helpers (hs); +    return GNUNET_SYSERR; +  } +  hs->esh = TALER_CRYPTO_helper_esign_connect (cfg, +                                               &helper_esign_cb, +                                               hs); +  if (NULL == hs->esh) +  { +    destroy_key_helpers (hs); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Synchronize helper state. Polls the key helper for updates. + * + * @param[in,out] hs helper state to synchronize + */ +static void +sync_key_helpers (struct HelperState *hs) +{ +  TALER_CRYPTO_helper_denom_poll (hs->dh); +  TALER_CRYPTO_helper_esign_poll (hs->esh); +} + + +/** + * Free denomination key data. + * + * @param cls a `struct TEH_KeyStateHandle`, unused + * @param h_denom_pub hash of the denomination public key, unused + * @param value a `struct TEH_DenominationKey` to free + * @return #GNUNET_OK (continue to iterate) + */ +static int +clear_denomination_cb (void *cls, +                       const struct GNUNET_HashCode *h_denom_pub, +                       void *value) +{ +  struct TEH_DenominationKey *dk = value; + +  (void) cls; +  (void) h_denom_pub; +  GNUNET_CRYPTO_rsa_public_key_free (dk->denom_pub.rsa_public_key); +  GNUNET_free (dk); +  return GNUNET_OK; +} + + +/** + * Free resources associated with @a cls, possibly excluding + * the helper data. + * + * @param[in] ksh key state to release + * @param free_helper true to also release the helper state + */ +static void +destroy_key_state (struct TEH_KeyStateHandle *ksh, +                   bool free_helper) +{ +  clear_response_cache (ksh); +  GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, +                                         &clear_denomination_cb, +                                         ksh); +  GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map); +  if (free_helper) +    destroy_key_helpers (&ksh->helpers); +  GNUNET_free (ksh); +} + + +/** + * Free all resources associated with @a cls.  Called when + * the respective pthread is destroyed. + * + * @param[in] cls a `struct TEH_KeyStateHandle`. + */ +static void +destroy_key_state_cb (void *cls) +{ +  struct TEH_KeyStateHandle *ksh = cls; + +  destroy_key_state (ksh, +                     true); +} + + +/** + * Function called with information about the exchange's denomination keys. + * + * @param cls closure with a `struct TEH_KeyStateHandle *` + * @param denom_pub public key of the denomination + * @param issue detailed information about the denomination (value, expiration times, fees) + */ +// FIXME: want a different function with +// + revocation data +// - private key data +static void +denomination_info_cb ( +  void *cls, +  const struct TALER_DenominationPublicKey *denom_pub, +  const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue) +{ +  struct TEH_KeyStateHandle *ksh = cls; + +  // FIXME: check with helper to see if denomination is OK +  //        for use with signing! +} + + +/** + * Create a key state. + * + * @param[in] hs helper state to (re)use, NULL if not available + * @return NULL on error (i.e. failed to access database) + */ +static struct TEH_KeyStateHandle * +build_key_state (struct HelperState *hs) +{ +  struct TEH_KeyStateHandle *ksh; +  enum GNUNET_DB_QueryStatus qs; + +  ksh = GNUNET_new (struct TEH_KeyStateHandle); +  /* We must use the key_generation from when we STARTED the process! */ +  ksh->key_generation = key_generation; +  if (NULL == hs) +  { +    if (GNUNET_OK != +        setup_key_helpers (&ksh->helpers)) +    { +      GNUNET_free (ksh); +      return NULL; +    } +  } +  else +  { +    ksh->helpers = *hs; +  } +  ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024, +                                                            GNUNET_YES); +  // FIXME: should _also_ fetch revocation status here! +  qs = TEH_plugin->iterate_denomination_info (TEH_plugin->cls, +                                              &denomination_info_cb, +                                              ksh); +  if (qs < 0) +  { +    // now what!? +  } + +#if TBD +  qs = TEH_plugin->iterate_auditor_info (TEH_plugin->cls, +                                         &auditor_info_cb, +                                         ksh); +  if (qs < 0) +  { +    // now what!? +  } +#endif +  // FIXME: initialize more: fetch everything we care about from DB/CFG! +  // STILL NEEDED: +  // - revocation signatures (if any) +  // - auditor signatures +  // - master signatures??? + +  // FIXME: should _also_ fetch master signatures and revocation status on signing keys! + + +  return ksh; +} + + +/** + * Update the "/keys" responses in @a ksh up to @a now into the future. + * + * @param[in,out] ksh state handle to update + * @param now timestamp for when to compute the replies. + */ +static void +update_keys_response (struct TEH_KeyStateHandle *ksh, +                      struct GNUNET_TIME_Absolute now) +{ +  // FIXME: update 'krd_array' here! +} + + +/** + * Something changed in the database. Rebuild all key states.  This function + * should be called if the exchange learns about a new signature from an + * auditor or our master key. + * + * (We do not do so immediately, but merely signal to all threads that they + * need to rebuild their key state upon the next call to + * #TEH_get_key_state()). + */ +void +TEH_keys_update_states () +{ +  __sync_fetch_and_add (&key_generation, +                        1); +} + + +/** + * Return the current key state for this thread.  Possibly + * re-builds the key state if we have reason to believe + * that something changed. + * + * @return NULL on error + */ +struct TEH_KeyStateHandle * +TEH_keys_get_state (void) +{ +  struct TEH_KeyStateHandle *old_ksh; +  struct TEH_KeyStateHandle *ksh; + +  old_ksh = pthread_getspecific (key_state); +  if (NULL == old_ksh) +  { +    ksh = build_key_state (NULL); +    if (NULL == ksh) +      return NULL; +    if (0 != pthread_setspecific (key_state, +                                  ksh)) +    { +      GNUNET_break (0); +      destroy_key_state_cb (ksh, +                            true); +      return NULL; +    } +    return ksh; +  } +  if (old_ksh->key_generation < key_generation) +  { +    ksh = build_key_state (key_generation, +                           &old_ksh->helpers); +    if (0 != pthread_setspecific (key_state, +                                  ksh)) +    { +      GNUNET_break (0); +      if (NULL != ksh) +        destroy_key_state (ksh, +                           false); +      return NULL; +    } +    if (NULL != old_ksh) +      destroy_key_state (old_ksh, +                         false); +    return ksh; +  } +  sync_key_helpers (&old_ksh->helpers); +  return old_ksh; +} + + +struct TEH_DenominationKey * +TEH_keys_denomination_by_hash ( +  const struct GNUNET_HashCode *h_denom_pub, +  enum TALER_ErrorCode *ec, +  unsigned int *hc) +{ +  struct TEH_KeyStateHandle *ksh; +  struct TEH_DenominationKey *dk; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    *hc = MHD_HTTP_INTERNAL_SERVER_ERROR; +    *ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; +    return NULL; +  } +  dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map, +                                          h_denom_pub); +  if (NULL == dk) +  { +    *hc = MHD_HTTP_NOT_FOUND; +    *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; +    return NULL; +  } +  return dk; +} + + +struct TALER_DenominationSignature +TEH_keys_denomination_sign ( +  const struct GNUNET_HashCode *h_denom_pub, +  const void *msg, +  size_t msg_size, +  enum TALER_ErrorCode *ec) +{ +  struct TEH_KeyStateHandle *ksh; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    *ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; +    return; +  } +  return TALER_CRYPTO_helper_denom_sign (ksh->dh, +                                         h_denom_pub, +                                         msg, +                                         msg_size, +                                         ec); +} + + +void +TEH_keys_denomination_revoke ( +  const struct GNUNET_HashCode *h_denom_pub) +{ +  struct TEH_KeyStateHandle *ksh; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    GNUNET_break (0); +    return; +  } +  TALER_CRYPTO_helper_denom_revoke (ksh->dh, +                                    h_denom_pub); +  TEH_keys_update_states (); +} + + +enum TALER_ErrorCode +TEH_keys_exchange_sign_ (const struct +                         GNUNET_CRYPTO_EccSignaturePurpose *purpose, +                         struct TALER_ExchangePublicKeyP *pub, +                         struct TALER_ExchangeSignatureP *sig) +{ +  struct TEH_KeyStateHandle *ksh; +  enum TALER_ErrorCode ec; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    /* This *can* happen if the exchange's crypto helper is not running +       or had some bad error. */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Cannot sign request, no valid signing keys available.\n"); +    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; +  } +  ec = TALER_CRYPTO_helper_esign_sign_ (ksh->esh, +                                        purpose, +                                        pub, +                                        sig); +  if (TALER_EC_NONE != ec) +    return ec; +  /* FIXME: check here that 'pub' is set to an exchange public +     key that is actually signed by the master key! Otherwise, we +     happily continue to use key material even if the offline +     signatures have not been made yet! */ + +  return ec; +} + + +void +TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub) +{ +  struct TEH_KeyStateHandle *ksh; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    GNUNET_break (0); +    return; +  } +  TALER_CRYPTO_helper_esign_revoke (ksh->esh, +                                    exchange_pub); +  TEH_keys_update_states (); +} + + +/** + * Comparator used for a binary search by cherry_pick_date for @a key in the + * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions. + * + * @param key pointer to a `struct GNUNET_TIME_Absolute` + * @param value pointer to a `struct KeysResponseData` array entry + * @return 0 if time matches, -1 if key is smaller, 1 if key is larger + */ +static int +krd_search_comparator (const void *key, +                       const void *value) +{ +  const struct GNUNET_TIME_Absolute *kd = key; +  const struct KeysResponseData *krd = value; + +  if (kd->abs_value_us > krd->cherry_pick_date.abs_value_us) +    return 1; +  if (kd->abs_value_us < krd->cherry_pick_date.abs_value_us) +    return -1; +  return 0; +} + + +MHD_RESULT +TEH_handler_keys (const struct TEH_RequestHandler *rh, +                  struct MHD_Connection *connection, +                  const char *const args[]) +{ +  struct GNUNET_TIME_Absolute last_issue_date; +  struct GNUNET_TIME_Absolute now; + +  (void) rh; +  (void) args; +  { +    const char *have_cherrypick; + +    have_cherrypick = MHD_lookup_connection_value (connection, +                                                   MHD_GET_ARGUMENT_KIND, +                                                   "last_issue_date"); +    if (NULL != have_cherrypick) +    { +      unsigned long long cherrypickn; + +      if (1 != +          sscanf (have_cherrypick, +                  "%llu", +                  &cherrypickn)) +      { +        GNUNET_break_op (0); +        return TALER_MHD_reply_with_error (connection, +                                           MHD_HTTP_BAD_REQUEST, +                                           TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                           have_cherrypick); +      } +      /* The following multiplication may overflow; but this should not really +         be a problem, as giving back 'older' data than what the client asks for +         (given that the client asks for data in the distant future) is not +         problematic */ +      last_issue_date.abs_value_us = (uint64_t) cherrypickn * 1000000LLU; +    } +    else +    { +      last_issue_date.abs_value_us = 0LLU; +    } +  } + +  now = GNUNET_TIME_absolute_get (); +  { +    const char *have_fakenow; + +    have_fakenow = MHD_lookup_connection_value (connection, +                                                MHD_GET_ARGUMENT_KIND, +                                                "now"); +    if (NULL != have_fakenow) +    { +      unsigned long long fakenown; + +      if (1 != +          sscanf (have_fakenow, +                  "%llu", +                  &fakenown)) +      { +        GNUNET_break_op (0); +        return TALER_MHD_reply_with_error (connection, +                                           MHD_HTTP_FORBIDDEN, +                                           TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                           have_fakenow); +      } +      if (TEH_allow_keys_timetravel) +      { +        /* The following multiplication may overflow; but this should not really +           be a problem, as giving back 'older' data than what the client asks for +           (given that the client asks for data in the distant future) is not +           problematic */ +        now.abs_value_us = (uint64_t) fakenown * 1000000LLU; +      } +      else +      { +        /* Option not allowed by configuration */ +        return TALER_MHD_reply_with_error (connection, +                                           MHD_HTTP_FORBIDDEN, +                                           TALER_EC_EXCHANGE_KEYS_TIMETRAVEL_FORBIDDEN, +                                           NULL); +      } +    } +  } + +  { +    struct TEH_KeyStateHandle *ksh; +    const struct KeysResponseData *krd; + +    ksh = TEH_keys_get_state (); +    if (NULL == ksh) +    { +      GNUNET_break (0); +      return TALER_MHD_reply_with_error (connection, +                                         MHD_HTTP_INTERNAL_SERVER_ERROR, +                                         TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, +                                         "no key state"); +    } +    update_keys_response (ksh, +                          now); +    krd = bsearch (&last_issue_date, +                   key_state->krd_array, +                   key_state->krd_array_length, +                   sizeof (struct KeysResponseData), +                   &krd_search_comparator); +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Filtering /keys by cherry pick date %s found entry %u/%u\n", +                GNUNET_STRINGS_absolute_time_to_string (last_issue_date), +                (unsigned int) (krd - key_state->krd_array), +                key_state->krd_array_length); +    if ( (NULL == krd) && +         (key_state->krd_array_length > 0) ) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                  "Client provided invalid cherry picking timestamp %s, returning full response\n", +                  GNUNET_STRINGS_absolute_time_to_string (last_issue_date)); +      krd = &key_state->krd_array[0]; +    } +    if (NULL == krd) +    { +      /* Maybe client picked time stamp too far in the future?  In that case, +         "INTERNAL_SERVER_ERROR" might be misleading, could be more like a +         NOT_FOUND situation. But, OTOH, for 'sane' clients it is more likely +         to be our fault, so let's speculatively assume we are to blame ;-) */// +      GNUNET_break (0); +      TEH_KS_release (key_state); +      return TALER_MHD_reply_with_error (connection, +                                         MHD_HTTP_INTERNAL_SERVER_ERROR, +                                         TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, +                                         "no key data for given timestamp"); +    } +    return MHD_queue_response (connection, +                               MHD_HTTP_OK, +                               (MHD_YES == TALER_MHD_can_compress (connection)) +                               ? krd->response_compressed +                               : krd->response_uncompressed); +  } +} + + +/** + * Function to call to handle requests to "/management/keys" by sending + * back our future key material. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param args array of additional options (must be empty for this function) + * @return MHD result code + */ +MHD_RESULT +TEH_keys_management_get_handler (const struct TEH_RequestHandler *rh, +                                 struct MHD_Connection *connection, +                                 const char *const args[]) +{ +  struct TEH_KeyStateHandle *ksh; + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    GNUNET_break (0); +    return TALER_MHD_reply_with_error (connection, +                                       MHD_HTTP_INTERNAL_SERVER_ERROR, +                                       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, +                                       "no key state"); +  } +  // FIXME: iterate over both denomination and signing keys from the helpers; +  // filter by those that are already master-signed (and thus in the 'main' +  // key state).  COMBINE *here* with 'cfg' information about the +  // value/fees/etc. of the future denomination!  => return the rest! +  return MHD_NO; +} + + +/* end of taler-exchange-httpd_keystate.c */ diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h new file mode 100644 index 00000000..7b4d565f --- /dev/null +++ b/src/exchange/taler-exchange-httpd_keys.h @@ -0,0 +1,216 @@ +/* +  This file is part of TALER +  Copyright (C) 2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU Affero 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_keys.h + * @brief management of our various keys + * @author Christian Grothoff + */ +#include "platform.h" +#include <pthread.h> +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_responses.h" + + +#ifndef TALER_EXCHANGE_HTTPD_KEYS_H +#define TALER_EXCHANGE_HTTPD_KEYS_H + +/** + * @brief All information about a denomination key (which is used to + * sign coins into existence). + */ +struct TEH_DenominationKey +{ +  /** +   * The helper to sign with this denomination key. Will be NULL if the +   * private key is not available (this is the case after the key has expired +   * for signing coins, if it is too early, or if the key has been revoked). +   */ +  struct TALER_CRYPTO_DenominationHelper *dh; + +  /** +   * Decoded denomination public key (the hash of it is in +   * @e issue, but we sometimes need the full public key as well). +   */ +  struct TALER_DenominationPublicKey denom_pub; + +  /** +   * Signed public information about a denomination key. +   */ +  struct TALER_EXCHANGEDB_DenominationKeyInformationP issue; +}; + + +/** + * Something changed in the database. Rebuild all key states.  This function + * should be called if the exchange learns about a new signature from an + * auditor or our master key. + * + * (We do not do so immediately, but merely signal to all threads that they + * need to rebuild their key state upon the next call to + * #TEH_get_key_state()). + */ +void +TEH_keys_update_states (void); + + +/** + * Look up the issue for a denom public key.  Note that the result + * must only be used in this thread and only until another key or + * key state is resolved. + * + * @param key_state state to look in + * @param h_denom_pub hash of denomination public key + * @param[out] ec set to the error code, in case the operation failed + * @param[out] hc set to the HTTP status code to use + * @return the denomination key issue, + *         or NULL if @a h_denom_pub could not be found + */ +struct TEH_DenominationKey * +TEH_keys_denomination_by_hash ( +  const struct GNUNET_HashCode *h_denom_pub, +  enum TALER_ErrorCode *ec, +  unsigned int *hc); + + +/** + * Request to sign @a msg using the public key corresponding to + * @a h_denom_pub. + * + * @param h_denom_pub hash of the public key to use to sign + * @param msg message to sign + * @param msg_size number of bytes in @a msg + * @param[out] ec set to the error code (or #TALER_EC_NONE on success) + * @return signature, the value inside the structure will be NULL on failure, + *         see @a ec for details about the failure + */ +struct TALER_DenominationSignature +TEH_keys_denomination_sign ( +  const struct GNUNET_HashCode *h_denom_pub, +  const void *msg, +  size_t msg_size, +  enum TALER_ErrorCode *ec); + + +/** + * Revoke the public key associated with @param h_denom_pub . + * This function should be called AFTER the database was + * updated, as it also triggers #TEH_keys_update_states(). + * + * Note that the actual revocation happens asynchronously and + * may thus fail silently. To verify that the revocation succeeded, + * clients must watch for the associated change to the key state. + * + * @param h_denom_pub hash of the public key to revoke + */ +void +TEH_keys_denomination_revoke ( +  const struct GNUNET_HashCode *h_denom_pub); + + +/** + * Sign the message in @a purpose with the exchange's signing key. + * + * The @a purpose data is the beginning of the data of which the signature is + * to be created. The `size` field in @a purpose must correctly indicate the + * number of bytes of the data structure, including its header.  Use + * #TEH_keys_exchange_sign() instead of calling this function directly! + * + * @param purpose the message to sign + * @param[out] pub set to the current public signing key of the exchange + * @param[out] sig signature over purpose using current signing key + * @return #TALER_EC_NONE on success + */ +enum TALER_ErrorCode +TEH_keys_exchange_sign_ (const struct +                         GNUNET_CRYPTO_EccSignaturePurpose *purpose, +                         struct TALER_ExchangePublicKeyP *pub, +                         struct TALER_ExchangeSignatureP *sig) + + +/** + * @ingroup crypto + * @brief EdDSA sign a given block. + * + * The @a ps data must be a fixed-size struct for which the signature is to be + * created. The `size` field in @a ps->purpose must correctly indicate the + * number of bytes of the data structure, including its header. + * + * @param ps packed struct with what to sign, MUST begin with a purpose + * @param[out] pub where to store the public key to use for the signing + * @param[out] sig where to write the signature + * @return #TALER_EC_NONE on success + */ +#define TEH_keys_exchange_sign(ps,pub,sig) \ +  ({                                                  \ +    /* check size is set correctly */                 \ +    GNUNET_assert (htonl ((ps)->purpose.size) ==      \ +                   sizeof (*ps));                     \ +    /* check 'ps' begins with the purpose */          \ +    GNUNET_static_assert (((void*) (ps)) ==           \ +                          ((void*) &(ps)->purpose));  \ +    TEH_exchange_sign_ (&(ps)->purpose,               \ +                        pub,                          \ +                        sig);                         \ +  }) + + +/** + * Revoke the given exchange's signing key. + * This function should be called AFTER the database was + * updated, as it also triggers #TEH_keys_update_states(). + * + * Note that the actual revocation happens asynchronously and + * may thus fail silently. To verify that the revocation succeeded, + * clients must watch for the associated change to the key state. + * + * @param exchange_pub key to revoke + */ +void +TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub); + + +/** + * Function to call to handle requests to "/keys" by sending + * back our current key material. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param args array of additional options (must be empty for this function) + * @return MHD result code + */ +MHD_RESULT +TEH_keys_get_handler (const struct TEH_RequestHandler *rh, +                      struct MHD_Connection *connection, +                      const char *const args[]); + + +/** + * Function to call to handle requests to "/management/keys" by sending + * back our future key material. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param args array of additional options (must be empty for this function) + * @return MHD result code + */ +MHD_RESULT +TEH_keys_management_get_handler (const struct TEH_RequestHandler *rh, +                                 struct MHD_Connection *connection, +                                 const char *const args[]); + + +#endif diff --git a/src/exchangedb/exchange-0002.sql b/src/exchangedb/exchange-0002.sql index 146e0a7b..c7a31528 100644 --- a/src/exchangedb/exchange-0002.sql +++ b/src/exchangedb/exchange-0002.sql @@ -42,6 +42,9 @@ COMMENT ON INDEX prepare_get_index    IS 'for wire_prepare_data_get'; +-- NOTE: current thinking is that we will NOT need this table! +-- => Instead, 'future' keys are only with the secmod until +--    the offline key is provided!  CREATE TABLE IF NOT EXISTS future_denominations    (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)    ,denom_pub BYTEA NOT NULL diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 125cfe34..b1686ab6 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1624,8 +1624,7 @@ typedef void  typedef void  (*TALER_EXCHANGEDB_DenominationCallback)(    void *cls, -  const struct -  TALER_DenominationPublicKey *denom_pub, +  const struct TALER_DenominationPublicKey *denom_pub,    const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue); | 
