merchant

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

taler-merchant-httpd_patch-private-otp-devices-DEVICE_ID.c (8184B)


      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_patch-private-otp-devices-DEVICE_ID.c
     22  * @brief implementing PATCH /otp-devices/$ID request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_patch-private-otp-devices-DEVICE_ID.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 #include "merchant-database/update_otp_device.h"
     30 #include "merchant-database/get_otp_device.h"
     31 
     32 
     33 /**
     34  * Does @a alg confirm payments by signing a challenge with a key pair
     35  * generated by the backend?
     36  *
     37  * @param alg algorithm to check
     38  * @return true for the challenge-signature algorithms
     39  */
     40 static bool
     41 is_challenge_alg (enum TALER_MerchantConfirmationAlgorithm alg)
     42 {
     43   return ( (TALER_MCA_ECDSA_CHALLENGE == alg) ||
     44            (TALER_MCA_EDDSA_CHALLENGE == alg) );
     45 }
     46 
     47 
     48 enum MHD_Result
     49 TMH_private_patch_otp_devices_ID (const struct TMH_RequestHandler *rh,
     50                                   struct MHD_Connection *connection,
     51                                   struct TMH_HandlerContext *hc)
     52 {
     53   struct TMH_MerchantInstance *mi = hc->instance;
     54   const char *device_id = hc->infix;
     55   struct TALER_MERCHANTDB_OtpDeviceDetails tp = {0};
     56   uint64_t expected_serial;
     57   enum TALER_MerchantConfirmationAlgorithm expected_algorithm;
     58   enum GNUNET_DB_QueryStatus qs;
     59   struct GNUNET_JSON_Specification spec[] = {
     60     GNUNET_JSON_spec_string ("otp_device_description",
     61                              (const char **) &tp.otp_description),
     62     TALER_JSON_spec_otp_type ("otp_algorithm",
     63                               &tp.otp_algorithm),
     64     GNUNET_JSON_spec_mark_optional (
     65       GNUNET_JSON_spec_uint64 ("otp_ctr",
     66                                &tp.otp_ctr),
     67       NULL),
     68     GNUNET_JSON_spec_mark_optional (
     69 
     70       TALER_JSON_spec_otp_key ("otp_key",
     71                                (const char **) &tp.otp_key),
     72       NULL),
     73     GNUNET_JSON_spec_end ()
     74   };
     75 
     76   GNUNET_assert (NULL != mi);
     77   GNUNET_assert (NULL != device_id);
     78   {
     79     enum GNUNET_GenericReturnValue res;
     80 
     81     res = TALER_MHD_parse_json_data (connection,
     82                                      hc->request_body,
     83                                      spec);
     84     if (GNUNET_OK != res)
     85       return (GNUNET_NO == res)
     86              ? MHD_YES
     87              : MHD_NO;
     88   }
     89   /* returned to (JavaScript) clients as a JSON number, so it must be
     90      a "SafeUint64" (2^53 - 1), see api-common.rst */
     91   if (tp.otp_ctr > (1LLU << 53) - 1)
     92   {
     93     GNUNET_break_op (0);
     94     return TALER_MHD_reply_with_error (connection,
     95                                        MHD_HTTP_BAD_REQUEST,
     96                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     97                                        "otp_ctr");
     98   }
     99 
    100   switch (tp.otp_algorithm)
    101   {
    102   case TALER_MCA_ECDSA_CHALLENGE:
    103   case TALER_MCA_EDDSA_CHALLENGE:
    104     /* the key pair belongs to the backend; there is nothing here for a
    105        client to set, and replacing it would silently invalidate every
    106        offline verifier already configured with the public key */
    107     if (NULL != tp.otp_key)
    108     {
    109       GNUNET_break_op (0);
    110       return TALER_MHD_reply_with_error (connection,
    111                                          MHD_HTTP_BAD_REQUEST,
    112                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    113                                          "otp_key");
    114     }
    115     break;
    116   case TALER_MCA_NONE:
    117   case TALER_MCA_WITHOUT_PRICE:
    118   case TALER_MCA_WITH_PRICE:
    119     break;
    120   }
    121 
    122   {
    123     struct TALER_MERCHANTDB_OtpDeviceDetails etp;
    124     bool incompatible = false;
    125 
    126     qs = TALER_MERCHANTDB_get_otp_device (TMH_db,
    127                                           mi->settings.id,
    128                                           device_id,
    129                                           &etp);
    130     switch (qs)
    131     {
    132     case GNUNET_DB_STATUS_HARD_ERROR:
    133     case GNUNET_DB_STATUS_SOFT_ERROR:
    134       GNUNET_break (0);
    135       GNUNET_JSON_parse_free (spec);
    136       return TALER_MHD_reply_with_error (connection,
    137                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    138                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    139                                          "get_otp_device");
    140     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    141       GNUNET_JSON_parse_free (spec);
    142       return TALER_MHD_reply_with_error (connection,
    143                                          MHD_HTTP_NOT_FOUND,
    144                                          TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
    145                                          device_id);
    146     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    147       expected_serial = etp.otp_serial;
    148       expected_algorithm = etp.otp_algorithm;
    149       incompatible = (etp.otp_algorithm != tp.otp_algorithm) &&
    150                      (is_challenge_alg (etp.otp_algorithm) ||
    151                       is_challenge_alg (tp.otp_algorithm));
    152       GNUNET_free (etp.otp_description);
    153       GNUNET_free (etp.otp_key);
    154       GNUNET_free (etp.otp_device_pub);
    155       break;
    156     }
    157     if (incompatible)
    158     {
    159       /* Challenge keys belong to a specific algorithm and cannot be
    160          reused with another challenge algorithm or with TOTP.  Recreate
    161          the device to change its algorithm. */
    162       GNUNET_break_op (0);
    163       GNUNET_JSON_parse_free (spec);
    164       return TALER_MHD_reply_with_error (connection,
    165                                          MHD_HTTP_CONFLICT,
    166                                          TALER_EC_MERCHANT_PRIVATE_PATCH_OTP_DEVICES_CONFLICT,
    167                                          "otp_algorithm");
    168     }
    169   }
    170 
    171   qs = TALER_MERCHANTDB_update_otp_device (TMH_db,
    172                                            mi->settings.id,
    173                                            device_id,
    174                                            expected_serial,
    175                                            expected_algorithm,
    176                                            &tp);
    177   {
    178     enum MHD_Result ret = MHD_NO;
    179 
    180     switch (qs)
    181     {
    182     case GNUNET_DB_STATUS_HARD_ERROR:
    183       GNUNET_break (0);
    184       ret = TALER_MHD_reply_with_error (connection,
    185                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    186                                         TALER_EC_GENERIC_DB_STORE_FAILED,
    187                                         "update_pos");
    188       break;
    189     case GNUNET_DB_STATUS_SOFT_ERROR:
    190       GNUNET_break (0);
    191       ret = TALER_MHD_reply_with_error (connection,
    192                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    193                                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    194                                         "unexpected serialization problem");
    195       break;
    196     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    197       /* Validation no longer describes this device.  The conditional
    198          UPDATE left the concurrent writer's device untouched. */
    199       ret = TALER_MHD_reply_with_error (connection,
    200                                         MHD_HTTP_CONFLICT,
    201                                         TALER_EC_MERCHANT_PRIVATE_PATCH_OTP_DEVICES_CONFLICT,
    202                                         "OTP device changed during update");
    203       break;
    204     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    205       ret = TALER_MHD_reply_static (connection,
    206                                     MHD_HTTP_NO_CONTENT,
    207                                     NULL,
    208                                     NULL,
    209                                     0);
    210       break;
    211     }
    212     GNUNET_JSON_parse_free (spec);
    213     return ret;
    214   }
    215 }
    216 
    217 
    218 /* end of taler-merchant-httpd_patch-private-otp-devices-DEVICE_ID.c */