gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated: [age restriction] age verificati


From: gnunet
Subject: [taler-merchant] branch master updated: [age restriction] age verification implemented, but not tested.
Date: Tue, 15 Mar 2022 18:19:24 +0100

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

oec pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new cd0b3436 [age restriction] age verification implemented, but not 
tested.
cd0b3436 is described below

commit cd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c
Author: Özgür Kesim <oec-taler@kesim.org>
AuthorDate: Tue Mar 15 18:19:08 2022 +0100

    [age restriction] age verification implemented, but not tested.
    
    order/pay now verifies the minimum_age of an order for each coin, using
    the age restriction API from the exchange-lib.
    
    Note, tests are not implemented yet.
---
 .../taler-merchant-httpd_post-orders-ID-pay.c      | 248 +++++++++++++++++----
 src/include/taler_merchant_service.h               |   5 +
 src/lib/merchant_api_post_order_pay.c              |   2 +-
 src/testing/testing_api_cmd_pay_order.c            |   8 +
 4 files changed, 224 insertions(+), 39 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index edd37d24..3bdf5e50 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -1,21 +1,21 @@
 /*
-  This file is part of TALER
-  (C) 2014-2021 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as
-  published by the Free Software Foundation; either version 3,
-  or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU 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/>
-*/
+   This file is part of TALER
+   (C) 2014-2021 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify
+   it under the terms of the GNU Affero General Public License as
+   published by the Free Software Foundation; either version 3,
+   or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU 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 taler-merchant-httpd_post-orders-ID-pay.c
@@ -121,6 +121,25 @@ struct DepositConfirmation
    */
   struct TALER_CoinSpendSignatureP coin_sig;
 
+  /**
+   * If a minimum age was required (i. e. pc->minimum_age is large enough),
+   * this is the signature of the minimum age (as a single uint8_t), using the
+   * private key to the corresponding age group.  Might be NULL.
+   */
+  struct TALER_AgeAttestation *minimum_age_sig;
+
+  /* If a minimum age was required (i. e. pc->minimum_age is large enought),
+   * this is the age commitment (i. e. age mask and vector of EdDSA public
+   * keys, one per age group) that went into the mining of the coin.  The
+   * SHA256 hash of the mask and the vector of public keys was bound to the
+   * key.
+   */
+  struct TALER_AgeCommitment *age_commitment;
+
+  /* Age mask in the denomination that defines the age groups.  Only
+   * applicable, if minimum age was required. */
+  struct TALER_AgeMask age_mask;
+
   /**
    * Offset of this coin into the `dc` array of all coins in the
    * @e pc.
@@ -261,6 +280,12 @@ struct PayContext
    */
   struct TALER_Amount max_wire_fee;
 
