gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: [age restriction] progress 12/n


From: gnunet
Subject: [taler-exchange] branch master updated: [age restriction] progress 12/n
Date: Fri, 21 Jan 2022 15:45:10 +0100

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

oec pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 0b56de6c [age restriction] progress 12/n
0b56de6c is described below

commit 0b56de6c994d3e525aa2d0195ff4607db3f14715
Author: Özgür Kesim <oec-taler@kesim.org>
AuthorDate: Tue Jan 11 15:24:43 2022 +0100

    [age restriction] progress 12/n
    
    - taler-offline-tool now handles extensions
      - command "extensions" added with subcommands "show" and "sign"
      - parses extensions from taler config
      - shows and signs of extensions and their configurations
      - creates signed set of configurations for upload
      - added test for retrieval of extension config
    
    - simplified signature verification for extensions
      - remove per-extension signatures, also from DB schema
      - adjust prepared statements accordingly
      - adjust DB event handler for extensions
      - allow NULL for config for extension in DB schema
      - handler for /management/extensions adjusted to new datastructures
    
    - changed test for TALER_denom_blind/TALER_denom_sign_blinded with and
      without TALER_AgeHash
    
    - minor updates and various fixes
---
 src/auditordb/plugin_auditordb_postgres.c          |   2 +-
 src/exchange-tools/taler-exchange-offline.c        | 539 +++++++++++++++++++--
 src/exchange/taler-exchange-httpd.h                |   4 +-
 src/exchange/taler-exchange-httpd_extensions.c     |  10 +-
 .../taler-exchange-httpd_management_extensions.c   | 229 +++------
 src/exchangedb/exchange-0001.sql                   |   7 +-
 src/exchangedb/plugin_exchangedb_postgres.c        |  41 +-
 src/exchangedb/test_exchangedb.c                   | 108 ++++-
 src/include/taler_crypto_lib.h                     |   4 +-
 src/include/taler_exchange_service.h               |  15 +-
 src/include/taler_exchangedb_plugin.h              |   9 +-
 src/include/taler_extensions.h                     |  19 +-
 src/lib/exchange_api_handle.c                      |   2 +-
 src/lib/exchange_api_management_post_extensions.c  |  74 +--
 src/testing/test_exchange_api.conf                 |  66 +++
 src/util/extension_age_restriction.c               |  75 ++-
 src/util/offline_signatures.c                      |   8 +-
 17 files changed, 842 insertions(+), 370 deletions(-)

diff --git a/src/auditordb/plugin_auditordb_postgres.c 
b/src/auditordb/plugin_auditordb_postgres.c
index 7931900a..e0355d93 100644
--- a/src/auditordb/plugin_auditordb_postgres.c
+++ b/src/auditordb/plugin_auditordb_postgres.c
@@ -796,7 +796,7 @@ static enum GNUNET_GenericReturnValue
 postgres_gc (void *cls)
 {
   struct PostgresClosure *pg = cls;
-  struct GNUNET_TIME_Absolute now;
+  struct GNUNET_TIME_Absolute now = {0};
   struct GNUNET_PQ_QueryParam params_time[] = {
     GNUNET_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_end
diff --git a/src/exchange-tools/taler-exchange-offline.c 
b/src/exchange-tools/taler-exchange-offline.c
index 9255737a..8db1fc9f 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -1,18 +1,18 @@
 /*
-  This file is part of TALER
-  Copyright (C) 2020, 2021 Taler Systems SA
+   This file is part of TALER
+   Copyright (C) 2020, 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 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.
+   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 <http://www.gnu.org/licenses/>
-*/
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
 /**
  * @file taler-exchange-offline.c
  * @brief Support for operations involving the exchange's offline master key.
@@ -20,8 +20,10 @@
  */
 #include <platform.h>
 #include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
 #include "taler_json_lib.h"
 #include "taler_exchange_service.h"
+#include "taler_extensions.h"
 
 /**
  * Name of the input for the 'sign' and 'show' operation.
@@ -93,6 +95,11 @@
  */
 #define OP_SETUP "exchange-setup-0"
 
+/**
+ * sign the enabled and configured extensions.
+ */
+#define OP_EXTENSIONS "exchange-extensions-0"
+
 
 /**
  * Our private key, initialized in #load_offline_key().
@@ -392,6 +399,32 @@ struct UploadKeysRequest
   size_t idx;
 };
 
+/**
+ * Ongoing /management/extensions request.
+ */
+struct UploadExtensionsRequest
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadExtensionsRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadExtensionsRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementPostExtensionsHandle *h;
+
+  /**
+   * Operation index.
+   */
+  size_t idx;
+};
+
 
 /**
  * Next work item to perform.
@@ -483,6 +516,15 @@ static struct UploadKeysRequest *ukr_head;
  */
 static struct UploadKeysRequest *ukr_tail;
 
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_head;
+
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_tail;
 
 /**
  * Shutdown task. Invoked when the application is being terminated.
@@ -615,6 +657,21 @@ do_shutdown (void *cls)
       GNUNET_free (ukr);
     }
   }
+  {
+    struct UploadExtensionsRequest *uer;
+
+    while (NULL != (uer = uer_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete extensions signature upload #%u\n",
+                  (unsigned int) uer->idx);
+      TALER_EXCHANGE_post_management_extensions_cancel (uer->h);
+      GNUNET_CONTAINER_DLL_remove (uer_head,
+                                   uer_tail,
+                                   uer);
+      GNUNET_free (uer);
+    }
+  }
   if (NULL != out)
   {
     json_dumpf (out,
@@ -667,6 +724,7 @@ test_shutdown (void)
        (NULL == wdr_head) &&
        (NULL == wfr_head) &&
        (NULL == ukr_head) &&
+       (NULL == uer_head) &&
        (NULL == mgkh) &&
        (NULL == nxt) )
     GNUNET_SCHEDULER_shutdown ();
@@ -1661,6 +1719,136 @@ upload_keys (const char *exchange_url,
 }
 
 
+/**
+ * Function called with information about the post upload extensions operation 
result.
+ *
+ * @param cls closure with a `struct UploadExtensionsRequest`
+ * @param hr HTTP response data
+ */
+static void
+extensions_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_HttpResponse *hr)
+{
+  struct UploadExtensionsRequest *uer = cls;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) uer->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (uer_head,
+                               uer_tail,
+                               uer);
+  GNUNET_free (uer);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload extension configuration
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for POSTing configurations of extensions
+ */
+static void
+upload_extensions (const char *exchange_url,
+                   size_t idx,
+                   const json_t *value)
+{
+  json_t *extensions;
+  struct TALER_MasterSignatureP sig;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("extensions",
+                           &extensions),
+    GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+                                 &sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  /* 1. Parse the signed extensions */
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to set extensions: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+
+  /* 2. Verify the signature */
+  {
+    struct TALER_ExtensionConfigHash h_config;
+
+    if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config))
+    {
+      GNUNET_JSON_parse_free (spec);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "couldn't hash extensions\n");
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+
+    if (GNUNET_OK !=
+        load_offline_key (GNUNET_NO))
+      return;
+
+    if (GNUNET_OK != TALER_exchange_offline_extension_config_hash_verify (
+          &h_config,
+          &master_pub,
+          &sig))
+    {
+      GNUNET_JSON_parse_free (spec);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "invalid signature for extensions\n");
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+  }
+
+  /* 3. Upload the extensions */
+  {
+    struct TALER_EXCHANGE_ManagementPostExtensionsData ped = {
+      .extensions = extensions,
+      .extensions_sig = sig,
+    };
+    struct UploadExtensionsRequest *uer = GNUNET_new (struct
+                                                      UploadExtensionsRequest);
+    uer->idx = idx;
+    uer->h = TALER_EXCHANGE_management_post_extensions (
+      ctx,
+      exchange_url,
+      &ped,
+      &extensions_cb,
+      uer);
+    GNUNET_CONTAINER_DLL_insert (uer_head,
+                                 uer_tail,
+                                 uer);
+  }
+  GNUNET_JSON_parse_free (spec);
+}
+
+
 /**
  * Perform uploads based on the JSON in #out.
  *
@@ -1702,6 +1890,10 @@ trigger_upload (const char *exchange_url)
       .key = OP_UPLOAD_SIGS,
       .cb = &upload_keys
     },
+    {
+      .key = OP_EXTENSIONS,
+      .cb = &upload_extensions
+    },
     /* array termination */
     {
       .key = NULL
@@ -3314,6 +3506,297 @@ do_setup (char *const *args)
 }
 
 
