/* * 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. */ #define _XOPEN_SOURCE #define _DEFAULT_SOURCE #include #include #include #include #include #include #include "kmip.h" #include "utils.h" void __attribute__ ((constructor)) kmip_init(void); void __attribute__ ((destructor)) kmip_exit(void); /** * Constructs a new KMIP node with the specified tag and type, and an optional * name. The newly allocated node has a reference count of 1. * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param type the type of the new node * * @returns the allocated node, or NULL in case of an error */ static struct kmip_node *kmip_node_new(enum kmip_tag tag, const char *name, enum kmip_type type) { struct kmip_node *node; node = calloc(1, sizeof(struct kmip_node)); if (node == NULL) return NULL; node->ref_count = 1; node->tag = tag; node->type = type; if (name != NULL) { node->name = strdup(name); if (node->name == NULL) { free(node); return NULL; } } return node; } /** * Returns the tag of a KMIP node * * @param node the KMIP node * * @returns the tag */ enum kmip_tag kmip_node_get_tag(const struct kmip_node *node) { if (node == NULL) return 0; return node->tag; } /** * Returns the type of a KMIP node * * @param node the KMIP node * * @returns the type */ enum kmip_type kmip_node_get_type(const struct kmip_node *node) { if (node == NULL) return 0; return node->type; } /** * Returns the name of a KMIP node * * @param node the KMIP node * * @returns a copy of the name. The caller must free the returnd string. */ char *kmip_node_get_name(const struct kmip_node *node) { if (node == NULL) return NULL; if (node->name == NULL) return NULL; return strdup(node->name); } /** * Constructs a new KMIP node of type structure with the specified tag, and an * optional name, and the elements. The reference count of each added element is * increased. * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param num_elements the number of elements to add * @param elements the array elements to add. * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_structure(enum kmip_tag tag, const char *name, unsigned int num_elements, struct kmip_node **elements) { struct kmip_node *node; int rc; node = kmip_node_new(tag, name, KMIP_TYPE_STRUCTURE); if (node == NULL) return NULL; rc = kmip_node_add_structure_elements(node, num_elements, elements); if (rc != 0) { kmip_node_free(node); return NULL; } return node; } /** * Constructs a new KMIP node of type structure with the specified tag, and an * optional name, and the elements. The reference count of each added element is * increased. * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param num_elements the number of elements following as variable args * @param the elements to add. Elements may be NULL, those * are skipped * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_structure_va(enum kmip_tag tag, const char *name, unsigned int num_elements, ...) { struct kmip_node *node, **elements = NULL; unsigned int i; va_list ap; if (num_elements > 0) { elements = calloc(num_elements, sizeof(struct kmip_node *)); if (elements == NULL) return NULL; } va_start(ap, num_elements); for (i = 0; i < num_elements; i++) elements[i] = va_arg(ap, struct kmip_node *); va_end(ap); node = kmip_node_new_structure(tag, name, num_elements, elements); if (elements != NULL) free(elements); return node; } /** * Add an element to a KMIP node (which must be of type KMIP_TYPE_STRUCTURE). * The element is added as the last element. The reference count of the added * element is increased. * * @param node the structure node to add the element to * @param element the element to add * * @returns 0 in case of success, or a negative errno value */ int kmip_node_add_structure_element(struct kmip_node *node, struct kmip_node *element) { if (node == NULL || element == NULL) return -EINVAL; return kmip_node_add_structure_elements(node, 1, &element); } /** * Add elements to a KMIP node (which must be of type KMIP_TYPE_STRUCTURE). * The elements are added after the last element. The reference count of the * added elements is increased. * * @param node the structure node to add the element to * @param num_elements the number of elements to add * @param elements the array elements to add * * @returns 0 in case of success, or a negative errno value */ int kmip_node_add_structure_elements(struct kmip_node *node, unsigned int num_elements, struct kmip_node **elements) { struct kmip_node *element, *last; unsigned int i; if (node == NULL || (num_elements > 0 && elements == NULL)) return -EINVAL; if (node->type != KMIP_TYPE_STRUCTURE) return -EINVAL; if (node->structure_value == NULL) { last = NULL; } else { last = node->structure_value; while (last->next != NULL) last = last->next; } for (i = 0; i < num_elements; i++) { element = elements[i]; if (element == NULL) continue; kmip_node_upref(element); element->parent = node; element->next = NULL; if (last == NULL) node->structure_value = element; else last->next = element; last = element; } return 0; } /** * Returns the number of elements of a KMIP node of type structure * * @param node the KMIP node * * @returns the number of elements, or 0 if the node is not of type structure */ unsigned int kmip_node_get_structure_element_count(const struct kmip_node *node) { struct kmip_node *element; unsigned int i; if (node == NULL) return 0; if (node->type != KMIP_TYPE_STRUCTURE) return 0; element = node->structure_value; for (i = 0; element != NULL; i++) element = element->next; return i; } /** * Returns an element of a KMIP node of type structure * * @param node the KMIP node * @param index the index of the element to return * * @returns the element or NULL if no element is available at the specified * index, or the node is not of type structure. * The reference count of the returned element is increased. The caller must * free the element via kmip_node_free() when no longer needed. */ struct kmip_node *kmip_node_get_structure_element_by_index( const struct kmip_node *node, unsigned int index) { struct kmip_node *element; unsigned int i; if (node == NULL) return NULL; if (node->type != KMIP_TYPE_STRUCTURE) return NULL; element = node->structure_value; for (i = 0; i < index && element != NULL; i++) element = element->next; if (element != NULL) kmip_node_upref(element); return element; } /** * Returns the number of elements of a KMIP node of type structure of a * certain tag * * @param node the KMIP node * @param tag the tag to find * * @returns the number of elements, or 0 if the node is not of type structure */ unsigned int kmip_node_get_structure_element_by_tag_count( const struct kmip_node *node, enum kmip_tag tag) { struct kmip_node *element; unsigned int i; if (node == NULL) return 0; if (node->type != KMIP_TYPE_STRUCTURE) return 0; element = node->structure_value; for (i = 0; element != NULL; element = element->next) { if (element->tag != tag) continue; i++; } return i; } /** * Find a structure element by its tag. If multiple elements with the matching * tag are found, then the num'th one is returned. * * @param node the structure node to find the elements in * @param tag the tag to find * @param index the index of elements with the same tag to return. * * @returns the element node, or NULL if no element with the tag was found. * The reference count of the returned element is increased. The caller must * free the element via kmip_node_free() when no longer needed. */ struct kmip_node *kmip_node_get_structure_element_by_tag( const struct kmip_node *node, enum kmip_tag tag, unsigned int index) { struct kmip_node *e; if (node == NULL) return NULL; if (node->type != KMIP_TYPE_STRUCTURE) return NULL; e = node->structure_value; while (e != NULL) { if (e->tag == tag) { if (index == 0) { kmip_node_upref(e); return e; } index--; } e = e->next; } return NULL; } /** * Constructs a new KMIP node of type integer with the specified tag, and an * optional name, and the integer value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_integer(enum kmip_tag tag, const char *name, int32_t value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_INTEGER); if (node == NULL) return NULL; node->integer_value = value; node->length = sizeof(int32_t); return node; } /** * Returns the value of a KMIP node of type integer * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type integer */ int32_t kmip_node_get_integer(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_INTEGER) return 0; return node->integer_value; } /** * Constructs a new KMIP node of type long integer with the specified tag, and * an optional name, and the long integer value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_long(enum kmip_tag tag, const char *name, int64_t value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_LONG_INTEGER); if (node == NULL) return NULL; node->long_value = value; node->length = sizeof(int64_t); return node; } /** * Returns the value of a KMIP node of type long integer * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type long integer */ int64_t kmip_node_get_long(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_LONG_INTEGER) return 0; return node->long_value; } /** * Constructs a new KMIP node of type big integer with the specified tag, and * an optional name, and the big integer value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value as OpenSSL BIGNUM (can be NULL) * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_bigint(enum kmip_tag tag, const char *name, const BIGNUM *value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_BIG_INTEGER); if (node == NULL) return NULL; if (value != NULL) { node->big_integer_value = BN_dup(value); node->length = kmip_encode_bignum_length(value); } return node; } /** * Returns the value of a KMIP node of type big integer * * @param node the KMIP node * * @returns the value of the node, or NULL if the node is not of type big * integer, or no BIGNUM is set. The returned BIGNUM still belongs to the node, * and must not be freed by the caller. It is freed together with the node it * was obtained from. */ const BIGNUM *kmip_node_get_bigint(const struct kmip_node *node) { if (node == NULL) return NULL; if (node->type != KMIP_TYPE_BIG_INTEGER) return NULL; return node->big_integer_value; } /** * Constructs a new KMIP node of type enumeration with the specified tag, and * an optional name, and the enumeration value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param enumeration the enumeration value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_enumeration(enum kmip_tag tag, const char *name, uint32_t enumeration) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_ENUMERATION); if (node == NULL) return NULL; node->enumeration_value = enumeration; node->length = sizeof(uint32_t); return node; } /** * Returns the value of a KMIP node of type enumeration * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type enumeration */ uint32_t kmip_node_get_enumeration(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_ENUMERATION) return 0; return node->enumeration_value; } /** * Constructs a new KMIP node of type boolean with the specified tag, and * an optional name, and the boolean value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_boolean(enum kmip_tag tag, const char *name, bool value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_BOOLEAN); if (node == NULL) return NULL; node->boolean_value = value; node->length = sizeof(uint64_t); return node; } /** * Returns the value of a KMIP node of type boolean * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type boolean */ bool kmip_node_get_boolean(const struct kmip_node *node) { if (node == NULL) return false; if (node->type != KMIP_TYPE_BOOLEAN) return false; return node->boolean_value; } /** * Constructs a new KMIP node of type text string with the specified tag, and * an optional name, and the text string value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value (can be NULL) * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_text_string(enum kmip_tag tag, const char *name, const char *value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_TEXT_STRING); if (node == NULL) return NULL; if (value != NULL) { node->text_value = strdup(value); if (node->text_value == NULL) { free(node); return NULL; } node->length = strlen(value); } return node; } /** * Returns the value of a KMIP node of type text string * * @param node the KMIP node * * @returns the value of the node, or NULL if the node is not of type text * string or no string is set. The returned string still belongs to the node, * and must not be freed by the caller. It is freed together with the node it * was obtained from. */ const char *kmip_node_get_text_string(const struct kmip_node *node) { if (node == NULL) return NULL; if (node->type != KMIP_TYPE_TEXT_STRING) return NULL; return node->text_value; } /** * Constructs a new KMIP node of type byte string with the specified tag, and * an optional name, and the byte string value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the byte string (can be NULL) * @param length the length of the byte string * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_byte_string(enum kmip_tag tag, const char *name, const unsigned char *value, uint32_t length) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_BYTE_STRING); if (node == NULL) return NULL; if (value != NULL && length > 0) { node->bytes_value = malloc(length); if (node->bytes_value == NULL) { free(node); return NULL; } memcpy(node->bytes_value, value, length); node->length = length; } return node; } /** * Returns the value of a KMIP node of type byte string * * @param node the KMIP node * @param length On return, the length of the byte string * * @returns the value of the node, or NULL if the node is not of type byte * string or no string is set. The returned string still belongs to the node, * and must not be freed by the caller. It is freed together with the node it * was obtained from. */ const unsigned char *kmip_node_get_byte_string(const struct kmip_node *node, uint32_t *length) { if (node == NULL) return NULL; if (node->type != KMIP_TYPE_BYTE_STRING) return NULL; if (length != NULL) *length = node->length; return node->bytes_value; } /** * Constructs a new KMIP node of type date and time with the specified tag, and * an optional name, and the date and time value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_date_time(enum kmip_tag tag, const char *name, int64_t value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_DATE_TIME); if (node == NULL) return NULL; node->date_time_value = value; node->length = sizeof(int64_t); return node; } /** * Returns the value of a KMIP node of type date and time * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type date and time */ int64_t kmip_node_get_date_time(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_DATE_TIME) return 0; return node->date_time_value; } /** * Constructs a new KMIP node of type interval with the specified tag, and * an optional name, and the interval value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_interval(enum kmip_tag tag, const char *name, uint32_t value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_INTERVAL); if (node == NULL) return NULL; node->interval_value = value; node->length = sizeof(uint32_t); return node; } /** * Returns the value of a KMIP node of type interval * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type interval */ uint32_t kmip_node_get_interval(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_INTERVAL) return 0; return node->interval_value; } /** * Constructs a new KMIP node of type date and time extended with the specified * tag, and an optional name, and the date and time value * * @param tag the tag of the new node * @param name Optional: the name of the node (only used with JSON * or XML encoding). Can be NULL. * @param value the value * * @returns the allocated node, or NULL in case of an error */ struct kmip_node *kmip_node_new_date_time_ext(enum kmip_tag tag, const char *name, int64_t value) { struct kmip_node *node; node = kmip_node_new(tag, name, KMIP_TYPE_DATE_TIME_EXTENDED); if (node == NULL) return NULL; node->date_time_ext_value = value; node->length = sizeof(int64_t); return node; } /** * Returns the value of a KMIP node of type date and time extended * * @param node the KMIP node * * @returns the value of the node, or 0 if the node is not of type date and time * extended */ int64_t kmip_node_get_date_time_ext(const struct kmip_node *node) { if (node == NULL) return 0; if (node->type != KMIP_TYPE_DATE_TIME_EXTENDED) return 0; return node->date_time_ext_value; } /** * Clones (copies) a KMIP node with all its data and elements (in case of a * structure node). * * @param node the KMIP node to clone * * @returns the cloned node, or NULL in case of an error */ struct kmip_node *kmip_node_clone(const struct kmip_node *node) { struct kmip_node *clone, *element, *cloned_element; int rc; clone = kmip_node_new(node->tag, node->name, node->type); if (clone == NULL) return NULL; switch (clone->type) { case KMIP_TYPE_STRUCTURE: element = node->structure_value; while (element != NULL) { cloned_element = kmip_node_clone(element); if (cloned_element == NULL) goto error; rc = kmip_node_add_structure_element(clone, cloned_element); kmip_node_free(cloned_element); if (rc != 0) goto error; element = element->next; } break; case KMIP_TYPE_INTEGER: clone->integer_value = node->integer_value; break; case KMIP_TYPE_LONG_INTEGER: clone->long_value = node->long_value; break; case KMIP_TYPE_BIG_INTEGER: clone->big_integer_value = BN_dup(node->big_integer_value); if (clone->big_integer_value == NULL) goto error; break; case KMIP_TYPE_ENUMERATION: clone->enumeration_value = node->enumeration_value; break; case KMIP_TYPE_BOOLEAN: clone->boolean_value = node->boolean_value; break; case KMIP_TYPE_TEXT_STRING: if (node->text_value != NULL) { clone->text_value = strdup(node->text_value); if (clone->text_value == NULL) goto error; clone->length = strlen(clone->text_value); } break; case KMIP_TYPE_BYTE_STRING: if (node->bytes_value != NULL && node->length > 0) { clone->bytes_value = malloc(node->length); if (clone->bytes_value == NULL) goto error; memcpy(clone->bytes_value, node->bytes_value, node->length); clone->length = node->length; } break; case KMIP_TYPE_DATE_TIME: clone->date_time_value = node->date_time_value; break; case KMIP_TYPE_INTERVAL: clone->interval_value = node->interval_value; break; case KMIP_TYPE_DATE_TIME_EXTENDED: clone->date_time_ext_value = node->date_time_ext_value; break; default: goto error; } return clone; error: kmip_node_free(clone); return NULL; } /** * Increments the reference count of a KMIP node * * @param node the node to increase the reference count for */ void kmip_node_upref(struct kmip_node *node) { if (node == NULL) return; __sync_add_and_fetch((unsigned long *)&node->ref_count, 1); } /** * Free a KMIP node, including its value (structure elements, etc) * * @param node the node to free */ void kmip_node_free(struct kmip_node *node) { struct kmip_node *element, *next; unsigned long ref_count = 0; if (node == NULL) return; if (node->ref_count > 0) ref_count = __sync_sub_and_fetch( (unsigned long *)&node->ref_count, 1); if (ref_count > 0) return; switch (node->type) { case KMIP_TYPE_STRUCTURE: element = node->structure_value; while (element != NULL) { next = element->next; /* * Unchain the element from the parent and next element, * even if the element itself might not be freed (due * to reference count). But the parent is freed, and * thus the chain of elements is not longer existent. */ element->parent = NULL; element->next = NULL; kmip_node_free(element); element = next; } break; case KMIP_TYPE_BIG_INTEGER: BN_free(node->big_integer_value); break; case KMIP_TYPE_TEXT_STRING: free(node->text_value); break; case KMIP_TYPE_BYTE_STRING: free(node->bytes_value); break; default: break; } free(node->name); free(node); } static struct kmip_version default_protocol_version = { .major = KMIP_DEFAULT_PROTOCOL_VERSION_MAJOR, .minor = KMIP_DEFAULT_PROTOCOL_VERSION_MINOR }; /** * Sets the default KMIP protocol version * * @param version the version to set */ void kmip_set_default_protocol_version(const struct kmip_version *version) { if (version == NULL) return; default_protocol_version.major = version->major; default_protocol_version.minor = version->minor; } /** * Sets the default KMIP protocol version * * @returns the default KMIP protocol version */ const struct kmip_version *kmip_get_default_protocol_version(void) { return &default_protocol_version; } /** * Constructs a new connection to a KMIP server using the specified connection * configuration. The strings specified in the configuration are copied into the * newly allocated connection, they can be freed by the caller after the new * connection has been allocated. The reference count in the PKEY specified * in the configuration is increased. The caller can free its PKEY as needed. * * @param config the connection configuration * @param connection On return: a newly allocated KMIP connection * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_connection_new(const struct kmip_conn_config *config, struct kmip_connection **connection, bool debug) { struct kmip_connection *conn = NULL; int rc; if (config == NULL || connection == NULL) return -EINVAL; *connection = NULL; switch (config->encoding) { case KMIP_ENCODING_TTLV: /* TTLV can be used with both, plain-TLS and HTTPS */ switch (config->transport) { case KMIP_TRANSPORT_PLAIN_TLS: case KMIP_TRANSPORT_HTTPS: break; default: kmip_debug(debug, "Invalid transport: %d", config->transport); return -EINVAL; } break; case KMIP_ENCODING_JSON: case KMIP_ENCODING_XML: /* JSON(XML can only be used with HTTPS */ switch (config->transport) { case KMIP_TRANSPORT_PLAIN_TLS: kmip_debug(debug, "JSON/XML encode can only be used " "with HTTPS transport"); return -EINVAL; case KMIP_TRANSPORT_HTTPS: break; default: kmip_debug(debug, "Invalid transport: %d", config->transport); return -EINVAL; } break; default: kmip_debug(debug, "Invalid encoding: %d", config->encoding); return -EINVAL; } if (config->server == NULL) { kmip_debug(debug, "KMIP Server must be specified"); return -EINVAL; } if (config->tls_client_key == NULL) { kmip_debug(debug, "Client key must be specified"); return -EINVAL; } if (config->tls_client_cert == NULL) { kmip_debug(debug, "Client certificate must be specified"); return -EINVAL; } conn = calloc(1, sizeof(struct kmip_connection)); if (conn == NULL) { kmip_debug(debug, "calloc failed"); return -ENOMEM; } conn->config.encoding = config->encoding; conn->config.transport = config->transport; kmip_debug(debug, "encoding: %d", conn->config.encoding); kmip_debug(debug, "transport: %d", conn->config.transport); conn->config.server = strdup(config->server); if (conn->config.server == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "server: '%s'", conn->config.server); conn->config.tls_client_key = config->tls_client_key; if (EVP_PKEY_up_ref(conn->config.tls_client_key) != 1) { kmip_debug(debug, "EVP_PKEY_up_ref failed"); rc = -EIO; goto out; } kmip_debug(debug, "client key: %p", conn->config.tls_client_key); conn->config.tls_client_cert = strdup(config->tls_client_cert); if (conn->config.tls_client_cert == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "client cert: '%s'", conn->config.tls_client_cert); if (config->tls_ca != NULL) { conn->config.tls_ca = strdup(config->tls_ca); if (conn->config.tls_ca == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "CA: '%s'", conn->config.tls_ca); } if (config->tls_issuer_cert != NULL) { conn->config.tls_issuer_cert = strdup(config->tls_issuer_cert); if (conn->config.tls_issuer_cert == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "issuer cert: '%s'", conn->config.tls_issuer_cert); } if (config->tls_pinned_pubkey != NULL) { conn->config.tls_pinned_pubkey = strdup(config->tls_pinned_pubkey); if (conn->config.tls_pinned_pubkey == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "pinned pubkey: '%s'", conn->config.tls_pinned_pubkey); } if (config->tls_server_cert != NULL) { conn->config.tls_server_cert = strdup(config->tls_server_cert); if (conn->config.tls_server_cert == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "server cert: '%s'", conn->config.tls_server_cert); } conn->config.tls_verify_peer = config->tls_verify_peer; conn->config.tls_verify_host = config->tls_verify_host; kmip_debug(debug, "verify peer: %d", conn->config.tls_verify_peer); kmip_debug(debug, "verify host: %d", conn->config.tls_verify_host); if (config->tls_cipher_list != NULL) { conn->config.tls_cipher_list = strdup(config->tls_cipher_list); if (conn->config.tls_cipher_list == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "TLS cipher list: '%s'", conn->config.tls_cipher_list); } if (config->tls13_cipher_list != NULL) { conn->config.tls13_cipher_list = strdup(config->tls13_cipher_list); if (conn->config.tls13_cipher_list == NULL) { kmip_debug(debug, "strdup failed"); rc = -ENOMEM; goto out; } kmip_debug(debug, "TLSv1.3 cipher list: '%s'", conn->config.tls13_cipher_list); } switch (conn->config.transport) { case KMIP_TRANSPORT_PLAIN_TLS: rc = kmip_connection_tls_init(conn, debug); if (rc != 0) { kmip_debug(debug, "kmip_connection_tls_init failed"); goto out; } break; case KMIP_TRANSPORT_HTTPS: rc = kmip_connection_https_init(conn, debug); if (rc != 0) { kmip_debug(debug, "kmip_connection_https_init failed"); goto out; } break; default: kmip_debug(debug, "Invalid transport: %d", conn->config.transport); rc = -EINVAL; goto out; } *connection = conn; rc = 0; out: if (rc != 0) kmip_connection_free(conn); return rc; } /** * Perform a request over the KMIP connection * * @param connection the KMIP connection * @param request the request to send * @param response On return: the received response. Must be freed by * the caller. * * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_connection_perform(struct kmip_connection *connection, struct kmip_node *request, struct kmip_node **response, bool debug) { int rc; if (connection == NULL || request == NULL || response == NULL) return -EINVAL; kmip_debug(debug, "KMIP Request:"); kmip_node_dump(request, debug); switch (connection->config.transport) { case KMIP_TRANSPORT_PLAIN_TLS: rc = kmip_connection_tls_perform(connection, request, response, debug); break; case KMIP_TRANSPORT_HTTPS: rc = kmip_connection_https_perform(connection, request, response, debug); break; default: return -EINVAL; } if (rc == 0 && *response != NULL) { kmip_debug(debug, "KMIP Response:"); kmip_node_dump(*response, debug); } return rc; } /** * Terminates and frees a KMIP connection. * * @param connection the KMIP connection to free */ void kmip_connection_free(struct kmip_connection *connection) { if (connection == NULL) return; switch (connection->config.transport) { case KMIP_TRANSPORT_PLAIN_TLS: kmip_connection_tls_term(connection); break; case KMIP_TRANSPORT_HTTPS: kmip_connection_https_term(connection); break; default: break; } free((void *)connection->config.server); EVP_PKEY_free(connection->config.tls_client_key); free((void *)connection->config.tls_client_cert); if (connection->config.tls_ca != NULL) free((void *)connection->config.tls_ca); if (connection->config.tls_issuer_cert != NULL) free((void *)connection->config.tls_issuer_cert); if (connection->config.tls_pinned_pubkey != NULL) free((void *)connection->config.tls_pinned_pubkey); if (connection->config.tls_server_cert != NULL) free((void *)connection->config.tls_server_cert); if (connection->config.tls_cipher_list != NULL) free((void *)connection->config.tls_cipher_list); if (connection->config.tls13_cipher_list != NULL) free((void *)connection->config.tls13_cipher_list); free(connection); } /** * Retrieves the serevr's certificate, public key and certificate chain * * @param server the KMIP server. * For Plain-TLS transport, only the hostname and * optional port number. * For HTTPS transport, an URL in the form * 'https://hostname[:port]/uri' * @param transport the transport mode * @param ca Optional: File name of the CA bundle PEM file, or a * name of a directory the multiple CA certificates. * If this is NULL, then the default system path for * CA certificates is used. * @param client_key the client key as an OpenSSL PKEY object. * @param client_cert File name of the client certificate PEM file * @param server_cert_pem File name of a PEM file into which the server * certificate is written. If NULL then ignored. * @param server_pubkey_pem File name of a PEM file into which the server * public key is written. If NULL then ignored. * @param cert_chain_pem File name of a PEM file into which the certificate * chain (excluding the server certificate) is written. * If NULL then ignored. * @param verified On return: If the server 's certificate has been * verified using the CA specification (if ca = NULL: * default system CAs, otherwise path or file to CAs). * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_connection_get_server_cert(const char *server, enum kmip_transport transport, const char *ca, EVP_PKEY *client_key, const char *client_cert, const char *server_cert_pem, const char *server_pubkey_pem, const char *cert_chain_pem, bool *verified, bool debug) { struct kmip_conn_config config = { 0 }; struct kmip_connection *conn = NULL; int rc, numcerts, i, port_found = 0; const char *tok, *tok2; char *hostname = NULL; STACK_OF(X509) *chain; bool do_verify = true; FILE *fp = NULL; X509 *cert; if (server == NULL || client_key == NULL || client_cert == NULL) return -EINVAL; config.encoding = KMIP_ENCODING_TTLV; config.transport = KMIP_TRANSPORT_PLAIN_TLS; config.tls_ca = ca; config.tls_client_key = client_key; config.tls_client_cert = client_cert; config.tls_verify_host = false; config.tls_verify_peer = false; config.tls_cipher_list = NULL; config.tls13_cipher_list = NULL; if (transport == KMIP_TRANSPORT_HTTPS) { if (strncmp(server, "https://", 8) != 0) { kmip_debug(debug, "Server must start with 'https://'"); return -EINVAL; } server += 8; /* Find port (if any) and beginning of uri */ if (*server == '[') { /* IPv6 address enclosed in square brackets */ tok = strchr(server, ']'); if (tok == NULL) { kmip_debug(debug, "malformed IPv6 address"); return -EINVAL; } tok++; if (*tok == ':') { port_found = 1; tok2 = strchr(tok, '/'); if (tok2 == NULL) tok2 = tok + strlen(tok); } else { tok2 = strchr(tok, '/'); if (tok2 == NULL) tok2 = tok + strlen(tok); } } else { /* hostname or IPv4 address */ tok = strchr(server, ':'); if (tok != NULL) { port_found = 1; tok2 = strchr(tok, '/'); if (tok2 == NULL) tok2 = tok + strlen(tok); } else { tok2 = strchr(server, '/'); if (tok2 == NULL) tok2 = (char *)server + strlen(server); } } hostname = calloc(1, tok2 - server + (!port_found ? 5 : 1)); if (hostname == NULL) { kmip_debug(debug, "calloc failed"); return -ENOMEM; } strncpy(hostname, server, tok2 - server); if (!port_found) { strcat(hostname, ":"); strcat(hostname, KMIP_DEFAULT_HTTPS_PORT); } config.server = hostname; } else { config.server = server; } retry: config.tls_verify_peer = do_verify; rc = kmip_connection_new(&config, &conn, debug); if (rc != 0) { kmip_debug(debug, "kmip_connection_new failed (do_verify: %d)", do_verify); if (do_verify) { /* * If peer verification failed (e.g. due to a self * signed server certificate), try again without peer * verification. */ do_verify = false; goto retry; } goto out; } if (verified != NULL) *verified = do_verify; chain = SSL_get_peer_cert_chain(conn->plain_tls.ssl); if (chain == NULL) { kmip_debug(debug, "SSL_get_peer_cert_chain failed"); rc = -EIO; goto out; } numcerts = sk_X509_num(chain); for (i = 0; i < numcerts; i++) { cert = sk_X509_value(chain, i); if (cert == NULL) break; if (debug) { kmip_debug(debug, "%d. Certificate:", i); X509_print_ex_fp(stderr, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); } if (i == 0 && server_cert_pem != NULL) { fp = fopen_nofollow(server_cert_pem, "w"); if (fp == NULL) { rc = -errno; kmip_debug(debug, "Failed to open %s for write", server_cert_pem, strerror(-rc)); goto out; } if (PEM_write_X509(fp, cert) != 1) { kmip_debug(debug, "PEM_write_X509 failed to " "write to %s", server_cert_pem); rc = -EIO; goto out; } fclose(fp); fp = NULL; if (server_pubkey_pem != NULL) { fp = fopen_nofollow(server_pubkey_pem, "w"); if (fp == NULL) { rc = -errno; kmip_debug(debug, "Failed to open %s " "for write", server_pubkey_pem, strerror(-rc)); goto out; } if (PEM_write_PUBKEY(fp, X509_get0_pubkey(cert)) != 1) { kmip_debug(debug, "PEM_write_PUBKEY " "failed to write to %s", server_pubkey_pem); rc = -EIO; goto out; } fclose(fp); fp = NULL; } continue; } if (i > 0 && cert_chain_pem != NULL) { if (fp == NULL) fp = fopen_nofollow(cert_chain_pem, "w"); if (fp == NULL) { rc = -errno; kmip_debug(debug, "Failed to open %s for write", cert_chain_pem, strerror(-rc)); goto out; } if (PEM_write_X509(fp, cert) != 1) { kmip_debug(debug, "PEM_write_X509 failed to " "write to %s", cert_chain_pem); rc = -EIO; goto out; } } } rc = 0; out: if (fp != NULL) fclose(fp); if (conn != NULL) kmip_connection_free(conn); if (hostname != NULL) free(hostname); return rc; } /** * Library constructor */ void __attribute__ ((constructor)) kmip_init(void) { CURLsslset rc; /* * Ensure that curl uses OpenSSL as SSL backend. If curl has already * been itialized by the calling application, the backend can't be * changed anymore, but we continue anyway. However, it will later be * checked if curl uses the OpenSSL backend, and a HTTPS connection * will fail if it is not using the OpenSSL backend. */ rc = curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL); if (rc != CURLSSLSET_OK && rc != CURLSSLSET_TOO_LATE) errx(EXIT_FAILURE, "libkmipclient: libcurl was not built with " "the OpenSSL backend"); curl_global_init(CURL_GLOBAL_ALL); } /** * Library destructor */ void __attribute__ ((destructor)) kmip_exit(void) { curl_global_cleanup(); }