+
+  /**
+   * Minimum age required for this purchase.
+   */
+  unsigned int minimum_age;
+
   /**
    * Amount from @e root.  This is the amount the merchant expects
    * to make, minus @e max_fee.
@@ -1031,6 +1056,7 @@ process_pay_with_exchange (void *cls,
   struct PayContext *pc = cls;
   struct TMH_HandlerContext *hc = pc->hc;
   const struct TALER_EXCHANGE_Keys *keys;
+  struct TALER_AgeCommitmentHash h_age_commitment = {0};
 
   (void) payto_uri;
   pc->fo = NULL;
@@ -1076,10 +1102,11 @@ process_pay_with_exchange (void *cls,
     const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
     enum TALER_ErrorCode ec;
     unsigned int http_status;
+    bool age_verification_required = false;
 
     if (NULL != dc->dh)
       continue; /* we were here before (can happen due to
-                   tried_force_keys logic), don't go again */
+                         tried_force_keys logic), don't go again */
     if (dc->found_in_db)
       continue;
     if (0 != strcmp (dc->exchange_url,
@@ -1148,6 +1175,58 @@ process_pay_with_exchange (void *cls,
       return;
     }
 
+
+    /* Now that we have the details about the denomination, we can verify age
+     * restriction requirements, if applicable. Note that denominations with an
+     * age_mask equal to zero always pass the age verification.  */
+    age_verification_required = 0 < pc->minimum_age &&
+                                0 < denom_details->key.age_mask.bits;
+
+    if (age_verification_required)
+    {
+      unsigned int code = 0;
+
+      if (NULL == dc->age_commitment)
+      {
+        code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING;
+        goto AGE_FAIL;
+      }
+
+      dc->age_commitment->mask = denom_details->key.age_mask;
+      if (dc->age_commitment->num !=
+          __builtin_popcount (dc->age_commitment->mask.bits))
+      {
+        code =
+          TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH;
+        goto AGE_FAIL;
+      }
+
+      if (GNUNET_OK !=
+          TALER_age_commitment_verify (
+            dc->age_commitment,
+            pc->minimum_age,
+            dc->minimum_age_sig))
+        code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED;
+
+AGE_FAIL:
+      if (0 < code)
+      {
+        resume_pay_with_response (
+          pc,
+          MHD_HTTP_BAD_REQUEST,
+          TALER_MHD_MAKE_JSON_PACK (
+            TALER_JSON_pack_ec (code),
+            GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                        &denom_details->h_key)));
+        return;
+      }
+
+      /* Age restriction successfully verified!
+       * Calculate the hash of the age commitment. */
+      TALER_age_commitment_hash (dc->age_commitment,
+                                 &h_age_commitment);
+    }
+
     dc->deposit_fee = denom_details->fees.deposit;
     dc->refund_fee = denom_details->fees.refund;
     dc->wire_fee = *wire_fee;
@@ -1159,8 +1238,9 @@ process_pay_with_exchange (void *cls,
                                      pc->wm->payto_uri,
                                      &pc->wm->wire_salt,
                                      &pc->h_contract_terms,
-                                     NULL, /* FIXME-Oec */
-                                     NULL, /* FIXME-Oec */
+                                     age_verification_required ?
+                                     &h_age_commitment: NULL,
+                                     NULL, /* FIXME oec: extension json blob */
                                      &dc->coin_pub,
                                      &dc->ub_sig,
                                      &denom_details->key,
@@ -1932,8 +2012,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_GENERIC_PARAMETER_MISSING,
                                           "'coins' must be an array"))
-              ? GNUNET_NO
-              : GNUNET_SYSERR;
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
     }
 
     pc->coins_cnt = json_array_size (coins);
@@ -1945,8 +2025,8 @@ parse_pay (struct MHD_Connection *connection,
                                                      MHD_HTTP_BAD_REQUEST,
                                                      
TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                                      "'coins' array too long"))
-              ? GNUNET_NO
-              : GNUNET_SYSERR;
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
     }
 
     /* note: 1 coin = 1 deposit confirmation expected */
@@ -1961,6 +2041,8 @@ parse_pay (struct MHD_Connection *connection,
       {
         struct DepositConfirmation *dc = &pc->dc[coins_index];
         const char *exchange_url;
+        struct TALER_AgeAttestationPS minimum_age_sig = {0};
+        json_t *age_commitment = NULL;
         struct GNUNET_JSON_Specification ispec[] = {
           GNUNET_JSON_spec_fixed_auto ("coin_sig",
                                        &dc->coin_sig),
@@ -1975,6 +2057,12 @@ parse_pay (struct MHD_Connection *connection,
                                   &dc->amount_with_fee),
           GNUNET_JSON_spec_string ("exchange_url",
                                    &exchange_url),
+          GNUNET_JSON_spec_mark_optional (
+            GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
+                                         &minimum_age_sig)),
+          GNUNET_JSON_spec_mark_optional (
+            GNUNET_JSON_spec_json ("age_commitment",
+                                   &age_commitment)),
           GNUNET_JSON_spec_end ()
         };
         enum GNUNET_GenericReturnValue res;
@@ -1988,6 +2076,7 @@ parse_pay (struct MHD_Connection *connection,
           GNUNET_JSON_parse_free (spec);
           return res;
         }
+
         for (unsigned int j = 0; j<coins_index; j++)
         {
           if (0 ==
@@ -2000,10 +2089,11 @@ parse_pay (struct MHD_Connection *connection,
                                                 MHD_HTTP_BAD_REQUEST,
                                                 
TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                                 "duplicate coin in list"))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
           }
         }
