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