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 */