+
         dc->exchange_url = GNUNET_strdup (exchange_url);
         dc->index = coins_index;
         dc->pc = pc;
@@ -2019,6 +2109,74 @@ parse_pay (struct MHD_Connection *connection,
                                              
TALER_EC_GENERIC_CURRENCY_MISMATCH,
                                              TMH_currency);
         }
+
+        {
+          bool has_commitment = (NULL != age_commitment) &&
+                                json_is_array (age_commitment);
+          bool has_sig = ! GNUNET_is_zero_ (&minimum_age_sig,
+                                            sizeof(minimum_age_sig));
+          if (has_sig != has_commitment)
+          {
+            GNUNET_break_op (0);
+            GNUNET_JSON_parse_free (spec);
+            return TALER_MHD_reply_with_error (
+              connection,
+              MHD_HTTP_BAD_REQUEST,
+              TALER_EC_GENERIC_PARAMETER_MALFORMED,
+              "inconsistency: 'mininum_age_sig' vs. 'age_commitment'");
+          }
+
+          /* Parse the AgeCommitment, i. e. the public keys */
+          if (has_commitment)
+          {
+            json_t *pk;
+            unsigned int idx;
+            size_t num = json_array_size (age_commitment);
+
+            /* Sanity check the amount of AgeCommitment's public keys.  The
+             * actual check will be performed once we now the denominations. */
+            if (32 >= num)
+            {
+              GNUNET_break_op (0);
+              GNUNET_JSON_parse_free (spec);
+              return TALER_MHD_reply_with_error (connection,
+                                                 MHD_HTTP_BAD_REQUEST,
+                                                 
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                                 "'age_commitment' too large");
+            }
+
+            dc->age_commitment = GNUNET_new (struct TALER_AgeCommitment);
+            dc->age_commitment->num = num;
+            dc->age_commitment->keys =
+              GNUNET_new_array (num,
+                                struct TALER_AgeCommitmentPublicKeyP);
+            /* Note that dc->age_commitment.mask will be set later, based on
+             * the actual denomination. */
+
+            json_array_foreach (age_commitment, idx, pk) {
+              struct GNUNET_JSON_Specification pkspec[] = {
+                GNUNET_JSON_spec_fixed_auto (NULL,
+                                             &dc
+                                             ->age_commitment
+                                             ->keys[idx].pub),
+                GNUNET_JSON_spec_end ()
+              };
+
+              if (GNUNET_OK !=
+                  GNUNET_JSON_parse (pk,
+                                     pkspec,
+                                     NULL, NULL))
+              {
+                GNUNET_break_op (0);
+                GNUNET_JSON_parse_free (spec);
+                return TALER_MHD_reply_with_error (connection,
+                                                   MHD_HTTP_BAD_REQUEST,
+                                                   
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                                   "age_commitment");
+              }
+            }
+          }
+        }
       }
     }
     GNUNET_JSON_parse_free (spec);
@@ -2047,8 +2205,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
                                           "contract terms"))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
     }
     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     {
@@ -2057,8 +2215,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_NOT_FOUND,
                                           
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
                                           pc->order_id))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
     }
 
     /* hash contract (needed later) */
@@ -2073,8 +2231,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
                                           NULL))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
     }
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Handling payment for order `%s' with contract hash `%s'\n",
@@ -2093,8 +2251,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
                                           NULL))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
     }
 
     /* Get details from contract and check fundamentals */