+/**
+ * struct extension carries the information about an extension together with
+ * callbacks to parse the configuration and marshal it as JSON
+ */
+struct extension
+{
+  char *name;
+  bool enabled;
+  bool critical;
+  char *version;
+  void *config;
+
+  enum GNUNET_GenericReturnValue (*parse_config)(struct extension *this,
+                                                 const char *section);
+  json_t *(*config_json)(const struct extension *this);
+};
+
+#define EXT_PREFIX "exchange-extension-"
+
+#define DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21"
+
+static enum GNUNET_GenericReturnValue
+age_restriction_parse_config (struct extension *this, const char *section)
+{
+  char *age_groups = NULL;
+  struct TALER_AgeMask mask = {0};
+  enum GNUNET_GenericReturnValue ret;
+
+  ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, section, "ENABLED");
+
+  this->enabled = (GNUNET_YES == ret);
+
+  if (! this->enabled)
+    return GNUNET_OK;
+
+  if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                                          section,
+                                                          "AGE_GROUPS",
+                                                          &age_groups))
+    age_groups = DEFAULT_AGE_GROUPS;
+
+  if (GNUNET_OK != TALER_parse_age_group_string (age_groups, &mask))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "AGE_GROUPS");
+    test_shutdown ();
+    global_ret = EXIT_NOTCONFIGURED;
+    return GNUNET_SYSERR;
+  }
+
+  /* Don't look here. We just store the mask in/as the pointer .*/
+  this->config = (void *) (size_t) mask.mask;
+  return GNUNET_OK;
+}
+
+
+static json_t *
+age_restriction_json (const struct extension *this)
+{
+  struct TALER_AgeMask mask;
+  json_t *conf;
+
+  if (! this->enabled)
+    return NULL;
+
+  /* Don't look here. We just restore the mask from/as the pointer .*/
+  mask.mask = (uint32_t) (size_t) this->config;
+
+  conf = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string (
+      "age_groups",
+      TALER_age_mask_to_string (&mask)));
+
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_bool ("critical",
+                           this->critical),
+    GNUNET_JSON_pack_string ("version",
+                             this->version),
+    GNUNET_JSON_pack_object_steal ("config", conf));
+}
+
+
+static struct extension extensions[] = {
+  {
+    .name = "age_restriction",
+    .version = "1",
+    .config = 0,
+    .parse_config = &age_restriction_parse_config,
+    .config_json = &age_restriction_json,
+  },
+  /* TODO: add p2p here */
+  {0},
+};
+
+
+static const struct extension*
+get_extension (const char *extension)
+{
+  for (const struct extension *known = extensions;
+       NULL != known->name;
+       known++)
+  {
+    if (0 == strncasecmp (extension,
+                          known->name,
+                          strlen (known->name)))
+      return known;
+  }
+  return NULL;
+}
+
+
+static void
+collect_extensions (void *cls, const char *section)
+{
+  json_t *obj = (json_t *) cls;
+  const char *name;
+  const struct extension *extension;
+
+  if (0 != global_ret)
+    return;
+
+  if (0 != strncasecmp (section,
+                        EXT_PREFIX,
+                        sizeof(EXT_PREFIX) - 1))
+  {
+    return;
+  }
+
+  name = section + sizeof(EXT_PREFIX) - 1;
+
+  if (NULL == (extension = get_extension (name)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unsupported extension `%s` (section [%s]).\n", name,
+                section);
+    test_shutdown ();
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+
+  if (GNUNET_OK != extension->parse_config ((struct extension *) extension,
+                                            section))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Couldn't parse configuration for extension `%s` (section 
[%s]).\n",
+                name,
+                section);
+    test_shutdown ();
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+
+  json_object_set (obj, name, extension->config_json (extension));
+}
+
+
+/*
+ * Print the current extensions as configured
+ */
+static void
+do_extensions_show (char *const *args)
+{
+
+  json_t *obj = json_object ();
+  json_t *exts = json_object ();
+
+  GNUNET_CONFIGURATION_iterate_sections (kcfg,
+                                         &collect_extensions,
+                                         exts);
+  json_object_set (obj, "extensions", exts);
+
+  GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "%s\n",
+              json_dumps (obj, JSON_INDENT (2)));
+
+  json_decref (obj);
+}
+
+
+/*
+ * Sign the configurations of the enabled extensions
+ */
+static void
+do_extensions_sign (char *const *args)
+{
+  json_t *obj = json_object ();
+  json_t *extensions = json_object ();
+  struct TALER_ExtensionConfigHash h_config;
+  struct TALER_MasterSignatureP sig;
+
+  GNUNET_CONFIGURATION_iterate_sections (kcfg,
+                                         &collect_extensions,
+                                         extensions);
+
+  // TODO: check size of extensions?
+  if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "error while hashing config for extensions\n");
+    return;
+  }
+
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+
+
+  TALER_exchange_offline_extension_config_hash_sign (&h_config,
+                                                     &master_priv,
+                                                     &sig);
+  json_object_set (obj, "extensions", extensions);
+  json_object_update (obj,
+                      GNUNET_JSON_PACK (
+                        GNUNET_JSON_pack_data_auto (
+                          "extensions_sig",
+                          &sig)));
+
+  output_operation (OP_EXTENSIONS, obj);
+}
+
+
+static void
+cmd_handler (char *const *args, const struct SubCommand *cmds)
+{
+  nxt = NULL;
+  for (unsigned int i = 0; NULL != cmds[i].name; i++)
+  {
+    if (0 == strcasecmp (cmds[i].name,
+                         args[0]))
+    {
+      cmds[i].cb (&args[1]);
+      return;
+    }
+  }
+
+  if (0 != strcasecmp ("help",
+                       args[0]))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+                "Unexpected command `%s'\n",
+                args[0]);
+    global_ret = EXIT_INVALIDARGUMENT;
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+              "Supported subcommands:\n");
+  for (unsigned int i = 0; NULL != cmds[i].name; i++)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+                "- %s: %s\n",
+                cmds[i].name,
+                cmds[i].help);
+  }
+}
+
+
+static void
+do_work_extensions (char *const *args)
+{
+  struct SubCommand cmds[] = {
+    {
+      .name = "show",
+      .help =
+        "show the extensions in the Taler-config and their configured 
parameters",
+      .cb = &do_extensions_show
+    },
+    {
+      .name = "sign",
+      .help =
+        "sign the configuration of the extensions and publish it with the 
exchange",
+      .cb = &do_extensions_sign
+    },
+    {
+      .name = NULL,
+    }
+  };
+
+  if (NULL == args[0])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must provide a subcommand: `show` or `sign`.\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+
+  cmd_handler (args, cmds);
+  next (args + 1);
+}
+
+
 static void
 work (void *cls)
 {
@@ -3390,6 +3873,11 @@ work (void *cls)
         "upload operation result to exchange (to be performed online!)",
       .cb = &do_upload
     },
