diff --git a/.gitignore b/.gitignore index 378eac2..c1ec017 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +generated-docs diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d85c2c..c2f0760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,22 @@ set(KMIP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") add_subdirectory(libkmip/src) add_subdirectory(kmippp) +add_subdirectory(kmipclient) +find_package(Doxygen REQUIRED) + +if(DOXYGEN_FOUND) + configure_file(Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile COPYONLY) + + add_custom_target(doc + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM + ) + + # Make the 'doc' target depend on your build targets if necessary + # add_dependencies(doc your_library your_executable) +else() + message(STATUS "Doxygen not found, skipping documentation generation.") +endif() diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..ab18de1 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,36 @@ +# Project information +PROJECT_NAME = libkmip +PROJECT_NUMBER = 0.4.0 +OUTPUT_DIRECTORY = generated-docs + +# Input settings +INPUT = . # Scan the current directory for source files +RECURSIVE = YES + +# Output settings +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +GENERATE_XML = NO +GENERATE_HTMLHELP = YES + +# UML related settings +UML_LOOK = YES +HAVE_DOT = YES +DOT_PATH = /usr/bin/dot # Adjust this path to where your 'dot' executable is located +PLANTUML_JAR_PATH = /usr/share/java/plantuml.jar +PLANTUML_PREPROC = NO +PLANTUML_INCLUDE_PATH = +PLANTUML_CONFIG_FILE = +# Enable class diagram generation +CLASS_DIAGRAMS = YES +COLLABORATION_GRAPH = YES +UML_LIMIT_NUM_FIELDS = 50 +TEMPLATE_RELATIONS = YES +MAX_DOT_GRAPH_DEPTH = 0 +MAX_DOT_GRAPH_NODES = 0 +HIDE_UNDOC_MEMBERS = NO +HIDE_VIRTUAL_FUNCTIONS = NO +SHOW_INCLUDE_FILES = YES +SHOW_USED_FILES = YES +SHOW_FILES = YES diff --git a/kmipclient/CHANGELOG.md b/kmipclient/CHANGELOG.md new file mode 100644 index 0000000..c06cb63 --- /dev/null +++ b/kmipclient/CHANGELOG.md @@ -0,0 +1,3 @@ +Apr, 2025 Version 0.1.0 + +Initial implementation of all functionality available in "kmippp" diff --git a/kmipclient/CMakeLists.txt b/kmipclient/CMakeLists.txt new file mode 100644 index 0000000..6ee6487 --- /dev/null +++ b/kmipclient/CMakeLists.txt @@ -0,0 +1,76 @@ + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) # Optional, but recommended for standard compliance + +add_library( + kmipclient + STATIC + include/KmipClient.hpp + src/KmipClient.cpp + include/NetClient.hpp + src/NetClientOpenSSL.cpp + include/NetClientOpenSSL.hpp + include/v_expected.hpp + include/kmip_data_types.hpp + src/RequestFactory.cpp + src/RequestFactory.hpp + src/KmipCtx.hpp + src/KmipRequest.hpp + src/IOUtils.cpp + src/IOUtils.hpp + include/Kmip.hpp + src/ResponseResult.cpp + src/ResponseResult.hpp + src/kmip_exceptions.hpp + src/AttributesFactory.cpp + src/AttributesFactory.hpp + src/KeyFactory.cpp + src/KeyFactory.hpp + src/Key.cpp + include/Key.hpp + src/StringUtils.cpp + src/StringUtils.hpp + include/Logger.hpp +) + +target_link_libraries(kmipclient kmip) +set_property(TARGET kmipclient PROPERTY POSITION_INDEPENDENT_CODE ON) + +target_include_directories( + kmipclient PUBLIC + $ + $ + ) + +set_target_properties( + kmipclient PROPERTIES PUBLIC_HEADER "Kmip.hpp" +) + +export(TARGETS kmip kmipclient FILE "kmipclient.cmake") + +install( + TARGETS kmipclient + EXPORT kmipclient + DESTINATION cmake + ARCHIVE DESTINATION lib + PUBLIC_HEADER DESTINATION include/ + LIBRARY DESTINATION lib) + +macro(add_example name) + add_executable(example_${name} examples/example_${name}.cpp) + target_link_libraries(example_${name} kmipclient) +endmacro() + +add_example(create_aes) +add_example(register_secret) +add_example(activate) +add_example(get) +add_example(get_name) +add_example(get_secret) +add_example(revoke) +add_example(destroy) +add_example(register_key) +add_example(locate) +add_example(locate_by_group) +add_example(get_all_ids) diff --git a/kmipclient/README.md b/kmipclient/README.md new file mode 100644 index 0000000..f717d28 --- /dev/null +++ b/kmipclient/README.md @@ -0,0 +1,168 @@ +The "kmipclient" library +-- +KMIP client is the C++ library that allows simple access to the KMIP servers using the KMIP protocol. + +The "kmipclient" library wraps up the low-level libkmip (kmip.h, kmip.c) into C++ code. +The purpose of such wrap-up is to: + +## Design goals. + +1. Provide easy to use and hard to misuse interface with forced error processing. +2. Hide low-level details. +3. Minimize manual memory management +4. Make the library easy to extend +5. Exclude mid-level (kmip_bio.c), use the low-level (kmip.c) only +6. Easy to replace network communication level +7. Testability + +## External dependencies + +No extra external dependencies should be used, except existing OpenSSL dependency. +KmipClient itself does not depend on any library except "kmip". The network communication level is injected +into KmipClient instance as implementation of the NetClient interface. The library has ready to use +OpenSSL BIO based implementation called NetClientOpenSSL. User of the library can use any other library to +implement the communication level. + +## High level design + +The top interface wraps network communication level (based on OpenSSL) and the KMIP protocol encoding level. +It is implemented as header-only class in the file “Kmip.hpp” and can be used similar to the old C++ wrapper +(kmippp.h). Actual high level interface consists of two headers: NetClient.hpp. and KmipClient.hpp. + +The first interface is just a contract to wrap low-level network communications similar to well-known +interfaces (socket, OpenSSL bio and others). It contains 4 methods only: connect(), close(), send() +and receive(). This interface also has an implementation, declared “NetClientOpenSSL.hpp”. +It is based on OpenSSL BIO functions. + +The second interface is actual KMIP protocol implementation. It requires a NetClient implementation +as a dependency injection in the constructor. This interface is also similar to the existing C++ wrapper +and can be used the similar whay when properly initialized with the NetClient-derived instance. + +The main difference to the “kmippp.h” is in-band error processing. It uses a template similar to +std::expected from the C++ 23. Though, project may use older C++ standard (C++ 20), so the interface +includes a C++ 20 implementation, that wraps standard implementation or provides replacement if it is absent. + +All KMIP request creation and encoding are encapsulated in the RequestFactory class. All operations are +on stack and do not require memory management. + +All KMIP responses processing are encapsulated in ResponseFactory class. It should be operated on stack +to keep data in place. Copy and move operations are disabled. + +By the protocol, parsed response contains one or more response batch items. To process these items, +ResponseFactory class is used. It’s purpose is to extract values from the response batch item. V +alues are keys, secrets, attributes, etc. This class does not have a state and consists of static methods. + +All operation in the low-level KMIP library are based on context structure KMIP. This structure is +encapsulated in KmipCtx class along with operations on buffers, errors, etc. This class, once created, +is passed by the reference to other classes of the “kmipclient” library. Copy and move operations are +disabled for this class also. Usually, the instance of this class is created on stack in the high-level +methods and does not require memory management. + +The high-level interface usage example: + +```C++ +NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); +KmipClient client (net_client); + + const auto opt_key = client.op_get_key (argv[6]); + if (opt_key.has_value ()) + { + std::cout << "Key: 0x"; + auto k = opt_key.value (); + print_hex (k.value()); + } + else + { + std::cerr << "Can not get key with id:"<< argv[6] << " Cause: "<< opt_key.error().message << std::endl; + }; +``` +As can be seen from the code above, the NetClientOpenSSL class instance is injected as dependency +inversion into the KmipClient class instance. This approach allows to use any net connection with KmipClient. +It is enough to derive the class from NetClient class and wrap 4 calls. + +To understand, how to extend functionality, below is example of request creation: + +```C++ +void +RequestFactory::create_get_rq (KmipCtx &ctx, const id_t &id) +{ + KmipRequest rq (ctx); + TextString uuid = {}; + uuid.size = id.size (); + uuid.value = const_cast(id.c_str ()); + + GetRequestPayload grp {}; + grp.unique_identifier = &uuid; + + RequestBatchItem rbi {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_GET; + rbi.request_payload = &grp; + rq.set_batch_item (&rbi); + rq.encode (); +} +``` +In the example above we use low-level primitives from “kmip.h” to create the RequestBatchItem and +then we add it to the internal member of “KmipRequest” class, which performs appropriate +request encoding in to the KMIP context. + +Below is an example of the response processing: + +```C++ +ve::expected +ResponseResultFactory::get_key (ResponseBatchItem *rbi) +{ + auto *pld = static_cast (rbi->response_payload); + switch (pld->object_type) + { + //name known key to KeyFactory types + case KMIP_OBJTYPE_SYMMETRIC_KEY: + KMIP_OBJTYPE_PUBLIC_KEY: + KMIP_OBJTYPE_PRIVATE_KEY: + KMIP_OBJTYPE_CERTIFICATE: + { + return KeyFactory::parse_response(pld); + }; + default: + return Error(-1,"Invalid response object type."); + } +} + +``` +And here is an example of top-level function implementation + +```C++ +my::expected +KmipClient::op_get_key (const id_t &id) +{ + KmipCtx ctx; + RequestFactory request_factory(ctx); + ResponseFactory rf(ctx); + try + { + request_factory.create_get_rq (id); + io->do_exchange (ctx); + return rf.get_key(0); + } + catch (ErrorException &e) + { + return Error(e.code (), e.what ()); + } +} +``` +As can be seen from the source code, each KMIP low-level entity is encapsulated in some C++ class, +therefore advanced C++ memory management is utilized. Also, the design is avoiding any kind +of smart pointers (almost… sometimes we need it), utilizing on-stack variables. Raw pointers from +the low-level code are used rarely just to pass stack-based data for more detailed processing. + +It is worth of mentioning, that KMIP protocol supports multiple request items ( batch items ) +in one network request. For example, it might be combination of GET and GET_ATTRRIBUTE operations +to have a key with set of it’s attributes. It is important to have key state attribute, +because a key could be outdated, deactivated or marked as compromised. + +The design of this library supports multiple batch items in requests and in responses. + +## Usage + +Please, seee usage examples in the "examples" directory + diff --git a/kmipclient/TODO.md b/kmipclient/TODO.md new file mode 100644 index 0000000..02672fe --- /dev/null +++ b/kmipclient/TODO.md @@ -0,0 +1,10 @@ +TODO +-- +The list of things yet to be done + +1. Test suite for KmipClient class +2. Asymetric keys and certificates support +4. Version negotiation with the KMIP server (Default is 1.4) +5. Multiple batch items requests and responses for cases like "register and activate", "revoke and destroy" +6. Human-readable request and response logging + diff --git a/kmipclient/examples/example_activate.cpp b/kmipclient/examples/example_activate.cpp new file mode 100644 index 0000000..94163f5 --- /dev/null +++ b/kmipclient/examples/example_activate.cpp @@ -0,0 +1,37 @@ +// +// Created by al on 02.04.25. +// +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_activate " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + const auto opt_key = client.op_activate (argv[6]); + if (opt_key.has_value ()) + { + std::cout << "Key wih ID: " << argv[6] << " is activated." << std::endl; + } + else + { + std::cerr << "Can not activate key with id:" << argv[6] << " Cause: " << opt_key.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_create_aes.cpp b/kmipclient/examples/example_create_aes.cpp new file mode 100644 index 0000000..4012092 --- /dev/null +++ b/kmipclient/examples/example_create_aes.cpp @@ -0,0 +1,34 @@ + + +#include "../include/Kmip.hpp" +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_create_aes " + << std::endl; + return -1; + } + + Kmip kmip (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + + auto key_opt = kmip.client ().op_create_aes_key (argv[6], "TestGroup"); + if (key_opt.has_value ()) + { + const name_t &key_id = key_opt.value (); + std::cout << "Key ID: " << key_id << std::endl; + } + else + { + std::cerr << "Can not create key with name:" << argv[6] << " Cause: " << key_opt.error ().message << std::endl; + } + return 0; +} diff --git a/kmipclient/examples/example_destroy.cpp b/kmipclient/examples/example_destroy.cpp new file mode 100644 index 0000000..f547ab4 --- /dev/null +++ b/kmipclient/examples/example_destroy.cpp @@ -0,0 +1,38 @@ +// +// Created by al on 02.04.25. +// + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_destroy " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + const auto opt_key = client.op_destroy (argv[6]); + if (opt_key.has_value ()) + { + std::cout << "Key ID: " << argv[6] << " is destroyed." << std::endl; + } + else + { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << opt_key.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get.cpp b/kmipclient/examples/example_get.cpp new file mode 100644 index 0000000..230da42 --- /dev/null +++ b/kmipclient/examples/example_get.cpp @@ -0,0 +1,48 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +void +print_hex (const kmipclient::key_t &key) +{ + for (auto const &c : key) + { + std::cout << std::hex << static_cast (c); + } + std::cout << std::endl; +} + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIP_LIB_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_get " << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + const auto opt_key = client.op_get_key (argv[6]); + if (opt_key.has_value ()) + { + std::cout << "Key: 0x"; + auto k = opt_key.value (); + print_hex (k.value ()); + } + else + { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << opt_key.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_all_ids.cpp b/kmipclient/examples/example_get_all_ids.cpp new file mode 100644 index 0000000..9ff5a74 --- /dev/null +++ b/kmipclient/examples/example_get_all_ids.cpp @@ -0,0 +1,53 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 6) + { + std::cerr << "Usage: example_get_all_ids " << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + const auto opt_ids = client.op_all (KMIP_ENTITY_SYMMETRIC_KEY); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of symmetric keys:" << std::endl; + for (const auto &id : opt_ids.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get keys." << " Cause: " << opt_ids.error ().message << std::endl; + }; + + const auto opt_ids_s = client.op_all (KMIP_ENTITY_SECRET_DATA); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of secret data:" << std::endl; + for (const auto &id : opt_ids_s.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get secrets." << " Cause: " << opt_ids.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_name.cpp b/kmipclient/examples/example_get_name.cpp new file mode 100644 index 0000000..e5cdf8f --- /dev/null +++ b/kmipclient/examples/example_get_name.cpp @@ -0,0 +1,48 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_get_name " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + // get name + const auto opt_attr = client.op_get_attribute (argv[6], KMIP_ATTR_NAME_NAME); + if (opt_attr.has_value ()) + { + std::cout << "ID: " << argv[6] << " Name: " << opt_attr.value () << std::endl; + } + else + { + std::cerr << "Can not get name for id:" << argv[6] << " Cause: " << opt_attr.error ().message << std::endl; + }; + + // get state + const auto opt_attr2 = client.op_get_attribute (argv[6], KMIP_ATTR_NAME_STATE); + if (opt_attr.has_value ()) + { + std::cout << "ID: " << argv[6] << " State: " << opt_attr2.value () << std::endl; + } + else + { + std::cerr << "Can not get state for id:" << argv[6] << " Cause: " << opt_attr2.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_secret.cpp b/kmipclient/examples/example_get_secret.cpp new file mode 100644 index 0000000..4271e16 --- /dev/null +++ b/kmipclient/examples/example_get_secret.cpp @@ -0,0 +1,36 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 6) + { + std::cerr << "Usage: example_get_secret " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + auto opt_secret = client.op_get_secret (argv[6]); + if (opt_secret.has_value ()) + { + std::cout << "Secret: " << opt_secret.value ().value << std::endl; + } + else + { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << opt_secret.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_locate.cpp b/kmipclient/examples/example_locate.cpp new file mode 100644 index 0000000..074a229 --- /dev/null +++ b/kmipclient/examples/example_locate.cpp @@ -0,0 +1,55 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_locate " << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + std::cout << "Searching for name: " << argv[6] << std::endl; + + const auto opt_ids = client.op_locate_by_name (argv[6], KMIP_ENTITY_SYMMETRIC_KEY); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of symmetric keys:" << std::endl; + for (const auto &id : opt_ids.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get keys with name:" << argv[6] << " Cause: " << opt_ids.error ().message << std::endl; + }; + + const auto opt_ids_s = client.op_locate_by_name (argv[6], KMIP_ENTITY_SECRET_DATA); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of secret data:" << std::endl; + for (const auto &id : opt_ids_s.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get secrets with name:" << argv[6] << " Cause: " << opt_ids.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_locate_by_group.cpp b/kmipclient/examples/example_locate_by_group.cpp new file mode 100644 index 0000000..29e9d6a --- /dev/null +++ b/kmipclient/examples/example_locate_by_group.cpp @@ -0,0 +1,58 @@ + + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_locate_by_group " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + std::cout << "Searching for group with name: " << argv[6] << std::endl; + + const auto opt_ids = client.op_locate_by_group (argv[6], KMIP_ENTITY_SYMMETRIC_KEY); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of symmetric keys:"; + for (const auto &id : opt_ids.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get keys with group name:" << argv[6] << " Cause: " << opt_ids.error ().message + << std::endl; + }; + + const auto opt_ids_s = client.op_locate_by_group (argv[6], KMIP_ENTITY_SECRET_DATA); + if (opt_ids.has_value ()) + { + std::cout << "Found IDs of secret data:"; + for (const auto &id : opt_ids_s.value ()) + { + std::cout << id << std::endl; + } + } + else + { + std::cerr << "Can not get secrets with group name:" << argv[6] << " Cause: " << opt_ids.error ().message + << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_register_key.cpp b/kmipclient/examples/example_register_key.cpp new file mode 100644 index 0000000..143398d --- /dev/null +++ b/kmipclient/examples/example_register_key.cpp @@ -0,0 +1,44 @@ +// +// Created by al on 02.04.25. +// +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmip_data_types.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 8) + { + std::cerr << "Usage:example_register_key " + " " + << std::endl; + return -1; + } + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + auto k = Key::aes_from_hex (argv[7]); + if (k.has_error ()) + { + std::cerr << "Can not create AES key from input. Cause: " << k.error ().message << std::endl; + } + const auto opt_id = client.op_register_key (argv[6], "TestGroup", k.value ()); + if (opt_id.has_value ()) + { + std::cout << "Key registered. ID: " << opt_id.value () << std::endl; + ; + } + else + { + std::cerr << "Can not register key:" << argv[6] << " Cause: " << opt_id.error ().message << std::endl; + }; + + return 0; +} \ No newline at end of file diff --git a/kmipclient/examples/example_register_secret.cpp b/kmipclient/examples/example_register_secret.cpp new file mode 100644 index 0000000..820f614 --- /dev/null +++ b/kmipclient/examples/example_register_secret.cpp @@ -0,0 +1,36 @@ + + +#include "../include/Kmip.hpp" +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_register_secret " + " " + << std::endl; + return -1; + } + + Kmip kmip (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + + auto id_opt = kmip.client ().op_register_secret (argv[6], "TestGroup", argv[7], KMIP_SECRET_TYPE_PASSWORD); + if (id_opt.has_value ()) + { + const name_t &id = id_opt.value (); + std::cout << "Secret ID: " << id << std::endl; + } + else + { + std::cerr << "Can not register secret with name:" << argv[6] << " Cause: " << id_opt.error ().message + << std::endl; + } + return 0; +} diff --git a/kmipclient/examples/example_revoke.cpp b/kmipclient/examples/example_revoke.cpp new file mode 100644 index 0000000..b74f9e0 --- /dev/null +++ b/kmipclient/examples/example_revoke.cpp @@ -0,0 +1,38 @@ +// +// Created by al on 02.04.25. +// + +#include "../include/KmipClient.hpp" +#include "../include/NetClientOpenSSL.hpp" +#include "../include/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int +main (int argc, char **argv) +{ + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + if (argc < 7) + { + std::cerr << "Usage: example_revoke " + << std::endl; + return -1; + } + + NetClientOpenSSL net_client (argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client (net_client); + + const auto opt_key = client.op_revoke (argv[6], KMIP_REVIKE_UNSPECIFIED, "Deactivate", 0L); + if (opt_key.has_value ()) + { + std::cout << "Key ID: " << argv[6] << " is deactivated." << std::endl; + } + else + { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << opt_key.error ().message << std::endl; + }; + + return 0; +} diff --git a/kmipclient/include/Key.hpp b/kmipclient/include/Key.hpp new file mode 100644 index 0000000..6319f48 --- /dev/null +++ b/kmipclient/include/Key.hpp @@ -0,0 +1,85 @@ +// +// Created by al on 01.04.25. +// + +#ifndef KEY_HPP +#define KEY_HPP +#include + +#include "include/v_expected.hpp" +#include "kmip_data_types.hpp" + +namespace kmipclient +{ + +class KeyFactory; + +// TODO: should I expose kmip.h types here in the public interface? + +enum KeyType +{ + KEY_TYPE_PRIVATE_KEY, + KEY_TYPE_PUBLIC_KEY, + KEY_TYPE_SYMMETRIC_KEY, + KEY_TYPE_CERTIFICATE, + KEY_TYPE_OTHER +}; + +enum KeyAlgorithm +{ + KEY_ALGORITHM_AES, + KEY_ALGORITHM_RSA, + KEY_ALGORITHM_EC, +}; + +class Key +{ + friend class KeyFactory; + +public: + explicit Key (key_t value, KeyType type, KeyAlgorithm algo, attributes_t attributes) + : key_value (std::move (value)), key_attributes (std::move (attributes)), key_type (type), + cryptographic_algorithm (algo) {}; + + const key_t & + value () + { + return key_value; + }; + const attributes_t & + attributes () + { + return key_attributes; + }; + + KeyType + type () const + { + return key_type; + }; + KeyAlgorithm + algorithm () const + { + return cryptographic_algorithm; + }; + + static ve::expected aes_from_hex (std::string hex); + static ve::expected aes_from_base64 (std::string hex); + /** + * Reads a PEM-formatted string, decides what type of key it has + * (X.509 certificate, public key, private key) and creates the + * Key instance from it. + * @param pem PEM-formatted string + * @return Key of corresponding type + */ + static ve::expected from_PEM (std::string pem); + +private: + key_t key_value; + attributes_t key_attributes; + KeyType key_type; + KeyAlgorithm cryptographic_algorithm; +}; +} + +#endif // KEY_HPP diff --git a/kmipclient/include/Kmip.hpp b/kmipclient/include/Kmip.hpp new file mode 100644 index 0000000..47e849e --- /dev/null +++ b/kmipclient/include/Kmip.hpp @@ -0,0 +1,42 @@ +// +// Created by al on 24.03.25. +// + +#ifndef KMIP_HPP +#define KMIP_HPP +#include "KmipClient.hpp" +#include "NetClientOpenSSL.hpp" +#include "kmipclient_version.hpp" + +namespace kmipclient +{ +/** + * Simplified interface with network client initialization + */ +class Kmip +{ +public: + Kmip (const char *host, const char *port, const char *clientCertificateFn, const char *clientKeyFn, + const char *serverCaCertFn, int timeout_ms) + : m_net_client (host, port, clientCertificateFn, clientKeyFn, serverCaCertFn, timeout_ms), + m_client (m_net_client) { + + }; + KmipClient & + client () + { + return m_client; + }; + +private: + /** + * Instance of the NetClient using OpenSSL BIO + */ + NetClientOpenSSL m_net_client; + /** + * KMIP protocol client, initialized with m_net_client in the constructor + */ + KmipClient m_client; +}; +} +#endif // KMIP_HPP diff --git a/kmipclient/include/KmipClient.hpp b/kmipclient/include/KmipClient.hpp new file mode 100644 index 0000000..dc4f81f --- /dev/null +++ b/kmipclient/include/KmipClient.hpp @@ -0,0 +1,138 @@ +// +// Created by al on 10.03.25. +// + +#ifndef KMIP_CLIENT_HPP +#define KMIP_CLIENT_HPP + +#include "../src/RequestFactory.hpp" +#include "Key.hpp" +#include "Logger.hpp" +#include "NetClient.hpp" +#include "kmip_data_types.hpp" +#include "v_expected.hpp" + +#include + +namespace kmipclient +{ + +using namespace ve; // std::expected in C++ 23 or ve::expected in earlier versions + +class IOUtils; + +class KmipClient +{ +public: + explicit KmipClient (NetClient &net_client); + explicit KmipClient (NetClient &net_client, const std::shared_ptr &log); + ~KmipClient (); + // no copy, no move + KmipClient (const KmipClient &) = delete; + KmipClient &operator= (const KmipClient &) = delete; + KmipClient (KmipClient &&) = delete; + KmipClient &operator= (KmipClient &&) = delete; + + /** + * KMIP register operation, stores an proposed key on the server + * @param name The "Name" attribute of the key + * @param group The group name for the key + * @param k The key to register + * @return ID of the key if success or error + */ + [[nodiscard]] expected op_register_key (const name_t &name, const name_t &group, Key &k) const; + + /** + * + * @param name The "Name" attribute of the secret + * @param group The group name for the secret + * @param secret The secret to register + * @param secret_type Type of the secret, @see + * @return ID of the key if success or error + */ + [[nodiscard]] expected op_register_secret (const name_t &name, const name_t &group, std::string secret, + enum kmip_secret_type secret_type) const; + + /** KMIP::create operation, generates a new AES-256 symmetric key on the server + * @param name name attribute of the key + * @param group group attribute of the key + * @return ID of the created ley + */ + [[nodiscard]] expected op_create_aes_key (const name_t &name, const name_t &group) const; + + /** + * Gets key by ID + * @param id ?Id of the Key + * @return The Key or Error + */ + [[nodiscard]] expected op_get_key (const id_t &id) const; + + /** + * Gets secret by the ID + * @param id ID of the secret + * @return The secret or Error + */ + [[nodiscard]] expected op_get_secret (const id_t &id) const; + + /** + * Changes key/secret state from pre-active to active. + * @param id ID of the entity + * @return ID of the entity or Error + */ + [[nodiscard]] expected op_activate (const id_t &id) const; + + /** KMIP::get_attribute operation, retrieve the name of a symmetric key by id + * @paran id ID of the entity + * @param attr_name name of the attribute, e.g. "Name", "State" + * @return value of the attribute or error + */ + [[nodiscard]] expected op_get_attribute (const id_t &id, const name_t &attr_name) const; + + /** KMIP::locate operation, retrieve symmetric keys by name + * Note: HasiCorp Vault does not allow name duplication + * @param name name of the entity + * @param type type of the entity to retrieve + */ + [[nodiscard]] expected op_locate_by_name (const name_t &name, kmip_entity_type type) const; + + /** + * Gets IDs of entities by the group name + * @param group group name + * @return vector of key IDs or Error + */ + [[nodiscard]] expected op_locate_by_group (const name_t &group, kmip_entity_type type) const; + + /** + * Revokes/deactivates key or other entity + * @param id ID of the entity + * @param reason the reason to revoke + * @param message Message of revocation to be saved in the server side + * @param occurrence_time time of the incident, 0 for the key deactivation + * @return ID of the entity or error + */ + [[nodiscard]] expected op_revoke (const id_t &id, enum kmip_revocation_reason reason, const name_t &message, + time_t occurrence_time) const; + /** + * Destroys an entity by ID + * NOTE: Entity should be revoked/deactivated + * @param id ID of the entity + * @return ID of the entity or error + */ + [[nodiscard]] expected op_destroy (const id_t &id) const; + + /** + * KMIP::locate operation, retrieve all symmetric keys + * note: name can be empty, and will retrieve all keys + * @param type type of the entity to fetch + * @return vector of IDs of entities + */ + [[nodiscard]] expected op_all (kmip_entity_type type) const; + +private: + NetClient &net_client; + std::shared_ptr logger; + std::unique_ptr io; +}; + +} +#endif // KMIP_CLIENT_HPP diff --git a/kmipclient/include/Logger.hpp b/kmipclient/include/Logger.hpp new file mode 100644 index 0000000..7a01043 --- /dev/null +++ b/kmipclient/include/Logger.hpp @@ -0,0 +1,19 @@ +// +// Created by al on 09.04.25. +// + +#ifndef LOGGER_HPP +#define LOGGER_HPP +#include +class Logger +{ +public: + Logger () = default; + virtual ~Logger () = default; + Logger (const Logger &other) = delete; + Logger &operator= (const Logger &other) = delete; + Logger (Logger &&other) = delete; + Logger &operator= (Logger &&other) = delete; + virtual void log (int level, std::string msg) = 0; +}; +#endif // LOGGER_HPP diff --git a/kmipclient/include/NetClient.hpp b/kmipclient/include/NetClient.hpp new file mode 100644 index 0000000..f9ae812 --- /dev/null +++ b/kmipclient/include/NetClient.hpp @@ -0,0 +1,52 @@ +// +// Created by al on 11.03.25. +// + +#ifndef KMIP_NET_CLIENT_HPP +#define KMIP_NET_CLIENT_HPP + +#include + +namespace kmipclient +{ +class NetClient +{ +public: + NetClient (const char *host, const char *port, const char *clientCertificateFn, const char *clientKeyFn, + const char *serverCaCertFn, int timeout_ms) noexcept : m_host (host), + m_port (port), + m_clientCertificateFn (clientCertificateFn), + m_clientKeyFn (clientKeyFn), + m_serverCaCertificateFn (serverCaCertFn), + m_timeout_ms (timeout_ms) {}; + + virtual ~NetClient () = default; + // no copy, no move + NetClient (const NetClient &) = delete; + virtual NetClient &operator= (const NetClient &) = delete; + NetClient (NetClient &&) = delete; + virtual NetClient &operator= (NetClient &&) = delete; + + virtual int connect () = 0; + virtual void close () = 0; + [[nodiscard]] bool + is_connected () const + { + return m_isConnected; + } + + virtual int send (const void *data, int dlen) = 0; + virtual int recv (void *data, int dlen) = 0; + +protected: + std::string m_host; + std::string m_port; + std::string m_clientCertificateFn; + std::string m_clientKeyFn; + std::string m_serverCertFn; + std::string m_serverCaCertificateFn; + int m_timeout_ms; + bool m_isConnected = false; +}; +} +#endif // KMIP_NET_CLIENT_HPP diff --git a/kmipclient/include/NetClientOpenSSL.hpp b/kmipclient/include/NetClientOpenSSL.hpp new file mode 100644 index 0000000..30b6e43 --- /dev/null +++ b/kmipclient/include/NetClientOpenSSL.hpp @@ -0,0 +1,42 @@ +// +// Created by al on 17.03.25. +// + +#ifndef KMIPNETCLILENTOPENSSL_HPP +#define KMIPNETCLILENTOPENSSL_HPP + +#include "NetClient.hpp" + +extern "C" +{ // we do not want to expose SSL stuff ti class users + typedef struct ssl_ctx_st SSL_CTX; + typedef struct bio_st BIO; +} + +namespace kmipclient +{ + +class NetClientOpenSSL : public NetClient +{ +public: + NetClientOpenSSL (const char *host, const char *port, const char *clientCertificateFn, const char *clientKeyFn, + const char *serverCaCertFn, int timeout_ms); + ~NetClientOpenSSL () override; + // no copy, no move + NetClientOpenSSL (const NetClient &) = delete; + NetClientOpenSSL &operator= (const NetClient &) override = delete; + NetClientOpenSSL (NetClient &&) = delete; + NetClientOpenSSL &operator= (NetClient &&) override = delete; + + int connect () override; + void close () override; + int send (const void *data, int dlen) override; + int recv (void *data, int dlen) override; + +private: + SSL_CTX *ctx_ = nullptr; + BIO *bio_ = nullptr; +}; +} + +#endif // KMIPNETCLILENTOPENSSL_HPP diff --git a/kmipclient/include/kmip_data_types.hpp b/kmipclient/include/kmip_data_types.hpp new file mode 100644 index 0000000..6d3a118 --- /dev/null +++ b/kmipclient/include/kmip_data_types.hpp @@ -0,0 +1,118 @@ +// +// Created by al on 18.03.25. +// + +#ifndef DATA_TYPES_HPP +#define DATA_TYPES_HPP + +#include +#include +#include +#include + +namespace kmipclient +{ + +// known attributes so far +#define KMIP_ATTR_NAME_NAME "Name" +#define KMIP_ATTR_NAME_STATE "State" + +using key_t = std::vector; +using bin_data_t = std::vector; +using id_t = std::string; +using ids_t = std::vector; +using name_t = std::string; +using secret_t = std::string; +using attributes_t = std::unordered_map; + +// should be the same as enum object_type in kmip.h +enum kmip_entity_type +{ + /* KMIP 1.0 */ + KMIP_ENTITY_CERTIFICATE = 0x01, + KMIP_ENTITY_SYMMETRIC_KEY = 0x02, + KMIP_ENTITY_PUBLIC_KEY = 0x03, + KMIP_ENTITY_PRIVATE_KEY = 0x04, + KMIP_ENTITY_SPLIT_KEY = 0x05, + KMIP_ENTITY_TEMPLATE = 0x06, /* Deprecated as of KMIP 1.3 */ + KMIP_ENTITY_SECRET_DATA = 0x07, + KMIP_ENTITY_OPAQUE_OBJECT = 0x08, + /* KMIP 1.2 */ + KMIP_ENTITY_PGP_KEY = 0x09, + /* KMIP 2.0 */ + KMIP_ENTITY_CERTIFICATE_REQUEST = 0x0A +}; + +enum kmip_entity_state // should be the same as "enum" state in kmip.h +{ + /* KMIP 1.0 */ + KMIP_STATE_PRE_ACTIVE = 0x01, + KMIP_STATE_ACTIVE = 0x02, + KMIP_STATE_DEACTIVATED = 0x03, + KMIP_STATE_COMPROMISED = 0x04, + KMIP_STATE_DESTROYED = 0x05, + KMIP_STATE_DESTROYED_COMPROMISED = 0x06 +}; + +inline std::ostream & +operator<< (std::ostream &out, const kmip_entity_state value) +{ + const char *str; + switch (value) + { +#define PROCESS_VAL(p) \ + case (p): \ + str = #p; \ + break; + PROCESS_VAL (KMIP_STATE_PRE_ACTIVE); + PROCESS_VAL (KMIP_STATE_ACTIVE); + PROCESS_VAL (KMIP_STATE_DEACTIVATED); + PROCESS_VAL (KMIP_STATE_COMPROMISED); + PROCESS_VAL (KMIP_STATE_DESTROYED); + PROCESS_VAL (KMIP_STATE_DESTROYED_COMPROMISED); +#undef PROCESS_VAL + default: + str = "UNKNOWN_KMIP_STATE"; + break; // Handle unknown values + } + return out << str; +} + +enum kmip_secret_type +{ + KMIP_SECRET_TYPE_NONE = 0, + KMIP_SECRET_TYPE_PASSWORD = 0x01, + KMIP_SECRET_TYPE_SEED = 0x02, + KMIP_SECRET_TYPE_SECRET_DATA_EXTENSIONS = 0x80000000 +}; + +enum kmip_revocation_reason +{ + /* KMIP 1.0 */ + KMIP_REVIKE_UNSPECIFIED = 0x01, + KMIP_REVIKE_KEY_COMPROMISE = 0x02, + KMIP_REVIKE_CA_COMPROMISE = 0x03, + KMIP_REVIKE_AFFILIATION_CHANGED = 0x04, + KMIP_REVIKE_SUSPENDED = 0x05, + KMIP_REVIKE_CESSATION_OF_OPERATION = 0x06, + KMIP_REVIKE_PRIVILEDGE_WITHDRAWN = 0x07, + KMIP_REVIKE_REVOCATION_EXTENSIONS = 0x80000000 +}; + +class Secret +{ +public: + secret_t value; + int state = 0; + int secret_type = 0; +}; + +class Error +{ +public: + int code; + std::string message; +}; + +} +#endif // DATA_TYPES_HPP diff --git a/kmipclient/include/kmipclient_version.hpp b/kmipclient/include/kmipclient_version.hpp new file mode 100644 index 0000000..09f865c --- /dev/null +++ b/kmipclient/include/kmipclient_version.hpp @@ -0,0 +1,17 @@ +#ifndef KMIPCLIENT_VERSION_H +#define KMIPCLIENT_VERSION_H + +#include "libkmip_version.h" + +#define KMIPCLIENT_VERSION_MAJOR 0 +#define KMIPCLIENT_VERSION_MINOR 1 +#define KMIPCLIENT_VERSION_PATCH 0 + +#define KMIPCLIENT_STRINGIFY_I(x) #x +#define KMIPCLIENT_TOSTRING_I(x) KMIPCLIENT_STRINGIFY_I (x) + +#define KMIPCLIENT_VERSION_STR \ + KMIPCLIENT_TOSTRING_I (KMIPCLIENT_VERSION_MAJOR) \ + "." KMIPCLIENT_TOSTRING_I (KMIPCLIENT_VERSION_MINOR) "." KMIPCLIENT_TOSTRING_I (KMIPCLIENT_VERSION_PATCH) + +#endif // KMIPCLIENT_VERSION_H diff --git a/kmipclient/include/v_expected.hpp b/kmipclient/include/v_expected.hpp new file mode 100644 index 0000000..05d8088 --- /dev/null +++ b/kmipclient/include/v_expected.hpp @@ -0,0 +1,295 @@ +// +// Created by al on 18.03.25. +// +#ifndef MY_EXPECTED_HPP +#define MY_EXPECTED_HPP + +#if __cplusplus < 202302L // Check if the C++ standard is less than C++23 +#include +#include +#include +#include + +namespace ve +{ + +template class expected +{ +private: + std::optional value_; + std::optional error_; + +public: + expected () = default; + + expected (const T &val) : value_ (val) {} + expected (T &&val) : value_ (std::move (val)) {} + + expected (const E &err) : error_ (err) {} + expected (E &&err) : error_ (std::move (err)) {} + + expected (const expected &other) = default; + expected (expected &&other) = default; + + expected &operator= (const expected &other) = default; + expected &operator= (expected &&other) = default; + + [[nodiscard]] bool + has_value () const + { + return value_.has_value (); + } + + [[nodiscard]] bool + has_error () const + { + return error_.has_value (); + } + + [[nodiscard]] const T & + value () const + { + if (!has_value ()) + { + throw std::bad_optional_access (); // Or a custom exception + } + return *value_; + } + + [[nodiscard]] T & + value () + { + if (!has_value ()) + { + throw std::bad_optional_access (); // Or a custom exception + } + return *value_; + } + + [[nodiscard]] const E & + error () const + { + if (!has_error ()) + { + throw std::logic_error ("Accessing error of an expected that holds a value"); + } + return *error_; + } + + [[nodiscard]] E & + error () + { + if (!has_error ()) + { + throw std::logic_error ("Accessing error of an expected that holds a value"); + } + return *error_; + } + + template + [[nodiscard]] auto + and_then (F func) const + { + if (has_value ()) + { + return func (value ()); + } + else + { + return expected, E> (error ()); + } + } + + template + [[nodiscard]] auto + or_else (F func) const + { + if (has_error ()) + { + return func (error ()); + } + else + { + return expected > (value ()); + } + } + + template + [[nodiscard]] expected + transform (auto func) const + { + if (has_value ()) + { + return expected (func (value ())); + } + else + { + return expected (error ()); + } + } + + template + [[nodiscard]] auto + transform_error (F func) const -> expected > + { + if (has_error ()) + { + return expected > (std::invoke (func, error ())); + } + else + { + return expected > (value ()); + } + } + + // Add more functionalities as needed, like `value_or`, implicit conversions, etc. +}; + +} // namespace ve + +#else // C++23 or later +#include +#include +#include +#include + +namespace ve +{ + +template class expected +{ +private: + std::optional value_; + std::optional error_; + +public: + expected () = default; + + expected (const T &val) : value_ (val) {} + expected (T &&val) : value_ (std::move (val)) {} + + expected (const E &err) : error_ (err) {} + expected (E &&err) : error_ (std::move (err)) {} + + expected (const expected &other) = default; + expected (expected &&other) = default; + + expected &operator= (const expected &other) = default; + expected &operator= (expected &&other) = default; + + [[nodiscard]] bool + has_value () const + { + return value_.has_value (); + } + + [[nodiscard]] bool + has_error () const + { + return error_.has_value (); + } + + [[nodiscard]] const T & + value () const + { + if (!has_value ()) + { + throw std::bad_optional_access (); // Or a custom exception + } + return *value_; + } + + [[nodiscard]] T & + value () + { + if (!has_value ()) + { + throw std::bad_optional_access (); // Or a custom exception + } + return *value_; + } + + [[nodiscard]] const E & + error () const + { + if (!has_error ()) + { + throw std::logic_error ("Accessing error of an expected that holds a value"); + } + return *error_; + } + + [[nodiscard]] E & + error () + { + if (!has_error ()) + { + throw std::logic_error ("Accessing error of an expected that holds a value"); + } + return *error_; + } + + template + [[nodiscard]] auto + and_then (F func) const + { + if (has_value ()) + { + return func (value ()); + } + else + { + return expected, E> (error ()); + } + } + + template + [[nodiscard]] auto + or_else (F func) const + { + if (has_error ()) + { + return func (error ()); + } + else + { + return expected > (value ()); + } + } + + template + [[nodiscard]] expected + transform (auto func) const + { + if (has_value ()) + { + return expected (func (value ())); + } + else + { + return expected (error ()); + } + } + + template + [[nodiscard]] auto + transform_error (F func) const -> expected > + { + if (has_error ()) + { + return expected > (std::invoke (func, error ())); + } + else + { + return expected > (value ()); + } + } + + // Add more functionalities as needed, like `value_or`, implicit conversions, etc. +}; + +} // namespace ve + +#endif // __cplusplus < 202302L + +#endif // MY_EXPECTED_HPP \ No newline at end of file diff --git a/kmipclient/src/AttributesFactory.cpp b/kmipclient/src/AttributesFactory.cpp new file mode 100644 index 0000000..50cfbd6 --- /dev/null +++ b/kmipclient/src/AttributesFactory.cpp @@ -0,0 +1,16 @@ +// +// Created by al on 01.04.25. +// + +#include "AttributesFactory.hpp" + +namespace kmipclient +{ + +attributes_t +kmipclient::AttributesFactory::parse (Attribute *attribute, size_t attribute_count) +{ + attributes_t res; + return res; +} +} \ No newline at end of file diff --git a/kmipclient/src/AttributesFactory.hpp b/kmipclient/src/AttributesFactory.hpp new file mode 100644 index 0000000..64b5fc9 --- /dev/null +++ b/kmipclient/src/AttributesFactory.hpp @@ -0,0 +1,20 @@ +// +// Created by al on 01.04.25. +// + +#ifndef ATTRIBUTESFACTORY_HPP +#define ATTRIBUTESFACTORY_HPP +#include "include/kmip_data_types.hpp" +#include "kmip.h" + +namespace kmipclient +{ +class AttributesFactory +{ +public: + AttributesFactory () = default; + static attributes_t parse (Attribute *attribute, size_t attribute_count); +}; +} + +#endif // ATTRIBUTESFACTORY_HPP diff --git a/kmipclient/src/IOUtils.cpp b/kmipclient/src/IOUtils.cpp new file mode 100644 index 0000000..ffe7bfa --- /dev/null +++ b/kmipclient/src/IOUtils.cpp @@ -0,0 +1,93 @@ +// +// Created by al on 22.03.25. +// + +#include "IOUtils.hpp" +#include "ResponseFactory.hpp" +#include "kmip_exceptions.hpp" + +#include + +#include +#include + +namespace kmipclient +{ +#define KMIP_MSG_LENGTH_BYTES 8 + +void +IOUtils::send (KmipCtx &kmip_ctx) +{ + KMIP *ctx = kmip_ctx.get (); + // TODO: cleanup int type mess + int dlen = ctx->index - ctx->buffer; + if (int sent = net_client.send (ctx->buffer, dlen); sent < dlen) + { + kmip_ctx.free_buffer (); + throw ErrorException (-1, std::format ("Can not send request. Bytes total: {}, bytes sent: {}", dlen, sent)); + } + kmip_ctx.free_buffer (); +} + +void +IOUtils::receive_message_in_ctx (KmipCtx &kmip_ctx) +{ + uint8_t msg_len_buf[KMIP_MSG_LENGTH_BYTES]; + + int received = net_client.recv (&msg_len_buf, KMIP_MSG_LENGTH_BYTES); + if (received < KMIP_MSG_LENGTH_BYTES) + { + kmip_ctx.free_buffer (); + throw ErrorException (-1, std::format ("Can not receive response length. Bytes total: {}, bytes received: {}", + KMIP_MSG_LENGTH_BYTES, received)); + } + + // this is ugly method to get message length!!! + kmip_ctx.set_buffer (msg_len_buf, KMIP_MSG_LENGTH_BYTES); + KMIP *ctx = kmip_ctx.get (); + ctx->index += 4; + int length = 0; + kmip_decode_int32_be (ctx, &length); + // + if (length > ctx->max_message_size) + { + throw ErrorException (-1, std::format ("Message too long. Length: {}", length)); + } + // TODO: deallocate buffer + kmip_ctx.alloc_buffer (KMIP_MSG_LENGTH_BYTES + length); + memcpy (ctx->buffer, msg_len_buf, KMIP_MSG_LENGTH_BYTES); + received = net_client.recv (ctx->buffer + KMIP_MSG_LENGTH_BYTES, length); + if (received < length) + { + kmip_ctx.free_buffer (); + throw ErrorException ( + -1, std::format ("Can not receive response. Bytes total: {}, bytes received: {}", length, received)); + } + if (logger != nullptr) + { + logger->log (1, print_response ()); + } +} + +void +IOUtils::do_exchange (KmipCtx &kmip_ctx) +{ + send (kmip_ctx); + receive_message_in_ctx (kmip_ctx); +} + +std::string +IOUtils::print_request () +{ + // TODO: implement request text representation + return "NOT_IMPLEMENTED"; +} + +std::string +IOUtils::print_response () +{ + // TODO: implement response text representation + return "NOT_IMPLEMENTED"; +} + +} // namespace diff --git a/kmipclient/src/IOUtils.hpp b/kmipclient/src/IOUtils.hpp new file mode 100644 index 0000000..463ea04 --- /dev/null +++ b/kmipclient/src/IOUtils.hpp @@ -0,0 +1,40 @@ +// +// Created by al on 22.03.25. +// + +#ifndef IOUTILS_HPP +#define IOUTILS_HPP +#include "KmipCtx.hpp" +#include "include/Logger.hpp" +#include "include/NetClient.hpp" +#include "include/kmip_data_types.hpp" +#include "include/v_expected.hpp" + +#include +#include + +namespace kmipclient +{ + +class IOUtils +{ + +public: + explicit IOUtils (NetClient &nc) : net_client (nc) {}; + explicit IOUtils (NetClient &nc, std::shared_ptr log) : net_client (nc) { logger = log; }; + + void do_exchange (KmipCtx &kmip_ctx); + // log for debug purposes, not implemented yet + std::string print_request (); + std::string print_response (); + +private: + void send (KmipCtx &kmip_ctx); + void receive_message_in_ctx (KmipCtx &kmip_ctx); + NetClient &net_client; + std::shared_ptr logger; +}; + +} // namespace + +#endif // IOUTILS_HPP diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp new file mode 100644 index 0000000..92fc413 --- /dev/null +++ b/kmipclient/src/Key.cpp @@ -0,0 +1,39 @@ +// +// Created by al on 01.04.25. +// + +#include "../include/Key.hpp" +#include "../include/kmip_data_types.hpp" +#include "StringUtils.hpp" +#include "kmip.h" + +#include + +namespace kmipclient +{ + +ve::expected +Key::aes_from_hex (std::string hex) +{ + auto hex_parsed = StringUtils::fromHex (hex); + if (hex_parsed.has_error ()) + return hex_parsed.error (); + if (size_t size = hex_parsed.value ().size (); size == 128 || size == 256 || size == 192) + { + return Error{ -1, std::format ("Invalid RSA key length: {}. Should be 128, 192 or 256.", size) }; + } + return Key (hex_parsed.value (), KeyType::KEY_TYPE_SYMMETRIC_KEY, KeyAlgorithm::KEY_ALGORITHM_AES, {}); +} + +ve::expected +Key::aes_from_base64 (std::string hex) +{ + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); +} + +ve::expected +Key::from_PEM (std::string pem) +{ + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); +} +} \ No newline at end of file diff --git a/kmipclient/src/KeyFactory.cpp b/kmipclient/src/KeyFactory.cpp new file mode 100644 index 0000000..263b32b --- /dev/null +++ b/kmipclient/src/KeyFactory.cpp @@ -0,0 +1,52 @@ +// +// Created by al on 01.04.25. +// + +#include "KeyFactory.hpp" +#include "AttributesFactory.hpp" + +namespace kmipclient +{ + +ve::expected +kmipclient::KeyFactory::parse_response (GetResponsePayload *pld) +{ + switch (pld->object_type) + { + case KMIP_OBJTYPE_SYMMETRIC_KEY: + { + auto *symmetric_key = static_cast (pld->object); + KeyBlock *block = symmetric_key->key_block; + if ((block->key_format_type != KMIP_KEYFORMAT_RAW) || (block->key_wrapping_data != nullptr)) + { + return Error (-1, "Invalid response object format."); + } + auto block_value = static_cast (block->key_value); + auto material = static_cast (block_value->key_material); + + key_t kv (material->value, material->value + material->size); + + auto key_attributes = AttributesFactory::parse (block_value->attributes, block_value->attribute_count); + Key key (kv, KeyType::KEY_TYPE_SYMMETRIC_KEY, KeyAlgorithm::KEY_ALGORITHM_AES, key_attributes); + + return key; + } + case KMIP_OBJTYPE_PRIVATE_KEY: + { + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); + } + case KMIP_OBJTYPE_PUBLIC_KEY: + { + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); + } + case KMIP_OBJTYPE_CERTIFICATE: + { + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); + } + default: + { + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); + } + } +} +} \ No newline at end of file diff --git a/kmipclient/src/KeyFactory.hpp b/kmipclient/src/KeyFactory.hpp new file mode 100644 index 0000000..03a5c97 --- /dev/null +++ b/kmipclient/src/KeyFactory.hpp @@ -0,0 +1,24 @@ +// +// Created by al on 01.04.25. +// + +#ifndef KEYFACTORY_HPP +#define KEYFACTORY_HPP +#include "../include/Key.hpp" +#include "../include/kmip_data_types.hpp" +#include "../include/v_expected.hpp" +#include "kmip.h" +#include + +namespace kmipclient +{ +class KeyFactory +{ +public: + KeyFactory () = default; + static ve::expected parse_response (GetResponsePayload *pld); +}; + +} + +#endif // KEYFACTORY_HPP diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp new file mode 100644 index 0000000..7693789 --- /dev/null +++ b/kmipclient/src/KmipClient.cpp @@ -0,0 +1,295 @@ +// +// Created by al on 10.03.25. +// +#include "include/KmipClient.hpp" + +#include "IOUtils.hpp" +#include "KmipCtx.hpp" +#include "RequestFactory.hpp" +#include "ResponseFactory.hpp" +#include "kmip_exceptions.hpp" + +namespace kmipclient +{ + +#define MAX_ITEMS_IN_SEARCH 16 + +KmipClient::KmipClient (NetClient &net_client) : net_client (net_client) +{ + io = std::make_unique (net_client); +}; + +KmipClient::KmipClient (NetClient &net_client, const std::shared_ptr &log) : net_client (net_client) +{ + io = std::make_unique (net_client); + logger = log; +} + +KmipClient::~KmipClient () { net_client.close (); }; + +ve::expected +KmipClient::op_register_key (const name_t &name, const name_t &group, Key &k) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_register_key_rq (name, group, k); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_register_secret (const name_t &name, const name_t &group, std::string secret, + enum kmip_secret_type secret_type) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_register_secret_rq (name, group, secret, secret_type); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_create_aes_key (const name_t &name, const name_t &group) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_create_aes_rq (name, group); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_get_key (const id_t &id) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_get_rq (id); + io->do_exchange (ctx); + return rf.get_key (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_get_secret (const id_t &id) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_get_rq (id); + io->do_exchange (ctx); + return rf.get_secret (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_activate (const id_t &id) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_activate_rq (id); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +expected +KmipClient::op_get_attribute (const id_t &id, const name_t &attr_name) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_get_attribute_rq (id, attr_name); + io->do_exchange (ctx); + return rf.get_attributes (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_locate_by_name (const name_t &name, kmip_entity_type type) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + // actually, with Vault server there should be only one item with the name + request_factory.create_locate_by_name_rq (name, type, MAX_ITEMS_IN_SEARCH, 0); + io->do_exchange (ctx); + return rf.get_ids (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +expected +KmipClient::op_locate_by_group (const name_t &group, kmip_entity_type type) const +{ + + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + ids_t result; + try + { + size_t received = 0; + size_t offset = 0; + do + { + request_factory.create_locate_by_group_rq (group, type, MAX_ITEMS_IN_SEARCH, offset); + io->do_exchange (ctx); + auto exp = rf.get_ids (0); + if (exp.has_error ()) + { + return exp.error (); + } + + if (ids_t got = exp.value (); !got.empty ()) + { + received = got.size (); + offset += got.size (); + result.insert (result.end (), got.begin (), got.end ()); + } + else + { + break; + } + } + while (received == MAX_ITEMS_IN_SEARCH); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } + return result; +} + +ve::expected +KmipClient::op_all (kmip_entity_type type) const +{ + + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + ids_t result; + try + { + size_t received = 0; + size_t offset = 0; + do + { + request_factory.create_locate_all_rq (type, MAX_ITEMS_IN_SEARCH, 0); + io->do_exchange (ctx); + auto exp = rf.get_ids (0); + if (exp.has_error ()) + { + return exp.error (); + } + if (ids_t got = exp.value (); !got.empty ()) + { + received = got.size (); + offset += got.size (); + result.insert (result.end (), got.begin (), got.end ()); + } + else + { + break; + } + } + while (received == MAX_ITEMS_IN_SEARCH); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } + return result; +} + +ve::expected +KmipClient::op_revoke (const id_t &id, enum kmip_revocation_reason reason, const name_t &message, time_t occurrence_time) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_revoke_rq (id, reason, message, occurrence_time); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +ve::expected +KmipClient::op_destroy (const id_t &id) const +{ + KmipCtx ctx; + RequestFactory request_factory (ctx); + ResponseFactory rf (ctx); + try + { + request_factory.create_get_rq (id); + io->do_exchange (ctx); + return rf.get_id (0); + } + catch (ErrorException &e) + { + return Error (e.code (), e.what ()); + } +} + +} diff --git a/kmipclient/src/KmipCtx.hpp b/kmipclient/src/KmipCtx.hpp new file mode 100644 index 0000000..476f2b5 --- /dev/null +++ b/kmipclient/src/KmipCtx.hpp @@ -0,0 +1,166 @@ +// +// Created by al on 21.03.25. +// + +#ifndef KMIPCTX_HPP +#define KMIPCTX_HPP + +#include "kmip.h" +#include + +#include +#include + +namespace kmipclient +{ + +class KmipCtx +{ +public: + /** + * Initializes KMIP context with default version 1.4 + */ + KmipCtx () : KmipCtx (KMIP_1_4) {}; + /** + * Initializes KMIP context with version + */ + explicit KmipCtx (enum kmip_version version) + { + kmip_init (&m_ctx, nullptr, 0, version); + alloc_buffer (); + }; + /** + * Destroys KMIP context + */ + ~KmipCtx () + { + free_buffer (); + kmip_destroy (&m_ctx); + }; + /** + * get raw KMIP ctx pointer + * @return raw KMIP ctx pointer + */ + // no copy, no move + KmipCtx (const KmipCtx &other) = delete; + KmipCtx (KmipCtx &&other) noexcept = delete; + KmipCtx &operator= (const KmipCtx &other) = delete; + KmipCtx &operator= (KmipCtx &&other) noexcept = delete; + + KMIP *get (); + void increase_buffer (); + void alloc_buffer (); + void alloc_buffer (size_t buf_size); + void free_buffer (); + void set_buffer (uint8_t *buf, size_t buffer_total_size); + [[nodiscard]] std::string get_errors () const; + + template + [[nodiscard]] T * + allocate () + { + return static_cast (m_ctx.calloc_func (m_ctx.state, 1, sizeof (T))); + } + + void free (void *p) const; + + TextString *from_string (const std::string &str); + +private: + int buffer_blocks = 1; + const int buffer_block_size = 1024; + KMIP m_ctx{}; +}; + +inline KMIP * +KmipCtx::get () +{ + return &m_ctx; +}; + +void inline KmipCtx::free (void *p) const { m_ctx.free_func (m_ctx.state, p); } + +void inline KmipCtx::increase_buffer () +{ + uint8 *encoding = m_ctx.buffer; + + kmip_reset (&m_ctx); + m_ctx.free_func (m_ctx.state, encoding); + + buffer_blocks += 1; + int buffer_total_size = buffer_blocks * buffer_block_size; + + encoding = static_cast (m_ctx.calloc_func (m_ctx.state, buffer_blocks, buffer_block_size)); + if (encoding == nullptr) + { + kmip_destroy (&m_ctx); + throw std::bad_alloc (); + } + + kmip_set_buffer (&m_ctx, encoding, buffer_total_size); +} + +void inline KmipCtx::alloc_buffer (size_t buf_size) +{ + auto *buffer = static_cast (m_ctx.calloc_func (m_ctx.state, 1, buf_size)); + if (buffer == nullptr) + { + kmip_destroy (&m_ctx); + throw std::bad_alloc (); + } + m_ctx.memset_func (buffer, 0, buf_size); + kmip_set_buffer (&m_ctx, buffer, buf_size); +} + +void inline KmipCtx::alloc_buffer () +{ + int buffer_total_size = buffer_blocks * buffer_block_size; + auto *buffer = static_cast (m_ctx.calloc_func (m_ctx.state, buffer_blocks, buffer_block_size)); + if (buffer == nullptr) + { + kmip_destroy (&m_ctx); + throw std::bad_alloc (); + } + m_ctx.memset_func (buffer, 0, buffer_total_size); + kmip_set_buffer (&m_ctx, buffer, buffer_total_size); +} + +void inline KmipCtx::free_buffer () +{ + kmip_free_buffer (&m_ctx, m_ctx.buffer, m_ctx.size); + kmip_set_buffer (&m_ctx, nullptr, 0); + buffer_blocks = 1; +} + +void inline KmipCtx::set_buffer (uint8_t *buf, size_t buffer_total_size) +{ + free_buffer (); + kmip_set_buffer (&m_ctx, buf, buffer_total_size); +} + +std::string inline KmipCtx::get_errors () const +{ + std::string errors; + ErrorFrame *index = m_ctx.frame_index; + do + { + errors.append (std::format ("- %s @ line: %d\n", index->function, index->line)); + } + while (index-- != m_ctx.errors); + + return errors; +} + +inline TextString * +KmipCtx::from_string (const std::string &str) +{ + auto res = allocate (); + res->size = str.size (); + res->value = static_cast (m_ctx.calloc_func (m_ctx.state, 1, res->size)); + strncpy (res->value, str.c_str (), res->size); + return res; +} + +}; + +#endif // KMIPCTX_HPP diff --git a/kmipclient/src/KmipRequest.hpp b/kmipclient/src/KmipRequest.hpp new file mode 100644 index 0000000..fad43f3 --- /dev/null +++ b/kmipclient/src/KmipRequest.hpp @@ -0,0 +1,83 @@ +// +// Created by al on 21.03.25. +// + +#ifndef KMIPREQUEST_HPP +#define KMIPREQUEST_HPP +#include "KmipCtx.hpp" +#include "include/kmip_data_types.hpp" +#include "kmip_exceptions.hpp" +#include + +#include + +namespace kmipclient +{ +class KmipRequest +{ +public: + explicit KmipRequest (KmipCtx &ctx); + + RequestMessage & + get () + { + return request; + }; + void add_batch_item (RequestBatchItem *bi); + void encode () const; + [[nodiscard]] KmipCtx & + get_ctx () const + { + return ctx_; + }; + +private: + RequestMessage request{}; + RequestHeader rh{}; + ProtocolVersion pv{}; + KmipCtx &ctx_; +}; + +inline KmipRequest::KmipRequest (KmipCtx &ctx) : request (), rh (), ctx_ (ctx) +{ + kmip_init_protocol_version (&pv, ctx_.get ()->version); + kmip_init_request_header (&rh); + rh.protocol_version = &pv; + rh.maximum_response_size = ctx_.get ()->max_message_size; + rh.time_stamp = time (nullptr); + rh.batch_count = 0; + request.request_header = &rh; +}; + +inline void +KmipRequest::add_batch_item (RequestBatchItem *rbi) +{ + // TODO: multiple batch items + request.batch_items = rbi; //[request.batch_count] = rbi; + request.batch_count += 1; +} + +inline void +KmipRequest::encode () const +{ + /* Encode the request message. Dynamically resize the encoding buffer */ + /* if it's not big enough. Once encoding succeeds, send the request */ + /* message. */ + auto ctx = ctx_.get (); + auto encoding = ctx_.get ()->buffer; + + int encode_result = kmip_encode_request_message (ctx, &request); + while (encode_result == KMIP_ERROR_BUFFER_FULL) + { + ctx_.increase_buffer (); + encode_result = kmip_encode_request_message (ctx, &request); + } + if (encode_result != KMIP_OK) + { + // very low probability, we have plenty of memory usually + throw ErrorException (encode_result, "Error in the KMIP request encoding"); + } +} + +} +#endif // KMIPREQUEST_HPP diff --git a/kmipclient/src/NetClientOpenSSL.cpp b/kmipclient/src/NetClientOpenSSL.cpp new file mode 100644 index 0000000..7b66bf0 --- /dev/null +++ b/kmipclient/src/NetClientOpenSSL.cpp @@ -0,0 +1,121 @@ +// +// Created by al on 17.03.25. +// + +#include "../include/NetClientOpenSSL.hpp" + +#include "include/kmip_data_types.hpp" +#include "kmip_exceptions.hpp" +#include +#include + +namespace kmipclient +{ + +int +check_connected (NetClientOpenSSL *nc) +{ + if (nc->is_connected ()) + { + return 0; + } + else + { + return nc->connect (); + } +} + +NetClientOpenSSL::NetClientOpenSSL (const char *host, const char *port, const char *clientCertificateFn, + const char *clientKeyFn, + const char *serverCaCertFn, // should it be sever's CA certificate? + int timeout_ms) + : NetClient (host, port, clientCertificateFn, clientKeyFn, serverCaCertFn, timeout_ms) +{ +} + +NetClientOpenSSL::~NetClientOpenSSL () { close (); } + +int +NetClientOpenSSL::connect () +{ + ctx_ = SSL_CTX_new (SSLv23_method ()); + + if (SSL_CTX_use_certificate_file (ctx_, m_clientCertificateFn.c_str (), SSL_FILETYPE_PEM) != 1) + { + SSL_CTX_free (ctx_); + throw ErrorException (-1, "Loading the client certificate failed"); + return -1; + } + if (SSL_CTX_use_PrivateKey_file (ctx_, m_clientKeyFn.c_str (), SSL_FILETYPE_PEM) != 1) + { + SSL_CTX_free (ctx_); + throw ErrorException (-1, "Loading the client key failed"); + return -1; + } + if (SSL_CTX_load_verify_locations (ctx_, m_serverCaCertificateFn.c_str (), nullptr) != 1) + { + SSL_CTX_free (ctx_); + throw ErrorException (-1, "Loading the CA certificate failed"); + return -1; + } + + bio_ = BIO_new_ssl_connect (ctx_); + if (bio_ == nullptr) + { + SSL_CTX_free (ctx_); + throw ErrorException (-1, "BIO_new_ssl_connect failed"); + return -1; + } + + SSL *ssl = nullptr; + BIO_get_ssl (bio_, &ssl); + SSL_set_mode (ssl, SSL_MODE_AUTO_RETRY); + BIO_set_conn_hostname (bio_, m_host.c_str ()); + BIO_set_conn_port (bio_, m_port.c_str ()); + BIO_set_ssl_renegotiate_timeout (bio_, m_timeout_ms); + if (BIO_do_connect (bio_) != 1) + { + BIO_free_all (bio_); + SSL_CTX_free (ctx_); + bio_ = nullptr; + ctx_ = nullptr; + throw ErrorException (-1, "BIO_do_connect failed"); + return -1; + } + m_isConnected = true; + return 0; +} + +void +NetClientOpenSSL::close () +{ + if (bio_ != nullptr) + { + BIO_free_all (bio_); + bio_ = nullptr; + } + if (ctx_ != nullptr) + { + SSL_CTX_free (ctx_); + ctx_ = nullptr; + } + m_isConnected = false; +} + +int +NetClientOpenSSL::send (const void *data, int dlen) +{ + if (check_connected (this) < 0) + return -1; + return BIO_write (bio_, data, dlen); +} + +int +NetClientOpenSSL::recv (void *data, int dlen) +{ + if (check_connected (this) < 0) + return -1; + return BIO_read (bio_, data, dlen); +} + +} // namespace \ No newline at end of file diff --git a/kmipclient/src/RequestFactory.cpp b/kmipclient/src/RequestFactory.cpp new file mode 100644 index 0000000..89a69dd --- /dev/null +++ b/kmipclient/src/RequestFactory.cpp @@ -0,0 +1,450 @@ +// +// Created by al on 21.03.25. +// + +#include "RequestFactory.hpp" + +#include "KmipCtx.hpp" +#include "KmipRequest.hpp" +#include "kmip.h" + +#include + +namespace kmipclient +{ + +void +RequestFactory::create_get_rq (const id_t &id) +{ + + TextString uuid = {}; + uuid.size = id.size (); + uuid.value = const_cast (id.c_str ()); + + GetRequestPayload grp{}; + grp.unique_identifier = &uuid; + + RequestBatchItem rbi{}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_GET; + rbi.request_payload = &grp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_activate_rq (const id_t &id) +{ + TextString uuid = {}; + uuid.size = id.size (); + uuid.value = const_cast (id.c_str ()); + + ActivateRequestPayload arp{}; + arp.unique_identifier = &uuid; + + RequestBatchItem rbi{}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_ACTIVATE; + rbi.request_payload = &arp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_create_aes_rq (const name_t &name, const name_t &group) +{ + + Attribute a[5]; + for (auto &i : a) + { + kmip_init_attribute (&i); + } + + enum cryptographic_algorithm algorithm = KMIP_CRYPTOALG_AES; + a[0].type = KMIP_ATTR_CRYPTOGRAPHIC_ALGORITHM; + a[0].value = &algorithm; + + int32 length = 256; + a[1].type = KMIP_ATTR_CRYPTOGRAPHIC_LENGTH; + a[1].value = &length; + + int32 mask = KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT; + a[2].type = KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK; + a[2].value = &mask; + + Name ts; + TextString ts2 = { nullptr, 0 }; + ts2.value = const_cast (name.c_str ()); + ts2.size = kmip_strnlen_s (ts2.value, 250); + ts.value = &ts2; + ts.type = KMIP_NAME_UNINTERPRETED_TEXT_STRING; + a[3].type = KMIP_ATTR_NAME; + a[3].value = &ts; + + TextString gs2 = { 0, 0 }; + gs2.value = const_cast (group.c_str ()); + gs2.size = kmip_strnlen_s (gs2.value, 250); + a[4].type = KMIP_ATTR_OBJECT_GROUP; + a[4].value = &gs2; + + TemplateAttribute ta = {}; + ta.attributes = a; + ta.attribute_count = std::size (a); + + int id_max_len = 64; + char *idp = nullptr; + + CreateRequestPayload crp = {}; + crp.object_type = KMIP_OBJTYPE_SYMMETRIC_KEY; + crp.template_attribute = &ta; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_CREATE; + rbi.request_payload = &crp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_register_key_rq (const name_t &name, const name_t &group, Key &k) +{ + + int attr_count; + + group.empty () ? attr_count = 4 : attr_count = 5; + + Attribute a[attr_count]; + for (int i = 0; i < attr_count; i++) + { + kmip_init_attribute (&a[i]); + } + + enum cryptographic_algorithm algorithm = KMIP_CRYPTOALG_AES; + a[0].type = KMIP_ATTR_CRYPTOGRAPHIC_ALGORITHM; + a[0].value = &algorithm; + key_t key = k.value (); + int32 length = key.size () * 8; + a[1].type = KMIP_ATTR_CRYPTOGRAPHIC_LENGTH; + a[1].value = &length; + + int32 mask = KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT; + a[2].type = KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK; + a[2].value = &mask; + + Name ts; + TextString ts2 = { nullptr, 0 }; + ts2.value = const_cast (name.c_str ()); + ts2.size = kmip_strnlen_s (ts2.value, 250); + ts.value = &ts2; + ts.type = KMIP_NAME_UNINTERPRETED_TEXT_STRING; + a[3].type = KMIP_ATTR_NAME; + a[3].value = &ts; + + if (attr_count == 5) + { + TextString gs2 = { 0, 0 }; + gs2.value = const_cast (group.c_str ()); + gs2.size = kmip_strnlen_s (gs2.value, 250); + a[4].type = KMIP_ATTR_OBJECT_GROUP; + a[4].value = &gs2; + } + + TemplateAttribute ta = {}; + ta.attributes = a; + ta.attribute_count = attr_count; + RegisterRequestPayload crp = {}; + crp.object_type = KMIP_OBJTYPE_SYMMETRIC_KEY; + crp.template_attribute = &ta; + + KeyBlock kb; + crp.object.symmetric_key.key_block = &kb; + kmip_init_key_block (crp.object.symmetric_key.key_block); + crp.object.symmetric_key.key_block->key_format_type = KMIP_KEYFORMAT_RAW; + // key compression should be not set for HasiCorp Vault + // crp.object.symmetric_key.key_block->key_compression_type = KMIP_KEYCOMP_EC_PUB_UNCOMPRESSED; + + ByteString bs; + bs.value = key.data (); + bs.size = key.size (); + + KeyValue kv; + kv.key_material = &bs; + kv.attribute_count = 0; + kv.attributes = nullptr; + + crp.object.symmetric_key.key_block->key_value = &kv; + crp.object.symmetric_key.key_block->key_value_type = KMIP_TYPE_BYTE_STRING; + crp.object.symmetric_key.key_block->cryptographic_algorithm = KMIP_CRYPTOALG_AES; + crp.object.symmetric_key.key_block->cryptographic_length = key.size () * 8; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_REGISTER; + rbi.request_payload = &crp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_register_secret_rq (const name_t &name, const name_t &group, std::string &secret, + int secret_type) +{ + int attr_count; + group.empty () ? attr_count = 2 : attr_count = 3; + + Attribute a[attr_count]; + for (int i = 0; i < attr_count; i++) + { + kmip_init_attribute (&a[i]); + } + + int32 mask = KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT | KMIP_CRYPTOMASK_EXPORT; + a[0].type = KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK; + a[0].value = &mask; + + Name ts; + TextString ts2 = {}; + ts2.value = const_cast (name.c_str ()); + ts2.size = kmip_strnlen_s (ts2.value, 250); + ts.value = &ts2; + ts.type = KMIP_NAME_UNINTERPRETED_TEXT_STRING; + a[1].type = KMIP_ATTR_NAME; + a[1].value = &ts; + + if (attr_count == 3) + { + TextString gs2 = {}; + gs2.value = const_cast (group.c_str ()); + gs2.size = kmip_strnlen_s (gs2.value, 250); + a[2].type = KMIP_ATTR_OBJECT_GROUP; + a[2].value = &gs2; + } + + TemplateAttribute ta = {}; + ta.attributes = a; + ta.attribute_count = attr_count; + + int id_max_len = 64; + char *idp = nullptr; + + RegisterRequestPayload crp = {}; + crp.object_type = KMIP_OBJTYPE_SECRET_DATA; + crp.template_attribute = &ta; + + crp.object.secret_data.secret_data_type = static_cast (secret_type); + + KeyBlock kb; + crp.object.secret_data.key_block = &kb; + kmip_init_key_block (crp.object.secret_data.key_block); + crp.object.secret_data.key_block->key_format_type = KMIP_KEYFORMAT_OPAQUE; + + ByteString bs; + bs.value = reinterpret_cast (secret.data ()); + bs.size = secret.size (); + + KeyValue kv; + kv.key_material = &bs; + kv.attribute_count = 0; + kv.attributes = nullptr; + + crp.object.secret_data.key_block->key_value = &kv; + crp.object.secret_data.key_block->key_value_type = KMIP_TYPE_BYTE_STRING; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_REGISTER; + rbi.request_payload = &crp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_revoke_rq (const id_t &id, int reason, const name_t &message, time_t occurrence_time) +{ + RevokeRequestPayload rrp = {}; + + rrp.compromise_occurence_date = occurrence_time; + + RevocationReason revocation_reason = {}; + revocation_reason.reason = static_cast (reason); + + TextString msg = {}; + msg.value = const_cast (message.c_str ()); + msg.size = message.size (); + revocation_reason.message = &msg; + + rrp.revocation_reason = &revocation_reason; + + TextString uuid = {}; + uuid.value = const_cast (id.c_str ()); + uuid.size = id.size (); + rrp.unique_identifier = &uuid; + + RequestBatchItem rbi{}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_REVOKE; + rbi.request_payload = &rrp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_destroy_rq (const id_t &id) +{ + TextString idt = {}; + idt.value = const_cast (id.c_str ()); + idt.size = id.size (); + + DestroyRequestPayload drp = {}; + drp.unique_identifier = &idt; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_DESTROY; + rbi.request_payload = &drp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_get_attribute_rq (const id_t &id, const std::string &attr_name) +{ + TextString uuid = {}; + uuid.value = const_cast (id.c_str ()); + uuid.size = id.size (); + + TextString an = {}; + an.value = const_cast (attr_name.c_str ()); + ; + an.size = attr_name.size (); + + GetAttributeRequestPayload grp = {}; + grp.unique_identifier = &uuid; + grp.attribute_name = &an; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_GET_ATTRIBUTES; + rbi.request_payload = &grp; + + rq.add_batch_item (&rbi); + rq.encode (); +} + +void +RequestFactory::create_locate_by_name_rq (const name_t &name, const kmip_entity_type type, const int max_items, + const int offset) +{ + create_locate_rq (false, name, type, max_items, offset); +} + +void +RequestFactory::create_locate_by_group_rq (const name_t &group_name, kmip_entity_type type, int max_items, + size_t offset) +{ + create_locate_rq (true, group_name, type, max_items, offset); +} +void +RequestFactory::create_locate_all_rq (kmip_entity_type type, int max_items, int offset) +{ + create_locate_rq (true, "", type, max_items, offset); +} + +void +RequestFactory::create_locate_rq (bool is_group, const name_t &name, kmip_entity_type type, int max_items, + size_t offset) +{ + size_t attrib_count = name.empty () ? 1 : 2; + Attribute a[attrib_count]; + for (int i = 0; i < attrib_count; i++) + { + kmip_init_attribute (&a[i]); + } + + object_type loctype = from_entity_type (type); + a[0].type = KMIP_ATTR_OBJECT_TYPE; + a[0].value = &loctype; + + if (attrib_count == 2) + { + Name ts; + TextString ts2 = {}; + ts2.value = const_cast (name.c_str ()); + ts2.size = kmip_strnlen_s (ts2.value, 250); + ts.value = &ts2; + ts.type = KMIP_NAME_UNINTERPRETED_TEXT_STRING; + if (is_group) + { + a[1].type = KMIP_ATTR_OBJECT_GROUP; + } + else + { + a[1].type = KMIP_ATTR_NAME; + } + a[1].value = &ts; + } + + // TODO: this is a piece of bad code! Handle it somehow. Why they need lists at all? + // copy input array to list + auto attribute_list = rq.get_ctx ().allocate (); + if (attribute_list == nullptr) + { + throw std::bad_alloc (); + } + for (size_t i = 0; i < attrib_count; i++) + { + auto item = rq.get_ctx ().allocate (); + if (item == nullptr) + { + throw std::bad_alloc (); + } + item->data = kmip_deep_copy_attribute (rq.get_ctx ().get (), &a[i]); + if (item->data == nullptr) + { + throw std::bad_alloc (); + } + kmip_linked_list_enqueue (attribute_list, item); + } + + LocateRequestPayload lrp = {}; + lrp.maximum_items = max_items; + lrp.offset_items = offset; + lrp.storage_status_mask = 0; + lrp.group_member_option = group_member_option::group_member_default; + lrp.attribute_list = attribute_list; + + RequestBatchItem rbi = {}; + kmip_init_request_batch_item (&rbi); + rbi.operation = KMIP_OP_LOCATE; + rbi.request_payload = &lrp; + + rq.add_batch_item (&rbi); + rq.encode (); + + // TODO: this is dealoc of bad code from above + LinkedListItem *item = nullptr; + while ((item = kmip_linked_list_pop (attribute_list)) != nullptr) + { + kmip_free_attribute (rq.get_ctx ().get (), static_cast (item->data)); + free (item->data); + kmip_free_buffer (rq.get_ctx ().get (), item, sizeof (LinkedListItem)); + } + rq.get_ctx ().free (attribute_list); +} + +enum object_type +RequestFactory::from_entity_type (enum kmip_entity_type t) +{ + int val = static_cast (t); + return static_cast (val); +} + +} // namespace diff --git a/kmipclient/src/RequestFactory.hpp b/kmipclient/src/RequestFactory.hpp new file mode 100644 index 0000000..0b29da7 --- /dev/null +++ b/kmipclient/src/RequestFactory.hpp @@ -0,0 +1,45 @@ +// +// Created by al on 21.03.25. +// + +#ifndef REQUESTFACTORY_HPP +#define REQUESTFACTORY_HPP + +#include "KmipCtx.hpp" +#include "KmipRequest.hpp" +#include "include/Key.hpp" +#include "include/kmip_data_types.hpp" + +namespace kmipclient +{ + +class RequestFactory +{ +public: + explicit RequestFactory (KmipCtx &ctx) : rq (ctx) {}; + /** + * Created and encodes into ctx a GET request + * @param ctx KMIP context + * @param id Id of entity to get + */ + void create_get_rq (const id_t &id); + void create_activate_rq (const id_t &id); + void create_create_aes_rq (const name_t &name, const name_t &group); + void create_register_key_rq (const name_t &name, const name_t &group, Key &k); + void create_register_secret_rq (const name_t &name, const name_t &group, std::string &secret, int secret_data_type); + void create_revoke_rq (const id_t &id, int reason, const name_t &message, time_t occurrence_time); + void create_destroy_rq (const id_t &id); + void create_get_attribute_rq (const id_t &id, const std::string &attr_name); + void create_locate_by_name_rq (const name_t &name, kmip_entity_type type, int max_items, int offset); + void create_locate_by_group_rq (const name_t &group_name, kmip_entity_type type, int max_items, size_t offset); + void create_locate_all_rq (kmip_entity_type type, int max_items, int offset); + +private: + void create_locate_rq (bool is_group, const name_t &name, kmip_entity_type type, int max_items, size_t offset); + static enum object_type from_entity_type (enum kmip_entity_type t); + KmipRequest rq; +}; + +} // namespace + +#endif // REQUESTFACTORY_HPP diff --git a/kmipclient/src/ResponseFactory.hpp b/kmipclient/src/ResponseFactory.hpp new file mode 100644 index 0000000..6099665 --- /dev/null +++ b/kmipclient/src/ResponseFactory.hpp @@ -0,0 +1,148 @@ +// +// Created by al on 23.03.25. +// + +#ifndef RESPONSEFACTORY_HPP +#define RESPONSEFACTORY_HPP + +#include "KmipCtx.hpp" +#include "ResponseResult.hpp" +#include "include/v_expected.hpp" +#include "kmip.h" +#include "kmip_exceptions.hpp" + +namespace kmipclient +{ +#define LAST_RESULT_MAX_MESSAGE_SIZE 512 // could be big with HasiCorp Vault + +struct OperationResult +{ + enum operation operation; + enum result_status result_status; + enum result_reason result_reason; + char result_message[LAST_RESULT_MAX_MESSAGE_SIZE]; +}; + +class ResponseFactory +{ +public: + explicit ResponseFactory (KmipCtx &ctx) : ctx_ (ctx) {}; + ~ResponseFactory () { kmip_free_response_message (ctx_.get (), &m_resp); } + // disable copy and move + ResponseFactory (const ResponseFactory &) = delete; + ResponseFactory (ResponseFactory &&) = delete; + ResponseFactory &operator= (const ResponseFactory &) = delete; + ResponseFactory &operator= (ResponseFactory &&) = delete; + + ve::expected + get_id (int item_idx) + { + return ResponseResult::get_id (get_response_items ()[item_idx]); + } + + ve::expected + get_key (int item_idx) + { + return ResponseResult::get_key (get_response_items ()[item_idx]); + } + + ve::expected + get_secret (int item_idx) + { + return ResponseResult::get_secret (get_response_items ()[item_idx]); + } + + ve::expected + get_attributes (int item_idx) + { + return ResponseResult::get_attributes (get_response_items ()[item_idx]); + } + + ve::expected + get_ids (int item_idx) + { + return ResponseResult::get_ids (get_response_items ()[item_idx]); + } + +private: + void parse_response (); + std::vector get_response_items (); + static std::string get_operation_result (const ResponseBatchItem &value); + KmipCtx &ctx_; + ResponseMessage m_resp{}; + bool is_parsed = false; +}; + +inline void +ResponseFactory::parse_response () +{ + // decode response; + int decode_result = kmip_decode_response_message (ctx_.get (), &m_resp); + if (decode_result != KMIP_OK) + { + throw ErrorException{ decode_result, ctx_.get_errors () }; + } + is_parsed = true; +} + +inline std::vector +ResponseFactory::get_response_items () +{ + if (!is_parsed) + { + parse_response (); + } + + std::vector items; + if (m_resp.batch_count < 1) + { // something went wrong + throw ErrorException{ -1, "No response batch items from the server." }; + } + + for (int idx = 0; idx < m_resp.batch_count; idx++) + { + if (m_resp.batch_items[idx].result_status != KMIP_STATUS_SUCCESS) // error from the server + { + throw ErrorException (-1, get_operation_result (m_resp.batch_items[idx])); + } + items.push_back (&m_resp.batch_items[idx]); + } + return items; +} + +inline std::string +ResponseFactory::get_operation_result (const ResponseBatchItem &value) +{ + char *bp; + size_t size; + OperationResult last_result{}; + + last_result.operation = value.operation; + last_result.result_status = value.result_status; + last_result.result_reason = value.result_reason; + if (value.result_message) + kmip_copy_textstring (last_result.result_message, value.result_message, sizeof (last_result.result_message)); + else + last_result.result_message[0] = 0; + + FILE *mem_stream = open_memstream (&bp, &size); + fprintf (mem_stream, "Message: %s\nOperation: ", last_result.result_message); + fflush (mem_stream); + kmip_print_operation_enum (mem_stream, last_result.operation); + fflush (mem_stream); + fprintf (mem_stream, "; Result status: "); + fflush (mem_stream); + kmip_print_result_status_enum (mem_stream, last_result.result_status); + fflush (mem_stream); + fprintf (mem_stream, "; Result reason: "); + fflush (mem_stream); + kmip_print_result_reason_enum (mem_stream, last_result.result_reason); + fclose (mem_stream); + std::string res{ bp, size }; + free (bp); + return res; +} + +} + +#endif // RESPONSEFACTORY_HPP diff --git a/kmipclient/src/ResponseResult.cpp b/kmipclient/src/ResponseResult.cpp new file mode 100644 index 0000000..2bc7cb0 --- /dev/null +++ b/kmipclient/src/ResponseResult.cpp @@ -0,0 +1,184 @@ +// +// Created by al on 24.03.25. +// +#include + +#include "KeyFactory.hpp" +#include "ResponseResult.hpp" + +#include "StringUtils.hpp" + +#include "kmip.h" +#include "kmip_locate.h" + +namespace kmipclient +{ + +std::string +from_kmip_text (TextString *s) +{ + return std::string{ s->value, s->size }; +} + +ve::expected +ResponseResult::get_id (const ResponseBatchItem *rbi) +{ + id_t id; + switch (rbi->operation) + { + case KMIP_OP_CREATE: + { + auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + case KMIP_OP_GET: + { + auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + case KMIP_OP_REGISTER: + { + auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + case KMIP_OP_ACTIVATE: + { + auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + case KMIP_OP_REVOKE: + { + const auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + case KMIP_OP_DESTROY: + { + const auto *pld = static_cast (rbi->response_payload); + id = std::string{ pld->unique_identifier->value, pld->unique_identifier->size }; + } + break; + // TODO: other operations + default: + return Error (KMIP_NOT_IMPLEMENTED, "NOT IMPLEMENTED"); + } + return id; +} + +ve::expected +ResponseResult::get_key (ResponseBatchItem *rbi) +{ + auto *pld = static_cast (rbi->response_payload); + switch (pld->object_type) + { + // name known to KeyFactory key types + case KMIP_OBJTYPE_SYMMETRIC_KEY: + KMIP_OBJTYPE_PUBLIC_KEY: + KMIP_OBJTYPE_PRIVATE_KEY: + KMIP_OBJTYPE_CERTIFICATE: + { + return KeyFactory::parse_response (pld); + }; + default: + return Error (-1, "Invalid response object type."); + } +} + +ve::expected +ResponseResult::get_secret (ResponseBatchItem *rbi) +{ + if (auto *pld = static_cast (rbi->response_payload); + pld->object_type != KMIP_OBJTYPE_SECRET_DATA) + { + return Error (KMIP_REASON_INVALID_DATA_TYPE, "Secret data expected"); + } + else + { + auto *secret = static_cast (pld->object); + KeyBlock *block = secret->key_block; + if ((block->key_format_type != KMIP_KEYFORMAT_OPAQUE) || (block->key_wrapping_data != NULL)) + { + return Error (KMIP_OBJECT_MISMATCH, "Secret data key block format mismatch"); + } + + auto block_value = static_cast (block->key_value); + auto *material = static_cast (block_value->key_material); + size_t secret_size = material->size; + char result_key[secret_size]; + for (int i = 0; i < secret_size; i++) + { + result_key[i] = material->value[i]; + } + return Secret{ result_key, 0, static_cast (secret->secret_data_type) }; + } +} + +ve::expected +ResponseResult::get_attributes (ResponseBatchItem *rbi) +{ + auto *pld = static_cast (rbi->response_payload); + name_t res; + Attribute *attribute = pld->attribute; + switch (attribute->type) + { + case KMIP_ATTR_UNIQUE_IDENTIFIER: + case KMIP_ATTR_NAME: + { + const auto *ns = static_cast (pld->attribute->value); + const auto *ts = static_cast (ns->value); + res = StringUtils::fromKmipText (ts); + } + break; + case KMIP_ATTR_STATE: + { + const auto *a = static_cast (pld->attribute->value); + std::stringstream ss; + ss << *a; + res = ss.str (); + } + break; + default: + { + res = "Unknown attribute type, not converted"; + } + } + return res; +} + +void +copy_unique_ids (char ids[][MAX_LOCATE_LEN], size_t *id_size, const UniqueIdentifiers *value, const unsigned max_ids) +{ + size_t idx = 0; + if (value != nullptr) + { + LinkedListItem *curr = value->unique_identifier_list->head; + while (curr != nullptr && idx < max_ids) + { + kmip_copy_textstring (ids[idx], static_cast (curr->data), MAX_LOCATE_LEN - 1); + curr = curr->next; + idx++; + } + } + *id_size = idx; +} + +ve::expected +ResponseResult::get_ids (ResponseBatchItem *rbi) +{ + auto pld = static_cast (rbi->response_payload); + LocateResponse locate_result; + copy_unique_ids (locate_result.ids, &locate_result.ids_size, pld->unique_ids, MAX_LOCATE_IDS); + ids_t res; + for (int i = 0; i < locate_result.ids_size; ++i) + { + res.emplace_back (locate_result.ids[i]); + } + + return res; +} + +} \ No newline at end of file diff --git a/kmipclient/src/ResponseResult.hpp b/kmipclient/src/ResponseResult.hpp new file mode 100644 index 0000000..9ddf51c --- /dev/null +++ b/kmipclient/src/ResponseResult.hpp @@ -0,0 +1,29 @@ +// +// Created by al on 24.03.25. +// + +#ifndef RESPONSERESULT_HPP +#define RESPONSERESULT_HPP + +#include "KmipCtx.hpp" +#include "include/Key.hpp" +#include "include/kmip_data_types.hpp" +#include "include/v_expected.hpp" + +#include "kmip.h" + +namespace kmipclient +{ +class ResponseResult +{ +public: + static ve::expected get_id (const ResponseBatchItem *rbi); + static ve::expected get_key (ResponseBatchItem *rbi); + static ve::expected get_secret (ResponseBatchItem *rbi); + static ve::expected get_attributes (ResponseBatchItem *rbi); + static ve::expected get_ids (ResponseBatchItem *rbi); +}; + +} + +#endif // RESPONSERESULT_HPP diff --git a/kmipclient/src/StringUtils.cpp b/kmipclient/src/StringUtils.cpp new file mode 100644 index 0000000..19c04a2 --- /dev/null +++ b/kmipclient/src/StringUtils.cpp @@ -0,0 +1,46 @@ +// +// Created by al on 02.04.25. +// + +#include "StringUtils.hpp" +namespace kmipclient +{ + +unsigned char +char2int (const char input) +{ + if (input >= '0' && input <= '9') + return input - '0'; + if (input >= 'A' && input <= 'F') + return input - 'A' + 10; + if (input >= 'a' && input <= 'f') + return input - 'a' + 10; + throw std::invalid_argument ("Invalid input string"); +} + +ve::expected +StringUtils::fromHex (const std::string &hex) +{ + if (hex.empty () || hex.size () % 2 != 0) + { + return Error{ -1, "Invalid hex string length." }; + // return ve::unexpected(Error {-1, "Invalid hex string length."}); + } + key_t bytes; + try + { + for (unsigned int i = 0; i < hex.length (); i += 2) + { + std::string byteString = hex.substr (i, 2); + auto byte = char2int (byteString.c_str ()[0]) * 16 + char2int (byteString.c_str ()[1]); + bytes.push_back (byte); + } + } + catch (const std::invalid_argument &e) + { + return Error{ -1, "Invalid hex string length." }; + } + return bytes; +} + +} \ No newline at end of file diff --git a/kmipclient/src/StringUtils.hpp b/kmipclient/src/StringUtils.hpp new file mode 100644 index 0000000..81f6a54 --- /dev/null +++ b/kmipclient/src/StringUtils.hpp @@ -0,0 +1,28 @@ +// +// Created by al on 02.04.25. +// + +#ifndef STRINGUTILS_HPP +#define STRINGUTILS_HPP +#include "include/kmip_data_types.hpp" +#include "include/v_expected.hpp" + +#include + +namespace kmipclient +{ + +class StringUtils +{ +public: + static ve::expected fromHex (const std::string &hex); + static std::string + fromKmipText (const TextString *ts) + { + return std::string{ ts->value, ts->size }; + }; +}; + +} + +#endif // STRINGUTILS_HPP diff --git a/kmipclient/src/kmip_exceptions.hpp b/kmipclient/src/kmip_exceptions.hpp new file mode 100644 index 0000000..44fa7dc --- /dev/null +++ b/kmipclient/src/kmip_exceptions.hpp @@ -0,0 +1,35 @@ +// +// Created by al on 25.03.25. +// + +#ifndef KMIP_EXCEPTIONS_HPP +#define KMIP_EXCEPTIONS_HPP + +#include +#include + +namespace kmipclient +{ + +class ErrorException : public std::exception +{ +public: + explicit ErrorException (int code, std::string msg) : message (std::move (msg)) { kmip_code = code; }; + virtual const char * + what () + { + return message.c_str (); + }; + [[nodiscard]] int + code () const + { + return kmip_code; + }; + +private: + std::string message; + int kmip_code; +}; + +} +#endif // KMIP_EXCEPTIONS_HPP diff --git a/kmippp/kmippp.cpp b/kmippp/kmippp.cpp index b19b06b..171940e 100644 --- a/kmippp/kmippp.cpp +++ b/kmippp/kmippp.cpp @@ -129,7 +129,7 @@ context::op_register (context::name_t name, name_t group, key_t key) { int attr_count; - group.empty()? attr_count = 4 : attr_count = 5; + group.empty() ? attr_count = 4 : attr_count = 5; Attribute a[attr_count]; for (int i = 0; i < attr_count; i++) diff --git a/libkmip/include/kmip.h b/libkmip/include/kmip.h index a39c293..42972b6 100644 --- a/libkmip/include/kmip.h +++ b/libkmip/include/kmip.h @@ -12,6 +12,7 @@ #include #include #include +#include // we have FILE* vars #include "libkmip_version.h" @@ -20,6 +21,9 @@ extern "C" { #endif +#define KMIP_MAX_MESSAGE_SIZE 8192 +#define KMIP_ERROR_MESSAGE_SIZE 200 + /* Types and Constants */ diff --git a/libkmip/include/kmip_bio.h b/libkmip/include/kmip_bio.h index acc96ff..c75ff66 100644 --- a/libkmip/include/kmip_bio.h +++ b/libkmip/include/kmip_bio.h @@ -12,6 +12,7 @@ #include "kmip.h" #include +#define LAST_RESULT_MAX_MESSAGE_SIZE 512 //could be big with HasiCorp Vault #ifdef __cplusplus extern "C" { diff --git a/libkmip/include/kmip_locate.h b/libkmip/include/kmip_locate.h index 83db246..0f880d1 100644 --- a/libkmip/include/kmip_locate.h +++ b/libkmip/include/kmip_locate.h @@ -46,13 +46,13 @@ match. /* When the Object Group attribute and the Object Group Member flag are specified -in the request, and the value specified for Object Group Member is Group -Member Fresh, matching candidate objects SHALL be fresh objects from the +in the request, and the value specified for Object Group Member is �Group +Member Fresh�, matching candidate objects SHALL be fresh objects from the object group. If there are no more fresh objects in the group, the server MAY choose to generate a new object on-the-fly, based on server policy. If the value -specified for Object Group Member is Group Member Default, the server locates +specified for Object Group Member is �Group Member Default�, the server locates the default object as defined by server policy. diff --git a/libkmip/src/CMakeLists.txt b/libkmip/src/CMakeLists.txt index 2cc92cf..7ed441b 100644 --- a/libkmip/src/CMakeLists.txt +++ b/libkmip/src/CMakeLists.txt @@ -19,9 +19,15 @@ target_include_directories( $ $ ) + +set(KMIP_PUBLIC_HEADERS + "../include/kmip.h" + "../include/kmip_locate.h" + "../include/kmip_bio.h" +) + set_target_properties( - kmip PROPERTIES PUBLIC_HEADER - "../include/kmip.h" + kmip PROPERTIES PUBLIC_HEADER "${KMIP_PUBLIC_HEADERS}" ) diff --git a/libkmip/src/kmip.c b/libkmip/src/kmip.c index d9d775c..db91310 100644 --- a/libkmip/src/kmip.c +++ b/libkmip/src/kmip.c @@ -1265,8 +1265,8 @@ kmip_init (KMIP *ctx, void *buffer, size_t buffer_size, enum kmip_version v) if (ctx->memcpy_func == NULL) ctx->memcpy_func = &kmip_memcpy; - ctx->max_message_size = 8192; - ctx->error_message_size = 200; + ctx->max_message_size = KMIP_MAX_MESSAGE_SIZE; + ctx->error_message_size = KMIP_ERROR_MESSAGE_SIZE; ctx->error_message = NULL; ctx->error_frame_count = 20;