Files
s390-tools/libkmipclient/kmip.c
Ingo Franzki 56fecf1832 libkmipclient: Add KMIP client shared library
libkmipclient is a shared library that provides an KMIP client to
communicate with an KMIP server. KMIP stands for Key Management
Interoperability Protocol, and is an extensible communication protocol
that defines message formats for the manipulation of cryptographic
keys on a key management server.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2021-07-07 13:53:06 +02:00

1596 lines
40 KiB
C

/*
* 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 <errno.h>
#include <stdarg.h>
#include <string.h>
#include <err.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#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 <element ...> 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 -1 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 -1;
if (node->type != KMIP_TYPE_STRUCTURE)
return -1;
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 -1 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 -1;
if (node->type != KMIP_TYPE_STRUCTURE)
return -1;
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 (node->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;
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;
char *hostname = NULL, *tok, *tok2;
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");
return -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(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(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(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();
}