-
Notifications
You must be signed in to change notification settings - Fork 6
PS-9697 C++ KMIP client library added #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
build | ||
generated-docs |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Apr, 2025 Version 0.1.0 | ||
|
||
Initial implementation of all functionality available in "kmippp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
$<BUILD_INTERFACE:${KMIP_SOURCE_DIR}/kmipclient/> | ||
$<INSTALL_INTERFACE:include> | ||
) | ||
|
||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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<char *>(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<Key, Error> | ||
ResponseResultFactory::get_key (ResponseBatchItem *rbi) | ||
{ | ||
auto *pld = static_cast<GetResponsePayload *> (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<Key, Error> | ||
KmipClient::op_get_key (const id_t &id) | ||
{ | ||
KmipCtx ctx; | ||
RequestFactory request_factory(ctx); | ||
ResponseFactory rf(ctx); | ||
try | ||
{ | ||
request_factory.create_get_rq (id); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the code snippet above, where the |
||
io->do_exchange (ctx); | ||
return rf.get_key(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As defined in the code snippet above, the |
||
} | ||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// Created by al on 02.04.25. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can be better to use a proper file comment with a copyright and a license. |
||
// | ||
#include "../include/KmipClient.hpp" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually source code doesn't include relative paths to headers and Also, to prevent potential name clashes in the code that will use the libary, it is better if include directives use a library name as a prefix, e.g.:
In order to achieve that, headers need to be placed in the |
||
#include "../include/NetClientOpenSSL.hpp" | ||
#include "../include/kmipclient_version.hpp" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better if the headers used the same name scheme, either |
||
|
||
#include <iostream> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually, standard headers are listed before 3rd-party-library headers in include directives' block. |
||
|
||
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 <host> <port> <client_cert> <client_key> <server_cert> <key_id>" | ||
<< 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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
|
||
|
||
#include "../include/Kmip.hpp" | ||
#include "../include/KmipClient.hpp" | ||
#include "../include/NetClientOpenSSL.hpp" | ||
#include <iostream> | ||
|
||
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 <host> <port> <client_cert> <client_key> <server_cert> <key_id>" | ||
<< 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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a CMake expert, but I don't think we should list header files in the list of library files.