/* * libkmipclient - KMIP client library * * Copyright IBM Corp. 2021 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include "kmip.h" #include "names.h" #include "utils.h" #define KMIP_JSON_TAG "tag" #define KMIP_JSON_NAME "name" #define KMIP_JSON_TYPE "type" #define KMIP_JSON_VALUE "value" /** * Decode a KMIP node from the data in a JSON object using the JSON encoding. * * @param obj the JSON object to decode * @param parent the parent node or NULL if no parent exists. * @param node On return: the decoded node. The newly allocated * node has a reference count of 1. * @param max_nesting_level the maximum nesting levels of structures within the * KMIP node. If the nesting level is reached, E2BIG * is returned. * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_decode_json(const json_object *obj, struct kmip_node *parent, struct kmip_node **node, size_t max_nesting_level, bool debug) { json_object *tag_obj, *type_obj, *value_obj, *name_obj; enum kmip_tag tag, v1_attr_tag = 0; enum json_type value_type; struct kmip_node *n, *e; const char *str; int rc, num, i; int64_t int64; if (max_nesting_level == 0) return -E2BIG; if (obj == NULL || node == NULL) return -EINVAL; if (!json_object_is_type(obj, json_type_object)) { kmip_debug(debug, "Object is not a JSON object"); return -EINVAL; } n = calloc(1, sizeof(struct kmip_node)); if (n == NULL) { kmip_debug(debug, "calloc failed"); return -ENOMEM; } n->ref_count = 1; tag_obj = json_object_object_get(obj, KMIP_JSON_TAG); if (tag_obj == NULL || !json_object_is_type(tag_obj, json_type_string)) { kmip_debug(debug, "Missing or invalid '%s' in JSON object", KMIP_JSON_TAG); rc = -EBADMSG; goto out; } str = json_object_get_string(tag_obj); n->tag = kmip_tag_by_name_or_hex(str); if (n->tag == 0) { kmip_debug(debug, "Unknown 'tag' in JSON object: '%s'", str); rc = -EBADMSG; goto out; } name_obj = json_object_object_get(obj, KMIP_JSON_NAME); if (name_obj != NULL) { if (!json_object_is_type(name_obj, json_type_string)) { kmip_debug(debug, "Invalid '%s' in JSON object", KMIP_JSON_NAME); rc = -EBADMSG; goto out; } n->name = strdup(json_object_get_string(name_obj)); } type_obj = json_object_object_get(obj, KMIP_JSON_TYPE); if (type_obj == NULL) { n->type = KMIP_TYPE_STRUCTURE; } else { if (!json_object_is_type(type_obj, json_type_string)) { kmip_debug(debug, "Missing or invalid '%s' in JSON object", KMIP_JSON_TYPE); rc = -EBADMSG; goto out; } str = json_object_get_string(type_obj); n->type = kmip_type_by_name_or_hex(str); if (n->type == 0) { kmip_debug(debug, "Unknown 'type' in JSON object: '%s'", str); rc = -EBADMSG; goto out; } } value_obj = json_object_object_get(obj, KMIP_JSON_VALUE); if (value_obj == NULL) { kmip_debug(debug, "Missing '%s' in JSON object", KMIP_JSON_VALUE); rc = -EBADMSG; goto out; } value_type = json_object_get_type(value_obj); /* * KMIP v1.x attribute values may be Enumerations or Integer Masks. * To correctly decode them, we need to know the tag. This is contained * in a Attribute Name node, which is an element of our parent node. */ if (n->tag == KMIP_TAG_ATTRIBUTE_VALUE) v1_attr_tag = kmip_find_v1_attribute_name_tag(parent); tag = (v1_attr_tag != 0 ? v1_attr_tag : n->tag); kmip_debug(debug, "tag: 0x%x type: 0x%x value_type: %d,", n->tag, n->type, value_type); switch (n->type) { case KMIP_TYPE_STRUCTURE: switch (value_type) { case json_type_null: break; case json_type_array: num = json_object_array_length(value_obj); for (i = 0; i < num; i++) { rc = kmip_decode_json( json_object_array_get_idx(value_obj, i), n, &e, max_nesting_level - 1, debug); if (rc != 0) { kmip_debug(debug, "Failed to parse " "array element %d", i); goto out; } rc = kmip_node_add_structure_element(n, e); kmip_node_free(e); if (rc != 0) { kmip_debug(debug, "kmip_node_structure_add_element " "failed: rc: %d", rc); goto out; } } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_INTEGER: case KMIP_TYPE_LONG_INTEGER: switch (value_type) { case json_type_int: case json_type_double: int64 = json_object_get_int64(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); if (n->type == KMIP_TYPE_INTEGER && kmip_is_tag_mask(tag)) { rc = kmip_parse_mask(tag, str, '|', &int64); if (rc != 0) { kmip_debug(debug, "Failed to parse " "mask string '%s'", str); goto out; } } else { rc = kmip_parse_hex_int(str, &int64); if (rc != 0) { kmip_debug(debug, "Failed to parse " "hex string '%s'", str); goto out; } } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } if (n->type == KMIP_TYPE_INTEGER) n->integer_value = int64; else n->long_value = int64; break; case KMIP_TYPE_INTERVAL: switch (value_type) { case json_type_int: case json_type_double: n->interval_value = json_object_get_int64(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_hex_int(str, &int64); if (rc != 0) { kmip_debug(debug, "Failed to parse " "hex string '%s'", str); goto out; } n->interval_value = int64; break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_BIG_INTEGER: switch (value_type) { case json_type_int: case json_type_double: int64 = htobe64(json_object_get_int64(value_obj)); rc = kmip_decode_bignum((const unsigned char *)&int64, sizeof(int64), &n->big_integer_value); if (rc != 0) { kmip_debug(debug, "kmip_decode_bignum failed"); goto out; } break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_bignum(str, true, &n->big_integer_value); if (rc != 0) { kmip_debug(debug, "Failed to parse bignum string '%s'", str); goto out; } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_ENUMERATION: switch (value_type) { case json_type_int: case json_type_double: n->enumeration_value = json_object_get_int64(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_enum_value_by_tag_name_or_hex(tag, str, &n->enumeration_value); if (rc != 0) { kmip_debug(debug, "Failed to parse enumeration '%s'", str); goto out; } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_BOOLEAN: switch (value_type) { case json_type_boolean: n->boolean_value = json_object_get_boolean(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_hex_int(str, &int64); if (rc != 0) { kmip_debug(debug, "Failed to parse hex string '%s'", str); goto out; } n->boolean_value = (int64 != 0); break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_TEXT_STRING: switch (value_type) { case json_type_string: n->text_value = strdup( json_object_get_string(value_obj)); if (n->text_value == NULL) { rc = -ENOMEM; goto out; } n->length = strlen(n->text_value); break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_BYTE_STRING: switch (value_type) { case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_hex(str, false, &n->bytes_value, &n->length); if (rc != 0) { kmip_debug(debug, "Failed to parse hex string '%s'", str); goto out; } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_DATE_TIME: switch (value_type) { case json_type_int: case json_type_double: n->date_time_value = json_object_get_int64(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_timestamp(str, &n->date_time_value); if (rc != 0) { kmip_debug(debug, "Failed to parse time stamp '%s'", str); goto out; } break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; case KMIP_TYPE_DATE_TIME_EXTENDED: switch (value_type) { case json_type_int: case json_type_double: n->date_time_ext_value = json_object_get_int64(value_obj); break; case json_type_string: str = json_object_get_string(value_obj); rc = kmip_parse_hex_int(str, &int64); if (rc != 0) { kmip_debug(debug, "Failed to parse hex string '%s'", str); goto out; } n->date_time_ext_value = int64; break; default: kmip_debug(debug, "Invalid JSON type %d for node type " "0x%x", value_type, n->type); rc = -EBADMSG; goto out; } break; default: kmip_debug(debug, "unknown type: 0x%x", n->type); rc = -EBADMSG; goto out; } *node = n; rc = 0; out: if (rc != 0) kmip_node_free(n); return rc; } /** * Encode a KMIP node into a JSON object using the JSON encoding. * * @param node the node to encode * @param obj On return: the JSON object * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_encode_json(const struct kmip_node *node, json_object **obj, bool debug) { json_object *ret_obj = NULL, *memb_obj, *elem_obj; enum kmip_tag tag, v1_attr_tag = 0; struct kmip_node *element; char outstr[200] = { 0 }; const char *str; int64_t int64; struct tm *tm; char *tmp; char *s; int rc; if (node == NULL || obj == NULL) return -EINVAL; kmip_debug(debug, "tag: 0x%x type: 0x%x", node->tag, node->type); ret_obj = json_object_new_object(); if (ret_obj == NULL) { kmip_debug(debug, "Failed to allocate a JSON object"); return -ENOMEM; } memb_obj = json_object_new_string( kmip_tag_name_or_hex_by_tag(node->tag, outstr)); if (memb_obj == NULL) { kmip_debug(debug, "Failed to build JSON object for tag"); rc = -ENOMEM; goto out; } rc = json_object_object_add(ret_obj, KMIP_JSON_TAG, memb_obj); if (rc != 0) { kmip_debug(debug, "Failed to add JSON object for tag"); rc = -EIO; goto out; } if (node->name != NULL) { memb_obj = json_object_new_string(node->name); if (memb_obj == NULL) { kmip_debug(debug, "Failed to build JSON object for name"); rc = -ENOMEM; goto out; } rc = json_object_object_add(ret_obj, KMIP_JSON_NAME, memb_obj); if (rc != 0) { kmip_debug(debug, "Failed to add JSON object for name"); rc = -EIO; goto out; } } if (node->type != KMIP_TYPE_STRUCTURE) { str = kmip_type_name_by_type(node->type); if (str == NULL) { kmip_debug(debug, "unknown type 0x%x", node->type); rc = -EINVAL; goto out; } memb_obj = json_object_new_string(str); if (memb_obj == NULL) { kmip_debug(debug, "Failed to build JSON object for type"); rc = -ENOMEM; goto out; } rc = json_object_object_add(ret_obj, KMIP_JSON_TYPE, memb_obj); if (rc != 0) { kmip_debug(debug, "Failed to add JSON object for type"); rc = -EIO; goto out; } } /* * KMIP v1.x attribute values may be Enumerations or Integer Masks. * To correctly encode them, we need to know the tag. This is contained * in a Attribute Name node, which is an element of our parent node. */ if (node->tag == KMIP_TAG_ATTRIBUTE_VALUE) v1_attr_tag = kmip_find_v1_attribute_name_tag(node->parent); tag = (v1_attr_tag != 0 ? v1_attr_tag : node->tag); switch (node->type) { case KMIP_TYPE_STRUCTURE: memb_obj = json_object_new_array(); if (memb_obj == NULL) { kmip_debug(debug, "Failed to build JSON object for value array"); rc = -ENOMEM; goto out; } element = node->structure_value; while (element != NULL) { rc = kmip_encode_json(element, &elem_obj, debug); if (rc != 0) { kmip_debug(debug, "kmip_encode_json failed"); goto out; } rc = json_object_array_add(memb_obj, elem_obj); if (rc != 0) { kmip_debug(debug, "json_object_array_add failed"); rc = EIO; goto out; } element = element->next; } break; case KMIP_TYPE_INTEGER: if (kmip_is_tag_mask(tag) && node->integer_value != 0) { rc = kmip_format_mask(tag, node->integer_value, '|', &tmp); if (rc != 0) { kmip_debug(debug, "kmip_format_mask failed"); goto out; } memb_obj = json_object_new_string(tmp); free(tmp); } else { memb_obj = json_object_new_int(node->integer_value); } break; case KMIP_TYPE_INTERVAL: memb_obj = json_object_new_int(node->interval_value); break; case KMIP_TYPE_LONG_INTEGER: case KMIP_TYPE_DATE_TIME_EXTENDED: if (node->type == KMIP_TYPE_LONG_INTEGER) int64 = node->long_value; else int64 = node->date_time_ext_value; /* any values >= 2^52 must be represented as hex strings */ if (int64 < 4503599627370496 && int64 > -4503599627370496) { memb_obj = json_object_new_int64(int64); } else { rc = kmip_format_hex((const unsigned char *)&int64, sizeof(int64), true, &tmp); if (rc != 0) { kmip_debug(debug, "kmip_format_hex failed"); goto out; } memb_obj = json_object_new_string(tmp); free(tmp); } break; case KMIP_TYPE_BIG_INTEGER: rc = kmip_format_bignum(node->big_integer_value, true, &s); if (rc != 0) { kmip_debug(debug, "kmip_format_bignum failed"); goto out; } memb_obj = json_object_new_string(s); free(s); break; case KMIP_TYPE_ENUMERATION: str = kmip_enum_name_by_tag_value(tag, node->enumeration_value); if (str != NULL) memb_obj = json_object_new_string(str); else memb_obj = json_object_new_int(node->enumeration_value); break; case KMIP_TYPE_BOOLEAN: memb_obj = json_object_new_boolean(node->boolean_value); break; case KMIP_TYPE_TEXT_STRING: memb_obj = json_object_new_string(node->text_value); break; case KMIP_TYPE_BYTE_STRING: rc = kmip_format_hex(node->bytes_value, node->length, false, &s); if (rc != 0) { kmip_debug(debug, "kmip_format_hex_long failed"); goto out; } memb_obj = json_object_new_string(s); free(s); break; case KMIP_TYPE_DATE_TIME: tm = gmtime((time_t *)&node->date_time_value); if (tm == NULL) { rc = -EINVAL; goto out; } strftime(outstr, sizeof(outstr), KMIP_ISO8601_TIMESTAMP_UTC, tm); memb_obj = json_object_new_string(outstr); break; default: kmip_debug(debug, "unknown type: 0x%x", node->type); rc = -EINVAL; goto out; } if (memb_obj == NULL) { rc = -ENOMEM; goto out; } rc = json_object_object_add(ret_obj, KMIP_JSON_VALUE, memb_obj); if (rc != 0) { kmip_debug(debug, "Failed to add JSON object for value"); rc = -EIO; goto out; } rc = 0; *obj = ret_obj; out: if (rc != 0) json_object_put(ret_obj); return rc; }