+    {
+      .name = "extensions",
+      .help = "subcommands for extension handling",
+      .cb = &do_work_extensions
+    },
     /* list terminator */
     {
       .name = NULL,
@@ -3397,34 +3885,7 @@ work (void *cls)
   };
   (void) cls;
 
-  nxt = NULL;
-  for (unsigned int i = 0; NULL != cmds[i].name; i++)
-  {
-    if (0 == strcasecmp (cmds[i].name,
-                         args[0]))
-    {
-      cmds[i].cb (&args[1]);
-      return;
-    }
-  }
-
-  if (0 != strcasecmp ("help",
-                       args[0]))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
-                "Unexpected command `%s'\n",
-                args[0]);
-    global_ret = EXIT_INVALIDARGUMENT;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
-              "Supported subcommands:\n");
-  for (unsigned int i = 0; NULL != cmds[i].name; i++)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
-                "- %s: %s\n",
-                cmds[i].name,
-                cmds[i].help);
-  }
+  cmd_handler (args, cmds);
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd.h 
b/src/exchange/taler-exchange-httpd.h
index 4f04029e..39666379 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -206,7 +206,9 @@ extern struct GNUNET_CURL_Context *TEH_curl_ctx;
  */
 extern struct TALER_Extension **TEH_extensions;
 
