merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-private-otp-devices.c (11093B)


      1 /*
      2   This file is part of TALER
      3   (C) 2022 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-private-otp-devices.c
     22  * @brief implementing POST /otp-devices request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_post-private-otp-devices.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 #include "merchant-database/insert_otp_device.h"
     30 #include "merchant-database/get_otp_device.h"
     31 #include "merchant-database/start.h"
     32 
     33 
     34 /**
     35  * How often do we retry the simple INSERT database transaction?
     36  */
     37 #define MAX_RETRIES 3
     38 
     39 
     40 /**
     41  * Check if the two otp-devices are identical.
     42  *
     43  * @param t1 device to compare
     44  * @param t2 other device to compare
     45  * @return true if they are 'equal', false if not or of payto_uris is not an array
     46  */
     47 static bool
     48 otp_devices_equal (const struct TALER_MERCHANTDB_OtpDeviceDetails *t1,
     49                    const struct TALER_MERCHANTDB_OtpDeviceDetails *t2)
     50 {
     51   if ( (0 != strcmp (t1->otp_description,
     52                      t2->otp_description)) ||
     53        (t1->otp_ctr != t2->otp_ctr) ||
     54        (t1->otp_algorithm != t2->otp_algorithm) )
     55     return false;
     56   switch (t1->otp_algorithm)
     57   {
     58   case TALER_MCA_ECDSA_CHALLENGE:
     59   case TALER_MCA_EDDSA_CHALLENGE:
     60     /* the key was generated here and never shown to the client, so
     61        there is nothing of theirs left to compare */
     62     return true;
     63   case TALER_MCA_NONE:
     64   case TALER_MCA_WITHOUT_PRICE:
     65   case TALER_MCA_WITH_PRICE:
     66     return ( (NULL == t1->otp_key) == (NULL == t2->otp_key) ) &&
     67            ( (NULL == t1->otp_key) ||
     68              (0 == strcmp (t1->otp_key,
     69                            t2->otp_key)) );
     70   }
     71   GNUNET_break (0);
     72   return false;
     73 }
     74 
     75 
     76 enum MHD_Result
     77 TMH_private_post_otp_devices (const struct TMH_RequestHandler *rh,
     78                               struct MHD_Connection *connection,
     79                               struct TMH_HandlerContext *hc)
     80 {
     81   struct TMH_MerchantInstance *mi = hc->instance;
     82   struct TALER_MERCHANTDB_OtpDeviceDetails tp = { 0 };
     83   const char *device_id;
     84   enum GNUNET_DB_QueryStatus qs;
     85   enum MHD_Result ret;
     86   bool generated_keys = false;
     87   struct GNUNET_JSON_Specification spec[] = {
     88     TALER_JSON_spec_slug ("otp_device_id",
     89                           &device_id),
     90     GNUNET_JSON_spec_string ("otp_device_description",
     91                              (const char **) &tp.otp_description),
     92     TALER_JSON_spec_otp_type ("otp_algorithm",
     93                               &tp.otp_algorithm),
     94     GNUNET_JSON_spec_mark_optional (
     95       GNUNET_JSON_spec_uint64 ("otp_ctr",
     96                                &tp.otp_ctr),
     97       NULL),
     98     GNUNET_JSON_spec_mark_optional (
     99       TALER_JSON_spec_otp_key ("otp_key",
    100                                (const char **) &tp.otp_key),
    101       NULL),
    102     GNUNET_JSON_spec_end ()
    103   };
    104 
    105   GNUNET_assert (NULL != mi);
    106   {
    107     enum GNUNET_GenericReturnValue res;
    108 
    109     res = TALER_MHD_parse_json_data (connection,
    110                                      hc->request_body,
    111                                      spec);
    112     if (GNUNET_OK != res)
    113     {
    114       GNUNET_break_op (0);
    115       return (GNUNET_NO == res)
    116              ? MHD_YES
    117              : MHD_NO;
    118     }
    119   }
    120   /* returned to (JavaScript) clients as a JSON number, so it must be
    121      a "SafeUint64" (2^53 - 1), see api-common.rst */
    122   if (tp.otp_ctr > (1LLU << 53) - 1)
    123   {
    124     GNUNET_break_op (0);
    125     ret = TALER_MHD_reply_with_error (connection,
    126                                       MHD_HTTP_BAD_REQUEST,
    127                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    128                                       "otp_ctr");
    129     goto cleanup;
    130   }
    131 
    132   switch (tp.otp_algorithm)
    133   {
    134   case TALER_MCA_ECDSA_CHALLENGE:
    135   case TALER_MCA_EDDSA_CHALLENGE:
    136     if (NULL != tp.otp_key)
    137     {
    138       GNUNET_break_op (0);
    139       ret = TALER_MHD_reply_with_error (connection,
    140                                         MHD_HTTP_BAD_REQUEST,
    141                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    142                                         "otp_key");
    143       goto cleanup;
    144     }
    145     break;
    146   case TALER_MCA_NONE:
    147   case TALER_MCA_WITHOUT_PRICE:
    148   case TALER_MCA_WITH_PRICE:
    149     /* the merchant shares these with the OTP application itself */
    150     if (NULL == tp.otp_key)
    151     {
    152       GNUNET_break_op (0);
    153       ret = TALER_MHD_reply_with_error (connection,
    154                                         MHD_HTTP_BAD_REQUEST,
    155                                         TALER_EC_GENERIC_PARAMETER_MISSING,
    156                                         "otp_key");
    157       goto cleanup;
    158     }
    159     break;
    160   }
    161 
    162   /* finally, interact with DB until no serialization error */
    163   for (unsigned int i = 0; i<MAX_RETRIES; i++)
    164   {
    165     /* Test if a OTP device of this id is known */
    166     struct TALER_MERCHANTDB_OtpDeviceDetails etp;
    167 
    168     if (GNUNET_OK !=
    169         TALER_MERCHANTDB_start (TMH_db,
    170                                 "/post otp-devices"))
    171     {
    172       GNUNET_break (0);
    173       ret = TALER_MHD_reply_with_error (connection,
    174                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    175                                         TALER_EC_GENERIC_DB_START_FAILED,
    176                                         NULL);
    177       goto cleanup;
    178     }
    179     qs = TALER_MERCHANTDB_get_otp_device (TMH_db,
    180                                           mi->settings.id,
    181                                           device_id,
    182                                           &etp);
    183     switch (qs)
    184     {
    185     case GNUNET_DB_STATUS_HARD_ERROR:
    186       /* Clean up and fail hard */
    187       GNUNET_break (0);
    188       TALER_MERCHANTDB_rollback (TMH_db);
    189       ret = TALER_MHD_reply_with_error (connection,
    190                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    191                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    192                                         NULL);
    193       goto cleanup;
    194     case GNUNET_DB_STATUS_SOFT_ERROR:
    195       /* restart transaction */
    196       goto retry;
    197     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    198       /* Good, we can proceed! */
    199       break;
    200     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    201       /* idempotency check: is etp == tp? */
    202       {
    203         bool eq;
    204 
    205         eq = otp_devices_equal (&tp,
    206                                 &etp);
    207         GNUNET_free (etp.otp_description);
    208         GNUNET_free (etp.otp_key);
    209         TALER_MERCHANTDB_rollback (TMH_db);
    210         if (! eq)
    211           ret = TALER_MHD_reply_with_error (
    212             connection,
    213             MHD_HTTP_CONFLICT,
    214             TALER_EC_MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS,
    215             device_id);
    216         else if (NULL != etp.otp_device_pub)
    217           /* repeating the request returns the key generated the first
    218              time; the merchant is shown it once per request, never a
    219              new one */
    220           ret = TALER_MHD_REPLY_JSON_PACK (
    221             connection,
    222             MHD_HTTP_OK,
    223             GNUNET_JSON_pack_string ("otp_device_pub",
    224                                      etp.otp_device_pub));
    225         else
    226           ret = TALER_MHD_reply_static (connection,
    227                                         MHD_HTTP_NO_CONTENT,
    228                                         NULL,
    229                                         NULL,
    230                                         0);
    231         GNUNET_free (etp.otp_device_pub);
    232         goto cleanup;
    233       }
    234     } /* end switch (qs) */
    235 
    236     switch (tp.otp_algorithm)
    237     {
    238     case TALER_MCA_ECDSA_CHALLENGE:
    239     case TALER_MCA_EDDSA_CHALLENGE:
    240       /* an insert that hit a serialization failure brings us back here
    241          with the pair of the previous round still in hand */
    242       if (NULL != tp.otp_device_pub)
    243         break;
    244       if (GNUNET_OK !=
    245           TALER_otp_device_key_create (tp.otp_algorithm,
    246                                        &tp.otp_key,
    247                                        &tp.otp_device_pub))
    248       {
    249         GNUNET_break (0);
    250         TALER_MERCHANTDB_rollback (TMH_db);
    251         ret = TALER_MHD_reply_with_error (
    252           connection,
    253           MHD_HTTP_INTERNAL_SERVER_ERROR,
    254           TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    255           "failed to generate the OTP device key pair");
    256         goto cleanup;
    257       }
    258       generated_keys = true;
    259       break;
    260     default:
    261       /* the TOTP algorithms use the key the merchant supplied */
    262       break;
    263     }
    264 
    265     qs = TALER_MERCHANTDB_insert_otp_device (TMH_db,
    266                                              mi->settings.id,
    267                                              device_id,
    268                                              &tp);
    269     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    270     {
    271       TALER_MERCHANTDB_rollback (TMH_db);
    272       break;
    273     }
    274     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    275     {
    276       /* A concurrent request created an OTP device with the same ID
    277          between our lookup above and this INSERT. */
    278       TALER_MERCHANTDB_rollback (TMH_db);
    279       ret = TALER_MHD_reply_with_error (
    280         connection,
    281         MHD_HTTP_CONFLICT,
    282         TALER_EC_MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS,
    283         device_id);
    284       goto cleanup;
    285     }
    286     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    287     {
    288       qs = TALER_MERCHANTDB_commit (TMH_db);
    289       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    290         break;
    291     }
    292 retry:
    293     GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    294     TALER_MERCHANTDB_rollback (TMH_db);
    295   } /* for RETRIES loop */
    296   if (qs < 0)
    297   {
    298     GNUNET_break (0);
    299     ret = TALER_MHD_reply_with_error (
    300       connection,
    301       MHD_HTTP_INTERNAL_SERVER_ERROR,
    302       (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    303       ? TALER_EC_GENERIC_DB_SOFT_FAILURE
    304       : TALER_EC_GENERIC_DB_COMMIT_FAILED,
    305       NULL);
    306     goto cleanup;
    307   }
    308   if (NULL != tp.otp_device_pub)
    309   {
    310     ret = TALER_MHD_REPLY_JSON_PACK (
    311       connection,
    312       MHD_HTTP_OK,
    313       GNUNET_JSON_pack_string ("otp_device_pub",
    314                                tp.otp_device_pub));
    315   }
    316   else
    317     ret = TALER_MHD_reply_static (connection,
    318                                   MHD_HTTP_NO_CONTENT,
    319                                   NULL,
    320                                   NULL,
    321                                   0);
    322 cleanup:
    323   /* TOTP keys are borrowed from the request JSON.  Only a successfully
    324      generated challenge key pair belongs to this handler. */
    325   if (generated_keys)
    326   {
    327     GNUNET_free (tp.otp_key);
    328     GNUNET_free (tp.otp_device_pub);
    329   }
    330   GNUNET_JSON_parse_free (spec);
    331   return ret;
    332 }
    333 
    334 
    335 /* end of taler-merchant-httpd_post-private-otp-devices.c */