@@ -2125,10 +2283,15 @@ parse_pay (struct MHD_Connection *connection,
                                     &pc->wire_transfer_deadline),
         GNUNET_JSON_spec_fixed_auto ("h_wire",
                                      &pc->h_wire),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("minimum_age",
+                                       &pc->minimum_age)),
         GNUNET_JSON_spec_end ()
       };
       enum GNUNET_GenericReturnValue res;
 
+      pc->minimum_age = 0;
+
       res = TALER_MHD_parse_internal_json_data (connection,
                                                 contract_terms,
                                                 espec);
@@ -2162,8 +2325,8 @@ parse_pay (struct MHD_Connection *connection,
                                           MHD_HTTP_GONE,
                                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
                                           NULL))
-             ? GNUNET_NO
-             : GNUNET_SYSERR;
+       ? GNUNET_NO
+       : GNUNET_SYSERR;
     }
   }
 
@@ -2186,6 +2349,15 @@ parse_pay (struct MHD_Connection *connection,
     pc->wm = wm;
   }
 
+  if (0 < pc->minimum_age)
+  {
+    /* TODO oec: check
+     * 1. denomination are age restricted
+     * 2. consume their mask
+     * 3. valididate minimum age
+     */
+  }
+
   return GNUNET_OK;
 }
 
@@ -2260,8 +2432,8 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler 
*rh,
                      pc);
     if (GNUNET_OK != ret)
       return (GNUNET_NO == ret)
-             ? MHD_YES
-             : MHD_NO;
+       ? MHD_YES
+       : MHD_NO;
   }
 
   /* Payment not finished, suspend while we interact with the exchange */
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index 660f1024..d632df67 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -2240,6 +2240,11 @@ struct TALER_MERCHANT_PayCoin
    */
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
+  /**
+   * Coin's age commitment. Might be NULL, if not applicable.
+   */
+  const struct TALER_AgeCommitmentHash *h_age_commitment;
+
   /**
    * Amount this coin contributes to (including fee).
    */
diff --git a/src/lib/merchant_api_post_order_pay.c 
b/src/lib/merchant_api_post_order_pay.c
index 0cf27e02..fc188a98 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -619,7 +619,7 @@ TALER_MERCHANT_order_pay (
                                  &fee,
                                  h_wire,
                                  h_contract_terms,
-                                 NULL /* h_age_commitment! */,
+                                 coin->h_age_commitment,
                                  NULL /* h_extensions! */,
                                  &h_denom_pub,
                                  timestamp,
diff --git a/src/testing/testing_api_cmd_pay_order.c 
b/src/testing/testing_api_cmd_pay_order.c
index dbc8d6da..be2649c0 100644
--- a/src/testing/testing_api_cmd_pay_order.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -165,6 +165,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
       const struct TALER_DenominationSignature *denom_sig;
       const struct TALER_Amount *denom_value;
       const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+      const struct TALER_AgeCommitmentHash *h_age_commitment;
 
       GNUNET_assert (GNUNET_OK ==
                      TALER_TESTING_get_trait_coin_priv (coin_cmd,
@@ -181,11 +182,16 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
       GNUNET_assert (GNUNET_OK ==
                      TALER_TESTING_get_trait_amount (coin_cmd,
                                                      &denom_value));
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+                                                              0,
+                                                              
&h_age_commitment));
       icoin->coin_priv = *coin_priv;
       icoin->denom_pub = denom_pub->key;
       icoin->denom_sig = *denom_sig;
       icoin->denom_value = *denom_value;
       icoin->amount_with_fee = *denom_value;
+      icoin->h_age_commitment = h_age_commitment;
     }
     GNUNET_assert (NULL != (dpk =
                               TALER_TESTING_find_pk (is->keys,
@@ -303,6 +309,8 @@ pay_run (void *cls,
                                   &total_amount),
       TALER_JSON_spec_amount_any ("max_fee",
                                   &max_fee),
+      /* FIXME oec
+       * add minimum age */
       GNUNET_JSON_spec_end ()
     };
 

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



reply via email to

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