/* This file is part of TALER (C) 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 */ /** * @file taler-merchant-httpd_contract.c * @brief shared logic for contract terms handling * @author Christian Blättler */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_contract.h" enum TALER_MerchantContractInputType TMH_contract_input_type_from_string (const char *str) { /* For now, only 'token' is the only supported option. */ if (0 == strcmp ("token", str)) { return TALER_MCIT_TOKEN; } return TALER_MCIT_INVALID; } enum TALER_MerchantContractOutputType TMH_contract_output_type_from_string (const char *str) { /* For now, only 'token' is the only supported option. */ if (0 == strcmp ("token", str)) { return TALER_MCOT_TOKEN; } return TALER_MCOT_INVALID; } const char * TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t) { switch (t) { case TALER_MCIT_TOKEN: return "token"; case TALER_MCIT_COIN: return "coin"; default: return "invalid"; } } const char * TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t) { switch (t) { case TALER_MCOT_TOKEN: return "token"; case TALER_MCOT_COIN: return "coin"; case TALER_MCOT_TAX_RECEIPT: return "tax_receipt"; default: return "invalid"; } } /** * Parse given JSON object to choices array. * * @param cls closure, pointer to array length * @param root the json array representing the choices * @param[out] ospec where to write the data * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ static enum GNUNET_GenericReturnValue parse_choices (void *cls, json_t *root, struct GNUNET_JSON_Specification *ospec) { struct TALER_MerchantContractChoice **choices = ospec->ptr; unsigned int *choices_len = cls; if (! json_is_array (root)) { GNUNET_break_op (0); return GNUNET_SYSERR; } GNUNET_array_grow (*choices, *choices_len, json_array_size (root)); for (unsigned int i = 0; i<*choices_len; i++) { const json_t *jinputs; const json_t *joutputs; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("inputs", &jinputs), GNUNET_JSON_spec_array_const ("outputs", &joutputs), GNUNET_JSON_spec_end () }; const char *error_name; unsigned int error_line; struct TALER_MerchantContractChoice *choice = &(*choices)[i]; if (GNUNET_OK != GNUNET_JSON_parse (json_array_get (root, i), spec, &error_name, &error_line)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s at %u: %s\n", spec[error_line].field, error_line, error_name); GNUNET_break_op (0); return GNUNET_SYSERR; } { const json_t *jinput; size_t idx; json_array_foreach ((json_t *) jinputs, idx, jinput) { struct TALER_MerchantContractInput input = {.details.token.count = 1}; const char *kind; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &input.details.token.token_family_slug), GNUNET_JSON_spec_timestamp ("valid_after", &input.details.token.valid_after), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &input.details.token.count), NULL), GNUNET_JSON_spec_end () }; const char *ierror_name; unsigned int ierror_line; if (GNUNET_OK != GNUNET_JSON_parse (jinput, ispec, &ierror_name, &ierror_line)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s at %u: %s\n", ispec[ierror_line].field, ierror_line, ierror_name); GNUNET_break_op (0); return GNUNET_SYSERR; } input.type = TMH_contract_input_type_from_string (kind); if (TALER_MCIT_INVALID == input.type) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Field 'kind' invalid in input #%u\n", (unsigned int) idx); GNUNET_break_op (0); return GNUNET_SYSERR; } if (0 == input.details.token.count) { /* Ignore inputs with 'number' field set to 0 */ continue; } GNUNET_array_append (choice->inputs, choice->inputs_len, input); } } { const json_t *joutput; size_t idx; json_array_foreach ((json_t *) joutputs, idx, joutput) { struct TALER_MerchantContractOutput output = { .details.token.count = 1 }; const char *kind; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &output.details.token.token_family_slug), GNUNET_JSON_spec_timestamp ("valid_after", &output.details.token.valid_after), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &output.details.token.count), NULL), GNUNET_JSON_spec_end () }; const char *ierror_name; unsigned int ierror_line; if (GNUNET_OK != GNUNET_JSON_parse (joutput, ispec, &ierror_name, &ierror_line)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s at %u: %s\n", ispec[ierror_line].field, ierror_line, ierror_name); GNUNET_break_op (0); return GNUNET_SYSERR; } output.type = TMH_contract_output_type_from_string (kind); if (TALER_MCOT_INVALID == output.type) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Field 'kind' invalid in output #%u\n", (unsigned int) idx); GNUNET_break_op (0); return GNUNET_SYSERR; } if (0 == output.details.token.count) { /* Ignore outputs with 'number' field set to 0 */ continue; } GNUNET_array_append (choice->outputs, choice->outputs_len, output); } } } return GNUNET_OK; } struct GNUNET_JSON_Specification TALER_JSON_spec_choices (const char *name, struct TALER_MerchantContractChoice **choices, unsigned int *choices_len) { struct GNUNET_JSON_Specification ret = { .cls = (void *) choices_len, .parser = &parse_choices, .field = name, .ptr = choices, }; return ret; } /** * Parse given JSON object to token families array. * * @param cls closure, pointer to array length * @param root the json object representing the token families. The keys are * the token family slugs. * @param[out] ospec where to write the data * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ static enum GNUNET_GenericReturnValue parse_token_families (void *cls, json_t *root, struct GNUNET_JSON_Specification *ospec) { struct TALER_MerchantContractTokenFamily **families = ospec->ptr; unsigned int *families_len = cls; json_t *jfamily; const char *slug; if (! json_is_object (root)) { GNUNET_break_op (0); return GNUNET_SYSERR; } json_object_foreach (root, slug, jfamily) { const json_t *keys; struct TALER_MerchantContractTokenFamily family = { .slug = slug }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("keys", &keys), GNUNET_JSON_spec_bool ("critical", &family.critical), GNUNET_JSON_spec_end () /* TODO: Figure out if these fields should be 'const' */ // GNUNET_JSON_spec_string ("description", // &family.description), // GNUNET_JSON_spec_object_const ("description_i18n", // &family.description_i18n), }; const char *error_name; unsigned int error_line; if (GNUNET_OK != GNUNET_JSON_parse (jfamily, spec, &error_name, &error_line)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s at %u: %s\n", spec[error_line].field, error_line, error_name); GNUNET_break_op (0); return GNUNET_SYSERR; } GNUNET_array_grow (family.keys, family.keys_len, json_array_size (keys)); for (unsigned int i = 0; ipub.public_key = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); { struct GNUNET_JSON_Specification key_spec[] = { GNUNET_JSON_spec_fixed_auto ( "h_pub", &key->pub.public_key->pub_key_hash), GNUNET_JSON_spec_rsa_public_key ( "rsa_pub", &key->pub.public_key->details.rsa_public_key), // FIXME: sort out CS here! // GNUNET_JSON_spec_fixed_auto ("cs_pub", // &key.pub.public_key->details.cs_public_key)), GNUNET_JSON_spec_int64 ( "cipher", &cipher), GNUNET_JSON_spec_timestamp ( "valid_after", &key->valid_after), GNUNET_JSON_spec_timestamp ( "valid_before", &key->valid_before), GNUNET_JSON_spec_end () }; const char *ierror_name; unsigned int ierror_line; if (GNUNET_OK != GNUNET_JSON_parse (json_array_get (keys, i), key_spec, &ierror_name, &ierror_line)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s at %u: %s\n", key_spec[ierror_line].field, ierror_line, ierror_name); GNUNET_break_op (0); return GNUNET_SYSERR; } } switch (cipher) { case GNUNET_CRYPTO_BSA_RSA: key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA; break; case GNUNET_CRYPTO_BSA_CS: key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_CS; break; case GNUNET_CRYPTO_BSA_INVALID: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Field 'cipher' invalid in key #%u\n", i); GNUNET_break_op (0); return GNUNET_SYSERR; } } GNUNET_array_append (*families, *families_len, family); } return GNUNET_OK; } struct GNUNET_JSON_Specification TALER_JSON_spec_token_families ( const char *name, struct TALER_MerchantContractTokenFamily **families, unsigned int *families_len) { struct GNUNET_JSON_Specification ret = { .cls = (void *) families_len, .parser = &parse_token_families, .field = name, .ptr = families, }; return ret; } enum GNUNET_GenericReturnValue TMH_find_token_family_key ( const char *slug, struct GNUNET_TIME_Timestamp valid_after, const struct TALER_MerchantContractTokenFamily *families, unsigned int families_len, struct TALER_MerchantContractTokenFamily *family, struct TALER_MerchantContractTokenFamilyKey *key) { for (unsigned int i = 0; i < families_len; i++) { const struct TALER_MerchantContractTokenFamily *fami = &families[i]; if (0 != strcmp (fami->slug, slug)) continue; if (NULL != family) *family = *fami; for (unsigned int k = 0; k < fami->keys_len; k++) { struct TALER_MerchantContractTokenFamilyKey *ki = &fami->keys[k]; if (GNUNET_TIME_timestamp_cmp (ki->valid_after, ==, valid_after)) { if (NULL != key) *key = *ki; return GNUNET_OK; } } /* matching family found, but no key. */ return GNUNET_NO; } /* no matching family found */ return GNUNET_NO; }