/* * 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 "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; if (value_len > INT_MAX) { rc = -EMSGSIZE; goto out; } 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) { if (*size < pad_len) { rc = -EMSGSIZE; goto out; } *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, prev_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; prev_len = *length; *length += KMIP_TTLV_HEADER_LENGTH + len; if ((len % KMIP_TTLV_BLOCK_LENGTH) != 0) *length += KMIP_TTLV_BLOCK_LENGTH - (len % KMIP_TTLV_BLOCK_LENGTH); if (*length < prev_len) return -EOVERFLOW; 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; }