mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
KMIP STRUCTURE elements can be nested, which causes a recursion of functions kmip_decode_ttlv(), kmip_decode_xml(), and kmip_decode_json(). A malformed KMIP response may thus cause stack exhaustion. Limit the KMIP STRUCTURE nesting level to 32 levels. This is more than enough for currently defined KMIP responses. The practically used nesting level is 8 or 9, dependent on the type of KMIP response. Assisted-by: IBM Bob:2.0.0 Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com> Reviewed-by: Finn Callies <fcallies@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
519 lines
12 KiB
C
519 lines
12 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.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <endian.h>
|
|
#include <string.h>
|
|
|
|
#include "kmip.h"
|
|
#include "utils.h"
|
|
|
|
#define KMIP_TTLV_HEADER_LENGTH 8
|
|
#define KMIP_TTLV_BLOCK_LENGTH 8
|
|
|
|
/**
|
|
* Decode a KMIP node from the data in BIO using the TTLV encoding.
|
|
*
|
|
* @param bio the OpenSSL bio to read the data from
|
|
* @param size Optional: If not NULL:
|
|
* On entry: The number of bytes available to read
|
|
* On return: decremented by the number of bytes read
|
|
* If NULL, it is assumed that we can read from bio
|
|
* as many bytes as needed.
|
|
* @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_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
|
size_t max_nesting_level, bool debug)
|
|
{
|
|
unsigned char padding[KMIP_TTLV_BLOCK_LENGTH];
|
|
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
|
|
size_t value_len, pad_len;
|
|
struct kmip_node *n, *e;
|
|
void *value = NULL;
|
|
uint32_t int32;
|
|
uint64_t int64;
|
|
int rc;
|
|
|
|
if (max_nesting_level == 0)
|
|
return -E2BIG;
|
|
|
|
if (bio == NULL || node == NULL)
|
|
return -EINVAL;
|
|
|
|
if (size != NULL)
|
|
kmip_debug(debug, "size: %lu", *size);
|
|
else
|
|
kmip_debug(debug, "size: unknown");
|
|
|
|
if (size != NULL && *size < sizeof(ttlv)) {
|
|
kmip_debug(debug, "length %u > available size %lu",
|
|
sizeof(ttlv), *size);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (BIO_read(bio, ttlv, sizeof(ttlv)) != sizeof(ttlv)) {
|
|
kmip_debug(debug, "BIO_read failed");
|
|
return -EIO;
|
|
}
|
|
if (size != NULL)
|
|
*size -= sizeof(ttlv);
|
|
|
|
n = calloc(1, sizeof(struct kmip_node));
|
|
if (n == NULL) {
|
|
kmip_debug(debug, "calloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
n->ref_count = 1;
|
|
|
|
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
|
|
n->tag |= (uint32_t)(ttlv[0] << 16);
|
|
n->tag |= (uint32_t)(ttlv[1] << 8);
|
|
n->tag |= (uint32_t)(ttlv[2]);
|
|
|
|
/* Type: 1 byte containing a coded value that indicates the data type */
|
|
n->type = ttlv[3];
|
|
|
|
/* Length: 32-bit binary integer, transmitted big-endian */
|
|
n->length |= (uint32_t)(ttlv[4] << 24);
|
|
n->length |= (uint32_t)(ttlv[5] << 16);
|
|
n->length |= (uint32_t)(ttlv[6] << 8);
|
|
n->length |= (uint32_t)(ttlv[7]);
|
|
|
|
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", n->tag, n->type,
|
|
n->length);
|
|
|
|
switch (n->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
value_len = n->length;
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
value_len = n->length;
|
|
value = calloc(1, value_len + 1);
|
|
if (value == NULL) {
|
|
kmip_debug(debug, "calloc failed");
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
case KMIP_TYPE_ENUMERATION:
|
|
case KMIP_TYPE_INTERVAL:
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
case KMIP_TYPE_BOOLEAN:
|
|
case KMIP_TYPE_DATE_TIME:
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", n->type);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
if (n->length != value_len) {
|
|
kmip_debug(debug, "length %u not as expected (%lu)", n->length,
|
|
value_len);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
if (size != NULL && *size < n->length) {
|
|
kmip_debug(debug, "length %u > available size %lu", n->length,
|
|
*size);
|
|
rc = -EMSGSIZE;
|
|
goto out;
|
|
}
|
|
|
|
if (n->type != KMIP_TYPE_STRUCTURE && value_len > 0) {
|
|
if (BIO_read(bio, value, value_len) != (int)value_len) {
|
|
kmip_debug(debug, "BIO_read failed");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
if (size != NULL)
|
|
*size -= value_len;
|
|
|
|
if ((value_len % KMIP_TTLV_BLOCK_LENGTH) != 0) {
|
|
pad_len = KMIP_TTLV_BLOCK_LENGTH -
|
|
(value_len % KMIP_TTLV_BLOCK_LENGTH);
|
|
|
|
kmip_debug(debug, "pad_len: %lu", pad_len);
|
|
if (BIO_read(bio, padding, pad_len) != (int)pad_len) {
|
|
kmip_debug(debug, "BIO_read failed (padding)");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
if (size != NULL)
|
|
*size -= pad_len;
|
|
}
|
|
|
|
switch (n->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
while (value_len > 0) {
|
|
rc = kmip_decode_ttlv(bio, &value_len, &e,
|
|
max_nesting_level - 1, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_decode_ttlv failed: "
|
|
"rc: %d", rc);
|
|
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;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
n->integer_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
n->long_value = be64toh(int64);
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
rc = kmip_decode_bignum(value, value_len,
|
|
&n->big_integer_value);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_decode_bignum failed");
|
|
goto out;
|
|
}
|
|
free(value);
|
|
value = NULL;
|
|
break;
|
|
|
|
case KMIP_TYPE_ENUMERATION:
|
|
n->enumeration_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_BOOLEAN:
|
|
n->boolean_value = int64 != 0;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
n->text_value = value;
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
n->bytes_value = value;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME:
|
|
n->date_time_value = be64toh(int64);
|
|
break;
|
|
|
|
case KMIP_TYPE_INTERVAL:
|
|
n->interval_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
n->date_time_ext_value = be64toh(int64);
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", n->type);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
*node = n;
|
|
rc = 0;
|
|
|
|
out:
|
|
if (rc != 0) {
|
|
switch (n->type) {
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
free(value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
kmip_node_free(n);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Gets the length of the value part of a KMIP node (in TTLV encoding)
|
|
*/
|
|
static int kmip_node_get_length(struct kmip_node *node, size_t *length)
|
|
{
|
|
struct kmip_node *element;
|
|
size_t len;
|
|
int rc;
|
|
|
|
if (node == NULL || length == NULL)
|
|
return -EINVAL;
|
|
|
|
switch (node->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
*length = 0;
|
|
element = node->structure_value;
|
|
while (element != NULL) {
|
|
rc = kmip_node_get_length(element, &len);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
*length += KMIP_TTLV_HEADER_LENGTH + len;
|
|
if ((len % KMIP_TTLV_BLOCK_LENGTH) != 0)
|
|
*length += KMIP_TTLV_BLOCK_LENGTH -
|
|
(len % KMIP_TTLV_BLOCK_LENGTH);
|
|
|
|
element = element->next;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
case KMIP_TYPE_ENUMERATION:
|
|
case KMIP_TYPE_INTERVAL:
|
|
*length = sizeof(int32_t);
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
case KMIP_TYPE_BOOLEAN:
|
|
case KMIP_TYPE_DATE_TIME:
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
*length = sizeof(int64_t);
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
*length = kmip_encode_bignum_length(node->big_integer_value);
|
|
/* BIG INTEGERS must be a multiple of 8 bytes long */
|
|
if ((*length % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0)
|
|
*length += KMIP_BIG_INTEGER_BLOCK_LENGTH -
|
|
(*length % KMIP_BIG_INTEGER_BLOCK_LENGTH);
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
*length = node->length;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
if (node->text_value != NULL)
|
|
*length = strlen(node->text_value);
|
|
else
|
|
*length = 0;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Encode a KMIP node into a BIO using the TTLV encoding.
|
|
*
|
|
* @param node the node to encode
|
|
* @param bio the OpenSSL bio to write the data to
|
|
* @param size On return: the number of bytes written to BIO
|
|
* @param debug if true, debug messages are printed
|
|
*
|
|
* @returns 0 in case of success, or a negative errno value
|
|
*/
|
|
int kmip_encode_ttlv(struct kmip_node *node, BIO *bio, size_t *size,
|
|
bool debug)
|
|
{
|
|
const unsigned char padding[KMIP_TTLV_BLOCK_LENGTH] = { 0 };
|
|
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
|
|
size_t len, elem_len, value_len, pad_len;
|
|
struct kmip_node *element;
|
|
void *value = NULL;
|
|
uint32_t int32;
|
|
uint64_t int64;
|
|
int rc;
|
|
|
|
if (bio == NULL || node == NULL || size == NULL)
|
|
return -EINVAL;
|
|
|
|
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", node->tag,
|
|
node->type, node->length);
|
|
|
|
*size = 0;
|
|
|
|
/* Update node's length field to match node's current data */
|
|
rc = kmip_node_get_length(node, &len);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_node_get_length failed");
|
|
return rc;
|
|
}
|
|
node->length = len;
|
|
|
|
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
|
|
ttlv[0] = (node->tag & 0xff0000) >> 16;
|
|
ttlv[1] = (node->tag & 0xff00) >> 8;
|
|
ttlv[2] = (node->tag & 0xff);
|
|
|
|
/* Type: 1 byte containing a coded value that indicates the data type */
|
|
ttlv[3] = node->type;
|
|
|
|
/* Length: 32-bit binary integer, transmitted big-endian */
|
|
ttlv[4] = (node->length & 0xff000000) >> 24;
|
|
ttlv[5] = (node->length & 0xff0000) >> 16;
|
|
ttlv[6] = (node->length & 0xff00) >> 8;
|
|
ttlv[7] = (node->length & 0xff);
|
|
|
|
if (BIO_write(bio, ttlv, sizeof(ttlv)) != sizeof(ttlv)) {
|
|
kmip_debug(debug, "BIO_write failed");
|
|
return -EIO;
|
|
}
|
|
*size += sizeof(ttlv);
|
|
|
|
switch (node->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
value_len = 0;
|
|
element = node->structure_value;
|
|
while (element != NULL) {
|
|
rc = kmip_encode_ttlv(element, bio, &elem_len, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_encode_ttlv failed");
|
|
return rc;
|
|
}
|
|
value_len += elem_len;
|
|
element = element->next;
|
|
}
|
|
if (value_len != node->length) {
|
|
kmip_debug(debug, "written length %lu not as expected "
|
|
"(%u)", len, node->length);
|
|
return -EIO;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
int32 = htobe32(node->integer_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
int64 = htobe64(node->long_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
value_len = node->length; /* was already calculated above */
|
|
value = malloc(value_len);
|
|
if (value == NULL) {
|
|
kmip_debug(debug, "malloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
rc = kmip_encode_bignum(node->big_integer_value, value,
|
|
value_len);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_encode_bignum failed");
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_ENUMERATION:
|
|
int32 = htobe32(node->enumeration_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_BOOLEAN:
|
|
int64 = node->boolean_value ? 1 : 0;
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
value_len = node->length;
|
|
value = node->text_value;
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
value_len = node->length;
|
|
value = node->bytes_value;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME:
|
|
int64 = htobe64(node->date_time_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_INTERVAL:
|
|
int32 = htobe32(node->interval_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
int64 = htobe64(node->date_time_ext_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", node->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (value != NULL) {
|
|
if (BIO_write(bio, value, value_len) != (int)value_len) {
|
|
kmip_debug(debug, "BIO_write failed");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
}
|
|
*size += value_len;
|
|
|
|
if ((value_len % KMIP_TTLV_BLOCK_LENGTH) != 0) {
|
|
pad_len = KMIP_TTLV_BLOCK_LENGTH -
|
|
(value_len % KMIP_TTLV_BLOCK_LENGTH);
|
|
|
|
kmip_debug(debug, "pad_len: %lu", pad_len);
|
|
if (BIO_write(bio, padding, pad_len) != (int)pad_len) {
|
|
kmip_debug(debug, "BIO_write failed (padding)");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
*size += pad_len;
|
|
}
|
|
|
|
kmip_debug(debug, "size: %lu", *size);
|
|
|
|
rc = 0;
|
|
out:
|
|
if (node->type == KMIP_TYPE_BIG_INTEGER)
|
|
free(value);
|
|
|
|
return rc;
|
|
}
|
|
|