-#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_Max > ext && \
+/* TODO: this will not work anymore, once we have plugable extensions */
+#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_MaxPredefined 
> \
+                                    ext && \
                                     NULL != TEH_extensions[ext]->config)
 
 /**
diff --git a/src/exchange/taler-exchange-httpd_extensions.c 
b/src/exchange/taler-exchange-httpd_extensions.c
index 8723bebc..1a2c4552 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -116,11 +116,12 @@ static struct TALER_Extension **
 get_known_extensions ()
 {
 
-  struct TALER_Extension **list = GNUNET_new_array (TALER_Extension_Max + 1,
-                                                    struct TALER_Extension *);
+  struct TALER_Extension **list = GNUNET_new_array (
+    TALER_Extension_MaxPredefined + 1,
+    struct TALER_Extension *);
   list[TALER_Extension_AgeRestriction] = &extension_age_restriction;
   list[TALER_Extension_Peer2Peer] = &extension_peer2peer;
-  list[TALER_Extension_Max] = NULL;
+  list[TALER_Extension_MaxPredefined] = NULL;
 
   return list;
 }
@@ -160,7 +161,8 @@ extension_update_event_cb (void *cls,
   }
 
   type = *(enum TALER_Extension_Type *) extra;
-  if (type <0 || type >= TALER_Extension_Max)
+  /* TODO: This check will not work once we have plugable extensions */
+  if (type <0 || type >= TALER_Extension_MaxPredefined)
   {
     GNUNET_break (0);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c 
b/src/exchange/taler-exchange-httpd_management_extensions.c
index 96b855c3..8476e669 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -49,41 +49,8 @@ struct SetExtensionsContext
 {
   uint32_t num_extensions;
   struct Extension *extensions;
-  struct TALER_MasterSignatureP *extensions_sigs;
 };
 
-
-/**
- * @brief verifies the signature a configuration with the offline master key.
- *
- * @param config configuration of an extension given as JSON object
- * @param master_priv offline master public key of the exchange
- * @param[out] master_sig  signature
- * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-config_verify (
-  const json_t *config,
-  const struct TALER_MasterPublicKeyP *master_pub,
-  const struct TALER_MasterSignatureP *master_sig
-  )
-{
-  enum GNUNET_GenericReturnValue ret;
-  struct TALER_ExtensionConfigHash h_config;
-
-  ret = TALER_extension_config_hash (config, &h_config);
-  if (GNUNET_OK != ret)
-  {
-    GNUNET_break (0);
-    return ret;
-  }
-
-  return TALER_exchange_offline_extension_config_hash_verify (h_config,
-                                                              master_pub,
-                                                              master_sig);
-}
-
-
 /**
  * Function implementing database transaction to set the configuration of
  * extensions.  It runs the transaction logic.
@@ -111,14 +78,13 @@ set_extensions (void *cls,
   for (uint32_t i = 0; i<sec->num_extensions; i++)
   {
     struct Extension *ext = &sec->extensions[i];
-    struct TALER_MasterSignatureP *sig = &sec->extensions_sigs[i];
     enum GNUNET_DB_QueryStatus qs;
     char *config;
 
     /* Sanity check.
-     * TODO: replace with general API to retrieve the extension-handler
+     * TODO: This will not work anymore, once we have plugable extensions
      */
-    if (0 > ext->type || TALER_Extension_Max <= ext->type)
+    if (0 > ext->type || TALER_Extension_MaxPredefined <= ext->type)
     {
       GNUNET_break (0);
       return GNUNET_DB_STATUS_HARD_ERROR;
@@ -138,8 +104,7 @@ set_extensions (void *cls,
     qs = TEH_plugin->set_extension_config (
       TEH_plugin->cls,
       TEH_extensions[ext->type]->name,
-      config,
-      sig);
+      config);
 
     if (qs < 0)
     {
@@ -176,19 +141,19 @@ TEH_handler_management_post_extensions (
   struct MHD_Connection *connection,
   const json_t *root)
 {
-  struct SetExtensionsContext sec = {0};
+  MHD_RESULT ret;
   json_t *extensions;
-  json_t *extensions_sigs;
+  struct TALER_MasterSignatureP sig = {0};
   struct GNUNET_JSON_Specification top_spec[] = {
     GNUNET_JSON_spec_json ("extensions",
                            &extensions),
-    GNUNET_JSON_spec_json ("extensions_sigs",
-                           &extensions_sigs),
+    GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+                                 &sig),
     GNUNET_JSON_spec_end ()
   };
-  MHD_RESULT ret;
+  struct SetExtensionsContext sec = {0};
 
-  // Parse the top level json structure
+  /* Parse the top level json structure */
   {
     enum GNUNET_GenericReturnValue res;
 
@@ -201,153 +166,106 @@ TEH_handler_management_post_extensions (
       return MHD_YES; /* failure */
   }
 
-  // Ensure we have two arrays of the same size
-  if (! (json_is_array (extensions) &&
-         json_is_array (extensions_sigs)) )
+  /* Ensure we have an object */
+  if (! json_is_object (extensions))
   {
-    GNUNET_break_op (0);
     GNUNET_JSON_parse_free (top_spec);
     return TALER_MHD_reply_with_error (
       connection,
       MHD_HTTP_BAD_REQUEST,
       TALER_EC_GENERIC_PARAMETER_MALFORMED,
-      "array expected for extensions and extensions_sigs");
+      "invalid object");
   }
 
-  sec.num_extensions = json_array_size (extensions_sigs);
-  if (json_array_size (extensions) != sec.num_extensions)
+  /* Verify the signature */
   {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (top_spec);
-    return TALER_MHD_reply_with_error (
-      connection,
-      MHD_HTTP_BAD_REQUEST,
-      TALER_EC_GENERIC_PARAMETER_MALFORMED,
-      "arrays extensions and extensions_sigs are not of the same size");
+    struct TALER_ExtensionConfigHash h_config;
+    if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config))
+    {
+      GNUNET_JSON_parse_free (top_spec);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+        "invalid object, non-hashable");
+    }
+
+    if (GNUNET_OK != TALER_exchange_offline_extension_config_hash_verify (
+          &h_config,
+          &TEH_master_public_key,
+          &sig))
+    {
+      GNUNET_JSON_parse_free (top_spec);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+        "invalid signuture");
+    }
   }
 
+
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received /management/extensions\n");
 
+  sec.num_extensions = json_object_size (extensions);
   sec.extensions = GNUNET_new_array (sec.num_extensions,
                                      struct Extension);
-  sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
-                                          struct TALER_MasterSignatureP);
 
-  // Now parse individual extensions and signatures from those arrays.
-  for (unsigned int i = 0; i<sec.num_extensions; i++)
+  /* Now parse individual extensions and signatures from those objects. */
   {
-    // 1. parse the extension out of the json
-    enum GNUNET_GenericReturnValue res;
     const struct TALER_Extension *extension;
     const char *name;
-    struct GNUNET_JSON_Specification ext_spec[] = {
-      GNUNET_JSON_spec_string ("extension",
-                               &name),
-      GNUNET_JSON_spec_json ("config",
-                             &sec.extensions[i].config),
-      GNUNET_JSON_spec_end ()
-    };
-
-    res = TALER_MHD_parse_json_array (connection,
-                                      extensions,
-                                      ext_spec,
-                                      i,
-                                      -1);
-    if (GNUNET_SYSERR == res)
-    {
-      ret = MHD_NO; /* hard failure */
-      goto CLEANUP;
-    }
-    if (GNUNET_NO == res)
-    {
-      ret = MHD_YES;
-      goto CLEANUP;
-    }
+    json_t *config;
+    int idx = 0;
 
-    /* 2. Make sure name refers to a supported extension */
-    if (GNUNET_OK != TALER_extension_get_by_name (name,
-                                                  (const struct
-                                                   TALER_Extension **)
-                                                  TEH_extensions,
-                                                  &extension))
-    {
-      ret = TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_BAD_REQUEST,
-        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-        "invalid extension type");
-      goto CLEANUP;
-    }
-
-    sec.extensions[i].type = extension->type;
+    json_object_foreach (extensions, name, config){
 
-    /* 3. Extract the signature out of the json array */
-    {
-      enum GNUNET_GenericReturnValue res;
-      struct GNUNET_JSON_Specification sig_spec[] = {
-        GNUNET_JSON_spec_fixed_auto (NULL,
-                                     &sec.extensions_sigs[i]),
-        GNUNET_JSON_spec_end ()
-      };
-
-      res = TALER_MHD_parse_json_array (connection,
-                                        extensions_sigs,
-                                        sig_spec,
-                                        i,
-                                        -1);
-      if (GNUNET_SYSERR == res)
+      /* 1. Make sure name refers to a supported extension */
+      if (GNUNET_OK != TALER_extension_get_by_name (name,
+                                                    (const struct
+                                                     TALER_Extension **)
+                                                    TEH_extensions,
+                                                    &extension))
       {
-        ret = MHD_NO; /* hard failure */
+        ret = TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_BAD_REQUEST,
+          TALER_EC_GENERIC_PARAMETER_MALFORMED,
+          "invalid extension type");
         goto CLEANUP;
       }
-      if (GNUNET_NO == res)
+
+      sec.extensions[idx].config = config;
+      sec.extensions[idx].type = extension->type;
+
+      /* 2. Make sure the config is sound */
+      if (GNUNET_OK != extension->test_config (sec.extensions[idx].config))
       {
-        ret = MHD_YES;
+        ret = TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_BAD_REQUEST,
+          TALER_EC_GENERIC_PARAMETER_MALFORMED,
+          "invalid configuration for extension");
         goto CLEANUP;
-      }
-    }
-
-    /* 4. Verify the signature of the config */
-    if (GNUNET_OK != config_verify (
-          sec.extensions[i].config,
-          &TEH_master_public_key,
-          &sec.extensions_sigs[i]))
-    {
-      ret = TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_BAD_REQUEST,
-        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-        "invalid signature for extension");
-      goto CLEANUP;
-    }
 
