diff options
Diffstat (limited to 'src')
59 files changed, 1754 insertions, 346 deletions
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index d425b9ea..2ed8e5a1 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -1224,6 +1224,7 @@ static int  refresh_session_cb (void *cls,                      uint64_t rowid,                      const struct TALER_DenominationPublicKey *denom_pub, +                    const struct TALER_AgeCommitmentHash *h_age_commitment,                      const struct TALER_CoinSpendPublicKeyP *coin_pub,                      const struct TALER_CoinSpendSignatureP *coin_sig,                      const struct TALER_Amount *amount_with_fee, @@ -1286,6 +1287,7 @@ refresh_session_cb (void *cls,                                    &fee_refresh,                                    rc,                                    &h_denom_pub, +                                  h_age_commitment,                                    coin_pub,                                    coin_sig))      { @@ -1612,6 +1614,7 @@ deposit_cb (void *cls,      struct TALER_MerchantWireHash h_wire;      struct TALER_DenominationHash h_denom_pub;      struct TALER_Amount deposit_fee; +    struct TALER_AgeCommitmentHash *h_age_commitment = NULL; /* FIXME-oec */      TALER_denom_pub_hash (denom_pub,                            &h_denom_pub); @@ -1628,6 +1631,7 @@ deposit_cb (void *cls,                                       &deposit_fee,                                       &h_wire,                                       &deposit->h_contract_terms, +                                     h_age_commitment, /* FIXME-oec */                                       NULL /* h_extensions! */,                                       &h_denom_pub,                                       deposit->timestamp, diff --git a/src/benchmark/taler-aggregator-benchmark.c b/src/benchmark/taler-aggregator-benchmark.c index 005acfef..6452d6fc 100644 --- a/src/benchmark/taler-aggregator-benchmark.c +++ b/src/benchmark/taler-aggregator-benchmark.c @@ -300,7 +300,7 @@ add_deposit (const struct Merchant *m)    struct TALER_EXCHANGEDB_Deposit deposit;    uint64_t known_coin_id;    struct TALER_DenominationHash dph; -  struct TALER_AgeHash agh; +  struct TALER_AgeCommitmentHash agh;    RANDOMIZE (&d.coin.coin_pub);    d.coin.denom_pub_hash = h_denom_pub; diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c index 25c3b045..77ef94eb 100644 --- a/src/benchmark/taler-exchange-benchmark.c +++ b/src/benchmark/taler-exchange-benchmark.c @@ -366,6 +366,7 @@ run (void *cls,            (TALER_TESTING_cmd_withdraw_amount (wl,                                                create_reserve_label,                                                amount_5, +                                              0, /* age restriction off */                                                MHD_HTTP_OK));        unit[1] =          TALER_TESTING_cmd_deposit_with_retry diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index c5c9584d..55720a1b 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -152,6 +152,10 @@ static char *currency;   */  static char *CFG_exchange_url; +/** + * If age restriction is enabled, the age mask to be used + */ +static struct TALER_AgeMask age_mask = {0};  /**   * A subcommand supported by this program. @@ -1924,6 +1928,7 @@ trigger_upload (const char *exchange_url)        if (0 == strcasecmp (key,                             uhs[i].key))        { +          found = true;          uhs[i].cb (exchange_url,                     index, @@ -3092,6 +3097,7 @@ do_show (char *const *args)    keys = parse_keys_input ("show");    if (NULL == keys)      return; +    if (GNUNET_OK !=        load_offline_key (GNUNET_NO))      return; @@ -3254,6 +3260,43 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,  /** + * Looks up the AGE_RESTRICTED setting for a denomination in the config and + * returns the age restriction (mask) accordingly. + * + * @param section_name Section in the configuration for the particular + *    denomination. + */ +static struct TALER_AgeMask +load_age_mask (const char*section_name) +{ +  static const struct TALER_AgeMask null_mask = {0}; +  enum GNUNET_GenericReturnValue ret; + +  if (age_mask.mask == 0) +    return null_mask; + +  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( +                      kcfg, +                      section_name, +                      "AGE_RESTRICTED"))) +    return null_mask; + +  ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, +                                              section_name, +                                              "AGE_RESTRICTED"); +  if (GNUNET_YES == ret) +    return age_mask; + +  if (GNUNET_SYSERR == ret) +    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, +                               section_name, +                               "AGE_RESTRICTED", +                               "Value must be YES or NO\n"); +  return null_mask; +} + + +/**   * Sign @a denomkeys with offline key.   *   * @param secm_pub_rsa security module public key used to sign the RSA denominations @@ -3343,7 +3386,10 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,      duration = GNUNET_TIME_absolute_get_difference (        stamp_start.abs_time,        stamp_expire_withdraw.abs_time); -    // FIXME-Oec: setup age mask here? + +    /* Load the age mask, if applicable to this denomination */ +    denom_pub.age_mask = load_age_mask (section_name); +      TALER_denom_pub_hash (&denom_pub,                            &h_denom_pub);      switch (denom_pub.cipher) @@ -3604,14 +3650,6 @@ do_extensions_show (char *const *args)    json_t *exts = json_object ();    const struct TALER_Extension *it; -  TALER_extensions_init (); -  if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) -  { -    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, -                "error while loading taler config for extensions\n"); -    return; -  } -    for (it = TALER_extensions_get_head ();         NULL != it;         it = it->next) @@ -3865,6 +3903,17 @@ run (void *cls,      global_ret = EXIT_NOTCONFIGURED;      return;    } + +  /* load age mask, if age restriction is enabled */ +  TALER_extensions_init (); +  if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "error while loading taler config for extensions\n"); +    return; +  } +  age_mask = TALER_extensions_age_restriction_ageMask (); +    ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,                            &rc);    rc = GNUNET_CURL_gnunet_rc_create (ctx); diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 06ad7ca9..a0d0aa3b 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -128,6 +128,12 @@ char *TEH_currency;  char *TEH_base_url;  /** + * Age restriction flags and mask + */ +bool TEH_age_restriction_enabled = false; +struct TALER_AgeMask TEH_age_mask = {0}; + +/**   * Default timeout in seconds for HTTP requests.   */  static unsigned int connection_timeout = 30; @@ -737,6 +743,12 @@ handle_post_management (struct TEH_RequestContext *rc,      return TEH_handler_management_post_wire_fees (rc->connection,                                                    root);    } +  if (0 == strcmp (args[0], +                   "extensions")) +  { +    return TEH_handler_management_post_extensions (rc->connection, +                                                   root); +  }    GNUNET_break_op (0);    return r404 (rc->connection,                 "/management/*"); diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index d3b1ba84..ffbce0e9 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -186,6 +186,12 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;   */  extern char *TEH_currency; +/* + * Age restriction extension state + */ +extern bool TEH_age_restriction_enabled; +extern struct TALER_AgeMask TEH_age_mask; +  /**   * Our (externally visible) base URL.   */ diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 3600d793..f331e17d 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -50,7 +50,7 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,  {    enum TALER_EXCHANGEDB_CoinKnownStatus cks;    struct TALER_DenominationHash h_denom_pub; -  struct TALER_AgeHash age_hash; +  struct TALER_AgeCommitmentHash age_hash;    /* make sure coin is 'known' in database */    cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls, diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 053552f2..d750ec70 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -240,6 +240,9 @@ TEH_handler_deposit (struct MHD_Connection *connection,                                   &deposit.merchant_pub),      GNUNET_JSON_spec_fixed_auto ("h_contract_terms",                                   &deposit.h_contract_terms), +    GNUNET_JSON_spec_mark_optional ( +      GNUNET_JSON_spec_fixed_auto ("h_age_commitment", +                                   &deposit.coin.age_commitment_hash)),      GNUNET_JSON_spec_fixed_auto ("coin_sig",                                   &deposit.csig),      GNUNET_JSON_spec_timestamp ("timestamp", @@ -397,6 +400,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,                                     &deposit.deposit_fee,                                     &h_wire,                                     &deposit.h_contract_terms, +                                   &deposit.coin.age_commitment_hash,                                     NULL /* h_extensions! */,                                     &deposit.coin.denom_pub_hash,                                     deposit.timestamp, diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index 8edb24d4..6894a076 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -127,6 +127,16 @@ extension_update_event_cb (void *cls,        GNUNET_break (0);      }    } + +  /* Special case age restriction: Update global flag and mask  */ +  if (TALER_Extension_AgeRestriction == type) +  { +    TEH_age_mask.mask = 0; +    TEH_age_restriction_enabled = +      TALER_extensions_age_restriction_is_enabled (); +    if (TEH_age_restriction_enabled) +      TEH_age_mask = TALER_extensions_age_restriction_ageMask (); +  }  } @@ -151,6 +161,12 @@ TEH_extensions_init ()      return GNUNET_SYSERR;    } +  /* FIXME: shall we load the extensions from the config right away? +   * We do have to for now, as otherwise denominations with age restriction +   * will not have the age mask set right upon initial generation. +   */ +  TALER_extensions_load_taler_config (TEH_cfg); +    /* Trigger the initial load of configuration from the db */    for (const struct TALER_Extension *it = TALER_extensions_get_head ();         NULL != it->next; diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 4b1a6213..d1dfb28b 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -795,27 +795,17 @@ static struct TALER_AgeMask  load_age_mask (const char*section_name)  {    static const struct TALER_AgeMask null_mask = {0}; -  struct TALER_AgeMask age_mask = {0}; -  /* TODO: optimize by putting this into global? */ -  const struct TALER_Extension *age_ext = -    TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); - -  // Get the age mask from the extension, if configured -  /* TODO: optimize by putting this into global? */ -  if (TALER_extensions_is_enabled (age_ext)) -    age_mask = *(struct TALER_AgeMask *) age_ext->config; -  if (0 == age_mask.mask) -  { -    /* Age restriction support is not enabled.  Ignore the AGE_RESTRICTED field -     * for the particular denomination and simply return the null_mask -     */ +  struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask (); + +  if (age_mask.mask == 0)      return null_mask; -  } -  if (GNUNET_OK == (GNUNET_CONFIGURATION_have_value ( +  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (                        TEH_cfg,                        section_name,                        "AGE_RESTRICTED"))) +    return null_mask; +    {      enum GNUNET_GenericReturnValue ret; @@ -1331,6 +1321,8 @@ denomination_info_cb (    dk->meta = *meta;    dk->master_sig = *master_sig;    dk->recoup_possible = recoup_possible; +  dk->denom_pub.age_mask = meta->age_mask; +    GNUNET_assert (      GNUNET_OK ==      GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map, @@ -1745,7 +1737,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,   * @a recoup and @a denoms.   *   * @param[in,out] ksh key state handle we build @a krd for - * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms + * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms and age_restricted_denoms   * @param last_cpd timestamp to use   * @param signkeys list of sign keys to return   * @param recoup list of revoked keys to return @@ -1863,7 +1855,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,        int r;        /* skip if not configured == disabled */ -      if (NULL == extension->config) +      if (NULL == extension->config || +          NULL == extension->config_json)          continue;        /* flag our findings so far */ @@ -1899,7 +1892,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,        json_t *sig;        int r; -      r = json_object_set_new ( +      r = json_object_set (          keys,          "extensions",          extensions); @@ -1919,14 +1912,14 @@ create_krd (struct TEH_KeyStateHandle *ksh,        json_decref (extensions);      } -    // Special case for age restrictions: if enabled, provide the lits of +    // Special case for age restrictions: if enabled, provide the list of      // age-restricted denominations.      if (age_restriction_enabled &&          NULL != age_restricted_denoms)      {        GNUNET_assert (          0 == -        json_object_set_new ( +        json_object_set (            keys,            "age_restricted_denoms",            age_restricted_denoms)); @@ -2005,7 +1998,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)    json_t *age_restricted_denoms = NULL;    struct GNUNET_TIME_Timestamp last_cpd;    struct GNUNET_CONTAINER_Heap *heap; -  struct GNUNET_HashContext *hash_context; +  struct GNUNET_HashContext *hash_context = NULL; +  struct GNUNET_HashContext *hash_context_restricted = NULL; +  bool have_age_restricted_denoms = false;    sctx.signkeys = json_array ();    GNUNET_assert (NULL != sctx.signkeys); @@ -2030,19 +2025,23 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)        = GNUNET_TIME_relative_min (dkc.min_dk_frequency,                                    sctx.min_sk_frequency);    } +    denoms = json_array ();    GNUNET_assert (NULL != denoms); +  hash_context = GNUNET_CRYPTO_hash_context_start (); -  // If age restriction is enabled, initialize the array of age restricted denoms. -  /* TODO: optimize by putting this into global? */ -  if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction)) +  /* If age restriction is enabled, initialize the array of age restricted +   denoms  and prepare a hash for them, separate from the others.  We will join +   those hashes afterwards.*/ +  if (TEH_age_restriction_enabled)    {      age_restricted_denoms = json_array ();      GNUNET_assert (NULL != age_restricted_denoms); +    hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();    }    last_cpd = GNUNET_TIME_UNIT_ZERO_TS; -  hash_context = GNUNET_CRYPTO_hash_context_start (); +    {      struct TEH_DenominationKey *dk; @@ -2056,6 +2055,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)        {          struct GNUNET_HashCode hc; +        /* FIXME-oec: Do we need to take hash_context_restricted into account +         * in this if-branch!?  Current tests suggests: no, (they don't fail). +         * But something seems to be odd about only finishing hash_context. +         */ +          GNUNET_CRYPTO_hash_context_finish (            GNUNET_CRYPTO_hash_context_copy (hash_context),            &hc); @@ -2084,14 +2088,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)            return GNUNET_SYSERR;          }        } +        last_cpd = dk->meta.start; -      GNUNET_CRYPTO_hash_context_read (hash_context, -                                       &dk->h_denom_pub, -                                       sizeof (struct GNUNET_HashCode));        {          json_t *denom;          json_t *array; +        struct GNUNET_HashContext *hc; +          denom =            GNUNET_JSON_PACK ( @@ -2118,18 +2122,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)              TALER_JSON_pack_amount ("fee_refund",                                      &dk->meta.fee_refund)); -        /* Put the denom into the correct array - denoms or age_restricted_denoms - -         * depending on the settings and the properties of the denomination */ -        if (NULL != age_restricted_denoms && -            0 != dk->meta.age_restrictions.mask) +        /* Put the denom into the correct array depending on the settings and +         * the properties of the denomination.  Also, we build up the right +         * hash for the corresponding array. */ +        if (TEH_age_restriction_enabled && +            (0 != dk->denom_pub.age_mask.mask))          { +          have_age_restricted_denoms = true;            array = age_restricted_denoms; +          hc = hash_context_restricted;          }          else          {            array = denoms; +          hc = hash_context;          } +        GNUNET_CRYPTO_hash_context_read (hc, +                                         &dk->h_denom_pub, +                                         sizeof (struct GNUNET_HashCode)); +          GNUNET_assert (            0 ==            json_array_append_new ( @@ -2138,13 +2150,27 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)        }      }    } +    GNUNET_CONTAINER_heap_destroy (heap);    if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))    {      struct GNUNET_HashCode hc; +    /* If age restriction is active and we had at least one denomination of +     * that sort, we simply add the hash of all age restricted denominations at +     * the end of the others. */ +    if (TEH_age_restriction_enabled && have_age_restricted_denoms) +    { +      struct GNUNET_HashCode hcr; +      GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, &hcr); +      GNUNET_CRYPTO_hash_context_read (hash_context, +                                       &hcr, +                                       sizeof (struct GNUNET_HashCode)); +    } +      GNUNET_CRYPTO_hash_context_finish (hash_context,                                         &hc); +      if (GNUNET_OK !=          create_krd (ksh,                      &hc, @@ -2158,7 +2184,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)                    "Failed to generate key response data for %s\n",                    GNUNET_TIME_timestamp2s (last_cpd));        json_decref (denoms); -      if (NULL != age_restricted_denoms) +      if (TEH_age_restriction_enabled && NULL != age_restricted_denoms)          json_decref (age_restricted_denoms);        json_decref (sctx.signkeys);        json_decref (recoup); @@ -2849,7 +2875,9 @@ load_extension_data (const char *section_name,                  TEH_currency);      return GNUNET_SYSERR;    } -  meta->age_restrictions = load_age_mask (section_name); + +  meta->age_mask = load_age_mask (section_name); +    return GNUNET_OK;  } @@ -2976,7 +3004,7 @@ add_future_denomkey_cb (void *cls,    struct FutureBuilderContext *fbc = cls;    struct HelperDenomination *hd = value;    struct TEH_DenominationKey *dk; -  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; +  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};    dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,                                            h_denom_pub); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 17b00006..ab0287e3 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -31,7 +31,6 @@  #include "taler_extensions.h"  #include "taler_dbevents.h" -  /**   * Extension carries the necessary data for a particular extension.   * @@ -91,6 +90,8 @@ set_extensions (void *cls,        return GNUNET_DB_STATUS_HARD_ERROR;      } +    GNUNET_assert (NULL != ext->config); +      config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);      if (NULL == config)      { @@ -140,6 +141,57 @@ set_extensions (void *cls,  } +static enum GNUNET_GenericReturnValue +verify_extensions_from_json ( +  json_t *extensions, +  struct SetExtensionsContext *sec) +{ +  const char*name; +  const struct TALER_Extension *extension; +  size_t i = 0; +  json_t *blob; + +  GNUNET_assert (NULL != extensions); +  GNUNET_assert (json_is_object (extensions)); + +  sec->num_extensions = json_object_size (extensions); +  sec->extensions = GNUNET_new_array (sec->num_extensions, +                                      struct Extension); + +  json_object_foreach (extensions, name, blob) +  { +    int critical = 0; +    json_t *config; +    const char *version = NULL; + +    /* load and verify criticality, version, etc. */ +    extension = TALER_extensions_get_by_name (name); +    if (NULL == extension) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "no such extension: %s\n", name); +      return GNUNET_SYSERR; +    } + +    if (GNUNET_OK != +        TALER_extensions_is_json_config ( +          blob, &critical, &version, &config)) +      return GNUNET_SYSERR; + +    if (critical != extension->critical +        || 0 != strcmp (version, extension->version) // TODO: libtool compare? +        || NULL == config +        || GNUNET_OK != extension->test_json_config (config)) +      return GNUNET_SYSERR; + +    sec->extensions[i].type = extension->type; +    sec->extensions[i].config = config; +  } + +  return GNUNET_OK; +} + +  MHD_RESULT  TEH_handler_management_post_extensions (    struct MHD_Connection *connection, @@ -204,57 +256,18 @@ TEH_handler_management_post_extensions (    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); -    /* Now parse individual extensions and signatures from those objects. */ +  if (GNUNET_OK != +      verify_extensions_from_json (extensions, &sec))    { -    const struct TALER_Extension *extension = NULL; -    const char *name; -    json_t *config; -    int idx = 0; - -    json_object_foreach (extensions, name, config){ - -      /* 1. Make sure name refers to a supported extension */ -      extension = TALER_extensions_get_by_name (name); -      if (NULL == extension) -      { -        ret = TALER_MHD_reply_with_error ( -          connection, -          MHD_HTTP_BAD_REQUEST, -          TALER_EC_GENERIC_PARAMETER_MALFORMED, -          "invalid extension type"); -        goto CLEANUP; -      } - -      sec.extensions[idx].config = config; -      sec.extensions[idx].type = extension->type; - -      /* 2. Make sure the config is sound */ -      if (GNUNET_OK != -          extension->test_json_config ( -            sec.extensions[idx].config)) -      { -        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++; - -    } /* json_object_foreach */ +    GNUNET_JSON_parse_free (top_spec); +    return TALER_MHD_reply_with_error ( +      connection, +      MHD_HTTP_BAD_REQUEST, +      TALER_EC_GENERIC_PARAMETER_MALFORMED, +      "invalid object");    } -    GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Received %u extensions\n",                sec.num_extensions); @@ -281,6 +294,7 @@ TEH_handler_management_post_extensions (      NULL,      0); +  CLEANUP:    for (unsigned int i = 0; i < sec.num_extensions; i++)    { diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c index f0c3f1f3..c353a995 100644 --- a/src/exchange/taler-exchange-httpd_management_post_keys.c +++ b/src/exchange/taler-exchange-httpd_management_post_keys.c @@ -204,6 +204,7 @@ add_keys (void *cls,          TALER_denom_pub_free (&denom_pub);        return GNUNET_DB_STATUS_HARD_ERROR;      } +      if (is_active)      {        GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -211,6 +212,7 @@ add_keys (void *cls,                    GNUNET_h2s (&d->h_denom_pub.hash));        continue; /* skip, already known */      } +      qs = TEH_plugin->add_denomination_key (        TEH_plugin->cls,        &d->h_denom_pub, diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index 021b629b..8bfdf8ce 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -279,6 +279,7 @@ check_melt_valid (struct MHD_Connection *connection,      &mret);    if (NULL == dk)      return mret; +    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))    {      /* Way too late now, even zombies have expired */ @@ -288,6 +289,7 @@ check_melt_valid (struct MHD_Connection *connection,        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,        "MELT");    } +    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))    {      /* This denomination is not yet valid */ @@ -300,9 +302,11 @@ check_melt_valid (struct MHD_Connection *connection,    rmc->coin_refresh_fee = dk->meta.fee_refresh;    rmc->coin_value = dk->meta.value; +    GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Melted coin's denomination is worth %s\n",                TALER_amount2s (&dk->meta.value)); +    /* sanity-check that "total melt amount > melt fee" */    if (0 <        TALER_amount_cmp (&rmc->coin_refresh_fee, @@ -332,6 +336,7 @@ check_melt_valid (struct MHD_Connection *connection,                                  &rmc->coin_refresh_fee,                                  &rmc->refresh_session.rc,                                  &rmc->refresh_session.coin.denom_pub_hash, +                                &rmc->refresh_session.coin.age_commitment_hash,                                  &rmc->refresh_session.coin.coin_pub,                                  &rmc->refresh_session.coin_sig))    { @@ -407,6 +412,9 @@ TEH_handler_melt (struct MHD_Connection *connection,                                 &rmc.refresh_session.coin.denom_sig),      GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",                                   &rmc.refresh_session.coin.denom_pub_hash), +    GNUNET_JSON_spec_mark_optional ( +      GNUNET_JSON_spec_fixed_auto ("age_commitment_hash", +                                   &rmc.refresh_session.coin.age_commitment_hash)),      GNUNET_JSON_spec_fixed_auto ("confirm_sig",                                   &rmc.refresh_session.coin_sig),      TALER_JSON_spec_amount ("value_with_fee", diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c index 829e2cbd..bbf6defe 100644 --- a/src/exchange/taler-exchange-httpd_recoup-refresh.c +++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c @@ -251,7 +251,7 @@ verify_and_execute_recoup_refresh (      if (GNUNET_OK !=          TALER_denom_blind (&dk->denom_pub,                             coin_bks, -                           NULL, /* FIXME-Oec: TALER_AgeHash * */ +                           NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */                             &coin->coin_pub,                             exchange_vals,                             &c_hash, diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c index ea319d11..4ac997e9 100644 --- a/src/exchange/taler-exchange-httpd_recoup.c +++ b/src/exchange/taler-exchange-httpd_recoup.c @@ -256,7 +256,7 @@ verify_and_execute_recoup (      if (GNUNET_OK !=          TALER_denom_blind (&dk->denom_pub,                             coin_bks, -                           NULL, /* FIXME-Oec: TALER_AgeHash * */ +                           NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */                             &coin->coin_pub,                             exchange_vals,                             &c_hash, diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index 0d8f7bf9..1f0782aa 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -281,6 +281,7 @@ check_commitment (struct RevealContext *rctx,                                                   alg_value,                                                   &bks,                                                   &coin_priv, +                                                 NULL, /* FIXME-Oec, struct TALER_AgeCommitmentHash * */                                                   &c_hash,                                                   &pd));            if (TALER_DENOMINATION_CS == dk->cipher) @@ -380,6 +381,7 @@ check_commitment (struct RevealContext *rctx,   * @param rctx context for the operation, partially built at this time   * @param link_sigs_json link signatures in JSON format   * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL   * @param coin_evs envelopes of gamma-selected coins to be signed   * @return MHD result code   */ @@ -388,6 +390,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,                                          struct RevealContext *rctx,                                          const json_t *link_sigs_json,                                          const json_t *new_denoms_h_json, +                                        const json_t *old_age_commitment_json,                                          const json_t *coin_evs)  {    unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); @@ -412,6 +415,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,                                         TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,                                         NULL);    } +    /* Parse denomination key hashes */    for (unsigned int i = 0; i<num_fresh_coins; i++)    { @@ -537,6 +541,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,        goto cleanup;      }    } +    /* Parse link signatures array */    for (unsigned int i = 0; i<num_fresh_coins; i++)    { @@ -554,6 +559,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,                                        -1);      if (GNUNET_OK != res)        return (GNUNET_NO == res) ? MHD_YES : MHD_NO; +      /* Check signature */      if (GNUNET_OK !=          TALER_wallet_link_verify ( @@ -561,6 +567,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,            &rctx->gamma_tp,            &rrcs[i].coin_envelope_hash,            &rctx->melt.session.coin.coin_pub, +          NULL, // TODO-oec: calculate the correct h_age_commitment            &rrcs[i].orig_coin_link_sig))      {        GNUNET_break_op (0); @@ -592,6 +599,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,        goto cleanup;      }    } +    rctx->dks = dks;    rctx->rcds = rcds;    rctx->rrcs = rrcs; @@ -604,6 +612,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,                "Creating %u signatures\n",                (unsigned int) rctx->num_fresh_coins); +    /* create fresh coin signatures */    for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)    { @@ -622,8 +631,10 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,        goto cleanup;      }    } +    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,                "Signatures ready, starting DB interaction\n"); +    /* Persist operation result in DB */    {      enum GNUNET_DB_QueryStatus qs; @@ -678,11 +689,18 @@ cleanup:   * revealed information is valid then returns the signed refreshed   * coins.   * + * If the denomination has age restriction support, the array of EDDSA public + * keys, one for each age group that was activated during the withdrawal + * by the parent/ward, must be provided in old_age_commitment.  The hash of + * this array must be the same as the h_age_commitment of the persisted reveal + * request. + *   * @param connection the MHD connection to handle   * @param rctx context for the operation, partially built at this time   * @param tp_json private transfer keys in JSON format   * @param link_sigs_json link signatures in JSON format   * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL   * @param coin_evs envelopes of gamma-selected coins to be signed   * @return MHD result code   */ @@ -692,6 +710,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,                                const json_t *tp_json,                                const json_t *link_sigs_json,                                const json_t *new_denoms_h_json, +                              const json_t *old_age_commitment_json,                                const json_t *coin_evs)  {    unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); @@ -727,6 +746,19 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,                                         "new_denoms/link_sigs");    } +  /* Sanity check of age commitment: If it was provided, it _must_ be an array +   * of the size the # of age groups */ +  if (NULL != old_age_commitment_json +      && TALER_extensions_age_restriction_num_groups () != +      json_array_size (old_age_commitment_json)) +  { +    GNUNET_break_op (0); +    return TALER_MHD_reply_with_error (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, +                                       "old_age_commitment"); +  } +    /* Parse transfer private keys array */    for (unsigned int i = 0; i<num_tprivs; i++)    { @@ -750,6 +782,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,                                                   rctx,                                                   link_sigs_json,                                                   new_denoms_h_json, +                                                 old_age_commitment_json,                                                   coin_evs);  } @@ -763,6 +796,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,    json_t *transfer_privs;    json_t *link_sigs;    json_t *new_denoms_h; +  json_t *old_age_commitment = NULL;    struct RevealContext rctx;    struct GNUNET_JSON_Specification spec[] = {      GNUNET_JSON_spec_fixed_auto ("transfer_pub", @@ -775,6 +809,9 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,                             &coin_evs),      GNUNET_JSON_spec_json ("new_denoms_h",                             &new_denoms_h), +    GNUNET_JSON_spec_mark_optional ( +      GNUNET_JSON_spec_json ("old_age_commitment", +                             &old_age_commitment)),      GNUNET_JSON_spec_end ()    }; @@ -836,6 +873,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,                                          transfer_privs,                                          link_sigs,                                          new_denoms_h, +                                        old_age_commitment,                                          coin_evs);      GNUNET_JSON_parse_free (spec);      return res; diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 55b23044..00f04717 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -73,6 +73,7 @@ TEH_RESPONSE_compile_transaction_history (                                           &deposit->deposit_fee,                                           &h_wire,                                           &deposit->h_contract_terms, +                                         NULL, /* h_age_commitment, FIXME-oec */                                           NULL /* h_extensions! */,                                           &deposit->h_denom_pub,                                           deposit->timestamp, @@ -122,6 +123,7 @@ TEH_RESPONSE_compile_transaction_history (        {          const struct TALER_EXCHANGEDB_MeltListEntry *melt =            pos->details.melt; +        const struct TALER_AgeCommitmentHash *phac = NULL;  #if ENABLE_SANITY_CHECKS          if (GNUNET_OK != @@ -129,6 +131,7 @@ TEH_RESPONSE_compile_transaction_history (                                        &melt->melt_fee,                                        &melt->rc,                                        &melt->h_denom_pub, +                                      &melt->h_age_commitment,                                        coin_pub,                                        &melt->coin_sig))          { @@ -137,6 +140,12 @@ TEH_RESPONSE_compile_transaction_history (            return NULL;          }  #endif + +        /* Age restriction is optional.  We communicate a NULL value to +         * JSON_PACK below */ +        if (! TALER_AgeCommitmentHash_isNullOrZero (&melt->h_age_commitment)) +          phac = &melt->h_age_commitment; +          if (0 !=              json_array_append_new (                history, @@ -151,6 +160,9 @@ TEH_RESPONSE_compile_transaction_history (                                              &melt->rc),                  GNUNET_JSON_pack_data_auto ("h_denom_pub",                                              &melt->h_denom_pub), +                GNUNET_JSON_pack_allow_null ( +                  GNUNET_JSON_pack_data_auto ("h_age_commitment", +                                              phac)),                  GNUNET_JSON_pack_data_auto ("coin_sig",                                              &melt->coin_sig))))          { diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index 1111f381..df07e025 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS denominations    (denominations_serial BIGSERIAL UNIQUE    ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)    ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!) -  ,age_restrictions INT4 NOT NULL DEFAULT (0) +  ,age_mask INT4 NOT NULL DEFAULT (0)    ,denom_pub BYTEA NOT NULL    ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)    ,valid_from INT8 NOT NULL @@ -47,7 +47,7 @@ COMMENT ON TABLE denominations    IS 'Main denominations table. All the valid denominations the exchange knows about.';  COMMENT ON COLUMN denominations.denom_type    IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA'; -COMMENT ON COLUMN denominations.age_restrictions +COMMENT ON COLUMN denominations.age_mask    IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';  COMMENT ON COLUMN denominations.denominations_serial    IS 'needed for exchange-auditor replication logic'; @@ -345,6 +345,7 @@ CREATE TABLE IF NOT EXISTS refresh_commitments    (melt_serial_id BIGSERIAL -- UNIQUE    ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)    ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE +  ,h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32)    ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)    ,amount_with_fee_val INT8 NOT NULL    ,amount_with_fee_frac INT4 NOT NULL @@ -359,6 +360,8 @@ COMMENT ON COLUMN refresh_commitments.rc    IS 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol';  COMMENT ON COLUMN refresh_commitments.old_coin_pub    IS 'Coin being melted in the refresh process.'; +COMMENT ON COLUMN refresh_commitments.h_age_commitment +  IS '(optional) age commitment that was involved in the minting process of the coin, may be NULL.';  CREATE TABLE IF NOT EXISTS refresh_commitments_default    PARTITION OF refresh_commitments    FOR VALUES WITH (MODULUS 1, REMAINDER 0); diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 98724fa0..878c36f9 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -231,10 +231,11 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refresh_frac"        ",fee_refund_val"        ",fee_refund_frac" +      ",age_mask"        ") VALUES "        "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10," -      " $11, $12, $13, $14, $15, $16, $17);", -      17), +      " $11, $12, $13, $14, $15, $16, $17, $18);", +      18),      /* Used in #postgres_iterate_denomination_info() */      GNUNET_PQ_make_prepare (        "denomination_iterate", @@ -255,6 +256,7 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refund_val"        ",fee_refund_frac"        ",denom_pub" +      ",age_mask"        " FROM denominations;",        0),      /* Used in #postgres_iterate_denominations() */ @@ -278,6 +280,7 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refund_val"        ",fee_refund_frac"        ",denom_pub" +      ",age_mask"        " FROM denominations"        " LEFT JOIN "        "   denomination_revocations USING (denominations_serial);", @@ -341,6 +344,7 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refresh_frac"        ",fee_refund_val"        ",fee_refund_frac" +      ",age_mask"        " FROM denominations"        " WHERE denom_pub_hash=$1;",        1), @@ -825,6 +829,7 @@ prepare_statements (struct PostgresClosure *pg)        ",denoms.fee_refresh_frac"        ",old_coin_pub"        ",old_coin_sig" +      ",h_age_commitment"        ",amount_with_fee_val"        ",amount_with_fee_frac"        ",noreveal_index" @@ -843,6 +848,7 @@ prepare_statements (struct PostgresClosure *pg)        "SELECT"        " denom.denom_pub"        ",kc.coin_pub AS old_coin_pub" +      ",h_age_commitment"        ",old_coin_sig"        ",amount_with_fee_val"        ",amount_with_fee_frac" @@ -1842,6 +1848,7 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refresh_frac"        ",fee_refund_val"        ",fee_refund_frac" +      ",age_mask"        " FROM denominations"        " WHERE denom_pub_hash=$1;",        1), @@ -2069,7 +2076,6 @@ prepare_statements (struct PostgresClosure *pg)        "SELECT"        " denominations_serial AS serial"        ",denom_type" -      ",age_restrictions"        ",denom_pub"        ",master_sig"        ",valid_from" @@ -2086,6 +2092,7 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refresh_frac"        ",fee_refund_val"        ",fee_refund_frac" +      ",age_mask"        " FROM denominations"        " WHERE denominations_serial > $1"        " ORDER BY denominations_serial ASC;", @@ -2389,10 +2396,11 @@ prepare_statements (struct PostgresClosure *pg)        ",fee_refresh_frac"        ",fee_refund_val"        ",fee_refund_frac" +      ",age_mask"        ") VALUES "        "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10," -      " $11, $12, $13, $14, $15, $16, $17, $18);", -      18), +      " $11, $12, $13, $14, $15, $16, $17, $18, $19);", +      19),      GNUNET_PQ_make_prepare (        "insert_into_table_denomination_revocations",        "INSERT INTO denomination_revocations" @@ -3096,9 +3104,12 @@ postgres_insert_denomination_info (      TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),      TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),      TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund), +    GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.mask),      GNUNET_PQ_query_param_end    }; +  GNUNET_assert (denom_pub->age_mask.mask == issue->age_mask.mask); +    GNUNET_assert (! GNUNET_TIME_absolute_is_zero (                     GNUNET_TIME_timestamp_ntoh (                       issue->properties.start).abs_time)); @@ -3172,6 +3183,8 @@ postgres_get_denomination_info (                                       &issue->properties.fee_refresh),      TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",                                       &issue->properties.fee_refund), +    GNUNET_PQ_result_spec_uint32 ("age_mask", +                                  &issue->age_mask.mask),      GNUNET_PQ_result_spec_end    }; @@ -3258,12 +3271,15 @@ domination_cb_helper (void *cls,                                         &issue.properties.fee_refund),        TALER_PQ_result_spec_denom_pub ("denom_pub",                                        &denom_pub), +      GNUNET_PQ_result_spec_uint32 ("age_mask", +                                    &issue.age_mask.mask),        GNUNET_PQ_result_spec_end      };      memset (&issue.properties.master,              0,              sizeof (issue.properties.master)); +      if (GNUNET_OK !=          GNUNET_PQ_extract_result (result,                                    rs, @@ -3272,6 +3288,13 @@ domination_cb_helper (void *cls,        GNUNET_break (0);        return;      } + +    /* Unfortunately we have to carry the age mask in both, the +     * TALER_DenominationPublicKey and +     * TALER_EXCHANGEDB_DenominationKeyInformationP at different times. +     * Here we use _both_ so let's make sure the values are the same. */ +    denom_pub.age_mask = issue.age_mask; +      issue.properties.purpose.size        = htonl (sizeof (struct TALER_DenominationKeyValidityPS));      issue.properties.purpose.purpose @@ -3357,10 +3380,10 @@ dominations_cb_helper (void *cls,    for (unsigned int i = 0; i<num_results; i++)    { -    struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; -    struct TALER_DenominationPublicKey denom_pub; -    struct TALER_MasterSignatureP master_sig; -    struct TALER_DenominationHash h_denom_pub; +    struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0}; +    struct TALER_DenominationPublicKey denom_pub = {0}; +    struct TALER_MasterSignatureP master_sig = {0}; +    struct TALER_DenominationHash h_denom_pub = {0};      bool revoked;      struct GNUNET_PQ_ResultSpec rs[] = {        GNUNET_PQ_result_spec_auto_from_type ("master_sig", @@ -3387,6 +3410,8 @@ dominations_cb_helper (void *cls,                                     &meta.fee_refund),        TALER_PQ_result_spec_denom_pub ("denom_pub",                                        &denom_pub), +      GNUNET_PQ_result_spec_uint32 ("age_mask", +                                    &meta.age_mask.mask),        GNUNET_PQ_result_spec_end      }; @@ -3398,6 +3423,10 @@ dominations_cb_helper (void *cls,        GNUNET_break (0);        return;      } + +    /* make sure the mask information is the same */ +    denom_pub.age_mask = meta.age_mask; +      TALER_denom_pub_hash (&denom_pub,                            &h_denom_pub);      dic->cb (dic->cb_cls, @@ -5741,11 +5770,13 @@ postgres_ensure_coin_known (void *cls,                              const struct TALER_CoinPublicInfo *coin,                              uint64_t *known_coin_id,                              struct TALER_DenominationHash *denom_hash, -                            struct TALER_AgeHash *age_hash) +                            struct TALER_AgeCommitmentHash *age_hash)  {    struct PostgresClosure *pg = cls;    enum GNUNET_DB_QueryStatus qs;    bool existed; +  bool is_denom_pub_hash_null = false; +  bool is_age_hash_null = false;    struct GNUNET_PQ_QueryParam params[] = {      GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),      GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash), @@ -5753,24 +5784,22 @@ postgres_ensure_coin_known (void *cls,      TALER_PQ_query_param_denom_sig (&coin->denom_sig),      GNUNET_PQ_query_param_end    }; -  bool is_null = false;    struct GNUNET_PQ_ResultSpec rs[] = {      GNUNET_PQ_result_spec_bool ("existed",                                  &existed),      GNUNET_PQ_result_spec_uint64 ("known_coin_id",                                    known_coin_id),      GNUNET_PQ_result_spec_allow_null ( -      GNUNET_PQ_result_spec_auto_from_type ("age_hash", -                                            age_hash), -      &is_null), -    GNUNET_PQ_result_spec_allow_null (        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",                                              denom_hash), -      &is_null), +      &is_denom_pub_hash_null), +    GNUNET_PQ_result_spec_allow_null ( +      GNUNET_PQ_result_spec_auto_from_type ("age_hash", +                                            age_hash), +      &is_age_hash_null),      GNUNET_PQ_result_spec_end    }; -  GNUNET_break (GNUNET_is_zero (&coin->age_commitment_hash)); // FIXME-OEC    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,                                                   "insert_known_coin",                                                   params, @@ -5790,21 +5819,24 @@ postgres_ensure_coin_known (void *cls,        return TALER_EXCHANGEDB_CKS_ADDED;      break; /* continued below */    } -  if ( (! is_null) && -       (0 != GNUNET_memcmp (age_hash, -                            &coin->age_commitment_hash)) ) + +  if ( (! is_denom_pub_hash_null) && +       (0 != GNUNET_memcmp (&denom_hash->hash, +                            &coin->denom_pub_hash.hash)) )    { -    GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC      GNUNET_break_op (0); -    return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; +    return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;    } -  if ( (! is_null) && -       (0 != GNUNET_memcmp (denom_hash, -                            &coin->denom_pub_hash)) ) + +  if ( (! is_age_hash_null) && +       (0 != GNUNET_memcmp (age_hash, +                            &coin->age_commitment_hash)) )    { +    GNUNET_break (GNUNET_is_zero (age_hash));      GNUNET_break_op (0); -    return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; +    return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;    } +    return TALER_EXCHANGEDB_CKS_PRESENT;  } @@ -6030,6 +6062,7 @@ postgres_get_melt (void *cls,                     uint64_t *melt_serial_id)  {    struct PostgresClosure *pg = cls; +  bool h_age_commitment_is_null;    struct GNUNET_PQ_QueryParam params[] = {      GNUNET_PQ_query_param_auto_from_type (rc),      GNUNET_PQ_query_param_end @@ -6046,6 +6079,10 @@ postgres_get_melt (void *cls,                                            &melt->session.coin.coin_pub),      GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",                                            &melt->session.coin_sig), +    GNUNET_PQ_result_spec_allow_null ( +      GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment", +                                            &melt->session.h_age_commitment), +      &h_age_commitment_is_null),      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",                                   &melt->session.amount_with_fee),      GNUNET_PQ_result_spec_uint64 ("melt_serial_id", @@ -6061,6 +6098,11 @@ postgres_get_melt (void *cls,                                                   "get_melt",                                                   params,                                                   rs); +  if (h_age_commitment_is_null) +    memset (&melt->session.h_age_commitment, +            0, +            sizeof(melt->session.h_age_commitment)); +    melt->session.rc = *rc;    return qs;  } @@ -8225,6 +8267,8 @@ refreshs_serial_helper_cb (void *cls,      struct TALER_DenominationPublicKey denom_pub;      struct TALER_CoinSpendPublicKeyP coin_pub;      struct TALER_CoinSpendSignatureP coin_sig; +    struct TALER_AgeCommitmentHash h_age_commitment; +    bool ac_isnull;      struct TALER_Amount amount_with_fee;      uint32_t noreveal_index;      uint64_t rowid; @@ -8232,6 +8276,10 @@ refreshs_serial_helper_cb (void *cls,      struct GNUNET_PQ_ResultSpec rs[] = {        TALER_PQ_result_spec_denom_pub ("denom_pub",                                        &denom_pub), +      GNUNET_PQ_result_spec_allow_null ( +        GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment", +                                              &h_age_commitment), +        &ac_isnull),        GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",                                              &coin_pub),        GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", @@ -8257,9 +8305,11 @@ refreshs_serial_helper_cb (void *cls,        rsc->status = GNUNET_SYSERR;        return;      } +      ret = rsc->cb (rsc->cb_cls,                     rowid,                     &denom_pub, +                   ac_isnull ? NULL : &h_age_commitment,                     &coin_pub,                     &coin_sig,                     &amount_with_fee, @@ -10198,6 +10248,8 @@ postgres_lookup_denomination_key (                                   &meta->fee_refresh),      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",                                   &meta->fee_refund), +    GNUNET_PQ_result_spec_uint32 ("age_mask", +                                  &meta->age_mask.mask),      GNUNET_PQ_result_spec_end    }; @@ -10241,6 +10293,7 @@ postgres_add_denomination_key (      TALER_PQ_query_param_amount (&meta->fee_deposit),      TALER_PQ_query_param_amount (&meta->fee_refresh),      TALER_PQ_query_param_amount (&meta->fee_refund), +    GNUNET_PQ_query_param_uint32 (&meta->age_mask.mask),      GNUNET_PQ_query_param_end    }; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 0622e069..f9e64fdc 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -458,6 +458,7 @@ static unsigned int auditor_row_cnt;   * @param cls closure   * @param rowid unique serial ID for the refresh session in our DB   * @param denom_pub denomination of the @a coin_pub + * @param h_age_commitment hash of age commitment that went into the minting, may be NULL   * @param coin_pub public key of the coin   * @param coin_sig signature from the coin   * @param amount_with_fee amount that was deposited including fee @@ -470,6 +471,8 @@ static enum GNUNET_GenericReturnValue  audit_refresh_session_cb (void *cls,                            uint64_t rowid,                            const struct TALER_DenominationPublicKey *denom_pub, +                          const struct +                          TALER_AgeCommitmentHash *h_age_commitment,                            const struct TALER_CoinSpendPublicKeyP *coin_pub,                            const struct TALER_CoinSpendSignatureP *coin_sig,                            const struct TALER_Amount *amount_with_fee, @@ -1475,8 +1478,8 @@ run (void *cls)    {      struct TALER_PlanchetDetail pd;      struct TALER_CoinSpendPublicKeyP coin_pub; -    struct TALER_AgeHash age_hash; -    struct TALER_AgeHash *p_ah[2] = { +    struct TALER_AgeCommitmentHash age_hash; +    struct TALER_AgeCommitmentHash *p_ah[2] = {        NULL,        &age_hash      }; @@ -1597,7 +1600,7 @@ run (void *cls)    deadline = GNUNET_TIME_timestamp_get ();    {      struct TALER_DenominationHash dph; -    struct TALER_AgeHash agh; +    struct TALER_AgeCommitmentHash agh;      FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=              plugin->ensure_coin_known (plugin->cls, @@ -1847,7 +1850,7 @@ run (void *cls)      uint64_t new_known_coin_id;      struct TALER_CoinPublicInfo new_coin;      struct TALER_DenominationHash dph; -    struct TALER_AgeHash agh; +    struct TALER_AgeCommitmentHash agh;      bool recoup_ok;      bool internal_failure; @@ -2201,7 +2204,7 @@ run (void *cls)    {      uint64_t known_coin_id;      struct TALER_DenominationHash dph; -    struct TALER_AgeHash agh; +    struct TALER_AgeCommitmentHash agh;      FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=              plugin->ensure_coin_known (plugin->cls, diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c index a9ffb7f1..28b2dbb1 100644 --- a/src/extensions/extension_age_restriction.c +++ b/src/extensions/extension_age_restriction.c @@ -23,6 +23,19 @@  #include "taler_extensions.h"  #include "stdint.h" +/** + * Carries all the information we need for age restriction + */ +struct age_restriction_config +{ +  struct TALER_AgeMask mask; +  size_t num_groups; +}; + +/** + * Global config for this extension + */ +static struct age_restriction_config _config = {0};  /**   * @param groups String representation of the age groups. Must be of the form @@ -146,6 +159,9 @@ age_restriction_disable (      json_decref (this->config_json);      this->config_json = NULL;    } + +  _config.mask.mask = 0; +  _config.num_groups = 0;  } @@ -197,7 +213,6 @@ age_restriction_load_taler_config (    mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; -    ret = GNUNET_OK;    if (groups != NULL) @@ -208,7 +223,19 @@ age_restriction_load_taler_config (    }    if (GNUNET_OK == ret) -    this->config = (void *) (size_t) mask.mask; +  { +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "setting age mask to %x with #groups: %d\n", mask.mask, +                __builtin_popcount (mask.mask) - 1); +    _config.mask.mask = mask.mask; +    _config.num_groups = __builtin_popcount (mask.mask) - 1; /* no underflow, first bit always set */ +    this->config = &_config; + +    /* Note: we do now have _config set, however this->config_json is NOT set, +     * i.e. the extension is not yet active! For age restriction to become +     * active, load_json_config must have been called. */ +  } +    GNUNET_free (groups);    return ret; @@ -223,12 +250,12 @@ age_restriction_load_taler_config (  static enum GNUNET_GenericReturnValue  age_restriction_load_json_config (    struct TALER_Extension *this, -  json_t *config) +  json_t *jconfig)  {    struct TALER_AgeMask mask = {0};    enum GNUNET_GenericReturnValue ret; -  ret = TALER_JSON_parse_agemask (config, &mask); +  ret = TALER_JSON_parse_age_groups (jconfig, &mask);    if (GNUNET_OK != ret)      return ret; @@ -239,16 +266,28 @@ age_restriction_load_json_config (    if (TALER_Extension_AgeRestriction != this->type)      return GNUNET_SYSERR; -  if (NULL != this->config) -    GNUNET_free (this->config); +  _config.mask.mask = mask.mask; +  _config.num_groups = 0; + +  if (mask.mask > 0) +  { +    /* if the mask is not zero, the first bit MUST be set */ +    if (0 == (mask.mask & 1)) +      return GNUNET_SYSERR; + +    _config.num_groups = __builtin_popcount (mask.mask) - 1; +  } -  this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask)); -  GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask)); +  this->config = &_config;    if (NULL != this->config_json)      json_decref (this->config_json); -  this->config_json = config; +  this->config_json = jconfig; + +  GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +              "loaded new age restriction config with age groups: %s\n", +              TALER_age_mask_to_string (&mask));    return GNUNET_OK;  } @@ -263,7 +302,6 @@ json_t *  age_restriction_config_to_json (    const struct TALER_Extension *this)  { -  struct TALER_AgeMask mask;    char *mask_str;    json_t *conf; @@ -275,8 +313,7 @@ age_restriction_config_to_json (      return json_copy (this->config_json);    } -  mask.mask = (uint32_t) (size_t) this->config; -  mask_str = TALER_age_mask_to_string (&mask); +  mask_str = TALER_age_mask_to_string (&_config.mask);    conf = GNUNET_JSON_PACK (      GNUNET_JSON_pack_string ("age_groups", mask_str)      ); @@ -298,7 +335,7 @@ age_restriction_test_json_config (  {    struct TALER_AgeMask mask = {0}; -  return TALER_JSON_parse_agemask (config, &mask); +  return TALER_JSON_parse_age_groups (config, &mask);  } @@ -318,4 +355,50 @@ struct TALER_Extension _extension_age_restriction = {    .load_taler_config = &age_restriction_load_taler_config,  }; +bool +TALER_extensions_age_restriction_is_configured () +{ +  return (0 != _config.mask.mask); +} + + +struct TALER_AgeMask +TALER_extensions_age_restriction_ageMask () +{ +  return _config.mask; +} + + +size_t +TALER_extensions_age_restriction_num_groups () +{ +  return _config.num_groups; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, +                             struct TALER_AgeMask *mask) +{ +  enum GNUNET_GenericReturnValue ret; +  const char *str; +  struct GNUNET_JSON_Specification spec[] = { +    GNUNET_JSON_spec_string ("age_groups", +                             &str), +    GNUNET_JSON_spec_end () +  }; + +  ret = GNUNET_JSON_parse (root, +                           spec, +                           NULL, +                           NULL); +  if (GNUNET_OK == ret) +    TALER_parse_age_group_string (str, mask); + +  GNUNET_JSON_parse_free (spec); + +  return ret; +} + +  /* end of extension_age_restriction.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index 55d970c5..516c56a4 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -247,27 +247,31 @@ TALER_extensions_load_taler_config (  } -static enum GNUNET_GenericReturnValue -is_json_extension_config ( +enum GNUNET_GenericReturnValue +TALER_extensions_is_json_config (    json_t *obj,    int *critical,    const char **version,    json_t **config)  {    enum GNUNET_GenericReturnValue ret; +  json_t *cfg;    struct GNUNET_JSON_Specification spec[] = {      GNUNET_JSON_spec_boolean ("critical",                                critical),      GNUNET_JSON_spec_string ("version",                               version),      GNUNET_JSON_spec_json ("config", -                           config), +                           &cfg),      GNUNET_JSON_spec_end ()    };    ret = GNUNET_JSON_parse (obj, spec, NULL, NULL);    if (GNUNET_OK == ret) +  { +    *config = json_copy (cfg);      GNUNET_JSON_parse_free (spec); +  }    return ret;  } @@ -300,7 +304,7 @@ TALER_extensions_load_json_config (      /* load and verify criticality, version, etc. */      if (GNUNET_OK != -        is_json_extension_config ( +        TALER_extensions_is_json_config (            blob, &critical, &version, &config))        return GNUNET_SYSERR; @@ -330,4 +334,16 @@ TALER_extensions_load_json_config (  } +bool +TALER_extensions_age_restriction_is_enabled () +{ +  const struct TALER_Extension *age = +    TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + +  return (NULL != age && +          NULL != age->config_json && +          TALER_extensions_age_restriction_is_configured ()); +} + +  /* end of extensions.c */ diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index ab5202ba..9bbf29de 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -306,37 +306,6 @@ struct TALER_MasterSignatureP    struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;  }; -/* - * @brief Type of a list of age groups, represented as bit mask. - * - * The bits set in the mask mark the edges at the beginning of a next age - * group.  F.e. for the age groups - *     0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-* - * the following bits are set: - * - *   31     24        16        8         0 - *   |      |         |         |         | - *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1 - * - * A value of 0 means that the exchange does not support the extension for - * age-restriction. - */ -struct TALER_AgeMask -{ -  uint32_t mask; -}; - -/** - * @brief Age restriction commitment of a coin. - */ -struct TALER_AgeHash -{ -  /** -   * The commitment is a SHA-256 hash code. -   */ -  struct GNUNET_ShortHashCode shash; -}; -  /**   * @brief Type of public keys for Taler coins.  The same key material is used @@ -364,6 +333,29 @@ struct TALER_CoinSpendPrivateKeyP    struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;  }; +/** + * @brief Type of private keys for age commitment in coins. + */ +struct TALER_AgeCommitmentPrivateKeyP +{ +  /** +   * Taler uses EdDSA for coins when signing age verification attestation. +   */ +  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of public keys for age commitment in coins. + */ +struct TALER_AgeCommitmentPublicKeyP +{ +  /** +   * Taler uses EdDSA for coins when signing age verification attestation. +   */ +  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; +  /**   * @brief Type of signatures made with Taler coins. @@ -765,6 +757,46 @@ struct TALER_BlindedDenominationSignature  }; +/* *************** Age Restriction *********************************** */ + +/* + * @brief Type of a list of age groups, represented as bit mask. + * + * The bits set in the mask mark the edges at the beginning of a next age + * group.  F.e. for the age groups + *     0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-* + * the following bits are set: + * + *   31     24        16        8         0 + *   |      |         |         |         | + *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1 + * + * A value of 0 means that the exchange does not support the extension for + * age-restriction. + */ +struct TALER_AgeMask +{ +  uint32_t mask; +}; + +/** + * @brief Age commitment of a coin. + */ +struct TALER_AgeCommitmentHash +{ +  /** +   * The commitment is a SHA-256 hash code. +   */ +  struct GNUNET_ShortHashCode shash; +}; + +extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash; +#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \ +                                                  (0 == memcmp (ph, \ +                                                                & \ +                                                                TALER_ZeroAgeCommitmentHash, \ +                                                                sizeof(struct \ +                                                                       TALER_AgeCommitmentHash))))  /**   * @brief Type of public signing keys for verifying blindly signed coins. @@ -944,9 +976,10 @@ struct TALER_CoinPublicInfo    struct TALER_DenominationHash denom_pub_hash;    /** -   * Hash of the age commitment. +   * Hash of the age commitment.  If no age commitment was provided, it must be +   * set to all zeroes.     */ -  struct TALER_AgeHash age_commitment_hash; +  struct TALER_AgeCommitmentHash age_commitment_hash;    /**     * (Unblinded) signature over @e coin_pub with @e denom_pub, @@ -1117,7 +1150,7 @@ TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig);  enum GNUNET_GenericReturnValue  TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,                     const union TALER_DenominationBlindingKeyP *coin_bks, -                   const struct TALER_AgeHash *age_commitment_hash, +                   const struct TALER_AgeCommitmentHash *age_commitment_hash,                     const struct TALER_CoinSpendPublicKeyP *coin_pub,                     const struct TALER_ExchangeWithdrawValues *alg_values,                     struct TALER_CoinPubHash *c_hash, @@ -1349,7 +1382,7 @@ TALER_withdraw_request_hash (   */  void  TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, -                     const struct TALER_AgeHash *age_commitment_hash, +                     const struct TALER_AgeCommitmentHash *age_commitment_hash,                       struct TALER_CoinPubHash *coin_h); @@ -1402,8 +1435,9 @@ struct TALER_FreshCoin    struct TALER_CoinSpendPrivateKeyP coin_priv;    /** -   * FIXME-Oec: Age-verification vector, as pointer: Dyn alloc! +   * Optional hash of an age commitment bound to this coin, maybe NULL.     */ +  const struct TALER_AgeCommitmentHash *h_age_commitment;  }; @@ -1571,6 +1605,7 @@ TALER_planchet_blinding_secret_create (   * @param alg_values algorithm specific values   * @param bks blinding secrets   * @param coin_priv coin private key + * @param ach hash of age commitment to bind to this coin, maybe NULL   * @param[out] c_hash set to the hash of the public key of the coin (needed later)   * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and   *               other withdraw operations, `pd->blinded_planchet.cipher` will be set @@ -1582,6 +1617,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,                          const struct TALER_ExchangeWithdrawValues *alg_values,                          const union TALER_DenominationBlindingKeyP *bks,                          const struct TALER_CoinSpendPrivateKeyP *coin_priv, +                        const struct TALER_AgeCommitmentHash *ach,                          struct TALER_CoinPubHash *c_hash,                          struct TALER_PlanchetDetail *pd); @@ -1613,6 +1649,7 @@ TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd);   * @param blind_sig blind signature from the exchange   * @param bks blinding key secret   * @param coin_priv private key of the coin + * @param ach hash of age commitment that is bound to this coin, maybe NULL   * @param c_hash hash of the coin's public key for verification of the signature   * @param alg_values values obtained from the exchange for the withdrawal   * @param[out] coin set to the details of the fresh coin @@ -1624,6 +1661,7 @@ TALER_planchet_to_coin (    const struct TALER_BlindedDenominationSignature *blind_sig,    const union TALER_DenominationBlindingKeyP *bks,    const struct TALER_CoinSpendPrivateKeyP *coin_priv, +  const struct TALER_AgeCommitmentHash *ach,    const struct TALER_CoinPubHash *c_hash,    const struct TALER_ExchangeWithdrawValues *alg_values,    struct TALER_FreshCoin *coin); @@ -2202,6 +2240,7 @@ TALER_wallet_account_setup_sign (   * @param deposit_fee the deposit fee we expect to pay   * @param h_wire hash of the merchant’s account details   * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param h_age_commitment hash over the age commitment, if applicable to the denomination (maybe NULL)   * @param h_extensions hash over the extensions   * @param h_denom_pub hash of the coin denomination's public key   * @param coin_priv coin’s private key @@ -2216,6 +2255,7 @@ TALER_wallet_deposit_sign (    const struct TALER_Amount *deposit_fee,    const struct TALER_MerchantWireHash *h_wire,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_ExtensionContractHash *h_extensions,    const struct TALER_DenominationHash *h_denom_pub,    struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -2232,6 +2272,7 @@ TALER_wallet_deposit_sign (   * @param deposit_fee the deposit fee we expect to pay   * @param h_wire hash of the merchant’s account details   * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param h_age_commitment hash over the age commitment (maybe all zeroes, if not applicable to the denomination)   * @param h_extensions hash over the extensions   * @param h_denom_pub hash of the coin denomination's public key   * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future @@ -2247,6 +2288,7 @@ TALER_wallet_deposit_verify (    const struct TALER_Amount *deposit_fee,    const struct TALER_MerchantWireHash *h_wire,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_commitment_hash,    const struct TALER_ExtensionContractHash *h_extensions,    const struct TALER_DenominationHash *h_denom_pub,    struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -2283,6 +2325,7 @@ TALER_wallet_melt_sign (   * @param melt_fee the melt fee we expect to pay   * @param rc refresh session we are committed to   * @param h_denom_pub hash of the coin denomination's public key + * @param h_age_commitment hash of the age commitment (may be NULL)   * @param coin_pub coin’s public key   * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT   * @return #GNUNET_OK if the signature is valid @@ -2293,6 +2336,7 @@ TALER_wallet_melt_verify (    const struct TALER_Amount *melt_fee,    const struct TALER_RefreshCommitmentP *rc,    const struct TALER_DenominationHash *h_denom_pub, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_CoinSpendSignatureP *coin_sig); @@ -2321,6 +2365,7 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,   * @param transfer_pub transfer public key   * @param h_coin_ev hash of the coin envelope   * @param old_coin_pub old coin key that the link signature is for + * @param h_age_commitment hash of age commitment. Maybe NULL, if not applicable.   * @param coin_sig resulting signature   * @return #GNUNET_OK if the signature is valid   */ @@ -2330,6 +2375,7 @@ TALER_wallet_link_verify (    const struct TALER_TransferPublicKeyP *transfer_pub,    const struct TALER_BlindedCoinHash *h_coin_ev,    const struct TALER_CoinSpendPublicKeyP *old_coin_pub, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_CoinSpendSignatureP *coin_sig); @@ -3149,5 +3195,100 @@ TALER_exchange_offline_extension_config_hash_verify (    const struct TALER_MasterSignatureP *master_sig    ); +/* + * @brief Representation of an age commitment:  one public key per age group. + * + * The number of keys must be be the same as the number of bits set in the + * corresponding age mask. + */ +struct TALER_AgeCommitment +{ + +  /* The age mask defines the age groups that were a parameter during the +   * generation of this age commitment */ +  struct TALER_AgeMask mask; + +  /* The number of public keys, which must be the same as the number of +   * groups in the mask. +   */ +  size_t num_pub; + +  /* The list of #num_pub public keys.  In must have same size as the number of +   * age groups defined in the mask. +   * +   * A hash of this list is the hashed commitment that goes into FDC +   * calculation during the withdraw and refresh operations for new coins. That +   * way, the particular age commitment becomes mandatory and bound to a coin. +   * +   * The list has been allocated via GNUNET_malloc. +   */ +  struct TALER_AgeCommitmentPublicKeyP *pub; + +  /* The number of private keys, which must be at most num_pub_keys.  One minus +   * this number corresponds to the largest age group that is supported with +   * this age commitment. +   */ +  size_t num_priv; + +  /* List of #num_priv private keys. +   * +   * Note that the list can be _smaller_ than the corresponding list of public +   * keys. In that case, the wallet can sign off only for a subset of the age +   * groups. +   * +   * The list has been allocated via GNUNET_malloc. +   */ +  struct TALER_AgeCommitmentPrivateKeyP *priv; +}; + +/* + * @brief Generates a hash of the public keys in the age commitment. + * + * @param commitment the age commitment - one public key per age group + * @param[out] hash resulting hash + */ +void +TALER_age_commitment_hash ( +  const struct TALER_AgeCommitment *commitment, +  struct TALER_AgeCommitmentHash *hash); + +/* + * @brief Generates an age commitent for the given age. + * + * @param mask The age mask the defines the age groups + * @param age The actual age for which an age commitment is generated + * @param seed The seed that goes into the key generation.  MUST be choosen uniformly random. + * @param commitment[out] The generated age commitment, ->priv and ->pub allocated via GNUNET_malloc on success + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit ( +  const struct TALER_AgeMask *mask, +  const uint8_t age, +  const uint32_t seed, +  struct TALER_AgeCommitment *commitment); + +/* + * @brief Derives another, equivalent age commitment for a given one. + * + * @param orig Original age commitment + * @param seed Used to move the points on the elliptic curve in order to generate another, equivalent commitment. + * @param derived[out] The resulting age commitment, ->priv and ->pub allocated via GNUNET_malloc on success. + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +enum GNUNET_GenericReturnValue +TALER_age_commitment_derive ( +  const struct TALER_AgeCommitment *orig, +  const uint32_t seed, +  struct TALER_AgeCommitment *derived); + +/* + * @brief helper function to free memory inside a struct TALER_AgeCommitment + * @param cmt the commitment from which internal memory should be freed.  Note + * that cmt itself is NOT freed! + */ +void +TALER_age_restriction_commitment_free_inside ( +  struct TALER_AgeCommitment *cmt);  #endif diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 8c1b4bde..fef09f72 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -159,11 +159,6 @@ struct TALER_EXCHANGE_DenomPublicKey     * revoked by the exchange.     */    bool revoked; - -  /** -   * Is the denomination age-restricted? -   */ -  bool age_restricted;  }; @@ -785,6 +780,7 @@ TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh);   * @param h_extensions hash over the extensions   * @param h_denom_pub hash of the coin denomination's public key   * @param coin_priv coin’s private key + * @param age_commitment age commitment that went into the making of the coin, might be NULL   * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future   * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)   * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline @@ -799,6 +795,7 @@ TALER_EXCHANGE_deposit_permission_sign (    const struct TALER_ExtensionContractHash *h_extensions,    const struct TALER_DenominationHash *h_denom_pub,    const struct TALER_CoinSpendPrivateKeyP *coin_priv, +  const struct TALER_AgeCommitment *age_commitment,    struct GNUNET_TIME_Timestamp wallet_timestamp,    const struct TALER_MerchantPublicKeyP *merchant_pub,    struct GNUNET_TIME_Timestamp refund_deadline, @@ -924,6 +921,7 @@ TALER_EXCHANGE_deposit (    const char *merchant_payto_uri,    const struct TALER_WireSaltP *wire_salt,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const json_t *extension_details,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_DenominationSignature *denom_sig, @@ -1496,6 +1494,7 @@ typedef void   * @param reserve_priv private key of the reserve to withdraw from   * @param ps secrets of the planchet   *        caller must have committed this value to disk before the call (with @a pk) + * @param ach hash of the age commitment that should be bound to this coin. Maybe NULL.   * @param res_cb the callback to call when the final result for this request is available   * @param res_cb_cls closure for @a res_cb   * @return NULL @@ -1508,6 +1507,7 @@ TALER_EXCHANGE_withdraw (    const struct TALER_EXCHANGE_DenomPublicKey *pk,    const struct TALER_ReservePrivateKeyP *reserve_priv,    const struct TALER_PlanchetMasterSecretP *ps, +  const struct TALER_AgeCommitmentHash *ach,    TALER_EXCHANGE_WithdrawCallback res_cb,    void *res_cb_cls); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index ec647e9c..f0a6f8bd 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -70,6 +70,12 @@ struct TALER_EXCHANGEDB_DenominationKeyInformationP     * Signed properties of the denomination key.     */    struct TALER_DenominationKeyValidityPS properties; + +  /** +   * If denomination was setup for age restriction, non-zero age mask. +   * Note that the mask is not part of the signature. +   */ +  struct TALER_AgeMask age_mask;  }; @@ -295,7 +301,7 @@ struct TALER_EXCHANGEDB_TableData      struct      {        struct TALER_CoinSpendPublicKeyP coin_pub; -      struct TALER_AgeHash age_hash; +      struct TALER_AgeCommitmentHash age_hash;        uint64_t denominations_serial;        struct TALER_DenominationSignature denom_sig;      } known_coins; @@ -644,7 +650,7 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData     * A value of 0 means that the denomination does not support the extension for     * age-restriction.     */ -  struct TALER_AgeMask age_restrictions; +  struct TALER_AgeMask age_mask;  }; @@ -1262,6 +1268,13 @@ struct TALER_EXCHANGEDB_Refresh    struct TALER_CoinSpendSignatureP coin_sig;    /** +   * Hash of the age commitment used to sign the coin, if age restriction was +   * applicable to the denomination.  May be all zeroes if no age restriction +   * applies. +   */ +  struct TALER_AgeCommitmentHash h_age_commitment; + +  /**     * Refresh commitment this coin is melted into.     */    struct TALER_RefreshCommitmentP rc; @@ -1307,6 +1320,13 @@ struct TALER_EXCHANGEDB_MeltListEntry    struct TALER_DenominationHash h_denom_pub;    /** +   * Hash of the age commitment used to sign the coin, if age restriction was +   * applicable to the denomination.  May be all zeroes if no age restriction +   * applies. +   */ +  struct TALER_AgeCommitmentHash h_age_commitment; + +  /**     * How much value is being melted?  This amount includes the fees,     * so the final amount contributed to the melt is this value minus     * the fee for melting the coin.  We include the fee in what is @@ -1606,6 +1626,7 @@ typedef enum GNUNET_GenericReturnValue   * @param cls closure   * @param rowid unique serial ID for the refresh session in our DB   * @param denom_pub denomination public key of @a coin_pub + * @param h_age_commitment age commitment that went into the signing of the coin, may be NULL   * @param coin_pub public key of the coin   * @param coin_sig signature from the coin   * @param amount_with_fee amount that was deposited including fee @@ -1618,6 +1639,7 @@ typedef enum GNUNET_GenericReturnValue    void *cls,    uint64_t rowid,    const struct TALER_DenominationPublicKey *denom_pub, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_CoinSpendSignatureP *coin_sig,    const struct TALER_Amount *amount_with_fee, @@ -2758,7 +2780,7 @@ struct TALER_EXCHANGEDB_Plugin                         const struct TALER_CoinPublicInfo *coin,                         uint64_t *known_coin_id,                         struct TALER_DenominationHash *denom_pub_hash, -                       struct TALER_AgeHash *age_hash); +                       struct TALER_AgeCommitmentHash *age_hash);    /** diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index f00f3ed5..b7b93e17 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -86,6 +86,31 @@ TALER_extensions_load_taler_config (    const struct GNUNET_CONFIGURATION_Handle *cfg);  /* + * Check the given obj to be a valid extension object and fill the fields + * accordingly. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_is_json_config ( +  json_t *obj, +  int *critical, +  const char **version, +  json_t **config); + +/* + * Sets the configuration of the extensions from a given JSON object. + * + * he JSON object must be of type ExchangeKeysResponse as described in + * https://docs.taler.net/design-documents/006-extensions.html#exchange + * + * @param cfg JSON object containting the configuration for all extensions + * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found + *         or any particular configuration couldn't be parsed. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_load_json_config ( +  json_t *cfg); + +/*   * Returns the head of the linked list of extensions   */  const struct TALER_Extension * @@ -156,20 +181,6 @@ TALER_extensions_verify_json_config_signature (    struct TALER_MasterSignatureP *extensions_sig,    struct TALER_MasterPublicKeyP *master_pub); -/* - * Sets the configuration of the extensions from a given JSON object. - * - * The JSON object must be of type ExchangeKeysResponse as described in - * https://docs.taler.net/design-documents/006-extensions.html#exchange - * - * @param cfg Handle to the TALER configuration - * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found - *         or any particular configuration couldn't be parsed. - */ -enum GNUNET_GenericReturnValue -TALER_extensions_load_json_config ( -  json_t *extensions); -  /*   * TALER Age Restriction Extension @@ -221,6 +232,45 @@ char *  TALER_age_mask_to_string (    const struct TALER_AgeMask *mask); +/** + * Returns true when age restriction is configured and enabled. + */ +bool +TALER_extensions_age_restriction_is_enabled (); + +/** + * Returns true when age restriction is configured (might not be _enabled_, + * though). + */ +bool +TALER_extensions_age_restriction_is_configured (); + +/** + * Returns the currently set age mask.  Note that even if age restriction is + * not enabled, the age mask might be have a non-zero value. + */ +struct TALER_AgeMask +TALER_extensions_age_restriction_ageMask (); + + +/** + * Returns the amount of age groups defined.  0 means no age restriction + * enabled. + */ +size_t +TALER_extensions_age_restriction_num_groups (); + +/** + * Parses a JSON object { "age_groups": "a:b:...y:z" }. + * + * @param root is the json object + * @param[out] mask on succes, will contain the age mask + * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. + */ +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, +                             struct TALER_AgeMask *mask); +  /*   * TODO: Add Peer2Peer Extension diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index b7bcd845..e3e47222 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -608,17 +608,6 @@ TALER_JSON_extensions_config_hash (const json_t *config,                                     struct TALER_ExtensionConfigHash *eh);  /** - * Parses a JSON object `{ "extension": "age_restriction", "mask": uint32 }`. - * - * @param root is the json object - * @param[out] mask on succes, will contain the age mask - * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. - */ -enum GNUNET_GenericReturnValue -TALER_JSON_parse_agemask (const json_t *root, -                          struct TALER_AgeMask *mask); - -/**   * Canonicalize a JSON input to a string according to RFC 8785.   */  char * diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 17ed4b57..e3d9a893 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -419,6 +419,11 @@ struct TALER_LinkDataPS    struct TALER_TransferPublicKeyP transfer_pub;    /** +   * Hash of the age commitment, if applicable.  Can be all zero +   */ +  struct TALER_AgeCommitmentHash h_age_commitment; + +  /**     * Hash of the blinded new coin.     */    struct TALER_BlindedCoinHash coin_envelope_hash; @@ -477,6 +482,12 @@ struct TALER_DepositRequestPS    struct TALER_PrivateContractHash h_contract_terms GNUNET_PACKED;    /** +   * Hash over the age commitment that went into the coin. Maybe all zero, if +   * age commitment isn't applicable to the denomination. +   */ +  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + +  /**     * Hash over extension attributes shared with the exchange.     */    struct TALER_ExtensionContractHash h_extensions GNUNET_PACKED; @@ -710,6 +721,13 @@ struct TALER_RefreshMeltCoinAffirmationPS    struct TALER_DenominationHash h_denom_pub GNUNET_PACKED;    /** +   * If age commitment was provided during the withdrawal of the coin, this is +   * the hash of the age commitment vector.  It must be all zeroes if no age +   * commitment was provided. +   */ +  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + +  /**     * How much of the value of the coin should be melted?  This amount     * includes the fees, so the final amount contributed to the melt is     * this value minus the fee for melting the coin.  We include the diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 69cb9f68..ab8b64fc 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -66,11 +66,13 @@ TALER_TESTING_make_wire_details (const char *payto);   *   * @param keys array of keys to search   * @param amount coin value to look for + * @param age_restricted must the denomination be age restricted?   * @return NULL if no matching key was found   */  const struct TALER_EXCHANGE_DenomPublicKey *  TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, -                       const struct TALER_Amount *amount); +                       const struct TALER_Amount *amount, +                       bool age_restricted);  /** @@ -1278,6 +1280,7 @@ TALER_TESTING_cmd_exec_transfer (const char *label,   * @param label command label.   * @param reserve_reference command providing us with a reserve to withdraw from   * @param amount how much we withdraw. + * @param age if > 0, age restriction applies   * @param expected_response_code which HTTP response code   *        we expect from the exchange.   * @return the withdraw command to be executed by the interpreter. @@ -1286,6 +1289,7 @@ struct TALER_TESTING_Command  TALER_TESTING_cmd_withdraw_amount (const char *label,                                     const char *reserve_reference,                                     const char *amount, +                                   uint8_t age,                                     unsigned int expected_response_code); @@ -1298,6 +1302,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,   * @param label command label.   * @param reserve_reference command providing us with a reserve to withdraw from   * @param amount how much we withdraw. + * @param age if > 0, age restriction applies.   * @param coin_ref reference to (withdraw/reveal) command of a coin   *        from which we should re-use the private key   * @param expected_response_code which HTTP response code @@ -1309,6 +1314,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (    const char *label,    const char *reserve_reference,    const char *amount, +  uint8_t age,    const char *coin_ref,    unsigned int expected_response_code); @@ -2138,6 +2144,19 @@ TALER_TESTING_cmd_wire_del (const char *label,                              unsigned int expected_http_status,                              bool bad_sig); +/** + * Sign all extensions that the exchange has to offer, f. e. the extension for + * age restriction.  This has to be run before any withdrawal of age restricted + * can be performed. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label, +                                                const char *config_filename); +  /**   * Sign all exchange denomination and online signing keys @@ -2422,10 +2441,10 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,   */  #define TALER_TESTING_SIMPLE_TRAITS(op) \    op (bank_row, const uint64_t)                                    \ -  op (reserve_priv, const struct TALER_ReservePrivateKeyP) \ -  op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \ -  op (refresh_secret, const struct TALER_RefreshMasterSecretP) \ -  op (reserve_pub, const struct TALER_ReservePublicKeyP)         \ +  op (reserve_priv, const struct TALER_ReservePrivateKeyP)         \ +  op (planchet_secret, const struct TALER_PlanchetMasterSecretP)   \ +  op (refresh_secret, const struct TALER_RefreshMasterSecretP)     \ +  op (reserve_pub, const struct TALER_ReservePublicKeyP)           \    op (merchant_priv, const struct TALER_MerchantPrivateKeyP)       \    op (merchant_pub, const struct TALER_MerchantPublicKeyP)         \    op (merchant_sig, const struct TALER_MerchantSignatureP)         \ @@ -2438,8 +2457,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,    op (exchange_bank_account_url, const char *)                     \    op (taler_uri, const char *)                                     \    op (payto_uri, const char *)                                     \ -  op (kyc_url, const char *)                                     \ -  op (web_url, const char *)                                     \ +  op (kyc_url, const char *)                                       \ +  op (web_url, const char *)                                       \    op (row, const uint64_t)                                         \    op (payment_target_uuid, const uint64_t)                         \    op (array_length, const unsigned int)                            \ @@ -2464,7 +2483,9 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,  #define TALER_TESTING_INDEXED_TRAITS(op)                               \    op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey)           \    op (denom_sig, const struct TALER_DenominationSignature)             \ -  op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)           \ +  op (age_commitment, struct TALER_AgeCommitment)                      \ +  op (h_age_commitment, struct TALER_AgeCommitmentHash)                \ +  op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)      \    op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues)    \    op (coin_priv, const struct TALER_CoinSpendPrivateKeyP)              \    op (coin_pub, const struct TALER_CoinSpendPublicKeyP)                \ diff --git a/src/json/json_helper.c b/src/json/json_helper.c index 96e41b5e..4ec9a698 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -950,35 +950,4 @@ TALER_JSON_spec_i18n_str (const char *name,  } -enum GNUNET_GenericReturnValue -TALER_JSON_parse_agemask (const json_t *root, -                          struct TALER_AgeMask *mask) -{ -  const char *name; -  struct GNUNET_JSON_Specification spec[] = { -    GNUNET_JSON_spec_string ("extension", -                             &name), -    GNUNET_JSON_spec_uint32 ("mask", -                             &mask->mask), -    GNUNET_JSON_spec_end () -  }; - -  if (GNUNET_OK != GNUNET_JSON_parse (root, -                                      spec, -                                      NULL, -                                      NULL)) -  { -    return GNUNET_SYSERR; -  } - -  if (! strncmp (name, -                 "age_restriction", -                 sizeof("age_restriction"))) -  { -    return GNUNET_SYSERR; -  } -  return GNUNET_OK; -} - -  /* end of json/json_helper.c */ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 53a75a93..d0340924 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -477,6 +477,7 @@ TALER_EXCHANGE_verify_coin_history (        struct TALER_MerchantPublicKeyP merchant_pub;        struct GNUNET_TIME_Timestamp refund_deadline = {0};        struct TALER_CoinSpendSignatureP sig; +      struct TALER_AgeCommitmentHash *hac = NULL;        struct GNUNET_JSON_Specification spec[] = {          GNUNET_JSON_spec_fixed_auto ("coin_sig",                                       &sig), @@ -511,6 +512,7 @@ TALER_EXCHANGE_verify_coin_history (                                         &fee,                                         &h_wire,                                         &h_contract_terms, +                                       hac,                                         NULL /* h_extensions! */,                                         h_denom_pub,                                         wallet_timestamp, @@ -543,6 +545,7 @@ TALER_EXCHANGE_verify_coin_history (      {        struct TALER_CoinSpendSignatureP sig;        struct TALER_RefreshCommitmentP rc; +      struct TALER_AgeCommitmentHash h_age_commitment = {0};        struct GNUNET_JSON_Specification spec[] = {          GNUNET_JSON_spec_fixed_auto ("coin_sig",                                       &sig), @@ -550,6 +553,9 @@ TALER_EXCHANGE_verify_coin_history (                                       &rc),          GNUNET_JSON_spec_fixed_auto ("h_denom_pub",                                       h_denom_pub), +        GNUNET_JSON_spec_mark_optional ( +          GNUNET_JSON_spec_fixed_auto ("h_age_commitment", +                                       &h_age_commitment)),          TALER_JSON_spec_amount_any ("melt_fee",                                      &fee),          GNUNET_JSON_spec_end () @@ -563,6 +569,7 @@ TALER_EXCHANGE_verify_coin_history (          GNUNET_break_op (0);          return GNUNET_SYSERR;        } +        if (NULL != dk)        {          /* check that melt fee matches our expectations from /keys! */ @@ -577,16 +584,25 @@ TALER_EXCHANGE_verify_coin_history (            return GNUNET_SYSERR;          }        } -      if (GNUNET_OK != -          TALER_wallet_melt_verify (&amount, -                                    &fee, -                                    &rc, -                                    h_denom_pub, -                                    coin_pub, -                                    &sig)) +        { -        GNUNET_break_op (0); -        return GNUNET_SYSERR; +        const struct TALER_AgeCommitmentHash *ahc = &h_age_commitment; + +        if (TALER_AgeCommitmentHash_isNullOrZero (ahc)) +          ahc = NULL; + +        if (GNUNET_OK != +            TALER_wallet_melt_verify (&amount, +                                      &fee, +                                      &rc, +                                      h_denom_pub, +                                      ahc, +                                      coin_pub, +                                      &sig)) +        { +          GNUNET_break_op (0); +          return GNUNET_SYSERR; +        }        }        add = GNUNET_YES;      } diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index 7ff59651..2bfaaf6c 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -463,6 +463,7 @@ handle_deposit_finished (void *cls,   * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)   * @param ech hash over contract extensions   * @param coin_pub coin’s public key + * @param h_age_commitment coin’s hash of age commitment, might be NULL   * @param denom_sig exchange’s unblinded signature of the coin   * @param denom_pub denomination key with which the coin is signed   * @param denom_pub_hash hash of @a denom_pub @@ -479,6 +480,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,                     const struct TALER_PrivateContractHash *h_contract_terms,                     const struct TALER_ExtensionContractHash *ech,                     const struct TALER_CoinSpendPublicKeyP *coin_pub, +                   const struct TALER_AgeCommitmentHash *h_age_commitment,                     const struct TALER_DenominationSignature *denom_sig,                     const struct TALER_DenominationPublicKey *denom_pub,                     const struct TALER_DenominationHash *denom_pub_hash, @@ -492,6 +494,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,                                     &dki->fee_deposit,                                     h_wire,                                     h_contract_terms, +                                   h_age_commitment,                                     ech,                                     denom_pub_hash,                                     timestamp, @@ -515,8 +518,12 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,        .coin_pub = *coin_pub,        .denom_pub_hash = *denom_pub_hash,        .denom_sig = *denom_sig, -      .age_commitment_hash = {{{0}}} /* FIXME-Oec */ +      .age_commitment_hash = {{{0}}}      }; +    if (NULL != h_age_commitment) +    { +      coin_info.age_commitment_hash = *h_age_commitment; +    }      if (GNUNET_YES !=          TALER_test_coin_valid (&coin_info, @@ -548,6 +555,7 @@ TALER_EXCHANGE_deposit (    const char *merchant_payto_uri,    const struct TALER_WireSaltP *wire_salt,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const json_t *extension_details,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_DenominationSignature *denom_sig, @@ -600,11 +608,14 @@ TALER_EXCHANGE_deposit (    }    GNUNET_assert (GNUNET_YES ==                   TEAH_handle_is_ready (exchange)); +    /* initialize h_wire */    TALER_merchant_wire_signature_hash (merchant_payto_uri,                                        wire_salt,                                        &h_wire); +    key_state = TALER_EXCHANGE_get_keys (exchange); +    dki = TALER_EXCHANGE_get_denomination_key (key_state,                                               denom_pub);    if (NULL == dki) @@ -613,6 +624,7 @@ TALER_EXCHANGE_deposit (      GNUNET_break_op (0);      return NULL;    } +    if (0 >        TALER_amount_subtract (&amount_without_fee,                               amount, @@ -622,17 +634,18 @@ TALER_EXCHANGE_deposit (      GNUNET_break_op (0);      return NULL;    } +    TALER_denom_pub_hash (denom_pub,                          &denom_pub_hash); +    if (GNUNET_OK !=        verify_signatures (dki,                           amount,                           &h_wire,                           h_contract_terms, -                         (NULL != extension_details) -                         ? &ech -                         : NULL, +                         (NULL != extension_details) ? &ech : NULL,                           coin_pub, +                         h_age_commitment,                           denom_sig,                           denom_pub,                           &denom_pub_hash, @@ -655,6 +668,9 @@ TALER_EXCHANGE_deposit (                                  wire_salt),      GNUNET_JSON_pack_data_auto ("h_contract_terms",                                  h_contract_terms), +    GNUNET_JSON_pack_allow_null ( +      GNUNET_JSON_pack_data_auto ("h_age_commitment", +                                  h_age_commitment)),      GNUNET_JSON_pack_data_auto ("denom_pub_hash",                                  &denom_pub_hash),      TALER_JSON_pack_denom_sig ("ub_sig", diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index cf3d69d6..3243f5e9 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -667,7 +667,9 @@ decode_keys_json (const json_t *resp_obj,                    enum TALER_EXCHANGE_VersionCompatibility *vc)  {    struct TALER_ExchangeSignatureP sig; -  struct GNUNET_HashContext *hash_context; +  struct GNUNET_HashContext *hash_context = NULL; +  struct GNUNET_HashContext *hash_context_restricted = NULL; +  bool have_age_restricted_denom = false;    struct TALER_ExchangePublicKeyP pub;    const char *currency;    struct GNUNET_JSON_Specification mspec[] = { @@ -746,7 +748,6 @@ decode_keys_json (const json_t *resp_obj,      key_data->version = GNUNET_strdup (ver);    } -  hash_context = NULL;    EXITIF (GNUNET_OK !=            GNUNET_JSON_parse (resp_obj,                               (check_sig) ? mspec : &mspec[2], @@ -766,7 +767,10 @@ decode_keys_json (const json_t *resp_obj,    /* parse the master public key and issue date of the response */    if (check_sig) +  {      hash_context = GNUNET_CRYPTO_hash_context_start (); +    hash_context_restricted = GNUNET_CRYPTO_hash_context_start (); +  }    /* parse the signing keys */    { @@ -829,6 +833,9 @@ decode_keys_json (const json_t *resp_obj,        EXITIF (GNUNET_OK !=                TALER_extensions_load_json_config (extensions));      } + +    /* 4. assuming we might have now a new value for age_mask, set it in key_data */ +    key_data->age_mask = TALER_extensions_age_restriction_ageMask ();    }    /* parse the denomination keys, merging with the @@ -839,9 +846,15 @@ decode_keys_json (const json_t *resp_obj,       */      struct      { char *name; -      bool is_optional_age_restriction;} hive[2] = { -      { "denoms",                false }, -      { "age_restricted_denoms", true  }, +      struct GNUNET_HashContext *hc; +      bool is_optional_age_restriction;} +    hive[2] = { +      { "denoms", +        hash_context, +        false }, +      { "age_restricted_denoms", +        hash_context_restricted, +        true  }      };      for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++) @@ -853,25 +866,19 @@ decode_keys_json (const json_t *resp_obj,        denom_keys_array = json_object_get (resp_obj,                                            hive[s].name); -      EXITIF (NULL == denom_keys_array && -              ! hive[s].is_optional_age_restriction); - -      if (NULL == denom_keys_array && -          hive[s].is_optional_age_restriction) +      if (NULL == denom_keys_array)          continue; -      /* if "age_restricted_denoms" exists, age-restriction better be enabled -       * (that is: mask non-zero) */ -      EXITIF (NULL != denom_keys_array && -              hive[s].is_optional_age_restriction && -              0 == key_data->age_mask.mask); -        EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));        json_array_foreach (denom_keys_array, index, denom_key_obj) {          struct TALER_EXCHANGE_DenomPublicKey dk;          bool found = false; +        /* mark that we have at least one age restricted denomination, needed +         * for the hash calculation and signature verification below. */ +        have_age_restricted_denom |= hive[s].is_optional_age_restriction; +          memset (&dk,                  0,                  sizeof (dk)); @@ -880,12 +887,7 @@ decode_keys_json (const json_t *resp_obj,                                       check_sig,                                       denom_key_obj,                                       &key_data->master_pub, -                                     hash_context)); - -        /* Mark age restriction according where we got this denomination from, -         * "denoms" or "age_restricted_denoms" */ -        if (hive[s].is_optional_age_restriction) -          dk.age_restricted = true; +                                     hive[s].hc));          for (unsigned int j = 0;               j<key_data->num_denom_keys; @@ -1044,6 +1046,18 @@ decode_keys_json (const json_t *resp_obj,        .list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date)      }; +    /* If we had any age restricted denominations, add their hash to the end of +     * the normal denominations. */ +    if (have_age_restricted_denom) +    { +      struct GNUNET_HashCode hcr; +      GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, +                                         &hcr); +      GNUNET_CRYPTO_hash_context_read (hash_context, +                                       &hcr, +                                       sizeof(struct GNUNET_HashCode)); +    } +      GNUNET_CRYPTO_hash_context_finish (hash_context,                                         &ks.hc);      hash_context = NULL; diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c index a44ccdce..10ddd471 100644 --- a/src/lib/exchange_api_link.c +++ b/src/lib/exchange_api_link.c @@ -113,6 +113,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,    struct TALER_TransferSecretP secret;    struct TALER_PlanchetDetail pd;    struct TALER_CoinPubHash c_hash; +  struct TALER_AgeCommitmentHash h_age_commitment = {0}; // TODO, see below.    /* parse reply */    memset (&nonce, @@ -143,6 +144,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,                                &alg_values,                                &bks,                                &lci->coin_priv, +                              NULL, /* FIXME-oec. struct TALER_AgeCommitmentHash */                                &c_hash,                                &pd))    { @@ -179,6 +181,15 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,      GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,                                          &old_coin_pub.eddsa_pub); +    /* +     * TODO-oec: Derive the age commitment vector and hash it into +     * h_age_commitment. +     * Questions: +     *   - Where do we get the information about the support for age +     *     restriction of the denomination? +     *   - Where do we get the information bout the previous coin's age groups? +     */ +      TALER_coin_ev_hash (&pd.blinded_planchet,                          &pd.denom_pub_hash,                          &coin_envelope_hash); @@ -187,6 +198,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,                                    trans_pub,                                    &coin_envelope_hash,                                    &old_coin_pub, +                                  &h_age_commitment,                                    &link_sig))      {        GNUNET_break_op (0); diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c index 4d686633..ac419388 100644 --- a/src/lib/exchange_api_management_get_keys.c +++ b/src/lib/exchange_api_management_get_keys.c @@ -32,7 +32,7 @@  /**   * Set to 1 for extra debug logging.   */ -#define DEBUG 0 +#define DEBUG 1  /* FIXME-oec */  /** diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c index c0ab143f..87b0e0be 100644 --- a/src/lib/exchange_api_management_post_extensions.c +++ b/src/lib/exchange_api_management_post_extensions.c @@ -151,7 +151,7 @@ TALER_EXCHANGE_management_post_extensions (    body = GNUNET_JSON_PACK (      GNUNET_JSON_pack_object_steal ("extensions",                                     ped->extensions), -    GNUNET_JSON_pack_data_auto ("extensions_sigs", +    GNUNET_JSON_pack_data_auto ("extensions_sig",                                  &ped->extensions_sig));    eh = curl_easy_init (); @@ -168,7 +168,7 @@ TALER_EXCHANGE_management_post_extensions (      return NULL;    }    json_decref (body); -  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +  GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Requesting URL '%s'\n",                ph->url);    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c index e944b79a..89ee1e17 100644 --- a/src/lib/exchange_api_refresh_common.c +++ b/src/lib/exchange_api_refresh_common.c @@ -175,6 +175,7 @@ TALER_EXCHANGE_get_melt_data_ (                                    &alg_values[j],                                    bks,                                    coin_priv, +                                  NULL, /* FIXME-oec: This needs to be setup !*/                                    &c_hash,                                    &pd))        { diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h index ab19ad7d..b6926b51 100644 --- a/src/lib/exchange_api_refresh_common.h +++ b/src/lib/exchange_api_refresh_common.h @@ -53,6 +53,12 @@ struct MeltedCoin    struct TALER_Amount original_value;    /** +   * The original age commitment hash.  MUST be all zeroes, if no age +   * commitment was set. +   */ +  struct TALER_AgeCommitmentHash h_age_commitment; + +  /**     * Timestamp indicating when coins of this denomination become invalid.     */    struct GNUNET_TIME_Timestamp expire_deposit; @@ -93,6 +99,13 @@ struct FreshCoinData    struct TALER_CoinSpendPrivateKeyP coin_priv;    /** +   * Arrays age commitments to be created, one for each cut-and-choose +   * dimension.  The entries in each list might be NULL and indicate no age +   * commitment/restriction on the particular coin. +   */ +  struct TALER_AgeCommitment *age_commitment[TALER_CNC_KAPPA]; + +  /**     * Blinding key secrets for the coins, depending on the     * cut-and-choose.     */ diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c index 08357c14..8d04c279 100644 --- a/src/lib/exchange_api_refreshes_reveal.c +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -142,6 +142,7 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,        &rcis[i];      const struct FreshCoinData *fcd = &rrh->md.fcds[i];      const struct TALER_DenominationPublicKey *pk; +    struct TALER_AgeCommitmentHash *ach = NULL;      json_t *jsonai;      struct TALER_BlindedDenominationSignature blind_sig;      struct TALER_CoinSpendPublicKeyP coin_pub; @@ -160,6 +161,12 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,      jsonai = json_array_get (jsona, i);      GNUNET_assert (NULL != jsonai); +    if (! TALER_AgeCommitmentHash_isNullOrZero ( +          &rrh->md.melted_coin.h_age_commitment)) +    { +      /* FIXME-oec:  need to pull fresh_ach from somewhere */ +    } +      if (GNUNET_OK !=          GNUNET_JSON_parse (jsonai,                             spec, @@ -180,15 +187,15 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,         hence recomputing it here... */      GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,                                          &coin_pub.eddsa_pub); -    /* FIXME-Oec: Age commitment hash. */      TALER_coin_pub_hash (&coin_pub, -                         NULL, /* FIXME-Oec */ +                         ach,                           &coin_hash);      if (GNUNET_OK !=          TALER_planchet_to_coin (pk,                                  &blind_sig,                                  &bks,                                  &rci->coin_priv, +                                ach,                                  &coin_hash,                                  &rrh->alg_values[i],                                  &coin)) diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index 94909470..a9510715 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -203,6 +203,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,        struct TALER_Amount deposit_fee;        struct TALER_MerchantWireHash h_wire;        struct TALER_PrivateContractHash h_contract_terms; +      struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};        // struct TALER_ExtensionContractHash h_extensions; // FIXME!        struct TALER_DenominationHash h_denom_pub;        struct GNUNET_TIME_Timestamp wallet_timestamp; @@ -218,6 +219,9 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,                                       &h_wire),          GNUNET_JSON_spec_fixed_auto ("h_denom_pub",                                       &h_denom_pub), +        GNUNET_JSON_spec_mark_optional ( +          GNUNET_JSON_spec_fixed_auto ("h_age_commitment", +                                       &h_age_commitment)),          GNUNET_JSON_spec_timestamp ("timestamp",                                      &wallet_timestamp),          GNUNET_JSON_spec_timestamp ("refund_deadline", @@ -243,6 +247,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,                                         &deposit_fee,                                         &h_wire,                                         &h_contract_terms, +                                       &h_age_commitment,                                         NULL /* h_extensions! */,                                         &h_denom_pub,                                         wallet_timestamp, diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index c832699a..efc8a99c 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -89,6 +89,11 @@ struct TALER_EXCHANGE_WithdrawHandle    struct TALER_ExchangeWithdrawValues alg_values;    /** +   * Hash of the age commitment for this coin, if applicable. Maybe NULL +   */ +  const struct TALER_AgeCommitmentHash *ach; + +  /**     * Denomination key we are withdrawing.     */    struct TALER_EXCHANGE_DenomPublicKey pk; @@ -137,6 +142,7 @@ handle_reserve_withdraw_finished (                                    blind_sig,                                    &wh->bks,                                    &wh->priv, +                                  wh->ach,                                    &wh->c_hash,                                    &wh->alg_values,                                    &fc)) @@ -222,6 +228,7 @@ withdraw_cs_stage_two_callback (void *cls,                                  &wh->alg_values,                                  &wh->bks,                                  &wh->priv, +                                wh->ach,                                  &wh->c_hash,                                  &wh->pd))      { @@ -249,6 +256,7 @@ TALER_EXCHANGE_withdraw (    const struct TALER_EXCHANGE_DenomPublicKey *pk,    const struct TALER_ReservePrivateKeyP *reserve_priv,    const struct TALER_PlanchetMasterSecretP *ps, +  const struct TALER_AgeCommitmentHash *ach,    TALER_EXCHANGE_WithdrawCallback res_cb,    void *res_cb_cls)  { @@ -260,6 +268,7 @@ TALER_EXCHANGE_withdraw (    wh->cb_cls = res_cb_cls;    wh->reserve_priv = reserve_priv;    wh->ps = *ps; +  wh->ach = ach;    wh->pk = *pk;    TALER_denom_pub_deep_copy (&wh->pk.key,                               &pk->key); @@ -280,6 +289,7 @@ TALER_EXCHANGE_withdraw (                                    &wh->alg_values,                                    &wh->bks,                                    &wh->priv, +                                  wh->ach,                                    &wh->c_hash,                                    &wh->pd))        { diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index a6b58270..39cc6cbe 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -68,6 +68,7 @@ libtalertesting_la_SOURCES = \    testing_api_cmd_oauth.c \    testing_api_cmd_offline_sign_fees.c \    testing_api_cmd_offline_sign_keys.c \ +  testing_api_cmd_offline_sign_extensions.c \    testing_api_cmd_set_wire_fee.c \    testing_api_cmd_recoup.c \    testing_api_cmd_recoup_refresh.c \ @@ -249,6 +250,7 @@ test_exchange_api_cs_LDADD = \    -lgnunetcurl \    -lgnunetutil \    -ljansson \ +  -ltalerextensions \    $(XLIB)  test_exchange_api_rsa_SOURCES = \ @@ -265,6 +267,7 @@ test_exchange_api_rsa_LDADD = \    -lgnunetcurl \    -lgnunetutil \    -ljansson \ +  -ltalerextensions \    $(XLIB)  test_exchange_api_keys_cherry_picking_cs_SOURCES = \ diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c index 38b1b1ab..9ab78664 100644 --- a/src/testing/test_auditor_api.c +++ b/src/testing/test_auditor_api.c @@ -128,6 +128,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_end ()    }; @@ -168,6 +169,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",                                         "refresh-create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in @@ -315,6 +317,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",                                         "create-reserve-unaggregated",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("deposit-unaggregated",                                 "withdraw-coin-unaggregated", @@ -347,6 +350,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",                                         "create-reserve-r1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * Spend 5 EUR of the 5 EUR coin (in full). Merchant would @@ -402,6 +406,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",                                         "recoup-create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_revoke ("revoke-1",                                MHD_HTTP_OK, @@ -417,6 +422,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",                                         "recoup-create-reserve-1",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * These commands should close the reserve because the aggregator @@ -447,6 +453,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",                                         "recoup-create-reserve-2",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * Withdraw a 1 EUR coin, at fee of 1 ct @@ -454,6 +461,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",                                         "recoup-create-reserve-2",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("recoup-deposit-partial",                                 "recoup-withdraw-coin-2a", @@ -491,42 +499,52 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10",                                         "massive-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit (        "massive-deposit-1", @@ -719,7 +737,7 @@ main (int argc,      GNUNET_break (0);      return 1;    case GNUNET_NO: -    return 77; +    return 78;    case GNUNET_OK:      if (GNUNET_OK !=          /* Set up event loop and reschedule context, plus @@ -729,11 +747,11 @@ main (int argc,          TALER_TESTING_auditor_setup (&run,                                       NULL,                                       config_file)) -      return 1; +      return 2;      break;    default:      GNUNET_break (0); -    return 1; +    return 3;    }    return 0;  } diff --git a/src/testing/test_exchange_api-cs.conf b/src/testing/test_exchange_api-cs.conf index 3fbf4c3c..79332d64 100644 --- a/src/testing/test_exchange_api-cs.conf +++ b/src/testing/test_exchange_api-cs.conf @@ -149,7 +149,7 @@ fee_withdraw = EUR:0.00  fee_deposit = EUR:0.00  fee_refresh = EUR:0.01  fee_refund = EUR:0.01 -age_restricted = true +age_restricted = YES  CIPHER = CS  [coin_eur_ct_10_age_restricted] @@ -161,7 +161,7 @@ fee_withdraw = EUR:0.01  fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01 -age_restricted = true +age_restricted = YES  CIPHER = CS  [coin_eur_1_age_restricted] @@ -173,7 +173,7 @@ fee_withdraw = EUR:0.01  fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01 -age_restricted = true +age_restricted = YES  CIPHER = CS  [coin_eur_5_age_restricted] @@ -185,7 +185,7 @@ fee_withdraw = EUR:0.01  fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01 -age_restricted = true +age_restricted = YES  CIPHER = CS  [coin_eur_10_age_restricted] @@ -197,5 +197,5 @@ fee_withdraw = EUR:0.01  fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01 -age_restricted = true +age_restricted = YES  CIPHER = CS diff --git a/src/testing/test_exchange_api-rsa.conf b/src/testing/test_exchange_api-rsa.conf index cffe3b87..1d445662 100644 --- a/src/testing/test_exchange_api-rsa.conf +++ b/src/testing/test_exchange_api-rsa.conf @@ -155,7 +155,7 @@ fee_deposit = EUR:0.00  fee_refresh = EUR:0.01  fee_refund = EUR:0.01  rsa_keysize = 1024 -age_restricted = true +age_restricted = YES  CIPHER = RSA  [coin_eur_ct_10_age_restricted] @@ -168,7 +168,7 @@ fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01  rsa_keysize = 1024 -age_restricted = true +age_restricted = YES  CIPHER = RSA  [coin_eur_1_age_restricted] @@ -181,7 +181,7 @@ fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01  rsa_keysize = 1024 -age_restricted = true +age_restricted = YES  CIPHER = RSA  [coin_eur_5_age_restricted] @@ -194,7 +194,7 @@ fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01  rsa_keysize = 1024 -age_restricted = true +age_restricted = YES  CIPHER = RSA  [coin_eur_10_age_restricted] @@ -207,5 +207,5 @@ fee_deposit = EUR:0.01  fee_refresh = EUR:0.03  fee_refund = EUR:0.01  rsa_keysize = 1024 -age_restricted = true +age_restricted = YES  CIPHER = RSA diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index b1779a7d..957e42e8 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -34,6 +34,7 @@  #include "taler_bank_service.h"  #include "taler_fakebank_lib.h"  #include "taler_testing_lib.h" +#include "taler_extensions.h"  /**   * Configuration file we use.  One (big) configuration is used @@ -149,6 +150,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * Withdraw EUR:1 using the SAME private coin key as for the previous coin @@ -162,6 +164,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",                                                   "create-reserve-1",                                                   "EUR:1", +                                                 0, /* age restriction off */                                                   "withdraw-coin-1",                                                   MHD_HTTP_OK),      /** @@ -177,6 +180,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_CONFLICT),      TALER_TESTING_cmd_end ()    }; @@ -282,6 +286,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",                                         "refresh-create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin       * (in full) (merchant would receive EUR:0.99 due to 1 ct @@ -358,6 +363,61 @@ run (void *cls,      TALER_TESTING_cmd_end ()    }; +  /** +   * Test withdrawal with age restriction.  Success is expected, so it MUST be +   * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called, +   * i. e. age restriction is activated in the exchange! +   * +   * TODO: create a test that tries to withdraw coins with age restriction but +   * (expectedly) fails because the exchange doesn't support age restriction +   * yet. +   */ +  struct TALER_TESTING_Command withdraw_age[] = { +    /** +     * Move money to the exchange's bank account. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-age"), +    /** +     * Make a reserve exist, according to the previous +     * transfer. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-age"), +    /** +     * Withdraw EUR:5. +     */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1", +                                       "create-reserve-age", +                                       "EUR:5", +                                       13, +                                       MHD_HTTP_OK), + +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command spend_age[] = { +    /** +     * Spend the coin. +     */ +    TALER_TESTING_cmd_deposit ("deposit-simple-age", +                               "withdraw-coin-age-1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:4.99", +                               MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age", +                                      "deposit-simple-age", +                                      MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; +    struct TALER_TESTING_Command track[] = {      /* Try resolving a deposit's WTID, as we never triggered       * execution of transactions, the answer should be that @@ -400,6 +460,11 @@ run (void *cls,                                             "EUR:4.98",                                             bc.exchange_payto,                                             bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2", +                                           ec.exchange_url, +                                           "EUR:4.97", +                                           bc.exchange_payto, +                                           bc.user42_payto),      TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",                                             ec.exchange_url,                                             "EUR:0.98", @@ -463,6 +528,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",                                         "create-reserve-unaggregated",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("deposit-unaggregated",                                 "withdraw-coin-unaggregated", @@ -501,6 +567,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",                                         "create-reserve-aggtest",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("deposit-aggtest-1",                                 "withdraw-coin-aggtest", @@ -549,6 +616,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",                                         "create-reserve-r1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * Spend 5 EUR of the 5 EUR coin (in full) (merchant would @@ -649,6 +717,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",                                         "create-reserve-rb",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("deposit-refund-1b",                                 "withdraw-coin-rb", @@ -698,11 +767,13 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",                                         "recoup-create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* Withdraw a 10 EUR coin, at fee of 1 ct */      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b",                                         "recoup-create-reserve-1",                                         "EUR:10", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* melt 10 EUR coin to get 5 EUR refreshed coin */      TALER_TESTING_cmd_melt ("recoup-melt-coin-1b", @@ -793,6 +864,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",                                         "recoup-create-reserve-1",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /**       * This withdrawal will test the logic to create a "recoup" @@ -801,6 +873,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over",                                         "recoup-create-reserve-1",                                         "EUR:10", +                                       0, /* age restriction off */                                         MHD_HTTP_CONFLICT),      TALER_TESTING_cmd_status ("recoup-reserve-status-2",                                "recoup-create-reserve-1", @@ -833,6 +906,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("expired-withdraw",                                         "short-lived-reserve",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_CONFLICT),      TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",                                             ec.exchange_url, @@ -857,11 +931,13 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",                                         "recoup-create-reserve-2",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* Withdraw a 1 EUR coin, at fee of 1 ct */      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",                                         "recoup-create-reserve-2",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_deposit ("recoup-deposit-partial",                                 "recoup-withdraw-coin-2a", @@ -924,6 +1000,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",                                         "recoup-create-reserve-3",                                         "EUR:1", +                                       0, /* age restriction off */                                         MHD_HTTP_GONE),      /* check that we are empty before the rejection test */      TALER_TESTING_cmd_check_bank_empty ("check-empty-again"), @@ -970,6 +1047,8 @@ run (void *cls,        TALER_TESTING_cmd_auditor_add ("add-auditor-OK",                                       MHD_HTTP_NO_CONTENT,                                       false), +      TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions", +                                                      config_file),        TALER_TESTING_cmd_wire_add ("add-wire-account",                                    "payto://x-taler-bank/localhost/2",                                    MHD_HTTP_NO_CONTENT, @@ -990,6 +1069,10 @@ run (void *cls,                                 spend),        TALER_TESTING_cmd_batch ("refresh",                                 refresh), +      TALER_TESTING_cmd_batch ("withdraw-age", +                               withdraw_age), +      TALER_TESTING_cmd_batch ("spend-age", +                               spend_age),        TALER_TESTING_cmd_batch ("track",                                 track),        TALER_TESTING_cmd_batch ("unaggregation", @@ -1026,6 +1109,9 @@ main (int argc,    GNUNET_log_setup (argv[0],                      "INFO",                      NULL); + +  TALER_extensions_init (); +    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);    GNUNET_assert (NULL != cipher);    uses_cs = (0 == strcmp (cipher, "cs")); @@ -1036,6 +1122,7 @@ main (int argc,                     "test_exchange_api_expire_reserve_now-%s.conf",                     cipher);    GNUNET_free (cipher); +    /* Check fakebank port is available and get config */    if (GNUNET_OK !=        TALER_TESTING_prepare_fakebank (config_file, @@ -1054,7 +1141,7 @@ main (int argc,      GNUNET_break (0);      return 1;    case GNUNET_NO: -    return 77; +    return 78;    case GNUNET_OK:      if (GNUNET_OK !=          /* Set up event loop and reschedule context, plus @@ -1064,11 +1151,11 @@ main (int argc,          TALER_TESTING_setup_with_exchange (&run,                                             NULL,                                             config_file)) -      return 1; +      return 2;      break;    default:      GNUNET_break (0); -    return 1; +    return 3;    }    return 0;  } diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c index beb94dba..bb3dcc06 100644 --- a/src/testing/test_exchange_api_revocation.c +++ b/src/testing/test_exchange_api_revocation.c @@ -96,11 +96,13 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* Withdraw another 5 EUR coin, at fee of 1 ct */      TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-2",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full)       * (merchant would receive EUR:0.99 due to 1 ct deposit fee) */// diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c index b9e9a9f8..ca87edd8 100644 --- a/src/testing/test_kyc_api.c +++ b/src/testing/test_kyc_api.c @@ -105,10 +105,12 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc",                                         "create-reserve-1",                                         "EUR:10", +                                       0, /* age restriction off */                                         MHD_HTTP_ACCEPTED),      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_end ()    }; @@ -120,6 +122,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_ACCEPTED),      TALER_TESTING_cmd_proof_kyc ("proof-kyc",                                   "withdraw-coin-1-lacking-kyc", @@ -129,6 +132,7 @@ run (void *cls,      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",                                         "create-reserve-1",                                         "EUR:5", +                                       0, /* age restriction off */                                         MHD_HTTP_OK),      TALER_TESTING_cmd_end ()    }; diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c index b2fd7ddf..d3fafc63 100644 --- a/src/testing/testing_api_cmd_deposit.c +++ b/src/testing/testing_api_cmd_deposit.c @@ -287,6 +287,8 @@ deposit_run (void *cls,    const struct TALER_TESTING_Command *coin_cmd;    const struct TALER_CoinSpendPrivateKeyP *coin_priv;    struct TALER_CoinSpendPublicKeyP coin_pub; +  struct TALER_AgeCommitment *age_commitment = NULL; +  struct TALER_AgeCommitmentHash h_age_commitment = {0};    const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;    const struct TALER_DenominationSignature *denom_pub_sig;    struct TALER_CoinSpendSignatureP coin_sig; @@ -383,6 +385,10 @@ deposit_run (void *cls,                                             ds->coin_index,                                             &coin_priv)) ||         (GNUNET_OK != +        TALER_TESTING_get_trait_age_commitment (coin_cmd, +                                                ds->coin_index, +                                                &age_commitment)) || +       (GNUNET_OK !=          TALER_TESTING_get_trait_denom_pub (coin_cmd,                                             ds->coin_index,                                             &denom_pub)) || @@ -398,6 +404,12 @@ deposit_run (void *cls,      TALER_TESTING_interpreter_fail (is);      return;    } + +  if (NULL != age_commitment) +  { +    TALER_age_commitment_hash (age_commitment, &h_age_commitment); +  } +    ds->deposit_fee = denom_pub->fee_deposit;    GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,                                        &coin_pub.eddsa_pub); @@ -431,7 +443,8 @@ deposit_run (void *cls,                                 &denom_pub->fee_deposit,                                 &h_wire,                                 &h_contract_terms, -                               NULL, /* FIXME: extension hash! */ +                               &h_age_commitment, +                               NULL, /* FIXME: add hash of extensions */                                 &denom_pub->h_key,                                 ds->wallet_timestamp,                                 &merchant_pub, @@ -445,7 +458,8 @@ deposit_run (void *cls,                                     payto_uri,                                     &wire_salt,                                     &h_contract_terms, -                                   NULL, /* FIXME: extension object */ +                                   &h_age_commitment, +                                   NULL, /* FIXME: add hash of extensions */                                     &coin_pub,                                     denom_pub_sig,                                     &denom_pub->key, @@ -520,6 +534,7 @@ deposit_traits (void *cls,    const struct TALER_TESTING_Command *coin_cmd;    /* Will point to coin cmd internals. */    const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; +  struct TALER_AgeCommitment *age_commitment;    if (GNUNET_YES != ds->command_initialized)    { @@ -540,7 +555,11 @@ deposit_traits (void *cls,    if (GNUNET_OK !=        TALER_TESTING_get_trait_coin_priv (coin_cmd,                                           ds->coin_index, -                                         &coin_spent_priv)) +                                         &coin_spent_priv) || +      (GNUNET_OK != +       TALER_TESTING_get_trait_age_commitment (coin_cmd, +                                               ds->coin_index, +                                               &age_commitment)))    {      GNUNET_break (0);      TALER_TESTING_interpreter_fail (ds->is); @@ -555,6 +574,8 @@ deposit_traits (void *cls,        /* These traits are always available */        TALER_TESTING_make_trait_coin_priv (0,                                            coin_spent_priv), +      TALER_TESTING_make_trait_age_commitment (0, +                                               age_commitment),        TALER_TESTING_make_trait_wire_details (ds->wire_details),        TALER_TESTING_make_trait_contract_terms (ds->contract_terms),        TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv), diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c index be49df94..dcda7cf3 100644 --- a/src/testing/testing_api_cmd_insert_deposit.c +++ b/src/testing/testing_api_cmd_insert_deposit.c @@ -244,7 +244,7 @@ insert_deposit_run (void *cls,    {      uint64_t known_coin_id;      struct TALER_DenominationHash dph; -    struct TALER_AgeHash agh; +    struct TALER_AgeCommitmentHash agh;      if ( (GNUNET_OK !=            ids->dbc->plugin->start (ids->dbc->plugin->cls, diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c b/src/testing/testing_api_cmd_offline_sign_extensions.c new file mode 100644 index 00000000..f39679f9 --- /dev/null +++ b/src/testing/testing_api_cmd_offline_sign_extensions.c @@ -0,0 +1,164 @@ +/* +  This file is part of TALER +  Copyright (C) 2022 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, +  see <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_cmd_offline_sign_extensions.c + * @brief run the taler-exchange-offline command to sign extensions (and therefore activate them) + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "extensionssign" CMD. + */ +struct ExtensionsSignState +{ + +  /** +   * Process for the "extensionssign" command. +   */ +  struct GNUNET_OS_Process *extensionssign_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; + +}; + + +/** + * Run the command; calls the `taler-exchange-offline' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +extensionssign_run (void *cls, +                    const struct TALER_TESTING_Command *cmd, +                    struct TALER_TESTING_Interpreter *is) +{ +  struct ExtensionsSignState *ks = cls; + +  ks->extensionssign_proc +    = GNUNET_OS_start_process ( +        GNUNET_OS_INHERIT_STD_ALL, +        NULL, NULL, NULL, +        "taler-exchange-offline", +        "taler-exchange-offline", +        "-c", ks->config_filename, +        "-L", "INFO", +        "extensions", +        "sign", +        "upload", +        NULL); +  if (NULL == ks->extensionssign_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "extensionssign" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +extensionssign_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ +  struct ExtensionsSignState *ks = cls; + +  (void) cmd; +  if (NULL != ks->extensionssign_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ks->extensionssign_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ks->extensionssign_proc); +    GNUNET_OS_process_destroy (ks->extensionssign_proc); +    ks->extensionssign_proc = NULL; +  } +  GNUNET_free (ks); +} + + +/** + * Offer "extensionssign" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static enum GNUNET_GenericReturnValue +extensionssign_traits (void *cls, +                       const void **ret, +                       const char *trait, +                       unsigned int index) +{ +  struct ExtensionsSignState *ks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (&ks->extensionssign_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label, +                                                const char *config_filename) +{ +  struct ExtensionsSignState *ks; + +  ks = GNUNET_new (struct ExtensionsSignState); +  ks->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &extensionssign_run, +      .cleanup = &extensionssign_cleanup, +      .traits = &extensionssign_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_exec_offline_sign_extensions.c */ diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index de3efd13..11c88c19 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -70,6 +70,11 @@ struct TALER_TESTING_FreshCoinData     */    struct TALER_CoinSpendPrivateKeyP coin_priv; +  /* +   * Age commitment for the coin, NULL if not applicable. +   */ +  struct TALER_AgeCommitment *age_commitment; +    /**     * The blinding key (needed for recoup operations).     */ @@ -132,6 +137,11 @@ struct RefreshMeltState     */    const struct TALER_CoinSpendPrivateKeyP *melt_priv; +  /* +   * Age commitment for the coin, NULL if not applicable. +   */ +  struct TALER_AgeCommitment *age_commitment; +    /**     * Task scheduled to try later.     */ @@ -1038,6 +1048,7 @@ melt_run (void *cls,      const struct TALER_DenominationSignature *melt_sig;      const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;      const struct TALER_TESTING_Command *coin_command; +    bool age_restricted;      if (NULL == (coin_command                     = TALER_TESTING_interpreter_lookup_command ( @@ -1059,6 +1070,16 @@ melt_run (void *cls,        return;      }      if (GNUNET_OK != +        TALER_TESTING_get_trait_age_commitment (coin_command, +                                                0, +                                                &rms->age_commitment)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } + +    if (GNUNET_OK !=          TALER_TESTING_get_trait_denom_sig (coin_command,                                             0,                                             &melt_sig)) @@ -1067,6 +1088,7 @@ melt_run (void *cls,        TALER_TESTING_interpreter_fail (rms->is);        return;      } +      if (GNUNET_OK !=          TALER_TESTING_get_trait_denom_pub (coin_command,                                             0, @@ -1076,9 +1098,11 @@ melt_run (void *cls,        TALER_TESTING_interpreter_fail (rms->is);        return;      } +      /* Melt amount starts with the melt fee of the old coin; we'll add the         values and withdraw fees of the fresh coins next */      melt_amount = melt_denom_pub->fee_refresh; +    age_restricted = melt_denom_pub->key.age_mask.mask != 0;      for (unsigned int i = 0; i<num_fresh_coins; i++)      {        const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk; @@ -1096,7 +1120,8 @@ melt_run (void *cls,          return;        }        fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), -                                        &fresh_amount); +                                        &fresh_amount, +                                        age_restricted);        if (NULL == fresh_pk)        {          GNUNET_break (0); @@ -1117,12 +1142,36 @@ melt_run (void *cls,        TALER_denom_pub_deep_copy (&rms->fresh_pks[i].key,                                   &fresh_pk->key);      } /* end for */ +      rms->refresh_data.melt_priv = *rms->melt_priv;      rms->refresh_data.melt_amount = melt_amount;      rms->refresh_data.melt_sig = *melt_sig;      rms->refresh_data.melt_pk = *melt_denom_pub;      rms->refresh_data.fresh_pks = rms->fresh_pks;      rms->refresh_data.fresh_pks_len = num_fresh_coins; +/* FIXME-oec:  is this needed _here_? +    { +      struct TALER_AgeCommitment *ac = NULL; + +      GNUNET_assert (age_restricted == (NULL != rms->age_commitment)); + +      if (NULL != rms->age_commitment) +      { +        uint32_t seed = GNUNET_CRYPTO_random_u32 ( +          GNUNET_CRYPTO_QUALITY_WEAK, +          UINT32_MAX); + +        GNUNET_assert (GNUNET_OK == +                       TALER_age_commitment_derive ( +                         rms->age_commitment, +                         seed, +                         ac)); +      } + +      rms->refresh_data.age_commitment = ac +    } +*/ +      rms->rmh = TALER_EXCHANGE_melt (is->exchange,                                      &rms->rms,                                      &rms->refresh_data, @@ -1207,6 +1256,8 @@ melt_traits (void *cls,                                            &rms->fresh_pks[index]),        TALER_TESTING_make_trait_coin_priv (0,                                            rms->melt_priv), +      TALER_TESTING_make_trait_age_commitment (index, +                                               rms->age_commitment),        TALER_TESTING_make_trait_exchange_wd_value (index,                                                    &rms->mbds[index].alg_value),        TALER_TESTING_make_trait_refresh_secret (&rms->rms), @@ -1370,6 +1421,9 @@ refresh_reveal_traits (void *cls,        TALER_TESTING_make_trait_coin_priv (          index,          &rrs->fresh_coins[index].coin_priv), +      TALER_TESTING_make_trait_age_commitment ( +        index, +        rrs->fresh_coins[index].age_commitment),        TALER_TESTING_make_trait_denom_pub (          index,          rrs->fresh_coins[index].pk), diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index c7265c6c..e5e8adfd 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -27,6 +27,7 @@  #include <microhttpd.h>  #include <gnunet/gnunet_curl_lib.h>  #include "taler_signatures.h" +#include "taler_extensions.h"  #include "taler_testing_lib.h"  #include "backoff.h" @@ -132,6 +133,18 @@ struct WithdrawState    struct TALER_PlanchetMasterSecretP ps;    /** +   * An age > 0 signifies age restriction is required +   */ +  uint8_t age; + +  /** +   * If age > 0, put here the corresponding age commitment and its hash, +   * respectivelly, NULL otherwise. +   */ +  struct TALER_AgeCommitment *age_commitment; +  struct TALER_AgeCommitmentHash *h_age_commitment; + +  /**     * Reserve history entry that corresponds to this operation.     * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.     */ @@ -382,12 +395,14 @@ withdraw_run (void *cls,      = TALER_TESTING_interpreter_lookup_command (          is,          ws->reserve_reference); +    if (NULL == create_reserve)    {      GNUNET_break (0);      TALER_TESTING_interpreter_fail (is);      return;    } +    if (GNUNET_OK !=        TALER_TESTING_get_trait_reserve_priv (create_reserve,                                              &rp)) @@ -396,6 +411,7 @@ withdraw_run (void *cls,      TALER_TESTING_interpreter_fail (is);      return;    } +    if (NULL == ws->exchange_url)      ws->exchange_url        = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange)); @@ -405,6 +421,7 @@ withdraw_run (void *cls,    ws->reserve_payto_uri      = TALER_payto_from_reserve (ws->exchange_url,                                  &ws->reserve_pub); +    if (NULL == ws->reuse_coin_key_ref)    {      TALER_planchet_master_setup_random (&ws->ps); @@ -429,10 +446,12 @@ withdraw_run (void *cls,                                                              &ps));      ws->ps = *ps;    } +    if (NULL == ws->pk)    {      dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), -                                 &ws->amount); +                                 &ws->amount, +                                 ws->age > 0);      if (NULL == dpk)      {        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -450,18 +469,24 @@ withdraw_run (void *cls,    {      ws->amount = ws->pk->value;    } +    ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;    GNUNET_assert (0 <=                   TALER_amount_add (&ws->reserve_history.amount,                                     &ws->amount,                                     &ws->pk->fee_withdraw));    ws->reserve_history.details.withdraw.fee = ws->pk->fee_withdraw; -  ws->wsh = TALER_EXCHANGE_withdraw (is->exchange, -                                     ws->pk, -                                     rp, -                                     &ws->ps, -                                     &reserve_withdraw_cb, -                                     ws); + +  { + +    ws->wsh = TALER_EXCHANGE_withdraw (is->exchange, +                                       ws->pk, +                                       rp, +                                       &ws->ps, +                                       ws->h_age_commitment, +                                       &reserve_withdraw_cb, +                                       ws); +  }    if (NULL == ws->wsh)    {      GNUNET_break (0); @@ -503,6 +528,16 @@ withdraw_cleanup (void *cls,      TALER_EXCHANGE_destroy_denomination_key (ws->pk);      ws->pk = NULL;    } +  if (NULL != ws->age_commitment) +  { +    GNUNET_free (ws->age_commitment); +    ws->age_commitment = NULL; +  } +  if (NULL != ws->h_age_commitment) +  { +    GNUNET_free (ws->h_age_commitment); +    ws->h_age_commitment = NULL; +  }    GNUNET_free (ws->exchange_url);    GNUNET_free (ws->reserve_payto_uri);    GNUNET_free (ws); @@ -538,7 +573,7 @@ withdraw_traits (void *cls,                                                  &ws->exchange_vals),      TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,                                          ws->pk), -    TALER_TESTING_make_trait_denom_sig (0 /* only one coin */, +    TALER_TESTING_make_trait_denom_sig (index /* only one coin */,                                          &ws->sig),      TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),      TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub), @@ -548,6 +583,8 @@ withdraw_traits (void *cls,        (const char **) &ws->reserve_payto_uri),      TALER_TESTING_make_trait_exchange_url (        (const char **) &ws->exchange_url), +    TALER_TESTING_make_trait_age_commitment (index, ws->age_commitment), +    TALER_TESTING_make_trait_h_age_commitment (index, ws->h_age_commitment),      TALER_TESTING_trait_end ()    }; @@ -567,6 +604,7 @@ withdraw_traits (void *cls,   * @param label command label.   * @param reserve_reference command providing us with a reserve to withdraw from   * @param amount how much we withdraw. + * @param age if > 0, age restriction is activated   * @param expected_response_code which HTTP response code   *        we expect from the exchange.   * @return the withdraw command to be executed by the interpreter. @@ -575,11 +613,45 @@ struct TALER_TESTING_Command  TALER_TESTING_cmd_withdraw_amount (const char *label,                                     const char *reserve_reference,                                     const char *amount, +                                   const uint8_t age,                                     unsigned int expected_response_code)  {    struct WithdrawState *ws;    ws = GNUNET_new (struct WithdrawState); + +  ws->age = age; +  if (0 < age) +  { +    struct TALER_AgeCommitment *ac; +    struct TALER_AgeCommitmentHash *hac; +    uint32_t seed; +    struct TALER_AgeMask mask; + +    ac = GNUNET_new (struct TALER_AgeCommitment); +    hac = GNUNET_new (struct TALER_AgeCommitmentHash); +    seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX); +    mask = TALER_extensions_age_restriction_ageMask (); + +    if (GNUNET_OK != +        TALER_age_restriction_commit ( +          &mask, +          age, +          seed, +          ac)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to generate age commitment for age %d at %s\n", +                  age, +                  label); +      GNUNET_assert (0); +    } + +    TALER_age_commitment_hash (ac,hac); +    ws->age_commitment = ac; +    ws->h_age_commitment = hac; +  } +    ws->reserve_reference = reserve_reference;    if (GNUNET_OK !=        TALER_string_to_amount (amount, @@ -615,6 +687,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,   * @param label command label.   * @param reserve_reference command providing us with a reserve to withdraw from   * @param amount how much we withdraw. + * @param age if > 0, age restriction is activated   * @param coin_ref reference to (withdraw/reveal) command of a coin   *        from which we should re-use the private key   * @param expected_response_code which HTTP response code @@ -626,6 +699,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (    const char *label,    const char *reserve_reference,    const char *amount, +  uint8_t age,    const char *coin_ref,    unsigned int expected_response_code)  { @@ -634,6 +708,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (    cmd = TALER_TESTING_cmd_withdraw_amount (label,                                             reserve_reference,                                             amount, +                                           age,                                             expected_response_code);    {      struct WithdrawState *ws = cmd.cls; diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c index 8e0e0298..1eecbfeb 100644 --- a/src/testing/testing_api_helpers_exchange.c +++ b/src/testing/testing_api_helpers_exchange.c @@ -27,6 +27,7 @@  #include "taler_json_lib.h"  #include <gnunet/gnunet_curl_lib.h>  #include "taler_signatures.h" +#include "taler_extensions.h"  #include "taler_testing_lib.h"  /** @@ -312,6 +313,9 @@ sign_keys_for_exchange (void *cls,    char *exchange_master_pub;    int ret; +  /* Load the age restriction mask from the configuration */ +  TALER_extensions_load_taler_config (cfg); +    if (GNUNET_OK !=        GNUNET_CONFIGURATION_get_value_string (cfg,                                               "exchange", @@ -402,7 +406,8 @@ TALER_TESTING_prepare_exchange (const char *config_filename,  const struct TALER_EXCHANGE_DenomPublicKey *  TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, -                       const struct TALER_Amount *amount) +                       const struct TALER_Amount *amount, +                       bool age_restricted)  {    struct GNUNET_TIME_Timestamp now;    struct TALER_EXCHANGE_DenomPublicKey *pk; @@ -419,7 +424,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,                                       pk->valid_from)) &&           (GNUNET_TIME_timestamp_cmp (now,                                       <, -                                     pk->withdraw_valid_until)) ) +                                     pk->withdraw_valid_until)) && +         (age_restricted == (0 != pk->key.age_mask.mask)) )        return pk;    }    /* do 2nd pass to check if expiration times are to blame for @@ -435,7 +441,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,                                       pk->valid_from) ||            GNUNET_TIME_timestamp_cmp (now,                                       >, -                                     pk->withdraw_valid_until) ) ) +                                     pk->withdraw_valid_until) ) && +         (age_restricted == (0 != pk->key.age_mask.mask)) )      {        GNUNET_log          (GNUNET_ERROR_TYPE_WARNING, diff --git a/src/util/crypto.c b/src/util/crypto.c index 13b9188c..6bea984f 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -20,11 +20,16 @@   * @author Florian Dold   * @author Benedikt Mueller   * @author Christian Grothoff + * @author Özgür Kesim   */  #include "platform.h"  #include "taler_util.h"  #include <gcrypt.h> +/** + * Used in TALER_AgeCommitmentHash_isNullOrZero for comparison + */ +const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash = {0};  /**   * Function called by libgcrypt on serious errors. @@ -83,12 +88,11 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,                   GNUNET_memcmp (&d_hash,                                  &coin_public_info->denom_pub_hash));  #endif -  // FIXME-Oec: replace with function that -  // also hashes the age vector if we have -  // one! -  GNUNET_CRYPTO_hash (&coin_public_info->coin_pub, -                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), -                      &c_hash.hash); + +  TALER_coin_pub_hash (&coin_public_info->coin_pub, +                       &coin_public_info->age_commitment_hash, +                       &c_hash); +    if (GNUNET_OK !=        TALER_denom_pub_verify (denom_pub,                                &coin_public_info->denom_sig, @@ -251,6 +255,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,                          const struct TALER_ExchangeWithdrawValues *alg_values,                          const union TALER_DenominationBlindingKeyP *bks,                          const struct TALER_CoinSpendPrivateKeyP *coin_priv, +                        const struct TALER_AgeCommitmentHash *ach,                          struct TALER_CoinPubHash *c_hash,                          struct TALER_PlanchetDetail *pd                          ) @@ -263,7 +268,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,    if (GNUNET_OK !=        TALER_denom_blind (dk,                           bks, -                         NULL, /* FIXME-Oec */ +                         ach,                           &coin_pub,                           alg_values,                           c_hash, @@ -291,6 +296,7 @@ TALER_planchet_to_coin (    const struct TALER_BlindedDenominationSignature *blind_sig,    const union TALER_DenominationBlindingKeyP *bks,    const struct TALER_CoinSpendPrivateKeyP *coin_priv, +  const struct TALER_AgeCommitmentHash *ach,    const struct TALER_CoinPubHash *c_hash,    const struct TALER_ExchangeWithdrawValues *alg_values,    struct TALER_FreshCoin *coin) @@ -321,7 +327,9 @@ TALER_planchet_to_coin (      TALER_denom_sig_free (&coin->sig);      return GNUNET_SYSERR;    } +    coin->coin_priv = *coin_priv; +  coin->h_age_commitment = ach;    return GNUNET_OK;  } @@ -396,10 +404,10 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,  void  TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, -                     const struct TALER_AgeHash *age_commitment_hash, +                     const struct TALER_AgeCommitmentHash *ach,                       struct TALER_CoinPubHash *coin_h)  { -  if (NULL == age_commitment_hash) +  if (TALER_AgeCommitmentHash_isNullOrZero (ach))    {      /* No age commitment was set */      GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub, @@ -411,14 +419,14 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,      /* Coin comes with age commitment.  Take the hash of the age commitment       * into account */      const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey); -    const size_t age_s = sizeof(struct TALER_AgeHash); +    const size_t age_s = sizeof(struct TALER_AgeCommitmentHash);      char data[key_s + age_s];      GNUNET_memcpy (&data[0],                     &coin_pub->eddsa_pub,                     key_s);      GNUNET_memcpy (&data[key_s], -                   age_commitment_hash, +                   ach,                     age_s);      GNUNET_CRYPTO_hash (&data,                          key_s + age_s, @@ -427,4 +435,276 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,  } +void +TALER_age_commitment_hash ( +  const struct TALER_AgeCommitment *commitment, +  struct TALER_AgeCommitmentHash *ahash) +{ +  struct GNUNET_HashContext *hash_context; +  struct GNUNET_HashCode hash; + +  GNUNET_assert (NULL != ahash); +  if (NULL == commitment) +  { +    memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash)); +    return; +  } + +  GNUNET_assert (__builtin_popcount (commitment->mask.mask) - 1 == +                 commitment->num_pub); + +  hash_context = GNUNET_CRYPTO_hash_context_start (); + +  for (size_t i = 0; i < commitment->num_pub; i++) +  { +    GNUNET_CRYPTO_hash_context_read (hash_context, +                                     &commitment->pub[i], +                                     sizeof(struct +                                            GNUNET_CRYPTO_EddsaPublicKey)); +  } + +  GNUNET_CRYPTO_hash_context_finish (hash_context, +                                     &hash); +  GNUNET_memcpy (&ahash->shash.bits, +                 &hash.bits, +                 sizeof(ahash->shash.bits)); +} + + +/* To a given age value between 0 and 31, returns the index of the age group + * defined by the given mask. + */ +static uint8_t +get_age_group ( +  const struct TALER_AgeMask *mask, +  uint8_t age) +{ +  uint32_t m = mask->mask; +  uint8_t i = 0; + +  while (m > 0) +  { +    if (0 >= age) +      break; +    m = m >> 1; +    i += m & 1; +    age--; +  } +  return i; +} + + +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit ( +  const struct TALER_AgeMask *mask, +  const uint8_t age, +  const uint32_t seed, +  struct TALER_AgeCommitment *new) +{ +  uint8_t num_pub = __builtin_popcount (mask->mask) - 1; +  uint8_t num_priv = get_age_group (mask, age) - 1; +  size_t i; + +  GNUNET_assert (NULL != new); +  GNUNET_assert (mask->mask & 1); /* fist bit must have been set */ +  GNUNET_assert (0 <= num_priv); +  GNUNET_assert (31 > num_priv); + +  new->mask.mask = mask->mask; +  new->num_pub = num_pub; +  new->num_priv = num_priv; + +  new->pub = GNUNET_new_array ( +    num_pub, +    struct TALER_AgeCommitmentPublicKeyP); +  new->priv = GNUNET_new_array ( +    num_priv, +    struct TALER_AgeCommitmentPrivateKeyP); + +  /* Create as many private keys as we need */ +  for (i = 0; i < num_priv; i++) +  { +    uint32_t seedBE = htonl (seed + i); + +    if  (GNUNET_OK != +         GNUNET_CRYPTO_kdf (&new->priv[i], +                            sizeof (new->priv[i]), +                            &seedBE, +                            sizeof (seedBE), +                            "taler-age-commitment-derivation", +                            strlen ( +                              "taler-age-commitment-derivation"), +                            NULL, 0)) +      goto FAIL; + +    GNUNET_CRYPTO_eddsa_key_get_public (&new->priv[i].eddsa_priv, +                                        &new->pub[i].eddsa_pub); +  } + +  /* Fill the rest of the public keys with random values */ +  for (; i<num_pub; i++) +    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, +                                &new->pub[i], +                                sizeof(new->pub[i])); + +  return GNUNET_OK; + +FAIL: +  GNUNET_free (new->pub); +  GNUNET_free (new->priv); +  return GNUNET_SYSERR; +} + + +enum GNUNET_GenericReturnValue +TALER_age_commitment_derive ( +  const struct TALER_AgeCommitment *orig, +  const uint32_t seed, +  struct TALER_AgeCommitment *new) +{ +  struct GNUNET_CRYPTO_EccScalar val; + +  /* +  * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key +  * +  * GNUNET_CRYPTO_EddsaPrivateKey is a +  *   unsigned char d[256 / 8]; +  * +  * GNUNET_CRYPTO_EddsaPublicKey is a +  *   unsigned char q_y[256 / 8]; +  * +  * We want to multiply, both, the Private Key by an integer factor and the +  * public key (point on curve) with the equivalent scalar. +  * +  * From the seed we will derive +  *   1. a scalar to multiply the public keys with +  *   2. a factor to multiply the private key with +  * +  * Invariants: +  *   point*scalar == public(private*factor) +  * +  * A point on a curve is GNUNET_CRYPTO_EccPoint which is +  *   unsigned char v[256 / 8]; +  * +  * A ECC scaler for use in point multiplications is a +  * GNUNET_CRYPTO_EccScalar which is a +  *   unsigned car v[256 / 8]; +  * */ + +  GNUNET_assert (NULL != new); +  GNUNET_assert (orig->num_pub == __builtin_popcount (orig->mask.mask) - 1); +  GNUNET_assert (orig->num_priv <= orig->num_pub); + +  new->mask = orig->mask; +  new->num_pub = orig->num_pub; +  new->num_priv = orig->num_priv; +  new->pub = GNUNET_new_array ( +    new->num_pub, +    struct TALER_AgeCommitmentPublicKeyP); +  new->priv = GNUNET_new_array ( +    new->num_priv, +    struct TALER_AgeCommitmentPrivateKeyP); + + +  GNUNET_CRYPTO_ecc_scalar_from_int (seed, &val); + +  /* scalar multiply the public keys on the curve */ +  for (size_t i = 0; i < orig->num_pub; i++) +  { +    /* We shift all keys by the same scalar */ +    struct GNUNET_CRYPTO_EccPoint *p = (struct +                                        GNUNET_CRYPTO_EccPoint *) &orig->pub[i]; +    struct GNUNET_CRYPTO_EccPoint *np = (struct +                                         GNUNET_CRYPTO_EccPoint *) &new->pub[i]; +    if (GNUNET_OK != +        GNUNET_CRYPTO_ecc_pmul_mpi ( +          p, +          &val, +          np)) +      goto FAIL; + +  } + +  /* multiply the private keys */ +  /* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */ +  { +    uint32_t seedBE; +    uint8_t dc[32]; +    gcry_mpi_t f, x, d, n; +    gcry_ctx_t ctx; + +    GNUNET_assert (0==gcry_mpi_ec_new (&ctx,NULL, "Ed25519")); +    n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + +    /* make the seed big endian */ +    seedBE = GNUNET_htonll (seed); + +    GNUNET_CRYPTO_mpi_scan_unsigned (&f, &seedBE, sizeof(seedBE)); + +    for (size_t i = 0; i < orig->num_priv; i++) +    { + +      /* convert to big endian for libgrypt */ +      for (size_t j = 0; j < 32; j++) +        dc[i] = orig->priv[i].eddsa_priv.d[31 - j]; +      GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); + +      d = gcry_mpi_new (256); +      gcry_mpi_mulm (d, f, x, n); +      gcry_mpi_release (x); +      gcry_mpi_release (d); +      gcry_mpi_release (n); +      gcry_mpi_release (d); +      GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); + +      for (size_t j = 0; j <32; j++) +        new->priv[i].eddsa_priv.d[j] = dc[31 - 1]; + +      sodium_memzero (dc, sizeof(dc)); + +      /* TODO: +       * make sure that the calculated private key generate the same public +       * keys */ +    } + +    gcry_mpi_release (f); +    gcry_ctx_release (ctx); +  } + +  return GNUNET_OK; + +FAIL: +  GNUNET_free (new->pub); +  GNUNET_free (new->priv); +  return GNUNET_SYSERR; +} + + +void +TALER_age_restriction_commmitment_free_inside ( +  struct TALER_AgeCommitment *commitment) +{ +  if (NULL == commitment) +    return; + +  if (NULL != commitment->priv) +  { +    GNUNET_CRYPTO_zero_keys ( +      commitment->priv, +      sizeof(*commitment->priv) * commitment->num_priv); + +    GNUNET_free (commitment->priv); +    commitment->priv = NULL; +  } + +  if (NULL != commitment->pub) +  { +    GNUNET_free (commitment->pub); +    commitment->priv = NULL; +  } + +  /* Caller is responsible for commitment itself */ +} + +  /* end of crypto.c */ diff --git a/src/util/denom.c b/src/util/denom.c index 783e9a36..7c2c42c9 100644 --- a/src/util/denom.c +++ b/src/util/denom.c @@ -297,14 +297,14 @@ enum GNUNET_GenericReturnValue  TALER_denom_blind (    const struct TALER_DenominationPublicKey *dk,    const union TALER_DenominationBlindingKeyP *coin_bks, -  const struct TALER_AgeHash *age_commitment_hash, +  const struct TALER_AgeCommitmentHash *ach,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_ExchangeWithdrawValues *alg_values,    struct TALER_CoinPubHash *c_hash,    struct TALER_BlindedPlanchet *blinded_planchet)  {    TALER_coin_pub_hash (coin_pub, -                       age_commitment_hash, +                       ach,                         c_hash);    switch (dk->cipher)    { diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c index fbf30e3a..cda17d9b 100644 --- a/src/util/test_crypto.c +++ b/src/util/test_crypto.c @@ -152,6 +152,7 @@ test_planchets_rsa (void)                                           &alg_values,                                           &bks,                                           &coin_priv, +                                         NULL, /* no age commitment */                                           &c_hash,                                           &pd));    GNUNET_assert (GNUNET_OK == @@ -164,6 +165,7 @@ test_planchets_rsa (void)                                           &blind_sig,                                           &bks,                                           &coin_priv, +                                         NULL, /* no age commitment */                                           &c_hash,                                           &alg_values,                                           &coin)); @@ -175,6 +177,8 @@ test_planchets_rsa (void)  } +/** FIXME-oec: Add test for planchets with age commitment hash */ +  /**   * @brief Function for CS signatures to derive public R_0 and R_1   * @@ -392,10 +396,12 @@ main (int argc,      return 1;    if (0 != test_planchets ())      return 2; -  if (0 != test_exchange_sigs ()) +  if (0 != test_planchets_with_age_commitment ())      return 3; -  if (0 != test_merchant_sigs ()) +  if (0 != test_exchange_sigs ())      return 4; +  if (0 != test_merchant_sigs ()) +    return 5;    return 0;  } diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c index 679f5d7f..2ead8a6e 100644 --- a/src/util/test_helper_rsa.c +++ b/src/util/test_helper_rsa.c @@ -269,6 +269,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)    bool success = false;    struct TALER_PlanchetMasterSecretP ps;    struct TALER_ExchangeWithdrawValues alg_values; +  struct TALER_AgeCommitmentHash ach;    struct TALER_CoinPubHash c_hash;    struct TALER_CoinSpendPrivateKeyP coin_priv;    union TALER_DenominationBlindingKeyP bks; @@ -280,6 +281,9 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)    alg_values.cipher = TALER_DENOMINATION_RSA;    TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);    TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks); +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, +                              &ach, +                              sizeof(ach));    for (unsigned int i = 0; i<MAX_KEYS; i++)    { @@ -296,6 +300,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)                                               &alg_values,                                               &bks,                                               &coin_priv, +                                             &ach,                                               &c_hash,                                               &pd));        GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -440,6 +445,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,    struct GNUNET_TIME_Relative duration;    struct TALER_PlanchetMasterSecretP ps;    struct TALER_CoinSpendPrivateKeyP coin_priv; +  struct TALER_AgeCommitmentHash ach;    union TALER_DenominationBlindingKeyP bks;    struct TALER_ExchangeWithdrawValues alg_values; @@ -447,7 +453,9 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,    alg_values.cipher = TALER_DENOMINATION_RSA;    TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);    TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks); - +  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, +                              &ach, +                              sizeof(ach));    duration = GNUNET_TIME_UNIT_ZERO;    TALER_CRYPTO_helper_rsa_poll (dh);    for (unsigned int j = 0; j<NUM_SIGN_PERFS;) @@ -477,6 +485,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,                                                 &alg_values,                                                 &bks,                                                 &coin_priv, +                                               &ach,                                                 &c_hash,                                                 &pd));          /* use this key as long as it works */ diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 01f33ae8..88cd8de0 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -29,6 +29,7 @@ TALER_wallet_deposit_sign (    const struct TALER_Amount *deposit_fee,    const struct TALER_MerchantWireHash *h_wire,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_ExtensionContractHash *h_extensions,    const struct TALER_DenominationHash *h_denom_pub,    struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -48,8 +49,12 @@ TALER_wallet_deposit_sign (      .merchant = *merchant_pub    }; +  if (NULL != h_age_commitment) +    dr.h_age_commitment = *h_age_commitment; +    if (NULL != h_extensions)      dr.h_extensions = *h_extensions; +    TALER_amount_hton (&dr.amount_with_fee,                       amount);    TALER_amount_hton (&dr.deposit_fee, @@ -66,6 +71,7 @@ TALER_wallet_deposit_verify (    const struct TALER_Amount *deposit_fee,    const struct TALER_MerchantWireHash *h_wire,    const struct TALER_PrivateContractHash *h_contract_terms, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_ExtensionContractHash *h_extensions,    const struct TALER_DenominationHash *h_denom_pub,    struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -82,11 +88,17 @@ TALER_wallet_deposit_verify (      .h_denom_pub = *h_denom_pub,      .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),      .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), -    .merchant = *merchant_pub +    .merchant = *merchant_pub, +    .h_age_commitment = {{{0}}}, +    .h_extensions = {{{0}}}    }; +  if (NULL != h_age_commitment) +    dr.h_age_commitment = *h_age_commitment; +    if (NULL != h_extensions)      dr.h_extensions = *h_extensions; +    TALER_amount_hton (&dr.amount_with_fee,                       amount);    TALER_amount_hton (&dr.deposit_fee, @@ -131,6 +143,7 @@ TALER_wallet_link_verify (    const struct TALER_TransferPublicKeyP *transfer_pub,    const struct TALER_BlindedCoinHash *h_coin_ev,    const struct TALER_CoinSpendPublicKeyP *old_coin_pub, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_CoinSpendSignatureP *coin_sig)  {    struct TALER_LinkDataPS ldp = { @@ -138,9 +151,13 @@ TALER_wallet_link_verify (      .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),      .h_denom_pub = *h_denom_pub,      .transfer_pub = *transfer_pub, -    .coin_envelope_hash = *h_coin_ev +    .coin_envelope_hash = *h_coin_ev, +    .h_age_commitment = {{{0}}}    }; +  if (NULL != h_age_commitment) +    ldp.h_age_commitment = *h_age_commitment; +    return      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,                                  &ldp, @@ -263,6 +280,7 @@ TALER_wallet_melt_verify (    const struct TALER_Amount *melt_fee,    const struct TALER_RefreshCommitmentP *rc,    const struct TALER_DenominationHash *h_denom_pub, +  const struct TALER_AgeCommitmentHash *h_age_commitment,    const struct TALER_CoinSpendPublicKeyP *coin_pub,    const struct TALER_CoinSpendSignatureP *coin_sig)  { @@ -270,9 +288,13 @@ TALER_wallet_melt_verify (      .purpose.size = htonl (sizeof (melt)),      .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),      .rc = *rc, -    .h_denom_pub = *h_denom_pub +    .h_denom_pub = *h_denom_pub, +    .h_age_commitment = {{{0}}},    }; +  if (NULL != h_age_commitment) +    melt.h_age_commitment = *h_age_commitment; +    TALER_amount_hton (&melt.amount_with_fee,                       amount_with_fee);    TALER_amount_hton (&melt.melt_fee,  | 
