/* This file is part of TALER Copyright (C) 2014, 2015, 2016, 2020, 2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU 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 json/json.c * @brief helper functions for JSON processing using libjansson * @author Sree Harsha Totakura * @author Christian Grothoff */ #include "platform.h" #include #include "taler_util.h" #include "taler_json_lib.h" #include /** * Check if @a json contains a 'real' value anywhere. * * @param json json to check * @return true if a real is in it somewhere */ static bool contains_real (const json_t *json) { if (json_is_real (json)) return true; if (json_is_object (json)) { json_t *member; const char *name; json_object_foreach ((json_t *) json, name, member) if (contains_real (member)) return true; return false; } if (json_is_array (json)) { json_t *member; size_t index; json_array_foreach ((json_t *) json, index, member) if (contains_real (member)) return true; return false; } return false; } /** * Dump character in the low range into @a buf * following RFC 8785. * * @param[in,out] buf buffer to modify * @param val value to dump */ static void lowdump (struct GNUNET_Buffer *buf, unsigned char val) { char scratch[7]; switch (val) { case 0x8: GNUNET_buffer_write (buf, "\b", 2); break; case 0x9: GNUNET_buffer_write (buf, "\t", 2); break; case 0xA: GNUNET_buffer_write (buf, "\n", 2); break; case 0xC: GNUNET_buffer_write (buf, "\f", 2); break; case 0xD: GNUNET_buffer_write (buf, "\r", 2); break; default: GNUNET_snprintf (scratch, sizeof (scratch), "\\u%04x", (unsigned int) val); GNUNET_buffer_write (buf, scratch, 6); break; } } /** * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2). * * @param[in,out] inp pointer to string to re-encode * @return number of bytes in resulting @a inp */ static size_t rfc8785encode (char **inp) { struct GNUNET_Buffer buf = { 0 }; size_t left = strlen (*inp) + 1; size_t olen; char *in = *inp; const char *pos = in; GNUNET_buffer_prealloc (&buf, left + 40); buf.warn_grow = 0; /* disable, + 40 is just a wild guess */ while (1) { int mbl = u8_mblen ((unsigned char *) pos, left); unsigned char val; if (0 == mbl) break; val = (unsigned char) *pos; if ( (1 == mbl) && (val <= 0x1F) ) { lowdump (&buf, val); } else if ( (1 == mbl) && ('\\' == *pos) ) { switch (*(pos + 1)) { case '\\': mbl = 2; GNUNET_buffer_write (&buf, pos, mbl); break; case 'u': { unsigned int num; uint32_t n32; unsigned char res[8]; size_t rlen; GNUNET_assert ( (1 == sscanf (pos + 2, "%4x", &num)) || (1 == sscanf (pos + 2, "%4X", &num)) ); mbl = 6; n32 = (uint32_t) num; rlen = sizeof (res); u32_to_u8 (&n32, 1, res, &rlen); if ( (1 == rlen) && (res[0] <= 0x1F) ) { lowdump (&buf, res[0]); } else { GNUNET_buffer_write (&buf, (const char *) res, rlen); } } break; } } else { GNUNET_buffer_write (&buf, pos, mbl); } left -= mbl; pos += mbl; } /* 0-terminate buffer */ GNUNET_buffer_write (&buf, "", 1); GNUNET_free (in); *inp = GNUNET_buffer_reap (&buf, &olen); return olen; } /** * Dump the @a json to a string and hash it. * * @param json value to hash * @param salt salt value to include when using HKDF, * NULL to not use any salt and to use SHA512 * @param[out] hc where to store the hash * @return #GNUNET_OK on success, * #GNUNET_NO if @a json was not hash-able * #GNUNET_SYSERR on failure */ static enum GNUNET_GenericReturnValue dump_and_hash (const json_t *json, const char *salt, struct GNUNET_HashCode *hc) { char *wire_enc; size_t len; if (NULL == json) { GNUNET_break_op (0); return GNUNET_NO; } if (contains_real (json)) { GNUNET_break_op (0); return GNUNET_NO; } if (NULL == (wire_enc = json_dumps (json, JSON_ENCODE_ANY | JSON_COMPACT | JSON_SORT_KEYS))) { GNUNET_break (0); return GNUNET_SYSERR; } len = rfc8785encode (&wire_enc); if (NULL == salt) { GNUNET_CRYPTO_hash (wire_enc, len, hc); } else { if (GNUNET_YES != GNUNET_CRYPTO_kdf (hc, sizeof (*hc), salt, strlen (salt) + 1, wire_enc, len, NULL, 0)) { GNUNET_break (0); free (wire_enc); return GNUNET_SYSERR; } } free (wire_enc); return GNUNET_OK; } /** * Replace "forgettable" parts of a JSON object with their salted hash. * * @param[in] in some JSON value * @param[out] out resulting JSON value * @return #GNUNET_OK on success, * #GNUNET_NO if @a json was not hash-able * #GNUNET_SYSERR on failure */ static enum GNUNET_GenericReturnValue forget (const json_t *in, json_t **out) { if (json_is_real (in)) { /* floating point is not allowed! */ GNUNET_break_op (0); return GNUNET_NO; } if (json_is_array (in)) { /* array is a JSON array */ size_t index; json_t *value; json_t *ret; ret = json_array (); if (NULL == ret) { GNUNET_break (0); return GNUNET_SYSERR; } json_array_foreach (in, index, value) { enum GNUNET_GenericReturnValue iret; json_t *t; iret = forget (value, &t); if (GNUNET_OK != iret) { json_decref (ret); return iret; } if (0 != json_array_append_new (ret, t)) { GNUNET_break (0); json_decref (ret); return GNUNET_SYSERR; } } *out = ret; return GNUNET_OK; } if (json_is_object (in)) { json_t *ret; const char *key; json_t *value; json_t *fg; json_t *rx; fg = json_object_get (in, "$forgettable"); rx = json_object_get (in, "$forgotten"); if (NULL != rx) { rx = json_deep_copy (rx); /* should be shallow by structure, but deep copy is safer */ if (NULL == rx) { GNUNET_break (0); return GNUNET_SYSERR; } } ret = json_object (); if (NULL == ret) { GNUNET_break (0); return GNUNET_SYSERR; } json_object_foreach ((json_t*) in, key, value) { json_t *t; json_t *salt; enum GNUNET_GenericReturnValue iret; if (fg == value) continue; /* skip! */ if (rx == value) continue; /* skip! */ if ( (NULL != rx) && (NULL != json_object_get (rx, key)) ) { (void) json_object_del (ret, key); continue; /* already forgotten earlier */ } iret = forget (value, &t); if (GNUNET_OK != iret) { json_decref (ret); json_decref (rx); return iret; } if ( (NULL != fg) && (NULL != (salt = json_object_get (fg, key))) ) { /* 't' is to be forgotten! */ struct GNUNET_HashCode hc; if (! json_is_string (salt)) { GNUNET_break_op (0); json_decref (ret); json_decref (rx); json_decref (t); return GNUNET_NO; } iret = dump_and_hash (t, json_string_value (salt), &hc); if (GNUNET_OK != iret) { json_decref (ret); json_decref (rx); json_decref (t); return iret; } json_decref (t); /* scrub salt */ if (0 != json_object_del (fg, key)) { GNUNET_break_op (0); json_decref (ret); json_decref (rx); return GNUNET_NO; } if (NULL == rx) rx = json_object (); if (NULL == rx) { GNUNET_break (0); json_decref (ret); return GNUNET_SYSERR; } if (0 != json_object_set_new (rx, key, GNUNET_JSON_from_data_auto (&hc))) { GNUNET_break (0); json_decref (ret); json_decref (rx); return GNUNET_SYSERR; } } else { /* 't' to be used without 'forgetting' */ if (0 != json_object_set_new (ret, key, t)) { GNUNET_break (0); json_decref (ret); json_decref (rx); return GNUNET_SYSERR; } } } /* json_object_foreach */ if ( (NULL != rx) && (0 != json_object_set_new (ret, "$forgotten", rx)) ) { GNUNET_break (0); json_decref (ret); return GNUNET_SYSERR; } *out = ret; return GNUNET_OK; } *out = json_incref ((json_t *) in); return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_JSON_contract_hash (const json_t *json, struct TALER_PrivateContractHash *hc) { enum GNUNET_GenericReturnValue ret; json_t *cjson; json_t *dc; dc = json_deep_copy (json); ret = forget (dc, &cjson); json_decref (dc); if (GNUNET_OK != ret) return ret; ret = dump_and_hash (cjson, NULL, &hc->hash); json_decref (cjson); return ret; } enum GNUNET_GenericReturnValue TALER_JSON_contract_mark_forgettable (json_t *json, const char *field) { json_t *fg; struct GNUNET_ShortHashCode salt; if (! json_is_object (json)) { GNUNET_break (0); return GNUNET_SYSERR; } /* check field name is legal for forgettable field */ for (const char *f = field; '\0' != *f; f++) { char c = *f; if ( (c >= 'a') && (c <= 'z') ) continue; if ( (c >= 'A') && (c <= 'Z') ) continue; if ( (c >= '0') && (c <= '9') ) continue; if ('_' == c) continue; GNUNET_break (0); return GNUNET_SYSERR; } if (NULL == json_object_get (json, field)) { /* field must exist */ GNUNET_break (0); return GNUNET_SYSERR; } fg = json_object_get (json, "$forgettable"); if (NULL == fg) { fg = json_object (); if (0 != json_object_set_new (json, "$forgettable", fg)) { GNUNET_break (0); return GNUNET_SYSERR; } } GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &salt, sizeof (salt)); if (0 != json_object_set_new (fg, field, GNUNET_JSON_from_data_auto (&salt))) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_JSON_contract_part_forget (json_t *json, const char *field) { json_t *fg; const json_t *part; json_t *fp; json_t *rx; struct GNUNET_HashCode hc; const char *salt; enum GNUNET_GenericReturnValue ret; if (! json_is_object (json)) { GNUNET_break (0); return GNUNET_SYSERR; } if (NULL == (part = json_object_get (json, field))) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Did not find field `%s' we were asked to forget\n", field); return GNUNET_SYSERR; } fg = json_object_get (json, "$forgettable"); if (NULL == fg) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Did not find '$forgettable' attribute trying to forget field `%s'\n", field); return GNUNET_SYSERR; } rx = json_object_get (json, "$forgotten"); if (NULL == rx) { rx = json_object (); if (0 != json_object_set_new (json, "$forgotten", rx)) { GNUNET_break (0); return GNUNET_SYSERR; } } if (NULL != json_object_get (rx, field)) { if (! json_is_null (json_object_get (json, field))) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Field `%s' market as forgotten, but still exists!\n", field); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Already forgot field `%s'\n", field); return GNUNET_NO; } salt = json_string_value (json_object_get (fg, field)); if (NULL == salt) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Did not find required salt to forget field `%s'\n", field); return GNUNET_SYSERR; } /* need to recursively forget to compute 'hc' */ ret = forget (part, &fp); if (GNUNET_OK != ret) return ret; if (GNUNET_OK != dump_and_hash (fp, salt, &hc)) { json_decref (fp); GNUNET_break (0); return GNUNET_SYSERR; } json_decref (fp); /* drop salt */ if (0 != json_object_del (fg, field)) { json_decref (fp); GNUNET_break (0); return GNUNET_SYSERR; } /* remember field as 'forgotten' */ if (0 != json_object_set_new (rx, field, GNUNET_JSON_from_data_auto (&hc))) { GNUNET_break (0); return GNUNET_SYSERR; } /* finally, set 'forgotten' field to null */ if (0 != json_object_del (json, field)) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Look over all of the values of a '$forgettable' object. Replace 'True' * values with proper random salts. Fails if any forgettable values are * neither 'True' nor valid salts (strings). * * @param[in,out] f JSON to transform * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue seed_forgettable (json_t *f) { const char *key; json_t *val; json_object_foreach (f, key, val) { if (json_is_string (val)) continue; if (json_is_true (val)) { struct GNUNET_ShortHashCode sh; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &sh, sizeof (sh)); if (0 != json_object_set_new (f, key, GNUNET_JSON_from_data_auto (&sh))) { GNUNET_break (0); return GNUNET_SYSERR; } continue; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Forgettable field `%s' has invalid value\n", key); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Take a given contract with "forgettable" fields marked * but with 'True' instead of a real salt. Replaces all * 'True' values with proper random salts. Fails if any * forgettable markers are neither 'True' nor valid salts. * * @param[in,out] json JSON to transform * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue TALER_JSON_contract_seed_forgettable (json_t *json) { if (json_is_object (json)) { const char *key; json_t *val; json_object_foreach (json, key, val) { if (0 == strcmp ("$forgettable", key)) { if (GNUNET_OK != seed_forgettable (val)) return GNUNET_SYSERR; continue; } if (GNUNET_OK != TALER_JSON_contract_seed_forgettable (val)) return GNUNET_SYSERR; } } if (json_is_array (json)) { size_t index; json_t *val; json_array_foreach (json, index, val) { if (GNUNET_OK != TALER_JSON_contract_seed_forgettable (val)) return GNUNET_SYSERR; } } return GNUNET_OK; } /** * Parse a json path. * * @param obj the object that the path is relative to. * @param prev the parent of @e obj. * @param path the path to parse. * @param cb the callback to call, if we get to the end of @e path. * @param cb_cls the closure for the callback. * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed. */ static enum GNUNET_GenericReturnValue parse_path (json_t *obj, json_t *prev, const char *path, TALER_JSON_ExpandPathCallback cb, void *cb_cls) { char *id = GNUNET_strdup (path); char *next_id = strchr (id, '.'); char *next_path; char *bracket; json_t *next_obj = NULL; char *next_dot; if (NULL == next_id) { cb (cb_cls, id, prev); GNUNET_free (id); return GNUNET_OK; } bracket = strchr (next_id, '['); *next_id = '\0'; next_id++; next_path = GNUNET_strdup (next_id); next_dot = strchr (next_id, '.'); if (NULL != next_dot) *next_dot = '\0'; /* If this is the first time this is called, make sure id is "$" */ if ( (NULL == prev) && (0 != strcmp (id, "$"))) { GNUNET_free (id); GNUNET_free (next_path); return GNUNET_SYSERR; } /* Check for bracketed indices */ if (NULL != bracket) { char *end_bracket = strchr (bracket, ']'); if (NULL == end_bracket) { GNUNET_free (id); GNUNET_free (next_path); return GNUNET_SYSERR; } *end_bracket = '\0'; *bracket = '\0'; bracket++; json_t *array = json_object_get (obj, next_id); if (0 == strcmp (bracket, "*")) { size_t index; json_t *value; int ret = GNUNET_OK; json_array_foreach (array, index, value) { ret = parse_path (value, obj, next_path, cb, cb_cls); if (GNUNET_OK != ret) { GNUNET_free (id); GNUNET_free (next_path); return ret; } } } else { unsigned int index; char dummy; if (1 != sscanf (bracket, "%u%c", &index, &dummy)) { GNUNET_free (id); GNUNET_free (next_path); return GNUNET_SYSERR; } next_obj = json_array_get (array, index); } } else { /* No brackets, so just fetch the object by name */ next_obj = json_object_get (obj, next_id); } if (NULL != next_obj) { int ret = parse_path (next_obj, obj, next_path, cb, cb_cls); GNUNET_free (id); GNUNET_free (next_path); return ret; } GNUNET_free (id); GNUNET_free (next_path); return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_JSON_expand_path (json_t *json, const char *path, TALER_JSON_ExpandPathCallback cb, void *cb_cls) { return parse_path (json, NULL, path, cb, cb_cls); } enum TALER_ErrorCode TALER_JSON_get_error_code (const json_t *json) { const json_t *jc; if (NULL == json) return TALER_EC_GENERIC_INVALID_RESPONSE; jc = json_object_get (json, "code"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Expected Taler error code `code' in JSON, but field does not exist!\n"); return TALER_EC_INVALID; } if (json_is_integer (jc)) return (enum TALER_ErrorCode) json_integer_value (jc); GNUNET_break_op (0); return TALER_EC_INVALID; } const char * TALER_JSON_get_error_hint (const json_t *json) { const json_t *jc; if (NULL == json) return NULL; jc = json_object_get (json, "hint"); if (NULL == jc) return NULL; /* no hint, is allowed */ if (! json_is_string (jc)) { /* Hints must be strings */ GNUNET_break_op (0); return NULL; } return json_string_value (jc); } enum TALER_ErrorCode TALER_JSON_get_error_code2 (const void *data, size_t data_size) { json_t *json; enum TALER_ErrorCode ec; json_error_t err; json = json_loadb (data, data_size, JSON_REJECT_DUPLICATES, &err); if (NULL == json) return TALER_EC_INVALID; ec = TALER_JSON_get_error_code (json); json_decref (json); if (ec == TALER_EC_NONE) return TALER_EC_INVALID; return ec; } void TALER_deposit_extension_hash (const json_t *extensions, struct TALER_ExtensionContractHash *ech) { GNUNET_assert (GNUNET_OK == dump_and_hash (extensions, "taler-contract-extensions", &ech->hash)); } enum GNUNET_GenericReturnValue TALER_extension_config_hash (const json_t *config, struct TALER_ExtensionConfigHash *ech) { return dump_and_hash (config, "taler-extension-configuration", &ech->hash); } /* End of json/json.c */