-    /* 5. Make sure the config is sound */
-    if (GNUNET_OK != extension->test_config (sec.extensions[i].config))
-    {
-      GNUNET_JSON_parse_free (ext_spec);
-      ret = TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_BAD_REQUEST,
-        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-        "invalid configuration for extension");
-      goto CLEANUP;
+      }
 
-    }
+      /* We have a validly signed JSON object for the extension.  Increment its
+       * refcount.
+       */
+      json_incref (sec.extensions[idx].config);
+      idx++;
 
-    /* We have a validly signed JSON object for the extension.
-     * Increment its refcount and free the parser for the extension.
-     */
-    json_incref (sec.extensions[i].config);
-    GNUNET_JSON_parse_free (ext_spec);
+    } /* json_object_foreach */
+  }
 
-  } /* for-loop */
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received %u extensions\n",
               sec.num_extensions);
 
-  // now run the transaction to persist the configurations
+  /* now run the transaction to persist the configurations */
   {
     enum GNUNET_GenericReturnValue res;
 
@@ -378,7 +296,6 @@ CLEANUP:
     }
   }
   GNUNET_free (sec.extensions);
-  GNUNET_free (sec.extensions_sigs);
   GNUNET_JSON_parse_free (top_spec);
   return ret;
 }
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 51fd26ec..a8e79335 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -297,17 +297,14 @@ COMMENT ON TABLE signkey_revocations
 CREATE TABLE IF NOT EXISTS extensions
   (extension_id BIGSERIAL UNIQUE
   ,name VARCHAR NOT NULL UNIQUE
-  ,config BYTEA NOT NULL
-  ,config_sig BYTEA NOT NULL
+  ,config BYTEA
   );
 COMMENT ON TABLE extensions
   IS 'Configurations of the activated extensions';
 COMMENT ON COLUMN extensions.name
   IS 'Name of the extension';
 COMMENT ON COLUMN extensions.config
-  IS 'Configuration of the extension as JSON-blob';
-COMMENT ON COLUMN extensions.config
-  IS 'Signature of the configuration of an extension, signed with the master 
key of the exchange';
+  IS 'Configuration of the extension as JSON-blob, maybe NULL';
 
 
 CREATE TABLE IF NOT EXISTS known_coins
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 4b009607..918fc38c 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -2745,15 +2745,17 @@ prepare_statements (struct PostgresClosure *pg)
     /* Used in #postgres_set_extension_config */
     GNUNET_PQ_make_prepare (
       "set_extension_config",
-      "INSERT INTO extensions (name, config, config_sig) VALUES ($1, $2, $3) "
+      "INSERT INTO extensions (name, config) VALUES ($1, $2) "
       "ON CONFLICT (name) "
-      "DO UPDATE SET (config, config_sig) = ($2, $3)",
-      3),
+      "DO UPDATE SET config=$2",
+      2),
     /* Used in #postgres_get_extension_config */
     GNUNET_PQ_make_prepare (
       "get_extension_config",
-      "SELECT (config) FROM extensions"
-      " WHERE name=$1;",
+      "SELECT "
+      " config "
+      "FROM extensions"
+      "   WHERE name=$1;",
       1),
     GNUNET_PQ_PREPARED_STATEMENT_END
   };
@@ -11410,20 +11412,20 @@ postgres_delete_shard_locks (void *cls)
  * @param cls the @e cls of this struct with the plugin-specific state
  * @param extension_name the name of the extension
  * @param config JSON object of the configuration as string
- * @param config_sig signature of the configuration by the offline master key
  * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
 postgres_set_extension_config (void *cls,
                                const char *extension_name,
-                               const char *config,
-                               const struct TALER_MasterSignatureP *config_sig)
+                               const char *config)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam pcfg = (NULL == config || 0 == *config) ?
+                                     GNUNET_PQ_query_param_null () :
+                                     GNUNET_PQ_query_param_string (config);
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (extension_name),
-    GNUNET_PQ_query_param_string (config),
-    GNUNET_PQ_query_param_auto_from_type (config_sig),
+    pcfg,
     GNUNET_PQ_query_param_end
   };
 
@@ -11452,15 +11454,24 @@ postgres_get_extension_config (void *cls,
     GNUNET_PQ_query_param_string (extension_name),
     GNUNET_PQ_query_param_end
   };
+  bool is_null;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_string ("config", config),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_string ("config", config),
+      &is_null),
     GNUNET_PQ_result_spec_end
   };
