taler-exchange-httpd_common_deposit.c (9959B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_common_deposit.c 18 * @brief shared logic for handling deposited coins 19 * @author Christian Grothoff 20 */ 21 #include "taler-exchange-httpd_common_deposit.h" 22 #include "taler-exchange-httpd.h" 23 #include "taler-exchange-httpd_get-keys.h" 24 25 26 enum GNUNET_GenericReturnValue 27 TEH_common_purse_deposit_parse_coin ( 28 struct MHD_Connection *connection, 29 struct TEH_PurseDepositedCoin *coin, 30 const json_t *jcoin) 31 { 32 struct GNUNET_JSON_Specification spec[] = { 33 TALER_JSON_spec_amount ("amount", 34 TEH_currency, 35 &coin->amount), 36 GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", 37 &coin->cpi.denom_pub_hash), 38 TALER_JSON_spec_denom_sig ("ub_sig", 39 &coin->cpi.denom_sig), 40 GNUNET_JSON_spec_mark_optional ( 41 GNUNET_JSON_spec_fixed_auto ("attest", 42 &coin->attest), 43 &coin->no_attest), 44 GNUNET_JSON_spec_mark_optional ( 45 TALER_JSON_spec_age_commitment ("age_commitment", 46 &coin->age_commitment), 47 &coin->cpi.no_age_commitment), 48 GNUNET_JSON_spec_fixed_auto ("coin_sig", 49 &coin->coin_sig), 50 GNUNET_JSON_spec_fixed_auto ("coin_pub", 51 &coin->cpi.coin_pub), 52 GNUNET_JSON_spec_end () 53 }; 54 55 memset (coin, 56 0, 57 sizeof (*coin)); 58 coin->cpi.no_age_commitment = true; 59 coin->no_attest = true; 60 { 61 enum GNUNET_GenericReturnValue res; 62 63 res = TALER_MHD_parse_json_data (connection, 64 jcoin, 65 spec); 66 if (GNUNET_OK != res) 67 return res; 68 } 69 70 /* check denomination exists and is valid */ 71 { 72 struct TEH_DenominationKey *dk; 73 enum MHD_Result mret; 74 75 dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash, 76 connection, 77 &mret); 78 if (NULL == dk) 79 { 80 GNUNET_JSON_parse_free (spec); 81 return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR; 82 } 83 if (! coin->cpi.no_age_commitment) 84 { 85 coin->age_commitment.mask = dk->meta.age_mask; 86 TALER_age_commitment_hash (&coin->age_commitment, 87 &coin->cpi.h_age_commitment); 88 } 89 if (0 > TALER_amount_cmp (&dk->meta.value, 90 &coin->amount)) 91 { 92 GNUNET_break_op (0); 93 GNUNET_JSON_parse_free (spec); 94 return (MHD_YES == 95 TALER_MHD_reply_with_error (connection, 96 MHD_HTTP_BAD_REQUEST, 97 TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, 98 NULL)) 99 ? GNUNET_NO : GNUNET_SYSERR; 100 } 101 if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) 102 { 103 /* This denomination is past the expiration time for deposits */ 104 GNUNET_JSON_parse_free (spec); 105 return (MHD_YES == 106 TEH_RESPONSE_reply_expired_denom_pub_hash ( 107 connection, 108 &coin->cpi.denom_pub_hash, 109 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, 110 "PURSE CREATE")) 111 ? GNUNET_NO : GNUNET_SYSERR; 112 } 113 if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) 114 { 115 /* This denomination is not yet valid */ 116 GNUNET_JSON_parse_free (spec); 117 return (MHD_YES == 118 TEH_RESPONSE_reply_expired_denom_pub_hash ( 119 connection, 120 &coin->cpi.denom_pub_hash, 121 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, 122 "PURSE CREATE")) 123 ? GNUNET_NO : GNUNET_SYSERR; 124 } 125 if (dk->recoup_possible) 126 { 127 /* This denomination has been revoked */ 128 GNUNET_JSON_parse_free (spec); 129 return (MHD_YES == 130 TEH_RESPONSE_reply_expired_denom_pub_hash ( 131 connection, 132 &coin->cpi.denom_pub_hash, 133 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, 134 "PURSE CREATE")) 135 ? GNUNET_NO : GNUNET_SYSERR; 136 } 137 if (dk->denom_pub.bsign_pub_key->cipher != 138 coin->cpi.denom_sig.unblinded_sig->cipher) 139 { 140 /* denomination cipher and denomination signature cipher not the same */ 141 GNUNET_JSON_parse_free (spec); 142 return (MHD_YES == 143 TALER_MHD_reply_with_error (connection, 144 MHD_HTTP_BAD_REQUEST, 145 TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, 146 NULL)) 147 ? GNUNET_NO : GNUNET_SYSERR; 148 } 149 150 coin->deposit_fee = dk->meta.fees.deposit; 151 if (0 < TALER_amount_cmp (&coin->deposit_fee, 152 &coin->amount)) 153 { 154 GNUNET_break_op (0); 155 GNUNET_JSON_parse_free (spec); 156 return (MHD_YES == 157 TALER_MHD_reply_with_error (connection, 158 MHD_HTTP_BAD_REQUEST, 159 TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, 160 NULL)) 161 ? GNUNET_NO : GNUNET_SYSERR; 162 } 163 GNUNET_assert (0 <= 164 TALER_amount_subtract (&coin->amount_minus_fee, 165 &coin->amount, 166 &coin->deposit_fee)); 167 168 /* check coin signature */ 169 switch (dk->denom_pub.bsign_pub_key->cipher) 170 { 171 case GNUNET_CRYPTO_BSA_RSA: 172 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; 173 break; 174 case GNUNET_CRYPTO_BSA_CS: 175 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; 176 break; 177 default: 178 break; 179 } 180 if (GNUNET_YES != 181 TALER_test_coin_valid (&coin->cpi, 182 &dk->denom_pub)) 183 { 184 TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); 185 GNUNET_JSON_parse_free (spec); 186 return (MHD_YES == 187 TALER_MHD_reply_with_error (connection, 188 MHD_HTTP_FORBIDDEN, 189 TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, 190 NULL)) 191 ? GNUNET_NO : GNUNET_SYSERR; 192 } 193 } 194 return GNUNET_OK; 195 } 196 197 198 enum GNUNET_GenericReturnValue 199 TEH_common_deposit_check_purse_deposit ( 200 struct MHD_Connection *connection, 201 const struct TEH_PurseDepositedCoin *coin, 202 const struct TALER_PurseContractPublicKeyP *purse_pub, 203 uint32_t min_age) 204 { 205 if (GNUNET_OK != 206 TALER_wallet_purse_deposit_verify (TEH_base_url, 207 purse_pub, 208 &coin->amount, 209 &coin->cpi.denom_pub_hash, 210 &coin->cpi.h_age_commitment, 211 &coin->cpi.coin_pub, 212 &coin->coin_sig)) 213 { 214 TALER_LOG_WARNING ( 215 "Invalid coin signature to deposit into purse\n"); 216 return (MHD_YES == 217 TALER_MHD_reply_with_error (connection, 218 MHD_HTTP_FORBIDDEN, 219 TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID, 220 TEH_base_url)) 221 ? GNUNET_NO 222 : GNUNET_SYSERR; 223 } 224 225 if (0 == min_age) 226 return GNUNET_OK; /* no need to apply age checks */ 227 228 /* Check and verify the age restriction. */ 229 if (coin->no_attest != coin->cpi.no_age_commitment) 230 { 231 GNUNET_break_op (0); 232 return (MHD_YES == 233 TALER_MHD_reply_with_error (connection, 234 MHD_HTTP_BAD_REQUEST, 235 TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT, 236 "mismatch of attest and age_commitment") 237 ) 238 ? GNUNET_NO : GNUNET_SYSERR; 239 } 240 241 if (coin->cpi.no_age_commitment) 242 return GNUNET_OK; /* unrestricted coin */ 243 244 /* age attestation must be valid */ 245 if (GNUNET_OK != 246 TALER_age_commitment_verify (&coin->age_commitment, 247 min_age, 248 &coin->attest)) 249 { 250 GNUNET_break_op (0); 251 return (MHD_YES == 252 TALER_MHD_reply_with_error (connection, 253 MHD_HTTP_BAD_REQUEST, 254 TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE, 255 "invalid attest for minimum age")) 256 ? GNUNET_NO : GNUNET_SYSERR; 257 } 258 return GNUNET_OK; 259 } 260 261 262 /** 263 * Release data structures of @a coin. Note that 264 * @a coin itself is NOT freed. 265 * 266 * @param[in] coin information to release 267 */ 268 void 269 TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin) 270 { 271 TALER_denom_sig_free (&coin->cpi.denom_sig); 272 if (! coin->cpi.no_age_commitment) 273 GNUNET_free (coin->age_commitment.pubs); /* Only the keys have been allocated */ 274 }