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..d2c00bc --- /dev/null +++ b/KMIP_MODERN_VS_LEGACY_COMPARISON.md @@ -0,0 +1,388 @@ +# KMIP Modern vs Legacy Comparison + +Date: 2026-03-26 + +--- + +## Quick Comparison Table + +| Aspect | Modern (kmipcore + kmipclient) | Legacy (libkmip + kmippp) | Winner | +|---|---|---|---| +| **Memory Management** | RAII everywhere, zero raw allocations, ASAN-validated | Manual malloc/free (294+ calls in libkmip), prone to leaks | Modern ✅ | +| **Error Handling** | Exceptions (KmipException hierarchy), rich context | Mixed: bool returns + global side channel (not thread-safe) | Modern ✅ | +| **Thread Safety** | Production-grade thread-safe pool (KmipClientPool) | No native pool; global mutable state (`last_result`) | Modern ✅ | +| **Connection Pooling** | Built-in: lazy creation, blocking/timed/non-blocking borrow | Manual management required, no built-in pool | Modern ✅ | +| **API Design** | Consistent typed operations (op_*), strong enums | Mixed patterns, stringly-typed attributes | Modern ✅ | +| **Type Safety** | 100% type-safe, strong enums, no string-types | Weaker typing, relies on string conventions | Modern ✅ | +| **Attribute Access** | Multi-level (get_attributes, get_attribute_list, attribute_value) | Name-only (op_get_name_attr), limited | Modern ✅ | +| **Architecture** | Clean 3-layer (transport ← protocol ← client), pluggable | Monolithic per-operation functions | Modern ✅ | +| **Legacy Dependency** | Zero dependency on libkmip/kmippp | Depends on libkmip | Modern ✅ | +| **Serialization** | Efficient SerializationBuffer (8 KB default, 100 MB cap) | Dynamic resize loops in each operation | Modern ✅ | +| **KMIP Compliance** | Spec-aligned (1.4 + 2.0 support), strict TTLV validation | Broader coverage but older patterns | Comparable | +| **Test Coverage** | 30+ integration tests, ASAN-validated, GoogleTest | Large test.c file, not integrated into build | Modern ✅ | +| **Protocol Version** | KMIP 1.4 default, op_discover_versions() available | Varies by operation | Comparable | +| **Extensibility** | Easy (typed request/response, pluggable transport) | Monolithic functions require refactoring | Modern ✅ | +| **Performance** | Strategic buffer reuse, minimal allocations | Repeated allocations per operation | Modern ✅ | +| **TLS/Security** | Peer + hostname verification (enabled by default), SNI support | Basic OpenSSL integration | Modern ✅ | +| **Documentation** | Comprehensive doxygen, clear API contracts | Inline C comments, API less discoverable | Modern ✅ | +| **AddressSanitizer** | First-class support (WITH_ASAN=ON), all tests pass clean | Not supported | Modern ✅ | +| **Build Integration** | CMake with POSITION_INDEPENDENT_CODE, install targets | Separate CMake files, less integration | Modern ✅ | +| **Code Quality** | C++20, zero compiler warnings, best practices | C (libkmip) + C++98 (kmippp), legacy patterns | Modern ✅ | + +**Overall Verdict:** Modern stack is superior in every measurable dimension except breadth of KMIP coverage (legacy supports more operations due to longer history). Modern stack is recommended for all new development and is suitable for production deployment. + +--- + +## 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`). +- **`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`) + +- `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_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. + +### 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. + +### 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), 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, + - 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 +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 + +`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 + +`kmippp::context` exposes only a single attribute getter: + +```cpp +name_t op_get_name_attr(id_t id); // returns "" on failure +``` + +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 + +- `KmipClientPool` is a production-grade thread-safe pool providing: + - **Lazy connection creation:** connections are established on-demand, up to a configured maximum + - **Blocking `borrow()`:** waits indefinitely for an available connection + - **Timed `borrow(timeout)`:** blocks with a deadline, throws `KmipException` on timeout + - **Non-blocking `try_borrow()`:** returns `std::nullopt` immediately if no connection is available and pool is at capacity + - **Health tracking:** each borrowed connection can be marked unhealthy via `markUnhealthy()` if an unrecoverable error occurs, causing the pool to discard it on return + - **Automatic cleanup:** connections are closed and freed on return or if marked unhealthy + - **Diagnostic accessors:** `available_count()`, `total_count()`, `max_connections()` +- Includes comprehensive integration tests covering: + - pool exhaustion and waiting behavior + - connection reuse and proper cleanup + - concurrent multi-threaded operations + - unhealthy connection detection and replacement + - timeout behavior across simultaneous waiters +- Full thread-safety guarantees with internal mutex and condition variable coordination + +### Legacy + +- No native connection pool abstraction in `libkmip`/`kmippp`. +- Callers must manually manage multiple `kmippp::context` instances and synchronization. +- `kmippp` provides a global mutable `last_result` string for error details, which the documentation + explicitly warns is **not thread-safe**. Concurrent usage requires external synchronization + and introduces race conditions on error reporting. +- No built-in support for connection health tracking or automatic recovery from transient failures. + +## 6) Protocol, Serialization, and Performance Direction + +### 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, including + typed wrappers for `Discover Versions` and `Query` responses. + +### 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. + +## 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. + +## 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. +- 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 + +- `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. + +## 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 + - 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. `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/.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..a9d6777 --- /dev/null +++ b/kmipclient/CMakeLists.txt @@ -0,0 +1,137 @@ +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 + 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 +) + +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 PUBLIC "-fsanitize=address" "-fno-omit-frame-pointer") + target_link_options(kmipclient PUBLIC "-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_tls_verify) +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) +add_example(supported_versions) +add_example(query_server_info) + +# 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/IOUtilsTest.cpp + tests/KmipClientIntegrationTest.cpp + tests/KmipClientIntegrationTest_2_0.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..c372b80 --- /dev/null +++ b/kmipclient/README.md @@ -0,0 +1,707 @@ +# The `kmipclient` library + +`kmipclient` is a C++20 library that provides a clean, high-level interface to +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. + +--- + +## 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 `kmipcore` layer; no direct dependency on `libkmip` or `kmippp`. +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` | 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`) | + +--- + +## High-level design + +### `NetClient` interface + +Abstract base class for network transport. Defines four virtual methods and +optional TLS verification controls: + +```cpp +virtual bool connect(); +virtual void close(); +virtual int send(std::span data); +virtual int recv(std::span data); + +virtual void set_tls_verification(TlsVerificationOptions options) noexcept; +virtual TlsVerificationOptions tls_verification() const noexcept; +``` + +`NetClientOpenSSL` is the ready-to-use implementation based on OpenSSL BIO. + +`TlsVerificationOptions` defaults to secure verification: + +```cpp +NetClient::TlsVerificationOptions{ + .peer_verification = true, + .hostname_verification = true, +} +``` + +This means the server certificate chain is validated against the configured CA +file, and the certificate must also match the requested host name (or IP +address). These checks can be relaxed for lab/self-signed environments; see +[`TLS verification controls`](#tls-verification-controls) below. + +### `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"); +``` + +The façade also accepts an optional final +`NetClient::TlsVerificationOptions` parameter for environments that need to +relax TLS checks without constructing `NetClientOpenSSL` directly. + +### TLS verification controls + +`kmipclient` now exposes TLS verification explicitly through +`NetClient::TlsVerificationOptions`. + +Recommended settings: + +| Setting | Use case | +|---|---| +| `{true, true}` | Default and recommended for production | +| `{true, false}` | Self-signed / private CA deployments where the certificate chain is trusted but the certificate host name does not match the endpoint used by the client | +| `{false, false}` | Local development only; disables all server authentication | + +`hostname_verification = true` requires `peer_verification = true`. The +transport rejects the invalid combination `{false, true}`. + +#### Strict default (recommended) + +```cpp +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 5000); +net_client.connect(); +KmipClient client(net_client); +``` + +#### Trust the CA, but ignore host name mismatches + +This is the common setting for KMIP lab environments that use self-signed or +private-CA certificates issued for a different DNS name than the address the +client actually connects to. + +```cpp +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 5000); +net_client.set_tls_verification({ + .peer_verification = true, + .hostname_verification = false, +}); +net_client.connect(); + +KmipClient client(net_client); +``` + +The same setting can be passed through the `Kmip` façade: + +```cpp +Kmip kmip( + host, + port, + client_cert, + client_key, + server_ca, + 5000, + kmipcore::KMIP_VERSION_1_4, + {}, + { + .peer_verification = true, + .hostname_verification = false, + } +); +``` + +#### Fully disable verification (development only) + +```cpp +NetClientOpenSSL net_client(host, port, client_cert, client_key, server_ca, 5000); +net_client.set_tls_verification({ + .peer_verification = false, + .hostname_verification = false, +}); +net_client.connect(); +``` + +Do **not** use `{false, false}` in production: it disables server certificate +validation and makes TLS vulnerable to active interception. + +For a complete runnable sample, see `example_get_tls_verify`, which extends the +basic `example_get` flow with selectable TLS verification modes. + +### `Key` and factories + +`kmipclient::Key` is the abstract base class for typed key objects. + +| Factory | Description | +|---|---| +| `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` | + +`op_get_key(...)` returns `std::unique_ptr` (polymorphic typed key). +All derived key types expose these common `Key` accessors: + +| Method | Return type | Description | +|---|---|---| +| `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 | +| `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 + +`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 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 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 `""` +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); +``` + +### `KmipClientPool` + +Thread-safe pool of `KmipClient` connections. Connections are created lazily +on demand up to `max_connections`. Threads borrow a client via RAII. The pool +transparently manages TLS connection lifetimes, automatically discarding +unhealthy connections and establishing fresh ones as needed. + +**Constructor:** + +```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, + .logger = nullptr, // optional: pass shared_ptr + .version = kmipcore::KMIP_VERSION_1_4, + .tls_verification = { + .peer_verification = true, + .hostname_verification = false, + }, +}); +``` + +**Borrow variants:** + +All borrow calls are non-blocking internally; actual network operations (TLS handshake) +may take time but are not interruptible once started. + +```cpp +// Blocking: waits indefinitely until a connection becomes available +auto conn = pool.borrow(); + +// Timed: throws kmipcore::KmipException on timeout +auto conn = pool.borrow(std::chrono::seconds(10)); + +// Non-blocking: returns std::nullopt if no connection available and at capacity +auto opt_conn = pool.try_borrow(); +if (opt_conn) { + auto key_id = opt_conn->op_create_aes_key("k", "g"); +} +``` + +**Use pattern (RAII guard):** + +```cpp +// 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 +} +``` + +**Error handling with unhealthy connections:** + +If a KMIP operation throws an unrecoverable exception, mark the connection +unhealthy before the guard goes out of scope so the pool discards the connection +(freeing one slot for a fresh connection next time): + +```cpp +try { + auto conn = pool.borrow(); + conn->op_get_key(id); +} catch (const std::exception &e) { + conn.markUnhealthy(); // pool will discard this connection + throw; +} +``` + +**Diagnostic accessors:** + +```cpp +std::cout << "Available: " << pool.available_count() << '\n'; +std::cout << "Total: " << pool.total_count() << '\n'; +std::cout << "Limit: " << pool.max_connections() << '\n'; +``` + +`BorrowedClient` also provides `isHealthy()` to check the health state and +`markUnhealthy()` to indicate that the connection should be discarded on return. + +### `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_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 (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) | +| `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_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 | + +### 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. +- `Get Attributes` is version-aware: KMIP 1.x uses `Attribute Name`, while + KMIP 2.0 uses spec-correct `Attribute Reference` selectors. +- 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. + +--- + +## 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, /*all_attributes=*/true); + // 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'; +} +``` + +### 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 +#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"); +``` + +### Create a client with hostname verification disabled + +```cpp +Kmip kmip( + host, + port, + client_cert, + client_key, + server_ca, + 5000, + kmipcore::KMIP_VERSION_1_4, + {}, + { + .peer_verification = true, + .hostname_verification = false, + } +); + +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 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 + +```cpp +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, revocation_reason_type::KMIP_REVOKE_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); +``` + +### 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: + +Sensitive KMIP fields are redacted by default in these formatted dumps. + +At minimum, the formatter redacts credentials, key material, and secret +payload structures (for example `Username`, `Password`, `Credential Value`, +`Key Material`, `Key Value`, and `Secret Data`). + +Malformed TTLV input also fails closed: the formatter reports the parse error +without dumping raw bytes. + +Redaction is applied to formatter output itself, so it remains in effect for +all `KmipClient` protocol logs produced by `IOUtils::log_debug(...)`. + +```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, + .tls_verification = { + .peer_verification = true, + .hostname_verification = false, + }, +}); + +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**. + +### 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 + +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 +# Optional: enable KMIP 2.0 integration suite +export KMIP_RUN_2_0_TESTS=1 +``` + +For the local certificate layout commonly used in development, this becomes: + +```bash +export CERTS_DIR=/tmp/certs +export KMIP_ADDR="127.0.0.1" +export KMIP_PORT="5696" +export KMIP_CLIENT_CA="$CERTS_DIR/mysql-client-cert.pem" +export KMIP_CLIENT_KEY="$CERTS_DIR/mysql-client-key.pem" +export KMIP_SERVER_CA="$CERTS_DIR/vault-kmip-ca.pem" +# Optional: enable KMIP 2.0 integration suite +export KMIP_RUN_2_0_TESTS=1 +``` + +2. Configure and build: + +```bash +cmake -DBUILD_TESTS=ON .. +cmake --build . +``` + +3. Run: + +```bash +ctest --output-on-failure +# or directly: +./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.*`). + +The KMIP 1.4 integration suite includes enabled tests for: + +- `KmipClientIntegrationTest.RegisterAndActivateSymmetricKey` +- `KmipClientIntegrationTest.RegisterAndActivateSecret` + +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 +``` + +--- + +## Example programs + +| Binary | Description | +|---|---| +| `example_create_aes` | Create a server-side AES-256 key | +| `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_tls_verify` | Retrieve a symmetric key by ID while explicitly selecting TLS verification mode (`strict`, `no-hostname`, or `insecure`) | +| `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_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: + +``` + [extra args…] +``` + +`example_get_tls_verify` adds two required trailing arguments beyond the common +connection parameters: + +``` +example_get_tls_verify +``` + diff --git a/kmipclient/TODO.md b/kmipclient/TODO.md new file mode 100644 index 0000000..ad3a9d6 --- /dev/null +++ b/kmipclient/TODO.md @@ -0,0 +1,18 @@ +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. +8. Additional security features like optional certificates verification, client authentication, etc. + + 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..a88cfd8 --- /dev/null +++ b/kmipclient/examples/example_create_aes.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/Kmip.hpp" +#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; + + 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", + 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); + 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] + << " 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..c06e1c1 --- /dev/null +++ b/kmipclient/examples/example_get.cpp @@ -0,0 +1,64 @@ + +/* 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_hex(const std::vector &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 << "Attributes:" << std::endl; + 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) { + 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..6ff502a --- /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(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; + } + } 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(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 (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..9ade5f2 --- /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 std::vector &key) { + for (auto const &c : key) { + std::cout << std::hex << static_cast(c); + } + std::cout << std::endl; +} + +void print_attributes(const kmipcore::Attributes &attrs) { + for (const auto &[name, value] : attrs.as_string_map()) { + std::cout << name << ": " << value << 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..430c8ce --- /dev/null +++ b/kmipclient/examples/example_get_logger.cpp @@ -0,0 +1,84 @@ +/* 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 std::vector &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..256bac4 --- /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 kmipcore::Attributes &attrs) { + for (const auto &[name, value] : attrs.as_string_map()) { + std::cout << name << ": " << value << 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 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) { + 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..5a2b884 --- /dev/null +++ b/kmipclient/examples/example_get_secret.cpp @@ -0,0 +1,63 @@ +/* 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(); + std::cout << "Attributes:" << std::endl; + for (const auto &[key, value] : attrs.as_string_map()) { + std::cout << " " << key << ": " << value << 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_get_tls_verify.cpp b/kmipclient/examples/example_get_tls_verify.cpp new file mode 100644 index 0000000..b7dfd6c --- /dev/null +++ b/kmipclient/examples/example_get_tls_verify.cpp @@ -0,0 +1,120 @@ + +/* 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 +#include + +using namespace kmipclient; + +namespace { + + void print_hex(const std::vector &key) { + for (auto const &c : key) { + std::cout << std::hex << static_cast(c); + } + std::cout << std::endl; + } + + std::optional + parse_tls_mode(std::string_view mode) { + if (mode == "strict") { + return NetClient::TlsVerificationOptions{ + .peer_verification = true, + .hostname_verification = true, + }; + } + + if (mode == "no-hostname") { + return NetClient::TlsVerificationOptions{ + .peer_verification = true, + .hostname_verification = false, + }; + } + + if (mode == "insecure") { + return NetClient::TlsVerificationOptions{ + .peer_verification = false, + .hostname_verification = false, + }; + } + + return std::nullopt; + } + + void print_usage() { + std::cerr << "Usage: example_get_tls_verify " + " " + " " + << std::endl + << " strict - verify certificate chain and host name/IP " + "(default secure mode)" + << std::endl + << " no-hostname - verify certificate chain only; skip host " + "name/IP match" + << std::endl + << " insecure - disable all TLS server verification " + "(development only)" + << 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) { + print_usage(); + return -1; + } + + const auto tls_verification = parse_tls_mode(argv[7]); + if (!tls_verification.has_value()) { + std::cerr << "Unknown TLS verification mode: '" << argv[7] << "'" + << std::endl; + print_usage(); + return -1; + } + + NetClientOpenSSL net_client(argv[1], argv[2], argv[3], argv[4], argv[5], 200); + net_client.set_tls_verification(*tls_verification); + net_client.connect(); + 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 << "Attributes:" << std::endl; + 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) { + 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_locate.cpp b/kmipclient/examples/example_locate.cpp new file mode 100644 index 0000000..9a65981 --- /dev/null +++ b/kmipclient/examples/example_locate.cpp @@ -0,0 +1,71 @@ +/* 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], 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; + } + } 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], 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 (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..ed2e975 --- /dev/null +++ b/kmipclient/examples/example_locate_by_group.cpp @@ -0,0 +1,71 @@ +/* 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], 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; + } + } 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], 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 (const 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..2f15e8e --- /dev/null +++ b/kmipclient/examples/example_pool.cpp @@ -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 + */ + +/** + * 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_query_server_info.cpp b/kmipclient/examples/example_query_server_info.cpp new file mode 100644 index 0000000..45f3859 --- /dev/null +++ b/kmipclient/examples/example_query_server_info.cpp @@ -0,0 +1,162 @@ +/* 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 "kmipcore/kmip_errors.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(static_cast(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(static_cast(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_register_key.cpp b/kmipclient/examples/example_register_key.cpp new file mode 100644 index 0000000..a30c873 --- /dev/null +++ b/kmipclient/examples/example_register_key.cpp @@ -0,0 +1,92 @@ +/* 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_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 < 7) { + 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 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()); + + 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()); + 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, + "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; + 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..6459e7a --- /dev/null +++ b/kmipclient/examples/example_register_secret.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/Kmip.hpp" +#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 < 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( + 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; + return -1; + } + return 0; +} diff --git a/kmipclient/examples/example_revoke.cpp b/kmipclient/examples/example_revoke.cpp new file mode 100644 index 0000000..451aa84 --- /dev/null +++ b/kmipclient/examples/example_revoke.cpp @@ -0,0 +1,53 @@ +/* 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], + 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() + << std::endl; + return -1; + }; + return 0; +} diff --git a/kmipclient/examples/example_supported_versions.cpp b/kmipclient/examples/example_supported_versions.cpp new file mode 100644 index 0000000..f5fc2c3 --- /dev/null +++ b/kmipclient/examples/example_supported_versions.cpp @@ -0,0 +1,125 @@ +/* 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 "kmipcore/kmip_errors.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/Key.hpp b/kmipclient/include/kmipclient/Key.hpp new file mode 100644 index 0000000..2d71c65 --- /dev/null +++ b/kmipclient/include/kmipclient/Key.hpp @@ -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 + */ + +#ifndef KMIPCLIENT_KEY_HPP +#define KMIPCLIENT_KEY_HPP + +#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..6d46268 --- /dev/null +++ b/kmipclient/include/kmipclient/KeyBase.hpp @@ -0,0 +1,126 @@ +/* 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 + +namespace kmipclient { + + /** + * Client-level key abstraction. + * + * 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: + virtual ~Key() = default; + + Key(const Key &) = default; + Key &operator=(const Key &) = default; + Key(Key &&) noexcept = default; + Key &operator=(Key &&) noexcept = default; + + // ---- Raw bytes ---- + + [[nodiscard]] const std::vector &value() const noexcept { + return key_value_; + } + + // ---- Attribute bag ---- + + /** @brief Returns the type-safe attribute bag (read-only). */ + [[nodiscard]] const kmipcore::Attributes &attributes() const noexcept { + return attributes_; + } + + /** @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 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(); + } + [[nodiscard]] kmipcore::state state() const noexcept { + return attributes_.object_state(); + } + + // ---- 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; + + // ---- Core-layer bridge ---- + + /** @brief Build protocol-level representation from the client key object. + */ + [[nodiscard]] kmipcore::Key to_core_key() const; + /** @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, + kmipcore::Attributes attrs = {}); + + private: + std::vector key_value_; + kmipcore::Attributes attributes_; + }; + +} // namespace kmipclient + +#endif // KMIPCLIENT_KEY_BASE_HPP diff --git a/kmipclient/include/kmipclient/Kmip.hpp b/kmipclient/include/kmipclient/Kmip.hpp new file mode 100644 index 0000000..a1ae810 --- /dev/null +++ b/kmipclient/include/kmipclient/Kmip.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 KMIP_HPP +#define KMIP_HPP +#include "kmipclient/KmipClient.hpp" +#include "kmipclient/NetClientOpenSSL.hpp" +#include "kmipcore/kmip_protocol.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 version KMIP protocol version to use for requests. + * @param logger Optional KMIP protocol logger. + * @param tls_verification TLS peer/hostname verification settings applied + * before the OpenSSL transport connects. + * @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, + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4, + const std::shared_ptr &logger = {}, + NetClient::TlsVerificationOptions tls_verification = {} + ) + : m_net_client( + host, + port, + clientCertificateFn, + clientKeyFn, + serverCaCertFn, + timeout_ms + ), + m_client(m_net_client, logger, std::move(version)) { + m_net_client.set_tls_verification(tls_verification); + 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..88db001 --- /dev/null +++ b/kmipclient/include/kmipclient/KmipClient.hpp @@ -0,0 +1,321 @@ +/* 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_attributes.hpp" +#include "kmipcore/kmip_logger.hpp" +#include "kmipcore/kmip_protocol.hpp" + +#include +#include +#include +#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. + * @param version KMIP protocol version to use for requests. + */ + explicit KmipClient( + NetClient &net_client, + const std::shared_ptr &logger = {}, + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4 + ); + /** @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]] std::string op_register_key( + 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. + * @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_secret( + const std::string &name, const std::string &group, 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 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, + 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. + * @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]] std::unique_ptr + op_get_key(const std::string &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 std::string &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]] std::string op_activate(const std::string &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]] std::vector + op_get_attribute_list(const std::string &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 Type-safe @ref Attributes bag with the requested attributes. + * @throws kmipcore::KmipException on protocol or server-side failure. + */ + [[nodiscard]] kmipcore::Attributes op_get_attributes( + const std::string &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]] std::vector + op_locate_by_name(const std::string &name, 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]] 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; + + + /** + * @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]] std::string op_revoke( + const std::string &id, + revocation_reason_type reason, + const std::string &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]] std::string op_destroy(const std::string &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]] std::vector op_all( + object_type o_type, + std::size_t max_ids = MAX_BATCHES_IN_SEARCH * MAX_ITEMS_IN_BATCH + ) 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 +#endif // KMIP_CLIENT_HPP diff --git a/kmipclient/include/kmipclient/KmipClientPool.hpp b/kmipclient/include/kmipclient/KmipClientPool.hpp new file mode 100644 index 0000000..3d44743 --- /dev/null +++ b/kmipclient/include/kmipclient/KmipClientPool.hpp @@ -0,0 +1,257 @@ +/* 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_KMIP_CLIENT_POOL_HPP +#define KMIPCLIENT_KMIP_CLIENT_POOL_HPP + +#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; + /** KMIP protocol version used by all connections in the pool. */ + kmipcore::ProtocolVersion version = kmipcore::KMIP_VERSION_1_4; + /** TLS peer/hostname verification settings applied to each pooled + * transport. */ + NetClient::TlsVerificationOptions tls_verification{}; + }; + + // ---- 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(const 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 + +#endif /* KMIPCLIENT_KMIP_CLIENT_POOL_HPP */ diff --git a/kmipclient/include/kmipclient/KmipIOException.hpp b/kmipclient/include/kmipclient/KmipIOException.hpp new file mode 100644 index 0000000..23af221 --- /dev/null +++ b/kmipclient/include/kmipclient/KmipIOException.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 KMIPIOSEXCEPTION_HPP +#define KMIPIOSEXCEPTION_HPP + +#include "kmipcore/kmip_errors.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..91585a9 --- /dev/null +++ b/kmipclient/include/kmipclient/NetClient.hpp @@ -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 + */ + +#ifndef KMIP_NET_CLIENT_HPP +#define KMIP_NET_CLIENT_HPP + +#include +#include +#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 TLS certificate-verification settings applied on the next + * connect(). + * + * Hostname verification is meaningful only when peer verification is also + * enabled. Implementations may reject the invalid combination + * {peer_verification=false, hostname_verification=true}. + */ + struct TlsVerificationOptions { + bool peer_verification = true; + bool hostname_verification = true; + }; + + /** + * @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 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, + 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. + * Must honor @ref m_timeout_ms for connect + handshake phases. + * @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 Updates TLS peer/hostname verification settings for future + * connect() calls. + * + * Changing these options does not affect an already-established TLS + * session; disconnect and reconnect for the new settings to take effect. + */ + virtual void set_tls_verification(TlsVerificationOptions options) noexcept { + m_tls_verification = options; + } + + /** @brief Returns the TLS verification settings currently configured on + * this transport. */ + [[nodiscard]] virtual TlsVerificationOptions + tls_verification() const noexcept { + return m_tls_verification; + } + + /** + * @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. + * @return Number of bytes sent, or -1 on failure. + */ + virtual int send(std::span data) = 0; + + /** + * @brief Receives bytes from the established connection. + * @param data Destination buffer. + * @return Number of bytes received, or -1 on failure. + */ + virtual int recv(std::span data) = 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; + TlsVerificationOptions m_tls_verification{}; + 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..9c81585 --- /dev/null +++ b/kmipclient/include/kmipclient/NetClientOpenSSL.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 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 Timeout in milliseconds applied to TCP connect, TLS + * handshake, and each read/write operation. + */ + 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. + * Honors timeout_ms for both TCP connect and TLS handshake. + * The handshake also honors the TLS verification settings configured + * via @ref set_tls_verification(). + * @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. + * @return Number of bytes sent, or -1 on failure. + */ + int send(std::span data) override; + /** + * @brief Receives raw bytes through the TLS channel. + * @param data Destination buffer. + * @return Number of bytes read, or -1 on failure. + */ + int recv(std::span data) 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/PEMReader.hpp b/kmipclient/include/kmipclient/PEMReader.hpp new file mode 100644 index 0000000..61e66f8 --- /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..50183e4 --- /dev/null +++ b/kmipclient/include/kmipclient/PrivateKey.hpp @@ -0,0 +1,37 @@ +/* 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..2255656 --- /dev/null +++ b/kmipclient/include/kmipclient/PublicKey.hpp @@ -0,0 +1,37 @@ +/* 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..b3e943b --- /dev/null +++ b/kmipclient/include/kmipclient/SymmetricKey.hpp @@ -0,0 +1,45 @@ +/* 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(aes_key_size key_size = aes_key_size::AES_256); + }; + +} // 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..ab7841b --- /dev/null +++ b/kmipclient/include/kmipclient/X509Certificate.hpp @@ -0,0 +1,37 @@ +/* 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/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..4b4d415 --- /dev/null +++ b/kmipclient/include/kmipclient/types.hpp @@ -0,0 +1,70 @@ +#ifndef KMIPCLIENT_TYPES_HPP +#define KMIPCLIENT_TYPES_HPP + +#include "kmipcore/key.hpp" +#include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/kmip_attributes.hpp" +#include "kmipcore/secret.hpp" + +#include + +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. */ + 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 secret object representation. */ + using kmipcore::Secret; + + /** @brief Canonical KMIP attribute name for object 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 = + std::string(kmipcore::KMIP_ATTR_NAME_GROUP); + /** @brief Canonical KMIP attribute name for object 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 = + 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 + * (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 + * (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<<; + + /** @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 + +#endif /* KMIPCLIENT_TYPES_HPP */ diff --git a/kmipclient/src/IOUtils.cpp b/kmipclient/src/IOUtils.cpp new file mode 100644 index 0000000..d22d746 --- /dev/null +++ b/kmipclient/src/IOUtils.cpp @@ -0,0 +1,156 @@ +/* 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_formatter.hpp" +#include "kmipcore/kmip_logger.hpp" + +#include +#include +#include + +namespace kmipclient { +#define KMIP_MSG_LENGTH_BYTES 8 + + namespace { + + [[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) | + 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."); + } + + 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; + } + } + + void IOUtils::read_exact(std::span buf) { + int total_read = 0; + const int n = static_cast(buf.size()); + while (total_read < n) { + const int received = + net_client.recv(buf.subspan(static_cast(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) { + std::array msg_len_buf{}; + + read_exact(msg_len_buf); + + 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) + ); + } + + std::vector response( + KMIP_MSG_LENGTH_BYTES + static_cast(length) + ); + memcpy(response.data(), msg_len_buf.data(), KMIP_MSG_LENGTH_BYTES); + + read_exact( + std::span(response).subspan( + KMIP_MSG_LENGTH_BYTES, static_cast(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..283db77 --- /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, const std::shared_ptr &logger = {} + ) + : net_client(nc), logger_(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(std::span buf); + + 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..2c14db5 --- /dev/null +++ b/kmipclient/src/Key.cpp @@ -0,0 +1,57 @@ +/* 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 "kmipcore/kmip_errors.hpp" + +namespace kmipclient { + + Key::Key(const std::vector &value, kmipcore::Attributes attrs) + : key_value_(value), attributes_(std::move(attrs)) {} + + kmipcore::Key Key::to_core_key() const { + 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.attributes() + ); + case KeyType::PUBLIC_KEY: + return std::make_unique( + core_key.value(), core_key.attributes() + ); + case KeyType::PRIVATE_KEY: + return std::make_unique( + core_key.value(), core_key.attributes() + ); + case KeyType::CERTIFICATE: + 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 new file mode 100644 index 0000000..f6f1e48 --- /dev/null +++ b/kmipclient/src/KmipClient.cpp @@ -0,0 +1,849 @@ +/* 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_errors.hpp" +#include "kmipcore/kmip_requests.hpp" +#include "kmipcore/response_parser.hpp" + +#include +#include + +namespace kmipclient { + + 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_key_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, + KMIP_ATTR_NAME_CRYPTO_USAGE_MASK + }; + } + + 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 + }; + } + + 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; + } + } + + static bool should_retry_get_attributes_with_legacy_v2_encoding( + 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, + kmipcore::ProtocolVersion version + ) + : net_client(net_client), + io(std::make_unique(net_client, logger)), + version_(version) {}; + + + KmipClient::~KmipClient() { + net_client.close(); + }; + + std::string KmipClient::op_register_key( + const std::string &name, const std::string &group, const Key &k + ) const { + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::RegisterKeyRequest( + name, + group, + k.to_core_key(), + request.getHeader().getProtocolVersion() + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + std::string KmipClient::op_register_and_activate_key( + const std::string &name, const std::string &group, const Key &k + ) const { + 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() + ); + + 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; + } + + std::string KmipClient::op_register_secret( + const std::string &name, const std::string &group, const Secret &secret + ) const { + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::RegisterSecretRequest( + name, + group, + secret.value(), + secret.get_secret_type(), + request.getHeader().getProtocolVersion() + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + std::string KmipClient::op_register_and_activate_secret( + const std::string &name, const std::string &group, const Secret &secret + ) const { + 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() + ); + + 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; + } + + std::string KmipClient::op_create_aes_key( + const std::string &name, + const std::string &group, + aes_key_size key_size, + cryptographic_usage_mask usage_mask + ) const { + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::CreateSymmetricKeyRequest( + name, + group, + static_cast(key_size), + usage_mask, + request.getHeader().getProtocolVersion() + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + std::unique_ptr + KmipClient::op_get_key(const std::string &id, bool all_attributes) const { + const auto requested_attrs = default_get_key_attrs(all_attributes); + const auto execute = [&](bool legacy_attribute_names_for_v2) { + 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( + kmipcore::GetAttributesRequest( + id, + requested_attrs, + request.getHeader().getProtocolVersion(), + legacy_attribute_names_for_v2 + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + auto core_key = kmipcore::KeyParser::parseGetKeyResponse(get_response); + auto key = Key::from_core_key(core_key); + + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + kmipcore::Attributes server_attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + + // 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" + ); + } + // Merge server-provided metadata (state, name, dates, …) into the key. + key->attributes().merge(server_attrs); + return key; + }; + + try { + return execute(false); + } catch (const kmipcore::KmipException &first_error) { + if (!version_.is_at_least(2, 0) || + !should_retry_get_attributes_with_legacy_v2_encoding(first_error)) { + throw; + } + } + try { + return execute(true); + } catch (const kmipcore::KmipException &second_error) { + if (requested_attrs.empty() || + !should_retry_get_attributes_with_legacy_v2_encoding(second_error)) { + throw; + } + } + + // Compatibility fallback for servers that reject explicit selectors in + // Get Attributes requests: request all attributes and filter client-side. + 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( + kmipcore::GetAttributesRequest( + id, {}, request.getHeader().getProtocolVersion(), true + ) + ); + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + kmipcore::ResponseParser rf(response_bytes, request); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + auto core_key = kmipcore::KeyParser::parseGetKeyResponse(get_response); + auto key = Key::from_core_key(core_key); + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + kmipcore::Attributes server_attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + if (!server_attrs.has_attribute(KMIP_ATTR_NAME_STATE)) { + throw kmipcore::KmipException( + "Required attribute 'State' missing from server response" + ); + } + key->attributes().merge(server_attrs); + return key; + } + + Secret KmipClient::op_get_secret( + const std::string &id, bool all_attributes + ) const { + const auto requested_attrs = default_get_secret_attrs(all_attributes); + const auto execute = [&](bool legacy_attribute_names_for_v2) { + 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( + kmipcore::GetAttributesRequest( + id, + requested_attrs, + request.getHeader().getProtocolVersion(), + legacy_attribute_names_for_v2 + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + Secret secret = kmipcore::KeyParser::parseGetSecretResponse(get_response); + + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + kmipcore::Attributes server_attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + + if (all_attributes) { + // 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" + ); + } + // 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)) + ); + } + } + + return secret; + }; + + try { + return execute(false); + } catch (const kmipcore::KmipException &first_error) { + if (!version_.is_at_least(2, 0) || + !should_retry_get_attributes_with_legacy_v2_encoding(first_error)) { + throw; + } + } + try { + return execute(true); + } catch (const kmipcore::KmipException &second_error) { + if (requested_attrs.empty() || + !should_retry_get_attributes_with_legacy_v2_encoding(second_error)) { + throw; + } + } + + 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( + kmipcore::GetAttributesRequest( + id, {}, request.getHeader().getProtocolVersion(), true + ) + ); + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + kmipcore::ResponseParser rf(response_bytes, request); + auto get_response = + rf.getResponseByBatchItemId( + get_item_id + ); + Secret secret = kmipcore::KeyParser::parseGetSecretResponse(get_response); + auto attrs_response = + rf.getResponseByBatchItemId( + attributes_item_id + ); + kmipcore::Attributes server_attrs = + kmipcore::AttributesParser::parse(attrs_response.getAttributes()); + if (all_attributes) { + 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" + ); + } + 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)) + ); + } + } + return secret; + } + + std::string KmipClient::op_activate(const std::string &id) const { + auto request = make_request_message(); + 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, request); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + std::vector + KmipClient::op_get_attribute_list(const std::string &id) const { + auto request = make_request_message(); + 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, request); + auto response = rf.getResponseByBatchItemId< + kmipcore::GetAttributeListResponseBatchItem>(batch_item_id); + return std::vector{ + response.getAttributeNames().begin(), response.getAttributeNames().end() + }; + } + + kmipcore::Attributes KmipClient::op_get_attributes( + const std::string &id, const std::vector &attr_names + ) const { + const auto execute = [&](const std::vector &selectors, + bool legacy_attribute_names_for_v2) { + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::GetAttributesRequest( + id, + selectors, + request.getHeader().getProtocolVersion(), + legacy_attribute_names_for_v2 + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + return kmipcore::AttributesParser::parse(response.getAttributes()); + }; + + try { + return execute(attr_names, false); + } catch (const kmipcore::KmipException &first_error) { + if (!version_.is_at_least(2, 0) || + !should_retry_get_attributes_with_legacy_v2_encoding(first_error)) { + throw; + } + } + try { + return execute(attr_names, true); + } catch (const kmipcore::KmipException &second_error) { + if (attr_names.empty() || + !should_retry_get_attributes_with_legacy_v2_encoding(second_error)) { + throw; + } + } + return execute({}, true); + } + + std::vector KmipClient::op_locate_by_name( + const std::string &name, object_type o_type + ) const { + std::vector result; + std::size_t offset = 0; + + 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() + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + 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( + const std::string &group, object_type o_type, std::size_t max_ids + ) const { + if (max_ids == 0) { + return {}; + } + + std::vector result; + std::size_t offset = 0; + + 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); + + auto request = make_request_message(); + const auto batch_item_id = request.add_batch_item( + kmipcore::LocateRequest( + true, + group, + o_type, + page_size, + offset, + request.getHeader().getProtocolVersion() + ) + ); + + std::vector response_bytes; + io->do_exchange( + request.serialize(), response_bytes, request.getMaxResponseSize() + ); + + kmipcore::ResponseParser rf(response_bytes, request); + auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + auto got = std::vector( + response.getUniqueIdentifiers().begin(), + response.getUniqueIdentifiers().end() + ); + + if (got.empty()) { + break; + } + + 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; + } + + std::vector + KmipClient::op_all(object_type o_type, std::size_t max_ids) const { + 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, request); + auto response = rf.getResponseByBatchItemId< + kmipcore::DiscoverVersionsResponseBatchItem>(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, request); + const auto response = + rf.getResponseByBatchItemId( + batch_item_id + ); + + QueryServerInfo result; + 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(); + 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 { + auto request = make_request_message(); + 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, request); + return rf + .getResponseByBatchItemId( + batch_item_id + ) + .getUniqueIdentifier(); + } + + std::string KmipClient::op_destroy(const std::string &id) const { + auto request = make_request_message(); + 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, request); + 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..e2abeca --- /dev/null +++ b/kmipclient/src/KmipClientPool.cpp @@ -0,0 +1,218 @@ +/* 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_errors.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(const Config &config) : config_(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->set_tls_verification(config_.tls_verification); + slot->net_client->connect(); // throws KmipException on failure + + slot->kmip_client = std::make_unique( + *slot->net_client, config_.logger, config_.version + ); + + 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..7e6094b --- /dev/null +++ b/kmipclient/src/NetClientOpenSSL.cpp @@ -0,0 +1,456 @@ +/* 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 +#include +#include +#include +#include +#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(); + } + + static bool is_ip_address(const std::string &host) { + in_addr addr4{}; + if (inet_pton(AF_INET, host.c_str(), &addr4) == 1) { + return true; + } + + in6_addr addr6{}; + return inet_pton(AF_INET6, host.c_str(), &addr6) == 1; + } + + static void configure_tls_verification( + SSL_CTX *ctx, + SSL *ssl, + const std::string &host, + const NetClient::TlsVerificationOptions &options + ) { + if (options.hostname_verification && !options.peer_verification) { + throw KmipIOException( + -1, + "TLS hostname verification requires TLS peer verification to be " + "enabled" + ); + } + + SSL_CTX_set_verify( + ctx, + options.peer_verification ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, + nullptr + ); + + if (host.empty()) { + return; + } + + const bool host_is_ip = is_ip_address(host); + if (!host_is_ip) { + if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) { + throw KmipIOException( + -1, + "Failed to configure TLS SNI for host '" + host + + "': " + getOpenSslError() + ); + } + } + + if (!options.peer_verification || !options.hostname_verification) { + return; + } + + if (host_is_ip) { + if (X509_VERIFY_PARAM_set1_ip_asc(SSL_get0_param(ssl), host.c_str()) != + 1) { + throw KmipIOException( + -1, + "Failed to configure TLS IP-address verification for '" + host + + "': " + getOpenSslError() + ); + } + return; + } + + if (SSL_set1_host(ssl, host.c_str()) != 1) { + throw KmipIOException( + -1, + "Failed to configure TLS hostname verification for '" + host + + "': " + getOpenSslError() + ); + } + } + + static void ensure_tls_peer_verified( + SSL *ssl, const NetClient::TlsVerificationOptions &options + ) { + if (!options.peer_verification) { + return; + } + + if (SSL_get0_peer_certificate(ssl) == nullptr) { + throw KmipIOException( + -1, + "TLS peer verification failed: server did not present a certificate" + ); + } + + const long verify_result = SSL_get_verify_result(ssl); + if (verify_result != X509_V_OK) { + throw KmipIOException( + -1, + "TLS peer verification failed: " + + std::string(X509_verify_cert_error_string(verify_result)) + ); + } + } + + // 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. + 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()); + } + + configure_tls_verification( + ctx_.get(), nullptr, std::string{}, m_tls_verification + ); + + 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_check_private_key(ctx_.get()) != 1) { + throw KmipIOException( + -1, "Client certificate/private key mismatch: " + 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()); + } + + configure_tls_verification(ctx_.get(), ssl, m_host, m_tls_verification); + + 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()); + + 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() + ); + } + } + + ensure_tls_peer_verified(ssl, m_tls_verification); + + // 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(std::span data) { + if (!checkConnected()) { + return -1; + } + const int dlen = static_cast(data.size()); + errno = 0; + const int ret = BIO_write(bio_.get(), data.data(), dlen); + if (ret <= 0 && BIO_should_retry(bio_.get()) && is_timeout_errno()) { + throw KmipIOException(-1, timeoutMessage("send", m_timeout_ms)); + } + return ret; + } + + int NetClientOpenSSL::recv(std::span data) { + if (!checkConnected()) { + return -1; + } + const int dlen = static_cast(data.size()); + errno = 0; + const int ret = BIO_read(bio_.get(), data.data(), dlen); + if (ret <= 0 && BIO_should_retry(bio_.get()) && is_timeout_errno()) { + throw KmipIOException(-1, timeoutMessage("receive", m_timeout_ms)); + } + return ret; + } + +} // namespace kmipclient diff --git a/kmipclient/src/PEMReader.cpp b/kmipclient/src/PEMReader.cpp new file mode 100644 index 0000000..c318d53 --- /dev/null +++ b/kmipclient/src/PEMReader.cpp @@ -0,0 +1,180 @@ +/* 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/PrivateKey.hpp" +#include "kmipclient/PublicKey.hpp" +#include "kmipclient/SymmetricKey.hpp" +#include "kmipclient/X509Certificate.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); + + 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) { + 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); + + 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) { + 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); + + 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) { + 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..8dc64a5 --- /dev/null +++ b/kmipclient/src/PrivateKey.cpp @@ -0,0 +1,27 @@ +/* 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..e1bb9a3 --- /dev/null +++ b/kmipclient/src/PublicKey.cpp @@ -0,0 +1,27 @@ +/* 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/StringUtils.cpp b/kmipclient/src/StringUtils.cpp new file mode 100644 index 0000000..7d86213 --- /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_errors.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."}; + } + + std::vector StringUtils::fromHex(std::string_view hex) { + if (hex.empty() || hex.size() % 2 != 0) { + throw kmipcore::KmipException{"Invalid hex string length."}; + } + std::vector bytes; + 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(std::string_view 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..38ae8ba --- /dev/null +++ b/kmipclient/src/StringUtils.hpp @@ -0,0 +1,34 @@ +/* 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 +#include + +namespace kmipclient { + + class StringUtils { + public: + static std::vector fromHex(std::string_view hex); + static std::vector fromBase64(std::string_view base64); + }; + +} // namespace kmipclient + +#endif // STRINGUTILS_HPP diff --git a/kmipclient/src/SymmetricKey.cpp b/kmipclient/src/SymmetricKey.cpp new file mode 100644 index 0000000..56237ac --- /dev/null +++ b/kmipclient/src/SymmetricKey.cpp @@ -0,0 +1,84 @@ +/* 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" + }; + } + + 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 + + + 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(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); + 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..92aa478 --- /dev/null +++ b/kmipclient/src/X509Certificate.cpp @@ -0,0 +1,27 @@ +/* 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/IOUtilsTest.cpp b/kmipclient/tests/IOUtilsTest.cpp new file mode 100644 index 0000000..3a0b5b0 --- /dev/null +++ b/kmipclient/tests/IOUtilsTest.cpp @@ -0,0 +1,253 @@ +/* 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 "kmipclient/NetClientOpenSSL.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_logger.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + + class CollectingLogger final : public kmipcore::Logger { + public: + [[nodiscard]] bool shouldLog(kmipcore::LogLevel level) const override { + return level == kmipcore::LogLevel::Debug; + } + + void log(const kmipcore::LogRecord &record) override { + records.push_back(record); + } + + std::vector records; + }; + + 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; + } + + std::vector + serialize_element(const std::shared_ptr &element) { + kmipcore::SerializationBuffer buf; + element->serialize(buf); + return buf.release(); + } + +} // 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 + ); +} + +TEST(IOUtilsTest, DebugLoggingRedactsSensitiveTtlvFields) { + FakeNetClient nc; + + auto request = kmipcore::Element::createStructure( + kmipcore::tag::KMIP_TAG_REQUEST_MESSAGE + ); + request->asStructure()->add( + kmipcore::Element::createTextString( + kmipcore::tag::KMIP_TAG_USERNAME, "alice" + ) + ); + request->asStructure()->add( + kmipcore::Element::createTextString( + kmipcore::tag::KMIP_TAG_PASSWORD, "s3cr3t" + ) + ); + request->asStructure()->add( + kmipcore::Element::createByteString( + kmipcore::tag::KMIP_TAG_KEY_MATERIAL, {0xDE, 0xAD, 0xBE, 0xEF} + ) + ); + const auto request_bytes = serialize_element(request); + + auto response = kmipcore::Element::createStructure( + kmipcore::tag::KMIP_TAG_RESPONSE_MESSAGE + ); + auto secret_data = + kmipcore::Element::createStructure(kmipcore::tag::KMIP_TAG_SECRET_DATA); + secret_data->asStructure()->add( + kmipcore::Element::createEnumeration( + kmipcore::tag::KMIP_TAG_SECRET_DATA_TYPE, + static_cast( + kmipcore::secret_data_type::KMIP_SECDATA_PASSWORD + ) + ) + ); + response->asStructure()->add(secret_data); + nc.response_bytes = serialize_element(response); + + auto logger = std::make_shared(); + kmipclient::IOUtils io(nc, logger); + std::vector response_bytes; + + ASSERT_NO_THROW(io.do_exchange(request_bytes, response_bytes, 1024)); + ASSERT_EQ(logger->records.size(), 2u); + + const std::string combined = + logger->records[0].message + "\n" + logger->records[1].message; + EXPECT_NE(combined.find("Username"), std::string::npos); + EXPECT_NE(combined.find("Password"), std::string::npos); + EXPECT_NE(combined.find("KeyMaterial"), std::string::npos); + EXPECT_NE(combined.find("SecretData"), std::string::npos); + EXPECT_NE(combined.find(" +#include +#include +#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; + bool run_2_0_tests; + +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 = 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); + } + } + + 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 " + "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"; + } + + 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 { + 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 revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE + auto res_r = kmip.client().op_revoke( + 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) { + std::cerr << "Failed to destroy key: " << e.what() << std::endl; + } + } + } catch (...) { + // Ignore cleanup errors + } + } + } + + static std::unique_ptr createKmipClient() { + auto &config = KmipTestConfig::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 + ); + } 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) { + 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, object_type::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, object_type::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(object_type::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 { + const std::string key_id_128 = kmip->client().op_create_aes_key( + TESTING_NAME_PREFIX + "CreateSymmetricAESKey128", + TEST_GROUP, + aes_key_size::AES_128 + ); + 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(); + } +} + +// Test: Create and Get key +TEST_F(KmipClientIntegrationTest, CreateAndGetKey) { + auto kmip = createKmipClient(); + std::string 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(); + std::string 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_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; + } 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, + SymmetricKey::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 and Activate symmetric key in one request +TEST_F(KmipClientIntegrationTest, 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.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, 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, secret_data_type::KMIP_SECDATA_PASSWORD, secret_attrs + ); + + 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(); + 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, secret_data_type::KMIP_SECDATA_PASSWORD, secret_attrs + ); + try { + secret_id = kmip->client().op_register_secret( + 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) { + 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 { + 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); + // 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); + } catch (kmipcore::KmipException &e) { + FAIL() << "Get secret failed: " << e.what(); + } +} + +// Test: Locate keys +TEST_F(KmipClientIntegrationTest, LocateKeys) { + auto kmip = createKmipClient(); + 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); + 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, 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; + } catch (kmipcore::KmipException &e) { + FAIL() << "Failed to find a key: " << e.what(); + } +} + +// Test: Get attributes +TEST_F(KmipClientIntegrationTest, CreateAndGetAttributes) { + auto kmip = createKmipClient(); + 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); + 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.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); + 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 + 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); + 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, + 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(); + } +} + +// 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, + revocation_reason_type::KMIP_REVOKE_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(); + const std::string fake_id = "non-existent-key-id-12345"; + + 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) { + 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(); + std::string 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 { + 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 + // 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(); + std::string 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, 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); + 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(); + std::string 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, + 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(); + } + + try { + auto attrs = + kmip->client().op_get_attributes(key_id, {KMIP_ATTR_NAME_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) { + 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(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()) + << "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 = 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, key); + EXPECT_FALSE(key_id.empty()); + trackKeyForCleanup(key_id); + + auto attrs = kmip->client().op_get_attributes( + key_id, {KMIP_ATTR_NAME_NAME, KMIP_ATTR_NAME_CRYPTO_USAGE_MASK} + ); + 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" + << 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; + + // 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 + << "\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" + << " 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..6e95c0e --- /dev/null +++ b/kmipclient/tests/KmipClientIntegrationTest_2_0.cpp @@ -0,0 +1,985 @@ +/* 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 "TestEnvUtils.hpp" +#include "kmipclient/Kmip.hpp" +#include "kmipclient/KmipClient.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.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"); + + 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 = kmipclient::test::is_env_flag_enabled("KMIP_RUN_2_0_TESTS"); + + 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_.emplace_back(test_info->name()); + } else if (HasFailure()) { + failed_tests_.emplace_back(test_info->name()); + } else { + passed_tests_.emplace_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/kmipclient/tests/KmipClientPoolIntegrationTest.cpp b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp new file mode 100644 index 0000000..0315159 --- /dev/null +++ b/kmipclient/tests/KmipClientPoolIntegrationTest.cpp @@ -0,0 +1,503 @@ +/* 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 "kmipcore/kmip_errors.hpp" + +#include +#include +#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 = 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 " + "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, + 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) { + std::cerr << "Failed to destroy key: " << e.what() << std::endl; + } + } + } catch (...) { + // Ignore cleanup errors + } + } + } + + static 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 (const 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 (const kmipcore::KmipException &) { + 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]() { + 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_TRUE( + attrs.has_attribute(KMIP_ATTR_NAME_NAME) || + !attrs.generic().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/kmipclient/tests/TestEnvUtils.hpp b/kmipclient/tests/TestEnvUtils.hpp new file mode 100644 index 0000000..4efdb20 --- /dev/null +++ b/kmipclient/tests/TestEnvUtils.hpp @@ -0,0 +1,16 @@ +#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/.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..4951283 --- /dev/null +++ b/kmipcore/include/kmipcore/attributes_parser.hpp @@ -0,0 +1,36 @@ +#ifndef KMIPCORE_ATTRIBUTES_PARSER_HPP +#define KMIPCORE_ATTRIBUTES_PARSER_HPP + +#include "kmipcore/kmip_attributes.hpp" +#include "kmipcore/kmip_basics.hpp" + +#include +#include + +namespace kmipcore { + + /** + * @brief Decodes raw KMIP Attribute structures into a typed @ref Attributes + * bag. + */ + class AttributesParser { + public: + AttributesParser() = default; + /** + * @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 Populated @ref Attributes bag. + */ + static Attributes + parse(const std::vector> &attributes); + }; + +} // namespace kmipcore + +#endif /* KMIPCORE_ATTRIBUTES_PARSER_HPP */ diff --git a/kmipcore/include/kmipcore/key.hpp b/kmipcore/include/kmipcore/key.hpp new file mode 100644 index 0000000..dfbb8ae --- /dev/null +++ b/kmipcore/include/kmipcore/key.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 KMIPCORE_KEY_HPP +#define KMIPCORE_KEY_HPP + +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/managed_object.hpp" + +namespace kmipcore { + + /** @brief Key object families represented by @ref Key. */ + enum class KeyType { + UNSET, + SYMMETRIC_KEY, + PUBLIC_KEY, + PRIVATE_KEY, + CERTIFICATE + }; + + /** + * Minimal crypto key representation as KMIP spec sees it. + * 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 : public ManagedObject { + public: + /** + * @brief Constructs a KMIP key object. + * @param value Raw key bytes. + * @param k_type Key family. + * @param attrs Type-safe attribute bag. + */ + explicit Key( + const std::vector &value, + KeyType k_type, + Attributes attrs = {} + ) + : ManagedObject(value, std::move(attrs)), key_type(k_type) {} + + /** @brief Constructs an empty key object. */ + Key() = default; + + Key(const Key &) = default; + Key &operator=(const Key &) = default; + Key(Key &&) noexcept = default; + Key &operator=(Key &&) noexcept = default; + + /** @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 value_.size(); } + + private: + KeyType key_type = KeyType::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..89837a9 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_attribute_names.hpp @@ -0,0 +1,70 @@ +#ifndef KMIPCORE_KMIP_ATTRIBUTE_NAMES_HPP +#define KMIPCORE_KMIP_ATTRIBUTE_NAMES_HPP + +#include + +namespace kmipcore { + + // Known KMIP attribute names used across client/core layers. + /** @brief KMIP Name attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_NAME = "Name"; + /** @brief KMIP Object Group attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_GROUP = "Object Group"; + /** @brief KMIP State attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_STATE = "State"; + /** @brief KMIP Unique Identifier attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_UNIQUE_IDENTIFIER = + "Unique Identifier"; + /** @brief Backward-compatible alternative Unique Identifier attribute name. + */ + inline constexpr std::string_view KMIP_ATTR_NAME_UNIQUE_IDENTIFIER_ALT = + "UniqueID"; // backward compatibility + /** @brief KMIP Initial Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_INITIAL_DATE = + "Initial Date"; + /** @brief KMIP Activation Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_ACTIVATION_DATE = + "Activation Date"; + /** @brief KMIP Process Start Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_PROCESS_START_DATE = + "Process Start Date"; + /** @brief KMIP Protect Stop Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_PROTECT_STOP_DATE = + "Protect Stop Date"; + /** @brief KMIP Deactivation Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_DEACTIVATION_DATE = + "Deactivation Date"; + /** @brief KMIP Destroy Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_DESTROY_DATE = + "Destroy Date"; + /** @brief KMIP Compromise Occurrence Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_COMPROMISE_OCCURRENCE_DATE = + "Compromise Occurrence Date"; + /** @brief KMIP Compromise Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_COMPROMISE_DATE = + "Compromise Date"; + /** @brief KMIP Archive Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_ARCHIVE_DATE = + "Archive Date"; + /** @brief KMIP Last Change Date attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_LAST_CHANGE_DATE = + "Last Change Date"; + /** @brief KMIP Cryptographic Algorithm attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_ALG = + "Cryptographic Algorithm"; + /** @brief KMIP Cryptographic Length attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_LEN = + "Cryptographic Length"; + /** @brief KMIP Cryptographic Usage Mask attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_CRYPTO_USAGE_MASK = + "Cryptographic Usage Mask"; + /** @brief KMIP Contact Information attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_CONTACT_INFO = + "Contact Information"; + /** @brief KMIP Operation Policy Name attribute. */ + inline constexpr std::string_view KMIP_ATTR_NAME_OPERATION_POLICY_NAME = + "Operation Policy Name"; + +} // namespace kmipcore + +#endif /* KMIPCORE_KMIP_ATTRIBUTE_NAMES_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_attributes.hpp b/kmipcore/include/kmipcore/kmip_attributes.hpp new file mode 100644 index 0000000..c76f4b6 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_attributes.hpp @@ -0,0 +1,204 @@ +/* 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_KMIP_ATTRIBUTES_HPP +#define KMIPCORE_KMIP_ATTRIBUTES_HPP + +#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 + +#endif /* KMIPCORE_KMIP_ATTRIBUTES_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_basics.hpp b/kmipcore/include/kmipcore/kmip_basics.hpp new file mode 100644 index 0000000..ed79e1e --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_basics.hpp @@ -0,0 +1,200 @@ +#ifndef KMIPCORE_KMIP_BASICS_HPP +#define KMIPCORE_KMIP_BASICS_HPP + +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +#include +#include + + +namespace kmipcore { + + // Forward declaration for SerializationBuffer + class SerializationBuffer; + + /** @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; + /** @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 + + /** @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 child_tag) const; + /** @brief Finds all children with the specified tag. */ + [[nodiscard]] std::vector> + findAll(Tag child_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 = tag::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() = default; + + /** @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 child_tag) const; + /** @brief Returns all direct children with the given tag. */ + [[nodiscard]] std::vector> + getChildren(Tag child_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 Converts value to Interval representation. */ + [[nodiscard]] uint32_t toInterval() const; + }; + + +} // namespace kmipcore + +#endif /* KMIPCORE_KMIP_BASICS_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_enums.hpp b/kmipcore/include/kmipcore/kmip_enums.hpp new file mode 100644 index 0000000..19335c8 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_enums.hpp @@ -0,0 +1,2467 @@ +/* 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. + */ + +#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 std::size_t KMIP_MAX_MESSAGE_SIZE = 8192; + /** Suggested buffer size for human-readable error messages. */ + inline constexpr std::size_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 std::int32_t KMIP_UNSET = -1; + + + 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 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 class attribute_type : std::uint32_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 class batch_error_continuation_option : std::uint32_t { + // KMIP 1.0 + KMIP_BATCH_CONTINUE = 0x01, + KMIP_BATCH_STOP = 0x02, + KMIP_BATCH_UNDO = 0x03 + }; + + enum class block_cipher_mode : std::uint32_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 class credential_type : std::uint32_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 class cryptographic_algorithm : std::uint32_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 class cryptographic_usage_mask : std::uint32_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 class digital_signature_algorithm : std::uint32_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 class encoding_option : std::uint32_t { + // KMIP 1.1 + KMIP_ENCODE_NO_ENCODING = 0x01, + KMIP_ENCODE_TTLV_ENCODING = 0x02 + }; + + enum class hashing_algorithm : std::uint32_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 class key_compression_type : std::uint32_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 class key_format_type : std::uint32_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 class key_role_type : std::uint32_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 class key_wrap_type : std::uint32_t { + // KMIP 1.4 + KMIP_WRAPTYPE_NOT_WRAPPED = 0x01, + KMIP_WRAPTYPE_AS_REGISTERED = 0x02 + }; + + + enum class mask_generator : std::uint32_t { + // KMIP 1.4 + KMIP_MASKGEN_MGF1 = 0x01 + }; + + enum class name_type : std::uint32_t { + // KMIP 1.0 + KMIP_NAME_UNINTERPRETED_TEXT_STRING = 0x01, + KMIP_NAME_URI = 0x02 + }; + + enum class object_type : std::uint32_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 class operation : std::uint32_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 class padding_method : std::uint32_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 class protection_storage_mask : std::uint32_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 class query_function : std::uint32_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 class result_reason : std::uint32_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 class result_status : std::uint32_t { + // KMIP 1.0 + KMIP_STATUS_SUCCESS = 0x00, + KMIP_STATUS_OPERATION_FAILED = 0x01, + KMIP_STATUS_OPERATION_PENDING = 0x02, + KMIP_STATUS_OPERATION_UNDONE = 0x03 + }; + + enum class state : std::uint32_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 + }; + + /** 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"; + } + } + + /** Stream formatter for KMIP lifecycle state values. */ + 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, + 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_ATTRIBUTE_REFERENCE = 0x420005, + 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 class type : std::uint32_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 class wrapping_method : std::uint32_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 class revocation_reason_type : std::uint32_t { + // KMIP 1.0 + 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 class secret_data_type : std::uint32_t { + // KMIP 1.0 + 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_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_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); + 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..f666665 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_errors.hpp @@ -0,0 +1,35 @@ +#ifndef KMIPCORE_KMIP_ERRORS_HPP +#define KMIPCORE_KMIP_ERRORS_HPP + +#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 + +#endif /* KMIPCORE_KMIP_ERRORS_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_formatter.hpp b/kmipcore/include/kmipcore/kmip_formatter.hpp new file mode 100644 index 0000000..d69b434 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_formatter.hpp @@ -0,0 +1,40 @@ +#ifndef KMIPCORE_KMIP_FORMATTER_HPP +#define KMIPCORE_KMIP_FORMATTER_HPP + +#include +#include +#include +#include + +namespace kmipcore { + + class Element; + class RequestMessage; + class ResponseMessage; + + /** @brief Formats an Element tree into a human-readable, redacted text dump. + */ + [[nodiscard]] std::string + format_element(const std::shared_ptr &element); + /** @brief Formats a RequestMessage into a human-readable, redacted text dump. + */ + [[nodiscard]] std::string format_request(const RequestMessage &request); + /** @brief Formats a ResponseMessage into a human-readable, redacted text + * dump. */ + [[nodiscard]] std::string format_response(const ResponseMessage &response); + /** @brief Parses and formats raw TTLV bytes into human-readable, redacted + * 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 + +#endif /* KMIPCORE_KMIP_FORMATTER_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_logger.hpp b/kmipcore/include/kmipcore/kmip_logger.hpp new file mode 100644 index 0000000..fa25bbf --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_logger.hpp @@ -0,0 +1,59 @@ +#ifndef KMIPCORE_KMIP_LOGGER_HPP +#define KMIPCORE_KMIP_LOGGER_HPP + +#include +#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 std::string_view 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 + +#endif /* KMIPCORE_KMIP_LOGGER_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_protocol.hpp b/kmipcore/include/kmipcore/kmip_protocol.hpp new file mode 100644 index 0000000..138e417 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_protocol.hpp @@ -0,0 +1,452 @@ +#ifndef KMIPCORE_KMIP_PROTOCOL_HPP +#define KMIPCORE_KMIP_PROTOCOL_HPP + +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" + +#include +#include +#include +#include +namespace kmipcore { + + /** + * @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 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 on-wire major version component. */ + void setMajor(int32_t major) { major_ = major; } + /** @brief Returns on-wire minor version component. */ + [[nodiscard]] int32_t getMinor() const { return minor_; } + /** @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. */ + static ProtocolVersion fromElement(std::shared_ptr element); + + private: + int32_t major_ = 1; + 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: + /** @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 (KMIP 1.4) and limits. + */ + RequestMessage(); + /** @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_; } + /** @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 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 + +#endif /* KMIPCORE_KMIP_PROTOCOL_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_requests.hpp b/kmipcore/include/kmipcore/kmip_requests.hpp new file mode 100644 index 0000000..9aee9d1 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_requests.hpp @@ -0,0 +1,200 @@ +#ifndef KMIPCORE_KMIP_REQUESTS_HPP +#define KMIPCORE_KMIP_REQUESTS_HPP + +#include "kmipcore/key.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/kmip_protocol.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(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createTextString(tag::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 selectors to retrieve. + * KMIP 1.x encodes them as Attribute Name text strings; KMIP 2.0 + * encodes them as Attribute Reference structures. + * @param legacy_attribute_names_for_v2 When true, forces legacy Attribute + * Name text-string encoding even for KMIP 2.0 (interoperability + * fallback for non-compliant servers). + */ + GetAttributesRequest( + const std::string &unique_id, + const std::vector &attribute_names, + ProtocolVersion version = {}, + bool legacy_attribute_names_for_v2 = false + ); + }; + + // 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 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, + int32_t key_bits, + cryptographic_usage_mask usage_mask = + static_cast( + KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT + ), + ProtocolVersion version = {} + ); + }; + + /** @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, + ProtocolVersion version = {} + ); + }; + + /** @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, + ProtocolVersion version = {} + ); + }; + + /** @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 std::vector &secret, + secret_data_type secret_type, + ProtocolVersion version = {} + ); + }; + + /** @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. + * @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, + ProtocolVersion version = {} + ); + }; + + /** @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, + revocation_reason_type reason, + const std::string &message, + time_t occurrence_time = 0 + ); + }; + + +} // namespace kmipcore + +#endif /* KMIPCORE_KMIP_REQUESTS_HPP */ diff --git a/kmipcore/include/kmipcore/kmip_responses.hpp b/kmipcore/include/kmipcore/kmip_responses.hpp new file mode 100644 index 0000000..af0c6f9 --- /dev/null +++ b/kmipcore/include/kmipcore/kmip_responses.hpp @@ -0,0 +1,298 @@ +#ifndef KMIPCORE_KMIP_RESPONSES_HPP +#define KMIPCORE_KMIP_RESPONSES_HPP + +#include "kmipcore/kmip_errors.hpp" +#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(tag::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_; + }; + + /** + * @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 + +#endif /* KMIPCORE_KMIP_RESPONSES_HPP */ 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/managed_object.hpp b/kmipcore/include/kmipcore/managed_object.hpp new file mode 100644 index 0000000..11712d2 --- /dev/null +++ b/kmipcore/include/kmipcore/managed_object.hpp @@ -0,0 +1,104 @@ +/* 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/response_parser.hpp b/kmipcore/include/kmipcore/response_parser.hpp new file mode 100644 index 0000000..ba2d9e1 --- /dev/null +++ b/kmipcore/include/kmipcore/response_parser.hpp @@ -0,0 +1,150 @@ +#ifndef KMIPCORE_RESPONSE_PARSER_HPP +#define KMIPCORE_RESPONSE_PARSER_HPP + +#include "kmipcore/kmip_protocol.hpp" +#include "kmipcore/kmip_responses.hpp" + +#include +#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. */ + KmipResultStatusCode resultStatus = 0; + /** KMIP result_reason code when available. */ + KmipResultReasonCode 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 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; + 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, batchItemId); + 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); + + /** 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, 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 + +#endif /* KMIPCORE_RESPONSE_PARSER_HPP */ diff --git a/kmipcore/include/kmipcore/secret.hpp b/kmipcore/include/kmipcore/secret.hpp new file mode 100644 index 0000000..d075a00 --- /dev/null +++ b/kmipcore/include/kmipcore/secret.hpp @@ -0,0 +1,96 @@ +#ifndef KMIPCORE_SECRET_HPP +#define KMIPCORE_SECRET_HPP + +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/managed_object.hpp" + +#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 : public ManagedObject { + public: + /** @brief Constructs an empty secret. */ + Secret() = default; + + /** + * @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 { + return secret_type_; + } + + /** @brief Sets KMIP secret data type discriminator. */ + void set_secret_type(secret_data_type type) noexcept { + secret_type_ = type; + } + + // ---- Typed convenience accessors ---- + + /** @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 the object lifecycle state. */ + void set_state(kmipcore::state st) noexcept { attributes_.set_state(st); } + + /** + * @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, + secret_data_type type = secret_data_type::KMIP_SECDATA_PASSWORD, + kmipcore::state st = state::KMIP_STATE_PRE_ACTIVE + ) { + Attributes attrs; + attrs.set_state(st); + return Secret{ + std::vector(text.begin(), text.end()), + type, + std::move(attrs) + }; + } + + /** @brief Returns payload interpreted as UTF-8/byte-preserving text. */ + [[nodiscard]] std::string as_text() const { + return {value_.begin(), value_.end()}; + } + + private: + secret_data_type secret_type_ = secret_data_type::KMIP_SECDATA_PASSWORD; + }; + +} // namespace kmipcore + +#endif /* KMIPCORE_SECRET_HPP */ diff --git a/kmipcore/include/kmipcore/serialization_buffer.hpp b/kmipcore/include/kmipcore/serialization_buffer.hpp new file mode 100644 index 0000000..aeb4d4d --- /dev/null +++ b/kmipcore/include/kmipcore/serialization_buffer.hpp @@ -0,0 +1,197 @@ +#ifndef KMIPCORE_SERIALIZATION_BUFFER_HPP +#define KMIPCORE_SERIALIZATION_BUFFER_HPP + +#include +#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 Raw byte view to write + */ + 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 Raw byte view to write + */ + void writePadded(std::span data); + + // ==================== 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 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 + */ + 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 + * shrink() 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 shrink(); + + 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 + +#endif /* KMIPCORE_SERIALIZATION_BUFFER_HPP */ diff --git a/kmipcore/src/attributes_parser.cpp b/kmipcore/src/attributes_parser.cpp new file mode 100644 index 0000000..65fbc34 --- /dev/null +++ b/kmipcore/src/attributes_parser.cpp @@ -0,0 +1,293 @@ +#include "kmipcore/attributes_parser.hpp" + +#include "kmipcore/kmip_attribute_names.hpp" + +#include +#include +#include +#include + +namespace kmipcore { + + namespace { + + [[nodiscard]] std::string date_to_string(int64_t seconds) { + const auto 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 bytes_to_hex(const std::vector &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 generic_name_for_tag(Tag tag_value) { + std::ostringstream oss; + oss << "Tag(0x" << std::uppercase << std::hex << std::setfill('0') + << std::setw(6) << static_cast(tag_value) << ")"; + return oss.str(); + } + + /** 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; + } + switch (value->type) { + case type::KMIP_TYPE_TEXT_STRING: + result.set(name, value->toString()); + break; + case type::KMIP_TYPE_INTEGER: + // Routes well-known names (Length, Mask) to typed setters; others go + // to generic. + result.set(name, value->toInt()); + break; + case type::KMIP_TYPE_ENUMERATION: + // 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_DATE_TIME_EXTENDED: + // Extended timestamps are sub-second and are best preserved + // numerically. + result.set(name, value->toLong()); + break; + case type::KMIP_TYPE_BOOLEAN: + result.set(name, value->toBool()); + break; + case type::KMIP_TYPE_BYTE_STRING: + case type::KMIP_TYPE_BIG_INTEGER: + result.set(name, bytes_to_hex(value->toBytes())); + break; + case type::KMIP_TYPE_INTERVAL: + result.set(name, static_cast(value->toInterval())); + break; + case type::KMIP_TYPE_STRUCTURE: + // 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()); + } else { + result.set(name, std::string("")); + } + break; + default: + break; + } + } + + /** + * @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: + // Preserve unknown or currently-unmapped KMIP 2.0 typed attributes + // instead of silently dropping them. + switch (elem->type) { + case type::KMIP_TYPE_TEXT_STRING: + result.set(generic_name_for_tag(elem->tag), elem->toString()); + break; + case type::KMIP_TYPE_INTEGER: + result.set(generic_name_for_tag(elem->tag), elem->toInt()); + break; + case type::KMIP_TYPE_ENUMERATION: + result.set(generic_name_for_tag(elem->tag), elem->toEnum()); + break; + case type::KMIP_TYPE_LONG_INTEGER: + case type::KMIP_TYPE_DATE_TIME: + case type::KMIP_TYPE_DATE_TIME_EXTENDED: + result.set(generic_name_for_tag(elem->tag), elem->toLong()); + break; + case type::KMIP_TYPE_BOOLEAN: + result.set(generic_name_for_tag(elem->tag), elem->toBool()); + break; + case type::KMIP_TYPE_BYTE_STRING: + case type::KMIP_TYPE_BIG_INTEGER: + result.set( + generic_name_for_tag(elem->tag), bytes_to_hex(elem->toBytes()) + ); + break; + case type::KMIP_TYPE_INTERVAL: + result.set( + generic_name_for_tag(elem->tag), + static_cast(elem->toInterval()) + ); + break; + case type::KMIP_TYPE_STRUCTURE: + result.set( + generic_name_for_tag(elem->tag), std::string("") + ); + break; + default: + break; + } + break; + } + } + + } // namespace + + Attributes AttributesParser::parse( + const std::vector> &attributes + ) { + Attributes result; + + for (const auto &attribute : attributes) { + 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; + } + + // ---- KMIP 2.0: typed element with a specific KMIP tag ---- + parse_v2_typed_attribute(result, attribute); + } + + return result; + } + +} // namespace kmipcore diff --git a/kmipcore/src/key_parser.cpp b/kmipcore/src/key_parser.cpp new file mode 100644 index 0000000..158ccfd --- /dev/null +++ b/kmipcore/src/key_parser.cpp @@ -0,0 +1,222 @@ +/* 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 "kmipcore/kmip_errors.hpp" + +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(tag::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(tag::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(tag::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(); + std::vector kv(raw_bytes.begin(), raw_bytes.end()); + + // Parse attributes from the key value's Attribute children. + Attributes key_attrs = AttributesParser::parse( + key_value->getChildren(tag::KMIP_TAG_ATTRIBUTE) + ); + + // 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 + + Key KeyParser::parseGetKeyResponse(const GetResponseBatchItem &item) { + 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(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" + ); + } + + auto key_format = 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( + KMIP_OBJECT_MISMATCH, "Secret data key block format mismatch" + ); + } + + 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(tag::KMIP_TAG_KEY_MATERIAL); + if (!key_material) { + throw KmipException( + KMIP_INVALID_ENCODING, "Missing secret key material." + ); + } + + auto raw_bytes = key_material->toBytes(); + + 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) { + if (payload == nullptr) { + throw KmipException(KMIP_INVALID_ENCODING, "Missing response payload."); + } + + auto object_type = payload->getChild(tag::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(tag::KMIP_TAG_KEY_BLOCK); + if (key_block) { + auto key_format = 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." + ); + } + } + } + + 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_attributes.cpp b/kmipcore/src/kmip_attributes.cpp new file mode 100644 index 0000000..f2855c4 --- /dev/null +++ b/kmipcore/src/kmip_attributes.cpp @@ -0,0 +1,428 @@ +/* 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 "kmipcore/kmip_formatter.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)] = + 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; + } + + 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_basics.cpp b/kmipcore/src/kmip_basics.cpp new file mode 100644 index 0000000..e093900 --- /dev/null +++ b/kmipcore/src/kmip_basics.cpp @@ -0,0 +1,491 @@ +#include "kmipcore/kmip_basics.hpp" + +#include "kmipcore/kmip_errors.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include + +namespace kmipcore { + + // Helper functions for big-endian + static std::uint32_t to_be32(std::uint32_t v) { + return htonl(v); + } + 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; + } + // 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]); + } + + static void validate_zero_padding( + std::span data, + std::size_t value_offset, + std::size_t value_length, + std::size_t padded_length + ) { + for (std::size_t i = value_offset + value_length; + i < value_offset + padded_length; + ++i) { + if (data[i] != 0) { + throw KmipException( + "Invalid TTLV padding: non-zero padding byte found" + ); + } + } + } + + + void Element::serialize(SerializationBuffer &buf) const { + // Write Tag (3 bytes, big-endian) + 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)); + + // First pass: calculate content and payload length + SerializationBuffer content_buf; + std::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)) { + std::uint32_t wire = + to_be32(static_cast(std::get(value).value)); + content_buf.writeBytes(std::as_bytes(std::span{&wire, 1})); + payload_length = 4; + } else if (std::holds_alternative(value)) { + std::uint64_t wire = to_be64( + static_cast(std::get(value).value) + ); + content_buf.writeBytes(std::as_bytes(std::span{&wire, 1})); + payload_length = 8; + } else if (std::holds_alternative(value)) { + const auto &v = std::get(value).value; + content_buf.writeBytes(std::as_bytes(std::span(v.data(), v.size()))); + payload_length = v.size(); + } else if (std::holds_alternative(value)) { + std::uint32_t wire = to_be32( + static_cast(std::get(value).value) + ); + content_buf.writeBytes(std::as_bytes(std::span{&wire, 1})); + payload_length = 4; + } else if (std::holds_alternative(value)) { + std::uint64_t v = std::get(value).value ? 1 : 0; + v = to_be64(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(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(std::as_bytes(std::span(v.data(), v.size()))); + payload_length = v.size(); + } else if (std::holds_alternative(value)) { + std::uint64_t wire = + to_be64(static_cast(std::get(value).value)); + content_buf.writeBytes(std::as_bytes(std::span{&wire, 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. + std::uint64_t wire = to_be64( + static_cast(std::get(value).value) + ); + content_buf.writeBytes(std::as_bytes(std::span{&wire, 1})); + payload_length = 8; + } else if (std::holds_alternative(value)) { + std::uint32_t v = std::get(value).value; + v = to_be32(v); + content_buf.writeBytes(std::as_bytes(std::span{&v, 1})); + 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(std::as_bytes(content_buf.span())); + } + + // Add padding to align to 8 bytes + 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, std::size_t &offset + ) { + if (offset + 8 > data.size()) { + throw KmipException("Buffer too short for header"); + } + + // Read Tag (3 bytes) + std::uint32_t tag = (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 = read_be_u32(data, offset + 4); + + 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. + 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? + // 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 == 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{}; + + std::size_t current_struct_offset = 0; + while (current_struct_offset < length) { + std::size_t item_offset = offset; + auto child = deserialize(struct_view, item_offset); + std::get(struct_elem->value).add(child); + std::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 Type::KMIP_TYPE_INTEGER: { + if (length != 4) { + throw KmipException("Invalid length for Integer"); + } + std::int32_t val; + 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 + std::memcpy(&val, &raw, 4); // Interpreting uint32 as int32 + elem->value = Integer{val}; + break; + } + case Type::KMIP_TYPE_LONG_INTEGER: { + if (length != 8) { + throw KmipException("Invalid length for Long Integer"); + } + std::uint64_t raw = read_be_u64(data, offset); + std::int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = LongInteger{val}; + break; + } + case Type::KMIP_TYPE_BOOLEAN: { + if (length != 8) { + throw KmipException("Invalid length for Boolean"); + } + std::uint64_t raw = read_be_u64(data, offset); + elem->value = Boolean{raw != 0}; + break; + } + case Type::KMIP_TYPE_ENUMERATION: { + if (length != 4) { + throw KmipException("Invalid length for Enumeration"); + } + std::uint32_t raw = read_be_u32(data, offset); + elem->value = Enumeration{static_cast(raw)}; + break; + } + case Type::KMIP_TYPE_TEXT_STRING: { + std::string s(reinterpret_cast(&data[offset]), length); + elem->value = TextString{s}; + break; + } + case Type::KMIP_TYPE_BYTE_STRING: { + const auto value_view = data.subspan(offset, length); + std::vector v(value_view.begin(), value_view.end()); + elem->value = ByteString{v}; + break; + } + case Type::KMIP_TYPE_DATE_TIME: { + if (length != 8) { + throw KmipException("Invalid length for DateTime"); + } + std::uint64_t raw = read_be_u64(data, offset); + std::int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = DateTime{val}; + break; + } + case Type::KMIP_TYPE_INTERVAL: { + if (length != 4) { + throw KmipException("Invalid length for Interval"); + } + std::uint32_t raw = read_be_u32(data, offset); + elem->value = Interval{raw}; + break; + } + case Type::KMIP_TYPE_BIG_INTEGER: { + const auto value_view = data.subspan(offset, length); + std::vector v(value_view.begin(), value_view.end()); + elem->value = BigInteger{v}; + break; + } + 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"); + } + std::uint64_t raw = read_be_u64(data, offset); + std::int64_t val; + std::memcpy(&val, &raw, 8); + elem->value = DateTimeExtended{val}; + break; + } + default: + throw KmipException( + "Unknown type " + std::to_string(static_cast(type)) + ); + } + + validate_zero_padding(data, offset, length, padded_length); + offset += padded_length; + return elem; + } + } + + // Factory methods + std::shared_ptr Element::createStructure(Tag t) { + return std::make_shared( + t, static_cast(KMIP_TYPE_STRUCTURE), Structure{} + ); + } + 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, std::int64_t v) { + return std::make_shared( + t, static_cast(KMIP_TYPE_LONG_INTEGER), LongInteger{v} + ); + } + std::shared_ptr Element::createBoolean(Tag t, bool v) { + return std::make_shared( + t, static_cast(KMIP_TYPE_BOOLEAN), Boolean{v} + ); + } + std::shared_ptr Element::createEnumeration(Tag t, std::int32_t v) { + return std::make_shared( + t, static_cast(KMIP_TYPE_ENUMERATION), Enumeration{v} + ); + } + std::shared_ptr + Element::createTextString(Tag t, const std::string &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, static_cast(KMIP_TYPE_BYTE_STRING), ByteString{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, std::int64_t v) { + return std::make_shared( + t, static_cast(KMIP_TYPE_DATE_TIME_EXTENDED), DateTimeExtended{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, static_cast(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 child_tag) const { + for (const auto &item : items) { + if (item->tag == child_tag) { + return item; + } + } + return nullptr; + } + + std::vector> + Structure::findAll(Tag child_tag) const { + std::vector> matches; + for (const auto &item : items) { + if (item->tag == child_tag) { + matches.push_back(item); + } + } + return matches; + } + + std::shared_ptr Element::getChild(Tag child_tag) const { + const auto *s = std::get_if(&value); + if (!s) { + return nullptr; + } + return s->find(child_tag); + } + + std::vector> + Element::getChildren(Tag child_tag) const { + const auto *s = std::get_if(&value); + if (!s) { + return {}; + } + return s->findAll(child_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"); + } + + 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_errors.cpp b/kmipcore/src/kmip_errors.cpp new file mode 100644 index 0000000..690fcb8 --- /dev/null +++ b/kmipcore/src/kmip_errors.cpp @@ -0,0 +1,478 @@ +#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 new file mode 100644 index 0000000..62eecc5 --- /dev/null +++ b/kmipcore/src/kmip_formatter.cpp @@ -0,0 +1,662 @@ +#include "kmipcore/kmip_formatter.hpp" + +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_enums.hpp" +#include "kmipcore/kmip_protocol.hpp" + +#include +#include +#include +#include +#include + +namespace kmipcore { + + namespace { + + [[nodiscard]] std::string indent(size_t level) { + std::string s(level * 2, ' '); + return s; + } + + [[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'); + 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(); + } + + [[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) { + const auto 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 (static_cast(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]] bool is_sensitive_tag(Tag tag) { + switch (tag) { + case Tag::KMIP_TAG_CREDENTIAL_VALUE: + case Tag::KMIP_TAG_IV_COUNTER_NONCE: + case Tag::KMIP_TAG_KEY_MATERIAL: + case Tag::KMIP_TAG_KEY_VALUE: + case Tag::KMIP_TAG_MAC_SIGNATURE: + case Tag::KMIP_TAG_NONCE_VALUE: + case Tag::KMIP_TAG_PASSWORD: + case Tag::KMIP_TAG_SALT: + case Tag::KMIP_TAG_SECRET_DATA: + case Tag::KMIP_TAG_USERNAME: + return true; + default: + return false; + } + } + + [[nodiscard]] bool should_redact_subtree(const Element &element) { + if (!is_sensitive_tag(element.tag)) { + return false; + } + return element.type == Type::KMIP_TYPE_STRUCTURE; + } + + [[nodiscard]] std::optional + redacted_value_length(const Element &element) { + switch (static_cast(element.type)) { + case KMIP_TYPE_BIG_INTEGER: + case KMIP_TYPE_BYTE_STRING: + return element.toBytes().size(); + case KMIP_TYPE_TEXT_STRING: + return element.toString().size(); + default: + return std::nullopt; + } + } + + [[nodiscard]] std::string redacted_value_summary(const Element &element) { + std::ostringstream oss; + oss << "(value)) { + case secret_data_type::KMIP_SECDATA_PASSWORD: + return "Password"; + case secret_data_type::KMIP_SECDATA_SEED: + return "Seed"; + default: + return nullptr; + } + } + + [[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 revocation_reason_type::KMIP_REVOKE_KEY_COMPROMISE: + return "KeyCompromise"; + case revocation_reason_type::KMIP_REVOKE_CA_COMPROMISE: + return "CACompromise"; + case revocation_reason_type::KMIP_REVOKE_AFFILIATION_CHANGED: + return "AffiliationChanged"; + case revocation_reason_type::KMIP_REVOKE_SUSPENDED: + return "Suspended"; + case revocation_reason_type::KMIP_REVOKE_CESSATION_OF_OPERATION: + return "CessationOfOperation"; + case revocation_reason_type::KMIP_REVOKE_PRIVILEDGE_WITHDRAWN: + return "PrivilegeWithdrawn"; + case revocation_reason_type::KMIP_REVOKE_EXTENSIONS: + return "Extensions"; + default: + return nullptr; + } + } + + [[nodiscard]] std::string enum_value_name(Tag tag, std::int32_t value) { + const char *name = nullptr; + switch (tag) { + case Tag::KMIP_TAG_OPERATION: + name = operation_name(value); + break; + case Tag::KMIP_TAG_OBJECT_TYPE: + name = object_type_name(value); + break; + case Tag::KMIP_TAG_RESULT_STATUS: + name = result_status_name(value); + break; + case Tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM: + name = crypto_algorithm_name(value); + break; + case Tag::KMIP_TAG_NAME_TYPE: + name = name_type_name(value); + break; + case Tag::KMIP_TAG_KEY_FORMAT_TYPE: + name = key_format_type_name(value); + break; + case Tag::KMIP_TAG_SECRET_DATA_TYPE: + name = secret_data_type_name(value); + break; + case Tag::KMIP_TAG_STATE: + name = state_to_string(static_cast(value)); + break; + case Tag::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, + std::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 (should_redact_subtree(*element)) { + oss << " = \n"; + return; + } + + if (is_sensitive_tag(element->tag)) { + oss << " = " << redacted_value_summary(*element) << '\n'; + return; + } + + 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 (static_cast(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->toInterval(); + 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 safely: " << e.what() + << ". Raw payload omitted to avoid leaking sensitive data.\n"; + return oss.str(); + } + } + + 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_payloads.cpp b/kmipcore/src/kmip_payloads.cpp new file mode 100644 index 0000000..d14df8d --- /dev/null +++ b/kmipcore/src/kmip_payloads.cpp @@ -0,0 +1,133 @@ +#include "kmipcore/kmip_errors.hpp" +#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(tag::KMIP_TAG_ATTRIBUTE); + + structure->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, name_) + ); + // Assuming simple TextString value for now. Real world is complex. + structure->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_VALUE, value_) + ); + + return structure; + } + + Attribute Attribute::fromElement(std::shared_ptr element) { + if (!element || element->tag != tag::KMIP_TAG_ATTRIBUTE) { + throw KmipException("Invalid Attribute element"); + } + + Attribute attr; + auto name = element->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME); + if (name) { + attr.name_ = name->toString(); + } + + auto val = element->getChild(tag::KMIP_TAG_ATTRIBUTE_VALUE); + if (val) { + attr.value_ = val->toString(); + } + + return attr; + } + + // === LocateRequestPayload === + + std::shared_ptr LocateRequestPayload::toElement() const { + auto structure = Element::createStructure( + 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. + + if (maximumItems_ > 0) { + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_MAXIMUM_ITEMS, maximumItems_) + ); + } + + if (offsetItems_ > 0) { + structure->asStructure()->add( + Element::createInteger(tag::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 == tag::KMIP_TAG_MAXIMUM_ITEMS) { + req.maximumItems_ = child->toInt(); + } else if (child->tag == tag::KMIP_TAG_OFFSET_ITEMS) { + req.offsetItems_ = child->toInt(); + } else if (child->tag == tag::KMIP_TAG_ATTRIBUTE) { + req.attributes_.push_back(Attribute::fromElement(child)); + } + } + return req; + } + + // === LocateResponsePayload === + + std::shared_ptr LocateResponsePayload::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + + if (locatedItems_) { + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, *locatedItems_) + ); + } + + for (const auto &id : uniqueIdentifiers_) { + // Each ID is TextString with tag UNIQUE_IDENTIFIER + structure->asStructure()->add( + Element::createTextString(tag::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 == tag::KMIP_TAG_LOCATED_ITEMS) { + resp.setLocatedItems(child->toInt()); + } else if (child->tag == tag::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..0f3fa07 --- /dev/null +++ b/kmipcore/src/kmip_protocol.cpp @@ -0,0 +1,508 @@ +#include "kmipcore/kmip_protocol.hpp" + +#include "kmipcore/kmip_errors.hpp" +#include "kmipcore/serialization_buffer.hpp" + +#include +#include +#include +#include +namespace kmipcore { + + namespace { + + [[nodiscard]] bool + supports_date_time_extended(const ProtocolVersion &version) { + return version.is_at_least(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); + } + } + } + + [[nodiscard]] std::vector + encode_batch_item_id(std::uint32_t id) { + return { + static_cast((id >> 24) & 0xFF), + static_cast((id >> 16) & 0xFF), + static_cast((id >> 8) & 0xFF), + static_cast(id & 0xFF) + }; + } + + [[nodiscard]] bool decode_batch_item_id( + std::span encoded, std::uint32_t &decoded + ) { + if (encoded.empty() || encoded.size() > sizeof(decoded)) { + return false; + } + + decoded = 0; + for (const auto byte : encoded) { + decoded = (decoded << 8) | static_cast(byte); + } + return true; + } + + } // namespace + + // === ProtocolVersion === + std::shared_ptr ProtocolVersion::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_PROTOCOL_VERSION); + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_PROTOCOL_VERSION_MAJOR, major_) + ); + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_PROTOCOL_VERSION_MINOR, minor_) + ); + return structure; + } + ProtocolVersion + ProtocolVersion::fromElement(std::shared_ptr element) { + if (!element || element->tag != tag::KMIP_TAG_PROTOCOL_VERSION || + element->type != Type::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ProtocolVersion element"); + } + ProtocolVersion pv; + auto maj = element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION_MAJOR); + if (maj) { + pv.major_ = maj->toInt(); + } + auto min = element->getChild(tag::KMIP_TAG_PROTOCOL_VERSION_MINOR); + if (min) { + pv.minor_ = min->toInt(); + } + return pv; + } + // === RequestHeader === + std::shared_ptr RequestHeader::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_REQUEST_HEADER); + structure->asStructure()->add(protocolVersion_.toElement()); + if (maximumResponseSize_) { + structure->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_MAXIMUM_RESPONSE_SIZE, *maximumResponseSize_ + ) + ); + } + if (batchOrderOption_) { + structure->asStructure()->add( + Element::createBoolean( + tag::KMIP_TAG_BATCH_ORDER_OPTION, *batchOrderOption_ + ) + ); + } + if (timeStamp_) { + structure->asStructure()->add( + Element::createDateTime(tag::KMIP_TAG_TIME_STAMP, *timeStamp_) + ); + } + if (userName_ || password_) { + auto authentication = + Element::createStructure(tag::KMIP_TAG_AUTHENTICATION); + auto credential = Element::createStructure(tag::KMIP_TAG_CREDENTIAL); + credential->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_CREDENTIAL_TYPE, KMIP_CRED_USERNAME_AND_PASSWORD + ) + ); + + auto credential_value = + Element::createStructure(tag::KMIP_TAG_CREDENTIAL_VALUE); + if (userName_) { + credential_value->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_USERNAME, *userName_) + ); + } + if (password_) { + credential_value->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_PASSWORD, *password_) + ); + } + + credential->asStructure()->add(credential_value); + authentication->asStructure()->add(credential); + structure->asStructure()->add(authentication); + } + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_BATCH_COUNT, batchCount_) + ); + return structure; + } + RequestHeader RequestHeader::fromElement(std::shared_ptr element) { + 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(tag::KMIP_TAG_PROTOCOL_VERSION); + if (pv) { + rh.protocolVersion_ = ProtocolVersion::fromElement(pv); + } else { + throw KmipException("Missing ProtocolVersion in header"); + } + auto maxResponseSize = + element->getChild(tag::KMIP_TAG_MAXIMUM_RESPONSE_SIZE); + if (maxResponseSize) { + rh.maximumResponseSize_ = maxResponseSize->toInt(); + } + auto timeStamp = element->getChild(tag::KMIP_TAG_TIME_STAMP); + if (timeStamp) { + rh.timeStamp_ = timeStamp->toLong(); + } + auto batchOrderOption = element->getChild(tag::KMIP_TAG_BATCH_ORDER_OPTION); + if (batchOrderOption) { + rh.batchOrderOption_ = batchOrderOption->toBool(); + } + auto authentication = element->getChild(tag::KMIP_TAG_AUTHENTICATION); + if (authentication) { + auto credential = authentication->getChild(tag::KMIP_TAG_CREDENTIAL); + if (!credential) { + throw KmipException("Missing Credential in Authentication"); + } + + auto credentialType = credential->getChild(tag::KMIP_TAG_CREDENTIAL_TYPE); + auto credentialValue = + 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(tag::KMIP_TAG_USERNAME); + if (userName) { + rh.userName_ = userName->toString(); + } + + auto password = credentialValue->getChild(tag::KMIP_TAG_PASSWORD); + if (password) { + rh.password_ = password->toString(); + } + } + } + auto bc = element->getChild(tag::KMIP_TAG_BATCH_COUNT); + if (bc) { + rh.batchCount_ = bc->toInt(); + } + return rh; + } + // === RequestBatchItem === + std::shared_ptr RequestBatchItem::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); + structure->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, operation_) + ); + if (uniqueBatchItemId_ != 0) { + structure->asStructure()->add( + Element::createByteString( + tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID, + encode_batch_item_id(uniqueBatchItemId_) + ) + ); + } + if (requestPayload_) { + structure->asStructure()->add(requestPayload_); + } + return structure; + } + RequestBatchItem + RequestBatchItem::fromElement(std::shared_ptr element) { + 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(tag::KMIP_TAG_OPERATION); + if (op) { + rbi.operation_ = op->toEnum(); + } else { + throw KmipException("Missing Operation"); + } + auto id = element->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); + if (id) { + auto bytes = id->toBytes(); + std::uint32_t decoded_id = 0; + if (decode_batch_item_id(bytes, decoded_id)) { + rbi.uniqueBatchItemId_ = decoded_id; + } + } + auto payload = element->getChild(tag::KMIP_TAG_REQUEST_PAYLOAD); + if (payload) { + rbi.requestPayload_ = payload; + } + return rbi; + } + // === RequestMessage === + RequestMessage::RequestMessage() + : RequestMessage(KMIP_VERSION_1_4, DEFAULT_MAX_RESPONSE_SIZE) {} + + RequestMessage::RequestMessage(ProtocolVersion version) + : RequestMessage(std::move(version), DEFAULT_MAX_RESPONSE_SIZE) {} + + RequestMessage::RequestMessage( + ProtocolVersion version, size_t maxResponseSize + ) { + header_.setProtocolVersion(std::move(version)); + 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::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(tag::KMIP_TAG_REQUEST_MESSAGE); + structure->asStructure()->add(header_.toElement()); + 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) { + if (!element || element->tag != tag::KMIP_TAG_REQUEST_MESSAGE || + element->type != Type::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid RequestMessage element"); + } + RequestMessage rm; + auto hdr = element->getChild(tag::KMIP_TAG_REQUEST_HEADER); + if (hdr) { + rm.header_ = RequestHeader::fromElement(hdr); + } 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) { + 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(tag::KMIP_TAG_RESPONSE_HEADER); + structure->asStructure()->add(protocolVersion_.toElement()); + structure->asStructure()->add( + Element::createDateTime(tag::KMIP_TAG_TIME_STAMP, timeStamp_) + ); + structure->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_BATCH_COUNT, batchCount_) + ); + return structure; + } + ResponseHeader ResponseHeader::fromElement(std::shared_ptr element) { + if (!element || element->tag != tag::KMIP_TAG_RESPONSE_HEADER || + element->type != Type::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ResponseHeader element"); + } + ResponseHeader rh; + 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(tag::KMIP_TAG_TIME_STAMP); + if (ts) { + rh.timeStamp_ = ts->toLong(); + } + auto bc = element->getChild(tag::KMIP_TAG_BATCH_COUNT); + if (bc) { + rh.batchCount_ = bc->toInt(); + } + return rh; + } + // === ResponseBatchItem === + std::shared_ptr ResponseBatchItem::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_BATCH_ITEM); + structure->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_OPERATION, operation_) + ); + if (uniqueBatchItemId_ != 0) { + structure->asStructure()->add( + Element::createByteString( + tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID, + encode_batch_item_id(uniqueBatchItemId_) + ) + ); + } + structure->asStructure()->add( + Element::createEnumeration(tag::KMIP_TAG_RESULT_STATUS, resultStatus_) + ); + if (resultReason_) { + structure->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_RESULT_REASON, *resultReason_ + ) + ); + } + if (resultMessage_) { + structure->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_RESULT_MESSAGE, *resultMessage_ + ) + ); + } + if (responsePayload_) { + structure->asStructure()->add(responsePayload_); + } + return structure; + } + ResponseBatchItem + ResponseBatchItem::fromElement(std::shared_ptr element) { + 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(tag::KMIP_TAG_OPERATION); + if (op) { + rbi.operation_ = op->toEnum(); + } + // 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) { + auto bytes = id->toBytes(); + std::uint32_t decoded_id = 0; + if (decode_batch_item_id(bytes, decoded_id)) { + rbi.uniqueBatchItemId_ = decoded_id; + } + } + auto status = element->getChild(tag::KMIP_TAG_RESULT_STATUS); + if (status) { + rbi.resultStatus_ = status->toEnum(); + } else { + throw KmipException("Missing Result Status"); + } + auto reason = element->getChild(tag::KMIP_TAG_RESULT_REASON); + if (reason) { + rbi.resultReason_ = reason->toEnum(); + } + if (rbi.resultStatus_ == KMIP_STATUS_OPERATION_FAILED && + !rbi.resultReason_.has_value()) { + throw KmipException( + "Missing Result Reason for failed response batch item" + ); + } + auto msg = element->getChild(tag::KMIP_TAG_RESULT_MESSAGE); + if (msg) { + rbi.resultMessage_ = msg->toString(); + } + auto payload = element->getChild(tag::KMIP_TAG_RESPONSE_PAYLOAD); + if (payload) { + rbi.responsePayload_ = payload; + } + return rbi; + } + // === ResponseMessage === + std::shared_ptr ResponseMessage::toElement() const { + auto structure = Element::createStructure(tag::KMIP_TAG_RESPONSE_MESSAGE); + structure->asStructure()->add(header_.toElement()); + for (const auto &item : batchItems_) { + structure->asStructure()->add(item.toElement()); + } + validate_element_types_for_version(structure, header_.getProtocolVersion()); + return structure; + } + ResponseMessage + ResponseMessage::fromElement(std::shared_ptr element) { + if (!element || element->tag != tag::KMIP_TAG_RESPONSE_MESSAGE || + element->type != Type::KMIP_TYPE_STRUCTURE) { + throw KmipException("Invalid ResponseMessage element"); + } + ResponseMessage rm; + auto hdr = element->getChild(tag::KMIP_TAG_RESPONSE_HEADER); + if (hdr) { + rm.header_ = ResponseHeader::fromElement(hdr); + } 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) { + 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/src/kmip_requests.cpp b/kmipcore/src/kmip_requests.cpp new file mode 100644 index 0000000..17fa94e --- /dev/null +++ b/kmipcore/src/kmip_requests.cpp @@ -0,0 +1,831 @@ +#include "kmipcore/kmip_requests.hpp" + +#include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/kmip_errors.hpp" + +#include + +namespace kmipcore { + + namespace detail { + [[nodiscard]] bool + use_attributes_container(const ProtocolVersion &version) { + 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 + ) { + auto attribute = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + attribute->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name + ) + ); + auto attribute_value = + Element::createTextString(tag::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(tag::KMIP_TAG_ATTRIBUTE); + attribute->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name + ) + ); + auto attribute_value = + Element::createEnumeration(tag::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(tag::KMIP_TAG_ATTRIBUTE); + attribute->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, attribute_name + ) + ); + auto attribute_value = + Element::createInteger(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(tag::KMIP_TAG_ATTRIBUTE_VALUE); + attribute_value->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_NAME_VALUE, value) + ); + attribute_value->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_NAME_TYPE, KMIP_NAME_UNINTERPRETED_TEXT_STRING + ) + ); + auto attribute = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + attribute->asStructure()->add( + Element::createTextString(tag::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(tag::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(tag::KMIP_TAG_KEY_VALUE); + key_value->asStructure()->add( + Element::createByteString( + tag::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(tag::KMIP_TAG_KEY_BLOCK); + key_block->asStructure()->add( + Element::createEnumeration( + 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( + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, *algorithm + ) + ); + } + if (cryptographic_length) { + key_block->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH, *cryptographic_length + ) + ); + } + 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_alg, key_len) + ); + 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_alg, key_len + )); + 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_alg, key_len) + ); + 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 = + Element::createStructure(tag::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 std::vector &secret, secret_data_type secret_type + ) { + auto secret_data = Element::createStructure(tag::KMIP_TAG_SECRET_DATA); + secret_data->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_SECRET_DATA_TYPE, static_cast(secret_type) + ) + ); + secret_data->asStructure()->add(make_key_block( + KMIP_KEYFORMAT_OPAQUE, secret, std::nullopt, std::nullopt + )); + 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 + + GetAttributesRequest::GetAttributesRequest( + const std::string &unique_id, + const std::vector &attribute_names, + ProtocolVersion version, + bool legacy_attribute_names_for_v2 + ) { + 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) + ); + + if (detail::use_attributes_container(version) && + !legacy_attribute_names_for_v2) { + // KMIP 2.0: Get Attributes selectors are Attribute Reference structures. + for (const auto &attr_name : attribute_names) { + payload->asStructure()->add( + detail::make_v2_attribute_reference(attr_name) + ); + } + } else { + // KMIP 1.x and compatibility fallback: selectors as Attribute Name text + // strings. + for (const auto &attr_name : attribute_names) { + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, attr_name) + ); + } + } + + setRequestPayload(payload); + } + + // --------------------------------------------------------------------------- + // CreateSymmetricKeyRequest + // --------------------------------------------------------------------------- + CreateSymmetricKeyRequest::CreateSymmetricKeyRequest( + const std::string &name, + const std::string &group, + int32_t key_bits, + cryptographic_usage_mask usage_mask, + ProtocolVersion version + ) { + setOperation(KMIP_OP_CREATE); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + + 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); + } + + // --------------------------------------------------------------------------- + // RegisterSymmetricKeyRequest + // --------------------------------------------------------------------------- + RegisterSymmetricKeyRequest::RegisterSymmetricKeyRequest( + const std::string &name, + const std::string &group, + const std::vector &key_value, + ProtocolVersion version + ) { + setOperation(KMIP_OP_REGISTER); + + const int32_t key_bits = static_cast(key_value.size() * 8); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + + 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 + ) + ); + } else { + 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", + 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) + ); + } + 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( + tag::KMIP_TAG_OBJECT_TYPE, + detail::object_type_from_key_type(key.type()) + ) + ); + + 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); + } + + // --------------------------------------------------------------------------- + // RegisterSecretRequest + // --------------------------------------------------------------------------- + RegisterSecretRequest::RegisterSecretRequest( + const std::string &name, + const std::string &group, + const std::vector &secret, + secret_data_type secret_type, + ProtocolVersion version + ) { + setOperation(KMIP_OP_REGISTER); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA + ) + ); + + 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); + } + + // --------------------------------------------------------------------------- + // LocateRequest + // --------------------------------------------------------------------------- + LocateRequest::LocateRequest( + bool locate_by_group, + const std::string &name, + object_type obj_type, + size_t max_items, + size_t offset, + ProtocolVersion version + ) { + setOperation(KMIP_OP_LOCATE); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + if (max_items > 0) { + payload->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_MAXIMUM_ITEMS, static_cast(max_items) + ) + ); + } + if (offset > 0) { + payload->asStructure()->add( + Element::createInteger( + tag::KMIP_TAG_OFFSET_ITEMS, static_cast(offset) + ) + ); + } + + 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); + } + + // --------------------------------------------------------------------------- + // RevokeRequest + // --------------------------------------------------------------------------- + RevokeRequest::RevokeRequest( + const std::string &unique_id, + revocation_reason_type reason, + const std::string &message, + time_t occurrence_time + ) { + setOperation(KMIP_OP_REVOKE); + + auto payload = Element::createStructure(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, unique_id) + ); + + auto revocation_reason = + Element::createStructure(tag::KMIP_TAG_REVOCATION_REASON); + revocation_reason->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_REVOCATION_REASON_CODE, static_cast(reason) + ) + ); + if (!message.empty()) { + revocation_reason->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_REVOKATION_MESSAGE, message) + ); + } + payload->asStructure()->add(revocation_reason); + + if (occurrence_time > 0) { + payload->asStructure()->add( + Element::createDateTime( + tag::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..4a330f3 --- /dev/null +++ b/kmipcore/src/kmip_responses.cpp @@ -0,0 +1,321 @@ +#include "kmipcore/kmip_responses.hpp" + +#include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/kmip_errors.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(tag::KMIP_TAG_SYMMETRIC_KEY); + case KMIP_OBJTYPE_SECRET_DATA: + return payload->getChild(tag::KMIP_TAG_SECRET_DATA); + case KMIP_OBJTYPE_PRIVATE_KEY: + return payload->getChild(tag::KMIP_TAG_PRIVATE_KEY); + case KMIP_OBJTYPE_PUBLIC_KEY: + return payload->getChild(tag::KMIP_TAG_PUBLIC_KEY); + default: + return {}; + } + } + + [[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 --- + + 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(tag::KMIP_TAG_UNIQUE_IDENTIFIER); + if (!uniqueIdentifier) { + throw KmipException( + "GetResponseBatchItem: missing unique identifier in response payload" + ); + } + result.uniqueIdentifier_ = uniqueIdentifier->toString(); + + auto objectType = payload->getChild(tag::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" + ); + + // 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; + } + + // --- 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(tag::KMIP_TAG_ATTRIBUTE_NAME)) { + 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; + } + + // --- 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; + } + + // --- 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 new file mode 100644 index 0000000..a2c9a9e --- /dev/null +++ b/kmipcore/src/response_parser.cpp @@ -0,0 +1,202 @@ +#include "kmipcore/response_parser.hpp" + +#include "kmipcore/kmip_errors.hpp" + +#include + +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()) {} + + 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(); + } + + bool ResponseParser::isSuccess(int itemIdx) { + return getResponseItem(itemIdx).getResultStatus() == KMIP_STATUS_SUCCESS; + } + + OperationResult ResponseParser::getOperationResult(int itemIdx) { + const auto &item = getResponseItem(itemIdx); + return OperationResult{ + effectiveOperation(item), + item.getResultStatus(), + get_result_reason_or_default(item), + item.getResultMessage().value_or("") + }; + } + + OperationResult + ResponseParser::getOperationResultByBatchItemId(uint32_t batchItemId) { + const auto &item = getResponseItemByBatchItemId(batchItemId); + return OperationResult{ + effectiveOperation(item, batchItemId), + item.getResultStatus(), + get_result_reason_or_default(item), + 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(); + + // 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, + std::optional requestedBatchItemId + ) { + if (item.getResultStatus() != KMIP_STATUS_SUCCESS) { + const KmipResultReasonCode reason = get_result_reason_or_default(item); + throw KmipException( + reason, + formatOperationResult( + item, effectiveOperation(item, requestedBatchItemId) + ) + ); + } + } + + std::string ResponseParser::formatOperationResult( + const ResponseBatchItem &value, int32_t operation + ) { + OperationResult result = { + operation, + value.getResultStatus(), + get_result_reason_or_default(value), + value.getResultMessage().value_or("") + }; + + std::ostringstream stream; + stream << "Message: " << result.resultMessage + << "\nOperation: " << operationToString(result.operation) + << "; Result status: " << resultStatusToString(result.resultStatus) + << "; Result reason: " + << kmip_category().message(result.resultReason) << " (" + << result.resultReason << ")"; + return stream.str(); + } + + const char *ResponseParser::operationToString(int32_t operation) { + return kmipcore::operation_to_string(operation); + } + + 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..ae71a87 --- /dev/null +++ b/kmipcore/src/serialization_buffer.cpp @@ -0,0 +1,131 @@ +#include "kmipcore/serialization_buffer.hpp" + +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.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(std::span data) { + if (data.empty()) { + return; + } + + const size_t length = data.size(); + + 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.data(), length); + current_offset_ += length; + } + + void SerializationBuffer::writePadded(std::span data) { + const size_t length = data.size(); + + // Write data first + 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 + 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 shrink(). + buffer_.clear(); // size -> 0, capacity unchanged + current_offset_ = 0; + + return result; // NRVO / move + } + + 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_); + 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..8779e78 --- /dev/null +++ b/kmipcore/tests/test_core.cpp @@ -0,0 +1,804 @@ +#include +#include +#include +#include +#include +#include +#include +#include +using namespace kmipcore; +void test_integer() { + auto elem = Element::createInteger(tag::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 == 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(tag::KMIP_TAG_APPLICATION_DATA); + auto child1 = Element::createInteger(tag::KMIP_TAG_APPLICATION_NAMESPACE, 10); + auto child2 = Element::createBoolean( + tag::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 == 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 == tag::KMIP_TAG_APPLICATION_NAMESPACE); + assert(std::get(d1->value).value == 10); + auto d2 = s.items[1]; + assert(d2->tag == tag::KMIP_TAG_APPLICATION_SPECIFIC_INFORMATION); + 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_non_zero_padding_is_rejected() { + // Text String with declared length 1, but padding bytes must be zero. + const std::vector invalid = { + 0x42, + 0x00, + 0x3D, + static_cast(KMIP_TYPE_TEXT_STRING), + 0x00, + 0x00, + 0x00, + 0x01, + static_cast('A'), + 0xFF, + 0x00, + 0x00, + 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 << "Non-zero padding validation 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_VERSION_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(4); // wire minor for 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); + 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(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload->asStructure()->add( + Element::createInteger(tag::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(tag::KMIP_TAG_REQUEST_PAYLOAD); + payload2->asStructure()->add( + Element::createInteger(tag::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 encoded_batch_items = + deserialized->getChildren(tag::KMIP_TAG_BATCH_ITEM); + assert(encoded_batch_items.size() == 2); + auto first_encoded_id = + encoded_batch_items[0]->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); + auto second_encoded_id = + encoded_batch_items[1]->getChild(tag::KMIP_TAG_UNIQUE_BATCH_ITEM_ID); + assert(first_encoded_id != nullptr); + assert(second_encoded_id != nullptr); + const auto first_id_bytes = first_encoded_id->toBytes(); + const auto second_id_bytes = second_encoded_id->toBytes(); + assert(first_id_bytes.size() == 4); + assert(second_id_bytes.size() == 4); + assert( + first_id_bytes[0] == 0x00 && first_id_bytes[1] == 0x00 && + first_id_bytes[2] == 0x00 && first_id_bytes[3] == 0x01 + ); + assert( + second_id_bytes[0] == 0x00 && second_id_bytes[1] == 0x00 && + second_id_bytes[2] == 0x00 && second_id_bytes[3] == 0x02 + ); + + 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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + get_payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-get-1") + ); + get_payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + auto symmetric_key = Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); + auto key_block = Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); + key_block->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_RAW + ) + ); + auto key_value = Element::createStructure(tag::KMIP_TAG_KEY_VALUE); + key_value->asStructure()->add( + Element::createByteString( + tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + locate_payload->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) + ); + locate_payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "id-locate-1") + ); + locate_payload->asStructure()->add( + Element::createTextString(tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + create_payload->asStructure()->add( + Element::createTextString(tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + get_payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "get-id") + ); + get_payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA + ) + ); + auto secret_data = Element::createStructure(tag::KMIP_TAG_SECRET_DATA); + auto key_block = Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); + auto key_value = Element::createStructure(tag::KMIP_TAG_KEY_VALUE); + key_value->asStructure()->add( + Element::createByteString(tag::KMIP_TAG_KEY_MATERIAL, {0x61, 0x62}) + ); + key_block->asStructure()->add(key_value); + secret_data->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_SECRET_DATA_TYPE, + static_cast(secret_data_type::KMIP_SECDATA_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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + auto attribute = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + attribute->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "State") + ); + attribute->asStructure()->add( + Element::createEnumeration( + tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + attribute_list_payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Name") + ); + attribute_list_payload->asStructure()->add( + Element::createTextString(tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + locate_payload->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) + ); + locate_payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "loc-1") + ); + locate_payload->asStructure()->add( + Element::createTextString(tag::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); + + 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); + auto destroy_payload = + Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + destroy_payload->asStructure()->add( + Element::createTextString(tag::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(tag::KMIP_TAG_RESPONSE_MESSAGE); + 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); + } + + { + // 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 Result Status must be rejected. + 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(1); + 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) + ); + response_message->asStructure()->add(batch_item); + + bool threw = false; + try { + (void) ResponseMessage::fromElement(response_message); + } catch (const KmipException &) { + threw = true; + } + assert(threw); + } + + { + // Result Reason is required when Result Status is Operation Failed. + 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(1); + 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_OPERATION_FAILED + ) + ); + 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 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); + + 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(tag::KMIP_TAG_BATCH_ITEM); + batch_item->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_RESULT_STATUS, KMIP_STATUS_SUCCESS + ) + ); + response_message->asStructure()->add(batch_item); + + // 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; +} + +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(tag::KMIP_TAG_AUTHENTICATION); + assert(auth != nullptr); + + auto credential = auth->getChild(tag::KMIP_TAG_CREDENTIAL); + assert(credential != nullptr); + + auto 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(tag::KMIP_TAG_CREDENTIAL_VALUE); + assert(credential_value != nullptr); + + auto username = credential_value->getChild(tag::KMIP_TAG_USERNAME); + assert(username != nullptr); + assert(username->toString() == "alice"); + + auto password = credential_value->getChild(tag::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_date_time_extended_round_trip(); + test_date_time_extended_invalid_length(); + test_non_zero_padding_is_rejected(); + 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(); + 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..aecece0 --- /dev/null +++ b/kmipcore/tests/test_parsers.cpp @@ -0,0 +1,916 @@ +#include "kmipcore/attributes_parser.hpp" +#include "kmipcore/key_parser.hpp" +#include "kmipcore/kmip_attribute_names.hpp" +#include "kmipcore/kmip_basics.hpp" +#include "kmipcore/kmip_errors.hpp" +#include "kmipcore/kmip_formatter.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 +#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 + +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); + resp.getHeader().getProtocolVersion().setMinor(4); // wire minor for KMIP 1.4 + resp.getHeader().setTimeStamp(1234567890); + resp.getHeader().setBatchCount(1); + + ResponseBatchItem item; + item.setUniqueBatchItemId(1); + item.setOperation(operation); + item.setResultStatus(result_status); + item.setResultReason(result_reason); + item.setResultMessage(result_message); + if (payload) { + item.setResponsePayload(std::move(payload)); + } + + resp.add_batch_item(item); + SerializationBuffer buf; + resp.toElement()->serialize(buf); + 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; +} + +// 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); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + payload->asStructure()->add( + Element::createTextString(tag::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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) + ); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-1") + ); + payload->asStructure()->add( + Element::createTextString(tag::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_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 + auto payload = Element::createStructure(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "key-id") + ); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SYMMETRIC_KEY + ) + ); + + auto symmetric_key = Element::createStructure(tag::KMIP_TAG_SYMMETRIC_KEY); + auto key_block = Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); + + key_block->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_RAW + ) + ); + key_block->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM, KMIP_CRYPTOALG_AES + ) + ); + + auto key_value = Element::createStructure(tag::KMIP_TAG_KEY_VALUE); + std::vector actual_key = {0xDE, 0xAD, 0xBE, 0xEF}; + key_value->asStructure()->add( + Element::createByteString(tag::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.attributes().algorithm() == + cryptographic_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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "secret-id") + ); + payload->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_OBJECT_TYPE, KMIP_OBJTYPE_SECRET_DATA + ) + ); + + auto secret_data = Element::createStructure(tag::KMIP_TAG_SECRET_DATA); + secret_data->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_SECRET_DATA_TYPE, + static_cast(secret_data_type::KMIP_SECDATA_PASSWORD) + ) + ); + + auto key_block = Element::createStructure(tag::KMIP_TAG_KEY_BLOCK); + key_block->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_KEY_FORMAT_TYPE, KMIP_KEYFORMAT_OPAQUE + ) + ); + + auto key_value = Element::createStructure(tag::KMIP_TAG_KEY_VALUE); + const std::vector bytes = {'p', 'a', 's', 's', 0x00, 'x'}; + key_value->asStructure()->add( + Element::createByteString( + tag::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); + auto secret = KeyParser::parseGetSecretResponse(get_resp); + + 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()); + + std::cout << "KeyParser Secret Binary test passed" << std::endl; +} + +void test_register_secret_request_structure() { + 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(); + assert(payload != nullptr); + + 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(tag::KMIP_TAG_SECRET_DATA); + assert(secret_data != nullptr); + + auto secret_type = secret_data->getChild(tag::KMIP_TAG_SECRET_DATA_TYPE); + assert(secret_type != nullptr); + assert( + static_cast(secret_type->toEnum()) == + secret_data_type::KMIP_SECDATA_PASSWORD + ); + + auto key_block = secret_data->getChild(tag::KMIP_TAG_KEY_BLOCK); + assert(key_block != nullptr); + + auto key_format = 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(tag::KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM) == nullptr); + assert(key_block->getChild(tag::KMIP_TAG_CRYPTOGRAPHIC_LENGTH) == nullptr); + + auto key_value = key_block->getChild(tag::KMIP_TAG_KEY_VALUE); + assert(key_value != nullptr); + auto key_material = key_value->getChild(tag::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(tag::KMIP_TAG_ATTRIBUTE); + attr1->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Name") + ); + attr1->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_VALUE, "MyKey") + ); + attributes.push_back(attr1); + + auto attr2 = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + attr2->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, "Cryptographic Length" + ) + ); + attr2->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_ATTRIBUTE_VALUE, 256) + ); + attributes.push_back(attr2); + + auto parsed_attrs = AttributesParser::parse(attributes); + + assert(parsed_attrs.has_attribute("Name")); + assert(parsed_attrs.get("Name") == "MyKey"); + + // 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; +} + +void test_attributes_parser_extended() { + std::vector> attributes; + + // Test Date attribute + auto attr_date = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + attr_date->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Activation Date") + ); + attr_date->asStructure()->add( + Element::createDateTime(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(tag::KMIP_TAG_ATTRIBUTE); + attr_alg->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, "Cryptographic Algorithm" + ) + ); + attr_alg->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_ATTRIBUTE_VALUE, KMIP_CRYPTOALG_AES + ) + ); + attributes.push_back(attr_alg); + + auto parsed_attrs = AttributesParser::parse(attributes); + + assert(parsed_attrs.has_attribute("Activation Date")); + const auto &date_str = parsed_attrs.get("Activation Date"); + assert(date_str.find("2023-03-15") != std::string::npos); + + // Cryptographic Algorithm is now a typed field. + assert( + parsed_attrs.algorithm() == cryptographic_algorithm::KMIP_CRYPTOALG_AES + ); + + 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") + ); + // Unknown typed attribute should be preserved in generic form. + v2_attrs.push_back( + Element::createInteger(tag::KMIP_TAG_APPLICATION_NAMESPACE, 3600) + ); + + 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"); + assert(result.has_attribute("Tag(0x420003)")); + assert(result.get_int("Tag(0x420003)").has_value()); + assert(result.get_int("Tag(0x420003)").value() == 3600); + + std::cout << "AttributesParser KMIP 2.0 typed attributes test passed" + << std::endl; +} + +void test_attributes_parser_legacy_wrapper_preserves_generic_types() { + std::vector> attributes; + + auto bool_attr = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + bool_attr->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Custom Bool") + ); + bool_attr->asStructure()->add( + Element::createBoolean(tag::KMIP_TAG_ATTRIBUTE_VALUE, true) + ); + attributes.push_back(bool_attr); + + auto bytes_attr = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + bytes_attr->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Custom Bytes") + ); + bytes_attr->asStructure()->add( + Element::createByteString( + tag::KMIP_TAG_ATTRIBUTE_VALUE, + std::vector{0xDE, 0xAD, 0xBE, 0xEF} + ) + ); + attributes.push_back(bytes_attr); + + auto interval_attr = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + interval_attr->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_ATTRIBUTE_NAME, "Custom Interval") + ); + interval_attr->asStructure()->add( + Element::createInterval(tag::KMIP_TAG_ATTRIBUTE_VALUE, 3600) + ); + attributes.push_back(interval_attr); + + auto dt_ext_attr = Element::createStructure(tag::KMIP_TAG_ATTRIBUTE); + dt_ext_attr->asStructure()->add( + Element::createTextString( + tag::KMIP_TAG_ATTRIBUTE_NAME, "Custom DateTimeExtended" + ) + ); + dt_ext_attr->asStructure()->add( + Element::createDateTimeExtended( + tag::KMIP_TAG_ATTRIBUTE_VALUE, 1700000000123456LL + ) + ); + attributes.push_back(dt_ext_attr); + + auto parsed = AttributesParser::parse(attributes); + + assert(parsed.has_attribute("Custom Bool")); + assert(parsed.get_as_string("Custom Bool").has_value()); + assert(parsed.get_as_string("Custom Bool").value() == "true"); + + assert(parsed.has_attribute("Custom Bytes")); + assert(parsed.get("Custom Bytes") == "DE AD BE EF"); + + assert(parsed.has_attribute("Custom Interval")); + assert(parsed.get_long("Custom Interval").has_value()); + assert(parsed.get_long("Custom Interval").value() == 3600); + + assert(parsed.has_attribute("Custom DateTimeExtended")); + assert(parsed.get_long("Custom DateTimeExtended").has_value()); + assert( + parsed.get_long("Custom DateTimeExtended").value() == 1700000000123456LL + ); + + std::cout + << "AttributesParser legacy wrapper generic-type preservation 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" + }; + + // KMIP 1.4: selectors are repeated Attribute Name text strings. + { + GetAttributesRequest req("id-1", attrs, ProtocolVersion(1, 4)); + auto payload = req.getRequestPayload(); + assert(payload != nullptr); + assert( + payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_NAME).size() == + attrs.size() + ); + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_REFERENCE).empty()); + } + + // KMIP 2.0: selectors are Attribute Reference structures. + { + GetAttributesRequest req("id-1", attrs, ProtocolVersion(2, 0)); + auto payload = req.getRequestPayload(); + assert(payload != nullptr); + assert(payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_NAME).empty()); + const auto refs = payload->getChildren(tag::KMIP_TAG_ATTRIBUTE_REFERENCE); + assert(refs.size() == attrs.size()); + // Standard attrs are encoded as Attribute Reference enum children. + assert(refs[0]->getChild(tag::KMIP_TAG_ATTRIBUTE_REFERENCE) != nullptr); + assert(refs[1]->getChild(tag::KMIP_TAG_ATTRIBUTE_REFERENCE) != nullptr); + // Vendor-defined attrs are encoded by name. + assert(refs[2]->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME) != nullptr); + assert( + refs[2]->getChild(tag::KMIP_TAG_ATTRIBUTE_NAME)->toString() == + "Vendor Custom Attr" + ); + } + + // Empty attribute list means "return all" in both versions. + 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 version-aware 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")); + + 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(tag::KMIP_TAG_RESPONSE_PAYLOAD); + payload->asStructure()->add( + Element::createInteger(tag::KMIP_TAG_LOCATED_ITEMS, 2) + ); + payload->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_UNIQUE_IDENTIFIER, "uuid-1") + ); + payload->asStructure()->add( + Element::createTextString(tag::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_formatter_redacts_sensitive_fields() { + auto root = Element::createStructure(tag::KMIP_TAG_REQUEST_MESSAGE); + root->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_USERNAME, "alice") + ); + root->asStructure()->add( + Element::createTextString(tag::KMIP_TAG_PASSWORD, "s3cr3t") + ); + root->asStructure()->add( + Element::createByteString( + tag::KMIP_TAG_KEY_MATERIAL, {0xDE, 0xAD, 0xBE, 0xEF} + ) + ); + + auto secret_data = Element::createStructure(tag::KMIP_TAG_SECRET_DATA); + secret_data->asStructure()->add( + Element::createEnumeration( + tag::KMIP_TAG_SECRET_DATA_TYPE, + static_cast(secret_data_type::KMIP_SECDATA_PASSWORD) + ) + ); + root->asStructure()->add(secret_data); + + const auto formatted = format_element(root); + assert(formatted.find("Username") != std::string::npos); + assert(formatted.find("Password") != std::string::npos); + assert(formatted.find("KeyMaterial") != std::string::npos); + assert(formatted.find("SecretData") != std::string::npos); + assert(formatted.find(" malformed = { + static_cast('s'), + static_cast('3'), + static_cast('c'), + static_cast('r'), + static_cast('3'), + static_cast('t'), + }; + + const auto formatted = format_ttlv(malformed); + assert( + formatted.find("Unable to format KMIP TTLV safely") != std::string::npos + ); + assert(formatted.find("Raw bytes") == std::string::npos); + assert(formatted.find("s3cr3t") == std::string::npos); + + std::cout << "KMIP formatter parse-failure redaction 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_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_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_attributes_parser_legacy_wrapper_preserves_generic_types(); + test_get_attributes_request_encodes_per_protocol_version(); + test_get_attribute_list_response_supports_v2_attribute_reference(); + test_formatter_for_request_and_response(); + test_formatter_redacts_sensitive_fields(); + test_formatter_parse_failure_omits_raw_bytes(); + 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..b9215a6 --- /dev/null +++ b/kmipcore/tests/test_serialization_buffer.cpp @@ -0,0 +1,250 @@ +#include "kmipcore/serialization_buffer.hpp" + +#include +#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(std::as_bytes(std::span{data})); + + 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(std::as_bytes(std::span{data})); + + 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(std::as_bytes(std::span{data1})); + + // 2 bytes -> 8 bytes padded + uint8_t data2[] = {0x04, 0x05}; + buf.writePadded(std::as_bytes(std::span{data2})); + + 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(std::as_bytes(std::span{data})); + + 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(std::as_bytes(std::span{data})); + } + + // 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; + } +}