From bfd01887c7ce0a9be09d0ce24a441fd636f6b272 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Mon, 23 Mar 2026 17:03:23 +0200 Subject: [PATCH 01/11] PS-10068 Adding new KMIP C++ library and making it production ready https://perconadev.atlassian.net/browse/PS-10068 The new kmipclient added to replacre kmippp; the new kmipcore library added to replace legacy libkmip; This is AI assited code based on initial implementation: PS-9697 new KMIP C++ client library that replaces midle and top levels of old one For more information please see KMIP_MODERN_VS_LEGACY_COMPARISON.md file --- CMakeLists.txt | 29 +- Doxyfile | 36 + KMIP_MODERN_VS_LEGACY_COMPARISON.md | 138 +++ kmipclient/.clang-format | 110 +++ kmipclient/CHANGELOG.md | 17 + kmipclient/CMakeLists.txt | 122 +++ kmipclient/README.md | 362 ++++++++ kmipclient/TODO.md | 17 + kmipclient/examples/example_activate.cpp | 48 ++ kmipclient/examples/example_create_aes.cpp | 48 ++ kmipclient/examples/example_destroy.cpp | 48 ++ kmipclient/examples/example_get.cpp | 62 ++ kmipclient/examples/example_get_all_ids.cpp | 60 ++ .../examples/example_get_attributes.cpp | 80 ++ kmipclient/examples/example_get_logger.cpp | 85 ++ kmipclient/examples/example_get_name.cpp | 59 ++ kmipclient/examples/example_get_secret.cpp | 65 ++ kmipclient/examples/example_locate.cpp | 69 ++ .../examples/example_locate_by_group.cpp | 69 ++ kmipclient/examples/example_pool.cpp | 121 +++ kmipclient/examples/example_register_key.cpp | 49 ++ .../examples/example_register_secret.cpp | 50 ++ kmipclient/examples/example_revoke.cpp | 49 ++ kmipclient/include/kmipclient/Key.hpp | 91 ++ kmipclient/include/kmipclient/Kmip.hpp | 79 ++ kmipclient/include/kmipclient/KmipClient.hpp | 228 +++++ .../include/kmipclient/KmipClientPool.hpp | 237 ++++++ .../include/kmipclient/KmipIOException.hpp | 56 ++ kmipclient/include/kmipclient/NetClient.hpp | 103 +++ .../include/kmipclient/NetClientOpenSSL.hpp | 100 +++ .../include/kmipclient/kmipclient_version.hpp | 42 + kmipclient/include/kmipclient/types.hpp | 46 + kmipclient/src/IOUtils.cpp | 138 +++ kmipclient/src/IOUtils.hpp | 62 ++ kmipclient/src/Key.cpp | 272 ++++++ kmipclient/src/KmipClient.cpp | 412 +++++++++ kmipclient/src/KmipClientPool.cpp | 211 +++++ kmipclient/src/NetClientOpenSSL.cpp | 244 ++++++ kmipclient/src/StringUtils.cpp | 88 ++ kmipclient/src/StringUtils.hpp | 33 + .../tests/KmipClientIntegrationTest.cpp | 748 +++++++++++++++++ .../tests/KmipClientPoolIntegrationTest.cpp | 488 +++++++++++ kmipcore/.clang-format | 110 +++ kmipcore/CMakeLists.txt | 35 + .../include/kmipcore/attributes_parser.hpp | 27 + kmipcore/include/kmipcore/key.hpp | 122 +++ kmipcore/include/kmipcore/key_parser.hpp | 55 ++ .../include/kmipcore/kmip_attribute_names.hpp | 50 ++ kmipcore/include/kmipcore/kmip_basics.hpp | 207 +++++ kmipcore/include/kmipcore/kmip_enums.hpp | 793 ++++++++++++++++++ kmipcore/include/kmipcore/kmip_formatter.hpp | 24 + kmipcore/include/kmipcore/kmip_logger.hpp | 61 ++ kmipcore/include/kmipcore/kmip_protocol.hpp | 423 ++++++++++ kmipcore/include/kmipcore/kmip_requests.hpp | 176 ++++ kmipcore/include/kmipcore/kmip_responses.hpp | 200 +++++ .../include/kmipcore/kmipcore_version.hpp | 40 + kmipcore/include/kmipcore/response_parser.hpp | 111 +++ kmipcore/include/kmipcore/secret.hpp | 76 ++ .../include/kmipcore/serialization_buffer.hpp | 189 +++++ kmipcore/include/kmipcore/types.hpp | 56 ++ kmipcore/src/attributes_parser.cpp | 140 ++++ kmipcore/src/key_parser.cpp | 232 +++++ kmipcore/src/kmip_basics.cpp | 460 ++++++++++ kmipcore/src/kmip_formatter.cpp | 532 ++++++++++++ kmipcore/src/kmip_payloads.cpp | 146 ++++ kmipcore/src/kmip_protocol.cpp | 492 +++++++++++ kmipcore/src/kmip_requests.cpp | 370 ++++++++ kmipcore/src/kmip_responses.cpp | 119 +++ kmipcore/src/response_parser.cpp | 149 ++++ kmipcore/src/serialization_buffer.cpp | 119 +++ kmipcore/tests/test_core.cpp | 467 +++++++++++ kmipcore/tests/test_parsers.cpp | 432 ++++++++++ kmipcore/tests/test_serialization_buffer.cpp | 249 ++++++ 73 files changed, 12132 insertions(+), 1 deletion(-) create mode 100644 Doxyfile create mode 100644 KMIP_MODERN_VS_LEGACY_COMPARISON.md create mode 100644 kmipclient/.clang-format create mode 100644 kmipclient/CHANGELOG.md create mode 100644 kmipclient/CMakeLists.txt create mode 100644 kmipclient/README.md create mode 100644 kmipclient/TODO.md create mode 100644 kmipclient/examples/example_activate.cpp create mode 100644 kmipclient/examples/example_create_aes.cpp create mode 100644 kmipclient/examples/example_destroy.cpp create mode 100644 kmipclient/examples/example_get.cpp create mode 100644 kmipclient/examples/example_get_all_ids.cpp create mode 100644 kmipclient/examples/example_get_attributes.cpp create mode 100644 kmipclient/examples/example_get_logger.cpp create mode 100644 kmipclient/examples/example_get_name.cpp create mode 100644 kmipclient/examples/example_get_secret.cpp create mode 100644 kmipclient/examples/example_locate.cpp create mode 100644 kmipclient/examples/example_locate_by_group.cpp create mode 100644 kmipclient/examples/example_pool.cpp create mode 100644 kmipclient/examples/example_register_key.cpp create mode 100644 kmipclient/examples/example_register_secret.cpp create mode 100644 kmipclient/examples/example_revoke.cpp create mode 100644 kmipclient/include/kmipclient/Key.hpp create mode 100644 kmipclient/include/kmipclient/Kmip.hpp create mode 100644 kmipclient/include/kmipclient/KmipClient.hpp create mode 100644 kmipclient/include/kmipclient/KmipClientPool.hpp create mode 100644 kmipclient/include/kmipclient/KmipIOException.hpp create mode 100644 kmipclient/include/kmipclient/NetClient.hpp create mode 100644 kmipclient/include/kmipclient/NetClientOpenSSL.hpp create mode 100644 kmipclient/include/kmipclient/kmipclient_version.hpp create mode 100644 kmipclient/include/kmipclient/types.hpp create mode 100644 kmipclient/src/IOUtils.cpp create mode 100644 kmipclient/src/IOUtils.hpp create mode 100644 kmipclient/src/Key.cpp create mode 100644 kmipclient/src/KmipClient.cpp create mode 100644 kmipclient/src/KmipClientPool.cpp create mode 100644 kmipclient/src/NetClientOpenSSL.cpp create mode 100644 kmipclient/src/StringUtils.cpp create mode 100644 kmipclient/src/StringUtils.hpp create mode 100644 kmipclient/tests/KmipClientIntegrationTest.cpp create mode 100644 kmipclient/tests/KmipClientPoolIntegrationTest.cpp create mode 100644 kmipcore/.clang-format create mode 100644 kmipcore/CMakeLists.txt create mode 100644 kmipcore/include/kmipcore/attributes_parser.hpp create mode 100644 kmipcore/include/kmipcore/key.hpp create mode 100644 kmipcore/include/kmipcore/key_parser.hpp create mode 100644 kmipcore/include/kmipcore/kmip_attribute_names.hpp create mode 100644 kmipcore/include/kmipcore/kmip_basics.hpp create mode 100644 kmipcore/include/kmipcore/kmip_enums.hpp create mode 100644 kmipcore/include/kmipcore/kmip_formatter.hpp create mode 100644 kmipcore/include/kmipcore/kmip_logger.hpp create mode 100644 kmipcore/include/kmipcore/kmip_protocol.hpp create mode 100644 kmipcore/include/kmipcore/kmip_requests.hpp create mode 100644 kmipcore/include/kmipcore/kmip_responses.hpp create mode 100644 kmipcore/include/kmipcore/kmipcore_version.hpp create mode 100644 kmipcore/include/kmipcore/response_parser.hpp create mode 100644 kmipcore/include/kmipcore/secret.hpp create mode 100644 kmipcore/include/kmipcore/serialization_buffer.hpp create mode 100644 kmipcore/include/kmipcore/types.hpp create mode 100644 kmipcore/src/attributes_parser.cpp create mode 100644 kmipcore/src/key_parser.cpp create mode 100644 kmipcore/src/kmip_basics.cpp create mode 100644 kmipcore/src/kmip_formatter.cpp create mode 100644 kmipcore/src/kmip_payloads.cpp create mode 100644 kmipcore/src/kmip_protocol.cpp create mode 100644 kmipcore/src/kmip_requests.cpp create mode 100644 kmipcore/src/kmip_responses.cpp create mode 100644 kmipcore/src/response_parser.cpp create mode 100644 kmipcore/src/serialization_buffer.cpp create mode 100644 kmipcore/tests/test_core.cpp create mode 100644 kmipcore/tests/test_parsers.cpp create mode 100644 kmipcore/tests/test_serialization_buffer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d85c2c..45a90e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.0) +cmake_minimum_required(VERSION 3.10.0) if (POLICY CMP0048) cmake_policy(SET CMP0048 NEW) @@ -6,8 +6,35 @@ endif(POLICY CMP0048) project(kmip C CXX) +enable_testing() + set(KMIP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") add_subdirectory(libkmip/src) add_subdirectory(kmippp) +add_subdirectory(kmipcore) +add_subdirectory(kmipclient) + +find_package(Doxygen) + +option(BUILD_DOCS "Build API documentation with Doxygen" ${DOXYGEN_FOUND}) + +if(BUILD_DOCS) + if(NOT DOXYGEN_FOUND) + message(FATAL_ERROR "BUILD_DOCS is ON but Doxygen was not found.") + endif() + + 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 or BUILD_DOCS=OFF, 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/KMIP_MODERN_VS_LEGACY_COMPARISON.md b/KMIP_MODERN_VS_LEGACY_COMPARISON.md new file mode 100644 index 0000000..773158b --- /dev/null +++ b/KMIP_MODERN_VS_LEGACY_COMPARISON.md @@ -0,0 +1,138 @@ +# KMIP Modern vs Legacy Comparison + +Date: 2026-03-23 + +## Scope + +This document compares the modern stack (`kmipcore` + `kmipclient`) with the legacy stack (`libkmip` + `kmippp`) in this repository. + +## Stacks Compared + +- Modern: + - `kmipcore` (typed KMIP model, TTLV serialization/parsing, request/response classes) + - `kmipclient` (network client, high-level operations, connection pool) +- Legacy: + - `libkmip` (C API with `kmip_bio_*` operation functions) + - `kmippp` (thin C++ wrapper around `libkmip`) + +## 1) Architecture + +### Modern (`kmipcore` + `kmipclient`) + +- Clear layering: + - Protocol/domain model in `kmipcore` + - Transport and operation orchestration in `kmipclient` +- Request and response are represented as typed classes (`RequestMessage`, `ResponseBatchItem`, typed response wrappers). +- Multi-batch request handling is first-class via batch item IDs. +- Connection pooling is implemented as a dedicated thread-safe component (`KmipClientPool`). + +### Legacy (`libkmip` + `kmippp`) + +- `libkmip` is mostly monolithic per operation (`kmip_bio_create_symmetric_key`, `kmip_bio_register_symmetric_key`, etc.). +- Each operation function handles request assembly, serialization, network I/O, response decoding, and extraction. +- `kmippp` mostly forwards to `libkmip` and normalizes return values (often bool/empty string on errors). + +## 2) API Surface and Ergonomics + +### Modern + +- High-level typed API in `KmipClient`: + - `op_create_aes_key`, `op_register_key`, `op_register_secret` + - `op_get_key`, `op_get_secret` + - `op_get_attribute_list`, `op_get_attributes` + - `op_locate_by_name`, `op_locate_by_group`, `op_all` + - `op_activate`, `op_revoke`, `op_destroy` +- Errors are exception-based (`kmipcore::KmipException`) with rich context. +- Uses typed request/response parser pipeline. + +### Legacy + +- `kmippp::context` exposes similar operations, but error signaling is mixed: + - bool return for some methods + - empty string/vector for failures in others + - status details via `get_last_result()` side channel +- Behavior is less explicit at call sites because success/failure conventions vary by function. + +## 3) Error Handling and Memory Model + +### Modern + +- RAII ownership via `std::unique_ptr`/`std::shared_ptr`. +- Exceptions propagate failures with operation/result details. +- No global mutable status store required. + +### Legacy + +- Return-code based C style with explicit allocation/free patterns. +- Many manual memory management paths per operation. +- Global mutable `last_result` in `libkmip` is used for human-readable status retrieval. +- `kmippp` explicitly documents `get_last_result()` as not thread-safe due to global state. + +## 4) Concurrency and Pooling + +### Modern + +- `KmipClientPool` is thread-safe and supports: + - blocking `borrow()` + - timeout `borrow(timeout)` + - non-blocking `try_borrow()` + - unhealthy connection discard (`markUnhealthy()`) +- Includes integration tests for: + - pool exhaustion + - connection reuse + - concurrent operations + - unhealthy connection replacement + +### Legacy + +- No native connection pool abstraction in `libkmip`/`kmippp`. +- `kmippp` global last-result path creates thread-safety concerns for shared usage. + +## 5) Protocol, Serialization, and Performance Direction + +### Modern + +- Default protocol minor is KMIP 1.4 in `kmipcore`. +- Serialization uses `SerializationBuffer` to reduce repeated allocations during TTLV serialization. +- Response parsing validates success status and maps typed payloads. + +### Legacy + +- Protocol version can vary by operation (`KMIP_1_0` or `KMIP_1_4` depending on function). +- Serialization/decoding is repeated in operation functions with dynamic buffer resize loops. + +## 6) Locate/Pagination Behavior Differences + +- Modern `kmipclient` has large default locate pagination constants (`MAX_ITEMS_IN_BATCH=1024`, up to `MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH`). +- Legacy `kmippp` loops with a hardcoded locate page size of 16 and server-specific fallback behavior when `located_items == 0`. +- This can change practical behavior and performance across servers. + +## 7) Testing Comparison + +### Modern + +- `kmipclient` integrates GoogleTest for integration tests. +- `kmipcore` has dedicated core/parser/serialization test executables. +- Pool integration tests cover realistic concurrent scenarios. + +### Legacy + +- `libkmip` contains a large `tests.c`, but current CMake in `libkmip/src/CMakeLists.txt` builds library + demos; tests are not wired as a regular test target there. +- `kmippp` similarly provides demos but not a formal integrated test target in its CMake. + +## 8) Migration Notes (Practical) + +- Moving from `kmippp` to `kmipclient` generally improves: + - API consistency + - failure visibility (exceptions instead of mixed sentinel returns) + - thread-safe concurrent usage via pool +- Callers should adapt to exception handling and typed return values. +- Behavior changes to validate during migration: + - locate pagination/result ordering expectations + - protocol-version expectations for specific servers + - error-reporting flows (no `get_last_result()` global side channel) + +## Conclusion + +The modern stack is a significant architectural and operational improvement over the legacy stack, especially for concurrency, maintainability, and API clarity. The main migration risks are behavioral edge cases (pagination/version differences) and adapting legacy error-handling assumptions to exception-based control flow. + diff --git a/kmipclient/.clang-format b/kmipclient/.clang-format new file mode 100644 index 0000000..92bb8d1 --- /dev/null +++ b/kmipclient/.clang-format @@ -0,0 +1,110 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +Standard: c++17 +# Line length +ColumnLimit: 80 +# Indentation +IndentWidth: 2 +UseTab: Never +ContinuationIndentWidth: 4 +# Spacing +SpacesBeforeTrailingComments: 2 +SpaceAfterCStyleCast: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +# Braces +BreakBeforeBraces: Attach +BraceWrapping: + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: false + SplitEmptyRecord: false +# Pointer alignment +PointerAlignment: Right +# Function arguments +BinPackArguments: false +BinPackParameters: false +AlignAfterOpenBracket: BlockIndent +# Alignment +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +# Includes +SortIncludes: CaseSensitive +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^"' + Priority: 1 + - Regex: '^<.*>' + Priority: 2 +# Sorting +SortUsingDeclarations: true +# Other +AccessModifierOffset: -2 +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: BeforeHash +IndentWrappedFunctionNames: true +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +ReflowComments: true +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false diff --git a/kmipclient/CHANGELOG.md b/kmipclient/CHANGELOG.md new file mode 100644 index 0000000..c112123 --- /dev/null +++ b/kmipclient/CHANGELOG.md @@ -0,0 +1,17 @@ +Mar, 2026 Version 0.2.0 + + Interface optimization + Test suite update; + Now it uses completely new low-level KMIP protocol implementation from "kmipcore" library, + instead of old "libkmip" code. + +Dec, 2025 Version 0.1.1 + + Interface optimization + Test suite added + Bugs fixed + +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..e3bd9c3 --- /dev/null +++ b/kmipclient/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 3.10.0) +project(kmipclient) + +# Have to put these 2 lines here to compile +set(CMAKE_CXX_STANDARD 20) + +find_package(OpenSSL REQUIRED) + +add_library( + kmipclient + STATIC + include/kmipclient/KmipClient.hpp + src/KmipClient.cpp + include/kmipclient/KmipClientPool.hpp + src/KmipClientPool.cpp + include/kmipclient/NetClient.hpp + src/NetClientOpenSSL.cpp + include/kmipclient/NetClientOpenSSL.hpp + include/kmipclient/types.hpp + src/IOUtils.cpp + src/IOUtils.hpp + include/kmipclient/Kmip.hpp + src/Key.cpp + include/kmipclient/Key.hpp + src/StringUtils.cpp + src/StringUtils.hpp +) + +target_include_directories( + kmipclient PUBLIC + $ + $ + $ + $ +) + +target_link_libraries(kmipclient PUBLIC kmipcore OpenSSL::SSL OpenSSL::Crypto) + +set_property(TARGET kmipclient PROPERTY POSITION_INDEPENDENT_CODE ON) + +target_compile_features(kmipclient INTERFACE cxx_std_20) + +set_target_properties( + kmipclient + PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +option(WITH_ASAN "Enable Address Sanitizer" OFF) +if(WITH_ASAN) + target_compile_options(kmipclient INTERFACE "-fsanitize=address") + target_link_options(kmipclient INTERFACE "-fsanitize=address") +endif() + +export(TARGETS kmipcore 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} PRIVATE kmipclient) +endmacro() + +add_example(create_aes) +add_example(register_secret) +add_example(activate) +add_example(get) +add_example(get_logger) +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) +add_example(get_attributes) +add_example(pool) + +# Google Test integration +option(BUILD_TESTS "Build the tests" OFF) + +if(BUILD_TESTS) + if(NOT (TARGET gtest OR TARGET gmock)) + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 + ) + + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + endif() + + enable_testing() + + add_executable( + kmipclient_test + tests/KmipClientIntegrationTest.cpp + tests/KmipClientPoolIntegrationTest.cpp + ) + + target_link_libraries( + kmipclient_test + PRIVATE + GTest::gtest_main + kmipclient + ) + + include(GoogleTest) + gtest_discover_tests(kmipclient_test) +endif() diff --git a/kmipclient/README.md b/kmipclient/README.md new file mode 100644 index 0000000..f694c0b --- /dev/null +++ b/kmipclient/README.md @@ -0,0 +1,362 @@ +# The `kmipclient` library + +`kmipclient` is a C++20 library that provides a clean, high-level interface to +KMIP servers. It wraps the low-level `kmipcore` (and ultimately `libkmip`) +into safe C++ types, hiding raw memory management, buffer handling, and +TTLV encoding/decoding details from library users. + +Everything lives in the `kmipclient` namespace. + +--- + +## Design goals + +1. Easy to use and hard to misuse — forced error handling via exceptions. +2. Hide low-level details (no raw `KMIP` context, no manual buffer management). +3. Minimize manual memory management; prefer stack-allocated objects. +4. Make the library easy to extend. +5. Use only the low-level `kmipcore` layer; no mid-level `kmip_bio.c`. +6. Replaceable network communication layer (dependency injection). +7. Testability. + +--- + +## External dependencies + +The only external dependency is **OpenSSL** (already required by `kmipcore`). +`KmipClient` itself depends only on `kmipcore`. The network layer is injected +as an implementation of the `NetClient` interface. The library ships a +ready-to-use `NetClientOpenSSL` implementation; any custom transport can be +used by implementing the four-method `NetClient` interface. + +--- + +## Public headers + +| Header | Purpose | +|---|---| +| `kmipclient/KmipClient.hpp` | Main KMIP operations class | +| `kmipclient/KmipClientPool.hpp` | Thread-safe connection pool | +| `kmipclient/Kmip.hpp` | Simplified facade (bundles `NetClientOpenSSL` + `KmipClient`) | +| `kmipclient/NetClient.hpp` | Abstract network interface | +| `kmipclient/NetClientOpenSSL.hpp` | OpenSSL BIO implementation of `NetClient` | +| `kmipclient/Key.hpp` | Client-level crypto-key type with factory helpers | +| `kmipclient/KmipIOException.hpp` | Exception for network/IO errors | +| `kmipclient/types.hpp` | Type aliases re-exported from `kmipcore` | +| `kmipclient/kmipclient_version.hpp` | Version macros (`KMIPCLIENT_VERSION_STR`) | + +--- + +## High-level design + +### `NetClient` interface + +Abstract base class for network transport. Defines four virtual methods: + +```cpp +virtual bool connect(); // establish TLS connection +virtual void close(); // close TLS connection +virtual int send(const void *data, int dlen); +virtual int recv(void *data, int dlen); +``` + +`NetClientOpenSSL` is the ready-to-use implementation based on OpenSSL BIO. + +### `KmipClient` + +The main KMIP protocol client. It is constructed with a reference to an +already-created `NetClient` instance (dependency injection) and an optional +`kmipcore::Logger`: + +```cpp +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, timeout_ms); +net_client.connect(); +KmipClient client(net_client); // no logger +// or: +KmipClient client(net_client, logger); // with protocol logger +``` + +Copy and move are disabled. + +### `Kmip` façade + +`Kmip` bundles `NetClientOpenSSL` + `KmipClient` into a single object for the +common case where OpenSSL BIO transport is sufficient: + +```cpp +Kmip kmip(host, port, client_cert, client_key, server_ca, timeout_ms); +auto key_id = kmip.client().op_create_aes_key("mykey", "mygroup"); +``` + +### `Key` + +`kmipclient::Key` extends `kmipcore::Key` and adds factory helpers: + +| Factory | Description | +|---|---| +| `Key::aes_from_hex(hex)` | Create AES key from hexadecimal string | +| `Key::aes_from_base64(b64)` | Create AES key from Base64 string | +| `Key::aes_from_value(bytes)` | Create AES key from raw byte vector | +| `Key::generate_aes(size_bits)` | Generate a random AES key (128/192/256 bits) | +| `Key::from_PEM(pem)` | Parse a PEM-encoded certificate/public-key/private-key | + +### `KmipClientPool` + +Thread-safe pool of `KmipClient` connections. Connections are created lazily +on demand up to `max_connections`. Threads borrow a client via RAII: + +```cpp +KmipClientPool pool(KmipClientPool::Config{ + .host = "kmip-server", + .port = "5696", + .client_cert = "/path/to/cert.pem", + .client_key = "/path/to/key.pem", + .server_ca_cert = "/path/to/ca.pem", + .timeout_ms = 5000, + .max_connections = 8, +}); + +// In any thread: +auto conn = pool.borrow(); // blocks if all busy +auto key_id = conn->op_create_aes_key("k", "g"); +// conn returned to pool automatically on scope exit +``` + +Timed and non-blocking variants are also available: + +```cpp +auto conn = pool.borrow(std::chrono::seconds(10)); // throws on timeout +auto opt_conn = pool.try_borrow(); // returns std::nullopt if busy +``` + +If an operation throws an unrecoverable exception, mark the connection +unhealthy before the guard goes out of scope so the pool discards it: + +```cpp +try { + conn->op_get_key(id); +} catch (...) { + conn.markUnhealthy(); + throw; +} +``` + +Diagnostic accessors: `pool.available_count()`, `pool.total_count()`, +`pool.max_connections()`. + +### `KmipIOException` + +Thrown for network/IO errors (TLS handshake failure, send/receive error). +Inherits from `kmipcore::KmipException` so a single `catch` clause handles +both protocol and transport errors. + +--- + +## Available KMIP operations + +All operations are methods of `KmipClient`. They throw `kmipcore::KmipException` +(or `KmipIOException` for transport errors) on failure. + +| Method | Description | +|---|---| +| `op_create_aes_key(name, group)` | Server-side AES-256 key generation (KMIP CREATE) | +| `op_register_key(name, group, key)` | Register an existing key (KMIP REGISTER) | +| `op_register_secret(name, group, secret, type)` | Register a secret / password | +| `op_get_key(id [, all_attributes])` | Retrieve a symmetric key with optional attributes | +| `op_get_secret(id [, all_attributes])` | Retrieve a secret / password | +| `op_activate(id)` | Activate an entity (pre-active → active) | +| `op_revoke(id, reason, message, time)` | Revoke/deactivate an entity | +| `op_destroy(id)` | Destroy an entity (must be revoked first) | +| `op_locate_by_name(name, object_type)` | Find entity IDs by name | +| `op_locate_by_group(group, object_type [, max_ids])` | Find entity IDs by group | +| `op_all(object_type [, max_ids])` | Retrieve all entity IDs of a given type | +| `op_get_attribute_list(id)` | List attribute names for an entity | +| `op_get_attributes(id, attr_names)` | Retrieve specific attributes by name | + +--- + +## Usage examples + +### Get a symmetric key + +```cpp +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +using namespace kmipclient; + +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 200); +KmipClient client(net_client); + +try { + auto key = client.op_get_key(id); + // key.value() → std::vector with the raw key bytes + // key.attribute_value(KMIP_ATTR_NAME_STATE) → attribute string + // key.attribute_value(KMIP_ATTR_NAME_NAME) → key name +} catch (const std::exception &e) { + std::cerr << e.what() << '\n'; +} +``` + +### Create an AES-256 key on the server + +```cpp +#include "kmipclient/Kmip.hpp" +using namespace kmipclient; + +Kmip kmip(host, port, client_cert, client_key, server_ca, 200); +auto key_id = kmip.client().op_create_aes_key("mykey", "mygroup"); +``` + +### Register an existing key + +```cpp +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 200); +KmipClient client(net_client); + +auto k = Key::aes_from_hex("0102030405060708090a0b0c0d0e0f10..."); +auto id = client.op_register_key("mykey", "mygroup", k); +``` + +### Register a secret / password + +```cpp +Kmip kmip(host, port, client_cert, client_key, server_ca, 200); +auto id = kmip.client().op_register_secret("mysecret", "mygroup", "s3cr3t!", PASSWORD); +``` + +### Lifecycle: activate → revoke → destroy + +```cpp +client.op_activate(id); +client.op_revoke(id, UNSPECIFIED, "Deactivate", 0L); +client.op_destroy(id); +``` + +### Locate entities by name or group + +```cpp +auto ids = client.op_locate_by_name("mykey", KMIP_OBJTYPE_SYMMETRIC_KEY); +auto all = client.op_all(KMIP_OBJTYPE_SYMMETRIC_KEY); +auto grp = client.op_locate_by_group("mygroup", KMIP_OBJTYPE_SYMMETRIC_KEY); +``` + +### Retrieve attributes + +```cpp +auto attr_names = client.op_get_attribute_list(id); +auto attrs = client.op_get_attributes(id, attr_names); +``` + +### Protocol logging + +Pass any `kmipcore::Logger`-derived instance to enable TTLV message logging: + +```cpp +class StdoutLogger final : public kmipcore::Logger { +public: + bool shouldLog(kmipcore::LogLevel) const override { return true; } + void log(const kmipcore::LogRecord &r) override { + std::cout << '[' << kmipcore::to_string(r.level) << "] " + << r.component << ' ' << r.event << '\n' << r.message << '\n'; + } +}; + +auto logger = std::make_shared(); +KmipClient client(net_client, logger); +``` + +### Connection pool (multi-threaded) + +```cpp +#include "kmipclient/KmipClientPool.hpp" +using namespace kmipclient; + +KmipClientPool pool(KmipClientPool::Config{ + .host = host, .port = port, + .client_cert = cert, .client_key = key, .server_ca_cert = ca, + .timeout_ms = 5000, .max_connections = 8, +}); + +std::vector threads; +for (int i = 0; i < 16; ++i) { + threads.emplace_back([&pool, i] { + auto conn = pool.borrow(std::chrono::seconds(10)); + auto key_id = conn->op_create_aes_key("key_" + std::to_string(i), "group"); + std::cout << "thread " << i << " → " << key_id << '\n'; + }); +} +for (auto &t : threads) t.join(); +``` + +--- + +## Build + +```bash +mkdir build +cd build +cmake .. +cmake --build . +``` + +The library requires **C++20** and **OpenSSL**. + +--- + +## Integration testing + +Tests use the Google Test framework (fetched automatically when +`BUILD_TESTS=ON`). + +1. Export connection variables: + +```bash +export KMIP_ADDR=127.0.0.1 +export KMIP_PORT=5696 +export KMIP_CLIENT_CA=/path/to/client_cert.pem +export KMIP_CLIENT_KEY=/path/to/client_key.pem +export KMIP_SERVER_CA=/path/to/server_cert.pem +``` + +2. Configure and build: + +```bash +cmake -DBUILD_TESTS=ON .. +cmake --build . +``` + +3. Run: + +```bash +ctest --output-on-failure +# or directly: +./kmipclient_test +``` + +--- + +## Example programs + +| Binary | Description | +|---|---| +| `example_create_aes` | Create a server-side AES-256 key | +| `example_register_key` | Register an existing AES key | +| `example_register_secret` | Register a secret / password | +| `example_get` | Retrieve a symmetric key by ID | +| `example_get_logger` | Same as `example_get` with protocol-level TTLV logging | +| `example_get_secret` | Retrieve a secret by ID | +| `example_get_name` | Retrieve a key name attribute | +| `example_get_attributes` | List and print all attributes of a key | +| `example_get_all_ids` | List all symmetric-key and secret IDs on the server | +| `example_activate` | Activate (pre-active → active) a key or secret | +| `example_revoke` | Revoke / deactivate a key or secret | +| `example_destroy` | Destroy a revoked key or secret | +| `example_locate` | Find entity IDs by name | +| `example_locate_by_group` | Find entity IDs by group | +| `example_pool` | Multi-threaded pool demo (concurrent key creation) | + +All examples follow the same argument pattern: + +``` + [extra args…] +``` diff --git a/kmipclient/TODO.md b/kmipclient/TODO.md new file mode 100644 index 0000000..9138abf --- /dev/null +++ b/kmipclient/TODO.md @@ -0,0 +1,17 @@ +TODO +-- +Done: + +1. Re-write protocol serialization/deserialization completelly and remove the dependency on `kmip.c` +2. Multiple batch items requests and responses for cases like "register and activate", "revoke and destroy", +"get key and attributes", etc. +3. Multiple attributes getting +4. Human-readable request and response logging + +The list of things yet to be done + +5. Asymmetric keys and certificates support +6. Version negotiation with the KMIP server (Default is 1.4) +7. Complete version 2.0 specification support in the scope of current functionality. + + diff --git a/kmipclient/examples/example_activate.cpp b/kmipclient/examples/example_activate.cpp new file mode 100644 index 0000000..d6054f8 --- /dev/null +++ b/kmipclient/examples/example_activate.cpp @@ -0,0 +1,48 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + try { + const auto opt_key = client.op_activate(argv[6]); + std::cout << "Key wih ID: " << argv[6] << " is activated." << std::endl; + return 0; + } catch (const std::exception &e) { + std::cerr << "Can not activate key with id:" << argv[6] + << " Cause: " << e.what() << std::endl; + }; + + return -1; +} diff --git a/kmipclient/examples/example_create_aes.cpp b/kmipclient/examples/example_create_aes.cpp new file mode 100644 index 0000000..2cc8d28 --- /dev/null +++ b/kmipclient/examples/example_create_aes.cpp @@ -0,0 +1,48 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + try { + auto key_id = kmip.client().op_create_aes_key(argv[6], "TestGroup"); + std::cout << "Key ID: " << key_id << std::endl; + return 0; + } catch (std::exception &e) { + std::cerr << "Can not create key with name:" << argv[6] + << " Cause: " << e.what() << std::endl; + } + return -1; +} diff --git a/kmipclient/examples/example_destroy.cpp b/kmipclient/examples/example_destroy.cpp new file mode 100644 index 0000000..15f9648 --- /dev/null +++ b/kmipclient/examples/example_destroy.cpp @@ -0,0 +1,48 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + try { + const auto opt_key = client.op_destroy(argv[6]); + std::cout << "Key ID: " << argv[6] << " is destroyed." << std::endl; + } catch (const std::exception &e) { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() + << std::endl; + return -1; + } + return 0; +} diff --git a/kmipclient/examples/example_get.cpp b/kmipclient/examples/example_get.cpp new file mode 100644 index 0000000..f30a3f1 --- /dev/null +++ b/kmipclient/examples/example_get.cpp @@ -0,0 +1,62 @@ + +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_basics.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: " << KMIPCORE_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); + + try { + std::string id = argv[6]; + auto key = client.op_get_key(id); + std::cout << "Key: 0x"; + print_hex(key.value()); + std::cout << "State: " << key.attribute_value(KMIP_ATTR_NAME_STATE) << std::endl; + std::cout << "Name: " << key.attribute_value(KMIP_ATTR_NAME_NAME); + } catch (const std::exception &e) { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() + << std::endl; + return 1; + }; + + 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..052416b --- /dev/null +++ b/kmipclient/examples/example_get_all_ids.cpp @@ -0,0 +1,60 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + +This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + + try { + const auto opt_ids = client.op_all(KMIP_OBJTYPE_SYMMETRIC_KEY); + std::cout << "Found IDs of symmetric keys:" << std::endl; + for (const auto &id : opt_ids) { + std::cout << id << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Can not get keys." << " Cause: " << e.what() << std::endl; + }; + + try { + const auto opt_ids_s = client.op_all(KMIP_OBJTYPE_SECRET_DATA); + std::cout << "Found IDs of secret data:" << std::endl; + for (const auto &id : opt_ids_s) { + std::cout << id << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Can not get id-s. Cause: " << e.what() << std::endl; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_attributes.cpp b/kmipclient/examples/example_get_attributes.cpp new file mode 100644 index 0000000..11f68bf --- /dev/null +++ b/kmipclient/examples/example_get_attributes.cpp @@ -0,0 +1,80 @@ + +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_basics.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; +} + +void print_attributes(const attributes_t &attrs) { + for (auto const &attr : attrs) { + std::cout << attr.first << ": " << attr.second << std::endl; + } +} + +/* This example is incomplete because of the low-level kmip.c is quite + * incomplete, and there's no sense to complete exiting ugly C code. The next + * version of the "KMIPClient" library will remove dependency on old C code and + * will be replaced with C++ code of the protocol serialization/deserialization + */ + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + + try { + std::string id = argv[6]; + auto key = client.op_get_key(id); + std::cout << "Key: 0x"; + print_hex(key.value()); + auto attr_names = client.op_get_attribute_list(id); + + auto attr = client.op_get_attributes(id, attr_names); + + std::cout << "======= key attributes: =======" << std::endl; + print_attributes(key.attributes()); + std::cout << "======= all attributes: =======" << std::endl; + print_attributes(attr); + } catch (const std::exception &e) { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() + << std::endl; + return -1; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_logger.cpp b/kmipclient/examples/example_get_logger.cpp new file mode 100644 index 0000000..4d6b08c --- /dev/null +++ b/kmipclient/examples/example_get_logger.cpp @@ -0,0 +1,85 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_logger.hpp" + +#include +#include + +using namespace kmipclient; + +namespace { + + class StdoutLogger final : public kmipcore::Logger { + public: + [[nodiscard]] bool shouldLog(kmipcore::LogLevel) const override { + return true; + } + + void log(const kmipcore::LogRecord &record) override { + std::cout << '[' << kmipcore::to_string(record.level) << "] " + << record.component << " " << record.event << '\n' + << record.message << std::endl; + } + }; + + void print_hex(const kmipclient::key_t &key) { + for (auto const &c : key) { + std::cout << std::hex << static_cast(c); + } + std::cout << std::dec << std::endl; + } + +} // namespace + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; + if (argc < 7) { + std::cerr + << "Usage: example_get_logger " + " " + << std::endl; + return -1; + } + + auto logger = std::make_shared(); + NetClientOpenSSL net_client(argv[1], argv[2], argv[3], argv[4], argv[5], 200); + KmipClient client(net_client, logger); + + try { + std::string id = argv[6]; + auto key = client.op_get_key(id); + std::cout << "Key: 0x"; + print_hex(key.value()); + std::cout << "State: " << key.attribute_value(KMIP_ATTR_NAME_STATE) + << std::endl; + std::cout << "Name: " << key.attribute_value(KMIP_ATTR_NAME_NAME) + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " + << e.what() << std::endl; + return 1; + } + + return 0; +} + diff --git a/kmipclient/examples/example_get_name.cpp b/kmipclient/examples/example_get_name.cpp new file mode 100644 index 0000000..827e9c4 --- /dev/null +++ b/kmipclient/examples/example_get_name.cpp @@ -0,0 +1,59 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +void print_attributes(const attributes_t &attrs) { + for (auto const &attr : attrs) { + std::cout << attr.first << ": " << attr.second << std::endl; + } +} + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + try { + // get name + auto opt_attr = client.op_get_attributes(argv[6], {KMIP_ATTR_NAME_NAME}); + // get group + opt_attr.merge(client.op_get_attributes(argv[6], {KMIP_ATTR_NAME_GROUP})); + std::cout << "ID: " << argv[6] << " Attributes:" << std::endl; + print_attributes(opt_attr); + } catch (const std::exception &e) { + std::cerr << "Can not get name or group for id:" << argv[6] + << " Cause: " << e.what() << std::endl; + return -1; + }; + + return 0; +} diff --git a/kmipclient/examples/example_get_secret.cpp b/kmipclient/examples/example_get_secret.cpp new file mode 100644 index 0000000..c4db80b --- /dev/null +++ b/kmipclient/examples/example_get_secret.cpp @@ -0,0 +1,65 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; + + if (argc < 7) { + 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); + try { + auto secret = client.op_get_secret(argv[6], true); + std::cout << "Secret (text): " << secret.as_text() << std::endl; + std::cout << "Secret (hex): "; + for (const auto b : secret.value) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); + } + std::cout << std::dec << std::endl; + + const auto &attrs = secret.attributes(); + if (auto it = attrs.find(KMIP_ATTR_NAME_NAME); it != attrs.end()) { + std::cout << "Name: " << it->second << std::endl; + } + if (auto it = attrs.find(KMIP_ATTR_NAME_STATE); it != attrs.end()) { + std::cout << "State: " << it->second << std::endl; + } + } catch (std::exception &e) { + std::cerr << "Can not get secret with id:" << argv[6] + << " Cause: " << e.what() << std::endl; + return -1; + }; + + return 0; +} diff --git a/kmipclient/examples/example_locate.cpp b/kmipclient/examples/example_locate.cpp new file mode 100644 index 0000000..18430fd --- /dev/null +++ b/kmipclient/examples/example_locate.cpp @@ -0,0 +1,69 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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; + try { + const auto opt_ids = + client.op_locate_by_name(argv[6], KMIP_OBJTYPE_SYMMETRIC_KEY); + + std::cout << "Found IDs of symmetric keys:" << std::endl; + for (const auto &id : opt_ids) { + std::cout << id << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Can not get keys with name:" << argv[6] + << " Cause: " << e.what() << std::endl; + return 1; + }; + + try { + const auto opt_ids_s = + client.op_locate_by_name(argv[6], KMIP_OBJTYPE_SECRET_DATA); + std::cout << "Found IDs of secret data:" << std::endl; + for (const auto &id : opt_ids_s) { + std::cout << id << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Can not get secrets with name:" << argv[6] + << " Cause: " << e.what() << std::endl; + return 1; + }; + + 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..c4c0382 --- /dev/null +++ b/kmipclient/examples/example_locate_by_group.cpp @@ -0,0 +1,69 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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; + try { + const auto opt_ids = + client.op_locate_by_group(argv[6], KMIP_OBJTYPE_SYMMETRIC_KEY); + std::cout << "Found IDs of symmetric keys:"; + for (const auto &id : opt_ids) { + std::cout << id << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Can not get keys with group name:" << argv[6] + << " Cause: " << e.what() << std::endl; + return -1; + }; + + try { + const auto opt_ids_s = + client.op_locate_by_group(argv[6], KMIP_OBJTYPE_SECRET_DATA); + std::cout << "Found IDs of secret data:"; + for (const auto &id : opt_ids_s) { + std::cout << id << std::endl; + } + } catch (std::exception &e) { + std::cerr << "Can not get secrets with group name:" << argv[6] + << " Cause: " << e.what() << std::endl; + return -1; + }; + + return 0; +} diff --git a/kmipclient/examples/example_pool.cpp b/kmipclient/examples/example_pool.cpp new file mode 100644 index 0000000..665f4d1 --- /dev/null +++ b/kmipclient/examples/example_pool.cpp @@ -0,0 +1,121 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * example_pool.cpp + * + * Demonstrates KmipClientPool used from multiple threads simultaneously. + * + * Usage: + * example_pool + * [num_threads] [max_pool_size] + * + * Each thread borrows a KmipClient from the pool, creates one AES-256 key, + * and returns the connection automatically. + */ + +#include "kmipclient/KmipClientPool.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace kmipclient; +using namespace std::chrono_literals; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << "\n"; + + if (argc < 7) { + std::cerr + << "Usage: example_pool " + " [num_threads] [max_pool_size]\n"; + return 1; + } + + const std::string host = argv[1]; + const std::string port = argv[2]; + const std::string client_cert = argv[3]; + const std::string client_key = argv[4]; + const std::string server_ca_cert = argv[5]; + const std::string key_name_prefix = argv[6]; + const int num_threads = argc > 7 ? std::stoi(argv[7]) : 4; + const int max_pool_size = argc > 8 ? std::stoi(argv[8]) : 2; + + std::cout << std::format( + "Launching {} threads against a pool of max {} connections\n", + num_threads, max_pool_size + ); + + // ------------------------------------------------------------------ + // Build the pool. No connections are created here yet. + // ------------------------------------------------------------------ + KmipClientPool pool(KmipClientPool::Config{ + .host = host, + .port = port, + .client_cert = client_cert, + .client_key = client_key, + .server_ca_cert = server_ca_cert, + .timeout_ms = 5000, + .max_connections = static_cast(max_pool_size), + }); + + // ------------------------------------------------------------------ + // Spawn threads – each borrows a connection, uses it, returns it. + // ------------------------------------------------------------------ + std::vector threads; + threads.reserve(num_threads); + + for (int i = 0; i < num_threads; ++i) { + threads.emplace_back([&pool, &key_name_prefix, i]() { + const std::string key_name = + std::format("{}_{}", key_name_prefix, i); + try { + // borrow() blocks until a connection is available (or creates a + // new one if the pool is below its limit). + auto conn = pool.borrow(10s); // wait at most 10 s + auto key_id = conn->op_create_aes_key(key_name, "PoolTestGroup"); + + std::cout << std::format( + "[thread {:2d}] created key '{}' → id={}\n", i, key_name, key_id + ); + + // conn goes out of scope here → connection returned to pool. + } catch (const std::exception &e) { + std::cerr << std::format( + "[thread {:2d}] ERROR: {}\n", i, e.what() + ); + } + }); + } + + for (auto &t : threads) { + t.join(); + } + + std::cout << std::format( + "Pool stats: total={} available={} max={}\n", + pool.total_count(), pool.available_count(), pool.max_connections() + ); + + return 0; +} + diff --git a/kmipclient/examples/example_register_key.cpp b/kmipclient/examples/example_register_key.cpp new file mode 100644 index 0000000..fea3e09 --- /dev/null +++ b/kmipclient/examples/example_register_key.cpp @@ -0,0 +1,49 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + + try { + auto k = Key::aes_from_hex(argv[7]); + const auto opt_id = client.op_register_key(argv[6], "TestGroup", k); + std::cout << "Key registered. ID: " << opt_id << std::endl; + } catch (std::exception &e) { + std::cerr << "Can not register key:" << argv[6] << " Cause: " << e.what() + << std::endl; + return -1; + }; + + return 0; +} diff --git a/kmipclient/examples/example_register_secret.cpp b/kmipclient/examples/example_register_secret.cpp new file mode 100644 index 0000000..5e105c3 --- /dev/null +++ b/kmipclient/examples/example_register_secret.cpp @@ -0,0 +1,50 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; + + if (argc < 8) { + std::cerr << "Usage: example_register_secret " + " " + " " + << std::endl; + return -1; + } + + Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); + try { + auto id = kmip.client().op_register_secret( + argv[6], "TestGroup", argv[7], PASSWORD + ); + std::cout << "Secret ID: " << id << std::endl; + } catch (std::exception &e) { + std::cerr << "Can not register secret with name:" << argv[6] + << " Cause: " << e.what() << std::endl; + return -1; + } + return 0; +} diff --git a/kmipclient/examples/example_revoke.cpp b/kmipclient/examples/example_revoke.cpp new file mode 100644 index 0000000..3d9eedc --- /dev/null +++ b/kmipclient/examples/example_revoke.cpp @@ -0,0 +1,49 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipclient/kmipclient_version.hpp" + +#include + +using namespace kmipclient; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_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); + try { + const auto opt_key = + client.op_revoke(argv[6], UNSPECIFIED, "Deactivate", 0L); + std::cout << "Key ID: " << argv[6] << " is deactivated." << std::endl; + } catch (const std::exception &e) { + std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() + << std::endl; + return -1; + }; + return 0; +} diff --git a/kmipclient/include/kmipclient/Key.hpp b/kmipclient/include/kmipclient/Key.hpp new file mode 100644 index 0000000..9e4db94 --- /dev/null +++ b/kmipclient/include/kmipclient/Key.hpp @@ -0,0 +1,91 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_KEY_HPP +#define KMIPCLIENT_KEY_HPP + +#include "kmipclient/types.hpp" +#include "kmipcore/key.hpp" + +#include + +namespace kmipclient { + + /** + * Client-level crypto key extending the core Key with convenience + * factory methods for creating keys from hex, base64, PEM, etc. + */ + class Key : public kmipcore::Key { + public: + // Inherit all base-class constructors + using kmipcore::Key::Key; + + /** + * @brief Implicitly wraps an existing core key object. + * @param base Source key instance. + */ + Key(kmipcore::Key base) + : kmipcore::Key(std::move(base)) { + } // NOLINT(google-explicit-constructor) + + /** @brief Constructs an empty key instance. */ + Key() = default; + + /** + * @brief Creates an AES symmetric key from a hexadecimal string. + * @param hex Hex-encoded key bytes. + * @return Initialized AES key. + * @throws kmipcore::KmipException when decoding fails. + */ + static Key aes_from_hex(const std::string &hex); + /** + * @brief Creates an AES symmetric key from a Base64 string. + * @param base64 Base64-encoded key bytes. + * @return Initialized AES key. + * @throws kmipcore::KmipException when decoding fails. + */ + static Key aes_from_base64(const std::string &base64); + + /** + * @brief Creates an AES symmetric key from raw bytes. + * @param val Binary key value. + * @return Initialized AES key. + */ + static Key aes_from_value(const std::vector &val); + /** + * @brief Generates a random AES key of the requested size. + * @param size_bits Key size in bits. Supported values: 128, 192, 256. + * @return Randomly generated AES key. + * @throws kmipcore::KmipException when RNG fails or size is unsupported. + */ + static Key generate_aes(size_t size_bits); + /** + * @brief Parses a PEM payload into a KMIP key-like object. + * + * The parser recognizes X.509 certificate, public key, and private key + * PEM blocks and maps them to the appropriate KMIP representation. + * + * @param pem PEM-formatted input. + * @return Key mapped from the input PEM block. + * @throws kmipcore::KmipException when parsing fails. + */ + static Key from_PEM(const std::string &pem); + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_KEY_HPP diff --git a/kmipclient/include/kmipclient/Kmip.hpp b/kmipclient/include/kmipclient/Kmip.hpp new file mode 100644 index 0000000..2f054f8 --- /dev/null +++ b/kmipclient/include/kmipclient/Kmip.hpp @@ -0,0 +1,79 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIP_HPP +#define KMIP_HPP +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" + +#include + +namespace kmipclient { + /** + * @brief Convenience wrapper that owns transport and client instances. + * + * This class builds and connects @ref NetClientOpenSSL and then exposes + * the initialized @ref KmipClient instance. + */ + class Kmip { + public: + /** + * @brief Creates and connects an OpenSSL-based KMIP client stack. + * @param host KMIP server hostname or IP address. + * @param port KMIP server port. + * @param clientCertificateFn Path to client X.509 certificate in PEM. + * @param clientKeyFn Path to client private key in PEM. + * @param serverCaCertFn Path to trusted server CA/certificate in PEM. + * @param timeout_ms Connect/read/write timeout in milliseconds. + * @param logger Optional KMIP protocol logger. + * @throws kmipcore::KmipException when network/TLS initialization fails. + */ + Kmip( + const char *host, + const char *port, + const char *clientCertificateFn, + const char *clientKeyFn, + const char *serverCaCertFn, + int timeout_ms, + std::shared_ptr logger = {} + ) + : m_net_client( + host, + port, + clientCertificateFn, + clientKeyFn, + serverCaCertFn, + timeout_ms + ), + m_client(m_net_client, std::move(logger)) { + m_net_client.connect(); + }; + + /** + * @brief Returns the initialized high-level KMIP client. + * @return Mutable reference to the owned @ref KmipClient. + */ + KmipClient &client() { return m_client; }; + + private: + /** @brief OpenSSL BIO-based network transport. */ + NetClientOpenSSL m_net_client; + /** @brief High-level KMIP protocol client bound to @ref m_net_client. */ + KmipClient m_client; + }; +} // namespace kmipclient +#endif // KMIP_HPP diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp new file mode 100644 index 0000000..d5874db --- /dev/null +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -0,0 +1,228 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef KMIP_CLIENT_HPP +#define KMIP_CLIENT_HPP + +#include "kmipclient/Key.hpp" +#include "kmipclient/NetClient.hpp" +#include "kmipclient/types.hpp" +#include "kmipcore/kmip_logger.hpp" + +#include + +namespace kmipclient { + + /** Maximum number of KMIP locate response batches processed by search helpers. */ + constexpr size_t MAX_BATCHES_IN_SEARCH = 64; + /** Maximum number of response items expected per single KMIP batch. */ + constexpr size_t MAX_ITEMS_IN_BATCH = 1024; + + class IOUtils; + /** + * @brief High-level KMIP client API for key and secret lifecycle operations. + * + * The instance uses an already configured and connected @ref NetClient + * transport and provides typed wrappers around common KMIP operations. + */ + class KmipClient { + public: + /** + * @brief Creates a client bound to an existing transport. + * @param net_client Pre-initialized network transport implementation. + * @param logger Optional KMIP protocol logger. When set, serialized TTLV + * request and response payloads are logged at DEBUG level. + */ + explicit KmipClient( + NetClient &net_client, + std::shared_ptr logger = {} + ); + /** @brief Destroys the client and internal helpers. */ + ~KmipClient(); + // no copy, no move + KmipClient(const KmipClient &) = delete; + KmipClient &operator=(const KmipClient &) = delete; + KmipClient(KmipClient &&) = delete; + KmipClient &operator=(KmipClient &&) = delete; + + /** + * @brief Executes KMIP Register for a key object. + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @param k Key material and metadata to register. + * @return Unique identifier assigned by the KMIP server. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t op_register_key( + const name_t &name, const name_t &group, const Key &k + ) const; + + /** + * @brief Executes KMIP Register for secret data provided as text bytes. + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @param secret Secret payload to store. + * @param secret_type KMIP Secret Data Type for the payload. + * @return Unique identifier assigned by the KMIP server. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t op_register_secret( + const name_t &name, + const name_t &group, + std::string_view secret, + enum secret_data_type secret_type + ) const; + + /** + * @brief Executes KMIP Register for binary secret data. + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @param secret Binary secret payload to store. + * @param secret_type KMIP Secret Data Type for the payload. + * @return Unique identifier assigned by the KMIP server. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t op_register_secret( + const name_t &name, + const name_t &group, + const secret_t &secret, + enum secret_data_type secret_type + ) const; + + /** + * @brief Executes KMIP Create to generate a server-side AES-256 key. + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @return Unique identifier of the created key. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t + op_create_aes_key(const name_t &name, const name_t &group) const; + + /** + * @brief Executes KMIP Get and decodes a key object. + * @param id Unique identifier of the key object. + * @param all_attributes When true, fetches all available attributes. + * @return Decoded key object. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] Key op_get_key(const id_t &id, bool all_attributes = false) const; + + /** + * @brief Executes KMIP Get and decodes a secret object. + * @param id Unique identifier of the secret object. + * @param all_attributes When true, fetches all available attributes. + * @return Decoded secret object. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] Secret + op_get_secret(const id_t &id, bool all_attributes = false) const; + + /** + * @brief Executes KMIP Activate for a managed object. + * @param id Unique identifier of the object to activate. + * @return Identifier returned by the server (normally equals @p id). + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t op_activate(const id_t &id) const; + + /** + * @brief Executes KMIP Get Attribute List. + * @param id Unique identifier of the target object. + * @return List of attribute names available for the object. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] names_t op_get_attribute_list(const id_t &id) const; + + /** + * @brief Executes KMIP Get Attributes for selected attribute names. + * @param id Unique identifier of the target object. + * @param attr_names Attribute names to fetch (for example "Name", "State"). + * @return Map of requested attributes present in the server response. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] attributes_t op_get_attributes( + const id_t &id, const std::vector &attr_names + ) const; + + /** + * @brief Executes KMIP Locate using an exact object name filter. + * @param name Object name to match. + * @param o_type KMIP object type to search. + * @return Matching object identifiers; may contain multiple IDs. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] ids_t + op_locate_by_name(const name_t &name, enum object_type o_type) const; + + /** + * @brief Executes KMIP Locate using the object group filter. + * @param group Group name to match. + * @param o_type KMIP object type to search. + * @param max_ids Upper bound on collected IDs across locate batches. + * @return Matching object identifiers, up to @p max_ids entries. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] ids_t op_locate_by_group( + const name_t &group, + enum object_type o_type, + size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + ) const; + + /** + * @brief Executes KMIP Revoke for a managed object. + * @param id Unique identifier of the object to revoke. + * @param reason KMIP revocation reason code. + * @param message Optional human-readable revocation message. + * @param occurrence_time Incident time for reasons that require it; use 0 + * for regular deactivation flows. + * @return Identifier returned by the server (normally equals @p id). + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] id_t op_revoke( + const id_t &id, + enum revocation_reason_type reason, + const name_t &message, + time_t occurrence_time + ) const; + /** + * @brief Executes KMIP Destroy for a managed object. + * @param id Unique identifier of the object to destroy. + * @return Identifier returned by the server (normally equals @p id). + * @throws kmipcore::KmipException on protocol or server-side failure. + * @note Most KMIP servers require the object to be revoked first. + */ + [[nodiscard]] id_t op_destroy(const id_t &id) const; + + /** + * @brief Executes KMIP Locate without name/group filters. + * @param o_type KMIP object type to fetch. + * @param max_ids Upper bound on collected IDs across locate batches. + * @return Identifiers of matching objects, up to @p max_ids entries. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] ids_t op_all( + enum object_type o_type, + size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + ) const; + + private: + NetClient &net_client; + std::unique_ptr io; + }; + +} // namespace kmipclient +#endif // KMIP_CLIENT_HPP diff --git a/kmipclient/include/kmipclient/KmipClientPool.hpp b/kmipclient/include/kmipclient/KmipClientPool.hpp new file mode 100644 index 0000000..ea161f2 --- /dev/null +++ b/kmipclient/include/kmipclient/KmipClientPool.hpp @@ -0,0 +1,237 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#pragma once + +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace kmipclient { + +/** + * Thread-safe pool of KmipClient connections. + * + * Each thread borrows a KmipClient for the duration of one or more KMIP + * operations and then returns it automatically via RAII. The pool creates + * new TLS connections on demand, up to max_connections. When all connections + * are in use and the limit has been reached, borrow() blocks until one becomes + * available. + * + * Typical usage: + * @code + * KmipClientPool pool({ + * .host = "kmip-server", + * .port = "5696", + * .client_cert = "/path/to/cert.pem", + * .client_key = "/path/to/key.pem", + * .server_ca_cert = "/path/to/ca.pem", + * .timeout_ms = 5000, + * .max_connections = 8 + * }); + * + * // In any thread: + * auto conn = pool.borrow(); + * auto id = conn->op_create_aes_key("mykey", "mygroup"); + * // conn is returned to the pool automatically when it goes out of scope. + * @endcode + */ +class KmipClientPool { +private: + // ---- Slot ------------------------------------------------------------------ + // One "slot" = one TLS connection + one KmipClient bound to that connection. + // Slots are heap-allocated so that the KmipClient's reference to NetClient + // stays valid even if the unique_ptr to the Slot is moved around. + struct Slot { + std::unique_ptr net_client; + std::unique_ptr kmip_client; + }; + +public: + // ---- Constants ------------------------------------------------------------- + /** Default upper bound for simultaneously open KMIP connections. */ + static constexpr size_t DEFAULT_MAX_CONNECTIONS = 16; + + // ---- Config ---------------------------------------------------------------- + /** + * @brief Connection and pooling settings used to construct @ref KmipClientPool. + */ + struct Config { + /** KMIP server host name or IP address. */ + std::string host; + /** KMIP server service port. */ + std::string port; + std::string client_cert; ///< Path to PEM client certificate + std::string client_key; ///< Path to PEM client private key + std::string server_ca_cert; ///< Path to PEM server CA certificate (or server cert) + std::shared_ptr logger; ///< Optional KMIP protocol logger + /** Connect/read/write timeout in milliseconds. */ + int timeout_ms = 5000; + /** Maximum number of simultaneous live connections in the pool. */ + size_t max_connections = DEFAULT_MAX_CONNECTIONS; + }; + + // ---- BorrowedClient -------------------------------------------------------- + /** + * RAII guard wrapping a single borrowed connection. + * + * Provides KmipClient access via operator* / operator->. + * Automatically returns the connection to the pool on destruction. + * + * If the KMIP operation threw an exception, call markUnhealthy() before + * the guard goes out of scope so the pool discards the connection instead + * of re-using it. + */ + class BorrowedClient { + public: + ~BorrowedClient(); + + // Non-copyable (unique ownership of the borrowed slot) + BorrowedClient(const BorrowedClient &) = delete; + BorrowedClient &operator=(const BorrowedClient &) = delete; + + // Movable (e.g. return from a factory function) + BorrowedClient(BorrowedClient &&) noexcept; + BorrowedClient &operator=(BorrowedClient &&) noexcept; + + /** @brief Accesses the borrowed client as a reference. */ + KmipClient &operator*(); + /** @brief Accesses the borrowed client as a pointer. */ + KmipClient *operator->(); + + /** + * Mark this connection as unhealthy. + * When the BorrowedClient is destroyed the pool will close and discard + * this connection (freeing one slot for a fresh connection next time). + * + * Call this whenever a KMIP operation throws an exception you cannot + * recover from on the same connection. + */ + void markUnhealthy() noexcept { healthy_ = false; } + + /// Returns false if markUnhealthy() has been called. + [[nodiscard]] bool isHealthy() const noexcept { return healthy_; } + + private: + friend class KmipClientPool; + + BorrowedClient(KmipClientPool &pool, std::unique_ptr slot) noexcept; + + KmipClientPool *pool_ = nullptr; ///< non-owning; pool outlives any borrow + std::unique_ptr slot_; + bool healthy_ = true; + }; + + // ---- Construction / destruction -------------------------------------------- + + /** + * Construct the pool. No connections are created immediately; they are + * established lazily on the first borrow() call. + * + * @throws std::invalid_argument if max_connections == 0 + */ + explicit KmipClientPool(Config config); + ~KmipClientPool() = default; + + // Non-copyable, non-movable (holds a mutex and a condition_variable) + KmipClientPool(const KmipClientPool &) = delete; + KmipClientPool &operator=(const KmipClientPool &) = delete; + KmipClientPool(KmipClientPool &&) = delete; + KmipClientPool &operator=(KmipClientPool &&) = delete; + + // ---- Borrow methods -------------------------------------------------------- + + /** + * Borrow a client, blocking indefinitely until one is available. + * + * If the pool is below max_connections a new TLS connection is created + * on demand. If the pool is at capacity the call blocks until another + * thread returns a connection. + * + * @throws kmipcore::KmipException if a new connection must be created and + * the TLS handshake fails. + */ + [[nodiscard]] BorrowedClient borrow(); + + /** + * Like borrow(), but gives up after @p timeout. + * + * @throws kmipcore::KmipException on timeout or TLS connection failure. + */ + [[nodiscard]] BorrowedClient borrow(std::chrono::milliseconds timeout); + + /** + * Non-blocking variant. + * + * Returns std::nullopt immediately when no connection is available and the + * pool is at capacity. Otherwise behaves like borrow(). + * + * @throws kmipcore::KmipException if a new connection must be created and + * the TLS handshake fails. + */ + [[nodiscard]] std::optional try_borrow(); + + // ---- Diagnostic accessors -------------------------------------------------- + + /// Number of connections currently idle in the pool. + [[nodiscard]] size_t available_count() const; + + /// Total connections in existence (idle + currently borrowed). + [[nodiscard]] size_t total_count() const; + + /// Configured upper limit. + [[nodiscard]] size_t max_connections() const noexcept { + return config_.max_connections; + } + +private: + // ---- Internal helpers ------------------------------------------------------ + + /// Create a brand-new connected Slot. Called without the lock held. + /// Throws on TLS handshake failure. + std::unique_ptr create_slot(); + + /// Return a slot to the pool (or discard it if unhealthy / disconnected). + /// safe to call from BorrowedClient destructor (noexcept). + void return_slot(std::unique_ptr slot, bool healthy) noexcept; + + /// Acquire one slot with the lock already held (and still locked via lk). + /// Unlocks lk before the potentially slow TLS connect call. + BorrowedClient acquire_locked(std::unique_lock lk); + + // ---- Data members ---------------------------------------------------------- + + Config config_; + + mutable std::mutex mutex_; + std::condition_variable cv_; + + /// Idle (available) connections. + std::vector> available_; + + /// Total connections created and not yet destroyed (available + in-use). + size_t total_count_ = 0; +}; + +} // namespace kmipclient + diff --git a/kmipclient/include/kmipclient/KmipIOException.hpp b/kmipclient/include/kmipclient/KmipIOException.hpp new file mode 100644 index 0000000..3f5deab --- /dev/null +++ b/kmipclient/include/kmipclient/KmipIOException.hpp @@ -0,0 +1,56 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPIOSEXCEPTION_HPP +#define KMIPIOSEXCEPTION_HPP + +#include "kmipcore/kmip_basics.hpp" + +#include + +namespace kmipclient { + + /** + * Exception class for communication-level (IO/network) errors in the + * kmipclient library. Thrown whenever a network send, receive, SSL + * handshake, or connection operation fails. + * + * Inherits from kmipcore::KmipException so that existing catch handlers + * for the base class continue to work without modification. + */ + class KmipIOException : public kmipcore::KmipException { + public: + /** + * @brief Creates an IO exception with a message. + * @param msg Human-readable error description. + */ + explicit KmipIOException(const std::string &msg) + : kmipcore::KmipException(msg) {} + + /** + * @brief Creates an IO exception with status code and message. + * @param code Error code associated with the failure. + * @param msg Human-readable error description. + */ + KmipIOException(int code, const std::string &msg) + : kmipcore::KmipException(code, msg) {} + }; + +} // namespace kmipclient + +#endif // KMIPIOSEXCEPTION_HPP + diff --git a/kmipclient/include/kmipclient/NetClient.hpp b/kmipclient/include/kmipclient/NetClient.hpp new file mode 100644 index 0000000..b9f3c79 --- /dev/null +++ b/kmipclient/include/kmipclient/NetClient.hpp @@ -0,0 +1,103 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIP_NET_CLIENT_HPP +#define KMIP_NET_CLIENT_HPP + +#include + +namespace kmipclient { + /** + * @brief Abstract transport interface used by @ref KmipClient. + * + * Implementations provide connection lifecycle and raw byte send/receive + * primitives over a secure channel. + */ + class NetClient { + public: + /** + * @brief Stores transport configuration. + * @param host KMIP server host. + * @param port KMIP server port. + * @param clientCertificateFn Path to client X.509 certificate in PEM. + * @param clientKeyFn Path to matching client private key in PEM. + * @param serverCaCertFn Path to trusted server CA/certificate in PEM. + * @param timeout_ms Connect/read/write timeout in milliseconds. + */ + NetClient( + const std::string &host, + const std::string &port, + const std::string &clientCertificateFn, + const std::string &clientKeyFn, + const std::string &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) {}; + + /** @brief Virtual destructor for interface-safe cleanup. */ + 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; + /** + * @brief Establishes network/TLS connection to the KMIP server. + * @return true on successful connection establishment, false otherwise. + */ + + virtual bool connect() = 0; + /** @brief Closes the connection and releases underlying resources. */ + virtual void close() = 0; + + /** + * @brief Checks whether a connection is currently established. + * @return true when connected, false otherwise. + */ + [[nodiscard]] bool is_connected() const { return m_isConnected; } + /** + * @brief Sends bytes over the established connection. + * @param data Source buffer. + * @param dlen Number of bytes to send. + * @return Number of bytes sent, or -1 on failure. + */ + virtual int send(const void *data, int dlen) = 0; + + /** + * @brief Receives bytes from the established connection. + * @param data Destination buffer. + * @param dlen Number of bytes requested. + * @return Number of bytes received, or -1 on failure. + */ + 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_serverCaCertificateFn; + int m_timeout_ms; + bool m_isConnected = false; + }; +} // namespace kmipclient +#endif // KMIP_NET_CLIENT_HPP diff --git a/kmipclient/include/kmipclient/NetClientOpenSSL.hpp b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp new file mode 100644 index 0000000..dc6cf0f --- /dev/null +++ b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp @@ -0,0 +1,100 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPNETCLILENTOPENSSL_HPP +#define KMIPNETCLILENTOPENSSL_HPP + +#include "kmipclient/NetClient.hpp" + +#include + +extern "C" { // we do not want to expose SSL stuff to this class users +typedef struct ssl_ctx_st SSL_CTX; +typedef struct bio_st BIO; +void SSL_CTX_free(SSL_CTX *); +void BIO_free_all(BIO *); +} + +namespace kmipclient { + /** + * @brief OpenSSL BIO-based implementation of @ref NetClient. + */ + class NetClientOpenSSL : public NetClient { + public: + /** + * @brief Constructs an OpenSSL-backed transport. + * @param host KMIP server host. + * @param port KMIP server port. + * @param clientCertificateFn Path to client certificate in PEM. + * @param clientKeyFn Path to client private key in PEM. + * @param serverCaCertFn Path to trusted server CA/certificate in PEM. + * @param timeout_ms Connect/read/write timeout in milliseconds. + */ + NetClientOpenSSL( + const std::string &host, + const std::string &port, + const std::string &clientCertificateFn, + const std::string &clientKeyFn, + const std::string &serverCaCertFn, + int timeout_ms + ); + /** @brief Releases OpenSSL resources and closes any open connection. */ + ~NetClientOpenSSL() override; + // no copy, no move + NetClientOpenSSL(const NetClientOpenSSL &) = delete; + NetClientOpenSSL &operator=(const NetClientOpenSSL &) = delete; + NetClientOpenSSL(NetClientOpenSSL &&) = delete; + NetClientOpenSSL &operator=(NetClientOpenSSL &&) = delete; + + /** + * @brief Establishes a TLS connection to the configured KMIP endpoint. + * @return true on success, false on failure. + */ + bool connect() override; + /** @brief Closes the underlying OpenSSL BIO connection. */ + void close() override; + /** + * @brief Sends raw bytes through the TLS channel. + * @param data Source buffer. + * @param dlen Number of bytes to send. + * @return Number of bytes sent, or -1 on failure. + */ + int send(const void *data, int dlen) override; + /** + * @brief Receives raw bytes through the TLS channel. + * @param data Destination buffer. + * @param dlen Number of bytes requested. + * @return Number of bytes read, or -1 on failure. + */ + int recv(void *data, int dlen) override; + + private: + struct SslCtxDeleter { + void operator()(SSL_CTX *ptr) const { SSL_CTX_free(ptr); } + }; + struct BioDeleter { + void operator()(BIO *ptr) const { BIO_free_all(ptr); } + }; + + std::unique_ptr ctx_; + std::unique_ptr bio_; + + bool checkConnected(); + }; +} // namespace kmipclient + +#endif // KMIPNETCLILENTOPENSSL_HPP diff --git a/kmipclient/include/kmipclient/kmipclient_version.hpp b/kmipclient/include/kmipclient/kmipclient_version.hpp new file mode 100644 index 0000000..45e3419 --- /dev/null +++ b/kmipclient/include/kmipclient/kmipclient_version.hpp @@ -0,0 +1,42 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_VERSION_H +#define KMIPCLIENT_VERSION_H + +#include "kmipcore/kmipcore_version.hpp" + +/** @brief kmipclient semantic version major component. */ +#define KMIPCLIENT_VERSION_MAJOR 0 +/** @brief kmipclient semantic version minor component. */ +#define KMIPCLIENT_VERSION_MINOR 2 +/** @brief kmipclient semantic version patch component. */ +#define KMIPCLIENT_VERSION_PATCH 0 + +/** @brief Internal helper for macro-stringification. */ +#define KMIPCLIENT_STRINGIFY_I(x) #x +/** @brief Internal helper for macro-stringification. */ +#define KMIPCLIENT_TOSTRING_I(x) KMIPCLIENT_STRINGIFY_I(x) + +/** @brief Full kmipclient version string in "major.minor.patch" form. */ +#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/kmipclient/types.hpp b/kmipclient/include/kmipclient/types.hpp new file mode 100644 index 0000000..546331a --- /dev/null +++ b/kmipclient/include/kmipclient/types.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "kmipcore/key.hpp" +#include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/secret.hpp" +#include "kmipcore/types.hpp" + +namespace kmipclient { + + /** @brief Alias for KMIP attribute map type. */ + using kmipcore::attributes_t; + /** @brief Alias for binary data buffer type. */ + using kmipcore::bin_data_t; + /** @brief Alias for KMIP unique identifier type. */ + using kmipcore::id_t; + /** @brief Alias for a list of KMIP unique identifiers. */ + using kmipcore::ids_t; + /** @brief Alias for raw key bytes container type. */ + using kmipcore::key_t; + /** @brief Alias for supported key-kind discriminator enum. */ + using kmipcore::KeyType; + /** @brief Alias for KMIP textual name type. */ + using kmipcore::name_t; + /** @brief Alias for a list of textual names. */ + using kmipcore::names_t; + /** @brief Alias for KMIP secret object representation. */ + using kmipcore::Secret; + /** @brief Alias for secret binary payload container. */ + using kmipcore::secret_t; + + /** @brief Canonical KMIP attribute name for object name. */ + inline const std::string &KMIP_ATTR_NAME_NAME = kmipcore::KMIP_ATTR_NAME_NAME; + /** @brief Canonical KMIP attribute name for object group. */ + inline const std::string &KMIP_ATTR_NAME_GROUP = + kmipcore::KMIP_ATTR_NAME_GROUP; + /** @brief Canonical KMIP attribute name for object state. */ + inline const std::string &KMIP_ATTR_NAME_STATE = + kmipcore::KMIP_ATTR_NAME_STATE; + /** @brief Canonical KMIP attribute name for unique identifier. */ + inline const std::string &KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = + kmipcore::KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; + + /** @brief Re-export stream formatter overloads from kmipcore. */ + using kmipcore::operator<<; + +} // namespace kmipclient diff --git a/kmipclient/src/IOUtils.cpp b/kmipclient/src/IOUtils.cpp new file mode 100644 index 0000000..68006f9 --- /dev/null +++ b/kmipclient/src/IOUtils.cpp @@ -0,0 +1,138 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "IOUtils.hpp" + +#include "kmipclient/KmipIOException.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_formatter.hpp" +#include "kmipcore/kmip_logger.hpp" + +#include +#include + +namespace kmipclient { +#define KMIP_MSG_LENGTH_BYTES 8 + + namespace { + + [[nodiscard]] int32_t read_int32_be(const uint8_t *bytes) { + return (static_cast(bytes[0]) << 24) | + (static_cast(bytes[1]) << 16) | + (static_cast(bytes[2]) << 8) | + static_cast(bytes[3]); + } + + } // namespace + + void IOUtils::log_debug(const char *event, std::span ttlv) const { + try { + if (!logger_ || !logger_->shouldLog(kmipcore::LogLevel::Debug)) { + return; + } + + logger_->log(kmipcore::LogRecord{ + .level = kmipcore::LogLevel::Debug, + .component = "kmip.protocol", + .event = event, + .message = kmipcore::format_ttlv(ttlv) + }); + } catch (...) { + // Logging is strictly best-effort: protocol operations must not fail + // because a custom logger threw. + } + } + + void IOUtils::send(const std::vector &request_bytes) const { + const int dlen = static_cast(request_bytes.size()); + if (dlen <= 0) { + throw KmipIOException(-1, "Can not send empty KMIP request."); + } + + if (int sent = net_client.send(request_bytes.data(), dlen); sent < dlen) { + throw KmipIOException( + -1, + std::format( + "Can not send request. Bytes total: {}, bytes sent: {}", + dlen, + sent + ) + ); + } + } + + void IOUtils::read_exact(uint8_t *buf, int n) { + int total_read = 0; + while (total_read < n) { + int received = net_client.recv(buf + total_read, n - total_read); + if (received <= 0) { + throw KmipIOException( + -1, + std::format( + "Connection closed or error while reading. Expected {}, got {}", + n, + total_read + ) + ); + } + total_read += received; + } + } + + std::vector IOUtils::receive_message(size_t max_message_size) { + uint8_t msg_len_buf[KMIP_MSG_LENGTH_BYTES]; + + read_exact(msg_len_buf, KMIP_MSG_LENGTH_BYTES); + + const int32_t length = read_int32_be(msg_len_buf + 4); + if (length < 0 || static_cast(length) > max_message_size) { + throw KmipIOException( + -1, std::format("Message too long. Length: {}", length) + ); + } + + std::vector response( + KMIP_MSG_LENGTH_BYTES + static_cast(length) + ); + memcpy(response.data(), msg_len_buf, KMIP_MSG_LENGTH_BYTES); + + read_exact(response.data() + KMIP_MSG_LENGTH_BYTES, length); + + + return response; + } + + void IOUtils::do_exchange( + const std::vector &request_bytes, + std::vector &response_bytes, + size_t max_message_size + ) { + try { + log_debug("request", request_bytes); + send(request_bytes); + response_bytes = receive_message(max_message_size); + log_debug("response", response_bytes); + } catch (const KmipIOException &) { + // Mark the underlying connection as dead so the pool (via + // return_slot → is_connected() check) discards this slot + // automatically — no need for the caller to call markUnhealthy(). + net_client.close(); + throw; + } + } + +} // namespace kmipclient diff --git a/kmipclient/src/IOUtils.hpp b/kmipclient/src/IOUtils.hpp new file mode 100644 index 0000000..1e0fa83 --- /dev/null +++ b/kmipclient/src/IOUtils.hpp @@ -0,0 +1,62 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef IOUTILS_HPP +#define IOUTILS_HPP + +#include "kmipclient/NetClient.hpp" +#include "kmipclient/types.hpp" +#include "kmipcore/kmip_logger.hpp" + +#include +#include +#include +#include + +namespace kmipclient { + + class IOUtils { + public: + explicit IOUtils( + NetClient &nc, std::shared_ptr logger = {} + ) + : net_client(nc), logger_(std::move(logger)) {}; + + void do_exchange( + const std::vector &request_bytes, + std::vector &response_bytes, + size_t max_message_size + ); + + private: + void log_debug(const char *event, std::span ttlv) const; + void send(const std::vector &request_bytes) const; + std::vector receive_message(size_t max_message_size); + + /** + * Reads exactly n bytes from the network into the buffer. + * Throws KmipException on error or prematureEOF. + */ + void read_exact(uint8_t *buf, int n); + + NetClient &net_client; + std::shared_ptr logger_; + }; + +} // namespace kmipclient + +#endif // IOUTILS_HPP diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp new file mode 100644 index 0000000..7975544 --- /dev/null +++ b/kmipclient/src/Key.cpp @@ -0,0 +1,272 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/Key.hpp" + +#include "StringUtils.hpp" +#include "kmipclient/types.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kmipclient { + + namespace { + // Try to parse BIO as X509 certificate and return Key if successful + std::optional try_parse_certificate(BIO *bio) { + X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (!cert) { + return std::nullopt; + } + + EVP_PKEY *pkey = X509_get_pubkey(cert); + if (!pkey) { + X509_free(cert); + return std::nullopt; + } + + unsigned char *der = nullptr; + int der_len = i2d_PUBKEY(pkey, &der); + EVP_PKEY_free(pkey); + X509_free(cert); + + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector key_bytes(der, der + der_len); + OPENSSL_free(der); + + attributes_t attrs; + attrs[KMIP_ATTR_NAME_NAME] = "certificate_public_key"; + return Key( + key_bytes, + KeyType::PUBLIC_KEY, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + // Try to parse BIO as private key + std::optional try_parse_private_key(BIO *bio) { + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + if (!pkey) { + return std::nullopt; + } + + unsigned char *der = nullptr; + int der_len = i2d_PrivateKey(pkey, &der); + EVP_PKEY_free(pkey); + + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector key_bytes(der, der + der_len); + OPENSSL_free(der); + + attributes_t attrs; + attrs[KMIP_ATTR_NAME_NAME] = "private_key"; + return Key( + key_bytes, + KeyType::PRIVATE_KEY, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + // Try to parse BIO as public key + std::optional try_parse_public_key(BIO *bio) { + EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); + if (!pkey) { + return std::nullopt; + } + + unsigned char *der = nullptr; + int der_len = i2d_PUBKEY(pkey, &der); + EVP_PKEY_free(pkey); + + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector key_bytes(der, der + der_len); + OPENSSL_free(der); + + attributes_t attrs; + attrs[KMIP_ATTR_NAME_NAME] = "public_key"; + return Key( + key_bytes, + KeyType::PUBLIC_KEY, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + /** Validates AES byte-vector size and constructs a Key. */ + Key make_aes_key(std::vector bytes) { + const size_t size = bytes.size(); + if (size != 16 && size != 24 && size != 32) { + throw kmipcore::KmipException{ + -1, + std::string("Invalid AES key length: ") + std::to_string(size * 8) + + " bits. Should be 128, 192 or 256 bits" + }; + } + return Key( + std::move(bytes), + KeyType::SYMMETRIC_KEY, + cryptographic_algorithm::KMIP_CRYPTOALG_AES, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + {} + ); + } + + // Try to detect an AES/raw key encoded in PEM-like text by extracting + // base64 between headers + std::optional try_parse_aes_from_pem_text(const std::string &pem) { + // Find the first PEM header and footer lines, extract base64 content + // between them + std::istringstream iss(pem); + std::string line; + bool in_pem = false; + std::string b64; + while (std::getline(iss, line)) { + if (!in_pem) { + if (line.rfind("-----BEGIN", 0) == 0) { + in_pem = true; + } + } else { + if (line.rfind("-----END", 0) == 0) { + break; + } + // skip header/footer and empty lines + if (line.empty()) { + continue; + } + b64 += line; + } + } + + if (b64.empty()) { + return std::nullopt; + } + + try { + auto decoded = StringUtils::fromBase64(b64); + size_t size = decoded.size(); + if (size == 16 || size == 24 || size == 32) { + return make_aes_key(std::move(decoded)); + } + } catch (...) // any parsing errors + { + return std::nullopt; + } + + return std::nullopt; + } + } // anonymous namespace + + Key Key::aes_from_hex(const std::string &hex) { + return make_aes_key(StringUtils::fromHex(hex)); + } + + Key Key::aes_from_base64(const std::string &base64) { + return make_aes_key(StringUtils::fromBase64(base64)); + } + + Key Key::aes_from_value(const std::vector &val) { + return make_aes_key(std::vector(val)); + } + + Key Key::from_PEM(const std::string &pem) { + // 1) Try as certificate + BIO *bio = BIO_new_mem_buf(pem.data(), static_cast(pem.size())); + if (!bio) { + throw kmipcore::KmipException("Failed to create BIO for PEM data"); + } + + if (auto cert_key = try_parse_certificate(bio); cert_key.has_value()) { + BIO_free(bio); + return cert_key.value(); + } + + (void) BIO_reset(bio); + if (auto priv_key = try_parse_private_key(bio); priv_key.has_value()) { + BIO_free(bio); + return priv_key.value(); + } + + (void) BIO_reset(bio); + if (auto pub_key = try_parse_public_key(bio); pub_key.has_value()) { + BIO_free(bio); + return pub_key.value(); + } + + BIO_free(bio); + + // 2) Try to detect an AES/raw key encoded in PEM text (base64 between + // headers) + if (auto aes_key = try_parse_aes_from_pem_text(pem); aes_key.has_value()) { + return aes_key.value(); + } + + throw kmipcore::KmipException( + KMIP_NOT_IMPLEMENTED, "Unsupported PEM format or not implemented" + ); + } + + Key Key::generate_aes(size_t size_bits) { + if (size_bits != 128 && size_bits != 192 && size_bits != 256) { + throw kmipcore::KmipException( + "Unsupported AES key size. Use 128, 192 or 256 bits" + ); + } + + size_t size_bytes = size_bits / 8; + std::vector buf(size_bytes); + if (1 != RAND_bytes(buf.data(), static_cast(size_bytes))) { + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + throw kmipcore::KmipException( + std::string("OpenSSL RAND_bytes failed: ") + err_buf + ); + } + + return make_aes_key(std::move(buf)); + } +} // namespace kmipclient diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp new file mode 100644 index 0000000..e99e4e7 --- /dev/null +++ b/kmipclient/src/KmipClient.cpp @@ -0,0 +1,412 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClient.hpp" + +#include "IOUtils.hpp" +#include "kmipcore/attributes_parser.hpp" +#include "kmipcore/key_parser.hpp" +#include "kmipcore/kmip_requests.hpp" +#include "kmipcore/response_parser.hpp" + +#include + +namespace kmipclient { + +// Look up a required attribute in a response map. +// Throws kmipcore::KmipException when the server omitted the attribute, +// unlike attributes_t::operator[] which would silently insert an empty value. +static const std::string &require_attr( + const attributes_t &attrs, const std::string &name +) { + auto it = attrs.find(name); + if (it == attrs.end()) { + throw kmipcore::KmipException( + "Required attribute '" + name + "' missing from server response" + ); + } + return it->second; +} + + KmipClient::KmipClient( + NetClient &net_client, std::shared_ptr logger + ) + : net_client(net_client), + io(std::make_unique(net_client, std::move(logger))) {}; + + + KmipClient::~KmipClient() { + net_client.close(); + }; + + id_t KmipClient::op_register_key( + const name_t &name, const name_t &group, const Key &k + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::RegisterSymmetricKeyRequest(name, group, k.value()) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + id_t KmipClient::op_register_secret( + const name_t &name, + const name_t &group, + const std::string_view secret, + enum secret_data_type secret_type + ) const { + return op_register_secret( + name, + group, + secret_t(secret.begin(), secret.end()), + secret_type + ); + } + + id_t KmipClient::op_register_secret( + const name_t &name, + const name_t &group, + const secret_t &secret, + enum secret_data_type secret_type + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::RegisterSecretRequest(name, group, secret, secret_type) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + id_t KmipClient::op_create_aes_key( + const name_t &name, const name_t &group + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::CreateSymmetricKeyRequest(name, group) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + Key KmipClient::op_get_key(const id_t &id, bool all_attributes) const { + kmipcore::RequestMessage request; + const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); + + std::vector requested_attrs; + if (!all_attributes) { + requested_attrs = {KMIP_ATTR_NAME_STATE, KMIP_ATTR_NAME_NAME}; + } + + const auto attributes_item_id = request.add_batch_item( + kmipcore::GetAttributesRequest(id, requested_attrs) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + auto key = kmipcore::KeyParser::parseGetKeyResponse(get_response); + + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + attributes_t attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + + if (all_attributes) { + for (const auto &item : attrs) { + key.set_attribute(item.first, item.second); + } + } else { + key.set_attribute(KMIP_ATTR_NAME_STATE, require_attr(attrs, KMIP_ATTR_NAME_STATE)); + key.set_attribute(KMIP_ATTR_NAME_NAME, require_attr(attrs, KMIP_ATTR_NAME_NAME)); + } + + return key; + } + + Secret KmipClient::op_get_secret(const id_t &id, bool all_attributes) const { + kmipcore::RequestMessage request; + const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); + + std::vector requested_attrs; + if (!all_attributes) { + requested_attrs = {KMIP_ATTR_NAME_STATE, KMIP_ATTR_NAME_NAME}; + } + + const auto attributes_item_id = request.add_batch_item( + kmipcore::GetAttributesRequest(id, requested_attrs) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + Secret secret; + try { + secret = kmipcore::KeyParser::parseGetSecretResponse(get_response); + } catch (const kmipcore::KmipException &e) { + // KMIP_REASON_INVALID_DATA_TYPE means the object exists but its type is + // not Secret Data (e.g. the ID points to a symmetric key). Rethrowing + // as "Could not locate" would be misleading; surface the real cause. + if (e.code() == KMIP_REASON_INVALID_DATA_TYPE) { + throw kmipcore::KmipException( + KMIP_REASON_INVALID_DATA_TYPE, + "Object '" + id + "' is not Secret Data" + ); + } + throw; + } + + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + attributes_t attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + + if (all_attributes) { + for (const auto &item : attrs) { + secret.set_attribute(item.first, item.second); + } + } else { + secret.set_attribute(KMIP_ATTR_NAME_STATE, require_attr(attrs, KMIP_ATTR_NAME_STATE)); + secret.set_attribute(KMIP_ATTR_NAME_NAME, require_attr(attrs, KMIP_ATTR_NAME_NAME)); + } + + return secret; + } + + id_t KmipClient::op_activate(const id_t &id) const { + kmipcore::RequestMessage request; + const auto batch_item_id = + request.add_batch_item(kmipcore::ActivateRequest(id)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + names_t KmipClient::op_get_attribute_list(const id_t &id) const { + kmipcore::RequestMessage request; + const auto batch_item_id = + request.add_batch_item(kmipcore::GetAttributeListRequest(id)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = rf.getResponseByBatchItemId< + kmipcore::GetAttributeListResponseBatchItem>(batch_item_id); + return names_t{ + response.getAttributeNames().begin(), response.getAttributeNames().end() + }; + } + + attributes_t KmipClient::op_get_attributes( + const id_t &id, const std::vector &attr_names + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = + request.add_batch_item(kmipcore::GetAttributesRequest(id, attr_names)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + return kmipcore::AttributesParser::parse(response.getAttributes()); + } + + ids_t KmipClient::op_locate_by_name( + const name_t &name, enum object_type o_type + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::LocateRequest(false, name, o_type, MAX_ITEMS_IN_BATCH, 0) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + return ids_t{ + response.getUniqueIdentifiers().begin(), + response.getUniqueIdentifiers().end() + }; + } + + ids_t KmipClient::op_locate_by_group( + const name_t &group, enum object_type o_type, size_t max_ids + ) const { + if (max_ids == 0) { + return {}; + } + + ids_t result; + size_t received = 0; + size_t offset = 0; + + do { + const size_t remaining = max_ids - result.size(); + const size_t page_size = std::min(remaining, MAX_ITEMS_IN_BATCH); + + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::LocateRequest(true, group, o_type, page_size, offset) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + auto exp = ids_t( + response.getUniqueIdentifiers().begin(), + response.getUniqueIdentifiers().end() + ); + + if (ids_t got = exp; !got.empty()) { + received = got.size(); + offset += got.size(); + const size_t to_take = std::min(remaining, got.size()); + result.insert(result.end(), got.begin(), got.begin() + to_take); + } else { + break; + } + } while (received == MAX_ITEMS_IN_BATCH && result.size() < max_ids); + + return result; + } + + ids_t KmipClient::op_all(enum object_type o_type, size_t max_ids) const { + return op_locate_by_group("", o_type, max_ids); + } + + id_t KmipClient::op_revoke( + const id_t &id, + enum revocation_reason_type reason, + const name_t &message, + time_t occurrence_time + ) const { + kmipcore::RequestMessage request; + const auto batch_item_id = request.add_batch_item( + kmipcore::RevokeRequest(id, reason, message, occurrence_time) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + id_t KmipClient::op_destroy(const id_t &id) const { + kmipcore::RequestMessage request; + const auto batch_item_id = + request.add_batch_item(kmipcore::DestroyRequest(id)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + +} // namespace kmipclient diff --git a/kmipclient/src/KmipClientPool.cpp b/kmipclient/src/KmipClientPool.cpp new file mode 100644 index 0000000..70d4396 --- /dev/null +++ b/kmipclient/src/KmipClientPool.cpp @@ -0,0 +1,211 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/KmipClientPool.hpp" + +#include "kmipcore/kmip_basics.hpp" + +#include +#include + +namespace kmipclient { + +// ============================================================================ +// BorrowedClient +// ============================================================================ + +KmipClientPool::BorrowedClient::BorrowedClient( + KmipClientPool &pool, std::unique_ptr slot) noexcept + : pool_(&pool), slot_(std::move(slot)) {} + +KmipClientPool::BorrowedClient::BorrowedClient(BorrowedClient &&other) noexcept + : pool_(other.pool_), + slot_(std::move(other.slot_)), + healthy_(other.healthy_) { + other.pool_ = nullptr; // disown so other's dtor is a no-op +} + +KmipClientPool::BorrowedClient & +KmipClientPool::BorrowedClient::operator=(BorrowedClient &&other) noexcept { + if (this != &other) { + // Return our current slot before taking ownership of the incoming one. + if (pool_ != nullptr && slot_ != nullptr) { + pool_->return_slot(std::move(slot_), healthy_); + } + pool_ = other.pool_; + slot_ = std::move(other.slot_); + healthy_ = other.healthy_; + other.pool_ = nullptr; + } + return *this; +} + +KmipClientPool::BorrowedClient::~BorrowedClient() { + if (pool_ != nullptr && slot_ != nullptr) { + pool_->return_slot(std::move(slot_), healthy_); + } +} + +KmipClient &KmipClientPool::BorrowedClient::operator*() { + return *slot_->kmip_client; +} + +KmipClient *KmipClientPool::BorrowedClient::operator->() { + return slot_->kmip_client.get(); +} + +// ============================================================================ +// KmipClientPool +// ============================================================================ + +KmipClientPool::KmipClientPool(Config config) : config_(std::move(config)) { + if (config_.max_connections == 0) { + throw kmipcore::KmipException( + -1, + "KmipClientPool: max_connections must be greater than zero" + ); + } + available_.reserve(config_.max_connections); +} + +// ---------------------------------------------------------------------------- +// Private helpers +// ---------------------------------------------------------------------------- + +std::unique_ptr KmipClientPool::create_slot() { + auto slot = std::make_unique(); + + slot->net_client = std::make_unique( + config_.host, + config_.port, + config_.client_cert, + config_.client_key, + config_.server_ca_cert, + config_.timeout_ms + ); + slot->net_client->connect(); // throws KmipException on failure + + slot->kmip_client = + std::make_unique(*slot->net_client, config_.logger); + + return slot; +} + +void KmipClientPool::return_slot( + std::unique_ptr slot, bool healthy) noexcept { + // Decide under the lock what to do with the slot. + bool discard = !healthy || !slot->net_client->is_connected(); + + { + std::lock_guard lk(mutex_); + if (!discard) { + available_.push_back(std::move(slot)); + } else { + --total_count_; // one fewer live connection + } + cv_.notify_one(); + } + // If slot was not moved into available_, its destructor runs here – + // outside the lock – which calls KmipClient::~KmipClient() → net_client.close(). +} + +KmipClientPool::BorrowedClient +KmipClientPool::acquire_locked(std::unique_lock lk) { + if (!available_.empty()) { + // Re-use an idle connection. + auto slot = std::move(available_.back()); + available_.pop_back(); + lk.unlock(); + return BorrowedClient(*this, std::move(slot)); + } + + // total_count_ < max_connections is guaranteed by the caller. + // Reserve the slot under the lock, then release before the slow TLS connect. + ++total_count_; + lk.unlock(); + + try { + return BorrowedClient(*this, create_slot()); + } catch (...) { + // Connection failed: give the reserved slot back. + std::lock_guard guard(mutex_); + --total_count_; + cv_.notify_one(); + throw; + } +} + +// ---------------------------------------------------------------------------- +// Public borrow methods +// ---------------------------------------------------------------------------- + +KmipClientPool::BorrowedClient KmipClientPool::borrow() { + std::unique_lock lk(mutex_); + cv_.wait(lk, [this] { + return !available_.empty() || total_count_ < config_.max_connections; + }); + return acquire_locked(std::move(lk)); +} + +KmipClientPool::BorrowedClient +KmipClientPool::borrow(std::chrono::milliseconds timeout) { + std::unique_lock lk(mutex_); + + const bool slot_available = cv_.wait_for(lk, timeout, [this] { + return !available_.empty() || total_count_ < config_.max_connections; + }); + + if (!slot_available) { + throw kmipcore::KmipException( + -1, + std::format( + "KmipClientPool: no connection available after {}ms " + "(pool size: {}, all {} connections in use)", + timeout.count(), + config_.max_connections, + total_count_ + ) + ); + } + return acquire_locked(std::move(lk)); +} + +std::optional KmipClientPool::try_borrow() { + std::unique_lock lk(mutex_); + + if (available_.empty() && total_count_ >= config_.max_connections) { + return std::nullopt; + } + return acquire_locked(std::move(lk)); +} + +// ---------------------------------------------------------------------------- +// Diagnostic accessors +// ---------------------------------------------------------------------------- + +size_t KmipClientPool::available_count() const { + std::lock_guard lk(mutex_); + return available_.size(); +} + +size_t KmipClientPool::total_count() const { + std::lock_guard lk(mutex_); + return total_count_; +} + +} // namespace kmipclient + diff --git a/kmipclient/src/NetClientOpenSSL.cpp b/kmipclient/src/NetClientOpenSSL.cpp new file mode 100644 index 0000000..acfabb1 --- /dev/null +++ b/kmipclient/src/NetClientOpenSSL.cpp @@ -0,0 +1,244 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/NetClientOpenSSL.hpp" + +#include "kmipclient/KmipIOException.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace kmipclient { + + // Replaces get_openssl_error using Queue + static std::string getOpenSslError() { + std::ostringstream oss; + unsigned long err; + while ((err = ERR_get_error()) != 0) { + char buf[256]; + ERR_error_string_n(err, buf, sizeof(buf)); + oss << buf << "; "; + } + std::string errStr = oss.str(); + if (errStr.empty()) { + return "Unknown OpenSSL error"; + } + return errStr; + } + + static std::string timeoutMessage(const char *op, int timeout_ms) { + std::ostringstream oss; + oss << "KMIP " << op << " timed out after " << timeout_ms << "ms"; + return oss.str(); + } + + // Apply SO_RCVTIMEO / SO_SNDTIMEO on the underlying socket so that every + // BIO_read / BIO_write call times out after timeout_ms milliseconds. + // Must be called after BIO_do_connect() succeeds. + static void apply_socket_io_timeout(BIO *bio, int timeout_ms) { + if (timeout_ms <= 0) { + return; + } + + int fd = -1; + if (BIO_get_fd(bio, &fd) < 0 || fd < 0) { + // Unable to obtain socket fd – skip silently (non-fatal). + return; + } + + struct timeval tv {}; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) { + throw KmipIOException( + -1, + "Failed to set SO_RCVTIMEO (" + std::to_string(timeout_ms) + + "ms): " + strerror(errno) + ); + } + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) { + throw KmipIOException( + -1, + "Failed to set SO_SNDTIMEO (" + std::to_string(timeout_ms) + + "ms): " + strerror(errno) + ); + } + } + + // Returns true when errno indicates that a socket operation was interrupted + // by the kernel because the configured SO_RCVTIMEO / SO_SNDTIMEO expired. + static bool is_timeout_errno() { + return errno == EAGAIN || errno == EWOULDBLOCK || errno == ETIMEDOUT; + } + + bool NetClientOpenSSL::checkConnected() { + if (is_connected()) { + return true; + } + return connect(); + } + + NetClientOpenSSL::NetClientOpenSSL( + const std::string &host, + const std::string &port, + const std::string &clientCertificateFn, + const std::string &clientKeyFn, + const std::string &serverCaCertFn, + int timeout_ms + ) + : NetClient( + host, + port, + clientCertificateFn, + clientKeyFn, + serverCaCertFn, + timeout_ms + ) {} + + NetClientOpenSSL::~NetClientOpenSSL() { + // Avoid calling virtual methods from destructor. + if (bio_) { + bio_.reset(); + } + if (ctx_) { + ctx_.reset(); + } + m_isConnected = false; + } + + bool NetClientOpenSSL::connect() { + // RAII for SSL_CTX + ctx_.reset(SSL_CTX_new(TLS_method())); + if (!ctx_) { + throw KmipIOException( + -1, "SSL_CTX_new failed: " + getOpenSslError() + ); + } + + if (SSL_CTX_use_certificate_file( + ctx_.get(), m_clientCertificateFn.c_str(), SSL_FILETYPE_PEM + ) != 1) { + throw KmipIOException( + -1, + "Loading client certificate failed: " + m_clientCertificateFn + " (" + + getOpenSslError() + ")" + ); + } + + if (SSL_CTX_use_PrivateKey_file( + ctx_.get(), m_clientKeyFn.c_str(), SSL_FILETYPE_PEM + ) != 1) { + throw KmipIOException( + -1, + "Loading client key failed: " + m_clientKeyFn + " (" + + getOpenSslError() + ")" + ); + } + + if (SSL_CTX_load_verify_locations( + ctx_.get(), m_serverCaCertificateFn.c_str(), nullptr + ) != 1) { + throw KmipIOException( + -1, + "Loading server CA certificate failed: " + m_serverCaCertificateFn + + " (" + getOpenSslError() + ")" + ); + } + + // RAII for BIO + bio_.reset(BIO_new_ssl_connect(ctx_.get())); + if (!bio_) { + throw KmipIOException( + -1, "BIO_new_ssl_connect failed: " + getOpenSslError() + ); + } + + SSL *ssl = nullptr; + BIO_get_ssl(bio_.get(), &ssl); + if (!ssl) { + throw KmipIOException( + -1, "BIO_get_ssl failed: " + getOpenSslError() + ); + } + + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + BIO_set_conn_hostname(bio_.get(), m_host.c_str()); + BIO_set_conn_port(bio_.get(), m_port.c_str()); + + // Using generic blocking BIO connect timeout if supported or rely on system + // socket timeout BIO_set_ssl_renegotiate_timeout(bio_, m_timeout_ms); // + // This function is non-standard or deprecated from initial code analysis. + + if (BIO_do_connect(bio_.get()) != 1) { + throw KmipIOException( + -1, "BIO_do_connect failed: " + getOpenSslError() + ); + } + + // Apply per-operation I/O timeouts on the now-connected socket so that + // every subsequent BIO_read / BIO_write times out after m_timeout_ms ms. + apply_socket_io_timeout(bio_.get(), m_timeout_ms); + + m_isConnected = true; + return true; + } + + void NetClientOpenSSL::close() { + if (bio_) { + // BIO_free_all is called by unique_ptr reset + bio_.reset(); + } + if (ctx_) { + ctx_.reset(); + } + m_isConnected = false; + } + + int NetClientOpenSSL::send(const void *data, int dlen) { + if (!checkConnected()) { + return -1; + } + const int ret = BIO_write(bio_.get(), data, dlen); + if (ret <= 0 && is_timeout_errno()) { + throw KmipIOException( + -1, timeoutMessage("send", m_timeout_ms) + ); + } + return ret; + } + + int NetClientOpenSSL::recv(void *data, int dlen) { + if (!checkConnected()) { + return -1; + } + const int ret = BIO_read(bio_.get(), data, dlen); + if (ret <= 0 && is_timeout_errno()) { + throw KmipIOException( + -1, timeoutMessage("receive", m_timeout_ms) + ); + } + return ret; + } + +} // namespace kmipclient diff --git a/kmipclient/src/StringUtils.cpp b/kmipclient/src/StringUtils.cpp new file mode 100644 index 0000000..7674206 --- /dev/null +++ b/kmipclient/src/StringUtils.cpp @@ -0,0 +1,88 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "StringUtils.hpp" + +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include +namespace kmipclient { + + unsigned char char2int(const char input) { + if (input >= '0' && input <= '9') { + return input - '0'; + } + if (input >= 'A' && input <= 'F') { + return input - 'A' + 10; + } + if (input >= 'a' && input <= 'f') { + return input - 'a' + 10; + } + throw kmipcore::KmipException{"Invalid hex character."}; + } + + key_t StringUtils::fromHex(const std::string &hex) { + if (hex.empty() || hex.size() % 2 != 0) { + throw kmipcore::KmipException{"Invalid hex string length."}; + } + key_t bytes; + for (unsigned int i = 0; i < hex.length(); i += 2) { + std::string byteString = hex.substr(i, 2); + auto byte = char2int(byteString.c_str()[0]) * 16 + + char2int(byteString.c_str()[1]); + bytes.push_back(byte); + } + return bytes; + } + + std::vector + StringUtils::fromBase64(const std::string &base64) { + static const std::array lookup = []() { + std::array l{}; + l.fill(-1); + for (int i = 0; i < 64; ++i) { + l["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + [i]] = i; + } + return l; + }(); + + std::vector out; + out.reserve((base64.size() / 4) * 3); + + int val = 0, val_b = -8; + for (unsigned char c : base64) { + if (lookup[c] == -1) { + if (c == '=') { + break; // Padding reached + } + continue; // Skip whitespace or invalid chars + } + val = (val << 6) + lookup[c]; + val_b += 6; + if (val_b >= 0) { + out.push_back(static_cast((val >> val_b) & 0xFF)); + val_b -= 8; + } + } + return out; + } + + +} // namespace kmipclient \ No newline at end of file diff --git a/kmipclient/src/StringUtils.hpp b/kmipclient/src/StringUtils.hpp new file mode 100644 index 0000000..c074871 --- /dev/null +++ b/kmipclient/src/StringUtils.hpp @@ -0,0 +1,33 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRINGUTILS_HPP +#define STRINGUTILS_HPP + +#include "kmipclient/types.hpp" + +namespace kmipclient { + + class StringUtils { + public: + static key_t fromHex(const std::string &hex); + static std::vector fromBase64(const std::string &base64); + }; + +} // namespace kmipclient + +#endif // STRINGUTILS_HPP diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp new file mode 100644 index 0000000..5897bf9 --- /dev/null +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -0,0 +1,748 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include +#include +#include +#include + +#define TEST_GROUP "tests" + +using namespace kmipclient; + + +static std::string TESTING_NAME_PREFIX = "tests_"; + + +// Helper class to manage environment variables +class KmipTestConfig { +public: + static KmipTestConfig &getInstance() { + static KmipTestConfig instance; + return instance; + } + + [[nodiscard]] bool isConfigured() const { + return !kmip_addr.empty() && !kmip_port.empty() && + !kmip_client_ca.empty() && !kmip_client_key.empty() && + !kmip_server_ca.empty(); + } + + std::string kmip_addr; + std::string kmip_port; + std::string kmip_client_ca; + std::string kmip_client_key; + std::string kmip_server_ca; + int timeout_ms; + +private: + KmipTestConfig() { + const char *addr = std::getenv("KMIP_ADDR"); + const char *port = std::getenv("KMIP_PORT"); + const char *client_ca = std::getenv("KMIP_CLIENT_CA"); + const char *client_key = std::getenv("KMIP_CLIENT_KEY"); + const char *server_ca = std::getenv("KMIP_SERVER_CA"); + const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); + + if (addr) { + kmip_addr = addr; + } + if (port) { + kmip_port = port; + } + if (client_ca) { + kmip_client_ca = client_ca; + } + if (client_key) { + kmip_client_key = client_key; + } + if (server_ca) { + kmip_server_ca = server_ca; + } + + timeout_ms = timeout ? std::atoi(timeout) : 5000; // Default 5 seconds + + if (!isConfigured()) { + std::cerr << "WARNING: KMIP environment variables not set. Tests will be " + "skipped.\n" + << "Required variables:\n" + << " KMIP_ADDR\n" + << " KMIP_PORT\n" + << " KMIP_CLIENT_CA\n" + << " KMIP_CLIENT_KEY\n" + << " KMIP_SERVER_CA\n"; + } + } +}; + +// Base test fixture for KMIP integration tests +class KmipClientIntegrationTest : public ::testing::Test { +protected: + std::vector created_key_ids; + + void SetUp() override { + auto &config = KmipTestConfig::getInstance(); + + if (!config.isConfigured()) { + GTEST_SKIP() << "KMIP environment variables not configured"; + } + } + + void TearDown() override { + const auto *test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + if (HasFailure()) { + std::cout << test_info->name() << ": FAIL" << std::endl; + } else { + std::cout << test_info->name() << ": OK" << std::endl; + } + + // Cleanup created keys if stored + auto &config = KmipTestConfig::getInstance(); + if (config.isConfigured() && !created_key_ids.empty()) { + try { + Kmip kmip( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms + ); + + for (const auto &key_id : created_key_ids) { + // Try to destroy the key (best effort cleanup) + try { + // if the object is not active then it cannot be revoked with reason + // other than KEY_COMPROMISE + auto res_r = kmip.client().op_revoke( + key_id, KEY_COMPROMISE, "Test cleanup", 0 + ); + auto res_d = kmip.client().op_destroy(key_id); + } catch (kmipcore::KmipException &e) { + std::cerr << "Failed to destroy key: " << e.what() << std::endl; + } + } + } catch (...) { + // Ignore cleanup errors + } + } + } + + static std::unique_ptr createKmipClient() { + auto &config = KmipTestConfig::getInstance(); + return std::make_unique( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms + ); + } + + void trackKeyForCleanup(const std::string &key_id) { + created_key_ids.push_back(key_id); + } +}; +// Test: Locate keys by group +TEST_F(KmipClientIntegrationTest, LocateKeysByGroup) { + auto kmip = createKmipClient(); + std::string group_name = + "test_locate_group_" + std::to_string(std::time(nullptr)); + std::vector expected_ids; + + try { + // Create a few keys in the same unique group + for (int i = 0; i < 3; ++i) { + auto key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "LocateByGroup_" + std::to_string(i), group_name + ); + expected_ids.push_back(key_id); + trackKeyForCleanup(key_id); + } + + // Locate by group + auto found_ids = kmip->client().op_locate_by_group( + group_name, KMIP_OBJTYPE_SYMMETRIC_KEY + ); + + // Verify all created keys are found + for (const auto &expected_id : expected_ids) { + auto it = std::find(found_ids.begin(), found_ids.end(), expected_id); + EXPECT_NE(it, found_ids.end()) + << "Key " << expected_id << " not found in group " << group_name; + } + + std::cout << "Successfully located " << expected_ids.size() + << " keys in group: " << group_name << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Locate by group failed: " << e.what(); + } +} + +// Test: op_locate_by_group respects max_ids upper bound +TEST_F(KmipClientIntegrationTest, LocateKeysByGroupHonorsMaxIds) { + auto kmip = createKmipClient(); + std::string group_name = + "test_locate_group_limit_" + std::to_string(std::time(nullptr)); + std::vector created_ids; + + try { + for (int i = 0; i < 3; ++i) { + auto key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "LocateByGroupLimit_" + std::to_string(i), + group_name + ); + created_ids.push_back(key_id); + trackKeyForCleanup(key_id); + } + + const size_t max_ids = 2; + auto found_ids = kmip->client().op_locate_by_group( + group_name, KMIP_OBJTYPE_SYMMETRIC_KEY, max_ids + ); + + EXPECT_LE(found_ids.size(), max_ids); + EXPECT_EQ(found_ids.size(), max_ids); + for (const auto &id : found_ids) { + auto it = std::find(created_ids.begin(), created_ids.end(), id); + EXPECT_NE(it, created_ids.end()) + << "Located id " << id << " was not created by this test"; + } + } catch (kmipcore::KmipException &e) { + FAIL() << "LocateKeysByGroupHonorsMaxIds failed: " << e.what(); + } +} + +// Test: op_all with max_ids=0 returns no ids +TEST_F(KmipClientIntegrationTest, GetAllIdsWithZeroLimitReturnsEmpty) { + auto kmip = createKmipClient(); + try { + auto all_ids = kmip->client().op_all(KMIP_OBJTYPE_SYMMETRIC_KEY, 0); + EXPECT_TRUE(all_ids.empty()); + } catch (kmipcore::KmipException &e) { + FAIL() << "GetAllIdsWithZeroLimitReturnsEmpty failed: " << e.what(); + } +} + +// Test: Create symmetric AES key +TEST_F(KmipClientIntegrationTest, CreateSymmetricAESKey) { + auto kmip = createKmipClient(); + + try { + auto key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateSymmetricAESKey", TEST_GROUP + ); + trackKeyForCleanup(key_id); + std::cout << "Created key with ID: " << key_id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } +} + +// Test: Create and Get key +TEST_F(KmipClientIntegrationTest, CreateAndGetKey) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + // Create key + try { + key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateAndGetKey", TEST_GROUP + ); + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } + + // Get key + try { + auto key = kmip->client().op_get_key(key_id); + EXPECT_FALSE(key.value().empty()); + EXPECT_EQ(key.value().size(), 32); // 256 bits = 32 bytes + std::cout << "Retrieved key with " << key.value().size() << " bytes" + << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to get key: " << e.what(); + } +} + +// Test: Create, Activate, and Get key +TEST_F(KmipClientIntegrationTest, CreateActivateAndGetKey) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + // Create key + try { + key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateActivateAndGetKey", TEST_GROUP + ); + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } + // Activate key + try { + auto active_id = kmip->client().op_activate(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to activate key: " << e.what(); + } + + // Get key and it's state + try { + auto get_result = kmip->client().op_get_key(key_id); + ASSERT_FALSE(get_result.value().empty()) + << "Failed to get activated key: " << key_id; + auto attrs = + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); + auto state = attrs[KMIP_ATTR_NAME_STATE]; + EXPECT_TRUE(state == "KMIP_STATE_ACTIVE") + << "State is not ACTIVE for key: " << key_id; + std::cout << "Successfully activated and retrieved key: " << key_id + << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to activate key: " << e.what(); + } +} + +// Test: Register symmetric key +TEST_F(KmipClientIntegrationTest, RegisterSymmetricKey) { + auto kmip = createKmipClient(); + + // Create a test key value + std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + try { + auto key_id = kmip->client().op_register_key( + TESTING_NAME_PREFIX + "RegisterSymmetricKey", + TEST_GROUP, + Key::aes_from_value(key_value) + ); + EXPECT_FALSE(key_id.empty()); + std::cout << "Registered key with ID: " << key_id << std::endl; + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to register key: " << e.what(); + } +} + +// Test: Register secret data +TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { + auto kmip = createKmipClient(); + kmipclient::id_t secret_id; + secret_t secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + try { + secret_id = kmip->client().op_register_secret( + TESTING_NAME_PREFIX + "a_secret", TEST_GROUP, secret_data, PASSWORD + ); + EXPECT_FALSE(secret_id.empty()); + std::cout << "Registered secret with ID: " << secret_id << std::endl; + trackKeyForCleanup(secret_id); + } catch (kmipcore::KmipException &e) { + std::cout << "Registered secret failed: " << e.what() << std::endl; + } + + try { + auto retrieved_secret = kmip->client().op_get_secret(secret_id, true); + EXPECT_EQ(retrieved_secret.value.size(), secret_data.size()); + EXPECT_EQ(retrieved_secret.value, secret_data); + EXPECT_FALSE(retrieved_secret.attributes().empty()); + EXPECT_TRUE( + retrieved_secret.attributes().count(KMIP_ATTR_NAME_NAME) > 0 || + retrieved_secret.attributes().count("Name") > 0 + ); + } catch (kmipcore::KmipException &e) { + std::cout << "Get secret failed: " << e.what() << std::endl; + } +} + +// Test: Locate keys +TEST_F(KmipClientIntegrationTest, LocateKeys) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + kmipclient::ids_t result; + kmipclient::name_t name = TESTING_NAME_PREFIX + "LocateKeys"; + // Create key + try { + key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } + // Find by name + try { + auto fkey_ids = + kmip->client().op_locate_by_name(name, KMIP_OBJTYPE_SYMMETRIC_KEY); + ASSERT_TRUE(fkey_ids.size() > 1); + EXPECT_EQ(fkey_ids[0], key_id); + std::cout << "Found " << fkey_ids.size() << " keys" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to find a key: " << e.what(); + } +} + +// Test: Get attributes +TEST_F(KmipClientIntegrationTest, CreateAndGetAttributes) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + kmipclient::name_t name = TESTING_NAME_PREFIX + "CreateAndGetAttributes"; + // Create key + try { + key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } + + // Get attributes + try { + auto attr_result = + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); + attr_result.merge( + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_GROUP}) + ); + auto attr_name = attr_result[KMIP_ATTR_NAME_NAME]; + auto attr_group = attr_result[KMIP_ATTR_NAME_GROUP]; + std::cout << "Successfully retrieved attributes for key: " << key_id + << std::endl; + EXPECT_EQ(name, attr_name); + EXPECT_EQ(TEST_GROUP, attr_group); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to get a key attribute: " << e.what(); + } +} + +// Test: Revoke key +TEST_F(KmipClientIntegrationTest, CreateAndRevokeKey) { + auto kmip = createKmipClient(); + + // Create and activate key + kmipclient::id_t key_id; + kmipclient::name_t name = TESTING_NAME_PREFIX + "CreateAndRevokeKey"; + // Create key + try { + key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackKeyForCleanup(key_id); + auto activate_result = kmip->client().op_activate(key_id); + EXPECT_EQ(activate_result, key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key: " << e.what(); + } + + // Revoke key + try { + auto revoke_result = + kmip->client().op_revoke(key_id, UNSPECIFIED, "Test revocation", 0); + std::cout << "Successfully revoked key: " << key_id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to revoke key: " << e.what(); + } +} + +// Test: Full lifecycle - Create, Activate, Get, Revoke, Destroy +TEST_F(KmipClientIntegrationTest, FullKeyLifecycle) { + auto kmip = createKmipClient(); + try { + // Create + auto key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "FullKeyLifecycle", TEST_GROUP + ); + std::cout << "1. Created key: " << key_id << std::endl; + + // Activate + auto activate_result = kmip->client().op_activate(key_id); + ASSERT_FALSE(activate_result.empty()) << "Activate failed: "; + std::cout << "2. Activated key" << std::endl; + + // Get + auto get_result = kmip->client().op_get_key(key_id); + ASSERT_FALSE(get_result.value().empty()) << "Get failed: "; + std::cout << "3. Retrieved key" << std::endl; + + // Revoke + auto revoke_result = + kmip->client().op_revoke(key_id, UNSPECIFIED, "Test lifecycle", 0); + ASSERT_FALSE(revoke_result.empty()) << "Revoke failed"; + std::cout << "4. Revoked key" << std::endl; + + // Destroy + auto destroy_result = kmip->client().op_destroy(key_id); + ASSERT_TRUE(destroy_result == key_id) << "Destroy failed"; + std::cout << "5. Destroyed key" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed full life cycle of key: " << e.what(); + } + // Don't track for cleanup since we already destroyed it +} + +// Test: Get non-existent key should fail +TEST_F(KmipClientIntegrationTest, GetNonExistentKey) { + auto kmip = createKmipClient(); + Key key; + std::string fake_id = "non-existent-key-id-12345"; + try { + key = kmip->client().op_get_key(fake_id); + } catch (kmipcore::KmipException &) {} + + ASSERT_TRUE(key.value().empty()) << "Should fail to get non-existent key"; + std::cout << "Successfully verified non-existent key cannot be retrieved" + << std::endl; +} + +TEST_F(KmipClientIntegrationTest, GetNonExistentSecret) { + auto kmip = createKmipClient(); + const std::string fake_id = "non-existent-secret-id-12345"; + + try { + auto secret = kmip->client().op_get_secret(fake_id); + (void) secret; + FAIL() << "Should fail to get non-existent secret"; + } catch (const kmipcore::KmipException &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("Operation: Get"), std::string::npos) + << "Expected Get operation failure path, got: " << msg; + std::cout << "Successfully verified non-existent secret cannot be " + "retrieved" + << std::endl; + } +} + +// Test: Multiple keys creation +TEST_F(KmipClientIntegrationTest, CreateMultipleKeys) { + auto kmip = createKmipClient(); + + constexpr int num_keys = 3; + std::vector key_ids; + try { + for (int i = 0; i < num_keys; ++i) { + auto result = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "_CreateMultipleKeys_" + std::to_string(i), + TEST_GROUP + ); + ASSERT_FALSE(result.empty()) << "Failed to create key " << i; + + key_ids.push_back(result); + trackKeyForCleanup(result); + } + + EXPECT_EQ(key_ids.size(), num_keys); + } catch (kmipcore::KmipException &e) { + FAIL() << "Multiple keys creation failed" << e.what(); + } + // Verify all keys are different + for (size_t i = 0; i < key_ids.size(); ++i) { + for (size_t j = i + 1; j < key_ids.size(); ++j) { + EXPECT_NE(key_ids[i], key_ids[j]) << "Keys should have unique IDs"; + } + } + + std::cout << "Successfully created " << num_keys << " unique keys" + << std::endl; +} + +// Test: Destroying a key removes it (cannot be retrieved) +TEST_F(KmipClientIntegrationTest, DestroyKeyRemovesKey) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + try { + key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "DestroyKeyRemovesKey", TEST_GROUP + ); + ASSERT_FALSE(key_id.empty()); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key for destroy test: " << e.what(); + } + + // Destroy the key + try { + auto destroy_result = kmip->client().op_destroy(key_id); + ASSERT_EQ(destroy_result, key_id) + << "Destroy did not return the expected id"; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to destroy key: " << e.what(); + } + + // Attempt to get the destroyed key - should not be retrievable + try { + Key key = kmip->client().op_get_key(key_id); + EXPECT_TRUE(key.value().empty()) + << "Destroyed key should not be retrievable"; + } catch (kmipcore::KmipException &) { + // Some servers respond with an error for non-existent objects; this is + // acceptable + SUCCEED(); + } + + std::cout << "Successfully verified destroyed key is not retrievable" + << std::endl; +} + +// Test: Creating two keys with the same name should yield distinct IDs and both +// should be locatable +TEST_F(KmipClientIntegrationTest, CreateDuplicateNames) { + auto kmip = createKmipClient(); + kmipclient::name_t name = TESTING_NAME_PREFIX + "DuplicateNameTest"; + std::string id1, id2; + try { + id1 = kmip->client().op_create_aes_key(name, TEST_GROUP); + id2 = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackKeyForCleanup(id1); + trackKeyForCleanup(id2); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create duplicate-name keys: " << e.what(); + } + + ASSERT_FALSE(id1.empty()); + ASSERT_FALSE(id2.empty()); + EXPECT_NE(id1, id2) << "Duplicate name keys should have unique IDs"; + + try { + auto found = + kmip->client().op_locate_by_name(name, KMIP_OBJTYPE_SYMMETRIC_KEY); + // Both created IDs should be present + auto it1 = std::find(found.begin(), found.end(), id1); + auto it2 = std::find(found.begin(), found.end(), id2); + EXPECT_NE(it1, found.end()) << "First key not found by name"; + EXPECT_NE(it2, found.end()) << "Second key not found by name"; + std::cout << "Successfully verified duplicate names yield unique IDs" + << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Locate by name failed for duplicate names: " << e.what(); + } +} + +// Test: Revoke changes state to REVOKED +TEST_F(KmipClientIntegrationTest, RevokeChangesState) { + auto kmip = createKmipClient(); + kmipclient::id_t key_id; + try { + key_id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "RevokeChangesState", TEST_GROUP + ); + trackKeyForCleanup(key_id); + auto activate_res = kmip->client().op_activate(key_id); + EXPECT_EQ(activate_res, key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create/activate key for revoke test: " << e.what(); + } + + try { + auto revoke_res = + kmip->client().op_revoke(key_id, UNSPECIFIED, "Test revoke state", 0); + EXPECT_FALSE(revoke_res.empty()); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to revoke key: " << e.what(); + } + + try { + auto attrs = + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); + auto state = attrs[KMIP_ATTR_NAME_STATE]; + EXPECT_TRUE(state == "KMIP_STATE_DEACTIVATED") + << "Expected DEACTIVATED state, got: " << state; + std::cout << "Successfully verified key state changed to DEACTIVATED" + << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to get attributes after revoke: " << e.what(); + } +} + +// Test: op_get_all_ids should include newly created keys of the requested +// object type +TEST_F(KmipClientIntegrationTest, GetAllIdsIncludesCreatedKeys) { + auto kmip = createKmipClient(); + std::vector created_ids; + try { + for (int i = 0; i < 5; ++i) { + auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "GetAllIds_" + std::to_string(i), TEST_GROUP + ); + created_ids.push_back(id); + trackKeyForCleanup(id); + } + + auto all_ids = kmip->client().op_all(KMIP_OBJTYPE_SYMMETRIC_KEY); + for (const auto &cid : created_ids) { + auto it = std::find(all_ids.begin(), all_ids.end(), cid); + EXPECT_NE(it, all_ids.end()) + << "Created id " << cid << " not found in op_get_all_ids"; + } + std::cout << "Successfully verified " << created_ids.size() + << " created keys are in op_all results" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "GetAllIdsIncludesCreatedKeys failed: " << e.what(); + } +} + +// Test: Register a symmetric key and verify its NAME attribute +TEST_F(KmipClientIntegrationTest, RegisterKeyAndGetAttributes) { + auto kmip = createKmipClient(); + std::string name = TESTING_NAME_PREFIX + "RegisterKeyAttrs"; + try { + // Use a deterministic 256-bit (32 byte) key value for registration + std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + + auto key_id = kmip->client().op_register_key( + name, TEST_GROUP, Key::aes_from_value(key_value) + ); + EXPECT_FALSE(key_id.empty()); + trackKeyForCleanup(key_id); + + auto attrs = + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); + auto attr_name = attrs[KMIP_ATTR_NAME_NAME]; + EXPECT_EQ(attr_name, name); + std::cout << "Successfully verified registered key attributes match" + << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "RegisterKeyAndGetAttributes failed: " << e.what(); + } +} + +// Main function +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + // Disable test shuffling + ::testing::GTEST_FLAG(shuffle) = false; + + // Print configuration + auto &config = KmipTestConfig::getInstance(); + if (config.isConfigured()) { + std::cout << "KMIP Test Configuration:\n" + << " Server: " << config.kmip_addr << ":" << config.kmip_port + << "\n" + << " Client CA: " << config.kmip_client_ca << "\n" + << " Client Key: " << config.kmip_client_key << "\n" + << " Server CA: " << config.kmip_server_ca << "\n" + << " Timeout: " << config.timeout_ms << "ms\n" + << std::endl; + } + return RUN_ALL_TESTS(); +} diff --git a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp new file mode 100644 index 0000000..82726cf --- /dev/null +++ b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp @@ -0,0 +1,488 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * KmipClientPoolIntegrationTest.cpp + * + * Integration tests for KmipClientPool, verifying thread-safe concurrent + * operations and proper connection pooling behavior. + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/KmipClientPool.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_GROUP "pool_tests" + +using namespace kmipclient; +using namespace std::chrono_literals; + +static std::string POOL_TEST_NAME_PREFIX = "pool_test_"; + +// Helper class to manage environment variables +class KmipPoolTestConfig { +public: + static KmipPoolTestConfig &getInstance() { + static KmipPoolTestConfig instance; + return instance; + } + + [[nodiscard]] bool isConfigured() const { + return !kmip_addr.empty() && !kmip_port.empty() && + !kmip_client_ca.empty() && !kmip_client_key.empty() && + !kmip_server_ca.empty(); + } + + std::string kmip_addr; + std::string kmip_port; + std::string kmip_client_ca; + std::string kmip_client_key; + std::string kmip_server_ca; + int timeout_ms; + +private: + KmipPoolTestConfig() { + const char *addr = std::getenv("KMIP_ADDR"); + const char *port = std::getenv("KMIP_PORT"); + const char *client_ca = std::getenv("KMIP_CLIENT_CA"); + const char *client_key = std::getenv("KMIP_CLIENT_KEY"); + const char *server_ca = std::getenv("KMIP_SERVER_CA"); + const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); + + if (addr) { + kmip_addr = addr; + } + if (port) { + kmip_port = port; + } + if (client_ca) { + kmip_client_ca = client_ca; + } + if (client_key) { + kmip_client_key = client_key; + } + if (server_ca) { + kmip_server_ca = server_ca; + } + + timeout_ms = timeout ? std::atoi(timeout) : 5000; + + if (!isConfigured()) { + std::cerr << "WARNING: KMIP environment variables not set. Pool tests " + "will be skipped.\n" + << "Required variables:\n" + << " KMIP_ADDR\n" + << " KMIP_PORT\n" + << " KMIP_CLIENT_CA\n" + << " KMIP_CLIENT_KEY\n" + << " KMIP_SERVER_CA\n"; + } + } +}; + +// Base test fixture for KMIP connection pool integration tests +class KmipClientPoolIntegrationTest : public ::testing::Test { +protected: + std::vector created_key_ids; + std::mutex cleanup_mutex; + + void SetUp() override { + auto &config = KmipPoolTestConfig::getInstance(); + + if (!config.isConfigured()) { + GTEST_SKIP() << "KMIP environment variables not configured"; + } + } + + void TearDown() override { + const auto *test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + if (HasFailure()) { + std::cout << test_info->name() << ": FAIL" << std::endl; + } else { + std::cout << test_info->name() << ": OK" << std::endl; + } + + // Cleanup created keys + auto &config = KmipPoolTestConfig::getInstance(); + if (config.isConfigured() && !created_key_ids.empty()) { + try { + Kmip kmip( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms + ); + + for (const auto &key_id : created_key_ids) { + try { + [[maybe_unused]] auto revoke_result = kmip.client().op_revoke( + key_id, KEY_COMPROMISE, "Pool test cleanup", 0 + ); + [[maybe_unused]] auto destroy_result = kmip.client().op_destroy(key_id); + } catch (kmipcore::KmipException &e) { + std::cerr << "Failed to destroy key: " << e.what() << std::endl; + } + } + } catch (...) { + // Ignore cleanup errors + } + } + } + + KmipClientPool::Config createPoolConfig(size_t max_connections = 4) { + auto &config = KmipPoolTestConfig::getInstance(); + return KmipClientPool::Config{ + .host = config.kmip_addr, + .port = config.kmip_port, + .client_cert = config.kmip_client_ca, + .client_key = config.kmip_client_key, + .server_ca_cert = config.kmip_server_ca, + .timeout_ms = config.timeout_ms, + .max_connections = max_connections, + }; + } + + void trackKeyForCleanup(const std::string &key_id) { + std::lock_guard lk(cleanup_mutex); + created_key_ids.push_back(key_id); + } +}; + +// ============================================================================ +// Basic Pool Functionality Tests +// ============================================================================ + +TEST_F(KmipClientPoolIntegrationTest, PoolConstruction) { + auto pool = KmipClientPool(createPoolConfig(4)); + + EXPECT_EQ(pool.available_count(), 0) + << "Pool should have no available connections at construction"; + EXPECT_EQ(pool.total_count(), 0) + << "Pool should have no total connections at construction"; + EXPECT_EQ(pool.max_connections(), 4); +} + +TEST_F(KmipClientPoolIntegrationTest, BorrowAndReturn) { + auto pool = KmipClientPool(createPoolConfig(2)); + + // Borrow a connection + { + auto conn = pool.borrow(); + EXPECT_EQ(pool.available_count(), 0) + << "Borrowed connection should not be available"; + EXPECT_EQ(pool.total_count(), 1); + } + + // Connection returned to pool + EXPECT_EQ(pool.available_count(), 1) + << "Returned connection should be available"; + EXPECT_EQ(pool.total_count(), 1); +} + +TEST_F(KmipClientPoolIntegrationTest, MultipleConnections) { + auto pool = KmipClientPool(createPoolConfig(3)); + + std::vector connections; + + // Borrow multiple connections + for (int i = 0; i < 3; ++i) { + connections.push_back(pool.borrow()); + EXPECT_EQ(pool.available_count(), 0) + << "No connections should be available when all borrowed"; + EXPECT_EQ(pool.total_count(), i + 1); + } + + // All connections back to pool + connections.clear(); + EXPECT_EQ(pool.available_count(), 3); + EXPECT_EQ(pool.total_count(), 3); +} + +// ============================================================================ +// Single-Threaded KMIP Operations via Pool +// ============================================================================ + +TEST_F(KmipClientPoolIntegrationTest, PoolCreateAesKey) { + auto pool = KmipClientPool(createPoolConfig(2)); + + try { + auto conn = pool.borrow(); + auto key_id = + conn->op_create_aes_key(POOL_TEST_NAME_PREFIX + "CreateAesKey", + TEST_GROUP); + EXPECT_FALSE(key_id.empty()); + trackKeyForCleanup(key_id); + std::cout << "Created key via pool: " << key_id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to create key via pool: " << e.what(); + } +} + +TEST_F(KmipClientPoolIntegrationTest, PoolCreateAndGet) { + auto pool = KmipClientPool(createPoolConfig(2)); + + try { + std::string key_id; + + // Create via pool + { + auto conn = pool.borrow(); + key_id = conn->op_create_aes_key(POOL_TEST_NAME_PREFIX + "CreateAndGet", + TEST_GROUP); + trackKeyForCleanup(key_id); + } + + // Get via pool (reusing connection) + { + auto conn = pool.borrow(); + auto key = conn->op_get_key(key_id); + EXPECT_EQ(key.value().size(), 32); // 256-bit AES + std::cout << "Retrieved key via pool: " << key_id << std::endl; + } + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed pool create-and-get: " << e.what(); + } +} + +// ============================================================================ +// Concurrent Operations Tests +// ============================================================================ + +TEST_F(KmipClientPoolIntegrationTest, ConcurrentKeyCreation) { + auto pool = KmipClientPool(createPoolConfig(4)); + + const int num_threads = 8; + std::vector threads; + + for (int i = 0; i < num_threads; ++i) { + threads.emplace_back([this, &pool, i]() { + try { + auto conn = pool.borrow(); + auto key_id = conn->op_create_aes_key( + std::format("{}concurrent_{}", POOL_TEST_NAME_PREFIX, i), + TEST_GROUP + ); + trackKeyForCleanup(key_id); + std::cout << "Thread " << i << " created key: " << key_id << std::endl; + } catch (const std::exception &e) { + FAIL() << "Thread " << i << " failed: " << e.what(); + } + }); + } + + for (auto &t : threads) { + t.join(); + } + + EXPECT_EQ(created_key_ids.size(), num_threads) + << "All threads should have created a key"; + std::cout << "Successfully created " << created_key_ids.size() + << " keys concurrently" << std::endl; +} + +TEST_F(KmipClientPoolIntegrationTest, PoolExhaustion) { + auto pool = KmipClientPool(createPoolConfig(2)); + + std::vector borrowed; + + // Borrow all available connections + for (int i = 0; i < 2; ++i) { + borrowed.push_back(pool.borrow()); + } + + EXPECT_EQ(pool.available_count(), 0); + EXPECT_EQ(pool.total_count(), 2); + + // Third borrow will block until timeout + auto start = std::chrono::high_resolution_clock::now(); + try { + [[maybe_unused]] auto conn = pool.borrow(500ms); + FAIL() << "Should have thrown exception on timeout"; + } catch (kmipcore::KmipException &e) { + auto elapsed = std::chrono::high_resolution_clock::now() - start; + EXPECT_GE(elapsed, 500ms) + << "Timeout should have waited approximately 500ms"; + std::cout << "Pool exhaustion test: timeout waited " + << std::chrono::duration_cast(elapsed) + .count() + << "ms" << std::endl; + } + + borrowed.clear(); +} + +TEST_F(KmipClientPoolIntegrationTest, TryBorrowNonBlocking) { + auto pool = KmipClientPool(createPoolConfig(1)); + + // Borrow the only connection + { + auto conn1 = pool.borrow(); + EXPECT_EQ(pool.available_count(), 0); + + // try_borrow should return nullopt immediately + auto result = pool.try_borrow(); + EXPECT_FALSE(result.has_value()) + << "try_borrow should return nullopt when pool exhausted"; + } + + // Connection returned, now try_borrow should succeed + auto result = pool.try_borrow(); + EXPECT_TRUE(result.has_value()) + << "try_borrow should succeed when connection available"; +} + +TEST_F(KmipClientPoolIntegrationTest, ConnectionReuse) { + auto pool = KmipClientPool(createPoolConfig(1)); + + std::string conn_id_1, conn_id_2; + + // Borrow, note its identity, return + { + auto conn = pool.borrow(); + // The NetClientOpenSSL address acts as a "connection ID" + conn_id_1 = std::format("{:p}", static_cast(conn.operator->())); + } + + // Borrow again - should get the same connection + { + auto conn = pool.borrow(); + conn_id_2 = std::format("{:p}", static_cast(conn.operator->())); + } + + EXPECT_EQ(conn_id_1, conn_id_2) + << "Same KmipClient should be reused from the pool"; + std::cout << "Connection reuse verified: " << conn_id_1 << std::endl; +} + +TEST_F(KmipClientPoolIntegrationTest, UnhealthyConnectionDiscard) { + auto pool = KmipClientPool(createPoolConfig(1)); + + { + auto conn = pool.borrow(); + // Simulate a network error by marking unhealthy + conn.markUnhealthy(); + EXPECT_FALSE(conn.isHealthy()); + } + + // Connection should be discarded, pool should be able to create a new one + EXPECT_EQ(pool.available_count(), 0) + << "Unhealthy connection should not be returned to pool"; + EXPECT_EQ(pool.total_count(), 0) + << "Unhealthy connection should decrement total count"; + + // New borrow should create a fresh connection + { + auto conn = pool.borrow(); + EXPECT_EQ(pool.total_count(), 1); + } +} + +// ============================================================================ +// Stress and Realistic Load Tests +// ============================================================================ + +TEST_F(KmipClientPoolIntegrationTest, ConcurrentOperationsWithReuse) { + auto pool = KmipClientPool(createPoolConfig(4)); + + const int num_threads = 8; + const int ops_per_thread = 5; + + std::vector threads; + + for (int t = 0; t < num_threads; ++t) { + threads.emplace_back([this, &pool, t, ops_per_thread]() { + try { + for (int op = 0; op < ops_per_thread; ++op) { + auto conn = pool.borrow(); + + // Create a key + auto key_id = conn->op_create_aes_key( + std::format("{}stress_t{}_op{}", POOL_TEST_NAME_PREFIX, t, op), + TEST_GROUP + ); + trackKeyForCleanup(key_id); + + // Get the key back + auto key = conn->op_get_key(key_id); + EXPECT_FALSE(key.value().empty()); + + // Get attributes + auto attrs = + conn->op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); + EXPECT_FALSE(attrs.empty()); + + // Connection is returned here when out of scope + } + } catch (const std::exception &e) { + FAIL() << "Thread " << t << " failed: " << e.what(); + } + }); + } + + for (auto &t : threads) { + t.join(); + } + + EXPECT_EQ(created_key_ids.size(), num_threads * ops_per_thread); + std::cout << "Successfully completed " << (num_threads * ops_per_thread) + << " concurrent operations with " << num_threads << " threads" + << std::endl; +} + +TEST_F(KmipClientPoolIntegrationTest, PoolStatistics) { + auto pool = KmipClientPool(createPoolConfig(3)); + + std::cout << "\nPool statistics:" << std::endl; + std::cout << " Max connections: " << pool.max_connections() << std::endl; + std::cout << " Available: " << pool.available_count() << std::endl; + std::cout << " Total: " << pool.total_count() << std::endl; + + // Borrow one + { + auto conn = pool.borrow(); + std::cout << "\nAfter borrowing one:" << std::endl; + std::cout << " Available: " << pool.available_count() << std::endl; + std::cout << " Total: " << pool.total_count() << std::endl; + EXPECT_EQ(pool.available_count(), 0); + EXPECT_EQ(pool.total_count(), 1); + } + + // After return + std::cout << "\nAfter returning:" << std::endl; + std::cout << " Available: " << pool.available_count() << std::endl; + std::cout << " Total: " << pool.total_count() << std::endl; + EXPECT_EQ(pool.available_count(), 1); + EXPECT_EQ(pool.total_count(), 1); +} + + + + diff --git a/kmipcore/.clang-format b/kmipcore/.clang-format new file mode 100644 index 0000000..92bb8d1 --- /dev/null +++ b/kmipcore/.clang-format @@ -0,0 +1,110 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +Standard: c++17 +# Line length +ColumnLimit: 80 +# Indentation +IndentWidth: 2 +UseTab: Never +ContinuationIndentWidth: 4 +# Spacing +SpacesBeforeTrailingComments: 2 +SpaceAfterCStyleCast: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +# Braces +BreakBeforeBraces: Attach +BraceWrapping: + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: false + SplitEmptyRecord: false +# Pointer alignment +PointerAlignment: Right +# Function arguments +BinPackArguments: false +BinPackParameters: false +AlignAfterOpenBracket: BlockIndent +# Alignment +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +# Includes +SortIncludes: CaseSensitive +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^"' + Priority: 1 + - Regex: '^<.*>' + Priority: 2 +# Sorting +SortUsingDeclarations: true +# Other +AccessModifierOffset: -2 +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: BeforeHash +IndentWrappedFunctionNames: true +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +ReflowComments: true +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false diff --git a/kmipcore/CMakeLists.txt b/kmipcore/CMakeLists.txt new file mode 100644 index 0000000..06cb639 --- /dev/null +++ b/kmipcore/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.10.0) + +project(kmipcore LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS "include/kmipcore/*.hpp") + +add_library(kmipcore STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(kmipcore PUBLIC + $ + $ +) + +set_property(TARGET kmipcore PROPERTY POSITION_INDEPENDENT_CODE ON) + +install( + TARGETS kmipcore + EXPORT kmipclient + DESTINATION cmake + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib) + +add_executable(kmip_core_test tests/test_core.cpp) +target_link_libraries(kmip_core_test PRIVATE kmipcore) + +add_executable(kmip_parser_test tests/test_parsers.cpp) +target_link_libraries(kmip_parser_test PRIVATE kmipcore) + +add_executable(kmip_serialization_buffer_test tests/test_serialization_buffer.cpp) +target_link_libraries(kmip_serialization_buffer_test PRIVATE kmipcore) diff --git a/kmipcore/include/kmipcore/attributes_parser.hpp b/kmipcore/include/kmipcore/attributes_parser.hpp new file mode 100644 index 0000000..6d528a9 --- /dev/null +++ b/kmipcore/include/kmipcore/attributes_parser.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/types.hpp" + +#include +#include + +namespace kmipcore { + + /** + * @brief Utilities for decoding KMIP Attribute structures into a string map. + */ + class AttributesParser { + public: + /** @brief Default constructor. */ + AttributesParser() = default; + /** + * @brief Parses KMIP attribute elements into name/value pairs. + * @param attributes Raw KMIP attribute elements. + * @return Parsed attribute map keyed by attribute name. + */ + static attributes_t + parse(const std::vector> &attributes); + }; + +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/key.hpp b/kmipcore/include/kmipcore/key.hpp new file mode 100644 index 0000000..8151294 --- /dev/null +++ b/kmipcore/include/kmipcore/key.hpp @@ -0,0 +1,122 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCORE_KEY_HPP +#define KMIPCORE_KEY_HPP + +#include "kmipcore/types.hpp" + +#include + +namespace kmipcore { + + /** @brief Key object families represented by @ref Key. */ + enum KeyType { UNSET, SYMMETRIC_KEY, PUBLIC_KEY, PRIVATE_KEY, CERTIFICATE }; + + /** + * Minimal crypto key representation as KMIP spec sees it. + * Contains key value, type, algorithm, usage mask, and attributes. + * No factory methods — those belong in higher-level layers. + */ + class Key { + public: + /** + * @brief Constructs a KMIP key object. + * @param value Raw key bytes. + * @param k_type Key family. + * @param algo Cryptographic algorithm. + * @param usage_mask Cryptographic usage mask flags. + * @param attributes Additional key attributes. + */ + explicit Key( + key_t value, + KeyType k_type, + cryptographic_algorithm algo, + cryptographic_usage_mask usage_mask, + attributes_t attributes + ) + : key_value(std::move(value)), + key_type(k_type), + key_attributes(std::move(attributes)), + crypto_algorithm(algo), + crypto_usage_mask(usage_mask) {}; + + /** @brief Constructs an empty key object. */ + Key() = default; + /** @brief Virtual destructor for subclass-safe cleanup. */ + virtual ~Key() = default; + + Key(const Key &) = default; + Key &operator=(const Key &) = default; + Key(Key &&) noexcept = default; + Key &operator=(Key &&) noexcept = default; + + /** @brief Returns raw key bytes. */ + [[nodiscard]] const key_t &value() const noexcept { return key_value; }; + + /** @brief Returns all attached key attributes. */ + [[nodiscard]] const attributes_t &attributes() const noexcept { + return key_attributes; + }; + + /** + * @brief Returns value of a required attribute. + * @param name Attribute name. + * @return Attribute value. + * @throws std::out_of_range if attribute is absent. + */ + [[nodiscard]] const std::string & + attribute_value(const std::string &name) const { + return key_attributes.at(name); + }; + + /** @brief Sets or replaces one attribute value. */ + void set_attribute( + const std::string &name, const std::string &val + ) noexcept { + key_attributes[name] = val; + }; + + /** @brief Returns KMIP usage mask flags. */ + [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { + return crypto_usage_mask; + } + + /** @brief Returns KMIP cryptographic algorithm. */ + [[nodiscard]] cryptographic_algorithm algorithm() const noexcept { + return crypto_algorithm; + }; + + /** @brief Returns key family discriminator. */ + [[nodiscard]] KeyType type() const noexcept { return key_type; }; + + /** @brief Returns key length in bytes. */ + [[nodiscard]] size_t size() const noexcept { return key_value.size(); } + + private: + key_t key_value; + KeyType key_type = UNSET; + attributes_t key_attributes; + cryptographic_algorithm crypto_algorithm = + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; + cryptographic_usage_mask crypto_usage_mask = + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET; + }; + +} // namespace kmipcore + +#endif // KMIPCORE_KEY_HPP diff --git a/kmipcore/include/kmipcore/key_parser.hpp b/kmipcore/include/kmipcore/key_parser.hpp new file mode 100644 index 0000000..b406b6c --- /dev/null +++ b/kmipcore/include/kmipcore/key_parser.hpp @@ -0,0 +1,55 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCORE_KEY_PARSER_HPP +#define KMIPCORE_KEY_PARSER_HPP + +#include "kmipcore/key.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_responses.hpp" +#include "kmipcore/secret.hpp" + +#include + +namespace kmipcore { + + /** + * @brief Decodes KMIP Get payloads into Key or Secret model objects. + */ + class KeyParser { + public: + /** @brief Default constructor. */ + KeyParser() = default; + /** + * @brief Parses typed Get response item into a key object. + * @param item Typed Get response batch item. + */ + static Key parseGetKeyResponse(const GetResponseBatchItem &item); + /** + * @brief Parses typed Get response item into a secret object. + * @param item Typed Get response batch item. + */ + static Secret parseGetSecretResponse(const GetResponseBatchItem &item); + + private: + /** @brief Internal key parser used by typed public entry points. */ + static Key parseResponse(const std::shared_ptr &payload); + }; + +} // namespace kmipcore + +#endif // KMIPCORE_KEY_PARSER_HPP diff --git a/kmipcore/include/kmipcore/kmip_attribute_names.hpp b/kmipcore/include/kmipcore/kmip_attribute_names.hpp new file mode 100644 index 0000000..6988445 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_attribute_names.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +namespace kmipcore { + + // Known KMIP attribute names used across client/core layers. + /** @brief KMIP Name attribute. */ + inline const std::string KMIP_ATTR_NAME_NAME = "Name"; + /** @brief KMIP Object Group attribute. */ + inline const std::string KMIP_ATTR_NAME_GROUP = "Object Group"; + /** @brief KMIP State attribute. */ + inline const std::string KMIP_ATTR_NAME_STATE = "State"; + /** @brief KMIP Unique Identifier attribute. */ + inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = "Unique Identifier"; + /** @brief Backward-compatible alternative Unique Identifier attribute name. */ + inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER_ALT = "UniqueID"; // backward compatibility + /** @brief KMIP Initial Date attribute. */ + inline const std::string KMIP_ATTR_NAME_INITIAL_DATE = "Initial Date"; + /** @brief KMIP Activation Date attribute. */ + inline const std::string KMIP_ATTR_NAME_ACTIVATION_DATE = "Activation Date"; + /** @brief KMIP Process Start Date attribute. */ + inline const std::string KMIP_ATTR_NAME_PROCESS_START_DATE = "Process Start Date"; + /** @brief KMIP Protect Stop Date attribute. */ + inline const std::string KMIP_ATTR_NAME_PROTECT_STOP_DATE = "Protect Stop Date"; + /** @brief KMIP Deactivation Date attribute. */ + inline const std::string KMIP_ATTR_NAME_DEACTIVATION_DATE = "Deactivation Date"; + /** @brief KMIP Destroy Date attribute. */ + inline const std::string KMIP_ATTR_NAME_DESTROY_DATE = "Destroy Date"; + /** @brief KMIP Compromise Occurrence Date attribute. */ + inline const std::string KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE = "Compromise Occurrence Date"; + /** @brief KMIP Compromise Date attribute. */ + inline const std::string KMIP_ATTR_NAME_COMPROMISE_DATE = "Compromise Date"; + /** @brief KMIP Archive Date attribute. */ + inline const std::string KMIP_ATTR_NAME_ARCHIVE_DATE = "Archive Date"; + /** @brief KMIP Last Change Date attribute. */ + inline const std::string KMIP_ATTR_NAME_LAST_CHANGE_DATE = "Last Change Date"; + /** @brief KMIP Cryptographic Algorithm attribute. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_ALG = "Cryptographic Algorithm"; + /** @brief KMIP Cryptographic Length attribute. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_LEN = "Cryptographic Length"; + /** @brief KMIP Cryptographic Usage Mask attribute. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_USAGE_MASK = "Cryptographic Usage Mask"; + /** @brief KMIP Contact Information attribute. */ + inline const std::string KMIP_ATTR_NAME_CONTACT_INFO = "Contact Information"; + /** @brief KMIP Operation Policy Name attribute. */ + inline const std::string KMIP_ATTR_NAME_OPERATION_POLICY_NAME = "Operation Policy Name"; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/kmip_basics.hpp b/kmipcore/include/kmipcore/kmip_basics.hpp new file mode 100644 index 0000000..57dddb1 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_basics.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// Forward declaration for SerializationBuffer +namespace kmipcore { + class SerializationBuffer; +} + +namespace kmipcore { + + /** @brief Alias of KMIP tag enumeration type used by TTLV elements. */ + using Tag = ::tag; + /** @brief Alias of KMIP type enumeration used by TTLV elements. */ + using Type = ::type; + + struct Element; // Forward declaration + + /** @brief TTLV Integer wrapper. */ + struct Integer { + int32_t value; + }; + /** @brief TTLV Long Integer wrapper. */ + struct LongInteger { + int64_t value; + }; + /** @brief TTLV Big Integer wrapper. */ + struct BigInteger { + std::vector value; + }; + /** @brief TTLV Enumeration wrapper. */ + struct Enumeration { + int32_t value; + }; + /** @brief TTLV Boolean wrapper. */ + struct Boolean { + bool value; + }; // usually uint64_t 0/1 in TTLV, but bool is fine in abstract + // representation + /** @brief TTLV Text String wrapper. */ + struct TextString { + std::string value; + }; + /** @brief TTLV Byte String wrapper. */ + struct ByteString { + std::vector value; + }; + /** @brief TTLV Date-Time wrapper (POSIX time). */ + struct DateTime { + int64_t value; + }; // 64-bit signed integer POSIX time + /** + * @brief TTLV Date-Time Extended wrapper (KMIP 2.0, microsecond precision). + * + * Encoded as a signed 64-bit big-endian integer representing microseconds + * since the Unix epoch (midnight, January 1, 1970 UTC). Wire format is + * identical to DateTime; the two are distinguished by type code 0x09 vs 0x0B. + */ + struct DateTimeExtended { + int64_t value; + }; // 64-bit signed integer, microseconds since Unix epoch + /** @brief TTLV Interval wrapper (seconds). */ + struct Interval { + uint32_t value; + }; // 32-bit unsigned integer + + /** + * @brief TTLV Structure wrapper containing nested elements. + */ + struct Structure { + std::vector> items; + + /** @brief Appends a child element to the structure. */ + void add(const std::shared_ptr &element) { items.push_back(element); } + + /** @brief Finds the first child with the specified tag. */ + [[nodiscard]] std::shared_ptr find(Tag tag) const; + /** @brief Finds all children with the specified tag. */ + [[nodiscard]] std::vector> findAll(Tag tag) const; + }; + + /** @brief Variant that represents any supported KMIP TTLV value type. */ + using Value = std::variant< + Structure, + Integer, + LongInteger, + BigInteger, + Enumeration, + Boolean, + TextString, + ByteString, + DateTime, + DateTimeExtended, + Interval>; + + /** + * @brief Generic TTLV node containing tag, type, and typed value. + */ + struct Element { + /** KMIP tag describing semantic meaning of this node. */ + Tag tag = static_cast(KMIP_TAG_DEFAULT); + /** KMIP TTLV type code for @ref value. */ + Type type = static_cast(KMIP_TYPE_STRUCTURE); + /** Typed payload value of this node. */ + Value value = Structure{}; + + /** + * @brief Constructs a TTLV element with explicit fields. + */ + Element(Tag t, Type tp, Value v) : tag(t), type(tp), value(std::move(v)) {} + /** @brief Default-constructs an empty element. */ + Element() + : tag(static_cast(KMIP_TAG_DEFAULT)), + type(static_cast(KMIP_TYPE_STRUCTURE)), + value(Structure{}) {} + + /** @brief Creates a Structure element. */ + static std::shared_ptr createStructure(Tag t); + /** @brief Creates an Integer element. */ + static std::shared_ptr createInteger(Tag t, int32_t v); + /** @brief Creates a Long Integer element. */ + static std::shared_ptr createLongInteger(Tag t, int64_t v); + /** @brief Creates a Big Integer element. */ + static std::shared_ptr + createBigInteger(Tag t, const std::vector &v); + /** @brief Creates an Enumeration element. */ + static std::shared_ptr createEnumeration(Tag t, int32_t v); + /** @brief Creates a Boolean element. */ + static std::shared_ptr createBoolean(Tag t, bool v); + /** @brief Creates a Text String element. */ + static std::shared_ptr + createTextString(Tag t, const std::string &v); + /** @brief Creates a Byte String element. */ + static std::shared_ptr + createByteString(Tag t, const std::vector &v); + /** @brief Creates a Date-Time element (seconds since Unix epoch). */ + static std::shared_ptr createDateTime(Tag t, int64_t v); + /** @brief Creates a Date-Time Extended element (KMIP 2.0, microseconds since Unix epoch). */ + static std::shared_ptr createDateTimeExtended(Tag t, int64_t v); + /** @brief Creates an Interval element. */ + static std::shared_ptr createInterval(Tag t, uint32_t v); + + /** + * @brief Serializes this node into the provided TTLV buffer. + * @param buf Destination serialization buffer. + */ + void serialize(SerializationBuffer& buf) const; + + /** + * @brief Deserializes one element from raw TTLV data. + * @param data Input TTLV byte span. + * @param offset Current read offset; advanced past parsed element. + * @return Parsed element tree rooted at this node. + */ + static std::shared_ptr + deserialize(std::span data, size_t &offset); + + /** @brief Returns mutable structure view when this node is a structure. */ + [[nodiscard]] Structure *asStructure(); + /** @brief Returns const structure view when this node is a structure. */ + [[nodiscard]] const Structure *asStructure() const; + + /** @brief Returns first direct child with the given tag, if present. */ + [[nodiscard]] std::shared_ptr getChild(Tag tag) const; + /** @brief Returns all direct children with the given tag. */ + [[nodiscard]] std::vector> getChildren(Tag tag) const; + + /** @brief Converts value to Integer representation. */ + [[nodiscard]] int32_t toInt() const; + /** @brief Converts value to Long Integer representation. */ + [[nodiscard]] int64_t toLong() const; + /** @brief Converts value to Boolean representation. */ + [[nodiscard]] bool toBool() const; + /** @brief Converts value to Text String representation. */ + [[nodiscard]] std::string toString() const; + /** @brief Converts value to Byte String representation. */ + [[nodiscard]] std::vector toBytes() const; + /** @brief Converts value to Enumeration representation. */ + [[nodiscard]] int32_t toEnum() const; + }; + + /** + * @brief Base exception for KMIP core protocol/encoding failures. + */ + class KmipException : public std::runtime_error { + public: + /** @brief Creates an exception with message only. */ + explicit KmipException(const std::string &msg) : std::runtime_error(msg) {} + /** @brief Creates an exception with numeric status code and message. */ + KmipException(int code, const std::string &msg) + : std::runtime_error(msg), code_(code) {} + /** @brief Returns optional KMIP/library error code (or -1 if unset). */ + [[nodiscard]] int code() const noexcept { return code_; } + + private: + int code_ = -1; + }; + +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp new file mode 100644 index 0000000..4b3138a --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -0,0 +1,793 @@ +/* Copyright (c) 2018 The Johns Hopkins University/Applied Physics Laboratory + * All Rights Reserved. + * + * This file is dual licensed under the terms of the Apache 2.0 License and + * the BSD 3-Clause License. See the LICENSE file in the root of this + * repository for more information. + * + * C++20 version of KMIP enumerations and constants. + */ + +#pragma once + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Maximum encoded KMIP message size handled by default helpers. */ +inline constexpr int32_t KMIP_MAX_MESSAGE_SIZE = 8192; +/** Suggested buffer size for human-readable error messages. */ +inline constexpr int32_t KMIP_ERROR_MESSAGE_SIZE = 200; + +/** Canonical KMIP boolean true value. */ +inline constexpr bool KMIP_TRUE = true; +/** Canonical KMIP boolean false value. */ +inline constexpr bool KMIP_FALSE = false; + +/** Generic sentinel value representing unset enum/int fields. */ +inline constexpr int32_t KMIP_UNSET = -1; + +/** + * @brief Returns the lower of two values. + * @tparam T Comparable numeric/value type. + */ +template constexpr T KMIP_MIN(T a, T b) { + return (a < b) ? a : b; +} + +inline constexpr int32_t KMIP_OK = 0; +inline constexpr int32_t KMIP_NOT_IMPLEMENTED = -1; +inline constexpr int32_t KMIP_ERROR_BUFFER_FULL = -2; +inline constexpr int32_t KMIP_ERROR_ATTR_UNSUPPORTED = -3; +inline constexpr int32_t KMIP_TAG_MISMATCH = -4; +inline constexpr int32_t KMIP_TYPE_MISMATCH = -5; +inline constexpr int32_t KMIP_LENGTH_MISMATCH = -6; +inline constexpr int32_t KMIP_PADDING_MISMATCH = -7; +inline constexpr int32_t KMIP_BOOLEAN_MISMATCH = -8; +inline constexpr int32_t KMIP_ENUM_MISMATCH = -9; +inline constexpr int32_t KMIP_ENUM_UNSUPPORTED = -10; +inline constexpr int32_t KMIP_INVALID_FOR_VERSION = -11; +inline constexpr int32_t KMIP_MEMORY_ALLOC_FAILED = -12; +inline constexpr int32_t KMIP_IO_FAILURE = -13; +inline constexpr int32_t KMIP_EXCEED_MAX_MESSAGE_SIZE = -14; +inline constexpr int32_t KMIP_MALFORMED_RESPONSE = -15; +inline constexpr int32_t KMIP_OBJECT_MISMATCH = -16; +inline constexpr int32_t KMIP_ARG_INVALID = -17; +inline constexpr int32_t KMIP_ERROR_BUFFER_UNDERFULL = -18; +inline constexpr int32_t KMIP_INVALID_ENCODING = -19; +inline constexpr int32_t KMIP_INVALID_FIELD = -20; +inline constexpr int32_t KMIP_INVALID_LENGTH = -21; + +// --------------------------------------------------------------------------- +// Enumerations +// --------------------------------------------------------------------------- + +enum attestation_type : int32_t { + // KMIP 1.2 + KMIP_ATTEST_TPM_QUOTE = 0x01, + KMIP_ATTEST_TCG_INTEGRITY_REPORT = 0x02, + KMIP_ATTEST_SAML_ASSERTION = 0x03 +}; + +enum attribute_type : int32_t { + // KMIP 1.0 + KMIP_ATTR_UNIQUE_IDENTIFIER = 0, + KMIP_ATTR_NAME = 1, + KMIP_ATTR_OBJECT_TYPE = 2, + KMIP_ATTR_CRYPTOGRAPHIC_ALGORITHM = 3, + KMIP_ATTR_CRYPTOGRAPHIC_LENGTH = 4, + KMIP_ATTR_OPERATION_POLICY_NAME = 5, + KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK = 6, + KMIP_ATTR_STATE = 7, + KMIP_ATTR_APPLICATION_SPECIFIC_INFORMATION = 8, + KMIP_ATTR_OBJECT_GROUP = 9, + KMIP_ATTR_ACTIVATION_DATE = 10, + KMIP_ATTR_DEACTIVATION_DATE = 11, + KMIP_ATTR_PROCESS_START_DATE = 12, + KMIP_ATTR_PROTECT_STOP_DATE = 13, + KMIP_ATTR_CRYPTOGRAPHIC_PARAMETERS = 14 +}; + +enum batch_error_continuation_option : int32_t { + // KMIP 1.0 + KMIP_BATCH_CONTINUE = 0x01, + KMIP_BATCH_STOP = 0x02, + KMIP_BATCH_UNDO = 0x03 +}; + +enum block_cipher_mode : int32_t { + // KMIP 1.0 + KMIP_BLOCK_CBC = 0x01, + KMIP_BLOCK_ECB = 0x02, + KMIP_BLOCK_PCBC = 0x03, + KMIP_BLOCK_CFB = 0x04, + KMIP_BLOCK_OFB = 0x05, + KMIP_BLOCK_CTR = 0x06, + KMIP_BLOCK_CMAC = 0x07, + KMIP_BLOCK_CCM = 0x08, + KMIP_BLOCK_GCM = 0x09, + KMIP_BLOCK_CBC_MAC = 0x0A, + KMIP_BLOCK_XTS = 0x0B, + KMIP_BLOCK_AES_KEY_WRAP_PADDING = 0x0C, + KMIP_BLOCK_NIST_KEY_WRAP = 0x0D, + KMIP_BLOCK_X9102_AESKW = 0x0E, + KMIP_BLOCK_X9102_TDKW = 0x0F, + KMIP_BLOCK_X9102_AKW1 = 0x10, + KMIP_BLOCK_X9102_AKW2 = 0x11, + // KMIP 1.4 + KMIP_BLOCK_AEAD = 0x12 +}; + +enum credential_type : int32_t { + // KMIP 1.0 + KMIP_CRED_USERNAME_AND_PASSWORD = 0x01, + // KMIP 1.1 + KMIP_CRED_DEVICE = 0x02, + // KMIP 1.2 + KMIP_CRED_ATTESTATION = 0x03, + // KMIP 2.0 + KMIP_CRED_ONE_TIME_PASSWORD = 0x04, + KMIP_CRED_HASHED_PASSWORD = 0x05, + KMIP_CRED_TICKET = 0x06 +}; + +enum cryptographic_algorithm : int32_t { + KMIP_CRYPTOALG_UNSET = 0x00, + // KMIP 1.0 + KMIP_CRYPTOALG_DES = 0x01, + KMIP_CRYPTOALG_TRIPLE_DES = 0x02, + KMIP_CRYPTOALG_AES = 0x03, + KMIP_CRYPTOALG_RSA = 0x04, + KMIP_CRYPTOALG_DSA = 0x05, + KMIP_CRYPTOALG_ECDSA = 0x06, + KMIP_CRYPTOALG_HMAC_SHA1 = 0x07, + KMIP_CRYPTOALG_HMAC_SHA224 = 0x08, + KMIP_CRYPTOALG_HMAC_SHA256 = 0x09, + KMIP_CRYPTOALG_HMAC_SHA384 = 0x0A, + KMIP_CRYPTOALG_HMAC_SHA512 = 0x0B, + KMIP_CRYPTOALG_HMAC_MD5 = 0x0C, + KMIP_CRYPTOALG_DH = 0x0D, + KMIP_CRYPTOALG_ECDH = 0x0E, + KMIP_CRYPTOALG_ECMQV = 0x0F, + KMIP_CRYPTOALG_BLOWFISH = 0x10, + KMIP_CRYPTOALG_CAMELLIA = 0x11, + KMIP_CRYPTOALG_CAST5 = 0x12, + KMIP_CRYPTOALG_IDEA = 0x13, + KMIP_CRYPTOALG_MARS = 0x14, + KMIP_CRYPTOALG_RC2 = 0x15, + KMIP_CRYPTOALG_RC4 = 0x16, + KMIP_CRYPTOALG_RC5 = 0x17, + KMIP_CRYPTOALG_SKIPJACK = 0x18, + KMIP_CRYPTOALG_TWOFISH = 0x19, + // KMIP 1.2 + KMIP_CRYPTOALG_EC = 0x1A, + // KMIP 1.3 + KMIP_CRYPTOALG_ONE_TIME_PAD = 0x1B, + // KMIP 1.4 + KMIP_CRYPTOALG_CHACHA20 = 0x1C, + KMIP_CRYPTOALG_POLY1305 = 0x1D, + KMIP_CRYPTOALG_CHACHA20_POLY1305 = 0x1E, + KMIP_CRYPTOALG_SHA3_224 = 0x1F, + KMIP_CRYPTOALG_SHA3_256 = 0x20, + KMIP_CRYPTOALG_SHA3_384 = 0x21, + KMIP_CRYPTOALG_SHA3_512 = 0x22, + KMIP_CRYPTOALG_HMAC_SHA3_224 = 0x23, + KMIP_CRYPTOALG_HMAC_SHA3_256 = 0x24, + KMIP_CRYPTOALG_HMAC_SHA3_384 = 0x25, + KMIP_CRYPTOALG_HMAC_SHA3_512 = 0x26, + KMIP_CRYPTOALG_SHAKE_128 = 0x27, + KMIP_CRYPTOALG_SHAKE_256 = 0x28, + // KMIP 2.0 + KMIP_CRYPTOALG_ARIA = 0x29, + KMIP_CRYPTOALG_SEED = 0x2A, + KMIP_CRYPTOALG_SM2 = 0x2B, + KMIP_CRYPTOALG_SM3 = 0x2C, + KMIP_CRYPTOALG_SM4 = 0x2D, + KMIP_CRYPTOALG_GOST_R_34_10_2012 = 0x2E, + KMIP_CRYPTOALG_GOST_R_34_11_2012 = 0x2F, + KMIP_CRYPTOALG_GOST_R_34_13_2015 = 0x30, + KMIP_CRYPTOALG_GOST_28147_89 = 0x31, + KMIP_CRYPTOALG_XMSS = 0x32, + KMIP_CRYPTOALG_SPHINCS_256 = 0x33, + KMIP_CRYPTOALG_MCELIECE = 0x34, + KMIP_CRYPTOALG_MCELIECE_6960119 = 0x35, + KMIP_CRYPTOALG_MCELIECE_8192128 = 0x36, + KMIP_CRYPTOALG_ED25519 = 0x37, + KMIP_CRYPTOALG_ED448 = 0x38 +}; + +enum cryptographic_usage_mask : int32_t { + KMIP_CRYPTOMASK_UNSET = 0x00000000, + // KMIP 1.0 + KMIP_CRYPTOMASK_SIGN = 0x00000001, + KMIP_CRYPTOMASK_VERIFY = 0x00000002, + KMIP_CRYPTOMASK_ENCRYPT = 0x00000004, + KMIP_CRYPTOMASK_DECRYPT = 0x00000008, + KMIP_CRYPTOMASK_WRAP_KEY = 0x00000010, + KMIP_CRYPTOMASK_UNWRAP_KEY = 0x00000020, + KMIP_CRYPTOMASK_EXPORT = 0x00000040, + KMIP_CRYPTOMASK_MAC_GENERATE = 0x00000080, + KMIP_CRYPTOMASK_MAC_VERIFY = 0x00000100, + KMIP_CRYPTOMASK_DERIVE_KEY = 0x00000200, + KMIP_CRYPTOMASK_CONTENT_COMMITMENT = 0x00000400, + KMIP_CRYPTOMASK_KEY_AGREEMENT = 0x00000800, + KMIP_CRYPTOMASK_CERTIFICATE_SIGN = 0x00001000, + KMIP_CRYPTOMASK_CRL_SIGN = 0x00002000, + KMIP_CRYPTOMASK_GENERATE_CRYPTOGRAM = 0x00004000, + KMIP_CRYPTOMASK_VALIDATE_CRYPTOGRAM = 0x00008000, + KMIP_CRYPTOMASK_TRANSLATE_ENCRYPT = 0x00010000, + KMIP_CRYPTOMASK_TRANSLATE_DECRYPT = 0x00020000, + KMIP_CRYPTOMASK_TRANSLATE_WRAP = 0x00040000, + KMIP_CRYPTOMASK_TRANSLATE_UNWRAP = 0x00080000, + // KMIP 2.0 + KMIP_CRYPTOMASK_AUTHENTICATE = 0x00100000, + KMIP_CRYPTOMASK_UNRESTRICTED = 0x00200000, + KMIP_CRYPTOMASK_FPE_ENCRYPT = 0x00400000, + KMIP_CRYPTOMASK_FPE_DECRYPT = 0x00800000 +}; + +enum digital_signature_algorithm : int32_t { + // KMIP 1.1 + KMIP_DIGITAL_MD2_WITH_RSA = 0x01, + KMIP_DIGITAL_MD5_WITH_RSA = 0x02, + KMIP_DIGITAL_SHA1_WITH_RSA = 0x03, + KMIP_DIGITAL_SHA224_WITH_RSA = 0x04, + KMIP_DIGITAL_SHA256_WITH_RSA = 0x05, + KMIP_DIGITAL_SHA384_WITH_RSA = 0x06, + KMIP_DIGITAL_SHA512_WITH_RSA = 0x07, + KMIP_DIGITAL_RSASSA_PSS = 0x08, + KMIP_DIGITAL_DSA_WITH_SHA1 = 0x09, + KMIP_DIGITAL_DSA_WITH_SHA224 = 0x0A, + KMIP_DIGITAL_DSA_WITH_SHA256 = 0x0B, + KMIP_DIGITAL_ECDSA_WITH_SHA1 = 0x0C, + KMIP_DIGITAL_ECDSA_WITH_SHA224 = 0x0D, + KMIP_DIGITAL_ECDSA_WITH_SHA256 = 0x0E, + KMIP_DIGITAL_ECDSA_WITH_SHA384 = 0x0F, + KMIP_DIGITAL_ECDSA_WITH_SHA512 = 0x10, + // KMIP 1.4 + KMIP_DIGITAL_SHA3_256_WITH_RSA = 0x11, + KMIP_DIGITAL_SHA3_384_WITH_RSA = 0x12, + KMIP_DIGITAL_SHA3_512_WITH_RSA = 0x13 +}; + +enum encoding_option : int32_t { + // KMIP 1.1 + KMIP_ENCODE_NO_ENCODING = 0x01, + KMIP_ENCODE_TTLV_ENCODING = 0x02 +}; + +enum hashing_algorithm : int32_t { + // KMIP 1.0 + KMIP_HASH_MD2 = 0x01, + KMIP_HASH_MD4 = 0x02, + KMIP_HASH_MD5 = 0x03, + KMIP_HASH_SHA1 = 0x04, + KMIP_HASH_SHA224 = 0x05, + KMIP_HASH_SHA256 = 0x06, + KMIP_HASH_SHA384 = 0x07, + KMIP_HASH_SHA512 = 0x08, + KMIP_HASH_RIPEMD160 = 0x09, + KMIP_HASH_TIGER = 0x0A, + KMIP_HASH_WHIRLPOOL = 0x0B, + // KMIP 1.2 + KMIP_HASH_SHA512_224 = 0x0C, + KMIP_HASH_SHA512_256 = 0x0D, + // KMIP 1.4 + KMIP_HASH_SHA3_224 = 0x0E, + KMIP_HASH_SHA3_256 = 0x0F, + KMIP_HASH_SHA3_384 = 0x10, + KMIP_HASH_SHA3_512 = 0x11 +}; + +enum key_compression_type : int32_t { + // KMIP 1.0 + KMIP_KEYCOMP_EC_PUB_UNCOMPRESSED = 0x01, + KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_PRIME = 0x02, + KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_CHAR2 = 0x03, + KMIP_KEYCOMP_EC_PUB_X962_HYBRID = 0x04 +}; + +enum key_format_type : int32_t { + // KMIP 1.0 + KMIP_KEYFORMAT_RAW = 0x01, + KMIP_KEYFORMAT_OPAQUE = 0x02, + KMIP_KEYFORMAT_PKCS1 = 0x03, + KMIP_KEYFORMAT_PKCS8 = 0x04, + KMIP_KEYFORMAT_X509 = 0x05, + KMIP_KEYFORMAT_EC_PRIVATE_KEY = 0x06, + KMIP_KEYFORMAT_TRANS_SYMMETRIC_KEY = 0x07, + KMIP_KEYFORMAT_TRANS_DSA_PRIVATE_KEY = 0x08, + KMIP_KEYFORMAT_TRANS_DSA_PUBLIC_KEY = 0x09, + KMIP_KEYFORMAT_TRANS_RSA_PRIVATE_KEY = 0x0A, + KMIP_KEYFORMAT_TRANS_RSA_PUBLIC_KEY = 0x0B, + KMIP_KEYFORMAT_TRANS_DH_PRIVATE_KEY = 0x0C, + KMIP_KEYFORMAT_TRANS_DH_PUBLIC_KEY = 0x0D, + KMIP_KEYFORMAT_TRANS_ECDSA_PRIVATE_KEY = 0x0E, // Deprecated as of KMIP 1.3 + KMIP_KEYFORMAT_TRANS_ECDSA_PUBLIC_KEY = 0x0F, // Deprecated as of KMIP 1.3 + KMIP_KEYFORMAT_TRANS_ECDH_PRIVATE_KEY = 0x10, // Deprecated as of KMIP 1.3 + KMIP_KEYFORMAT_TRANS_ECDH_PUBLIC_KEY = 0x11, // Deprecated as of KMIP 1.3 + KMIP_KEYFORMAT_TRANS_ECMQV_PRIVATE_KEY = 0x12, // Deprecated as of KMIP 1.3 + KMIP_KEYFORMAT_TRANS_ECMQV_PUBLIC_KEY = 0x13, // Deprecated as of KMIP 1.3 + // KMIP 1.3 + KMIP_KEYFORMAT_TRANS_EC_PRIVATE_KEY = 0x14, + KMIP_KEYFORMAT_TRANS_EC_PUBLIC_KEY = 0x15, + // KMIP 1.4 + KMIP_KEYFORMAT_PKCS12 = 0x16, + // KMIP 2.0 + KMIP_KEYFORMAT_PKCS10 = 0x17 +}; + +enum key_role_type : int32_t { + // KMIP 1.0 + KMIP_ROLE_BDK = 0x01, + KMIP_ROLE_CVK = 0x02, + KMIP_ROLE_DEK = 0x03, + KMIP_ROLE_MKAC = 0x04, + KMIP_ROLE_MKSMC = 0x05, + KMIP_ROLE_MKSMI = 0x06, + KMIP_ROLE_MKDAC = 0x07, + KMIP_ROLE_MKDN = 0x08, + KMIP_ROLE_MKCP = 0x09, + KMIP_ROLE_MKOTH = 0x0A, + KMIP_ROLE_KEK = 0x0B, + KMIP_ROLE_MAC16609 = 0x0C, + KMIP_ROLE_MAC97971 = 0x0D, + KMIP_ROLE_MAC97972 = 0x0E, + KMIP_ROLE_MAC97973 = 0x0F, + KMIP_ROLE_MAC97974 = 0x10, + KMIP_ROLE_MAC97975 = 0x11, + KMIP_ROLE_ZPK = 0x12, + KMIP_ROLE_PVKIBM = 0x13, + KMIP_ROLE_PVKPVV = 0x14, + KMIP_ROLE_PVKOTH = 0x15, + // KMIP 1.4 + KMIP_ROLE_DUKPT = 0x16, + KMIP_ROLE_IV = 0x17, + KMIP_ROLE_TRKBK = 0x18 +}; + +enum key_wrap_type : int32_t { + // KMIP 1.4 + KMIP_WRAPTYPE_NOT_WRAPPED = 0x01, + KMIP_WRAPTYPE_AS_REGISTERED = 0x02 +}; + +enum kmip_version : int32_t { + KMIP_1_0 = 0, + KMIP_1_1 = 1, + KMIP_1_2 = 2, + KMIP_1_3 = 3, + KMIP_1_4 = 4, + KMIP_2_0 = 5 +}; + +enum mask_generator : int32_t { + // KMIP 1.4 + KMIP_MASKGEN_MGF1 = 0x01 +}; + +enum name_type : int32_t { + // KMIP 1.0 + KMIP_NAME_UNINTERPRETED_TEXT_STRING = 0x01, + KMIP_NAME_URI = 0x02 +}; + +enum object_type : int32_t { + // KMIP 1.0 + KMIP_OBJTYPE_CERTIFICATE = 0x01, + KMIP_OBJTYPE_SYMMETRIC_KEY = 0x02, + KMIP_OBJTYPE_PUBLIC_KEY = 0x03, + KMIP_OBJTYPE_PRIVATE_KEY = 0x04, + KMIP_OBJTYPE_SPLIT_KEY = 0x05, + KMIP_OBJTYPE_TEMPLATE = 0x06, // Deprecated as of KMIP 1.3 + KMIP_OBJTYPE_SECRET_DATA = 0x07, + KMIP_OBJTYPE_OPAQUE_OBJECT = 0x08, + // KMIP 1.2 + KMIP_OBJTYPE_PGP_KEY = 0x09, + // KMIP 2.0 + KMIP_OBJTYPE_CERTIFICATE_REQUEST = 0x0A +}; + +enum operation : int32_t { + // KMIP 1.0 + KMIP_OP_CREATE = 0x01, + KMIP_OP_CREATE_KEY_PAIR = 0x02, + KMIP_OP_REGISTER = 0x03, + KMIP_OP_REKEY = 0x04, + KMIP_OP_DERIVE_KEY = 0x05, + KMIP_OP_CERTIFY = 0x06, + KMIP_OP_RECERTIFY = 0x07, + KMIP_OP_LOCATE = 0x08, + KMIP_OP_CHECK = 0x09, + KMIP_OP_GET = 0x0A, + KMIP_OP_GET_ATTRIBUTES = 0x0B, + KMIP_OP_GET_ATTRIBUTE_LIST = 0x0C, + KMIP_OP_ADD_ATTRIBUTE = 0x0D, + KMIP_OP_MODIFY_ATTRIBUTE = 0x0E, + KMIP_OP_DELETE_ATTRIBUTE = 0x0F, + KMIP_OP_OBTAIN_LEASE = 0x10, + KMIP_OP_GET_USAGE_ALLOCATION = 0x11, + KMIP_OP_ACTIVATE = 0x12, + KMIP_OP_REVOKE = 0x13, + KMIP_OP_DESTROY = 0x14, + KMIP_OP_ARCHIVE = 0x15, + KMIP_OP_RECOVER = 0x16, + KMIP_OP_VALIDATE = 0x17, + KMIP_OP_QUERY = 0x18, + KMIP_OP_CANCEL = 0x19, + KMIP_OP_POLL = 0x1A, + KMIP_OP_NOTIFY = 0x1B, + KMIP_OP_PUT = 0x1C, + // KMIP 1.1 + KMIP_OP_REKEY_KEY_PAIR = 0x1D, + KMIP_OP_DISCOVER_VERSIONS = 0x1E, + // KMIP 1.2 + KMIP_OP_ENCRYPT = 0x1F, + KMIP_OP_DECRYPT = 0x20, + KMIP_OP_SIGN = 0x21, + KMIP_OP_SIGNATURE_VERIFY = 0x22, + KMIP_OP_MAC = 0x23, + KMIP_OP_MAC_VERIFY = 0x24, + KMIP_OP_RNG_RETRIEVE = 0x25, + KMIP_OP_RNG_SEED = 0x26, + KMIP_OP_HASH = 0x27, + KMIP_OP_CREATE_SPLIT_KEY = 0x28, + KMIP_OP_JOIN_SPLIT_KEY = 0x29, + // KMIP 1.4 + KMIP_OP_IMPORT = 0x2A, + KMIP_OP_EXPORT = 0x2B, + // KMIP 2.0 + KMIP_OP_LOG = 0x2C, + KMIP_OP_LOGIN = 0x2D, + KMIP_OP_LOGOUT = 0x2E, + KMIP_OP_DELEGATED_LOGIN = 0x2F, + KMIP_OP_ADJUST_ATTRIBUTE = 0x30, + KMIP_OP_SET_ATTRIBUTE = 0x31, + KMIP_OP_SET_ENDPOINT_ROLE = 0x32, + KMIP_OP_PKCS_11 = 0x33, + KMIP_OP_INTEROP = 0x34, + KMIP_OP_REPROVISION = 0x35 +}; + +enum padding_method : int32_t { + // KMIP 1.0 + KMIP_PAD_NONE = 0x01, + KMIP_PAD_OAEP = 0x02, + KMIP_PAD_PKCS5 = 0x03, + KMIP_PAD_SSL3 = 0x04, + KMIP_PAD_ZEROS = 0x05, + KMIP_PAD_ANSI_X923 = 0x06, + KMIP_PAD_ISO_10126 = 0x07, + KMIP_PAD_PKCS1v15 = 0x08, + KMIP_PAD_X931 = 0x09, + KMIP_PAD_PSS = 0x0A +}; + +enum protection_storage_mask : int32_t { + // KMIP 2.0 + KMIP_PROTECT_SOFTWARE = 0x00000001, + KMIP_PROTECT_HARDWARE = 0x00000002, + KMIP_PROTECT_ON_PROCESSOR = 0x00000004, + KMIP_PROTECT_ON_SYSTEM = 0x00000008, + KMIP_PROTECT_OFF_SYSTEM = 0x00000010, + KMIP_PROTECT_HYPERVISOR = 0x00000020, + KMIP_PROTECT_OPERATING_SYSTEM = 0x00000040, + KMIP_PROTECT_CONTAINER = 0x00000080, + KMIP_PROTECT_ON_PREMISES = 0x00000100, + KMIP_PROTECT_OFF_PREMISES = 0x00000200, + KMIP_PROTECT_SELF_MANAGED = 0x00000400, + KMIP_PROTECT_OUTSOURCED = 0x00000800, + KMIP_PROTECT_VALIDATED = 0x00001000, + KMIP_PROTECT_SAME_JURISDICTION = 0x00002000 +}; + +enum query_function : int32_t { + // KMIP 1.0 + KMIP_QUERY_OPERATIONS = 0x0001, + KMIP_QUERY_OBJECTS = 0x0002, + KMIP_QUERY_SERVER_INFORMATION = 0x0003, + KMIP_QUERY_APPLICATION_NAMESPACES = 0x0004, + // KMIP 1.1 + KMIP_QUERY_EXTENSION_LIST = 0x0005, + KMIP_QUERY_EXTENSION_MAP = 0x0006, + // KMIP 1.2 + KMIP_QUERY_ATTESTATION_TYPES = 0x0007, + // KMIP 1.3 + KMIP_QUERY_RNGS = 0x0008, + KMIP_QUERY_VALIDATIONS = 0x0009, + KMIP_QUERY_PROFILES = 0x000A, + KMIP_QUERY_CAPABILITIES = 0x000B, + KMIP_QUERY_CLIENT_REGISTRATION_METHODS = 0x000C, + // KMIP 2.0 + KMIP_QUERY_DEFAULTS_INFORMATION = 0x000D, + KMIP_QUERY_STORAGE_PROTECTION_MASKS = 0x000E +}; + +enum result_reason : int32_t { + // KMIP 1.0 + KMIP_REASON_GENERAL_FAILURE = 0x0100, + KMIP_REASON_ITEM_NOT_FOUND = 0x0001, + KMIP_REASON_RESPONSE_TOO_LARGE = 0x0002, + KMIP_REASON_AUTHENTICATION_NOT_SUCCESSFUL = 0x0003, + KMIP_REASON_INVALID_MESSAGE = 0x0004, + KMIP_REASON_OPERATION_NOT_SUPPORTED = 0x0005, + KMIP_REASON_MISSING_DATA = 0x0006, + KMIP_REASON_INVALID_FIELD = 0x0007, + KMIP_REASON_FEATURE_NOT_SUPPORTED = 0x0008, + KMIP_REASON_OPERATION_CANCELED_BY_REQUESTER = 0x0009, + KMIP_REASON_CRYPTOGRAPHIC_FAILURE = 0x000A, + KMIP_REASON_ILLEGAL_OPERATION = 0x000B, + KMIP_REASON_PERMISSION_DENIED = 0x000C, + KMIP_REASON_OBJECT_ARCHIVED = 0x000D, + KMIP_REASON_INDEX_OUT_OF_BOUNDS = 0x000E, + KMIP_REASON_APPLICATION_NAMESPACE_NOT_SUPPORTED = 0x000F, + KMIP_REASON_KEY_FORMAT_TYPE_NOT_SUPPORTED = 0x0010, + KMIP_REASON_KEY_COMPRESSION_TYPE_NOT_SUPPORTED = 0x0011, + // KMIP 1.1 + KMIP_REASON_ENCODING_OPTION_FAILURE = 0x0012, + // KMIP 1.2 + KMIP_REASON_KEY_VALUE_NOT_PRESENT = 0x0013, + KMIP_REASON_ATTESTATION_REQUIRED = 0x0014, + KMIP_REASON_ATTESTATION_FAILED = 0x0015, + // KMIP 1.4 + KMIP_REASON_SENSITIVE = 0x0016, + KMIP_REASON_NOT_EXTRACTABLE = 0x0017, + KMIP_REASON_OBJECT_ALREADY_EXISTS = 0x0018, + // KMIP 2.0 + KMIP_REASON_INVALID_TICKET = 0x0019, + KMIP_REASON_USAGE_LIMIT_EXCEEDED = 0x001A, + KMIP_REASON_NUMERIC_RANGE = 0x001B, + KMIP_REASON_INVALID_DATA_TYPE = 0x001C, + KMIP_REASON_READ_ONLY_ATTRIBUTE = 0x001D, + KMIP_REASON_MULTI_VALUED_ATTRIBUTE = 0x001E, + KMIP_REASON_UNSUPPORTED_ATTRIBUTE = 0x001F, + KMIP_REASON_ATTRIBUTE_INSTANCE_NOT_FOUND = 0x0020, + KMIP_REASON_ATTRIBUTE_NOT_FOUND = 0x0021, + KMIP_REASON_ATTRIBUTE_READ_ONLY = 0x0022, + KMIP_REASON_ATTRIBUTE_SINGLE_VALUED = 0x0023, + KMIP_REASON_BAD_CRYPTOGRAPHIC_PARAMETERS = 0x0024, + KMIP_REASON_BAD_PASSWORD = 0x0025, + KMIP_REASON_CODEC_ERROR = 0x0026, + KMIP_REASON_ILLEGAL_OBJECT_TYPE = 0x0028, + KMIP_REASON_INCOMPATIBLE_CRYPTOGRAPHIC_USAGE_MASK = 0x0029, + KMIP_REASON_INTERNAL_SERVER_ERROR = 0x002A, + KMIP_REASON_INVALID_ASYNCHRONOUS_CORRELATION_VALUE = 0x002B, + KMIP_REASON_INVALID_ATTRIBUTE = 0x002C, + KMIP_REASON_INVALID_ATTRIBUTE_VALUE = 0x002D, + KMIP_REASON_INVALID_CORRELATION_VALUE = 0x002E, + KMIP_REASON_INVALID_CSR = 0x002F, + KMIP_REASON_INVALID_OBJECT_TYPE = 0x0030, + KMIP_REASON_KEY_WRAP_TYPE_NOT_SUPPORTED = 0x0032, + KMIP_REASON_MISSING_INITIALIZATION_VECTOR = 0x0034, + KMIP_REASON_NON_UNIQUE_NAME_ATTRIBUTE = 0x0035, + KMIP_REASON_OBJECT_DESTROYED = 0x0036, + KMIP_REASON_OBJECT_NOT_FOUND = 0x0037, + KMIP_REASON_NOT_AUTHORISED = 0x0039, + KMIP_REASON_SERVER_LIMIT_EXCEEDED = 0x003A, + KMIP_REASON_UNKNOWN_ENUMERATION = 0x003B, + KMIP_REASON_UNKNOWN_MESSAGE_EXTENSION = 0x003C, + KMIP_REASON_UNKNOWN_TAG = 0x003D, + KMIP_REASON_UNSUPPORTED_CRYPTOGRAPHIC_PARAMETERS = 0x003E, + KMIP_REASON_UNSUPPORTED_PROTOCOL_VERSION = 0x003F, + KMIP_REASON_WRAPPING_OBJECT_ARCHIVED = 0x0040, + KMIP_REASON_WRAPPING_OBJECT_DESTROYED = 0x0041, + KMIP_REASON_WRAPPING_OBJECT_NOT_FOUND = 0x0042, + KMIP_REASON_WRONG_KEY_LIFECYCLE_STATE = 0x0043, + KMIP_REASON_PROTECTION_STORAGE_UNAVAILABLE = 0x0044, + KMIP_REASON_PKCS11_CODEC_ERROR = 0x0045, + KMIP_REASON_PKCS11_INVALID_FUNCTION = 0x0046, + KMIP_REASON_PKCS11_INVALID_INTERFACE = 0x0047, + KMIP_REASON_PRIVATE_PROTECTION_STORAGE_UNAVAILABLE = 0x0048, + KMIP_REASON_PUBLIC_PROTECTION_STORAGE_UNAVAILABLE = 0x0049 +}; + +enum result_status : int32_t { + // KMIP 1.0 + KMIP_STATUS_SUCCESS = 0x00, + KMIP_STATUS_OPERATION_FAILED = 0x01, + KMIP_STATUS_OPERATION_PENDING = 0x02, + KMIP_STATUS_OPERATION_UNDONE = 0x03 +}; + +enum state : int32_t { + // 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 +}; + +enum tag : int32_t { + KMIP_TAG_TAG = 0x000000, + KMIP_TAG_TYPE = 0x000001, + KMIP_TAG_DEFAULT = 0x420000, + // KMIP 1.0 + KMIP_TAG_ACTIVATION_DATE = 0x420001, + KMIP_TAG_APPLICATION_DATA = 0x420002, + KMIP_TAG_APPLICATION_NAMESPACE = 0x420003, + KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION = 0x420004, + KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE = 0x420006, + KMIP_TAG_ASYNCHRONOUS_INDICATOR = 0x420007, + KMIP_TAG_ATTRIBUTE = 0x420008, + KMIP_TAG_ATTRIBUTE_INDEX = 0x420009, + KMIP_TAG_ATTRIBUTE_NAME = 0x42000A, + KMIP_TAG_ATTRIBUTE_VALUE = 0x42000B, + KMIP_TAG_AUTHENTICATION = 0x42000C, + KMIP_TAG_BATCH_COUNT = 0x42000D, + KMIP_TAG_BATCH_ERROR_CONTINUATION_OPTION = 0x42000E, + KMIP_TAG_BATCH_ITEM = 0x42000F, + KMIP_TAG_BATCH_ORDER_OPTION = 0x420010, + KMIP_TAG_BLOCK_CIPHER_MODE = 0x420011, + KMIP_TAG_COMPROMISE_OCCURRANCE_DATE = 0x420021, + KMIP_TAG_CREDENTIAL = 0x420023, + KMIP_TAG_CREDENTIAL_TYPE = 0x420024, + KMIP_TAG_CREDENTIAL_VALUE = 0x420025, + KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM = 0x420028, + KMIP_TAG_CRYPTOGRAPHIC_LENGTH = 0x42002A, + KMIP_TAG_CRYPTOGRAPHIC_PARAMETERS = 0x42002B, + KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK = 0x42002C, + KMIP_TAG_DEACTIVATION_DATE = 0x42002F, + KMIP_TAG_ENCRYPTION_KEY_INFORMATION = 0x420036, + KMIP_TAG_HASHING_ALGORITHM = 0x420038, + KMIP_TAG_IV_COUNTER_NONCE = 0x42003D, + KMIP_TAG_KEY = 0x42003F, + KMIP_TAG_KEY_BLOCK = 0x420040, + KMIP_TAG_KEY_COMPRESSION_TYPE = 0x420041, + KMIP_TAG_KEY_FORMAT_TYPE = 0x420042, + KMIP_TAG_KEY_MATERIAL = 0x420043, + KMIP_TAG_KEY_VALUE = 0x420045, + KMIP_TAG_KEY_WRAPPING_DATA = 0x420046, + KMIP_TAG_KEY_WRAPPING_SPECIFICATION = 0x420047, + KMIP_TAG_MAC_SIGNATURE = 0x42004D, + KMIP_TAG_MAC_SIGNATURE_KEY_INFORMATION = 0x42004E, + KMIP_TAG_MAXIMUM_ITEMS = 0x42004F, + KMIP_TAG_MAXIMUM_RESPONSE_SIZE = 0x420050, + KMIP_TAG_NAME = 0x420053, + KMIP_TAG_NAME_TYPE = 0x420054, + KMIP_TAG_NAME_VALUE = 0x420055, + KMIP_TAG_OBJECT_GROUP = 0x420056, + KMIP_TAG_OBJECT_TYPE = 0x420057, + KMIP_TAG_OPERATION = 0x42005C, + KMIP_TAG_OPERATION_POLICY_NAME = 0x42005D, + KMIP_TAG_PADDING_METHOD = 0x42005F, + KMIP_TAG_PRIVATE_KEY = 0x420064, + KMIP_TAG_PROCESS_START_DATE = 0x420067, + KMIP_TAG_PROTECT_STOP_DATE = 0x420068, + KMIP_TAG_PROTOCOL_VERSION = 0x420069, + KMIP_TAG_PROTOCOL_VERSION_MAJOR = 0x42006A, + KMIP_TAG_PROTOCOL_VERSION_MINOR = 0x42006B, + KMIP_TAG_PUBLIC_KEY = 0x42006D, + KMIP_TAG_QUERY_FUNCTION = 0x420074, + KMIP_TAG_REQUEST_HEADER = 0x420077, + KMIP_TAG_REQUEST_MESSAGE = 0x420078, + KMIP_TAG_REQUEST_PAYLOAD = 0x420079, + KMIP_TAG_RESPONSE_HEADER = 0x42007A, + KMIP_TAG_RESPONSE_MESSAGE = 0x42007B, + KMIP_TAG_RESPONSE_PAYLOAD = 0x42007C, + KMIP_TAG_RESULT_MESSAGE = 0x42007D, + KMIP_TAG_RESULT_REASON = 0x42007E, + KMIP_TAG_RESULT_STATUS = 0x42007F, + KMIP_TAG_REVOKATION_MESSAGE = 0x420080, + KMIP_TAG_REVOCATION_REASON = 0x420081, + KMIP_TAG_REVOCATION_REASON_CODE = 0x420082, + KMIP_TAG_KEY_ROLE_TYPE = 0x420083, + KMIP_TAG_SALT = 0x420084, + KMIP_TAG_SECRET_DATA = 0x420085, + KMIP_TAG_SECRET_DATA_TYPE = 0x420086, + KMIP_TAG_SERVER_INFORMATION = 0x420088, + KMIP_TAG_STATE = 0x42008D, + KMIP_TAG_STORAGE_STATUS_MASK = 0x42008E, + KMIP_TAG_SYMMETRIC_KEY = 0x42008F, + KMIP_TAG_TEMPLATE_ATTRIBUTE = 0x420091, + KMIP_TAG_TIME_STAMP = 0x420092, + KMIP_TAG_UNIQUE_BATCH_ITEM_ID = 0x420093, + KMIP_TAG_UNIQUE_IDENTIFIER = 0x420094, + KMIP_TAG_USERNAME = 0x420099, + KMIP_TAG_VENDOR_IDENTIFICATION = 0x42009D, + KMIP_TAG_WRAPPING_METHOD = 0x42009E, + KMIP_TAG_PASSWORD = 0x4200A1, + // KMIP 1.1 + KMIP_TAG_DEVICE_IDENTIFIER = 0x4200A2, + KMIP_TAG_ENCODING_OPTION = 0x4200A3, + KMIP_TAG_MACHINE_IDENTIFIER = 0x4200A9, + KMIP_TAG_MEDIA_IDENTIFIER = 0x4200AA, + KMIP_TAG_NETWORK_IDENTIFIER = 0x4200AB, + KMIP_TAG_OBJECT_GROUP_MEMBER = 0x4200AC, + KMIP_TAG_DIGITAL_SIGNATURE_ALGORITHM = 0x4200AE, + KMIP_TAG_DEVICE_SERIAL_NUMBER = 0x4200B0, + // KMIP 1.2 + KMIP_TAG_RANDOM_IV = 0x4200C5, + KMIP_TAG_ATTESTATION_TYPE = 0x4200C7, + KMIP_TAG_NONCE = 0x4200C8, + KMIP_TAG_NONCE_ID = 0x4200C9, + KMIP_TAG_NONCE_VALUE = 0x4200CA, + KMIP_TAG_ATTESTATION_MEASUREMENT = 0x4200CB, + KMIP_TAG_ATTESTATION_ASSERTION = 0x4200CC, + KMIP_TAG_IV_LENGTH = 0x4200CD, + KMIP_TAG_TAG_LENGTH = 0x4200CE, + KMIP_TAG_FIXED_FIELD_LENGTH = 0x4200CF, + KMIP_TAG_COUNTER_LENGTH = 0x4200D0, + KMIP_TAG_INITIAL_COUNTER_VALUE = 0x4200D1, + KMIP_TAG_INVOCATION_FIELD_LENGTH = 0x4200D2, + KMIP_TAG_ATTESTATION_CAPABLE_INDICATOR = 0x4200D3, + KMIP_TAG_OFFSET_ITEMS = 0x4200D4, + KMIP_TAG_LOCATED_ITEMS = 0x4200D5, + // KMIP 1.4 + KMIP_TAG_KEY_WRAP_TYPE = 0x4200F8, + KMIP_TAG_SALT_LENGTH = 0x420100, + KMIP_TAG_MASK_GENERATOR = 0x420101, + KMIP_TAG_MASK_GENERATOR_HASHING_ALGORITHM = 0x420102, + KMIP_TAG_P_SOURCE = 0x420103, + KMIP_TAG_TRAILER_FIELD = 0x420104, + KMIP_TAG_CLIENT_CORRELATION_VALUE = 0x420105, + KMIP_TAG_SERVER_CORRELATION_VALUE = 0x420106, + // KMIP 2.0 + KMIP_TAG_ATTRIBUTES = 0x420125, + KMIP_TAG_SERVER_NAME = 0x42012D, + KMIP_TAG_SERVER_SERIAL_NUMBER = 0x42012E, + KMIP_TAG_SERVER_VERSION = 0x42012F, + KMIP_TAG_SERVER_LOAD = 0x420130, + KMIP_TAG_PRODUCT_NAME = 0x420131, + KMIP_TAG_BUILD_LEVEL = 0x420132, + KMIP_TAG_BUILD_DATE = 0x420133, + KMIP_TAG_CLUSTER_INFO = 0x420134, + KMIP_TAG_ALTERNATE_FAILOVER_ENDPOINTS = 0x420135, + KMIP_TAG_EPHEMERAL = 0x420154, + KMIP_TAG_SERVER_HASHED_PASSWORD = 0x420155, + KMIP_TAG_PROTECTION_STORAGE_MASK = 0x42015E, + KMIP_TAG_PROTECTION_STORAGE_MASKS = 0x42015F, + KMIP_TAG_COMMON_PROTECTION_STORAGE_MASKS = 0x420163, + KMIP_TAG_PRIVATE_PROTECTION_STORAGE_MASKS = 0x420164, + KMIP_TAG_PUBLIC_PROTECTION_STORAGE_MASKS = 0x420165 +}; + +enum type : int32_t { + // KMIP 1.0 + KMIP_TYPE_STRUCTURE = 0x01, + KMIP_TYPE_INTEGER = 0x02, + KMIP_TYPE_LONG_INTEGER = 0x03, + KMIP_TYPE_BIG_INTEGER = 0x04, + KMIP_TYPE_ENUMERATION = 0x05, + KMIP_TYPE_BOOLEAN = 0x06, + KMIP_TYPE_TEXT_STRING = 0x07, + KMIP_TYPE_BYTE_STRING = 0x08, + KMIP_TYPE_DATE_TIME = 0x09, + KMIP_TYPE_INTERVAL = 0x0A, + // KMIP 2.0 + KMIP_TYPE_DATE_TIME_EXTENDED = 0x0B +}; + +enum wrapping_method : int32_t { + // KMIP 1.0 + KMIP_WRAP_ENCRYPT = 0x01, + KMIP_WRAP_MAC_SIGN = 0x02, + KMIP_WRAP_ENCRYPT_MAC_SIGN = 0x03, + KMIP_WRAP_MAC_SIGN_ENCRYPT = 0x04, + KMIP_WRAP_TR31 = 0x05 +}; + +/** @brief KMIP revocation reason codes used by Revoke operations. */ +enum revocation_reason_type : int32_t { + // KMIP 1.0 + UNSPECIFIED = 0x01, + KEY_COMPROMISE = 0x02, + CA_COMPROMISE = 0x03, + AFFILIATION_CHANGED = 0x04, + SUSPENDED = 0x05, + CESSATION_OF_OPERATION = 0x06, + PRIVILEDGE_WITHDRAWN = 0x07, + REVOCATION_EXTENSIONS = static_cast(0x80000000u) +}; + +/** @brief KMIP secret payload data type identifiers. */ +enum secret_data_type : int32_t { + // KMIP 1.0 + PASSWORD = 0x01, + SEED = 0x02, + SECRET_DATA_EXTENSIONS = static_cast(0x80000000u) +}; diff --git a/kmipcore/include/kmipcore/kmip_formatter.hpp b/kmipcore/include/kmipcore/kmip_formatter.hpp new file mode 100644 index 0000000..8770b7e --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_formatter.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace kmipcore { + + class Element; + class RequestMessage; + class ResponseMessage; + + /** @brief Formats an Element tree into a human-readable text dump. */ + [[nodiscard]] std::string format_element(const std::shared_ptr &element); + /** @brief Formats a RequestMessage into a human-readable text dump. */ + [[nodiscard]] std::string format_request(const RequestMessage &request); + /** @brief Formats a ResponseMessage into a human-readable text dump. */ + [[nodiscard]] std::string format_response(const ResponseMessage &response); + /** @brief Parses and formats raw TTLV bytes into human-readable text. */ + [[nodiscard]] std::string format_ttlv(std::span ttlv); + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/kmip_logger.hpp b/kmipcore/include/kmipcore/kmip_logger.hpp new file mode 100644 index 0000000..369f306 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_logger.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +namespace kmipcore { + + /** @brief Log severity levels used by KMIP protocol logging. */ + enum class LogLevel { + Debug, + Info, + Warning, + Error + }; + + /** @brief Structured log record emitted by KMIP components. */ + struct LogRecord { + LogLevel level = LogLevel::Debug; + std::string component; + std::string event; + std::string message; + }; + + /** @brief Converts a log level enum to uppercase text label. */ + [[nodiscard]] inline const char *to_string(LogLevel level) { + switch (level) { + case LogLevel::Debug: + return "DEBUG"; + case LogLevel::Info: + return "INFO"; + case LogLevel::Warning: + return "WARNING"; + case LogLevel::Error: + return "ERROR"; + default: + return "UNKNOWN"; + } + } + + /** @brief Abstract logger sink interface used by kmipcore/kmipclient. */ + class Logger { + public: + /** @brief Virtual destructor for interface-safe cleanup. */ + virtual ~Logger() = default; + + /** @brief Returns whether a record at @p level should be emitted. */ + [[nodiscard]] virtual bool shouldLog(LogLevel level) const = 0; + /** @brief Emits one log record. */ + virtual void log(const LogRecord &record) = 0; + }; + + /** @brief Logger implementation that drops all records. */ + class NullLogger final : public Logger { + public: + /** @brief Always returns false because logging is disabled. */ + [[nodiscard]] bool shouldLog(LogLevel) const override { return false; } + /** @brief No-op sink implementation. */ + void log(const LogRecord &) override {} + }; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/kmip_protocol.hpp b/kmipcore/include/kmipcore/kmip_protocol.hpp new file mode 100644 index 0000000..677be1b --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_protocol.hpp @@ -0,0 +1,423 @@ +#pragma once +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +namespace kmipcore { + + /** @brief Default KMIP protocol minor version used for requests. */ + inline constexpr int32_t DEFAULT_PROTOCOL_VERSION = KMIP_1_4; + + /** @brief KMIP protocol version tuple. */ + class ProtocolVersion { + public: + /** @brief Constructs protocol version 1.DEFAULT_PROTOCOL_VERSION. */ + ProtocolVersion() = default; + /** @brief Constructs protocol version with explicit major/minor values. */ + ProtocolVersion(int32_t major, int32_t minor); + /** @brief Returns major version component. */ + [[nodiscard]] int32_t getMajor() const { return major_; } + /** @brief Sets major version component. */ + void setMajor(int32_t major) { major_ = major; } + /** @brief Returns minor version component. */ + [[nodiscard]] int32_t getMinor() const { return minor_; } + /** @brief Sets minor version component. */ + void setMinor(int32_t minor) { minor_ = minor; } + /** @brief Encodes version to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes version from TTLV element form. */ + static ProtocolVersion fromElement(std::shared_ptr element); + + private: + int32_t major_ = 1; + int32_t minor_ = DEFAULT_PROTOCOL_VERSION; + }; + + /** @brief KMIP request header model. */ + class RequestHeader { + public: + /** @brief Creates a header with default protocol fields. */ + RequestHeader() = default; + /** @brief Returns protocol version. */ + [[nodiscard]] const ProtocolVersion &getProtocolVersion() const { + return protocolVersion_; + } + /** @brief Sets protocol version. */ + void setProtocolVersion(const ProtocolVersion &version) { + protocolVersion_ = version; + } + /** @brief Returns mutable protocol version reference. */ + ProtocolVersion &getProtocolVersion() { return protocolVersion_; } + /** @brief Returns declared request batch count. */ + [[nodiscard]] int32_t getBatchCount() const { return batchCount_; } + /** @brief Sets request batch count. */ + void setBatchCount(int32_t batchCount) { batchCount_ = batchCount; } + /** @brief Returns optional maximum response size limit. */ + [[nodiscard]] std::optional getMaximumResponseSize() const { + return maximumResponseSize_; + } + /** @brief Sets optional maximum response size limit. */ + void setMaximumResponseSize(std::optional maximumResponseSize) { + maximumResponseSize_ = maximumResponseSize; + } + /** @brief Returns optional client timestamp. */ + [[nodiscard]] std::optional getTimeStamp() const { return timeStamp_; } + /** @brief Sets optional client timestamp. */ + void setTimeStamp(std::optional timeStamp) { + timeStamp_ = timeStamp; + } + /** @brief Returns optional batch-order processing flag. */ + [[nodiscard]] std::optional getBatchOrderOption() const { + return batchOrderOption_; + } + /** @brief Sets optional batch-order processing flag. */ + void setBatchOrderOption(std::optional batchOrderOption) { + batchOrderOption_ = batchOrderOption; + } + /** @brief Returns optional authentication username. */ + [[nodiscard]] const std::optional &getUserName() const { + return userName_; + } + /** @brief Sets optional authentication username. */ + void setUserName(const std::optional &userName) { + userName_ = userName; + } + /** @brief Returns optional authentication password. */ + [[nodiscard]] const std::optional &getPassword() const { + return password_; + } + /** @brief Sets optional authentication password. */ + void setPassword(const std::optional &password) { + password_ = password; + } + /** @brief Encodes header to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes header from TTLV element form. */ + static RequestHeader fromElement(std::shared_ptr element); + + private: + ProtocolVersion protocolVersion_; + int32_t batchCount_ = 0; + std::optional maximumResponseSize_; + std::optional timeStamp_; + std::optional batchOrderOption_; + std::optional userName_; + std::optional password_; + }; + + /** @brief One KMIP operation entry within a request batch. */ + class RequestBatchItem { + public: + /** @brief Constructs an empty batch item. */ + RequestBatchItem() = default; + /** @brief Returns unique batch item identifier. */ + [[nodiscard]] uint32_t getUniqueBatchItemId() const { + return uniqueBatchItemId_; + } + /** @brief Sets unique batch item identifier. */ + void setUniqueBatchItemId(uint32_t id) { uniqueBatchItemId_ = id; } + /** @brief Returns KMIP operation code for this item. */ + [[nodiscard]] int32_t getOperation() const { return operation_; } + /** @brief Sets KMIP operation code for this item. */ + void setOperation(int32_t operation) { operation_ = operation; } + /** @brief Returns request payload element. */ + [[nodiscard]] std::shared_ptr getRequestPayload() const { + return requestPayload_; + } + /** @brief Sets request payload element. */ + void setRequestPayload(std::shared_ptr payload) { + requestPayload_ = std::move(payload); + } + /** @brief Encodes batch item to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes batch item from TTLV element form. */ + static RequestBatchItem fromElement(std::shared_ptr element); + + private: + uint32_t uniqueBatchItemId_ = 0; + int32_t operation_ = 0; + std::shared_ptr requestPayload_; + }; + + /** @brief Name/value attribute pair used in locate filters. */ + class Attribute { + public: + /** @brief Constructs an empty attribute. */ + Attribute() = default; + /** @brief Constructs an attribute from name/value pair. */ + Attribute(const std::string &name, const std::string &value); + + /** @brief Returns attribute name. */ + [[nodiscard]] std::string getName() const { return name_; } + /** @brief Sets attribute name. */ + void setName(const std::string &name) { name_ = name; } + + /** @brief Returns attribute value. */ + [[nodiscard]] std::string getValue() const { return value_; } + /** @brief Sets attribute value. */ + void setValue(const std::string &value) { value_ = value; } + + /** @brief Encodes attribute to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes attribute from TTLV element form. */ + static Attribute fromElement(std::shared_ptr element); + + private: + std::string name_; + std::string value_; + }; + + /** @brief Payload model for KMIP Locate request. */ + class LocateRequestPayload { + public: + /** @brief Constructs an empty locate payload. */ + LocateRequestPayload() = default; + + /** @brief Returns requested maximum number of items. */ + [[nodiscard]] int32_t getMaximumItems() const { return maximumItems_; } + /** @brief Sets requested maximum number of items. */ + void setMaximumItems(int32_t val) { maximumItems_ = val; } + + /** @brief Returns locate pagination offset. */ + [[nodiscard]] int32_t getOffsetItems() const { return offsetItems_; } + /** @brief Sets locate pagination offset. */ + void setOffsetItems(int32_t val) { offsetItems_ = val; } + + /** @brief Returns locate filter attributes. */ + [[nodiscard]] const std::vector &getAttributes() const { + return attributes_; + } + /** @brief Appends one locate filter attribute. */ + void addAttribute(const Attribute &attr) { attributes_.push_back(attr); } + + /** @brief Encodes locate payload to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes locate payload from TTLV element form. */ + static LocateRequestPayload fromElement(std::shared_ptr element); + + private: + int32_t maximumItems_ = 0; + int32_t offsetItems_ = 0; + std::vector attributes_; + }; + + /** @brief Payload model for KMIP Locate response. */ + class LocateResponsePayload { + public: + /** @brief Constructs an empty locate response payload. */ + LocateResponsePayload() = default; + + /** @brief Returns optional total number of located items. */ + [[nodiscard]] std::optional getLocatedItems() const { + return locatedItems_; + } + /** @brief Sets optional total number of located items. */ + void setLocatedItems(std::optional val) { locatedItems_ = val; } + + /** @brief Returns located unique identifiers. */ + [[nodiscard]] const std::vector &getUniqueIdentifiers() const { + return uniqueIdentifiers_; + } + /** @brief Appends one located unique identifier. */ + void addUniqueIdentifier(const std::string &id) { + uniqueIdentifiers_.push_back(id); + } + + /** @brief Encodes locate response payload to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes locate response payload from TTLV element form. */ + static LocateResponsePayload fromElement(std::shared_ptr element); + + private: + std::optional locatedItems_; + std::vector uniqueIdentifiers_; + }; + + /** @brief Full KMIP request message including header and batch items. */ + class RequestMessage { + public: + /** Default maximum response-size hint used in request headers. */ + static constexpr size_t DEFAULT_MAX_RESPONSE_SIZE = KMIP_MAX_MESSAGE_SIZE; + + /** @brief Constructs message with default protocol and limits. */ + RequestMessage(); + /** @brief Constructs message using a specific protocol minor version. */ + explicit RequestMessage(int32_t protocolVersionMinor); + /** @brief Constructs message with protocol minor and response size hint. */ + RequestMessage(int32_t protocolVersionMinor, size_t maxResponseSize); + + /** @brief Returns const request header. */ + [[nodiscard]] const RequestHeader &getHeader() const { return header_; } + /** @brief Returns mutable request header. */ + RequestHeader &getHeader() { return header_; } + /** @brief Replaces request header. */ + void setHeader(const RequestHeader &header) { header_ = header; } + /** @brief Returns const batch item list. */ + [[nodiscard]] const std::vector &getBatchItems() const { + return batchItems_; + } + /** @brief Returns mutable batch item list. */ + std::vector &getBatchItems() { return batchItems_; } + /** + * @brief Adds one batch item and assigns a unique batch id. + * @return Assigned batch item id. + */ + uint32_t add_batch_item(RequestBatchItem item); + /** @brief Replaces all batch items and updates header batch count. */ + void setBatchItems(const std::vector &items); + /** @brief Returns number of batch items in the message. */ + [[nodiscard]] size_t getBatchItemCount() const { return batchItems_.size(); } + /** @brief Clears all batch items and resets batch id sequence. */ + void clearBatchItems() { + batchItems_.clear(); + nextBatchItemId_ = 1; + } + + /** @brief Sets protocol minor version in request header. */ + void setProtocolVersionMinor(int32_t minor); + /** @brief Returns protocol minor version from request header. */ + [[nodiscard]] int32_t getProtocolVersionMinor() const; + + /** @brief Sets maximum response size hint in request header. */ + void setMaxResponseSize(size_t size); + /** @brief Returns maximum response size hint from request header. */ + [[nodiscard]] size_t getMaxResponseSize() const; + + /** @brief Serializes complete message to TTLV bytes. */ + [[nodiscard]] std::vector serialize() const; + + /** @brief Encodes request message to TTLV element tree. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes request message from TTLV element tree. */ + static RequestMessage fromElement(std::shared_ptr element); + + private: + RequestHeader header_; + std::vector batchItems_; + uint32_t nextBatchItemId_ = 1; + }; + /** @brief KMIP response header model. */ + class ResponseHeader { + public: + /** @brief Constructs an empty response header. */ + ResponseHeader() = default; + /** @brief Returns protocol version returned by server. */ + [[nodiscard]] const ProtocolVersion &getProtocolVersion() const { + return protocolVersion_; + } + /** @brief Returns mutable protocol version. */ + ProtocolVersion &getProtocolVersion() { return protocolVersion_; } + /** @brief Sets protocol version. */ + void setProtocolVersion(const ProtocolVersion &version) { + protocolVersion_ = version; + } + /** @brief Returns server timestamp. */ + [[nodiscard]] int64_t getTimeStamp() const { return timeStamp_; } + /** @brief Sets server timestamp. */ + void setTimeStamp(int64_t timeStamp) { timeStamp_ = timeStamp; } + /** @brief Returns number of response batch items. */ + [[nodiscard]] int32_t getBatchCount() const { return batchCount_; } + /** @brief Sets number of response batch items. */ + void setBatchCount(int32_t batchCount) { batchCount_ = batchCount; } + /** @brief Encodes header to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes header from TTLV element form. */ + static ResponseHeader fromElement(std::shared_ptr element); + + private: + ProtocolVersion protocolVersion_; + int64_t timeStamp_ = 0; + int32_t batchCount_ = 0; + }; + + /** @brief One KMIP operation entry within a response batch. */ + class ResponseBatchItem { + public: + /** @brief Constructs an empty response batch item. */ + ResponseBatchItem() = default; + /** @brief Returns unique batch item id correlating to request item. */ + [[nodiscard]] uint32_t getUniqueBatchItemId() const { + return uniqueBatchItemId_; + } + /** @brief Sets unique batch item id correlating to request item. */ + void setUniqueBatchItemId(uint32_t id) { uniqueBatchItemId_ = id; } + /** @brief Returns operation code executed by server. */ + [[nodiscard]] int32_t getOperation() const { return operation_; } + /** @brief Sets operation code executed by server. */ + void setOperation(int32_t operation) { operation_ = operation; } + /** @brief Returns operation result status. */ + [[nodiscard]] int32_t getResultStatus() const { return resultStatus_; } + /** @brief Sets operation result status. */ + void setResultStatus(int32_t status) { resultStatus_ = status; } + /** @brief Returns optional result reason enum value. */ + [[nodiscard]] std::optional getResultReason() const { + return resultReason_; + } + /** @brief Sets optional result reason enum value. */ + void setResultReason(std::optional reason) { + resultReason_ = reason; + } + /** @brief Returns optional result message text. */ + [[nodiscard]] const std::optional &getResultMessage() const { + return resultMessage_; + } + /** @brief Sets optional result message text. */ + void setResultMessage(const std::optional &message) { + resultMessage_ = message; + } + /** @brief Returns operation-specific response payload. */ + [[nodiscard]] std::shared_ptr getResponsePayload() const { + return responsePayload_; + } + /** @brief Sets operation-specific response payload. */ + void setResponsePayload(std::shared_ptr payload) { + responsePayload_ = std::move(payload); + } + /** @brief Encodes response batch item to TTLV element form. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes response batch item from TTLV element form. */ + static ResponseBatchItem fromElement(std::shared_ptr element); + + private: + uint32_t uniqueBatchItemId_ = 0; + int32_t operation_ = 0; + int32_t resultStatus_ = 0; + std::optional resultReason_; + std::optional resultMessage_; + std::shared_ptr responsePayload_; + }; + + /** @brief Full KMIP response message including header and batch items. */ + class ResponseMessage { + public: + /** @brief Constructs an empty response message. */ + ResponseMessage() = default; + /** @brief Returns const response header. */ + [[nodiscard]] const ResponseHeader &getHeader() const { return header_; } + /** @brief Returns mutable response header. */ + ResponseHeader &getHeader() { return header_; } + /** @brief Replaces response header. */ + void setHeader(const ResponseHeader &header) { header_ = header; } + /** @brief Returns const response batch items. */ + [[nodiscard]] const std::vector &getBatchItems() const { + return batchItems_; + } + /** @brief Returns mutable response batch items. */ + std::vector &getBatchItems() { return batchItems_; } + /** @brief Appends one response batch item. */ + void add_batch_item(const ResponseBatchItem &item) { + batchItems_.push_back(item); + } + /** @brief Encodes response message to TTLV element tree. */ + [[nodiscard]] std::shared_ptr toElement() const; + /** @brief Decodes response message from TTLV element tree. */ + static ResponseMessage fromElement(std::shared_ptr element); + + private: + ResponseHeader header_; + std::vector batchItems_; + }; +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp new file mode 100644 index 0000000..9bb1af9 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -0,0 +1,176 @@ +#pragma once +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/kmip_protocol.hpp" +#include "kmipcore/types.hpp" + +#include +#include +#include +#include + +namespace kmipcore { + + // --------------------------------------------------------------------------- + // Each Request class IS a RequestBatchItem. + // Construct one and pass it directly to RequestMessage::addBatchItem(). + // --------------------------------------------------------------------------- + + + // --------------------------------------------------------------------------- + // Template for simple requests that only carry a unique identifier. + // --------------------------------------------------------------------------- + /** + * @brief Generic request carrying only a unique identifier in payload. + * @tparam OpCode KMIP operation code encoded in the batch item. + */ + template class SimpleIdRequest : public RequestBatchItem { + public: + /** + * @brief Builds a simple request payload with unique identifier. + * @param unique_id KMIP unique identifier of target object. + */ + explicit SimpleIdRequest(const std::string &unique_id) { + setOperation(OpCode); + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + ) + ); + setRequestPayload(payload); + } + }; + + /** @brief Typed request alias for KMIP Get operation. */ + using GetRequest = SimpleIdRequest; + /** @brief Typed request alias for KMIP Activate operation. */ + using ActivateRequest = SimpleIdRequest; + /** @brief Typed request alias for KMIP Destroy operation. */ + using DestroyRequest = SimpleIdRequest; + /** @brief Typed request alias for KMIP Get Attribute List operation. */ + using GetAttributeListRequest = SimpleIdRequest; + + + /** @brief Request for KMIP Get Attributes operation. */ + class GetAttributesRequest : public RequestBatchItem { + public: + /** + * @brief Builds Get Attributes request for selected attribute names. + * @param unique_id KMIP unique identifier of target object. + * @param attribute_names Attribute names to retrieve. + */ + GetAttributesRequest( + const std::string &unique_id, + const std::vector &attribute_names + ) { + setOperation(KMIP_OP_GET_ATTRIBUTES); + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + ) + ); + for (const auto &attr_name : attribute_names) { + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), attr_name + ) + ); + } + setRequestPayload(payload); + } + }; + + // Constructors for the following classes are defined in kmip_requests.cpp + // because they rely on internal detail:: helpers. + + /** @brief Request for KMIP Create (symmetric key) operation. */ + class CreateSymmetricKeyRequest : public RequestBatchItem { + public: + /** + * @brief Builds a create-key request for AES-256 server-side generation. + * @param name Value for KMIP Name attribute. + * @param group Value for KMIP Object Group attribute. + */ + CreateSymmetricKeyRequest( + const std::string &name, const std::string &group + ); + }; + + /** @brief Request for KMIP Register (symmetric key) operation. */ + class RegisterSymmetricKeyRequest : public RequestBatchItem { + public: + /** + * @brief Builds a register request for raw symmetric key bytes. + * @param name Value for KMIP Name attribute. + * @param group Value for KMIP Object Group attribute. + * @param key_value Raw symmetric key payload. + */ + RegisterSymmetricKeyRequest( + const std::string &name, + const std::string &group, + const std::vector &key_value + ); + }; + + /** @brief Request for KMIP Register (secret data) operation. */ + class RegisterSecretRequest : public RequestBatchItem { + public: + /** + * @brief Builds a register request for secret payload bytes. + * @param name Value for KMIP Name attribute. + * @param group Value for KMIP Object Group attribute. + * @param secret Secret payload bytes. + * @param secret_type KMIP secret_data_type enum value. + */ + RegisterSecretRequest( + const std::string &name, + const std::string &group, + const secret_t &secret, + int32_t secret_type + ); + }; + + /** @brief Request for KMIP Locate operation. */ + class LocateRequest : public RequestBatchItem { + public: + /** + * @brief Builds a locate request by name or group. + * @param locate_by_group true to filter by Object Group, false by Name. + * @param name Filter value. + * @param object_type KMIP object_type to match. + * @param max_items Maximum number of items requested per locate call. + * @param offset Locate offset used for paged reads. + */ + LocateRequest( + bool locate_by_group, + const std::string &name, + int32_t object_type, + size_t max_items = 0, + size_t offset = 0 + ); + }; + + /** @brief Request for KMIP Revoke operation. */ + class RevokeRequest : public RequestBatchItem { + public: + /** + * @brief Builds a revoke request with reason and optional occurrence time. + * @param unique_id KMIP unique identifier of target object. + * @param reason KMIP revocation reason enum value. + * @param message Human-readable revocation note. + * @param occurrence_time Incident timestamp, 0 for default deactivation flow. + */ + RevokeRequest( + const std::string &unique_id, + int32_t reason, + const std::string &message, + time_t occurrence_time = 0 + ); + }; + + +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_responses.hpp b/kmipcore/include/kmipcore/kmip_responses.hpp new file mode 100644 index 0000000..800aeaf --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_responses.hpp @@ -0,0 +1,200 @@ +#pragma once + +#include "kmipcore/kmip_protocol.hpp" + +namespace kmipcore { + + namespace detail { + /** @brief Validates response operation code against expected value. */ + inline void expect_operation( + const ResponseBatchItem &item, + int32_t expectedOperation, + const char *className + ) { + if (item.getOperation() != expectedOperation) { + throw KmipException( + std::string(className) + + ": unexpected operation in response batch item" + ); + } + } + + /** @brief Returns response payload or throws when it is missing. */ + inline std::shared_ptr require_response_payload( + const ResponseBatchItem &item, const char *className + ) { + auto payload = item.getResponsePayload(); + if (!payload) { + throw KmipException( + std::string(className) + ": missing response payload" + ); + } + return payload; + } + } // namespace detail + + // --------------------------------------------------------------------------- + // CRTP Base class to reduce boilerplate for fromElement and constructors. + // --------------------------------------------------------------------------- + template + class BaseResponseBatchItem : public ResponseBatchItem { + public: + using ResponseBatchItem::ResponseBatchItem; + + /** @brief Constructs typed wrapper from plain response batch item. */ + explicit BaseResponseBatchItem(const ResponseBatchItem &other) + : ResponseBatchItem(other) {} + + /** @brief Decodes typed wrapper directly from TTLV element form. */ + static Derived fromElement(std::shared_ptr element) { + return Derived::fromBatchItem( + ResponseBatchItem::fromElement(std::move(element)) + ); + } + }; + + // --------------------------------------------------------------------------- + // Common base template for simple response batch items that only carry a + // unique-identifier extracted from the response payload. + // OpCode is the expected KMIP operation enum value (e.g. KMIP_OP_CREATE). + // --------------------------------------------------------------------------- + template + class SimpleIdResponseBatchItem + : public BaseResponseBatchItem> { + public: + using Base = BaseResponseBatchItem>; + using Base::Base; // Inherit constructors + + /** @brief Converts generic response item into typed simple-id response. */ + static SimpleIdResponseBatchItem + fromBatchItem(const ResponseBatchItem &item) { + detail::expect_operation(item, OpCode, "SimpleIdResponseBatchItem"); + + SimpleIdResponseBatchItem result(item); + auto payload = + detail::require_response_payload(item, "SimpleIdResponseBatchItem"); + + auto uid = + payload->getChild(static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)); + if (!uid) { + throw KmipException( + "SimpleIdResponseBatchItem: missing unique identifier in response " + "payload" + ); + } + result.uniqueIdentifier_ = uid->toString(); + return result; + } + + /** @brief Returns response unique identifier field. */ + [[nodiscard]] const std::string &getUniqueIdentifier() const { + return uniqueIdentifier_; + } + + private: + std::string uniqueIdentifier_; + }; + + /** @brief Typed response alias for KMIP Create operation. */ + using CreateResponseBatchItem = SimpleIdResponseBatchItem; + /** @brief Typed response alias for KMIP Register operation. */ + using RegisterResponseBatchItem = SimpleIdResponseBatchItem; + /** @brief Typed response alias for KMIP Activate operation. */ + using ActivateResponseBatchItem = SimpleIdResponseBatchItem; + /** @brief Typed response alias for KMIP Revoke operation. */ + using RevokeResponseBatchItem = SimpleIdResponseBatchItem; + /** @brief Typed response alias for KMIP Destroy operation. */ + using DestroyResponseBatchItem = SimpleIdResponseBatchItem; + + // --------------------------------------------------------------------------- + // Response types with additional fields beyond unique-identifier. + // --------------------------------------------------------------------------- + + /** @brief Typed response for KMIP Get operation. */ + class GetResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Get response view. */ + static GetResponseBatchItem fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns unique identifier from response payload. */ + [[nodiscard]] const std::string &getUniqueIdentifier() const { + return uniqueIdentifier_; + } + /** @brief Returns KMIP object_type value from response payload. */ + [[nodiscard]] int32_t getObjectType() const { return objectType_; } + /** @brief Returns element containing the returned KMIP object content. */ + [[nodiscard]] std::shared_ptr getObjectElement() const { + return objectElement_; + } + + private: + std::string uniqueIdentifier_; + int32_t objectType_ = 0; + std::shared_ptr objectElement_; + }; + + /** @brief Typed response for KMIP Get Attributes operation. */ + class GetAttributesResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Get Attributes response. */ + static GetAttributesResponseBatchItem + fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns raw attribute elements carried by the payload. */ + [[nodiscard]] const std::vector> &getAttributes() const { + return attributes_; + } + + private: + std::vector> attributes_; + }; + + /** @brief Typed response for KMIP Get Attribute List operation. */ + class GetAttributeListResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Get Attribute List response. */ + static GetAttributeListResponseBatchItem + fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns attribute names present in the target object. */ + [[nodiscard]] const std::vector &getAttributeNames() const { + return attributeNames_; + } + + private: + std::vector attributeNames_; + }; + + /** @brief Typed response for KMIP Locate operation. */ + class LocateResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Locate response view. */ + static LocateResponseBatchItem fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns parsed locate payload metadata and identifiers. */ + [[nodiscard]] const LocateResponsePayload &getLocatePayload() const { + return locatePayload_; + } + /** @brief Returns located unique identifiers from payload. */ + [[nodiscard]] const std::vector &getUniqueIdentifiers() const { + return locatePayload_.getUniqueIdentifiers(); + } + + private: + LocateResponsePayload locatePayload_; + }; + + +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmipcore_version.hpp b/kmipcore/include/kmipcore/kmipcore_version.hpp new file mode 100644 index 0000000..f9171fc --- /dev/null +++ b/kmipcore/include/kmipcore/kmipcore_version.hpp @@ -0,0 +1,40 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCORE_VERSION_HPP +#define KMIPCORE_VERSION_HPP + +/** @brief kmipcore semantic version major component. */ +#define KMIPCORE_VERSION_MAJOR 0 +/** @brief kmipcore semantic version minor component. */ +#define KMIPCORE_VERSION_MINOR 1 +/** @brief kmipcore semantic version patch component. */ +#define KMIPCORE_VERSION_PATCH 0 + +/** @brief Internal helper for macro stringification. */ +#define KMIPCORE_STRINGIFY_I(x) #x +/** @brief Internal helper for macro stringification. */ +#define KMIPCORE_TOSTRING_I(x) KMIPCORE_STRINGIFY_I(x) + +/** @brief Full kmipcore version string in "major.minor.patch" form. */ +#define KMIPCORE_VERSION_STR \ + KMIPCORE_TOSTRING_I(KMIPCORE_VERSION_MAJOR) \ + "." KMIPCORE_TOSTRING_I(KMIPCORE_VERSION_MINOR) "." KMIPCORE_TOSTRING_I( \ + KMIPCORE_VERSION_PATCH \ + ) + +#endif // KMIPCORE_VERSION_HPP diff --git a/kmipcore/include/kmipcore/response_parser.hpp b/kmipcore/include/kmipcore/response_parser.hpp new file mode 100644 index 0000000..08b506b --- /dev/null +++ b/kmipcore/include/kmipcore/response_parser.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "kmipcore/kmip_protocol.hpp" +#include "kmipcore/kmip_responses.hpp" + +#include +#include +#include + +namespace kmipcore { + + /** @brief Compact status summary for one KMIP response batch item. */ + struct OperationResult { + /** Operation code reported by the response item. */ + int32_t operation = 0; + /** KMIP result_status code. */ + int32_t resultStatus = 0; + /** KMIP result_reason code when available. */ + int32_t resultReason = 0; + /** Human-readable result message when available. */ + std::string resultMessage; + }; + + /** Parses KMIP response batch items and decodes typed operation-specific + * items. */ + class ResponseParser { + public: + /** + * @brief Creates a parser for one encoded KMIP response message. + * @param responseBytes Raw TTLV response payload. + */ + explicit ResponseParser(std::span responseBytes); + /** @brief Default destructor. */ + ~ResponseParser() = default; + ResponseParser(const ResponseParser &) = delete; + ResponseParser(ResponseParser &&) = delete; + ResponseParser &operator=(const ResponseParser &) = delete; + ResponseParser &operator=(ResponseParser &&) = delete; + + /** @brief Returns number of batch items in the parsed response. */ + [[nodiscard]] size_t getBatchItemCount(); + /** + * @brief Returns whether a batch item completed with KMIP_STATUS_SUCCESS. + * @param itemIdx Zero-based batch item index. + */ + [[nodiscard]] bool isSuccess(int itemIdx); + + /** + * @brief Returns operation status fields for one batch item. + * @param itemIdx Zero-based batch item index. + */ + [[nodiscard]] OperationResult getOperationResult(int itemIdx); + /** + * @brief Returns operation status fields by unique batch item id. + * @param batchItemId Request/response correlation id. + */ + [[nodiscard]] OperationResult + getOperationResultByBatchItemId(uint32_t batchItemId); + + /** + * @brief Returns typed response object by index after success check. + * @tparam TypedResponseBatchItem One of kmip_responses typed wrappers. + * @param itemIdx Zero-based batch item index. + * @throws KmipException if item is not successful or payload is invalid. + */ + template + [[nodiscard]] TypedResponseBatchItem getResponse(int itemIdx) { + const auto &item = getResponseItem(itemIdx); + ensureSuccess(item); + return TypedResponseBatchItem::fromBatchItem(item); + } + + /** + * @brief Returns typed response object by unique batch id after success check. + * @tparam TypedResponseBatchItem One of kmip_responses typed wrappers. + * @param batchItemId Request/response correlation id. + * @throws KmipException if item is not successful or payload is invalid. + */ + template + [[nodiscard]] TypedResponseBatchItem + getResponseByBatchItemId(uint32_t batchItemId) { + const auto &item = getResponseItemByBatchItemId(batchItemId); + ensureSuccess(item); + return TypedResponseBatchItem::fromBatchItem(item); + } + + private: + void parseResponse(); + + void ensureParsed() { + if (!isParsed_) { + parseResponse(); + } + } + + [[nodiscard]] const ResponseBatchItem &getResponseItem(int itemIdx); + [[nodiscard]] const ResponseBatchItem & + getResponseItemByBatchItemId(uint32_t batchItemId); + + static void ensureSuccess(const ResponseBatchItem &item); + + static std::string formatOperationResult(const ResponseBatchItem &value); + static const char *operationToString(int32_t operation); + static const char *resultStatusToString(int32_t status); + + std::vector responseBytes_; + ResponseMessage responseMessage_{}; + bool isParsed_ = false; + }; + +} // namespace kmipcore diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp new file mode 100644 index 0000000..73a42ca --- /dev/null +++ b/kmipcore/include/kmipcore/secret.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/types.hpp" + +#include +#include +#include + +namespace kmipcore { + + /** + * @brief Minimal KMIP Secret Data model. + */ + class Secret { + public: + /** Raw secret payload bytes. */ + secret_t value; + /** Lifecycle state of this secret object. */ + enum state state = KMIP_STATE_PRE_ACTIVE; + /** KMIP secret data type discriminator. */ + enum secret_data_type secret_type = PASSWORD; + + /** @brief Constructs an empty secret. */ + Secret() = default; + /** @brief Constructs a secret from payload and metadata. */ + Secret(secret_t val, enum state st, enum secret_data_type type) + : value(std::move(val)), state(st), secret_type(type) {} + + /** @brief Returns all attached secret attributes. */ + [[nodiscard]] const attributes_t &attributes() const noexcept { + return secret_attributes; + } + + /** + * @brief Returns value of a required secret attribute. + * @throws std::out_of_range if the attribute is missing. + */ + [[nodiscard]] const std::string & + attribute_value(const std::string &name) const { + return secret_attributes.at(name); + } + + /** @brief Sets or replaces one secret attribute value. */ + void set_attribute( + const std::string &name, const std::string &val + ) noexcept { + secret_attributes[name] = val; + } + + /** + * @brief Creates a Secret from text bytes. + * @param text Source text payload. + * @param type KMIP secret data type. + * @param st Initial lifecycle state. + */ + [[nodiscard]] static Secret from_text( + std::string_view text, + enum secret_data_type type = PASSWORD, + enum state st = KMIP_STATE_PRE_ACTIVE + ) { + return Secret{ + secret_t(text.begin(), text.end()), st, type}; + } + + /** @brief Returns payload interpreted as UTF-8/byte-preserving text. */ + [[nodiscard]] std::string as_text() const { + return std::string(value.begin(), value.end()); + } + + private: + attributes_t secret_attributes; + }; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/serialization_buffer.hpp b/kmipcore/include/kmipcore/serialization_buffer.hpp new file mode 100644 index 0000000..0dc22f3 --- /dev/null +++ b/kmipcore/include/kmipcore/serialization_buffer.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include + +namespace kmipcore { + +/** + * SerializationBuffer provides efficient buffering for KMIP TTLV serialization. + * + * Instead of creating many small std::vector allocations during recursive + * Element serialization, all data is written to a single pre-allocated buffer. + * This significantly reduces heap fragmentation and improves performance. + * + * Key features: + * - Single pre-allocated buffer (default 8KB) + * - Auto-expansion if message exceeds capacity + * - TTLV-aware padding (8-byte alignment) + * - RAII-based automatic cleanup + * - Non-copyable, movable for transfer of ownership + */ +class SerializationBuffer { +public: + // ==================== CONSTANTS ==================== + + /// KMIP TTLV requires all values to be padded to a multiple of 8 bytes + static constexpr size_t TTLV_ALIGNMENT = 8; + + /// Default initial buffer capacity (covers the vast majority of KMIP messages) + static constexpr size_t DEFAULT_CAPACITY = 8192; + + /// Minimum capacity used as the starting point when the buffer is empty + static constexpr size_t MIN_CAPACITY = 1024; + + /// Hard upper limit on buffer growth to catch runaway allocations + static constexpr size_t MAX_CAPACITY = 100 * 1024 * 1024; // 100 MB + + // ==================== CONSTRUCTION ==================== + + /** + * Construct a SerializationBuffer with specified capacity. + * @param initial_capacity Initial buffer size (default: DEFAULT_CAPACITY) + */ + explicit SerializationBuffer(size_t initial_capacity = DEFAULT_CAPACITY); + + // Non-copyable (unique ownership of buffer) + SerializationBuffer(const SerializationBuffer&) = delete; + SerializationBuffer& operator=(const SerializationBuffer&) = delete; + + // Movable (transfer buffer ownership) + SerializationBuffer(SerializationBuffer&&) = default; + SerializationBuffer& operator=(SerializationBuffer&&) = default; + + /** + * Destructor - cleans up buffer automatically (RAII) + */ + ~SerializationBuffer() = default; + + // ==================== WRITE OPERATIONS ==================== + + /** + * Write a single byte to the buffer. + * @param value Byte value to write + */ + void writeByte(uint8_t value); + + /** + * Write raw bytes (unpadded) to the buffer. + * @param data Pointer to data + * @param length Number of bytes to write + */ + void writeBytes(const uint8_t* data, size_t length); + void writeBytes(const void* data, size_t length) { + writeBytes(static_cast(data), length); + } + + /** + * Write raw bytes with KMIP padding (8-byte aligned). + * Adds zero-fill padding to align to 8-byte boundary. + * @param data Pointer to data + * @param length Number of bytes to write + */ + void writePadded(const uint8_t* data, size_t length); + void writePadded(const void* data, size_t length) { + writePadded(static_cast(data), length); + } + + // ==================== QUERY OPERATIONS ==================== + + /** + * Get current write position / serialized data size. + * @return Number of bytes of valid data in buffer + */ + [[nodiscard]] size_t size() const { return current_offset_; } + + /** + * Get total allocated capacity. + * @return Total capacity in bytes + */ + [[nodiscard]] size_t capacity() const { return buffer_.capacity(); } + + /** + * Get remaining space before reallocation needed. + * @return Number of free bytes + */ + [[nodiscard]] size_t remaining() const { + return current_offset_ < buffer_.capacity() + ? buffer_.capacity() - current_offset_ + : 0; + } + + // ==================== UTILITY OPERATIONS ==================== + + /** + * Reset buffer to empty state (reuse for next message). + * Keeps capacity for reuse, only clears write position. + */ + void reset() { current_offset_ = 0; } + + /** + * Ensure sufficient space is available for the specified bytes. + * Auto-expands if necessary. + * @param required_bytes Number of bytes needed + */ + void ensureSpace(size_t required_bytes); + + // ==================== ACCESS OPERATIONS ==================== + + /** + * Get const pointer to buffer data. + * @return Pointer to serialized data (only first size() bytes are valid) + */ + [[nodiscard]] const uint8_t* data() const { return buffer_.data(); } + + /** + * Get mutable pointer to buffer data (use with caution). + * @return Pointer to buffer + */ + uint8_t* mutableData() { return buffer_.data(); } + + /** + * Get const reference to underlying vector. + * @return Reference to internal vector + */ + [[nodiscard]] const std::vector& getBuffer() const { return buffer_; } + + // ==================== TRANSFER OWNERSHIP ==================== + + /** + * Copy serialized data into a new vector and reset this buffer for reuse. + * + * The returned vector contains exactly the serialized data (size() bytes). + * The internal buffer is cleared (write position reset to 0) but its + * reserved capacity is intentionally kept so the buffer can be reused for + * the next message without a new heap allocation — consistent with the + * pre-allocation performance goal of this class. + * + * To aggressively reclaim heap memory after the last use, call + * freeMemory() instead of (or after) release(). + * + * @return Vector containing serialized data + */ + std::vector release(); + + /** + * Release all heap memory, including reserved capacity. + * + * Use this when the buffer will not be reused and memory must be returned + * to the allocator immediately (e.g., in a pool tear-down or when handling + * a very large one-off message). After this call the buffer is in the + * same state as a freshly constructed one with zero capacity. + */ + void freeMemory(); + +private: + std::vector buffer_; + size_t current_offset_ = 0; + + /** + * Internal helper to expand buffer capacity. + * Uses exponential growth strategy. + * @param required Minimum required capacity + */ + void expandCapacity(size_t required); +}; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/types.hpp b/kmipcore/include/kmipcore/types.hpp new file mode 100644 index 0000000..5d121d6 --- /dev/null +++ b/kmipcore/include/kmipcore/types.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +#include + +namespace kmipcore { + + /** @brief Raw key bytes container type. */ + using key_t = std::vector; + /** @brief Generic binary payload container type. */ + using bin_data_t = std::vector; + /** @brief KMIP unique identifier textual type. */ + using id_t = std::string; + /** @brief Collection of KMIP unique identifiers. */ + using ids_t = std::vector; + /** @brief KMIP Name attribute textual type. */ + using name_t = std::string; + /** @brief Collection of textual names. */ + using names_t = std::vector; + /** @brief Secret payload bytes container type. */ + using secret_t = std::vector; + /** @brief Generic string attribute map type. */ + using attributes_t = std::unordered_map; + + /** Convert a KMIP state enum value to a human-readable string. */ + inline const char *state_to_string(int32_t value) { + switch (static_cast(value)) { + case KMIP_STATE_PRE_ACTIVE: + return "KMIP_STATE_PRE_ACTIVE"; + case KMIP_STATE_ACTIVE: + return "KMIP_STATE_ACTIVE"; + case KMIP_STATE_DEACTIVATED: + return "KMIP_STATE_DEACTIVATED"; + case KMIP_STATE_COMPROMISED: + return "KMIP_STATE_COMPROMISED"; + case KMIP_STATE_DESTROYED: + return "KMIP_STATE_DESTROYED"; + case KMIP_STATE_DESTROYED_COMPROMISED: + return "KMIP_STATE_DESTROYED_COMPROMISED"; + default: + return "UNKNOWN_KMIP_STATE"; + } + } + + /** @brief Stream formatter for KMIP lifecycle state values. */ + inline std::ostream &operator<<(std::ostream &out, const state value) { + return out << state_to_string(static_cast(value)); + } + +} // namespace kmipcore + diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp new file mode 100644 index 0000000..c299680 --- /dev/null +++ b/kmipcore/src/attributes_parser.cpp @@ -0,0 +1,140 @@ +#include "kmipcore/attributes_parser.hpp" + +#include "kmipcore/kmip_attribute_names.hpp" + +#include +#include +#include + +namespace kmipcore { + + namespace { + + + [[nodiscard]] std::string attribute_key_from_name(const std::string &name) { + if (name == "Name") return KMIP_ATTR_NAME_NAME; + if (name == "Object Group") return KMIP_ATTR_NAME_GROUP; + if (name == "State") return KMIP_ATTR_NAME_STATE; + if (name == "Unique Identifier") return KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; + if (name == "UniqueID") return KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; // Legacy/PyKMIP compat + if (name == "Initial Date") return KMIP_ATTR_NAME_INITIAL_DATE; + if (name == "Activation Date") return KMIP_ATTR_NAME_ACTIVATION_DATE; + if (name == "Process Start Date") return KMIP_ATTR_NAME_PROCESS_START_DATE; + if (name == "Protect Stop Date") return KMIP_ATTR_NAME_PROTECT_STOP_DATE; + if (name == "Deactivation Date") return KMIP_ATTR_NAME_DEACTIVATION_DATE; + if (name == "Destroy Date") return KMIP_ATTR_NAME_DESTROY_DATE; + if (name == "Compromise Occurrence Date") return KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE; + if (name == "Compromise Date") return KMIP_ATTR_NAME_COMPROMISE_DATE; + if (name == "Archive Date") return KMIP_ATTR_NAME_ARCHIVE_DATE; + if (name == "Last Change Date") return KMIP_ATTR_NAME_LAST_CHANGE_DATE; + if (name == "Cryptographic Algorithm") return KMIP_ATTR_NAME_CRYPTO_ALG; + if (name == "Cryptographic Length") return KMIP_ATTR_NAME_CRYPTO_LEN; + if (name == "Cryptographic Usage Mask") return KMIP_ATTR_NAME_CRYPTO_USAGE_MASK; + if (name == "Contact Information") return KMIP_ATTR_NAME_CONTACT_INFO; + if (name == "Operation Policy Name") return KMIP_ATTR_NAME_OPERATION_POLICY_NAME; + return name; + } + + [[nodiscard]] std::string crypto_alg_to_string(int32_t val) { + switch (val) { + case KMIP_CRYPTOALG_DES: return "DES"; + case KMIP_CRYPTOALG_TRIPLE_DES: return "3DES"; + case KMIP_CRYPTOALG_AES: return "AES"; + case KMIP_CRYPTOALG_RSA: return "RSA"; + case KMIP_CRYPTOALG_DSA: return "DSA"; + case KMIP_CRYPTOALG_ECDSA: return "ECDSA"; + case KMIP_CRYPTOALG_HMAC_SHA1: return "HMAC-SHA1"; + case KMIP_CRYPTOALG_HMAC_SHA224: return "HMAC-SHA224"; + case KMIP_CRYPTOALG_HMAC_SHA256: return "HMAC-SHA256"; + case KMIP_CRYPTOALG_HMAC_SHA384: return "HMAC-SHA384"; + case KMIP_CRYPTOALG_HMAC_SHA512: return "HMAC-SHA512"; + case KMIP_CRYPTOALG_HMAC_MD5: return "HMAC-MD5"; + case KMIP_CRYPTOALG_DH: return "DH"; + case KMIP_CRYPTOALG_ECDH: return "ECDH"; + default: return std::to_string(val); + } + } + + [[nodiscard]] std::string date_to_string(int64_t seconds) { + // Return ISO 8601 format or similar "YYYY-MM-DD HH:MM:SS" + // KMIP Date-Time is standard UNIX epoch seconds. + std::time_t t = static_cast(seconds); + std::tm tm_buf{}; +#ifdef _WIN32 + gmtime_s(&tm_buf, &t); +#else + gmtime_r(&t, &tm_buf); +#endif + std::ostringstream oss; + oss << std::put_time(&tm_buf, "%Y-%m-%dT%H:%M:%SZ"); + return oss.str(); + } + + [[nodiscard]] std::string parse_attribute_value( + const std::string &attribute_name, const std::shared_ptr &value + ) { + if (!value) { + return {}; + } + + switch (value->type) { + case KMIP_TYPE_TEXT_STRING: + return value->toString(); + case KMIP_TYPE_INTEGER: + return std::to_string(value->toInt()); + case KMIP_TYPE_DATE_TIME: + return date_to_string(value->toLong()); + case KMIP_TYPE_LONG_INTEGER: + return std::to_string(value->toLong()); + case KMIP_TYPE_ENUMERATION: + if (attribute_name == KMIP_ATTR_NAME_STATE) { + return state_to_string(value->toEnum()); + } + if (attribute_name == KMIP_ATTR_NAME_CRYPTO_ALG) { + return crypto_alg_to_string(value->toEnum()); + } + return std::to_string(value->toEnum()); + case KMIP_TYPE_STRUCTURE: + if (attribute_name == KMIP_ATTR_NAME_NAME) { + if (auto name_value = + value->getChild(static_cast(KMIP_TAG_NAME_VALUE)); + name_value) { + return name_value->toString(); + } + } + break; + default: + break; + } + + return {}; + } + + } // namespace + + attributes_t AttributesParser::parse( + const std::vector> &attributes + ) { + attributes_t res; + for (const auto &attribute : attributes) { + if (attribute == nullptr || + attribute->tag != static_cast(KMIP_TAG_ATTRIBUTE)) { + continue; + } + + auto attribute_name = + attribute->getChild(static_cast(KMIP_TAG_ATTRIBUTE_NAME)); + auto attribute_value = + attribute->getChild(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + if (!attribute_name) { + continue; + } + + const auto name = attribute_name->toString(); + res[attribute_key_from_name(name)] = + parse_attribute_value(name, attribute_value); + } + return res; + } + +} // namespace kmipcore diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp new file mode 100644 index 0000000..a144243 --- /dev/null +++ b/kmipcore/src/key_parser.cpp @@ -0,0 +1,232 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipcore/key_parser.hpp" + +#include "kmipcore/attributes_parser.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include + +namespace kmipcore { + + namespace { + + /** Extract a Key from an element that contains a KeyBlock (Symmetric Key, + * Private Key, Public Key). */ + Key parse_key_from_key_block_holder( + const std::shared_ptr &object_element, + KeyType key_type, + const char *type_name + ) { + auto key_block = + object_element->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + if (!key_block) { + throw KmipException( + KMIP_INVALID_ENCODING, + std::string("Missing Key Block in ") + type_name + " response." + ); + } + + auto key_value = + key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + if (!key_value) { + throw KmipException( + KMIP_INVALID_ENCODING, + std::string("Missing Key Value in ") + type_name + " response." + ); + } + + auto key_material = + key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + if (!key_material) { + throw KmipException( + KMIP_INVALID_ENCODING, + std::string("Missing Key Material in ") + type_name + " response." + ); + } + + auto raw_bytes = key_material->toBytes(); + key_t kv(raw_bytes.begin(), raw_bytes.end()); + + auto algorithm = key_block->getChild( + static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM) + ); + auto key_attributes = AttributesParser::parse( + key_value->getChildren(static_cast(KMIP_TAG_ATTRIBUTE)) + ); + + return Key( + std::move(kv), + key_type, + algorithm ? static_cast(algorithm->toEnum()) + : KMIP_CRYPTOALG_UNSET, + KMIP_CRYPTOMASK_UNSET, + std::move(key_attributes) + ); + } + + } // anonymous namespace + + Key KeyParser::parseGetKeyResponse(const GetResponseBatchItem &item) { + if (item.getObjectType() != KMIP_OBJTYPE_SYMMETRIC_KEY) { + throw KmipException( + KMIP_REASON_INVALID_DATA_TYPE, + "Symmetric key expected in Get response." + ); + } + return parseResponse(item.getResponsePayload()); + } + + Secret KeyParser::parseGetSecretResponse(const GetResponseBatchItem &item) { + if (item.getObjectType() != KMIP_OBJTYPE_SECRET_DATA) { + throw KmipException( + KMIP_REASON_INVALID_DATA_TYPE, "Secret data expected in Get response." + ); + } + + auto object = item.getObjectElement(); + if (!object) { + throw KmipException( + KMIP_INVALID_ENCODING, "Missing Secret Data object in response." + ); + } + + auto secret_type = + object->getChild(static_cast(KMIP_TAG_SECRET_DATA_TYPE)); + auto key_block = object->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + if (!secret_type || !key_block) { + throw KmipException( + KMIP_INVALID_ENCODING, "Secret data key block format mismatch" + ); + } + + auto key_format = + key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + if (!key_format || (key_format->toEnum() != KMIP_KEYFORMAT_OPAQUE && + key_format->toEnum() != KMIP_KEYFORMAT_RAW)) { + throw KmipException( + KMIP_OBJECT_MISMATCH, "Secret data key block format mismatch" + ); + } + + auto key_value = key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + if (!key_value) { + throw KmipException(KMIP_INVALID_ENCODING, "Missing secret key value."); + } + + auto key_material = + key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + if (!key_material) { + throw KmipException( + KMIP_INVALID_ENCODING, "Missing secret key material." + ); + } + + auto raw_bytes = key_material->toBytes(); + + return Secret{ + secret_t(raw_bytes.begin(), raw_bytes.end()), + KMIP_STATE_PRE_ACTIVE, + static_cast(secret_type->toEnum()) + }; + } + + Key KeyParser::parseResponse(const std::shared_ptr &payload) { + if (payload == nullptr) { + throw KmipException(KMIP_INVALID_ENCODING, "Missing response payload."); + } + + auto object_type = + payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + if (!object_type) { + throw KmipException( + KMIP_INVALID_ENCODING, "Missing Object Type in Get response." + ); + } + + // Map KMIP object type to wrapper tag, KeyType, and human-readable name. + struct ObjectTypeMapping { + int32_t obj_type; + int32_t wrapper_tag; + KeyType key_type; + const char *name; + }; + static constexpr ObjectTypeMapping mappings[] = { + {KMIP_OBJTYPE_SYMMETRIC_KEY, + KMIP_TAG_SYMMETRIC_KEY, + KeyType::SYMMETRIC_KEY, + "Symmetric Key"}, + {KMIP_OBJTYPE_PRIVATE_KEY, + KMIP_TAG_PRIVATE_KEY, + KeyType::PRIVATE_KEY, + "Private Key"}, + {KMIP_OBJTYPE_PUBLIC_KEY, + KMIP_TAG_PUBLIC_KEY, + KeyType::PUBLIC_KEY, + "Public Key"}, + }; + + const auto obj_type_val = object_type->toEnum(); + + for (const auto &m : mappings) { + if (obj_type_val != m.obj_type) { + continue; + } + + auto object_element = payload->getChild(static_cast(m.wrapper_tag)); + if (!object_element) { + throw KmipException( + KMIP_INVALID_ENCODING, + std::string("Missing ") + m.name + " object in Get response." + ); + } + + // Symmetric keys require RAW format + if (m.obj_type == KMIP_OBJTYPE_SYMMETRIC_KEY) { + auto key_block = + object_element->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + if (key_block) { + auto key_format = + key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + if (!key_format || key_format->toEnum() != KMIP_KEYFORMAT_RAW) { + throw KmipException( + KMIP_INVALID_ENCODING, "Invalid response object format." + ); + } + } + } + + return parse_key_from_key_block_holder( + object_element, m.key_type, m.name + ); + } + + if (obj_type_val == KMIP_OBJTYPE_CERTIFICATE) { + throw KmipException( + KMIP_NOT_IMPLEMENTED, + "Certificate object type parsing is not yet supported." + ); + } + + throw KmipException( + KMIP_NOT_IMPLEMENTED, + std::string("Unsupported object type: ") + std::to_string(obj_type_val) + ); + } + +} // namespace kmipcore diff --git a/kmipcore/src/kmip_basics.cpp b/kmipcore/src/kmip_basics.cpp new file mode 100644 index 0000000..c74bf38 --- /dev/null +++ b/kmipcore/src/kmip_basics.cpp @@ -0,0 +1,460 @@ +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace kmipcore { + + // Helper functions for big-endian + static uint32_t to_be32(uint32_t v) { + return htonl(v); + } + static uint64_t to_be64(uint64_t v) { + uint32_t high = htonl(v >> 32); + uint32_t low = htonl(v & 0xFFFFFFFF); + return ((uint64_t) low << 32) | high; + } + static uint32_t from_be32(uint32_t v) { + return ntohl(v); + } + static uint64_t from_be64(uint64_t v) { + uint32_t high = ntohl(v >> 32); + uint32_t low = ntohl(v & 0xFFFFFFFF); + return ((uint64_t) high << 32) | low; + } + + + void Element::serialize(SerializationBuffer& buf) const { + // Write Tag (3 bytes, big-endian) + buf.writeByte((tag >> 16) & 0xFF); + buf.writeByte((tag >> 8) & 0xFF); + buf.writeByte(tag & 0xFF); + + // Write Type (1 byte) + buf.writeByte(static_cast(type)); + + // First pass: calculate content and payload length + SerializationBuffer content_buf; + uint32_t payload_length = 0; + + if (std::holds_alternative(value)) { + const auto &s = std::get(value); + for (const auto &item : s.items) { + item->serialize(content_buf); // Recursive call + } + payload_length = content_buf.size(); + } else if (std::holds_alternative(value)) { + int32_t v = std::get(value).value; + v = to_be32(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 4; + } else if (std::holds_alternative(value)) { + int64_t v = std::get(value).value; + v = to_be64(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 8; + } else if (std::holds_alternative(value)) { + const auto &v = std::get(value).value; + content_buf.writeBytes(v.data(), v.size()); + payload_length = v.size(); + } else if (std::holds_alternative(value)) { + int32_t v = std::get(value).value; + v = to_be32(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 4; + } else if (std::holds_alternative(value)) { + uint64_t v = std::get(value).value ? 1 : 0; + v = to_be64(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 8; + } else if (std::holds_alternative(value)) { + const auto &v = std::get(value).value; + content_buf.writePadded(v.data(), v.size()); + payload_length = v.size(); + } else if (std::holds_alternative(value)) { + const auto &v = std::get(value).value; + content_buf.writePadded(v.data(), v.size()); + payload_length = v.size(); + } else if (std::holds_alternative(value)) { + int64_t v = std::get(value).value; + v = to_be64(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 8; + } else if (std::holds_alternative(value)) { + // KMIP 2.0: microseconds since Unix epoch; same 8-byte big-endian wire + // format as DateTime, distinguished only by type code 0x0B. + int64_t v = std::get(value).value; + v = to_be64(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 8; + } else if (std::holds_alternative(value)) { + uint32_t v = std::get(value).value; + v = to_be32(v); + content_buf.writeBytes(&v, sizeof(v)); + payload_length = 4; + } + + // Write Length (4 bytes, big-endian) + buf.writeByte((payload_length >> 24) & 0xFF); + buf.writeByte((payload_length >> 16) & 0xFF); + buf.writeByte((payload_length >> 8) & 0xFF); + buf.writeByte(payload_length & 0xFF); + + // Write content (already padded from content_buf) + if (content_buf.size() > 0) { + buf.writeBytes(content_buf.data(), content_buf.size()); + } + + // Add padding to align to 8 bytes + size_t total_so_far = 3 + 1 + 4 + content_buf.size(); // tag + type + length + content + size_t padding = (8 - (total_so_far % 8)) % 8; + for (size_t i = 0; i < padding; ++i) { + buf.writeByte(0); + } + } + + std::shared_ptr + Element::deserialize(std::span data, size_t &offset) { + if (offset + 8 > data.size()) { + throw KmipException("Buffer too short for header"); + } + + // Read Tag (3 bytes) + uint32_t tag = + (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]; + + // Read Type (1 byte) + Type type = static_cast(data[offset + 3]); + + // Read Length (4 bytes) + uint32_t length = (data[offset + 4] << 24) | (data[offset + 5] << 16) | + (data[offset + 6] << 8) | data[offset + 7]; + + offset += 8; + + // Check bounds + // For Structure, length is the length of contents. + // For Primitives, length is the unpadded length. + // We need to calculate padded length to skip correctly. + size_t padded_length = length; + if (length % 8 != 0 && type != ::KMIP_TYPE_STRUCTURE) { + padded_length += + (8 - + (length % 8)); // Doesn't apply to structure? + // Structure variable length is usually handled + // differently because it contains other items + // aligned on 8-byte boundaries. Actually for + // Structure type, length is sum of encoded items. + // Since all encoded items are multiple of 8 bytes, Structure length + // should be multiple of 8. + } + + if (type == ::KMIP_TYPE_STRUCTURE) { + // Guard: the declared structure body must fit within the available buffer. + if (offset + length > data.size()) { + throw KmipException("Buffer too short for structure body"); + } + + // Narrow the view to exactly the declared structure body so that a + // malformed child cannot silently consume bytes that belong to a + // sibling or a parent structure. std::span::subspan is O(1) — + // pointer + size only, no allocation, no copy. + const auto struct_view = data.subspan(0, offset + length); + + auto struct_elem = std::make_shared(); + struct_elem->tag = static_cast(tag); + struct_elem->type = type; + struct_elem->value = Structure{}; + + size_t current_struct_offset = 0; + while (current_struct_offset < length) { + size_t item_offset = offset; + auto child = deserialize(struct_view, item_offset); + std::get(struct_elem->value).add(child); + size_t consumed = item_offset - offset; + current_struct_offset += consumed; + offset = item_offset; + } + return struct_elem; + } else { + if (offset + padded_length > data.size()) { + throw KmipException("Buffer too short for value"); + } + + auto elem = std::make_shared(); + elem->tag = static_cast(tag); + elem->type = type; + + switch (type) { + case ::KMIP_TYPE_INTEGER: { + if (length != 4) { + throw KmipException("Invalid length for Integer"); + } + int32_t val; + uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]; + // raw is equivalent to big-endian read + // we can just use memcpy if valid but manual reconstruction is safer + // for endianness Actually raw is correct for big endian 4 bytes + std::memcpy(&val, &raw, 4); // Interpreting uint32 as int32 + elem->value = Integer{val}; + break; + } + case ::KMIP_TYPE_LONG_INTEGER: { + if (length != 8) { + throw KmipException("Invalid length for Long Integer"); + } + uint64_t raw = ((uint64_t) data[offset] << 56) | + ((uint64_t) data[offset + 1] << 48) | + ((uint64_t) data[offset + 2] << 40) | + ((uint64_t) data[offset + 3] << 32) | + ((uint64_t) data[offset + 4] << 24) | + ((uint64_t) data[offset + 5] << 16) | + ((uint64_t) data[offset + 6] << 8) | + (uint64_t) data[offset + 7]; + int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = LongInteger{val}; + break; + } + case ::KMIP_TYPE_BOOLEAN: { + if (length != 8) { + throw KmipException("Invalid length for Boolean"); + } + uint64_t raw = ((uint64_t) data[offset] << 56) | + ((uint64_t) data[offset + 1] << 48) | + ((uint64_t) data[offset + 2] << 40) | + ((uint64_t) data[offset + 3] << 32) | + ((uint64_t) data[offset + 4] << 24) | + ((uint64_t) data[offset + 5] << 16) | + ((uint64_t) data[offset + 6] << 8) | + (uint64_t) data[offset + 7]; + elem->value = Boolean{raw != 0}; + break; + } + case ::KMIP_TYPE_ENUMERATION: { + if (length != 4) { + throw KmipException("Invalid length for Enumeration"); + } + uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]; + elem->value = Enumeration{(int32_t) raw}; + break; + } + case ::KMIP_TYPE_TEXT_STRING: { + std::string s(reinterpret_cast(&data[offset]), length); + elem->value = TextString{s}; + break; + } + case ::KMIP_TYPE_BYTE_STRING: { + std::vector v( + data.begin() + offset, data.begin() + offset + length + ); + elem->value = ByteString{v}; + break; + } + case ::KMIP_TYPE_DATE_TIME: { + if (length != 8) { + throw KmipException("Invalid length for DateTime"); + } + uint64_t raw = ((uint64_t) data[offset] << 56) | + ((uint64_t) data[offset + 1] << 48) | + ((uint64_t) data[offset + 2] << 40) | + ((uint64_t) data[offset + 3] << 32) | + ((uint64_t) data[offset + 4] << 24) | + ((uint64_t) data[offset + 5] << 16) | + ((uint64_t) data[offset + 6] << 8) | + (uint64_t) data[offset + 7]; + int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = DateTime{val}; + break; + } + case ::KMIP_TYPE_INTERVAL: { + if (length != 4) { + throw KmipException("Invalid length for Interval"); + } + uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]; + elem->value = Interval{raw}; + break; + } + case ::KMIP_TYPE_BIG_INTEGER: { + std::vector v( + data.begin() + offset, data.begin() + offset + length + ); + elem->value = BigInteger{v}; + break; + } + case ::KMIP_TYPE_DATE_TIME_EXTENDED: { + // KMIP 2.0: microseconds since Unix epoch, 8-byte big-endian int64. + if (length != 8) { + throw KmipException("Invalid length for DateTimeExtended"); + } + uint64_t raw = ((uint64_t) data[offset] << 56) | + ((uint64_t) data[offset + 1] << 48) | + ((uint64_t) data[offset + 2] << 40) | + ((uint64_t) data[offset + 3] << 32) | + ((uint64_t) data[offset + 4] << 24) | + ((uint64_t) data[offset + 5] << 16) | + ((uint64_t) data[offset + 6] << 8) | + (uint64_t) data[offset + 7]; + int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = DateTimeExtended{val}; + break; + } + default: + throw KmipException("Unknown type " + std::to_string(type)); + } + + offset += padded_length; + return elem; + } + } + + // Factory methods + std::shared_ptr Element::createStructure(Tag t) { + return std::make_shared(t, ::KMIP_TYPE_STRUCTURE, Structure{}); + } + std::shared_ptr Element::createInteger(Tag t, int32_t v) { + return std::make_shared(t, ::KMIP_TYPE_INTEGER, Integer{v}); + } + std::shared_ptr Element::createLongInteger(Tag t, int64_t v) { + return std::make_shared( + t, ::KMIP_TYPE_LONG_INTEGER, LongInteger{v} + ); + } + std::shared_ptr Element::createBoolean(Tag t, bool v) { + return std::make_shared(t, ::KMIP_TYPE_BOOLEAN, Boolean{v}); + } + std::shared_ptr Element::createEnumeration(Tag t, int32_t v) { + return std::make_shared( + t, ::KMIP_TYPE_ENUMERATION, Enumeration{v} + ); + } + std::shared_ptr + Element::createTextString(Tag t, const std::string &v) { + return std::make_shared(t, ::KMIP_TYPE_TEXT_STRING, TextString{v}); + } + std::shared_ptr + Element::createByteString(Tag t, const std::vector &v) { + return std::make_shared(t, ::KMIP_TYPE_BYTE_STRING, ByteString{v}); + } + std::shared_ptr Element::createDateTime(Tag t, int64_t v) { + return std::make_shared(t, ::KMIP_TYPE_DATE_TIME, DateTime{v}); + } + std::shared_ptr Element::createDateTimeExtended(Tag t, int64_t v) { + return std::make_shared( + t, ::KMIP_TYPE_DATE_TIME_EXTENDED, DateTimeExtended{v} + ); + } + std::shared_ptr Element::createInterval(Tag t, uint32_t v) { + return std::make_shared(t, ::KMIP_TYPE_INTERVAL, Interval{v}); + } + std::shared_ptr + Element::createBigInteger(Tag t, const std::vector &v) { + return std::make_shared(t, ::KMIP_TYPE_BIG_INTEGER, BigInteger{v}); + } + + // Helper accessors + Structure *Element::asStructure() { + return std::get_if(&value); + } + const Structure *Element::asStructure() const { + return std::get_if(&value); + } + + std::shared_ptr Structure::find(Tag tag) const { + for (const auto &item : items) { + if (item->tag == tag) { + return item; + } + } + return nullptr; + } + + std::vector> Structure::findAll(Tag tag) const { + std::vector> matches; + for (const auto &item : items) { + if (item->tag == tag) { + matches.push_back(item); + } + } + return matches; + } + + std::shared_ptr Element::getChild(Tag tag) const { + const auto *s = std::get_if(&value); + if (!s) { + return nullptr; + } + return s->find(tag); + } + + std::vector> Element::getChildren(Tag tag) const { + const auto *s = std::get_if(&value); + if (!s) { + return {}; + } + return s->findAll(tag); + } + + int32_t Element::toInt() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not Integer"); + } + + int64_t Element::toLong() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + if (auto *v = std::get_if(&value)) { + return v->value; + } + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not Long/DateTime/DateTimeExtended"); + } + + bool Element::toBool() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not Boolean"); + } + + std::string Element::toString() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not TextString"); + } + + std::vector Element::toBytes() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not ByteString/BigInteger"); + } + + int32_t Element::toEnum() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not Enumeration"); + } + +} // namespace kmipcore diff --git a/kmipcore/src/kmip_formatter.cpp b/kmipcore/src/kmip_formatter.cpp new file mode 100644 index 0000000..9503b30 --- /dev/null +++ b/kmipcore/src/kmip_formatter.cpp @@ -0,0 +1,532 @@ +#include "kmipcore/kmip_formatter.hpp" + +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/kmip_protocol.hpp" +#include "kmipcore/types.hpp" + +#include +#include +#include + +namespace kmipcore { + + namespace { + + [[nodiscard]] std::string indent(size_t level) { + return std::string(level * 2, ' '); + } + + [[nodiscard]] std::string format_hex_uint(uint64_t value, size_t width = 0) { + std::ostringstream oss; + oss << "0x" << std::uppercase << std::hex << std::setfill('0'); + if (width > 0) { + oss << std::setw(static_cast(width)); + } + oss << value; + return oss.str(); + } + + [[nodiscard]] std::string format_bytes_hex(std::span bytes) { + std::ostringstream oss; + oss << std::uppercase << std::hex << std::setfill('0'); + for (size_t i = 0; i < bytes.size(); ++i) { + if (i > 0) { + oss << ' '; + } + oss << std::setw(2) << static_cast(bytes[i]); + } + return oss.str(); + } + + [[nodiscard]] std::string quote_string(const std::string &value) { + std::ostringstream oss; + oss << '"'; + for (const char ch : value) { + switch (ch) { + case '\\': + oss << "\\\\"; + break; + case '"': + oss << "\\\""; + break; + case '\n': + oss << "\\n"; + break; + case '\r': + oss << "\\r"; + break; + case '\t': + oss << "\\t"; + break; + default: + oss << ch; + break; + } + } + oss << '"'; + return oss.str(); + } + + [[nodiscard]] std::string format_datetime(int64_t seconds) { + std::time_t t = static_cast(seconds); + std::tm tm_buf{}; +#if defined(_WIN32) + gmtime_s(&tm_buf, &t); +#else + gmtime_r(&t, &tm_buf); +#endif + std::ostringstream oss; + oss << std::put_time(&tm_buf, "%Y-%m-%dT%H:%M:%SZ") << " (" << seconds + << ')'; + return oss.str(); + } + + [[nodiscard]] const char *type_name(Type type) { + switch (type) { + case KMIP_TYPE_STRUCTURE: + return "Structure"; + case KMIP_TYPE_INTEGER: + return "Integer"; + case KMIP_TYPE_LONG_INTEGER: + return "LongInteger"; + case KMIP_TYPE_BIG_INTEGER: + return "BigInteger"; + case KMIP_TYPE_ENUMERATION: + return "Enumeration"; + case KMIP_TYPE_BOOLEAN: + return "Boolean"; + case KMIP_TYPE_TEXT_STRING: + return "TextString"; + case KMIP_TYPE_BYTE_STRING: + return "ByteString"; + case KMIP_TYPE_DATE_TIME: + return "DateTime"; + case KMIP_TYPE_INTERVAL: + return "Interval"; + case KMIP_TYPE_DATE_TIME_EXTENDED: + return "DateTimeExtended"; + default: + return "UnknownType"; + } + } + + [[nodiscard]] const char *tag_name(Tag tag) { + switch (tag) { + case KMIP_TAG_ATTRIBUTE: + return "Attribute"; + case KMIP_TAG_ATTRIBUTE_NAME: + return "AttributeName"; + case KMIP_TAG_ATTRIBUTE_VALUE: + return "AttributeValue"; + case KMIP_TAG_BATCH_COUNT: + return "BatchCount"; + case KMIP_TAG_BATCH_ITEM: + return "BatchItem"; + case KMIP_TAG_BATCH_ORDER_OPTION: + return "BatchOrderOption"; + case KMIP_TAG_COMPROMISE_OCCURRANCE_DATE: + return "CompromiseOccurrenceDate"; + case KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + return "CryptographicAlgorithm"; + case KMIP_TAG_CRYPTOGRAPHIC_LENGTH: + return "CryptographicLength"; + case KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK: + return "CryptographicUsageMask"; + case KMIP_TAG_KEY_BLOCK: + return "KeyBlock"; + case KMIP_TAG_KEY_FORMAT_TYPE: + return "KeyFormatType"; + case KMIP_TAG_KEY_MATERIAL: + return "KeyMaterial"; + case KMIP_TAG_KEY_VALUE: + return "KeyValue"; + case KMIP_TAG_LOCATED_ITEMS: + return "LocatedItems"; + case KMIP_TAG_MAXIMUM_ITEMS: + return "MaximumItems"; + case KMIP_TAG_MAXIMUM_RESPONSE_SIZE: + return "MaximumResponseSize"; + case KMIP_TAG_NAME: + return "Name"; + case KMIP_TAG_NAME_TYPE: + return "NameType"; + case KMIP_TAG_NAME_VALUE: + return "NameValue"; + case KMIP_TAG_OBJECT_GROUP: + return "ObjectGroup"; + case KMIP_TAG_OBJECT_TYPE: + return "ObjectType"; + case KMIP_TAG_OFFSET_ITEMS: + return "OffsetItems"; + case KMIP_TAG_OPERATION: + return "Operation"; + case KMIP_TAG_PROTOCOL_VERSION: + return "ProtocolVersion"; + case KMIP_TAG_PROTOCOL_VERSION_MAJOR: + return "ProtocolVersionMajor"; + case KMIP_TAG_PROTOCOL_VERSION_MINOR: + return "ProtocolVersionMinor"; + case KMIP_TAG_REQUEST_HEADER: + return "RequestHeader"; + case KMIP_TAG_REQUEST_MESSAGE: + return "RequestMessage"; + case KMIP_TAG_REQUEST_PAYLOAD: + return "RequestPayload"; + case KMIP_TAG_RESPONSE_HEADER: + return "ResponseHeader"; + case KMIP_TAG_RESPONSE_MESSAGE: + return "ResponseMessage"; + case KMIP_TAG_RESPONSE_PAYLOAD: + return "ResponsePayload"; + case KMIP_TAG_RESULT_MESSAGE: + return "ResultMessage"; + case KMIP_TAG_RESULT_REASON: + return "ResultReason"; + case KMIP_TAG_RESULT_STATUS: + return "ResultStatus"; + case KMIP_TAG_REVOKATION_MESSAGE: + return "RevocationMessage"; + case KMIP_TAG_REVOCATION_REASON: + return "RevocationReason"; + case KMIP_TAG_REVOCATION_REASON_CODE: + return "RevocationReasonCode"; + case KMIP_TAG_SECRET_DATA: + return "SecretData"; + case KMIP_TAG_SECRET_DATA_TYPE: + return "SecretDataType"; + case KMIP_TAG_STATE: + return "State"; + case KMIP_TAG_SYMMETRIC_KEY: + return "SymmetricKey"; + case KMIP_TAG_TEMPLATE_ATTRIBUTE: + return "TemplateAttribute"; + case KMIP_TAG_TIME_STAMP: + return "TimeStamp"; + case KMIP_TAG_UNIQUE_BATCH_ITEM_ID: + return "UniqueBatchItemId"; + case KMIP_TAG_UNIQUE_IDENTIFIER: + return "UniqueIdentifier"; + case KMIP_TAG_USERNAME: + return "Username"; + case KMIP_TAG_PASSWORD: + return "Password"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *operation_name(int32_t value) { + switch (value) { + case KMIP_OP_CREATE: + return "Create"; + case KMIP_OP_REGISTER: + return "Register"; + case KMIP_OP_LOCATE: + return "Locate"; + case KMIP_OP_GET: + return "Get"; + case KMIP_OP_GET_ATTRIBUTES: + return "GetAttributes"; + case KMIP_OP_GET_ATTRIBUTE_LIST: + return "GetAttributeList"; + case KMIP_OP_ACTIVATE: + return "Activate"; + case KMIP_OP_REVOKE: + return "Revoke"; + case KMIP_OP_DESTROY: + return "Destroy"; + case KMIP_OP_QUERY: + return "Query"; + case KMIP_OP_DISCOVER_VERSIONS: + return "DiscoverVersions"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *object_type_name(int32_t value) { + switch (value) { + case KMIP_OBJTYPE_CERTIFICATE: + return "Certificate"; + case KMIP_OBJTYPE_SYMMETRIC_KEY: + return "SymmetricKey"; + case KMIP_OBJTYPE_PUBLIC_KEY: + return "PublicKey"; + case KMIP_OBJTYPE_PRIVATE_KEY: + return "PrivateKey"; + case KMIP_OBJTYPE_SECRET_DATA: + return "SecretData"; + case KMIP_OBJTYPE_OPAQUE_OBJECT: + return "OpaqueObject"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *result_status_name(int32_t value) { + switch (value) { + case KMIP_STATUS_SUCCESS: + return "Success"; + case KMIP_STATUS_OPERATION_FAILED: + return "OperationFailed"; + case KMIP_STATUS_OPERATION_PENDING: + return "OperationPending"; + case KMIP_STATUS_OPERATION_UNDONE: + return "OperationUndone"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *crypto_algorithm_name(int32_t value) { + switch (value) { + case KMIP_CRYPTOALG_DES: + return "DES"; + case KMIP_CRYPTOALG_TRIPLE_DES: + return "3DES"; + case KMIP_CRYPTOALG_AES: + return "AES"; + case KMIP_CRYPTOALG_RSA: + return "RSA"; + case KMIP_CRYPTOALG_DSA: + return "DSA"; + case KMIP_CRYPTOALG_ECDSA: + return "ECDSA"; + case KMIP_CRYPTOALG_HMAC_SHA1: + return "HMAC-SHA1"; + case KMIP_CRYPTOALG_HMAC_SHA224: + return "HMAC-SHA224"; + case KMIP_CRYPTOALG_HMAC_SHA256: + return "HMAC-SHA256"; + case KMIP_CRYPTOALG_HMAC_SHA384: + return "HMAC-SHA384"; + case KMIP_CRYPTOALG_HMAC_SHA512: + return "HMAC-SHA512"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *name_type_name(int32_t value) { + switch (value) { + case KMIP_NAME_UNINTERPRETED_TEXT_STRING: + return "UninterpretedTextString"; + case KMIP_NAME_URI: + return "URI"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *key_format_type_name(int32_t value) { + switch (value) { + case KMIP_KEYFORMAT_RAW: + return "Raw"; + case KMIP_KEYFORMAT_OPAQUE: + return "Opaque"; + case KMIP_KEYFORMAT_PKCS1: + return "PKCS1"; + case KMIP_KEYFORMAT_PKCS8: + return "PKCS8"; + case KMIP_KEYFORMAT_X509: + return "X509"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *secret_data_type_name(int32_t value) { + switch (value) { + case PASSWORD: + return "Password"; + case SEED: + return "Seed"; + default: + return nullptr; + } + } + + [[nodiscard]] const char *revocation_reason_name(int32_t value) { + switch (value) { + case UNSPECIFIED: + return "Unspecified"; + case KEY_COMPROMISE: + return "KeyCompromise"; + case CA_COMPROMISE: + return "CACompromise"; + case AFFILIATION_CHANGED: + return "AffiliationChanged"; + case SUSPENDED: + return "Suspended"; + case CESSATION_OF_OPERATION: + return "CessationOfOperation"; + case PRIVILEDGE_WITHDRAWN: + return "PrivilegeWithdrawn"; + case REVOCATION_EXTENSIONS: + return "Extensions"; + default: + return nullptr; + } + } + + [[nodiscard]] std::string enum_value_name(Tag tag, int32_t value) { + const char *name = nullptr; + switch (tag) { + case KMIP_TAG_OPERATION: + name = operation_name(value); + break; + case KMIP_TAG_OBJECT_TYPE: + name = object_type_name(value); + break; + case KMIP_TAG_RESULT_STATUS: + name = result_status_name(value); + break; + case KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + name = crypto_algorithm_name(value); + break; + case KMIP_TAG_NAME_TYPE: + name = name_type_name(value); + break; + case KMIP_TAG_KEY_FORMAT_TYPE: + name = key_format_type_name(value); + break; + case KMIP_TAG_SECRET_DATA_TYPE: + name = secret_data_type_name(value); + break; + case KMIP_TAG_STATE: + name = state_to_string(value); + break; + case KMIP_TAG_REVOCATION_REASON_CODE: + name = revocation_reason_name(value); + break; + default: + break; + } + + if (name != nullptr) { + std::ostringstream oss; + oss << name << " (" << value << ')'; + return oss.str(); + } + + std::ostringstream oss; + oss << value << " / " << format_hex_uint(static_cast(value), 8); + return oss.str(); + } + + void format_element_impl( + const std::shared_ptr &element, + std::ostringstream &oss, + size_t depth + ) { + if (!element) { + oss << indent(depth) << "\n"; + return; + } + + const char *known_tag_name = tag_name(element->tag); + oss << indent(depth) + << (known_tag_name != nullptr ? known_tag_name : "UnknownTag") + << " (" << format_hex_uint(static_cast(element->tag), 6) + << ") [" << type_name(element->type) << ']'; + + if (const auto *structure = element->asStructure(); structure != nullptr) { + oss << '\n'; + if (structure->items.empty()) { + oss << indent(depth + 1) << "\n"; + } + for (const auto &child : structure->items) { + format_element_impl(child, oss, depth + 1); + } + return; + } + + oss << " = "; + switch (element->type) { + case KMIP_TYPE_INTEGER: + oss << element->toInt(); + break; + case KMIP_TYPE_LONG_INTEGER: + oss << element->toLong(); + break; + case KMIP_TYPE_BIG_INTEGER: { + const auto value = element->toBytes(); + oss << "len=" << value.size() << ", hex=[" + << format_bytes_hex(std::span(value.data(), value.size())) + << ']'; + break; + } + case KMIP_TYPE_ENUMERATION: + oss << enum_value_name(element->tag, element->toEnum()); + break; + case KMIP_TYPE_BOOLEAN: + oss << (element->toBool() ? "true" : "false"); + break; + case KMIP_TYPE_TEXT_STRING: + oss << quote_string(element->toString()); + break; + case KMIP_TYPE_BYTE_STRING: { + const auto value = element->toBytes(); + oss << "len=" << value.size() << ", hex=[" + << format_bytes_hex(std::span(value.data(), value.size())) + << ']'; + break; + } + case KMIP_TYPE_DATE_TIME: + oss << format_datetime(element->toLong()); + break; + case KMIP_TYPE_INTERVAL: + oss << element->toInt(); + break; + default: + oss << ""; + break; + } + oss << '\n'; + } + + } // namespace + + std::string format_element(const std::shared_ptr &element) { + std::ostringstream oss; + format_element_impl(element, oss, 0); + return oss.str(); + } + + std::string format_request(const RequestMessage &request) { + return format_element(request.toElement()); + } + + std::string format_response(const ResponseMessage &response) { + return format_element(response.toElement()); + } + + std::string format_ttlv(std::span ttlv) { + try { + if (ttlv.empty()) { + return "\n"; + } + + size_t offset = 0; + auto root = Element::deserialize(ttlv, offset); + auto formatted = format_element(root); + if (offset != ttlv.size()) { + std::ostringstream oss; + oss << formatted + << "Trailing bytes: " << (ttlv.size() - offset) << "\n"; + return oss.str(); + } + return formatted; + } catch (const std::exception &e) { + std::ostringstream oss; + oss << "Unable to format KMIP TTLV: " << e.what() << "\n" + << "Raw bytes: [" << format_bytes_hex(ttlv) << "]\n"; + return oss.str(); + } + } + +} // namespace kmipcore + + + diff --git a/kmipcore/src/kmip_payloads.cpp b/kmipcore/src/kmip_payloads.cpp new file mode 100644 index 0000000..ee20347 --- /dev/null +++ b/kmipcore/src/kmip_payloads.cpp @@ -0,0 +1,146 @@ +#include "kmipcore/kmip_protocol.hpp" + +namespace kmipcore { + + // === Attribute === + + Attribute::Attribute(const std::string &name, const std::string &value) + : name_(name), value_(value) {} + + std::shared_ptr Attribute::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + + structure->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), name_ + ) + ); + // Assuming simple TextString value for now. Real world is complex. + structure->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value_ + ) + ); + + return structure; + } + + Attribute Attribute::fromElement(std::shared_ptr element) { + if (!element || element->tag != static_cast(KMIP_TAG_ATTRIBUTE)) { + throw KmipException("Invalid Attribute element"); + } + + Attribute attr; + auto name = element->getChild(static_cast(KMIP_TAG_ATTRIBUTE_NAME)); + if (name) { + attr.name_ = name->toString(); + } + + auto val = element->getChild(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + if (val) { + attr.value_ = val->toString(); + } + + return attr; + } + + // === LocateRequestPayload === + + std::shared_ptr LocateRequestPayload::toElement() const { + auto structure = Element::createStructure( + static_cast(KMIP_TAG_REQUEST_PAYLOAD) + ); // or payload tag depending on usage + // Actually usually inserted into BatchItem with specific tag, or just as + // payload. Spec says Locate Request Payload is a Structure. + + if (maximumItems_ > 0) { + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_MAXIMUM_ITEMS), maximumItems_ + ) + ); + } + + if (offsetItems_ > 0) { + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_OFFSET_ITEMS), offsetItems_ + ) + ); + } + + for (const auto &attr : attributes_) { + // Tag for Attribute structure in list is Attribute (0x420008) + structure->asStructure()->add(attr.toElement()); + } + + return structure; + } + + LocateRequestPayload + LocateRequestPayload::fromElement(std::shared_ptr element) { + // Check if structure + // Iterate children + LocateRequestPayload req; + const auto *s = element->asStructure(); + if (!s) { + throw KmipException("Payload is not a structure"); + } + + for (const auto &child : s->items) { + if (child->tag == static_cast(KMIP_TAG_MAXIMUM_ITEMS)) { + req.maximumItems_ = child->toInt(); + } else if (child->tag == static_cast(KMIP_TAG_OFFSET_ITEMS)) { + req.offsetItems_ = child->toInt(); + } else if (child->tag == static_cast(KMIP_TAG_ATTRIBUTE)) { + req.attributes_.push_back(Attribute::fromElement(child)); + } + } + return req; + } + + // === LocateResponsePayload === + + std::shared_ptr LocateResponsePayload::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + + if (locatedItems_) { + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_LOCATED_ITEMS), *locatedItems_ + ) + ); + } + + for (const auto &id : uniqueIdentifiers_) { + // Each ID is TextString with tag UNIQUE_IDENTIFIER + structure->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), id + ) + ); + } + return structure; + } + + LocateResponsePayload + LocateResponsePayload::fromElement(std::shared_ptr element) { + LocateResponsePayload resp; + const auto *s = element->asStructure(); + if (!s) { + throw KmipException("Response Payload is not a structure"); + } + + for (const auto &child : s->items) { + if (child->tag == static_cast(KMIP_TAG_LOCATED_ITEMS)) { + resp.setLocatedItems(child->toInt()); + } else if (child->tag == static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)) { + resp.uniqueIdentifiers_.push_back(child->toString()); + } + } + return resp; + } + +} // namespace kmipcore diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp new file mode 100644 index 0000000..14d998c --- /dev/null +++ b/kmipcore/src/kmip_protocol.cpp @@ -0,0 +1,492 @@ +#include "kmipcore/kmip_protocol.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +namespace kmipcore { + + // === ProtocolVersion === + ProtocolVersion::ProtocolVersion(int32_t major, int32_t minor) + : major_(major), minor_(minor) {} + std::shared_ptr ProtocolVersion::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_PROTOCOL_VERSION_MAJOR), major_ + ) + ); + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_PROTOCOL_VERSION_MINOR), minor_ + ) + ); + return structure; + } + ProtocolVersion + ProtocolVersion::fromElement(std::shared_ptr element) { + if (!element || + element->tag != static_cast(KMIP_TAG_PROTOCOL_VERSION) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ProtocolVersion element"); + } + ProtocolVersion pv; + auto maj = + element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION_MAJOR)); + if (maj) { + pv.major_ = maj->toInt(); + } + auto min = + element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION_MINOR)); + if (min) { + pv.minor_ = min->toInt(); + } + return pv; + } + // === RequestHeader === + std::shared_ptr RequestHeader::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_HEADER)); + structure->asStructure()->add(protocolVersion_.toElement()); + if (maximumResponseSize_) { + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_MAXIMUM_RESPONSE_SIZE), + *maximumResponseSize_ + ) + ); + } + if (batchOrderOption_) { + structure->asStructure()->add( + Element::createBoolean( + static_cast(KMIP_TAG_BATCH_ORDER_OPTION), *batchOrderOption_ + ) + ); + } + if (timeStamp_) { + structure->asStructure()->add( + Element::createDateTime( + static_cast(KMIP_TAG_TIME_STAMP), *timeStamp_ + ) + ); + } + if (userName_ || password_) { + auto authentication = + Element::createStructure(static_cast(KMIP_TAG_AUTHENTICATION)); + auto credential = + Element::createStructure(static_cast(KMIP_TAG_CREDENTIAL)); + credential->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_CREDENTIAL_TYPE), + KMIP_CRED_USERNAME_AND_PASSWORD + ) + ); + + auto credential_value = + Element::createStructure(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + if (userName_) { + credential_value->asStructure()->add(Element::createTextString( + static_cast(KMIP_TAG_USERNAME), *userName_ + )); + } + if (password_) { + credential_value->asStructure()->add(Element::createTextString( + static_cast(KMIP_TAG_PASSWORD), *password_ + )); + } + + credential->asStructure()->add(credential_value); + authentication->asStructure()->add(credential); + structure->asStructure()->add(authentication); + } + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_BATCH_COUNT), batchCount_ + ) + ); + return structure; + } + RequestHeader RequestHeader::fromElement(std::shared_ptr element) { + if (!element || element->tag != static_cast(KMIP_TAG_REQUEST_HEADER) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid RequestHeader element"); + } + RequestHeader rh; + auto pv = element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + if (pv) { + rh.protocolVersion_ = ProtocolVersion::fromElement(pv); + } else { + throw KmipException("Missing ProtocolVersion in header"); + } + auto maxResponseSize = + element->getChild(static_cast(KMIP_TAG_MAXIMUM_RESPONSE_SIZE)); + if (maxResponseSize) { + rh.maximumResponseSize_ = maxResponseSize->toInt(); + } + auto timeStamp = element->getChild(static_cast(KMIP_TAG_TIME_STAMP)); + if (timeStamp) { + rh.timeStamp_ = timeStamp->toLong(); + } + auto batchOrderOption = + element->getChild(static_cast(KMIP_TAG_BATCH_ORDER_OPTION)); + if (batchOrderOption) { + rh.batchOrderOption_ = batchOrderOption->toBool(); + } + auto authentication = + element->getChild(static_cast(KMIP_TAG_AUTHENTICATION)); + if (authentication) { + auto credential = + authentication->getChild(static_cast(KMIP_TAG_CREDENTIAL)); + if (!credential) { + throw KmipException("Missing Credential in Authentication"); + } + + auto credentialType = + credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_TYPE)); + auto credentialValue = + credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + if (!credentialType || !credentialValue) { + throw KmipException("Invalid Credential in Authentication"); + } + + if (credentialType->toEnum() == KMIP_CRED_USERNAME_AND_PASSWORD) { + auto userName = + credentialValue->getChild(static_cast(KMIP_TAG_USERNAME)); + if (userName) { + rh.userName_ = userName->toString(); + } + + auto password = + credentialValue->getChild(static_cast(KMIP_TAG_PASSWORD)); + if (password) { + rh.password_ = password->toString(); + } + } + } + auto bc = element->getChild(static_cast(KMIP_TAG_BATCH_COUNT)); + if (bc) { + rh.batchCount_ = bc->toInt(); + } + return rh; + } + // === RequestBatchItem === + std::shared_ptr RequestBatchItem::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + structure->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OPERATION), operation_ + ) + ); + if (uniqueBatchItemId_ != 0) { + std::vector idBytes(sizeof(uniqueBatchItemId_)); + std::memcpy( + idBytes.data(), &uniqueBatchItemId_, sizeof(uniqueBatchItemId_) + ); + structure->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID), idBytes + ) + ); + } + if (requestPayload_) { + structure->asStructure()->add(requestPayload_); + } + return structure; + } + RequestBatchItem + RequestBatchItem::fromElement(std::shared_ptr element) { + if (!element || element->tag != static_cast(KMIP_TAG_BATCH_ITEM) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid RequestBatchItem element"); + } + RequestBatchItem rbi; + auto op = element->getChild(static_cast(KMIP_TAG_OPERATION)); + if (op) { + rbi.operation_ = op->toEnum(); + } else { + throw KmipException("Missing Operation"); + } + auto id = + element->getChild(static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID)); + if (id) { + auto bytes = id->toBytes(); + if (bytes.size() == sizeof(rbi.uniqueBatchItemId_)) { + std::memcpy( + &rbi.uniqueBatchItemId_, + bytes.data(), + sizeof(rbi.uniqueBatchItemId_) + ); + } + } + auto payload = + element->getChild(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + if (payload) { + rbi.requestPayload_ = payload; + } + return rbi; + } + // === RequestMessage === + RequestMessage::RequestMessage() + : RequestMessage(DEFAULT_PROTOCOL_VERSION, DEFAULT_MAX_RESPONSE_SIZE) {} + + RequestMessage::RequestMessage(int32_t protocolVersionMinor) + : RequestMessage(protocolVersionMinor, DEFAULT_MAX_RESPONSE_SIZE) {} + + RequestMessage::RequestMessage( + int32_t protocolVersionMinor, size_t maxResponseSize + ) { + setProtocolVersionMinor(protocolVersionMinor); + setMaxResponseSize(maxResponseSize); + } + + uint32_t RequestMessage::add_batch_item(RequestBatchItem item) { + const auto id = nextBatchItemId_++; + item.setUniqueBatchItemId(id); + batchItems_.push_back(std::move(item)); + return id; + } + + void RequestMessage::setBatchItems( + const std::vector &items + ) { + clearBatchItems(); + for (const auto &item : items) { + add_batch_item(item); + } + } + + void RequestMessage::setProtocolVersionMinor(int32_t minor) { + ProtocolVersion version = header_.getProtocolVersion(); + version.setMajor(1); + version.setMinor(minor); + header_.setProtocolVersion(version); + } + + int32_t RequestMessage::getProtocolVersionMinor() const { + return header_.getProtocolVersion().getMinor(); + } + + void RequestMessage::setMaxResponseSize(size_t size) { + header_.setMaximumResponseSize(static_cast(size)); + } + + size_t RequestMessage::getMaxResponseSize() const { + auto maxResponseSize = header_.getMaximumResponseSize(); + return maxResponseSize ? static_cast(*maxResponseSize) + : DEFAULT_MAX_RESPONSE_SIZE; + } + + std::vector RequestMessage::serialize() const { + if (batchItems_.empty()) { + throw KmipException("Cannot serialize RequestMessage with no batch items"); + } + + RequestMessage request(*this); + request.header_.setBatchCount( + static_cast(request.batchItems_.size()) + ); + if (!request.header_.getBatchOrderOption().has_value()) { + request.header_.setBatchOrderOption(true); + } + request.header_.setTimeStamp(static_cast(time(nullptr))); + + // Use SerializationBuffer for efficient serialization + SerializationBuffer buf(request.getMaxResponseSize()); + request.toElement()->serialize(buf); + return buf.release(); + } + + std::shared_ptr RequestMessage::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_MESSAGE)); + structure->asStructure()->add(header_.toElement()); + for (const auto &item : batchItems_) { + structure->asStructure()->add(item.toElement()); + } + return structure; + } + RequestMessage RequestMessage::fromElement(std::shared_ptr element) { + if (!element || + element->tag != static_cast(KMIP_TAG_REQUEST_MESSAGE) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid RequestMessage element"); + } + RequestMessage rm; + auto hdr = element->getChild(static_cast(KMIP_TAG_REQUEST_HEADER)); + if (hdr) { + rm.header_ = RequestHeader::fromElement(hdr); + } else { + throw KmipException("Missing Request Header"); + } + const auto *s = std::get_if(&element->value); + for (const auto &child : s->items) { + if (child->tag == static_cast(KMIP_TAG_BATCH_ITEM)) { + rm.batchItems_.push_back(RequestBatchItem::fromElement(child)); + } + } + rm.nextBatchItemId_ = static_cast(rm.batchItems_.size() + 1); + return rm; + } + // === ResponseHeader === + std::shared_ptr ResponseHeader::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_HEADER)); + structure->asStructure()->add(protocolVersion_.toElement()); + structure->asStructure()->add( + Element::createDateTime( + static_cast(KMIP_TAG_TIME_STAMP), timeStamp_ + ) + ); + structure->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_BATCH_COUNT), batchCount_ + ) + ); + return structure; + } + ResponseHeader ResponseHeader::fromElement(std::shared_ptr element) { + if (!element || + element->tag != static_cast(KMIP_TAG_RESPONSE_HEADER) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ResponseHeader element"); + } + ResponseHeader rh; + auto pv = element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + if (pv) { + rh.protocolVersion_ = ProtocolVersion::fromElement(pv); + } else { + throw KmipException("Missing ProtocolVersion"); + } + auto ts = element->getChild(static_cast(KMIP_TAG_TIME_STAMP)); + if (ts) { + rh.timeStamp_ = ts->toLong(); + } + auto bc = element->getChild(static_cast(KMIP_TAG_BATCH_COUNT)); + if (bc) { + rh.batchCount_ = bc->toInt(); + } + return rh; + } + // === ResponseBatchItem === + std::shared_ptr ResponseBatchItem::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + structure->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OPERATION), operation_ + ) + ); + if (uniqueBatchItemId_ != 0) { + std::vector idBytes(sizeof(uniqueBatchItemId_)); + std::memcpy( + idBytes.data(), &uniqueBatchItemId_, sizeof(uniqueBatchItemId_) + ); + structure->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID), idBytes + ) + ); + } + structure->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_RESULT_STATUS), resultStatus_ + ) + ); + if (resultReason_) { + structure->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_RESULT_REASON), *resultReason_ + ) + ); + } + if (resultMessage_) { + structure->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_RESULT_MESSAGE), *resultMessage_ + ) + ); + } + if (responsePayload_) { + structure->asStructure()->add(responsePayload_); + } + return structure; + } + ResponseBatchItem + ResponseBatchItem::fromElement(std::shared_ptr element) { + if (!element || element->tag != static_cast(KMIP_TAG_BATCH_ITEM) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ResponseBatchItem element"); + } + ResponseBatchItem rbi; + auto op = element->getChild(static_cast(KMIP_TAG_OPERATION)); + if (op) { + rbi.operation_ = op->toEnum(); + } else { + throw KmipException("Missing Operation"); + } + auto id = + element->getChild(static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID)); + if (id) { + auto bytes = id->toBytes(); + if (bytes.size() == sizeof(rbi.uniqueBatchItemId_)) { + std::memcpy( + &rbi.uniqueBatchItemId_, + bytes.data(), + sizeof(rbi.uniqueBatchItemId_) + ); + } + } + auto status = element->getChild(static_cast(KMIP_TAG_RESULT_STATUS)); + if (status) { + rbi.resultStatus_ = status->toEnum(); + } + auto reason = element->getChild(static_cast(KMIP_TAG_RESULT_REASON)); + if (reason) { + rbi.resultReason_ = reason->toEnum(); + } + auto msg = element->getChild(static_cast(KMIP_TAG_RESULT_MESSAGE)); + if (msg) { + rbi.resultMessage_ = msg->toString(); + } + auto payload = + element->getChild(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + if (payload) { + rbi.responsePayload_ = payload; + } + return rbi; + } + // === ResponseMessage === + std::shared_ptr ResponseMessage::toElement() const { + auto structure = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + structure->asStructure()->add(header_.toElement()); + for (const auto &item : batchItems_) { + structure->asStructure()->add(item.toElement()); + } + return structure; + } + ResponseMessage + ResponseMessage::fromElement(std::shared_ptr element) { + if (!element || + element->tag != static_cast(KMIP_TAG_RESPONSE_MESSAGE) || + element->type != ::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ResponseMessage element"); + } + ResponseMessage rm; + auto hdr = element->getChild(static_cast(KMIP_TAG_RESPONSE_HEADER)); + if (hdr) { + rm.header_ = ResponseHeader::fromElement(hdr); + } else { + throw KmipException("Missing Response Header"); + } + const auto *s = std::get_if(&element->value); + for (const auto &child : s->items) { + if (child->tag == static_cast(KMIP_TAG_BATCH_ITEM)) { + rm.batchItems_.push_back(ResponseBatchItem::fromElement(child)); + } + } + return rm; + } +} // namespace kmipcore diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp new file mode 100644 index 0000000..52f153d --- /dev/null +++ b/kmipcore/src/kmip_requests.cpp @@ -0,0 +1,370 @@ +#include "kmipcore/kmip_requests.hpp" + +namespace kmipcore { + + namespace detail { + std::shared_ptr make_text_attribute( + const std::string &attribute_name, const std::string &value + ) { + auto attribute = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attribute->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + ) + ); + auto attribute_value = Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + ); + attribute->asStructure()->add(attribute_value); + return attribute; + } + std::shared_ptr + make_enum_attribute(const std::string &attribute_name, int32_t value) { + auto attribute = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attribute->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + ) + ); + auto attribute_value = Element::createEnumeration( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + ); + attribute->asStructure()->add(attribute_value); + return attribute; + } + std::shared_ptr make_integer_attribute( + const std::string &attribute_name, int32_t value + ) { + auto attribute = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attribute->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + ) + ); + auto attribute_value = Element::createInteger( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + ); + attribute->asStructure()->add(attribute_value); + return attribute; + } + std::shared_ptr make_name_attribute(const std::string &value) { + auto attribute_value = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + attribute_value->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_NAME_VALUE), value + ) + ); + attribute_value->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_NAME_TYPE), + KMIP_NAME_UNINTERPRETED_TEXT_STRING + ) + ); + auto attribute = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attribute->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + ) + ); + attribute->asStructure()->add(attribute_value); + return attribute; + } + std::shared_ptr make_template_attribute( + const std::vector> &attributes + ) { + auto template_attribute = Element::createStructure( + static_cast(KMIP_TAG_TEMPLATE_ATTRIBUTE) + ); + for (const auto &attribute : attributes) { + template_attribute->asStructure()->add(attribute); + } + return template_attribute; + } + std::shared_ptr + make_key_value(const std::vector &bytes) { + auto key_value = + Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + key_value->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_KEY_MATERIAL), + std::vector(bytes.begin(), bytes.end()) + ) + ); + return key_value; + } + std::shared_ptr make_key_block( + int32_t key_format_type, + const std::vector &bytes, + std::optional algorithm, + std::optional cryptographic_length + ) { + auto key_block = + Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_KEY_FORMAT_TYPE), key_format_type + ) + ); + key_block->asStructure()->add(make_key_value(bytes)); + if (algorithm) { + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM), *algorithm + ) + ); + } + if (cryptographic_length) { + key_block->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_CRYPTOGRAPHIC_LENGTH), + *cryptographic_length + ) + ); + } + return key_block; + } + std::shared_ptr + make_symmetric_key(const std::vector &key_value) { + auto symmetric_key = + Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + symmetric_key->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_RAW, + key_value, + KMIP_CRYPTOALG_AES, + static_cast(key_value.size() * 8) + )); + return symmetric_key; + } + std::shared_ptr + make_secret_data(const secret_t &secret, int32_t secret_type) { + auto secret_data = + Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + secret_data->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_SECRET_DATA_TYPE), secret_type + ) + ); + secret_data->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_OPAQUE, secret, std::nullopt, std::nullopt + )); + return secret_data; + } + } // namespace detail + + // --------------------------------------------------------------------------- + // CreateSymmetricKeyRequest + // --------------------------------------------------------------------------- + CreateSymmetricKeyRequest::CreateSymmetricKeyRequest( + const std::string &name, const std::string &group + ) { + setOperation(KMIP_OP_CREATE); + + std::vector> attributes; + attributes.push_back( + detail::make_enum_attribute( + "Cryptographic Algorithm", KMIP_CRYPTOALG_AES + ) + ); + attributes.push_back( + detail::make_integer_attribute("Cryptographic Length", 256) + ); + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + ) + ); + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + payload->asStructure()->add(detail::make_template_attribute(attributes)); + setRequestPayload(payload); + } + + // --------------------------------------------------------------------------- + // RegisterSymmetricKeyRequest + // --------------------------------------------------------------------------- + RegisterSymmetricKeyRequest::RegisterSymmetricKeyRequest( + const std::string &name, + const std::string &group, + const std::vector &key_value + ) { + setOperation(KMIP_OP_REGISTER); + + std::vector> attributes; + attributes.push_back( + detail::make_enum_attribute( + "Cryptographic Algorithm", KMIP_CRYPTOALG_AES + ) + ); + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Length", static_cast(key_value.size() * 8) + ) + ); + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + ) + ); + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + payload->asStructure()->add(detail::make_template_attribute(attributes)); + payload->asStructure()->add(detail::make_symmetric_key(key_value)); + setRequestPayload(payload); + } + + // --------------------------------------------------------------------------- + // RegisterSecretRequest + // --------------------------------------------------------------------------- + RegisterSecretRequest::RegisterSecretRequest( + const std::string &name, + const std::string &group, + const secret_t &secret, + int32_t secret_type + ) { + setOperation(KMIP_OP_REGISTER); + + std::vector> attributes; + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + KMIP_CRYPTOMASK_DERIVE_KEY | KMIP_CRYPTOMASK_EXPORT + ) + ); + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + ) + ); + payload->asStructure()->add(detail::make_template_attribute(attributes)); + payload->asStructure()->add(detail::make_secret_data(secret, secret_type)); + setRequestPayload(payload); + } + + // --------------------------------------------------------------------------- + // LocateRequest + // --------------------------------------------------------------------------- + LocateRequest::LocateRequest( + bool locate_by_group, + const std::string &name, + int32_t object_type, + size_t max_items, + size_t offset + ) { + setOperation(KMIP_OP_LOCATE); + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + if (max_items > 0) { + payload->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_MAXIMUM_ITEMS), + static_cast(max_items) + ) + ); + } + if (offset > 0) { + payload->asStructure()->add( + Element::createInteger( + static_cast(KMIP_TAG_OFFSET_ITEMS), + static_cast(offset) + ) + ); + } + + payload->asStructure()->add( + detail::make_enum_attribute("Object Type", object_type) + ); + if (!name.empty()) { + if (locate_by_group) { + payload->asStructure()->add( + detail::make_text_attribute("Object Group", name) + ); + } else { + payload->asStructure()->add(detail::make_name_attribute(name)); + } + } + setRequestPayload(payload); + } + + // --------------------------------------------------------------------------- + // RevokeRequest + // --------------------------------------------------------------------------- + RevokeRequest::RevokeRequest( + const std::string &unique_id, + int32_t reason, + const std::string &message, + time_t occurrence_time + ) { + setOperation(KMIP_OP_REVOKE); + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + ) + ); + + auto revocation_reason = + Element::createStructure(static_cast(KMIP_TAG_REVOCATION_REASON)); + revocation_reason->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_REVOCATION_REASON_CODE), reason + ) + ); + if (!message.empty()) { + revocation_reason->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_REVOKATION_MESSAGE), message + ) + ); + } + payload->asStructure()->add(revocation_reason); + + if (occurrence_time > 0) { + payload->asStructure()->add( + Element::createDateTime( + static_cast(KMIP_TAG_COMPROMISE_OCCURRANCE_DATE), + static_cast(occurrence_time) + ) + ); + } + setRequestPayload(payload); + } + + +} // namespace kmipcore diff --git a/kmipcore/src/kmip_responses.cpp b/kmipcore/src/kmip_responses.cpp new file mode 100644 index 0000000..7562335 --- /dev/null +++ b/kmipcore/src/kmip_responses.cpp @@ -0,0 +1,119 @@ +#include "kmipcore/kmip_responses.hpp" + +namespace kmipcore { + + namespace { + + std::shared_ptr get_object_element_for_type( + const std::shared_ptr &payload, int32_t objectType + ) { + switch (objectType) { + case KMIP_OBJTYPE_SYMMETRIC_KEY: + return payload->getChild(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + case KMIP_OBJTYPE_SECRET_DATA: + return payload->getChild(static_cast(KMIP_TAG_SECRET_DATA)); + case KMIP_OBJTYPE_PRIVATE_KEY: + return payload->getChild(static_cast(KMIP_TAG_PRIVATE_KEY)); + case KMIP_OBJTYPE_PUBLIC_KEY: + return payload->getChild(static_cast(KMIP_TAG_PUBLIC_KEY)); + default: + return {}; + } + } + + } // namespace + + // --- GetResponseBatchItem --- + + GetResponseBatchItem + GetResponseBatchItem::fromBatchItem(const ResponseBatchItem &item) { + detail::expect_operation(item, KMIP_OP_GET, "GetResponseBatchItem"); + + GetResponseBatchItem result(item); + + auto payload = + detail::require_response_payload(item, "GetResponseBatchItem"); + + auto uniqueIdentifier = + payload->getChild(static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)); + if (!uniqueIdentifier) { + throw KmipException( + "GetResponseBatchItem: missing unique identifier in response payload" + ); + } + result.uniqueIdentifier_ = uniqueIdentifier->toString(); + + auto objectType = payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + if (!objectType) { + throw KmipException( + "GetResponseBatchItem: missing object type in response payload" + ); + } + + result.objectType_ = objectType->toEnum(); + result.objectElement_ = + get_object_element_for_type(payload, result.objectType_); + if (!result.objectElement_) { + throw KmipException( + "GetResponseBatchItem: missing object payload for object type" + ); + } + + return result; + } + + // --- GetAttributesResponseBatchItem --- + + GetAttributesResponseBatchItem GetAttributesResponseBatchItem::fromBatchItem( + const ResponseBatchItem &item + ) { + detail::expect_operation( + item, KMIP_OP_GET_ATTRIBUTES, "GetAttributesResponseBatchItem" + ); + + GetAttributesResponseBatchItem result(item); + auto payload = detail::require_response_payload( + item, "GetAttributesResponseBatchItem" + ); + result.attributes_ = + payload->getChildren(static_cast(KMIP_TAG_ATTRIBUTE)); + return result; + } + + // --- GetAttributeListResponseBatchItem --- + + GetAttributeListResponseBatchItem + GetAttributeListResponseBatchItem::fromBatchItem( + const ResponseBatchItem &item + ) { + detail::expect_operation( + item, KMIP_OP_GET_ATTRIBUTE_LIST, "GetAttributeListResponseBatchItem" + ); + + GetAttributeListResponseBatchItem result(item); + auto payload = detail::require_response_payload( + item, "GetAttributeListResponseBatchItem" + ); + + for (const auto &attributeName : + payload->getChildren(static_cast(KMIP_TAG_ATTRIBUTE_NAME))) { + result.attributeNames_.push_back(attributeName->toString()); + } + + return result; + } + + // --- LocateResponseBatchItem --- + + LocateResponseBatchItem + LocateResponseBatchItem::fromBatchItem(const ResponseBatchItem &item) { + detail::expect_operation(item, KMIP_OP_LOCATE, "LocateResponseBatchItem"); + + LocateResponseBatchItem result(item); + result.locatePayload_ = LocateResponsePayload::fromElement( + detail::require_response_payload(item, "LocateResponseBatchItem") + ); + return result; + } + +} // namespace kmipcore diff --git a/kmipcore/src/response_parser.cpp b/kmipcore/src/response_parser.cpp new file mode 100644 index 0000000..0788f0d --- /dev/null +++ b/kmipcore/src/response_parser.cpp @@ -0,0 +1,149 @@ +#include "kmipcore/response_parser.hpp" + +#include + +namespace kmipcore { + + ResponseParser::ResponseParser(std::span responseBytes) + : responseBytes_(responseBytes.begin(), responseBytes.end()) {} + + size_t ResponseParser::getBatchItemCount() { + ensureParsed(); + return responseMessage_.getBatchItems().size(); + } + + bool ResponseParser::isSuccess(int itemIdx) { + return getResponseItem(itemIdx).getResultStatus() == KMIP_STATUS_SUCCESS; + } + + OperationResult ResponseParser::getOperationResult(int itemIdx) { + const auto &item = getResponseItem(itemIdx); + return OperationResult{ + item.getOperation(), + item.getResultStatus(), + item.getResultReason().value_or(0), + item.getResultMessage().value_or("") + }; + } + + OperationResult + ResponseParser::getOperationResultByBatchItemId(uint32_t batchItemId) { + const auto &item = getResponseItemByBatchItemId(batchItemId); + return OperationResult{ + item.getOperation(), + item.getResultStatus(), + item.getResultReason().value_or(0), + item.getResultMessage().value_or("") + }; + } + + void ResponseParser::parseResponse() { + if (responseBytes_.empty()) { + throw KmipException("Empty response from the server."); + } + + size_t offset = 0; + auto root = Element::deserialize( + std::span(responseBytes_.data(), responseBytes_.size()), + offset + ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + if (offset != responseBytes_.size()) { + throw KmipException("Trailing bytes found after KMIP response message."); + } + + responseMessage_ = ResponseMessage::fromElement(root); + isParsed_ = true; + } + + const ResponseBatchItem &ResponseParser::getResponseItem(int itemIdx) { + ensureParsed(); + + const auto &items = responseMessage_.getBatchItems(); + if (items.empty()) { + throw KmipException("No response batch items from the server."); + } + if (itemIdx < 0 || static_cast(itemIdx) >= items.size()) { + throw KmipException("Response batch item index is out of range."); + } + + return items[static_cast(itemIdx)]; + } + + const ResponseBatchItem & + ResponseParser::getResponseItemByBatchItemId(uint32_t batchItemId) { + ensureParsed(); + + const auto &items = responseMessage_.getBatchItems(); + for (const auto &item : items) { + if (item.getUniqueBatchItemId() == batchItemId) { + return item; + } + } + + throw KmipException("Response batch item id was not found."); + } + + void ResponseParser::ensureSuccess(const ResponseBatchItem &item) { + if (item.getResultStatus() != KMIP_STATUS_SUCCESS) { + throw KmipException(formatOperationResult(item)); + } + } + + std::string + ResponseParser::formatOperationResult(const ResponseBatchItem &value) { + OperationResult result = { + value.getOperation(), + value.getResultStatus(), + value.getResultReason().value_or(0), + value.getResultMessage().value_or("") + }; + + std::ostringstream stream; + stream << "Message: " << result.resultMessage + << "\nOperation: " << operationToString(result.operation) + << "; Result status: " << resultStatusToString(result.resultStatus) + << "; Result reason: " << result.resultReason; + return stream.str(); + } + + const char *ResponseParser::operationToString(int32_t operation) { + switch (operation) { + case KMIP_OP_CREATE: + return "Create"; + case KMIP_OP_REGISTER: + return "Register"; + case KMIP_OP_GET: + return "Get"; + case KMIP_OP_GET_ATTRIBUTES: + return "Get Attributes"; + case KMIP_OP_ACTIVATE: + return "Activate"; + case KMIP_OP_DESTROY: + return "Destroy"; + case KMIP_OP_LOCATE: + return "Locate"; + case KMIP_OP_REVOKE: + return "Revoke"; + case KMIP_OP_GET_ATTRIBUTE_LIST: + return "Get Attribute List"; + default: + return "Unknown"; + } + } + + const char *ResponseParser::resultStatusToString(int32_t status) { + switch (status) { + case KMIP_STATUS_SUCCESS: + return "Success"; + case KMIP_STATUS_OPERATION_FAILED: + return "Operation Failed"; + case KMIP_STATUS_OPERATION_PENDING: + return "Operation Pending"; + case KMIP_STATUS_OPERATION_UNDONE: + return "Operation Undone"; + default: + return "Unknown"; + } + } + +} // namespace kmipcore diff --git a/kmipcore/src/serialization_buffer.cpp b/kmipcore/src/serialization_buffer.cpp new file mode 100644 index 0000000..ef3d3b7 --- /dev/null +++ b/kmipcore/src/serialization_buffer.cpp @@ -0,0 +1,119 @@ +#include "kmipcore/serialization_buffer.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include + +namespace kmipcore { + +SerializationBuffer::SerializationBuffer(size_t initial_capacity) + : current_offset_(0) { + // Pre-allocate to requested capacity + // This single allocation covers most common messages + buffer_.reserve(initial_capacity); +} + +void SerializationBuffer::writeByte(uint8_t value) { + ensureSpace(1); + + // Ensure buffer is large enough (resize if needed) + if (current_offset_ >= buffer_.size()) { + buffer_.resize(current_offset_ + 1); + } + + buffer_[current_offset_++] = value; +} + +void SerializationBuffer::writeBytes(const uint8_t* data, size_t length) { + if (!data || length == 0) return; + + ensureSpace(length); + + // Ensure buffer is large enough (resize to accommodate) + if (current_offset_ + length > buffer_.size()) { + buffer_.resize(current_offset_ + length); + } + + std::memcpy(&buffer_[current_offset_], data, length); + current_offset_ += length; +} + +void SerializationBuffer::writePadded(const uint8_t* data, size_t length) { + // Write data first + writeBytes(data, length); + + // Calculate KMIP padding (align to TTLV_ALIGNMENT bytes) + // Formula: (A - (size % A)) % A gives 0 for multiples of A, otherwise 1..A-1 + size_t padding = (TTLV_ALIGNMENT - (length % TTLV_ALIGNMENT)) % TTLV_ALIGNMENT; + + // Write zero-fill padding + if (padding > 0) { + ensureSpace(padding); + + // Ensure buffer is large enough + if (current_offset_ + padding > buffer_.size()) { + buffer_.resize(current_offset_ + padding); + } + + // Zero-fill padding + std::memset(&buffer_[current_offset_], 0, padding); + current_offset_ += padding; + } +} + +void SerializationBuffer::ensureSpace(size_t required_bytes) { + // Check if we have enough capacity + if (current_offset_ + required_bytes <= buffer_.capacity()) { + return; // No reallocation needed + } + + // Need to expand + expandCapacity(current_offset_ + required_bytes); +} + +void SerializationBuffer::expandCapacity(size_t required) { + // Start with current capacity + size_t new_capacity = buffer_.capacity(); + + if (new_capacity == 0) { + new_capacity = MIN_CAPACITY; + } + + // Double capacity until we have enough + while (new_capacity < required) { + new_capacity *= 2; + } + + // Cap maximum to prevent pathological allocations + if (new_capacity > MAX_CAPACITY) { + throw KmipException( + "SerializationBuffer exceeded maximum size of 100 MB" + ); + } + + buffer_.reserve(new_capacity); +} + +std::vector SerializationBuffer::release() { + // Copy only the serialized bytes into the result. + // Use iterators instead of buffer_.data() + offset to avoid pointer + // arithmetic on a potentially-null data() when size()==0 (UB even for +0). + std::vector result(buffer_.begin(), buffer_.begin() + static_cast(current_offset_)); + + // Reset write position and logical size but KEEP the reserved capacity so + // the buffer can be reused immediately without a new heap allocation. + // Callers that need to reclaim memory explicitly can call freeMemory(). + buffer_.clear(); // size -> 0, capacity unchanged + current_offset_ = 0; + + return result; // NRVO / move +} + +void SerializationBuffer::freeMemory() { + // Aggressively release all heap memory (capacity included). + // Use the swap-with-empty idiom because shrink_to_fit() is advisory. + std::vector().swap(buffer_); + current_offset_ = 0; +} + +} // namespace kmipcore + diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp new file mode 100644 index 0000000..b225a18 --- /dev/null +++ b/kmipcore/tests/test_core.cpp @@ -0,0 +1,467 @@ +#include +#include +#include +#include +#include +#include +using namespace kmipcore; +void test_integer() { + auto elem = + Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 12345); + SerializationBuffer buf_i; + elem->serialize(buf_i); + auto data = buf_i.release(); + assert(data.size() == 16); + size_t offset = 0; + auto decoded = Element::deserialize(data, offset); + assert(offset == 16); + assert(decoded->tag == static_cast(KMIP_TAG_ACTIVATION_DATE)); + assert(decoded->type == ::KMIP_TYPE_INTEGER); + assert(std::get(decoded->value).value == 12345); + std::cout << "Integer test passed" << std::endl; +} +void test_structure() { + auto root = + Element::createStructure(static_cast(KMIP_TAG_APPLICATION_DATA)); + auto child1 = Element::createInteger( + static_cast(KMIP_TAG_APPLICATION_NAMESPACE), 10 + ); + auto child2 = Element::createBoolean( + static_cast(KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION), true + ); + std::get(root->value).add(child1); + std::get(root->value).add(child2); + SerializationBuffer buf_s; + root->serialize(buf_s); + auto data = buf_s.release(); + size_t offset = 0; + auto decoded = Element::deserialize(data, offset); + assert(decoded->tag == static_cast(KMIP_TAG_APPLICATION_DATA)); + assert(decoded->type == ::KMIP_TYPE_STRUCTURE); + auto &s = std::get(decoded->value); + assert(s.items.size() == 2); + auto d1 = s.items[0]; + assert(d1->tag == static_cast(KMIP_TAG_APPLICATION_NAMESPACE)); + assert(std::get(d1->value).value == 10); + auto d2 = s.items[1]; + assert( + d2->tag == static_cast(KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION) + ); + assert(std::get(d2->value).value == true); + std::cout << "Structure test passed" << std::endl; +} +void test_request_message() { + RequestMessage req; + req.getHeader().getProtocolVersion().setMajor(1); + req.getHeader().getProtocolVersion().setMinor(4); + req.getHeader().setBatchOrderOption(true); + + RequestBatchItem item; + item.setOperation(KMIP_OP_GET); // Some operation code + // Fake payload + auto payload = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 999) + ); + item.setRequestPayload(payload); + auto first_id = req.add_batch_item(item); + + RequestBatchItem item2; + item2.setOperation(KMIP_OP_GET_ATTRIBUTE_LIST); + auto payload2 = + Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + payload2->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 111) + ); + item2.setRequestPayload(payload2); + auto second_id = req.add_batch_item(item2); + + assert(first_id == 1); + assert(second_id == 2); + assert(first_id != second_id); + + auto bytes = req.serialize(); + std::cout << "Serialized RequestMessage size: " << bytes.size() << std::endl; + size_t offset = 0; + auto deserialized = Element::deserialize(bytes, offset); + auto req2 = RequestMessage::fromElement(deserialized); + assert(req2.getHeader().getProtocolVersion().getMajor() == 1); + assert(req2.getHeader().getBatchOrderOption().has_value()); + assert(req2.getHeader().getBatchOrderOption().value() == true); + assert(req2.getBatchItems().size() == 2); + assert(req2.getBatchItems()[0].getUniqueBatchItemId() == 1u); + assert(req2.getBatchItems()[1].getUniqueBatchItemId() == 2u); + assert(req2.getBatchItems()[0].getOperation() == KMIP_OP_GET); + assert(req2.getBatchItems()[1].getOperation() == KMIP_OP_GET_ATTRIBUTE_LIST); + std::cout << "RequestMessage test passed" << std::endl; +} +void test_response_message() { + ResponseMessage resp; + resp.getHeader().getProtocolVersion().setMajor(1); + resp.getHeader().getProtocolVersion().setMinor(4); + resp.getHeader().setTimeStamp(1678886400); // 2023-03-15 or so + resp.getHeader().setBatchCount(2); + + ResponseBatchItem get_item; + get_item.setUniqueBatchItemId(0x01020304u); + get_item.setOperation(KMIP_OP_GET); + get_item.setResultStatus(KMIP_STATUS_SUCCESS); // Success + get_item.setResultMessage("OK"); + + auto get_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + get_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-get-1" + ) + ); + get_payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + auto symmetric_key = + Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + auto key_block = + Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_RAW + ) + ); + auto key_value = + Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + key_value->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_KEY_MATERIAL), {0x10, 0x11, 0x12, 0x13} + ) + ); + key_block->asStructure()->add(key_value); + symmetric_key->asStructure()->add(key_block); + get_payload->asStructure()->add(symmetric_key); + get_item.setResponsePayload(get_payload); + resp.add_batch_item(get_item); + + ResponseBatchItem locate_item; + locate_item.setOperation(KMIP_OP_LOCATE); + locate_item.setResultStatus(KMIP_STATUS_SUCCESS); + auto locate_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + locate_payload->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + ); + locate_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-locate-1" + ) + ); + locate_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-locate-2" + ) + ); + locate_item.setResponsePayload(locate_payload); + resp.add_batch_item(locate_item); + + auto elem = resp.toElement(); + SerializationBuffer buf_r; + elem->serialize(buf_r); + auto bytes = buf_r.release(); + size_t offset = 0; + auto deserialized = Element::deserialize(bytes, offset); + auto resp2 = ResponseMessage::fromElement(deserialized); + assert(resp2.getHeader().getTimeStamp() == 1678886400); + assert(resp2.getBatchItems().size() == 2); + assert(resp2.getBatchItems()[0].getResultStatus() == KMIP_STATUS_SUCCESS); + assert(*resp2.getBatchItems()[0].getResultMessage() == "OK"); + assert(resp2.getBatchItems()[1].getOperation() == KMIP_OP_LOCATE); + std::cout << "ResponseMessage test passed" << std::endl; +} +void test_typed_response_batch_items() { + auto create_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + create_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "create-id" + ) + ); + + ResponseBatchItem create_item; + create_item.setOperation(KMIP_OP_CREATE); + create_item.setResultStatus(KMIP_STATUS_SUCCESS); + create_item.setResponsePayload(create_payload); + + auto create_response = CreateResponseBatchItem::fromBatchItem(create_item); + assert(create_response.getUniqueIdentifier() == "create-id"); + + auto get_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + get_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "get-id" + ) + ); + get_payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + ) + ); + auto secret_data = + Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + auto key_block = + Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + auto key_value = + Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + key_value->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_KEY_MATERIAL), {0x61, 0x62} + ) + ); + key_block->asStructure()->add(key_value); + secret_data->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_SECRET_DATA_TYPE), PASSWORD + ) + ); + secret_data->asStructure()->add(key_block); + get_payload->asStructure()->add(secret_data); + + ResponseBatchItem get_item; + get_item.setOperation(KMIP_OP_GET); + get_item.setResultStatus(KMIP_STATUS_SUCCESS); + get_item.setResponsePayload(get_payload); + + auto get_response = GetResponseBatchItem::fromBatchItem(get_item); + assert(get_response.getUniqueIdentifier() == "get-id"); + assert(get_response.getObjectType() == KMIP_OBJTYPE_SECRET_DATA); + assert(get_response.getObjectElement() != nullptr); + + auto attributes_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + auto attribute = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attribute->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "State" + ) + ); + attribute->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), KMIP_STATE_ACTIVE + ) + ); + attributes_payload->asStructure()->add(attribute); + + ResponseBatchItem attributes_item; + attributes_item.setOperation(KMIP_OP_GET_ATTRIBUTES); + attributes_item.setResultStatus(KMIP_STATUS_SUCCESS); + attributes_item.setResponsePayload(attributes_payload); + + auto attributes_response = + GetAttributesResponseBatchItem::fromBatchItem(attributes_item); + assert(attributes_response.getAttributes().size() == 1); + + auto attribute_list_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + attribute_list_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + ) + ); + attribute_list_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "State" + ) + ); + + ResponseBatchItem attribute_list_item; + attribute_list_item.setOperation(KMIP_OP_GET_ATTRIBUTE_LIST); + attribute_list_item.setResultStatus(KMIP_STATUS_SUCCESS); + attribute_list_item.setResponsePayload(attribute_list_payload); + + auto attribute_list_response = + GetAttributeListResponseBatchItem::fromBatchItem(attribute_list_item); + assert(attribute_list_response.getAttributeNames().size() == 2); + assert(attribute_list_response.getAttributeNames()[0] == "Name"); + + auto locate_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + locate_payload->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + ); + locate_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "loc-1" + ) + ); + locate_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "loc-2" + ) + ); + + ResponseBatchItem locate_item; + locate_item.setOperation(KMIP_OP_LOCATE); + locate_item.setResultStatus(KMIP_STATUS_SUCCESS); + locate_item.setResponsePayload(locate_payload); + + auto locate_response = LocateResponseBatchItem::fromBatchItem(locate_item); + assert(locate_response.getLocatePayload().getLocatedItems().value() == 2); + assert(locate_response.getUniqueIdentifiers().size() == 2); + + ResponseBatchItem destroy_item; + destroy_item.setOperation(KMIP_OP_DESTROY); + destroy_item.setResultStatus(KMIP_STATUS_SUCCESS); + auto destroy_payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + destroy_payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "destroy-id" + ) + ); + destroy_item.setResponsePayload(destroy_payload); + + auto destroy_response = DestroyResponseBatchItem::fromBatchItem(destroy_item); + assert(destroy_response.getUniqueIdentifier() == "destroy-id"); + + std::cout << "Typed response batch item tests passed" << std::endl; +} +void test_locate_payload() { + LocateRequestPayload locReq; + locReq.setMaximumItems(10); + locReq.setOffsetItems(5); + locReq.addAttribute(Attribute("Name", "Key1")); + + auto elem = locReq.toElement(); + SerializationBuffer buf_l; + elem->serialize(buf_l); + auto bytes = buf_l.release(); + + size_t offset = 0; + auto deserialized = Element::deserialize(bytes, offset); + auto locReq2 = LocateRequestPayload::fromElement(deserialized); + + assert(locReq2.getMaximumItems() == 10); + assert(locReq2.getOffsetItems() == 5); + assert(locReq2.getAttributes().size() == 1); + assert(locReq2.getAttributes()[0].getName() == "Name"); + + std::cout << "Locate Payload test passed" << std::endl; +} + +void test_response_required_fields() { + { + // Missing ResponseHeader must be rejected. + auto response_message = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + auto batch_item = + Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + batch_item->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OPERATION), KMIP_OP_GET + ) + ); + batch_item->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_RESULT_STATUS), KMIP_STATUS_SUCCESS + ) + ); + response_message->asStructure()->add(batch_item); + + bool threw = false; + try { + (void) ResponseMessage::fromElement(response_message); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + } + + { + // Missing Operation inside ResponseBatchItem must be rejected. + auto response_message = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + + ResponseHeader header; + header.getProtocolVersion().setMajor(1); + header.getProtocolVersion().setMinor(4); + header.setTimeStamp(1234567890); + header.setBatchCount(1); + response_message->asStructure()->add(header.toElement()); + + auto batch_item = + Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + batch_item->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_RESULT_STATUS), KMIP_STATUS_SUCCESS + ) + ); + response_message->asStructure()->add(batch_item); + + bool threw = false; + try { + (void) ResponseMessage::fromElement(response_message); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + } + + std::cout << "Response required-fields validation test passed" << std::endl; +} + +void test_request_header_authentication() { + RequestHeader header; + header.getProtocolVersion().setMajor(1); + header.getProtocolVersion().setMinor(4); + header.setBatchCount(1); + header.setUserName(std::string("alice")); + header.setPassword(std::string("s3cr3t")); + + auto element = header.toElement(); + auto auth = element->getChild(static_cast(KMIP_TAG_AUTHENTICATION)); + assert(auth != nullptr); + + auto credential = auth->getChild(static_cast(KMIP_TAG_CREDENTIAL)); + assert(credential != nullptr); + + auto credential_type = + credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_TYPE)); + assert(credential_type != nullptr); + assert(credential_type->toEnum() == KMIP_CRED_USERNAME_AND_PASSWORD); + + auto credential_value = + credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + assert(credential_value != nullptr); + + auto username = + credential_value->getChild(static_cast(KMIP_TAG_USERNAME)); + assert(username != nullptr); + assert(username->toString() == "alice"); + + auto password = + credential_value->getChild(static_cast(KMIP_TAG_PASSWORD)); + assert(password != nullptr); + assert(password->toString() == "s3cr3t"); + + auto parsed = RequestHeader::fromElement(element); + assert(parsed.getUserName().has_value()); + assert(parsed.getPassword().has_value()); + assert(parsed.getUserName().value() == "alice"); + assert(parsed.getPassword().value() == "s3cr3t"); + + std::cout << "RequestHeader authentication test passed" << std::endl; +} + +int main() { + test_integer(); + test_structure(); + test_request_message(); + test_response_message(); + test_typed_response_batch_items(); + test_locate_payload(); + test_response_required_fields(); + test_request_header_authentication(); + return 0; +} diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp new file mode 100644 index 0000000..4ae4957 --- /dev/null +++ b/kmipcore/tests/test_parsers.cpp @@ -0,0 +1,432 @@ +#include "kmipcore/attributes_parser.hpp" +#include "kmipcore/kmip_formatter.hpp" +#include "kmipcore/key_parser.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_logger.hpp" +#include "kmipcore/kmip_requests.hpp" +#include "kmipcore/response_parser.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include + +using namespace kmipcore; + +namespace { + + class CollectingLogger : public Logger { + public: + [[nodiscard]] bool shouldLog(LogLevel level) const override { + return level == LogLevel::Debug; + } + + void log(const LogRecord &record) override { records.push_back(record); } + + std::vector records; + }; + +} // namespace + +// Helper to create a basic success response message with one item +std::vector create_mock_response_bytes( + int32_t operation, std::shared_ptr payload +) { + ResponseMessage resp; + resp.getHeader().getProtocolVersion().setMajor(1); + resp.getHeader().getProtocolVersion().setMinor(4); + resp.getHeader().setTimeStamp(1234567890); + resp.getHeader().setBatchCount(1); + + ResponseBatchItem item; + item.setUniqueBatchItemId(1); + item.setOperation(operation); + item.setResultStatus(KMIP_STATUS_SUCCESS); + if (payload) { + item.setResponsePayload(payload); + } + + resp.add_batch_item(item); + SerializationBuffer buf; + resp.toElement()->serialize(buf); + return buf.release(); +} + +void test_response_parser_create() { + auto payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1234" + ) + ); + + auto bytes = create_mock_response_bytes(KMIP_OP_CREATE, payload); + ResponseParser parser(bytes); + + assert(parser.getBatchItemCount() == 1); + assert(parser.isSuccess(0)); + + auto result = parser.getOperationResult(0); + assert(result.operation == KMIP_OP_CREATE); + assert(result.resultStatus == KMIP_STATUS_SUCCESS); + + auto create_resp = parser.getResponse(0); + assert(create_resp.getUniqueIdentifier() == "uuid-1234"); + + std::cout << "ResponseParser Create test passed" << std::endl; +} + +void test_response_parser_locate() { + auto payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + payload->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + ); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1" + ) + ); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-2" + ) + ); + + auto bytes = create_mock_response_bytes(KMIP_OP_LOCATE, payload); + ResponseParser parser(bytes); + + auto locate_resp = parser.getResponse(0); + assert(locate_resp.getLocatePayload().getUniqueIdentifiers().size() == 2); + assert(locate_resp.getUniqueIdentifiers()[0] == "uuid-1"); + assert(locate_resp.getUniqueIdentifiers()[1] == "uuid-2"); + + std::cout << "ResponseParser Locate test passed" << std::endl; +} + + +void test_key_parser_symmetric() { + // Construct a mock GetResponse with Symmetric Key + auto payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "key-id" + ) + ); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + + auto symmetric_key = + Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + auto key_block = + Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_RAW + ) + ); + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM), KMIP_CRYPTOALG_AES + ) + ); + + auto key_value = + Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + std::vector actual_key = {0xDE, 0xAD, 0xBE, 0xEF}; + key_value->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_KEY_MATERIAL), actual_key + ) + ); + + key_block->asStructure()->add(key_value); + symmetric_key->asStructure()->add(key_block); + payload->asStructure()->add(symmetric_key); + + ResponseBatchItem item; + item.setOperation(KMIP_OP_GET); + item.setResultStatus(KMIP_STATUS_SUCCESS); + item.setResponsePayload(payload); + + GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); + Key key = KeyParser::parseGetKeyResponse(get_resp); + + assert(key.algorithm() == KMIP_CRYPTOALG_AES); + assert(key.value() == actual_key); + + std::cout << "KeyParser Symmetric Key test passed" << std::endl; +} + +void test_key_parser_secret_binary() { + auto payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "secret-id" + ) + ); + payload->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + ) + ); + + auto secret_data = + Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + secret_data->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_SECRET_DATA_TYPE), PASSWORD + ) + ); + + auto key_block = + Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + key_block->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_OPAQUE + ) + ); + + auto key_value = + Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + const secret_t bytes = {'p', 'a', 's', 's', 0x00, 'x'}; + key_value->asStructure()->add( + Element::createByteString( + static_cast(KMIP_TAG_KEY_MATERIAL), + std::vector(bytes.begin(), bytes.end()) + ) + ); + key_block->asStructure()->add(key_value); + secret_data->asStructure()->add(key_block); + payload->asStructure()->add(secret_data); + + ResponseBatchItem item; + item.setOperation(KMIP_OP_GET); + item.setResultStatus(KMIP_STATUS_SUCCESS); + item.setResponsePayload(payload); + + GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); + Secret secret = KeyParser::parseGetSecretResponse(get_resp); + + assert(secret.secret_type == PASSWORD); + assert(secret.value == bytes); + assert(secret.as_text().size() == bytes.size()); + + std::cout << "KeyParser Secret Binary test passed" << std::endl; +} + +void test_register_secret_request_structure() { + const secret_t secret = {'a', 'b', 0x00, 'c'}; + RegisterSecretRequest req("s-name", "s-group", secret, PASSWORD); + + auto payload = req.getRequestPayload(); + assert(payload != nullptr); + + auto object_type = payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + assert(object_type != nullptr); + assert(object_type->toEnum() == KMIP_OBJTYPE_SECRET_DATA); + + auto secret_data = payload->getChild(static_cast(KMIP_TAG_SECRET_DATA)); + assert(secret_data != nullptr); + + auto secret_type = + secret_data->getChild(static_cast(KMIP_TAG_SECRET_DATA_TYPE)); + assert(secret_type != nullptr); + assert(secret_type->toEnum() == PASSWORD); + + auto key_block = secret_data->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + assert(key_block != nullptr); + + auto key_format = + key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + assert(key_format != nullptr); + assert(key_format->toEnum() == KMIP_KEYFORMAT_OPAQUE); + + // KMIP 1.4: Secret Data Key Block does not require algorithm/length. + assert( + key_block->getChild(static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM)) == + nullptr + ); + assert( + key_block->getChild(static_cast(KMIP_TAG_CRYPTOGRAPHIC_LENGTH)) == + nullptr + ); + + auto key_value = key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + assert(key_value != nullptr); + auto key_material = + key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + assert(key_material != nullptr); + auto parsed = key_material->toBytes(); + assert(parsed.size() == secret.size()); + assert(std::equal(parsed.begin(), parsed.end(), secret.begin())); + + std::cout << "RegisterSecretRequest structure test passed" << std::endl; +} + +void test_attributes_parser() { + std::vector> attributes; + + auto attr1 = Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attr1->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + ) + ); + attr1->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), "MyKey" + ) + ); + attributes.push_back(attr1); + + auto attr2 = Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attr2->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Cryptographic Length" + ) + ); + attr2->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_ATTRIBUTE_VALUE), 256) + ); + attributes.push_back(attr2); + + auto parsed_attrs = AttributesParser::parse(attributes); + + assert(parsed_attrs.count("Name")); + assert(parsed_attrs.at("Name") == "MyKey"); + + assert(parsed_attrs.count("Cryptographic Length")); + assert(parsed_attrs.at("Cryptographic Length") == "256"); + + std::cout << "AttributesParser test passed" << std::endl; +} + +void test_attributes_parser_extended() { + std::vector> attributes; + + // Test Date attribute + auto attr_date = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attr_date->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Activation Date" + ) + ); + attr_date->asStructure()->add( + Element::createDateTime( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), 1678886400 + ) + ); // 2023-03-15T13:20:00Z (approx) + attributes.push_back(attr_date); + + // Test Crypto Algorithm Enum + auto attr_alg = + Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + attr_alg->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Cryptographic Algorithm" + ) + ); + attr_alg->asStructure()->add( + Element::createEnumeration( + static_cast(KMIP_TAG_ATTRIBUTE_VALUE), KMIP_CRYPTOALG_AES + ) + ); + attributes.push_back(attr_alg); + + auto parsed_attrs = AttributesParser::parse(attributes); + + assert(parsed_attrs.count("Activation Date")); + std::string date_str = parsed_attrs.at("Activation Date"); + assert(date_str.find("2023-03-15") != std::string::npos); + + assert(parsed_attrs.count("Cryptographic Algorithm")); + assert(parsed_attrs.at("Cryptographic Algorithm") == "AES"); + + std::cout << "AttributesParser Extended test passed" << std::endl; +} + +void test_formatter_for_request_and_response() { + RequestMessage request; + request.add_batch_item(GetRequest("request-id-123")); + + auto formatted_request = format_request(request); + assert(formatted_request.find("RequestMessage") != std::string::npos); + assert(formatted_request.find("Operation") != std::string::npos); + assert(formatted_request.find("Get") != std::string::npos); + assert(formatted_request.find("request-id-123") != std::string::npos); + + auto payload = + Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + payload->asStructure()->add( + Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + ); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1" + ) + ); + payload->asStructure()->add( + Element::createTextString( + static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-2" + ) + ); + + auto bytes = create_mock_response_bytes(KMIP_OP_LOCATE, payload); + auto formatted_response = format_ttlv(bytes); + assert(formatted_response.find("ResponseMessage") != std::string::npos); + assert(formatted_response.find("Locate") != std::string::npos); + assert(formatted_response.find("uuid-1") != std::string::npos); + assert(formatted_response.find("uuid-2") != std::string::npos); + + std::cout << "KMIP formatter test passed" << std::endl; +} + +void test_logger_interface() { + CollectingLogger logger; + assert(logger.shouldLog(LogLevel::Debug)); + assert(!logger.shouldLog(LogLevel::Info)); + + logger.log(LogRecord{ + .level = LogLevel::Debug, + .component = "kmip.protocol", + .event = "request", + .message = "formatted ttlv" + }); + + assert(logger.records.size() == 1); + assert(logger.records[0].level == LogLevel::Debug); + assert(logger.records[0].component == "kmip.protocol"); + assert(logger.records[0].event == "request"); + assert(logger.records[0].message == "formatted ttlv"); + assert(std::string(to_string(LogLevel::Debug)) == "DEBUG"); + + std::cout << "Logger interface test passed" << std::endl; +} + +int main() { + test_response_parser_create(); + test_response_parser_locate(); + test_key_parser_symmetric(); + test_key_parser_secret_binary(); + test_register_secret_request_structure(); + test_attributes_parser(); + test_attributes_parser_extended(); + test_formatter_for_request_and_response(); + test_logger_interface(); + return 0; +} diff --git a/kmipcore/tests/test_serialization_buffer.cpp b/kmipcore/tests/test_serialization_buffer.cpp new file mode 100644 index 0000000..79ddbe2 --- /dev/null +++ b/kmipcore/tests/test_serialization_buffer.cpp @@ -0,0 +1,249 @@ +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include +#include + +using namespace kmipcore; + +// Throws std::runtime_error with file/line context on failure. +// Unlike assert(), this propagates through the try/catch in main() so every +// test failure is reported cleanly instead of calling abort(). +#define EXPECT(cond) \ + do { \ + if (!(cond)) { \ + throw std::runtime_error( \ + std::string(__FILE__) + ":" + std::to_string(__LINE__) + \ + ": expectation failed: " #cond); \ + } \ + } while (false) + +void testWriteByte() { + SerializationBuffer buf(100); + + buf.writeByte(0xAB); + buf.writeByte(0xCD); + buf.writeByte(0xEF); + + EXPECT(buf.size() == 3); + EXPECT(buf.data()[0] == 0xAB); + EXPECT(buf.data()[1] == 0xCD); + EXPECT(buf.data()[2] == 0xEF); + + std::cout << "✓ testWriteByte passed" << std::endl; +} + +void testWriteBytes() { + SerializationBuffer buf(100); + + uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + buf.writeBytes(data, 5); + + EXPECT(buf.size() == 5); + EXPECT(std::memcmp(buf.data(), data, 5) == 0); + + std::cout << "✓ testWriteBytes passed" << std::endl; +} + +void testWritePadded() { + SerializationBuffer buf(100); + + // Write 3 bytes (should add 5 bytes of padding to reach 8) + uint8_t data[] = {0x01, 0x02, 0x03}; + buf.writePadded(data, 3); + + EXPECT(buf.size() == 8); + EXPECT(buf.data()[0] == 0x01); + EXPECT(buf.data()[1] == 0x02); + EXPECT(buf.data()[2] == 0x03); + EXPECT(buf.data()[3] == 0x00); // Padding + EXPECT(buf.data()[4] == 0x00); // Padding + EXPECT(buf.data()[5] == 0x00); // Padding + EXPECT(buf.data()[6] == 0x00); // Padding + EXPECT(buf.data()[7] == 0x00); // Padding + + std::cout << "✓ testWritePadded passed" << std::endl; +} + +void testMultiplePaddedWrites() { + SerializationBuffer buf(100); + + // 3 bytes -> 8 bytes padded + uint8_t data1[] = {0x01, 0x02, 0x03}; + buf.writePadded(data1, 3); + + // 2 bytes -> 8 bytes padded + uint8_t data2[] = {0x04, 0x05}; + buf.writePadded(data2, 2); + + EXPECT(buf.size() == 16); // 8 + 8 + + // First block (8 bytes) + EXPECT(buf.data()[0] == 0x01); + EXPECT(buf.data()[1] == 0x02); + EXPECT(buf.data()[2] == 0x03); + EXPECT(buf.data()[7] == 0x00); + + // Second block (8 bytes) + EXPECT(buf.data()[8] == 0x04); + EXPECT(buf.data()[9] == 0x05); + EXPECT(buf.data()[15] == 0x00); + + std::cout << "✓ testMultiplePaddedWrites passed" << std::endl; +} + +void testAutoExpansion() { + SerializationBuffer buf(10); // Small initial size + + EXPECT(buf.capacity() >= 10); + + // Write more than initial capacity + for (int i = 0; i < 50; ++i) { + buf.writeByte(static_cast(i & 0xFF)); + } + + EXPECT(buf.size() == 50); + EXPECT(buf.capacity() >= 50); + + // Verify data is correct + for (int i = 0; i < 50; ++i) { + EXPECT(buf.data()[i] == (i & 0xFF)); + } + + std::cout << "✓ testAutoExpansion passed" << std::endl; +} + +void testReset() { + SerializationBuffer buf(100); + + buf.writeByte(0xFF); + buf.writeByte(0xFF); + buf.writeByte(0xFF); + + EXPECT(buf.size() == 3); + + buf.reset(); + + EXPECT(buf.size() == 0); + EXPECT(buf.capacity() >= 100); // Capacity preserved + + // Can reuse the buffer + buf.writeByte(0xAA); + EXPECT(buf.size() == 1); + EXPECT(buf.data()[0] == 0xAA); + + std::cout << "✓ testReset passed" << std::endl; +} + +void testRelease() { + SerializationBuffer buf(100); + + uint8_t data[] = {0x11, 0x22, 0x33, 0x44, 0x55}; + buf.writeBytes(data, 5); + + EXPECT(buf.size() == 5); + + std::vector result = buf.release(); + + EXPECT(result.size() == 5); + EXPECT(result[0] == 0x11); + EXPECT(result[1] == 0x22); + EXPECT(result[2] == 0x33); + EXPECT(result[3] == 0x44); + EXPECT(result[4] == 0x55); + + // Buffer is reset but capacity is kept for reuse + EXPECT(buf.size() == 0); + EXPECT(buf.capacity() >= 100); + + std::cout << "✓ testRelease passed" << std::endl; +} + +void testRemaining() { + SerializationBuffer buf(100); + + EXPECT(buf.remaining() == 100); + + buf.writeByte(0xFF); + EXPECT(buf.remaining() == 99); + + for (int i = 0; i < 99; ++i) { + buf.writeByte(0xFF); + } + + EXPECT(buf.size() == 100); + EXPECT(buf.remaining() == 0); + + // Should auto-expand + buf.writeByte(0xFF); + EXPECT(buf.size() == 101); + EXPECT(buf.remaining() > 0); + + std::cout << "✓ testRemaining passed" << std::endl; +} + +void testLargeMessage() { + SerializationBuffer buf(8192); // Default KMIP buffer size + + // Simulate writing a large message + for (int i = 0; i < 1000; ++i) { + uint8_t data[] = { + static_cast((i >> 24) & 0xFF), + static_cast((i >> 16) & 0xFF), + static_cast((i >> 8) & 0xFF), + static_cast(i & 0xFF), + }; + buf.writePadded(data, 4); + } + + // Each write is 4 bytes + 4 bytes padding = 8 bytes + // 1000 writes = 8000 bytes + EXPECT(buf.size() == 8000); + + std::cout << "✓ testLargeMessage passed" << std::endl; +} + +void testConsecutiveAllocation() { + // Test 10 sequential buffers + for (int iteration = 0; iteration < 10; ++iteration) { + SerializationBuffer buf(512); + + for (int i = 0; i < 64; ++i) { + auto val = static_cast((iteration * 64 + i) & 0xFF); + buf.writeByte(val); + } + + EXPECT(buf.size() == 64); + + auto result = buf.release(); + EXPECT(result.size() == 64); + } + + std::cout << "✓ testConsecutiveAllocation passed" << std::endl; +} + +int main() { + std::cout << "Running SerializationBuffer tests...\n" << std::endl; + + try { + testWriteByte(); + testWriteBytes(); + testWritePadded(); + testMultiplePaddedWrites(); + testAutoExpansion(); + testReset(); + testRelease(); + testRemaining(); + testLargeMessage(); + testConsecutiveAllocation(); + + std::cout << "\n✅ All SerializationBuffer tests passed!" << std::endl; + return 0; + } catch (const std::exception &e) { + std::cerr << "❌ Test failed: " << e.what() << std::endl; + return 1; + } +} + From d818498f9e1ca34829ba8ce15d473cee8d1ada0b Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Wed, 25 Mar 2026 11:13:02 +0200 Subject: [PATCH 02/11] Fixes of review comments and other fixes small fixes and big fix of enum->enum class replacement, std::span, passing complex types by const&, fixing missing namespace, advanced KmipException, string_view usage --- kmipclient/CMakeLists.txt | 5 +- kmipclient/examples/example_get_all_ids.cpp | 4 +- kmipclient/examples/example_get_secret.cpp | 2 +- kmipclient/examples/example_locate.cpp | 4 +- .../examples/example_locate_by_group.cpp | 10 +- .../examples/example_register_secret.cpp | 2 +- kmipclient/examples/example_revoke.cpp | 2 +- kmipclient/include/kmipclient/Key.hpp | 6 +- kmipclient/include/kmipclient/Kmip.hpp | 4 +- kmipclient/include/kmipclient/KmipClient.hpp | 80 +- .../include/kmipclient/KmipClientPool.hpp | 2 +- .../include/kmipclient/KmipIOException.hpp | 2 +- kmipclient/include/kmipclient/NetClient.hpp | 8 +- .../include/kmipclient/NetClientOpenSSL.hpp | 6 +- kmipclient/include/kmipclient/types.hpp | 27 +- kmipclient/src/IOUtils.cpp | 24 +- kmipclient/src/IOUtils.hpp | 7 +- kmipclient/src/Key.cpp | 9 +- kmipclient/src/KmipClient.cpp | 65 +- kmipclient/src/KmipClientPool.cpp | 4 +- kmipclient/src/NetClientOpenSSL.cpp | 11 +- kmipclient/src/StringUtils.cpp | 17 +- kmipclient/src/StringUtils.hpp | 6 +- .../tests/KmipClientIntegrationTest.cpp | 28 +- .../tests/KmipClientPoolIntegrationTest.cpp | 2 +- kmipcore/include/kmipcore/key.hpp | 25 +- .../include/kmipcore/kmip_attribute_names.hpp | 42 +- kmipcore/include/kmipcore/kmip_basics.hpp | 37 +- kmipcore/include/kmipcore/kmip_enums.hpp | 749 ++++++++++++++++-- kmipcore/include/kmipcore/kmip_errors.hpp | 33 + kmipcore/include/kmipcore/kmip_logger.hpp | 3 +- kmipcore/include/kmipcore/kmip_requests.hpp | 16 +- kmipcore/include/kmipcore/kmip_responses.hpp | 2 +- kmipcore/include/kmipcore/secret.hpp | 54 +- .../include/kmipcore/serialization_buffer.hpp | 29 +- kmipcore/include/kmipcore/types.hpp | 23 +- kmipcore/src/attributes_parser.cpp | 71 +- kmipcore/src/key_parser.cpp | 34 +- kmipcore/src/kmip_basics.cpp | 235 +++--- kmipcore/src/kmip_errors.cpp | 268 +++++++ kmipcore/src/kmip_formatter.cpp | 60 +- kmipcore/src/kmip_payloads.cpp | 34 +- kmipcore/src/kmip_protocol.cpp | 142 ++-- kmipcore/src/kmip_requests.cpp | 88 +- kmipcore/src/kmip_responses.cpp | 16 +- kmipcore/src/response_parser.cpp | 11 +- kmipcore/src/serialization_buffer.cpp | 18 +- kmipcore/tests/test_core.cpp | 124 +-- kmipcore/tests/test_parsers.cpp | 110 +-- kmipcore/tests/test_serialization_buffer.cpp | 13 +- 50 files changed, 1785 insertions(+), 789 deletions(-) create mode 100644 kmipcore/include/kmipcore/kmip_errors.hpp create mode 100644 kmipcore/src/kmip_errors.cpp diff --git a/kmipclient/CMakeLists.txt b/kmipclient/CMakeLists.txt index e3bd9c3..4dc32be 100644 --- a/kmipclient/CMakeLists.txt +++ b/kmipclient/CMakeLists.txt @@ -30,7 +30,6 @@ target_include_directories( kmipclient PUBLIC $ $ - $ $ ) @@ -49,8 +48,8 @@ set_target_properties( option(WITH_ASAN "Enable Address Sanitizer" OFF) if(WITH_ASAN) - target_compile_options(kmipclient INTERFACE "-fsanitize=address") - target_link_options(kmipclient INTERFACE "-fsanitize=address") + target_compile_options(kmipclient PUBLIC "-fsanitize=address" "-fno-omit-frame-pointer") + target_link_options(kmipclient PUBLIC "-fsanitize=address") endif() export(TARGETS kmipcore kmipclient FILE "kmipclient.cmake") diff --git a/kmipclient/examples/example_get_all_ids.cpp b/kmipclient/examples/example_get_all_ids.cpp index 052416b..6ff502a 100644 --- a/kmipclient/examples/example_get_all_ids.cpp +++ b/kmipclient/examples/example_get_all_ids.cpp @@ -37,7 +37,7 @@ int main(int argc, char **argv) { KmipClient client(net_client); try { - const auto opt_ids = client.op_all(KMIP_OBJTYPE_SYMMETRIC_KEY); + const auto opt_ids = client.op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); std::cout << "Found IDs of symmetric keys:" << std::endl; for (const auto &id : opt_ids) { std::cout << id << std::endl; @@ -47,7 +47,7 @@ int main(int argc, char **argv) { }; try { - const auto opt_ids_s = client.op_all(KMIP_OBJTYPE_SECRET_DATA); + const auto opt_ids_s = client.op_all(object_type::KMIP_OBJTYPE_SECRET_DATA); std::cout << "Found IDs of secret data:" << std::endl; for (const auto &id : opt_ids_s) { std::cout << id << std::endl; diff --git a/kmipclient/examples/example_get_secret.cpp b/kmipclient/examples/example_get_secret.cpp index c4db80b..a1d033a 100644 --- a/kmipclient/examples/example_get_secret.cpp +++ b/kmipclient/examples/example_get_secret.cpp @@ -42,7 +42,7 @@ int main(int argc, char **argv) { auto secret = client.op_get_secret(argv[6], true); std::cout << "Secret (text): " << secret.as_text() << std::endl; std::cout << "Secret (hex): "; - for (const auto b : secret.value) { + for (const auto b : secret.value()) { std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); } diff --git a/kmipclient/examples/example_locate.cpp b/kmipclient/examples/example_locate.cpp index 18430fd..5736057 100644 --- a/kmipclient/examples/example_locate.cpp +++ b/kmipclient/examples/example_locate.cpp @@ -40,7 +40,7 @@ int main(int argc, char **argv) { std::cout << "Searching for name: " << argv[6] << std::endl; try { const auto opt_ids = - client.op_locate_by_name(argv[6], KMIP_OBJTYPE_SYMMETRIC_KEY); + client.op_locate_by_name(argv[6], object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); std::cout << "Found IDs of symmetric keys:" << std::endl; for (const auto &id : opt_ids) { @@ -54,7 +54,7 @@ int main(int argc, char **argv) { try { const auto opt_ids_s = - client.op_locate_by_name(argv[6], KMIP_OBJTYPE_SECRET_DATA); + client.op_locate_by_name(argv[6], object_type::KMIP_OBJTYPE_SECRET_DATA); std::cout << "Found IDs of secret data:" << std::endl; for (const auto &id : opt_ids_s) { std::cout << id << std::endl; diff --git a/kmipclient/examples/example_locate_by_group.cpp b/kmipclient/examples/example_locate_by_group.cpp index c4c0382..38b51cd 100644 --- a/kmipclient/examples/example_locate_by_group.cpp +++ b/kmipclient/examples/example_locate_by_group.cpp @@ -41,8 +41,8 @@ int main(int argc, char **argv) { std::cout << "Searching for group with name: " << argv[6] << std::endl; try { const auto opt_ids = - client.op_locate_by_group(argv[6], KMIP_OBJTYPE_SYMMETRIC_KEY); - std::cout << "Found IDs of symmetric keys:"; + client.op_locate_by_group(argv[6], object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); + std::cout << "Found IDs of symmetric keys:" << std::endl; for (const auto &id : opt_ids) { std::cout << id << std::endl; } @@ -54,12 +54,12 @@ int main(int argc, char **argv) { try { const auto opt_ids_s = - client.op_locate_by_group(argv[6], KMIP_OBJTYPE_SECRET_DATA); - std::cout << "Found IDs of secret data:"; + client.op_locate_by_group(argv[6], object_type::KMIP_OBJTYPE_SECRET_DATA); + std::cout << "Found IDs of secret data:" << std::endl; for (const auto &id : opt_ids_s) { std::cout << id << std::endl; } - } catch (std::exception &e) { + } catch (const std::exception &e) { std::cerr << "Can not get secrets with group name:" << argv[6] << " Cause: " << e.what() << std::endl; return -1; diff --git a/kmipclient/examples/example_register_secret.cpp b/kmipclient/examples/example_register_secret.cpp index 5e105c3..28b5ef4 100644 --- a/kmipclient/examples/example_register_secret.cpp +++ b/kmipclient/examples/example_register_secret.cpp @@ -38,7 +38,7 @@ int main(int argc, char **argv) { Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); try { auto id = kmip.client().op_register_secret( - argv[6], "TestGroup", argv[7], PASSWORD + argv[6], "TestGroup", argv[7], secret_data_type::KMIP_SECDATA_PASSWORD ); std::cout << "Secret ID: " << id << std::endl; } catch (std::exception &e) { diff --git a/kmipclient/examples/example_revoke.cpp b/kmipclient/examples/example_revoke.cpp index 3d9eedc..b278d5e 100644 --- a/kmipclient/examples/example_revoke.cpp +++ b/kmipclient/examples/example_revoke.cpp @@ -38,7 +38,7 @@ int main(int argc, char **argv) { KmipClient client(net_client); try { const auto opt_key = - client.op_revoke(argv[6], UNSPECIFIED, "Deactivate", 0L); + client.op_revoke(argv[6], revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, "Deactivate", 0L); std::cout << "Key ID: " << argv[6] << " is deactivated." << std::endl; } catch (const std::exception &e) { std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() diff --git a/kmipclient/include/kmipclient/Key.hpp b/kmipclient/include/kmipclient/Key.hpp index 9e4db94..40252d3 100644 --- a/kmipclient/include/kmipclient/Key.hpp +++ b/kmipclient/include/kmipclient/Key.hpp @@ -21,8 +21,6 @@ #include "kmipclient/types.hpp" #include "kmipcore/key.hpp" -#include - namespace kmipclient { /** @@ -38,8 +36,8 @@ namespace kmipclient { * @brief Implicitly wraps an existing core key object. * @param base Source key instance. */ - Key(kmipcore::Key base) - : kmipcore::Key(std::move(base)) { + Key(const kmipcore::Key &base) + : kmipcore::Key(base) { } // NOLINT(google-explicit-constructor) /** @brief Constructs an empty key instance. */ diff --git a/kmipclient/include/kmipclient/Kmip.hpp b/kmipclient/include/kmipclient/Kmip.hpp index 2f054f8..6385bea 100644 --- a/kmipclient/include/kmipclient/Kmip.hpp +++ b/kmipclient/include/kmipclient/Kmip.hpp @@ -49,7 +49,7 @@ namespace kmipclient { const char *clientKeyFn, const char *serverCaCertFn, int timeout_ms, - std::shared_ptr logger = {} + const std::shared_ptr &logger = {} ) : m_net_client( host, @@ -59,7 +59,7 @@ namespace kmipclient { serverCaCertFn, timeout_ms ), - m_client(m_net_client, std::move(logger)) { + m_client(m_net_client, logger) { m_net_client.connect(); }; diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index d5874db..f02dd04 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -48,7 +48,7 @@ namespace kmipclient { */ explicit KmipClient( NetClient &net_client, - std::shared_ptr logger = {} + const std::shared_ptr &logger = {} ); /** @brief Destroys the client and internal helpers. */ ~KmipClient(); @@ -83,9 +83,23 @@ namespace kmipclient { const name_t &name, const name_t &group, std::string_view secret, - enum secret_data_type secret_type + secret_data_type secret_type ) const; + [[nodiscard]] id_t op_register_secret( + const name_t &name, + const name_t &group, + std::string_view secret, + std::uint32_t secret_type + ) const { + return op_register_secret( + name, + group, + secret, + static_cast(secret_type) + ); + } + /** * @brief Executes KMIP Register for binary secret data. * @param name Value of the KMIP "Name" attribute. @@ -99,9 +113,23 @@ namespace kmipclient { const name_t &name, const name_t &group, const secret_t &secret, - enum secret_data_type secret_type + secret_data_type secret_type ) const; + [[nodiscard]] id_t op_register_secret( + const name_t &name, + const name_t &group, + const secret_t &secret, + std::uint32_t secret_type + ) const { + return op_register_secret( + name, + group, + secret, + static_cast(secret_type) + ); + } + /** * @brief Executes KMIP Create to generate a server-side AES-256 key. * @param name Value of the KMIP "Name" attribute. @@ -166,7 +194,12 @@ namespace kmipclient { * @throws kmipcore::KmipException on protocol or server-side failure. */ [[nodiscard]] ids_t - op_locate_by_name(const name_t &name, enum object_type o_type) const; + op_locate_by_name(const name_t &name, object_type o_type) const; + + [[nodiscard]] ids_t + op_locate_by_name(const name_t &name, std::uint32_t o_type) const { + return op_locate_by_name(name, static_cast(o_type)); + } /** * @brief Executes KMIP Locate using the object group filter. @@ -178,10 +211,18 @@ namespace kmipclient { */ [[nodiscard]] ids_t op_locate_by_group( const name_t &group, - enum object_type o_type, - size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + object_type o_type, + std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH ) const; + [[nodiscard]] ids_t op_locate_by_group( + const name_t &group, + std::uint32_t o_type, + std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + ) const { + return op_locate_by_group(group, static_cast(o_type), max_ids); + } + /** * @brief Executes KMIP Revoke for a managed object. * @param id Unique identifier of the object to revoke. @@ -194,10 +235,24 @@ namespace kmipclient { */ [[nodiscard]] id_t op_revoke( const id_t &id, - enum revocation_reason_type reason, + revocation_reason_type reason, const name_t &message, time_t occurrence_time ) const; + + [[nodiscard]] id_t op_revoke( + const id_t &id, + std::uint32_t reason, + const name_t &message, + time_t occurrence_time + ) const { + return op_revoke( + id, + static_cast(reason), + message, + occurrence_time + ); + } /** * @brief Executes KMIP Destroy for a managed object. * @param id Unique identifier of the object to destroy. @@ -215,10 +270,17 @@ namespace kmipclient { * @throws kmipcore::KmipException on protocol or server-side failure. */ [[nodiscard]] ids_t op_all( - enum object_type o_type, - size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + object_type o_type, + std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH ) const; + [[nodiscard]] ids_t op_all( + std::uint32_t o_type, + std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + ) const { + return op_all(static_cast(o_type), max_ids); + } + private: NetClient &net_client; std::unique_ptr io; diff --git a/kmipclient/include/kmipclient/KmipClientPool.hpp b/kmipclient/include/kmipclient/KmipClientPool.hpp index ea161f2..d3828d2 100644 --- a/kmipclient/include/kmipclient/KmipClientPool.hpp +++ b/kmipclient/include/kmipclient/KmipClientPool.hpp @@ -150,7 +150,7 @@ class KmipClientPool { * * @throws std::invalid_argument if max_connections == 0 */ - explicit KmipClientPool(Config config); + explicit KmipClientPool(const Config &config); ~KmipClientPool() = default; // Non-copyable, non-movable (holds a mutex and a condition_variable) diff --git a/kmipclient/include/kmipclient/KmipIOException.hpp b/kmipclient/include/kmipclient/KmipIOException.hpp index 3f5deab..d756986 100644 --- a/kmipclient/include/kmipclient/KmipIOException.hpp +++ b/kmipclient/include/kmipclient/KmipIOException.hpp @@ -18,7 +18,7 @@ #ifndef KMIPIOSEXCEPTION_HPP #define KMIPIOSEXCEPTION_HPP -#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.hpp" #include diff --git a/kmipclient/include/kmipclient/NetClient.hpp b/kmipclient/include/kmipclient/NetClient.hpp index b9f3c79..7945d26 100644 --- a/kmipclient/include/kmipclient/NetClient.hpp +++ b/kmipclient/include/kmipclient/NetClient.hpp @@ -18,6 +18,8 @@ #ifndef KMIP_NET_CLIENT_HPP #define KMIP_NET_CLIENT_HPP +#include +#include #include namespace kmipclient { @@ -77,18 +79,16 @@ namespace kmipclient { /** * @brief Sends bytes over the established connection. * @param data Source buffer. - * @param dlen Number of bytes to send. * @return Number of bytes sent, or -1 on failure. */ - virtual int send(const void *data, int dlen) = 0; + virtual int send(std::span data) = 0; /** * @brief Receives bytes from the established connection. * @param data Destination buffer. - * @param dlen Number of bytes requested. * @return Number of bytes received, or -1 on failure. */ - virtual int recv(void *data, int dlen) = 0; + virtual int recv(std::span data) = 0; protected: std::string m_host; diff --git a/kmipclient/include/kmipclient/NetClientOpenSSL.hpp b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp index dc6cf0f..83a144b 100644 --- a/kmipclient/include/kmipclient/NetClientOpenSSL.hpp +++ b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp @@ -70,17 +70,15 @@ namespace kmipclient { /** * @brief Sends raw bytes through the TLS channel. * @param data Source buffer. - * @param dlen Number of bytes to send. * @return Number of bytes sent, or -1 on failure. */ - int send(const void *data, int dlen) override; + int send(std::span data) override; /** * @brief Receives raw bytes through the TLS channel. * @param data Destination buffer. - * @param dlen Number of bytes requested. * @return Number of bytes read, or -1 on failure. */ - int recv(void *data, int dlen) override; + int recv(std::span data) override; private: struct SslCtxDeleter { diff --git a/kmipclient/include/kmipclient/types.hpp b/kmipclient/include/kmipclient/types.hpp index 546331a..8caa183 100644 --- a/kmipclient/include/kmipclient/types.hpp +++ b/kmipclient/include/kmipclient/types.hpp @@ -19,6 +19,18 @@ namespace kmipclient { using kmipcore::key_t; /** @brief Alias for supported key-kind discriminator enum. */ using kmipcore::KeyType; + /** @brief Alias for KMIP object type enum. */ + using kmipcore::object_type; + /** @brief Alias for KMIP secret data type enum. */ + using kmipcore::secret_data_type; + /** @brief Alias for KMIP revocation reason enum. */ + using kmipcore::revocation_reason_type; + /** @brief Alias for KMIP cryptographic algorithm enum. */ + using kmipcore::cryptographic_algorithm; + /** @brief Alias for KMIP cryptographic usage mask enum. */ + using kmipcore::cryptographic_usage_mask; + /** @brief Alias for KMIP lifecycle state enum. */ + using kmipcore::state; /** @brief Alias for KMIP textual name type. */ using kmipcore::name_t; /** @brief Alias for a list of textual names. */ @@ -29,16 +41,17 @@ namespace kmipclient { using kmipcore::secret_t; /** @brief Canonical KMIP attribute name for object name. */ - inline const std::string &KMIP_ATTR_NAME_NAME = kmipcore::KMIP_ATTR_NAME_NAME; + inline const std::string KMIP_ATTR_NAME_NAME = + std::string(kmipcore::KMIP_ATTR_NAME_NAME); /** @brief Canonical KMIP attribute name for object group. */ - inline const std::string &KMIP_ATTR_NAME_GROUP = - kmipcore::KMIP_ATTR_NAME_GROUP; + inline const std::string KMIP_ATTR_NAME_GROUP = + std::string(kmipcore::KMIP_ATTR_NAME_GROUP); /** @brief Canonical KMIP attribute name for object state. */ - inline const std::string &KMIP_ATTR_NAME_STATE = - kmipcore::KMIP_ATTR_NAME_STATE; + inline const std::string KMIP_ATTR_NAME_STATE = + std::string(kmipcore::KMIP_ATTR_NAME_STATE); /** @brief Canonical KMIP attribute name for unique identifier. */ - inline const std::string &KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = - kmipcore::KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; + inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = + std::string(kmipcore::KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); /** @brief Re-export stream formatter overloads from kmipcore. */ using kmipcore::operator<<; diff --git a/kmipclient/src/IOUtils.cpp b/kmipclient/src/IOUtils.cpp index 68006f9..64d20e1 100644 --- a/kmipclient/src/IOUtils.cpp +++ b/kmipclient/src/IOUtils.cpp @@ -18,10 +18,10 @@ #include "IOUtils.hpp" #include "kmipclient/KmipIOException.hpp" -#include "kmipcore/kmip_basics.hpp" #include "kmipcore/kmip_formatter.hpp" #include "kmipcore/kmip_logger.hpp" +#include #include #include @@ -30,7 +30,7 @@ namespace kmipclient { namespace { - [[nodiscard]] int32_t read_int32_be(const uint8_t *bytes) { + [[nodiscard]] int32_t read_int32_be(std::span bytes) { return (static_cast(bytes[0]) << 24) | (static_cast(bytes[1]) << 16) | (static_cast(bytes[2]) << 8) | @@ -63,7 +63,9 @@ namespace kmipclient { throw KmipIOException(-1, "Can not send empty KMIP request."); } - if (int sent = net_client.send(request_bytes.data(), dlen); sent < dlen) { + const int sent = + net_client.send(std::span(request_bytes.data(), request_bytes.size())); + if (sent < dlen) { throw KmipIOException( -1, std::format( @@ -75,10 +77,12 @@ namespace kmipclient { } } - void IOUtils::read_exact(uint8_t *buf, int n) { + void IOUtils::read_exact(std::span buf) { int total_read = 0; + const int n = static_cast(buf.size()); while (total_read < n) { - int received = net_client.recv(buf + total_read, n - total_read); + const int received = + net_client.recv(buf.subspan(static_cast(total_read))); if (received <= 0) { throw KmipIOException( -1, @@ -94,11 +98,11 @@ namespace kmipclient { } std::vector IOUtils::receive_message(size_t max_message_size) { - uint8_t msg_len_buf[KMIP_MSG_LENGTH_BYTES]; + std::array msg_len_buf{}; - read_exact(msg_len_buf, KMIP_MSG_LENGTH_BYTES); + read_exact(msg_len_buf); - const int32_t length = read_int32_be(msg_len_buf + 4); + const int32_t length = read_int32_be(std::span(msg_len_buf).subspan(4, 4)); if (length < 0 || static_cast(length) > max_message_size) { throw KmipIOException( -1, std::format("Message too long. Length: {}", length) @@ -108,9 +112,9 @@ namespace kmipclient { std::vector response( KMIP_MSG_LENGTH_BYTES + static_cast(length) ); - memcpy(response.data(), msg_len_buf, KMIP_MSG_LENGTH_BYTES); + memcpy(response.data(), msg_len_buf.data(), KMIP_MSG_LENGTH_BYTES); - read_exact(response.data() + KMIP_MSG_LENGTH_BYTES, length); + read_exact(std::span(response).subspan(KMIP_MSG_LENGTH_BYTES, static_cast(length))); return response; diff --git a/kmipclient/src/IOUtils.hpp b/kmipclient/src/IOUtils.hpp index 1e0fa83..07bb7ca 100644 --- a/kmipclient/src/IOUtils.hpp +++ b/kmipclient/src/IOUtils.hpp @@ -32,9 +32,10 @@ namespace kmipclient { class IOUtils { public: explicit IOUtils( - NetClient &nc, std::shared_ptr logger = {} + NetClient &nc, + const std::shared_ptr &logger = {} ) - : net_client(nc), logger_(std::move(logger)) {}; + : net_client(nc), logger_(logger) {}; void do_exchange( const std::vector &request_bytes, @@ -51,7 +52,7 @@ namespace kmipclient { * Reads exactly n bytes from the network into the buffer. * Throws KmipException on error or prematureEOF. */ - void read_exact(uint8_t *buf, int n); + void read_exact(std::span buf); NetClient &net_client; std::shared_ptr logger_; diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp index 7975544..27c5eb3 100644 --- a/kmipclient/src/Key.cpp +++ b/kmipclient/src/Key.cpp @@ -19,7 +19,7 @@ #include "StringUtils.hpp" #include "kmipclient/types.hpp" -#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.hpp" #include #include @@ -137,7 +137,7 @@ namespace kmipclient { } /** Validates AES byte-vector size and constructs a Key. */ - Key make_aes_key(std::vector bytes) { + Key make_aes_key(const std::vector &bytes) { const size_t size = bytes.size(); if (size != 16 && size != 24 && size != 32) { throw kmipcore::KmipException{ @@ -147,7 +147,7 @@ namespace kmipclient { }; } return Key( - std::move(bytes), + bytes, KeyType::SYMMETRIC_KEY, cryptographic_algorithm::KMIP_CRYPTOALG_AES, cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, @@ -245,7 +245,8 @@ namespace kmipclient { } throw kmipcore::KmipException( - KMIP_NOT_IMPLEMENTED, "Unsupported PEM format or not implemented" + kmipcore::KMIP_NOT_IMPLEMENTED, + "Unsupported PEM format or not implemented" ); } diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index e99e4e7..e5c0e94 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -43,10 +43,11 @@ static const std::string &require_attr( } KmipClient::KmipClient( - NetClient &net_client, std::shared_ptr logger + NetClient &net_client, + const std::shared_ptr &logger ) : net_client(net_client), - io(std::make_unique(net_client, std::move(logger))) {}; + io(std::make_unique(net_client, logger)) {}; KmipClient::~KmipClient() { @@ -78,7 +79,7 @@ static const std::string &require_attr( const name_t &name, const name_t &group, const std::string_view secret, - enum secret_data_type secret_type + secret_data_type secret_type ) const { return op_register_secret( name, @@ -92,11 +93,16 @@ static const std::string &require_attr( const name_t &name, const name_t &group, const secret_t &secret, - enum secret_data_type secret_type + secret_data_type secret_type ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::RegisterSecretRequest(name, group, secret, secret_type) + kmipcore::RegisterSecretRequest( + name, + group, + secret, + secret_type + ) ); std::vector response_bytes; @@ -207,9 +213,9 @@ static const std::string &require_attr( // KMIP_REASON_INVALID_DATA_TYPE means the object exists but its type is // not Secret Data (e.g. the ID points to a symmetric key). Rethrowing // as "Could not locate" would be misleading; surface the real cause. - if (e.code() == KMIP_REASON_INVALID_DATA_TYPE) { + if (e.code().value() == kmipcore::KMIP_REASON_INVALID_DATA_TYPE) { throw kmipcore::KmipException( - KMIP_REASON_INVALID_DATA_TYPE, + kmipcore::KMIP_REASON_INVALID_DATA_TYPE, "Object '" + id + "' is not Secret Data" ); } @@ -292,11 +298,17 @@ static const std::string &require_attr( } ids_t KmipClient::op_locate_by_name( - const name_t &name, enum object_type o_type + const name_t &name, object_type o_type ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::LocateRequest(false, name, o_type, MAX_ITEMS_IN_BATCH, 0) + kmipcore::LocateRequest( + false, + name, + o_type, + MAX_ITEMS_IN_BATCH, + 0 + ) ); std::vector response_bytes; @@ -309,30 +321,36 @@ static const std::string &require_attr( rf.getResponseByBatchItemId( batch_item_id ); - return ids_t{ + return ids_t( response.getUniqueIdentifiers().begin(), response.getUniqueIdentifiers().end() - }; + ); } ids_t KmipClient::op_locate_by_group( - const name_t &group, enum object_type o_type, size_t max_ids + const name_t &group, object_type o_type, std::size_t max_ids ) const { if (max_ids == 0) { return {}; } ids_t result; - size_t received = 0; - size_t offset = 0; + std::size_t received = 0; + std::size_t offset = 0; do { - const size_t remaining = max_ids - result.size(); - const size_t page_size = std::min(remaining, MAX_ITEMS_IN_BATCH); + const std::size_t remaining = max_ids - result.size(); + const std::size_t page_size = std::min(remaining, MAX_ITEMS_IN_BATCH); kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::LocateRequest(true, group, o_type, page_size, offset) + kmipcore::LocateRequest( + true, + group, + o_type, + page_size, + offset + ) ); std::vector response_bytes; @@ -353,7 +371,7 @@ static const std::string &require_attr( if (ids_t got = exp; !got.empty()) { received = got.size(); offset += got.size(); - const size_t to_take = std::min(remaining, got.size()); + const std::size_t to_take = std::min(remaining, got.size()); result.insert(result.end(), got.begin(), got.begin() + to_take); } else { break; @@ -363,19 +381,24 @@ static const std::string &require_attr( return result; } - ids_t KmipClient::op_all(enum object_type o_type, size_t max_ids) const { + ids_t KmipClient::op_all(object_type o_type, std::size_t max_ids) const { return op_locate_by_group("", o_type, max_ids); } id_t KmipClient::op_revoke( const id_t &id, - enum revocation_reason_type reason, + revocation_reason_type reason, const name_t &message, time_t occurrence_time ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::RevokeRequest(id, reason, message, occurrence_time) + kmipcore::RevokeRequest( + id, + reason, + message, + occurrence_time + ) ); std::vector response_bytes; diff --git a/kmipclient/src/KmipClientPool.cpp b/kmipclient/src/KmipClientPool.cpp index 70d4396..3215c09 100644 --- a/kmipclient/src/KmipClientPool.cpp +++ b/kmipclient/src/KmipClientPool.cpp @@ -17,7 +17,7 @@ #include "kmipclient/KmipClientPool.hpp" -#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.hpp" #include #include @@ -72,7 +72,7 @@ KmipClient *KmipClientPool::BorrowedClient::operator->() { // KmipClientPool // ============================================================================ -KmipClientPool::KmipClientPool(Config config) : config_(std::move(config)) { +KmipClientPool::KmipClientPool(const Config &config) : config_(config) { if (config_.max_connections == 0) { throw kmipcore::KmipException( -1, diff --git a/kmipclient/src/NetClientOpenSSL.cpp b/kmipclient/src/NetClientOpenSSL.cpp index acfabb1..7e02bcd 100644 --- a/kmipclient/src/NetClientOpenSSL.cpp +++ b/kmipclient/src/NetClientOpenSSL.cpp @@ -18,7 +18,6 @@ #include "kmipclient/NetClientOpenSSL.hpp" #include "kmipclient/KmipIOException.hpp" -#include "kmipcore/kmip_basics.hpp" #include #include @@ -215,11 +214,12 @@ namespace kmipclient { m_isConnected = false; } - int NetClientOpenSSL::send(const void *data, int dlen) { + int NetClientOpenSSL::send(std::span data) { if (!checkConnected()) { return -1; } - const int ret = BIO_write(bio_.get(), data, dlen); + const int dlen = static_cast(data.size()); + const int ret = BIO_write(bio_.get(), data.data(), dlen); if (ret <= 0 && is_timeout_errno()) { throw KmipIOException( -1, timeoutMessage("send", m_timeout_ms) @@ -228,11 +228,12 @@ namespace kmipclient { return ret; } - int NetClientOpenSSL::recv(void *data, int dlen) { + int NetClientOpenSSL::recv(std::span data) { if (!checkConnected()) { return -1; } - const int ret = BIO_read(bio_.get(), data, dlen); + const int dlen = static_cast(data.size()); + const int ret = BIO_read(bio_.get(), data.data(), dlen); if (ret <= 0 && is_timeout_errno()) { throw KmipIOException( -1, timeoutMessage("receive", m_timeout_ms) diff --git a/kmipclient/src/StringUtils.cpp b/kmipclient/src/StringUtils.cpp index 7674206..e0f6eec 100644 --- a/kmipclient/src/StringUtils.cpp +++ b/kmipclient/src/StringUtils.cpp @@ -17,10 +17,10 @@ #include "StringUtils.hpp" -#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.hpp" #include -#include +#include #include namespace kmipclient { @@ -37,22 +37,23 @@ namespace kmipclient { throw kmipcore::KmipException{"Invalid hex character."}; } - key_t StringUtils::fromHex(const std::string &hex) { + key_t StringUtils::fromHex(std::string_view hex) { if (hex.empty() || hex.size() % 2 != 0) { throw kmipcore::KmipException{"Invalid hex string length."}; } key_t bytes; - for (unsigned int i = 0; i < hex.length(); i += 2) { - std::string byteString = hex.substr(i, 2); - auto byte = char2int(byteString.c_str()[0]) * 16 + - char2int(byteString.c_str()[1]); + bytes.reserve(hex.size() / 2); + for (size_t i = 0; i < hex.size(); i += 2) { + const auto byte = static_cast( + char2int(hex[i]) * 16 + char2int(hex[i + 1]) + ); bytes.push_back(byte); } return bytes; } std::vector - StringUtils::fromBase64(const std::string &base64) { + StringUtils::fromBase64(std::string_view base64) { static const std::array lookup = []() { std::array l{}; l.fill(-1); diff --git a/kmipclient/src/StringUtils.hpp b/kmipclient/src/StringUtils.hpp index c074871..24f1bc2 100644 --- a/kmipclient/src/StringUtils.hpp +++ b/kmipclient/src/StringUtils.hpp @@ -20,12 +20,14 @@ #include "kmipclient/types.hpp" +#include + namespace kmipclient { class StringUtils { public: - static key_t fromHex(const std::string &hex); - static std::vector fromBase64(const std::string &base64); + static key_t fromHex(std::string_view hex); + static std::vector fromBase64(std::string_view base64); }; } // namespace kmipclient diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 5897bf9..939df38 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -134,9 +134,9 @@ class KmipClientIntegrationTest : public ::testing::Test { // Try to destroy the key (best effort cleanup) try { // if the object is not active then it cannot be revoked with reason - // other than KEY_COMPROMISE + // other than revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE auto res_r = kmip.client().op_revoke( - key_id, KEY_COMPROMISE, "Test cleanup", 0 + key_id, revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, "Test cleanup", 0 ); auto res_d = kmip.client().op_destroy(key_id); } catch (kmipcore::KmipException &e) { @@ -184,7 +184,7 @@ TEST_F(KmipClientIntegrationTest, LocateKeysByGroup) { // Locate by group auto found_ids = kmip->client().op_locate_by_group( - group_name, KMIP_OBJTYPE_SYMMETRIC_KEY + group_name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY ); // Verify all created keys are found @@ -220,7 +220,7 @@ TEST_F(KmipClientIntegrationTest, LocateKeysByGroupHonorsMaxIds) { const size_t max_ids = 2; auto found_ids = kmip->client().op_locate_by_group( - group_name, KMIP_OBJTYPE_SYMMETRIC_KEY, max_ids + group_name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, max_ids ); EXPECT_LE(found_ids.size(), max_ids); @@ -239,7 +239,7 @@ TEST_F(KmipClientIntegrationTest, LocateKeysByGroupHonorsMaxIds) { TEST_F(KmipClientIntegrationTest, GetAllIdsWithZeroLimitReturnsEmpty) { auto kmip = createKmipClient(); try { - auto all_ids = kmip->client().op_all(KMIP_OBJTYPE_SYMMETRIC_KEY, 0); + auto all_ids = kmip->client().op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, 0); EXPECT_TRUE(all_ids.empty()); } catch (kmipcore::KmipException &e) { FAIL() << "GetAllIdsWithZeroLimitReturnsEmpty failed: " << e.what(); @@ -355,7 +355,7 @@ TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { secret_t secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; try { secret_id = kmip->client().op_register_secret( - TESTING_NAME_PREFIX + "a_secret", TEST_GROUP, secret_data, PASSWORD + TESTING_NAME_PREFIX + "a_secret", TEST_GROUP, secret_data, secret_data_type::KMIP_SECDATA_PASSWORD ); EXPECT_FALSE(secret_id.empty()); std::cout << "Registered secret with ID: " << secret_id << std::endl; @@ -366,8 +366,8 @@ TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { try { auto retrieved_secret = kmip->client().op_get_secret(secret_id, true); - EXPECT_EQ(retrieved_secret.value.size(), secret_data.size()); - EXPECT_EQ(retrieved_secret.value, secret_data); + EXPECT_EQ(retrieved_secret.value().size(), secret_data.size()); + EXPECT_EQ(retrieved_secret.value(), secret_data); EXPECT_FALSE(retrieved_secret.attributes().empty()); EXPECT_TRUE( retrieved_secret.attributes().count(KMIP_ATTR_NAME_NAME) > 0 || @@ -394,7 +394,7 @@ TEST_F(KmipClientIntegrationTest, LocateKeys) { // Find by name try { auto fkey_ids = - kmip->client().op_locate_by_name(name, KMIP_OBJTYPE_SYMMETRIC_KEY); + kmip->client().op_locate_by_name(name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); ASSERT_TRUE(fkey_ids.size() > 1); EXPECT_EQ(fkey_ids[0], key_id); std::cout << "Found " << fkey_ids.size() << " keys" << std::endl; @@ -454,7 +454,7 @@ TEST_F(KmipClientIntegrationTest, CreateAndRevokeKey) { // Revoke key try { auto revoke_result = - kmip->client().op_revoke(key_id, UNSPECIFIED, "Test revocation", 0); + kmip->client().op_revoke(key_id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, "Test revocation", 0); std::cout << "Successfully revoked key: " << key_id << std::endl; } catch (kmipcore::KmipException &e) { FAIL() << "Failed to revoke key: " << e.what(); @@ -483,7 +483,7 @@ TEST_F(KmipClientIntegrationTest, FullKeyLifecycle) { // Revoke auto revoke_result = - kmip->client().op_revoke(key_id, UNSPECIFIED, "Test lifecycle", 0); + kmip->client().op_revoke(key_id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, "Test lifecycle", 0); ASSERT_FALSE(revoke_result.empty()) << "Revoke failed"; std::cout << "4. Revoked key" << std::endl; @@ -620,7 +620,7 @@ TEST_F(KmipClientIntegrationTest, CreateDuplicateNames) { try { auto found = - kmip->client().op_locate_by_name(name, KMIP_OBJTYPE_SYMMETRIC_KEY); + kmip->client().op_locate_by_name(name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); // Both created IDs should be present auto it1 = std::find(found.begin(), found.end(), id1); auto it2 = std::find(found.begin(), found.end(), id2); @@ -650,7 +650,7 @@ TEST_F(KmipClientIntegrationTest, RevokeChangesState) { try { auto revoke_res = - kmip->client().op_revoke(key_id, UNSPECIFIED, "Test revoke state", 0); + kmip->client().op_revoke(key_id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, "Test revoke state", 0); EXPECT_FALSE(revoke_res.empty()); } catch (kmipcore::KmipException &e) { FAIL() << "Failed to revoke key: " << e.what(); @@ -683,7 +683,7 @@ TEST_F(KmipClientIntegrationTest, GetAllIdsIncludesCreatedKeys) { trackKeyForCleanup(id); } - auto all_ids = kmip->client().op_all(KMIP_OBJTYPE_SYMMETRIC_KEY); + auto all_ids = kmip->client().op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); for (const auto &cid : created_ids) { auto it = std::find(all_ids.begin(), all_ids.end(), cid); EXPECT_NE(it, all_ids.end()) diff --git a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp index 82726cf..2e64a0e 100644 --- a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp @@ -144,7 +144,7 @@ class KmipClientPoolIntegrationTest : public ::testing::Test { for (const auto &key_id : created_key_ids) { try { [[maybe_unused]] auto revoke_result = kmip.client().op_revoke( - key_id, KEY_COMPROMISE, "Pool test cleanup", 0 + key_id, revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, "Pool test cleanup", 0 ); [[maybe_unused]] auto destroy_result = kmip.client().op_destroy(key_id); } catch (kmipcore::KmipException &e) { diff --git a/kmipcore/include/kmipcore/key.hpp b/kmipcore/include/kmipcore/key.hpp index 8151294..0c59e0b 100644 --- a/kmipcore/include/kmipcore/key.hpp +++ b/kmipcore/include/kmipcore/key.hpp @@ -20,12 +20,10 @@ #include "kmipcore/types.hpp" -#include - namespace kmipcore { /** @brief Key object families represented by @ref Key. */ - enum KeyType { UNSET, SYMMETRIC_KEY, PUBLIC_KEY, PRIVATE_KEY, CERTIFICATE }; + enum class KeyType { UNSET, SYMMETRIC_KEY, PUBLIC_KEY, PRIVATE_KEY, CERTIFICATE }; /** * Minimal crypto key representation as KMIP spec sees it. @@ -43,15 +41,15 @@ namespace kmipcore { * @param attributes Additional key attributes. */ explicit Key( - key_t value, + const key_t &value, KeyType k_type, cryptographic_algorithm algo, cryptographic_usage_mask usage_mask, - attributes_t attributes + const attributes_t &attributes ) - : key_value(std::move(value)), + : key_value(value), key_type(k_type), - key_attributes(std::move(attributes)), + key_attributes(attributes), crypto_algorithm(algo), crypto_usage_mask(usage_mask) {}; @@ -74,14 +72,15 @@ namespace kmipcore { }; /** - * @brief Returns value of a required attribute. + * @brief Returns value of a key attribute, or empty string if absent. * @param name Attribute name. - * @return Attribute value. - * @throws std::out_of_range if attribute is absent. + * @return Attribute value, or empty string when the attribute is missing. */ [[nodiscard]] const std::string & - attribute_value(const std::string &name) const { - return key_attributes.at(name); + attribute_value(const std::string &name) const noexcept { + static const std::string empty; + const auto it = key_attributes.find(name); + return it != key_attributes.end() ? it->second : empty; }; /** @brief Sets or replaces one attribute value. */ @@ -109,7 +108,7 @@ namespace kmipcore { private: key_t key_value; - KeyType key_type = UNSET; + KeyType key_type = KeyType::UNSET; attributes_t key_attributes; cryptographic_algorithm crypto_algorithm = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; diff --git a/kmipcore/include/kmipcore/kmip_attribute_names.hpp b/kmipcore/include/kmipcore/kmip_attribute_names.hpp index 6988445..6464ca0 100644 --- a/kmipcore/include/kmipcore/kmip_attribute_names.hpp +++ b/kmipcore/include/kmipcore/kmip_attribute_names.hpp @@ -1,50 +1,50 @@ #pragma once -#include +#include namespace kmipcore { // Known KMIP attribute names used across client/core layers. /** @brief KMIP Name attribute. */ - inline const std::string KMIP_ATTR_NAME_NAME = "Name"; + inline constexpr std::string_view KMIP_ATTR_NAME_NAME = "Name"; /** @brief KMIP Object Group attribute. */ - inline const std::string KMIP_ATTR_NAME_GROUP = "Object Group"; + inline constexpr std::string_view KMIP_ATTR_NAME_GROUP = "Object Group"; /** @brief KMIP State attribute. */ - inline const std::string KMIP_ATTR_NAME_STATE = "State"; + inline constexpr std::string_view KMIP_ATTR_NAME_STATE = "State"; /** @brief KMIP Unique Identifier attribute. */ - inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = "Unique Identifier"; + inline constexpr std::string_view KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = "Unique Identifier"; /** @brief Backward-compatible alternative Unique Identifier attribute name. */ - inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER_ALT = "UniqueID"; // backward compatibility + inline constexpr std::string_view KMIP_ATTR_NAME_UNIQUE_IDENTIFIER_ALT = "UniqueID"; // backward compatibility /** @brief KMIP Initial Date attribute. */ - inline const std::string KMIP_ATTR_NAME_INITIAL_DATE = "Initial Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_INITIAL_DATE = "Initial Date"; /** @brief KMIP Activation Date attribute. */ - inline const std::string KMIP_ATTR_NAME_ACTIVATION_DATE = "Activation Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_ACTIVATION_DATE = "Activation Date"; /** @brief KMIP Process Start Date attribute. */ - inline const std::string KMIP_ATTR_NAME_PROCESS_START_DATE = "Process Start Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_PROCESS_START_DATE = "Process Start Date"; /** @brief KMIP Protect Stop Date attribute. */ - inline const std::string KMIP_ATTR_NAME_PROTECT_STOP_DATE = "Protect Stop Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_PROTECT_STOP_DATE = "Protect Stop Date"; /** @brief KMIP Deactivation Date attribute. */ - inline const std::string KMIP_ATTR_NAME_DEACTIVATION_DATE = "Deactivation Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_DEACTIVATION_DATE = "Deactivation Date"; /** @brief KMIP Destroy Date attribute. */ - inline const std::string KMIP_ATTR_NAME_DESTROY_DATE = "Destroy Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_DESTROY_DATE = "Destroy Date"; /** @brief KMIP Compromise Occurrence Date attribute. */ - inline const std::string KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE = "Compromise Occurrence Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE = "Compromise Occurrence Date"; /** @brief KMIP Compromise Date attribute. */ - inline const std::string KMIP_ATTR_NAME_COMPROMISE_DATE = "Compromise Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_COMPROMISE_DATE = "Compromise Date"; /** @brief KMIP Archive Date attribute. */ - inline const std::string KMIP_ATTR_NAME_ARCHIVE_DATE = "Archive Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_ARCHIVE_DATE = "Archive Date"; /** @brief KMIP Last Change Date attribute. */ - inline const std::string KMIP_ATTR_NAME_LAST_CHANGE_DATE = "Last Change Date"; + inline constexpr std::string_view KMIP_ATTR_NAME_LAST_CHANGE_DATE = "Last Change Date"; /** @brief KMIP Cryptographic Algorithm attribute. */ - inline const std::string KMIP_ATTR_NAME_CRYPTO_ALG = "Cryptographic Algorithm"; + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_ALG = "Cryptographic Algorithm"; /** @brief KMIP Cryptographic Length attribute. */ - inline const std::string KMIP_ATTR_NAME_CRYPTO_LEN = "Cryptographic Length"; + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_LEN = "Cryptographic Length"; /** @brief KMIP Cryptographic Usage Mask attribute. */ - inline const std::string KMIP_ATTR_NAME_CRYPTO_USAGE_MASK = "Cryptographic Usage Mask"; + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_USAGE_MASK = "Cryptographic Usage Mask"; /** @brief KMIP Contact Information attribute. */ - inline const std::string KMIP_ATTR_NAME_CONTACT_INFO = "Contact Information"; + inline constexpr std::string_view KMIP_ATTR_NAME_CONTACT_INFO = "Contact Information"; /** @brief KMIP Operation Policy Name attribute. */ - inline const std::string KMIP_ATTR_NAME_OPERATION_POLICY_NAME = "Operation Policy Name"; + inline constexpr std::string_view KMIP_ATTR_NAME_OPERATION_POLICY_NAME = "Operation Policy Name"; } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_basics.hpp b/kmipcore/include/kmipcore/kmip_basics.hpp index 57dddb1..3de9397 100644 --- a/kmipcore/include/kmipcore/kmip_basics.hpp +++ b/kmipcore/include/kmipcore/kmip_basics.hpp @@ -1,26 +1,26 @@ #pragma once #include "kmipcore/kmip_enums.hpp" +#include "kmipcore/kmip_errors.hpp" #include #include #include -#include #include #include #include -// Forward declaration for SerializationBuffer -namespace kmipcore { - class SerializationBuffer; -} + namespace kmipcore { + // Forward declaration for SerializationBuffer + class SerializationBuffer; + /** @brief Alias of KMIP tag enumeration type used by TTLV elements. */ - using Tag = ::tag; + using Tag = tag; /** @brief Alias of KMIP type enumeration used by TTLV elements. */ - using Type = ::type; + using Type = type; struct Element; // Forward declaration @@ -106,7 +106,7 @@ namespace kmipcore { */ struct Element { /** KMIP tag describing semantic meaning of this node. */ - Tag tag = static_cast(KMIP_TAG_DEFAULT); + Tag tag = tag::KMIP_TAG_DEFAULT; /** KMIP TTLV type code for @ref value. */ Type type = static_cast(KMIP_TYPE_STRUCTURE); /** Typed payload value of this node. */ @@ -117,10 +117,7 @@ namespace kmipcore { */ Element(Tag t, Type tp, Value v) : tag(t), type(tp), value(std::move(v)) {} /** @brief Default-constructs an empty element. */ - Element() - : tag(static_cast(KMIP_TAG_DEFAULT)), - type(static_cast(KMIP_TYPE_STRUCTURE)), - value(Structure{}) {} + Element() = default; /** @brief Creates a Structure element. */ static std::shared_ptr createStructure(Tag t); @@ -187,21 +184,5 @@ namespace kmipcore { [[nodiscard]] int32_t toEnum() const; }; - /** - * @brief Base exception for KMIP core protocol/encoding failures. - */ - class KmipException : public std::runtime_error { - public: - /** @brief Creates an exception with message only. */ - explicit KmipException(const std::string &msg) : std::runtime_error(msg) {} - /** @brief Creates an exception with numeric status code and message. */ - KmipException(int code, const std::string &msg) - : std::runtime_error(msg), code_(code) {} - /** @brief Returns optional KMIP/library error code (or -1 if unset). */ - [[nodiscard]] int code() const noexcept { return code_; } - - private: - int code_ = -1; - }; } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp index 4b3138a..8d6b0a2 100644 --- a/kmipcore/include/kmipcore/kmip_enums.hpp +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -8,19 +8,22 @@ * C++20 version of KMIP enumerations and constants. */ -#pragma once +#ifndef KMIPCORE_KMIP_ENUMS_HPP +#define KMIPCORE_KMIP_ENUMS_HPP +#include #include -#include + +namespace kmipcore { // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- /** Maximum encoded KMIP message size handled by default helpers. */ -inline constexpr int32_t KMIP_MAX_MESSAGE_SIZE = 8192; +inline constexpr std::size_t KMIP_MAX_MESSAGE_SIZE = 8192; /** Suggested buffer size for human-readable error messages. */ -inline constexpr int32_t KMIP_ERROR_MESSAGE_SIZE = 200; +inline constexpr std::size_t KMIP_ERROR_MESSAGE_SIZE = 200; /** Canonical KMIP boolean true value. */ inline constexpr bool KMIP_TRUE = true; @@ -28,51 +31,44 @@ inline constexpr bool KMIP_TRUE = true; inline constexpr bool KMIP_FALSE = false; /** Generic sentinel value representing unset enum/int fields. */ -inline constexpr int32_t KMIP_UNSET = -1; +inline constexpr std::int32_t KMIP_UNSET = -1; -/** - * @brief Returns the lower of two values. - * @tparam T Comparable numeric/value type. - */ -template constexpr T KMIP_MIN(T a, T b) { - return (a < b) ? a : b; -} -inline constexpr int32_t KMIP_OK = 0; -inline constexpr int32_t KMIP_NOT_IMPLEMENTED = -1; -inline constexpr int32_t KMIP_ERROR_BUFFER_FULL = -2; -inline constexpr int32_t KMIP_ERROR_ATTR_UNSUPPORTED = -3; -inline constexpr int32_t KMIP_TAG_MISMATCH = -4; -inline constexpr int32_t KMIP_TYPE_MISMATCH = -5; -inline constexpr int32_t KMIP_LENGTH_MISMATCH = -6; -inline constexpr int32_t KMIP_PADDING_MISMATCH = -7; -inline constexpr int32_t KMIP_BOOLEAN_MISMATCH = -8; -inline constexpr int32_t KMIP_ENUM_MISMATCH = -9; -inline constexpr int32_t KMIP_ENUM_UNSUPPORTED = -10; -inline constexpr int32_t KMIP_INVALID_FOR_VERSION = -11; -inline constexpr int32_t KMIP_MEMORY_ALLOC_FAILED = -12; -inline constexpr int32_t KMIP_IO_FAILURE = -13; -inline constexpr int32_t KMIP_EXCEED_MAX_MESSAGE_SIZE = -14; -inline constexpr int32_t KMIP_MALFORMED_RESPONSE = -15; -inline constexpr int32_t KMIP_OBJECT_MISMATCH = -16; -inline constexpr int32_t KMIP_ARG_INVALID = -17; -inline constexpr int32_t KMIP_ERROR_BUFFER_UNDERFULL = -18; -inline constexpr int32_t KMIP_INVALID_ENCODING = -19; -inline constexpr int32_t KMIP_INVALID_FIELD = -20; -inline constexpr int32_t KMIP_INVALID_LENGTH = -21; +inline constexpr std::int32_t KMIP_OK = 0; +inline constexpr std::int32_t KMIP_NOT_IMPLEMENTED = -1; +inline constexpr std::int32_t KMIP_ERROR_BUFFER_FULL = -2; +inline constexpr std::int32_t KMIP_ERROR_ATTR_UNSUPPORTED = -3; +inline constexpr std::int32_t KMIP_TAG_MISMATCH = -4; +inline constexpr std::int32_t KMIP_TYPE_MISMATCH = -5; +inline constexpr std::int32_t KMIP_LENGTH_MISMATCH = -6; +inline constexpr std::int32_t KMIP_PADDING_MISMATCH = -7; +inline constexpr std::int32_t KMIP_BOOLEAN_MISMATCH = -8; +inline constexpr std::int32_t KMIP_ENUM_MISMATCH = -9; +inline constexpr std::int32_t KMIP_ENUM_UNSUPPORTED = -10; +inline constexpr std::int32_t KMIP_INVALID_FOR_VERSION = -11; +inline constexpr std::int32_t KMIP_MEMORY_ALLOC_FAILED = -12; +inline constexpr std::int32_t KMIP_IO_FAILURE = -13; +inline constexpr std::int32_t KMIP_EXCEED_MAX_MESSAGE_SIZE = -14; +inline constexpr std::int32_t KMIP_MALFORMED_RESPONSE = -15; +inline constexpr std::int32_t KMIP_OBJECT_MISMATCH = -16; +inline constexpr std::int32_t KMIP_ARG_INVALID = -17; +inline constexpr std::int32_t KMIP_ERROR_BUFFER_UNDERFULL = -18; +inline constexpr std::int32_t KMIP_INVALID_ENCODING = -19; +inline constexpr std::int32_t KMIP_INVALID_FIELD = -20; +inline constexpr std::int32_t KMIP_INVALID_LENGTH = -21; // --------------------------------------------------------------------------- // Enumerations // --------------------------------------------------------------------------- -enum attestation_type : int32_t { +enum class attestation_type : std::uint32_t { // KMIP 1.2 KMIP_ATTEST_TPM_QUOTE = 0x01, KMIP_ATTEST_TCG_INTEGRITY_REPORT = 0x02, KMIP_ATTEST_SAML_ASSERTION = 0x03 }; -enum attribute_type : int32_t { +enum class attribute_type : std::uint32_t { // KMIP 1.0 KMIP_ATTR_UNIQUE_IDENTIFIER = 0, KMIP_ATTR_NAME = 1, @@ -91,14 +87,14 @@ enum attribute_type : int32_t { KMIP_ATTR_CRYPTOGRAPHIC_PARAMETERS = 14 }; -enum batch_error_continuation_option : int32_t { +enum class batch_error_continuation_option : std::uint32_t { // KMIP 1.0 KMIP_BATCH_CONTINUE = 0x01, KMIP_BATCH_STOP = 0x02, KMIP_BATCH_UNDO = 0x03 }; -enum block_cipher_mode : int32_t { +enum class block_cipher_mode : std::uint32_t { // KMIP 1.0 KMIP_BLOCK_CBC = 0x01, KMIP_BLOCK_ECB = 0x02, @@ -121,7 +117,7 @@ enum block_cipher_mode : int32_t { KMIP_BLOCK_AEAD = 0x12 }; -enum credential_type : int32_t { +enum class credential_type : std::uint32_t { // KMIP 1.0 KMIP_CRED_USERNAME_AND_PASSWORD = 0x01, // KMIP 1.1 @@ -134,7 +130,7 @@ enum credential_type : int32_t { KMIP_CRED_TICKET = 0x06 }; -enum cryptographic_algorithm : int32_t { +enum class cryptographic_algorithm : std::uint32_t { KMIP_CRYPTOALG_UNSET = 0x00, // KMIP 1.0 KMIP_CRYPTOALG_DES = 0x01, @@ -199,7 +195,7 @@ enum cryptographic_algorithm : int32_t { KMIP_CRYPTOALG_ED448 = 0x38 }; -enum cryptographic_usage_mask : int32_t { +enum class cryptographic_usage_mask : std::uint32_t { KMIP_CRYPTOMASK_UNSET = 0x00000000, // KMIP 1.0 KMIP_CRYPTOMASK_SIGN = 0x00000001, @@ -229,7 +225,7 @@ enum cryptographic_usage_mask : int32_t { KMIP_CRYPTOMASK_FPE_DECRYPT = 0x00800000 }; -enum digital_signature_algorithm : int32_t { +enum class digital_signature_algorithm : std::uint32_t { // KMIP 1.1 KMIP_DIGITAL_MD2_WITH_RSA = 0x01, KMIP_DIGITAL_MD5_WITH_RSA = 0x02, @@ -253,13 +249,13 @@ enum digital_signature_algorithm : int32_t { KMIP_DIGITAL_SHA3_512_WITH_RSA = 0x13 }; -enum encoding_option : int32_t { +enum class encoding_option : std::uint32_t { // KMIP 1.1 KMIP_ENCODE_NO_ENCODING = 0x01, KMIP_ENCODE_TTLV_ENCODING = 0x02 }; -enum hashing_algorithm : int32_t { +enum class hashing_algorithm : std::uint32_t { // KMIP 1.0 KMIP_HASH_MD2 = 0x01, KMIP_HASH_MD4 = 0x02, @@ -282,7 +278,7 @@ enum hashing_algorithm : int32_t { KMIP_HASH_SHA3_512 = 0x11 }; -enum key_compression_type : int32_t { +enum class key_compression_type : std::uint32_t { // KMIP 1.0 KMIP_KEYCOMP_EC_PUB_UNCOMPRESSED = 0x01, KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_PRIME = 0x02, @@ -290,7 +286,7 @@ enum key_compression_type : int32_t { KMIP_KEYCOMP_EC_PUB_X962_HYBRID = 0x04 }; -enum key_format_type : int32_t { +enum class key_format_type : std::uint32_t { // KMIP 1.0 KMIP_KEYFORMAT_RAW = 0x01, KMIP_KEYFORMAT_OPAQUE = 0x02, @@ -320,7 +316,7 @@ enum key_format_type : int32_t { KMIP_KEYFORMAT_PKCS10 = 0x17 }; -enum key_role_type : int32_t { +enum class key_role_type : std::uint32_t { // KMIP 1.0 KMIP_ROLE_BDK = 0x01, KMIP_ROLE_CVK = 0x02, @@ -349,13 +345,13 @@ enum key_role_type : int32_t { KMIP_ROLE_TRKBK = 0x18 }; -enum key_wrap_type : int32_t { +enum class key_wrap_type : std::uint32_t { // KMIP 1.4 KMIP_WRAPTYPE_NOT_WRAPPED = 0x01, KMIP_WRAPTYPE_AS_REGISTERED = 0x02 }; -enum kmip_version : int32_t { +enum class kmip_version : std::uint32_t { KMIP_1_0 = 0, KMIP_1_1 = 1, KMIP_1_2 = 2, @@ -364,18 +360,18 @@ enum kmip_version : int32_t { KMIP_2_0 = 5 }; -enum mask_generator : int32_t { +enum class mask_generator : std::uint32_t { // KMIP 1.4 KMIP_MASKGEN_MGF1 = 0x01 }; -enum name_type : int32_t { +enum class name_type : std::uint32_t { // KMIP 1.0 KMIP_NAME_UNINTERPRETED_TEXT_STRING = 0x01, KMIP_NAME_URI = 0x02 }; -enum object_type : int32_t { +enum class object_type : std::uint32_t { // KMIP 1.0 KMIP_OBJTYPE_CERTIFICATE = 0x01, KMIP_OBJTYPE_SYMMETRIC_KEY = 0x02, @@ -391,7 +387,7 @@ enum object_type : int32_t { KMIP_OBJTYPE_CERTIFICATE_REQUEST = 0x0A }; -enum operation : int32_t { +enum class operation : std::uint32_t { // KMIP 1.0 KMIP_OP_CREATE = 0x01, KMIP_OP_CREATE_KEY_PAIR = 0x02, @@ -452,7 +448,7 @@ enum operation : int32_t { KMIP_OP_REPROVISION = 0x35 }; -enum padding_method : int32_t { +enum class padding_method : std::uint32_t { // KMIP 1.0 KMIP_PAD_NONE = 0x01, KMIP_PAD_OAEP = 0x02, @@ -466,7 +462,7 @@ enum padding_method : int32_t { KMIP_PAD_PSS = 0x0A }; -enum protection_storage_mask : int32_t { +enum class protection_storage_mask : std::uint32_t { // KMIP 2.0 KMIP_PROTECT_SOFTWARE = 0x00000001, KMIP_PROTECT_HARDWARE = 0x00000002, @@ -484,7 +480,7 @@ enum protection_storage_mask : int32_t { KMIP_PROTECT_SAME_JURISDICTION = 0x00002000 }; -enum query_function : int32_t { +enum class query_function : std::uint32_t { // KMIP 1.0 KMIP_QUERY_OPERATIONS = 0x0001, KMIP_QUERY_OBJECTS = 0x0002, @@ -506,7 +502,7 @@ enum query_function : int32_t { KMIP_QUERY_STORAGE_PROTECTION_MASKS = 0x000E }; -enum result_reason : int32_t { +enum class result_reason : std::uint32_t { // KMIP 1.0 KMIP_REASON_GENERAL_FAILURE = 0x0100, KMIP_REASON_ITEM_NOT_FOUND = 0x0001, @@ -584,7 +580,7 @@ enum result_reason : int32_t { KMIP_REASON_PUBLIC_PROTECTION_STORAGE_UNAVAILABLE = 0x0049 }; -enum result_status : int32_t { +enum class result_status : std::uint32_t { // KMIP 1.0 KMIP_STATUS_SUCCESS = 0x00, KMIP_STATUS_OPERATION_FAILED = 0x01, @@ -592,7 +588,7 @@ enum result_status : int32_t { KMIP_STATUS_OPERATION_UNDONE = 0x03 }; -enum state : int32_t { +enum class state : std::uint32_t { // KMIP 1.0 KMIP_STATE_PRE_ACTIVE = 0x01, KMIP_STATE_ACTIVE = 0x02, @@ -602,7 +598,27 @@ enum state : int32_t { KMIP_STATE_DESTROYED_COMPROMISED = 0x06 }; -enum tag : int32_t { +/** Convert a KMIP state enum value to a human-readable string. */ +inline const char *state_to_string(state value) { + switch (value) { + case state::KMIP_STATE_PRE_ACTIVE: + return "KMIP_STATE_PRE_ACTIVE"; + case state::KMIP_STATE_ACTIVE: + return "KMIP_STATE_ACTIVE"; + case state::KMIP_STATE_DEACTIVATED: + return "KMIP_STATE_DEACTIVATED"; + case state::KMIP_STATE_COMPROMISED: + return "KMIP_STATE_COMPROMISED"; + case state::KMIP_STATE_DESTROYED: + return "KMIP_STATE_DESTROYED"; + case state::KMIP_STATE_DESTROYED_COMPROMISED: + return "KMIP_STATE_DESTROYED_COMPROMISED"; + default: + return "UNKNOWN_KMIP_STATE"; + } +} + +enum class tag : std::uint32_t { KMIP_TAG_TAG = 0x000000, KMIP_TAG_TYPE = 0x000001, KMIP_TAG_DEFAULT = 0x420000, @@ -746,7 +762,7 @@ enum tag : int32_t { KMIP_TAG_PUBLIC_PROTECTION_STORAGE_MASKS = 0x420165 }; -enum type : int32_t { +enum class type : std::uint32_t { // KMIP 1.0 KMIP_TYPE_STRUCTURE = 0x01, KMIP_TYPE_INTEGER = 0x02, @@ -762,7 +778,7 @@ enum type : int32_t { KMIP_TYPE_DATE_TIME_EXTENDED = 0x0B }; -enum wrapping_method : int32_t { +enum class wrapping_method : std::uint32_t { // KMIP 1.0 KMIP_WRAP_ENCRYPT = 0x01, KMIP_WRAP_MAC_SIGN = 0x02, @@ -772,22 +788,605 @@ enum wrapping_method : int32_t { }; /** @brief KMIP revocation reason codes used by Revoke operations. */ -enum revocation_reason_type : int32_t { +enum class revocation_reason_type : std::uint32_t { // KMIP 1.0 - UNSPECIFIED = 0x01, - KEY_COMPROMISE = 0x02, - CA_COMPROMISE = 0x03, - AFFILIATION_CHANGED = 0x04, - SUSPENDED = 0x05, - CESSATION_OF_OPERATION = 0x06, - PRIVILEDGE_WITHDRAWN = 0x07, - REVOCATION_EXTENSIONS = static_cast(0x80000000u) + KMIP_REVOKE_UNSPECIFIED = 0x01, + KMIP_REVOKE_KEY_COMPROMISE = 0x02, + KMIP_REVOKE_CA_COMPROMISE = 0x03, + KMIP_REVOKE_AFFILIATION_CHANGED = 0x04, + KMIP_REVOKE_SUSPENDED = 0x05, + KMIP_REVOKE_CESSATION_OF_OPERATION = 0x06, + KMIP_REVOKE_PRIVILEDGE_WITHDRAWN = 0x07, + KMIP_REVOKE_EXTENSIONS = static_cast(0x80000000u) }; /** @brief KMIP secret payload data type identifiers. */ -enum secret_data_type : int32_t { +enum class secret_data_type : std::uint32_t { // KMIP 1.0 - PASSWORD = 0x01, - SEED = 0x02, - SECRET_DATA_EXTENSIONS = static_cast(0x80000000u) + KMIP_SECDATA_PASSWORD = 0x01, + KMIP_SECDATA_SEED = 0x02, + KMIP_SECDATA_EXTENSIONS = static_cast(0x80000000u) }; + +// --------------------------------------------------------------------------- +// Compatibility constants for legacy unqualified enumerator usage. +// These preserve existing integer-based call sites while the codebase is +// migrated to explicit scoped-enum references. +// --------------------------------------------------------------------------- + +inline constexpr std::uint32_t KMIP_ATTEST_TPM_QUOTE = static_cast(attestation_type::KMIP_ATTEST_TPM_QUOTE); +inline constexpr std::uint32_t KMIP_ATTEST_TCG_INTEGRITY_REPORT = static_cast(attestation_type::KMIP_ATTEST_TCG_INTEGRITY_REPORT); +inline constexpr std::uint32_t KMIP_ATTEST_SAML_ASSERTION = static_cast(attestation_type::KMIP_ATTEST_SAML_ASSERTION); +inline constexpr std::uint32_t KMIP_ATTR_UNIQUE_IDENTIFIER = static_cast(attribute_type::KMIP_ATTR_UNIQUE_IDENTIFIER); +inline constexpr std::uint32_t KMIP_ATTR_NAME = static_cast(attribute_type::KMIP_ATTR_NAME); +inline constexpr std::uint32_t KMIP_ATTR_OBJECT_TYPE = static_cast(attribute_type::KMIP_ATTR_OBJECT_TYPE); +inline constexpr std::uint32_t KMIP_ATTR_CRYPTOGRAPHIC_ALGORITHM = static_cast(attribute_type::KMIP_ATTR_CRYPTOGRAPHIC_ALGORITHM); +inline constexpr std::uint32_t KMIP_ATTR_CRYPTOGRAPHIC_LENGTH = static_cast(attribute_type::KMIP_ATTR_CRYPTOGRAPHIC_LENGTH); +inline constexpr std::uint32_t KMIP_ATTR_OPERATION_POLICY_NAME = static_cast(attribute_type::KMIP_ATTR_OPERATION_POLICY_NAME); +inline constexpr std::uint32_t KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK = static_cast(attribute_type::KMIP_ATTR_CRYPTOGRAPHIC_USAGE_MASK); +inline constexpr std::uint32_t KMIP_ATTR_STATE = static_cast(attribute_type::KMIP_ATTR_STATE); +inline constexpr std::uint32_t KMIP_ATTR_APPLICATION_SPECIFIC_INFORMATION = static_cast(attribute_type::KMIP_ATTR_APPLICATION_SPECIFIC_INFORMATION); +inline constexpr std::uint32_t KMIP_ATTR_OBJECT_GROUP = static_cast(attribute_type::KMIP_ATTR_OBJECT_GROUP); +inline constexpr std::uint32_t KMIP_ATTR_ACTIVATION_DATE = static_cast(attribute_type::KMIP_ATTR_ACTIVATION_DATE); +inline constexpr std::uint32_t KMIP_ATTR_DEACTIVATION_DATE = static_cast(attribute_type::KMIP_ATTR_DEACTIVATION_DATE); +inline constexpr std::uint32_t KMIP_ATTR_PROCESS_START_DATE = static_cast(attribute_type::KMIP_ATTR_PROCESS_START_DATE); +inline constexpr std::uint32_t KMIP_ATTR_PROTECT_STOP_DATE = static_cast(attribute_type::KMIP_ATTR_PROTECT_STOP_DATE); +inline constexpr std::uint32_t KMIP_ATTR_CRYPTOGRAPHIC_PARAMETERS = static_cast(attribute_type::KMIP_ATTR_CRYPTOGRAPHIC_PARAMETERS); +inline constexpr std::uint32_t KMIP_BATCH_CONTINUE = static_cast(batch_error_continuation_option::KMIP_BATCH_CONTINUE); +inline constexpr std::uint32_t KMIP_BATCH_STOP = static_cast(batch_error_continuation_option::KMIP_BATCH_STOP); +inline constexpr std::uint32_t KMIP_BATCH_UNDO = static_cast(batch_error_continuation_option::KMIP_BATCH_UNDO); +inline constexpr std::uint32_t KMIP_BLOCK_CBC = static_cast(block_cipher_mode::KMIP_BLOCK_CBC); +inline constexpr std::uint32_t KMIP_BLOCK_ECB = static_cast(block_cipher_mode::KMIP_BLOCK_ECB); +inline constexpr std::uint32_t KMIP_BLOCK_PCBC = static_cast(block_cipher_mode::KMIP_BLOCK_PCBC); +inline constexpr std::uint32_t KMIP_BLOCK_CFB = static_cast(block_cipher_mode::KMIP_BLOCK_CFB); +inline constexpr std::uint32_t KMIP_BLOCK_OFB = static_cast(block_cipher_mode::KMIP_BLOCK_OFB); +inline constexpr std::uint32_t KMIP_BLOCK_CTR = static_cast(block_cipher_mode::KMIP_BLOCK_CTR); +inline constexpr std::uint32_t KMIP_BLOCK_CMAC = static_cast(block_cipher_mode::KMIP_BLOCK_CMAC); +inline constexpr std::uint32_t KMIP_BLOCK_CCM = static_cast(block_cipher_mode::KMIP_BLOCK_CCM); +inline constexpr std::uint32_t KMIP_BLOCK_GCM = static_cast(block_cipher_mode::KMIP_BLOCK_GCM); +inline constexpr std::uint32_t KMIP_BLOCK_CBC_MAC = static_cast(block_cipher_mode::KMIP_BLOCK_CBC_MAC); +inline constexpr std::uint32_t KMIP_BLOCK_XTS = static_cast(block_cipher_mode::KMIP_BLOCK_XTS); +inline constexpr std::uint32_t KMIP_BLOCK_AES_KEY_WRAP_PADDING = static_cast(block_cipher_mode::KMIP_BLOCK_AES_KEY_WRAP_PADDING); +inline constexpr std::uint32_t KMIP_BLOCK_NIST_KEY_WRAP = static_cast(block_cipher_mode::KMIP_BLOCK_NIST_KEY_WRAP); +inline constexpr std::uint32_t KMIP_BLOCK_X9102_AESKW = static_cast(block_cipher_mode::KMIP_BLOCK_X9102_AESKW); +inline constexpr std::uint32_t KMIP_BLOCK_X9102_TDKW = static_cast(block_cipher_mode::KMIP_BLOCK_X9102_TDKW); +inline constexpr std::uint32_t KMIP_BLOCK_X9102_AKW1 = static_cast(block_cipher_mode::KMIP_BLOCK_X9102_AKW1); +inline constexpr std::uint32_t KMIP_BLOCK_X9102_AKW2 = static_cast(block_cipher_mode::KMIP_BLOCK_X9102_AKW2); +inline constexpr std::uint32_t KMIP_BLOCK_AEAD = static_cast(block_cipher_mode::KMIP_BLOCK_AEAD); +inline constexpr std::uint32_t KMIP_CRED_USERNAME_AND_PASSWORD = static_cast(credential_type::KMIP_CRED_USERNAME_AND_PASSWORD); +inline constexpr std::uint32_t KMIP_CRED_DEVICE = static_cast(credential_type::KMIP_CRED_DEVICE); +inline constexpr std::uint32_t KMIP_CRED_ATTESTATION = static_cast(credential_type::KMIP_CRED_ATTESTATION); +inline constexpr std::uint32_t KMIP_CRED_ONE_TIME_PASSWORD = static_cast(credential_type::KMIP_CRED_ONE_TIME_PASSWORD); +inline constexpr std::uint32_t KMIP_CRED_HASHED_PASSWORD = static_cast(credential_type::KMIP_CRED_HASHED_PASSWORD); +inline constexpr std::uint32_t KMIP_CRED_TICKET = static_cast(credential_type::KMIP_CRED_TICKET); +inline constexpr std::uint32_t KMIP_CRYPTOALG_UNSET = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_UNSET); +inline constexpr std::uint32_t KMIP_CRYPTOALG_DES = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_DES); +inline constexpr std::uint32_t KMIP_CRYPTOALG_TRIPLE_DES = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_TRIPLE_DES); +inline constexpr std::uint32_t KMIP_CRYPTOALG_AES = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_AES); +inline constexpr std::uint32_t KMIP_CRYPTOALG_RSA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_RSA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_DSA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_DSA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ECDSA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ECDSA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA1 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA1); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA224 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA224); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA256 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA256); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA384 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA384); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA512 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA512); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_MD5 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_MD5); +inline constexpr std::uint32_t KMIP_CRYPTOALG_DH = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_DH); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ECDH = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ECDH); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ECMQV = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ECMQV); +inline constexpr std::uint32_t KMIP_CRYPTOALG_BLOWFISH = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_BLOWFISH); +inline constexpr std::uint32_t KMIP_CRYPTOALG_CAMELLIA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_CAMELLIA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_CAST5 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_CAST5); +inline constexpr std::uint32_t KMIP_CRYPTOALG_IDEA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_IDEA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_MARS = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_MARS); +inline constexpr std::uint32_t KMIP_CRYPTOALG_RC2 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_RC2); +inline constexpr std::uint32_t KMIP_CRYPTOALG_RC4 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_RC4); +inline constexpr std::uint32_t KMIP_CRYPTOALG_RC5 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_RC5); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SKIPJACK = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SKIPJACK); +inline constexpr std::uint32_t KMIP_CRYPTOALG_TWOFISH = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_TWOFISH); +inline constexpr std::uint32_t KMIP_CRYPTOALG_EC = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_EC); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ONE_TIME_PAD = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ONE_TIME_PAD); +inline constexpr std::uint32_t KMIP_CRYPTOALG_CHACHA20 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_CHACHA20); +inline constexpr std::uint32_t KMIP_CRYPTOALG_POLY1305 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_POLY1305); +inline constexpr std::uint32_t KMIP_CRYPTOALG_CHACHA20_POLY1305 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_CHACHA20_POLY1305); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHA3_224 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHA3_224); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHA3_256 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHA3_256); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHA3_384 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHA3_384); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHA3_512 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHA3_512); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA3_224 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA3_224); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA3_256 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA3_256); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA3_384 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA3_384); +inline constexpr std::uint32_t KMIP_CRYPTOALG_HMAC_SHA3_512 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_HMAC_SHA3_512); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHAKE_128 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHAKE_128); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SHAKE_256 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SHAKE_256); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ARIA = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ARIA); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SEED = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SEED); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SM2 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SM2); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SM3 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SM3); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SM4 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SM4); +inline constexpr std::uint32_t KMIP_CRYPTOALG_GOST_R_34_10_2012 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_GOST_R_34_10_2012); +inline constexpr std::uint32_t KMIP_CRYPTOALG_GOST_R_34_11_2012 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_GOST_R_34_11_2012); +inline constexpr std::uint32_t KMIP_CRYPTOALG_GOST_R_34_13_2015 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_GOST_R_34_13_2015); +inline constexpr std::uint32_t KMIP_CRYPTOALG_GOST_28147_89 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_GOST_28147_89); +inline constexpr std::uint32_t KMIP_CRYPTOALG_XMSS = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_XMSS); +inline constexpr std::uint32_t KMIP_CRYPTOALG_SPHINCS_256 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_SPHINCS_256); +inline constexpr std::uint32_t KMIP_CRYPTOALG_MCELIECE = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_MCELIECE); +inline constexpr std::uint32_t KMIP_CRYPTOALG_MCELIECE_6960119 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_MCELIECE_6960119); +inline constexpr std::uint32_t KMIP_CRYPTOALG_MCELIECE_8192128 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_MCELIECE_8192128); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ED25519 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ED25519); +inline constexpr std::uint32_t KMIP_CRYPTOALG_ED448 = static_cast(cryptographic_algorithm::KMIP_CRYPTOALG_ED448); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_UNSET = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_SIGN = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_SIGN); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_VERIFY = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_VERIFY); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_ENCRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_ENCRYPT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_DECRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_DECRYPT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_WRAP_KEY = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_WRAP_KEY); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_UNWRAP_KEY = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_UNWRAP_KEY); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_EXPORT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_EXPORT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_MAC_GENERATE = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_MAC_GENERATE); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_MAC_VERIFY = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_MAC_VERIFY); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_DERIVE_KEY = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_DERIVE_KEY); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_CONTENT_COMMITMENT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_CONTENT_COMMITMENT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_KEY_AGREEMENT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_KEY_AGREEMENT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_CERTIFICATE_SIGN = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_CERTIFICATE_SIGN); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_CRL_SIGN = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_CRL_SIGN); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_GENERATE_CRYPTOGRAM = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_GENERATE_CRYPTOGRAM); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_VALIDATE_CRYPTOGRAM = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_VALIDATE_CRYPTOGRAM); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_TRANSLATE_ENCRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_TRANSLATE_ENCRYPT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_TRANSLATE_DECRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_TRANSLATE_DECRYPT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_TRANSLATE_WRAP = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_TRANSLATE_WRAP); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_TRANSLATE_UNWRAP = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_TRANSLATE_UNWRAP); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_AUTHENTICATE = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_AUTHENTICATE); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_UNRESTRICTED = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_UNRESTRICTED); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_FPE_ENCRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_FPE_ENCRYPT); +inline constexpr std::uint32_t KMIP_CRYPTOMASK_FPE_DECRYPT = static_cast(cryptographic_usage_mask::KMIP_CRYPTOMASK_FPE_DECRYPT); +inline constexpr std::uint32_t KMIP_DIGITAL_MD2_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_MD2_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_MD5_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_MD5_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA1_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA1_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA224_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA224_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA256_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA256_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA384_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA384_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA512_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA512_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_RSASSA_PSS = static_cast(digital_signature_algorithm::KMIP_DIGITAL_RSASSA_PSS); +inline constexpr std::uint32_t KMIP_DIGITAL_DSA_WITH_SHA1 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_DSA_WITH_SHA1); +inline constexpr std::uint32_t KMIP_DIGITAL_DSA_WITH_SHA224 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_DSA_WITH_SHA224); +inline constexpr std::uint32_t KMIP_DIGITAL_DSA_WITH_SHA256 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_DSA_WITH_SHA256); +inline constexpr std::uint32_t KMIP_DIGITAL_ECDSA_WITH_SHA1 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_ECDSA_WITH_SHA1); +inline constexpr std::uint32_t KMIP_DIGITAL_ECDSA_WITH_SHA224 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_ECDSA_WITH_SHA224); +inline constexpr std::uint32_t KMIP_DIGITAL_ECDSA_WITH_SHA256 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_ECDSA_WITH_SHA256); +inline constexpr std::uint32_t KMIP_DIGITAL_ECDSA_WITH_SHA384 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_ECDSA_WITH_SHA384); +inline constexpr std::uint32_t KMIP_DIGITAL_ECDSA_WITH_SHA512 = static_cast(digital_signature_algorithm::KMIP_DIGITAL_ECDSA_WITH_SHA512); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA3_256_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA3_256_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA3_384_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA3_384_WITH_RSA); +inline constexpr std::uint32_t KMIP_DIGITAL_SHA3_512_WITH_RSA = static_cast(digital_signature_algorithm::KMIP_DIGITAL_SHA3_512_WITH_RSA); +inline constexpr std::uint32_t KMIP_ENCODE_NO_ENCODING = static_cast(encoding_option::KMIP_ENCODE_NO_ENCODING); +inline constexpr std::uint32_t KMIP_ENCODE_TTLV_ENCODING = static_cast(encoding_option::KMIP_ENCODE_TTLV_ENCODING); +inline constexpr std::uint32_t KMIP_HASH_MD2 = static_cast(hashing_algorithm::KMIP_HASH_MD2); +inline constexpr std::uint32_t KMIP_HASH_MD4 = static_cast(hashing_algorithm::KMIP_HASH_MD4); +inline constexpr std::uint32_t KMIP_HASH_MD5 = static_cast(hashing_algorithm::KMIP_HASH_MD5); +inline constexpr std::uint32_t KMIP_HASH_SHA1 = static_cast(hashing_algorithm::KMIP_HASH_SHA1); +inline constexpr std::uint32_t KMIP_HASH_SHA224 = static_cast(hashing_algorithm::KMIP_HASH_SHA224); +inline constexpr std::uint32_t KMIP_HASH_SHA256 = static_cast(hashing_algorithm::KMIP_HASH_SHA256); +inline constexpr std::uint32_t KMIP_HASH_SHA384 = static_cast(hashing_algorithm::KMIP_HASH_SHA384); +inline constexpr std::uint32_t KMIP_HASH_SHA512 = static_cast(hashing_algorithm::KMIP_HASH_SHA512); +inline constexpr std::uint32_t KMIP_HASH_RIPEMD160 = static_cast(hashing_algorithm::KMIP_HASH_RIPEMD160); +inline constexpr std::uint32_t KMIP_HASH_TIGER = static_cast(hashing_algorithm::KMIP_HASH_TIGER); +inline constexpr std::uint32_t KMIP_HASH_WHIRLPOOL = static_cast(hashing_algorithm::KMIP_HASH_WHIRLPOOL); +inline constexpr std::uint32_t KMIP_HASH_SHA512_224 = static_cast(hashing_algorithm::KMIP_HASH_SHA512_224); +inline constexpr std::uint32_t KMIP_HASH_SHA512_256 = static_cast(hashing_algorithm::KMIP_HASH_SHA512_256); +inline constexpr std::uint32_t KMIP_HASH_SHA3_224 = static_cast(hashing_algorithm::KMIP_HASH_SHA3_224); +inline constexpr std::uint32_t KMIP_HASH_SHA3_256 = static_cast(hashing_algorithm::KMIP_HASH_SHA3_256); +inline constexpr std::uint32_t KMIP_HASH_SHA3_384 = static_cast(hashing_algorithm::KMIP_HASH_SHA3_384); +inline constexpr std::uint32_t KMIP_HASH_SHA3_512 = static_cast(hashing_algorithm::KMIP_HASH_SHA3_512); +inline constexpr std::uint32_t KMIP_KEYCOMP_EC_PUB_UNCOMPRESSED = static_cast(key_compression_type::KMIP_KEYCOMP_EC_PUB_UNCOMPRESSED); +inline constexpr std::uint32_t KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_PRIME = static_cast(key_compression_type::KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_PRIME); +inline constexpr std::uint32_t KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_CHAR2 = static_cast(key_compression_type::KMIP_KEYCOMP_EC_PUB_X962_COMPRESSED_CHAR2); +inline constexpr std::uint32_t KMIP_KEYCOMP_EC_PUB_X962_HYBRID = static_cast(key_compression_type::KMIP_KEYCOMP_EC_PUB_X962_HYBRID); +inline constexpr std::uint32_t KMIP_KEYFORMAT_RAW = static_cast(key_format_type::KMIP_KEYFORMAT_RAW); +inline constexpr std::uint32_t KMIP_KEYFORMAT_OPAQUE = static_cast(key_format_type::KMIP_KEYFORMAT_OPAQUE); +inline constexpr std::uint32_t KMIP_KEYFORMAT_PKCS1 = static_cast(key_format_type::KMIP_KEYFORMAT_PKCS1); +inline constexpr std::uint32_t KMIP_KEYFORMAT_PKCS8 = static_cast(key_format_type::KMIP_KEYFORMAT_PKCS8); +inline constexpr std::uint32_t KMIP_KEYFORMAT_X509 = static_cast(key_format_type::KMIP_KEYFORMAT_X509); +inline constexpr std::uint32_t KMIP_KEYFORMAT_EC_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_EC_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_SYMMETRIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_SYMMETRIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_DSA_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_DSA_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_DSA_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_DSA_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_RSA_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_RSA_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_RSA_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_RSA_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_DH_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_DH_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_DH_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_DH_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECDSA_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECDSA_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECDSA_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECDSA_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECDH_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECDH_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECDH_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECDH_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECMQV_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECMQV_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_ECMQV_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_ECMQV_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_EC_PRIVATE_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_EC_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_TRANS_EC_PUBLIC_KEY = static_cast(key_format_type::KMIP_KEYFORMAT_TRANS_EC_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_KEYFORMAT_PKCS12 = static_cast(key_format_type::KMIP_KEYFORMAT_PKCS12); +inline constexpr std::uint32_t KMIP_KEYFORMAT_PKCS10 = static_cast(key_format_type::KMIP_KEYFORMAT_PKCS10); +inline constexpr std::uint32_t KMIP_ROLE_BDK = static_cast(key_role_type::KMIP_ROLE_BDK); +inline constexpr std::uint32_t KMIP_ROLE_CVK = static_cast(key_role_type::KMIP_ROLE_CVK); +inline constexpr std::uint32_t KMIP_ROLE_DEK = static_cast(key_role_type::KMIP_ROLE_DEK); +inline constexpr std::uint32_t KMIP_ROLE_MKAC = static_cast(key_role_type::KMIP_ROLE_MKAC); +inline constexpr std::uint32_t KMIP_ROLE_MKSMC = static_cast(key_role_type::KMIP_ROLE_MKSMC); +inline constexpr std::uint32_t KMIP_ROLE_MKSMI = static_cast(key_role_type::KMIP_ROLE_MKSMI); +inline constexpr std::uint32_t KMIP_ROLE_MKDAC = static_cast(key_role_type::KMIP_ROLE_MKDAC); +inline constexpr std::uint32_t KMIP_ROLE_MKDN = static_cast(key_role_type::KMIP_ROLE_MKDN); +inline constexpr std::uint32_t KMIP_ROLE_MKCP = static_cast(key_role_type::KMIP_ROLE_MKCP); +inline constexpr std::uint32_t KMIP_ROLE_MKOTH = static_cast(key_role_type::KMIP_ROLE_MKOTH); +inline constexpr std::uint32_t KMIP_ROLE_KEK = static_cast(key_role_type::KMIP_ROLE_KEK); +inline constexpr std::uint32_t KMIP_ROLE_MAC16609 = static_cast(key_role_type::KMIP_ROLE_MAC16609); +inline constexpr std::uint32_t KMIP_ROLE_MAC97971 = static_cast(key_role_type::KMIP_ROLE_MAC97971); +inline constexpr std::uint32_t KMIP_ROLE_MAC97972 = static_cast(key_role_type::KMIP_ROLE_MAC97972); +inline constexpr std::uint32_t KMIP_ROLE_MAC97973 = static_cast(key_role_type::KMIP_ROLE_MAC97973); +inline constexpr std::uint32_t KMIP_ROLE_MAC97974 = static_cast(key_role_type::KMIP_ROLE_MAC97974); +inline constexpr std::uint32_t KMIP_ROLE_MAC97975 = static_cast(key_role_type::KMIP_ROLE_MAC97975); +inline constexpr std::uint32_t KMIP_ROLE_ZPK = static_cast(key_role_type::KMIP_ROLE_ZPK); +inline constexpr std::uint32_t KMIP_ROLE_PVKIBM = static_cast(key_role_type::KMIP_ROLE_PVKIBM); +inline constexpr std::uint32_t KMIP_ROLE_PVKPVV = static_cast(key_role_type::KMIP_ROLE_PVKPVV); +inline constexpr std::uint32_t KMIP_ROLE_PVKOTH = static_cast(key_role_type::KMIP_ROLE_PVKOTH); +inline constexpr std::uint32_t KMIP_ROLE_DUKPT = static_cast(key_role_type::KMIP_ROLE_DUKPT); +inline constexpr std::uint32_t KMIP_ROLE_IV = static_cast(key_role_type::KMIP_ROLE_IV); +inline constexpr std::uint32_t KMIP_ROLE_TRKBK = static_cast(key_role_type::KMIP_ROLE_TRKBK); +inline constexpr std::uint32_t KMIP_WRAPTYPE_NOT_WRAPPED = static_cast(key_wrap_type::KMIP_WRAPTYPE_NOT_WRAPPED); +inline constexpr std::uint32_t KMIP_WRAPTYPE_AS_REGISTERED = static_cast(key_wrap_type::KMIP_WRAPTYPE_AS_REGISTERED); +inline constexpr std::uint32_t KMIP_1_0 = static_cast(kmip_version::KMIP_1_0); +inline constexpr std::uint32_t KMIP_1_1 = static_cast(kmip_version::KMIP_1_1); +inline constexpr std::uint32_t KMIP_1_2 = static_cast(kmip_version::KMIP_1_2); +inline constexpr std::uint32_t KMIP_1_3 = static_cast(kmip_version::KMIP_1_3); +inline constexpr std::uint32_t KMIP_1_4 = static_cast(kmip_version::KMIP_1_4); +inline constexpr std::uint32_t KMIP_2_0 = static_cast(kmip_version::KMIP_2_0); +inline constexpr std::uint32_t KMIP_MASKGEN_MGF1 = static_cast(mask_generator::KMIP_MASKGEN_MGF1); +inline constexpr std::uint32_t KMIP_NAME_UNINTERPRETED_TEXT_STRING = static_cast(name_type::KMIP_NAME_UNINTERPRETED_TEXT_STRING); +inline constexpr std::uint32_t KMIP_NAME_URI = static_cast(name_type::KMIP_NAME_URI); +inline constexpr std::uint32_t KMIP_OBJTYPE_CERTIFICATE = static_cast(object_type::KMIP_OBJTYPE_CERTIFICATE); +inline constexpr std::uint32_t KMIP_OBJTYPE_SYMMETRIC_KEY = static_cast(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); +inline constexpr std::uint32_t KMIP_OBJTYPE_PUBLIC_KEY = static_cast(object_type::KMIP_OBJTYPE_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_OBJTYPE_PRIVATE_KEY = static_cast(object_type::KMIP_OBJTYPE_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_OBJTYPE_SPLIT_KEY = static_cast(object_type::KMIP_OBJTYPE_SPLIT_KEY); +inline constexpr std::uint32_t KMIP_OBJTYPE_TEMPLATE = static_cast(object_type::KMIP_OBJTYPE_TEMPLATE); +inline constexpr std::uint32_t KMIP_OBJTYPE_SECRET_DATA = static_cast(object_type::KMIP_OBJTYPE_SECRET_DATA); +inline constexpr std::uint32_t KMIP_OBJTYPE_OPAQUE_OBJECT = static_cast(object_type::KMIP_OBJTYPE_OPAQUE_OBJECT); +inline constexpr std::uint32_t KMIP_OBJTYPE_PGP_KEY = static_cast(object_type::KMIP_OBJTYPE_PGP_KEY); +inline constexpr std::uint32_t KMIP_OBJTYPE_CERTIFICATE_REQUEST = static_cast(object_type::KMIP_OBJTYPE_CERTIFICATE_REQUEST); +inline constexpr std::uint32_t KMIP_OP_CREATE = static_cast(operation::KMIP_OP_CREATE); +inline constexpr std::uint32_t KMIP_OP_CREATE_KEY_PAIR = static_cast(operation::KMIP_OP_CREATE_KEY_PAIR); +inline constexpr std::uint32_t KMIP_OP_REGISTER = static_cast(operation::KMIP_OP_REGISTER); +inline constexpr std::uint32_t KMIP_OP_REKEY = static_cast(operation::KMIP_OP_REKEY); +inline constexpr std::uint32_t KMIP_OP_DERIVE_KEY = static_cast(operation::KMIP_OP_DERIVE_KEY); +inline constexpr std::uint32_t KMIP_OP_CERTIFY = static_cast(operation::KMIP_OP_CERTIFY); +inline constexpr std::uint32_t KMIP_OP_RECERTIFY = static_cast(operation::KMIP_OP_RECERTIFY); +inline constexpr std::uint32_t KMIP_OP_LOCATE = static_cast(operation::KMIP_OP_LOCATE); +inline constexpr std::uint32_t KMIP_OP_CHECK = static_cast(operation::KMIP_OP_CHECK); +inline constexpr std::uint32_t KMIP_OP_GET = static_cast(operation::KMIP_OP_GET); +inline constexpr std::uint32_t KMIP_OP_GET_ATTRIBUTES = static_cast(operation::KMIP_OP_GET_ATTRIBUTES); +inline constexpr std::uint32_t KMIP_OP_GET_ATTRIBUTE_LIST = static_cast(operation::KMIP_OP_GET_ATTRIBUTE_LIST); +inline constexpr std::uint32_t KMIP_OP_ADD_ATTRIBUTE = static_cast(operation::KMIP_OP_ADD_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_OP_MODIFY_ATTRIBUTE = static_cast(operation::KMIP_OP_MODIFY_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_OP_DELETE_ATTRIBUTE = static_cast(operation::KMIP_OP_DELETE_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_OP_OBTAIN_LEASE = static_cast(operation::KMIP_OP_OBTAIN_LEASE); +inline constexpr std::uint32_t KMIP_OP_GET_USAGE_ALLOCATION = static_cast(operation::KMIP_OP_GET_USAGE_ALLOCATION); +inline constexpr std::uint32_t KMIP_OP_ACTIVATE = static_cast(operation::KMIP_OP_ACTIVATE); +inline constexpr std::uint32_t KMIP_OP_REVOKE = static_cast(operation::KMIP_OP_REVOKE); +inline constexpr std::uint32_t KMIP_OP_DESTROY = static_cast(operation::KMIP_OP_DESTROY); +inline constexpr std::uint32_t KMIP_OP_ARCHIVE = static_cast(operation::KMIP_OP_ARCHIVE); +inline constexpr std::uint32_t KMIP_OP_RECOVER = static_cast(operation::KMIP_OP_RECOVER); +inline constexpr std::uint32_t KMIP_OP_VALIDATE = static_cast(operation::KMIP_OP_VALIDATE); +inline constexpr std::uint32_t KMIP_OP_QUERY = static_cast(operation::KMIP_OP_QUERY); +inline constexpr std::uint32_t KMIP_OP_CANCEL = static_cast(operation::KMIP_OP_CANCEL); +inline constexpr std::uint32_t KMIP_OP_POLL = static_cast(operation::KMIP_OP_POLL); +inline constexpr std::uint32_t KMIP_OP_NOTIFY = static_cast(operation::KMIP_OP_NOTIFY); +inline constexpr std::uint32_t KMIP_OP_PUT = static_cast(operation::KMIP_OP_PUT); +inline constexpr std::uint32_t KMIP_OP_REKEY_KEY_PAIR = static_cast(operation::KMIP_OP_REKEY_KEY_PAIR); +inline constexpr std::uint32_t KMIP_OP_DISCOVER_VERSIONS = static_cast(operation::KMIP_OP_DISCOVER_VERSIONS); +inline constexpr std::uint32_t KMIP_OP_ENCRYPT = static_cast(operation::KMIP_OP_ENCRYPT); +inline constexpr std::uint32_t KMIP_OP_DECRYPT = static_cast(operation::KMIP_OP_DECRYPT); +inline constexpr std::uint32_t KMIP_OP_SIGN = static_cast(operation::KMIP_OP_SIGN); +inline constexpr std::uint32_t KMIP_OP_SIGNATURE_VERIFY = static_cast(operation::KMIP_OP_SIGNATURE_VERIFY); +inline constexpr std::uint32_t KMIP_OP_MAC = static_cast(operation::KMIP_OP_MAC); +inline constexpr std::uint32_t KMIP_OP_MAC_VERIFY = static_cast(operation::KMIP_OP_MAC_VERIFY); +inline constexpr std::uint32_t KMIP_OP_RNG_RETRIEVE = static_cast(operation::KMIP_OP_RNG_RETRIEVE); +inline constexpr std::uint32_t KMIP_OP_RNG_SEED = static_cast(operation::KMIP_OP_RNG_SEED); +inline constexpr std::uint32_t KMIP_OP_HASH = static_cast(operation::KMIP_OP_HASH); +inline constexpr std::uint32_t KMIP_OP_CREATE_SPLIT_KEY = static_cast(operation::KMIP_OP_CREATE_SPLIT_KEY); +inline constexpr std::uint32_t KMIP_OP_JOIN_SPLIT_KEY = static_cast(operation::KMIP_OP_JOIN_SPLIT_KEY); +inline constexpr std::uint32_t KMIP_OP_IMPORT = static_cast(operation::KMIP_OP_IMPORT); +inline constexpr std::uint32_t KMIP_OP_EXPORT = static_cast(operation::KMIP_OP_EXPORT); +inline constexpr std::uint32_t KMIP_OP_LOG = static_cast(operation::KMIP_OP_LOG); +inline constexpr std::uint32_t KMIP_OP_LOGIN = static_cast(operation::KMIP_OP_LOGIN); +inline constexpr std::uint32_t KMIP_OP_LOGOUT = static_cast(operation::KMIP_OP_LOGOUT); +inline constexpr std::uint32_t KMIP_OP_DELEGATED_LOGIN = static_cast(operation::KMIP_OP_DELEGATED_LOGIN); +inline constexpr std::uint32_t KMIP_OP_ADJUST_ATTRIBUTE = static_cast(operation::KMIP_OP_ADJUST_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_OP_SET_ATTRIBUTE = static_cast(operation::KMIP_OP_SET_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_OP_SET_ENDPOINT_ROLE = static_cast(operation::KMIP_OP_SET_ENDPOINT_ROLE); +inline constexpr std::uint32_t KMIP_OP_PKCS_11 = static_cast(operation::KMIP_OP_PKCS_11); +inline constexpr std::uint32_t KMIP_OP_INTEROP = static_cast(operation::KMIP_OP_INTEROP); +inline constexpr std::uint32_t KMIP_OP_REPROVISION = static_cast(operation::KMIP_OP_REPROVISION); +inline constexpr std::uint32_t KMIP_PAD_NONE = static_cast(padding_method::KMIP_PAD_NONE); +inline constexpr std::uint32_t KMIP_PAD_OAEP = static_cast(padding_method::KMIP_PAD_OAEP); +inline constexpr std::uint32_t KMIP_PAD_PKCS5 = static_cast(padding_method::KMIP_PAD_PKCS5); +inline constexpr std::uint32_t KMIP_PAD_SSL3 = static_cast(padding_method::KMIP_PAD_SSL3); +inline constexpr std::uint32_t KMIP_PAD_ZEROS = static_cast(padding_method::KMIP_PAD_ZEROS); +inline constexpr std::uint32_t KMIP_PAD_ANSI_X923 = static_cast(padding_method::KMIP_PAD_ANSI_X923); +inline constexpr std::uint32_t KMIP_PAD_ISO_10126 = static_cast(padding_method::KMIP_PAD_ISO_10126); +inline constexpr std::uint32_t KMIP_PAD_X931 = static_cast(padding_method::KMIP_PAD_X931); +inline constexpr std::uint32_t KMIP_PAD_PSS = static_cast(padding_method::KMIP_PAD_PSS); +inline constexpr std::uint32_t KMIP_PROTECT_SOFTWARE = static_cast(protection_storage_mask::KMIP_PROTECT_SOFTWARE); +inline constexpr std::uint32_t KMIP_PROTECT_HARDWARE = static_cast(protection_storage_mask::KMIP_PROTECT_HARDWARE); +inline constexpr std::uint32_t KMIP_PROTECT_ON_PROCESSOR = static_cast(protection_storage_mask::KMIP_PROTECT_ON_PROCESSOR); +inline constexpr std::uint32_t KMIP_PROTECT_ON_SYSTEM = static_cast(protection_storage_mask::KMIP_PROTECT_ON_SYSTEM); +inline constexpr std::uint32_t KMIP_PROTECT_OFF_SYSTEM = static_cast(protection_storage_mask::KMIP_PROTECT_OFF_SYSTEM); +inline constexpr std::uint32_t KMIP_PROTECT_HYPERVISOR = static_cast(protection_storage_mask::KMIP_PROTECT_HYPERVISOR); +inline constexpr std::uint32_t KMIP_PROTECT_OPERATING_SYSTEM = static_cast(protection_storage_mask::KMIP_PROTECT_OPERATING_SYSTEM); +inline constexpr std::uint32_t KMIP_PROTECT_CONTAINER = static_cast(protection_storage_mask::KMIP_PROTECT_CONTAINER); +inline constexpr std::uint32_t KMIP_PROTECT_ON_PREMISES = static_cast(protection_storage_mask::KMIP_PROTECT_ON_PREMISES); +inline constexpr std::uint32_t KMIP_PROTECT_OFF_PREMISES = static_cast(protection_storage_mask::KMIP_PROTECT_OFF_PREMISES); +inline constexpr std::uint32_t KMIP_PROTECT_SELF_MANAGED = static_cast(protection_storage_mask::KMIP_PROTECT_SELF_MANAGED); +inline constexpr std::uint32_t KMIP_PROTECT_OUTSOURCED = static_cast(protection_storage_mask::KMIP_PROTECT_OUTSOURCED); +inline constexpr std::uint32_t KMIP_PROTECT_VALIDATED = static_cast(protection_storage_mask::KMIP_PROTECT_VALIDATED); +inline constexpr std::uint32_t KMIP_PROTECT_SAME_JURISDICTION = static_cast(protection_storage_mask::KMIP_PROTECT_SAME_JURISDICTION); +inline constexpr std::uint32_t KMIP_QUERY_OPERATIONS = static_cast(query_function::KMIP_QUERY_OPERATIONS); +inline constexpr std::uint32_t KMIP_QUERY_OBJECTS = static_cast(query_function::KMIP_QUERY_OBJECTS); +inline constexpr std::uint32_t KMIP_QUERY_SERVER_INFORMATION = static_cast(query_function::KMIP_QUERY_SERVER_INFORMATION); +inline constexpr std::uint32_t KMIP_QUERY_APPLICATION_NAMESPACES = static_cast(query_function::KMIP_QUERY_APPLICATION_NAMESPACES); +inline constexpr std::uint32_t KMIP_QUERY_EXTENSION_LIST = static_cast(query_function::KMIP_QUERY_EXTENSION_LIST); +inline constexpr std::uint32_t KMIP_QUERY_EXTENSION_MAP = static_cast(query_function::KMIP_QUERY_EXTENSION_MAP); +inline constexpr std::uint32_t KMIP_QUERY_ATTESTATION_TYPES = static_cast(query_function::KMIP_QUERY_ATTESTATION_TYPES); +inline constexpr std::uint32_t KMIP_QUERY_RNGS = static_cast(query_function::KMIP_QUERY_RNGS); +inline constexpr std::uint32_t KMIP_QUERY_VALIDATIONS = static_cast(query_function::KMIP_QUERY_VALIDATIONS); +inline constexpr std::uint32_t KMIP_QUERY_PROFILES = static_cast(query_function::KMIP_QUERY_PROFILES); +inline constexpr std::uint32_t KMIP_QUERY_CAPABILITIES = static_cast(query_function::KMIP_QUERY_CAPABILITIES); +inline constexpr std::uint32_t KMIP_QUERY_CLIENT_REGISTRATION_METHODS = static_cast(query_function::KMIP_QUERY_CLIENT_REGISTRATION_METHODS); +inline constexpr std::uint32_t KMIP_QUERY_DEFAULTS_INFORMATION = static_cast(query_function::KMIP_QUERY_DEFAULTS_INFORMATION); +inline constexpr std::uint32_t KMIP_QUERY_STORAGE_PROTECTION_MASKS = static_cast(query_function::KMIP_QUERY_STORAGE_PROTECTION_MASKS); +inline constexpr std::uint32_t KMIP_REASON_GENERAL_FAILURE = static_cast(result_reason::KMIP_REASON_GENERAL_FAILURE); +inline constexpr std::uint32_t KMIP_REASON_ITEM_NOT_FOUND = static_cast(result_reason::KMIP_REASON_ITEM_NOT_FOUND); +inline constexpr std::uint32_t KMIP_REASON_RESPONSE_TOO_LARGE = static_cast(result_reason::KMIP_REASON_RESPONSE_TOO_LARGE); +inline constexpr std::uint32_t KMIP_REASON_AUTHENTICATION_NOT_SUCCESSFUL = static_cast(result_reason::KMIP_REASON_AUTHENTICATION_NOT_SUCCESSFUL); +inline constexpr std::uint32_t KMIP_REASON_INVALID_MESSAGE = static_cast(result_reason::KMIP_REASON_INVALID_MESSAGE); +inline constexpr std::uint32_t KMIP_REASON_OPERATION_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_OPERATION_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_MISSING_DATA = static_cast(result_reason::KMIP_REASON_MISSING_DATA); +inline constexpr std::uint32_t KMIP_REASON_INVALID_FIELD = static_cast(result_reason::KMIP_REASON_INVALID_FIELD); +inline constexpr std::uint32_t KMIP_REASON_FEATURE_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_FEATURE_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_OPERATION_CANCELED_BY_REQUESTER = static_cast(result_reason::KMIP_REASON_OPERATION_CANCELED_BY_REQUESTER); +inline constexpr std::uint32_t KMIP_REASON_CRYPTOGRAPHIC_FAILURE = static_cast(result_reason::KMIP_REASON_CRYPTOGRAPHIC_FAILURE); +inline constexpr std::uint32_t KMIP_REASON_ILLEGAL_OPERATION = static_cast(result_reason::KMIP_REASON_ILLEGAL_OPERATION); +inline constexpr std::uint32_t KMIP_REASON_PERMISSION_DENIED = static_cast(result_reason::KMIP_REASON_PERMISSION_DENIED); +inline constexpr std::uint32_t KMIP_REASON_OBJECT_ARCHIVED = static_cast(result_reason::KMIP_REASON_OBJECT_ARCHIVED); +inline constexpr std::uint32_t KMIP_REASON_INDEX_OUT_OF_BOUNDS = static_cast(result_reason::KMIP_REASON_INDEX_OUT_OF_BOUNDS); +inline constexpr std::uint32_t KMIP_REASON_APPLICATION_NAMESPACE_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_APPLICATION_NAMESPACE_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_KEY_FORMAT_TYPE_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_KEY_FORMAT_TYPE_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_KEY_COMPRESSION_TYPE_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_KEY_COMPRESSION_TYPE_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_ENCODING_OPTION_FAILURE = static_cast(result_reason::KMIP_REASON_ENCODING_OPTION_FAILURE); +inline constexpr std::uint32_t KMIP_REASON_KEY_VALUE_NOT_PRESENT = static_cast(result_reason::KMIP_REASON_KEY_VALUE_NOT_PRESENT); +inline constexpr std::uint32_t KMIP_REASON_ATTESTATION_REQUIRED = static_cast(result_reason::KMIP_REASON_ATTESTATION_REQUIRED); +inline constexpr std::uint32_t KMIP_REASON_ATTESTATION_FAILED = static_cast(result_reason::KMIP_REASON_ATTESTATION_FAILED); +inline constexpr std::uint32_t KMIP_REASON_SENSITIVE = static_cast(result_reason::KMIP_REASON_SENSITIVE); +inline constexpr std::uint32_t KMIP_REASON_NOT_EXTRACTABLE = static_cast(result_reason::KMIP_REASON_NOT_EXTRACTABLE); +inline constexpr std::uint32_t KMIP_REASON_OBJECT_ALREADY_EXISTS = static_cast(result_reason::KMIP_REASON_OBJECT_ALREADY_EXISTS); +inline constexpr std::uint32_t KMIP_REASON_INVALID_TICKET = static_cast(result_reason::KMIP_REASON_INVALID_TICKET); +inline constexpr std::uint32_t KMIP_REASON_USAGE_LIMIT_EXCEEDED = static_cast(result_reason::KMIP_REASON_USAGE_LIMIT_EXCEEDED); +inline constexpr std::uint32_t KMIP_REASON_NUMERIC_RANGE = static_cast(result_reason::KMIP_REASON_NUMERIC_RANGE); +inline constexpr std::uint32_t KMIP_REASON_INVALID_DATA_TYPE = static_cast(result_reason::KMIP_REASON_INVALID_DATA_TYPE); +inline constexpr std::uint32_t KMIP_REASON_READ_ONLY_ATTRIBUTE = static_cast(result_reason::KMIP_REASON_READ_ONLY_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_REASON_MULTI_VALUED_ATTRIBUTE = static_cast(result_reason::KMIP_REASON_MULTI_VALUED_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_REASON_UNSUPPORTED_ATTRIBUTE = static_cast(result_reason::KMIP_REASON_UNSUPPORTED_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_REASON_ATTRIBUTE_INSTANCE_NOT_FOUND = static_cast(result_reason::KMIP_REASON_ATTRIBUTE_INSTANCE_NOT_FOUND); +inline constexpr std::uint32_t KMIP_REASON_ATTRIBUTE_NOT_FOUND = static_cast(result_reason::KMIP_REASON_ATTRIBUTE_NOT_FOUND); +inline constexpr std::uint32_t KMIP_REASON_ATTRIBUTE_READ_ONLY = static_cast(result_reason::KMIP_REASON_ATTRIBUTE_READ_ONLY); +inline constexpr std::uint32_t KMIP_REASON_ATTRIBUTE_SINGLE_VALUED = static_cast(result_reason::KMIP_REASON_ATTRIBUTE_SINGLE_VALUED); +inline constexpr std::uint32_t KMIP_REASON_BAD_CRYPTOGRAPHIC_PARAMETERS = static_cast(result_reason::KMIP_REASON_BAD_CRYPTOGRAPHIC_PARAMETERS); +inline constexpr std::uint32_t KMIP_REASON_BAD_PASSWORD = static_cast(result_reason::KMIP_REASON_BAD_PASSWORD); +inline constexpr std::uint32_t KMIP_REASON_CODEC_ERROR = static_cast(result_reason::KMIP_REASON_CODEC_ERROR); +inline constexpr std::uint32_t KMIP_REASON_ILLEGAL_OBJECT_TYPE = static_cast(result_reason::KMIP_REASON_ILLEGAL_OBJECT_TYPE); +inline constexpr std::uint32_t KMIP_REASON_INCOMPATIBLE_CRYPTOGRAPHIC_USAGE_MASK = static_cast(result_reason::KMIP_REASON_INCOMPATIBLE_CRYPTOGRAPHIC_USAGE_MASK); +inline constexpr std::uint32_t KMIP_REASON_INTERNAL_SERVER_ERROR = static_cast(result_reason::KMIP_REASON_INTERNAL_SERVER_ERROR); +inline constexpr std::uint32_t KMIP_REASON_INVALID_ASYNCHRONOUS_CORRELATION_VALUE = static_cast(result_reason::KMIP_REASON_INVALID_ASYNCHRONOUS_CORRELATION_VALUE); +inline constexpr std::uint32_t KMIP_REASON_INVALID_ATTRIBUTE = static_cast(result_reason::KMIP_REASON_INVALID_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_REASON_INVALID_ATTRIBUTE_VALUE = static_cast(result_reason::KMIP_REASON_INVALID_ATTRIBUTE_VALUE); +inline constexpr std::uint32_t KMIP_REASON_INVALID_CORRELATION_VALUE = static_cast(result_reason::KMIP_REASON_INVALID_CORRELATION_VALUE); +inline constexpr std::uint32_t KMIP_REASON_INVALID_CSR = static_cast(result_reason::KMIP_REASON_INVALID_CSR); +inline constexpr std::uint32_t KMIP_REASON_INVALID_OBJECT_TYPE = static_cast(result_reason::KMIP_REASON_INVALID_OBJECT_TYPE); +inline constexpr std::uint32_t KMIP_REASON_KEY_WRAP_TYPE_NOT_SUPPORTED = static_cast(result_reason::KMIP_REASON_KEY_WRAP_TYPE_NOT_SUPPORTED); +inline constexpr std::uint32_t KMIP_REASON_MISSING_INITIALIZATION_VECTOR = static_cast(result_reason::KMIP_REASON_MISSING_INITIALIZATION_VECTOR); +inline constexpr std::uint32_t KMIP_REASON_NON_UNIQUE_NAME_ATTRIBUTE = static_cast(result_reason::KMIP_REASON_NON_UNIQUE_NAME_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_REASON_OBJECT_DESTROYED = static_cast(result_reason::KMIP_REASON_OBJECT_DESTROYED); +inline constexpr std::uint32_t KMIP_REASON_OBJECT_NOT_FOUND = static_cast(result_reason::KMIP_REASON_OBJECT_NOT_FOUND); +inline constexpr std::uint32_t KMIP_REASON_NOT_AUTHORISED = static_cast(result_reason::KMIP_REASON_NOT_AUTHORISED); +inline constexpr std::uint32_t KMIP_REASON_SERVER_LIMIT_EXCEEDED = static_cast(result_reason::KMIP_REASON_SERVER_LIMIT_EXCEEDED); +inline constexpr std::uint32_t KMIP_REASON_UNKNOWN_ENUMERATION = static_cast(result_reason::KMIP_REASON_UNKNOWN_ENUMERATION); +inline constexpr std::uint32_t KMIP_REASON_UNKNOWN_MESSAGE_EXTENSION = static_cast(result_reason::KMIP_REASON_UNKNOWN_MESSAGE_EXTENSION); +inline constexpr std::uint32_t KMIP_REASON_UNKNOWN_TAG = static_cast(result_reason::KMIP_REASON_UNKNOWN_TAG); +inline constexpr std::uint32_t KMIP_REASON_UNSUPPORTED_CRYPTOGRAPHIC_PARAMETERS = static_cast(result_reason::KMIP_REASON_UNSUPPORTED_CRYPTOGRAPHIC_PARAMETERS); +inline constexpr std::uint32_t KMIP_REASON_UNSUPPORTED_PROTOCOL_VERSION = static_cast(result_reason::KMIP_REASON_UNSUPPORTED_PROTOCOL_VERSION); +inline constexpr std::uint32_t KMIP_REASON_WRAPPING_OBJECT_ARCHIVED = static_cast(result_reason::KMIP_REASON_WRAPPING_OBJECT_ARCHIVED); +inline constexpr std::uint32_t KMIP_REASON_WRAPPING_OBJECT_DESTROYED = static_cast(result_reason::KMIP_REASON_WRAPPING_OBJECT_DESTROYED); +inline constexpr std::uint32_t KMIP_REASON_WRAPPING_OBJECT_NOT_FOUND = static_cast(result_reason::KMIP_REASON_WRAPPING_OBJECT_NOT_FOUND); +inline constexpr std::uint32_t KMIP_REASON_WRONG_KEY_LIFECYCLE_STATE = static_cast(result_reason::KMIP_REASON_WRONG_KEY_LIFECYCLE_STATE); +inline constexpr std::uint32_t KMIP_REASON_PROTECTION_STORAGE_UNAVAILABLE = static_cast(result_reason::KMIP_REASON_PROTECTION_STORAGE_UNAVAILABLE); +inline constexpr std::uint32_t KMIP_REASON_PKCS11_CODEC_ERROR = static_cast(result_reason::KMIP_REASON_PKCS11_CODEC_ERROR); +inline constexpr std::uint32_t KMIP_REASON_PKCS11_INVALID_FUNCTION = static_cast(result_reason::KMIP_REASON_PKCS11_INVALID_FUNCTION); +inline constexpr std::uint32_t KMIP_REASON_PKCS11_INVALID_INTERFACE = static_cast(result_reason::KMIP_REASON_PKCS11_INVALID_INTERFACE); +inline constexpr std::uint32_t KMIP_REASON_PRIVATE_PROTECTION_STORAGE_UNAVAILABLE = static_cast(result_reason::KMIP_REASON_PRIVATE_PROTECTION_STORAGE_UNAVAILABLE); +inline constexpr std::uint32_t KMIP_REASON_PUBLIC_PROTECTION_STORAGE_UNAVAILABLE = static_cast(result_reason::KMIP_REASON_PUBLIC_PROTECTION_STORAGE_UNAVAILABLE); +inline constexpr std::uint32_t KMIP_STATUS_SUCCESS = static_cast(result_status::KMIP_STATUS_SUCCESS); +inline constexpr std::uint32_t KMIP_STATUS_OPERATION_FAILED = static_cast(result_status::KMIP_STATUS_OPERATION_FAILED); +inline constexpr std::uint32_t KMIP_STATUS_OPERATION_PENDING = static_cast(result_status::KMIP_STATUS_OPERATION_PENDING); +inline constexpr std::uint32_t KMIP_STATUS_OPERATION_UNDONE = static_cast(result_status::KMIP_STATUS_OPERATION_UNDONE); +inline constexpr std::uint32_t KMIP_STATE_PRE_ACTIVE = static_cast(state::KMIP_STATE_PRE_ACTIVE); +inline constexpr std::uint32_t KMIP_STATE_ACTIVE = static_cast(state::KMIP_STATE_ACTIVE); +inline constexpr std::uint32_t KMIP_STATE_DEACTIVATED = static_cast(state::KMIP_STATE_DEACTIVATED); +inline constexpr std::uint32_t KMIP_STATE_COMPROMISED = static_cast(state::KMIP_STATE_COMPROMISED); +inline constexpr std::uint32_t KMIP_STATE_DESTROYED = static_cast(state::KMIP_STATE_DESTROYED); +inline constexpr std::uint32_t KMIP_STATE_DESTROYED_COMPROMISED = static_cast(state::KMIP_STATE_DESTROYED_COMPROMISED); +inline constexpr std::uint32_t KMIP_TAG_TAG = static_cast(tag::KMIP_TAG_TAG); +inline constexpr std::uint32_t KMIP_TAG_TYPE = static_cast(tag::KMIP_TAG_TYPE); +inline constexpr std::uint32_t KMIP_TAG_DEFAULT = static_cast(tag::KMIP_TAG_DEFAULT); +inline constexpr std::uint32_t KMIP_TAG_ACTIVATION_DATE = static_cast(tag::KMIP_TAG_ACTIVATION_DATE); +inline constexpr std::uint32_t KMIP_TAG_APPLICATION_DATA = static_cast(tag::KMIP_TAG_APPLICATION_DATA); +inline constexpr std::uint32_t KMIP_TAG_APPLICATION_NAMESPACE = static_cast(tag::KMIP_TAG_APPLICATION_NAMESPACE); +inline constexpr std::uint32_t KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION = static_cast(tag::KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION); +inline constexpr std::uint32_t KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE = static_cast(tag::KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE); +inline constexpr std::uint32_t KMIP_TAG_ASYNCHRONOUS_INDICATOR = static_cast(tag::KMIP_TAG_ASYNCHRONOUS_INDICATOR); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE = static_cast(tag::KMIP_TAG_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE_INDEX = static_cast(tag::KMIP_TAG_ATTRIBUTE_INDEX); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE_NAME = static_cast(tag::KMIP_TAG_ATTRIBUTE_NAME); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE_VALUE = static_cast(tag::KMIP_TAG_ATTRIBUTE_VALUE); +inline constexpr std::uint32_t KMIP_TAG_AUTHENTICATION = static_cast(tag::KMIP_TAG_AUTHENTICATION); +inline constexpr std::uint32_t KMIP_TAG_BATCH_COUNT = static_cast(tag::KMIP_TAG_BATCH_COUNT); +inline constexpr std::uint32_t KMIP_TAG_BATCH_ERROR_CONTINUATION_OPTION = static_cast(tag::KMIP_TAG_BATCH_ERROR_CONTINUATION_OPTION); +inline constexpr std::uint32_t KMIP_TAG_BATCH_ITEM = static_cast(tag::KMIP_TAG_BATCH_ITEM); +inline constexpr std::uint32_t KMIP_TAG_BATCH_ORDER_OPTION = static_cast(tag::KMIP_TAG_BATCH_ORDER_OPTION); +inline constexpr std::uint32_t KMIP_TAG_BLOCK_CIPHER_MODE = static_cast(tag::KMIP_TAG_BLOCK_CIPHER_MODE); +inline constexpr std::uint32_t KMIP_TAG_COMPROMISE_OCCURRANCE_DATE = static_cast(tag::KMIP_TAG_COMPROMISE_OCCURRANCE_DATE); +inline constexpr std::uint32_t KMIP_TAG_CREDENTIAL = static_cast(tag::KMIP_TAG_CREDENTIAL); +inline constexpr std::uint32_t KMIP_TAG_CREDENTIAL_TYPE = static_cast(tag::KMIP_TAG_CREDENTIAL_TYPE); +inline constexpr std::uint32_t KMIP_TAG_CREDENTIAL_VALUE = static_cast(tag::KMIP_TAG_CREDENTIAL_VALUE); +inline constexpr std::uint32_t KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM = static_cast(tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM); +inline constexpr std::uint32_t KMIP_TAG_CRYPTOGRAPHIC_LENGTH = static_cast(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_CRYPTOGRAPHIC_PARAMETERS = static_cast(tag::KMIP_TAG_CRYPTOGRAPHIC_PARAMETERS); +inline constexpr std::uint32_t KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK = static_cast(tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK); +inline constexpr std::uint32_t KMIP_TAG_DEACTIVATION_DATE = static_cast(tag::KMIP_TAG_DEACTIVATION_DATE); +inline constexpr std::uint32_t KMIP_TAG_ENCRYPTION_KEY_INFORMATION = static_cast(tag::KMIP_TAG_ENCRYPTION_KEY_INFORMATION); +inline constexpr std::uint32_t KMIP_TAG_HASHING_ALGORITHM = static_cast(tag::KMIP_TAG_HASHING_ALGORITHM); +inline constexpr std::uint32_t KMIP_TAG_IV_COUNTER_NONCE = static_cast(tag::KMIP_TAG_IV_COUNTER_NONCE); +inline constexpr std::uint32_t KMIP_TAG_KEY = static_cast(tag::KMIP_TAG_KEY); +inline constexpr std::uint32_t KMIP_TAG_KEY_BLOCK = static_cast(tag::KMIP_TAG_KEY_BLOCK); +inline constexpr std::uint32_t KMIP_TAG_KEY_COMPRESSION_TYPE = static_cast(tag::KMIP_TAG_KEY_COMPRESSION_TYPE); +inline constexpr std::uint32_t KMIP_TAG_KEY_FORMAT_TYPE = static_cast(tag::KMIP_TAG_KEY_FORMAT_TYPE); +inline constexpr std::uint32_t KMIP_TAG_KEY_MATERIAL = static_cast(tag::KMIP_TAG_KEY_MATERIAL); +inline constexpr std::uint32_t KMIP_TAG_KEY_VALUE = static_cast(tag::KMIP_TAG_KEY_VALUE); +inline constexpr std::uint32_t KMIP_TAG_KEY_WRAPPING_DATA = static_cast(tag::KMIP_TAG_KEY_WRAPPING_DATA); +inline constexpr std::uint32_t KMIP_TAG_KEY_WRAPPING_SPECIFICATION = static_cast(tag::KMIP_TAG_KEY_WRAPPING_SPECIFICATION); +inline constexpr std::uint32_t KMIP_TAG_MAC_SIGNATURE = static_cast(tag::KMIP_TAG_MAC_SIGNATURE); +inline constexpr std::uint32_t KMIP_TAG_MAC_SIGNATURE_KEY_INFORMATION = static_cast(tag::KMIP_TAG_MAC_SIGNATURE_KEY_INFORMATION); +inline constexpr std::uint32_t KMIP_TAG_MAXIMUM_ITEMS = static_cast(tag::KMIP_TAG_MAXIMUM_ITEMS); +inline constexpr std::uint32_t KMIP_TAG_MAXIMUM_RESPONSE_SIZE = static_cast(tag::KMIP_TAG_MAXIMUM_RESPONSE_SIZE); +inline constexpr std::uint32_t KMIP_TAG_NAME = static_cast(tag::KMIP_TAG_NAME); +inline constexpr std::uint32_t KMIP_TAG_NAME_TYPE = static_cast(tag::KMIP_TAG_NAME_TYPE); +inline constexpr std::uint32_t KMIP_TAG_NAME_VALUE = static_cast(tag::KMIP_TAG_NAME_VALUE); +inline constexpr std::uint32_t KMIP_TAG_OBJECT_GROUP = static_cast(tag::KMIP_TAG_OBJECT_GROUP); +inline constexpr std::uint32_t KMIP_TAG_OBJECT_TYPE = static_cast(tag::KMIP_TAG_OBJECT_TYPE); +inline constexpr std::uint32_t KMIP_TAG_OPERATION = static_cast(tag::KMIP_TAG_OPERATION); +inline constexpr std::uint32_t KMIP_TAG_OPERATION_POLICY_NAME = static_cast(tag::KMIP_TAG_OPERATION_POLICY_NAME); +inline constexpr std::uint32_t KMIP_TAG_PADDING_METHOD = static_cast(tag::KMIP_TAG_PADDING_METHOD); +inline constexpr std::uint32_t KMIP_TAG_PRIVATE_KEY = static_cast(tag::KMIP_TAG_PRIVATE_KEY); +inline constexpr std::uint32_t KMIP_TAG_PROCESS_START_DATE = static_cast(tag::KMIP_TAG_PROCESS_START_DATE); +inline constexpr std::uint32_t KMIP_TAG_PROTECT_STOP_DATE = static_cast(tag::KMIP_TAG_PROTECT_STOP_DATE); +inline constexpr std::uint32_t KMIP_TAG_PROTOCOL_VERSION = static_cast(tag::KMIP_TAG_PROTOCOL_VERSION); +inline constexpr std::uint32_t KMIP_TAG_PROTOCOL_VERSION_MAJOR = static_cast(tag::KMIP_TAG_PROTOCOL_VERSION_MAJOR); +inline constexpr std::uint32_t KMIP_TAG_PROTOCOL_VERSION_MINOR = static_cast(tag::KMIP_TAG_PROTOCOL_VERSION_MINOR); +inline constexpr std::uint32_t KMIP_TAG_PUBLIC_KEY = static_cast(tag::KMIP_TAG_PUBLIC_KEY); +inline constexpr std::uint32_t KMIP_TAG_QUERY_FUNCTION = static_cast(tag::KMIP_TAG_QUERY_FUNCTION); +inline constexpr std::uint32_t KMIP_TAG_REQUEST_HEADER = static_cast(tag::KMIP_TAG_REQUEST_HEADER); +inline constexpr std::uint32_t KMIP_TAG_REQUEST_MESSAGE = static_cast(tag::KMIP_TAG_REQUEST_MESSAGE); +inline constexpr std::uint32_t KMIP_TAG_REQUEST_PAYLOAD = static_cast(tag::KMIP_TAG_REQUEST_PAYLOAD); +inline constexpr std::uint32_t KMIP_TAG_RESPONSE_HEADER = static_cast(tag::KMIP_TAG_RESPONSE_HEADER); +inline constexpr std::uint32_t KMIP_TAG_RESPONSE_MESSAGE = static_cast(tag::KMIP_TAG_RESPONSE_MESSAGE); +inline constexpr std::uint32_t KMIP_TAG_RESPONSE_PAYLOAD = static_cast(tag::KMIP_TAG_RESPONSE_PAYLOAD); +inline constexpr std::uint32_t KMIP_TAG_RESULT_MESSAGE = static_cast(tag::KMIP_TAG_RESULT_MESSAGE); +inline constexpr std::uint32_t KMIP_TAG_RESULT_REASON = static_cast(tag::KMIP_TAG_RESULT_REASON); +inline constexpr std::uint32_t KMIP_TAG_RESULT_STATUS = static_cast(tag::KMIP_TAG_RESULT_STATUS); +inline constexpr std::uint32_t KMIP_TAG_REVOKATION_MESSAGE = static_cast(tag::KMIP_TAG_REVOKATION_MESSAGE); +inline constexpr std::uint32_t KMIP_TAG_REVOCATION_REASON = static_cast(tag::KMIP_TAG_REVOCATION_REASON); +inline constexpr std::uint32_t KMIP_TAG_REVOCATION_REASON_CODE = static_cast(tag::KMIP_TAG_REVOCATION_REASON_CODE); +inline constexpr std::uint32_t KMIP_TAG_KEY_ROLE_TYPE = static_cast(tag::KMIP_TAG_KEY_ROLE_TYPE); +inline constexpr std::uint32_t KMIP_TAG_SALT = static_cast(tag::KMIP_TAG_SALT); +inline constexpr std::uint32_t KMIP_TAG_SECRET_DATA = static_cast(tag::KMIP_TAG_SECRET_DATA); +inline constexpr std::uint32_t KMIP_TAG_SECRET_DATA_TYPE = static_cast(tag::KMIP_TAG_SECRET_DATA_TYPE); +inline constexpr std::uint32_t KMIP_TAG_SERVER_INFORMATION = static_cast(tag::KMIP_TAG_SERVER_INFORMATION); +inline constexpr std::uint32_t KMIP_TAG_STATE = static_cast(tag::KMIP_TAG_STATE); +inline constexpr std::uint32_t KMIP_TAG_STORAGE_STATUS_MASK = static_cast(tag::KMIP_TAG_STORAGE_STATUS_MASK); +inline constexpr std::uint32_t KMIP_TAG_SYMMETRIC_KEY = static_cast(tag::KMIP_TAG_SYMMETRIC_KEY); +inline constexpr std::uint32_t KMIP_TAG_TEMPLATE_ATTRIBUTE = static_cast(tag::KMIP_TAG_TEMPLATE_ATTRIBUTE); +inline constexpr std::uint32_t KMIP_TAG_TIME_STAMP = static_cast(tag::KMIP_TAG_TIME_STAMP); +inline constexpr std::uint32_t KMIP_TAG_UNIQUE_BATCH_ITEM_ID = static_cast(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); +inline constexpr std::uint32_t KMIP_TAG_UNIQUE_IDENTIFIER = static_cast(tag::KMIP_TAG_UNIQUE_IDENTIFIER); +inline constexpr std::uint32_t KMIP_TAG_USERNAME = static_cast(tag::KMIP_TAG_USERNAME); +inline constexpr std::uint32_t KMIP_TAG_VENDOR_IDENTIFICATION = static_cast(tag::KMIP_TAG_VENDOR_IDENTIFICATION); +inline constexpr std::uint32_t KMIP_TAG_WRAPPING_METHOD = static_cast(tag::KMIP_TAG_WRAPPING_METHOD); +inline constexpr std::uint32_t KMIP_TAG_PASSWORD = static_cast(tag::KMIP_TAG_PASSWORD); +inline constexpr std::uint32_t KMIP_TAG_DEVICE_IDENTIFIER = static_cast(tag::KMIP_TAG_DEVICE_IDENTIFIER); +inline constexpr std::uint32_t KMIP_TAG_ENCODING_OPTION = static_cast(tag::KMIP_TAG_ENCODING_OPTION); +inline constexpr std::uint32_t KMIP_TAG_MACHINE_IDENTIFIER = static_cast(tag::KMIP_TAG_MACHINE_IDENTIFIER); +inline constexpr std::uint32_t KMIP_TAG_MEDIA_IDENTIFIER = static_cast(tag::KMIP_TAG_MEDIA_IDENTIFIER); +inline constexpr std::uint32_t KMIP_TAG_NETWORK_IDENTIFIER = static_cast(tag::KMIP_TAG_NETWORK_IDENTIFIER); +inline constexpr std::uint32_t KMIP_TAG_OBJECT_GROUP_MEMBER = static_cast(tag::KMIP_TAG_OBJECT_GROUP_MEMBER); +inline constexpr std::uint32_t KMIP_TAG_DIGITAL_SIGNATURE_ALGORITHM = static_cast(tag::KMIP_TAG_DIGITAL_SIGNATURE_ALGORITHM); +inline constexpr std::uint32_t KMIP_TAG_DEVICE_SERIAL_NUMBER = static_cast(tag::KMIP_TAG_DEVICE_SERIAL_NUMBER); +inline constexpr std::uint32_t KMIP_TAG_RANDOM_IV = static_cast(tag::KMIP_TAG_RANDOM_IV); +inline constexpr std::uint32_t KMIP_TAG_ATTESTATION_TYPE = static_cast(tag::KMIP_TAG_ATTESTATION_TYPE); +inline constexpr std::uint32_t KMIP_TAG_NONCE = static_cast(tag::KMIP_TAG_NONCE); +inline constexpr std::uint32_t KMIP_TAG_NONCE_ID = static_cast(tag::KMIP_TAG_NONCE_ID); +inline constexpr std::uint32_t KMIP_TAG_NONCE_VALUE = static_cast(tag::KMIP_TAG_NONCE_VALUE); +inline constexpr std::uint32_t KMIP_TAG_ATTESTATION_MEASUREMENT = static_cast(tag::KMIP_TAG_ATTESTATION_MEASUREMENT); +inline constexpr std::uint32_t KMIP_TAG_ATTESTATION_ASSERTION = static_cast(tag::KMIP_TAG_ATTESTATION_ASSERTION); +inline constexpr std::uint32_t KMIP_TAG_IV_LENGTH = static_cast(tag::KMIP_TAG_IV_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_TAG_LENGTH = static_cast(tag::KMIP_TAG_TAG_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_FIXED_FIELD_LENGTH = static_cast(tag::KMIP_TAG_FIXED_FIELD_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_COUNTER_LENGTH = static_cast(tag::KMIP_TAG_COUNTER_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_INITIAL_COUNTER_VALUE = static_cast(tag::KMIP_TAG_INITIAL_COUNTER_VALUE); +inline constexpr std::uint32_t KMIP_TAG_INVOCATION_FIELD_LENGTH = static_cast(tag::KMIP_TAG_INVOCATION_FIELD_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_ATTESTATION_CAPABLE_INDICATOR = static_cast(tag::KMIP_TAG_ATTESTATION_CAPABLE_INDICATOR); +inline constexpr std::uint32_t KMIP_TAG_OFFSET_ITEMS = static_cast(tag::KMIP_TAG_OFFSET_ITEMS); +inline constexpr std::uint32_t KMIP_TAG_LOCATED_ITEMS = static_cast(tag::KMIP_TAG_LOCATED_ITEMS); +inline constexpr std::uint32_t KMIP_TAG_KEY_WRAP_TYPE = static_cast(tag::KMIP_TAG_KEY_WRAP_TYPE); +inline constexpr std::uint32_t KMIP_TAG_SALT_LENGTH = static_cast(tag::KMIP_TAG_SALT_LENGTH); +inline constexpr std::uint32_t KMIP_TAG_MASK_GENERATOR = static_cast(tag::KMIP_TAG_MASK_GENERATOR); +inline constexpr std::uint32_t KMIP_TAG_MASK_GENERATOR_HASHING_ALGORITHM = static_cast(tag::KMIP_TAG_MASK_GENERATOR_HASHING_ALGORITHM); +inline constexpr std::uint32_t KMIP_TAG_P_SOURCE = static_cast(tag::KMIP_TAG_P_SOURCE); +inline constexpr std::uint32_t KMIP_TAG_TRAILER_FIELD = static_cast(tag::KMIP_TAG_TRAILER_FIELD); +inline constexpr std::uint32_t KMIP_TAG_CLIENT_CORRELATION_VALUE = static_cast(tag::KMIP_TAG_CLIENT_CORRELATION_VALUE); +inline constexpr std::uint32_t KMIP_TAG_SERVER_CORRELATION_VALUE = static_cast(tag::KMIP_TAG_SERVER_CORRELATION_VALUE); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTES = static_cast(tag::KMIP_TAG_ATTRIBUTES); +inline constexpr std::uint32_t KMIP_TAG_SERVER_NAME = static_cast(tag::KMIP_TAG_SERVER_NAME); +inline constexpr std::uint32_t KMIP_TAG_SERVER_SERIAL_NUMBER = static_cast(tag::KMIP_TAG_SERVER_SERIAL_NUMBER); +inline constexpr std::uint32_t KMIP_TAG_SERVER_VERSION = static_cast(tag::KMIP_TAG_SERVER_VERSION); +inline constexpr std::uint32_t KMIP_TAG_SERVER_LOAD = static_cast(tag::KMIP_TAG_SERVER_LOAD); +inline constexpr std::uint32_t KMIP_TAG_PRODUCT_NAME = static_cast(tag::KMIP_TAG_PRODUCT_NAME); +inline constexpr std::uint32_t KMIP_TAG_BUILD_LEVEL = static_cast(tag::KMIP_TAG_BUILD_LEVEL); +inline constexpr std::uint32_t KMIP_TAG_BUILD_DATE = static_cast(tag::KMIP_TAG_BUILD_DATE); +inline constexpr std::uint32_t KMIP_TAG_CLUSTER_INFO = static_cast(tag::KMIP_TAG_CLUSTER_INFO); +inline constexpr std::uint32_t KMIP_TAG_ALTERNATE_FAILOVER_ENDPOINTS = static_cast(tag::KMIP_TAG_ALTERNATE_FAILOVER_ENDPOINTS); +inline constexpr std::uint32_t KMIP_TAG_EPHEMERAL = static_cast(tag::KMIP_TAG_EPHEMERAL); +inline constexpr std::uint32_t KMIP_TAG_SERVER_HASHED_PASSWORD = static_cast(tag::KMIP_TAG_SERVER_HASHED_PASSWORD); +inline constexpr std::uint32_t KMIP_TAG_PROTECTION_STORAGE_MASK = static_cast(tag::KMIP_TAG_PROTECTION_STORAGE_MASK); +inline constexpr std::uint32_t KMIP_TAG_PROTECTION_STORAGE_MASKS = static_cast(tag::KMIP_TAG_PROTECTION_STORAGE_MASKS); +inline constexpr std::uint32_t KMIP_TAG_COMMON_PROTECTION_STORAGE_MASKS = static_cast(tag::KMIP_TAG_COMMON_PROTECTION_STORAGE_MASKS); +inline constexpr std::uint32_t KMIP_TAG_PRIVATE_PROTECTION_STORAGE_MASKS = static_cast(tag::KMIP_TAG_PRIVATE_PROTECTION_STORAGE_MASKS); +inline constexpr std::uint32_t KMIP_TAG_PUBLIC_PROTECTION_STORAGE_MASKS = static_cast(tag::KMIP_TAG_PUBLIC_PROTECTION_STORAGE_MASKS); +inline constexpr std::uint32_t KMIP_TYPE_STRUCTURE = static_cast(type::KMIP_TYPE_STRUCTURE); +inline constexpr std::uint32_t KMIP_TYPE_INTEGER = static_cast(type::KMIP_TYPE_INTEGER); +inline constexpr std::uint32_t KMIP_TYPE_LONG_INTEGER = static_cast(type::KMIP_TYPE_LONG_INTEGER); +inline constexpr std::uint32_t KMIP_TYPE_BIG_INTEGER = static_cast(type::KMIP_TYPE_BIG_INTEGER); +inline constexpr std::uint32_t KMIP_TYPE_ENUMERATION = static_cast(type::KMIP_TYPE_ENUMERATION); +inline constexpr std::uint32_t KMIP_TYPE_BOOLEAN = static_cast(type::KMIP_TYPE_BOOLEAN); +inline constexpr std::uint32_t KMIP_TYPE_TEXT_STRING = static_cast(type::KMIP_TYPE_TEXT_STRING); +inline constexpr std::uint32_t KMIP_TYPE_BYTE_STRING = static_cast(type::KMIP_TYPE_BYTE_STRING); +inline constexpr std::uint32_t KMIP_TYPE_DATE_TIME = static_cast(type::KMIP_TYPE_DATE_TIME); +inline constexpr std::uint32_t KMIP_TYPE_INTERVAL = static_cast(type::KMIP_TYPE_INTERVAL); +inline constexpr std::uint32_t KMIP_TYPE_DATE_TIME_EXTENDED = static_cast(type::KMIP_TYPE_DATE_TIME_EXTENDED); +inline constexpr std::uint32_t KMIP_WRAP_ENCRYPT = static_cast(wrapping_method::KMIP_WRAP_ENCRYPT); +inline constexpr std::uint32_t KMIP_WRAP_MAC_SIGN = static_cast(wrapping_method::KMIP_WRAP_MAC_SIGN); +inline constexpr std::uint32_t KMIP_WRAP_ENCRYPT_MAC_SIGN = static_cast(wrapping_method::KMIP_WRAP_ENCRYPT_MAC_SIGN); +inline constexpr std::uint32_t KMIP_WRAP_MAC_SIGN_ENCRYPT = static_cast(wrapping_method::KMIP_WRAP_MAC_SIGN_ENCRYPT); +inline constexpr std::uint32_t KMIP_WRAP_TR31 = static_cast(wrapping_method::KMIP_WRAP_TR31); +inline constexpr std::uint32_t KMIP_REVOKE_UNSPECIFIED = static_cast(revocation_reason_type::KMIP_REVOKE_UNSPECIFIED); +inline constexpr std::uint32_t KMIP_REVOKE_KEY_COMPROMISE = static_cast(revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE); +inline constexpr std::uint32_t KMIP_REVOKE_CA_COMPROMISE = static_cast(revocation_reason_type::KMIP_REVOKE_CA_COMPROMISE); +inline constexpr std::uint32_t KMIP_REVOKE_AFFILIATION_CHANGED = static_cast(revocation_reason_type::KMIP_REVOKE_AFFILIATION_CHANGED); +inline constexpr std::uint32_t KMIP_REVOKE_SUSPENDED = static_cast(revocation_reason_type::KMIP_REVOKE_SUSPENDED); +inline constexpr std::uint32_t KMIP_REVOKE_CESSATION_OF_OPERATION = static_cast(revocation_reason_type::KMIP_REVOKE_CESSATION_OF_OPERATION); +inline constexpr std::uint32_t KMIP_REVOKE_PRIVILEDGE_WITHDRAWN = static_cast(revocation_reason_type::KMIP_REVOKE_PRIVILEDGE_WITHDRAWN); +inline constexpr std::uint32_t KMIP_REVOKE_EXTENSIONS = static_cast(revocation_reason_type::KMIP_REVOKE_EXTENSIONS); +inline constexpr std::uint32_t KMIP_SECDATA_PASSWORD = static_cast(secret_data_type::KMIP_SECDATA_PASSWORD); +inline constexpr std::uint32_t KMIP_SECDATA_SEED = static_cast(secret_data_type::KMIP_SECDATA_SEED); +inline constexpr std::uint32_t KMIP_SECDATA_EXTENSIONS = static_cast(secret_data_type::KMIP_SECDATA_EXTENSIONS); + + +} // namespace kmipcore + +#endif // KMIPCORE_KMIP_ENUMS_HPP + diff --git a/kmipcore/include/kmipcore/kmip_errors.hpp b/kmipcore/include/kmipcore/kmip_errors.hpp new file mode 100644 index 0000000..86c46fd --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_errors.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "kmipcore/kmip_enums.hpp" + +#include +#include + +namespace kmipcore { + + /** + * @brief Returns the KMIP-specific std::error_category. + */ + [[nodiscard]] const std::error_category &kmip_category() noexcept; + + /** + * @brief Creates an error_code in the KMIP category from a native code. + */ + [[nodiscard]] std::error_code + make_kmip_error_code(int native_error_code) noexcept; + + /** + * @brief Base exception for KMIP core protocol/encoding failures. + */ + class KmipException : public std::system_error { + public: + /** @brief Creates an exception with message only. */ + explicit KmipException(const std::string &msg); + /** @brief Creates an exception with numeric status code and message. */ + KmipException(int native_error_code, const std::string &msg); + }; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/kmip_logger.hpp b/kmipcore/include/kmipcore/kmip_logger.hpp index 369f306..0e18962 100644 --- a/kmipcore/include/kmipcore/kmip_logger.hpp +++ b/kmipcore/include/kmipcore/kmip_logger.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace kmipcore { @@ -21,7 +22,7 @@ namespace kmipcore { }; /** @brief Converts a log level enum to uppercase text label. */ - [[nodiscard]] inline const char *to_string(LogLevel level) { + [[nodiscard]] inline std::string_view to_string(LogLevel level) { switch (level) { case LogLevel::Debug: return "DEBUG"; diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index 9bb1af9..afcdf55 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -33,10 +33,10 @@ namespace kmipcore { explicit SimpleIdRequest(const std::string &unique_id) { setOperation(OpCode); auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id ) ); setRequestPayload(payload); @@ -67,16 +67,16 @@ namespace kmipcore { ) { setOperation(KMIP_OP_GET_ATTRIBUTES); auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id ) ); for (const auto &attr_name : attribute_names) { payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), attr_name + tag::KMIP_TAG_ATTRIBUTE_NAME, attr_name ) ); } @@ -130,7 +130,7 @@ namespace kmipcore { const std::string &name, const std::string &group, const secret_t &secret, - int32_t secret_type + secret_data_type secret_type ); }; @@ -148,7 +148,7 @@ namespace kmipcore { LocateRequest( bool locate_by_group, const std::string &name, - int32_t object_type, + object_type obj_type, size_t max_items = 0, size_t offset = 0 ); @@ -166,7 +166,7 @@ namespace kmipcore { */ RevokeRequest( const std::string &unique_id, - int32_t reason, + revocation_reason_type reason, const std::string &message, time_t occurrence_time = 0 ); diff --git a/kmipcore/include/kmipcore/kmip_responses.hpp b/kmipcore/include/kmipcore/kmip_responses.hpp index 800aeaf..a1fbcd6 100644 --- a/kmipcore/include/kmipcore/kmip_responses.hpp +++ b/kmipcore/include/kmipcore/kmip_responses.hpp @@ -75,7 +75,7 @@ namespace kmipcore { detail::require_response_payload(item, "SimpleIdResponseBatchItem"); auto uid = - payload->getChild(static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)); + payload->getChild(tag::KMIP_TAG_UNIQUE_IDENTIFIER); if (!uid) { throw KmipException( "SimpleIdResponseBatchItem: missing unique identifier in response " diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp index 73a42ca..60f8d34 100644 --- a/kmipcore/include/kmipcore/secret.hpp +++ b/kmipcore/include/kmipcore/secret.hpp @@ -5,7 +5,6 @@ #include #include -#include namespace kmipcore { @@ -14,18 +13,33 @@ namespace kmipcore { */ class Secret { public: - /** Raw secret payload bytes. */ - secret_t value; - /** Lifecycle state of this secret object. */ - enum state state = KMIP_STATE_PRE_ACTIVE; - /** KMIP secret data type discriminator. */ - enum secret_data_type secret_type = PASSWORD; - /** @brief Constructs an empty secret. */ Secret() = default; /** @brief Constructs a secret from payload and metadata. */ - Secret(secret_t val, enum state st, enum secret_data_type type) - : value(std::move(val)), state(st), secret_type(type) {} + Secret(const secret_t &val, state st, secret_data_type type) + : value_(val), state_(st), secret_type_(type) {} + + /** @brief Returns raw secret payload bytes. */ + [[nodiscard]] const secret_t &value() const noexcept { return value_; } + + /** @brief Replaces raw secret payload bytes. */ + void set_value(const secret_t &val) noexcept { value_ = val; } + + /** @brief Returns lifecycle state of this secret object. */ + [[nodiscard]] state get_state() const noexcept { return state_; } + + /** @brief Sets lifecycle state of this secret object. */ + void set_state(state st) noexcept { state_ = st; } + + /** @brief Returns KMIP secret data type discriminator. */ + [[nodiscard]] secret_data_type get_secret_type() const noexcept { + return secret_type_; + } + + /** @brief Sets KMIP secret data type discriminator. */ + void set_secret_type(secret_data_type type) noexcept { + secret_type_ = type; + } /** @brief Returns all attached secret attributes. */ [[nodiscard]] const attributes_t &attributes() const noexcept { @@ -33,12 +47,15 @@ namespace kmipcore { } /** - * @brief Returns value of a required secret attribute. - * @throws std::out_of_range if the attribute is missing. + * @brief Returns value of a secret attribute, or empty string if absent. + * @param name Attribute name. + * @return Attribute value, or empty string when the attribute is missing. */ [[nodiscard]] const std::string & - attribute_value(const std::string &name) const { - return secret_attributes.at(name); + attribute_value(const std::string &name) const noexcept { + static const std::string empty; + const auto it = secret_attributes.find(name); + return it != secret_attributes.end() ? it->second : empty; } /** @brief Sets or replaces one secret attribute value. */ @@ -56,8 +73,8 @@ namespace kmipcore { */ [[nodiscard]] static Secret from_text( std::string_view text, - enum secret_data_type type = PASSWORD, - enum state st = KMIP_STATE_PRE_ACTIVE + secret_data_type type = secret_data_type::KMIP_SECDATA_PASSWORD, + state st = state::KMIP_STATE_PRE_ACTIVE ) { return Secret{ secret_t(text.begin(), text.end()), st, type}; @@ -65,10 +82,13 @@ namespace kmipcore { /** @brief Returns payload interpreted as UTF-8/byte-preserving text. */ [[nodiscard]] std::string as_text() const { - return std::string(value.begin(), value.end()); + return std::string(value_.begin(), value_.end()); } private: + secret_t value_; + state state_ = state::KMIP_STATE_PRE_ACTIVE; + secret_data_type secret_type_ = secret_data_type::KMIP_SECDATA_PASSWORD; attributes_t secret_attributes; }; diff --git a/kmipcore/include/kmipcore/serialization_buffer.hpp b/kmipcore/include/kmipcore/serialization_buffer.hpp index 0dc22f3..964eee4 100644 --- a/kmipcore/include/kmipcore/serialization_buffer.hpp +++ b/kmipcore/include/kmipcore/serialization_buffer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace kmipcore { @@ -67,24 +68,16 @@ class SerializationBuffer { /** * Write raw bytes (unpadded) to the buffer. - * @param data Pointer to data - * @param length Number of bytes to write + * @param data Raw byte view to write */ - void writeBytes(const uint8_t* data, size_t length); - void writeBytes(const void* data, size_t length) { - writeBytes(static_cast(data), length); - } + void writeBytes(std::span data); /** * Write raw bytes with KMIP padding (8-byte aligned). * Adds zero-fill padding to align to 8-byte boundary. - * @param data Pointer to data - * @param length Number of bytes to write + * @param data Raw byte view to write */ - void writePadded(const uint8_t* data, size_t length); - void writePadded(const void* data, size_t length) { - writePadded(static_cast(data), length); - } + void writePadded(std::span data); // ==================== QUERY OPERATIONS ==================== @@ -133,6 +126,14 @@ class SerializationBuffer { */ [[nodiscard]] const uint8_t* data() const { return buffer_.data(); } + /** + * Get a read-only view of the serialized bytes. + * @return Span covering exactly the valid serialized payload. + */ + [[nodiscard]] std::span span() const { + return {buffer_.data(), current_offset_}; + } + /** * Get mutable pointer to buffer data (use with caution). * @return Pointer to buffer @@ -157,7 +158,7 @@ class SerializationBuffer { * pre-allocation performance goal of this class. * * To aggressively reclaim heap memory after the last use, call - * freeMemory() instead of (or after) release(). + * shrink() instead of (or after) release(). * * @return Vector containing serialized data */ @@ -171,7 +172,7 @@ class SerializationBuffer { * a very large one-off message). After this call the buffer is in the * same state as a freshly constructed one with zero capacity. */ - void freeMemory(); + void shrink(); private: std::vector buffer_; diff --git a/kmipcore/include/kmipcore/types.hpp b/kmipcore/include/kmipcore/types.hpp index 5d121d6..b27372a 100644 --- a/kmipcore/include/kmipcore/types.hpp +++ b/kmipcore/include/kmipcore/types.hpp @@ -2,7 +2,6 @@ #include "kmipcore/kmip_enums.hpp" -#include #include #include #include @@ -27,29 +26,9 @@ namespace kmipcore { /** @brief Generic string attribute map type. */ using attributes_t = std::unordered_map; - /** Convert a KMIP state enum value to a human-readable string. */ - inline const char *state_to_string(int32_t value) { - switch (static_cast(value)) { - case KMIP_STATE_PRE_ACTIVE: - return "KMIP_STATE_PRE_ACTIVE"; - case KMIP_STATE_ACTIVE: - return "KMIP_STATE_ACTIVE"; - case KMIP_STATE_DEACTIVATED: - return "KMIP_STATE_DEACTIVATED"; - case KMIP_STATE_COMPROMISED: - return "KMIP_STATE_COMPROMISED"; - case KMIP_STATE_DESTROYED: - return "KMIP_STATE_DESTROYED"; - case KMIP_STATE_DESTROYED_COMPROMISED: - return "KMIP_STATE_DESTROYED_COMPROMISED"; - default: - return "UNKNOWN_KMIP_STATE"; - } - } - /** @brief Stream formatter for KMIP lifecycle state values. */ inline std::ostream &operator<<(std::ostream &out, const state value) { - return out << state_to_string(static_cast(value)); + return out << state_to_string(value); } } // namespace kmipcore diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp index c299680..4ee342a 100644 --- a/kmipcore/src/attributes_parser.cpp +++ b/kmipcore/src/attributes_parser.cpp @@ -5,37 +5,38 @@ #include #include #include +#include namespace kmipcore { namespace { - [[nodiscard]] std::string attribute_key_from_name(const std::string &name) { - if (name == "Name") return KMIP_ATTR_NAME_NAME; - if (name == "Object Group") return KMIP_ATTR_NAME_GROUP; - if (name == "State") return KMIP_ATTR_NAME_STATE; - if (name == "Unique Identifier") return KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; - if (name == "UniqueID") return KMIP_ATTR_NAME_UNIQUE_IDENTIFIER; // Legacy/PyKMIP compat - if (name == "Initial Date") return KMIP_ATTR_NAME_INITIAL_DATE; - if (name == "Activation Date") return KMIP_ATTR_NAME_ACTIVATION_DATE; - if (name == "Process Start Date") return KMIP_ATTR_NAME_PROCESS_START_DATE; - if (name == "Protect Stop Date") return KMIP_ATTR_NAME_PROTECT_STOP_DATE; - if (name == "Deactivation Date") return KMIP_ATTR_NAME_DEACTIVATION_DATE; - if (name == "Destroy Date") return KMIP_ATTR_NAME_DESTROY_DATE; - if (name == "Compromise Occurrence Date") return KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE; - if (name == "Compromise Date") return KMIP_ATTR_NAME_COMPROMISE_DATE; - if (name == "Archive Date") return KMIP_ATTR_NAME_ARCHIVE_DATE; - if (name == "Last Change Date") return KMIP_ATTR_NAME_LAST_CHANGE_DATE; - if (name == "Cryptographic Algorithm") return KMIP_ATTR_NAME_CRYPTO_ALG; - if (name == "Cryptographic Length") return KMIP_ATTR_NAME_CRYPTO_LEN; - if (name == "Cryptographic Usage Mask") return KMIP_ATTR_NAME_CRYPTO_USAGE_MASK; - if (name == "Contact Information") return KMIP_ATTR_NAME_CONTACT_INFO; - if (name == "Operation Policy Name") return KMIP_ATTR_NAME_OPERATION_POLICY_NAME; - return name; + [[nodiscard]] std::string attribute_key_from_name(std::string_view name) { + if (name == "Name") return std::string(KMIP_ATTR_NAME_NAME); + if (name == "Object Group") return std::string(KMIP_ATTR_NAME_GROUP); + if (name == "State") return std::string(KMIP_ATTR_NAME_STATE); + if (name == "Unique Identifier") return std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); + if (name == "UniqueID") return std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); // Legacy/PyKMIP compat + if (name == "Initial Date") return std::string(KMIP_ATTR_NAME_INITIAL_DATE); + if (name == "Activation Date") return std::string(KMIP_ATTR_NAME_ACTIVATION_DATE); + if (name == "Process Start Date") return std::string(KMIP_ATTR_NAME_PROCESS_START_DATE); + if (name == "Protect Stop Date") return std::string(KMIP_ATTR_NAME_PROTECT_STOP_DATE); + if (name == "Deactivation Date") return std::string(KMIP_ATTR_NAME_DEACTIVATION_DATE); + if (name == "Destroy Date") return std::string(KMIP_ATTR_NAME_DESTROY_DATE); + if (name == "Compromise Occurrence Date") return std::string(KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE); + if (name == "Compromise Date") return std::string(KMIP_ATTR_NAME_COMPROMISE_DATE); + if (name == "Archive Date") return std::string(KMIP_ATTR_NAME_ARCHIVE_DATE); + if (name == "Last Change Date") return std::string(KMIP_ATTR_NAME_LAST_CHANGE_DATE); + if (name == "Cryptographic Algorithm") return std::string(KMIP_ATTR_NAME_CRYPTO_ALG); + if (name == "Cryptographic Length") return std::string(KMIP_ATTR_NAME_CRYPTO_LEN); + if (name == "Cryptographic Usage Mask") return std::string(KMIP_ATTR_NAME_CRYPTO_USAGE_MASK); + if (name == "Contact Information") return std::string(KMIP_ATTR_NAME_CONTACT_INFO); + if (name == "Operation Policy Name") return std::string(KMIP_ATTR_NAME_OPERATION_POLICY_NAME); + return std::string(name); } - [[nodiscard]] std::string crypto_alg_to_string(int32_t val) { + [[nodiscard]] std::string crypto_alg_to_string(std::int32_t val) { switch (val) { case KMIP_CRYPTOALG_DES: return "DES"; case KMIP_CRYPTOALG_TRIPLE_DES: return "3DES"; @@ -71,33 +72,33 @@ namespace kmipcore { } [[nodiscard]] std::string parse_attribute_value( - const std::string &attribute_name, const std::shared_ptr &value + std::string_view attribute_name, const std::shared_ptr &value ) { if (!value) { return {}; } switch (value->type) { - case KMIP_TYPE_TEXT_STRING: + case type::KMIP_TYPE_TEXT_STRING: return value->toString(); - case KMIP_TYPE_INTEGER: + case type::KMIP_TYPE_INTEGER: return std::to_string(value->toInt()); - case KMIP_TYPE_DATE_TIME: + case type::KMIP_TYPE_DATE_TIME: return date_to_string(value->toLong()); - case KMIP_TYPE_LONG_INTEGER: + case type::KMIP_TYPE_LONG_INTEGER: return std::to_string(value->toLong()); - case KMIP_TYPE_ENUMERATION: + case type::KMIP_TYPE_ENUMERATION: if (attribute_name == KMIP_ATTR_NAME_STATE) { - return state_to_string(value->toEnum()); + return state_to_string(static_cast(value->toEnum())); } if (attribute_name == KMIP_ATTR_NAME_CRYPTO_ALG) { return crypto_alg_to_string(value->toEnum()); } return std::to_string(value->toEnum()); - case KMIP_TYPE_STRUCTURE: + case type::KMIP_TYPE_STRUCTURE: if (attribute_name == KMIP_ATTR_NAME_NAME) { if (auto name_value = - value->getChild(static_cast(KMIP_TAG_NAME_VALUE)); + value->getChild(tag::KMIP_TAG_NAME_VALUE); name_value) { return name_value->toString(); } @@ -118,14 +119,14 @@ namespace kmipcore { attributes_t res; for (const auto &attribute : attributes) { if (attribute == nullptr || - attribute->tag != static_cast(KMIP_TAG_ATTRIBUTE)) { + attribute->tag != tag::KMIP_TAG_ATTRIBUTE) { continue; } auto attribute_name = - attribute->getChild(static_cast(KMIP_TAG_ATTRIBUTE_NAME)); + attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); auto attribute_value = - attribute->getChild(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); if (!attribute_name) { continue; } diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp index a144243..213bfca 100644 --- a/kmipcore/src/key_parser.cpp +++ b/kmipcore/src/key_parser.cpp @@ -34,7 +34,7 @@ namespace kmipcore { const char *type_name ) { auto key_block = - object_element->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + object_element->getChild(tag::KMIP_TAG_KEY_BLOCK); if (!key_block) { throw KmipException( KMIP_INVALID_ENCODING, @@ -43,7 +43,7 @@ namespace kmipcore { } auto key_value = - key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + key_block->getChild(tag::KMIP_TAG_KEY_VALUE); if (!key_value) { throw KmipException( KMIP_INVALID_ENCODING, @@ -52,7 +52,7 @@ namespace kmipcore { } auto key_material = - key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + key_value->getChild(tag::KMIP_TAG_KEY_MATERIAL); if (!key_material) { throw KmipException( KMIP_INVALID_ENCODING, @@ -64,18 +64,18 @@ namespace kmipcore { key_t kv(raw_bytes.begin(), raw_bytes.end()); auto algorithm = key_block->getChild( - static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM) + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM ); auto key_attributes = AttributesParser::parse( - key_value->getChildren(static_cast(KMIP_TAG_ATTRIBUTE)) + key_value->getChildren(tag::KMIP_TAG_ATTRIBUTE) ); return Key( std::move(kv), key_type, algorithm ? static_cast(algorithm->toEnum()) - : KMIP_CRYPTOALG_UNSET, - KMIP_CRYPTOMASK_UNSET, + : cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, std::move(key_attributes) ); } @@ -107,8 +107,8 @@ namespace kmipcore { } auto secret_type = - object->getChild(static_cast(KMIP_TAG_SECRET_DATA_TYPE)); - auto key_block = object->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + object->getChild(tag::KMIP_TAG_SECRET_DATA_TYPE); + auto key_block = object->getChild(tag::KMIP_TAG_KEY_BLOCK); if (!secret_type || !key_block) { throw KmipException( KMIP_INVALID_ENCODING, "Secret data key block format mismatch" @@ -116,7 +116,7 @@ namespace kmipcore { } auto key_format = - key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + key_block->getChild(tag::KMIP_TAG_KEY_FORMAT_TYPE); if (!key_format || (key_format->toEnum() != KMIP_KEYFORMAT_OPAQUE && key_format->toEnum() != KMIP_KEYFORMAT_RAW)) { throw KmipException( @@ -124,13 +124,13 @@ namespace kmipcore { ); } - auto key_value = key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + auto key_value = key_block->getChild(tag::KMIP_TAG_KEY_VALUE); if (!key_value) { throw KmipException(KMIP_INVALID_ENCODING, "Missing secret key value."); } auto key_material = - key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + key_value->getChild(tag::KMIP_TAG_KEY_MATERIAL); if (!key_material) { throw KmipException( KMIP_INVALID_ENCODING, "Missing secret key material." @@ -141,8 +141,8 @@ namespace kmipcore { return Secret{ secret_t(raw_bytes.begin(), raw_bytes.end()), - KMIP_STATE_PRE_ACTIVE, - static_cast(secret_type->toEnum()) + state::KMIP_STATE_PRE_ACTIVE, + static_cast(secret_type->toEnum()) }; } @@ -152,7 +152,7 @@ namespace kmipcore { } auto object_type = - payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + payload->getChild(tag::KMIP_TAG_OBJECT_TYPE); if (!object_type) { throw KmipException( KMIP_INVALID_ENCODING, "Missing Object Type in Get response." @@ -199,10 +199,10 @@ namespace kmipcore { // Symmetric keys require RAW format if (m.obj_type == KMIP_OBJTYPE_SYMMETRIC_KEY) { auto key_block = - object_element->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + object_element->getChild(tag::KMIP_TAG_KEY_BLOCK); if (key_block) { auto key_format = - key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + key_block->getChild(tag::KMIP_TAG_KEY_FORMAT_TYPE); if (!key_format || key_format->toEnum() != KMIP_KEYFORMAT_RAW) { throw KmipException( KMIP_INVALID_ENCODING, "Invalid response object format." diff --git a/kmipcore/src/kmip_basics.cpp b/kmipcore/src/kmip_basics.cpp index c74bf38..cd613ba 100644 --- a/kmipcore/src/kmip_basics.cpp +++ b/kmipcore/src/kmip_basics.cpp @@ -12,36 +12,37 @@ namespace kmipcore { // Helper functions for big-endian - static uint32_t to_be32(uint32_t v) { + static std::uint32_t to_be32(std::uint32_t v) { return htonl(v); } - static uint64_t to_be64(uint64_t v) { - uint32_t high = htonl(v >> 32); - uint32_t low = htonl(v & 0xFFFFFFFF); - return ((uint64_t) low << 32) | high; + static std::uint64_t to_be64(std::uint64_t v) { + std::uint32_t high = htonl(v >> 32); + std::uint32_t low = htonl(v & 0xFFFFFFFF); + return (static_cast(low) << 32) | high; } - static uint32_t from_be32(uint32_t v) { + static std::uint32_t from_be32(std::uint32_t v) { return ntohl(v); } - static uint64_t from_be64(uint64_t v) { - uint32_t high = ntohl(v >> 32); - uint32_t low = ntohl(v & 0xFFFFFFFF); - return ((uint64_t) high << 32) | low; + static std::uint64_t from_be64(std::uint64_t v) { + std::uint32_t high = ntohl(v >> 32); + std::uint32_t low = ntohl(v & 0xFFFFFFFF); + return (static_cast(high) << 32) | low; } void Element::serialize(SerializationBuffer& buf) const { // Write Tag (3 bytes, big-endian) - buf.writeByte((tag >> 16) & 0xFF); - buf.writeByte((tag >> 8) & 0xFF); - buf.writeByte(tag & 0xFF); + const auto raw_tag = static_cast(tag); + buf.writeByte((raw_tag >> 16) & 0xFF); + buf.writeByte((raw_tag >> 8) & 0xFF); + buf.writeByte(raw_tag & 0xFF); // Write Type (1 byte) - buf.writeByte(static_cast(type)); + buf.writeByte(static_cast(type)); // First pass: calculate content and payload length SerializationBuffer content_buf; - uint32_t payload_length = 0; + std::uint32_t payload_length = 0; if (std::holds_alternative(value)) { const auto &s = std::get(value); @@ -50,53 +51,53 @@ namespace kmipcore { } payload_length = content_buf.size(); } else if (std::holds_alternative(value)) { - int32_t v = std::get(value).value; + std::int32_t v = std::get(value).value; v = to_be32(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 4; } else if (std::holds_alternative(value)) { - int64_t v = std::get(value).value; + std::int64_t v = std::get(value).value; v = to_be64(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 8; } else if (std::holds_alternative(value)) { const auto &v = std::get(value).value; - content_buf.writeBytes(v.data(), v.size()); + content_buf.writeBytes(std::as_bytes(std::span(v.data(), v.size()))); payload_length = v.size(); } else if (std::holds_alternative(value)) { - int32_t v = std::get(value).value; + std::int32_t v = std::get(value).value; v = to_be32(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 4; } else if (std::holds_alternative(value)) { - uint64_t v = std::get(value).value ? 1 : 0; + std::uint64_t v = std::get(value).value ? 1 : 0; v = to_be64(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 8; } else if (std::holds_alternative(value)) { const auto &v = std::get(value).value; - content_buf.writePadded(v.data(), v.size()); + content_buf.writePadded(std::as_bytes(std::span(v.data(), v.size()))); payload_length = v.size(); } else if (std::holds_alternative(value)) { const auto &v = std::get(value).value; - content_buf.writePadded(v.data(), v.size()); + content_buf.writePadded(std::as_bytes(std::span(v.data(), v.size()))); payload_length = v.size(); } else if (std::holds_alternative(value)) { - int64_t v = std::get(value).value; + std::int64_t v = std::get(value).value; v = to_be64(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 8; } else if (std::holds_alternative(value)) { // KMIP 2.0: microseconds since Unix epoch; same 8-byte big-endian wire // format as DateTime, distinguished only by type code 0x0B. - int64_t v = std::get(value).value; + std::int64_t v = std::get(value).value; v = to_be64(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 8; } else if (std::holds_alternative(value)) { - uint32_t v = std::get(value).value; + std::uint32_t v = std::get(value).value; v = to_be32(v); - content_buf.writeBytes(&v, sizeof(v)); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); payload_length = 4; } @@ -108,32 +109,32 @@ namespace kmipcore { // Write content (already padded from content_buf) if (content_buf.size() > 0) { - buf.writeBytes(content_buf.data(), content_buf.size()); + buf.writeBytes(std::as_bytes(content_buf.span())); } // Add padding to align to 8 bytes - size_t total_so_far = 3 + 1 + 4 + content_buf.size(); // tag + type + length + content - size_t padding = (8 - (total_so_far % 8)) % 8; - for (size_t i = 0; i < padding; ++i) { + std::size_t total_so_far = 3 + 1 + 4 + content_buf.size(); // tag + type + length + content + std::size_t padding = (8 - (total_so_far % 8)) % 8; + for (std::size_t i = 0; i < padding; ++i) { buf.writeByte(0); } } std::shared_ptr - Element::deserialize(std::span data, size_t &offset) { + Element::deserialize(std::span data, std::size_t &offset) { if (offset + 8 > data.size()) { throw KmipException("Buffer too short for header"); } // Read Tag (3 bytes) - uint32_t tag = + std::uint32_t tag = (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]; // Read Type (1 byte) Type type = static_cast(data[offset + 3]); // Read Length (4 bytes) - uint32_t length = (data[offset + 4] << 24) | (data[offset + 5] << 16) | + std::uint32_t length = (data[offset + 4] << 24) | (data[offset + 5] << 16) | (data[offset + 6] << 8) | data[offset + 7]; offset += 8; @@ -142,8 +143,8 @@ namespace kmipcore { // For Structure, length is the length of contents. // For Primitives, length is the unpadded length. // We need to calculate padded length to skip correctly. - size_t padded_length = length; - if (length % 8 != 0 && type != ::KMIP_TYPE_STRUCTURE) { + std::size_t padded_length = length; + if (length % 8 != 0 && type != Type::KMIP_TYPE_STRUCTURE) { padded_length += (8 - (length % 8)); // Doesn't apply to structure? @@ -155,7 +156,7 @@ namespace kmipcore { // should be multiple of 8. } - if (type == ::KMIP_TYPE_STRUCTURE) { + if (type == Type::KMIP_TYPE_STRUCTURE) { // Guard: the declared structure body must fit within the available buffer. if (offset + length > data.size()) { throw KmipException("Buffer too short for structure body"); @@ -172,12 +173,12 @@ namespace kmipcore { struct_elem->type = type; struct_elem->value = Structure{}; - size_t current_struct_offset = 0; + std::size_t current_struct_offset = 0; while (current_struct_offset < length) { - size_t item_offset = offset; + std::size_t item_offset = offset; auto child = deserialize(struct_view, item_offset); std::get(struct_elem->value).add(child); - size_t consumed = item_offset - offset; + std::size_t consumed = item_offset - offset; current_struct_offset += consumed; offset = item_offset; } @@ -192,12 +193,12 @@ namespace kmipcore { elem->type = type; switch (type) { - case ::KMIP_TYPE_INTEGER: { + case Type::KMIP_TYPE_INTEGER: { if (length != 4) { throw KmipException("Invalid length for Integer"); } - int32_t val; - uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + std::int32_t val; + std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; // raw is equivalent to big-endian read // we can just use memcpy if valid but manual reconstruction is safer @@ -206,112 +207,112 @@ namespace kmipcore { elem->value = Integer{val}; break; } - case ::KMIP_TYPE_LONG_INTEGER: { + case Type::KMIP_TYPE_LONG_INTEGER: { if (length != 8) { throw KmipException("Invalid length for Long Integer"); } - uint64_t raw = ((uint64_t) data[offset] << 56) | - ((uint64_t) data[offset + 1] << 48) | - ((uint64_t) data[offset + 2] << 40) | - ((uint64_t) data[offset + 3] << 32) | - ((uint64_t) data[offset + 4] << 24) | - ((uint64_t) data[offset + 5] << 16) | - ((uint64_t) data[offset + 6] << 8) | - (uint64_t) data[offset + 7]; - int64_t val; + std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | + ((std::uint64_t) data[offset + 1] << 48) | + ((std::uint64_t) data[offset + 2] << 40) | + ((std::uint64_t) data[offset + 3] << 32) | + ((std::uint64_t) data[offset + 4] << 24) | + ((std::uint64_t) data[offset + 5] << 16) | + ((std::uint64_t) data[offset + 6] << 8) | + (std::uint64_t) data[offset + 7]; + std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = LongInteger{val}; break; } - case ::KMIP_TYPE_BOOLEAN: { + case Type::KMIP_TYPE_BOOLEAN: { if (length != 8) { throw KmipException("Invalid length for Boolean"); } - uint64_t raw = ((uint64_t) data[offset] << 56) | - ((uint64_t) data[offset + 1] << 48) | - ((uint64_t) data[offset + 2] << 40) | - ((uint64_t) data[offset + 3] << 32) | - ((uint64_t) data[offset + 4] << 24) | - ((uint64_t) data[offset + 5] << 16) | - ((uint64_t) data[offset + 6] << 8) | - (uint64_t) data[offset + 7]; + std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | + ((std::uint64_t) data[offset + 1] << 48) | + ((std::uint64_t) data[offset + 2] << 40) | + ((std::uint64_t) data[offset + 3] << 32) | + ((std::uint64_t) data[offset + 4] << 24) | + ((std::uint64_t) data[offset + 5] << 16) | + ((std::uint64_t) data[offset + 6] << 8) | + (std::uint64_t) data[offset + 7]; elem->value = Boolean{raw != 0}; break; } - case ::KMIP_TYPE_ENUMERATION: { + case Type::KMIP_TYPE_ENUMERATION: { if (length != 4) { throw KmipException("Invalid length for Enumeration"); } - uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; - elem->value = Enumeration{(int32_t) raw}; + elem->value = Enumeration{static_cast(raw)}; break; } - case ::KMIP_TYPE_TEXT_STRING: { + case Type::KMIP_TYPE_TEXT_STRING: { std::string s(reinterpret_cast(&data[offset]), length); elem->value = TextString{s}; break; } - case ::KMIP_TYPE_BYTE_STRING: { - std::vector v( + case Type::KMIP_TYPE_BYTE_STRING: { + std::vector v( data.begin() + offset, data.begin() + offset + length ); elem->value = ByteString{v}; break; } - case ::KMIP_TYPE_DATE_TIME: { + case Type::KMIP_TYPE_DATE_TIME: { if (length != 8) { throw KmipException("Invalid length for DateTime"); } - uint64_t raw = ((uint64_t) data[offset] << 56) | - ((uint64_t) data[offset + 1] << 48) | - ((uint64_t) data[offset + 2] << 40) | - ((uint64_t) data[offset + 3] << 32) | - ((uint64_t) data[offset + 4] << 24) | - ((uint64_t) data[offset + 5] << 16) | - ((uint64_t) data[offset + 6] << 8) | - (uint64_t) data[offset + 7]; - int64_t val; + std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | + ((std::uint64_t) data[offset + 1] << 48) | + ((std::uint64_t) data[offset + 2] << 40) | + ((std::uint64_t) data[offset + 3] << 32) | + ((std::uint64_t) data[offset + 4] << 24) | + ((std::uint64_t) data[offset + 5] << 16) | + ((std::uint64_t) data[offset + 6] << 8) | + (std::uint64_t) data[offset + 7]; + std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = DateTime{val}; break; } - case ::KMIP_TYPE_INTERVAL: { + case Type::KMIP_TYPE_INTERVAL: { if (length != 4) { throw KmipException("Invalid length for Interval"); } - uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | + std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; elem->value = Interval{raw}; break; } - case ::KMIP_TYPE_BIG_INTEGER: { - std::vector v( + case Type::KMIP_TYPE_BIG_INTEGER: { + std::vector v( data.begin() + offset, data.begin() + offset + length ); elem->value = BigInteger{v}; break; } - case ::KMIP_TYPE_DATE_TIME_EXTENDED: { + case Type::KMIP_TYPE_DATE_TIME_EXTENDED: { // KMIP 2.0: microseconds since Unix epoch, 8-byte big-endian int64. if (length != 8) { throw KmipException("Invalid length for DateTimeExtended"); } - uint64_t raw = ((uint64_t) data[offset] << 56) | - ((uint64_t) data[offset + 1] << 48) | - ((uint64_t) data[offset + 2] << 40) | - ((uint64_t) data[offset + 3] << 32) | - ((uint64_t) data[offset + 4] << 24) | - ((uint64_t) data[offset + 5] << 16) | - ((uint64_t) data[offset + 6] << 8) | - (uint64_t) data[offset + 7]; - int64_t val; + std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | + ((std::uint64_t) data[offset + 1] << 48) | + ((std::uint64_t) data[offset + 2] << 40) | + ((std::uint64_t) data[offset + 3] << 32) | + ((std::uint64_t) data[offset + 4] << 24) | + ((std::uint64_t) data[offset + 5] << 16) | + ((std::uint64_t) data[offset + 6] << 8) | + (std::uint64_t) data[offset + 7]; + std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = DateTimeExtended{val}; break; } default: - throw KmipException("Unknown type " + std::to_string(type)); + throw KmipException("Unknown type " + std::to_string(static_cast(type))); } offset += padded_length; @@ -321,46 +322,46 @@ namespace kmipcore { // Factory methods std::shared_ptr Element::createStructure(Tag t) { - return std::make_shared(t, ::KMIP_TYPE_STRUCTURE, Structure{}); + return std::make_shared(t, static_cast(KMIP_TYPE_STRUCTURE), Structure{}); } - std::shared_ptr Element::createInteger(Tag t, int32_t v) { - return std::make_shared(t, ::KMIP_TYPE_INTEGER, Integer{v}); + std::shared_ptr Element::createInteger(Tag t, std::int32_t v) { + return std::make_shared(t, static_cast(KMIP_TYPE_INTEGER), Integer{v}); } - std::shared_ptr Element::createLongInteger(Tag t, int64_t v) { + std::shared_ptr Element::createLongInteger(Tag t, std::int64_t v) { return std::make_shared( - t, ::KMIP_TYPE_LONG_INTEGER, LongInteger{v} + t, static_cast(KMIP_TYPE_LONG_INTEGER), LongInteger{v} ); } std::shared_ptr Element::createBoolean(Tag t, bool v) { - return std::make_shared(t, ::KMIP_TYPE_BOOLEAN, Boolean{v}); + return std::make_shared(t, static_cast(KMIP_TYPE_BOOLEAN), Boolean{v}); } - std::shared_ptr Element::createEnumeration(Tag t, int32_t v) { + std::shared_ptr Element::createEnumeration(Tag t, std::int32_t v) { return std::make_shared( - t, ::KMIP_TYPE_ENUMERATION, Enumeration{v} + t, static_cast(KMIP_TYPE_ENUMERATION), Enumeration{v} ); } std::shared_ptr Element::createTextString(Tag t, const std::string &v) { - return std::make_shared(t, ::KMIP_TYPE_TEXT_STRING, TextString{v}); + return std::make_shared(t, static_cast(KMIP_TYPE_TEXT_STRING), TextString{v}); } std::shared_ptr - Element::createByteString(Tag t, const std::vector &v) { - return std::make_shared(t, ::KMIP_TYPE_BYTE_STRING, ByteString{v}); + Element::createByteString(Tag t, const std::vector &v) { + return std::make_shared(t, static_cast(KMIP_TYPE_BYTE_STRING), ByteString{v}); } - std::shared_ptr Element::createDateTime(Tag t, int64_t v) { - return std::make_shared(t, ::KMIP_TYPE_DATE_TIME, DateTime{v}); + std::shared_ptr Element::createDateTime(Tag t, std::int64_t v) { + return std::make_shared(t, static_cast(KMIP_TYPE_DATE_TIME), DateTime{v}); } - std::shared_ptr Element::createDateTimeExtended(Tag t, int64_t v) { + std::shared_ptr Element::createDateTimeExtended(Tag t, std::int64_t v) { return std::make_shared( - t, ::KMIP_TYPE_DATE_TIME_EXTENDED, DateTimeExtended{v} + t, static_cast(KMIP_TYPE_DATE_TIME_EXTENDED), DateTimeExtended{v} ); } - std::shared_ptr Element::createInterval(Tag t, uint32_t v) { - return std::make_shared(t, ::KMIP_TYPE_INTERVAL, Interval{v}); + std::shared_ptr Element::createInterval(Tag t, std::uint32_t v) { + return std::make_shared(t, static_cast(KMIP_TYPE_INTERVAL), Interval{v}); } std::shared_ptr - Element::createBigInteger(Tag t, const std::vector &v) { - return std::make_shared(t, ::KMIP_TYPE_BIG_INTEGER, BigInteger{v}); + Element::createBigInteger(Tag t, const std::vector &v) { + return std::make_shared(t, static_cast(KMIP_TYPE_BIG_INTEGER), BigInteger{v}); } // Helper accessors diff --git a/kmipcore/src/kmip_errors.cpp b/kmipcore/src/kmip_errors.cpp new file mode 100644 index 0000000..b42b104 --- /dev/null +++ b/kmipcore/src/kmip_errors.cpp @@ -0,0 +1,268 @@ +#include "kmipcore/kmip_errors.hpp" + +#include +#include +#include + +namespace kmipcore { + + namespace { + + struct KmipReasonInfo { + const char *name; + const char *description; + }; + + [[nodiscard]] std::optional lookup_kmip_reason_info(int code) { + switch (code) { + case KMIP_REASON_ITEM_NOT_FOUND: + return KmipReasonInfo{"Item Not Found", "No object with the specified Unique Identifier exists."}; + case KMIP_REASON_RESPONSE_TOO_LARGE: + return KmipReasonInfo{"Response Too Large", "Maximum Response Size has been exceeded."}; + case KMIP_REASON_AUTHENTICATION_NOT_SUCCESSFUL: + return KmipReasonInfo{"Authentication Not Successful", "Authentication did not succeed."}; + case KMIP_REASON_INVALID_MESSAGE: + return KmipReasonInfo{"Invalid Message", "The request message was not syntactically understood by the server."}; + case KMIP_REASON_OPERATION_NOT_SUPPORTED: + return KmipReasonInfo{"Operation Not Supported", "The operation requested by the request message is not supported by the server."}; + case KMIP_REASON_MISSING_DATA: + return KmipReasonInfo{"Missing Data", "The operation required additional information in the request, which was not present."}; + case KMIP_REASON_INVALID_FIELD: + return KmipReasonInfo{"Invalid Field", "The request is syntactically valid but some non-attribute data field is invalid."}; + case KMIP_REASON_FEATURE_NOT_SUPPORTED: + return KmipReasonInfo{"Feature Not Supported", "The operation is supported, but a specific feature requested is not supported."}; + case KMIP_REASON_OPERATION_CANCELED_BY_REQUESTER: + return KmipReasonInfo{"Operation Canceled By Requester", "The asynchronous operation was canceled before it completed."}; + case KMIP_REASON_CRYPTOGRAPHIC_FAILURE: + return KmipReasonInfo{"Cryptographic Failure", "A cryptographic operation failed."}; + case KMIP_REASON_ILLEGAL_OPERATION: + return KmipReasonInfo{"Illegal Operation", "The requested operation is not legal in the current context."}; + case KMIP_REASON_PERMISSION_DENIED: + return KmipReasonInfo{"Permission Denied", "Client is not allowed to perform the specified operation."}; + case KMIP_REASON_OBJECT_ARCHIVED: + return KmipReasonInfo{"Object Archived", "The object must be recovered from archive before this operation."}; + case KMIP_REASON_INDEX_OUT_OF_BOUNDS: + return KmipReasonInfo{"Index Out Of Bounds", "An index in the request exceeded valid bounds."}; + case KMIP_REASON_APPLICATION_NAMESPACE_NOT_SUPPORTED: + return KmipReasonInfo{"Application Namespace Not Supported", "The application namespace is not supported by the server."}; + case KMIP_REASON_KEY_FORMAT_TYPE_NOT_SUPPORTED: + return KmipReasonInfo{"Key Format Type Not Supported", "The server cannot provide the object in the desired Key Format Type."}; + case KMIP_REASON_KEY_COMPRESSION_TYPE_NOT_SUPPORTED: + return KmipReasonInfo{"Key Compression Type Not Supported", "The server cannot provide the object in the desired Key Compression Type."}; + case KMIP_REASON_ENCODING_OPTION_FAILURE: + return KmipReasonInfo{"Encoding Option Error", "The requested encoding option failed."}; + case KMIP_REASON_KEY_VALUE_NOT_PRESENT: + return KmipReasonInfo{"Key Value Not Present", "The key value is not present on the server."}; + case KMIP_REASON_ATTESTATION_REQUIRED: + return KmipReasonInfo{"Attestation Required", "Attestation is required to complete this request."}; + case KMIP_REASON_ATTESTATION_FAILED: + return KmipReasonInfo{"Attestation Failed", "Attestation data validation failed."}; + case KMIP_REASON_SENSITIVE: + return KmipReasonInfo{"Sensitive", "Sensitive keys may not be retrieved unwrapped."}; + case KMIP_REASON_NOT_EXTRACTABLE: + return KmipReasonInfo{"Not Extractable", "The object is not extractable."}; + case KMIP_REASON_OBJECT_ALREADY_EXISTS: + return KmipReasonInfo{"Object Already Exists", "An object with the requested identifier already exists."}; + case KMIP_REASON_INVALID_TICKET: + return KmipReasonInfo{"Invalid Ticket", "The ticket was invalid."}; + case KMIP_REASON_USAGE_LIMIT_EXCEEDED: + return KmipReasonInfo{"Usage Limit Exceeded", "The usage limit or request count has been exceeded."}; + case KMIP_REASON_NUMERIC_RANGE: + return KmipReasonInfo{"Numeric Range", "A numeric result is too large or too small for the requested data type."}; + case KMIP_REASON_INVALID_DATA_TYPE: + return KmipReasonInfo{"Invalid Data Type", "A data type was invalid for the requested operation."}; + case KMIP_REASON_READ_ONLY_ATTRIBUTE: + return KmipReasonInfo{"Read Only Attribute", "Attempted to set a read-only attribute."}; + case KMIP_REASON_MULTI_VALUED_ATTRIBUTE: + return KmipReasonInfo{"Multi Valued Attribute", "Attempted to set or adjust an attribute that has multiple values."}; + case KMIP_REASON_UNSUPPORTED_ATTRIBUTE: + return KmipReasonInfo{"Unsupported Attribute", "Attribute is valid in the specification but unsupported by the server."}; + case KMIP_REASON_ATTRIBUTE_INSTANCE_NOT_FOUND: + return KmipReasonInfo{"Attribute Instance Not Found", "A specific attribute instance could not be found."}; + case KMIP_REASON_ATTRIBUTE_NOT_FOUND: + return KmipReasonInfo{"Attribute Not Found", "A requested attribute does not exist on the object."}; + case KMIP_REASON_ATTRIBUTE_READ_ONLY: + return KmipReasonInfo{"Attribute Read Only", "Attempted to modify an attribute that is read-only."}; + case KMIP_REASON_ATTRIBUTE_SINGLE_VALUED: + return KmipReasonInfo{"Attribute Single Valued", "Attempted to provide multiple values for a single-valued attribute."}; + case KMIP_REASON_BAD_CRYPTOGRAPHIC_PARAMETERS: + return KmipReasonInfo{"Bad Cryptographic Parameters", "Cryptographic parameters are invalid for the requested operation."}; + case KMIP_REASON_BAD_PASSWORD: + return KmipReasonInfo{"Bad Password", "Provided password is invalid."}; + case KMIP_REASON_CODEC_ERROR: + return KmipReasonInfo{"Codec Error", "A codec error occurred while processing the request."}; + case KMIP_REASON_ILLEGAL_OBJECT_TYPE: + return KmipReasonInfo{"Illegal Object Type", "This operation cannot be performed on the specified object type."}; + case KMIP_REASON_INCOMPATIBLE_CRYPTOGRAPHIC_USAGE_MASK: + return KmipReasonInfo{"Incompatible Cryptographic Usage Mask", "Cryptographic parameters or usage mask are incompatible with the operation."}; + case KMIP_REASON_INTERNAL_SERVER_ERROR: + return KmipReasonInfo{"Internal Server Error", "The server had an internal error and could not process the request."}; + case KMIP_REASON_INVALID_ASYNCHRONOUS_CORRELATION_VALUE: + return KmipReasonInfo{"Invalid Asynchronous Correlation Value", "No outstanding operation exists for the provided asynchronous correlation value."}; + case KMIP_REASON_INVALID_ATTRIBUTE: + return KmipReasonInfo{"Invalid Attribute", "An attribute is invalid for this object and operation."}; + case KMIP_REASON_INVALID_ATTRIBUTE_VALUE: + return KmipReasonInfo{"Invalid Attribute Value", "The supplied value for an attribute is invalid."}; + case KMIP_REASON_INVALID_CORRELATION_VALUE: + return KmipReasonInfo{"Invalid Correlation Value", "Correlation value is invalid for this request context."}; + case KMIP_REASON_INVALID_CSR: + return KmipReasonInfo{"Invalid CSR", "Certificate Signing Request is invalid."}; + case KMIP_REASON_INVALID_OBJECT_TYPE: + return KmipReasonInfo{"Invalid Object Type", "The specified object type is invalid for the operation."}; + case KMIP_REASON_KEY_WRAP_TYPE_NOT_SUPPORTED: + return KmipReasonInfo{"Key Wrap Type Not Supported", "The key wrap type is not supported by the server."}; + case KMIP_REASON_MISSING_INITIALIZATION_VECTOR: + return KmipReasonInfo{"Missing Initialization Vector", "Initialization vector is required but missing."}; + case KMIP_REASON_NON_UNIQUE_NAME_ATTRIBUTE: + return KmipReasonInfo{"Non Unique Name Attribute", "The request violates uniqueness constraints on the Name attribute."}; + case KMIP_REASON_OBJECT_DESTROYED: + return KmipReasonInfo{"Object Destroyed", "The object exists but has already been destroyed."}; + case KMIP_REASON_OBJECT_NOT_FOUND: + return KmipReasonInfo{"Object Not Found", "A requested managed object was not found."}; + case KMIP_REASON_NOT_AUTHORISED: + return KmipReasonInfo{"Not Authorised", "Client is not authorised for the requested operation."}; + case KMIP_REASON_SERVER_LIMIT_EXCEEDED: + return KmipReasonInfo{"Server Limit Exceeded", "A server-side limit has been exceeded."}; + case KMIP_REASON_UNKNOWN_ENUMERATION: + return KmipReasonInfo{"Unknown Enumeration", "An enumeration value is not known by the server."}; + case KMIP_REASON_UNKNOWN_MESSAGE_EXTENSION: + return KmipReasonInfo{"Unknown Message Extension", "The server does not support the supplied message extension."}; + case KMIP_REASON_UNKNOWN_TAG: + return KmipReasonInfo{"Unknown Tag", "A tag in the request is not known by the server."}; + case KMIP_REASON_UNSUPPORTED_CRYPTOGRAPHIC_PARAMETERS: + return KmipReasonInfo{"Unsupported Cryptographic Parameters", "Cryptographic parameters are valid in spec but unsupported by server."}; + case KMIP_REASON_UNSUPPORTED_PROTOCOL_VERSION: + return KmipReasonInfo{"Unsupported Protocol Version", "The operation cannot be performed with the provided protocol version."}; + case KMIP_REASON_WRAPPING_OBJECT_ARCHIVED: + return KmipReasonInfo{"Wrapping Object Archived", "Wrapping object is archived."}; + case KMIP_REASON_WRAPPING_OBJECT_DESTROYED: + return KmipReasonInfo{"Wrapping Object Destroyed", "Wrapping object exists but is destroyed."}; + case KMIP_REASON_WRAPPING_OBJECT_NOT_FOUND: + return KmipReasonInfo{"Wrapping Object Not Found", "Wrapping object does not exist."}; + case KMIP_REASON_WRONG_KEY_LIFECYCLE_STATE: + return KmipReasonInfo{"Wrong Key Lifecycle State", "Key lifecycle state is invalid for the requested operation."}; + case KMIP_REASON_PROTECTION_STORAGE_UNAVAILABLE: + return KmipReasonInfo{"Protection Storage Unavailable", "Requested protection storage is unavailable."}; + case KMIP_REASON_PKCS11_CODEC_ERROR: + return KmipReasonInfo{"PKCS#11 Codec Error", "There is a codec error in PKCS#11 input parameters."}; + case KMIP_REASON_PKCS11_INVALID_FUNCTION: + return KmipReasonInfo{"PKCS#11 Invalid Function", "The PKCS#11 function is not in the selected interface."}; + case KMIP_REASON_PKCS11_INVALID_INTERFACE: + return KmipReasonInfo{"PKCS#11 Invalid Interface", "The PKCS#11 interface is unknown or unavailable."}; + case KMIP_REASON_PRIVATE_PROTECTION_STORAGE_UNAVAILABLE: + return KmipReasonInfo{"Private Protection Storage Unavailable", "Requested private protection storage is unavailable."}; + case KMIP_REASON_PUBLIC_PROTECTION_STORAGE_UNAVAILABLE: + return KmipReasonInfo{"Public Protection Storage Unavailable", "Requested public protection storage is unavailable."}; + case KMIP_REASON_GENERAL_FAILURE: + return KmipReasonInfo{"General Failure", "The request failed for a reason outside the other reason codes."}; + default: + return std::nullopt; + } + } + + [[nodiscard]] const char *lookup_internal_error_name(int code) { + switch (code) { + case KMIP_OK: + return "OK"; + case KMIP_NOT_IMPLEMENTED: + return "Not Implemented"; + case KMIP_ERROR_BUFFER_FULL: + return "Buffer Full"; + case KMIP_ERROR_ATTR_UNSUPPORTED: + return "Attribute Unsupported"; + case KMIP_TAG_MISMATCH: + return "Tag Mismatch"; + case KMIP_TYPE_MISMATCH: + return "Type Mismatch"; + case KMIP_LENGTH_MISMATCH: + return "Length Mismatch"; + case KMIP_PADDING_MISMATCH: + return "Padding Mismatch"; + case KMIP_BOOLEAN_MISMATCH: + return "Boolean Mismatch"; + case KMIP_ENUM_MISMATCH: + return "Enum Mismatch"; + case KMIP_ENUM_UNSUPPORTED: + return "Enum Unsupported"; + case KMIP_INVALID_FOR_VERSION: + return "Invalid For Version"; + case KMIP_MEMORY_ALLOC_FAILED: + return "Memory Allocation Failed"; + case KMIP_IO_FAILURE: + return "I/O Failure"; + case KMIP_EXCEED_MAX_MESSAGE_SIZE: + return "Exceeded Max Message Size"; + case KMIP_MALFORMED_RESPONSE: + return "Malformed Response"; + case KMIP_OBJECT_MISMATCH: + return "Object Mismatch"; + case KMIP_ARG_INVALID: + return "Invalid Argument"; + case KMIP_ERROR_BUFFER_UNDERFULL: + return "Buffer Underfull"; + case KMIP_INVALID_ENCODING: + return "Invalid Encoding"; + case KMIP_INVALID_FIELD: + return "Invalid Field"; + case KMIP_INVALID_LENGTH: + return "Invalid Length"; + default: + return nullptr; + } + } + + [[nodiscard]] std::string hex_code(int code) { + std::ostringstream oss; + oss << "0x" << std::hex << std::uppercase << static_cast(code); + return oss.str(); + } + + [[nodiscard]] std::string kmip_message_for_code(int code) { + if (const auto info = lookup_kmip_reason_info(code)) { + return std::string(info->name) + ": " + info->description; + } + + if (const auto *internal_name = lookup_internal_error_name(code)) { + return std::string(internal_name); + } + + if (code == KMIP_STATUS_SUCCESS) { + return "Success"; + } + + return "Unknown KMIP error code " + hex_code(code); + } + + } // namespace + + const std::error_category &kmip_category() noexcept { + class category_impl : public std::error_category { + public: + [[nodiscard]] const char *name() const noexcept override { + return "kmip"; + } + + [[nodiscard]] std::string message(int code) const override { + return kmip_message_for_code(code); + } + }; + + static const category_impl instance; + return instance; + } + + std::error_code make_kmip_error_code(int native_error_code) noexcept { + return {native_error_code, kmip_category()}; + } + + KmipException::KmipException(const std::string &msg) + : std::system_error(make_kmip_error_code(KMIP_REASON_GENERAL_FAILURE), msg) {} + + KmipException::KmipException( + int native_error_code, + const std::string &msg + ) + : std::system_error(make_kmip_error_code(native_error_code), msg) {} + +} // namespace kmipcore + diff --git a/kmipcore/src/kmip_formatter.cpp b/kmipcore/src/kmip_formatter.cpp index 9503b30..0a0ec99 100644 --- a/kmipcore/src/kmip_formatter.cpp +++ b/kmipcore/src/kmip_formatter.cpp @@ -83,7 +83,7 @@ namespace kmipcore { } [[nodiscard]] const char *type_name(Type type) { - switch (type) { + switch (static_cast(type)) { case KMIP_TYPE_STRUCTURE: return "Structure"; case KMIP_TYPE_INTEGER: @@ -112,7 +112,7 @@ namespace kmipcore { } [[nodiscard]] const char *tag_name(Tag tag) { - switch (tag) { + switch (static_cast(tag)) { case KMIP_TAG_ATTRIBUTE: return "Attribute"; case KMIP_TAG_ATTRIBUTE_NAME: @@ -337,67 +337,67 @@ namespace kmipcore { } [[nodiscard]] const char *secret_data_type_name(int32_t value) { - switch (value) { - case PASSWORD: + switch (static_cast(value)) { + case secret_data_type::KMIP_SECDATA_PASSWORD: return "Password"; - case SEED: + case secret_data_type::KMIP_SECDATA_SEED: return "Seed"; default: return nullptr; } } - [[nodiscard]] const char *revocation_reason_name(int32_t value) { - switch (value) { - case UNSPECIFIED: + [[nodiscard]] const char *revocation_reason_name(std::int32_t value) { + switch (static_cast(static_cast(value))) { + case revocation_reason_type::KMIP_REVOKE_UNSPECIFIED: return "Unspecified"; - case KEY_COMPROMISE: + case revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE: return "KeyCompromise"; - case CA_COMPROMISE: + case revocation_reason_type::KMIP_REVOKE_CA_COMPROMISE: return "CACompromise"; - case AFFILIATION_CHANGED: + case revocation_reason_type::KMIP_REVOKE_AFFILIATION_CHANGED: return "AffiliationChanged"; - case SUSPENDED: + case revocation_reason_type::KMIP_REVOKE_SUSPENDED: return "Suspended"; - case CESSATION_OF_OPERATION: + case revocation_reason_type::KMIP_REVOKE_CESSATION_OF_OPERATION: return "CessationOfOperation"; - case PRIVILEDGE_WITHDRAWN: + case revocation_reason_type::KMIP_REVOKE_PRIVILEDGE_WITHDRAWN: return "PrivilegeWithdrawn"; - case REVOCATION_EXTENSIONS: + case revocation_reason_type::KMIP_REVOKE_EXTENSIONS: return "Extensions"; default: return nullptr; } } - [[nodiscard]] std::string enum_value_name(Tag tag, int32_t value) { + [[nodiscard]] std::string enum_value_name(Tag tag, std::int32_t value) { const char *name = nullptr; switch (tag) { - case KMIP_TAG_OPERATION: + case Tag::KMIP_TAG_OPERATION: name = operation_name(value); break; - case KMIP_TAG_OBJECT_TYPE: + case Tag::KMIP_TAG_OBJECT_TYPE: name = object_type_name(value); break; - case KMIP_TAG_RESULT_STATUS: + case Tag::KMIP_TAG_RESULT_STATUS: name = result_status_name(value); break; - case KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + case Tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: name = crypto_algorithm_name(value); break; - case KMIP_TAG_NAME_TYPE: + case Tag::KMIP_TAG_NAME_TYPE: name = name_type_name(value); break; - case KMIP_TAG_KEY_FORMAT_TYPE: + case Tag::KMIP_TAG_KEY_FORMAT_TYPE: name = key_format_type_name(value); break; - case KMIP_TAG_SECRET_DATA_TYPE: + case Tag::KMIP_TAG_SECRET_DATA_TYPE: name = secret_data_type_name(value); break; - case KMIP_TAG_STATE: - name = state_to_string(value); + case Tag::KMIP_TAG_STATE: + name = state_to_string(static_cast(value)); break; - case KMIP_TAG_REVOCATION_REASON_CODE: + case Tag::KMIP_TAG_REVOCATION_REASON_CODE: name = revocation_reason_name(value); break; default: @@ -411,14 +411,14 @@ namespace kmipcore { } std::ostringstream oss; - oss << value << " / " << format_hex_uint(static_cast(value), 8); + oss << value << " / " << format_hex_uint(static_cast(value), 8); return oss.str(); } void format_element_impl( const std::shared_ptr &element, std::ostringstream &oss, - size_t depth + std::size_t depth ) { if (!element) { oss << indent(depth) << "\n"; @@ -428,7 +428,7 @@ namespace kmipcore { const char *known_tag_name = tag_name(element->tag); oss << indent(depth) << (known_tag_name != nullptr ? known_tag_name : "UnknownTag") - << " (" << format_hex_uint(static_cast(element->tag), 6) + << " (" << format_hex_uint(static_cast(element->tag), 6) << ") [" << type_name(element->type) << ']'; if (const auto *structure = element->asStructure(); structure != nullptr) { @@ -443,7 +443,7 @@ namespace kmipcore { } oss << " = "; - switch (element->type) { + switch (static_cast(element->type)) { case KMIP_TYPE_INTEGER: oss << element->toInt(); break; diff --git a/kmipcore/src/kmip_payloads.cpp b/kmipcore/src/kmip_payloads.cpp index ee20347..ae33730 100644 --- a/kmipcore/src/kmip_payloads.cpp +++ b/kmipcore/src/kmip_payloads.cpp @@ -9,17 +9,17 @@ namespace kmipcore { std::shared_ptr Attribute::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); structure->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), name_ + tag::KMIP_TAG_ATTRIBUTE_NAME, name_ ) ); // Assuming simple TextString value for now. Real world is complex. structure->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value_ + tag::KMIP_TAG_ATTRIBUTE_VALUE, value_ ) ); @@ -27,17 +27,17 @@ namespace kmipcore { } Attribute Attribute::fromElement(std::shared_ptr element) { - if (!element || element->tag != static_cast(KMIP_TAG_ATTRIBUTE)) { + if (!element || element->tag != tag::KMIP_TAG_ATTRIBUTE) { throw KmipException("Invalid Attribute element"); } Attribute attr; - auto name = element->getChild(static_cast(KMIP_TAG_ATTRIBUTE_NAME)); + auto name = element->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); if (name) { attr.name_ = name->toString(); } - auto val = element->getChild(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + auto val = element->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); if (val) { attr.value_ = val->toString(); } @@ -49,7 +49,7 @@ namespace kmipcore { std::shared_ptr LocateRequestPayload::toElement() const { auto structure = Element::createStructure( - static_cast(KMIP_TAG_REQUEST_PAYLOAD) + tag::KMIP_TAG_REQUEST_PAYLOAD ); // or payload tag depending on usage // Actually usually inserted into BatchItem with specific tag, or just as // payload. Spec says Locate Request Payload is a Structure. @@ -57,7 +57,7 @@ namespace kmipcore { if (maximumItems_ > 0) { structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_MAXIMUM_ITEMS), maximumItems_ + tag::KMIP_TAG_MAXIMUM_ITEMS, maximumItems_ ) ); } @@ -65,7 +65,7 @@ namespace kmipcore { if (offsetItems_ > 0) { structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_OFFSET_ITEMS), offsetItems_ + tag::KMIP_TAG_OFFSET_ITEMS, offsetItems_ ) ); } @@ -89,11 +89,11 @@ namespace kmipcore { } for (const auto &child : s->items) { - if (child->tag == static_cast(KMIP_TAG_MAXIMUM_ITEMS)) { + if (child->tag == tag::KMIP_TAG_MAXIMUM_ITEMS) { req.maximumItems_ = child->toInt(); - } else if (child->tag == static_cast(KMIP_TAG_OFFSET_ITEMS)) { + } else if (child->tag == tag::KMIP_TAG_OFFSET_ITEMS) { req.offsetItems_ = child->toInt(); - } else if (child->tag == static_cast(KMIP_TAG_ATTRIBUTE)) { + } else if (child->tag == tag::KMIP_TAG_ATTRIBUTE) { req.attributes_.push_back(Attribute::fromElement(child)); } } @@ -104,12 +104,12 @@ namespace kmipcore { std::shared_ptr LocateResponsePayload::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); if (locatedItems_) { structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_LOCATED_ITEMS), *locatedItems_ + tag::KMIP_TAG_LOCATED_ITEMS, *locatedItems_ ) ); } @@ -118,7 +118,7 @@ namespace kmipcore { // Each ID is TextString with tag UNIQUE_IDENTIFIER structure->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), id + tag::KMIP_TAG_UNIQUE_IDENTIFIER, id ) ); } @@ -134,9 +134,9 @@ namespace kmipcore { } for (const auto &child : s->items) { - if (child->tag == static_cast(KMIP_TAG_LOCATED_ITEMS)) { + if (child->tag == tag::KMIP_TAG_LOCATED_ITEMS) { resp.setLocatedItems(child->toInt()); - } else if (child->tag == static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)) { + } else if (child->tag == tag::KMIP_TAG_UNIQUE_IDENTIFIER) { resp.uniqueIdentifiers_.push_back(child->toString()); } } diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp index 14d998c..e73736d 100644 --- a/kmipcore/src/kmip_protocol.cpp +++ b/kmipcore/src/kmip_protocol.cpp @@ -11,15 +11,15 @@ namespace kmipcore { : major_(major), minor_(minor) {} std::shared_ptr ProtocolVersion::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + Element::createStructure(tag::KMIP_TAG_PROTOCOL_VERSION); structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_PROTOCOL_VERSION_MAJOR), major_ + tag::KMIP_TAG_PROTOCOL_VERSION_MAJOR, major_ ) ); structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_PROTOCOL_VERSION_MINOR), minor_ + tag::KMIP_TAG_PROTOCOL_VERSION_MINOR, minor_ ) ); return structure; @@ -27,18 +27,18 @@ namespace kmipcore { ProtocolVersion ProtocolVersion::fromElement(std::shared_ptr element) { if (!element || - element->tag != static_cast(KMIP_TAG_PROTOCOL_VERSION) || - element->type != ::KMIP_TYPE_STRUCTURE) { + element->tag != tag::KMIP_TAG_PROTOCOL_VERSION || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid ProtocolVersion element"); } ProtocolVersion pv; auto maj = - element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION_MAJOR)); + element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION_MAJOR); if (maj) { pv.major_ = maj->toInt(); } auto min = - element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION_MINOR)); + element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION_MINOR); if (min) { pv.minor_ = min->toInt(); } @@ -47,12 +47,12 @@ namespace kmipcore { // === RequestHeader === std::shared_ptr RequestHeader::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_HEADER)); + Element::createStructure(tag::KMIP_TAG_REQUEST_HEADER); structure->asStructure()->add(protocolVersion_.toElement()); if (maximumResponseSize_) { structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_MAXIMUM_RESPONSE_SIZE), + tag::KMIP_TAG_MAXIMUM_RESPONSE_SIZE, *maximumResponseSize_ ) ); @@ -60,39 +60,39 @@ namespace kmipcore { if (batchOrderOption_) { structure->asStructure()->add( Element::createBoolean( - static_cast(KMIP_TAG_BATCH_ORDER_OPTION), *batchOrderOption_ + tag::KMIP_TAG_BATCH_ORDER_OPTION, *batchOrderOption_ ) ); } if (timeStamp_) { structure->asStructure()->add( Element::createDateTime( - static_cast(KMIP_TAG_TIME_STAMP), *timeStamp_ + tag::KMIP_TAG_TIME_STAMP, *timeStamp_ ) ); } if (userName_ || password_) { auto authentication = - Element::createStructure(static_cast(KMIP_TAG_AUTHENTICATION)); + Element::createStructure(tag::KMIP_TAG_AUTHENTICATION); auto credential = - Element::createStructure(static_cast(KMIP_TAG_CREDENTIAL)); + Element::createStructure(tag::KMIP_TAG_CREDENTIAL); credential->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_CREDENTIAL_TYPE), + tag::KMIP_TAG_CREDENTIAL_TYPE, KMIP_CRED_USERNAME_AND_PASSWORD ) ); auto credential_value = - Element::createStructure(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + Element::createStructure(tag::KMIP_TAG_CREDENTIAL_VALUE); if (userName_) { credential_value->asStructure()->add(Element::createTextString( - static_cast(KMIP_TAG_USERNAME), *userName_ + tag::KMIP_TAG_USERNAME, *userName_ )); } if (password_) { credential_value->asStructure()->add(Element::createTextString( - static_cast(KMIP_TAG_PASSWORD), *password_ + tag::KMIP_TAG_PASSWORD, *password_ )); } @@ -102,69 +102,69 @@ namespace kmipcore { } structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_BATCH_COUNT), batchCount_ + tag::KMIP_TAG_BATCH_COUNT, batchCount_ ) ); return structure; } RequestHeader RequestHeader::fromElement(std::shared_ptr element) { - if (!element || element->tag != static_cast(KMIP_TAG_REQUEST_HEADER) || - element->type != ::KMIP_TYPE_STRUCTURE) { + if (!element || element->tag != tag::KMIP_TAG_REQUEST_HEADER || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid RequestHeader element"); } RequestHeader rh; - auto pv = element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + auto pv = element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION); if (pv) { rh.protocolVersion_ = ProtocolVersion::fromElement(pv); } else { throw KmipException("Missing ProtocolVersion in header"); } auto maxResponseSize = - element->getChild(static_cast(KMIP_TAG_MAXIMUM_RESPONSE_SIZE)); + element->getChild(tag::KMIP_TAG_MAXIMUM_RESPONSE_SIZE); if (maxResponseSize) { rh.maximumResponseSize_ = maxResponseSize->toInt(); } - auto timeStamp = element->getChild(static_cast(KMIP_TAG_TIME_STAMP)); + auto timeStamp = element->getChild(tag::KMIP_TAG_TIME_STAMP); if (timeStamp) { rh.timeStamp_ = timeStamp->toLong(); } auto batchOrderOption = - element->getChild(static_cast(KMIP_TAG_BATCH_ORDER_OPTION)); + element->getChild(tag::KMIP_TAG_BATCH_ORDER_OPTION); if (batchOrderOption) { rh.batchOrderOption_ = batchOrderOption->toBool(); } auto authentication = - element->getChild(static_cast(KMIP_TAG_AUTHENTICATION)); + element->getChild(tag::KMIP_TAG_AUTHENTICATION); if (authentication) { auto credential = - authentication->getChild(static_cast(KMIP_TAG_CREDENTIAL)); + authentication->getChild(tag::KMIP_TAG_CREDENTIAL); if (!credential) { throw KmipException("Missing Credential in Authentication"); } auto credentialType = - credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_TYPE)); + credential->getChild(tag::KMIP_TAG_CREDENTIAL_TYPE); auto credentialValue = - credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + credential->getChild(tag::KMIP_TAG_CREDENTIAL_VALUE); if (!credentialType || !credentialValue) { throw KmipException("Invalid Credential in Authentication"); } if (credentialType->toEnum() == KMIP_CRED_USERNAME_AND_PASSWORD) { auto userName = - credentialValue->getChild(static_cast(KMIP_TAG_USERNAME)); + credentialValue->getChild(tag::KMIP_TAG_USERNAME); if (userName) { rh.userName_ = userName->toString(); } auto password = - credentialValue->getChild(static_cast(KMIP_TAG_PASSWORD)); + credentialValue->getChild(tag::KMIP_TAG_PASSWORD); if (password) { rh.password_ = password->toString(); } } } - auto bc = element->getChild(static_cast(KMIP_TAG_BATCH_COUNT)); + auto bc = element->getChild(tag::KMIP_TAG_BATCH_COUNT); if (bc) { rh.batchCount_ = bc->toInt(); } @@ -173,10 +173,10 @@ namespace kmipcore { // === RequestBatchItem === std::shared_ptr RequestBatchItem::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); structure->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OPERATION), operation_ + tag::KMIP_TAG_OPERATION, operation_ ) ); if (uniqueBatchItemId_ != 0) { @@ -186,7 +186,7 @@ namespace kmipcore { ); structure->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID), idBytes + tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID, idBytes ) ); } @@ -197,19 +197,19 @@ namespace kmipcore { } RequestBatchItem RequestBatchItem::fromElement(std::shared_ptr element) { - if (!element || element->tag != static_cast(KMIP_TAG_BATCH_ITEM) || - element->type != ::KMIP_TYPE_STRUCTURE) { + if (!element || element->tag != tag::KMIP_TAG_BATCH_ITEM || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid RequestBatchItem element"); } RequestBatchItem rbi; - auto op = element->getChild(static_cast(KMIP_TAG_OPERATION)); + auto op = element->getChild(tag::KMIP_TAG_OPERATION); if (op) { rbi.operation_ = op->toEnum(); } else { throw KmipException("Missing Operation"); } auto id = - element->getChild(static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID)); + element->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); if (id) { auto bytes = id->toBytes(); if (bytes.size() == sizeof(rbi.uniqueBatchItemId_)) { @@ -221,7 +221,7 @@ namespace kmipcore { } } auto payload = - element->getChild(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + element->getChild(tag::KMIP_TAG_REQUEST_PAYLOAD); if (payload) { rbi.requestPayload_ = payload; } @@ -300,7 +300,7 @@ namespace kmipcore { std::shared_ptr RequestMessage::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_MESSAGE)); + Element::createStructure(tag::KMIP_TAG_REQUEST_MESSAGE); structure->asStructure()->add(header_.toElement()); for (const auto &item : batchItems_) { structure->asStructure()->add(item.toElement()); @@ -309,12 +309,12 @@ namespace kmipcore { } RequestMessage RequestMessage::fromElement(std::shared_ptr element) { if (!element || - element->tag != static_cast(KMIP_TAG_REQUEST_MESSAGE) || - element->type != ::KMIP_TYPE_STRUCTURE) { + element->tag != tag::KMIP_TAG_REQUEST_MESSAGE || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid RequestMessage element"); } RequestMessage rm; - auto hdr = element->getChild(static_cast(KMIP_TAG_REQUEST_HEADER)); + auto hdr = element->getChild(tag::KMIP_TAG_REQUEST_HEADER); if (hdr) { rm.header_ = RequestHeader::fromElement(hdr); } else { @@ -322,7 +322,7 @@ namespace kmipcore { } const auto *s = std::get_if(&element->value); for (const auto &child : s->items) { - if (child->tag == static_cast(KMIP_TAG_BATCH_ITEM)) { + if (child->tag == tag::KMIP_TAG_BATCH_ITEM) { rm.batchItems_.push_back(RequestBatchItem::fromElement(child)); } } @@ -332,38 +332,38 @@ namespace kmipcore { // === ResponseHeader === std::shared_ptr ResponseHeader::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_HEADER)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_HEADER); structure->asStructure()->add(protocolVersion_.toElement()); structure->asStructure()->add( Element::createDateTime( - static_cast(KMIP_TAG_TIME_STAMP), timeStamp_ + tag::KMIP_TAG_TIME_STAMP, timeStamp_ ) ); structure->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_BATCH_COUNT), batchCount_ + tag::KMIP_TAG_BATCH_COUNT, batchCount_ ) ); return structure; } ResponseHeader ResponseHeader::fromElement(std::shared_ptr element) { if (!element || - element->tag != static_cast(KMIP_TAG_RESPONSE_HEADER) || - element->type != ::KMIP_TYPE_STRUCTURE) { + element->tag != tag::KMIP_TAG_RESPONSE_HEADER || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid ResponseHeader element"); } ResponseHeader rh; - auto pv = element->getChild(static_cast(KMIP_TAG_PROTOCOL_VERSION)); + auto pv = element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION); if (pv) { rh.protocolVersion_ = ProtocolVersion::fromElement(pv); } else { throw KmipException("Missing ProtocolVersion"); } - auto ts = element->getChild(static_cast(KMIP_TAG_TIME_STAMP)); + auto ts = element->getChild(tag::KMIP_TAG_TIME_STAMP); if (ts) { rh.timeStamp_ = ts->toLong(); } - auto bc = element->getChild(static_cast(KMIP_TAG_BATCH_COUNT)); + auto bc = element->getChild(tag::KMIP_TAG_BATCH_COUNT); if (bc) { rh.batchCount_ = bc->toInt(); } @@ -372,10 +372,10 @@ namespace kmipcore { // === ResponseBatchItem === std::shared_ptr ResponseBatchItem::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); structure->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OPERATION), operation_ + tag::KMIP_TAG_OPERATION, operation_ ) ); if (uniqueBatchItemId_ != 0) { @@ -385,26 +385,26 @@ namespace kmipcore { ); structure->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID), idBytes + tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID, idBytes ) ); } structure->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_RESULT_STATUS), resultStatus_ + tag::KMIP_TAG_RESULT_STATUS, resultStatus_ ) ); if (resultReason_) { structure->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_RESULT_REASON), *resultReason_ + tag::KMIP_TAG_RESULT_REASON, *resultReason_ ) ); } if (resultMessage_) { structure->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_RESULT_MESSAGE), *resultMessage_ + tag::KMIP_TAG_RESULT_MESSAGE, *resultMessage_ ) ); } @@ -415,19 +415,19 @@ namespace kmipcore { } ResponseBatchItem ResponseBatchItem::fromElement(std::shared_ptr element) { - if (!element || element->tag != static_cast(KMIP_TAG_BATCH_ITEM) || - element->type != ::KMIP_TYPE_STRUCTURE) { + if (!element || element->tag != tag::KMIP_TAG_BATCH_ITEM || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid ResponseBatchItem element"); } ResponseBatchItem rbi; - auto op = element->getChild(static_cast(KMIP_TAG_OPERATION)); + auto op = element->getChild(tag::KMIP_TAG_OPERATION); if (op) { rbi.operation_ = op->toEnum(); } else { throw KmipException("Missing Operation"); } auto id = - element->getChild(static_cast(KMIP_TAG_UNIQUE_BATCH_ITEM_ID)); + element->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); if (id) { auto bytes = id->toBytes(); if (bytes.size() == sizeof(rbi.uniqueBatchItemId_)) { @@ -438,20 +438,20 @@ namespace kmipcore { ); } } - auto status = element->getChild(static_cast(KMIP_TAG_RESULT_STATUS)); + auto status = element->getChild(tag::KMIP_TAG_RESULT_STATUS); if (status) { rbi.resultStatus_ = status->toEnum(); } - auto reason = element->getChild(static_cast(KMIP_TAG_RESULT_REASON)); + auto reason = element->getChild(tag::KMIP_TAG_RESULT_REASON); if (reason) { rbi.resultReason_ = reason->toEnum(); } - auto msg = element->getChild(static_cast(KMIP_TAG_RESULT_MESSAGE)); + auto msg = element->getChild(tag::KMIP_TAG_RESULT_MESSAGE); if (msg) { rbi.resultMessage_ = msg->toString(); } auto payload = - element->getChild(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + element->getChild(tag::KMIP_TAG_RESPONSE_PAYLOAD); if (payload) { rbi.responsePayload_ = payload; } @@ -460,7 +460,7 @@ namespace kmipcore { // === ResponseMessage === std::shared_ptr ResponseMessage::toElement() const { auto structure = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); structure->asStructure()->add(header_.toElement()); for (const auto &item : batchItems_) { structure->asStructure()->add(item.toElement()); @@ -470,12 +470,12 @@ namespace kmipcore { ResponseMessage ResponseMessage::fromElement(std::shared_ptr element) { if (!element || - element->tag != static_cast(KMIP_TAG_RESPONSE_MESSAGE) || - element->type != ::KMIP_TYPE_STRUCTURE) { + element->tag != tag::KMIP_TAG_RESPONSE_MESSAGE || + element->type != Type::KMIP_TYPE_STRUCTURE) { throw KmipException("Invalid ResponseMessage element"); } ResponseMessage rm; - auto hdr = element->getChild(static_cast(KMIP_TAG_RESPONSE_HEADER)); + auto hdr = element->getChild(tag::KMIP_TAG_RESPONSE_HEADER); if (hdr) { rm.header_ = ResponseHeader::fromElement(hdr); } else { @@ -483,7 +483,7 @@ namespace kmipcore { } const auto *s = std::get_if(&element->value); for (const auto &child : s->items) { - if (child->tag == static_cast(KMIP_TAG_BATCH_ITEM)) { + if (child->tag == tag::KMIP_TAG_BATCH_ITEM) { rm.batchItems_.push_back(ResponseBatchItem::fromElement(child)); } } diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index 52f153d..0806874 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -7,14 +7,14 @@ namespace kmipcore { const std::string &attribute_name, const std::string &value ) { auto attribute = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attribute->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name ) ); auto attribute_value = Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + tag::KMIP_TAG_ATTRIBUTE_VALUE, value ); attribute->asStructure()->add(attribute_value); return attribute; @@ -22,14 +22,14 @@ namespace kmipcore { std::shared_ptr make_enum_attribute(const std::string &attribute_name, int32_t value) { auto attribute = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attribute->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name ) ); auto attribute_value = Element::createEnumeration( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + tag::KMIP_TAG_ATTRIBUTE_VALUE, value ); attribute->asStructure()->add(attribute_value); return attribute; @@ -38,37 +38,37 @@ namespace kmipcore { const std::string &attribute_name, int32_t value ) { auto attribute = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attribute->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), attribute_name + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name ) ); auto attribute_value = Element::createInteger( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), value + tag::KMIP_TAG_ATTRIBUTE_VALUE, value ); attribute->asStructure()->add(attribute_value); return attribute; } std::shared_ptr make_name_attribute(const std::string &value) { auto attribute_value = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE_VALUE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE_VALUE); attribute_value->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_NAME_VALUE), value + tag::KMIP_TAG_NAME_VALUE, value ) ); attribute_value->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_NAME_TYPE), + tag::KMIP_TAG_NAME_TYPE, KMIP_NAME_UNINTERPRETED_TEXT_STRING ) ); auto attribute = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attribute->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Name" ) ); attribute->asStructure()->add(attribute_value); @@ -78,7 +78,7 @@ namespace kmipcore { const std::vector> &attributes ) { auto template_attribute = Element::createStructure( - static_cast(KMIP_TAG_TEMPLATE_ATTRIBUTE) + tag::KMIP_TAG_TEMPLATE_ATTRIBUTE ); for (const auto &attribute : attributes) { template_attribute->asStructure()->add(attribute); @@ -88,10 +88,10 @@ namespace kmipcore { std::shared_ptr make_key_value(const std::vector &bytes) { auto key_value = - Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + Element::createStructure(tag::KMIP_TAG_KEY_VALUE); key_value->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_KEY_MATERIAL), + tag::KMIP_TAG_KEY_MATERIAL, std::vector(bytes.begin(), bytes.end()) ) ); @@ -104,24 +104,24 @@ namespace kmipcore { std::optional cryptographic_length ) { auto key_block = - Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_KEY_FORMAT_TYPE), key_format_type + tag::KMIP_TAG_KEY_FORMAT_TYPE, key_format_type ) ); key_block->asStructure()->add(make_key_value(bytes)); if (algorithm) { key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM), *algorithm + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, *algorithm ) ); } if (cryptographic_length) { key_block->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_CRYPTOGRAPHIC_LENGTH), + tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, *cryptographic_length ) ); @@ -131,7 +131,7 @@ namespace kmipcore { std::shared_ptr make_symmetric_key(const std::vector &key_value) { auto symmetric_key = - Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); symmetric_key->asStructure()->add(make_key_block( KMIP_KEYFORMAT_RAW, key_value, @@ -141,12 +141,12 @@ namespace kmipcore { return symmetric_key; } std::shared_ptr - make_secret_data(const secret_t &secret, int32_t secret_type) { + make_secret_data(const secret_t &secret, secret_data_type secret_type) { auto secret_data = - Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + Element::createStructure(tag::KMIP_TAG_SECRET_DATA); secret_data->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_SECRET_DATA_TYPE), secret_type + tag::KMIP_TAG_SECRET_DATA_TYPE, static_cast(secret_type) ) ); secret_data->asStructure()->add(make_key_block( @@ -185,10 +185,10 @@ namespace kmipcore { } auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); payload->asStructure()->add(detail::make_template_attribute(attributes)); @@ -228,10 +228,10 @@ namespace kmipcore { } auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); payload->asStructure()->add(detail::make_template_attribute(attributes)); @@ -246,7 +246,7 @@ namespace kmipcore { const std::string &name, const std::string &group, const secret_t &secret, - int32_t secret_type + secret_data_type secret_type ) { setOperation(KMIP_OP_REGISTER); @@ -263,10 +263,10 @@ namespace kmipcore { } auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA ) ); payload->asStructure()->add(detail::make_template_attribute(attributes)); @@ -280,18 +280,18 @@ namespace kmipcore { LocateRequest::LocateRequest( bool locate_by_group, const std::string &name, - int32_t object_type, + object_type obj_type, size_t max_items, size_t offset ) { setOperation(KMIP_OP_LOCATE); auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); if (max_items > 0) { payload->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_MAXIMUM_ITEMS), + tag::KMIP_TAG_MAXIMUM_ITEMS, static_cast(max_items) ) ); @@ -299,14 +299,14 @@ namespace kmipcore { if (offset > 0) { payload->asStructure()->add( Element::createInteger( - static_cast(KMIP_TAG_OFFSET_ITEMS), + tag::KMIP_TAG_OFFSET_ITEMS, static_cast(offset) ) ); } payload->asStructure()->add( - detail::make_enum_attribute("Object Type", object_type) + detail::make_enum_attribute("Object Type", static_cast(obj_type)) ); if (!name.empty()) { if (locate_by_group) { @@ -325,31 +325,31 @@ namespace kmipcore { // --------------------------------------------------------------------------- RevokeRequest::RevokeRequest( const std::string &unique_id, - int32_t reason, + revocation_reason_type reason, const std::string &message, time_t occurrence_time ) { setOperation(KMIP_OP_REVOKE); auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), unique_id + tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id ) ); auto revocation_reason = - Element::createStructure(static_cast(KMIP_TAG_REVOCATION_REASON)); + Element::createStructure(tag::KMIP_TAG_REVOCATION_REASON); revocation_reason->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_REVOCATION_REASON_CODE), reason + tag::KMIP_TAG_REVOCATION_REASON_CODE, static_cast(reason) ) ); if (!message.empty()) { revocation_reason->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_REVOKATION_MESSAGE), message + tag::KMIP_TAG_REVOKATION_MESSAGE, message ) ); } @@ -358,7 +358,7 @@ namespace kmipcore { if (occurrence_time > 0) { payload->asStructure()->add( Element::createDateTime( - static_cast(KMIP_TAG_COMPROMISE_OCCURRANCE_DATE), + tag::KMIP_TAG_COMPROMISE_OCCURRANCE_DATE, static_cast(occurrence_time) ) ); diff --git a/kmipcore/src/kmip_responses.cpp b/kmipcore/src/kmip_responses.cpp index 7562335..2d6b460 100644 --- a/kmipcore/src/kmip_responses.cpp +++ b/kmipcore/src/kmip_responses.cpp @@ -9,13 +9,13 @@ namespace kmipcore { ) { switch (objectType) { case KMIP_OBJTYPE_SYMMETRIC_KEY: - return payload->getChild(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + return payload->getChild(tag::KMIP_TAG_SYMMETRIC_KEY); case KMIP_OBJTYPE_SECRET_DATA: - return payload->getChild(static_cast(KMIP_TAG_SECRET_DATA)); + return payload->getChild(tag::KMIP_TAG_SECRET_DATA); case KMIP_OBJTYPE_PRIVATE_KEY: - return payload->getChild(static_cast(KMIP_TAG_PRIVATE_KEY)); + return payload->getChild(tag::KMIP_TAG_PRIVATE_KEY); case KMIP_OBJTYPE_PUBLIC_KEY: - return payload->getChild(static_cast(KMIP_TAG_PUBLIC_KEY)); + return payload->getChild(tag::KMIP_TAG_PUBLIC_KEY); default: return {}; } @@ -35,7 +35,7 @@ namespace kmipcore { detail::require_response_payload(item, "GetResponseBatchItem"); auto uniqueIdentifier = - payload->getChild(static_cast(KMIP_TAG_UNIQUE_IDENTIFIER)); + payload->getChild(tag::KMIP_TAG_UNIQUE_IDENTIFIER); if (!uniqueIdentifier) { throw KmipException( "GetResponseBatchItem: missing unique identifier in response payload" @@ -43,7 +43,7 @@ namespace kmipcore { } result.uniqueIdentifier_ = uniqueIdentifier->toString(); - auto objectType = payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + auto objectType = payload->getChild(tag::KMIP_TAG_OBJECT_TYPE); if (!objectType) { throw KmipException( "GetResponseBatchItem: missing object type in response payload" @@ -76,7 +76,7 @@ namespace kmipcore { item, "GetAttributesResponseBatchItem" ); result.attributes_ = - payload->getChildren(static_cast(KMIP_TAG_ATTRIBUTE)); + payload->getChildren(tag::KMIP_TAG_ATTRIBUTE); return result; } @@ -96,7 +96,7 @@ namespace kmipcore { ); for (const auto &attributeName : - payload->getChildren(static_cast(KMIP_TAG_ATTRIBUTE_NAME))) { + payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_NAME)) { result.attributeNames_.push_back(attributeName->toString()); } diff --git a/kmipcore/src/response_parser.cpp b/kmipcore/src/response_parser.cpp index 0788f0d..fadf672 100644 --- a/kmipcore/src/response_parser.cpp +++ b/kmipcore/src/response_parser.cpp @@ -85,7 +85,10 @@ namespace kmipcore { void ResponseParser::ensureSuccess(const ResponseBatchItem &item) { if (item.getResultStatus() != KMIP_STATUS_SUCCESS) { - throw KmipException(formatOperationResult(item)); + const int reason = static_cast( + item.getResultReason().value_or(KMIP_REASON_GENERAL_FAILURE) + ); + throw KmipException(reason, formatOperationResult(item)); } } @@ -94,7 +97,7 @@ namespace kmipcore { OperationResult result = { value.getOperation(), value.getResultStatus(), - value.getResultReason().value_or(0), + value.getResultReason().value_or(KMIP_REASON_GENERAL_FAILURE), value.getResultMessage().value_or("") }; @@ -102,7 +105,9 @@ namespace kmipcore { stream << "Message: " << result.resultMessage << "\nOperation: " << operationToString(result.operation) << "; Result status: " << resultStatusToString(result.resultStatus) - << "; Result reason: " << result.resultReason; + << "; Result reason: " + << kmip_category().message(result.resultReason) + << " (" << result.resultReason << ")"; return stream.str(); } diff --git a/kmipcore/src/serialization_buffer.cpp b/kmipcore/src/serialization_buffer.cpp index ef3d3b7..344a34f 100644 --- a/kmipcore/src/serialization_buffer.cpp +++ b/kmipcore/src/serialization_buffer.cpp @@ -23,8 +23,10 @@ void SerializationBuffer::writeByte(uint8_t value) { buffer_[current_offset_++] = value; } -void SerializationBuffer::writeBytes(const uint8_t* data, size_t length) { - if (!data || length == 0) return; +void SerializationBuffer::writeBytes(std::span data) { + if (data.empty()) return; + + const size_t length = data.size(); ensureSpace(length); @@ -33,13 +35,15 @@ void SerializationBuffer::writeBytes(const uint8_t* data, size_t length) { buffer_.resize(current_offset_ + length); } - std::memcpy(&buffer_[current_offset_], data, length); + std::memcpy(&buffer_[current_offset_], data.data(), length); current_offset_ += length; } -void SerializationBuffer::writePadded(const uint8_t* data, size_t length) { +void SerializationBuffer::writePadded(std::span data) { + const size_t length = data.size(); + // Write data first - writeBytes(data, length); + writeBytes(data); // Calculate KMIP padding (align to TTLV_ALIGNMENT bytes) // Formula: (A - (size % A)) % A gives 0 for multiples of A, otherwise 1..A-1 @@ -101,14 +105,14 @@ std::vector SerializationBuffer::release() { // Reset write position and logical size but KEEP the reserved capacity so // the buffer can be reused immediately without a new heap allocation. - // Callers that need to reclaim memory explicitly can call freeMemory(). + // Callers that need to reclaim memory explicitly can call shrink(). buffer_.clear(); // size -> 0, capacity unchanged current_offset_ = 0; return result; // NRVO / move } -void SerializationBuffer::freeMemory() { +void SerializationBuffer::shrink() { // Aggressively release all heap memory (capacity included). // Use the swap-with-empty idiom because shrink_to_fit() is advisory. std::vector().swap(buffer_); diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp index b225a18..abc8e8c 100644 --- a/kmipcore/tests/test_core.cpp +++ b/kmipcore/tests/test_core.cpp @@ -7,7 +7,7 @@ using namespace kmipcore; void test_integer() { auto elem = - Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 12345); + Element::createInteger(tag::KMIP_TAG_ACTIVATION_DATE, 12345); SerializationBuffer buf_i; elem->serialize(buf_i); auto data = buf_i.release(); @@ -15,19 +15,19 @@ void test_integer() { size_t offset = 0; auto decoded = Element::deserialize(data, offset); assert(offset == 16); - assert(decoded->tag == static_cast(KMIP_TAG_ACTIVATION_DATE)); - assert(decoded->type == ::KMIP_TYPE_INTEGER); + assert(decoded->tag == tag::KMIP_TAG_ACTIVATION_DATE); + assert(decoded->type == kmipcore::Type::KMIP_TYPE_INTEGER); assert(std::get(decoded->value).value == 12345); std::cout << "Integer test passed" << std::endl; } void test_structure() { auto root = - Element::createStructure(static_cast(KMIP_TAG_APPLICATION_DATA)); + Element::createStructure(tag::KMIP_TAG_APPLICATION_DATA); auto child1 = Element::createInteger( - static_cast(KMIP_TAG_APPLICATION_NAMESPACE), 10 + tag::KMIP_TAG_APPLICATION_NAMESPACE, 10 ); auto child2 = Element::createBoolean( - static_cast(KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION), true + tag::KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION, true ); std::get(root->value).add(child1); std::get(root->value).add(child2); @@ -36,16 +36,16 @@ void test_structure() { auto data = buf_s.release(); size_t offset = 0; auto decoded = Element::deserialize(data, offset); - assert(decoded->tag == static_cast(KMIP_TAG_APPLICATION_DATA)); - assert(decoded->type == ::KMIP_TYPE_STRUCTURE); + assert(decoded->tag == tag::KMIP_TAG_APPLICATION_DATA); + assert(decoded->type == kmipcore::Type::KMIP_TYPE_STRUCTURE); auto &s = std::get(decoded->value); assert(s.items.size() == 2); auto d1 = s.items[0]; - assert(d1->tag == static_cast(KMIP_TAG_APPLICATION_NAMESPACE)); + assert(d1->tag == tag::KMIP_TAG_APPLICATION_NAMESPACE); assert(std::get(d1->value).value == 10); auto d2 = s.items[1]; assert( - d2->tag == static_cast(KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION) + d2->tag == tag::KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION ); assert(std::get(d2->value).value == true); std::cout << "Structure test passed" << std::endl; @@ -60,9 +60,9 @@ void test_request_message() { item.setOperation(KMIP_OP_GET); // Some operation code // Fake payload auto payload = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 999) + Element::createInteger(tag::KMIP_TAG_ACTIVATION_DATE, 999) ); item.setRequestPayload(payload); auto first_id = req.add_batch_item(item); @@ -70,9 +70,9 @@ void test_request_message() { RequestBatchItem item2; item2.setOperation(KMIP_OP_GET_ATTRIBUTE_LIST); auto payload2 = - Element::createStructure(static_cast(KMIP_TAG_REQUEST_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload2->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_ACTIVATION_DATE), 111) + Element::createInteger(tag::KMIP_TAG_ACTIVATION_DATE, 111) ); item2.setRequestPayload(payload2); auto second_id = req.add_batch_item(item2); @@ -110,31 +110,31 @@ void test_response_message() { get_item.setResultMessage("OK"); auto get_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); get_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-get-1" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-get-1" ) ); get_payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); auto symmetric_key = - Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); auto key_block = - Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_RAW + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_RAW ) ); auto key_value = - Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + Element::createStructure(tag::KMIP_TAG_KEY_VALUE); key_value->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_KEY_MATERIAL), {0x10, 0x11, 0x12, 0x13} + tag::KMIP_TAG_KEY_MATERIAL, {0x10, 0x11, 0x12, 0x13} ) ); key_block->asStructure()->add(key_value); @@ -147,18 +147,18 @@ void test_response_message() { locate_item.setOperation(KMIP_OP_LOCATE); locate_item.setResultStatus(KMIP_STATUS_SUCCESS); auto locate_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); locate_payload->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) ); locate_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-locate-1" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-locate-1" ) ); locate_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "id-locate-2" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-locate-2" ) ); locate_item.setResponsePayload(locate_payload); @@ -180,10 +180,10 @@ void test_response_message() { } void test_typed_response_batch_items() { auto create_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); create_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "create-id" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "create-id" ) ); @@ -196,32 +196,32 @@ void test_typed_response_batch_items() { assert(create_response.getUniqueIdentifier() == "create-id"); auto get_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); get_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "get-id" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "get-id" ) ); get_payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA ) ); auto secret_data = - Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + Element::createStructure(tag::KMIP_TAG_SECRET_DATA); auto key_block = - Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); auto key_value = - Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + Element::createStructure(tag::KMIP_TAG_KEY_VALUE); key_value->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_KEY_MATERIAL), {0x61, 0x62} + tag::KMIP_TAG_KEY_MATERIAL, {0x61, 0x62} ) ); key_block->asStructure()->add(key_value); secret_data->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_SECRET_DATA_TYPE), PASSWORD + tag::KMIP_TAG_SECRET_DATA_TYPE, static_cast(secret_data_type::KMIP_SECDATA_PASSWORD) ) ); secret_data->asStructure()->add(key_block); @@ -238,17 +238,17 @@ void test_typed_response_batch_items() { assert(get_response.getObjectElement() != nullptr); auto attributes_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); auto attribute = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attribute->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "State" + tag::KMIP_TAG_ATTRIBUTE_NAME, "State" ) ); attribute->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), KMIP_STATE_ACTIVE + tag::KMIP_TAG_ATTRIBUTE_VALUE, KMIP_STATE_ACTIVE ) ); attributes_payload->asStructure()->add(attribute); @@ -263,15 +263,15 @@ void test_typed_response_batch_items() { assert(attributes_response.getAttributes().size() == 1); auto attribute_list_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); attribute_list_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Name" ) ); attribute_list_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "State" + tag::KMIP_TAG_ATTRIBUTE_NAME, "State" ) ); @@ -286,18 +286,18 @@ void test_typed_response_batch_items() { assert(attribute_list_response.getAttributeNames()[0] == "Name"); auto locate_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); locate_payload->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) ); locate_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "loc-1" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "loc-1" ) ); locate_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "loc-2" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "loc-2" ) ); @@ -314,10 +314,10 @@ void test_typed_response_batch_items() { destroy_item.setOperation(KMIP_OP_DESTROY); destroy_item.setResultStatus(KMIP_STATUS_SUCCESS); auto destroy_payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); destroy_payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "destroy-id" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "destroy-id" ) ); destroy_item.setResponsePayload(destroy_payload); @@ -354,17 +354,17 @@ void test_response_required_fields() { { // Missing ResponseHeader must be rejected. auto response_message = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); auto batch_item = - Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); batch_item->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OPERATION), KMIP_OP_GET + tag::KMIP_TAG_OPERATION, KMIP_OP_GET ) ); batch_item->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_RESULT_STATUS), KMIP_STATUS_SUCCESS + tag::KMIP_TAG_RESULT_STATUS, KMIP_STATUS_SUCCESS ) ); response_message->asStructure()->add(batch_item); @@ -381,7 +381,7 @@ void test_response_required_fields() { { // Missing Operation inside ResponseBatchItem must be rejected. auto response_message = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_MESSAGE)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); ResponseHeader header; header.getProtocolVersion().setMajor(1); @@ -391,10 +391,10 @@ void test_response_required_fields() { response_message->asStructure()->add(header.toElement()); auto batch_item = - Element::createStructure(static_cast(KMIP_TAG_BATCH_ITEM)); + Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); batch_item->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_RESULT_STATUS), KMIP_STATUS_SUCCESS + tag::KMIP_TAG_RESULT_STATUS, KMIP_STATUS_SUCCESS ) ); response_message->asStructure()->add(batch_item); @@ -420,28 +420,28 @@ void test_request_header_authentication() { header.setPassword(std::string("s3cr3t")); auto element = header.toElement(); - auto auth = element->getChild(static_cast(KMIP_TAG_AUTHENTICATION)); + auto auth = element->getChild(tag::KMIP_TAG_AUTHENTICATION); assert(auth != nullptr); - auto credential = auth->getChild(static_cast(KMIP_TAG_CREDENTIAL)); + auto credential = auth->getChild(tag::KMIP_TAG_CREDENTIAL); assert(credential != nullptr); auto credential_type = - credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_TYPE)); + credential->getChild(tag::KMIP_TAG_CREDENTIAL_TYPE); assert(credential_type != nullptr); assert(credential_type->toEnum() == KMIP_CRED_USERNAME_AND_PASSWORD); auto credential_value = - credential->getChild(static_cast(KMIP_TAG_CREDENTIAL_VALUE)); + credential->getChild(tag::KMIP_TAG_CREDENTIAL_VALUE); assert(credential_value != nullptr); auto username = - credential_value->getChild(static_cast(KMIP_TAG_USERNAME)); + credential_value->getChild(tag::KMIP_TAG_USERNAME); assert(username != nullptr); assert(username->toString() == "alice"); auto password = - credential_value->getChild(static_cast(KMIP_TAG_PASSWORD)); + credential_value->getChild(tag::KMIP_TAG_PASSWORD); assert(password != nullptr); assert(password->toString() == "s3cr3t"); diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp index 4ae4957..fc03854 100644 --- a/kmipcore/tests/test_parsers.cpp +++ b/kmipcore/tests/test_parsers.cpp @@ -55,15 +55,15 @@ std::vector create_mock_response_bytes( void test_response_parser_create() { auto payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1234" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-1234" ) ); @@ -85,18 +85,18 @@ void test_response_parser_create() { void test_response_parser_locate() { auto payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); payload->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) ); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-1" ) ); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-2" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-2" ) ); @@ -115,40 +115,40 @@ void test_response_parser_locate() { void test_key_parser_symmetric() { // Construct a mock GetResponse with Symmetric Key auto payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "key-id" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "key-id" ) ); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SYMMETRIC_KEY + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); auto symmetric_key = - Element::createStructure(static_cast(KMIP_TAG_SYMMETRIC_KEY)); + Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); auto key_block = - Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_RAW + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_RAW ) ); key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM), KMIP_CRYPTOALG_AES + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, KMIP_CRYPTOALG_AES ) ); auto key_value = - Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + Element::createStructure(tag::KMIP_TAG_KEY_VALUE); std::vector actual_key = {0xDE, 0xAD, 0xBE, 0xEF}; key_value->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_KEY_MATERIAL), actual_key + tag::KMIP_TAG_KEY_MATERIAL, actual_key ) ); @@ -164,7 +164,7 @@ void test_key_parser_symmetric() { GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); Key key = KeyParser::parseGetKeyResponse(get_resp); - assert(key.algorithm() == KMIP_CRYPTOALG_AES); + assert(key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES); assert(key.value() == actual_key); std::cout << "KeyParser Symmetric Key test passed" << std::endl; @@ -172,40 +172,40 @@ void test_key_parser_symmetric() { void test_key_parser_secret_binary() { auto payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "secret-id" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "secret-id" ) ); payload->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_OBJECT_TYPE), KMIP_OBJTYPE_SECRET_DATA + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA ) ); auto secret_data = - Element::createStructure(static_cast(KMIP_TAG_SECRET_DATA)); + Element::createStructure(tag::KMIP_TAG_SECRET_DATA); secret_data->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_SECRET_DATA_TYPE), PASSWORD + tag::KMIP_TAG_SECRET_DATA_TYPE, static_cast(secret_data_type::KMIP_SECDATA_PASSWORD) ) ); auto key_block = - Element::createStructure(static_cast(KMIP_TAG_KEY_BLOCK)); + Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); key_block->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_KEY_FORMAT_TYPE), KMIP_KEYFORMAT_OPAQUE + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_OPAQUE ) ); auto key_value = - Element::createStructure(static_cast(KMIP_TAG_KEY_VALUE)); + Element::createStructure(tag::KMIP_TAG_KEY_VALUE); const secret_t bytes = {'p', 'a', 's', 's', 0x00, 'x'}; key_value->asStructure()->add( Element::createByteString( - static_cast(KMIP_TAG_KEY_MATERIAL), + tag::KMIP_TAG_KEY_MATERIAL, std::vector(bytes.begin(), bytes.end()) ) ); @@ -221,8 +221,8 @@ void test_key_parser_secret_binary() { GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); Secret secret = KeyParser::parseGetSecretResponse(get_resp); - assert(secret.secret_type == PASSWORD); - assert(secret.value == bytes); + assert(secret.get_secret_type() == secret_data_type::KMIP_SECDATA_PASSWORD); + assert(secret.value() == bytes); assert(secret.as_text().size() == bytes.size()); std::cout << "KeyParser Secret Binary test passed" << std::endl; @@ -230,45 +230,45 @@ void test_key_parser_secret_binary() { void test_register_secret_request_structure() { const secret_t secret = {'a', 'b', 0x00, 'c'}; - RegisterSecretRequest req("s-name", "s-group", secret, PASSWORD); + RegisterSecretRequest req("s-name", "s-group", secret, secret_data_type::KMIP_SECDATA_PASSWORD); auto payload = req.getRequestPayload(); assert(payload != nullptr); - auto object_type = payload->getChild(static_cast(KMIP_TAG_OBJECT_TYPE)); + auto object_type = payload->getChild(tag::KMIP_TAG_OBJECT_TYPE); assert(object_type != nullptr); assert(object_type->toEnum() == KMIP_OBJTYPE_SECRET_DATA); - auto secret_data = payload->getChild(static_cast(KMIP_TAG_SECRET_DATA)); + auto secret_data = payload->getChild(tag::KMIP_TAG_SECRET_DATA); assert(secret_data != nullptr); auto secret_type = - secret_data->getChild(static_cast(KMIP_TAG_SECRET_DATA_TYPE)); + secret_data->getChild(tag::KMIP_TAG_SECRET_DATA_TYPE); assert(secret_type != nullptr); - assert(secret_type->toEnum() == PASSWORD); + assert(static_cast(secret_type->toEnum()) == secret_data_type::KMIP_SECDATA_PASSWORD); - auto key_block = secret_data->getChild(static_cast(KMIP_TAG_KEY_BLOCK)); + auto key_block = secret_data->getChild(tag::KMIP_TAG_KEY_BLOCK); assert(key_block != nullptr); auto key_format = - key_block->getChild(static_cast(KMIP_TAG_KEY_FORMAT_TYPE)); + key_block->getChild(tag::KMIP_TAG_KEY_FORMAT_TYPE); assert(key_format != nullptr); assert(key_format->toEnum() == KMIP_KEYFORMAT_OPAQUE); // KMIP 1.4: Secret Data Key Block does not require algorithm/length. assert( - key_block->getChild(static_cast(KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM)) == + key_block->getChild(tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM) == nullptr ); assert( - key_block->getChild(static_cast(KMIP_TAG_CRYPTOGRAPHIC_LENGTH)) == + key_block->getChild(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH) == nullptr ); - auto key_value = key_block->getChild(static_cast(KMIP_TAG_KEY_VALUE)); + auto key_value = key_block->getChild(tag::KMIP_TAG_KEY_VALUE); assert(key_value != nullptr); auto key_material = - key_value->getChild(static_cast(KMIP_TAG_KEY_MATERIAL)); + key_value->getChild(tag::KMIP_TAG_KEY_MATERIAL); assert(key_material != nullptr); auto parsed = key_material->toBytes(); assert(parsed.size() == secret.size()); @@ -280,27 +280,27 @@ void test_register_secret_request_structure() { void test_attributes_parser() { std::vector> attributes; - auto attr1 = Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + auto attr1 = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attr1->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Name" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Name" ) ); attr1->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), "MyKey" + tag::KMIP_TAG_ATTRIBUTE_VALUE, "MyKey" ) ); attributes.push_back(attr1); - auto attr2 = Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + auto attr2 = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attr2->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Cryptographic Length" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Cryptographic Length" ) ); attr2->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_ATTRIBUTE_VALUE), 256) + Element::createInteger(tag::KMIP_TAG_ATTRIBUTE_VALUE, 256) ); attributes.push_back(attr2); @@ -320,30 +320,30 @@ void test_attributes_parser_extended() { // Test Date attribute auto attr_date = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attr_date->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Activation Date" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Activation Date" ) ); attr_date->asStructure()->add( Element::createDateTime( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), 1678886400 + tag::KMIP_TAG_ATTRIBUTE_VALUE, 1678886400 ) ); // 2023-03-15T13:20:00Z (approx) attributes.push_back(attr_date); // Test Crypto Algorithm Enum auto attr_alg = - Element::createStructure(static_cast(KMIP_TAG_ATTRIBUTE)); + Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); attr_alg->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_ATTRIBUTE_NAME), "Cryptographic Algorithm" + tag::KMIP_TAG_ATTRIBUTE_NAME, "Cryptographic Algorithm" ) ); attr_alg->asStructure()->add( Element::createEnumeration( - static_cast(KMIP_TAG_ATTRIBUTE_VALUE), KMIP_CRYPTOALG_AES + tag::KMIP_TAG_ATTRIBUTE_VALUE, KMIP_CRYPTOALG_AES ) ); attributes.push_back(attr_alg); @@ -371,18 +371,18 @@ void test_formatter_for_request_and_response() { assert(formatted_request.find("request-id-123") != std::string::npos); auto payload = - Element::createStructure(static_cast(KMIP_TAG_RESPONSE_PAYLOAD)); + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); payload->asStructure()->add( - Element::createInteger(static_cast(KMIP_TAG_LOCATED_ITEMS), 2) + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) ); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-1" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-1" ) ); payload->asStructure()->add( Element::createTextString( - static_cast(KMIP_TAG_UNIQUE_IDENTIFIER), "uuid-2" + tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-2" ) ); diff --git a/kmipcore/tests/test_serialization_buffer.cpp b/kmipcore/tests/test_serialization_buffer.cpp index 79ddbe2..b9413fa 100644 --- a/kmipcore/tests/test_serialization_buffer.cpp +++ b/kmipcore/tests/test_serialization_buffer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,7 @@ void testWriteBytes() { SerializationBuffer buf(100); uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; - buf.writeBytes(data, 5); + buf.writeBytes(std::as_bytes(std::span{data})); EXPECT(buf.size() == 5); EXPECT(std::memcmp(buf.data(), data, 5) == 0); @@ -52,7 +53,7 @@ void testWritePadded() { // Write 3 bytes (should add 5 bytes of padding to reach 8) uint8_t data[] = {0x01, 0x02, 0x03}; - buf.writePadded(data, 3); + buf.writePadded(std::as_bytes(std::span{data})); EXPECT(buf.size() == 8); EXPECT(buf.data()[0] == 0x01); @@ -72,11 +73,11 @@ void testMultiplePaddedWrites() { // 3 bytes -> 8 bytes padded uint8_t data1[] = {0x01, 0x02, 0x03}; - buf.writePadded(data1, 3); + buf.writePadded(std::as_bytes(std::span{data1})); // 2 bytes -> 8 bytes padded uint8_t data2[] = {0x04, 0x05}; - buf.writePadded(data2, 2); + buf.writePadded(std::as_bytes(std::span{data2})); EXPECT(buf.size() == 16); // 8 + 8 @@ -141,7 +142,7 @@ void testRelease() { SerializationBuffer buf(100); uint8_t data[] = {0x11, 0x22, 0x33, 0x44, 0x55}; - buf.writeBytes(data, 5); + buf.writeBytes(std::as_bytes(std::span{data})); EXPECT(buf.size() == 5); @@ -195,7 +196,7 @@ void testLargeMessage() { static_cast((i >> 8) & 0xFF), static_cast(i & 0xFF), }; - buf.writePadded(data, 4); + buf.writePadded(std::as_bytes(std::span{data})); } // Each write is 4 bytes + 4 bytes padding = 8 bytes From 077fb2cf75d178ba77c5206c7ecf954f6930dc77 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Thu, 26 Mar 2026 17:59:02 +0200 Subject: [PATCH 03/11] PS-10068 Adding new KMIP C++ library: docs update --- KMIP_MODERN_VS_LEGACY_COMPARISON.md | 232 ++++++++++++++++++++++++++-- kmipclient/README.md | 105 +++++++++++-- 2 files changed, 310 insertions(+), 27 deletions(-) diff --git a/KMIP_MODERN_VS_LEGACY_COMPARISON.md b/KMIP_MODERN_VS_LEGACY_COMPARISON.md index 773158b..8dfdf5c 100644 --- a/KMIP_MODERN_VS_LEGACY_COMPARISON.md +++ b/KMIP_MODERN_VS_LEGACY_COMPARISON.md @@ -1,6 +1,6 @@ # KMIP Modern vs Legacy Comparison -Date: 2026-03-23 +Date: 2026-03-26 ## Scope @@ -25,6 +25,9 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg - Request and response are represented as typed classes (`RequestMessage`, `ResponseBatchItem`, typed response wrappers). - Multi-batch request handling is first-class via batch item IDs. - Connection pooling is implemented as a dedicated thread-safe component (`KmipClientPool`). +- **`kmipclient` has zero compile-time or link-time dependency on `libkmip` or `kmippp`.** + Both include-path and link references to legacy code have been fully removed from + `kmipclient/CMakeLists.txt`. The only external dependency is OpenSSL (via `kmipcore`). ### Legacy (`libkmip` + `kmippp`) @@ -53,22 +56,186 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg - status details via `get_last_result()` side channel - Behavior is less explicit at call sites because success/failure conventions vary by function. -## 3) Error Handling and Memory Model +### Extensibility and protocol-completeness status + +The modern stack is structurally easier to extend than the legacy stack. + +**Why extensibility is better in modern code:** +- New KMIP features are added in layers instead of inside one monolithic function: + 1. add typed request/response objects in `kmipcore`, + 2. add parse/serialize wiring, + 3. expose a focused `KmipClient` method. +- Transport is decoupled behind `NetClient`, so protocol features do not require + reworking OpenSSL/BIO internals. +- Strong value types (`Key`, `Secret`, typed enums, attribute maps) reduce ad-hoc + stringly-typed glue and make API additions safer. + +**Protocol completeness today:** +- Current public API is comprehensive for key/secret lifecycle workflows + (create/register/get/activate/revoke/destroy/locate/attributes). +- It is **not a full KMIP implementation yet**. The current roadmap in + `kmipclient/TODO.md` explicitly lists remaining gaps, including: + - asymmetric keys and certificates support, + - server version negotiation (default is KMIP 1.4), + - broader KMIP 2.0 support in the current scope. + +So the modern stack is best described as: **easy to extend, production-ready for +implemented lifecycle flows, and intentionally incremental toward broader KMIP +coverage**. + +## 3) Memory Management + +### Legacy — manual allocation everywhere + +Every single operation in `libkmip`/`kmippp` follows this manual lifecycle: + +**Encoding buffer (per operation in `kmip_bio.c`):** +1. `calloc` an initial block of fixed size. +2. Try to encode the request into it. +3. If the buffer is too small: `free` it, `calloc` a larger block, repeat. +4. Send the encoded bytes over BIO. +5. `free` the request buffer. +6. `calloc` a fresh buffer for the response header. +7. Read response size from header, `realloc` to fit the full payload. +8. Decode the response. +9. `free` the response buffer. + +This retry-resize loop runs inside **every** `kmip_bio_*` function. +`kmip_bio.c` contains **294** calls to `calloc_func`/`realloc_func`/`free_func`/`kmip_free` +and **371** references to `buffer_total_size`/`buffer_blocks`. + +**Result ownership transfer to the caller (`kmippp.cpp`):** + +Every `kmip_bio_*` that returns a string (key ID, key bytes, secret, name) heap-allocates +the result buffer and passes ownership to the caller via an output `char **` pointer. +`kmippp.cpp` must `free()` each one: + +```cpp +// pattern repeated in op_create, op_register, op_get, op_get_name_attr, +// op_register_secret, op_get_secret … +char *idp = nullptr; +int result = kmip_bio_create_symmetric_key(bio_, &ta, &idp, &id_max_len); +std::string ret; +if (idp != nullptr) { + ret = std::string(idp, id_max_len); + free(idp); // ← caller must remember to free every time +} +``` + +`kmippp.cpp` contains **7** such `free()` call sites, one per result-returning +operation. A missed `free()` on any early-return or exception path is a +memory leak. + +**Error handling is interleaved with deallocation:** + +Because each error path must also free whatever buffers were allocated so far, +`kmip_bio.c` has dozens of `kmip_free_encoding_and_ctx(...)` guard calls +scattered through every function — another source of correctness risk. + +--- + +### Modern — zero raw heap calls + +The modern stack eliminates manual memory management completely. + +**`SerializationBuffer` (in `kmipcore`):** + +A single pre-allocated 8 KB `std::vector` is used for the entire +encode/send/receive/decode cycle of one operation. It expands automatically +(doubling up to a 100 MB hard cap) only when a message genuinely exceeds +capacity — which is rare in practice. RAII ensures it is freed when the +scope exits, regardless of whether an exception was thrown: + +```cpp +// From SerializationBuffer docs: +// - Default initial capacity: 8 KB (covers the vast majority of KMIP messages) +// - Auto-expansion: doubles on overflow, hard cap at 100 MB +// - Cleanup: RAII destructor — no free() needed anywhere +// - Ownership: non-copyable, movable +``` + +**Return types are plain value types — no ownership transfer:** + +`KmipClient` operations return `std::string`, `Key`, `Secret`, or +`std::vector` by value. The caller never receives a raw pointer +and never needs to call `free()`: + +```cpp +// Modern — no memory management at the call site +Key key = client.op_get_key(id); +Secret secret = client.op_get_secret(id); +id_t new_id = client.op_create_aes_key("name", "group"); +``` + +**RAII for all resources:** + +| Resource | Owner | Mechanism | +|---|---|---| +| Serialization buffer | `SerializationBuffer` | `std::vector` destructor | +| I/O helper | `KmipClient` | `std::unique_ptr` | +| Pool connections | `KmipClientPool` | `std::unique_ptr` | +| Borrowed connection | caller scope | RAII guard (`ConnectionGuard`) | +| SSL context / BIO | `NetClientOpenSSL` | destructor calls `SSL_CTX_free` / `BIO_free_all` | + +**Zero raw allocations in the modern codebase:** + +A search across all `kmipclient/src/` and `kmipcore/src/` source files finds +**zero** calls to `malloc`, `calloc`, `realloc`, `free`, `new`, or `delete` +(outside of OpenSSL C API calls in `NetClientOpenSSL.cpp`, which are +encapsulated and paired within the same constructor/destructor). + +--- + +### Error Handling + +**Modern:** exceptions (`kmipcore::KmipException`, `KmipIOException`) propagate +failures with full context. Because all resources are RAII-managed, there are +no error-path `free()` calls needed — the stack unwinds cleanly. + +**Legacy:** return-code based. Every error branch in `kmip_bio.c` must +manually free all buffers allocated so far before returning. `kmippp` +additionally uses a global mutable `last_result` side channel for +human-readable status, which `kmippp.h` explicitly documents as +**not thread-safe**. + +## 4) Attribute Access ### Modern -- RAII ownership via `std::unique_ptr`/`std::shared_ptr`. -- Exceptions propagate failures with operation/result details. -- No global mutable status store required. +`kmipclient` provides multi-level attribute access: + +| Method | Where | Description | +|---|---|---| +| `op_get_attribute_list(id)` | `KmipClient` | Returns all attribute names for an object | +| `op_get_attributes(id, names)` | `KmipClient` | Fetches specific named attributes from the server | +| `Key::attribute_value(name)` | `kmipcore::Key` | Reads an attribute from a retrieved key object | +| `Secret::attribute_value(name)` | `kmipcore::Secret` | Reads an attribute from a retrieved secret object | + +`attribute_value` (on both `Key` and `Secret`) is `noexcept` and returns a reference +to a static empty string when the server did not supply the requested attribute — it +**never throws**. This makes it safe to call unconditionally even when attribute +availability depends on server version or configuration: + +```cpp +auto key = client.op_get_key(id, /*all_attributes=*/true); +auto state = key.attribute_value(KMIP_ATTR_NAME_STATE); // "" if server omitted it +auto name = key.attribute_value(KMIP_ATTR_NAME_NAME); // "" if server omitted it +``` ### Legacy -- Return-code based C style with explicit allocation/free patterns. -- Many manual memory management paths per operation. -- Global mutable `last_result` in `libkmip` is used for human-readable status retrieval. -- `kmippp` explicitly documents `get_last_result()` as not thread-safe due to global state. +`kmippp::context` exposes only a single attribute getter: + +```cpp +name_t op_get_name_attr(id_t id); // returns "" on failure +``` -## 4) Concurrency and Pooling +The "Name" attribute is the only one reachable through the `kmippp` API. All other +attributes require dropping down to raw `libkmip` C calls. Error and +"attribute not present" are both signaled by an empty return string with no +distinction between them. + +## 5) Concurrency and Pooling ### Modern @@ -88,7 +255,7 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg - No native connection pool abstraction in `libkmip`/`kmippp`. - `kmippp` global last-result path creates thread-safety concerns for shared usage. -## 5) Protocol, Serialization, and Performance Direction +## 6) Protocol, Serialization, and Performance Direction ### Modern @@ -101,32 +268,66 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg - Protocol version can vary by operation (`KMIP_1_0` or `KMIP_1_4` depending on function). - Serialization/decoding is repeated in operation functions with dynamic buffer resize loops. -## 6) Locate/Pagination Behavior Differences +## 7) Locate/Pagination Behavior Differences - Modern `kmipclient` has large default locate pagination constants (`MAX_ITEMS_IN_BATCH=1024`, up to `MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH`). - Legacy `kmippp` loops with a hardcoded locate page size of 16 and server-specific fallback behavior when `located_items == 0`. - This can change practical behavior and performance across servers. -## 7) Testing Comparison +## 8) AddressSanitizer Support + +### Modern + +`kmipclient` has first-class ASAN support via the `WITH_ASAN` CMake option. +The option uses `PUBLIC` scope on `target_compile_options` / `target_link_options` +so that both the `kmipclient` static library itself **and** all consumers (test +executables) are fully instrumented: + +```bash +cmake -DBUILD_TESTS=ON -DWITH_ASAN=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + "-DCMAKE_CXX_FLAGS=-fsanitize=address -fno-omit-frame-pointer" \ + "-DCMAKE_C_FLAGS=-fsanitize=address -fno-omit-frame-pointer" \ + "-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address" \ + .. +``` + +Passing the flags via `CMAKE_CXX_FLAGS` in addition to `WITH_ASAN` extends +instrumentation to `kmipcore` and any other targets in the build tree. + +All 32 integration tests pass cleanly under ASAN with `detect_leaks=1`. + +### Legacy + +No ASAN build option is provided in `libkmip` or `kmippp` CMake files. + +## 9) Testing Comparison ### Modern - `kmipclient` integrates GoogleTest for integration tests. - `kmipcore` has dedicated core/parser/serialization test executables. - Pool integration tests cover realistic concurrent scenarios. +- Full ASAN integration test run (32 tests, 0 findings) validated. ### Legacy - `libkmip` contains a large `tests.c`, but current CMake in `libkmip/src/CMakeLists.txt` builds library + demos; tests are not wired as a regular test target there. - `kmippp` similarly provides demos but not a formal integrated test target in its CMake. -## 8) Migration Notes (Practical) +## 10) Migration Notes (Practical) - Moving from `kmippp` to `kmipclient` generally improves: - API consistency - failure visibility (exceptions instead of mixed sentinel returns) - thread-safe concurrent usage via pool + - full attribute access (vs name-only in `kmippp`) - Callers should adapt to exception handling and typed return values. +- **Attribute access pattern changes:** + - Legacy: `ctx.op_get_name_attr(id)` returns `""` for both missing and error. + - Modern: `client.op_get_key(id, true)` then `key.attribute_value(name)` returns `""` + only when absent (server did not return it); actual operation errors throw an + exception instead of silently returning empty. - Behavior changes to validate during migration: - locate pagination/result ordering expectations - protocol-version expectations for specific servers @@ -134,5 +335,4 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg ## Conclusion -The modern stack is a significant architectural and operational improvement over the legacy stack, especially for concurrency, maintainability, and API clarity. The main migration risks are behavioral edge cases (pagination/version differences) and adapting legacy error-handling assumptions to exception-based control flow. - +The modern stack is a significant architectural and operational improvement over the legacy stack, especially for concurrency, maintainability, and API clarity. `kmipclient` is now fully decoupled from `libkmip` and `kmippp` at both the source and build levels. The main migration risks are behavioral edge cases (pagination/version differences) and adapting legacy error-handling assumptions to exception-based control flow. diff --git a/kmipclient/README.md b/kmipclient/README.md index f694c0b..5ce5250 100644 --- a/kmipclient/README.md +++ b/kmipclient/README.md @@ -1,9 +1,8 @@ # The `kmipclient` library `kmipclient` is a C++20 library that provides a clean, high-level interface to -KMIP servers. It wraps the low-level `kmipcore` (and ultimately `libkmip`) -into safe C++ types, hiding raw memory management, buffer handling, and -TTLV encoding/decoding details from library users. +KMIP servers. It wraps the `kmipcore` layer into safe C++ types, hiding buffer +handling and TTLV encoding/decoding details from library users. Everything lives in the `kmipclient` namespace. @@ -15,7 +14,7 @@ Everything lives in the `kmipclient` namespace. 2. Hide low-level details (no raw `KMIP` context, no manual buffer management). 3. Minimize manual memory management; prefer stack-allocated objects. 4. Make the library easy to extend. -5. Use only the low-level `kmipcore` layer; no mid-level `kmip_bio.c`. +5. Use only the `kmipcore` layer; no direct dependency on `libkmip` or `kmippp`. 6. Replaceable network communication layer (dependency injection). 7. Testability. @@ -100,6 +99,54 @@ auto key_id = kmip.client().op_create_aes_key("mykey", "mygroup"); | `Key::generate_aes(size_bits)` | Generate a random AES key (128/192/256 bits) | | `Key::from_PEM(pem)` | Parse a PEM-encoded certificate/public-key/private-key | +Objects returned by `op_get_key` expose the following read accessors on +`kmipcore::Key`: + +| Method | Return type | Description | +|---|---|---| +| `value()` | `const key_t &` | Raw key bytes | +| `type()` | `KeyType` | Key family (`SYMMETRIC_KEY`, `PUBLIC_KEY`, …) | +| `algorithm()` | `cryptographic_algorithm` | KMIP cryptographic algorithm enum | +| `usage_mask()` | `cryptographic_usage_mask` | KMIP usage mask flags | +| `size()` | `size_t` | Key length in bytes | +| `attributes()` | `const attributes_t &` | Full attribute map | +| `attribute_value(name)` | `const std::string &` | Single attribute by name (see below) | + +#### `attribute_value` — no-throw behaviour + +`attribute_value` is `noexcept`. When the server did not return the requested +attribute it returns a reference to a static empty string rather than throwing: + +```cpp +auto key = client.op_get_key(id, /*all_attributes=*/true); +auto name = key.attribute_value(KMIP_ATTR_NAME_NAME); // "" if not present +auto state = key.attribute_value(KMIP_ATTR_NAME_STATE); // "" if not present +if (name.empty()) { /* attribute was not returned by the server */ } +``` + +### `Secret` + +Objects returned by `op_get_secret` are instances of `kmipcore::Secret`: + +| Method | Return type | Description | +|---|---|---| +| `value()` | `const secret_t &` | Raw payload bytes | +| `as_text()` | `std::string` | Payload as a UTF-8 string copy | +| `get_state()` | `state` | KMIP lifecycle state | +| `get_secret_type()` | `secret_data_type` | KMIP secret data type (e.g. `KMIP_SECDATA_PASSWORD`) | +| `attributes()` | `const attributes_t &` | Full attribute map | +| `attribute_value(name)` | `const std::string &` | Single attribute by name (see below) | + +`attribute_value` follows the same no-throw rule as on `Key`: returns `""` +when the attribute was not returned by the server. + +`Secret` also provides a static factory for building secrets client-side: + +```cpp +auto s = Secret::from_text("s3cr3t!", secret_data_type::KMIP_SECDATA_PASSWORD); +auto id = client.op_register_secret("name", "group", s.value(), s.get_secret_type()); +``` + ### `KmipClientPool` Thread-safe pool of `KmipClient` connections. Connections are created lazily @@ -188,15 +235,29 @@ NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 200) KmipClient client(net_client); try { - auto key = client.op_get_key(id); - // key.value() → std::vector with the raw key bytes - // key.attribute_value(KMIP_ATTR_NAME_STATE) → attribute string - // key.attribute_value(KMIP_ATTR_NAME_NAME) → key name + auto key = client.op_get_key(id, /*all_attributes=*/true); + // key.value() → raw key bytes (std::vector) + // key.size() → key length in bytes + // key.algorithm() → cryptographic_algorithm enum + // key.usage_mask() → cryptographic_usage_mask flags + // key.attribute_value(name) → attribute string, "" if absent (noexcept) + auto state = key.attribute_value(KMIP_ATTR_NAME_STATE); + auto kname = key.attribute_value(KMIP_ATTR_NAME_NAME); } catch (const std::exception &e) { std::cerr << e.what() << '\n'; } ``` +### Get a secret / password + +```cpp +auto secret = client.op_get_secret(id, /*all_attributes=*/true); +// secret.as_text() → payload as std::string +// secret.get_state() → KMIP lifecycle state +// secret.get_secret_type() → KMIP secret data type +// secret.attribute_value(name) → attribute string, "" if absent (noexcept) +``` + ### Create an AES-256 key on the server ```cpp @@ -293,14 +354,29 @@ for (auto &t : threads) t.join(); ## Build ```bash -mkdir build -cd build +mkdir build && cd build cmake .. cmake --build . ``` The library requires **C++20** and **OpenSSL**. +### AddressSanitizer build + +Pass `-DWITH_ASAN=ON` to instrument the library and all its consumers. To +also instrument the full dependency chain (`kmipcore`, standard C++ runtime +included) supply the ASAN flags globally: + +```bash +cmake -DBUILD_TESTS=ON -DWITH_ASAN=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + "-DCMAKE_CXX_FLAGS=-fsanitize=address -fno-omit-frame-pointer" \ + "-DCMAKE_C_FLAGS=-fsanitize=address -fno-omit-frame-pointer" \ + "-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address" \ + .. +cmake --build . --target kmipclient_test +``` + --- ## Integration testing @@ -330,7 +406,14 @@ cmake --build . ```bash ctest --output-on-failure # or directly: -./kmipclient_test +./kmipclient/kmipclient_test +``` + +4. Run with AddressSanitizer (requires the ASAN build above): + +```bash +ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:print_stats=1" \ + ./kmipclient/kmipclient_test ``` --- From 9ba5afc5e1b9789430a3e0a739e04467cfe86b83 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Fri, 27 Mar 2026 14:01:36 +0200 Subject: [PATCH 04/11] PS-10068 Fix PR comment bfd0188 https://perconadev.atlassian.net/browse/PS-10949 1. mission extended time serialization 2. network timeouts 3. IOUtils advanced send 4. fix of server error propagation 5. big cleanup in the kmipclilent public interface --- kmipclient/CMakeLists.txt | 1 + kmipclient/README.md | 15 +- kmipclient/examples/example_get.cpp | 9 +- .../examples/example_get_attributes.cpp | 4 +- kmipclient/examples/example_get_logger.cpp | 2 +- kmipclient/examples/example_get_name.cpp | 8 +- kmipclient/examples/example_get_secret.cpp | 8 +- .../examples/example_register_secret.cpp | 5 +- kmipclient/include/kmipclient/Key.hpp | 2 + kmipclient/include/kmipclient/KmipClient.hpp | 130 +++--------- kmipclient/include/kmipclient/NetClient.hpp | 5 +- .../include/kmipclient/NetClientOpenSSL.hpp | 4 +- kmipclient/include/kmipclient/types.hpp | 27 +-- kmipclient/src/IOUtils.cpp | 26 ++- kmipclient/src/Key.cpp | 13 +- kmipclient/src/KmipClient.cpp | 186 ++++++++++-------- kmipclient/src/NetClientOpenSSL.cpp | 136 ++++++++++++- kmipclient/src/StringUtils.cpp | 4 +- kmipclient/src/StringUtils.hpp | 5 +- kmipclient/tests/IOUtilsTest.cpp | 119 +++++++++++ .../tests/KmipClientIntegrationTest.cpp | 87 +++++--- .../include/kmipcore/attributes_parser.hpp | 5 +- kmipcore/include/kmipcore/key.hpp | 18 +- kmipcore/include/kmipcore/kmip_basics.hpp | 4 + kmipcore/include/kmipcore/kmip_enums.hpp | 6 + kmipcore/include/kmipcore/kmip_protocol.hpp | 8 +- kmipcore/include/kmipcore/kmip_requests.hpp | 3 +- kmipcore/include/kmipcore/response_parser.hpp | 4 +- kmipcore/include/kmipcore/secret.hpp | 20 +- kmipcore/include/kmipcore/types.hpp | 35 ---- kmipcore/src/attributes_parser.cpp | 7 +- kmipcore/src/key_parser.cpp | 11 +- kmipcore/src/kmip_formatter.cpp | 5 +- kmipcore/src/kmip_protocol.cpp | 65 +++++- kmipcore/src/kmip_requests.cpp | 4 +- kmipcore/src/response_parser.cpp | 18 +- kmipcore/tests/test_core.cpp | 158 +++++++++++++++ kmipcore/tests/test_parsers.cpp | 61 +++++- 38 files changed, 851 insertions(+), 377 deletions(-) create mode 100644 kmipclient/tests/IOUtilsTest.cpp delete mode 100644 kmipcore/include/kmipcore/types.hpp diff --git a/kmipclient/CMakeLists.txt b/kmipclient/CMakeLists.txt index 4dc32be..ee55dd0 100644 --- a/kmipclient/CMakeLists.txt +++ b/kmipclient/CMakeLists.txt @@ -105,6 +105,7 @@ if(BUILD_TESTS) add_executable( kmipclient_test + tests/IOUtilsTest.cpp tests/KmipClientIntegrationTest.cpp tests/KmipClientPoolIntegrationTest.cpp ) diff --git a/kmipclient/README.md b/kmipclient/README.md index 5ce5250..a59812e 100644 --- a/kmipclient/README.md +++ b/kmipclient/README.md @@ -104,12 +104,12 @@ Objects returned by `op_get_key` expose the following read accessors on | Method | Return type | Description | |---|---|---| -| `value()` | `const key_t &` | Raw key bytes | +| `value()` | `const std::vector &` | Raw key bytes | | `type()` | `KeyType` | Key family (`SYMMETRIC_KEY`, `PUBLIC_KEY`, …) | | `algorithm()` | `cryptographic_algorithm` | KMIP cryptographic algorithm enum | | `usage_mask()` | `cryptographic_usage_mask` | KMIP usage mask flags | | `size()` | `size_t` | Key length in bytes | -| `attributes()` | `const attributes_t &` | Full attribute map | +| `attributes()` | `const std::unordered_map &` | Full attribute map | | `attribute_value(name)` | `const std::string &` | Single attribute by name (see below) | #### `attribute_value` — no-throw behaviour @@ -130,11 +130,11 @@ Objects returned by `op_get_secret` are instances of `kmipcore::Secret`: | Method | Return type | Description | |---|---|---| -| `value()` | `const secret_t &` | Raw payload bytes | +| `value()` | `const std::vector &` | Raw payload bytes | | `as_text()` | `std::string` | Payload as a UTF-8 string copy | | `get_state()` | `state` | KMIP lifecycle state | | `get_secret_type()` | `secret_data_type` | KMIP secret data type (e.g. `KMIP_SECDATA_PASSWORD`) | -| `attributes()` | `const attributes_t &` | Full attribute map | +| `attributes()` | `const std::unordered_map &` | Full attribute map | | `attribute_value(name)` | `const std::string &` | Single attribute by name (see below) | `attribute_value` follows the same no-throw rule as on `Key`: returns `""` @@ -144,7 +144,7 @@ when the attribute was not returned by the server. ```cpp auto s = Secret::from_text("s3cr3t!", secret_data_type::KMIP_SECDATA_PASSWORD); -auto id = client.op_register_secret("name", "group", s.value(), s.get_secret_type()); +auto id = client.op_register_secret("name", "group", s); ``` ### `KmipClientPool` @@ -208,7 +208,7 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException |---|---| | `op_create_aes_key(name, group)` | Server-side AES-256 key generation (KMIP CREATE) | | `op_register_key(name, group, key)` | Register an existing key (KMIP REGISTER) | -| `op_register_secret(name, group, secret, type)` | Register a secret / password | +| `op_register_secret(name, group, secret)` | Register a secret / password | | `op_get_key(id [, all_attributes])` | Retrieve a symmetric key with optional attributes | | `op_get_secret(id [, all_attributes])` | Retrieve a secret / password | | `op_activate(id)` | Activate an entity (pre-active → active) | @@ -282,7 +282,8 @@ auto id = client.op_register_key("mykey", "mygroup", k); ```cpp Kmip kmip(host, port, client_cert, client_key, server_ca, 200); -auto id = kmip.client().op_register_secret("mysecret", "mygroup", "s3cr3t!", PASSWORD); +auto s = Secret::from_text("s3cr3t!", secret_data_type::KMIP_SECDATA_PASSWORD); +auto id = kmip.client().op_register_secret("mysecret", "mygroup", s); ``` ### Lifecycle: activate → revoke → destroy diff --git a/kmipclient/examples/example_get.cpp b/kmipclient/examples/example_get.cpp index f30a3f1..c50dda2 100644 --- a/kmipclient/examples/example_get.cpp +++ b/kmipclient/examples/example_get.cpp @@ -25,7 +25,7 @@ using namespace kmipclient; -void print_hex(const kmipclient::key_t &key) { +void print_hex(const std::vector &key) { for (auto const &c : key) { std::cout << std::hex << static_cast(c); } @@ -50,8 +50,11 @@ int main(int argc, char **argv) { auto key = client.op_get_key(id); std::cout << "Key: 0x"; print_hex(key.value()); - std::cout << "State: " << key.attribute_value(KMIP_ATTR_NAME_STATE) << std::endl; - std::cout << "Name: " << key.attribute_value(KMIP_ATTR_NAME_NAME); + std::cout << "Attributes:" << std::endl; + const auto &attrs = key.attributes(); + for (const auto &[attr_name, attr_value] : attrs) { + std::cout << " " << attr_name << ": " << attr_value << std::endl; + } } catch (const std::exception &e) { std::cerr << "Can not get key with id:" << argv[6] << " Cause: " << e.what() << std::endl; diff --git a/kmipclient/examples/example_get_attributes.cpp b/kmipclient/examples/example_get_attributes.cpp index 11f68bf..a604cfd 100644 --- a/kmipclient/examples/example_get_attributes.cpp +++ b/kmipclient/examples/example_get_attributes.cpp @@ -25,14 +25,14 @@ using namespace kmipclient; -void print_hex(const kmipclient::key_t &key) { +void print_hex(const std::vector &key) { for (auto const &c : key) { std::cout << std::hex << static_cast(c); } std::cout << std::endl; } -void print_attributes(const attributes_t &attrs) { +void print_attributes(const std::unordered_map &attrs) { for (auto const &attr : attrs) { std::cout << attr.first << ": " << attr.second << std::endl; } diff --git a/kmipclient/examples/example_get_logger.cpp b/kmipclient/examples/example_get_logger.cpp index 4d6b08c..e8545c8 100644 --- a/kmipclient/examples/example_get_logger.cpp +++ b/kmipclient/examples/example_get_logger.cpp @@ -41,7 +41,7 @@ namespace { } }; - void print_hex(const kmipclient::key_t &key) { + void print_hex(const std::vector &key) { for (auto const &c : key) { std::cout << std::hex << static_cast(c); } diff --git a/kmipclient/examples/example_get_name.cpp b/kmipclient/examples/example_get_name.cpp index 827e9c4..0f77f5e 100644 --- a/kmipclient/examples/example_get_name.cpp +++ b/kmipclient/examples/example_get_name.cpp @@ -23,7 +23,7 @@ using namespace kmipclient; -void print_attributes(const attributes_t &attrs) { +void print_attributes(const std::unordered_map &attrs) { for (auto const &attr : attrs) { std::cout << attr.first << ": " << attr.second << std::endl; } @@ -43,10 +43,8 @@ int main(int argc, char **argv) { NetClientOpenSSL net_client(argv[1], argv[2], argv[3], argv[4], argv[5], 200); KmipClient client(net_client); try { - // get name - auto opt_attr = client.op_get_attributes(argv[6], {KMIP_ATTR_NAME_NAME}); - // get group - opt_attr.merge(client.op_get_attributes(argv[6], {KMIP_ATTR_NAME_GROUP})); + // get name and group + auto opt_attr = client.op_get_attributes(argv[6], {KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_GROUP}); std::cout << "ID: " << argv[6] << " Attributes:" << std::endl; print_attributes(opt_attr); } catch (const std::exception &e) { diff --git a/kmipclient/examples/example_get_secret.cpp b/kmipclient/examples/example_get_secret.cpp index a1d033a..80f7e7e 100644 --- a/kmipclient/examples/example_get_secret.cpp +++ b/kmipclient/examples/example_get_secret.cpp @@ -49,11 +49,9 @@ int main(int argc, char **argv) { std::cout << std::dec << std::endl; const auto &attrs = secret.attributes(); - if (auto it = attrs.find(KMIP_ATTR_NAME_NAME); it != attrs.end()) { - std::cout << "Name: " << it->second << std::endl; - } - if (auto it = attrs.find(KMIP_ATTR_NAME_STATE); it != attrs.end()) { - std::cout << "State: " << it->second << std::endl; + std::cout << "Attributes:" << std::endl; + for (const auto &[key, value] : attrs) { + std::cout << " " << key << ": " << value << std::endl; } } catch (std::exception &e) { std::cerr << "Can not get secret with id:" << argv[6] diff --git a/kmipclient/examples/example_register_secret.cpp b/kmipclient/examples/example_register_secret.cpp index 28b5ef4..4805fd5 100644 --- a/kmipclient/examples/example_register_secret.cpp +++ b/kmipclient/examples/example_register_secret.cpp @@ -37,8 +37,11 @@ int main(int argc, char **argv) { Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); try { + auto secret = Secret::from_text( + argv[7], secret_data_type::KMIP_SECDATA_PASSWORD + ); auto id = kmip.client().op_register_secret( - argv[6], "TestGroup", argv[7], secret_data_type::KMIP_SECDATA_PASSWORD + argv[6], "TestGroup", secret ); std::cout << "Secret ID: " << id << std::endl; } catch (std::exception &e) { diff --git a/kmipclient/include/kmipclient/Key.hpp b/kmipclient/include/kmipclient/Key.hpp index 40252d3..4bf4f97 100644 --- a/kmipclient/include/kmipclient/Key.hpp +++ b/kmipclient/include/kmipclient/Key.hpp @@ -21,6 +21,8 @@ #include "kmipclient/types.hpp" #include "kmipcore/key.hpp" +#include + namespace kmipclient { /** diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index f02dd04..c6193fe 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -22,7 +22,11 @@ #include "kmipclient/types.hpp" #include "kmipcore/kmip_logger.hpp" +#include #include +#include +#include +#include namespace kmipclient { @@ -66,69 +70,24 @@ namespace kmipclient { * @return Unique identifier assigned by the KMIP server. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] id_t op_register_key( - const name_t &name, const name_t &group, const Key &k + [[nodiscard]] std::string op_register_key( + const std::string &name, const std::string &group, const Key &k ) const; /** - * @brief Executes KMIP Register for secret data provided as text bytes. + * @brief Executes KMIP Register for Secret Data. * @param name Value of the KMIP "Name" attribute. * @param group Value of the KMIP "Object Group" attribute. - * @param secret Secret payload to store. - * @param secret_type KMIP Secret Data Type for the payload. + * @param secret Secret payload and type descriptor. * @return Unique identifier assigned by the KMIP server. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] id_t op_register_secret( - const name_t &name, - const name_t &group, - std::string_view secret, - secret_data_type secret_type + [[nodiscard]] std::string op_register_secret( + const std::string &name, + const std::string &group, + const Secret &secret ) const; - [[nodiscard]] id_t op_register_secret( - const name_t &name, - const name_t &group, - std::string_view secret, - std::uint32_t secret_type - ) const { - return op_register_secret( - name, - group, - secret, - static_cast(secret_type) - ); - } - - /** - * @brief Executes KMIP Register for binary secret data. - * @param name Value of the KMIP "Name" attribute. - * @param group Value of the KMIP "Object Group" attribute. - * @param secret Binary secret payload to store. - * @param secret_type KMIP Secret Data Type for the payload. - * @return Unique identifier assigned by the KMIP server. - * @throws kmipcore::KmipException on protocol or server-side failure. - */ - [[nodiscard]] id_t op_register_secret( - const name_t &name, - const name_t &group, - const secret_t &secret, - secret_data_type secret_type - ) const; - - [[nodiscard]] id_t op_register_secret( - const name_t &name, - const name_t &group, - const secret_t &secret, - std::uint32_t secret_type - ) const { - return op_register_secret( - name, - group, - secret, - static_cast(secret_type) - ); - } /** * @brief Executes KMIP Create to generate a server-side AES-256 key. @@ -137,8 +96,8 @@ namespace kmipclient { * @return Unique identifier of the created key. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] id_t - op_create_aes_key(const name_t &name, const name_t &group) const; + [[nodiscard]] std::string + op_create_aes_key(const std::string &name, const std::string &group) const; /** * @brief Executes KMIP Get and decodes a key object. @@ -147,7 +106,7 @@ namespace kmipclient { * @return Decoded key object. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] Key op_get_key(const id_t &id, bool all_attributes = false) const; + [[nodiscard]] Key op_get_key(const std::string &id, bool all_attributes = false) const; /** * @brief Executes KMIP Get and decodes a secret object. @@ -157,7 +116,7 @@ namespace kmipclient { * @throws kmipcore::KmipException on protocol or server-side failure. */ [[nodiscard]] Secret - op_get_secret(const id_t &id, bool all_attributes = false) const; + op_get_secret(const std::string &id, bool all_attributes = false) const; /** * @brief Executes KMIP Activate for a managed object. @@ -165,7 +124,7 @@ namespace kmipclient { * @return Identifier returned by the server (normally equals @p id). * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] id_t op_activate(const id_t &id) const; + [[nodiscard]] std::string op_activate(const std::string &id) const; /** * @brief Executes KMIP Get Attribute List. @@ -173,7 +132,7 @@ namespace kmipclient { * @return List of attribute names available for the object. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] names_t op_get_attribute_list(const id_t &id) const; + [[nodiscard]] std::vector op_get_attribute_list(const std::string &id) const; /** * @brief Executes KMIP Get Attributes for selected attribute names. @@ -182,8 +141,8 @@ namespace kmipclient { * @return Map of requested attributes present in the server response. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] attributes_t op_get_attributes( - const id_t &id, const std::vector &attr_names + [[nodiscard]] std::unordered_map op_get_attributes( + const std::string &id, const std::vector &attr_names ) const; /** @@ -193,13 +152,9 @@ namespace kmipclient { * @return Matching object identifiers; may contain multiple IDs. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] ids_t - op_locate_by_name(const name_t &name, object_type o_type) const; + [[nodiscard]] std::vector + op_locate_by_name(const std::string &name, object_type o_type) const; - [[nodiscard]] ids_t - op_locate_by_name(const name_t &name, std::uint32_t o_type) const { - return op_locate_by_name(name, static_cast(o_type)); - } /** * @brief Executes KMIP Locate using the object group filter. @@ -209,19 +164,12 @@ namespace kmipclient { * @return Matching object identifiers, up to @p max_ids entries. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] ids_t op_locate_by_group( - const name_t &group, + [[nodiscard]] std::vector op_locate_by_group( + const std::string &group, object_type o_type, std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH ) const; - [[nodiscard]] ids_t op_locate_by_group( - const name_t &group, - std::uint32_t o_type, - std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH - ) const { - return op_locate_by_group(group, static_cast(o_type), max_ids); - } /** * @brief Executes KMIP Revoke for a managed object. @@ -233,26 +181,12 @@ namespace kmipclient { * @return Identifier returned by the server (normally equals @p id). * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] id_t op_revoke( - const id_t &id, + [[nodiscard]] std::string op_revoke( + const std::string &id, revocation_reason_type reason, - const name_t &message, + const std::string &message, time_t occurrence_time ) const; - - [[nodiscard]] id_t op_revoke( - const id_t &id, - std::uint32_t reason, - const name_t &message, - time_t occurrence_time - ) const { - return op_revoke( - id, - static_cast(reason), - message, - occurrence_time - ); - } /** * @brief Executes KMIP Destroy for a managed object. * @param id Unique identifier of the object to destroy. @@ -260,7 +194,7 @@ namespace kmipclient { * @throws kmipcore::KmipException on protocol or server-side failure. * @note Most KMIP servers require the object to be revoked first. */ - [[nodiscard]] id_t op_destroy(const id_t &id) const; + [[nodiscard]] std::string op_destroy(const std::string &id) const; /** * @brief Executes KMIP Locate without name/group filters. @@ -269,17 +203,11 @@ namespace kmipclient { * @return Identifiers of matching objects, up to @p max_ids entries. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] ids_t op_all( + [[nodiscard]] std::vector op_all( object_type o_type, std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH ) const; - [[nodiscard]] ids_t op_all( - std::uint32_t o_type, - std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH - ) const { - return op_all(static_cast(o_type), max_ids); - } private: NetClient &net_client; diff --git a/kmipclient/include/kmipclient/NetClient.hpp b/kmipclient/include/kmipclient/NetClient.hpp index 7945d26..0e2728e 100644 --- a/kmipclient/include/kmipclient/NetClient.hpp +++ b/kmipclient/include/kmipclient/NetClient.hpp @@ -38,7 +38,9 @@ namespace kmipclient { * @param clientCertificateFn Path to client X.509 certificate in PEM. * @param clientKeyFn Path to matching client private key in PEM. * @param serverCaCertFn Path to trusted server CA/certificate in PEM. - * @param timeout_ms Connect/read/write timeout in milliseconds. + * @param timeout_ms Timeout in milliseconds applied to TCP connect, TLS + * handshake, and each read/write operation. Non-positive values mean + * no explicit timeout is enforced by this layer. */ NetClient( const std::string &host, @@ -64,6 +66,7 @@ namespace kmipclient { virtual NetClient &operator=(NetClient &&) = delete; /** * @brief Establishes network/TLS connection to the KMIP server. + * Must honor @ref m_timeout_ms for connect + handshake phases. * @return true on successful connection establishment, false otherwise. */ diff --git a/kmipclient/include/kmipclient/NetClientOpenSSL.hpp b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp index 83a144b..6631aff 100644 --- a/kmipclient/include/kmipclient/NetClientOpenSSL.hpp +++ b/kmipclient/include/kmipclient/NetClientOpenSSL.hpp @@ -42,7 +42,8 @@ namespace kmipclient { * @param clientCertificateFn Path to client certificate in PEM. * @param clientKeyFn Path to client private key in PEM. * @param serverCaCertFn Path to trusted server CA/certificate in PEM. - * @param timeout_ms Connect/read/write timeout in milliseconds. + * @param timeout_ms Timeout in milliseconds applied to TCP connect, TLS + * handshake, and each read/write operation. */ NetClientOpenSSL( const std::string &host, @@ -62,6 +63,7 @@ namespace kmipclient { /** * @brief Establishes a TLS connection to the configured KMIP endpoint. + * Honors timeout_ms for both TCP connect and TLS handshake. * @return true on success, false on failure. */ bool connect() override; diff --git a/kmipclient/include/kmipclient/types.hpp b/kmipclient/include/kmipclient/types.hpp index 8caa183..c1adb91 100644 --- a/kmipclient/include/kmipclient/types.hpp +++ b/kmipclient/include/kmipclient/types.hpp @@ -3,20 +3,8 @@ #include "kmipcore/key.hpp" #include "kmipcore/kmip_attribute_names.hpp" #include "kmipcore/secret.hpp" -#include "kmipcore/types.hpp" namespace kmipclient { - - /** @brief Alias for KMIP attribute map type. */ - using kmipcore::attributes_t; - /** @brief Alias for binary data buffer type. */ - using kmipcore::bin_data_t; - /** @brief Alias for KMIP unique identifier type. */ - using kmipcore::id_t; - /** @brief Alias for a list of KMIP unique identifiers. */ - using kmipcore::ids_t; - /** @brief Alias for raw key bytes container type. */ - using kmipcore::key_t; /** @brief Alias for supported key-kind discriminator enum. */ using kmipcore::KeyType; /** @brief Alias for KMIP object type enum. */ @@ -31,14 +19,8 @@ namespace kmipclient { using kmipcore::cryptographic_usage_mask; /** @brief Alias for KMIP lifecycle state enum. */ using kmipcore::state; - /** @brief Alias for KMIP textual name type. */ - using kmipcore::name_t; - /** @brief Alias for a list of textual names. */ - using kmipcore::names_t; /** @brief Alias for KMIP secret object representation. */ using kmipcore::Secret; - /** @brief Alias for secret binary payload container. */ - using kmipcore::secret_t; /** @brief Canonical KMIP attribute name for object name. */ inline const std::string KMIP_ATTR_NAME_NAME = @@ -52,6 +34,15 @@ namespace kmipclient { /** @brief Canonical KMIP attribute name for unique identifier. */ inline const std::string KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = std::string(kmipcore::KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); + /** @brief Canonical KMIP attribute name for operation policy name. */ + inline const std::string KMIP_ATTR_NAME_OPERATION_POLICY_NAME = + std::string(kmipcore::KMIP_ATTR_NAME_OPERATION_POLICY_NAME); + /** @brief Canonical KMIP attribute name for cryptographic algorithm. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_ALG = + std::string(kmipcore::KMIP_ATTR_NAME_CRYPTO_ALG); + /** @brief Canonical KMIP attribute name for cryptographic length. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_LEN = + std::string(kmipcore::KMIP_ATTR_NAME_CRYPTO_LEN); /** @brief Re-export stream formatter overloads from kmipcore. */ using kmipcore::operator<<; diff --git a/kmipclient/src/IOUtils.cpp b/kmipclient/src/IOUtils.cpp index 64d20e1..22473af 100644 --- a/kmipclient/src/IOUtils.cpp +++ b/kmipclient/src/IOUtils.cpp @@ -63,17 +63,23 @@ namespace kmipclient { throw KmipIOException(-1, "Can not send empty KMIP request."); } - const int sent = - net_client.send(std::span(request_bytes.data(), request_bytes.size())); - if (sent < dlen) { - throw KmipIOException( - -1, - std::format( - "Can not send request. Bytes total: {}, bytes sent: {}", - dlen, - sent - ) + int total_sent = 0; + while (total_sent < dlen) { + const int sent = net_client.send( + std::span(request_bytes) + .subspan(static_cast(total_sent)) ); + if (sent <= 0) { + throw KmipIOException( + -1, + std::format( + "Can not send request. Bytes total: {}, bytes sent: {}", + dlen, + total_sent + ) + ); + } + total_sent += sent; } } diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp index 27c5eb3..3a5df82 100644 --- a/kmipclient/src/Key.cpp +++ b/kmipclient/src/Key.cpp @@ -61,7 +61,7 @@ namespace kmipclient { std::vector key_bytes(der, der + der_len); OPENSSL_free(der); - attributes_t attrs; + std::unordered_map attrs; attrs[KMIP_ATTR_NAME_NAME] = "certificate_public_key"; return Key( key_bytes, @@ -93,7 +93,7 @@ namespace kmipclient { std::vector key_bytes(der, der + der_len); OPENSSL_free(der); - attributes_t attrs; + std::unordered_map attrs; attrs[KMIP_ATTR_NAME_NAME] = "private_key"; return Key( key_bytes, @@ -125,7 +125,7 @@ namespace kmipclient { std::vector key_bytes(der, der + der_len); OPENSSL_free(der); - attributes_t attrs; + std::unordered_map attrs; attrs[KMIP_ATTR_NAME_NAME] = "public_key"; return Key( key_bytes, @@ -146,13 +146,14 @@ namespace kmipclient { " bits. Should be 128, 192 or 256 bits" }; } - return Key( + auto key = Key( bytes, KeyType::SYMMETRIC_KEY, cryptographic_algorithm::KMIP_CRYPTOALG_AES, cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, {} ); + return key; } // Try to detect an AES/raw key encoded in PEM-like text by extracting @@ -189,7 +190,7 @@ namespace kmipclient { auto decoded = StringUtils::fromBase64(b64); size_t size = decoded.size(); if (size == 16 || size == 24 || size == 32) { - return make_aes_key(std::move(decoded)); + return make_aes_key(decoded); } } catch (...) // any parsing errors { @@ -268,6 +269,6 @@ namespace kmipclient { ); } - return make_aes_key(std::move(buf)); + return make_aes_key(buf); } } // namespace kmipclient diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index e5c0e94..e0c7939 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -24,14 +24,18 @@ #include "kmipcore/response_parser.hpp" #include +#include +#include +#include +#include namespace kmipclient { // Look up a required attribute in a response map. // Throws kmipcore::KmipException when the server omitted the attribute, -// unlike attributes_t::operator[] which would silently insert an empty value. +// unlike std::unordered_map::operator[] which would silently insert an empty value. static const std::string &require_attr( - const attributes_t &attrs, const std::string &name + const std::unordered_map &attrs, const std::string &name ) { auto it = attrs.find(name); if (it == attrs.end()) { @@ -42,6 +46,49 @@ static const std::string &require_attr( return it->second; } +static std::vector default_get_attrs(bool all_attributes) { + if (all_attributes) { + return {}; + } + return { + KMIP_ATTR_NAME_STATE, + KMIP_ATTR_NAME_NAME, + KMIP_ATTR_NAME_OPERATION_POLICY_NAME, + KMIP_ATTR_NAME_CRYPTO_ALG, + KMIP_ATTR_NAME_CRYPTO_LEN + }; +} + +static std::optional parse_state_attr(std::string_view state_attr) { + constexpr std::string_view prefix = "KMIP_STATE_"; + if (state_attr.starts_with(prefix)) { + state_attr.remove_prefix(prefix.size()); + } + + static constexpr std::array, 6> known = {{ + {"PRE_ACTIVE", state::KMIP_STATE_PRE_ACTIVE}, + {"ACTIVE", state::KMIP_STATE_ACTIVE}, + {"DEACTIVATED", state::KMIP_STATE_DEACTIVATED}, + {"COMPROMISED", state::KMIP_STATE_COMPROMISED}, + {"DESTROYED", state::KMIP_STATE_DESTROYED}, + {"DESTROYED_COMPROMISED", state::KMIP_STATE_DESTROYED_COMPROMISED}, + }}; + + for (const auto &[name, value] : known) { + if (state_attr == name) { + return value; + } + } + return std::nullopt; +} + +static void sync_secret_state_from_attr(Secret &secret, const std::string &state_attr) { + // Unknown values are ignored to preserve backward-compatible behavior. + if (auto parsed = parse_state_attr(state_attr); parsed.has_value()) { + secret.set_state(*parsed); + } +} + KmipClient::KmipClient( NetClient &net_client, const std::shared_ptr &logger @@ -54,8 +101,8 @@ static const std::string &require_attr( net_client.close(); }; - id_t KmipClient::op_register_key( - const name_t &name, const name_t &group, const Key &k + std::string KmipClient::op_register_key( + const std::string &name, const std::string &group, const Key &k ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( @@ -75,33 +122,18 @@ static const std::string &require_attr( .getUniqueIdentifier(); } - id_t KmipClient::op_register_secret( - const name_t &name, - const name_t &group, - const std::string_view secret, - secret_data_type secret_type - ) const { - return op_register_secret( - name, - group, - secret_t(secret.begin(), secret.end()), - secret_type - ); - } - - id_t KmipClient::op_register_secret( - const name_t &name, - const name_t &group, - const secret_t &secret, - secret_data_type secret_type + std::string KmipClient::op_register_secret( + const std::string &name, + const std::string &group, + const Secret &secret ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( kmipcore::RegisterSecretRequest( name, group, - secret, - secret_type + secret.value(), + secret.get_secret_type() ) ); @@ -118,8 +150,8 @@ static const std::string &require_attr( .getUniqueIdentifier(); } - id_t KmipClient::op_create_aes_key( - const name_t &name, const name_t &group + std::string KmipClient::op_create_aes_key( + const std::string &name, const std::string &group ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( @@ -139,17 +171,12 @@ static const std::string &require_attr( .getUniqueIdentifier(); } - Key KmipClient::op_get_key(const id_t &id, bool all_attributes) const { + Key KmipClient::op_get_key(const std::string &id, bool all_attributes) const { kmipcore::RequestMessage request; const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); - std::vector requested_attrs; - if (!all_attributes) { - requested_attrs = {KMIP_ATTR_NAME_STATE, KMIP_ATTR_NAME_NAME}; - } - const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, requested_attrs) + kmipcore::GetAttributesRequest(id, default_get_attrs(all_attributes)) ); std::vector response_bytes; @@ -168,32 +195,27 @@ static const std::string &require_attr( rf.getResponseByBatchItemId( attributes_item_id ); - attributes_t attrs = + std::unordered_map attrs = kmipcore::AttributesParser::parse(attrs_response.getAttributes()); - if (all_attributes) { - for (const auto &item : attrs) { - key.set_attribute(item.first, item.second); - } - } else { - key.set_attribute(KMIP_ATTR_NAME_STATE, require_attr(attrs, KMIP_ATTR_NAME_STATE)); - key.set_attribute(KMIP_ATTR_NAME_NAME, require_attr(attrs, KMIP_ATTR_NAME_NAME)); + // Verify required attributes are present + require_attr(attrs, KMIP_ATTR_NAME_STATE); + require_attr(attrs, KMIP_ATTR_NAME_NAME); + + // Copy all attributes from response to key + for (const auto &item : attrs) { + key.set_attribute(item.first, item.second); } return key; } - Secret KmipClient::op_get_secret(const id_t &id, bool all_attributes) const { + Secret KmipClient::op_get_secret(const std::string &id, bool all_attributes) const { kmipcore::RequestMessage request; const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); - std::vector requested_attrs; - if (!all_attributes) { - requested_attrs = {KMIP_ATTR_NAME_STATE, KMIP_ATTR_NAME_NAME}; - } - const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, requested_attrs) + kmipcore::GetAttributesRequest(id, default_get_attrs(all_attributes)) ); std::vector response_bytes; @@ -206,42 +228,34 @@ static const std::string &require_attr( rf.getResponseByBatchItemId( get_item_id ); - Secret secret; - try { - secret = kmipcore::KeyParser::parseGetSecretResponse(get_response); - } catch (const kmipcore::KmipException &e) { - // KMIP_REASON_INVALID_DATA_TYPE means the object exists but its type is - // not Secret Data (e.g. the ID points to a symmetric key). Rethrowing - // as "Could not locate" would be misleading; surface the real cause. - if (e.code().value() == kmipcore::KMIP_REASON_INVALID_DATA_TYPE) { - throw kmipcore::KmipException( - kmipcore::KMIP_REASON_INVALID_DATA_TYPE, - "Object '" + id + "' is not Secret Data" - ); - } - throw; - } + Secret secret = kmipcore::KeyParser::parseGetSecretResponse(get_response); auto attrs_response = rf.getResponseByBatchItemId( attributes_item_id ); - attributes_t attrs = + std::unordered_map attrs = kmipcore::AttributesParser::parse(attrs_response.getAttributes()); if (all_attributes) { for (const auto &item : attrs) { secret.set_attribute(item.first, item.second); } + const auto it = attrs.find(KMIP_ATTR_NAME_STATE); + if (it != attrs.end()) { + sync_secret_state_from_attr(secret, it->second); + } } else { - secret.set_attribute(KMIP_ATTR_NAME_STATE, require_attr(attrs, KMIP_ATTR_NAME_STATE)); + const auto &state_attr = require_attr(attrs, KMIP_ATTR_NAME_STATE); + sync_secret_state_from_attr(secret, state_attr); + secret.set_attribute(KMIP_ATTR_NAME_STATE, state_attr); secret.set_attribute(KMIP_ATTR_NAME_NAME, require_attr(attrs, KMIP_ATTR_NAME_NAME)); } return secret; } - id_t KmipClient::op_activate(const id_t &id) const { + std::string KmipClient::op_activate(const std::string &id) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item(kmipcore::ActivateRequest(id)); @@ -259,7 +273,7 @@ static const std::string &require_attr( .getUniqueIdentifier(); } - names_t KmipClient::op_get_attribute_list(const id_t &id) const { + std::vector KmipClient::op_get_attribute_list(const std::string &id) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item(kmipcore::GetAttributeListRequest(id)); @@ -272,13 +286,13 @@ static const std::string &require_attr( kmipcore::ResponseParser rf(response_bytes); auto response = rf.getResponseByBatchItemId< kmipcore::GetAttributeListResponseBatchItem>(batch_item_id); - return names_t{ + return std::vector{ response.getAttributeNames().begin(), response.getAttributeNames().end() }; } - attributes_t KmipClient::op_get_attributes( - const id_t &id, const std::vector &attr_names + std::unordered_map KmipClient::op_get_attributes( + const std::string &id, const std::vector &attr_names ) const { kmipcore::RequestMessage request; const auto batch_item_id = @@ -297,8 +311,8 @@ static const std::string &require_attr( return kmipcore::AttributesParser::parse(response.getAttributes()); } - ids_t KmipClient::op_locate_by_name( - const name_t &name, object_type o_type + std::vector KmipClient::op_locate_by_name( + const std::string &name, object_type o_type ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( @@ -321,20 +335,20 @@ static const std::string &require_attr( rf.getResponseByBatchItemId( batch_item_id ); - return ids_t( + return { response.getUniqueIdentifiers().begin(), response.getUniqueIdentifiers().end() - ); + }; } - ids_t KmipClient::op_locate_by_group( - const name_t &group, object_type o_type, std::size_t max_ids + std::vector KmipClient::op_locate_by_group( + const std::string &group, object_type o_type, std::size_t max_ids ) const { if (max_ids == 0) { return {}; } - ids_t result; + std::vector result; std::size_t received = 0; std::size_t offset = 0; @@ -363,16 +377,16 @@ static const std::string &require_attr( rf.getResponseByBatchItemId( batch_item_id ); - auto exp = ids_t( + auto exp = std::vector( response.getUniqueIdentifiers().begin(), response.getUniqueIdentifiers().end() ); - if (ids_t got = exp; !got.empty()) { + if (std::vector got = exp; !got.empty()) { received = got.size(); offset += got.size(); const std::size_t to_take = std::min(remaining, got.size()); - result.insert(result.end(), got.begin(), got.begin() + to_take); + std::copy_n(got.begin(), to_take, std::back_inserter(result)); } else { break; } @@ -381,14 +395,14 @@ static const std::string &require_attr( return result; } - ids_t KmipClient::op_all(object_type o_type, std::size_t max_ids) const { + std::vector KmipClient::op_all(object_type o_type, std::size_t max_ids) const { return op_locate_by_group("", o_type, max_ids); } - id_t KmipClient::op_revoke( - const id_t &id, + std::string KmipClient::op_revoke( + const std::string &id, revocation_reason_type reason, - const name_t &message, + const std::string &message, time_t occurrence_time ) const { kmipcore::RequestMessage request; @@ -414,7 +428,7 @@ static const std::string &require_attr( .getUniqueIdentifier(); } - id_t KmipClient::op_destroy(const id_t &id) const { + std::string KmipClient::op_destroy(const std::string &id) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item(kmipcore::DestroyRequest(id)); diff --git a/kmipclient/src/NetClientOpenSSL.cpp b/kmipclient/src/NetClientOpenSSL.cpp index 7e02bcd..edb5f6e 100644 --- a/kmipclient/src/NetClientOpenSSL.cpp +++ b/kmipclient/src/NetClientOpenSSL.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of @@ -19,10 +20,13 @@ #include "kmipclient/KmipIOException.hpp" +#include +#include #include #include #include #include +#include #include #include #include @@ -51,6 +55,85 @@ namespace kmipclient { return oss.str(); } + // Waits until the SSL BIO can make forward progress, bounded by deadline. + static void wait_for_bio_retry( + BIO *bio, + const std::chrono::steady_clock::time_point &deadline, + const char *op, + int timeout_ms + ) { + int fd = -1; + if (BIO_get_fd(bio, &fd) < 0 || fd < 0) { + throw KmipIOException( + -1, + std::string("Unable to obtain socket while waiting for ") + op + ); + } + + const auto now = std::chrono::steady_clock::now(); + if (now >= deadline) { + throw KmipIOException(-1, timeoutMessage(op, timeout_ms)); + } + + auto remaining_ms = + std::chrono::duration_cast(deadline - now) + .count(); + if (remaining_ms <= 0) { + remaining_ms = 1; + } + + struct pollfd pfd {}; + pfd.fd = fd; + if (BIO_should_read(bio)) { + pfd.events |= POLLIN; + } + if (BIO_should_write(bio)) { + pfd.events |= POLLOUT; + } + if (pfd.events == 0) { + pfd.events = POLLIN | POLLOUT; + } + + int poll_ret = 0; + do { + poll_ret = poll(&pfd, 1, static_cast(remaining_ms)); + } while (poll_ret < 0 && errno == EINTR); + + if (poll_ret == 0) { + throw KmipIOException(-1, timeoutMessage(op, timeout_ms)); + } + if (poll_ret < 0) { + throw KmipIOException( + -1, + std::string("poll failed while waiting for ") + op + ": " + + strerror(errno) + ); + } + } + + static void restore_socket_blocking(BIO *bio) { + int fd = -1; + if (BIO_get_fd(bio, &fd) < 0 || fd < 0) { + throw KmipIOException(-1, "Unable to obtain socket fd for mode switch"); + } + + const int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + throw KmipIOException( + -1, + std::string("fcntl(F_GETFL) failed: ") + strerror(errno) + ); + } + + const int desired_flags = flags & ~O_NONBLOCK; + if (fcntl(fd, F_SETFL, desired_flags) != 0) { + throw KmipIOException( + -1, + std::string("fcntl(F_SETFL) failed: ") + strerror(errno) + ); + } + } + // Apply SO_RCVTIMEO / SO_SNDTIMEO on the underlying socket so that every // BIO_read / BIO_write call times out after timeout_ms milliseconds. // Must be called after BIO_do_connect() succeeds. @@ -185,14 +268,45 @@ namespace kmipclient { BIO_set_conn_hostname(bio_.get(), m_host.c_str()); BIO_set_conn_port(bio_.get(), m_port.c_str()); - // Using generic blocking BIO connect timeout if supported or rely on system - // socket timeout BIO_set_ssl_renegotiate_timeout(bio_, m_timeout_ms); // - // This function is non-standard or deprecated from initial code analysis. - - if (BIO_do_connect(bio_.get()) != 1) { - throw KmipIOException( - -1, "BIO_do_connect failed: " + getOpenSslError() - ); + if (m_timeout_ms > 0) { + if (BIO_set_nbio(bio_.get(), 1) != 1) { + throw KmipIOException( + -1, "BIO_set_nbio(1) failed before connect" + ); + } + + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(m_timeout_ms); + for (;;) { + ERR_clear_error(); + const int connect_ret = BIO_do_connect(bio_.get()); + if (connect_ret == 1) { + break; + } + + if (!BIO_should_retry(bio_.get())) { + throw KmipIOException( + -1, "BIO_do_connect failed: " + getOpenSslError() + ); + } + + wait_for_bio_retry( + bio_.get(), deadline, "connect/handshake", m_timeout_ms + ); + } + + restore_socket_blocking(bio_.get()); + if (BIO_set_nbio(bio_.get(), 0) != 1) { + throw KmipIOException( + -1, "BIO_set_nbio(0) failed after connect" + ); + } + } else { + if (BIO_do_connect(bio_.get()) != 1) { + throw KmipIOException( + -1, "BIO_do_connect failed: " + getOpenSslError() + ); + } } // Apply per-operation I/O timeouts on the now-connected socket so that @@ -219,8 +333,9 @@ namespace kmipclient { return -1; } const int dlen = static_cast(data.size()); + errno = 0; const int ret = BIO_write(bio_.get(), data.data(), dlen); - if (ret <= 0 && is_timeout_errno()) { + if (ret <= 0 && BIO_should_retry(bio_.get()) && is_timeout_errno()) { throw KmipIOException( -1, timeoutMessage("send", m_timeout_ms) ); @@ -233,8 +348,9 @@ namespace kmipclient { return -1; } const int dlen = static_cast(data.size()); + errno = 0; const int ret = BIO_read(bio_.get(), data.data(), dlen); - if (ret <= 0 && is_timeout_errno()) { + if (ret <= 0 && BIO_should_retry(bio_.get()) && is_timeout_errno()) { throw KmipIOException( -1, timeoutMessage("receive", m_timeout_ms) ); diff --git a/kmipclient/src/StringUtils.cpp b/kmipclient/src/StringUtils.cpp index e0f6eec..278ea12 100644 --- a/kmipclient/src/StringUtils.cpp +++ b/kmipclient/src/StringUtils.cpp @@ -37,11 +37,11 @@ namespace kmipclient { throw kmipcore::KmipException{"Invalid hex character."}; } - key_t StringUtils::fromHex(std::string_view hex) { + std::vector StringUtils::fromHex(std::string_view hex) { if (hex.empty() || hex.size() % 2 != 0) { throw kmipcore::KmipException{"Invalid hex string length."}; } - key_t bytes; + std::vector bytes; bytes.reserve(hex.size() / 2); for (size_t i = 0; i < hex.size(); i += 2) { const auto byte = static_cast( diff --git a/kmipclient/src/StringUtils.hpp b/kmipclient/src/StringUtils.hpp index 24f1bc2..38ae8ba 100644 --- a/kmipclient/src/StringUtils.hpp +++ b/kmipclient/src/StringUtils.hpp @@ -18,15 +18,14 @@ #ifndef STRINGUTILS_HPP #define STRINGUTILS_HPP -#include "kmipclient/types.hpp" - #include +#include namespace kmipclient { class StringUtils { public: - static key_t fromHex(std::string_view hex); + static std::vector fromHex(std::string_view hex); static std::vector fromBase64(std::string_view base64); }; diff --git a/kmipclient/tests/IOUtilsTest.cpp b/kmipclient/tests/IOUtilsTest.cpp new file mode 100644 index 0000000..fcac24e --- /dev/null +++ b/kmipclient/tests/IOUtilsTest.cpp @@ -0,0 +1,119 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "../src/IOUtils.hpp" + +#include "kmipclient/KmipIOException.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace { + +class FakeNetClient final : public kmipclient::NetClient { +public: + FakeNetClient() + : NetClient("host", "5696", "client.pem", "client.key", "ca.pem", 1000) {} + + bool connect() override { + m_isConnected = true; + return true; + } + + void close() override { m_isConnected = false; } + + int send(std::span data) override { + ++send_calls; + + const int desired = + send_plan_index < static_cast(send_plan.size()) ? send_plan[send_plan_index++] : + static_cast(data.size()); + if (desired <= 0) { + return desired; + } + + const int sent = std::min(desired, static_cast(data.size())); + sent_bytes.insert(sent_bytes.end(), data.begin(), data.begin() + sent); + return sent; + } + + int recv(std::span data) override { + if (recv_offset >= response_bytes.size()) { + return 0; + } + + const size_t count = std::min(data.size(), response_bytes.size() - recv_offset); + std::copy_n(response_bytes.data() + recv_offset, count, data.data()); + recv_offset += count; + return static_cast(count); + } + + std::vector send_plan; + std::vector response_bytes; + std::vector sent_bytes; + int send_calls = 0; + +private: + int send_plan_index = 0; + size_t recv_offset = 0; +}; + +std::vector build_response_with_payload(const std::vector &payload) { + const auto len = static_cast(payload.size()); + std::vector out{0, 0, 0, 0, + static_cast((len >> 24) & 0xFF), + static_cast((len >> 16) & 0xFF), + static_cast((len >> 8) & 0xFF), + static_cast(len & 0xFF)}; + out.insert(out.end(), payload.begin(), payload.end()); + return out; +} + +} // namespace + +TEST(IOUtilsTest, SendRetriesOnShortWritesUntilComplete) { + FakeNetClient nc; + nc.send_plan = {3, 2, 128}; + nc.response_bytes = build_response_with_payload({0x42}); + + kmipclient::IOUtils io(nc); + const std::vector request{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + std::vector response; + + ASSERT_NO_THROW(io.do_exchange(request, response, 1024)); + EXPECT_EQ(nc.sent_bytes, request); + EXPECT_EQ(nc.send_calls, 3); + EXPECT_EQ(response, nc.response_bytes); +} + +TEST(IOUtilsTest, SendFailsIfTransportStopsProgress) { + FakeNetClient nc; + nc.send_plan = {2, 0}; + nc.response_bytes = build_response_with_payload({0x42}); + + kmipclient::IOUtils io(nc); + const std::vector request{0, 1, 2, 3}; + std::vector response; + + EXPECT_THROW(io.do_exchange(request, response, 1024), kmipclient::KmipIOException); +} + diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 939df38..11efae1 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -20,6 +20,8 @@ #include "kmipcore/kmip_basics.hpp" #include +#include +#include #include #include #include @@ -80,7 +82,15 @@ class KmipTestConfig { kmip_server_ca = server_ca; } - timeout_ms = timeout ? std::atoi(timeout) : 5000; // Default 5 seconds + timeout_ms = 5000; // Default 5 seconds + if (timeout) { + errno = 0; + char *end = nullptr; + const long parsed = std::strtol(timeout, &end, 10); + if (errno == 0 && end != timeout && *end == '\0' && parsed >= 0 && parsed <= INT_MAX) { + timeout_ms = static_cast(parsed); + } + } if (!isConfigured()) { std::cerr << "WARNING: KMIP environment variables not set. Tests will be " @@ -264,7 +274,7 @@ TEST_F(KmipClientIntegrationTest, CreateSymmetricAESKey) { // Test: Create and Get key TEST_F(KmipClientIntegrationTest, CreateAndGetKey) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; + std::string key_id; // Create key try { key_id = kmip->client().op_create_aes_key( @@ -290,7 +300,7 @@ TEST_F(KmipClientIntegrationTest, CreateAndGetKey) { // Test: Create, Activate, and Get key TEST_F(KmipClientIntegrationTest, CreateActivateAndGetKey) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; + std::string key_id; // Create key try { key_id = kmip->client().op_create_aes_key( @@ -351,17 +361,29 @@ TEST_F(KmipClientIntegrationTest, RegisterSymmetricKey) { // Test: Register secret data TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { auto kmip = createKmipClient(); - kmipclient::id_t secret_id; - secret_t secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + std::string secret_id; + std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + Secret secret( + secret_data, + state::KMIP_STATE_PRE_ACTIVE, + secret_data_type::KMIP_SECDATA_PASSWORD + ); try { secret_id = kmip->client().op_register_secret( - TESTING_NAME_PREFIX + "a_secret", TEST_GROUP, secret_data, secret_data_type::KMIP_SECDATA_PASSWORD + TESTING_NAME_PREFIX + "a_secret", TEST_GROUP, secret ); EXPECT_FALSE(secret_id.empty()); std::cout << "Registered secret with ID: " << secret_id << std::endl; trackKeyForCleanup(secret_id); } catch (kmipcore::KmipException &e) { - std::cout << "Registered secret failed: " << e.what() << std::endl; + FAIL() << "Registered secret failed: " << e.what(); + } + + try { + auto activated_id = kmip->client().op_activate(secret_id); + EXPECT_EQ(activated_id, secret_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to activate secret: " << e.what(); } try { @@ -373,17 +395,23 @@ TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { retrieved_secret.attributes().count(KMIP_ATTR_NAME_NAME) > 0 || retrieved_secret.attributes().count("Name") > 0 ); + + const auto state_it = retrieved_secret.attributes().find(KMIP_ATTR_NAME_STATE); + ASSERT_NE(state_it, retrieved_secret.attributes().end()) + << "State attribute is missing"; + EXPECT_EQ(retrieved_secret.get_state(), state::KMIP_STATE_ACTIVE); + EXPECT_EQ(state_it->second, kmipcore::state_to_string(retrieved_secret.get_state())); } catch (kmipcore::KmipException &e) { - std::cout << "Get secret failed: " << e.what() << std::endl; + FAIL() << "Get secret failed: " << e.what(); } } // Test: Locate keys TEST_F(KmipClientIntegrationTest, LocateKeys) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; - kmipclient::ids_t result; - kmipclient::name_t name = TESTING_NAME_PREFIX + "LocateKeys"; + std::string key_id; + std::vector result; + std::string name = TESTING_NAME_PREFIX + "LocateKeys"; // Create key try { key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); @@ -406,8 +434,8 @@ TEST_F(KmipClientIntegrationTest, LocateKeys) { // Test: Get attributes TEST_F(KmipClientIntegrationTest, CreateAndGetAttributes) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; - kmipclient::name_t name = TESTING_NAME_PREFIX + "CreateAndGetAttributes"; + std::string key_id; + std::string name = TESTING_NAME_PREFIX + "CreateAndGetAttributes"; // Create key try { key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); @@ -439,8 +467,8 @@ TEST_F(KmipClientIntegrationTest, CreateAndRevokeKey) { auto kmip = createKmipClient(); // Create and activate key - kmipclient::id_t key_id; - kmipclient::name_t name = TESTING_NAME_PREFIX + "CreateAndRevokeKey"; + std::string key_id; + std::string name = TESTING_NAME_PREFIX + "CreateAndRevokeKey"; // Create key try { key_id = kmip->client().op_create_aes_key(name, TEST_GROUP); @@ -500,15 +528,22 @@ TEST_F(KmipClientIntegrationTest, FullKeyLifecycle) { // Test: Get non-existent key should fail TEST_F(KmipClientIntegrationTest, GetNonExistentKey) { auto kmip = createKmipClient(); - Key key; - std::string fake_id = "non-existent-key-id-12345"; - try { - key = kmip->client().op_get_key(fake_id); - } catch (kmipcore::KmipException &) {} + const std::string fake_id = "non-existent-key-id-12345"; - ASSERT_TRUE(key.value().empty()) << "Should fail to get non-existent key"; - std::cout << "Successfully verified non-existent key cannot be retrieved" - << std::endl; + try { + auto key = kmip->client().op_get_key(fake_id); + (void) key; + FAIL() << "Should fail to get non-existent key"; + } catch (const kmipcore::KmipException &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("Operation: Get"), std::string::npos) + << "Expected Get operation failure path, got: " << msg; + EXPECT_NE(msg.find("Result reason:"), std::string::npos) + << "Expected server Result Reason in error, got: " << msg; + std::cout << "Successfully verified non-existent key returns server " + "error details" + << std::endl; + } } TEST_F(KmipClientIntegrationTest, GetNonExistentSecret) { @@ -565,7 +600,7 @@ TEST_F(KmipClientIntegrationTest, CreateMultipleKeys) { // Test: Destroying a key removes it (cannot be retrieved) TEST_F(KmipClientIntegrationTest, DestroyKeyRemovesKey) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; + std::string key_id; try { key_id = kmip->client().op_create_aes_key( TESTING_NAME_PREFIX + "DestroyKeyRemovesKey", TEST_GROUP @@ -603,7 +638,7 @@ TEST_F(KmipClientIntegrationTest, DestroyKeyRemovesKey) { // should be locatable TEST_F(KmipClientIntegrationTest, CreateDuplicateNames) { auto kmip = createKmipClient(); - kmipclient::name_t name = TESTING_NAME_PREFIX + "DuplicateNameTest"; + std::string name = TESTING_NAME_PREFIX + "DuplicateNameTest"; std::string id1, id2; try { id1 = kmip->client().op_create_aes_key(name, TEST_GROUP); @@ -636,7 +671,7 @@ TEST_F(KmipClientIntegrationTest, CreateDuplicateNames) { // Test: Revoke changes state to REVOKED TEST_F(KmipClientIntegrationTest, RevokeChangesState) { auto kmip = createKmipClient(); - kmipclient::id_t key_id; + std::string key_id; try { key_id = kmip->client().op_create_aes_key( TESTING_NAME_PREFIX + "RevokeChangesState", TEST_GROUP diff --git a/kmipcore/include/kmipcore/attributes_parser.hpp b/kmipcore/include/kmipcore/attributes_parser.hpp index 6d528a9..e4f4126 100644 --- a/kmipcore/include/kmipcore/attributes_parser.hpp +++ b/kmipcore/include/kmipcore/attributes_parser.hpp @@ -1,9 +1,10 @@ #pragma once #include "kmipcore/kmip_basics.hpp" -#include "kmipcore/types.hpp" #include +#include +#include #include namespace kmipcore { @@ -20,7 +21,7 @@ namespace kmipcore { * @param attributes Raw KMIP attribute elements. * @return Parsed attribute map keyed by attribute name. */ - static attributes_t + static std::unordered_map parse(const std::vector> &attributes); }; diff --git a/kmipcore/include/kmipcore/key.hpp b/kmipcore/include/kmipcore/key.hpp index 0c59e0b..64a8dfd 100644 --- a/kmipcore/include/kmipcore/key.hpp +++ b/kmipcore/include/kmipcore/key.hpp @@ -18,7 +18,11 @@ #ifndef KMIPCORE_KEY_HPP #define KMIPCORE_KEY_HPP -#include "kmipcore/types.hpp" +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include namespace kmipcore { @@ -41,11 +45,11 @@ namespace kmipcore { * @param attributes Additional key attributes. */ explicit Key( - const key_t &value, + const std::vector &value, KeyType k_type, cryptographic_algorithm algo, cryptographic_usage_mask usage_mask, - const attributes_t &attributes + const std::unordered_map &attributes ) : key_value(value), key_type(k_type), @@ -64,10 +68,10 @@ namespace kmipcore { Key &operator=(Key &&) noexcept = default; /** @brief Returns raw key bytes. */ - [[nodiscard]] const key_t &value() const noexcept { return key_value; }; + [[nodiscard]] const std::vector &value() const noexcept { return key_value; }; /** @brief Returns all attached key attributes. */ - [[nodiscard]] const attributes_t &attributes() const noexcept { + [[nodiscard]] const std::unordered_map &attributes() const noexcept { return key_attributes; }; @@ -107,9 +111,9 @@ namespace kmipcore { [[nodiscard]] size_t size() const noexcept { return key_value.size(); } private: - key_t key_value; + std::vector key_value; KeyType key_type = KeyType::UNSET; - attributes_t key_attributes; + std::unordered_map key_attributes; cryptographic_algorithm crypto_algorithm = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; cryptographic_usage_mask crypto_usage_mask = diff --git a/kmipcore/include/kmipcore/kmip_basics.hpp b/kmipcore/include/kmipcore/kmip_basics.hpp index 3de9397..cf03f6c 100644 --- a/kmipcore/include/kmipcore/kmip_basics.hpp +++ b/kmipcore/include/kmipcore/kmip_basics.hpp @@ -21,6 +21,10 @@ namespace kmipcore { using Tag = tag; /** @brief Alias of KMIP type enumeration used by TTLV elements. */ using Type = type; + /** @brief KMIP Result Status numeric code type. */ + using KmipResultStatusCode = int32_t; + /** @brief KMIP Result Reason numeric code type. */ + using KmipResultReasonCode = int32_t; struct Element; // Forward declaration diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp index 8d6b0a2..b41da0c 100644 --- a/kmipcore/include/kmipcore/kmip_enums.hpp +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -13,6 +13,7 @@ #include #include +#include namespace kmipcore { @@ -618,6 +619,11 @@ inline const char *state_to_string(state value) { } } +/** Stream formatter for KMIP lifecycle state values. */ +inline std::ostream &operator<<(std::ostream &out, const state value) { + return out << state_to_string(value); +} + enum class tag : std::uint32_t { KMIP_TAG_TAG = 0x000000, KMIP_TAG_TYPE = 0x000001, diff --git a/kmipcore/include/kmipcore/kmip_protocol.hpp b/kmipcore/include/kmipcore/kmip_protocol.hpp index 677be1b..5fd10a0 100644 --- a/kmipcore/include/kmipcore/kmip_protocol.hpp +++ b/kmipcore/include/kmipcore/kmip_protocol.hpp @@ -244,9 +244,9 @@ namespace kmipcore { /** @brief Constructs message with default protocol and limits. */ RequestMessage(); - /** @brief Constructs message using a specific protocol minor version. */ + /** @brief Constructs message using a KMIP version constant or raw 1.x minor. */ explicit RequestMessage(int32_t protocolVersionMinor); - /** @brief Constructs message with protocol minor and response size hint. */ + /** @brief Constructs message with a KMIP version selector and response size hint. */ RequestMessage(int32_t protocolVersionMinor, size_t maxResponseSize); /** @brief Returns const request header. */ @@ -276,9 +276,9 @@ namespace kmipcore { nextBatchItemId_ = 1; } - /** @brief Sets protocol minor version in request header. */ + /** @brief Sets request protocol version from a KMIP version constant or raw 1.x minor. */ void setProtocolVersionMinor(int32_t minor); - /** @brief Returns protocol minor version from request header. */ + /** @brief Returns the raw wire minor version from the request header. */ [[nodiscard]] int32_t getProtocolVersionMinor() const; /** @brief Sets maximum response size hint in request header. */ diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index afcdf55..8e0dee9 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -2,7 +2,6 @@ #include "kmipcore/kmip_basics.hpp" #include "kmipcore/kmip_enums.hpp" #include "kmipcore/kmip_protocol.hpp" -#include "kmipcore/types.hpp" #include #include @@ -129,7 +128,7 @@ namespace kmipcore { RegisterSecretRequest( const std::string &name, const std::string &group, - const secret_t &secret, + const std::vector &secret, secret_data_type secret_type ); }; diff --git a/kmipcore/include/kmipcore/response_parser.hpp b/kmipcore/include/kmipcore/response_parser.hpp index 08b506b..3155bc2 100644 --- a/kmipcore/include/kmipcore/response_parser.hpp +++ b/kmipcore/include/kmipcore/response_parser.hpp @@ -14,9 +14,9 @@ namespace kmipcore { /** Operation code reported by the response item. */ int32_t operation = 0; /** KMIP result_status code. */ - int32_t resultStatus = 0; + KmipResultStatusCode resultStatus = 0; /** KMIP result_reason code when available. */ - int32_t resultReason = 0; + KmipResultReasonCode resultReason = 0; /** Human-readable result message when available. */ std::string resultMessage; }; diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp index 60f8d34..35f42ee 100644 --- a/kmipcore/include/kmipcore/secret.hpp +++ b/kmipcore/include/kmipcore/secret.hpp @@ -1,10 +1,11 @@ #pragma once #include "kmipcore/kmip_enums.hpp" -#include "kmipcore/types.hpp" #include #include +#include +#include namespace kmipcore { @@ -16,14 +17,14 @@ namespace kmipcore { /** @brief Constructs an empty secret. */ Secret() = default; /** @brief Constructs a secret from payload and metadata. */ - Secret(const secret_t &val, state st, secret_data_type type) + Secret(const std::vector &val, state st, secret_data_type type) : value_(val), state_(st), secret_type_(type) {} /** @brief Returns raw secret payload bytes. */ - [[nodiscard]] const secret_t &value() const noexcept { return value_; } + [[nodiscard]] const std::vector &value() const noexcept { return value_; } /** @brief Replaces raw secret payload bytes. */ - void set_value(const secret_t &val) noexcept { value_ = val; } + void set_value(const std::vector &val) noexcept { value_ = val; } /** @brief Returns lifecycle state of this secret object. */ [[nodiscard]] state get_state() const noexcept { return state_; } @@ -42,7 +43,7 @@ namespace kmipcore { } /** @brief Returns all attached secret attributes. */ - [[nodiscard]] const attributes_t &attributes() const noexcept { + [[nodiscard]] const std::unordered_map &attributes() const noexcept { return secret_attributes; } @@ -76,8 +77,9 @@ namespace kmipcore { secret_data_type type = secret_data_type::KMIP_SECDATA_PASSWORD, state st = state::KMIP_STATE_PRE_ACTIVE ) { - return Secret{ - secret_t(text.begin(), text.end()), st, type}; + return Secret( + std::vector(text.begin(), text.end()), st, type + ); } /** @brief Returns payload interpreted as UTF-8/byte-preserving text. */ @@ -86,10 +88,10 @@ namespace kmipcore { } private: - secret_t value_; + std::vector value_; state state_ = state::KMIP_STATE_PRE_ACTIVE; secret_data_type secret_type_ = secret_data_type::KMIP_SECDATA_PASSWORD; - attributes_t secret_attributes; + std::unordered_map secret_attributes; }; } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/types.hpp b/kmipcore/include/kmipcore/types.hpp deleted file mode 100644 index b27372a..0000000 --- a/kmipcore/include/kmipcore/types.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "kmipcore/kmip_enums.hpp" - -#include -#include -#include -#include - -namespace kmipcore { - - /** @brief Raw key bytes container type. */ - using key_t = std::vector; - /** @brief Generic binary payload container type. */ - using bin_data_t = std::vector; - /** @brief KMIP unique identifier textual type. */ - using id_t = std::string; - /** @brief Collection of KMIP unique identifiers. */ - using ids_t = std::vector; - /** @brief KMIP Name attribute textual type. */ - using name_t = std::string; - /** @brief Collection of textual names. */ - using names_t = std::vector; - /** @brief Secret payload bytes container type. */ - using secret_t = std::vector; - /** @brief Generic string attribute map type. */ - using attributes_t = std::unordered_map; - - /** @brief Stream formatter for KMIP lifecycle state values. */ - inline std::ostream &operator<<(std::ostream &out, const state value) { - return out << state_to_string(value); - } - -} // namespace kmipcore - diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp index 4ee342a..d6373cc 100644 --- a/kmipcore/src/attributes_parser.cpp +++ b/kmipcore/src/attributes_parser.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace kmipcore { @@ -59,7 +60,7 @@ namespace kmipcore { [[nodiscard]] std::string date_to_string(int64_t seconds) { // Return ISO 8601 format or similar "YYYY-MM-DD HH:MM:SS" // KMIP Date-Time is standard UNIX epoch seconds. - std::time_t t = static_cast(seconds); + const auto t = static_cast(seconds); std::tm tm_buf{}; #ifdef _WIN32 gmtime_s(&tm_buf, &t); @@ -113,10 +114,10 @@ namespace kmipcore { } // namespace - attributes_t AttributesParser::parse( + std::unordered_map AttributesParser::parse( const std::vector> &attributes ) { - attributes_t res; + std::unordered_map res; for (const auto &attribute : attributes) { if (attribute == nullptr || attribute->tag != tag::KMIP_TAG_ATTRIBUTE) { diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp index 213bfca..86896f5 100644 --- a/kmipcore/src/key_parser.cpp +++ b/kmipcore/src/key_parser.cpp @@ -61,7 +61,7 @@ namespace kmipcore { } auto raw_bytes = key_material->toBytes(); - key_t kv(raw_bytes.begin(), raw_bytes.end()); + std::vector kv(raw_bytes.begin(), raw_bytes.end()); auto algorithm = key_block->getChild( tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM @@ -139,11 +139,10 @@ namespace kmipcore { auto raw_bytes = key_material->toBytes(); - return Secret{ - secret_t(raw_bytes.begin(), raw_bytes.end()), - state::KMIP_STATE_PRE_ACTIVE, - static_cast(secret_type->toEnum()) - }; + Secret secret; + secret.set_value(std::vector(raw_bytes.begin(), raw_bytes.end())); + secret.set_secret_type(static_cast(secret_type->toEnum())); + return secret; } Key KeyParser::parseResponse(const std::shared_ptr &payload) { diff --git a/kmipcore/src/kmip_formatter.cpp b/kmipcore/src/kmip_formatter.cpp index 0a0ec99..188c834 100644 --- a/kmipcore/src/kmip_formatter.cpp +++ b/kmipcore/src/kmip_formatter.cpp @@ -3,7 +3,6 @@ #include "kmipcore/kmip_basics.hpp" #include "kmipcore/kmip_enums.hpp" #include "kmipcore/kmip_protocol.hpp" -#include "kmipcore/types.hpp" #include #include @@ -14,7 +13,7 @@ namespace kmipcore { namespace { [[nodiscard]] std::string indent(size_t level) { - return std::string(level * 2, ' '); + return {level * 2, ' '}; } [[nodiscard]] std::string format_hex_uint(uint64_t value, size_t width = 0) { @@ -69,7 +68,7 @@ namespace kmipcore { } [[nodiscard]] std::string format_datetime(int64_t seconds) { - std::time_t t = static_cast(seconds); + const auto t = static_cast(seconds); std::tm tm_buf{}; #if defined(_WIN32) gmtime_s(&tm_buf, &t); diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp index e73736d..bebd566 100644 --- a/kmipcore/src/kmip_protocol.cpp +++ b/kmipcore/src/kmip_protocol.cpp @@ -6,6 +6,62 @@ #include namespace kmipcore { + namespace { + + [[nodiscard]] ProtocolVersion protocol_version_from_api_value(int32_t value) { + switch (value) { + case KMIP_1_0: + return {1, 0}; + case KMIP_1_1: + return {1, 1}; + case KMIP_1_2: + return {1, 2}; + case KMIP_1_3: + return {1, 3}; + case KMIP_1_4: + return {1, 4}; + case KMIP_2_0: + return {2, 0}; + default: + return {1, value}; + } + } + + [[nodiscard]] bool protocol_version_at_least( + const ProtocolVersion &version, int32_t major, int32_t minor + ) { + return version.getMajor() > major || + (version.getMajor() == major && version.getMinor() >= minor); + } + + [[nodiscard]] bool supports_date_time_extended(const ProtocolVersion &version) { + return protocol_version_at_least(version, 2, 0); + } + + void validate_element_types_for_version( + const std::shared_ptr &element, + const ProtocolVersion &version + ) { + if (!element) { + return; + } + + if (element->type == Type::KMIP_TYPE_DATE_TIME_EXTENDED && + !supports_date_time_extended(version)) { + throw KmipException( + "DateTimeExtended requires KMIP 2.0 or later" + ); + } + + if (const auto *structure = element->asStructure(); structure != nullptr) { + for (const auto &child : structure->items) { + validate_element_types_for_version(child, version); + } + } + } + + } // namespace + // === ProtocolVersion === ProtocolVersion::ProtocolVersion(int32_t major, int32_t minor) : major_(major), minor_(minor) {} @@ -258,10 +314,7 @@ namespace kmipcore { } void RequestMessage::setProtocolVersionMinor(int32_t minor) { - ProtocolVersion version = header_.getProtocolVersion(); - version.setMajor(1); - version.setMinor(minor); - header_.setProtocolVersion(version); + header_.setProtocolVersion(protocol_version_from_api_value(minor)); } int32_t RequestMessage::getProtocolVersionMinor() const { @@ -305,6 +358,7 @@ namespace kmipcore { for (const auto &item : batchItems_) { structure->asStructure()->add(item.toElement()); } + validate_element_types_for_version(structure, header_.getProtocolVersion()); return structure; } RequestMessage RequestMessage::fromElement(std::shared_ptr element) { @@ -320,6 +374,7 @@ namespace kmipcore { } else { throw KmipException("Missing Request Header"); } + validate_element_types_for_version(element, rm.header_.getProtocolVersion()); const auto *s = std::get_if(&element->value); for (const auto &child : s->items) { if (child->tag == tag::KMIP_TAG_BATCH_ITEM) { @@ -465,6 +520,7 @@ namespace kmipcore { for (const auto &item : batchItems_) { structure->asStructure()->add(item.toElement()); } + validate_element_types_for_version(structure, header_.getProtocolVersion()); return structure; } ResponseMessage @@ -481,6 +537,7 @@ namespace kmipcore { } else { throw KmipException("Missing Response Header"); } + validate_element_types_for_version(element, rm.header_.getProtocolVersion()); const auto *s = std::get_if(&element->value); for (const auto &child : s->items) { if (child->tag == tag::KMIP_TAG_BATCH_ITEM) { diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index 0806874..9e2d656 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -141,7 +141,7 @@ namespace kmipcore { return symmetric_key; } std::shared_ptr - make_secret_data(const secret_t &secret, secret_data_type secret_type) { + make_secret_data(const std::vector &secret, secret_data_type secret_type) { auto secret_data = Element::createStructure(tag::KMIP_TAG_SECRET_DATA); secret_data->asStructure()->add( @@ -245,7 +245,7 @@ namespace kmipcore { RegisterSecretRequest::RegisterSecretRequest( const std::string &name, const std::string &group, - const secret_t &secret, + const std::vector &secret, secret_data_type secret_type ) { setOperation(KMIP_OP_REGISTER); diff --git a/kmipcore/src/response_parser.cpp b/kmipcore/src/response_parser.cpp index fadf672..6e02904 100644 --- a/kmipcore/src/response_parser.cpp +++ b/kmipcore/src/response_parser.cpp @@ -4,6 +4,14 @@ namespace kmipcore { + namespace { + [[nodiscard]] KmipResultReasonCode get_result_reason_or_default( + const ResponseBatchItem &item + ) { + return item.getResultReason().value_or(KMIP_REASON_GENERAL_FAILURE); + } + } // namespace + ResponseParser::ResponseParser(std::span responseBytes) : responseBytes_(responseBytes.begin(), responseBytes.end()) {} @@ -21,7 +29,7 @@ namespace kmipcore { return OperationResult{ item.getOperation(), item.getResultStatus(), - item.getResultReason().value_or(0), + get_result_reason_or_default(item), item.getResultMessage().value_or("") }; } @@ -32,7 +40,7 @@ namespace kmipcore { return OperationResult{ item.getOperation(), item.getResultStatus(), - item.getResultReason().value_or(0), + get_result_reason_or_default(item), item.getResultMessage().value_or("") }; } @@ -85,9 +93,7 @@ namespace kmipcore { void ResponseParser::ensureSuccess(const ResponseBatchItem &item) { if (item.getResultStatus() != KMIP_STATUS_SUCCESS) { - const int reason = static_cast( - item.getResultReason().value_or(KMIP_REASON_GENERAL_FAILURE) - ); + const KmipResultReasonCode reason = get_result_reason_or_default(item); throw KmipException(reason, formatOperationResult(item)); } } @@ -97,7 +103,7 @@ namespace kmipcore { OperationResult result = { value.getOperation(), value.getResultStatus(), - value.getResultReason().value_or(KMIP_REASON_GENERAL_FAILURE), + get_result_reason_or_default(value), value.getResultMessage().value_or("") }; diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp index abc8e8c..a121e66 100644 --- a/kmipcore/tests/test_core.cpp +++ b/kmipcore/tests/test_core.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,159 @@ void test_structure() { assert(std::get(d2->value).value == true); std::cout << "Structure test passed" << std::endl; } + +void test_date_time_extended_round_trip() { + constexpr int64_t micros = 1743075078123456LL; + + auto elem = Element::createDateTimeExtended( + tag::KMIP_TAG_TIME_STAMP, micros + ); + + SerializationBuffer buf; + elem->serialize(buf); + auto data = buf.release(); + + assert(data.size() == 16); + assert(data[3] == KMIP_TYPE_DATE_TIME_EXTENDED); + assert(data[4] == 0x00); + assert(data[5] == 0x00); + assert(data[6] == 0x00); + assert(data[7] == 0x08); + + size_t offset = 0; + auto decoded = Element::deserialize(data, offset); + assert(offset == 16); + assert(decoded->tag == tag::KMIP_TAG_TIME_STAMP); + assert(decoded->type == Type::KMIP_TYPE_DATE_TIME_EXTENDED); + assert(std::holds_alternative(decoded->value)); + assert(std::get(decoded->value).value == micros); + assert(decoded->toLong() == micros); + + const auto formatted = format_element(decoded); + assert(formatted.find("DateTimeExtended") != std::string::npos); + + std::cout << "DateTimeExtended round-trip test passed" << std::endl; +} + +void test_date_time_extended_invalid_length() { + const std::vector invalid = { + 0x42, 0x00, 0x92, static_cast(KMIP_TYPE_DATE_TIME_EXTENDED), + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + }; + + size_t offset = 0; + bool threw = false; + try { + (void) Element::deserialize(invalid, offset); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + + std::cout << "DateTimeExtended invalid-length test passed" << std::endl; +} + +void test_date_time_extended_requires_kmip_2_0_for_requests() { + RequestBatchItem item; + item.setOperation(KMIP_OP_GET); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createDateTimeExtended( + tag::KMIP_TAG_TIME_STAMP, 1743075078123456LL + ) + ); + item.setRequestPayload(payload); + + RequestMessage request_14; + request_14.add_batch_item(item); + + bool threw = false; + try { + (void) request_14.serialize(); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + + RequestMessage request_20(KMIP_2_0); + assert(request_20.getHeader().getProtocolVersion().getMajor() == 2); + assert(request_20.getHeader().getProtocolVersion().getMinor() == 0); + request_20.add_batch_item(item); + const auto bytes = request_20.serialize(); + assert(!bytes.empty()); + + size_t offset = 0; + auto request_element = Element::deserialize(bytes, offset); + assert(offset == bytes.size()); + + auto request_header = request_element->getChild(tag::KMIP_TAG_REQUEST_HEADER); + assert(request_header != nullptr); + + auto protocol_version = + request_header->getChild(tag::KMIP_TAG_PROTOCOL_VERSION); + assert(protocol_version != nullptr); + + auto parsed_version = ProtocolVersion::fromElement(protocol_version); + assert(parsed_version.getMajor() == 2); + assert(parsed_version.getMinor() == 0); + + std::cout << "DateTimeExtended KMIP 2.0 request-version test passed" + << std::endl; +} + +void test_date_time_extended_requires_kmip_2_0_for_responses() { + auto response = Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); + + ResponseHeader header; + header.getProtocolVersion().setMajor(1); + header.getProtocolVersion().setMinor(KMIP_1_4); + header.setTimeStamp(1234567890); + header.setBatchCount(1); + response->asStructure()->add(header.toElement()); + + auto batch_item = Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); + batch_item->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, KMIP_OP_GET) + ); + batch_item->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_RESULT_STATUS, KMIP_STATUS_SUCCESS + ) + ); + + auto payload = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createDateTimeExtended( + tag::KMIP_TAG_TIME_STAMP, 1743075078123456LL + ) + ); + batch_item->asStructure()->add(payload); + response->asStructure()->add(batch_item); + + bool threw = false; + try { + (void) ResponseMessage::fromElement(response); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + + header.setProtocolVersion(ProtocolVersion(2, 0)); + auto response_20 = Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); + response_20->asStructure()->add(header.toElement()); + response_20->asStructure()->add(batch_item); + + auto parsed = ResponseMessage::fromElement(response_20); + assert(parsed.getHeader().getProtocolVersion().getMajor() == 2); + assert(parsed.getHeader().getProtocolVersion().getMinor() == 0); + + std::cout << "DateTimeExtended KMIP 2.0 response-version test passed" + << std::endl; +} + void test_request_message() { RequestMessage req; req.getHeader().getProtocolVersion().setMajor(1); @@ -457,6 +611,10 @@ void test_request_header_authentication() { int main() { test_integer(); test_structure(); + test_date_time_extended_round_trip(); + test_date_time_extended_invalid_length(); + test_date_time_extended_requires_kmip_2_0_for_requests(); + test_date_time_extended_requires_kmip_2_0_for_responses(); test_request_message(); test_response_message(); test_typed_response_batch_items(); diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp index fc03854..d5a8408 100644 --- a/kmipcore/tests/test_parsers.cpp +++ b/kmipcore/tests/test_parsers.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include using namespace kmipcore; @@ -29,9 +31,33 @@ namespace { } // namespace +std::vector create_mock_response_bytes_with_result( + int32_t operation, + std::shared_ptr payload, + int32_t result_status, + std::optional result_reason, + const std::optional &result_message +); + // Helper to create a basic success response message with one item std::vector create_mock_response_bytes( int32_t operation, std::shared_ptr payload +) { + return create_mock_response_bytes_with_result( + operation, + std::move(payload), + KMIP_STATUS_SUCCESS, + std::nullopt, + std::nullopt + ); +} + +std::vector create_mock_response_bytes_with_result( + int32_t operation, + std::shared_ptr payload, + int32_t result_status, + std::optional result_reason, + const std::optional &result_message ) { ResponseMessage resp; resp.getHeader().getProtocolVersion().setMajor(1); @@ -42,9 +68,11 @@ std::vector create_mock_response_bytes( ResponseBatchItem item; item.setUniqueBatchItemId(1); item.setOperation(operation); - item.setResultStatus(KMIP_STATUS_SUCCESS); + item.setResultStatus(result_status); + item.setResultReason(result_reason); + item.setResultMessage(result_message); if (payload) { - item.setResponsePayload(payload); + item.setResponsePayload(std::move(payload)); } resp.add_batch_item(item); @@ -53,6 +81,29 @@ std::vector create_mock_response_bytes( return buf.release(); } +void test_response_parser_failure_preserves_reason_code() { + auto bytes = create_mock_response_bytes_with_result( + KMIP_OP_GET, + nullptr, + KMIP_STATUS_OPERATION_FAILED, + KMIP_REASON_ITEM_NOT_FOUND, + std::string("Object missing") + ); + ResponseParser parser(bytes); + + bool threw = false; + try { + [[maybe_unused]] auto resp = parser.getResponse(0); + } catch (const KmipException &e) { + threw = true; + assert(e.code().value() == KMIP_REASON_ITEM_NOT_FOUND); + } + + assert(threw); + std::cout << "ResponseParser failure reason-code preservation test passed" + << std::endl; +} + void test_response_parser_create() { auto payload = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); @@ -202,7 +253,7 @@ void test_key_parser_secret_binary() { auto key_value = Element::createStructure(tag::KMIP_TAG_KEY_VALUE); - const secret_t bytes = {'p', 'a', 's', 's', 0x00, 'x'}; + const std::vector bytes = {'p', 'a', 's', 's', 0x00, 'x'}; key_value->asStructure()->add( Element::createByteString( tag::KMIP_TAG_KEY_MATERIAL, @@ -223,13 +274,14 @@ void test_key_parser_secret_binary() { assert(secret.get_secret_type() == secret_data_type::KMIP_SECDATA_PASSWORD); assert(secret.value() == bytes); + assert(secret.get_state() == state::KMIP_STATE_PRE_ACTIVE); assert(secret.as_text().size() == bytes.size()); std::cout << "KeyParser Secret Binary test passed" << std::endl; } void test_register_secret_request_structure() { - const secret_t secret = {'a', 'b', 0x00, 'c'}; + const std::vector secret = {'a', 'b', 0x00, 'c'}; RegisterSecretRequest req("s-name", "s-group", secret, secret_data_type::KMIP_SECDATA_PASSWORD); auto payload = req.getRequestPayload(); @@ -421,6 +473,7 @@ void test_logger_interface() { int main() { test_response_parser_create(); test_response_parser_locate(); + test_response_parser_failure_preserves_reason_code(); test_key_parser_symmetric(); test_key_parser_secret_binary(); test_register_secret_request_structure(); From 9916e8c8a27abbe0f49a6ef4eab9485e01bb0ef4 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Fri, 27 Mar 2026 17:43:22 +0200 Subject: [PATCH 05/11] PS-10068 Fix PR API refinement https://perconadev.atlassian.net/browse/PS-10949 API cleanup and refinement. Last fixes from "Review of bfd0188". Fix of docs --- kmipclient/CMakeLists.txt | 11 + kmipclient/README.md | 76 +++-- kmipclient/examples/example_get.cpp | 5 +- .../examples/example_get_attributes.cpp | 4 +- kmipclient/examples/example_get_logger.cpp | 6 +- kmipclient/examples/example_register_key.cpp | 40 ++- .../examples/example_register_secret.cpp | 40 ++- kmipclient/include/kmipclient/Key.hpp | 75 +---- kmipclient/include/kmipclient/KeyBase.hpp | 86 ++++++ kmipclient/include/kmipclient/KmipClient.hpp | 37 ++- kmipclient/include/kmipclient/PEMReader.hpp | 39 +++ kmipclient/include/kmipclient/PrivateKey.hpp | 38 +++ kmipclient/include/kmipclient/PublicKey.hpp | 38 +++ .../include/kmipclient/SymmetricKey.hpp | 43 +++ .../include/kmipclient/X509Certificate.hpp | 39 +++ kmipclient/src/Key.cpp | 291 ++++-------------- kmipclient/src/KmipClient.cpp | 89 +++++- kmipclient/src/PEMReader.cpp | 194 ++++++++++++ kmipclient/src/PrivateKey.cpp | 28 ++ kmipclient/src/PublicKey.cpp | 28 ++ kmipclient/src/SymmetricKey.cpp | 83 +++++ kmipclient/src/X509Certificate.cpp | 29 ++ .../tests/KmipClientIntegrationTest.cpp | 119 ++++++- .../tests/KmipClientPoolIntegrationTest.cpp | 25 +- kmipcore/include/kmipcore/kmip_basics.hpp | 2 + kmipcore/include/kmipcore/kmip_requests.hpp | 17 + kmipcore/include/kmipcore/secret.hpp | 6 +- kmipcore/src/key_parser.cpp | 6 - kmipcore/src/kmip_basics.cpp | 78 +++-- kmipcore/src/kmip_formatter.cpp | 17 +- kmipcore/src/kmip_requests.cpp | 121 ++++++++ 31 files changed, 1275 insertions(+), 435 deletions(-) create mode 100644 kmipclient/include/kmipclient/KeyBase.hpp create mode 100644 kmipclient/include/kmipclient/PEMReader.hpp create mode 100644 kmipclient/include/kmipclient/PrivateKey.hpp create mode 100644 kmipclient/include/kmipclient/PublicKey.hpp create mode 100644 kmipclient/include/kmipclient/SymmetricKey.hpp create mode 100644 kmipclient/include/kmipclient/X509Certificate.hpp create mode 100644 kmipclient/src/PEMReader.cpp create mode 100644 kmipclient/src/PrivateKey.cpp create mode 100644 kmipclient/src/PublicKey.cpp create mode 100644 kmipclient/src/SymmetricKey.cpp create mode 100644 kmipclient/src/X509Certificate.cpp diff --git a/kmipclient/CMakeLists.txt b/kmipclient/CMakeLists.txt index ee55dd0..9ac67f2 100644 --- a/kmipclient/CMakeLists.txt +++ b/kmipclient/CMakeLists.txt @@ -21,7 +21,18 @@ add_library( src/IOUtils.hpp include/kmipclient/Kmip.hpp src/Key.cpp + src/PEMReader.cpp + src/SymmetricKey.cpp + src/PublicKey.cpp + src/PrivateKey.cpp + src/X509Certificate.cpp include/kmipclient/Key.hpp + include/kmipclient/KeyBase.hpp + include/kmipclient/PEMReader.hpp + include/kmipclient/SymmetricKey.hpp + include/kmipclient/PublicKey.hpp + include/kmipclient/PrivateKey.hpp + include/kmipclient/X509Certificate.hpp src/StringUtils.cpp src/StringUtils.hpp ) diff --git a/kmipclient/README.md b/kmipclient/README.md index a59812e..36d40d8 100644 --- a/kmipclient/README.md +++ b/kmipclient/README.md @@ -39,7 +39,7 @@ used by implementing the four-method `NetClient` interface. | `kmipclient/Kmip.hpp` | Simplified facade (bundles `NetClientOpenSSL` + `KmipClient`) | | `kmipclient/NetClient.hpp` | Abstract network interface | | `kmipclient/NetClientOpenSSL.hpp` | OpenSSL BIO implementation of `NetClient` | -| `kmipclient/Key.hpp` | Client-level crypto-key type with factory helpers | +| `kmipclient/Key.hpp` | Typed key model umbrella header (`Key`, `SymmetricKey`, `PublicKey`, `PrivateKey`, `X509Certificate`, `PEMReader`) | | `kmipclient/KmipIOException.hpp` | Exception for network/IO errors | | `kmipclient/types.hpp` | Type aliases re-exported from `kmipcore` | | `kmipclient/kmipclient_version.hpp` | Version macros (`KMIPCLIENT_VERSION_STR`) | @@ -87,20 +87,20 @@ Kmip kmip(host, port, client_cert, client_key, server_ca, timeout_ms); auto key_id = kmip.client().op_create_aes_key("mykey", "mygroup"); ``` -### `Key` +### `Key` and factories -`kmipclient::Key` extends `kmipcore::Key` and adds factory helpers: +`kmipclient::Key` is the abstract base class for typed key objects. | Factory | Description | |---|---| -| `Key::aes_from_hex(hex)` | Create AES key from hexadecimal string | -| `Key::aes_from_base64(b64)` | Create AES key from Base64 string | -| `Key::aes_from_value(bytes)` | Create AES key from raw byte vector | -| `Key::generate_aes(size_bits)` | Generate a random AES key (128/192/256 bits) | -| `Key::from_PEM(pem)` | Parse a PEM-encoded certificate/public-key/private-key | +| `SymmetricKey::aes_from_hex(hex)` | Create AES key from hexadecimal string | +| `SymmetricKey::aes_from_base64(b64)` | Create AES key from Base64 string | +| `SymmetricKey::aes_from_value(bytes)` | Create AES key from raw byte vector | +| `SymmetricKey::generate_aes(size_bits)` | Generate a random AES key (128/192/256 bits) | +| `PEMReader::from_PEM(pem)` | Parse PEM into `X509Certificate` / `PublicKey` / `PrivateKey` / `SymmetricKey` | -Objects returned by `op_get_key` expose the following read accessors on -`kmipcore::Key`: +`op_get_key(...)` returns `std::unique_ptr` (polymorphic typed key). +All derived key types expose these common `Key` accessors: | Method | Return type | Description | |---|---|---| @@ -108,7 +108,6 @@ Objects returned by `op_get_key` expose the following read accessors on | `type()` | `KeyType` | Key family (`SYMMETRIC_KEY`, `PUBLIC_KEY`, …) | | `algorithm()` | `cryptographic_algorithm` | KMIP cryptographic algorithm enum | | `usage_mask()` | `cryptographic_usage_mask` | KMIP usage mask flags | -| `size()` | `size_t` | Key length in bytes | | `attributes()` | `const std::unordered_map &` | Full attribute map | | `attribute_value(name)` | `const std::string &` | Single attribute by name (see below) | @@ -119,8 +118,8 @@ attribute it returns a reference to a static empty string rather than throwing: ```cpp auto key = client.op_get_key(id, /*all_attributes=*/true); -auto name = key.attribute_value(KMIP_ATTR_NAME_NAME); // "" if not present -auto state = key.attribute_value(KMIP_ATTR_NAME_STATE); // "" if not present +auto name = key->attribute_value(KMIP_ATTR_NAME_NAME); // "" if not present +auto state = key->attribute_value(KMIP_ATTR_NAME_STATE); // "" if not present if (name.empty()) { /* attribute was not returned by the server */ } ``` @@ -208,8 +207,10 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException |---|---| | `op_create_aes_key(name, group)` | Server-side AES-256 key generation (KMIP CREATE) | | `op_register_key(name, group, key)` | Register an existing key (KMIP REGISTER) | +| `op_register_and_activate_key(name, group, key)` | Register and activate key in one batched request (**experimental**) | | `op_register_secret(name, group, secret)` | Register a secret / password | -| `op_get_key(id [, all_attributes])` | Retrieve a symmetric key with optional attributes | +| `op_register_and_activate_secret(name, group, secret)` | Register and activate secret in one batched request (**experimental**) | +| `op_get_key(id [, all_attributes])` | Retrieve key object (`std::unique_ptr`) with optional attributes | | `op_get_secret(id [, all_attributes])` | Retrieve a secret / password | | `op_activate(id)` | Activate an entity (pre-active → active) | | `op_revoke(id, reason, message, time)` | Revoke/deactivate an entity | @@ -220,6 +221,12 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException | `op_get_attribute_list(id)` | List attribute names for an entity | | `op_get_attributes(id, attr_names)` | Retrieve specific attributes by name | +> **Experimental methods** +> +> `op_register_and_activate_key(...)` and `op_register_and_activate_secret(...)` +> are experimental. They rely on batched Register+Activate behavior and are +> known **not to work with pyKmip server**. + --- ## Usage examples @@ -236,13 +243,12 @@ KmipClient client(net_client); try { auto key = client.op_get_key(id, /*all_attributes=*/true); - // key.value() → raw key bytes (std::vector) - // key.size() → key length in bytes - // key.algorithm() → cryptographic_algorithm enum - // key.usage_mask() → cryptographic_usage_mask flags - // key.attribute_value(name) → attribute string, "" if absent (noexcept) - auto state = key.attribute_value(KMIP_ATTR_NAME_STATE); - auto kname = key.attribute_value(KMIP_ATTR_NAME_NAME); + // key->value() → raw key bytes (std::vector) + // key->algorithm() → cryptographic_algorithm enum + // key->usage_mask() → cryptographic_usage_mask flags + // key->attribute_value(name) → attribute string, "" if absent (noexcept) + auto state = key->attribute_value(KMIP_ATTR_NAME_STATE); + auto kname = key->attribute_value(KMIP_ATTR_NAME_NAME); } catch (const std::exception &e) { std::cerr << e.what() << '\n'; } @@ -274,8 +280,17 @@ auto key_id = kmip.client().op_create_aes_key("mykey", "mygroup"); NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 200); KmipClient client(net_client); -auto k = Key::aes_from_hex("0102030405060708090a0b0c0d0e0f10..."); -auto id = client.op_register_key("mykey", "mygroup", k); +auto generated = SymmetricKey::generate_aes(256); +auto id = client.op_register_key("mykey", "mygroup", generated); +auto fetched = client.op_get_key(id); + +client.op_revoke( + id, + revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, + "cleanup", + 0 +); +client.op_destroy(id); ``` ### Register a secret / password @@ -284,13 +299,22 @@ auto id = client.op_register_key("mykey", "mygroup", k); Kmip kmip(host, port, client_cert, client_key, server_ca, 200); auto s = Secret::from_text("s3cr3t!", secret_data_type::KMIP_SECDATA_PASSWORD); auto id = kmip.client().op_register_secret("mysecret", "mygroup", s); +auto fetched = kmip.client().op_get_secret(id); + +kmip.client().op_revoke( + id, + revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, + "cleanup", + 0 +); +kmip.client().op_destroy(id); ``` ### Lifecycle: activate → revoke → destroy ```cpp client.op_activate(id); -client.op_revoke(id, UNSPECIFIED, "Deactivate", 0L); +client.op_revoke(id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, "Deactivate", 0L); client.op_destroy(id); ``` @@ -424,8 +448,8 @@ ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:print_stats=1" \ | Binary | Description | |---|---| | `example_create_aes` | Create a server-side AES-256 key | -| `example_register_key` | Register an existing AES key | -| `example_register_secret` | Register a secret / password | +| `example_register_key` | Generate local AES-256 key, register it, fetch it, then revoke+destroy | +| `example_register_secret` | Generate local secret, register it, fetch it, then revoke+destroy | | `example_get` | Retrieve a symmetric key by ID | | `example_get_logger` | Same as `example_get` with protocol-level TTLV logging | | `example_get_secret` | Retrieve a secret by ID | diff --git a/kmipclient/examples/example_get.cpp b/kmipclient/examples/example_get.cpp index c50dda2..eb1b17a 100644 --- a/kmipclient/examples/example_get.cpp +++ b/kmipclient/examples/example_get.cpp @@ -19,7 +19,6 @@ #include "kmipclient/KmipClient.hpp" #include "kmipclient/NetClientOpenSSL.hpp" #include "kmipclient/kmipclient_version.hpp" -#include "kmipcore/kmip_basics.hpp" #include @@ -49,9 +48,9 @@ int main(int argc, char **argv) { std::string id = argv[6]; auto key = client.op_get_key(id); std::cout << "Key: 0x"; - print_hex(key.value()); + print_hex(key->value()); std::cout << "Attributes:" << std::endl; - const auto &attrs = key.attributes(); + const auto &attrs = key->attributes(); for (const auto &[attr_name, attr_value] : attrs) { std::cout << " " << attr_name << ": " << attr_value << std::endl; } diff --git a/kmipclient/examples/example_get_attributes.cpp b/kmipclient/examples/example_get_attributes.cpp index a604cfd..31dfc27 100644 --- a/kmipclient/examples/example_get_attributes.cpp +++ b/kmipclient/examples/example_get_attributes.cpp @@ -61,13 +61,13 @@ int main(int argc, char **argv) { std::string id = argv[6]; auto key = client.op_get_key(id); std::cout << "Key: 0x"; - print_hex(key.value()); + print_hex(key->value()); auto attr_names = client.op_get_attribute_list(id); auto attr = client.op_get_attributes(id, attr_names); std::cout << "======= key attributes: =======" << std::endl; - print_attributes(key.attributes()); + print_attributes(key->attributes()); std::cout << "======= all attributes: =======" << std::endl; print_attributes(attr); } catch (const std::exception &e) { diff --git a/kmipclient/examples/example_get_logger.cpp b/kmipclient/examples/example_get_logger.cpp index e8545c8..bef3aed 100644 --- a/kmipclient/examples/example_get_logger.cpp +++ b/kmipclient/examples/example_get_logger.cpp @@ -69,10 +69,10 @@ int main(int argc, char **argv) { std::string id = argv[6]; auto key = client.op_get_key(id); std::cout << "Key: 0x"; - print_hex(key.value()); - std::cout << "State: " << key.attribute_value(KMIP_ATTR_NAME_STATE) + print_hex(key->value()); + std::cout << "State: " << key->attribute_value(KMIP_ATTR_NAME_STATE) << std::endl; - std::cout << "Name: " << key.attribute_value(KMIP_ATTR_NAME_NAME) + std::cout << "Name: " << key->attribute_value(KMIP_ATTR_NAME_NAME) << std::endl; } catch (const std::exception &e) { std::cerr << "Can not get key with id:" << argv[6] << " Cause: " diff --git a/kmipclient/examples/example_register_key.cpp b/kmipclient/examples/example_register_key.cpp index fea3e09..47a4c16 100644 --- a/kmipclient/examples/example_register_key.cpp +++ b/kmipclient/examples/example_register_key.cpp @@ -17,18 +17,32 @@ #include "kmipclient/KmipClient.hpp" #include "kmipclient/NetClientOpenSSL.hpp" #include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_errors.hpp" +#include #include using namespace kmipclient; +namespace { + +void print_hex(const std::vector &bytes) { + for (const auto b : bytes) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); + } + std::cout << std::dec << std::endl; +} + +} // namespace + int main(int argc, char **argv) { std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; - if (argc < 8) { + if (argc < 7) { std::cerr << "Usage:example_register_key " - " " + " " << std::endl; return -1; } @@ -36,9 +50,25 @@ int main(int argc, char **argv) { KmipClient client(net_client); try { - auto k = Key::aes_from_hex(argv[7]); - const auto opt_id = client.op_register_key(argv[6], "TestGroup", k); - std::cout << "Key registered. ID: " << opt_id << std::endl; + auto generated_key = SymmetricKey::generate_aes(256); + std::cout << "Generated AES-256 key (hex): "; + print_hex(generated_key.value()); + + const auto key_id = client.op_register_key(argv[6], "TestGroup", generated_key); + std::cout << "Key registered. ID: " << key_id << std::endl; + + auto fetched_key = client.op_get_key(key_id); + std::cout << "Fetched key from server (hex): "; + print_hex(fetched_key->value()); + + (void) client.op_revoke( + key_id, + revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, + "example cleanup", + 0 + ); + (void) client.op_destroy(key_id); + std::cout << "Key revoked and destroyed: " << key_id << std::endl; } catch (std::exception &e) { std::cerr << "Can not register key:" << argv[6] << " Cause: " << e.what() << std::endl; diff --git a/kmipclient/examples/example_register_secret.cpp b/kmipclient/examples/example_register_secret.cpp index 4805fd5..4112d80 100644 --- a/kmipclient/examples/example_register_secret.cpp +++ b/kmipclient/examples/example_register_secret.cpp @@ -19,31 +19,65 @@ #include "kmipclient/KmipClient.hpp" #include "kmipclient/kmipclient_version.hpp" +#include #include +#include using namespace kmipclient; +namespace { + +void print_hex(const std::vector &bytes) { + for (const auto b : bytes) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); + } + std::cout << std::dec << std::endl; +} + +} // namespace + int main(int argc, char **argv) { std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; - if (argc < 8) { + 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); try { + const std::string generated_secret_text = "example_secret_2026"; auto secret = Secret::from_text( - argv[7], secret_data_type::KMIP_SECDATA_PASSWORD + generated_secret_text, secret_data_type::KMIP_SECDATA_PASSWORD ); + std::cout << "Generated secret (text): " << secret.as_text() << std::endl; + std::cout << "Generated secret (hex): "; + print_hex(secret.value()); + auto id = kmip.client().op_register_secret( argv[6], "TestGroup", secret ); std::cout << "Secret ID: " << id << std::endl; + + auto fetched_secret = kmip.client().op_get_secret(id); + std::cout << "Fetched secret (text): " << fetched_secret.as_text() + << std::endl; + std::cout << "Fetched secret (hex): "; + print_hex(fetched_secret.value()); + + (void) kmip.client().op_revoke( + id, + revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, + "example cleanup", + 0 + ); + (void) kmip.client().op_destroy(id); + std::cout << "Secret revoked and destroyed: " << id << std::endl; } catch (std::exception &e) { std::cerr << "Can not register secret with name:" << argv[6] << " Cause: " << e.what() << std::endl; diff --git a/kmipclient/include/kmipclient/Key.hpp b/kmipclient/include/kmipclient/Key.hpp index 4bf4f97..2d71c65 100644 --- a/kmipclient/include/kmipclient/Key.hpp +++ b/kmipclient/include/kmipclient/Key.hpp @@ -18,74 +18,11 @@ #ifndef KMIPCLIENT_KEY_HPP #define KMIPCLIENT_KEY_HPP -#include "kmipclient/types.hpp" -#include "kmipcore/key.hpp" - -#include - -namespace kmipclient { - - /** - * Client-level crypto key extending the core Key with convenience - * factory methods for creating keys from hex, base64, PEM, etc. - */ - class Key : public kmipcore::Key { - public: - // Inherit all base-class constructors - using kmipcore::Key::Key; - - /** - * @brief Implicitly wraps an existing core key object. - * @param base Source key instance. - */ - Key(const kmipcore::Key &base) - : kmipcore::Key(base) { - } // NOLINT(google-explicit-constructor) - - /** @brief Constructs an empty key instance. */ - Key() = default; - - /** - * @brief Creates an AES symmetric key from a hexadecimal string. - * @param hex Hex-encoded key bytes. - * @return Initialized AES key. - * @throws kmipcore::KmipException when decoding fails. - */ - static Key aes_from_hex(const std::string &hex); - /** - * @brief Creates an AES symmetric key from a Base64 string. - * @param base64 Base64-encoded key bytes. - * @return Initialized AES key. - * @throws kmipcore::KmipException when decoding fails. - */ - static Key aes_from_base64(const std::string &base64); - - /** - * @brief Creates an AES symmetric key from raw bytes. - * @param val Binary key value. - * @return Initialized AES key. - */ - static Key aes_from_value(const std::vector &val); - /** - * @brief Generates a random AES key of the requested size. - * @param size_bits Key size in bits. Supported values: 128, 192, 256. - * @return Randomly generated AES key. - * @throws kmipcore::KmipException when RNG fails or size is unsupported. - */ - static Key generate_aes(size_t size_bits); - /** - * @brief Parses a PEM payload into a KMIP key-like object. - * - * The parser recognizes X.509 certificate, public key, and private key - * PEM blocks and maps them to the appropriate KMIP representation. - * - * @param pem PEM-formatted input. - * @return Key mapped from the input PEM block. - * @throws kmipcore::KmipException when parsing fails. - */ - static Key from_PEM(const std::string &pem); - }; - -} // namespace kmipclient +#include "kmipclient/KeyBase.hpp" +#include "kmipclient/PEMReader.hpp" +#include "kmipclient/PrivateKey.hpp" +#include "kmipclient/PublicKey.hpp" +#include "kmipclient/SymmetricKey.hpp" +#include "kmipclient/X509Certificate.hpp" #endif // KMIPCLIENT_KEY_HPP diff --git a/kmipclient/include/kmipclient/KeyBase.hpp b/kmipclient/include/kmipclient/KeyBase.hpp new file mode 100644 index 0000000..e35dbbb --- /dev/null +++ b/kmipclient/include/kmipclient/KeyBase.hpp @@ -0,0 +1,86 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_KEY_BASE_HPP +#define KMIPCLIENT_KEY_BASE_HPP + +#include "kmipclient/types.hpp" +#include "kmipcore/key.hpp" + +#include +#include +#include +#include + +namespace kmipclient { + + /** + * Client-level key abstraction. + * + * kmipcore keeps a single protocol-oriented key value type, while this layer + * exposes distinct key families via polymorphism. + */ + class Key { + public: + virtual ~Key() = default; + + Key(const Key &) = default; + Key &operator=(const Key &) = default; + Key(Key &&) noexcept = default; + Key &operator=(Key &&) noexcept = default; + + [[nodiscard]] const std::vector &value() const noexcept { + return key_value_; + } + [[nodiscard]] const std::unordered_map &attributes() const noexcept { + return key_attributes_; + } + [[nodiscard]] const std::string &attribute_value(const std::string &name) const noexcept; + void set_attribute(const std::string &name, const std::string &val) noexcept; + [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { + return crypto_usage_mask_; + } + [[nodiscard]] cryptographic_algorithm algorithm() const noexcept { + return crypto_algorithm_; + } + + [[nodiscard]] virtual KeyType type() const noexcept = 0; + [[nodiscard]] virtual std::unique_ptr clone() const = 0; + + /** Build protocol-level representation from the client key object. */ + [[nodiscard]] kmipcore::Key to_core_key() const; + /** Build the corresponding client key subclass from protocol-level data. */ + [[nodiscard]] static std::unique_ptr from_core_key(const kmipcore::Key &core_key); + + Key( + const std::vector &value, + cryptographic_algorithm algo = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask usage_mask = cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + const std::unordered_map &attributes = {} + ); + + private: + std::vector key_value_; + std::unordered_map key_attributes_; + cryptographic_algorithm crypto_algorithm_ = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; + cryptographic_usage_mask crypto_usage_mask_ = cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET; + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_KEY_BASE_HPP + diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index c6193fe..fc61f17 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -74,6 +74,22 @@ namespace kmipclient { const std::string &name, const std::string &group, const Key &k ) const; + /** + * @brief Executes KMIP Register and Activate for a key object in one request. + * + * The request is sent as a single KMIP message with multiple batch items: + * Register followed by Activate (using an ID placeholder). + * + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @param k Key material and metadata to register. + * @return Unique identifier assigned by the KMIP server. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] std::string op_register_and_activate_key( + const std::string &name, const std::string &group, const Key &k + ) const; + /** * @brief Executes KMIP Register for Secret Data. * @param name Value of the KMIP "Name" attribute. @@ -88,6 +104,24 @@ namespace kmipclient { const Secret &secret ) const; + /** + * @brief Executes KMIP Register and Activate for Secret Data in one request. + * + * The request is sent as a single KMIP message with multiple batch items: + * Register followed by Activate (using an ID placeholder). + * + * @param name Value of the KMIP "Name" attribute. + * @param group Value of the KMIP "Object Group" attribute. + * @param secret Secret payload and type descriptor. + * @return Unique identifier assigned by the KMIP server. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] std::string op_register_and_activate_secret( + const std::string &name, + const std::string &group, + const Secret &secret + ) const; + /** * @brief Executes KMIP Create to generate a server-side AES-256 key. @@ -106,7 +140,8 @@ namespace kmipclient { * @return Decoded key object. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] Key op_get_key(const std::string &id, bool all_attributes = false) const; + [[nodiscard]] std::unique_ptr + op_get_key(const std::string &id, bool all_attributes = false) const; /** * @brief Executes KMIP Get and decodes a secret object. diff --git a/kmipclient/include/kmipclient/PEMReader.hpp b/kmipclient/include/kmipclient/PEMReader.hpp new file mode 100644 index 0000000..cf446c8 --- /dev/null +++ b/kmipclient/include/kmipclient/PEMReader.hpp @@ -0,0 +1,39 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_PEM_READER_HPP +#define KMIPCLIENT_PEM_READER_HPP + +#include "kmipclient/KeyBase.hpp" + +#include +#include + +namespace kmipclient { + + /** + * Factory that parses PEM text and returns an object of the matching client key type. + */ + class PEMReader { + public: + [[nodiscard]] static std::unique_ptr from_PEM(const std::string &pem); + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_PEM_READER_HPP + diff --git a/kmipclient/include/kmipclient/PrivateKey.hpp b/kmipclient/include/kmipclient/PrivateKey.hpp new file mode 100644 index 0000000..a416722 --- /dev/null +++ b/kmipclient/include/kmipclient/PrivateKey.hpp @@ -0,0 +1,38 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_PRIVATE_KEY_HPP +#define KMIPCLIENT_PRIVATE_KEY_HPP + +#include "kmipclient/KeyBase.hpp" + +namespace kmipclient { + + class PrivateKey final : public Key { + public: + using Key::Key; + + [[nodiscard]] KeyType type() const noexcept override { + return KeyType::PRIVATE_KEY; + } + [[nodiscard]] std::unique_ptr clone() const override; + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_PRIVATE_KEY_HPP + diff --git a/kmipclient/include/kmipclient/PublicKey.hpp b/kmipclient/include/kmipclient/PublicKey.hpp new file mode 100644 index 0000000..767f5ed --- /dev/null +++ b/kmipclient/include/kmipclient/PublicKey.hpp @@ -0,0 +1,38 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_PUBLIC_KEY_HPP +#define KMIPCLIENT_PUBLIC_KEY_HPP + +#include "kmipclient/KeyBase.hpp" + +namespace kmipclient { + + class PublicKey final : public Key { + public: + using Key::Key; + + [[nodiscard]] KeyType type() const noexcept override { + return KeyType::PUBLIC_KEY; + } + [[nodiscard]] std::unique_ptr clone() const override; + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_PUBLIC_KEY_HPP + diff --git a/kmipclient/include/kmipclient/SymmetricKey.hpp b/kmipclient/include/kmipclient/SymmetricKey.hpp new file mode 100644 index 0000000..4d2b5a4 --- /dev/null +++ b/kmipclient/include/kmipclient/SymmetricKey.hpp @@ -0,0 +1,43 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_SYMMETRIC_KEY_HPP +#define KMIPCLIENT_SYMMETRIC_KEY_HPP + +#include "kmipclient/KeyBase.hpp" + +namespace kmipclient { + + class SymmetricKey final : public Key { + public: + using Key::Key; + + [[nodiscard]] KeyType type() const noexcept override { + return KeyType::SYMMETRIC_KEY; + } + [[nodiscard]] std::unique_ptr clone() const override; + + [[nodiscard]] static SymmetricKey aes_from_hex(const std::string &hex); + [[nodiscard]] static SymmetricKey aes_from_base64(const std::string &base64); + [[nodiscard]] static SymmetricKey aes_from_value(const std::vector &val); + [[nodiscard]] static SymmetricKey generate_aes(size_t size_bits); + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_SYMMETRIC_KEY_HPP + diff --git a/kmipclient/include/kmipclient/X509Certificate.hpp b/kmipclient/include/kmipclient/X509Certificate.hpp new file mode 100644 index 0000000..5375ac4 --- /dev/null +++ b/kmipclient/include/kmipclient/X509Certificate.hpp @@ -0,0 +1,39 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCLIENT_X509_CERTIFICATE_HPP +#define KMIPCLIENT_X509_CERTIFICATE_HPP + +#include "kmipclient/KeyBase.hpp" + +namespace kmipclient { + + class X509Certificate final : public Key { + public: + using Key::Key; + + [[nodiscard]] KeyType type() const noexcept override { + return KeyType::CERTIFICATE; + } + [[nodiscard]] std::unique_ptr clone() const override; + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_X509_CERTIFICATE_HPP + + diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp index 3a5df82..b97ac8f 100644 --- a/kmipclient/src/Key.cpp +++ b/kmipclient/src/Key.cpp @@ -17,258 +17,71 @@ #include "kmipclient/Key.hpp" -#include "StringUtils.hpp" -#include "kmipclient/types.hpp" #include "kmipcore/kmip_errors.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - namespace kmipclient { - namespace { - // Try to parse BIO as X509 certificate and return Key if successful - std::optional try_parse_certificate(BIO *bio) { - X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); - if (!cert) { - return std::nullopt; - } - - EVP_PKEY *pkey = X509_get_pubkey(cert); - if (!pkey) { - X509_free(cert); - return std::nullopt; - } - - unsigned char *der = nullptr; - int der_len = i2d_PUBKEY(pkey, &der); - EVP_PKEY_free(pkey); - X509_free(cert); - - if (der_len <= 0) { - if (der) { - OPENSSL_free(der); - } - return std::nullopt; - } - - std::vector key_bytes(der, der + der_len); - OPENSSL_free(der); - - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "certificate_public_key"; - return Key( - key_bytes, - KeyType::PUBLIC_KEY, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); - } - - // Try to parse BIO as private key - std::optional try_parse_private_key(BIO *bio) { - EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); - if (!pkey) { - return std::nullopt; - } - - unsigned char *der = nullptr; - int der_len = i2d_PrivateKey(pkey, &der); - EVP_PKEY_free(pkey); - - if (der_len <= 0) { - if (der) { - OPENSSL_free(der); - } - return std::nullopt; - } - - std::vector key_bytes(der, der + der_len); - OPENSSL_free(der); - - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "private_key"; - return Key( - key_bytes, - KeyType::PRIVATE_KEY, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); - } - - // Try to parse BIO as public key - std::optional try_parse_public_key(BIO *bio) { - EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); - if (!pkey) { - return std::nullopt; - } - - unsigned char *der = nullptr; - int der_len = i2d_PUBKEY(pkey, &der); - EVP_PKEY_free(pkey); - - if (der_len <= 0) { - if (der) { - OPENSSL_free(der); - } - return std::nullopt; - } - - std::vector key_bytes(der, der + der_len); - OPENSSL_free(der); - - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "public_key"; - return Key( - key_bytes, - KeyType::PUBLIC_KEY, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); - } - - /** Validates AES byte-vector size and constructs a Key. */ - Key make_aes_key(const std::vector &bytes) { - const size_t size = bytes.size(); - if (size != 16 && size != 24 && size != 32) { - throw kmipcore::KmipException{ - -1, - std::string("Invalid AES key length: ") + std::to_string(size * 8) + - " bits. Should be 128, 192 or 256 bits" - }; - } - auto key = Key( - bytes, - KeyType::SYMMETRIC_KEY, - cryptographic_algorithm::KMIP_CRYPTOALG_AES, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - {} - ); - return key; - } - - // Try to detect an AES/raw key encoded in PEM-like text by extracting - // base64 between headers - std::optional try_parse_aes_from_pem_text(const std::string &pem) { - // Find the first PEM header and footer lines, extract base64 content - // between them - std::istringstream iss(pem); - std::string line; - bool in_pem = false; - std::string b64; - while (std::getline(iss, line)) { - if (!in_pem) { - if (line.rfind("-----BEGIN", 0) == 0) { - in_pem = true; - } - } else { - if (line.rfind("-----END", 0) == 0) { - break; - } - // skip header/footer and empty lines - if (line.empty()) { - continue; - } - b64 += line; - } - } - - if (b64.empty()) { - return std::nullopt; - } - - try { - auto decoded = StringUtils::fromBase64(b64); - size_t size = decoded.size(); - if (size == 16 || size == 24 || size == 32) { - return make_aes_key(decoded); - } - } catch (...) // any parsing errors - { - return std::nullopt; - } - - return std::nullopt; - } - } // anonymous namespace - - Key Key::aes_from_hex(const std::string &hex) { - return make_aes_key(StringUtils::fromHex(hex)); + Key::Key( + const std::vector &value, + cryptographic_algorithm algo, + cryptographic_usage_mask usage_mask, + const std::unordered_map &attributes + ) + : key_value_(value), + key_attributes_(attributes), + crypto_algorithm_(algo), + crypto_usage_mask_(usage_mask) { } - Key Key::aes_from_base64(const std::string &base64) { - return make_aes_key(StringUtils::fromBase64(base64)); + const std::string &Key::attribute_value(const std::string &name) const noexcept { + static const std::string empty; + const auto it = key_attributes_.find(name); + return it != key_attributes_.end() ? it->second : empty; } - Key Key::aes_from_value(const std::vector &val) { - return make_aes_key(std::vector(val)); + void Key::set_attribute(const std::string &name, const std::string &val) noexcept { + key_attributes_[name] = val; } - Key Key::from_PEM(const std::string &pem) { - // 1) Try as certificate - BIO *bio = BIO_new_mem_buf(pem.data(), static_cast(pem.size())); - if (!bio) { - throw kmipcore::KmipException("Failed to create BIO for PEM data"); - } - - if (auto cert_key = try_parse_certificate(bio); cert_key.has_value()) { - BIO_free(bio); - return cert_key.value(); - } - - (void) BIO_reset(bio); - if (auto priv_key = try_parse_private_key(bio); priv_key.has_value()) { - BIO_free(bio); - return priv_key.value(); - } - - (void) BIO_reset(bio); - if (auto pub_key = try_parse_public_key(bio); pub_key.has_value()) { - BIO_free(bio); - return pub_key.value(); - } - - BIO_free(bio); - - // 2) Try to detect an AES/raw key encoded in PEM text (base64 between - // headers) - if (auto aes_key = try_parse_aes_from_pem_text(pem); aes_key.has_value()) { - return aes_key.value(); - } - - throw kmipcore::KmipException( - kmipcore::KMIP_NOT_IMPLEMENTED, - "Unsupported PEM format or not implemented" - ); + kmipcore::Key Key::to_core_key() const { + return kmipcore::Key(value(), type(), algorithm(), usage_mask(), attributes()); } - Key Key::generate_aes(size_t size_bits) { - if (size_bits != 128 && size_bits != 192 && size_bits != 256) { - throw kmipcore::KmipException( - "Unsupported AES key size. Use 128, 192 or 256 bits" - ); + std::unique_ptr Key::from_core_key(const kmipcore::Key &core_key) { + switch (core_key.type()) { + case KeyType::SYMMETRIC_KEY: + return std::make_unique( + core_key.value(), + core_key.algorithm(), + core_key.usage_mask(), + core_key.attributes() + ); + case KeyType::PUBLIC_KEY: + return std::make_unique( + core_key.value(), + core_key.algorithm(), + core_key.usage_mask(), + core_key.attributes() + ); + case KeyType::PRIVATE_KEY: + return std::make_unique( + core_key.value(), + core_key.algorithm(), + core_key.usage_mask(), + core_key.attributes() + ); + case KeyType::CERTIFICATE: + return std::make_unique( + core_key.value(), + core_key.algorithm(), + core_key.usage_mask(), + core_key.attributes() + ); + case KeyType::UNSET: + default: + throw kmipcore::KmipException("Unsupported key type in core->client conversion"); } + } - size_t size_bytes = size_bits / 8; - std::vector buf(size_bytes); - if (1 != RAND_bytes(buf.data(), static_cast(size_bytes))) { - unsigned long err = ERR_get_error(); - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - throw kmipcore::KmipException( - std::string("OpenSSL RAND_bytes failed: ") + err_buf - ); - } - return make_aes_key(buf); - } } // namespace kmipclient diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index e0c7939..1e0b8d0 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -46,6 +46,16 @@ static const std::string &require_attr( return it->second; } +static kmipcore::RequestBatchItem make_activate_using_id_placeholder_request() { + kmipcore::RequestBatchItem item; + item.setOperation(kmipcore::KMIP_OP_ACTIVATE); + // KMIP ID Placeholder is used by omitting Unique Identifier in this batch item payload. + item.setRequestPayload( + kmipcore::Element::createStructure(kmipcore::tag::KMIP_TAG_REQUEST_PAYLOAD) + ); + return item; +} + static std::vector default_get_attrs(bool all_attributes) { if (all_attributes) { return {}; @@ -106,7 +116,7 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::RegisterSymmetricKeyRequest(name, group, k.value()) + kmipcore::RegisterKeyRequest(name, group, k.to_core_key()) ); std::vector response_bytes; @@ -122,6 +132,37 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state .getUniqueIdentifier(); } + std::string KmipClient::op_register_and_activate_key( + const std::string &name, + const std::string &group, + const Key &k + ) const { + kmipcore::RequestMessage request; + request.getHeader().setBatchOrderOption(true); + const auto register_item_id = request.add_batch_item( + kmipcore::RegisterKeyRequest(name, group, k.to_core_key()) + ); + const auto activate_item_id = request.add_batch_item( + make_activate_using_id_placeholder_request() + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + const auto id = rf + .getResponseByBatchItemId( + register_item_id + ) + .getUniqueIdentifier(); + (void) rf.getResponseByBatchItemId( + activate_item_id + ); + return id; + } + std::string KmipClient::op_register_secret( const std::string &name, const std::string &group, @@ -150,6 +191,42 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state .getUniqueIdentifier(); } + std::string KmipClient::op_register_and_activate_secret( + const std::string &name, + const std::string &group, + const Secret &secret + ) const { + kmipcore::RequestMessage request; + request.getHeader().setBatchOrderOption(true); + const auto register_item_id = request.add_batch_item( + kmipcore::RegisterSecretRequest( + name, + group, + secret.value(), + secret.get_secret_type() + ) + ); + const auto activate_item_id = request.add_batch_item( + make_activate_using_id_placeholder_request() + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + const auto id = rf + .getResponseByBatchItemId( + register_item_id + ) + .getUniqueIdentifier(); + (void) rf.getResponseByBatchItemId( + activate_item_id + ); + return id; + } + std::string KmipClient::op_create_aes_key( const std::string &name, const std::string &group ) const { @@ -171,7 +248,10 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state .getUniqueIdentifier(); } - Key KmipClient::op_get_key(const std::string &id, bool all_attributes) const { + std::unique_ptr KmipClient::op_get_key( + const std::string &id, + bool all_attributes + ) const { kmipcore::RequestMessage request; const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); @@ -189,7 +269,8 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state rf.getResponseByBatchItemId( get_item_id ); - auto key = kmipcore::KeyParser::parseGetKeyResponse(get_response); + auto core_key = kmipcore::KeyParser::parseGetKeyResponse(get_response); + auto key = Key::from_core_key(core_key); auto attrs_response = rf.getResponseByBatchItemId( @@ -204,7 +285,7 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state // Copy all attributes from response to key for (const auto &item : attrs) { - key.set_attribute(item.first, item.second); + key->set_attribute(item.first, item.second); } return key; diff --git a/kmipclient/src/PEMReader.cpp b/kmipclient/src/PEMReader.cpp new file mode 100644 index 0000000..2f8e20c --- /dev/null +++ b/kmipclient/src/PEMReader.cpp @@ -0,0 +1,194 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/PEMReader.hpp" + +#include "kmipclient/X509Certificate.hpp" +#include "kmipclient/PrivateKey.hpp" +#include "kmipclient/PublicKey.hpp" +#include "kmipclient/SymmetricKey.hpp" +#include "kmipclient/types.hpp" +#include "kmipcore/kmip_errors.hpp" + +#include +#include +#include +#include + +namespace kmipclient { + + namespace { + + std::optional try_parse_x509_certificate(BIO *bio) { + X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (!cert) { + return std::nullopt; + } + + unsigned char *der = nullptr; + const int der_len = i2d_X509(cert, &der); + X509_free(cert); + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector cert_bytes(der, der + der_len); + OPENSSL_free(der); + + std::unordered_map attrs; + attrs[KMIP_ATTR_NAME_NAME] = "certificate"; + return X509Certificate( + cert_bytes, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + std::optional try_parse_private_key(BIO *bio) { + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + if (!pkey) { + return std::nullopt; + } + + unsigned char *der = nullptr; + const int der_len = i2d_PrivateKey(pkey, &der); + EVP_PKEY_free(pkey); + + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector key_bytes(der, der + der_len); + OPENSSL_free(der); + + std::unordered_map attrs; + attrs[KMIP_ATTR_NAME_NAME] = "private_key"; + return PrivateKey( + key_bytes, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + std::optional try_parse_public_key(BIO *bio) { + EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); + if (!pkey) { + return std::nullopt; + } + + unsigned char *der = nullptr; + const int der_len = i2d_PUBKEY(pkey, &der); + EVP_PKEY_free(pkey); + + if (der_len <= 0) { + if (der) { + OPENSSL_free(der); + } + return std::nullopt; + } + + std::vector key_bytes(der, der + der_len); + OPENSSL_free(der); + + std::unordered_map attrs; + attrs[KMIP_ATTR_NAME_NAME] = "public_key"; + return PublicKey( + key_bytes, + cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, + attrs + ); + } + + std::optional try_parse_aes_from_pem_text(const std::string &pem) { + std::istringstream iss(pem); + std::string line; + bool in_pem = false; + std::string b64; + while (std::getline(iss, line)) { + if (!in_pem) { + if (line.rfind("-----BEGIN", 0) == 0) { + in_pem = true; + } + continue; + } + if (line.rfind("-----END", 0) == 0) { + break; + } + if (!line.empty()) { + b64 += line; + } + } + + if (b64.empty()) { + return std::nullopt; + } + + try { + return SymmetricKey::aes_from_base64(b64); + } catch (...) { + return std::nullopt; + } + } + + } // namespace + + std::unique_ptr PEMReader::from_PEM(const std::string &pem) { + BIO *bio = BIO_new_mem_buf(pem.data(), static_cast(pem.size())); + if (!bio) { + throw kmipcore::KmipException("Failed to create BIO for PEM data"); + } + + if (auto x509_cert = try_parse_x509_certificate(bio); x509_cert.has_value()) { + BIO_free(bio); + return std::make_unique(*x509_cert); + } + + (void) BIO_reset(bio); + if (auto priv_key = try_parse_private_key(bio); priv_key.has_value()) { + BIO_free(bio); + return std::make_unique(*priv_key); + } + + (void) BIO_reset(bio); + if (auto pub_key = try_parse_public_key(bio); pub_key.has_value()) { + BIO_free(bio); + return std::make_unique(*pub_key); + } + + BIO_free(bio); + + if (auto aes_key = try_parse_aes_from_pem_text(pem); aes_key.has_value()) { + return std::make_unique(*aes_key); + } + + throw kmipcore::KmipException( + kmipcore::KMIP_NOT_IMPLEMENTED, + "Unsupported PEM format or not implemented" + ); + } + +} // namespace kmipclient + diff --git a/kmipclient/src/PrivateKey.cpp b/kmipclient/src/PrivateKey.cpp new file mode 100644 index 0000000..ea50ffb --- /dev/null +++ b/kmipclient/src/PrivateKey.cpp @@ -0,0 +1,28 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/PrivateKey.hpp" + +namespace kmipclient { + + + std::unique_ptr PrivateKey::clone() const { + return std::make_unique(*this); + } + +} // namespace kmipclient + diff --git a/kmipclient/src/PublicKey.cpp b/kmipclient/src/PublicKey.cpp new file mode 100644 index 0000000..d2ea676 --- /dev/null +++ b/kmipclient/src/PublicKey.cpp @@ -0,0 +1,28 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/PublicKey.hpp" + +namespace kmipclient { + + + std::unique_ptr PublicKey::clone() const { + return std::make_unique(*this); + } + +} // namespace kmipclient + diff --git a/kmipclient/src/SymmetricKey.cpp b/kmipclient/src/SymmetricKey.cpp new file mode 100644 index 0000000..352082b --- /dev/null +++ b/kmipclient/src/SymmetricKey.cpp @@ -0,0 +1,83 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/SymmetricKey.hpp" + +#include "StringUtils.hpp" +#include "kmipcore/kmip_errors.hpp" + +#include +#include + +namespace kmipclient { + + namespace { + + SymmetricKey make_aes_key(const std::vector &bytes) { + const size_t size = bytes.size(); + if (size != 16 && size != 24 && size != 32) { + throw kmipcore::KmipException{ + -1, + std::string("Invalid AES key length: ") + std::to_string(size * 8) + + " bits. Should be 128, 192 or 256 bits" + }; + } + return SymmetricKey( + bytes, + cryptographic_algorithm::KMIP_CRYPTOALG_AES, + cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET + ); + } + + } // anonymous namespace + + + std::unique_ptr SymmetricKey::clone() const { + return std::make_unique(*this); + } + + SymmetricKey SymmetricKey::aes_from_hex(const std::string &hex) { + return make_aes_key(StringUtils::fromHex(hex)); + } + + SymmetricKey SymmetricKey::aes_from_base64(const std::string &base64) { + return make_aes_key(StringUtils::fromBase64(base64)); + } + + SymmetricKey SymmetricKey::aes_from_value(const std::vector &val) { + return make_aes_key(val); + } + + SymmetricKey SymmetricKey::generate_aes(size_t size_bits) { + if (size_bits != 128 && size_bits != 192 && size_bits != 256) { + throw kmipcore::KmipException("Unsupported AES key size. Use 128, 192 or 256 bits"); + } + + const size_t size_bytes = size_bits / 8; + std::vector buf(size_bytes); + if (1 != RAND_bytes(buf.data(), static_cast(size_bytes))) { + const unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + throw kmipcore::KmipException(std::string("OpenSSL RAND_bytes failed: ") + err_buf); + } + + return make_aes_key(buf); + } + +} // namespace kmipclient + diff --git a/kmipclient/src/X509Certificate.cpp b/kmipclient/src/X509Certificate.cpp new file mode 100644 index 0000000..0125311 --- /dev/null +++ b/kmipclient/src/X509Certificate.cpp @@ -0,0 +1,29 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipclient/X509Certificate.hpp" + +namespace kmipclient { + + + std::unique_ptr X509Certificate::clone() const { + return std::make_unique(*this); + } + +} // namespace kmipclient + + diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 11efae1..862279d 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -116,6 +117,14 @@ class KmipClientIntegrationTest : public ::testing::Test { if (!config.isConfigured()) { GTEST_SKIP() << "KMIP environment variables not configured"; } + + try { + auto kmip = createKmipClient(); + // Use a minimal request to surface transport/auth issues with context. + (void) kmip->client().op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, 0); + } catch (const std::exception &e) { + GTEST_SKIP() << "KMIP server connectivity check failed: " << e.what(); + } } void TearDown() override { @@ -161,14 +170,23 @@ class KmipClientIntegrationTest : public ::testing::Test { static std::unique_ptr createKmipClient() { auto &config = KmipTestConfig::getInstance(); - return std::make_unique( - config.kmip_addr.c_str(), - config.kmip_port.c_str(), - config.kmip_client_ca.c_str(), - config.kmip_client_key.c_str(), - config.kmip_server_ca.c_str(), - config.timeout_ms - ); + try { + return std::make_unique( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms + ); + } catch (const std::exception &e) { + throw std::runtime_error( + "Failed to initialize KMIP client for " + config.kmip_addr + ":" + + config.kmip_port + " (client cert: " + config.kmip_client_ca + + ", client key: " + config.kmip_client_key + + ", server cert: " + config.kmip_server_ca + "): " + e.what() + ); + } } void trackKeyForCleanup(const std::string &key_id) { @@ -288,9 +306,9 @@ TEST_F(KmipClientIntegrationTest, CreateAndGetKey) { // Get key try { auto key = kmip->client().op_get_key(key_id); - EXPECT_FALSE(key.value().empty()); - EXPECT_EQ(key.value().size(), 32); // 256 bits = 32 bytes - std::cout << "Retrieved key with " << key.value().size() << " bytes" + EXPECT_FALSE(key->value().empty()); + EXPECT_EQ(key->value().size(), 32); // 256 bits = 32 bytes + std::cout << "Retrieved key with " << key->value().size() << " bytes" << std::endl; } catch (kmipcore::KmipException &e) { FAIL() << "Failed to get key: " << e.what(); @@ -320,7 +338,7 @@ TEST_F(KmipClientIntegrationTest, CreateActivateAndGetKey) { // Get key and it's state try { auto get_result = kmip->client().op_get_key(key_id); - ASSERT_FALSE(get_result.value().empty()) + ASSERT_FALSE(get_result->value().empty()) << "Failed to get activated key: " << key_id; auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); @@ -348,7 +366,7 @@ TEST_F(KmipClientIntegrationTest, RegisterSymmetricKey) { auto key_id = kmip->client().op_register_key( TESTING_NAME_PREFIX + "RegisterSymmetricKey", TEST_GROUP, - Key::aes_from_value(key_value) + SymmetricKey::aes_from_value(key_value) ); EXPECT_FALSE(key_id.empty()); std::cout << "Registered key with ID: " << key_id << std::endl; @@ -358,6 +376,73 @@ TEST_F(KmipClientIntegrationTest, RegisterSymmetricKey) { } } +// Test: Register and Activate symmetric key in one request +TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSymmetricKey) { + auto kmip = createKmipClient(); + + std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + + std::string key_id; + try { + key_id = kmip->client().op_register_and_activate_key( + TESTING_NAME_PREFIX + "RegisterAndActivateSymmetricKey", + TEST_GROUP, + SymmetricKey::aes_from_value(key_value) + ); + ASSERT_FALSE(key_id.empty()); + trackKeyForCleanup(key_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "RegisterAndActivateSymmetricKey failed: " << e.what(); + } + + try { + auto key = kmip->client().op_get_key(key_id); + ASSERT_FALSE(key->value().empty()); + auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); + EXPECT_EQ(attrs[KMIP_ATTR_NAME_STATE], "KMIP_STATE_ACTIVE"); + } catch (kmipcore::KmipException &e) { + FAIL() << "Get after RegisterAndActivateSymmetricKey failed: " << e.what(); + } +} + +// Test: Register and Activate secret in one request +TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSecret) { + auto kmip = createKmipClient(); + + const std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + const Secret secret( + secret_data, + state::KMIP_STATE_PRE_ACTIVE, + secret_data_type::KMIP_SECDATA_PASSWORD + ); + + std::string secret_id; + try { + secret_id = kmip->client().op_register_and_activate_secret( + TESTING_NAME_PREFIX + "RegisterAndActivateSecret", + TEST_GROUP, + secret + ); + ASSERT_FALSE(secret_id.empty()); + trackKeyForCleanup(secret_id); + } catch (kmipcore::KmipException &e) { + FAIL() << "RegisterAndActivateSecret failed: " << e.what(); + } + + try { + auto retrieved_secret = kmip->client().op_get_secret(secret_id, true); + EXPECT_EQ(retrieved_secret.value(), secret_data); + EXPECT_EQ(retrieved_secret.get_state(), state::KMIP_STATE_ACTIVE); + } catch (kmipcore::KmipException &e) { + FAIL() << "Get after RegisterAndActivateSecret failed: " << e.what(); + } +} + // Test: Register secret data TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { auto kmip = createKmipClient(); @@ -506,7 +591,7 @@ TEST_F(KmipClientIntegrationTest, FullKeyLifecycle) { // Get auto get_result = kmip->client().op_get_key(key_id); - ASSERT_FALSE(get_result.value().empty()) << "Get failed: "; + ASSERT_FALSE(get_result->value().empty()) << "Get failed: "; std::cout << "3. Retrieved key" << std::endl; // Revoke @@ -621,8 +706,8 @@ TEST_F(KmipClientIntegrationTest, DestroyKeyRemovesKey) { // Attempt to get the destroyed key - should not be retrievable try { - Key key = kmip->client().op_get_key(key_id); - EXPECT_TRUE(key.value().empty()) + auto key = kmip->client().op_get_key(key_id); + EXPECT_TRUE(key->value().empty()) << "Destroyed key should not be retrievable"; } catch (kmipcore::KmipException &) { // Some servers respond with an error for non-existent objects; this is @@ -744,7 +829,7 @@ TEST_F(KmipClientIntegrationTest, RegisterKeyAndGetAttributes) { }; auto key_id = kmip->client().op_register_key( - name, TEST_GROUP, Key::aes_from_value(key_value) + name, TEST_GROUP, SymmetricKey::aes_from_value(key_value) ); EXPECT_FALSE(key_id.empty()); trackKeyForCleanup(key_id); diff --git a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp index 2e64a0e..ef065c3 100644 --- a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp @@ -28,7 +28,9 @@ #include "kmipcore/kmip_basics.hpp" #include +#include #include +#include #include #include #include @@ -90,7 +92,15 @@ class KmipPoolTestConfig { kmip_server_ca = server_ca; } - timeout_ms = timeout ? std::atoi(timeout) : 5000; + timeout_ms = 5000; + if (timeout) { + errno = 0; + char *end = nullptr; + const long parsed = std::strtol(timeout, &end, 10); + if (errno == 0 && end != timeout && *end == '\0' && parsed >= 0 && parsed <= INT_MAX) { + timeout_ms = static_cast(parsed); + } + } if (!isConfigured()) { std::cerr << "WARNING: KMIP environment variables not set. Pool tests " @@ -157,7 +167,7 @@ class KmipClientPoolIntegrationTest : public ::testing::Test { } } - KmipClientPool::Config createPoolConfig(size_t max_connections = 4) { + static KmipClientPool::Config createPoolConfig(size_t max_connections = 4) { auto &config = KmipPoolTestConfig::getInstance(); return KmipClientPool::Config{ .host = config.kmip_addr, @@ -241,7 +251,7 @@ TEST_F(KmipClientPoolIntegrationTest, PoolCreateAesKey) { EXPECT_FALSE(key_id.empty()); trackKeyForCleanup(key_id); std::cout << "Created key via pool: " << key_id << std::endl; - } catch (kmipcore::KmipException &e) { + } catch (const kmipcore::KmipException &e) { FAIL() << "Failed to create key via pool: " << e.what(); } } @@ -264,7 +274,7 @@ TEST_F(KmipClientPoolIntegrationTest, PoolCreateAndGet) { { auto conn = pool.borrow(); auto key = conn->op_get_key(key_id); - EXPECT_EQ(key.value().size(), 32); // 256-bit AES + EXPECT_EQ(key->value().size(), 32); // 256-bit AES std::cout << "Retrieved key via pool: " << key_id << std::endl; } } catch (kmipcore::KmipException &e) { @@ -272,6 +282,7 @@ TEST_F(KmipClientPoolIntegrationTest, PoolCreateAndGet) { } } + // ============================================================================ // Concurrent Operations Tests // ============================================================================ @@ -326,7 +337,7 @@ TEST_F(KmipClientPoolIntegrationTest, PoolExhaustion) { try { [[maybe_unused]] auto conn = pool.borrow(500ms); FAIL() << "Should have thrown exception on timeout"; - } catch (kmipcore::KmipException &e) { + } catch (const kmipcore::KmipException &) { auto elapsed = std::chrono::high_resolution_clock::now() - start; EXPECT_GE(elapsed, 500ms) << "Timeout should have waited approximately 500ms"; @@ -418,7 +429,7 @@ TEST_F(KmipClientPoolIntegrationTest, ConcurrentOperationsWithReuse) { std::vector threads; for (int t = 0; t < num_threads; ++t) { - threads.emplace_back([this, &pool, t, ops_per_thread]() { + threads.emplace_back([this, &pool, t]() { try { for (int op = 0; op < ops_per_thread; ++op) { auto conn = pool.borrow(); @@ -432,7 +443,7 @@ TEST_F(KmipClientPoolIntegrationTest, ConcurrentOperationsWithReuse) { // Get the key back auto key = conn->op_get_key(key_id); - EXPECT_FALSE(key.value().empty()); + EXPECT_FALSE(key->value().empty()); // Get attributes auto attrs = diff --git a/kmipcore/include/kmipcore/kmip_basics.hpp b/kmipcore/include/kmipcore/kmip_basics.hpp index cf03f6c..40a1790 100644 --- a/kmipcore/include/kmipcore/kmip_basics.hpp +++ b/kmipcore/include/kmipcore/kmip_basics.hpp @@ -186,6 +186,8 @@ namespace kmipcore { [[nodiscard]] std::vector toBytes() const; /** @brief Converts value to Enumeration representation. */ [[nodiscard]] int32_t toEnum() const; + /** @brief Converts value to Interval representation. */ + [[nodiscard]] uint32_t toInterval() const; }; diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index 8e0dee9..deaeb13 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -1,6 +1,7 @@ #pragma once #include "kmipcore/kmip_basics.hpp" #include "kmipcore/kmip_enums.hpp" +#include "kmipcore/key.hpp" #include "kmipcore/kmip_protocol.hpp" #include @@ -115,6 +116,22 @@ namespace kmipcore { ); }; + /** @brief Request for KMIP Register operation using generic key object input. */ + class RegisterKeyRequest : public RequestBatchItem { + public: + /** + * @brief Builds a register request for a key object and common attributes. + * @param name Value for KMIP Name attribute. + * @param group Value for KMIP Object Group attribute. + * @param key Key payload and metadata mapped to protocol object fields. + */ + RegisterKeyRequest( + const std::string &name, + const std::string &group, + const Key &key + ); + }; + /** @brief Request for KMIP Register (secret data) operation. */ class RegisterSecretRequest : public RequestBatchItem { public: diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp index 35f42ee..cef2227 100644 --- a/kmipcore/include/kmipcore/secret.hpp +++ b/kmipcore/include/kmipcore/secret.hpp @@ -77,14 +77,14 @@ namespace kmipcore { secret_data_type type = secret_data_type::KMIP_SECDATA_PASSWORD, state st = state::KMIP_STATE_PRE_ACTIVE ) { - return Secret( + return { std::vector(text.begin(), text.end()), st, type - ); + }; } /** @brief Returns payload interpreted as UTF-8/byte-preserving text. */ [[nodiscard]] std::string as_text() const { - return std::string(value_.begin(), value_.end()); + return {value_.begin(), value_.end()}; } private: diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp index 86896f5..45df6d5 100644 --- a/kmipcore/src/key_parser.cpp +++ b/kmipcore/src/key_parser.cpp @@ -83,12 +83,6 @@ namespace kmipcore { } // anonymous namespace Key KeyParser::parseGetKeyResponse(const GetResponseBatchItem &item) { - if (item.getObjectType() != KMIP_OBJTYPE_SYMMETRIC_KEY) { - throw KmipException( - KMIP_REASON_INVALID_DATA_TYPE, - "Symmetric key expected in Get response." - ); - } return parseResponse(item.getResponsePayload()); } diff --git a/kmipcore/src/kmip_basics.cpp b/kmipcore/src/kmip_basics.cpp index cd613ba..da1ed99 100644 --- a/kmipcore/src/kmip_basics.cpp +++ b/kmipcore/src/kmip_basics.cpp @@ -29,6 +29,25 @@ namespace kmipcore { return (static_cast(high) << 32) | low; } + // Safe big-endian decoders from raw byte spans. + static std::uint32_t read_be_u32(std::span data, std::size_t off) { + return (static_cast(data[off]) << 24) | + (static_cast(data[off + 1]) << 16) | + (static_cast(data[off + 2]) << 8) | + static_cast(data[off + 3]); + } + + static std::uint64_t read_be_u64(std::span data, std::size_t off) { + return (static_cast(data[off]) << 56) | + (static_cast(data[off + 1]) << 48) | + (static_cast(data[off + 2]) << 40) | + (static_cast(data[off + 3]) << 32) | + (static_cast(data[off + 4]) << 24) | + (static_cast(data[off + 5]) << 16) | + (static_cast(data[off + 6]) << 8) | + static_cast(data[off + 7]); + } + void Element::serialize(SerializationBuffer& buf) const { // Write Tag (3 bytes, big-endian) @@ -128,14 +147,15 @@ namespace kmipcore { // Read Tag (3 bytes) std::uint32_t tag = - (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]; + (static_cast(data[offset]) << 16) | + (static_cast(data[offset + 1]) << 8) | + static_cast(data[offset + 2]); // Read Type (1 byte) Type type = static_cast(data[offset + 3]); // Read Length (4 bytes) - std::uint32_t length = (data[offset + 4] << 24) | (data[offset + 5] << 16) | - (data[offset + 6] << 8) | data[offset + 7]; + std::uint32_t length = read_be_u32(data, offset + 4); offset += 8; @@ -198,8 +218,7 @@ namespace kmipcore { throw KmipException("Invalid length for Integer"); } std::int32_t val; - std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; + std::uint32_t raw = read_be_u32(data, offset); // raw is equivalent to big-endian read // we can just use memcpy if valid but manual reconstruction is safer // for endianness Actually raw is correct for big endian 4 bytes @@ -211,14 +230,7 @@ namespace kmipcore { if (length != 8) { throw KmipException("Invalid length for Long Integer"); } - std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | - ((std::uint64_t) data[offset + 1] << 48) | - ((std::uint64_t) data[offset + 2] << 40) | - ((std::uint64_t) data[offset + 3] << 32) | - ((std::uint64_t) data[offset + 4] << 24) | - ((std::uint64_t) data[offset + 5] << 16) | - ((std::uint64_t) data[offset + 6] << 8) | - (std::uint64_t) data[offset + 7]; + std::uint64_t raw = read_be_u64(data, offset); std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = LongInteger{val}; @@ -228,14 +240,7 @@ namespace kmipcore { if (length != 8) { throw KmipException("Invalid length for Boolean"); } - std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | - ((std::uint64_t) data[offset + 1] << 48) | - ((std::uint64_t) data[offset + 2] << 40) | - ((std::uint64_t) data[offset + 3] << 32) | - ((std::uint64_t) data[offset + 4] << 24) | - ((std::uint64_t) data[offset + 5] << 16) | - ((std::uint64_t) data[offset + 6] << 8) | - (std::uint64_t) data[offset + 7]; + std::uint64_t raw = read_be_u64(data, offset); elem->value = Boolean{raw != 0}; break; } @@ -243,8 +248,7 @@ namespace kmipcore { if (length != 4) { throw KmipException("Invalid length for Enumeration"); } - std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; + std::uint32_t raw = read_be_u32(data, offset); elem->value = Enumeration{static_cast(raw)}; break; } @@ -264,14 +268,7 @@ namespace kmipcore { if (length != 8) { throw KmipException("Invalid length for DateTime"); } - std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | - ((std::uint64_t) data[offset + 1] << 48) | - ((std::uint64_t) data[offset + 2] << 40) | - ((std::uint64_t) data[offset + 3] << 32) | - ((std::uint64_t) data[offset + 4] << 24) | - ((std::uint64_t) data[offset + 5] << 16) | - ((std::uint64_t) data[offset + 6] << 8) | - (std::uint64_t) data[offset + 7]; + std::uint64_t raw = read_be_u64(data, offset); std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = DateTime{val}; @@ -281,8 +278,7 @@ namespace kmipcore { if (length != 4) { throw KmipException("Invalid length for Interval"); } - std::uint32_t raw = (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; + std::uint32_t raw = read_be_u32(data, offset); elem->value = Interval{raw}; break; } @@ -298,14 +294,7 @@ namespace kmipcore { if (length != 8) { throw KmipException("Invalid length for DateTimeExtended"); } - std::uint64_t raw = ((std::uint64_t) data[offset] << 56) | - ((std::uint64_t) data[offset + 1] << 48) | - ((std::uint64_t) data[offset + 2] << 40) | - ((std::uint64_t) data[offset + 3] << 32) | - ((std::uint64_t) data[offset + 4] << 24) | - ((std::uint64_t) data[offset + 5] << 16) | - ((std::uint64_t) data[offset + 6] << 8) | - (std::uint64_t) data[offset + 7]; + std::uint64_t raw = read_be_u64(data, offset); std::int64_t val; std::memcpy(&val, &raw, 8); elem->value = DateTimeExtended{val}; @@ -458,4 +447,11 @@ namespace kmipcore { throw KmipException("Element is not Enumeration"); } + uint32_t Element::toInterval() const { + if (auto *v = std::get_if(&value)) { + return v->value; + } + throw KmipException("Element is not Interval"); + } + } // namespace kmipcore diff --git a/kmipcore/src/kmip_formatter.cpp b/kmipcore/src/kmip_formatter.cpp index 188c834..45ec12d 100644 --- a/kmipcore/src/kmip_formatter.cpp +++ b/kmipcore/src/kmip_formatter.cpp @@ -4,8 +4,10 @@ #include "kmipcore/kmip_enums.hpp" #include "kmipcore/kmip_protocol.hpp" +#include #include #include +#include #include namespace kmipcore { @@ -13,15 +15,18 @@ namespace kmipcore { namespace { [[nodiscard]] std::string indent(size_t level) { - return {level * 2, ' '}; + std::string s(level * 2, ' '); + return s; } - [[nodiscard]] std::string format_hex_uint(uint64_t value, size_t width = 0) { + [[nodiscard]] std::string format_hex_uint(uint64_t value, std::streamsize width = 0) { std::ostringstream oss; oss << "0x" << std::uppercase << std::hex << std::setfill('0'); - if (width > 0) { - oss << std::setw(static_cast(width)); - } + const auto bounded_width = std::max( + 0, + std::min(width, std::numeric_limits::max()) + ); + oss << std::setw(static_cast(bounded_width)); oss << value; return oss.str(); } @@ -476,7 +481,7 @@ namespace kmipcore { oss << format_datetime(element->toLong()); break; case KMIP_TYPE_INTERVAL: - oss << element->toInt(); + oss << element->toInterval(); break; default: oss << ""; diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index 9e2d656..638401d 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -128,6 +128,66 @@ namespace kmipcore { } return key_block; } + std::shared_ptr make_key_object_from_key(const Key &key) { + switch (key.type()) { + case KeyType::SYMMETRIC_KEY: { + auto symmetric_key = Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); + symmetric_key->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_RAW, + key.value(), + key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET + ? std::nullopt + : std::optional(static_cast(key.algorithm())), + static_cast(key.value().size() * 8) + )); + return symmetric_key; + } + case KeyType::PRIVATE_KEY: { + auto private_key = Element::createStructure(tag::KMIP_TAG_PRIVATE_KEY); + private_key->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_PKCS8, + key.value(), + key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET + ? std::nullopt + : std::optional(static_cast(key.algorithm())), + static_cast(key.value().size() * 8) + )); + return private_key; + } + case KeyType::PUBLIC_KEY: { + auto public_key = Element::createStructure(tag::KMIP_TAG_PUBLIC_KEY); + public_key->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_X509, + key.value(), + key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET + ? std::nullopt + : std::optional(static_cast(key.algorithm())), + static_cast(key.value().size() * 8) + )); + return public_key; + } + case KeyType::CERTIFICATE: + throw KmipException(KMIP_NOT_IMPLEMENTED, "Certificate registration is not yet supported"); + case KeyType::UNSET: + default: + throw KmipException(KMIP_INVALID_FIELD, "Unsupported key type for Register"); + } + } + int32_t object_type_from_key_type(KeyType key_type) { + switch (key_type) { + case KeyType::SYMMETRIC_KEY: + return KMIP_OBJTYPE_SYMMETRIC_KEY; + case KeyType::PRIVATE_KEY: + return KMIP_OBJTYPE_PRIVATE_KEY; + case KeyType::PUBLIC_KEY: + return KMIP_OBJTYPE_PUBLIC_KEY; + case KeyType::CERTIFICATE: + return KMIP_OBJTYPE_CERTIFICATE; + case KeyType::UNSET: + default: + throw KmipException(KMIP_INVALID_FIELD, "Unsupported key type for Register"); + } + } std::shared_ptr make_symmetric_key(const std::vector &key_value) { auto symmetric_key = @@ -239,6 +299,67 @@ namespace kmipcore { setRequestPayload(payload); } + RegisterKeyRequest::RegisterKeyRequest( + const std::string &name, + const std::string &group, + const Key &key + ) { + setOperation(KMIP_OP_REGISTER); + + std::vector> attributes; + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + + if (key.algorithm() != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { + attributes.push_back( + detail::make_enum_attribute( + "Cryptographic Algorithm", + static_cast(key.algorithm()) + ) + ); + } + if (!key.value().empty()) { + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Length", + static_cast(key.value().size() * 8) + ) + ); + } + if (key.usage_mask() != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + static_cast(key.usage_mask()) + ) + ); + } + + for (const auto &attr : key.attributes()) { + if (attr.first == "Name" || + attr.first == "Object Group" || + attr.first == "Cryptographic Algorithm" || + attr.first == "Cryptographic Length" || + attr.first == "Cryptographic Usage Mask") { + continue; + } + attributes.push_back(detail::make_text_attribute(attr.first, attr.second)); + } + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, + detail::object_type_from_key_type(key.type()) + ) + ); + payload->asStructure()->add(detail::make_template_attribute(attributes)); + payload->asStructure()->add(detail::make_key_object_from_key(key)); + setRequestPayload(payload); + } + // --------------------------------------------------------------------------- // RegisterSecretRequest // --------------------------------------------------------------------------- From 95c61477d129b647939adc63eaece864f9081aec Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sat, 28 Mar 2026 14:23:20 +0200 Subject: [PATCH 06/11] PS-10068 Fix PR API refinement https://perconadev.atlassian.net/browse/PS-10949 API cleanup and refinement. Refactoring of attributes processing --- kmipclient/examples/example_create_aes.cpp | 23 ++ kmipclient/examples/example_get.cpp | 3 +- .../examples/example_get_attributes.cpp | 6 +- kmipclient/examples/example_get_name.cpp | 6 +- kmipclient/examples/example_get_secret.cpp | 2 +- kmipclient/examples/example_register_key.cpp | 5 +- kmipclient/include/kmipclient/KeyBase.hpp | 70 ++-- kmipclient/include/kmipclient/KmipClient.hpp | 6 +- kmipclient/include/kmipclient/types.hpp | 12 +- kmipclient/src/Key.cpp | 51 +-- kmipclient/src/KmipClient.cpp | 97 ++---- kmipclient/src/PEMReader.cpp | 33 +- kmipclient/src/SymmetricKey.cpp | 11 +- .../tests/KmipClientIntegrationTest.cpp | 49 +-- .../tests/KmipClientPoolIntegrationTest.cpp | 3 +- .../include/kmipcore/attributes_parser.hpp | 18 +- kmipcore/include/kmipcore/key.hpp | 80 +---- kmipcore/include/kmipcore/kmip_attributes.hpp | 186 +++++++++++ kmipcore/include/kmipcore/managed_object.hpp | 102 ++++++ kmipcore/include/kmipcore/secret.hpp | 82 +++-- kmipcore/src/attributes_parser.cpp | 149 ++++----- kmipcore/src/key_parser.cpp | 27 +- kmipcore/src/kmip_attributes.cpp | 299 ++++++++++++++++++ kmipcore/src/kmip_requests.cpp | 74 +++-- kmipcore/tests/test_parsers.cpp | 23 +- 25 files changed, 959 insertions(+), 458 deletions(-) create mode 100644 kmipcore/include/kmipcore/kmip_attributes.hpp create mode 100644 kmipcore/include/kmipcore/managed_object.hpp create mode 100644 kmipcore/src/kmip_attributes.cpp diff --git a/kmipclient/examples/example_create_aes.cpp b/kmipclient/examples/example_create_aes.cpp index 2cc8d28..0ac8035 100644 --- a/kmipclient/examples/example_create_aes.cpp +++ b/kmipclient/examples/example_create_aes.cpp @@ -19,10 +19,23 @@ #include "kmipclient/KmipClient.hpp" #include "kmipclient/kmipclient_version.hpp" +#include #include using namespace kmipclient; +namespace { + +void print_hex(const std::vector &bytes) { + for (const auto b : bytes) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(b); + } + std::cout << std::dec << std::endl; +} + +} // namespace + int main(int argc, char **argv) { std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; @@ -39,6 +52,16 @@ int main(int argc, char **argv) { try { auto key_id = kmip.client().op_create_aes_key(argv[6], "TestGroup"); std::cout << "Key ID: " << key_id << std::endl; + + auto key = kmip.client().op_get_key(key_id, true); + std::cout << "Created key value (hex): "; + print_hex(key->value()); + + std::cout << "Created key attributes:" << std::endl; + for (const auto &[name, value] : key->attributes().as_string_map()) { + std::cout << " " << name << ": " << value << std::endl; + } + return 0; } catch (std::exception &e) { std::cerr << "Can not create key with name:" << argv[6] diff --git a/kmipclient/examples/example_get.cpp b/kmipclient/examples/example_get.cpp index eb1b17a..8363ce7 100644 --- a/kmipclient/examples/example_get.cpp +++ b/kmipclient/examples/example_get.cpp @@ -50,8 +50,7 @@ int main(int argc, char **argv) { std::cout << "Key: 0x"; print_hex(key->value()); std::cout << "Attributes:" << std::endl; - const auto &attrs = key->attributes(); - for (const auto &[attr_name, attr_value] : attrs) { + for (const auto &[attr_name, attr_value] : key->attributes().as_string_map()) { std::cout << " " << attr_name << ": " << attr_value << std::endl; } } catch (const std::exception &e) { diff --git a/kmipclient/examples/example_get_attributes.cpp b/kmipclient/examples/example_get_attributes.cpp index 31dfc27..0f494da 100644 --- a/kmipclient/examples/example_get_attributes.cpp +++ b/kmipclient/examples/example_get_attributes.cpp @@ -32,9 +32,9 @@ void print_hex(const std::vector &key) { std::cout << std::endl; } -void print_attributes(const std::unordered_map &attrs) { - for (auto const &attr : attrs) { - std::cout << attr.first << ": " << attr.second << std::endl; +void print_attributes(const kmipcore::Attributes &attrs) { + for (const auto &[name, value] : attrs.as_string_map()) { + std::cout << name << ": " << value << std::endl; } } diff --git a/kmipclient/examples/example_get_name.cpp b/kmipclient/examples/example_get_name.cpp index 0f77f5e..093d7d8 100644 --- a/kmipclient/examples/example_get_name.cpp +++ b/kmipclient/examples/example_get_name.cpp @@ -23,9 +23,9 @@ using namespace kmipclient; -void print_attributes(const std::unordered_map &attrs) { - for (auto const &attr : attrs) { - std::cout << attr.first << ": " << attr.second << std::endl; +void print_attributes(const kmipcore::Attributes &attrs) { + for (const auto &[name, value] : attrs.as_string_map()) { + std::cout << name << ": " << value << std::endl; } } diff --git a/kmipclient/examples/example_get_secret.cpp b/kmipclient/examples/example_get_secret.cpp index 80f7e7e..5a2b884 100644 --- a/kmipclient/examples/example_get_secret.cpp +++ b/kmipclient/examples/example_get_secret.cpp @@ -50,7 +50,7 @@ int main(int argc, char **argv) { const auto &attrs = secret.attributes(); std::cout << "Attributes:" << std::endl; - for (const auto &[key, value] : attrs) { + for (const auto &[key, value] : attrs.as_string_map()) { std::cout << " " << key << ": " << value << std::endl; } } catch (std::exception &e) { diff --git a/kmipclient/examples/example_register_key.cpp b/kmipclient/examples/example_register_key.cpp index 47a4c16..51ae08e 100644 --- a/kmipclient/examples/example_register_key.cpp +++ b/kmipclient/examples/example_register_key.cpp @@ -60,7 +60,10 @@ int main(int argc, char **argv) { auto fetched_key = client.op_get_key(key_id); std::cout << "Fetched key from server (hex): "; print_hex(fetched_key->value()); - + std::cout << "Attributes:" << std::endl; + for (const auto &[attr_name, attr_value] : fetched_key->attributes().as_string_map()) { + std::cout << " " << attr_name << ": " << attr_value << std::endl; + } (void) client.op_revoke( key_id, revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, diff --git a/kmipclient/include/kmipclient/KeyBase.hpp b/kmipclient/include/kmipclient/KeyBase.hpp index e35dbbb..ed9b007 100644 --- a/kmipclient/include/kmipclient/KeyBase.hpp +++ b/kmipclient/include/kmipclient/KeyBase.hpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace kmipclient { @@ -31,8 +30,10 @@ namespace kmipclient { /** * Client-level key abstraction. * - * kmipcore keeps a single protocol-oriented key value type, while this layer - * exposes distinct key families via polymorphism. + * All attributes — Cryptographic Algorithm, Length, Usage Mask, lifecycle + * State, Name, Object Group, Unique Identifier, dates … — are held in a + * single @ref kmipcore::Attributes bag. Dedicated typed accessors are + * provided for the most frequently accessed fields. */ class Key { public: @@ -43,41 +44,74 @@ namespace kmipclient { Key(Key &&) noexcept = default; Key &operator=(Key &&) noexcept = default; + // ---- Raw bytes ---- + [[nodiscard]] const std::vector &value() const noexcept { return key_value_; } - [[nodiscard]] const std::unordered_map &attributes() const noexcept { - return key_attributes_; + + // ---- Attribute bag ---- + + /** @brief Returns the type-safe attribute bag (read-only). */ + [[nodiscard]] const kmipcore::Attributes &attributes() const noexcept { + return attributes_; } - [[nodiscard]] const std::string &attribute_value(const std::string &name) const noexcept; - void set_attribute(const std::string &name, const std::string &val) noexcept; - [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { - return crypto_usage_mask_; + + /** @brief Returns the type-safe attribute bag (mutable). */ + [[nodiscard]] kmipcore::Attributes &attributes() noexcept { + return attributes_; } + + // ---- Typed convenience accessors ---- + [[nodiscard]] cryptographic_algorithm algorithm() const noexcept { - return crypto_algorithm_; + return attributes_.algorithm(); + } + [[nodiscard]] std::optional crypto_length() const noexcept { + return attributes_.crypto_length(); + } + [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { + return attributes_.usage_mask(); + } + + // ---- Generic string attribute helpers (backward compatibility) ---- + + /** @brief Returns a generic string attribute value, or empty string. */ + [[nodiscard]] const std::string & + attribute_value(const std::string &name) const noexcept { + return attributes_.get(name); } + /** @brief Sets a string attribute by name (routes typed attrs to typed setters). */ + void set_attribute(const std::string &name, const std::string &val) noexcept { + attributes_.set(name, val); + } + + // ---- Key kind ---- + [[nodiscard]] virtual KeyType type() const noexcept = 0; [[nodiscard]] virtual std::unique_ptr clone() const = 0; - /** Build protocol-level representation from the client key object. */ + // ---- Core-layer bridge ---- + + /** @brief Build protocol-level representation from the client key object. */ [[nodiscard]] kmipcore::Key to_core_key() const; - /** Build the corresponding client key subclass from protocol-level data. */ + /** @brief Build the corresponding client key subclass from protocol-level data. */ [[nodiscard]] static std::unique_ptr from_core_key(const kmipcore::Key &core_key); + /** + * @brief Constructor: raw bytes + full attribute bag. + * @param value Key material bytes. + * @param attrs Type-safe attribute bag (algorithm, length, mask, …). + */ Key( const std::vector &value, - cryptographic_algorithm algo = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask usage_mask = cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - const std::unordered_map &attributes = {} + kmipcore::Attributes attrs = {} ); private: std::vector key_value_; - std::unordered_map key_attributes_; - cryptographic_algorithm crypto_algorithm_ = cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; - cryptographic_usage_mask crypto_usage_mask_ = cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET; + kmipcore::Attributes attributes_; }; } // namespace kmipclient diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index fc61f17..115ddc0 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -20,12 +20,12 @@ #include "kmipclient/Key.hpp" #include "kmipclient/NetClient.hpp" #include "kmipclient/types.hpp" +#include "kmipcore/kmip_attributes.hpp" #include "kmipcore/kmip_logger.hpp" #include #include #include -#include #include namespace kmipclient { @@ -173,10 +173,10 @@ namespace kmipclient { * @brief Executes KMIP Get Attributes for selected attribute names. * @param id Unique identifier of the target object. * @param attr_names Attribute names to fetch (for example "Name", "State"). - * @return Map of requested attributes present in the server response. + * @return Type-safe @ref Attributes bag with the requested attributes. * @throws kmipcore::KmipException on protocol or server-side failure. */ - [[nodiscard]] std::unordered_map op_get_attributes( + [[nodiscard]] kmipcore::Attributes op_get_attributes( const std::string &id, const std::vector &attr_names ) const; diff --git a/kmipclient/include/kmipclient/types.hpp b/kmipclient/include/kmipclient/types.hpp index c1adb91..a0cabc2 100644 --- a/kmipclient/include/kmipclient/types.hpp +++ b/kmipclient/include/kmipclient/types.hpp @@ -1,10 +1,13 @@ #pragma once +#include "kmipcore/kmip_attributes.hpp" #include "kmipcore/key.hpp" #include "kmipcore/kmip_attribute_names.hpp" #include "kmipcore/secret.hpp" namespace kmipclient { + /** @brief Alias for the type-safe KMIP attribute bag. */ + using kmipcore::Attributes; /** @brief Alias for supported key-kind discriminator enum. */ using kmipcore::KeyType; /** @brief Alias for KMIP object type enum. */ @@ -37,12 +40,17 @@ namespace kmipclient { /** @brief Canonical KMIP attribute name for operation policy name. */ inline const std::string KMIP_ATTR_NAME_OPERATION_POLICY_NAME = std::string(kmipcore::KMIP_ATTR_NAME_OPERATION_POLICY_NAME); - /** @brief Canonical KMIP attribute name for cryptographic algorithm. */ + /** @brief Canonical KMIP attribute name for cryptographic algorithm + * (used when constructing GetAttributes requests). */ inline const std::string KMIP_ATTR_NAME_CRYPTO_ALG = std::string(kmipcore::KMIP_ATTR_NAME_CRYPTO_ALG); - /** @brief Canonical KMIP attribute name for cryptographic length. */ + /** @brief Canonical KMIP attribute name for cryptographic length + * (used when constructing GetAttributes requests). */ inline const std::string KMIP_ATTR_NAME_CRYPTO_LEN = std::string(kmipcore::KMIP_ATTR_NAME_CRYPTO_LEN); + /** @brief Canonical KMIP attribute name for cryptographic usage mask. */ + inline const std::string KMIP_ATTR_NAME_CRYPTO_USAGE_MASK = + std::string(kmipcore::KMIP_ATTR_NAME_CRYPTO_USAGE_MASK); /** @brief Re-export stream formatter overloads from kmipcore. */ using kmipcore::operator<<; diff --git a/kmipclient/src/Key.cpp b/kmipclient/src/Key.cpp index b97ac8f..1da126c 100644 --- a/kmipclient/src/Key.cpp +++ b/kmipclient/src/Key.cpp @@ -23,65 +23,28 @@ namespace kmipclient { Key::Key( const std::vector &value, - cryptographic_algorithm algo, - cryptographic_usage_mask usage_mask, - const std::unordered_map &attributes + kmipcore::Attributes attrs ) - : key_value_(value), - key_attributes_(attributes), - crypto_algorithm_(algo), - crypto_usage_mask_(usage_mask) { - } - - const std::string &Key::attribute_value(const std::string &name) const noexcept { - static const std::string empty; - const auto it = key_attributes_.find(name); - return it != key_attributes_.end() ? it->second : empty; - } - - void Key::set_attribute(const std::string &name, const std::string &val) noexcept { - key_attributes_[name] = val; - } + : key_value_(value), attributes_(std::move(attrs)) {} kmipcore::Key Key::to_core_key() const { - return kmipcore::Key(value(), type(), algorithm(), usage_mask(), attributes()); + return kmipcore::Key(key_value_, type(), attributes_); } std::unique_ptr Key::from_core_key(const kmipcore::Key &core_key) { switch (core_key.type()) { case KeyType::SYMMETRIC_KEY: - return std::make_unique( - core_key.value(), - core_key.algorithm(), - core_key.usage_mask(), - core_key.attributes() - ); + return std::make_unique(core_key.value(), core_key.attributes()); case KeyType::PUBLIC_KEY: - return std::make_unique( - core_key.value(), - core_key.algorithm(), - core_key.usage_mask(), - core_key.attributes() - ); + return std::make_unique(core_key.value(), core_key.attributes()); case KeyType::PRIVATE_KEY: - return std::make_unique( - core_key.value(), - core_key.algorithm(), - core_key.usage_mask(), - core_key.attributes() - ); + return std::make_unique(core_key.value(), core_key.attributes()); case KeyType::CERTIFICATE: - return std::make_unique( - core_key.value(), - core_key.algorithm(), - core_key.usage_mask(), - core_key.attributes() - ); + return std::make_unique(core_key.value(), core_key.attributes()); case KeyType::UNSET: default: throw kmipcore::KmipException("Unsupported key type in core->client conversion"); } } - } // namespace kmipclient diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index 1e0b8d0..e5b5203 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -24,28 +24,11 @@ #include "kmipcore/response_parser.hpp" #include -#include #include #include -#include namespace kmipclient { -// Look up a required attribute in a response map. -// Throws kmipcore::KmipException when the server omitted the attribute, -// unlike std::unordered_map::operator[] which would silently insert an empty value. -static const std::string &require_attr( - const std::unordered_map &attrs, const std::string &name -) { - auto it = attrs.find(name); - if (it == attrs.end()) { - throw kmipcore::KmipException( - "Required attribute '" + name + "' missing from server response" - ); - } - return it->second; -} - static kmipcore::RequestBatchItem make_activate_using_id_placeholder_request() { kmipcore::RequestBatchItem item; item.setOperation(kmipcore::KMIP_OP_ACTIVATE); @@ -69,36 +52,6 @@ static std::vector default_get_attrs(bool all_attributes) { }; } -static std::optional parse_state_attr(std::string_view state_attr) { - constexpr std::string_view prefix = "KMIP_STATE_"; - if (state_attr.starts_with(prefix)) { - state_attr.remove_prefix(prefix.size()); - } - - static constexpr std::array, 6> known = {{ - {"PRE_ACTIVE", state::KMIP_STATE_PRE_ACTIVE}, - {"ACTIVE", state::KMIP_STATE_ACTIVE}, - {"DEACTIVATED", state::KMIP_STATE_DEACTIVATED}, - {"COMPROMISED", state::KMIP_STATE_COMPROMISED}, - {"DESTROYED", state::KMIP_STATE_DESTROYED}, - {"DESTROYED_COMPROMISED", state::KMIP_STATE_DESTROYED_COMPROMISED}, - }}; - - for (const auto &[name, value] : known) { - if (state_attr == name) { - return value; - } - } - return std::nullopt; -} - -static void sync_secret_state_from_attr(Secret &secret, const std::string &state_attr) { - // Unknown values are ignored to preserve backward-compatible behavior. - if (auto parsed = parse_state_attr(state_attr); parsed.has_value()) { - secret.set_state(*parsed); - } -} - KmipClient::KmipClient( NetClient &net_client, const std::shared_ptr &logger @@ -276,18 +229,24 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state rf.getResponseByBatchItemId( attributes_item_id ); - std::unordered_map attrs = + kmipcore::Attributes server_attrs = kmipcore::AttributesParser::parse(attrs_response.getAttributes()); - // Verify required attributes are present - require_attr(attrs, KMIP_ATTR_NAME_STATE); - require_attr(attrs, KMIP_ATTR_NAME_NAME); - - // Copy all attributes from response to key - for (const auto &item : attrs) { - key->set_attribute(item.first, item.second); + // Verify required attributes are present in the server response. + if (!server_attrs.has_attribute(KMIP_ATTR_NAME_STATE)) { + throw kmipcore::KmipException( + "Required attribute 'State' missing from server response" + ); + } + if (!server_attrs.has_attribute(KMIP_ATTR_NAME_NAME)) { + throw kmipcore::KmipException( + "Required attribute 'Name' missing from server response" + ); } + // Merge server-provided metadata (state, name, dates, …) into the key. + key->attributes().merge(server_attrs); + return key; } @@ -315,22 +274,26 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state rf.getResponseByBatchItemId( attributes_item_id ); - std::unordered_map attrs = + kmipcore::Attributes server_attrs = kmipcore::AttributesParser::parse(attrs_response.getAttributes()); if (all_attributes) { - for (const auto &item : attrs) { - secret.set_attribute(item.first, item.second); + // Merge all server-provided attributes into the secret. + secret.attributes().merge(server_attrs); + } else { + if (!server_attrs.has_attribute(KMIP_ATTR_NAME_STATE)) { + throw kmipcore::KmipException( + "Required attribute 'State' missing from server response" + ); } - const auto it = attrs.find(KMIP_ATTR_NAME_STATE); - if (it != attrs.end()) { - sync_secret_state_from_attr(secret, it->second); + if (!server_attrs.has_attribute(KMIP_ATTR_NAME_NAME)) { + throw kmipcore::KmipException( + "Required attribute 'Name' missing from server response" + ); } - } else { - const auto &state_attr = require_attr(attrs, KMIP_ATTR_NAME_STATE); - sync_secret_state_from_attr(secret, state_attr); - secret.set_attribute(KMIP_ATTR_NAME_STATE, state_attr); - secret.set_attribute(KMIP_ATTR_NAME_NAME, require_attr(attrs, KMIP_ATTR_NAME_NAME)); + // Copy only the minimal set: state (typed) + name (generic). + secret.set_state(server_attrs.object_state()); + secret.set_attribute(KMIP_ATTR_NAME_NAME, std::string(server_attrs.get(KMIP_ATTR_NAME_NAME))); } return secret; @@ -372,7 +335,7 @@ static void sync_secret_state_from_attr(Secret &secret, const std::string &state }; } - std::unordered_map KmipClient::op_get_attributes( + kmipcore::Attributes KmipClient::op_get_attributes( const std::string &id, const std::vector &attr_names ) const { kmipcore::RequestMessage request; diff --git a/kmipclient/src/PEMReader.cpp b/kmipclient/src/PEMReader.cpp index 2f8e20c..f26ead4 100644 --- a/kmipclient/src/PEMReader.cpp +++ b/kmipclient/src/PEMReader.cpp @@ -52,14 +52,9 @@ namespace kmipclient { std::vector cert_bytes(der, der + der_len); OPENSSL_free(der); - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "certificate"; - return X509Certificate( - cert_bytes, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); + kmipcore::Attributes attrs; + attrs.set(KMIP_ATTR_NAME_NAME, "certificate"); + return X509Certificate(cert_bytes, std::move(attrs)); } std::optional try_parse_private_key(BIO *bio) { @@ -82,14 +77,9 @@ namespace kmipclient { std::vector key_bytes(der, der + der_len); OPENSSL_free(der); - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "private_key"; - return PrivateKey( - key_bytes, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); + kmipcore::Attributes attrs; + attrs.set(KMIP_ATTR_NAME_NAME, "private_key"); + return PrivateKey(key_bytes, std::move(attrs)); } std::optional try_parse_public_key(BIO *bio) { @@ -112,14 +102,9 @@ namespace kmipclient { std::vector key_bytes(der, der + der_len); OPENSSL_free(der); - std::unordered_map attrs; - attrs[KMIP_ATTR_NAME_NAME] = "public_key"; - return PublicKey( - key_bytes, - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - attrs - ); + kmipcore::Attributes attrs; + attrs.set(KMIP_ATTR_NAME_NAME, "public_key"); + return PublicKey(key_bytes, std::move(attrs)); } std::optional try_parse_aes_from_pem_text(const std::string &pem) { diff --git a/kmipclient/src/SymmetricKey.cpp b/kmipclient/src/SymmetricKey.cpp index 352082b..dfcd3b4 100644 --- a/kmipclient/src/SymmetricKey.cpp +++ b/kmipclient/src/SymmetricKey.cpp @@ -36,11 +36,12 @@ namespace kmipclient { " bits. Should be 128, 192 or 256 bits" }; } - return SymmetricKey( - bytes, - cryptographic_algorithm::KMIP_CRYPTOALG_AES, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET - ); + + kmipcore::Attributes attrs; + attrs.set_algorithm(cryptographic_algorithm::KMIP_CRYPTOALG_AES) + .set_crypto_length(static_cast(size * 8)); + + return SymmetricKey(bytes, std::move(attrs)); } } // anonymous namespace diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 862279d..0f46754 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -342,8 +342,8 @@ TEST_F(KmipClientIntegrationTest, CreateActivateAndGetKey) { << "Failed to get activated key: " << key_id; auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); - auto state = attrs[KMIP_ATTR_NAME_STATE]; - EXPECT_TRUE(state == "KMIP_STATE_ACTIVE") + auto state_value = attrs.object_state(); + EXPECT_TRUE(state_value == state::KMIP_STATE_ACTIVE) << "State is not ACTIVE for key: " << key_id; std::cout << "Successfully activated and retrieved key: " << key_id << std::endl; @@ -404,7 +404,8 @@ TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSymmetricKey) { auto key = kmip->client().op_get_key(key_id); ASSERT_FALSE(key->value().empty()); auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); - EXPECT_EQ(attrs[KMIP_ATTR_NAME_STATE], "KMIP_STATE_ACTIVE"); + auto state_str = attrs.get_as_string(KMIP_ATTR_NAME_STATE); + EXPECT_TRUE(state_str && *state_str == "KMIP_STATE_ACTIVE"); } catch (kmipcore::KmipException &e) { FAIL() << "Get after RegisterAndActivateSymmetricKey failed: " << e.what(); } @@ -415,10 +416,12 @@ TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSecret) { auto kmip = createKmipClient(); const std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + kmipcore::Attributes secret_attrs; + secret_attrs.set_state(state::KMIP_STATE_PRE_ACTIVE); const Secret secret( secret_data, - state::KMIP_STATE_PRE_ACTIVE, - secret_data_type::KMIP_SECDATA_PASSWORD + secret_data_type::KMIP_SECDATA_PASSWORD, + secret_attrs ); std::string secret_id; @@ -448,10 +451,12 @@ TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { auto kmip = createKmipClient(); std::string secret_id; std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + kmipcore::Attributes secret_attrs; + secret_attrs.set_state(state::KMIP_STATE_PRE_ACTIVE); Secret secret( secret_data, - state::KMIP_STATE_PRE_ACTIVE, - secret_data_type::KMIP_SECDATA_PASSWORD + secret_data_type::KMIP_SECDATA_PASSWORD, + secret_attrs ); try { secret_id = kmip->client().op_register_secret( @@ -475,17 +480,15 @@ TEST_F(KmipClientIntegrationTest, RegisterAndGetSecret) { auto retrieved_secret = kmip->client().op_get_secret(secret_id, true); EXPECT_EQ(retrieved_secret.value().size(), secret_data.size()); EXPECT_EQ(retrieved_secret.value(), secret_data); - EXPECT_FALSE(retrieved_secret.attributes().empty()); - EXPECT_TRUE( - retrieved_secret.attributes().count(KMIP_ATTR_NAME_NAME) > 0 || - retrieved_secret.attributes().count("Name") > 0 - ); - - const auto state_it = retrieved_secret.attributes().find(KMIP_ATTR_NAME_STATE); - ASSERT_NE(state_it, retrieved_secret.attributes().end()) - << "State attribute is missing"; + // Check that attributes exist - Name or State or any other typed/generic attribute + EXPECT_TRUE(retrieved_secret.attributes().has_attribute(KMIP_ATTR_NAME_NAME) || + retrieved_secret.attributes().has_attribute("Name") || + !retrieved_secret.attributes().generic().empty()); + + // Check State attribute + auto state_value = retrieved_secret.attributes().object_state(); + EXPECT_EQ(state_value, state::KMIP_STATE_ACTIVE); EXPECT_EQ(retrieved_secret.get_state(), state::KMIP_STATE_ACTIVE); - EXPECT_EQ(state_it->second, kmipcore::state_to_string(retrieved_secret.get_state())); } catch (kmipcore::KmipException &e) { FAIL() << "Get secret failed: " << e.what(); } @@ -536,8 +539,8 @@ TEST_F(KmipClientIntegrationTest, CreateAndGetAttributes) { attr_result.merge( kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_GROUP}) ); - auto attr_name = attr_result[KMIP_ATTR_NAME_NAME]; - auto attr_group = attr_result[KMIP_ATTR_NAME_GROUP]; + auto attr_name = attr_result.get(KMIP_ATTR_NAME_NAME); + auto attr_group = attr_result.get(KMIP_ATTR_NAME_GROUP); std::cout << "Successfully retrieved attributes for key: " << key_id << std::endl; EXPECT_EQ(name, attr_name); @@ -779,9 +782,9 @@ TEST_F(KmipClientIntegrationTest, RevokeChangesState) { try { auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); - auto state = attrs[KMIP_ATTR_NAME_STATE]; - EXPECT_TRUE(state == "KMIP_STATE_DEACTIVATED") - << "Expected DEACTIVATED state, got: " << state; + auto state_value = attrs.object_state(); + EXPECT_TRUE(state_value == state::KMIP_STATE_DEACTIVATED) + << "Expected DEACTIVATED state"; std::cout << "Successfully verified key state changed to DEACTIVATED" << std::endl; } catch (kmipcore::KmipException &e) { @@ -836,7 +839,7 @@ TEST_F(KmipClientIntegrationTest, RegisterKeyAndGetAttributes) { auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); - auto attr_name = attrs[KMIP_ATTR_NAME_NAME]; + auto attr_name = attrs.get(KMIP_ATTR_NAME_NAME); EXPECT_EQ(attr_name, name); std::cout << "Successfully verified registered key attributes match" << std::endl; diff --git a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp index ef065c3..6ce55da 100644 --- a/kmipclient/tests/KmipClientPoolIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp @@ -448,7 +448,8 @@ TEST_F(KmipClientPoolIntegrationTest, ConcurrentOperationsWithReuse) { // Get attributes auto attrs = conn->op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); - EXPECT_FALSE(attrs.empty()); + EXPECT_TRUE(attrs.has_attribute(KMIP_ATTR_NAME_NAME) || + !attrs.generic().empty()); // Connection is returned here when out of scope } diff --git a/kmipcore/include/kmipcore/attributes_parser.hpp b/kmipcore/include/kmipcore/attributes_parser.hpp index e4f4126..d1fed96 100644 --- a/kmipcore/include/kmipcore/attributes_parser.hpp +++ b/kmipcore/include/kmipcore/attributes_parser.hpp @@ -1,28 +1,30 @@ #pragma once +#include "kmipcore/kmip_attributes.hpp" #include "kmipcore/kmip_basics.hpp" #include -#include -#include #include namespace kmipcore { /** - * @brief Utilities for decoding KMIP Attribute structures into a string map. + * @brief Decodes raw KMIP Attribute structures into a typed @ref Attributes bag. */ class AttributesParser { public: - /** @brief Default constructor. */ AttributesParser() = default; /** - * @brief Parses KMIP attribute elements into name/value pairs. + * @brief Parses KMIP attribute elements into a typed @ref Attributes object. + * + * Well-known attributes (Cryptographic Algorithm, Cryptographic Length, + * Cryptographic Usage Mask, State) are stored in their dedicated typed + * fields. All other attributes are stored in the generic string map. + * * @param attributes Raw KMIP attribute elements. - * @return Parsed attribute map keyed by attribute name. + * @return Populated @ref Attributes bag. */ - static std::unordered_map - parse(const std::vector> &attributes); + static Attributes parse(const std::vector> &attributes); }; } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/key.hpp b/kmipcore/include/kmipcore/key.hpp index 64a8dfd..cd12c8b 100644 --- a/kmipcore/include/kmipcore/key.hpp +++ b/kmipcore/include/kmipcore/key.hpp @@ -18,12 +18,9 @@ #ifndef KMIPCORE_KEY_HPP #define KMIPCORE_KEY_HPP +#include "kmipcore/managed_object.hpp" #include "kmipcore/kmip_enums.hpp" -#include -#include -#include - namespace kmipcore { /** @brief Key object families represented by @ref Key. */ @@ -31,93 +28,44 @@ namespace kmipcore { /** * Minimal crypto key representation as KMIP spec sees it. - * Contains key value, type, algorithm, usage mask, and attributes. - * No factory methods — those belong in higher-level layers. + * Contains raw key bytes, key type, and a type-safe @ref Attributes bag. + * + * All attribute access goes through @ref attributes(). + * Use @ref attributes().algorithm(), @ref attributes().usage_mask(), etc. + * for the well-known typed attributes, and @ref attributes().get() / + * @ref attributes().generic() for user-defined / generic ones. */ - class Key { + class Key : public ManagedObject { public: /** * @brief Constructs a KMIP key object. * @param value Raw key bytes. * @param k_type Key family. - * @param algo Cryptographic algorithm. - * @param usage_mask Cryptographic usage mask flags. - * @param attributes Additional key attributes. + * @param attrs Type-safe attribute bag. */ explicit Key( - const std::vector &value, + const std::vector &value, KeyType k_type, - cryptographic_algorithm algo, - cryptographic_usage_mask usage_mask, - const std::unordered_map &attributes + Attributes attrs = {} ) - : key_value(value), - key_type(k_type), - key_attributes(attributes), - crypto_algorithm(algo), - crypto_usage_mask(usage_mask) {}; + : ManagedObject(value, std::move(attrs)), key_type(k_type) {} /** @brief Constructs an empty key object. */ Key() = default; - /** @brief Virtual destructor for subclass-safe cleanup. */ - virtual ~Key() = default; Key(const Key &) = default; Key &operator=(const Key &) = default; Key(Key &&) noexcept = default; Key &operator=(Key &&) noexcept = default; - /** @brief Returns raw key bytes. */ - [[nodiscard]] const std::vector &value() const noexcept { return key_value; }; - - /** @brief Returns all attached key attributes. */ - [[nodiscard]] const std::unordered_map &attributes() const noexcept { - return key_attributes; - }; - - /** - * @brief Returns value of a key attribute, or empty string if absent. - * @param name Attribute name. - * @return Attribute value, or empty string when the attribute is missing. - */ - [[nodiscard]] const std::string & - attribute_value(const std::string &name) const noexcept { - static const std::string empty; - const auto it = key_attributes.find(name); - return it != key_attributes.end() ? it->second : empty; - }; - - /** @brief Sets or replaces one attribute value. */ - void set_attribute( - const std::string &name, const std::string &val - ) noexcept { - key_attributes[name] = val; - }; - - /** @brief Returns KMIP usage mask flags. */ - [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { - return crypto_usage_mask; - } - - /** @brief Returns KMIP cryptographic algorithm. */ - [[nodiscard]] cryptographic_algorithm algorithm() const noexcept { - return crypto_algorithm; - }; - /** @brief Returns key family discriminator. */ - [[nodiscard]] KeyType type() const noexcept { return key_type; }; + [[nodiscard]] KeyType type() const noexcept { return key_type; } /** @brief Returns key length in bytes. */ - [[nodiscard]] size_t size() const noexcept { return key_value.size(); } + [[nodiscard]] size_t size() const noexcept { return value_.size(); } private: - std::vector key_value; KeyType key_type = KeyType::UNSET; - std::unordered_map key_attributes; - cryptographic_algorithm crypto_algorithm = - cryptographic_algorithm::KMIP_CRYPTOALG_UNSET; - cryptographic_usage_mask crypto_usage_mask = - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET; }; } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_attributes.hpp b/kmipcore/include/kmipcore/kmip_attributes.hpp new file mode 100644 index 0000000..e529edb --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_attributes.hpp @@ -0,0 +1,186 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +#include +#include + +namespace kmipcore { + + /** + * @brief Type-safe KMIP attribute bag shared by Key, Secret, and other managed objects. + * + * **Well-known typed attributes** — Cryptographic Algorithm, Cryptographic Length, + * Cryptographic Usage Mask and the object lifecycle @ref state — are stored with + * their native C++ types and accessed via dedicated typed getters/setters. + * + * **All other attributes** — including user-defined / vendor attributes allowed by + * the KMIP specification — are stored in a generic map keyed by attribute name. + * Values preserve their native KMIP type through an @ref AttributeValue variant + * (string, int32, int64, bool), so integer user-defined attributes are not + * silently down-cast to strings. + * + * The @ref set(name, …) overloads automatically route calls for the four + * well-known typed attributes to the appropriate typed setter, making it + * straightforward to absorb raw server-response data without losing type safety. + * + * Use @ref as_string_map() for a human-readable snapshot of all attributes + * (typed fields serialised to strings, generic values converted via their type). + */ + class Attributes { + public: + /** Discriminated union for user-defined / generic attribute values. */ + using AttributeValue = std::variant; + using GenericMap = std::unordered_map; + /** Convenience alias for the plain string snapshot returned by @ref as_string_map(). */ + using StringMap = std::unordered_map; + + Attributes() = default; + ~Attributes() = default; + Attributes(const Attributes &) = default; + Attributes &operator=(const Attributes &) = default; + Attributes(Attributes &&) noexcept = default; + Attributes &operator=(Attributes &&) noexcept = default; + + // ------------------------------------------------------------------------- + // Well-known typed getters + // ------------------------------------------------------------------------- + + /** @brief Returns Cryptographic Algorithm, or KMIP_CRYPTOALG_UNSET when absent. */ + [[nodiscard]] cryptographic_algorithm algorithm() const noexcept; + + /** @brief Returns Cryptographic Length in bits, or std::nullopt when absent. */ + [[nodiscard]] std::optional crypto_length() const noexcept; + + /** @brief Returns Cryptographic Usage Mask, or KMIP_CRYPTOMASK_UNSET when absent. */ + [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept; + + /** @brief Returns the object lifecycle state, or KMIP_STATE_PRE_ACTIVE when absent. */ + [[nodiscard]] state object_state() const noexcept; + + // ------------------------------------------------------------------------- + // Well-known typed setters — fluent, return *this + // ------------------------------------------------------------------------- + + /** @brief Sets Cryptographic Algorithm. Passing KMIP_CRYPTOALG_UNSET clears. */ + Attributes &set_algorithm(cryptographic_algorithm alg) noexcept; + /** @brief Sets Cryptographic Length in bits. */ + Attributes &set_crypto_length(int32_t len) noexcept; + /** @brief Clears Cryptographic Length (marks it absent). */ + Attributes &clear_crypto_length() noexcept; + /** @brief Sets Cryptographic Usage Mask. Passing KMIP_CRYPTOMASK_UNSET clears. */ + Attributes &set_usage_mask(cryptographic_usage_mask mask) noexcept; + /** @brief Sets the object lifecycle state. */ + Attributes &set_state(state st) noexcept; + + // ------------------------------------------------------------------------- + // Generic / user-defined attribute setters + // + // Well-known attribute names (Cryptographic Algorithm, Length, Mask, State) + // are automatically routed to the corresponding typed setter regardless of + // which overload is used. + // ------------------------------------------------------------------------- + + /** @brief Stores a string attribute (routes well-known names to typed setters). */ + Attributes &set(std::string_view name, std::string value); + /** @brief Stores an integer attribute (routes well-known names to typed setters). */ + Attributes &set(std::string_view name, int32_t value) noexcept; + /** @brief Stores a long-integer attribute. */ + Attributes &set(std::string_view name, int64_t value) noexcept; + /** @brief Stores a boolean attribute. */ + Attributes &set(std::string_view name, bool value) noexcept; + + /** @brief Removes a generic attribute (no-op when absent). */ + void remove(std::string_view name) noexcept; + + // ------------------------------------------------------------------------- + // Generic attribute getters + // ------------------------------------------------------------------------- + + /** + * @brief Returns true if the attribute is present — typed field or generic. + */ + [[nodiscard]] bool has_attribute(std::string_view name) const noexcept; + + /** + * @brief Returns the string value of a generic attribute, or empty string. + * + * Only returns a non-empty value when the stored variant holds a + * @c std::string. Use @ref get_int() or @ref get_long() for numeric attrs, + * or @ref get_as_string() for a type-converting accessor. + * + * Does NOT look up the well-known typed fields (algorithm, length, etc.). + * Use the dedicated typed getters for those. + */ + [[nodiscard]] const std::string &get(std::string_view name) const noexcept; + + /** + * @brief Returns a string representation of any generic attribute. + * + * Converts the stored variant to string (int → decimal, bool → "true"/"false"). + * Returns @c std::nullopt when the attribute is absent. + */ + [[nodiscard]] std::optional get_as_string(std::string_view name) const; + + /** @brief Returns the int32 value of a generic attribute, or nullopt. */ + [[nodiscard]] std::optional get_int(std::string_view name) const noexcept; + + /** @brief Returns the int64 value of a generic attribute, or nullopt. */ + [[nodiscard]] std::optional get_long(std::string_view name) const noexcept; + + // ------------------------------------------------------------------------- + // Iteration / export + // ------------------------------------------------------------------------- + + /** @brief Direct read-only access to the typed generic attribute map. */ + [[nodiscard]] const GenericMap &generic() const noexcept; + + /** + * @brief Returns all attributes as a plain string map. + * + * Well-known typed fields are serialised to their canonical string forms. + * Generic @ref AttributeValue entries are converted (int → decimal, + * bool → "true"/"false"). Suitable for display, logging, and + * backward-compatible enumeration. + */ + [[nodiscard]] StringMap as_string_map() const; + + /** + * @brief Merges attributes from @p other into this object. + * + * Only fields explicitly set in @p other overwrite the corresponding field + * in @p this. Fields absent in @p other are left unchanged. + */ + Attributes &merge(const Attributes &other); + + private: + std::optional algo_; + std::optional crypto_length_; + std::optional usage_mask_; + std::optional state_; + GenericMap generic_; + }; + +} // namespace kmipcore + diff --git a/kmipcore/include/kmipcore/managed_object.hpp b/kmipcore/include/kmipcore/managed_object.hpp new file mode 100644 index 0000000..6c7b122 --- /dev/null +++ b/kmipcore/include/kmipcore/managed_object.hpp @@ -0,0 +1,102 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KMIPCORE_MANAGED_OBJECT_HPP +#define KMIPCORE_MANAGED_OBJECT_HPP + +#include "kmipcore/kmip_attributes.hpp" + +#include + +namespace kmipcore { + + /** + * @brief Base class for KMIP managed objects (Key, Secret, Certificate, etc.) + * + * Encapsulates the common pattern: raw payload bytes + type-safe @ref Attributes bag. + * All KMIP objects in the KMS store metadata in a consistent way through this base. + */ + class ManagedObject { + public: + /** @brief Constructs an empty managed object. */ + ManagedObject() = default; + + /** + * @brief Constructs a managed object from payload and attributes. + * @param value Raw object bytes. + * @param attrs Type-safe attribute bag. + */ + explicit ManagedObject( + const std::vector &value, + Attributes attrs = {} + ) + : value_(value), attributes_(std::move(attrs)) {} + + /** @brief Virtual destructor for subclass-safe cleanup. */ + virtual ~ManagedObject() = default; + + ManagedObject(const ManagedObject &) = default; + ManagedObject &operator=(const ManagedObject &) = default; + ManagedObject(ManagedObject &&) noexcept = default; + ManagedObject &operator=(ManagedObject &&) noexcept = default; + + // ---- Raw bytes ---- + + /** @brief Returns raw object payload bytes. */ + [[nodiscard]] const std::vector &value() const noexcept { + return value_; + } + + /** @brief Replaces raw object payload bytes. */ + void set_value(const std::vector &val) noexcept { value_ = val; } + + // ---- Attribute bag ---- + + /** @brief Returns the type-safe attribute bag (read-only). */ + [[nodiscard]] const Attributes &attributes() const noexcept { + return attributes_; + } + + /** @brief Returns the type-safe attribute bag (mutable). */ + [[nodiscard]] Attributes &attributes() noexcept { return attributes_; } + + // ---- Generic string attribute helpers ---- + + /** + * @brief Returns a generic string attribute value, or empty string. + * @note Does not look up typed attributes (state, algorithm, …). + * Use attributes().object_state() etc. for those. + */ + [[nodiscard]] const std::string & + attribute_value(const std::string &name) const noexcept { + return attributes_.get(name); + } + + /** @brief Sets a string attribute by name (routes typed attrs to typed setters). */ + void set_attribute(const std::string &name, std::string val) noexcept { + attributes_.set(name, std::move(val)); + } + + protected: + std::vector value_; + Attributes attributes_; + }; + +} // namespace kmipcore + +#endif // KMIPCORE_MANAGED_OBJECT_HPP + diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp index cef2227..950fe53 100644 --- a/kmipcore/include/kmipcore/secret.hpp +++ b/kmipcore/include/kmipcore/secret.hpp @@ -1,36 +1,43 @@ #pragma once +#include "kmipcore/managed_object.hpp" #include "kmipcore/kmip_enums.hpp" #include #include -#include #include namespace kmipcore { /** * @brief Minimal KMIP Secret Data model. + * + * Intrinsic properties (raw bytes, secret data type) are stored as dedicated + * typed fields. All other attributes — including the object lifecycle state, + * Name, Object Group, dates, etc. — live in the type-safe @ref Attributes bag. */ - class Secret { + class Secret : public ManagedObject { public: /** @brief Constructs an empty secret. */ Secret() = default; - /** @brief Constructs a secret from payload and metadata. */ - Secret(const std::vector &val, state st, secret_data_type type) - : value_(val), state_(st), secret_type_(type) {} - /** @brief Returns raw secret payload bytes. */ - [[nodiscard]] const std::vector &value() const noexcept { return value_; } - - /** @brief Replaces raw secret payload bytes. */ - void set_value(const std::vector &val) noexcept { value_ = val; } - - /** @brief Returns lifecycle state of this secret object. */ - [[nodiscard]] state get_state() const noexcept { return state_; } - - /** @brief Sets lifecycle state of this secret object. */ - void set_state(state st) noexcept { state_ = st; } + /** + * @brief Constructs a secret from payload and metadata. + * @param val Raw secret bytes. + * @param type KMIP secret data type. + * @param attrs Attribute bag (may include state, name, …). + */ + Secret( + const std::vector &val, + secret_data_type type, + Attributes attrs = {} + ) + : ManagedObject(val, std::move(attrs)), secret_type_(type) {} + + Secret(const Secret &) = default; + Secret &operator=(const Secret &) = default; + Secret(Secret &&) noexcept = default; + Secret &operator=(Secret &&) noexcept = default; /** @brief Returns KMIP secret data type discriminator. */ [[nodiscard]] secret_data_type get_secret_type() const noexcept { @@ -38,33 +45,17 @@ namespace kmipcore { } /** @brief Sets KMIP secret data type discriminator. */ - void set_secret_type(secret_data_type type) noexcept { - secret_type_ = type; - } + void set_secret_type(secret_data_type type) noexcept { secret_type_ = type; } - /** @brief Returns all attached secret attributes. */ - [[nodiscard]] const std::unordered_map &attributes() const noexcept { - return secret_attributes; - } + // ---- Typed convenience accessors ---- - /** - * @brief Returns value of a secret attribute, or empty string if absent. - * @param name Attribute name. - * @return Attribute value, or empty string when the attribute is missing. - */ - [[nodiscard]] const std::string & - attribute_value(const std::string &name) const noexcept { - static const std::string empty; - const auto it = secret_attributes.find(name); - return it != secret_attributes.end() ? it->second : empty; + /** @brief Returns the object lifecycle state (KMIP_STATE_PRE_ACTIVE when unset). */ + [[nodiscard]] kmipcore::state get_state() const noexcept { + return attributes_.object_state(); } - /** @brief Sets or replaces one secret attribute value. */ - void set_attribute( - const std::string &name, const std::string &val - ) noexcept { - secret_attributes[name] = val; - } + /** @brief Sets the object lifecycle state. */ + void set_state(kmipcore::state st) noexcept { attributes_.set_state(st); } /** * @brief Creates a Secret from text bytes. @@ -75,10 +66,14 @@ namespace kmipcore { [[nodiscard]] static Secret from_text( std::string_view text, secret_data_type type = secret_data_type::KMIP_SECDATA_PASSWORD, - state st = state::KMIP_STATE_PRE_ACTIVE + kmipcore::state st = state::KMIP_STATE_PRE_ACTIVE ) { - return { - std::vector(text.begin(), text.end()), st, type + Attributes attrs; + attrs.set_state(st); + return Secret{ + std::vector(text.begin(), text.end()), + type, + std::move(attrs) }; } @@ -88,10 +83,7 @@ namespace kmipcore { } private: - std::vector value_; - state state_ = state::KMIP_STATE_PRE_ACTIVE; secret_data_type secret_type_ = secret_data_type::KMIP_SECDATA_PASSWORD; - std::unordered_map secret_attributes; }; } // namespace kmipcore diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp index d6373cc..0e66d87 100644 --- a/kmipcore/src/attributes_parser.cpp +++ b/kmipcore/src/attributes_parser.cpp @@ -6,60 +6,12 @@ #include #include #include -#include namespace kmipcore { namespace { - - [[nodiscard]] std::string attribute_key_from_name(std::string_view name) { - if (name == "Name") return std::string(KMIP_ATTR_NAME_NAME); - if (name == "Object Group") return std::string(KMIP_ATTR_NAME_GROUP); - if (name == "State") return std::string(KMIP_ATTR_NAME_STATE); - if (name == "Unique Identifier") return std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); - if (name == "UniqueID") return std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); // Legacy/PyKMIP compat - if (name == "Initial Date") return std::string(KMIP_ATTR_NAME_INITIAL_DATE); - if (name == "Activation Date") return std::string(KMIP_ATTR_NAME_ACTIVATION_DATE); - if (name == "Process Start Date") return std::string(KMIP_ATTR_NAME_PROCESS_START_DATE); - if (name == "Protect Stop Date") return std::string(KMIP_ATTR_NAME_PROTECT_STOP_DATE); - if (name == "Deactivation Date") return std::string(KMIP_ATTR_NAME_DEACTIVATION_DATE); - if (name == "Destroy Date") return std::string(KMIP_ATTR_NAME_DESTROY_DATE); - if (name == "Compromise Occurrence Date") return std::string(KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE); - if (name == "Compromise Date") return std::string(KMIP_ATTR_NAME_COMPROMISE_DATE); - if (name == "Archive Date") return std::string(KMIP_ATTR_NAME_ARCHIVE_DATE); - if (name == "Last Change Date") return std::string(KMIP_ATTR_NAME_LAST_CHANGE_DATE); - if (name == "Cryptographic Algorithm") return std::string(KMIP_ATTR_NAME_CRYPTO_ALG); - if (name == "Cryptographic Length") return std::string(KMIP_ATTR_NAME_CRYPTO_LEN); - if (name == "Cryptographic Usage Mask") return std::string(KMIP_ATTR_NAME_CRYPTO_USAGE_MASK); - if (name == "Contact Information") return std::string(KMIP_ATTR_NAME_CONTACT_INFO); - if (name == "Operation Policy Name") return std::string(KMIP_ATTR_NAME_OPERATION_POLICY_NAME); - return std::string(name); - } - - [[nodiscard]] std::string crypto_alg_to_string(std::int32_t val) { - switch (val) { - case KMIP_CRYPTOALG_DES: return "DES"; - case KMIP_CRYPTOALG_TRIPLE_DES: return "3DES"; - case KMIP_CRYPTOALG_AES: return "AES"; - case KMIP_CRYPTOALG_RSA: return "RSA"; - case KMIP_CRYPTOALG_DSA: return "DSA"; - case KMIP_CRYPTOALG_ECDSA: return "ECDSA"; - case KMIP_CRYPTOALG_HMAC_SHA1: return "HMAC-SHA1"; - case KMIP_CRYPTOALG_HMAC_SHA224: return "HMAC-SHA224"; - case KMIP_CRYPTOALG_HMAC_SHA256: return "HMAC-SHA256"; - case KMIP_CRYPTOALG_HMAC_SHA384: return "HMAC-SHA384"; - case KMIP_CRYPTOALG_HMAC_SHA512: return "HMAC-SHA512"; - case KMIP_CRYPTOALG_HMAC_MD5: return "HMAC-MD5"; - case KMIP_CRYPTOALG_DH: return "DH"; - case KMIP_CRYPTOALG_ECDH: return "ECDH"; - default: return std::to_string(val); - } - } - [[nodiscard]] std::string date_to_string(int64_t seconds) { - // Return ISO 8601 format or similar "YYYY-MM-DD HH:MM:SS" - // KMIP Date-Time is standard UNIX epoch seconds. const auto t = static_cast(seconds); std::tm tm_buf{}; #ifdef _WIN32 @@ -72,71 +24,90 @@ namespace kmipcore { return oss.str(); } - [[nodiscard]] std::string parse_attribute_value( - std::string_view attribute_name, const std::shared_ptr &value + /** Store one KMIP attribute element into @p result, preserving native types + * for user-defined / generic attributes. */ + void store_generic( + Attributes &result, + const std::string &name, + const std::shared_ptr &value ) { - if (!value) { - return {}; - } - + if (!value) return; switch (value->type) { case type::KMIP_TYPE_TEXT_STRING: - return value->toString(); + result.set(name, value->toString()); + break; case type::KMIP_TYPE_INTEGER: - return std::to_string(value->toInt()); - case type::KMIP_TYPE_DATE_TIME: - return date_to_string(value->toLong()); - case type::KMIP_TYPE_LONG_INTEGER: - return std::to_string(value->toLong()); + // Routes well-known names (Length, Mask) to typed setters; others go to generic. + result.set(name, value->toInt()); + break; case type::KMIP_TYPE_ENUMERATION: - if (attribute_name == KMIP_ATTR_NAME_STATE) { - return state_to_string(static_cast(value->toEnum())); - } - if (attribute_name == KMIP_ATTR_NAME_CRYPTO_ALG) { - return crypto_alg_to_string(value->toEnum()); - } - return std::to_string(value->toEnum()); + // Routes well-known names (Algorithm, State) to typed setters. + result.set(name, value->toEnum()); + break; + case type::KMIP_TYPE_LONG_INTEGER: + result.set(name, value->toLong()); + break; + case type::KMIP_TYPE_DATE_TIME: + // Store as ISO 8601 string — dates are primarily for display. + result.set(name, date_to_string(value->toLong())); + break; case type::KMIP_TYPE_STRUCTURE: - if (attribute_name == KMIP_ATTR_NAME_NAME) { - if (auto name_value = - value->getChild(tag::KMIP_TAG_NAME_VALUE); - name_value) { - return name_value->toString(); - } + // Name attribute: extract the NameValue child. + if (auto name_val = value->getChild(tag::KMIP_TAG_NAME_VALUE); name_val) { + result.set(name, name_val->toString()); } break; default: break; } - - return {}; } } // namespace - std::unordered_map AttributesParser::parse( + Attributes AttributesParser::parse( const std::vector> &attributes ) { - std::unordered_map res; + Attributes result; + for (const auto &attribute : attributes) { - if (attribute == nullptr || - attribute->tag != tag::KMIP_TAG_ATTRIBUTE) { + if (!attribute || attribute->tag != tag::KMIP_TAG_ATTRIBUTE) continue; + + auto attr_name_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); + auto attr_value_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); + if (!attr_name_elem) continue; + + const auto raw_name = attr_name_elem->toString(); + + // ---- Well-known typed attributes: handled explicitly for clarity ---- + if (raw_name == "Cryptographic Algorithm") { + if (attr_value_elem) + result.set_algorithm(static_cast(attr_value_elem->toEnum())); continue; } - - auto attribute_name = - attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); - auto attribute_value = - attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); - if (!attribute_name) { + if (raw_name == "Cryptographic Length") { + if (attr_value_elem) result.set_crypto_length(attr_value_elem->toInt()); + continue; + } + if (raw_name == "Cryptographic Usage Mask") { + if (attr_value_elem) + result.set_usage_mask(static_cast(attr_value_elem->toInt())); continue; } + if (raw_name == "State") { + if (attr_value_elem) + result.set_state(static_cast(attr_value_elem->toEnum())); + continue; + } + + // ---- Legacy name normalisation ---- + const std::string name = + (raw_name == "UniqueID") ? std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER) : raw_name; - const auto name = attribute_name->toString(); - res[attribute_key_from_name(name)] = - parse_attribute_value(name, attribute_value); + // ---- All other attributes: preserve native type in generic map ---- + store_generic(result, name, attr_value_elem); } - return res; + + return result; } } // namespace kmipcore diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp index 45df6d5..5ee295a 100644 --- a/kmipcore/src/key_parser.cpp +++ b/kmipcore/src/key_parser.cpp @@ -20,8 +20,6 @@ #include "kmipcore/attributes_parser.hpp" #include "kmipcore/kmip_basics.hpp" -#include - namespace kmipcore { namespace { @@ -63,21 +61,22 @@ namespace kmipcore { auto raw_bytes = key_material->toBytes(); std::vector kv(raw_bytes.begin(), raw_bytes.end()); - auto algorithm = key_block->getChild( - tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM - ); - auto key_attributes = AttributesParser::parse( + // Parse attributes from the key value's Attribute children. + Attributes key_attrs = AttributesParser::parse( key_value->getChildren(tag::KMIP_TAG_ATTRIBUTE) ); - return Key( - std::move(kv), - key_type, - algorithm ? static_cast(algorithm->toEnum()) - : cryptographic_algorithm::KMIP_CRYPTOALG_UNSET, - cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET, - std::move(key_attributes) - ); + // Algorithm and Length may also appear directly in the Key Block. + if (auto alg_elem = key_block->getChild(tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM)) { + key_attrs.set_algorithm( + static_cast(alg_elem->toEnum()) + ); + } + if (auto len_elem = key_block->getChild(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH)) { + key_attrs.set_crypto_length(len_elem->toInt()); + } + + return Key(kv, key_type, std::move(key_attrs)); } } // anonymous namespace diff --git a/kmipcore/src/kmip_attributes.cpp b/kmipcore/src/kmip_attributes.cpp new file mode 100644 index 0000000..73d7217 --- /dev/null +++ b/kmipcore/src/kmip_attributes.cpp @@ -0,0 +1,299 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "kmipcore/kmip_attributes.hpp" + +#include "kmipcore/kmip_attribute_names.hpp" + +#include +#include +#include + +namespace kmipcore { + + namespace { + + // ---- Algorithm string conversion ---------------------------------------- + + [[nodiscard]] std::string algorithm_to_string(cryptographic_algorithm alg) { + using A = cryptographic_algorithm; + switch (alg) { + case A::KMIP_CRYPTOALG_DES: return "DES"; + case A::KMIP_CRYPTOALG_TRIPLE_DES: return "3DES"; + case A::KMIP_CRYPTOALG_AES: return "AES"; + case A::KMIP_CRYPTOALG_RSA: return "RSA"; + case A::KMIP_CRYPTOALG_DSA: return "DSA"; + case A::KMIP_CRYPTOALG_ECDSA: return "ECDSA"; + case A::KMIP_CRYPTOALG_HMAC_SHA1: return "HMAC-SHA1"; + case A::KMIP_CRYPTOALG_HMAC_SHA224: return "HMAC-SHA224"; + case A::KMIP_CRYPTOALG_HMAC_SHA256: return "HMAC-SHA256"; + case A::KMIP_CRYPTOALG_HMAC_SHA384: return "HMAC-SHA384"; + case A::KMIP_CRYPTOALG_HMAC_SHA512: return "HMAC-SHA512"; + case A::KMIP_CRYPTOALG_HMAC_MD5: return "HMAC-MD5"; + case A::KMIP_CRYPTOALG_DH: return "DH"; + case A::KMIP_CRYPTOALG_ECDH: return "ECDH"; + default: + return std::to_string(static_cast(alg)); + } + } + + [[nodiscard]] std::optional + parse_algorithm_string(std::string_view s) { + using A = cryptographic_algorithm; + if (s == "AES") return A::KMIP_CRYPTOALG_AES; + if (s == "RSA") return A::KMIP_CRYPTOALG_RSA; + if (s == "DSA") return A::KMIP_CRYPTOALG_DSA; + if (s == "ECDSA") return A::KMIP_CRYPTOALG_ECDSA; + if (s == "DES") return A::KMIP_CRYPTOALG_DES; + if (s == "3DES") return A::KMIP_CRYPTOALG_TRIPLE_DES; + if (s == "DH") return A::KMIP_CRYPTOALG_DH; + if (s == "ECDH") return A::KMIP_CRYPTOALG_ECDH; + if (s == "HMAC-SHA1") return A::KMIP_CRYPTOALG_HMAC_SHA1; + if (s == "HMAC-SHA224") return A::KMIP_CRYPTOALG_HMAC_SHA224; + if (s == "HMAC-SHA256") return A::KMIP_CRYPTOALG_HMAC_SHA256; + if (s == "HMAC-SHA384") return A::KMIP_CRYPTOALG_HMAC_SHA384; + if (s == "HMAC-SHA512") return A::KMIP_CRYPTOALG_HMAC_SHA512; + if (s == "HMAC-MD5") return A::KMIP_CRYPTOALG_HMAC_MD5; + // Try raw numeric + if (s.empty()) return std::nullopt; + const std::string str(s); + char *end = nullptr; + errno = 0; + const long v = std::strtol(str.c_str(), &end, 10); + if (errno == 0 && end != str.c_str() && *end == '\0') { + return static_cast(v); + } + return std::nullopt; + } + + [[nodiscard]] std::optional parse_state_string(std::string_view s) { + constexpr std::string_view prefix = "KMIP_STATE_"; + if (s.starts_with(prefix)) s.remove_prefix(prefix.size()); + if (s == "PRE_ACTIVE") return state::KMIP_STATE_PRE_ACTIVE; + if (s == "ACTIVE") return state::KMIP_STATE_ACTIVE; + if (s == "DEACTIVATED") return state::KMIP_STATE_DEACTIVATED; + if (s == "COMPROMISED") return state::KMIP_STATE_COMPROMISED; + if (s == "DESTROYED") return state::KMIP_STATE_DESTROYED; + if (s == "DESTROYED_COMPROMISED") return state::KMIP_STATE_DESTROYED_COMPROMISED; + return std::nullopt; + } + + // ---- AttributeValue → string -------------------------------------------- + + [[nodiscard]] std::string value_to_string(const Attributes::AttributeValue &v) { + return std::visit([](const auto &val) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return val; + } else if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else { + return std::to_string(val); + } + }, v); + } + + } // namespace + + // --------------------------------------------------------------------------- + // Typed getters + // --------------------------------------------------------------------------- + + cryptographic_algorithm Attributes::algorithm() const noexcept { + return algo_.value_or(cryptographic_algorithm::KMIP_CRYPTOALG_UNSET); + } + std::optional Attributes::crypto_length() const noexcept { + return crypto_length_; + } + cryptographic_usage_mask Attributes::usage_mask() const noexcept { + return usage_mask_.value_or(cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET); + } + state Attributes::object_state() const noexcept { + return state_.value_or(state::KMIP_STATE_PRE_ACTIVE); + } + + // --------------------------------------------------------------------------- + // Typed setters + // --------------------------------------------------------------------------- + + Attributes &Attributes::set_algorithm(cryptographic_algorithm alg) noexcept { + if (alg == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) algo_.reset(); + else algo_ = alg; + return *this; + } + Attributes &Attributes::set_crypto_length(int32_t len) noexcept { + crypto_length_ = len; + return *this; + } + Attributes &Attributes::clear_crypto_length() noexcept { + crypto_length_.reset(); + return *this; + } + Attributes &Attributes::set_usage_mask(cryptographic_usage_mask mask) noexcept { + if (mask == cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) usage_mask_.reset(); + else usage_mask_ = mask; + return *this; + } + Attributes &Attributes::set_state(state st) noexcept { + state_ = st; + return *this; + } + + // --------------------------------------------------------------------------- + // Generic setters — with routing for well-known names + // --------------------------------------------------------------------------- + + Attributes &Attributes::set(std::string_view name, std::string value) { + if (name == KMIP_ATTR_NAME_CRYPTO_ALG) { + if (const auto parsed = parse_algorithm_string(value); parsed) algo_ = *parsed; + return *this; + } + if (name == KMIP_ATTR_NAME_CRYPTO_LEN) { + const std::string s(value); + char *end = nullptr; errno = 0; + const long v = std::strtol(s.c_str(), &end, 10); + if (errno == 0 && end != s.c_str() && *end == '\0') crypto_length_ = static_cast(v); + return *this; + } + if (name == KMIP_ATTR_NAME_CRYPTO_USAGE_MASK) { + const std::string s(value); + char *end = nullptr; errno = 0; + const long v = std::strtol(s.c_str(), &end, 10); + if (errno == 0 && end != s.c_str() && *end == '\0') { + const auto mask = static_cast(v); + if (mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) usage_mask_ = mask; + } + return *this; + } + if (name == KMIP_ATTR_NAME_STATE) { + if (const auto parsed = parse_state_string(value); parsed) state_ = *parsed; + return *this; + } + generic_[std::string(name)] = std::move(value); + return *this; + } + + Attributes &Attributes::set(std::string_view name, int32_t value) noexcept { + if (name == KMIP_ATTR_NAME_CRYPTO_LEN) { crypto_length_ = value; return *this; } + if (name == KMIP_ATTR_NAME_CRYPTO_USAGE_MASK) { + const auto mask = static_cast(value); + if (mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) usage_mask_ = mask; + return *this; + } + if (name == KMIP_ATTR_NAME_CRYPTO_ALG) { + const auto alg = static_cast(value); + if (alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) algo_ = alg; + return *this; + } + if (name == KMIP_ATTR_NAME_STATE) { + state_ = static_cast(value); + return *this; + } + generic_[std::string(name)] = value; + return *this; + } + + Attributes &Attributes::set(std::string_view name, int64_t value) noexcept { + generic_[std::string(name)] = value; + return *this; + } + + Attributes &Attributes::set(std::string_view name, bool value) noexcept { + generic_[std::string(name)] = value; + return *this; + } + + void Attributes::remove(std::string_view name) noexcept { + generic_.erase(std::string(name)); + } + + // --------------------------------------------------------------------------- + // Generic getters + // --------------------------------------------------------------------------- + + bool Attributes::has_attribute(std::string_view name) const noexcept { + if (name == KMIP_ATTR_NAME_CRYPTO_ALG) return algo_.has_value(); + if (name == KMIP_ATTR_NAME_CRYPTO_LEN) return crypto_length_.has_value(); + if (name == KMIP_ATTR_NAME_CRYPTO_USAGE_MASK) return usage_mask_.has_value(); + if (name == KMIP_ATTR_NAME_STATE) return state_.has_value(); + return generic_.count(std::string(name)) > 0; + } + + const std::string &Attributes::get(std::string_view name) const noexcept { + static const std::string empty; + const auto it = generic_.find(std::string(name)); + if (it == generic_.end()) return empty; + if (const auto *s = std::get_if(&it->second)) return *s; + return empty; + } + + std::optional Attributes::get_as_string(std::string_view name) const { + const auto it = generic_.find(std::string(name)); + if (it == generic_.end()) return std::nullopt; + return value_to_string(it->second); + } + + std::optional Attributes::get_int(std::string_view name) const noexcept { + const auto it = generic_.find(std::string(name)); + if (it == generic_.end()) return std::nullopt; + if (const auto *i = std::get_if(&it->second)) return *i; + return std::nullopt; + } + + std::optional Attributes::get_long(std::string_view name) const noexcept { + const auto it = generic_.find(std::string(name)); + if (it == generic_.end()) return std::nullopt; + if (const auto *l = std::get_if(&it->second)) return *l; + return std::nullopt; + } + + const Attributes::GenericMap &Attributes::generic() const noexcept { + return generic_; + } + + // --------------------------------------------------------------------------- + // Iteration / export + // --------------------------------------------------------------------------- + + Attributes::StringMap Attributes::as_string_map() const { + StringMap result; + for (const auto &[key, val] : generic_) { + result[key] = value_to_string(val); + } + if (algo_.has_value()) + result[std::string(KMIP_ATTR_NAME_CRYPTO_ALG)] = algorithm_to_string(*algo_); + if (crypto_length_.has_value()) + result[std::string(KMIP_ATTR_NAME_CRYPTO_LEN)] = std::to_string(*crypto_length_); + if (usage_mask_.has_value()) + result[std::string(KMIP_ATTR_NAME_CRYPTO_USAGE_MASK)] = + std::to_string(static_cast(*usage_mask_)); + if (state_.has_value()) + result[std::string(KMIP_ATTR_NAME_STATE)] = state_to_string(*state_); + return result; + } + + Attributes &Attributes::merge(const Attributes &other) { + if (other.algo_.has_value()) algo_ = other.algo_; + if (other.crypto_length_.has_value()) crypto_length_ = other.crypto_length_; + if (other.usage_mask_.has_value()) usage_mask_ = other.usage_mask_; + if (other.state_.has_value()) state_ = other.state_; + for (const auto &[k, v] : other.generic_) generic_[k] = v; + return *this; + } + +} // namespace kmipcore + diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index 638401d..cdd90f0 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -1,9 +1,12 @@ #include "kmipcore/kmip_requests.hpp" +#include "kmipcore/kmip_attribute_names.hpp" + +#include + namespace kmipcore { - namespace detail { - std::shared_ptr make_text_attribute( + namespace detail { std::shared_ptr make_text_attribute( const std::string &attribute_name, const std::string &value ) { auto attribute = @@ -129,16 +132,21 @@ namespace kmipcore { return key_block; } std::shared_ptr make_key_object_from_key(const Key &key) { + const auto alg = key.attributes().algorithm(); + const std::optional key_alg = + (alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) + ? std::optional(static_cast(alg)) + : std::nullopt; + const int32_t key_len = key.attributes().crypto_length() + .value_or(static_cast(key.value().size() * 8)); switch (key.type()) { case KeyType::SYMMETRIC_KEY: { auto symmetric_key = Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); symmetric_key->asStructure()->add(make_key_block( KMIP_KEYFORMAT_RAW, key.value(), - key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET - ? std::nullopt - : std::optional(static_cast(key.algorithm())), - static_cast(key.value().size() * 8) + key_alg, + key_len )); return symmetric_key; } @@ -147,10 +155,8 @@ namespace kmipcore { private_key->asStructure()->add(make_key_block( KMIP_KEYFORMAT_PKCS8, key.value(), - key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET - ? std::nullopt - : std::optional(static_cast(key.algorithm())), - static_cast(key.value().size() * 8) + key_alg, + key_len )); return private_key; } @@ -159,10 +165,8 @@ namespace kmipcore { public_key->asStructure()->add(make_key_block( KMIP_KEYFORMAT_X509, key.value(), - key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_UNSET - ? std::nullopt - : std::optional(static_cast(key.algorithm())), - static_cast(key.value().size() * 8) + key_alg, + key_len )); return public_key; } @@ -312,15 +316,19 @@ namespace kmipcore { attributes.push_back(detail::make_text_attribute("Object Group", group)); } - if (key.algorithm() != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { + if (const auto alg = key.attributes().algorithm(); + alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { attributes.push_back( detail::make_enum_attribute( - "Cryptographic Algorithm", - static_cast(key.algorithm()) + "Cryptographic Algorithm", static_cast(alg) ) ); } - if (!key.value().empty()) { + if (const auto len = key.attributes().crypto_length(); len.has_value()) { + attributes.push_back( + detail::make_integer_attribute("Cryptographic Length", *len) + ); + } else if (!key.value().empty()) { attributes.push_back( detail::make_integer_attribute( "Cryptographic Length", @@ -328,24 +336,34 @@ namespace kmipcore { ) ); } - if (key.usage_mask() != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { + if (const auto mask = key.attributes().usage_mask(); + mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { attributes.push_back( detail::make_integer_attribute( - "Cryptographic Usage Mask", - static_cast(key.usage_mask()) + "Cryptographic Usage Mask", static_cast(mask) ) ); } - for (const auto &attr : key.attributes()) { - if (attr.first == "Name" || - attr.first == "Object Group" || - attr.first == "Cryptographic Algorithm" || - attr.first == "Cryptographic Length" || - attr.first == "Cryptographic Usage Mask") { + for (const auto &[attr_name, attr_val] : key.attributes().generic()) { + if (attr_name == "Name" || attr_name == "Object Group") { continue; } - attributes.push_back(detail::make_text_attribute(attr.first, attr.second)); + // Convert AttributeValue variant to string + std::string str_val = std::visit( + [](const auto &val) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return val; + } else if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else { + return std::to_string(val); + } + }, + attr_val + ); + attributes.push_back(detail::make_text_attribute(attr_name, str_val)); } auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp index d5a8408..8b26213 100644 --- a/kmipcore/tests/test_parsers.cpp +++ b/kmipcore/tests/test_parsers.cpp @@ -215,7 +215,7 @@ void test_key_parser_symmetric() { GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); Key key = KeyParser::parseGetKeyResponse(get_resp); - assert(key.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES); + assert(key.attributes().algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES); assert(key.value() == actual_key); std::cout << "KeyParser Symmetric Key test passed" << std::endl; @@ -270,9 +270,9 @@ void test_key_parser_secret_binary() { item.setResponsePayload(payload); GetResponseBatchItem get_resp = GetResponseBatchItem::fromBatchItem(item); - Secret secret = KeyParser::parseGetSecretResponse(get_resp); + auto secret = KeyParser::parseGetSecretResponse(get_resp); - assert(secret.get_secret_type() == secret_data_type::KMIP_SECDATA_PASSWORD); + assert(secret.get_state() == state::KMIP_STATE_PRE_ACTIVE); assert(secret.value() == bytes); assert(secret.get_state() == state::KMIP_STATE_PRE_ACTIVE); assert(secret.as_text().size() == bytes.size()); @@ -358,11 +358,12 @@ void test_attributes_parser() { auto parsed_attrs = AttributesParser::parse(attributes); - assert(parsed_attrs.count("Name")); - assert(parsed_attrs.at("Name") == "MyKey"); + assert(parsed_attrs.has_attribute("Name")); + assert(parsed_attrs.get("Name") == "MyKey"); - assert(parsed_attrs.count("Cryptographic Length")); - assert(parsed_attrs.at("Cryptographic Length") == "256"); + // Cryptographic Length is now stored as a typed field. + assert(parsed_attrs.crypto_length().has_value()); + assert(parsed_attrs.crypto_length().value() == 256); std::cout << "AttributesParser test passed" << std::endl; } @@ -402,12 +403,12 @@ void test_attributes_parser_extended() { auto parsed_attrs = AttributesParser::parse(attributes); - assert(parsed_attrs.count("Activation Date")); - std::string date_str = parsed_attrs.at("Activation Date"); + assert(parsed_attrs.has_attribute("Activation Date")); + std::string date_str = parsed_attrs.get("Activation Date"); assert(date_str.find("2023-03-15") != std::string::npos); - assert(parsed_attrs.count("Cryptographic Algorithm")); - assert(parsed_attrs.at("Cryptographic Algorithm") == "AES"); + // Cryptographic Algorithm is now a typed field. + assert(parsed_attrs.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES); std::cout << "AttributesParser Extended test passed" << std::endl; } From cce9886110a289057e2e7759395e52027ee3a8b2 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sun, 29 Mar 2026 07:59:56 +0300 Subject: [PATCH 07/11] PS-10068 Fix PR API refinement https://perconadev.atlassian.net/browse/PS-10949 API cleanup and refinement. Better crypto usage mask handling, AES key sizes --- kmipclient/examples/example_create_aes.cpp | 11 +++- kmipclient/examples/example_register_key.cpp | 10 ++- kmipclient/include/kmipclient/KmipClient.hpp | 13 +++- .../include/kmipclient/SymmetricKey.hpp | 4 +- kmipclient/include/kmipclient/types.hpp | 9 +++ kmipclient/src/KmipClient.cpp | 15 ++++- kmipclient/src/SymmetricKey.cpp | 6 +- .../tests/KmipClientIntegrationTest.cpp | 42 ++++++++++-- kmipcore/include/kmipcore/kmip_formatter.hpp | 8 +++ kmipcore/include/kmipcore/kmip_requests.hpp | 11 +++- kmipcore/src/kmip_attributes.cpp | 3 +- kmipcore/src/kmip_formatter.cpp | 65 +++++++++++++++++++ kmipcore/src/kmip_requests.cpp | 15 ++++- 13 files changed, 187 insertions(+), 25 deletions(-) diff --git a/kmipclient/examples/example_create_aes.cpp b/kmipclient/examples/example_create_aes.cpp index 0ac8035..2dfa22a 100644 --- a/kmipclient/examples/example_create_aes.cpp +++ b/kmipclient/examples/example_create_aes.cpp @@ -50,7 +50,16 @@ int main(int argc, char **argv) { Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); try { - auto key_id = kmip.client().op_create_aes_key(argv[6], "TestGroup"); + auto key_id = kmip.client().op_create_aes_key( + argv[6], + "TestGroup", + aes_key_size::AES_256, + static_cast( + kmipcore::KMIP_CRYPTOMASK_ENCRYPT | + kmipcore::KMIP_CRYPTOMASK_DECRYPT | + kmipcore::KMIP_CRYPTOMASK_MAC_GENERATE + ) + ); std::cout << "Key ID: " << key_id << std::endl; auto key = kmip.client().op_get_key(key_id, true); diff --git a/kmipclient/examples/example_register_key.cpp b/kmipclient/examples/example_register_key.cpp index 51ae08e..ad4f639 100644 --- a/kmipclient/examples/example_register_key.cpp +++ b/kmipclient/examples/example_register_key.cpp @@ -50,7 +50,14 @@ int main(int argc, char **argv) { KmipClient client(net_client); try { - auto generated_key = SymmetricKey::generate_aes(256); + auto generated_key = SymmetricKey::generate_aes(aes_key_size::AES_256); + generated_key.attributes().set_usage_mask( + static_cast( + kmipcore::KMIP_CRYPTOMASK_ENCRYPT | + kmipcore::KMIP_CRYPTOMASK_DECRYPT | + kmipcore::KMIP_CRYPTOMASK_MAC_GENERATE + ) + ); std::cout << "Generated AES-256 key (hex): "; print_hex(generated_key.value()); @@ -58,6 +65,7 @@ int main(int argc, char **argv) { std::cout << "Key registered. ID: " << key_id << std::endl; auto fetched_key = client.op_get_key(key_id); + std::cout << "Fetched key from server (hex): "; print_hex(fetched_key->value()); std::cout << "Attributes:" << std::endl; diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index 115ddc0..0c74446 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -124,14 +124,23 @@ namespace kmipclient { /** - * @brief Executes KMIP Create to generate a server-side AES-256 key. + * @brief Executes KMIP Create to generate a server-side AES key. * @param name Value of the KMIP "Name" attribute. * @param group Value of the KMIP "Object Group" attribute. + * @param key_size AES key size (typed constants: AES_128/AES_192/AES_256). + * @param usage_mask Cryptographic Usage Mask bits to assign to the key. * @return Unique identifier of the created key. * @throws kmipcore::KmipException on protocol or server-side failure. */ [[nodiscard]] std::string - op_create_aes_key(const std::string &name, const std::string &group) const; + op_create_aes_key( + const std::string &name, + const std::string &group, + aes_key_size key_size = aes_key_size::AES_256, + cryptographic_usage_mask usage_mask = static_cast( + kmipcore::KMIP_CRYPTOMASK_ENCRYPT | kmipcore::KMIP_CRYPTOMASK_DECRYPT + ) + ) const; /** * @brief Executes KMIP Get and decodes a key object. diff --git a/kmipclient/include/kmipclient/SymmetricKey.hpp b/kmipclient/include/kmipclient/SymmetricKey.hpp index 4d2b5a4..3dfa883 100644 --- a/kmipclient/include/kmipclient/SymmetricKey.hpp +++ b/kmipclient/include/kmipclient/SymmetricKey.hpp @@ -34,7 +34,9 @@ namespace kmipclient { [[nodiscard]] static SymmetricKey aes_from_hex(const std::string &hex); [[nodiscard]] static SymmetricKey aes_from_base64(const std::string &base64); [[nodiscard]] static SymmetricKey aes_from_value(const std::vector &val); - [[nodiscard]] static SymmetricKey generate_aes(size_t size_bits); + [[nodiscard]] static SymmetricKey generate_aes( + aes_key_size key_size = aes_key_size::AES_256 + ); }; } // namespace kmipclient diff --git a/kmipclient/include/kmipclient/types.hpp b/kmipclient/include/kmipclient/types.hpp index a0cabc2..f339928 100644 --- a/kmipclient/include/kmipclient/types.hpp +++ b/kmipclient/include/kmipclient/types.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "kmipcore/kmip_attributes.hpp" #include "kmipcore/key.hpp" #include "kmipcore/kmip_attribute_names.hpp" @@ -55,4 +57,11 @@ namespace kmipclient { /** @brief Re-export stream formatter overloads from kmipcore. */ using kmipcore::operator<<; + /** @brief Strongly-typed AES key sizes for KMIP Create/Register APIs. */ + enum class aes_key_size : int32_t { + AES_128 = 128, + AES_192 = 192, + AES_256 = 256, + }; + } // namespace kmipclient diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index e5b5203..bd611e2 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -48,7 +48,8 @@ static std::vector default_get_attrs(bool all_attributes) { KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_OPERATION_POLICY_NAME, KMIP_ATTR_NAME_CRYPTO_ALG, - KMIP_ATTR_NAME_CRYPTO_LEN + KMIP_ATTR_NAME_CRYPTO_LEN, + KMIP_ATTR_NAME_CRYPTO_USAGE_MASK }; } @@ -181,11 +182,19 @@ static std::vector default_get_attrs(bool all_attributes) { } std::string KmipClient::op_create_aes_key( - const std::string &name, const std::string &group + const std::string &name, + const std::string &group, + aes_key_size key_size, + cryptographic_usage_mask usage_mask ) const { kmipcore::RequestMessage request; const auto batch_item_id = request.add_batch_item( - kmipcore::CreateSymmetricKeyRequest(name, group) + kmipcore::CreateSymmetricKeyRequest( + name, + group, + static_cast(key_size), + usage_mask + ) ); std::vector response_bytes; diff --git a/kmipclient/src/SymmetricKey.cpp b/kmipclient/src/SymmetricKey.cpp index dfcd3b4..b8a799b 100644 --- a/kmipclient/src/SymmetricKey.cpp +++ b/kmipclient/src/SymmetricKey.cpp @@ -63,10 +63,8 @@ namespace kmipclient { return make_aes_key(val); } - SymmetricKey SymmetricKey::generate_aes(size_t size_bits) { - if (size_bits != 128 && size_bits != 192 && size_bits != 256) { - throw kmipcore::KmipException("Unsupported AES key size. Use 128, 192 or 256 bits"); - } + SymmetricKey SymmetricKey::generate_aes(aes_key_size key_size) { + const size_t size_bits = static_cast(key_size); const size_t size_bytes = size_bits / 8; std::vector buf(size_bytes); diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 0f46754..3e2cb3b 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -279,11 +279,28 @@ TEST_F(KmipClientIntegrationTest, CreateSymmetricAESKey) { auto kmip = createKmipClient(); try { - auto key_id = kmip->client().op_create_aes_key( - TESTING_NAME_PREFIX + "CreateSymmetricAESKey", TEST_GROUP + const std::string key_id_128 = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateSymmetricAESKey128", + TEST_GROUP, + aes_key_size::AES_128 ); - trackKeyForCleanup(key_id); - std::cout << "Created key with ID: " << key_id << std::endl; + const std::string key_id_256 = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateSymmetricAESKey256", + TEST_GROUP, + aes_key_size::AES_256 + ); + + trackKeyForCleanup(key_id_128); + trackKeyForCleanup(key_id_256); + + auto key_128 = kmip->client().op_get_key(key_id_128); + auto key_256 = kmip->client().op_get_key(key_id_256); + + EXPECT_EQ(key_128->value().size(), 16); // 128 bits + EXPECT_EQ(key_256->value().size(), 32); // 256 bits + + std::cout << "Created AES-128 key ID: " << key_id_128 + << ", AES-256 key ID: " << key_id_256 << std::endl; } catch (kmipcore::KmipException &e) { FAIL() << "Failed to create key: " << e.what(); } @@ -831,16 +848,27 @@ TEST_F(KmipClientIntegrationTest, RegisterKeyAndGetAttributes) { 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F }; + auto key = SymmetricKey::aes_from_value(key_value); + const auto expected_mask = static_cast( + kmipcore::KMIP_CRYPTOMASK_ENCRYPT | + kmipcore::KMIP_CRYPTOMASK_DECRYPT | + kmipcore::KMIP_CRYPTOMASK_MAC_GENERATE + ); + key.attributes().set_usage_mask(expected_mask); + auto key_id = kmip->client().op_register_key( - name, TEST_GROUP, SymmetricKey::aes_from_value(key_value) + name, TEST_GROUP, key ); EXPECT_FALSE(key_id.empty()); trackKeyForCleanup(key_id); - auto attrs = - kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_NAME}); + auto attrs = kmip->client().op_get_attributes( + key_id, + {KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_CRYPTO_USAGE_MASK} + ); auto attr_name = attrs.get(KMIP_ATTR_NAME_NAME); EXPECT_EQ(attr_name, name); + EXPECT_EQ(attrs.usage_mask(), expected_mask); std::cout << "Successfully verified registered key attributes match" << std::endl; } catch (kmipcore::KmipException &e) { diff --git a/kmipcore/include/kmipcore/kmip_formatter.hpp b/kmipcore/include/kmipcore/kmip_formatter.hpp index 8770b7e..1f59d94 100644 --- a/kmipcore/include/kmipcore/kmip_formatter.hpp +++ b/kmipcore/include/kmipcore/kmip_formatter.hpp @@ -20,5 +20,13 @@ namespace kmipcore { /** @brief Parses and formats raw TTLV bytes into human-readable text. */ [[nodiscard]] std::string format_ttlv(std::span ttlv); + /** + * @brief Converts a cryptographic usage mask to human-readable bit names. + * @param mask The usage mask as a bitmask (e.g., 12 = ENCRYPT | DECRYPT). + * @return A comma-separated string of flag names (e.g., "ENCRYPT, DECRYPT"). + * Returns "UNSET" if no bits are set, or "UNKNOWN_BITS" if unrecognized bits present. + */ + [[nodiscard]] std::string usage_mask_to_string(std::uint32_t mask); + } // namespace kmipcore diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index deaeb13..eeee8a3 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -91,12 +91,19 @@ namespace kmipcore { class CreateSymmetricKeyRequest : public RequestBatchItem { public: /** - * @brief Builds a create-key request for AES-256 server-side generation. + * @brief Builds a create-key request for server-side AES generation. * @param name Value for KMIP Name attribute. * @param group Value for KMIP Object Group attribute. + * @param key_bits AES key size in bits (128, 192, 256). + * @param usage_mask Cryptographic Usage Mask bitset to store with key. */ CreateSymmetricKeyRequest( - const std::string &name, const std::string &group + const std::string &name, + const std::string &group, + int32_t key_bits, + cryptographic_usage_mask usage_mask = static_cast( + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + ) ); }; diff --git a/kmipcore/src/kmip_attributes.cpp b/kmipcore/src/kmip_attributes.cpp index 73d7217..ca6d1e4 100644 --- a/kmipcore/src/kmip_attributes.cpp +++ b/kmipcore/src/kmip_attributes.cpp @@ -18,6 +18,7 @@ #include "kmipcore/kmip_attributes.hpp" #include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/kmip_formatter.hpp" #include #include @@ -280,7 +281,7 @@ namespace kmipcore { result[std::string(KMIP_ATTR_NAME_CRYPTO_LEN)] = std::to_string(*crypto_length_); if (usage_mask_.has_value()) result[std::string(KMIP_ATTR_NAME_CRYPTO_USAGE_MASK)] = - std::to_string(static_cast(*usage_mask_)); + usage_mask_to_string(static_cast(*usage_mask_)); if (state_.has_value()) result[std::string(KMIP_ATTR_NAME_STATE)] = state_to_string(*state_); return result; diff --git a/kmipcore/src/kmip_formatter.cpp b/kmipcore/src/kmip_formatter.cpp index 45ec12d..fe6d747 100644 --- a/kmipcore/src/kmip_formatter.cpp +++ b/kmipcore/src/kmip_formatter.cpp @@ -530,7 +530,72 @@ namespace kmipcore { } } + std::string usage_mask_to_string(std::uint32_t mask) { + if (mask == 0) { + return "UNSET"; + } + + std::ostringstream oss; + bool first = true; + + // Define all known flags with their bit values (in order from KMIP spec) + const struct { + std::uint32_t bit; + const char *name; + } flags[] = { + {0x00000001, "SIGN"}, + {0x00000002, "VERIFY"}, + {0x00000004, "ENCRYPT"}, + {0x00000008, "DECRYPT"}, + {0x00000010, "WRAP_KEY"}, + {0x00000020, "UNWRAP_KEY"}, + {0x00000040, "EXPORT"}, + {0x00000080, "MAC_GENERATE"}, + {0x00000100, "MAC_VERIFY"}, + {0x00000200, "DERIVE_KEY"}, + {0x00000400, "CONTENT_COMMITMENT"}, + {0x00000800, "KEY_AGREEMENT"}, + {0x00001000, "CERTIFICATE_SIGN"}, + {0x00002000, "CRL_SIGN"}, + {0x00004000, "GENERATE_CRYPTOGRAM"}, + {0x00008000, "VALIDATE_CRYPTOGRAM"}, + {0x00010000, "TRANSLATE_ENCRYPT"}, + {0x00020000, "TRANSLATE_DECRYPT"}, + {0x00040000, "TRANSLATE_WRAP"}, + {0x00080000, "TRANSLATE_UNWRAP"}, + {0x00100000, "AUTHENTICATE"}, + {0x00200000, "UNRESTRICTED"}, + {0x00400000, "FPE_ENCRYPT"}, + {0x00800000, "FPE_DECRYPT"}, + }; + + std::uint32_t remaining = mask; + for (const auto &flag : flags) { + if ((mask & flag.bit) != 0) { + if (!first) { + oss << ", "; + } + oss << flag.name; + first = false; + remaining &= ~flag.bit; + } + } + + // If there are bits we don't recognize, add a notice + if (remaining != 0) { + if (!first) { + oss << ", "; + } + oss << "UNKNOWN_BITS(" << std::hex << std::setfill('0') << std::setw(8) + << remaining << ")"; + } + + return oss.str(); + } + } // namespace kmipcore + + diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index cdd90f0..b9547cb 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -224,8 +224,17 @@ namespace kmipcore { // CreateSymmetricKeyRequest // --------------------------------------------------------------------------- CreateSymmetricKeyRequest::CreateSymmetricKeyRequest( - const std::string &name, const std::string &group + const std::string &name, + const std::string &group, + int32_t key_bits, + cryptographic_usage_mask usage_mask ) { + if (key_bits != 128 && key_bits != 192 && key_bits != 256) { + throw KmipException( + KMIP_INVALID_FIELD, + "Unsupported AES key size for CreateSymmetricKeyRequest" + ); + } setOperation(KMIP_OP_CREATE); std::vector> attributes; @@ -235,12 +244,12 @@ namespace kmipcore { ) ); attributes.push_back( - detail::make_integer_attribute("Cryptographic Length", 256) + detail::make_integer_attribute("Cryptographic Length", key_bits) ); attributes.push_back( detail::make_integer_attribute( "Cryptographic Usage Mask", - KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + static_cast(usage_mask) ) ); attributes.push_back(detail::make_name_attribute(name)); From 7dba35a0b83caae9e81e9cafe85173a11e06a672 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sun, 29 Mar 2026 10:51:18 +0300 Subject: [PATCH 08/11] PS-10068 Fix PR API refinement https://perconadev.atlassian.net/browse/PS-10949 API cleanup and refinement. Clean version separation, methods for getting supported versions and server capabilities --- kmipclient/CMakeLists.txt | 3 + .../examples/example_query_server_info.cpp | 158 +++ .../examples/example_supported_versions.cpp | 121 +++ kmipclient/include/kmipclient/KeyBase.hpp | 3 + kmipclient/include/kmipclient/Kmip.hpp | 5 +- kmipclient/include/kmipclient/KmipClient.hpp | 55 +- .../include/kmipclient/KmipClientPool.hpp | 2 + kmipclient/src/KmipClient.cpp | 150 ++- kmipclient/src/KmipClientPool.cpp | 2 +- .../tests/KmipClientIntegrationTest.cpp | 18 +- .../tests/KmipClientIntegrationTest_2_0.cpp | 986 ++++++++++++++++++ kmipcore/include/kmipcore/kmip_enums.hpp | 156 ++- kmipcore/include/kmipcore/kmip_protocol.hpp | 61 +- kmipcore/include/kmipcore/kmip_requests.hpp | 16 +- kmipcore/include/kmipcore/kmip_responses.hpp | 93 ++ kmipcore/src/attributes_parser.cpp | 146 ++- kmipcore/src/kmip_protocol.cpp | 49 +- kmipcore/src/kmip_requests.cpp | 490 ++++++--- kmipcore/src/kmip_responses.cpp | 112 +- kmipcore/src/response_parser.cpp | 4 + kmipcore/tests/test_core.cpp | 70 +- kmipcore/tests/test_parsers.cpp | 157 ++- 22 files changed, 2576 insertions(+), 281 deletions(-) create mode 100644 kmipclient/examples/example_query_server_info.cpp create mode 100644 kmipclient/examples/example_supported_versions.cpp create mode 100644 kmipclient/tests/KmipClientIntegrationTest_2_0.cpp diff --git a/kmipclient/CMakeLists.txt b/kmipclient/CMakeLists.txt index 9ac67f2..be3b087 100644 --- a/kmipclient/CMakeLists.txt +++ b/kmipclient/CMakeLists.txt @@ -94,6 +94,8 @@ add_example(locate_by_group) add_example(get_all_ids) add_example(get_attributes) add_example(pool) +add_example(supported_versions) +add_example(query_server_info) # Google Test integration option(BUILD_TESTS "Build the tests" OFF) @@ -118,6 +120,7 @@ if(BUILD_TESTS) kmipclient_test tests/IOUtilsTest.cpp tests/KmipClientIntegrationTest.cpp + tests/KmipClientIntegrationTest_2_0.cpp tests/KmipClientPoolIntegrationTest.cpp ) diff --git a/kmipclient/examples/example_query_server_info.cpp b/kmipclient/examples/example_query_server_info.cpp new file mode 100644 index 0000000..40cf3cc --- /dev/null +++ b/kmipclient/examples/example_query_server_info.cpp @@ -0,0 +1,158 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file example_query_server_info.cpp + * @brief Queries KMIP server for capabilities and vendor information + * + * This example demonstrates how to: + * 1. Connect to a KMIP server + * 2. Call op_query() to get server capabilities and metadata + * 3. Display supported operations, object types, and vendor information + * + * Usage: + * ./example_query_server_info + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include + +using namespace kmipclient; +using namespace kmipcore; + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; + + if (argc < 6) { + std::cerr << "Usage: example_query_server_info " + " " + << std::endl; + return -1; + } + + try { + std::cout << "\nQuerying KMIP server capabilities and information...\n" + << "Server: " << argv[1] << ":" << argv[2] << "\n\n"; + + // Create KMIP client with TLS configuration + Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); + + // Call Query operation + std::cout << "Sending Query request...\n"; + auto server_info = kmip.client().op_query(); + + // Display results + std::cout << "\n" << std::string(70, '=') << "\n"; + std::cout << "SERVER INFORMATION\n"; + std::cout << std::string(70, '=') << "\n\n"; + + // Display vendor information + if (!server_info.vendor_name.empty()) { + std::cout << "Vendor Name: " << server_info.vendor_name << "\n"; + } + if (!server_info.server_name.empty()) { + std::cout << "Server Name: " << server_info.server_name << "\n"; + } + if (!server_info.product_name.empty()) { + std::cout << "Product Name: " << server_info.product_name << "\n"; + } + if (!server_info.server_version.empty()) { + std::cout << "Server Version: " << server_info.server_version << "\n"; + } + if (!server_info.build_level.empty()) { + std::cout << "Build Level: " << server_info.build_level << "\n"; + } + if (!server_info.build_date.empty()) { + std::cout << "Build Date: " << server_info.build_date << "\n"; + } + if (!server_info.server_serial_number.empty()) { + std::cout << "Serial Number: " << server_info.server_serial_number << "\n"; + } + if (!server_info.server_load.empty()) { + std::cout << "Server Load: " << server_info.server_load << "\n"; + } + if (!server_info.cluster_info.empty()) { + std::cout << "Cluster Info: " << server_info.cluster_info << "\n"; + } + + // Display supported operations + std::cout << "\n" << std::string(70, '-') << "\n"; + std::cout << "SUPPORTED OPERATIONS (" << server_info.supported_operations.size() + << " total)\n"; + std::cout << std::string(70, '-') << "\n\n"; + + int col_width = 35; + int col_count = 0; + for (const auto &op : server_info.supported_operations) { + if (col_count > 0) { + std::cout << std::setw(col_width) << " "; + } + const auto *name = kmipcore::operation_to_string(op); + std::cout << std::left << std::setw(col_width) << name; + col_count++; + if (col_count >= 2) { + std::cout << "\n"; + col_count = 0; + } + } + if (col_count > 0) { + std::cout << "\n"; + } + + // Display supported object types + std::cout << "\n" << std::string(70, '-') << "\n"; + std::cout << "SUPPORTED OBJECT TYPES (" << server_info.supported_object_types.size() + << " total)\n"; + std::cout << std::string(70, '-') << "\n\n"; + + col_count = 0; + for (const auto &type : server_info.supported_object_types) { + if (col_count > 0) { + std::cout << std::setw(col_width) << " "; + } + const auto *name = kmipcore::object_type_to_string(type); + std::cout << std::left << std::setw(col_width) << name; + col_count++; + if (col_count >= 2) { + std::cout << "\n"; + col_count = 0; + } + } + if (col_count > 0) { + std::cout << "\n"; + } + + std::cout << "\n" << std::string(70, '=') << "\n"; + + return 0; + + } catch (const kmipcore::KmipException &e) { + std::cerr << "KMIP Error: " << e.what() << "\n"; + return 1; + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << "\n"; + return 1; + } +} + + diff --git a/kmipclient/examples/example_supported_versions.cpp b/kmipclient/examples/example_supported_versions.cpp new file mode 100644 index 0000000..af11096 --- /dev/null +++ b/kmipclient/examples/example_supported_versions.cpp @@ -0,0 +1,121 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file example_supported_versions.cpp + * @brief Discovers KMIP protocol versions supported by a server + * + * This example demonstrates how to: + * 1. Connect to a KMIP server + * 2. Call op_discover_versions() to query supported protocol versions + * 3. Display the versions in a readable format + * + * Usage: + * ./example_supported_versions + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/kmipclient_version.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include +#include + +using namespace kmipclient; + +// Helper function to format protocol version for display +static std::string formatVersion(const kmipcore::ProtocolVersion &version) { + return std::to_string(version.getMajor()) + "." + std::to_string(version.getMinor()); +} + +int main(int argc, char **argv) { + std::cout << "KMIP CLIENT version: " << KMIPCLIENT_VERSION_STR << std::endl; + std::cout << "KMIP library version: " << KMIPCORE_VERSION_STR << std::endl; + + if (argc < 6) { + std::cerr << "Usage: example_supported_versions " + " " + << std::endl; + return -1; + } + + try { + std::cout << "\nDiscovering KMIP versions from server...\n" + << "Server: " << argv[1] << ":" << argv[2] << "\n\n"; + + // Create KMIP client with TLS configuration + // Use default timeout of 200ms and KMIP 1.4 protocol version + Kmip kmip(argv[1], argv[2], argv[3], argv[4], argv[5], 200); + + // Call Discover Versions operation + std::cout << "Sending Discover Versions request...\n"; + auto supported_versions = kmip.client().op_discover_versions(); + + // Display results + if (supported_versions.empty()) { + std::cout << "\nServer advertises: (empty list)\n"; + std::cout << "This typically means the server supports KMIP 1.0\n"; + return 0; + } + + std::cout << "\nServer supports the following KMIP protocol versions:\n" + << std::string(50, '=') << "\n\n"; + + for (size_t i = 0; i < supported_versions.size(); ++i) { + const auto &version = supported_versions[i]; + std::cout << std::setw(2) << (i + 1) << ". KMIP " << formatVersion(version); + + if (i == 0) { + std::cout << " (preferred/recommended by server)"; + } + std::cout << "\n"; + } + + std::cout << "\n" << std::string(50, '=') << "\n"; + std::cout << "Total supported versions: " << supported_versions.size() << "\n"; + + // Show which versions are considered "modern" (2.0 or later) + size_t modern_count = 0; + for (const auto &version : supported_versions) { + if (version.getMajor() >= 2) { + modern_count++; + } + } + + if (modern_count > 0) { + std::cout << "Modern versions (2.0+): " << modern_count << "\n"; + } + + std::cout << "\nNote: The first version in the list is the server's " + << "preferred version.\n"; + std::cout << " Clients should typically use the first supported " + << "version for best\n"; + std::cout << " compatibility and performance.\n"; + + return 0; + + } catch (const kmipcore::KmipException &e) { + std::cerr << "KMIP Error: " << e.what() << "\n"; + return 1; + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << "\n"; + return 1; + } +} + + diff --git a/kmipclient/include/kmipclient/KeyBase.hpp b/kmipclient/include/kmipclient/KeyBase.hpp index ed9b007..70c1580 100644 --- a/kmipclient/include/kmipclient/KeyBase.hpp +++ b/kmipclient/include/kmipclient/KeyBase.hpp @@ -73,6 +73,9 @@ namespace kmipclient { [[nodiscard]] cryptographic_usage_mask usage_mask() const noexcept { return attributes_.usage_mask(); } + [[nodiscard]] kmipcore::state state() const noexcept { + return attributes_.object_state(); + } // ---- Generic string attribute helpers (backward compatibility) ---- diff --git a/kmipclient/include/kmipclient/Kmip.hpp b/kmipclient/include/kmipclient/Kmip.hpp index 6385bea..f3cbd29 100644 --- a/kmipclient/include/kmipclient/Kmip.hpp +++ b/kmipclient/include/kmipclient/Kmip.hpp @@ -19,6 +19,7 @@ #define KMIP_HPP #include "kmipclient/KmipClient.hpp" #include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipcore/kmip_protocol.hpp" #include @@ -39,6 +40,7 @@ namespace kmipclient { * @param clientKeyFn Path to client private key in PEM. * @param serverCaCertFn Path to trusted server CA/certificate in PEM. * @param timeout_ms Connect/read/write timeout in milliseconds. + * @param version KMIP protocol version to use for requests. * @param logger Optional KMIP protocol logger. * @throws kmipcore::KmipException when network/TLS initialization fails. */ @@ -49,6 +51,7 @@ namespace kmipclient { const char *clientKeyFn, const char *serverCaCertFn, int timeout_ms, + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4, const std::shared_ptr &logger = {} ) : m_net_client( @@ -59,7 +62,7 @@ namespace kmipclient { serverCaCertFn, timeout_ms ), - m_client(m_net_client, logger) { + m_client(m_net_client, logger, std::move(version)) { m_net_client.connect(); }; diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index 0c74446..4ba68e2 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -22,6 +22,7 @@ #include "kmipclient/types.hpp" #include "kmipcore/kmip_attributes.hpp" #include "kmipcore/kmip_logger.hpp" +#include "kmipcore/kmip_protocol.hpp" #include #include @@ -49,10 +50,12 @@ namespace kmipclient { * @param net_client Pre-initialized network transport implementation. * @param logger Optional KMIP protocol logger. When set, serialized TTLV * request and response payloads are logged at DEBUG level. + * @param version KMIP protocol version to use for requests. */ explicit KmipClient( NetClient &net_client, - const std::shared_ptr &logger = {} + const std::shared_ptr &logger = {}, + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4 ); /** @brief Destroys the client and internal helpers. */ ~KmipClient(); @@ -253,9 +256,59 @@ namespace kmipclient { ) const; + /** + * @brief Executes KMIP Discover Versions to query supported protocol versions. + * + * @return Ordered list of KMIP protocol versions supported by the server. + * An empty list means the server returned no version information. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] std::vector + op_discover_versions() const; + + /** + * @brief Executes KMIP Query to get server information and capabilities. + * + * Queries the server for its capabilities and vendor-specific information. + * Returns supported operations, object types, and server metadata. + * + * @return Structure containing server information and capabilities. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + struct QueryServerInfo { + std::vector supported_operations; ///< Operations supported by server + std::vector supported_object_types; ///< Object types supported by server + std::string server_name; ///< Human-readable server name + std::string vendor_name; ///< Vendor identification string + std::string product_name; ///< Product name + std::string server_version; ///< Server version string + std::string build_level; ///< Build level + std::string build_date; ///< Build date + std::string server_serial_number; ///< Server serial number + std::string server_load; ///< Current server load + std::string cluster_info; ///< Cluster information + }; + + [[nodiscard]] QueryServerInfo op_query() const; + + /** @brief Returns the configured KMIP protocol version. */ + [[nodiscard]] const kmipcore::ProtocolVersion &protocol_version() const noexcept { + return version_; + } + + /** @brief Replaces the configured KMIP protocol version for subsequent requests. */ + void set_protocol_version(kmipcore::ProtocolVersion version) noexcept { + version_ = version; + } + private: NetClient &net_client; std::unique_ptr io; + kmipcore::ProtocolVersion version_; + + [[nodiscard]] kmipcore::RequestMessage make_request_message() const { + return kmipcore::RequestMessage(version_); + } }; } // namespace kmipclient diff --git a/kmipclient/include/kmipclient/KmipClientPool.hpp b/kmipclient/include/kmipclient/KmipClientPool.hpp index d3828d2..2a3f235 100644 --- a/kmipclient/include/kmipclient/KmipClientPool.hpp +++ b/kmipclient/include/kmipclient/KmipClientPool.hpp @@ -89,6 +89,8 @@ class KmipClientPool { int timeout_ms = 5000; /** Maximum number of simultaneous live connections in the pool. */ size_t max_connections = DEFAULT_MAX_CONNECTIONS; + /** KMIP protocol version used by all connections in the pool. */ + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4; }; // ---- BorrowedClient -------------------------------------------------------- diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index bd611e2..4e94c76 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -55,10 +55,12 @@ static std::vector default_get_attrs(bool all_attributes) { KmipClient::KmipClient( NetClient &net_client, - const std::shared_ptr &logger + const std::shared_ptr &logger, + kmipcore::ProtocolVersion version ) : net_client(net_client), - io(std::make_unique(net_client, logger)) {}; + io(std::make_unique(net_client, logger)), + version_(version) {}; KmipClient::~KmipClient() { @@ -68,9 +70,14 @@ static std::vector default_get_attrs(bool all_attributes) { std::string KmipClient::op_register_key( const std::string &name, const std::string &group, const Key &k ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( - kmipcore::RegisterKeyRequest(name, group, k.to_core_key()) + kmipcore::RegisterKeyRequest( + name, + group, + k.to_core_key(), + request.getHeader().getProtocolVersion() + ) ); std::vector response_bytes; @@ -91,10 +98,15 @@ static std::vector default_get_attrs(bool all_attributes) { const std::string &group, const Key &k ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); request.getHeader().setBatchOrderOption(true); const auto register_item_id = request.add_batch_item( - kmipcore::RegisterKeyRequest(name, group, k.to_core_key()) + kmipcore::RegisterKeyRequest( + name, + group, + k.to_core_key(), + request.getHeader().getProtocolVersion() + ) ); const auto activate_item_id = request.add_batch_item( make_activate_using_id_placeholder_request() @@ -122,13 +134,14 @@ static std::vector default_get_attrs(bool all_attributes) { const std::string &group, const Secret &secret ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( kmipcore::RegisterSecretRequest( name, group, secret.value(), - secret.get_secret_type() + secret.get_secret_type(), + request.getHeader().getProtocolVersion() ) ); @@ -150,14 +163,15 @@ static std::vector default_get_attrs(bool all_attributes) { const std::string &group, const Secret &secret ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); request.getHeader().setBatchOrderOption(true); const auto register_item_id = request.add_batch_item( kmipcore::RegisterSecretRequest( name, group, secret.value(), - secret.get_secret_type() + secret.get_secret_type(), + request.getHeader().getProtocolVersion() ) ); const auto activate_item_id = request.add_batch_item( @@ -187,13 +201,14 @@ static std::vector default_get_attrs(bool all_attributes) { aes_key_size key_size, cryptographic_usage_mask usage_mask ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( kmipcore::CreateSymmetricKeyRequest( name, group, static_cast(key_size), - usage_mask + usage_mask, + request.getHeader().getProtocolVersion() ) ); @@ -214,7 +229,7 @@ static std::vector default_get_attrs(bool all_attributes) { const std::string &id, bool all_attributes ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( @@ -260,7 +275,7 @@ static std::vector default_get_attrs(bool all_attributes) { } Secret KmipClient::op_get_secret(const std::string &id, bool all_attributes) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( @@ -309,7 +324,7 @@ static std::vector default_get_attrs(bool all_attributes) { } std::string KmipClient::op_activate(const std::string &id) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item(kmipcore::ActivateRequest(id)); @@ -327,7 +342,7 @@ static std::vector default_get_attrs(bool all_attributes) { } std::vector KmipClient::op_get_attribute_list(const std::string &id) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item(kmipcore::GetAttributeListRequest(id)); @@ -347,9 +362,9 @@ static std::vector default_get_attrs(bool all_attributes) { kmipcore::Attributes KmipClient::op_get_attributes( const std::string &id, const std::vector &attr_names ) const { - kmipcore::RequestMessage request; - const auto batch_item_id = - request.add_batch_item(kmipcore::GetAttributesRequest(id, attr_names)); + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::GetAttributesRequest(id, attr_names)); std::vector response_bytes; io->do_exchange( @@ -367,14 +382,15 @@ static std::vector default_get_attrs(bool all_attributes) { std::vector KmipClient::op_locate_by_name( const std::string &name, object_type o_type ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( kmipcore::LocateRequest( false, name, o_type, MAX_ITEMS_IN_BATCH, - 0 + 0, + request.getHeader().getProtocolVersion() ) ); @@ -409,14 +425,15 @@ static std::vector default_get_attrs(bool all_attributes) { const std::size_t remaining = max_ids - result.size(); const std::size_t page_size = std::min(remaining, MAX_ITEMS_IN_BATCH); - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( kmipcore::LocateRequest( true, group, o_type, page_size, - offset + offset, + request.getHeader().getProtocolVersion() ) ); @@ -452,13 +469,96 @@ static std::vector default_get_attrs(bool all_attributes) { return op_locate_by_group("", o_type, max_ids); } + std::vector KmipClient::op_discover_versions() const { + auto request = make_request_message(); + + kmipcore::RequestBatchItem item; + item.setOperation(kmipcore::KMIP_OP_DISCOVER_VERSIONS); + item.setRequestPayload( + kmipcore::Element::createStructure(kmipcore::tag::KMIP_TAG_REQUEST_PAYLOAD) + ); + const auto batch_item_id = request.add_batch_item(std::move(item)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + return std::vector{ + response.getProtocolVersions().begin(), response.getProtocolVersions().end() + }; + } + + KmipClient::QueryServerInfo KmipClient::op_query() const { + auto request = make_request_message(); + + kmipcore::RequestBatchItem item; + item.setOperation(kmipcore::KMIP_OP_QUERY); + + // Create request payload with query functions + // Request: Query Operations, Query Objects, and Query Server Information + auto payload = kmipcore::Element::createStructure(kmipcore::tag::KMIP_TAG_REQUEST_PAYLOAD); + + // Add Query Function items for: Operations, Objects, and Server Information + auto query_ops_elem = kmipcore::Element::createEnumeration( + kmipcore::tag::KMIP_TAG_QUERY_FUNCTION, + static_cast(kmipcore::KMIP_QUERY_OPERATIONS) + ); + payload->asStructure()->add(query_ops_elem); + + auto query_objs_elem = kmipcore::Element::createEnumeration( + kmipcore::tag::KMIP_TAG_QUERY_FUNCTION, + static_cast(kmipcore::KMIP_QUERY_OBJECTS) + ); + payload->asStructure()->add(query_objs_elem); + + auto query_info_elem = kmipcore::Element::createEnumeration( + kmipcore::tag::KMIP_TAG_QUERY_FUNCTION, + static_cast(kmipcore::KMIP_QUERY_SERVER_INFORMATION) + ); + payload->asStructure()->add(query_info_elem); + + item.setRequestPayload(payload); + const auto batch_item_id = request.add_batch_item(std::move(item)); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + const auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + + QueryServerInfo result; + result.supported_operations = response.getOperations(); + result.supported_object_types = response.getObjectTypes(); + result.vendor_name = response.getVendorIdentification(); + result.server_name = response.getServerName(); + result.product_name = response.getProductName(); + result.server_version = response.getServerVersion(); + result.build_level = response.getBuildLevel(); + result.build_date = response.getBuildDate(); + result.server_serial_number = response.getServerSerialNumber(); + result.server_load = response.getServerLoad(); + result.cluster_info = response.getClusterInfo(); + return result; + } + std::string KmipClient::op_revoke( const std::string &id, revocation_reason_type reason, const std::string &message, time_t occurrence_time ) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( kmipcore::RevokeRequest( id, @@ -482,7 +582,7 @@ static std::vector default_get_attrs(bool all_attributes) { } std::string KmipClient::op_destroy(const std::string &id) const { - kmipcore::RequestMessage request; + auto request = make_request_message(); const auto batch_item_id = request.add_batch_item(kmipcore::DestroyRequest(id)); diff --git a/kmipclient/src/KmipClientPool.cpp b/kmipclient/src/KmipClientPool.cpp index 3215c09..a206a23 100644 --- a/kmipclient/src/KmipClientPool.cpp +++ b/kmipclient/src/KmipClientPool.cpp @@ -100,7 +100,7 @@ std::unique_ptr KmipClientPool::create_slot() { slot->net_client->connect(); // throws KmipException on failure slot->kmip_client = - std::make_unique(*slot->net_client, config_.logger); + std::make_unique(*slot->net_client, config_.logger, config_.version); return slot; } diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 3e2cb3b..53f7a96 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -57,6 +57,7 @@ class KmipTestConfig { std::string kmip_client_key; std::string kmip_server_ca; int timeout_ms; + bool run_2_0_tests; private: KmipTestConfig() { @@ -66,6 +67,7 @@ class KmipTestConfig { const char *client_key = std::getenv("KMIP_CLIENT_KEY"); const char *server_ca = std::getenv("KMIP_SERVER_CA"); const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); + const char *run_2_0 = std::getenv("KMIP_RUN_2_0_TESTS"); if (addr) { kmip_addr = addr; @@ -93,6 +95,8 @@ class KmipTestConfig { } } + run_2_0_tests = (run_2_0 != nullptr && std::string(run_2_0) == "1"); + if (!isConfigured()) { std::cerr << "WARNING: KMIP environment variables not set. Tests will be " "skipped.\n" @@ -883,8 +887,19 @@ int main(int argc, char **argv) { // Disable test shuffling ::testing::GTEST_FLAG(shuffle) = false; - // Print configuration + // Get configuration auto &config = KmipTestConfig::getInstance(); + + // Check if KMIP 2.0 tests should be skipped + if (!config.run_2_0_tests) { + // Exclude the entire KMIP 2.0 test suite if env var is not set + ::testing::GTEST_FLAG(filter) = "-KmipClientIntegrationTest20.*"; + std::cout << "INFO: KMIP_RUN_2_0_TESTS is not set. " + << "KMIP 2.0 tests will not run.\n" + << "Set KMIP_RUN_2_0_TESTS=1 to enable the KMIP 2.0 suite.\n" << std::endl; + } + + // Print configuration if (config.isConfigured()) { std::cout << "KMIP Test Configuration:\n" << " Server: " << config.kmip_addr << ":" << config.kmip_port @@ -893,6 +908,7 @@ int main(int argc, char **argv) { << " Client Key: " << config.kmip_client_key << "\n" << " Server CA: " << config.kmip_server_ca << "\n" << " Timeout: " << config.timeout_ms << "ms\n" + << " KMIP 2.0 Tests: " << (config.run_2_0_tests ? "ENABLED" : "DISABLED") << "\n" << std::endl; } return RUN_ALL_TESTS(); diff --git a/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp b/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp new file mode 100644 index 0000000..6e6f823 --- /dev/null +++ b/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp @@ -0,0 +1,986 @@ +/* Copyright (c) 2025 Percona LLC and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 of + the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file KmipClientIntegrationTest_2_0.cpp + * @brief Integration tests exercising KMIP 2.0-specific wire encoding. + * + * All tests in this suite connect using protocol version 2.0. The primary + * difference from the 1.4 suite is that requests carry the new + * Attributes container tag (0x420125) instead of legacy TemplateAttribute / + * Attribute wrappers. Tests that are not meaningful for a 2.0 server + * (e.g. single-shot Register+Activate via the ID-placeholder mechanism) are + * enabled here and NOT marked DISABLED. + * + * @note These tests require a KMIP 2.0-capable server. Set the same + * environment variables as for the 1.4 suite: + * KMIP_ADDR, KMIP_PORT, KMIP_CLIENT_CA, KMIP_CLIENT_KEY, KMIP_SERVER_CA + * Optional: + * KMIP_TIMEOUT_MS (default 5000) + * KMIP_RUN_2_0_TESTS=1 (required to enable this suite) + */ + +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_protocol.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_GROUP "tests_2_0" + +using namespace kmipclient; + +static std::string TESTING_NAME_PREFIX = "tests_2_0_"; + +// --------------------------------------------------------------------------- +// Environment-variable configuration (shared singleton) +// --------------------------------------------------------------------------- +class KmipTestConfig20 { +public: + static KmipTestConfig20 &getInstance() { + static KmipTestConfig20 instance; + return instance; + } + + [[nodiscard]] bool isConfigured() const { + return !kmip_addr.empty() && !kmip_port.empty() && + !kmip_client_ca.empty() && !kmip_client_key.empty() && + !kmip_server_ca.empty(); + } + + [[nodiscard]] bool is2_0_enabled() const { return run_2_0_tests; } + + std::string kmip_addr; + std::string kmip_port; + std::string kmip_client_ca; + std::string kmip_client_key; + std::string kmip_server_ca; + int timeout_ms; + bool run_2_0_tests = false; + +private: + KmipTestConfig20() { + const char *addr = std::getenv("KMIP_ADDR"); + const char *port = std::getenv("KMIP_PORT"); + const char *client_ca = std::getenv("KMIP_CLIENT_CA"); + const char *client_key = std::getenv("KMIP_CLIENT_KEY"); + const char *server_ca = std::getenv("KMIP_SERVER_CA"); + const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); + const char *run_2_0 = std::getenv("KMIP_RUN_2_0_TESTS"); + + if (addr) kmip_addr = addr; + if (port) kmip_port = port; + if (client_ca) kmip_client_ca = client_ca; + if (client_key) kmip_client_key = client_key; + if (server_ca) kmip_server_ca = server_ca; + run_2_0_tests = run_2_0 != nullptr; + + timeout_ms = 5000; + if (timeout) { + errno = 0; + char *end = nullptr; + const long parsed = std::strtol(timeout, &end, 10); + if (errno == 0 && end != timeout && *end == '\0' && parsed >= 0 && parsed <= INT_MAX) { + timeout_ms = static_cast(parsed); + } + } + + if (!isConfigured()) { + std::cerr + << "WARNING: KMIP environment variables not set. " + "KMIP 2.0 tests will be skipped.\n" + << "Required variables:\n" + << " KMIP_ADDR\n" + << " KMIP_PORT\n" + << " KMIP_CLIENT_CA\n" + << " KMIP_CLIENT_KEY\n" + << " KMIP_SERVER_CA\n"; + } + + if (!run_2_0_tests) { + std::cerr + << "INFO: KMIP_RUN_2_0_TESTS is not set. " + "KMIP 2.0 tests will not run.\n" + << "Set KMIP_RUN_2_0_TESTS=1 to enable the KMIP 2.0 suite.\n"; + } + } +}; + +namespace { + +struct VersionProbeResult { + bool can_probe = false; + bool supports_kmip_2_0 = false; + std::vector advertised_versions; + std::string details; +}; + +std::string format_versions(const std::vector &versions) { + if (versions.empty()) { + return ""; + } + + std::string out; + for (size_t i = 0; i < versions.size(); ++i) { + if (i > 0) { + out += ", "; + } + out += std::to_string(versions[i].getMajor()); + out += "."; + out += std::to_string(versions[i].getMinor()); + } + return out; +} + +const VersionProbeResult &probe_server_versions_once() { + static const VersionProbeResult result = []() { + auto &config = KmipTestConfig20::getInstance(); + if (!config.isConfigured()) { + return VersionProbeResult{ + false, + false, + {}, + "KMIP environment variables are not configured" + }; + } + + try { + // Probe with KMIP 1.4 for negotiation safety, then inspect Discover Versions. + Kmip kmip( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms, + kmipcore::KMIP_VERSION_1_4 + ); + const auto versions = kmip.client().op_discover_versions(); + const bool supports_2_0 = std::any_of( + versions.begin(), + versions.end(), + [](const kmipcore::ProtocolVersion &v) { + return v.getMajor() == 2 && v.getMinor() == 0; + } + ); + return VersionProbeResult{ + true, + supports_2_0, + versions, + supports_2_0 + ? "server advertises KMIP 2.0" + : "server does not advertise KMIP 2.0" + }; + } catch (const std::exception &e) { + return VersionProbeResult{ + false, + false, + {}, + std::string("Discover Versions probe failed: ") + e.what() + }; + } + }(); + return result; +} + +} // namespace + +// --------------------------------------------------------------------------- +// Base fixture +// --------------------------------------------------------------------------- +class KmipClientIntegrationTest20 : public ::testing::Test { +protected: + inline static std::vector passed_tests_{}; + inline static std::vector failed_tests_{}; + inline static std::vector skipped_tests_{}; + inline static bool suite_enabled_ = true; + + static void SetUpTestSuite() { + auto &config = KmipTestConfig20::getInstance(); + suite_enabled_ = config.is2_0_enabled(); + if (!suite_enabled_) { + std::cout << "\n[KMIP 2.0 Suite] KMIP_RUN_2_0_TESTS is not set; suite is disabled." + << std::endl; + return; + } + + const auto &probe = probe_server_versions_once(); + std::cout << "\n[KMIP 2.0 Suite] Discover Versions pre-check" << std::endl; + if (probe.can_probe) { + std::cout << "[KMIP 2.0 Suite] Advertised server versions: " + << format_versions(probe.advertised_versions) << std::endl; + std::cout << "[KMIP 2.0 Suite] " << probe.details << std::endl; + } else { + std::cout << "[KMIP 2.0 Suite] Version probe unavailable: " + << probe.details << std::endl; + } + } + + static void TearDownTestSuite() { + if (!suite_enabled_) { + std::cout << "\n[KMIP 2.0 Suite] Capability summary: not evaluated (suite disabled by KMIP_RUN_2_0_TESTS)." + << std::endl; + return; + } + + std::cout << "\n[KMIP 2.0 Suite] Capability summary (from test outcomes)" << std::endl; + std::cout << "[KMIP 2.0 Suite] Supported (passed): " + << passed_tests_.size() << std::endl; + for (const auto &name : passed_tests_) { + std::cout << " + " << name << std::endl; + } + + std::cout << "[KMIP 2.0 Suite] Not supported or failing (failed): " + << failed_tests_.size() << std::endl; + for (const auto &name : failed_tests_) { + std::cout << " - " << name << std::endl; + } + + std::cout << "[KMIP 2.0 Suite] Not evaluated (skipped): " + << skipped_tests_.size() << std::endl; + for (const auto &name : skipped_tests_) { + std::cout << " ~ " << name << std::endl; + } + } + + std::vector created_ids; + + void SetUp() override { + auto &config = KmipTestConfig20::getInstance(); + + if (!config.is2_0_enabled()) { + GTEST_SKIP(); + } + + if (!config.isConfigured()) { + GTEST_SKIP() << "KMIP environment variables not configured"; + } + + const auto &version_probe = probe_server_versions_once(); + if (version_probe.can_probe && !version_probe.supports_kmip_2_0) { + GTEST_SKIP() << "Skipping KMIP 2.0 suite: " << version_probe.details; + } + + // Probe connectivity with a zero-result op_all – fast and side-effect free. + try { + auto kmip = createKmipClient(); + (void) kmip->client().op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, 0); + } catch (const std::exception &e) { + GTEST_SKIP() << "KMIP 2.0 server connectivity check failed: " << e.what(); + } + } + + void TearDown() override { + if (!suite_enabled_) { + return; + } + + const auto *test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + + if (test_info != nullptr) { + const auto *result = test_info->result(); + if (result != nullptr && result->Skipped()) { + skipped_tests_.push_back(test_info->name()); + } else if (HasFailure()) { + failed_tests_.push_back(test_info->name()); + } else { + passed_tests_.push_back(test_info->name()); + } + } + + if (HasFailure()) { + std::cout << test_info->name() << ": FAIL" << std::endl; + } else { + std::cout << test_info->name() << ": OK" << std::endl; + } + + // Best-effort cleanup of objects created during the test. + auto &config = KmipTestConfig20::getInstance(); + if (config.isConfigured() && !created_ids.empty()) { + try { + Kmip kmip( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms, + kmipcore::KMIP_VERSION_2_0 + ); + for (const auto &id : created_ids) { + try { + (void) kmip.client().op_revoke( + id, + revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE, + "Test cleanup", + 0 + ); + (void) kmip.client().op_destroy(id); + } catch (kmipcore::KmipException &e) { + std::cerr << "Cleanup: failed to destroy " << id + << ": " << e.what() << std::endl; + } + } + } catch (...) { + // Silently ignore cleanup errors. + } + } + } + + static std::unique_ptr createKmipClient() { + auto &config = KmipTestConfig20::getInstance(); + try { + return std::make_unique( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms, + kmipcore::KMIP_VERSION_2_0 + ); + } catch (const std::exception &e) { + throw std::runtime_error( + std::string("Failed to initialise KMIP 2.0 client: ") + e.what() + ); + } + } + + void trackForCleanup(const std::string &id) { created_ids.push_back(id); } +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +// Test: Verify the configured protocol version is 2.0 +TEST_F(KmipClientIntegrationTest20, ProtocolVersionIs20) { + auto kmip = createKmipClient(); + const auto &ver = kmip->client().protocol_version(); + EXPECT_EQ(ver.getMajor(), 2); + EXPECT_EQ(ver.getMinor(), 0); + std::cout << "Protocol version: " << ver.getMajor() << "." << ver.getMinor() + << std::endl; +} + +// Test: Create AES key via KMIP 2.0 Attributes container encoding +TEST_F(KmipClientIntegrationTest20, CreateSymmetricAESKey) { + auto kmip = createKmipClient(); + try { + const std::string id128 = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateAES128", + TEST_GROUP, + aes_key_size::AES_128 + ); + const std::string id256 = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateAES256", + TEST_GROUP, + aes_key_size::AES_256 + ); + trackForCleanup(id128); + trackForCleanup(id256); + + auto key128 = kmip->client().op_get_key(id128); + auto key256 = kmip->client().op_get_key(id256); + + EXPECT_EQ(key128->value().size(), 16u); // 128 bits + EXPECT_EQ(key256->value().size(), 32u); // 256 bits + + std::cout << "AES-128 id: " << id128 << ", AES-256 id: " << id256 << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "CreateSymmetricAESKey (2.0) failed: " << e.what(); + } +} + +// Test: Create and Get key – round-trip +TEST_F(KmipClientIntegrationTest20, CreateAndGetKey) { + auto kmip = createKmipClient(); + try { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateAndGetKey", + TEST_GROUP + ); + trackForCleanup(id); + + auto key = kmip->client().op_get_key(id); + ASSERT_NE(key, nullptr); + EXPECT_EQ(key->value().size(), 32u); // default AES-256 + std::cout << "Retrieved key size: " << key->value().size() << " bytes" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "CreateAndGetKey (2.0) failed: " << e.what(); + } +} + +// Test: Create, Activate and confirm state == ACTIVE +TEST_F(KmipClientIntegrationTest20, CreateActivateAndGetKey) { + auto kmip = createKmipClient(); + try { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateActivateGet", + TEST_GROUP + ); + trackForCleanup(id); + + const auto activated_id = kmip->client().op_activate(id); + EXPECT_EQ(activated_id, id); + + auto key = kmip->client().op_get_key(id); + ASSERT_NE(key, nullptr); + ASSERT_FALSE(key->value().empty()); + + auto attrs = kmip->client().op_get_attributes(id, {KMIP_ATTR_NAME_STATE}); + EXPECT_EQ(attrs.object_state(), state::KMIP_STATE_ACTIVE) + << "Key should be ACTIVE after activation"; + + std::cout << "Activated key id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "CreateActivateAndGetKey (2.0) failed: " << e.what(); + } +} + +// Test: Register symmetric key using KMIP 2.0 Attributes encoding +TEST_F(KmipClientIntegrationTest20, RegisterSymmetricKey) { + auto kmip = createKmipClient(); + const std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + try { + const auto id = kmip->client().op_register_key( + TESTING_NAME_PREFIX + "RegisterSymmetricKey", + TEST_GROUP, + SymmetricKey::aes_from_value(key_value) + ); + EXPECT_FALSE(id.empty()); + trackForCleanup(id); + std::cout << "Registered symmetric key id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "RegisterSymmetricKey (2.0) failed: " << e.what(); + } +} + +// Test: Register + Activate symmetric key in a single multi-batch request +// (KMIP ID-placeholder mechanism, enabled for 2.0) +TEST_F(KmipClientIntegrationTest20, RegisterAndActivateSymmetricKey) { + auto kmip = createKmipClient(); + const std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + + std::string id; + try { + id = kmip->client().op_register_and_activate_key( + TESTING_NAME_PREFIX + "RegisterAndActivateKey", + TEST_GROUP, + SymmetricKey::aes_from_value(key_value) + ); + ASSERT_FALSE(id.empty()); + trackForCleanup(id); + } catch (kmipcore::KmipException &e) { + FAIL() << "op_register_and_activate_key (2.0) failed: " << e.what(); + } + + try { + auto key = kmip->client().op_get_key(id); + ASSERT_NE(key, nullptr); + ASSERT_FALSE(key->value().empty()); + + auto attrs = kmip->client().op_get_attributes(id, {KMIP_ATTR_NAME_STATE}); + EXPECT_EQ(attrs.object_state(), state::KMIP_STATE_ACTIVE) + << "Key should be ACTIVE immediately after RegisterAndActivate"; + + std::cout << "RegisterAndActivate key id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Get after RegisterAndActivateKey (2.0) failed: " << e.what(); + } +} + +// Test: Register secret data via KMIP 2.0 +TEST_F(KmipClientIntegrationTest20, RegisterAndGetSecret) { + auto kmip = createKmipClient(); + const std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; + kmipcore::Attributes secret_attrs; + secret_attrs.set_state(state::KMIP_STATE_PRE_ACTIVE); + const Secret secret( + secret_data, + secret_data_type::KMIP_SECDATA_PASSWORD, + secret_attrs + ); + + std::string id; + try { + id = kmip->client().op_register_secret( + TESTING_NAME_PREFIX + "RegisterSecret", + TEST_GROUP, + secret + ); + EXPECT_FALSE(id.empty()); + trackForCleanup(id); + std::cout << "Registered secret id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "op_register_secret (2.0) failed: " << e.what(); + } + + try { + const auto activated_id = kmip->client().op_activate(id); + EXPECT_EQ(activated_id, id); + } catch (kmipcore::KmipException &e) { + FAIL() << "op_activate for secret (2.0) failed: " << e.what(); + } + + try { + auto retrieved = kmip->client().op_get_secret(id, true); + EXPECT_EQ(retrieved.value(), secret_data); + EXPECT_EQ(retrieved.get_state(), state::KMIP_STATE_ACTIVE); + std::cout << "Retrieved secret size: " << retrieved.value().size() + << " bytes, state ACTIVE" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "op_get_secret (2.0) failed: " << e.what(); + } +} + +// Test: Register + Activate secret in a single multi-batch request +TEST_F(KmipClientIntegrationTest20, RegisterAndActivateSecret) { + auto kmip = createKmipClient(); + const std::vector secret_data = {'p', 'a', 's', 's', 'w', 'd'}; + kmipcore::Attributes secret_attrs; + secret_attrs.set_state(state::KMIP_STATE_PRE_ACTIVE); + const Secret secret( + secret_data, + secret_data_type::KMIP_SECDATA_PASSWORD, + secret_attrs + ); + + std::string id; + try { + id = kmip->client().op_register_and_activate_secret( + TESTING_NAME_PREFIX + "RegisterAndActivateSecret", + TEST_GROUP, + secret + ); + ASSERT_FALSE(id.empty()); + trackForCleanup(id); + } catch (kmipcore::KmipException &e) { + FAIL() << "op_register_and_activate_secret (2.0) failed: " << e.what(); + } + + try { + auto retrieved = kmip->client().op_get_secret(id, true); + EXPECT_EQ(retrieved.value(), secret_data); + EXPECT_EQ(retrieved.get_state(), state::KMIP_STATE_ACTIVE) + << "Secret should be ACTIVE immediately after RegisterAndActivate"; + std::cout << "RegisterAndActivate secret id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Get after RegisterAndActivateSecret (2.0) failed: " << e.what(); + } +} + +// Test: Locate keys by name +TEST_F(KmipClientIntegrationTest20, LocateKeysByName) { + auto kmip = createKmipClient(); + const std::string name = TESTING_NAME_PREFIX + "LocateByName"; + try { + const auto id = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackForCleanup(id); + + auto found = kmip->client().op_locate_by_name( + name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY + ); + EXPECT_FALSE(found.empty()); + const auto it = std::find(found.begin(), found.end(), id); + EXPECT_NE(it, found.end()) << "Newly created key not found by name"; + std::cout << "Locate by name returned " << found.size() << " result(s)" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "LocateKeysByName (2.0) failed: " << e.what(); + } +} + +// Test: Locate keys by group +TEST_F(KmipClientIntegrationTest20, LocateKeysByGroup) { + auto kmip = createKmipClient(); + const std::string group = + "test_2_0_locate_group_" + std::to_string(std::time(nullptr)); + std::vector expected_ids; + + try { + for (int i = 0; i < 3; ++i) { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "LocateByGroup_" + std::to_string(i), group + ); + expected_ids.push_back(id); + trackForCleanup(id); + } + + auto found = kmip->client().op_locate_by_group( + group, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY + ); + + for (const auto &expected_id : expected_ids) { + const auto it = std::find(found.begin(), found.end(), expected_id); + EXPECT_NE(it, found.end()) + << "Key " << expected_id << " not found in group " << group; + } + std::cout << "Locate by group found " << found.size() << " key(s)" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "LocateKeysByGroup (2.0) failed: " << e.what(); + } +} + +// Test: op_locate_by_group respects max_ids upper bound +TEST_F(KmipClientIntegrationTest20, LocateKeysByGroupHonorsMaxIds) { + auto kmip = createKmipClient(); + const std::string group = + "test_2_0_locate_limit_" + std::to_string(std::time(nullptr)); + std::vector created; + + try { + for (int i = 0; i < 3; ++i) { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "LocateLimit_" + std::to_string(i), group + ); + created.push_back(id); + trackForCleanup(id); + } + + const size_t max_ids = 2; + auto found = kmip->client().op_locate_by_group( + group, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, max_ids + ); + + EXPECT_LE(found.size(), max_ids); + EXPECT_EQ(found.size(), max_ids); + for (const auto &id : found) { + EXPECT_NE( + std::find(created.begin(), created.end(), id), created.end() + ) << "Located id " << id << " was not created by this test"; + } + } catch (kmipcore::KmipException &e) { + FAIL() << "LocateKeysByGroupHonorsMaxIds (2.0) failed: " << e.what(); + } +} + +// Test: Get attributes – Name and Object Group +TEST_F(KmipClientIntegrationTest20, CreateAndGetAttributes) { + auto kmip = createKmipClient(); + const std::string name = TESTING_NAME_PREFIX + "GetAttributes"; + try { + const auto id = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackForCleanup(id); + + auto attrs = kmip->client().op_get_attributes(id, {KMIP_ATTR_NAME_NAME}); + attrs.merge( + kmip->client().op_get_attributes(id, {KMIP_ATTR_NAME_GROUP}) + ); + + EXPECT_EQ(attrs.get(KMIP_ATTR_NAME_NAME), name); + EXPECT_EQ(attrs.get(KMIP_ATTR_NAME_GROUP), TEST_GROUP); + std::cout << "Attributes verified for id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "CreateAndGetAttributes (2.0) failed: " << e.what(); + } +} + +// Test: Register key and verify NAME and CryptographicUsageMask attributes +TEST_F(KmipClientIntegrationTest20, RegisterKeyAndGetAttributes) { + auto kmip = createKmipClient(); + const std::string name = TESTING_NAME_PREFIX + "RegisterKeyAttrs"; + const std::vector key_value = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + try { + auto key = SymmetricKey::aes_from_value(key_value); + const auto expected_mask = static_cast( + kmipcore::KMIP_CRYPTOMASK_ENCRYPT | kmipcore::KMIP_CRYPTOMASK_DECRYPT | + kmipcore::KMIP_CRYPTOMASK_MAC_GENERATE + ); + key.attributes().set_usage_mask(expected_mask); + + const auto id = kmip->client().op_register_key(name, TEST_GROUP, key); + EXPECT_FALSE(id.empty()); + trackForCleanup(id); + + auto attrs = kmip->client().op_get_attributes( + id, {KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_CRYPTO_USAGE_MASK} + ); + EXPECT_EQ(attrs.get(KMIP_ATTR_NAME_NAME), name); + EXPECT_EQ(attrs.usage_mask(), expected_mask); + std::cout << "Registered key attributes verified for id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "RegisterKeyAndGetAttributes (2.0) failed: " << e.what(); + } +} + +// Test: Revoke changes state to DEACTIVATED +TEST_F(KmipClientIntegrationTest20, RevokeChangesState) { + auto kmip = createKmipClient(); + try { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "RevokeState", TEST_GROUP + ); + trackForCleanup(id); + + (void) kmip->client().op_activate(id); + const auto revoke_id = kmip->client().op_revoke( + id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, + "Integration test revoke", 0 + ); + EXPECT_FALSE(revoke_id.empty()); + + auto attrs = kmip->client().op_get_attributes(id, {KMIP_ATTR_NAME_STATE}); + EXPECT_EQ(attrs.object_state(), state::KMIP_STATE_DEACTIVATED) + << "Expected DEACTIVATED after revoke"; + std::cout << "State is DEACTIVATED after revoke for id: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "RevokeChangesState (2.0) failed: " << e.what(); + } +} + +// Test: Full lifecycle – Create / Activate / Get / Revoke / Destroy +TEST_F(KmipClientIntegrationTest20, FullKeyLifecycle) { + auto kmip = createKmipClient(); + try { + // 1. Create + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "FullLifecycle", TEST_GROUP + ); + std::cout << "1. Created key: " << id << std::endl; + + // 2. Activate + const auto activated_id = kmip->client().op_activate(id); + ASSERT_FALSE(activated_id.empty()); + std::cout << "2. Activated key" << std::endl; + + // 3. Get + auto key = kmip->client().op_get_key(id); + ASSERT_NE(key, nullptr); + ASSERT_FALSE(key->value().empty()); + std::cout << "3. Retrieved key (" << key->value().size() << " bytes)" << std::endl; + + // 4. Revoke + const auto revoked_id = kmip->client().op_revoke( + id, revocation_reason_type::KMIP_REVOKE_UNSPECIFIED, + "Full lifecycle test", 0 + ); + ASSERT_FALSE(revoked_id.empty()); + std::cout << "4. Revoked key" << std::endl; + + // 5. Destroy + const auto destroyed_id = kmip->client().op_destroy(id); + EXPECT_EQ(destroyed_id, id); + std::cout << "5. Destroyed key" << std::endl; + + // No cleanup tracking – already destroyed. + } catch (kmipcore::KmipException &e) { + FAIL() << "FullKeyLifecycle (2.0) failed: " << e.what(); + } +} + +// Test: Destroy removes the key (retrieval should fail or return empty) +TEST_F(KmipClientIntegrationTest20, DestroyKeyRemovesKey) { + auto kmip = createKmipClient(); + std::string id; + try { + id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "DestroyRemoves", TEST_GROUP + ); + ASSERT_FALSE(id.empty()); + } catch (kmipcore::KmipException &e) { + FAIL() << "Create failed: " << e.what(); + } + + try { + const auto destroyed = kmip->client().op_destroy(id); + EXPECT_EQ(destroyed, id); + } catch (kmipcore::KmipException &e) { + FAIL() << "Destroy failed: " << e.what(); + } + + try { + auto key = kmip->client().op_get_key(id); + EXPECT_TRUE(key->value().empty()) + << "Destroyed key should not be retrievable"; + } catch (kmipcore::KmipException &) { + // Server may return an error for a destroyed object – acceptable. + SUCCEED(); + } + std::cout << "Destroyed key is not retrievable" << std::endl; +} + +// Test: Get non-existent key returns a meaningful server error +TEST_F(KmipClientIntegrationTest20, GetNonExistentKey) { + auto kmip = createKmipClient(); + const std::string fake_id = "non-existent-key-2-0-12345"; + try { + auto key = kmip->client().op_get_key(fake_id); + (void) key; + FAIL() << "Expected exception for non-existent key"; + } catch (const kmipcore::KmipException &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("Operation: Get"), std::string::npos) + << "Expected Get operation failure, got: " << msg; + EXPECT_NE(msg.find("Result reason:"), std::string::npos) + << "Expected Result Reason in error, got: " << msg; + std::cout << "Non-existent key error details verified" << std::endl; + } +} + +// Test: Get non-existent secret returns a meaningful server error +TEST_F(KmipClientIntegrationTest20, GetNonExistentSecret) { + auto kmip = createKmipClient(); + const std::string fake_id = "non-existent-secret-2-0-12345"; + try { + auto secret = kmip->client().op_get_secret(fake_id); + (void) secret; + FAIL() << "Expected exception for non-existent secret"; + } catch (const kmipcore::KmipException &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("Operation: Get"), std::string::npos) + << "Expected Get operation failure, got: " << msg; + std::cout << "Non-existent secret error details verified" << std::endl; + } +} + +// Test: op_all with max_ids=0 returns no results +TEST_F(KmipClientIntegrationTest20, GetAllIdsWithZeroLimitReturnsEmpty) { + auto kmip = createKmipClient(); + try { + auto ids = kmip->client().op_all( + object_type::KMIP_OBJTYPE_SYMMETRIC_KEY, 0 + ); + EXPECT_TRUE(ids.empty()); + } catch (kmipcore::KmipException &e) { + FAIL() << "GetAllIdsWithZeroLimit (2.0) failed: " << e.what(); + } +} + +// Test: op_all includes newly created keys +TEST_F(KmipClientIntegrationTest20, GetAllIdsIncludesCreatedKeys) { + auto kmip = createKmipClient(); + std::vector created; + try { + for (int i = 0; i < 3; ++i) { + const auto id = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "GetAllIds_" + std::to_string(i), + TEST_GROUP + ); + created.push_back(id); + trackForCleanup(id); + } + + auto all = kmip->client().op_all(object_type::KMIP_OBJTYPE_SYMMETRIC_KEY); + for (const auto &cid : created) { + EXPECT_NE(std::find(all.begin(), all.end(), cid), all.end()) + << "Created id " << cid << " not found in op_all"; + } + std::cout << "op_all includes all " << created.size() + << " created key(s)" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "GetAllIdsIncludesCreatedKeys (2.0) failed: " << e.what(); + } +} + +// Test: Duplicate-name keys yield distinct IDs and are both locatable +TEST_F(KmipClientIntegrationTest20, CreateDuplicateNames) { + auto kmip = createKmipClient(); + const std::string name = TESTING_NAME_PREFIX + "DuplicateName"; + std::string id1, id2; + try { + id1 = kmip->client().op_create_aes_key(name, TEST_GROUP); + id2 = kmip->client().op_create_aes_key(name, TEST_GROUP); + trackForCleanup(id1); + trackForCleanup(id2); + } catch (kmipcore::KmipException &e) { + FAIL() << "Create duplicate names (2.0) failed: " << e.what(); + } + + ASSERT_FALSE(id1.empty()); + ASSERT_FALSE(id2.empty()); + EXPECT_NE(id1, id2) << "Duplicate-name keys must have unique IDs"; + + try { + auto found = kmip->client().op_locate_by_name( + name, object_type::KMIP_OBJTYPE_SYMMETRIC_KEY + ); + EXPECT_NE(std::find(found.begin(), found.end(), id1), found.end()) + << "First key not found by name"; + EXPECT_NE(std::find(found.begin(), found.end(), id2), found.end()) + << "Second key not found by name"; + std::cout << "Both duplicate-name keys found by name" << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "Locate duplicate names (2.0) failed: " << e.what(); + } +} + +// Test: set_protocol_version switches from 1.4 default to 2.0 at runtime +TEST_F(KmipClientIntegrationTest20, SetProtocolVersionSwitchesTo20) { + auto &config = KmipTestConfig20::getInstance(); + + // Intentionally start with 1.4 default. + Kmip kmip14( + config.kmip_addr.c_str(), + config.kmip_port.c_str(), + config.kmip_client_ca.c_str(), + config.kmip_client_key.c_str(), + config.kmip_server_ca.c_str(), + config.timeout_ms + // version defaults to KMIP_VERSION_1_4 + ); + + EXPECT_EQ(kmip14.client().protocol_version().getMajor(), 1); + EXPECT_EQ(kmip14.client().protocol_version().getMinor(), 4); + + // Switch to 2.0 and issue a request that uses the new encoding. + kmip14.client().set_protocol_version(kmipcore::KMIP_VERSION_2_0); + EXPECT_EQ(kmip14.client().protocol_version().getMajor(), 2); + EXPECT_EQ(kmip14.client().protocol_version().getMinor(), 0); + + try { + const auto id = kmip14.client().op_create_aes_key( + TESTING_NAME_PREFIX + "SetVersion20", + TEST_GROUP + ); + EXPECT_FALSE(id.empty()); + trackForCleanup(id); + std::cout << "Created key after runtime version switch to 2.0: " << id << std::endl; + } catch (kmipcore::KmipException &e) { + FAIL() << "CreateAES after set_protocol_version(2.0) failed: " << e.what(); + } +} + +// NOTE: main() is defined in KmipClientIntegrationTest.cpp which is compiled +// into the same kmipclient_test binary. KmipTestConfig20 prints its own +// diagnostic banner to stderr during static initialisation when the required +// environment variables are absent. + diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp index b41da0c..7524636 100644 --- a/kmipcore/include/kmipcore/kmip_enums.hpp +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -352,14 +352,6 @@ enum class key_wrap_type : std::uint32_t { KMIP_WRAPTYPE_AS_REGISTERED = 0x02 }; -enum class kmip_version : std::uint32_t { - KMIP_1_0 = 0, - KMIP_1_1 = 1, - KMIP_1_2 = 2, - KMIP_1_3 = 3, - KMIP_1_4 = 4, - KMIP_2_0 = 5 -}; enum class mask_generator : std::uint32_t { // KMIP 1.4 @@ -624,6 +616,148 @@ inline std::ostream &operator<<(std::ostream &out, const state value) { return out << state_to_string(value); } +/** Convert a KMIP operation code to a human-readable string. */ +inline const char *operation_to_string(int32_t value) { + switch (static_cast(value)) { + case operation::KMIP_OP_CREATE: + return "Create"; + case operation::KMIP_OP_CREATE_KEY_PAIR: + return "Create Key Pair"; + case operation::KMIP_OP_REGISTER: + return "Register"; + case operation::KMIP_OP_REKEY: + return "Rekey"; + case operation::KMIP_OP_DERIVE_KEY: + return "Derive Key"; + case operation::KMIP_OP_CERTIFY: + return "Certify"; + case operation::KMIP_OP_RECERTIFY: + return "Recertify"; + case operation::KMIP_OP_LOCATE: + return "Locate"; + case operation::KMIP_OP_CHECK: + return "Check"; + case operation::KMIP_OP_GET: + return "Get"; + case operation::KMIP_OP_GET_ATTRIBUTES: + return "Get Attributes"; + case operation::KMIP_OP_GET_ATTRIBUTE_LIST: + return "Get Attribute List"; + case operation::KMIP_OP_ADD_ATTRIBUTE: + return "Add Attribute"; + case operation::KMIP_OP_MODIFY_ATTRIBUTE: + return "Modify Attribute"; + case operation::KMIP_OP_DELETE_ATTRIBUTE: + return "Delete Attribute"; + case operation::KMIP_OP_OBTAIN_LEASE: + return "Obtain Lease"; + case operation::KMIP_OP_GET_USAGE_ALLOCATION: + return "Get Usage Allocation"; + case operation::KMIP_OP_ACTIVATE: + return "Activate"; + case operation::KMIP_OP_REVOKE: + return "Revoke"; + case operation::KMIP_OP_DESTROY: + return "Destroy"; + case operation::KMIP_OP_ARCHIVE: + return "Archive"; + case operation::KMIP_OP_RECOVER: + return "Recover"; + case operation::KMIP_OP_VALIDATE: + return "Validate"; + case operation::KMIP_OP_QUERY: + return "Query"; + case operation::KMIP_OP_CANCEL: + return "Cancel"; + case operation::KMIP_OP_POLL: + return "Poll"; + case operation::KMIP_OP_NOTIFY: + return "Notify"; + case operation::KMIP_OP_PUT: + return "Put"; + case operation::KMIP_OP_REKEY_KEY_PAIR: + return "Rekey Key Pair"; + case operation::KMIP_OP_DISCOVER_VERSIONS: + return "Discover Versions"; + case operation::KMIP_OP_ENCRYPT: + return "Encrypt"; + case operation::KMIP_OP_DECRYPT: + return "Decrypt"; + case operation::KMIP_OP_SIGN: + return "Sign"; + case operation::KMIP_OP_SIGNATURE_VERIFY: + return "Signature Verify"; + case operation::KMIP_OP_MAC: + return "MAC"; + case operation::KMIP_OP_MAC_VERIFY: + return "MAC Verify"; + case operation::KMIP_OP_RNG_RETRIEVE: + return "RNG Retrieve"; + case operation::KMIP_OP_RNG_SEED: + return "RNG Seed"; + case operation::KMIP_OP_HASH: + return "Hash"; + case operation::KMIP_OP_CREATE_SPLIT_KEY: + return "Create Split Key"; + case operation::KMIP_OP_JOIN_SPLIT_KEY: + return "Join Split Key"; + case operation::KMIP_OP_IMPORT: + return "Import"; + case operation::KMIP_OP_EXPORT: + return "Export"; + case operation::KMIP_OP_LOG: + return "Log"; + case operation::KMIP_OP_LOGIN: + return "Login"; + case operation::KMIP_OP_LOGOUT: + return "Logout"; + case operation::KMIP_OP_DELEGATED_LOGIN: + return "Delegated Login"; + case operation::KMIP_OP_ADJUST_ATTRIBUTE: + return "Adjust Attribute"; + case operation::KMIP_OP_SET_ATTRIBUTE: + return "Set Attribute"; + case operation::KMIP_OP_SET_ENDPOINT_ROLE: + return "Set Endpoint Role"; + case operation::KMIP_OP_PKCS_11: + return "PKCS#11"; + case operation::KMIP_OP_INTEROP: + return "Interop"; + case operation::KMIP_OP_REPROVISION: + return "Reprovision"; + default: + return "Unknown Operation"; + } +} + +/** Convert a KMIP object type code to a human-readable string. */ +inline const char *object_type_to_string(int32_t value) { + switch (static_cast(value)) { + case object_type::KMIP_OBJTYPE_CERTIFICATE: + return "Certificate"; + case object_type::KMIP_OBJTYPE_SYMMETRIC_KEY: + return "Symmetric Key"; + case object_type::KMIP_OBJTYPE_PUBLIC_KEY: + return "Public Key"; + case object_type::KMIP_OBJTYPE_PRIVATE_KEY: + return "Private Key"; + case object_type::KMIP_OBJTYPE_SPLIT_KEY: + return "Split Key"; + case object_type::KMIP_OBJTYPE_TEMPLATE: + return "Template"; + case object_type::KMIP_OBJTYPE_SECRET_DATA: + return "Secret Data"; + case object_type::KMIP_OBJTYPE_OPAQUE_OBJECT: + return "Opaque Object"; + case object_type::KMIP_OBJTYPE_PGP_KEY: + return "PGP Key"; + case object_type::KMIP_OBJTYPE_CERTIFICATE_REQUEST: + return "Certificate Request"; + default: + return "Unknown Object Type"; + } +} + enum class tag : std::uint32_t { KMIP_TAG_TAG = 0x000000, KMIP_TAG_TYPE = 0x000001, @@ -1038,12 +1172,6 @@ inline constexpr std::uint32_t KMIP_ROLE_IV = static_cast(key_rol inline constexpr std::uint32_t KMIP_ROLE_TRKBK = static_cast(key_role_type::KMIP_ROLE_TRKBK); inline constexpr std::uint32_t KMIP_WRAPTYPE_NOT_WRAPPED = static_cast(key_wrap_type::KMIP_WRAPTYPE_NOT_WRAPPED); inline constexpr std::uint32_t KMIP_WRAPTYPE_AS_REGISTERED = static_cast(key_wrap_type::KMIP_WRAPTYPE_AS_REGISTERED); -inline constexpr std::uint32_t KMIP_1_0 = static_cast(kmip_version::KMIP_1_0); -inline constexpr std::uint32_t KMIP_1_1 = static_cast(kmip_version::KMIP_1_1); -inline constexpr std::uint32_t KMIP_1_2 = static_cast(kmip_version::KMIP_1_2); -inline constexpr std::uint32_t KMIP_1_3 = static_cast(kmip_version::KMIP_1_3); -inline constexpr std::uint32_t KMIP_1_4 = static_cast(kmip_version::KMIP_1_4); -inline constexpr std::uint32_t KMIP_2_0 = static_cast(kmip_version::KMIP_2_0); inline constexpr std::uint32_t KMIP_MASKGEN_MGF1 = static_cast(mask_generator::KMIP_MASKGEN_MGF1); inline constexpr std::uint32_t KMIP_NAME_UNINTERPRETED_TEXT_STRING = static_cast(name_type::KMIP_NAME_UNINTERPRETED_TEXT_STRING); inline constexpr std::uint32_t KMIP_NAME_URI = static_cast(name_type::KMIP_NAME_URI); diff --git a/kmipcore/include/kmipcore/kmip_protocol.hpp b/kmipcore/include/kmipcore/kmip_protocol.hpp index 5fd10a0..4b94095 100644 --- a/kmipcore/include/kmipcore/kmip_protocol.hpp +++ b/kmipcore/include/kmipcore/kmip_protocol.hpp @@ -8,24 +8,40 @@ #include namespace kmipcore { - /** @brief Default KMIP protocol minor version used for requests. */ - inline constexpr int32_t DEFAULT_PROTOCOL_VERSION = KMIP_1_4; - - /** @brief KMIP protocol version tuple. */ + /** + * @brief KMIP protocol version tuple (wire major.minor). + * + * Stores the on-wire major/minor pair directly, mirroring the spec's + * own notation: @c ProtocolVersion(1,4) for KMIP 1.4, + * @c ProtocolVersion(2,0) for KMIP 2.0, and so on. + */ class ProtocolVersion { public: - /** @brief Constructs protocol version 1.DEFAULT_PROTOCOL_VERSION. */ - ProtocolVersion() = default; - /** @brief Constructs protocol version with explicit major/minor values. */ - ProtocolVersion(int32_t major, int32_t minor); - /** @brief Returns major version component. */ + /** @brief Constructs the default version: KMIP 1.4. */ + constexpr ProtocolVersion() = default; + + /** @brief Constructs from explicit on-wire major/minor values. */ + constexpr ProtocolVersion(int32_t major, int32_t minor) + : major_(major), minor_(minor) {} + + /** @brief Returns on-wire major version component. */ [[nodiscard]] int32_t getMajor() const { return major_; } - /** @brief Sets major version component. */ + /** @brief Sets on-wire major version component. */ void setMajor(int32_t major) { major_ = major; } - /** @brief Returns minor version component. */ + /** @brief Returns on-wire minor version component. */ [[nodiscard]] int32_t getMinor() const { return minor_; } - /** @brief Sets minor version component. */ + /** @brief Sets on-wire minor version component. */ void setMinor(int32_t minor) { minor_ = minor; } + + /** + * @brief Returns true when this version is >= the given on-wire major.minor. + * + * Example: @c version.is_at_least(2, 0) is true for KMIP 2.0 and later. + */ + [[nodiscard]] bool is_at_least(int32_t major, int32_t minor) const noexcept { + return major_ > major || (major_ == major && minor_ >= minor); + } + /** @brief Encodes version to TTLV element form. */ [[nodiscard]] std::shared_ptr toElement() const; /** @brief Decodes version from TTLV element form. */ @@ -33,9 +49,14 @@ namespace kmipcore { private: int32_t major_ = 1; - int32_t minor_ = DEFAULT_PROTOCOL_VERSION; + int32_t minor_ = 4; }; + /** @brief KMIP protocol version 1.4. */ + inline constexpr ProtocolVersion KMIP_VERSION_1_4{1, 4}; + /** @brief KMIP protocol version 2.0. */ + inline constexpr ProtocolVersion KMIP_VERSION_2_0{2, 0}; + /** @brief KMIP request header model. */ class RequestHeader { public: @@ -242,12 +263,12 @@ namespace kmipcore { /** Default maximum response-size hint used in request headers. */ static constexpr size_t DEFAULT_MAX_RESPONSE_SIZE = KMIP_MAX_MESSAGE_SIZE; - /** @brief Constructs message with default protocol and limits. */ + /** @brief Constructs message with default protocol (KMIP 1.4) and limits. */ RequestMessage(); - /** @brief Constructs message using a KMIP version constant or raw 1.x minor. */ - explicit RequestMessage(int32_t protocolVersionMinor); - /** @brief Constructs message with a KMIP version selector and response size hint. */ - RequestMessage(int32_t protocolVersionMinor, size_t maxResponseSize); + /** @brief Constructs message from an explicit @ref ProtocolVersion. */ + explicit RequestMessage(ProtocolVersion version); + /** @brief Constructs message from an explicit @ref ProtocolVersion and response size hint. */ + RequestMessage(ProtocolVersion version, size_t maxResponseSize); /** @brief Returns const request header. */ [[nodiscard]] const RequestHeader &getHeader() const { return header_; } @@ -276,10 +297,6 @@ namespace kmipcore { nextBatchItemId_ = 1; } - /** @brief Sets request protocol version from a KMIP version constant or raw 1.x minor. */ - void setProtocolVersionMinor(int32_t minor); - /** @brief Returns the raw wire minor version from the request header. */ - [[nodiscard]] int32_t getProtocolVersionMinor() const; /** @brief Sets maximum response size hint in request header. */ void setMaxResponseSize(size_t size); diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index eeee8a3..086476c 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -103,7 +103,8 @@ namespace kmipcore { int32_t key_bits, cryptographic_usage_mask usage_mask = static_cast( KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT - ) + ), + ProtocolVersion version = {} ); }; @@ -119,7 +120,8 @@ namespace kmipcore { RegisterSymmetricKeyRequest( const std::string &name, const std::string &group, - const std::vector &key_value + const std::vector &key_value, + ProtocolVersion version = {} ); }; @@ -135,7 +137,8 @@ namespace kmipcore { RegisterKeyRequest( const std::string &name, const std::string &group, - const Key &key + const Key &key, + ProtocolVersion version = {} ); }; @@ -153,7 +156,8 @@ namespace kmipcore { const std::string &name, const std::string &group, const std::vector &secret, - secret_data_type secret_type + secret_data_type secret_type, + ProtocolVersion version = {} ); }; @@ -167,13 +171,15 @@ namespace kmipcore { * @param object_type KMIP object_type to match. * @param max_items Maximum number of items requested per locate call. * @param offset Locate offset used for paged reads. + * @param version Protocol version; controls KMIP 2.0 Attributes vs 1.x Attribute format. */ LocateRequest( bool locate_by_group, const std::string &name, object_type obj_type, size_t max_items = 0, - size_t offset = 0 + size_t offset = 0, + ProtocolVersion version = {} ); }; diff --git a/kmipcore/include/kmipcore/kmip_responses.hpp b/kmipcore/include/kmipcore/kmip_responses.hpp index a1fbcd6..8212f76 100644 --- a/kmipcore/include/kmipcore/kmip_responses.hpp +++ b/kmipcore/include/kmipcore/kmip_responses.hpp @@ -196,5 +196,98 @@ namespace kmipcore { LocateResponsePayload locatePayload_; }; + /** + * @brief Typed response for KMIP Discover Versions operation (KMIP 1.1+). + * + * The response payload contains zero or more ProtocolVersion structures + * listing all versions supported by the server. + */ + class DiscoverVersionsResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Discover Versions response. */ + static DiscoverVersionsResponseBatchItem + fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns the list of protocol versions advertised by the server. */ + [[nodiscard]] const std::vector & + getProtocolVersions() const { + return protocolVersions_; + } + + private: + std::vector protocolVersions_; + }; + + /** @brief Typed response for KMIP Query operation. */ + class QueryResponseBatchItem + : public BaseResponseBatchItem { + public: + using BaseResponseBatchItem::BaseResponseBatchItem; + + /** @brief Converts generic response item into Query response view. */ + static QueryResponseBatchItem fromBatchItem(const ResponseBatchItem &item); + + /** @brief Returns operation codes supported by the server. */ + [[nodiscard]] const std::vector &getOperations() const { + return operations_; + } + /** @brief Returns object types supported by the server. */ + [[nodiscard]] const std::vector &getObjectTypes() const { + return objectTypes_; + } + /** @brief Returns vendor identification returned by the server. */ + [[nodiscard]] const std::string &getVendorIdentification() const { + return vendorIdentification_; + } + /** @brief Returns server name returned by the server. */ + [[nodiscard]] const std::string &getServerName() const { + return serverName_; + } + /** @brief Returns product name returned by the server. */ + [[nodiscard]] const std::string &getProductName() const { + return productName_; + } + /** @brief Returns server version returned by the server. */ + [[nodiscard]] const std::string &getServerVersion() const { + return serverVersion_; + } + /** @brief Returns build level returned by the server. */ + [[nodiscard]] const std::string &getBuildLevel() const { + return buildLevel_; + } + /** @brief Returns build date returned by the server. */ + [[nodiscard]] const std::string &getBuildDate() const { + return buildDate_; + } + /** @brief Returns server serial number returned by the server. */ + [[nodiscard]] const std::string &getServerSerialNumber() const { + return serverSerialNumber_; + } + /** @brief Returns server load returned by the server. */ + [[nodiscard]] const std::string &getServerLoad() const { + return serverLoad_; + } + /** @brief Returns cluster information returned by the server. */ + [[nodiscard]] const std::string &getClusterInfo() const { + return clusterInfo_; + } + + private: + std::vector operations_; + std::vector objectTypes_; + std::string vendorIdentification_; + std::string serverName_; + std::string productName_; + std::string serverVersion_; + std::string buildLevel_; + std::string buildDate_; + std::string serverSerialNumber_; + std::string serverLoad_; + std::string clusterInfo_; + }; + } // namespace kmipcore diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp index 0e66d87..bec23c2 100644 --- a/kmipcore/src/attributes_parser.cpp +++ b/kmipcore/src/attributes_parser.cpp @@ -62,6 +62,82 @@ namespace kmipcore { } } + /** + * @brief Parses a single KMIP 2.0 typed attribute element (not an Attribute + * name/value wrapper) and stores its value in @p result. + * + * KMIP 2.0 returns attributes as specific tagged elements inside an Attributes + * container, e.g. KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM rather than an Attribute + * structure with Attribute Name = "Cryptographic Algorithm". + */ + void parse_v2_typed_attribute( + Attributes &result, + const std::shared_ptr &elem + ) { + if (!elem) return; + + switch (elem->tag) { + case tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + result.set_algorithm( + static_cast(elem->toEnum()) + ); + break; + case tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH: + result.set_crypto_length(elem->toInt()); + break; + case tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK: + result.set_usage_mask( + static_cast(elem->toInt()) + ); + break; + case tag::KMIP_TAG_STATE: + result.set_state(static_cast(elem->toEnum())); + break; + case tag::KMIP_TAG_NAME: + // Name is a Structure: { Name Value (TextString), Name Type (Enum) } + if (const auto name_val = elem->getChild(tag::KMIP_TAG_NAME_VALUE); + name_val) { + result.set(std::string(KMIP_ATTR_NAME_NAME), name_val->toString()); + } + break; + case tag::KMIP_TAG_OBJECT_GROUP: + result.set(std::string(KMIP_ATTR_NAME_GROUP), elem->toString()); + break; + case tag::KMIP_TAG_UNIQUE_IDENTIFIER: + result.set( + std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER), elem->toString() + ); + break; + case tag::KMIP_TAG_ACTIVATION_DATE: + result.set( + std::string(KMIP_ATTR_NAME_ACTIVATION_DATE), + date_to_string(elem->toLong()) + ); + break; + case tag::KMIP_TAG_DEACTIVATION_DATE: + result.set( + std::string(KMIP_ATTR_NAME_DEACTIVATION_DATE), + date_to_string(elem->toLong()) + ); + break; + case tag::KMIP_TAG_PROCESS_START_DATE: + result.set( + std::string(KMIP_ATTR_NAME_PROCESS_START_DATE), + date_to_string(elem->toLong()) + ); + break; + case tag::KMIP_TAG_PROTECT_STOP_DATE: + result.set( + std::string(KMIP_ATTR_NAME_PROTECT_STOP_DATE), + date_to_string(elem->toLong()) + ); + break; + default: + // Unknown KMIP 2.0 attribute — skip silently. + break; + } + } + } // namespace Attributes AttributesParser::parse( @@ -70,41 +146,47 @@ namespace kmipcore { Attributes result; for (const auto &attribute : attributes) { - if (!attribute || attribute->tag != tag::KMIP_TAG_ATTRIBUTE) continue; - - auto attr_name_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); - auto attr_value_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); - if (!attr_name_elem) continue; - - const auto raw_name = attr_name_elem->toString(); - - // ---- Well-known typed attributes: handled explicitly for clarity ---- - if (raw_name == "Cryptographic Algorithm") { - if (attr_value_elem) - result.set_algorithm(static_cast(attr_value_elem->toEnum())); + if (!attribute) continue; + + // ---- KMIP 1.x: Attribute structure with Attribute Name + Attribute Value ---- + if (attribute->tag == tag::KMIP_TAG_ATTRIBUTE) { + auto attr_name_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); + auto attr_value_elem = attribute->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); + if (!attr_name_elem) continue; + + const auto raw_name = attr_name_elem->toString(); + + if (raw_name == "Cryptographic Algorithm") { + if (attr_value_elem) + result.set_algorithm(static_cast(attr_value_elem->toEnum())); + continue; + } + if (raw_name == "Cryptographic Length") { + if (attr_value_elem) result.set_crypto_length(attr_value_elem->toInt()); + continue; + } + if (raw_name == "Cryptographic Usage Mask") { + if (attr_value_elem) + result.set_usage_mask(static_cast(attr_value_elem->toInt())); + continue; + } + if (raw_name == "State") { + if (attr_value_elem) + result.set_state(static_cast(attr_value_elem->toEnum())); + continue; + } + + // ---- Legacy name normalisation ---- + const std::string name = + (raw_name == "UniqueID") ? std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER) : raw_name; + + // ---- All other 1.x attributes: preserve native type in generic map ---- + store_generic(result, name, attr_value_elem); continue; } - if (raw_name == "Cryptographic Length") { - if (attr_value_elem) result.set_crypto_length(attr_value_elem->toInt()); - continue; - } - if (raw_name == "Cryptographic Usage Mask") { - if (attr_value_elem) - result.set_usage_mask(static_cast(attr_value_elem->toInt())); - continue; - } - if (raw_name == "State") { - if (attr_value_elem) - result.set_state(static_cast(attr_value_elem->toEnum())); - continue; - } - - // ---- Legacy name normalisation ---- - const std::string name = - (raw_name == "UniqueID") ? std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER) : raw_name; - // ---- All other attributes: preserve native type in generic map ---- - store_generic(result, name, attr_value_elem); + // ---- KMIP 2.0: typed element with a specific KMIP tag ---- + parse_v2_typed_attribute(result, attribute); } return result; diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp index bebd566..5c3e50e 100644 --- a/kmipcore/src/kmip_protocol.cpp +++ b/kmipcore/src/kmip_protocol.cpp @@ -8,34 +8,8 @@ namespace kmipcore { namespace { - [[nodiscard]] ProtocolVersion protocol_version_from_api_value(int32_t value) { - switch (value) { - case KMIP_1_0: - return {1, 0}; - case KMIP_1_1: - return {1, 1}; - case KMIP_1_2: - return {1, 2}; - case KMIP_1_3: - return {1, 3}; - case KMIP_1_4: - return {1, 4}; - case KMIP_2_0: - return {2, 0}; - default: - return {1, value}; - } - } - - [[nodiscard]] bool protocol_version_at_least( - const ProtocolVersion &version, int32_t major, int32_t minor - ) { - return version.getMajor() > major || - (version.getMajor() == major && version.getMinor() >= minor); - } - [[nodiscard]] bool supports_date_time_extended(const ProtocolVersion &version) { - return protocol_version_at_least(version, 2, 0); + return version.is_at_least(2, 0); } void validate_element_types_for_version( @@ -63,8 +37,6 @@ namespace kmipcore { } // namespace // === ProtocolVersion === - ProtocolVersion::ProtocolVersion(int32_t major, int32_t minor) - : major_(major), minor_(minor) {} std::shared_ptr ProtocolVersion::toElement() const { auto structure = Element::createStructure(tag::KMIP_TAG_PROTOCOL_VERSION); @@ -285,15 +257,13 @@ namespace kmipcore { } // === RequestMessage === RequestMessage::RequestMessage() - : RequestMessage(DEFAULT_PROTOCOL_VERSION, DEFAULT_MAX_RESPONSE_SIZE) {} + : RequestMessage(KMIP_VERSION_1_4, DEFAULT_MAX_RESPONSE_SIZE) {} - RequestMessage::RequestMessage(int32_t protocolVersionMinor) - : RequestMessage(protocolVersionMinor, DEFAULT_MAX_RESPONSE_SIZE) {} + RequestMessage::RequestMessage(ProtocolVersion version) + : RequestMessage(std::move(version), DEFAULT_MAX_RESPONSE_SIZE) {} - RequestMessage::RequestMessage( - int32_t protocolVersionMinor, size_t maxResponseSize - ) { - setProtocolVersionMinor(protocolVersionMinor); + RequestMessage::RequestMessage(ProtocolVersion version, size_t maxResponseSize) { + header_.setProtocolVersion(std::move(version)); setMaxResponseSize(maxResponseSize); } @@ -313,13 +283,6 @@ namespace kmipcore { } } - void RequestMessage::setProtocolVersionMinor(int32_t minor) { - header_.setProtocolVersion(protocol_version_from_api_value(minor)); - } - - int32_t RequestMessage::getProtocolVersionMinor() const { - return header_.getProtocolVersion().getMinor(); - } void RequestMessage::setMaxResponseSize(size_t size) { header_.setMaximumResponseSize(static_cast(size)); diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index b9547cb..b1e016d 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -6,7 +6,12 @@ namespace kmipcore { - namespace detail { std::shared_ptr make_text_attribute( + namespace detail { + [[nodiscard]] bool use_attributes_container(const ProtocolVersion &version) { + return version.is_at_least(2, 0); + } + + std::shared_ptr make_text_attribute( const std::string &attribute_name, const std::string &value ) { auto attribute = @@ -218,6 +223,176 @@ namespace kmipcore { )); return secret_data; } + std::shared_ptr + make_attributes_container( + const ProtocolVersion &version, + const std::vector> &attributes + ) { + if (use_attributes_container(version)) { + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + for (const auto &attribute : attributes) { + attrs->asStructure()->add(attribute); + } + return attrs; + } + return make_template_attribute(attributes); + } + + // ------------------------------------------------------------------------- + // KMIP 2.0: helpers that build properly-typed child elements for the + // Attributes container (no Attribute name/value wrappers). + // ------------------------------------------------------------------------- + + std::shared_ptr make_v2_name_struct(const std::string &value) { + auto name = Element::createStructure(tag::KMIP_TAG_NAME); + name->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_NAME_VALUE, value) + ); + name->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_NAME_TYPE, KMIP_NAME_UNINTERPRETED_TEXT_STRING + ) + ); + return name; + } + + /** + * @brief Builds an Attributes container (KMIP 2.0) for a symmetric-key + * Create request using properly typed child elements. + */ + std::shared_ptr make_v2_create_symmetric_attrs( + const std::string &name, + const std::string &group, + int32_t key_bits, + cryptographic_usage_mask usage_mask + ) { + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + attrs->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, KMIP_CRYPTOALG_AES + ) + ); + attrs->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, key_bits) + ); + attrs->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK, + static_cast(usage_mask) + ) + ); + attrs->asStructure()->add(make_v2_name_struct(name)); + if (!group.empty()) { + attrs->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, group) + ); + } + return attrs; + } + + /** + * @brief Builds an Attributes container (KMIP 2.0) for a Register + * (symmetric key) request. + */ + std::shared_ptr make_v2_register_symmetric_attrs( + const std::string &name, + const std::string &group, + int32_t key_bits, + int32_t usage_mask_bits + ) { + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + attrs->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, KMIP_CRYPTOALG_AES + ) + ); + attrs->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, key_bits) + ); + attrs->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK, usage_mask_bits + ) + ); + attrs->asStructure()->add(make_v2_name_struct(name)); + if (!group.empty()) { + attrs->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, group) + ); + } + return attrs; + } + + /** + * @brief Builds an Attributes container (KMIP 2.0) for a Register + * (secret data) request. + */ + std::shared_ptr make_v2_register_secret_attrs( + const std::string &name, + const std::string &group + ) { + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + attrs->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK, + KMIP_CRYPTOMASK_DERIVE_KEY | KMIP_CRYPTOMASK_EXPORT + ) + ); + attrs->asStructure()->add(make_v2_name_struct(name)); + if (!group.empty()) { + attrs->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, group) + ); + } + return attrs; + } + + /** + * @brief Builds an Attributes container (KMIP 2.0) for a generic Register + * (key) request from a Key object's typed and generic attributes. + */ + std::shared_ptr make_v2_register_key_attrs( + const std::string &name, + const std::string &group, + const Key &key + ) { + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + attrs->asStructure()->add(make_v2_name_struct(name)); + if (!group.empty()) { + attrs->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, group) + ); + } + if (const auto alg = key.attributes().algorithm(); + alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { + attrs->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, static_cast(alg) + ) + ); + } + const int32_t key_len = + key.attributes().crypto_length() + .value_or(static_cast(key.value().size() * 8)); + if (key_len > 0) { + attrs->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, key_len) + ); + } + if (const auto mask = key.attributes().usage_mask(); + mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { + attrs->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK, static_cast(mask) + ) + ); + } + // Generic attributes: not representable as typed KMIP 2.0 elements; + // omit them to avoid protocol errors. Callers should use well-known + // typed fields for all standard attributes. + return attrs; + } + } // namespace detail // --------------------------------------------------------------------------- @@ -227,36 +402,11 @@ namespace kmipcore { const std::string &name, const std::string &group, int32_t key_bits, - cryptographic_usage_mask usage_mask + cryptographic_usage_mask usage_mask, + ProtocolVersion version ) { - if (key_bits != 128 && key_bits != 192 && key_bits != 256) { - throw KmipException( - KMIP_INVALID_FIELD, - "Unsupported AES key size for CreateSymmetricKeyRequest" - ); - } setOperation(KMIP_OP_CREATE); - std::vector> attributes; - attributes.push_back( - detail::make_enum_attribute( - "Cryptographic Algorithm", KMIP_CRYPTOALG_AES - ) - ); - attributes.push_back( - detail::make_integer_attribute("Cryptographic Length", key_bits) - ); - attributes.push_back( - detail::make_integer_attribute( - "Cryptographic Usage Mask", - static_cast(usage_mask) - ) - ); - attributes.push_back(detail::make_name_attribute(name)); - if (!group.empty()) { - attributes.push_back(detail::make_text_attribute("Object Group", group)); - } - auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( @@ -264,7 +414,36 @@ namespace kmipcore { tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); - payload->asStructure()->add(detail::make_template_attribute(attributes)); + + if (detail::use_attributes_container(version)) { + // KMIP 2.0: properly typed elements in Attributes container. + payload->asStructure()->add( + detail::make_v2_create_symmetric_attrs(name, group, key_bits, usage_mask) + ); + } else { + // KMIP 1.x: Attribute name/value pairs wrapped in TemplateAttribute. + std::vector> attributes; + attributes.push_back( + detail::make_enum_attribute( + "Cryptographic Algorithm", KMIP_CRYPTOALG_AES + ) + ); + attributes.push_back( + detail::make_integer_attribute("Cryptographic Length", key_bits) + ); + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + static_cast(usage_mask) + ) + ); + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + payload->asStructure()->add(detail::make_template_attribute(attributes)); + } + setRequestPayload(payload); } @@ -274,31 +453,12 @@ namespace kmipcore { RegisterSymmetricKeyRequest::RegisterSymmetricKeyRequest( const std::string &name, const std::string &group, - const std::vector &key_value + const std::vector &key_value, + ProtocolVersion version ) { setOperation(KMIP_OP_REGISTER); - std::vector> attributes; - attributes.push_back( - detail::make_enum_attribute( - "Cryptographic Algorithm", KMIP_CRYPTOALG_AES - ) - ); - attributes.push_back( - detail::make_integer_attribute( - "Cryptographic Length", static_cast(key_value.size() * 8) - ) - ); - attributes.push_back( - detail::make_integer_attribute( - "Cryptographic Usage Mask", - KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT - ) - ); - attributes.push_back(detail::make_name_attribute(name)); - if (!group.empty()) { - attributes.push_back(detail::make_text_attribute("Object Group", group)); - } + const int32_t key_bits = static_cast(key_value.size() * 8); auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); @@ -307,74 +467,49 @@ namespace kmipcore { tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY ) ); - payload->asStructure()->add(detail::make_template_attribute(attributes)); - payload->asStructure()->add(detail::make_symmetric_key(key_value)); - setRequestPayload(payload); - } - RegisterKeyRequest::RegisterKeyRequest( - const std::string &name, - const std::string &group, - const Key &key - ) { - setOperation(KMIP_OP_REGISTER); - - std::vector> attributes; - attributes.push_back(detail::make_name_attribute(name)); - if (!group.empty()) { - attributes.push_back(detail::make_text_attribute("Object Group", group)); - } - - if (const auto alg = key.attributes().algorithm(); - alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { - attributes.push_back( - detail::make_enum_attribute( - "Cryptographic Algorithm", static_cast(alg) + if (detail::use_attributes_container(version)) { + payload->asStructure()->add( + detail::make_v2_register_symmetric_attrs( + name, group, key_bits, + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT ) ); - } - if (const auto len = key.attributes().crypto_length(); len.has_value()) { + } else { + std::vector> attributes; attributes.push_back( - detail::make_integer_attribute("Cryptographic Length", *len) + detail::make_enum_attribute( + "Cryptographic Algorithm", KMIP_CRYPTOALG_AES + ) ); - } else if (!key.value().empty()) { attributes.push_back( - detail::make_integer_attribute( - "Cryptographic Length", - static_cast(key.value().size() * 8) - ) + detail::make_integer_attribute("Cryptographic Length", key_bits) ); - } - if (const auto mask = key.attributes().usage_mask(); - mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { attributes.push_back( detail::make_integer_attribute( - "Cryptographic Usage Mask", static_cast(mask) + "Cryptographic Usage Mask", + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT ) ); - } - - for (const auto &[attr_name, attr_val] : key.attributes().generic()) { - if (attr_name == "Name" || attr_name == "Object Group") { - continue; + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); } - // Convert AttributeValue variant to string - std::string str_val = std::visit( - [](const auto &val) -> std::string { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return val; - } else if constexpr (std::is_same_v) { - return val ? "true" : "false"; - } else { - return std::to_string(val); - } - }, - attr_val - ); - attributes.push_back(detail::make_text_attribute(attr_name, str_val)); + payload->asStructure()->add(detail::make_template_attribute(attributes)); } + payload->asStructure()->add(detail::make_symmetric_key(key_value)); + setRequestPayload(payload); + } + + RegisterKeyRequest::RegisterKeyRequest( + const std::string &name, + const std::string &group, + const Key &key, + ProtocolVersion version + ) { + setOperation(KMIP_OP_REGISTER); + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( Element::createEnumeration( @@ -382,7 +517,69 @@ namespace kmipcore { detail::object_type_from_key_type(key.type()) ) ); - payload->asStructure()->add(detail::make_template_attribute(attributes)); + + if (detail::use_attributes_container(version)) { + // KMIP 2.0: properly typed elements in Attributes container. + payload->asStructure()->add(detail::make_v2_register_key_attrs(name, group, key)); + } else { + // KMIP 1.x: Attribute name/value pairs in TemplateAttribute. + std::vector> attributes; + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + + if (const auto alg = key.attributes().algorithm(); + alg != cryptographic_algorithm::KMIP_CRYPTOALG_UNSET) { + attributes.push_back( + detail::make_enum_attribute( + "Cryptographic Algorithm", static_cast(alg) + ) + ); + } + if (const auto len = key.attributes().crypto_length(); len.has_value()) { + attributes.push_back( + detail::make_integer_attribute("Cryptographic Length", *len) + ); + } else if (!key.value().empty()) { + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Length", + static_cast(key.value().size() * 8) + ) + ); + } + if (const auto mask = key.attributes().usage_mask(); + mask != cryptographic_usage_mask::KMIP_CRYPTOMASK_UNSET) { + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", static_cast(mask) + ) + ); + } + + for (const auto &[attr_name, attr_val] : key.attributes().generic()) { + if (attr_name == "Name" || attr_name == "Object Group") { + continue; + } + std::string str_val = std::visit( + [](const auto &val) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return val; + } else if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else { + return std::to_string(val); + } + }, + attr_val + ); + attributes.push_back(detail::make_text_attribute(attr_name, str_val)); + } + payload->asStructure()->add(detail::make_template_attribute(attributes)); + } + payload->asStructure()->add(detail::make_key_object_from_key(key)); setRequestPayload(payload); } @@ -394,22 +591,11 @@ namespace kmipcore { const std::string &name, const std::string &group, const std::vector &secret, - secret_data_type secret_type + secret_data_type secret_type, + ProtocolVersion version ) { setOperation(KMIP_OP_REGISTER); - std::vector> attributes; - attributes.push_back( - detail::make_integer_attribute( - "Cryptographic Usage Mask", - KMIP_CRYPTOMASK_DERIVE_KEY | KMIP_CRYPTOMASK_EXPORT - ) - ); - attributes.push_back(detail::make_name_attribute(name)); - if (!group.empty()) { - attributes.push_back(detail::make_text_attribute("Object Group", group)); - } - auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); payload->asStructure()->add( @@ -417,7 +603,26 @@ namespace kmipcore { tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA ) ); - payload->asStructure()->add(detail::make_template_attribute(attributes)); + + if (detail::use_attributes_container(version)) { + payload->asStructure()->add( + detail::make_v2_register_secret_attrs(name, group) + ); + } else { + std::vector> attributes; + attributes.push_back( + detail::make_integer_attribute( + "Cryptographic Usage Mask", + KMIP_CRYPTOMASK_DERIVE_KEY | KMIP_CRYPTOMASK_EXPORT + ) + ); + attributes.push_back(detail::make_name_attribute(name)); + if (!group.empty()) { + attributes.push_back(detail::make_text_attribute("Object Group", group)); + } + payload->asStructure()->add(detail::make_template_attribute(attributes)); + } + payload->asStructure()->add(detail::make_secret_data(secret, secret_type)); setRequestPayload(payload); } @@ -430,7 +635,8 @@ namespace kmipcore { const std::string &name, object_type obj_type, size_t max_items, - size_t offset + size_t offset, + ProtocolVersion version ) { setOperation(KMIP_OP_LOCATE); @@ -453,16 +659,38 @@ namespace kmipcore { ); } - payload->asStructure()->add( - detail::make_enum_attribute("Object Type", static_cast(obj_type)) - ); - if (!name.empty()) { - if (locate_by_group) { - payload->asStructure()->add( - detail::make_text_attribute("Object Group", name) - ); - } else { - payload->asStructure()->add(detail::make_name_attribute(name)); + if (detail::use_attributes_container(version)) { + // KMIP 2.0: filter attributes go into an Attributes container with + // properly typed child elements. + auto attrs = Element::createStructure(tag::KMIP_TAG_ATTRIBUTES); + attrs->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, static_cast(obj_type) + ) + ); + if (!name.empty()) { + if (locate_by_group) { + attrs->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, name) + ); + } else { + attrs->asStructure()->add(detail::make_v2_name_struct(name)); + } + } + payload->asStructure()->add(attrs); + } else { + // KMIP 1.x: individual Attribute structures directly in payload. + payload->asStructure()->add( + detail::make_enum_attribute("Object Type", static_cast(obj_type)) + ); + if (!name.empty()) { + if (locate_by_group) { + payload->asStructure()->add( + detail::make_text_attribute("Object Group", name) + ); + } else { + payload->asStructure()->add(detail::make_name_attribute(name)); + } } } setRequestPayload(payload); diff --git a/kmipcore/src/kmip_responses.cpp b/kmipcore/src/kmip_responses.cpp index 2d6b460..0b0961c 100644 --- a/kmipcore/src/kmip_responses.cpp +++ b/kmipcore/src/kmip_responses.cpp @@ -75,8 +75,22 @@ namespace kmipcore { auto payload = detail::require_response_payload( item, "GetAttributesResponseBatchItem" ); - result.attributes_ = - payload->getChildren(tag::KMIP_TAG_ATTRIBUTE); + + // KMIP 1.x returns Attribute elements directly under Response Payload. + // KMIP 2.0 wraps attributes inside an Attributes structure with typed children. + result.attributes_ = payload->getChildren(tag::KMIP_TAG_ATTRIBUTE); + if (result.attributes_.empty()) { + if (const auto attributes = payload->getChild(tag::KMIP_TAG_ATTRIBUTES); + attributes) { + // Try transitional style (Attribute wrappers inside Attributes container). + result.attributes_ = attributes->getChildren(tag::KMIP_TAG_ATTRIBUTE); + if (result.attributes_.empty() && attributes->asStructure()) { + // Pure KMIP 2.0: typed elements (e.g. KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM) + // directly inside the Attributes container. + result.attributes_ = attributes->asStructure()->items; + } + } + } return result; } @@ -116,4 +130,98 @@ namespace kmipcore { return result; } + // --- DiscoverVersionsResponseBatchItem --- + + DiscoverVersionsResponseBatchItem + DiscoverVersionsResponseBatchItem::fromBatchItem( + const ResponseBatchItem &item + ) { + detail::expect_operation( + item, KMIP_OP_DISCOVER_VERSIONS, "DiscoverVersionsResponseBatchItem" + ); + + DiscoverVersionsResponseBatchItem result(item); + + // The response payload is optional: the server may return no payload at all + // when it supports no versions other than the negotiated one, or when the + // request contained a version list the server couldn't match. + auto payload = item.getResponsePayload(); + if (payload) { + for (const auto &pvElement : + payload->getChildren(tag::KMIP_TAG_PROTOCOL_VERSION)) { + result.protocolVersions_.push_back( + ProtocolVersion::fromElement(pvElement) + ); + } + } + + return result; + } + + // --- QueryResponseBatchItem --- + + QueryResponseBatchItem + QueryResponseBatchItem::fromBatchItem(const ResponseBatchItem &item) { + detail::expect_operation(item, KMIP_OP_QUERY, "QueryResponseBatchItem"); + + QueryResponseBatchItem result(item); + + // Query response payload is optional; an empty payload means no values. + auto payload = item.getResponsePayload(); + if (!payload) { + return result; + } + + for (const auto &opElement : payload->getChildren(tag::KMIP_TAG_OPERATION)) { + result.operations_.push_back(opElement->toEnum()); + } + for (const auto &objElement : payload->getChildren(tag::KMIP_TAG_OBJECT_TYPE)) { + result.objectTypes_.push_back(objElement->toEnum()); + } + + if (const auto vendor = payload->getChild(tag::KMIP_TAG_VENDOR_IDENTIFICATION); + vendor) { + result.vendorIdentification_ = vendor->toString(); + } + + if (const auto serverInfo = payload->getChild(tag::KMIP_TAG_SERVER_INFORMATION); + serverInfo && serverInfo->asStructure()) { + if (const auto serverName = serverInfo->getChild(tag::KMIP_TAG_SERVER_NAME); + serverName) { + result.serverName_ = serverName->toString(); + } + if (const auto serial = + serverInfo->getChild(tag::KMIP_TAG_SERVER_SERIAL_NUMBER); + serial) { + result.serverSerialNumber_ = serial->toString(); + } + if (const auto version = serverInfo->getChild(tag::KMIP_TAG_SERVER_VERSION); + version) { + result.serverVersion_ = version->toString(); + } + if (const auto load = serverInfo->getChild(tag::KMIP_TAG_SERVER_LOAD); + load) { + result.serverLoad_ = load->toString(); + } + if (const auto product = serverInfo->getChild(tag::KMIP_TAG_PRODUCT_NAME); + product) { + result.productName_ = product->toString(); + } + if (const auto buildLevel = serverInfo->getChild(tag::KMIP_TAG_BUILD_LEVEL); + buildLevel) { + result.buildLevel_ = buildLevel->toString(); + } + if (const auto buildDate = serverInfo->getChild(tag::KMIP_TAG_BUILD_DATE); + buildDate) { + result.buildDate_ = buildDate->toString(); + } + if (const auto clusterInfo = serverInfo->getChild(tag::KMIP_TAG_CLUSTER_INFO); + clusterInfo) { + result.clusterInfo_ = clusterInfo->toString(); + } + } + + return result; + } + } // namespace kmipcore diff --git a/kmipcore/src/response_parser.cpp b/kmipcore/src/response_parser.cpp index 6e02904..fa6ab81 100644 --- a/kmipcore/src/response_parser.cpp +++ b/kmipcore/src/response_parser.cpp @@ -137,6 +137,10 @@ namespace kmipcore { return "Revoke"; case KMIP_OP_GET_ATTRIBUTE_LIST: return "Get Attribute List"; + case KMIP_OP_DISCOVER_VERSIONS: + return "Discover Versions"; + case KMIP_OP_QUERY: + return "Query"; default: return "Unknown"; } diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp index a121e66..d8ed87f 100644 --- a/kmipcore/tests/test_core.cpp +++ b/kmipcore/tests/test_core.cpp @@ -128,7 +128,7 @@ void test_date_time_extended_requires_kmip_2_0_for_requests() { } assert(threw); - RequestMessage request_20(KMIP_2_0); + RequestMessage request_20(KMIP_VERSION_2_0); assert(request_20.getHeader().getProtocolVersion().getMajor() == 2); assert(request_20.getHeader().getProtocolVersion().getMinor() == 0); request_20.add_batch_item(item); @@ -159,7 +159,7 @@ void test_date_time_extended_requires_kmip_2_0_for_responses() { ResponseHeader header; header.getProtocolVersion().setMajor(1); - header.getProtocolVersion().setMinor(KMIP_1_4); + header.getProtocolVersion().setMinor(4); // wire minor for KMIP 1.4 header.setTimeStamp(1234567890); header.setBatchCount(1); response->asStructure()->add(header.toElement()); @@ -464,6 +464,72 @@ void test_typed_response_batch_items() { assert(locate_response.getLocatePayload().getLocatedItems().value() == 2); assert(locate_response.getUniqueIdentifiers().size() == 2); + auto discover_payload = + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + discover_payload->asStructure()->add(ProtocolVersion(2, 0).toElement()); + discover_payload->asStructure()->add(ProtocolVersion(1, 4).toElement()); + + ResponseBatchItem discover_item; + discover_item.setOperation(KMIP_OP_DISCOVER_VERSIONS); + discover_item.setResultStatus(KMIP_STATUS_SUCCESS); + discover_item.setResponsePayload(discover_payload); + + auto discover_response = + DiscoverVersionsResponseBatchItem::fromBatchItem(discover_item); + assert(discover_response.getProtocolVersions().size() == 2); + assert(discover_response.getProtocolVersions()[0].getMajor() == 2); + assert(discover_response.getProtocolVersions()[0].getMinor() == 0); + assert(discover_response.getProtocolVersions()[1].getMajor() == 1); + assert(discover_response.getProtocolVersions()[1].getMinor() == 4); + + ResponseBatchItem discover_empty_item; + discover_empty_item.setOperation(KMIP_OP_DISCOVER_VERSIONS); + discover_empty_item.setResultStatus(KMIP_STATUS_SUCCESS); + + auto discover_empty_response = + DiscoverVersionsResponseBatchItem::fromBatchItem(discover_empty_item); + assert(discover_empty_response.getProtocolVersions().empty()); + + auto query_payload = + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + query_payload->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, KMIP_OP_GET) + ); + query_payload->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA) + ); + query_payload->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_VENDOR_IDENTIFICATION, "VendorX" + ) + ); + auto query_server_info = + Element::createStructure(tag::KMIP_TAG_SERVER_INFORMATION); + query_server_info->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_SERVER_NAME, "node-a") + ); + query_payload->asStructure()->add(query_server_info); + + ResponseBatchItem query_item; + query_item.setOperation(KMIP_OP_QUERY); + query_item.setResultStatus(KMIP_STATUS_SUCCESS); + query_item.setResponsePayload(query_payload); + + auto query_response = QueryResponseBatchItem::fromBatchItem(query_item); + assert(query_response.getOperations().size() == 1); + assert(query_response.getOperations()[0] == KMIP_OP_GET); + assert(query_response.getObjectTypes().size() == 1); + assert(query_response.getObjectTypes()[0] == KMIP_OBJTYPE_SECRET_DATA); + assert(query_response.getVendorIdentification() == "VendorX"); + assert(query_response.getServerName() == "node-a"); + + ResponseBatchItem query_empty_item; + query_empty_item.setOperation(KMIP_OP_QUERY); + query_empty_item.setResultStatus(KMIP_STATUS_SUCCESS); + auto query_empty_response = QueryResponseBatchItem::fromBatchItem(query_empty_item); + assert(query_empty_response.getOperations().empty()); + assert(query_empty_response.getObjectTypes().empty()); + ResponseBatchItem destroy_item; destroy_item.setOperation(KMIP_OP_DESTROY); destroy_item.setResultStatus(KMIP_STATUS_SUCCESS); diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp index 8b26213..186dacd 100644 --- a/kmipcore/tests/test_parsers.cpp +++ b/kmipcore/tests/test_parsers.cpp @@ -61,7 +61,7 @@ std::vector create_mock_response_bytes_with_result( ) { ResponseMessage resp; resp.getHeader().getProtocolVersion().setMajor(1); - resp.getHeader().getProtocolVersion().setMinor(4); + resp.getHeader().getProtocolVersion().setMinor(4); // wire minor for KMIP 1.4 resp.getHeader().setTimeStamp(1234567890); resp.getHeader().setBatchCount(1); @@ -162,6 +162,95 @@ void test_response_parser_locate() { std::cout << "ResponseParser Locate test passed" << std::endl; } +void test_response_parser_discover_versions() { + auto payload = + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add(ProtocolVersion(2, 1).toElement()); + payload->asStructure()->add(ProtocolVersion(2, 0).toElement()); + payload->asStructure()->add(ProtocolVersion(1, 4).toElement()); + + auto bytes = create_mock_response_bytes(KMIP_OP_DISCOVER_VERSIONS, payload); + ResponseParser parser(bytes); + + auto discover_resp = parser.getResponse(0); + const auto &versions = discover_resp.getProtocolVersions(); + assert(versions.size() == 3); + assert(versions[0].getMajor() == 2 && versions[0].getMinor() == 1); + assert(versions[1].getMajor() == 2 && versions[1].getMinor() == 0); + assert(versions[2].getMajor() == 1 && versions[2].getMinor() == 4); + + std::cout << "ResponseParser Discover Versions test passed" << std::endl; +} + +void test_response_parser_discover_versions_empty_payload() { + auto bytes = create_mock_response_bytes(KMIP_OP_DISCOVER_VERSIONS, nullptr); + ResponseParser parser(bytes); + + auto discover_resp = parser.getResponse(0); + assert(discover_resp.getProtocolVersions().empty()); + + std::cout << "ResponseParser Discover Versions empty-payload test passed" + << std::endl; +} + +void test_response_parser_query() { + auto payload = + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, KMIP_OP_GET) + ); + payload->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, KMIP_OP_CREATE) + ); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + payload->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_VENDOR_IDENTIFICATION, "ExampleVendor" + ) + ); + + auto server_info = + Element::createStructure(tag::KMIP_TAG_SERVER_INFORMATION); + server_info->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_SERVER_NAME, "example-kmip") + ); + server_info->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_SERVER_VERSION, "2.0.1") + ); + payload->asStructure()->add(server_info); + + auto bytes = create_mock_response_bytes(KMIP_OP_QUERY, payload); + ResponseParser parser(bytes); + + auto query_resp = parser.getResponse(0); + assert(query_resp.getOperations().size() == 2); + assert(query_resp.getOperations()[0] == KMIP_OP_GET); + assert(query_resp.getObjectTypes().size() == 1); + assert(query_resp.getObjectTypes()[0] == KMIP_OBJTYPE_SYMMETRIC_KEY); + assert(query_resp.getVendorIdentification() == "ExampleVendor"); + assert(query_resp.getServerName() == "example-kmip"); + assert(query_resp.getServerVersion() == "2.0.1"); + + std::cout << "ResponseParser Query test passed" << std::endl; +} + +void test_response_parser_query_empty_payload() { + auto bytes = create_mock_response_bytes(KMIP_OP_QUERY, nullptr); + ResponseParser parser(bytes); + + auto query_resp = parser.getResponse(0); + assert(query_resp.getOperations().empty()); + assert(query_resp.getObjectTypes().empty()); + assert(query_resp.getServerName().empty()); + + std::cout << "ResponseParser Query empty-payload test passed" + << std::endl; +} + void test_key_parser_symmetric() { // Construct a mock GetResponse with Symmetric Key @@ -413,6 +502,67 @@ void test_attributes_parser_extended() { std::cout << "AttributesParser Extended test passed" << std::endl; } +void test_attributes_parser_v2_typed() { + // KMIP 2.0 response attributes: typed elements, no Attribute name/value wrappers. + std::vector> v2_attrs; + + // Cryptographic Algorithm (Enumeration with specific tag) + v2_attrs.push_back( + Element::createEnumeration(tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, KMIP_CRYPTOALG_AES) + ); + // Cryptographic Length (Integer with specific tag) + v2_attrs.push_back( + Element::createInteger(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, 256) + ); + // Cryptographic Usage Mask (Integer with specific tag) + v2_attrs.push_back( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK, + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + ) + ); + // State (Enumeration with specific tag) + v2_attrs.push_back( + Element::createEnumeration( + tag::KMIP_TAG_STATE, static_cast(state::KMIP_STATE_ACTIVE) + ) + ); + // Name (Structure with Name Value + Name Type) + { + auto name_elem = Element::createStructure(tag::KMIP_TAG_NAME); + name_elem->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_NAME_VALUE, "TestKey2") + ); + name_elem->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_NAME_TYPE, KMIP_NAME_UNINTERPRETED_TEXT_STRING + ) + ); + v2_attrs.push_back(name_elem); + } + // Object Group (Text String with specific tag) + v2_attrs.push_back( + Element::createTextString(tag::KMIP_TAG_OBJECT_GROUP, "production") + ); + + auto result = AttributesParser::parse(v2_attrs); + + assert(result.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES); + assert(result.crypto_length().has_value()); + assert(result.crypto_length().value() == 256); + assert( + result.usage_mask() == + static_cast(KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT) + ); + assert(result.object_state() == state::KMIP_STATE_ACTIVE); + assert(result.has_attribute("Name")); + assert(result.get("Name") == "TestKey2"); + assert(result.has_attribute("Object Group")); + assert(result.get("Object Group") == "production"); + + std::cout << "AttributesParser KMIP 2.0 typed attributes test passed" << std::endl; +} + void test_formatter_for_request_and_response() { RequestMessage request; request.add_batch_item(GetRequest("request-id-123")); @@ -474,12 +624,17 @@ void test_logger_interface() { int main() { test_response_parser_create(); test_response_parser_locate(); + test_response_parser_discover_versions(); + test_response_parser_discover_versions_empty_payload(); + test_response_parser_query(); + test_response_parser_query_empty_payload(); test_response_parser_failure_preserves_reason_code(); test_key_parser_symmetric(); test_key_parser_secret_binary(); test_register_secret_request_structure(); test_attributes_parser(); test_attributes_parser_extended(); + test_attributes_parser_v2_typed(); test_formatter_for_request_and_response(); test_logger_interface(); return 0; From 77ea25230fcc419d0c6d3ece80107bbcb0d2b4c3 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sun, 29 Mar 2026 10:56:12 +0300 Subject: [PATCH 09/11] PS-10068 Fix PR API refinement https://perconadev.atlassian.net/browse/PS-10949 Docs update --- KMIP_MODERN_VS_LEGACY_COMPARISON.md | 18 ++++++++++++++---- kmipclient/README.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/KMIP_MODERN_VS_LEGACY_COMPARISON.md b/KMIP_MODERN_VS_LEGACY_COMPARISON.md index 8dfdf5c..ce2a2b6 100644 --- a/KMIP_MODERN_VS_LEGACY_COMPARISON.md +++ b/KMIP_MODERN_VS_LEGACY_COMPARISON.md @@ -44,6 +44,8 @@ This document compares the modern stack (`kmipcore` + `kmipclient`) with the leg - `op_get_key`, `op_get_secret` - `op_get_attribute_list`, `op_get_attributes` - `op_locate_by_name`, `op_locate_by_group`, `op_all` + - `op_discover_versions` (server-advertised protocol versions) + - `op_query` (server capabilities and server-information fields) - `op_activate`, `op_revoke`, `op_destroy` - Errors are exception-based (`kmipcore::KmipException`) with rich context. - Uses typed request/response parser pipeline. @@ -72,11 +74,13 @@ The modern stack is structurally easier to extend than the legacy stack. **Protocol completeness today:** - Current public API is comprehensive for key/secret lifecycle workflows - (create/register/get/activate/revoke/destroy/locate/attributes). + (create/register/get/activate/revoke/destroy/locate/attributes), and now + includes capability discovery (`Discover Versions`) plus server capability + introspection (`Query`). - It is **not a full KMIP implementation yet**. The current roadmap in `kmipclient/TODO.md` explicitly lists remaining gaps, including: - asymmetric keys and certificates support, - - server version negotiation (default is KMIP 1.4), + - automatic server-version negotiation policy (default request version remains KMIP 1.4 unless changed by caller), - broader KMIP 2.0 support in the current scope. So the modern stack is best described as: **easy to extend, production-ready for @@ -260,8 +264,12 @@ distinction between them. ### Modern - Default protocol minor is KMIP 1.4 in `kmipcore`. +- Server capability checks are available via: + - `op_discover_versions()` for advertised KMIP versions, + - `op_query()` for supported operations/object types and server info fields. - Serialization uses `SerializationBuffer` to reduce repeated allocations during TTLV serialization. -- Response parsing validates success status and maps typed payloads. +- Response parsing validates success status and maps typed payloads, including + typed wrappers for `Discover Versions` and `Query` responses. ### Legacy @@ -308,7 +316,9 @@ No ASAN build option is provided in `libkmip` or `kmippp` CMake files. - `kmipclient` integrates GoogleTest for integration tests. - `kmipcore` has dedicated core/parser/serialization test executables. - Pool integration tests cover realistic concurrent scenarios. -- Full ASAN integration test run (32 tests, 0 findings) validated. +- KMIP 2.0 integration tests are opt-in via `KMIP_RUN_2_0_TESTS=1`; when not + enabled, the 2.0 suite is excluded by test filter in `main()`. +- ASAN runs are supported via `WITH_ASAN=ON` and validated in this repository. ### Legacy diff --git a/kmipclient/README.md b/kmipclient/README.md index 36d40d8..8855560 100644 --- a/kmipclient/README.md +++ b/kmipclient/README.md @@ -218,6 +218,8 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException | `op_locate_by_name(name, object_type)` | Find entity IDs by name | | `op_locate_by_group(group, object_type [, max_ids])` | Find entity IDs by group | | `op_all(object_type [, max_ids])` | Retrieve all entity IDs of a given type | +| `op_discover_versions()` | Discover KMIP protocol versions advertised by the server | +| `op_query()` | Query server capabilities, supported operations/object types, and server metadata | | `op_get_attribute_list(id)` | List attribute names for an entity | | `op_get_attributes(id, attr_names)` | Retrieve specific attributes by name | @@ -333,6 +335,25 @@ auto attr_names = client.op_get_attribute_list(id); auto attrs = client.op_get_attributes(id, attr_names); ``` +### Discover supported KMIP protocol versions + +```cpp +auto versions = client.op_discover_versions(); +for (const auto &v : versions) { + std::cout << "KMIP " << v.getMajor() << '.' << v.getMinor() << '\n'; +} +``` + +### Query server capabilities and metadata + +```cpp +auto info = client.op_query(); +std::cout << "Vendor: " << info.vendor_name << '\n'; +std::cout << "Server: " << info.server_name << '\n'; +std::cout << "Supported operations: " << info.supported_operations.size() << '\n'; +std::cout << "Supported object types: " << info.supported_object_types.size() << '\n'; +``` + ### Protocol logging Pass any `kmipcore::Logger`-derived instance to enable TTLV message logging: @@ -417,6 +438,8 @@ export KMIP_PORT=5696 export KMIP_CLIENT_CA=/path/to/client_cert.pem export KMIP_CLIENT_KEY=/path/to/client_key.pem export KMIP_SERVER_CA=/path/to/server_cert.pem +# Optional: enable KMIP 2.0 integration suite +export KMIP_RUN_2_0_TESTS=1 ``` 2. Configure and build: @@ -434,6 +457,9 @@ ctest --output-on-failure ./kmipclient/kmipclient_test ``` +When `KMIP_RUN_2_0_TESTS` is not set to `1`, the KMIP 2.0 integration suite +is excluded via GoogleTest filter (`-KmipClientIntegrationTest20.*`). + 4. Run with AddressSanitizer (requires the ASAN build above): ```bash @@ -461,6 +487,8 @@ ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:print_stats=1" \ | `example_destroy` | Destroy a revoked key or secret | | `example_locate` | Find entity IDs by name | | `example_locate_by_group` | Find entity IDs by group | +| `example_supported_versions` | Discover and print protocol versions advertised by server | +| `example_query_server_info` | Query and print supported operations/object types and server metadata | | `example_pool` | Multi-threaded pool demo (concurrent key creation) | All examples follow the same argument pattern: From 3ab9736d7c5d8b011fe9148240645255b79b2803 Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sun, 29 Mar 2026 11:26:27 +0300 Subject: [PATCH 10/11] Fix KMIP locate paging and response batch-count validation --- kmipclient/src/KmipClient.cpp | 150 ++++++++++++++++++++---------- kmipclient/tests/TestEnvUtils.hpp | 0 kmipcore/src/kmip_protocol.cpp | 3 + kmipcore/tests/test_core.cpp | 35 +++++++ 4 files changed, 137 insertions(+), 51 deletions(-) create mode 100644 kmipclient/tests/TestEnvUtils.hpp diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index 4e94c76..09ebd9c 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -39,7 +39,7 @@ static kmipcore::RequestBatchItem make_activate_using_id_placeholder_request() { return item; } -static std::vector default_get_attrs(bool all_attributes) { +static std::vector default_get_key_attrs(bool all_attributes) { if (all_attributes) { return {}; } @@ -53,6 +53,17 @@ static std::vector default_get_attrs(bool all_attributes) { }; } +static std::vector default_get_secret_attrs(bool all_attributes) { + if (all_attributes) { + return {}; + } + return { + KMIP_ATTR_NAME_STATE, + KMIP_ATTR_NAME_NAME, + KMIP_ATTR_NAME_OPERATION_POLICY_NAME + }; +} + KmipClient::KmipClient( NetClient &net_client, const std::shared_ptr &logger, @@ -233,7 +244,7 @@ static std::vector default_get_attrs(bool all_attributes) { const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, default_get_attrs(all_attributes)) + kmipcore::GetAttributesRequest(id, default_get_key_attrs(all_attributes)) ); std::vector response_bytes; @@ -262,12 +273,6 @@ static std::vector default_get_attrs(bool all_attributes) { "Required attribute 'State' missing from server response" ); } - if (!server_attrs.has_attribute(KMIP_ATTR_NAME_NAME)) { - throw kmipcore::KmipException( - "Required attribute 'Name' missing from server response" - ); - } - // Merge server-provided metadata (state, name, dates, …) into the key. key->attributes().merge(server_attrs); @@ -279,7 +284,7 @@ static std::vector default_get_attrs(bool all_attributes) { const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, default_get_attrs(all_attributes)) + kmipcore::GetAttributesRequest(id, default_get_secret_attrs(all_attributes)) ); std::vector response_bytes; @@ -310,14 +315,13 @@ static std::vector default_get_attrs(bool all_attributes) { "Required attribute 'State' missing from server response" ); } - if (!server_attrs.has_attribute(KMIP_ATTR_NAME_NAME)) { - throw kmipcore::KmipException( - "Required attribute 'Name' missing from server response" + // Copy only the minimal set: state (typed) + optional name (generic). + secret.set_state(server_attrs.object_state()); + if (server_attrs.has_attribute(KMIP_ATTR_NAME_NAME)) { + secret.set_attribute( + KMIP_ATTR_NAME_NAME, std::string(server_attrs.get(KMIP_ATTR_NAME_NAME)) ); } - // Copy only the minimal set: state (typed) + name (generic). - secret.set_state(server_attrs.object_state()); - secret.set_attribute(KMIP_ATTR_NAME_NAME, std::string(server_attrs.get(KMIP_ATTR_NAME_NAME))); } return secret; @@ -382,32 +386,56 @@ static std::vector default_get_attrs(bool all_attributes) { std::vector KmipClient::op_locate_by_name( const std::string &name, object_type o_type ) const { - auto request = make_request_message(); - const auto batch_item_id = request.add_batch_item( - kmipcore::LocateRequest( - false, - name, - o_type, - MAX_ITEMS_IN_BATCH, - 0, - request.getHeader().getProtocolVersion() - ) - ); + std::vector result; + std::size_t offset = 0; - std::vector response_bytes; - io->do_exchange( - request.serialize(), response_bytes, request.getMaxResponseSize() - ); + for (std::size_t batch = 0; batch < MAX_BATCHES_IN_SEARCH; ++batch) { + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::LocateRequest( + false, + name, + o_type, + MAX_ITEMS_IN_BATCH, + offset, + request.getHeader().getProtocolVersion() + ) + ); - kmipcore::ResponseParser rf(response_bytes); - auto response = - rf.getResponseByBatchItemId( - batch_item_id - ); - return { - response.getUniqueIdentifiers().begin(), - response.getUniqueIdentifiers().end() - }; + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + auto got = std::vector( + response.getUniqueIdentifiers().begin(), + response.getUniqueIdentifiers().end() + ); + + if (got.empty()) { + break; + } + + offset += got.size(); + result.insert(result.end(), got.begin(), got.end()); + + if (const auto located_items = response.getLocatePayload().getLocatedItems(); + located_items.has_value() && *located_items >= 0 && + offset >= static_cast(*located_items)) { + break; + } + + if (got.size() < MAX_ITEMS_IN_BATCH) { + break; + } + } + + return result; } std::vector KmipClient::op_locate_by_group( @@ -418,10 +446,11 @@ static std::vector default_get_attrs(bool all_attributes) { } std::vector result; - std::size_t received = 0; std::size_t offset = 0; - do { + for (std::size_t batch = 0; + batch < MAX_BATCHES_IN_SEARCH && result.size() < max_ids; + ++batch) { const std::size_t remaining = max_ids - result.size(); const std::size_t page_size = std::min(remaining, MAX_ITEMS_IN_BATCH); @@ -447,20 +476,29 @@ static std::vector default_get_attrs(bool all_attributes) { rf.getResponseByBatchItemId( batch_item_id ); - auto exp = std::vector( + auto got = std::vector( response.getUniqueIdentifiers().begin(), response.getUniqueIdentifiers().end() ); - if (std::vector got = exp; !got.empty()) { - received = got.size(); - offset += got.size(); - const std::size_t to_take = std::min(remaining, got.size()); - std::copy_n(got.begin(), to_take, std::back_inserter(result)); - } else { + if (got.empty()) { break; } - } while (received == MAX_ITEMS_IN_BATCH && result.size() < max_ids); + + offset += got.size(); + const std::size_t to_take = std::min(remaining, got.size()); + std::copy_n(got.begin(), to_take, std::back_inserter(result)); + + if (const auto located_items = response.getLocatePayload().getLocatedItems(); + located_items.has_value() && *located_items >= 0 && + offset >= static_cast(*located_items)) { + break; + } + + if (got.size() < page_size) { + break; + } + } return result; } @@ -538,8 +576,18 @@ static std::vector default_get_attrs(bool all_attributes) { ); QueryServerInfo result; - result.supported_operations = response.getOperations(); - result.supported_object_types = response.getObjectTypes(); + result.supported_operations.reserve(response.getOperations().size()); + for (const auto op : response.getOperations()) { + result.supported_operations.push_back( + static_cast(op) + ); + } + result.supported_object_types.reserve(response.getObjectTypes().size()); + for (const auto type : response.getObjectTypes()) { + result.supported_object_types.push_back( + static_cast(type) + ); + } result.vendor_name = response.getVendorIdentification(); result.server_name = response.getServerName(); result.product_name = response.getProductName(); diff --git a/kmipclient/tests/TestEnvUtils.hpp b/kmipclient/tests/TestEnvUtils.hpp new file mode 100644 index 0000000..e69de29 diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp index 5c3e50e..7be65b9 100644 --- a/kmipcore/src/kmip_protocol.cpp +++ b/kmipcore/src/kmip_protocol.cpp @@ -507,6 +507,9 @@ namespace kmipcore { rm.batchItems_.push_back(ResponseBatchItem::fromElement(child)); } } + if (rm.header_.getBatchCount() != static_cast(rm.batchItems_.size())) { + throw KmipException("Response Header Batch Count does not match number of Batch Items"); + } return rm; } } // namespace kmipcore diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp index d8ed87f..26c9ba5 100644 --- a/kmipcore/tests/test_core.cpp +++ b/kmipcore/tests/test_core.cpp @@ -598,6 +598,41 @@ void test_response_required_fields() { assert(threw); } + { + // Header Batch Count must match actual number of Batch Items. + auto response_message = + Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); + + ResponseHeader header; + header.getProtocolVersion().setMajor(1); + header.getProtocolVersion().setMinor(4); + header.setTimeStamp(1234567890); + header.setBatchCount(2); + response_message->asStructure()->add(header.toElement()); + + auto batch_item = + Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); + batch_item->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OPERATION, KMIP_OP_GET + ) + ); + batch_item->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_RESULT_STATUS, KMIP_STATUS_SUCCESS + ) + ); + response_message->asStructure()->add(batch_item); + + bool threw = false; + try { + (void) ResponseMessage::fromElement(response_message); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + } + { // Missing Operation inside ResponseBatchItem must be rejected. auto response_message = From 64d1103a55a1556448e8efb54ff0c5ec6cdff42f Mon Sep 17 00:00:00 2001 From: Oleksiy Lukin Date: Sun, 29 Mar 2026 15:05:11 +0300 Subject: [PATCH 11/11] PS-10068 Fix all code and test of KMIP v.2.0 https://perconadev.atlassian.net/browse/PS-10949 Docs update --- kmipclient/README.md | 26 ++- .../examples/example_query_server_info.cpp | 4 +- kmipclient/include/kmipclient/KmipClient.hpp | 4 +- kmipclient/src/KmipClient.cpp | 202 ++++++++++++------ .../tests/KmipClientIntegrationTest.cpp | 14 +- .../tests/KmipClientIntegrationTest_2_0.cpp | 10 +- kmipclient/tests/TestEnvUtils.hpp | 17 ++ kmipcore/include/kmipcore/kmip_enums.hpp | 2 + kmipcore/include/kmipcore/kmip_requests.hpp | 22 +- kmipcore/include/kmipcore/response_parser.hpp | 40 +++- kmipcore/src/kmip_protocol.cpp | 8 +- kmipcore/src/kmip_requests.cpp | 96 +++++++++ kmipcore/src/kmip_responses.cpp | 82 +++++++ kmipcore/src/response_parser.cpp | 99 ++++++--- kmipcore/tests/test_core.cpp | 17 +- kmipcore/tests/test_parsers.cpp | 146 +++++++++++++ 16 files changed, 636 insertions(+), 153 deletions(-) diff --git a/kmipclient/README.md b/kmipclient/README.md index 8855560..b364e66 100644 --- a/kmipclient/README.md +++ b/kmipclient/README.md @@ -207,9 +207,9 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException |---|---| | `op_create_aes_key(name, group)` | Server-side AES-256 key generation (KMIP CREATE) | | `op_register_key(name, group, key)` | Register an existing key (KMIP REGISTER) | -| `op_register_and_activate_key(name, group, key)` | Register and activate key in one batched request (**experimental**) | +| `op_register_and_activate_key(name, group, key)` | Register and activate key (prefers single batched request; auto-fallback to sequential register→activate when server does not support ID Placeholder batching) | | `op_register_secret(name, group, secret)` | Register a secret / password | -| `op_register_and_activate_secret(name, group, secret)` | Register and activate secret in one batched request (**experimental**) | +| `op_register_and_activate_secret(name, group, secret)` | Register and activate secret (prefers single batched request; auto-fallback to sequential register→activate when server does not support ID Placeholder batching) | | `op_get_key(id [, all_attributes])` | Retrieve key object (`std::unique_ptr`) with optional attributes | | `op_get_secret(id [, all_attributes])` | Retrieve a secret / password | | `op_activate(id)` | Activate an entity (pre-active → active) | @@ -223,11 +223,18 @@ All operations are methods of `KmipClient`. They throw `kmipcore::KmipException | `op_get_attribute_list(id)` | List attribute names for an entity | | `op_get_attributes(id, attr_names)` | Retrieve specific attributes by name | -> **Experimental methods** -> -> `op_register_and_activate_key(...)` and `op_register_and_activate_secret(...)` -> are experimental. They rely on batched Register+Activate behavior and are -> known **not to work with pyKmip server**. +### Interoperability notes (KMIP 2.0 / pyKMIP) + +- `op_register_and_activate_key(...)` and `op_register_and_activate_secret(...)` + first try the single-request batched flow (Register + Activate with ID Placeholder). + If the server rejects that shape (common with pyKMIP), the client automatically + falls back to the interoperable two-request sequence: Register, then Activate. +- For KMIP 2.0, pyKMIP may reject `Get Attributes` requests that include explicit + selector names; `KmipClient` therefore requests all attributes and filters/consumes + the needed fields client-side. +- Some servers omit `Operation` and/or `Unique Batch Item ID` in responses. + The parser tolerates this and uses request-derived hints for correlation and + error formatting. --- @@ -460,6 +467,11 @@ ctest --output-on-failure When `KMIP_RUN_2_0_TESTS` is not set to `1`, the KMIP 2.0 integration suite is excluded via GoogleTest filter (`-KmipClientIntegrationTest20.*`). +The KMIP 1.4 integration suite includes enabled tests for: + +- `KmipClientIntegrationTest.RegisterAndActivateSymmetricKey` +- `KmipClientIntegrationTest.RegisterAndActivateSecret` + 4. Run with AddressSanitizer (requires the ASAN build above): ```bash diff --git a/kmipclient/examples/example_query_server_info.cpp b/kmipclient/examples/example_query_server_info.cpp index 40cf3cc..887098e 100644 --- a/kmipclient/examples/example_query_server_info.cpp +++ b/kmipclient/examples/example_query_server_info.cpp @@ -107,7 +107,7 @@ int main(int argc, char **argv) { if (col_count > 0) { std::cout << std::setw(col_width) << " "; } - const auto *name = kmipcore::operation_to_string(op); + const auto *name = kmipcore::operation_to_string(static_cast(op)); std::cout << std::left << std::setw(col_width) << name; col_count++; if (col_count >= 2) { @@ -130,7 +130,7 @@ int main(int argc, char **argv) { if (col_count > 0) { std::cout << std::setw(col_width) << " "; } - const auto *name = kmipcore::object_type_to_string(type); + const auto *name = kmipcore::object_type_to_string(static_cast(type)); std::cout << std::left << std::setw(col_width) << name; col_count++; if (col_count >= 2) { diff --git a/kmipclient/include/kmipclient/KmipClient.hpp b/kmipclient/include/kmipclient/KmipClient.hpp index 4ba68e2..b35f3ee 100644 --- a/kmipclient/include/kmipclient/KmipClient.hpp +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -276,8 +276,8 @@ namespace kmipclient { * @throws kmipcore::KmipException on protocol or server-side failure. */ struct QueryServerInfo { - std::vector supported_operations; ///< Operations supported by server - std::vector supported_object_types; ///< Object types supported by server + std::vector supported_operations; ///< Operations supported by server + std::vector supported_object_types; ///< Object types supported by server std::string server_name; ///< Human-readable server name std::string vendor_name; ///< Vendor identification string std::string product_name; ///< Product name diff --git a/kmipclient/src/KmipClient.cpp b/kmipclient/src/KmipClient.cpp index 09ebd9c..2e78b40 100644 --- a/kmipclient/src/KmipClient.cpp +++ b/kmipclient/src/KmipClient.cpp @@ -64,6 +64,33 @@ static std::vector default_get_secret_attrs(bool all_attributes) { }; } +static std::vector make_get_attributes_request_names( + const kmipcore::ProtocolVersion &version, + const std::vector &requested_names +) { + // pyKMIP advertises KMIP 2.0 but rejects Get Attributes requests that carry + // explicit selectors. Requesting all attributes remains interoperable and + // callers can still read the specific fields they need from the parsed + // response. + if (version.is_at_least(2, 0)) { + return {}; + } + return requested_names; +} + +static bool should_fallback_to_sequential_activate( + const kmipcore::KmipException &e +) { + switch (e.code().value()) { + case kmipcore::KMIP_REASON_INVALID_MESSAGE: + case kmipcore::KMIP_REASON_INVALID_FIELD: + case kmipcore::KMIP_REASON_FEATURE_NOT_SUPPORTED: + return true; + default: + return false; + } +} + KmipClient::KmipClient( NetClient &net_client, const std::shared_ptr &logger, @@ -96,7 +123,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id @@ -109,34 +136,46 @@ static std::vector default_get_secret_attrs(bool all_attributes) { const std::string &group, const Key &k ) const { - auto request = make_request_message(); - request.getHeader().setBatchOrderOption(true); - const auto register_item_id = request.add_batch_item( - kmipcore::RegisterKeyRequest( - name, - group, - k.to_core_key(), - request.getHeader().getProtocolVersion() - ) - ); - const auto activate_item_id = request.add_batch_item( - make_activate_using_id_placeholder_request() - ); + try { + auto request = make_request_message(); + request.getHeader().setBatchOrderOption(true); + const auto register_item_id = request.add_batch_item( + kmipcore::RegisterKeyRequest( + name, + group, + k.to_core_key(), + request.getHeader().getProtocolVersion() + ) + ); + const auto activate_item_id = request.add_batch_item( + make_activate_using_id_placeholder_request() + ); - std::vector response_bytes; - io->do_exchange( - request.serialize(), response_bytes, request.getMaxResponseSize() - ); + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); - kmipcore::ResponseParser rf(response_bytes); - const auto id = rf - .getResponseByBatchItemId( - register_item_id - ) - .getUniqueIdentifier(); - (void) rf.getResponseByBatchItemId( - activate_item_id - ); + kmipcore::ResponseParser rf(response_bytes, request); + const auto id = rf + .getResponseByBatchItemId( + register_item_id + ) + .getUniqueIdentifier(); + (void) rf.getResponseByBatchItemId( + activate_item_id + ); + return id; + } catch (const kmipcore::KmipException &e) { + if (!should_fallback_to_sequential_activate(e)) { + throw; + } + } + + // pyKMIP advertises KMIP 2.0 but rejects the ID-placeholder batch form. + // Fall back to the interoperable sequential flow. + const auto id = op_register_key(name, group, k); + (void) op_activate(id); return id; } @@ -161,7 +200,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id @@ -174,35 +213,45 @@ static std::vector default_get_secret_attrs(bool all_attributes) { const std::string &group, const Secret &secret ) const { - auto request = make_request_message(); - request.getHeader().setBatchOrderOption(true); - const auto register_item_id = request.add_batch_item( - kmipcore::RegisterSecretRequest( - name, - group, - secret.value(), - secret.get_secret_type(), - request.getHeader().getProtocolVersion() - ) - ); - const auto activate_item_id = request.add_batch_item( - make_activate_using_id_placeholder_request() - ); + try { + auto request = make_request_message(); + request.getHeader().setBatchOrderOption(true); + const auto register_item_id = request.add_batch_item( + kmipcore::RegisterSecretRequest( + name, + group, + secret.value(), + secret.get_secret_type(), + request.getHeader().getProtocolVersion() + ) + ); + const auto activate_item_id = request.add_batch_item( + make_activate_using_id_placeholder_request() + ); - std::vector response_bytes; - io->do_exchange( - request.serialize(), response_bytes, request.getMaxResponseSize() - ); + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); - kmipcore::ResponseParser rf(response_bytes); - const auto id = rf - .getResponseByBatchItemId( - register_item_id - ) - .getUniqueIdentifier(); - (void) rf.getResponseByBatchItemId( - activate_item_id - ); + kmipcore::ResponseParser rf(response_bytes, request); + const auto id = rf + .getResponseByBatchItemId( + register_item_id + ) + .getUniqueIdentifier(); + (void) rf.getResponseByBatchItemId( + activate_item_id + ); + return id; + } catch (const kmipcore::KmipException &e) { + if (!should_fallback_to_sequential_activate(e)) { + throw; + } + } + + const auto id = op_register_secret(name, group, secret); + (void) op_activate(id); return id; } @@ -228,7 +277,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id @@ -244,7 +293,14 @@ static std::vector default_get_secret_attrs(bool all_attributes) { const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, default_get_key_attrs(all_attributes)) + kmipcore::GetAttributesRequest( + id, + make_get_attributes_request_names( + request.getHeader().getProtocolVersion(), + default_get_key_attrs(all_attributes) + ), + request.getHeader().getProtocolVersion() + ) ); std::vector response_bytes; @@ -252,7 +308,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); auto get_response = rf.getResponseByBatchItemId( get_item_id @@ -284,7 +340,14 @@ static std::vector default_get_secret_attrs(bool all_attributes) { const auto get_item_id = request.add_batch_item(kmipcore::GetRequest(id)); const auto attributes_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, default_get_secret_attrs(all_attributes)) + kmipcore::GetAttributesRequest( + id, + make_get_attributes_request_names( + request.getHeader().getProtocolVersion(), + default_get_secret_attrs(all_attributes) + ), + request.getHeader().getProtocolVersion() + ) ); std::vector response_bytes; @@ -292,7 +355,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); auto get_response = rf.getResponseByBatchItemId( get_item_id @@ -337,7 +400,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id @@ -355,7 +418,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); auto response = rf.getResponseByBatchItemId< kmipcore::GetAttributeListResponseBatchItem>(batch_item_id); return std::vector{ @@ -368,14 +431,21 @@ static std::vector default_get_secret_attrs(bool all_attributes) { ) const { auto request = make_request_message(); const auto batch_item_id = request.add_batch_item( - kmipcore::GetAttributesRequest(id, attr_names)); + kmipcore::GetAttributesRequest( + id, + make_get_attributes_request_names( + request.getHeader().getProtocolVersion(), + attr_names + ), + request.getHeader().getProtocolVersion() + )); std::vector response_bytes; io->do_exchange( request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); auto response = rf.getResponseByBatchItemId( batch_item_id @@ -621,7 +691,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id @@ -639,7 +709,7 @@ static std::vector default_get_secret_attrs(bool all_attributes) { request.serialize(), response_bytes, request.getMaxResponseSize() ); - kmipcore::ResponseParser rf(response_bytes); + kmipcore::ResponseParser rf(response_bytes, request); return rf .getResponseByBatchItemId( batch_item_id diff --git a/kmipclient/tests/KmipClientIntegrationTest.cpp b/kmipclient/tests/KmipClientIntegrationTest.cpp index 53f7a96..43ea8c6 100644 --- a/kmipclient/tests/KmipClientIntegrationTest.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest.cpp @@ -17,6 +17,7 @@ #include "kmipclient/Kmip.hpp" #include "kmipclient/KmipClient.hpp" +#include "TestEnvUtils.hpp" #include "kmipcore/kmip_basics.hpp" #include @@ -67,7 +68,6 @@ class KmipTestConfig { const char *client_key = std::getenv("KMIP_CLIENT_KEY"); const char *server_ca = std::getenv("KMIP_SERVER_CA"); const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); - const char *run_2_0 = std::getenv("KMIP_RUN_2_0_TESTS"); if (addr) { kmip_addr = addr; @@ -95,7 +95,7 @@ class KmipTestConfig { } } - run_2_0_tests = (run_2_0 != nullptr && std::string(run_2_0) == "1"); + run_2_0_tests = kmipclient::test::is_env_flag_enabled("KMIP_RUN_2_0_TESTS"); if (!isConfigured()) { std::cerr << "WARNING: KMIP environment variables not set. Tests will be " @@ -398,7 +398,7 @@ TEST_F(KmipClientIntegrationTest, RegisterSymmetricKey) { } // Test: Register and Activate symmetric key in one request -TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSymmetricKey) { +TEST_F(KmipClientIntegrationTest, RegisterAndActivateSymmetricKey) { auto kmip = createKmipClient(); std::vector key_value = { @@ -425,15 +425,15 @@ TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSymmetricKey) { auto key = kmip->client().op_get_key(key_id); ASSERT_FALSE(key->value().empty()); auto attrs = kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_STATE}); - auto state_str = attrs.get_as_string(KMIP_ATTR_NAME_STATE); - EXPECT_TRUE(state_str && *state_str == "KMIP_STATE_ACTIVE"); + EXPECT_EQ(attrs.object_state(), state::KMIP_STATE_ACTIVE) + << "Key should be ACTIVE immediately after RegisterAndActivate"; } catch (kmipcore::KmipException &e) { FAIL() << "Get after RegisterAndActivateSymmetricKey failed: " << e.what(); } } // Test: Register and Activate secret in one request -TEST_F(KmipClientIntegrationTest, DISABLED_RegisterAndActivateSecret) { +TEST_F(KmipClientIntegrationTest, RegisterAndActivateSecret) { auto kmip = createKmipClient(); const std::vector secret_data = {'s', 'e', 'c', 'r', 'e', 't'}; @@ -870,7 +870,7 @@ TEST_F(KmipClientIntegrationTest, RegisterKeyAndGetAttributes) { key_id, {KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_CRYPTO_USAGE_MASK} ); - auto attr_name = attrs.get(KMIP_ATTR_NAME_NAME); + const auto &attr_name = attrs.get(KMIP_ATTR_NAME_NAME); EXPECT_EQ(attr_name, name); EXPECT_EQ(attrs.usage_mask(), expected_mask); std::cout << "Successfully verified registered key attributes match" diff --git a/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp b/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp index 6e6f823..84a954c 100644 --- a/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp +++ b/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp @@ -36,6 +36,7 @@ #include "kmipclient/Kmip.hpp" #include "kmipclient/KmipClient.hpp" +#include "TestEnvUtils.hpp" #include "kmipcore/kmip_basics.hpp" #include "kmipcore/kmip_protocol.hpp" @@ -89,14 +90,13 @@ class KmipTestConfig20 { const char *client_key = std::getenv("KMIP_CLIENT_KEY"); const char *server_ca = std::getenv("KMIP_SERVER_CA"); const char *timeout = std::getenv("KMIP_TIMEOUT_MS"); - const char *run_2_0 = std::getenv("KMIP_RUN_2_0_TESTS"); if (addr) kmip_addr = addr; if (port) kmip_port = port; if (client_ca) kmip_client_ca = client_ca; if (client_key) kmip_client_key = client_key; if (server_ca) kmip_server_ca = server_ca; - run_2_0_tests = run_2_0 != nullptr; + run_2_0_tests = kmipclient::test::is_env_flag_enabled("KMIP_RUN_2_0_TESTS"); timeout_ms = 5000; if (timeout) { @@ -304,11 +304,11 @@ class KmipClientIntegrationTest20 : public ::testing::Test { if (test_info != nullptr) { const auto *result = test_info->result(); if (result != nullptr && result->Skipped()) { - skipped_tests_.push_back(test_info->name()); + skipped_tests_.emplace_back(test_info->name()); } else if (HasFailure()) { - failed_tests_.push_back(test_info->name()); + failed_tests_.emplace_back(test_info->name()); } else { - passed_tests_.push_back(test_info->name()); + passed_tests_.emplace_back(test_info->name()); } } diff --git a/kmipclient/tests/TestEnvUtils.hpp b/kmipclient/tests/TestEnvUtils.hpp index e69de29..70ab054 100644 --- a/kmipclient/tests/TestEnvUtils.hpp +++ b/kmipclient/tests/TestEnvUtils.hpp @@ -0,0 +1,17 @@ +#ifndef KMIPCLIENT_TESTS_TEST_ENV_UTILS_HPP +#define KMIPCLIENT_TESTS_TEST_ENV_UTILS_HPP + +#include +#include + +namespace kmipclient::test { + +inline bool is_env_flag_enabled(const char *name) { + const char *value = std::getenv(name); + return value != nullptr && std::string_view(value) == "1"; +} + +} // namespace kmipclient::test + +#endif // KMIPCLIENT_TESTS_TEST_ENV_UTILS_HPP + diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp index 7524636..7bef684 100644 --- a/kmipcore/include/kmipcore/kmip_enums.hpp +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -767,6 +767,7 @@ enum class tag : std::uint32_t { KMIP_TAG_APPLICATION_DATA = 0x420002, KMIP_TAG_APPLICATION_NAMESPACE = 0x420003, KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION = 0x420004, + KMIP_TAG_ATTRIBUTE_REFERENCE = 0x420005, KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE = 0x420006, KMIP_TAG_ASYNCHRONOUS_INDICATOR = 0x420007, KMIP_TAG_ATTRIBUTE = 0x420008, @@ -1362,6 +1363,7 @@ inline constexpr std::uint32_t KMIP_TAG_ACTIVATION_DATE = static_cast(tag::KMIP_TAG_APPLICATION_DATA); inline constexpr std::uint32_t KMIP_TAG_APPLICATION_NAMESPACE = static_cast(tag::KMIP_TAG_APPLICATION_NAMESPACE); inline constexpr std::uint32_t KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION = static_cast(tag::KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION); +inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE_REFERENCE = static_cast(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); inline constexpr std::uint32_t KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE = static_cast(tag::KMIP_TAG_ASYNCHRONOUS_CORRELATION_VALUE); inline constexpr std::uint32_t KMIP_TAG_ASYNCHRONOUS_INDICATOR = static_cast(tag::KMIP_TAG_ASYNCHRONOUS_INDICATOR); inline constexpr std::uint32_t KMIP_TAG_ATTRIBUTE = static_cast(tag::KMIP_TAG_ATTRIBUTE); diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp index 086476c..2ba362c 100644 --- a/kmipcore/include/kmipcore/kmip_requests.hpp +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -63,25 +63,9 @@ namespace kmipcore { */ GetAttributesRequest( const std::string &unique_id, - const std::vector &attribute_names - ) { - setOperation(KMIP_OP_GET_ATTRIBUTES); - auto payload = - Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); - payload->asStructure()->add( - Element::createTextString( - tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id - ) - ); - for (const auto &attr_name : attribute_names) { - payload->asStructure()->add( - Element::createTextString( - tag::KMIP_TAG_ATTRIBUTE_NAME, attr_name - ) - ); - } - setRequestPayload(payload); - } + const std::vector &attribute_names, + ProtocolVersion version = {} + ); }; // Constructors for the following classes are defined in kmip_requests.cpp diff --git a/kmipcore/include/kmipcore/response_parser.hpp b/kmipcore/include/kmipcore/response_parser.hpp index 3155bc2..eae06a2 100644 --- a/kmipcore/include/kmipcore/response_parser.hpp +++ b/kmipcore/include/kmipcore/response_parser.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace kmipcore { @@ -30,6 +31,22 @@ namespace kmipcore { * @param responseBytes Raw TTLV response payload. */ explicit ResponseParser(std::span responseBytes); + + /** + * @brief Creates a parser that also holds operation hints from the request. + * + * Some KMIP servers (e.g. pyKMIP) omit the Operation field from response + * Batch Items, particularly in failure responses. Supplying the original + * @p request lets the parser map each response item back to the operation + * that was requested, so that error messages remain informative. + * + * @param responseBytes Raw TTLV response payload. + * @param request The request whose response is being parsed. + */ + ResponseParser( + std::span responseBytes, + const RequestMessage &request + ); /** @brief Default destructor. */ ~ResponseParser() = default; ResponseParser(const ResponseParser &) = delete; @@ -80,7 +97,7 @@ namespace kmipcore { [[nodiscard]] TypedResponseBatchItem getResponseByBatchItemId(uint32_t batchItemId) { const auto &item = getResponseItemByBatchItemId(batchItemId); - ensureSuccess(item); + ensureSuccess(item, batchItemId); return TypedResponseBatchItem::fromBatchItem(item); } @@ -97,15 +114,32 @@ namespace kmipcore { [[nodiscard]] const ResponseBatchItem & getResponseItemByBatchItemId(uint32_t batchItemId); - static void ensureSuccess(const ResponseBatchItem &item); + /** Returns the operation code to report for @p item. + * When the server omits the Operation field, falls back to the hint + * stored for the requested batch item id (populated from the request). */ + [[nodiscard]] int32_t effectiveOperation( + const ResponseBatchItem &item, + std::optional requestedBatchItemId = std::nullopt + ) const; + + void ensureSuccess( + const ResponseBatchItem &item, + std::optional requestedBatchItemId = std::nullopt + ); - static std::string formatOperationResult(const ResponseBatchItem &value); + static std::string formatOperationResult(const ResponseBatchItem &value, int32_t operation); static const char *operationToString(int32_t operation); static const char *resultStatusToString(int32_t status); std::vector responseBytes_; ResponseMessage responseMessage_{}; bool isParsed_ = false; + /** Maps uniqueBatchItemId → operation code extracted from the request. */ + std::unordered_map operationHints_; + /** Maps uniqueBatchItemId → 0-based position in the request batch. + * Used as a positional fallback when the server does not echo batch item + * IDs in its responses (e.g. pyKMIP multi-batch responses). */ + std::unordered_map batchItemPositions_; }; } // namespace kmipcore diff --git a/kmipcore/src/kmip_protocol.cpp b/kmipcore/src/kmip_protocol.cpp index 7be65b9..4d2f8fc 100644 --- a/kmipcore/src/kmip_protocol.cpp +++ b/kmipcore/src/kmip_protocol.cpp @@ -441,9 +441,13 @@ namespace kmipcore { auto op = element->getChild(tag::KMIP_TAG_OPERATION); if (op) { rbi.operation_ = op->toEnum(); - } else { - throw KmipException("Missing Operation"); } + // Operation is optional in responses per KMIP spec §9.1.3: it is only + // REQUIRED when the request contained more than one Batch Item, or when + // the result is not Success. Several real-world servers (e.g. pyKMIP) + // omit it even in those cases. Callers that need the effective operation + // should consult ResponseParser::effectiveOperation() which falls back to + // the hint derived from the corresponding request batch item. auto id = element->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); if (id) { diff --git a/kmipcore/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp index b1e016d..11a3b68 100644 --- a/kmipcore/src/kmip_requests.cpp +++ b/kmipcore/src/kmip_requests.cpp @@ -11,6 +11,71 @@ namespace kmipcore { return version.is_at_least(2, 0); } + [[nodiscard]] std::optional + standard_attribute_name_to_tag(std::string_view name) { + if (name == KMIP_ATTR_NAME_NAME) { + return tag::KMIP_TAG_NAME; + } + if (name == KMIP_ATTR_NAME_GROUP) { + return tag::KMIP_TAG_OBJECT_GROUP; + } + if (name == KMIP_ATTR_NAME_STATE) { + return tag::KMIP_TAG_STATE; + } + if (name == KMIP_ATTR_NAME_UNIQUE_IDENTIFIER || + name == KMIP_ATTR_NAME_UNIQUE_IDENTIFIER_ALT) { + return tag::KMIP_TAG_UNIQUE_IDENTIFIER; + } + if (name == KMIP_ATTR_NAME_ACTIVATION_DATE) { + return tag::KMIP_TAG_ACTIVATION_DATE; + } + if (name == KMIP_ATTR_NAME_DEACTIVATION_DATE) { + return tag::KMIP_TAG_DEACTIVATION_DATE; + } + if (name == KMIP_ATTR_NAME_PROCESS_START_DATE) { + return tag::KMIP_TAG_PROCESS_START_DATE; + } + if (name == KMIP_ATTR_NAME_PROTECT_STOP_DATE) { + return tag::KMIP_TAG_PROTECT_STOP_DATE; + } + if (name == KMIP_ATTR_NAME_CRYPTO_ALG) { + return tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM; + } + if (name == KMIP_ATTR_NAME_CRYPTO_LEN) { + return tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH; + } + if (name == KMIP_ATTR_NAME_CRYPTO_USAGE_MASK) { + return tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK; + } + if (name == KMIP_ATTR_NAME_OPERATION_POLICY_NAME) { + return tag::KMIP_TAG_OPERATION_POLICY_NAME; + } + return std::nullopt; + } + + std::shared_ptr + make_v2_attribute_reference(std::string_view attribute_name) { + auto ref = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); + if (const auto tag_value = standard_attribute_name_to_tag(attribute_name); + tag_value.has_value()) { + ref->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_ATTRIBUTE_REFERENCE, + static_cast(*tag_value) + ) + ); + } else { + // Preserve interoperability with vendor-defined attributes by name. + ref->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, + std::string(attribute_name) + ) + ); + } + return ref; + } + std::shared_ptr make_text_attribute( const std::string &attribute_name, const std::string &value ) { @@ -395,6 +460,37 @@ namespace kmipcore { } // namespace detail + GetAttributesRequest::GetAttributesRequest( + const std::string &unique_id, + const std::vector &attribute_names, + ProtocolVersion /*version*/ + ) { + setOperation(KMIP_OP_GET_ATTRIBUTES); + auto payload = + Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id + ) + ); + + // Use Attribute Name text-strings for both KMIP 1.4 and 2.0. While the + // KMIP 2.0 spec defines Attribute Reference structures for this purpose, + // many real-world 2.0 servers (including pyKMIP) still accept the + // 1.4-style Attribute Name format and reject or mishandle Attribute + // Reference structures. Using Attribute Name keeps maximum + // interoperability without sacrificing functionality. + for (const auto &attr_name : attribute_names) { + payload->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, attr_name + ) + ); + } + + setRequestPayload(payload); + } + // --------------------------------------------------------------------------- // CreateSymmetricKeyRequest // --------------------------------------------------------------------------- diff --git a/kmipcore/src/kmip_responses.cpp b/kmipcore/src/kmip_responses.cpp index 0b0961c..6e91e46 100644 --- a/kmipcore/src/kmip_responses.cpp +++ b/kmipcore/src/kmip_responses.cpp @@ -1,5 +1,7 @@ #include "kmipcore/kmip_responses.hpp" +#include "kmipcore/kmip_attribute_names.hpp" + namespace kmipcore { namespace { @@ -21,6 +23,76 @@ namespace kmipcore { } } + [[nodiscard]] std::optional + attribute_name_from_tag_code(int32_t tag_code) { + switch (static_cast(tag_code)) { + case tag::KMIP_TAG_NAME: + return std::string(KMIP_ATTR_NAME_NAME); + case tag::KMIP_TAG_OBJECT_GROUP: + return std::string(KMIP_ATTR_NAME_GROUP); + case tag::KMIP_TAG_STATE: + return std::string(KMIP_ATTR_NAME_STATE); + case tag::KMIP_TAG_UNIQUE_IDENTIFIER: + return std::string(KMIP_ATTR_NAME_UNIQUE_IDENTIFIER); + case tag::KMIP_TAG_ACTIVATION_DATE: + return std::string(KMIP_ATTR_NAME_ACTIVATION_DATE); + case tag::KMIP_TAG_DEACTIVATION_DATE: + return std::string(KMIP_ATTR_NAME_DEACTIVATION_DATE); + case tag::KMIP_TAG_PROCESS_START_DATE: + return std::string(KMIP_ATTR_NAME_PROCESS_START_DATE); + case tag::KMIP_TAG_PROTECT_STOP_DATE: + return std::string(KMIP_ATTR_NAME_PROTECT_STOP_DATE); + case tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + return std::string(KMIP_ATTR_NAME_CRYPTO_ALG); + case tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH: + return std::string(KMIP_ATTR_NAME_CRYPTO_LEN); + case tag::KMIP_TAG_CRYPTOGRAPHIC_USAGE_MASK: + return std::string(KMIP_ATTR_NAME_CRYPTO_USAGE_MASK); + case tag::KMIP_TAG_OPERATION_POLICY_NAME: + return std::string(KMIP_ATTR_NAME_OPERATION_POLICY_NAME); + default: + return std::nullopt; + } + } + + void collect_attribute_list_entries_from_reference( + const std::shared_ptr &attribute_reference, + std::vector &out_names + ) { + if (!attribute_reference) { + return; + } + + if (attribute_reference->type == Type::KMIP_TYPE_ENUMERATION) { + if (const auto mapped = + attribute_name_from_tag_code(attribute_reference->toEnum()); + mapped.has_value()) { + out_names.push_back(*mapped); + } + return; + } + + if (attribute_reference->type != Type::KMIP_TYPE_STRUCTURE) { + return; + } + + if (const auto attr_name = + attribute_reference->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); + attr_name) { + out_names.push_back(attr_name->toString()); + return; + } + + if (const auto attr_tag = + attribute_reference->getChild(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); + attr_tag) { + if (const auto mapped = attribute_name_from_tag_code(attr_tag->toEnum()); + mapped.has_value()) { + out_names.push_back(*mapped); + } + } + } + } // namespace // --- GetResponseBatchItem --- @@ -114,6 +186,16 @@ namespace kmipcore { result.attributeNames_.push_back(attributeName->toString()); } + if (result.attributeNames_.empty()) { + // KMIP 2.0: Get Attribute List returns Attribute Reference values. + for (const auto &attributeReference : + payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_REFERENCE)) { + collect_attribute_list_entries_from_reference( + attributeReference, result.attributeNames_ + ); + } + } + return result; } diff --git a/kmipcore/src/response_parser.cpp b/kmipcore/src/response_parser.cpp index fa6ab81..df9f90d 100644 --- a/kmipcore/src/response_parser.cpp +++ b/kmipcore/src/response_parser.cpp @@ -15,6 +15,45 @@ namespace kmipcore { ResponseParser::ResponseParser(std::span responseBytes) : responseBytes_(responseBytes.begin(), responseBytes.end()) {} + ResponseParser::ResponseParser( + std::span responseBytes, + const RequestMessage &request + ) + : responseBytes_(responseBytes.begin(), responseBytes.end()) { + size_t pos = 0; + for (const auto &item : request.getBatchItems()) { + const uint32_t id = item.getUniqueBatchItemId(); + operationHints_[id] = item.getOperation(); + batchItemPositions_[id] = pos++; + } + } + + int32_t ResponseParser::effectiveOperation( + const ResponseBatchItem &item, + std::optional requestedBatchItemId + ) const { + const int32_t op = item.getOperation(); + if (op != 0) { + return op; + } + // Response didn't include Operation – first try the echoed batch item id. + const auto it = operationHints_.find(item.getUniqueBatchItemId()); + if (it != operationHints_.end()) { + return it->second; + } + + // Some servers omit both Operation and Unique Batch Item Id. In that case + // fall back to the batch item id requested by the caller. + if (requestedBatchItemId.has_value()) { + const auto requested_it = operationHints_.find(*requestedBatchItemId); + if (requested_it != operationHints_.end()) { + return requested_it->second; + } + } + + return 0; + } + size_t ResponseParser::getBatchItemCount() { ensureParsed(); return responseMessage_.getBatchItems().size(); @@ -27,7 +66,7 @@ namespace kmipcore { OperationResult ResponseParser::getOperationResult(int itemIdx) { const auto &item = getResponseItem(itemIdx); return OperationResult{ - item.getOperation(), + effectiveOperation(item), item.getResultStatus(), get_result_reason_or_default(item), item.getResultMessage().value_or("") @@ -38,7 +77,7 @@ namespace kmipcore { ResponseParser::getOperationResultByBatchItemId(uint32_t batchItemId) { const auto &item = getResponseItemByBatchItemId(batchItemId); return OperationResult{ - item.getOperation(), + effectiveOperation(item, batchItemId), item.getResultStatus(), get_result_reason_or_default(item), item.getResultMessage().value_or("") @@ -82,26 +121,47 @@ namespace kmipcore { ensureParsed(); const auto &items = responseMessage_.getBatchItems(); + + // Primary: match by the echoed Unique Batch Item Id. for (const auto &item : items) { if (item.getUniqueBatchItemId() == batchItemId) { return item; } } + // Fallback: some servers (e.g. pyKMIP in multi-batch mode) do not echo + // batch item IDs back. Use the 0-based position in the request batch + // (stored at construction time) to locate the correct response item. + if (!batchItemPositions_.empty()) { + const auto it = batchItemPositions_.find(batchItemId); + if (it != batchItemPositions_.end()) { + const size_t pos = it->second; + if (pos < items.size()) { + return items[pos]; + } + } + } + throw KmipException("Response batch item id was not found."); } - void ResponseParser::ensureSuccess(const ResponseBatchItem &item) { + void ResponseParser::ensureSuccess( + const ResponseBatchItem &item, + std::optional requestedBatchItemId + ) { if (item.getResultStatus() != KMIP_STATUS_SUCCESS) { const KmipResultReasonCode reason = get_result_reason_or_default(item); - throw KmipException(reason, formatOperationResult(item)); + throw KmipException( + reason, + formatOperationResult(item, effectiveOperation(item, requestedBatchItemId)) + ); } } std::string - ResponseParser::formatOperationResult(const ResponseBatchItem &value) { + ResponseParser::formatOperationResult(const ResponseBatchItem &value, int32_t operation) { OperationResult result = { - value.getOperation(), + operation, value.getResultStatus(), get_result_reason_or_default(value), value.getResultMessage().value_or("") @@ -118,32 +178,7 @@ namespace kmipcore { } const char *ResponseParser::operationToString(int32_t operation) { - switch (operation) { - case KMIP_OP_CREATE: - return "Create"; - case KMIP_OP_REGISTER: - return "Register"; - case KMIP_OP_GET: - return "Get"; - case KMIP_OP_GET_ATTRIBUTES: - return "Get Attributes"; - case KMIP_OP_ACTIVATE: - return "Activate"; - case KMIP_OP_DESTROY: - return "Destroy"; - case KMIP_OP_LOCATE: - return "Locate"; - case KMIP_OP_REVOKE: - return "Revoke"; - case KMIP_OP_GET_ATTRIBUTE_LIST: - return "Get Attribute List"; - case KMIP_OP_DISCOVER_VERSIONS: - return "Discover Versions"; - case KMIP_OP_QUERY: - return "Query"; - default: - return "Unknown"; - } + return kmipcore::operation_to_string(operation); } const char *ResponseParser::resultStatusToString(int32_t status) { diff --git a/kmipcore/tests/test_core.cpp b/kmipcore/tests/test_core.cpp index 26c9ba5..9890a59 100644 --- a/kmipcore/tests/test_core.cpp +++ b/kmipcore/tests/test_core.cpp @@ -634,7 +634,10 @@ void test_response_required_fields() { } { - // Missing Operation inside ResponseBatchItem must be rejected. + // Missing Operation inside ResponseBatchItem is now accepted: the field is + // optional for responses per the KMIP spec and several real-world servers + // (e.g. pyKMIP) omit it. The operation defaults to 0; callers should use + // the ResponseParser operation-hint mechanism to recover the expected value. auto response_message = Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); @@ -654,13 +657,11 @@ void test_response_required_fields() { ); response_message->asStructure()->add(batch_item); - bool threw = false; - try { - (void) ResponseMessage::fromElement(response_message); - } catch (const KmipException &) { - threw = true; - } - assert(threw); + // Parse must succeed without throwing. + auto parsed = ResponseMessage::fromElement(response_message); + assert(parsed.getBatchItems().size() == 1); + // Operation field is absent → defaults to 0. + assert(parsed.getBatchItems()[0].getOperation() == 0); } std::cout << "Response required-fields validation test passed" << std::endl; diff --git a/kmipcore/tests/test_parsers.cpp b/kmipcore/tests/test_parsers.cpp index 186dacd..1b324af 100644 --- a/kmipcore/tests/test_parsers.cpp +++ b/kmipcore/tests/test_parsers.cpp @@ -1,4 +1,5 @@ #include "kmipcore/attributes_parser.hpp" +#include "kmipcore/kmip_attribute_names.hpp" #include "kmipcore/kmip_formatter.hpp" #include "kmipcore/key_parser.hpp" #include "kmipcore/kmip_basics.hpp" @@ -104,6 +105,83 @@ void test_response_parser_failure_preserves_reason_code() { << std::endl; } +// Helper: build a response where the Operation tag is intentionally omitted +// (simulates the pyKMIP behaviour of omitting Operation in failure responses). +std::vector create_mock_response_bytes_no_operation( + int32_t result_status, + std::optional result_reason, + const std::optional &result_message, + uint32_t unique_batch_item_id = 1 +) { + ResponseMessage resp; + resp.getHeader().getProtocolVersion().setMajor(2); + resp.getHeader().getProtocolVersion().setMinor(0); + resp.getHeader().setTimeStamp(1234567890); + resp.getHeader().setBatchCount(1); + + ResponseBatchItem item; + item.setUniqueBatchItemId(unique_batch_item_id); + // Intentionally do NOT call item.setOperation() – mirrors pyKMIP behaviour. + item.setResultStatus(result_status); + item.setResultReason(result_reason); + item.setResultMessage(result_message); + + resp.add_batch_item(item); + SerializationBuffer buf; + resp.toElement()->serialize(buf); + return buf.release(); +} + +void test_response_parser_operation_hint_when_operation_absent() { + // Build a failure response with no Operation tag (as pyKMIP sends). + auto bytes = create_mock_response_bytes_no_operation( + KMIP_STATUS_OPERATION_FAILED, + KMIP_REASON_ITEM_NOT_FOUND, + std::string("Not found") + ); + + // ── Case 1: parser without hints → should not crash during parse, but + // the operation in the thrown error is 0 ("Unknown Operation"). + { + ResponseParser parser(bytes); + bool threw = false; + try { + [[maybe_unused]] auto resp = parser.getResponse(0); + } catch (const KmipException &e) { + threw = true; + assert(e.code().value() == KMIP_REASON_ITEM_NOT_FOUND); + // Operation string should be the "unknown" fallback. + const std::string msg = e.what(); + assert(msg.find("Operation: Unknown Operation") != std::string::npos); + } + assert(threw); + } + + // ── Case 2: parser WITH hints → error message should show the correct op. + { + // Craft a minimal request that matches the batch item id (1). + RequestMessage request; + request.add_batch_item(GetRequest("fake-id-abc")); + + ResponseParser parser(bytes, request); + bool threw = false; + try { + [[maybe_unused]] auto resp = + parser.getResponse(0); + } catch (const KmipException &e) { + threw = true; + assert(e.code().value() == KMIP_REASON_ITEM_NOT_FOUND); + const std::string msg = e.what(); + // With the hint the error should now say "Operation: Get". + assert(msg.find("Operation: Get") != std::string::npos); + assert(msg.find("Result reason:") != std::string::npos); + } + assert(threw); + } + + std::cout << "ResponseParser operation-hint fallback test passed" << std::endl; +} + void test_response_parser_create() { auto payload = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); @@ -563,6 +641,71 @@ void test_attributes_parser_v2_typed() { std::cout << "AttributesParser KMIP 2.0 typed attributes test passed" << std::endl; } +void test_get_attributes_request_encodes_per_protocol_version() { + const std::vector attrs = { + std::string(KMIP_ATTR_NAME_STATE), + std::string(KMIP_ATTR_NAME_CRYPTO_ALG), + "Vendor Custom Attr" + }; + + // GetAttributesRequest always uses Attribute Name text-strings regardless of + // protocol version. KMIP 2.0 defines Attribute Reference for this purpose, + // but many real-world 2.0 servers (e.g. pyKMIP) still accept the 1.4-style + // Attribute Name format and reject Attribute Reference structures. + for (const auto &ver : {ProtocolVersion(1, 4), ProtocolVersion(2, 0)}) { + GetAttributesRequest req("id-1", attrs, ver); + auto payload = req.getRequestPayload(); + assert(payload != nullptr); + // Every attribute name must appear as an Attribute Name child. + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_NAME).size() == attrs.size()); + // No Attribute Reference structures should be emitted. + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_REFERENCE).empty()); + } + + // Empty attribute list (means "return all") should produce no Attribute Name + // children and also no Attribute Reference children in either version. + for (const auto &ver : {ProtocolVersion(1, 4), ProtocolVersion(2, 0)}) { + GetAttributesRequest req_empty("id-1", {}, ver); + auto payload = req_empty.getRequestPayload(); + assert(payload != nullptr); + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_NAME).empty()); + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_REFERENCE).empty()); + } + + std::cout << "GetAttributesRequest always-Attribute-Name encoding test passed" << std::endl; +} + +void test_get_attribute_list_response_supports_v2_attribute_reference() { + auto payload = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-1") + ); + + auto state_ref = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); + state_ref->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_ATTRIBUTE_REFERENCE, KMIP_TAG_STATE) + ); + payload->asStructure()->add(state_ref); + + auto custom_ref = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); + custom_ref->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Vendor Custom Attr") + ); + payload->asStructure()->add(custom_ref); + + auto bytes = create_mock_response_bytes(KMIP_OP_GET_ATTRIBUTE_LIST, payload); + ResponseParser parser(bytes); + auto response = parser.getResponse(0); + + const auto &names = response.getAttributeNames(); + assert(names.size() == 2); + assert(names[0] == KMIP_ATTR_NAME_STATE); + assert(names[1] == "Vendor Custom Attr"); + + std::cout << "GetAttributeListResponse KMIP 2.0 Attribute Reference test passed" + << std::endl; +} + void test_formatter_for_request_and_response() { RequestMessage request; request.add_batch_item(GetRequest("request-id-123")); @@ -629,12 +772,15 @@ int main() { test_response_parser_query(); test_response_parser_query_empty_payload(); test_response_parser_failure_preserves_reason_code(); + test_response_parser_operation_hint_when_operation_absent(); test_key_parser_symmetric(); test_key_parser_secret_binary(); test_register_secret_request_structure(); test_attributes_parser(); test_attributes_parser_extended(); test_attributes_parser_v2_typed(); + test_get_attributes_request_encodes_per_protocol_version(); + test_get_attribute_list_response_supports_v2_attribute_reference(); test_formatter_for_request_and_response(); test_logger_interface(); return 0;