+  enum GNUNET_DB_QueryStatus qs;
 
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "get_extension_config",
-                                                   params,
-                                                   rs);
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "get_extension_config",
+                                                 params,
+                                                 rs);
+  if (is_null)
+  {
+    *config = NULL;
+  }
+  return qs;
 }
 
 
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 6724e7b4..cca7c3f4 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -108,6 +108,63 @@ mark_prepare_cb (void *cls,
 }
 
 
+/**
+ * Simple check that config retrieval and setting for extensions work
+ */
+static enum GNUNET_GenericReturnValue
+test_extension_config (void)
+{
+  char *config;
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->get_extension_config (plugin->cls,
+                                        "fnord",
+                                        &config));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_config (plugin->cls,
+                                        "fnord",
+                                        "bar"));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_config (plugin->cls,
+                                        "fnord",
+                                        &config));
+
+  FAILIF (0 != strcmp ("bar", config));
+
+  /* let's do this again! */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_config (plugin->cls,
+                                        "fnord",
+                                        "buzz"));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_config (plugin->cls,
+                                        "fnord",
+                                        &config));
+
+  FAILIF (0 != strcmp ("buzz", config));
+
+  /* let's do this again, with NULL */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_config (plugin->cls,
+                                        "fnord",
+                                        NULL));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_config (plugin->cls,
+                                        "fnord",
+                                        &config));
+
+  FAILIF (NULL != config);
+
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
 /**
  * Test API relating to persisting the wire plugins preparation data.
  *
@@ -1334,6 +1391,10 @@ run (void *cls)
                                                  0,
                                                  &recoup_cb,
                                                  NULL));
+  /* simple extension check */
+  FAILIF (GNUNET_OK !=
+          test_extension_config ());
+
   RND_BLK (&reserve_pub);
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":1.000010",
@@ -1406,27 +1467,36 @@ run (void *cls)
   {
     struct TALER_PlanchetDetail pd;
     struct TALER_CoinSpendPublicKeyP coin_pub;
+    struct TALER_AgeHash age_hash;
+    struct TALER_AgeHash *p_ah[2] = {NULL, &age_hash};
 
-    RND_BLK (&coin_pub);
-    TALER_blinding_secret_create (&bks);
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_denom_blind (&dkp->pub,
-                                      &bks,
-                                      NULL, /* FIXME-Oec */
-                                      &coin_pub,
-                                      &c_hash,
-                                      &pd.coin_ev,
-                                      &pd.coin_ev_size));
-    TALER_coin_ev_hash (pd.coin_ev,
-                        pd.coin_ev_size,
-                        &cbc.h_coin_envelope);
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_denom_sign_blinded (&cbc.sig,
-                                             &dkp->priv,
-                                             pd.coin_ev,
-                                             pd.coin_ev_size));
-    GNUNET_free (pd.coin_ev);
+    /* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without
+     * age_hash, once with age_hash */
+    RND_BLK (&age_hash);
+    for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++)
+    {
+      RND_BLK (&coin_pub);
+      TALER_blinding_secret_create (&bks);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_denom_blind (&dkp->pub,
+                                        &bks,
+                                        p_ah[i],
+                                        &coin_pub,
+                                        &c_hash,
+                                        &pd.coin_ev,
+                                        &pd.coin_ev_size));
+      TALER_coin_ev_hash (pd.coin_ev,
+                          pd.coin_ev_size,
+                          &cbc.h_coin_envelope);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_denom_sign_blinded (&cbc.sig,
+                                               &dkp->priv,
+                                               pd.coin_ev,
+                                               pd.coin_ev_size));
+      GNUNET_free (pd.coin_ev);
+    }
   }
