gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated: incomplete work on forthcoming /


From: gnunet
Subject: [taler-exchange] branch master updated: incomplete work on forthcoming /keys implementation
Date: Sun, 06 Dec 2020 16:53:31 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new d9ac8e79 incomplete work on forthcoming /keys implementation
d9ac8e79 is described below

commit d9ac8e7975d813f1a868ab4d0cd062408f5aaf00
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Dec 6 16:53:29 2020 +0100

    incomplete work on forthcoming /keys implementation
---
 src/exchange/taler-exchange-httpd_keys.c | 1134 ++++++++++++++++++++++++++++++
 src/exchange/taler-exchange-httpd_keys.h |  216 ++++++
 src/exchangedb/exchange-0002.sql         |    3 +
 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);
 
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]