+
   cbc.reserve_pub = reserve_pub;
   cbc.amount_with_fee = value;
   GNUNET_assert (GNUNET_OK ==
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index f1fa0285..6a805b64 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -2536,7 +2536,7 @@ TALER_merchant_wire_signature_make (
  */
 void
 TALER_exchange_offline_extension_config_hash_sign (
-  const struct TALER_ExtensionConfigHash h_config,
+  const struct TALER_ExtensionConfigHash *h_config,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig);
 
@@ -2552,7 +2552,7 @@ TALER_exchange_offline_extension_config_hash_sign (
  */
 enum GNUNET_GenericReturnValue
 TALER_exchange_offline_extension_config_hash_verify (
-  const struct TALER_ExtensionConfigHash h_config,
+  const struct TALER_ExtensionConfigHash *h_config,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig
   );
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 7fb5b4ec..5bc87cf4 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -2682,12 +2682,14 @@ TALER_EXCHANGE_post_management_keys_cancel (
 
 /**
  * Information needed for a POST /management/extensions operation.
+ *
+ * It represents the interface ExchangeKeysResponse as defined in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
  */
 struct TALER_EXCHANGE_ManagementPostExtensionsData
 {
-  struct TALER_Extension *extensions;
-  struct TALER_MasterSignatureP *extensions_sigs;
-  uint32_t num_extensions;
+  json_t *extensions;
+  struct TALER_MasterSignatureP extensions_sig;
 };
 
 /**
@@ -2708,11 +2710,12 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle;
 
 
 /**
- * FIXME-oec: Provide correct explanation of this function.
+ * Uploads the configurations of enabled extensions to the exchange, signed
+ * with the master key.
  *
  * @param ctx the context
  * @param url HTTP base URL for the exchange
- * @param pkd signature data to POST
+ * @param ped signature data to POST
  * @param cb function to call with the exchange's result
  * @param cb_cls closure for @a cb
  * @return the request handle; NULL upon error
@@ -2721,7 +2724,7 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
 TALER_EXCHANGE_management_post_extensions (
   struct GNUNET_CURL_Context *ctx,
   const char *url,
-  const struct TALER_EXCHANGE_ManagementPostExtensionsData *pkd,
+  struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
   TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
   void *cb_cls);
 
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 5eb168e1..cd68e1ed 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -4026,15 +4026,13 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param extension_name the name of the extension
-   * @param config JSON object of the configuration as string
-   * @param config_sig signature of the configuration by the offline master key
+   * @param config JSON object of the configuration as string, maybe NULL (== 
disabled extension)
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
   (*set_extension_config)(void *cls,
                           const char *extension_name,
-                          const char *config,
-                          const struct TALER_MasterSignatureP *config_sig);
+                          const char *config);
 
   /**
    * Function called to retrieve the configuration of an extension
@@ -4042,8 +4040,7 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param extension_name the name of the extension
-   * @param[out] config JSON object of the configuration as string
-   * @param[out] config_sig signature of the configuration by the master key
+   * @param[out] config JSON object of the configuration as string, maybe NULL 
(== disabled extension)
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
index 243811eb..31e5c673 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -28,29 +28,22 @@
 
 #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
 
-enum TALER_Extension_ReturnValue
-{
-  TALER_Extension_OK = 0,
-  TALER_Extension_ERROR_PARSING = 1,
-  TALER_Extension_ERROR_INVALID = 2,
-  TALER_Extension_ERROR_SYS = 3
-};
-
 enum TALER_Extension_Type
 {
   TALER_Extension_AgeRestriction = 0,
   TALER_Extension_Peer2Peer = 1,
-  TALER_Extension_Max = 2 // Must be last
+  TALER_Extension_MaxPredefined = 2 // Must be last
 };
 
 /*
- * TODO oec: documentation
+ * Represents the implementation of an extension.
  */
 struct TALER_Extension
 {
   enum TALER_Extension_Type type;
   char *name;
   bool critical;
+  bool enabled;
   void *config;
 
   enum GNUNET_GenericReturnValue (*test_config)(const json_t *config);
@@ -68,7 +61,7 @@ struct TALER_Extension
  * Finds and returns a supported extension by a given name.
  *
  * @param name name of the extension to lookup
- * @param extensions list of TALER_Extensions as haystack, terminated by an 
entry of type TALER_Extension_Max
+ * @param extensions list of TALER_Extensions as haystack, terminated by a 
NULL-entry
  * @param[out] ext set to the extension, if found, NULL otherwise
  * @return GNUNET_OK if extension was found, GNUNET_NO otherwise
  */
@@ -109,7 +102,7 @@ TALER_extension_get_by_name (const char *name,
  * @param[out] mask Mask representation for age restriction.
  * @return Error, if age groups were invalid, OK otherwise.
  */
-enum TALER_Extension_ReturnValue
+enum GNUNET_GenericReturnValue
 TALER_parse_age_group_string (const char *groups,
                               struct TALER_AgeMask *mask);
 
@@ -133,7 +126,7 @@ TALER_age_mask_to_string (const struct TALER_AgeMask *mask);
  * @return Error if extension for age restriction was set but age groups were
  *         invalid, OK otherwise.
  */
-enum TALER_Extension_ReturnValue
+enum GNUNET_GenericReturnValue
 TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
                     struct TALER_AgeMask *mask);
 
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index ac0e0584..aea09a81 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -831,7 +831,7 @@ decode_keys_json (const json_t *resp_obj,
         return GNUNET_SYSERR;
       }
 
-      if (TALER_Extension_OK !=
+      if (GNUNET_OK !=
           TALER_parse_age_group_string (age_groups,
                                         &key_data->age_mask))
       {
diff --git a/src/lib/exchange_api_management_post_extensions.c 
b/src/lib/exchange_api_management_post_extensions.c
index 862ff711..c0ab143f 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -1,19 +1,19 @@
 /*
-  This file is part of TALER
-  Copyright (C) 2015-2021 Taler Systems SA
+   This file is part of TALER
+   Copyright (C) 2015-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 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.
+   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
-  <http://www.gnu.org/licenses/>
-*/
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see
+   <http://www.gnu.org/licenses/>
+ */
 /**
  * @file lib/exchange_api_management_post_extensions.c
  * @brief functions to handle the settings for extensions (p2p and age 
restriction)
@@ -125,15 +125,13 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
 TALER_EXCHANGE_management_post_extensions (
   struct GNUNET_CURL_Context *ctx,
   const char *url,
-  const struct TALER_EXCHANGE_ManagementPostExtensionsData *pkd,
-  TALER_EXCHANGE_ManagementPostKeysCallback cb,
+  struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+  TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
   void *cb_cls)
 {
   struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph;
   CURL *eh = NULL;
   json_t *body = NULL;
-  json_t *extensions = NULL;
-  json_t *extensions_sigs = NULL;
 
   ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle);
   ph->cb = cb;
@@ -149,45 +147,13 @@ TALER_EXCHANGE_management_post_extensions (
     GNUNET_free (ph);
     return NULL;
   }
-  extensions = json_array ();
-  GNUNET_assert (NULL != extensions);
-  for (unsigned int i = 0; i<pkd->num_extensions; i++)
-  {
-    const json_t *config;
-    const struct TALER_Extension *ext = &pkd->extensions[i];
-
-    config = ext->config_to_json (ext);
-
-    GNUNET_assert (NULL != config);
-    GNUNET_assert (0 ==
-                   json_array_append_new (
-                     extensions,
-                     GNUNET_JSON_PACK (
-                       GNUNET_JSON_pack_data_auto ("extension",
-                                                   &ext->name),
-                       GNUNET_JSON_pack_data_auto ("config",
-                                                   config)
-                       )));
-  }
-  extensions_sigs = json_array ();
-  GNUNET_assert (NULL != extensions_sigs);
-  for (unsigned int i = 0; i<pkd->num_extensions; i++)
-  {
-    const struct TALER_MasterSignatureP *sks
-      = &pkd->extensions_sigs[i];
-
-    GNUNET_assert (0 ==
-                   json_array_append_new (
-                     extensions_sigs,
-                     GNUNET_JSON_PACK (
-                       GNUNET_JSON_pack_data_auto ("extension_sig",
-                                                   &sks->eddsa_signature))));
-  }
+
   body = GNUNET_JSON_PACK (
-    GNUNET_JSON_pack_array_steal ("extensions",
-                                  extensions),
-    GNUNET_JSON_pack_array_steal ("extensions_sigs",
-                                  extensions_sigs));
+    GNUNET_JSON_pack_object_steal ("extensions",
+                                   ped->extensions),
+    GNUNET_JSON_pack_data_auto ("extensions_sigs",
+                                &ped->extensions_sig));
+
   eh = curl_easy_init ();
   GNUNET_assert (NULL != eh);
   if (GNUNET_OK !=
diff --git a/src/testing/test_exchange_api.conf 
b/src/testing/test_exchange_api.conf
index a1b74365..48d5c200 100644
--- a/src/testing/test_exchange_api.conf
+++ b/src/testing/test_exchange_api.conf
@@ -77,6 +77,12 @@ WIRE_GATEWAY_URL = "http://localhost:9081/2/";
 [bank]
 HTTP_PORT = 9081
 
+# Enabled extensions
+[exchange-extension-age_restriction]
+ENABLED = YES
+# default age groups:
+#AGE_GROUPS = "8:10:12:14:16:18:21"
+
 # Sections starting with "coin_" specify which denominations
 # the exchange should support (and their respective fee structure)
 [coin_eur_ct_1]
@@ -133,3 +139,63 @@ fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
+
+[coin_eur_ct_1_age_restricted]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = true
+
+[coin_eur_ct_10_age_restricted]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = true
+
+[coin_eur_1_age_restricted]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = true
+
+[coin_eur_5_age_restricted]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = true
+
+[coin_eur_10_age_restricted]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = true
diff --git a/src/util/extension_age_restriction.c 
b/src/util/extension_age_restriction.c
index b29a8ca8..0b04c7d7 100644
--- a/src/util/extension_age_restriction.c
+++ b/src/util/extension_age_restriction.c
@@ -30,12 +30,12 @@
  * @return Error if extension for age restriction was set, but age groups were
  *         invalid, OK otherwise.
  */
-enum TALER_Extension_ReturnValue
+enum GNUNET_GenericReturnValue
 TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
                     struct TALER_AgeMask *mask)
 {
   char *groups;
-  enum TALER_Extension_ReturnValue ret = TALER_Extension_ERROR_SYS;
+  enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
 
   if ((GNUNET_YES != GNUNET_CONFIGURATION_have_value (cfg,
                                                       
TALER_EXTENSION_SECTION_AGE_RESTRICTION,
@@ -46,7 +46,7 @@ TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle 
*cfg,
   {
     /* Age restriction is not enabled */
     mask->mask = 0;
-    return TALER_Extension_OK;
+    return GNUNET_OK;
   }
 
   /* Age restriction is enabled, extract age groups */
@@ -56,13 +56,13 @@ TALER_get_age_mask (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
                                                           &groups))
   {
     /* FIXME: log error? */
-    return TALER_Extension_ERROR_SYS;
+    return GNUNET_SYSERR;
   }
   if (groups == NULL)
   {
     /* No groups defined in config, return default_age_mask */
     mask->mask = TALER_EXTENSION_DEFAULT_AGE_MASK;
-    return TALER_Extension_OK;
+    return GNUNET_OK;
   }
 
   ret = TALER_parse_age_group_string (groups, mask);
@@ -79,59 +79,46 @@ TALER_get_age_mask (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
  * @param[out] mask Bit representation of the age groups.
  * @return Error if string was invalid, OK otherwise.
  */
-enum TALER_Extension_ReturnValue
+enum GNUNET_GenericReturnValue
 TALER_parse_age_group_string (const char *groups,
                               struct TALER_AgeMask *mask)
 {
-  enum TALER_Extension_ReturnValue ret = TALER_Extension_ERROR_SYS;
-  char *pos;
+
+  const char *pos = groups;
   unsigned int prev = 0;
-  unsigned int val;
-  char dummy;
+  unsigned int val = 0;
+  char c;
 
-  while (1)
+  while (*pos)
   {
-    pos = strchr (groups, ':');
-    if (NULL != pos)
+    c = *pos++;
+    if (':' == c)
     {
-      *pos = 0;
-    }
+      if (prev >= val)
+        return GNUNET_SYSERR;
 
-    if (1 != sscanf (groups,
-                     "%u%c",
-                     &val,
-                     &dummy))
-    {
-      /* Invalid input */
-      mask->mask = 0;
-      ret = TALER_Extension_ERROR_PARSING;
-      break;
-    }
-    else if ((0 >= val) || (32 <= val) || (prev >= val))
-    {
-      /* Invalid value */
-      mask->mask = 0;
-      ret = TALER_Extension_ERROR_INVALID;
-      break;
+      mask->mask |= 1 << val;
+      prev = val;
+      val = 0;
+      continue;
     }
 
-    /* Set the corresponding bit in the mask */
-    mask->mask |= 1 << val;
+    if ('0'>c || '9'<c)
+      return GNUNET_SYSERR;
 
-    if (NULL == pos)
-    {
-      /* We reached the end. Mark zeroth age-group and exit. */
-      mask->mask |= 1;
-      ret = TALER_Extension_OK;
-      break;
-    }
+    val = 10 * val + c - '0';
 
-    prev = val;
-    *pos = ':';
-    groups = pos + 1;
+    if (0>=val || 32<=val)
+      return GNUNET_SYSERR;
   }
 
-  return ret;
+  if (0>val || 32<=val || prev>=val)
+    return GNUNET_SYSERR;
+
+  mask->mask |= (1 << val);
+  mask->mask |= 1; // mark zeroth group, too
+
+  return GNUNET_OK;
 }
 
 
diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c
index 1240a8bc..ab298834 100644
--- a/src/util/offline_signatures.c
+++ b/src/util/offline_signatures.c
@@ -492,14 +492,14 @@ TALER_exchange_offline_wire_fee_verify (
 
 void
 TALER_exchange_offline_extension_config_hash_sign (
-  const struct TALER_ExtensionConfigHash h_config,
+  const struct TALER_ExtensionConfigHash *h_config,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig)
 {
   struct TALER_MasterExtensionConfigurationPS ec = {
     .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
     .purpose.size = htonl (sizeof(ec)),
-    .h_config = h_config
+    .h_config = *h_config
   };
   GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
                             &ec,
@@ -509,7 +509,7 @@ TALER_exchange_offline_extension_config_hash_sign (
 
 enum GNUNET_GenericReturnValue
 TALER_exchange_offline_extension_config_hash_verify (
-  const struct TALER_ExtensionConfigHash h_config,
+  const struct TALER_ExtensionConfigHash *h_config,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig
   )
@@ -517,7 +517,7 @@ TALER_exchange_offline_extension_config_hash_verify (
   struct TALER_MasterExtensionConfigurationPS ec = {
     .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
     .purpose.size = htonl (sizeof(ec)),
-    .h_config = h_config
+    .h_config = *h_config
   };
 
   return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,

-- 
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]